From 473529df6046b56857daca03cc0061d01d462182 Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Thu, 26 Jul 2018 13:35:51 +0800 Subject: [PATCH 01/75] =?UTF-8?q?:bento:=20=E6=B7=BB=E5=8A=A0=E3=80=81?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...66\346\236\204\345\216\237\347\220\206.png" | Bin 0 -> 130288 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 "images/architecture/\345\210\206\345\270\203\345\274\217\346\266\210\346\201\257\351\230\237\345\210\227\346\236\266\346\236\204\345\216\237\347\220\206.png" diff --git "a/images/architecture/\345\210\206\345\270\203\345\274\217\346\266\210\346\201\257\351\230\237\345\210\227\346\236\266\346\236\204\345\216\237\347\220\206.png" "b/images/architecture/\345\210\206\345\270\203\345\274\217\346\266\210\346\201\257\351\230\237\345\210\227\346\236\266\346\236\204\345\216\237\347\220\206.png" new file mode 100644 index 0000000000000000000000000000000000000000..3a8bea90c1bcac5f04dc764f7008b12772fb7d11 GIT binary patch literal 130288 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�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

WP*=B!Kb}8D4np( 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 Date: Thu, 26 Jul 2018 14:35:30 +0800 Subject: [PATCH 02/75] :memo: Writing docs. --- README.md | 1 + ...51\345\261\225\346\236\266\346\236\204.md" | 69 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 "docs/architecture/\347\275\221\347\253\231\347\232\204\345\217\257\346\211\251\345\261\225\346\236\266\346\236\204.md" diff --git a/README.md b/README.md index 0e049d5a..6a702572 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ - [网站的高性能架构](docs/architecture/网站的高性能架构.md) - [网站的高可用架构](docs/architecture/网站的高可用架构.md) - [网站的伸缩性架构](docs/architecture/网站的伸缩性架构.md) +- [网站的可扩展架构](docs/architecture/网站的可扩展架构.md) ## :one: JavaEE diff --git "a/docs/architecture/\347\275\221\347\253\231\347\232\204\345\217\257\346\211\251\345\261\225\346\236\266\346\236\204.md" "b/docs/architecture/\347\275\221\347\253\231\347\232\204\345\217\257\346\211\251\345\261\225\346\236\266\346\236\204.md" new file mode 100644 index 00000000..e871da9f --- /dev/null +++ "b/docs/architecture/\347\275\221\347\253\231\347\232\204\345\217\257\346\211\251\345\261\225\346\236\266\346\236\204.md" @@ -0,0 +1,69 @@ +# 网站的可扩展架构 + + + +- [1. 构建可扩展的网站架构](#1-构建可扩展的网站架构) +- [2. 利用分布式消息队列降低系统耦合性](#2-利用分布式消息队列降低系统耦合性) + - [2.1. 事件驱动架构](#21-事件驱动架构) + - [2.2. 分布式消息队列](#22-分布式消息队列) +- [3. 利用分布式服务打造可复用的业务平台](#3-利用分布式服务打造可复用的业务平台) +- [4. 可扩展的数据结构](#4-可扩展的数据结构) +- [5. 资料](#5-资料) + + + +**扩展性(Extensibility)** - 指对现有系统影响最小的情况下,系统功能可持续扩展或提升的能力。表现在系统基础设施稳定不需要经常变更,应用之间较少依赖和耦合,对需求变更可以敏捷响应。它是系统架构设计层面的开闭原则(对扩展开放、对修改关闭),架构设计考虑未来功能扩展,当系统增加新功能时,不需要对现有系统的结构和代码进行修改。 + +**伸缩性(Scalability)** - 指系统能够通过增加减少自身资源规模的方式增减自己计算处理事务的能力。如果这种增减是成比例的,就被称作线性伸缩性。在网站架构中 ,通常指利用集群的方式增加服务器数量、提高系统的整体事务吞吐能力。 + +## 1. 构建可扩展的网站架构 + +低耦合的系统更容易扩展、复用。 + +设计网站可扩展架构的核心思想是模块化,并在此基础上,降低模块间的耦合性,提高模块的复用性。 + +分层和分割不仅可以进行架构伸缩,也是模块化设计的重要手段,利用分层和分割的方式将软件分割为若干个低耦合的独立的组件模块,这些组件模块以消息传递及依赖调用的方式聚合成一个完整的系统。 + +在大型网站中,这些模块通过分布式部署的方式,独立的模块部署在独立的服务器上,从物理上分离模块间的耦合关系,进一步降低耦合性提高复用性。 + +## 2. 利用分布式消息队列降低系统耦合性 + +### 2.1. 事件驱动架构 + +事件驱动架构通过在低耦合的模块间传输事件消息,以保持模块的松散耦合,并借助事件消息的通信完成模块间合作。典型的事件驱动架构就是操作系统中常见的生产者消费者模式。在大型网站中,最常见的实现手段就是分布式消息队列。 + +### 2.2. 分布式消息队列 + +消息生产者应用程序通过远程访问接口将消息推送给消息队列服务器,消息队列服务器将消息写入本地内存队列后立即返回成功响应给消息生产者。消息队列服务器根据消息订阅列表查找订阅该消息的消息消费者应用程序,将消息队列中的消息按照先进先出(FIFO)的原则将消息通过远程通信接口发送给消息消费者程序。 + +

+ +
+ +在伸缩性方面,由于消息队列服务器上的数据可以看作是即时处理的,因此类似于无状态的服务器,伸缩性设计比较简单。将新服务器加入分布式消息队列集群中,通知生产者服务器更改消息队列服务器列表即可。 + +在可用性方面,为了避免消费者进程处理缓慢,分布式消息队列服务器内存空间不足造成的问题,如果内存队列已满,会将消息写入磁盘,消息推送模块在将内存队列消息处理完成以后,将磁盘内容加载到内存队列继续处理。 + +## 3. 利用分布式服务打造可复用的业务平台 + +分布式服务则通过接口分解系统耦合性,不同子系统通过相同的接口描述进行服务调用。 + +大型网站分布式服务的需求与特点: + +- 负载均衡 +- 失效转移 +- 高效的远程通信 +- 整合异构系统 +- 对应用最少侵入 +- 版本管理 +- 实时监控 + +## 4. 可扩展的数据结构 + +传统的关系型数据库为了保证关系运算的正确性,在设计数据库表结构的时候,就需要指定表的 schema ——字段名称,数据类型等,并要遵循特定的设计范式。这些规范带来一个问题:难以面对需求变更带来的挑战,所以有人通过预先设计一些冗余字段来应对。 + +许多 NoSql 数据库使用 ColumnFamily 设计来设计可扩展的数据结构。 + +## 5. 资料 + +- [大型网站技术架构](https://item.jd.com/11322972.html) From 95bde84158ab2bf856455de924e56671a3f2ec73 Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Thu, 26 Jul 2018 14:46:17 +0800 Subject: [PATCH 03/75] =?UTF-8?q?:bento:=20=E6=B7=BB=E5=8A=A0=E3=80=81?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...45\205\250-CSRF\346\224\273\345\207\273.png" | Bin 0 -> 82317 bytes ...250\345\205\245\346\224\273\345\207\273.png" | Bin 0 -> 62493 bytes ...345\205\250-XSS\346\224\273\345\207\273.png" | Bin 0 -> 89148 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 "images/architecture/\345\256\211\345\205\250-CSRF\346\224\273\345\207\273.png" create mode 100644 "images/architecture/\345\256\211\345\205\250-SQL\346\263\250\345\205\245\346\224\273\345\207\273.png" create mode 100644 "images/architecture/\345\256\211\345\205\250-XSS\346\224\273\345\207\273.png" diff --git "a/images/architecture/\345\256\211\345\205\250-CSRF\346\224\273\345\207\273.png" "b/images/architecture/\345\256\211\345\205\250-CSRF\346\224\273\345\207\273.png" new file mode 100644 index 0000000000000000000000000000000000000000..6f3cdab6b793087823b88060b5f58cc14cf7daa9 GIT binary patch literal 82317 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 GF8v?7EG?q| literal 0 HcmV?d00001 diff --git "a/images/architecture/\345\256\211\345\205\250-XSS\346\224\273\345\207\273.png" "b/images/architecture/\345\256\211\345\205\250-XSS\346\224\273\345\207\273.png" new file mode 100644 index 0000000000000000000000000000000000000000..fc64692736432071be93838be4e44a97341d6b0f GIT binary patch literal 89148 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 literal 0 HcmV?d00001 From 4d4b16bbfb3dc9a800ffea0470c5a48b407b7594 Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Thu, 26 Jul 2018 15:00:24 +0800 Subject: [PATCH 04/75] =?UTF-8?q?:bento:=20=E6=B7=BB=E5=8A=A0=E3=80=81?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...345\236\213XSS\346\224\273\345\207\273.png" | Bin ...345\236\213XSS\346\224\273\345\207\273.png" | Bin 0 -> 115402 bytes 2 files changed, 0 insertions(+), 0 deletions(-) rename "images/architecture/\345\256\211\345\205\250-XSS\346\224\273\345\207\273.png" => "images/architecture/\345\256\211\345\205\250-\345\217\215\345\260\204\345\236\213XSS\346\224\273\345\207\273.png" (100%) create mode 100644 "images/architecture/\345\256\211\345\205\250-\346\214\201\344\271\205\345\236\213XSS\346\224\273\345\207\273.png" diff --git "a/images/architecture/\345\256\211\345\205\250-XSS\346\224\273\345\207\273.png" "b/images/architecture/\345\256\211\345\205\250-\345\217\215\345\260\204\345\236\213XSS\346\224\273\345\207\273.png" similarity index 100% rename from "images/architecture/\345\256\211\345\205\250-XSS\346\224\273\345\207\273.png" rename to "images/architecture/\345\256\211\345\205\250-\345\217\215\345\260\204\345\236\213XSS\346\224\273\345\207\273.png" diff --git "a/images/architecture/\345\256\211\345\205\250-\346\214\201\344\271\205\345\236\213XSS\346\224\273\345\207\273.png" "b/images/architecture/\345\256\211\345\205\250-\346\214\201\344\271\205\345\236\213XSS\346\224\273\345\207\273.png" new file mode 100644 index 0000000000000000000000000000000000000000..f1b165a49ef0f597cc153a36c7978b3e89371de2 GIT binary patch literal 115402 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 !KLF?9pa72h( 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�Wj-<4y(cxS*;D6{-nKuAmw% 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 Date: Thu, 26 Jul 2018 16:23:49 +0800 Subject: [PATCH 05/75] :memo: Writing docs. --- README.md | 1 + ...11\345\205\250\346\236\266\346\236\204.md" | 105 ++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 "docs/architecture/\347\275\221\347\253\231\347\232\204\345\256\211\345\205\250\346\236\266\346\236\204.md" diff --git a/README.md b/README.md index 6a702572..54cdd523 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ - [网站的高可用架构](docs/architecture/网站的高可用架构.md) - [网站的伸缩性架构](docs/architecture/网站的伸缩性架构.md) - [网站的可扩展架构](docs/architecture/网站的可扩展架构.md) +- [网站的安全架构](docs/architecture/网站的安全架构.md) ## :one: JavaEE diff --git "a/docs/architecture/\347\275\221\347\253\231\347\232\204\345\256\211\345\205\250\346\236\266\346\236\204.md" "b/docs/architecture/\347\275\221\347\253\231\347\232\204\345\256\211\345\205\250\346\236\266\346\236\204.md" new file mode 100644 index 00000000..b8dac4c7 --- /dev/null +++ "b/docs/architecture/\347\275\221\347\253\231\347\232\204\345\256\211\345\205\250\346\236\266\346\236\204.md" @@ -0,0 +1,105 @@ +# 网站的安全架构 + + + +- [1. 网站安全的攻与防](#1-网站安全的攻与防) + - [1.1. XSS 攻击](#11-xss-攻击) + - [1.2. 注入攻击](#12-注入攻击) + - [1.3. CSRF 攻击](#13-csrf-攻击) +- [2. 加密技术及密钥安全管理](#2-加密技术及密钥安全管理) + - [2.1. 消息摘要](#21-消息摘要) + - [2.2. 对称加密](#22-对称加密) + - [2.3. 非对称加密](#23-非对称加密) + - [2.4. 密钥安全管理](#24-密钥安全管理) +- [3. 资料](#3-资料) + + + +## 1. 网站安全的攻与防 + +### 1.1. XSS 攻击 + +XSS 攻击即跨站点脚本攻击(Cross Site Script),指黑客通过篡改网页,注入恶意 HTML 脚本,在用户浏览网页时,控制用户浏览器进行恶意操作的一种攻击方式。 + +XSS 攻击有两种类型: + +(1)反射型 XSS 攻击 + +
+ +
+ +(2)持久型 XSS 攻击 + +
+ +
+ +应对手段: + +- **消毒** - XSS 攻击一般是通过在请求中嵌入恶意脚本达到攻击的目的,这些脚本是一般用户输入中不使用的。过滤和消毒处理,即对某些 html 危险字符转移,如 `>` 转义为 `>`、`<` 转义为 `<` 等,就可以防止大部分攻击。为了避免对不必要的内容错误转移,如 `3<5` 中的 `<` 需要进行文本匹配后再转移,如:` + + + +应对手段: + +- **消毒** - 和防 XSS 攻击一样,请求参数消毒是一种比较简单粗暴又有效的手段。通过正则匹配,过滤请求数据中可能注入的 SQL。 +- **参数绑定** - 使用预编译手段,绑定参数是最好的防 SQL 注入方式。目前许多数据访问层框架,如 mybatis、hibernate 等,都实现 SQL 预编译和参数绑定,攻击者的恶意 SQL 会被当做 SQL 的参数,而不是 SQL 命令被执行。 + +### 1.3. CSRF 攻击 + +CSRF 攻击即跨站点请求伪造(Cross Site Request Forgery),攻击者通过跨站请求,以合法用户的身份进行非法操作,如转账交易、发表评论等。CSRF 的主要手法是利用跨站请求,在用户不知情的情况下,以用户身份伪造请求。其核心是利用了浏览器 Cookie 或服务器 Session 策略,盗取用户身份。 + +
+ +
+ +应对手段: + +- **表单 Token** - CSRF 是一个伪造用户请求的操作,所以需要构造用户请求的所有参数才可以。表单 Token 通过在请求参数中添加随机数的办法来组织攻击者获得所有请求参数。 +- **验证码** - 请求提交是,需要用户输入验证码,以避免用户在不知情的情况下被攻击者伪造请求。 +- **Referer check** - HTTP 请求头的 Referer 域中记录着请求资源,可通过检查请求来源,验证其是否合法。 + +## 2. 加密技术及密钥安全管理 + +### 2.1. 消息摘要 + +常用数字签名算法:MD5、SHA 等。 + +应用场景:将用户密码以消息摘要形式保存到数据库中。 + +### 2.2. 对称加密 + +对称加密指加密和解密所使用的密钥是同一个密钥。 + +常用对称加密算法:DES 等。 + +应用场景:Cookie 加密、通信机密等。 + +### 2.3. 非对称加密 + +非对称加密指加密和解密所使用的不是同一个密钥,而是一个公私钥对。用公钥加密的信息必须用私钥才能解开;反之,用私钥加密的信息只有用公钥才能解开。 + +常用非对称加密算法:RSA 等。 + +应用场景:HTTPS 传输中浏览器使用的数字证书实质上是经过权威机构认证的非对称加密公钥。 + +### 2.4. 密钥安全管理 + +保证密钥安全的方法: + +1. 把密钥和算法放在一个独立的服务器上,对外提供加密和解密服务,应用系统通过调用这个服务,实现数据的加解密。 +2. 把加解密算法放在应用系统中,密钥则放在独立服务器中,为了提高密钥的安全性,实际存储时,密钥被切分成数片,加密后分别保存在不同存储介质中。 + +## 3. 资料 + +- [大型网站技术架构](https://item.jd.com/11322972.html) From 12a19d9bd8b9389fa0f84ea95ffaac3b5a8f5636 Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Mon, 6 Aug 2018 14:05:13 +0800 Subject: [PATCH 06/75] :memo: Writing docs. --- README.md | 8 ++- ...66\346\236\204\346\246\202\350\277\260.md" | 8 +-- ...70\345\236\213\346\225\205\351\232\234.md" | 56 +++++++++++++++++++ ...51\346\200\247\346\236\266\346\236\204.md" | 28 ++++++---- ...57\347\224\250\346\236\266\346\236\204.md" | 20 +++---- ...47\350\203\275\346\236\266\346\236\204.md" | 22 ++++---- 6 files changed, 104 insertions(+), 38 deletions(-) create mode 100644 "docs/architecture/\347\275\221\347\253\231\345\205\270\345\236\213\346\225\205\351\232\234.md" diff --git a/README.md b/README.md index 54cdd523..a698b880 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,10 @@ > Java Web 开发之路经验总结 -| :one: | :two: | :three: | :four: | -| --------------------- | ----------------------------- | ------------------------------- | ------------------ | -| [JavaEE](#one-javaee) | [单点式技术](#two-单点式技术) | [分布式技术](#three-分布式技术) | [工具](#four-工具) | +| :recycle: | :one: | :two: | :three: | :four: | +| --------------------- | --------------------- | ----------------------------- | ------------------------------- | ------------------ | +| [架构设计](#recycle-架构设计) | [JavaEE](#one-javaee) | [单点式技术](#two-单点式技术) | [分布式技术](#three-分布式技术) | [工具](#four-工具) | + ## :recycle: 架构设计 @@ -16,6 +17,7 @@ - [网站的伸缩性架构](docs/architecture/网站的伸缩性架构.md) - [网站的可扩展架构](docs/architecture/网站的可扩展架构.md) - [网站的安全架构](docs/architecture/网站的安全架构.md) +- [网站典型故障](docs/architecture/网站典型故障.md) ## :one: JavaEE diff --git "a/docs/architecture/\345\244\247\345\236\213\347\275\221\347\253\231\346\236\266\346\236\204\346\246\202\350\277\260.md" "b/docs/architecture/\345\244\247\345\236\213\347\275\221\347\253\231\346\236\266\346\236\204\346\246\202\350\277\260.md" index feed05ab..2cdbde7c 100644 --- "a/docs/architecture/\345\244\247\345\236\213\347\275\221\347\253\231\346\236\266\346\236\204\346\246\202\350\277\260.md" +++ "b/docs/architecture/\345\244\247\345\236\213\347\275\221\347\253\231\346\236\266\346\236\204\346\246\202\350\277\260.md" @@ -98,7 +98,7 @@ 问题:网站使用缓存后,使绝大部分数据读操作访问都可以不通过数据库就能完成,但是仍有一部分读操作和全部的写操作需要访问数据库,在网站的用户达到一定规模后,数据库因为负载压力过高而成为网站的瓶颈。 -特征:目前大部分的主流数据库都提供主从热备功能,通过配置两台数据库主从关系,可以将一台数据库服务器的数据更新同步到一台服务器上。网站利用数据库的这一功能,实现数据库读写分离,从而改善数据库负载压力。 +特征:目前大部分的主流数据库都提供主从热备功能,通过配置两台数据库主从关系,可以将一台数据库服务器的数据更新同步到一台服务器上。**网站利用数据库的主从热备功能,实现数据库读写分离,从而改善数据库负载压力。** 描述:应用服务器在写操作的时候,访问主数据库,主数据库通过主从复制机制将数据更新同步到从数据库。这样当应用服务器在读操作的时候,访问从数据库获得数据。为了便于应用程序访问读写分离后的数据库,通常在应用服务器端使用专门的数据访问模块,使数据库读写分离的对应用透明。 @@ -110,7 +110,7 @@ 问题:中国网络环境复杂,不同地区的用户访问网站时,速度差别也极大。 -特征:**采用 CDN 和反向代理加快系统的访问速度。** +特征:**采用 CDN 和反向代理加快系统的静态资源访问速度。** 描述:CDN 和反向代理的基本原理都是缓存,区别在于 CDN 部署在网络提供商的机房,使用户在请求网站服务时,可以从距离自己最近的网络提供商机房获取数据;而反向代理则部署在网站的中心机房,当用户请求到达中心机房后,首先访问的服务器时反向代理服务器,如果反向代理服务器中缓存着用户请求的资源,就将其直接返回给用户。 @@ -236,12 +236,12 @@ 大型网站架构中,系统解耦的手段除了分层、分割、分布式等,还有一个重要手段——异步。 -业务间的消息传递不是同步调用,儿时将一个业务操作拆分成多阶段,每个阶段间通过共享数据的方式异步执行进行协作。 +业务间的消息传递不是同步调用,而是将一个业务操作拆分成多阶段,每个阶段间通过共享数据的方式异步执行进行协作。 - 在单一服务器内部可通过多线程共享内存队列的方式实现异步,处在业务操作前面的线程将操作输出到队列,后面的线程从队列中读取数据进行处理; - 在分布式系统中,多个服务器集群通过分布式消息队列实现异步。 -异步架构师典型的生产者消费模式,二者不存在直接调用。异步消息队列还有如下特性: +异步架构是典型的生产者消费模式,二者不存在直接调用。异步消息队列还有如下特性: - 提高系统可用性 - 加快响应速度 diff --git "a/docs/architecture/\347\275\221\347\253\231\345\205\270\345\236\213\346\225\205\351\232\234.md" "b/docs/architecture/\347\275\221\347\253\231\345\205\270\345\236\213\346\225\205\351\232\234.md" new file mode 100644 index 00000000..c24428a2 --- /dev/null +++ "b/docs/architecture/\347\275\221\347\253\231\345\205\270\345\236\213\346\225\205\351\232\234.md" @@ -0,0 +1,56 @@ +# 网站典型故障 + + + +- [1. 海量日志耗尽磁盘空间引发的故障](#1-海量日志耗尽磁盘空间引发的故障) +- [2. 高并发访问数据库引发的故障](#2-高并发访问数据库引发的故障) +- [3. 高并发情况下锁引发的故障](#3-高并发情况下锁引发的故障) +- [4. 缓存引发的故障](#4-缓存引发的故障) +- [5. 应用启动不同步引发的故障](#5-应用启动不同步引发的故障) +- [6. 大文件读写独占磁盘引发的故障](#6-大文件读写独占磁盘引发的故障) +- [7. 资料](#7-资料) + + + +## 1. 海量日志耗尽磁盘空间引发的故障 + +现象:应用发布后,硬盘空间低于警戒值,服务器宕机。 +分析:应用部署在服务器上,不断打印日志,但不定期清理日志,导致磁盘空间最终被耗尽。 +总结: + +- 应用自身的日志和第三方组件日志应分别配置。 +- 日志输出级别不要设置太低,导致打印很多无关痛痒的信息。 + +## 2. 高并发访问数据库引发的故障 + +现象:数据库负载居高不下。 +分析:某条 sql 执行频率非常高,追查发现,被网站首页调用。 +总结:首页不应该访问数据库。 + +## 3. 高并发情况下锁引发的故障 + +现象:应用不定时地因为响应超时而报警,但是很快又超时接触,恢复正常。 +分析:某个单例对象中多处使用了 synchronized,由于 this 对象只有一个,所有的并发请求都要排队获得者唯一的一把锁。 +总结:使用锁操作要谨慎。 + +## 4. 缓存引发的故障 + +现象:没有新应用发布,但是数据库突然负载飙升,并很快失去响应。 +分析:缓存服务器管理失当。 +总结:当网站架构对缓存依赖性很强时,应该重视对缓存服务的管理。 + +## 5. 应用启动不同步引发的故障 + +现象:某应用发布后,服务器立即崩溃。 +分析:后台服务还没准备好,前台应用就开始接受请求,导致故障。 +总结:发布脚本中不断用 curl 命令访问后台应用特定页面,直到收到 OK,再启动前台应用。 + +## 6. 大文件读写独占磁盘引发的故障 + +现象:上传图片非常慢。 +分析:文件存储最有可能出错的地方是存储服务器。检查发现,大部分文件大小比较小,少数几个文件非常大,读写大文件比较耗时,在这个读写时间内,磁盘基本被大文件操作独占,导致其他用户的文件操作缓慢。 +总结:文件存储下需要根据不同文件类型和用途进行管理。 + +## 7. 资料 + +- [大型网站技术架构](https://item.jd.com/11322972.html) diff --git "a/docs/architecture/\347\275\221\347\253\231\347\232\204\344\274\270\347\274\251\346\200\247\346\236\266\346\236\204.md" "b/docs/architecture/\347\275\221\347\253\231\347\232\204\344\274\270\347\274\251\346\200\247\346\236\266\346\236\204.md" index 3b679d9d..5cff88d0 100644 --- "a/docs/architecture/\347\275\221\347\253\231\347\232\204\344\274\270\347\274\251\346\200\247\346\236\266\346\236\204.md" +++ "b/docs/architecture/\347\275\221\347\253\231\347\232\204\344\274\270\347\274\251\346\200\247\346\236\266\346\236\204.md" @@ -3,19 +3,19 @@ - [1. 网站架构的伸缩性设计](#1-网站架构的伸缩性设计) - - [1.1. 不同功能进行物理分离实现伸缩](#11-不同功能进行物理分离实现伸缩) - - [1.2. 单一功能通过集群规模实现伸缩](#12-单一功能通过集群规模实现伸缩) + - [1.1. 不同功能进行物理分离实现伸缩](#11-不同功能进行物理分离实现伸缩) + - [1.2. 单一功能通过集群规模实现伸缩](#12-单一功能通过集群规模实现伸缩) - [2. 应用服务器集群的伸缩性设计](#2-应用服务器集群的伸缩性设计) - - [2.1. HTTP 重定向负载均衡](#21-http-重定向负载均衡) - - [2.2. DNS 域名解析负载均衡](#22-dns-域名解析负载均衡) - - [2.3. 反向代理负载均衡](#23-反向代理负载均衡) - - [2.4. IP 负载均衡](#24-ip-负载均衡) - - [2.5. 数据链路层负载均衡](#25-数据链路层负载均衡) - - [2.6. 负载均衡算法](#26-负载均衡算法) + - [2.1. HTTP 重定向负载均衡](#21-http-重定向负载均衡) + - [2.2. DNS 域名解析负载均衡](#22-dns-域名解析负载均衡) + - [2.3. 反向代理负载均衡](#23-反向代理负载均衡) + - [2.4. IP 负载均衡](#24-ip-负载均衡) + - [2.5. 数据链路层负载均衡](#25-数据链路层负载均衡) + - [2.6. 负载均衡算法](#26-负载均衡算法) - [3. 分布式缓存集群的伸缩性设计](#3-分布式缓存集群的伸缩性设计) - [4. 数据存储服务器集群的伸缩性设计](#4-数据存储服务器集群的伸缩性设计) - - [4.1. 关系型数据库的伸缩性设计](#41-关系型数据库的伸缩性设计) - - [4.2. NoSql 数据库的伸缩性设计](#42-nosql-数据库的伸缩性设计) + - [4.1. 关系型数据库的伸缩性设计](#41-关系型数据库的伸缩性设计) + - [4.2. NoSql 数据库的伸缩性设计](#42-nosql-数据库的伸缩性设计) - [5. 资料](#5-资料) @@ -121,12 +121,20 @@ IP 负载均衡在内核完成数据分发,所以处理性能优于反向代 ## 3. 分布式缓存集群的伸缩性设计 +一致性 HASH 算法 + ## 4. 数据存储服务器集群的伸缩性设计 ### 4.1. 关系型数据库的伸缩性设计 +- **主从复制** - 主流关系型数据库一般都支持主从复制。 +- **分库** - 根据业务对数据库进行分割。制约条件是跨库的表不能进行 Join 操作。 +- **分表** - 使用数据库分片中间件,如 Cobar 等。 + ### 4.2. NoSql 数据库的伸缩性设计 +一般而言,Nosql 不支持 SQL 和 ACID,但是强化了对于高可用和伸缩性的支持。 + ## 5. 资料 - [大型网站技术架构](https://item.jd.com/11322972.html) diff --git "a/docs/architecture/\347\275\221\347\253\231\347\232\204\351\253\230\345\217\257\347\224\250\346\236\266\346\236\204.md" "b/docs/architecture/\347\275\221\347\253\231\347\232\204\351\253\230\345\217\257\347\224\250\346\236\266\346\236\204.md" index 0498f95b..b134583c 100644 --- "a/docs/architecture/\347\275\221\347\253\231\347\232\204\351\253\230\345\217\257\347\224\250\346\236\266\346\236\204.md" +++ "b/docs/architecture/\347\275\221\347\253\231\347\232\204\351\253\230\345\217\257\347\224\250\346\236\266\346\236\204.md" @@ -5,13 +5,13 @@ - [1. 网站可用性的度量](#1-网站可用性的度量) - [2. 高可用的网站架构](#2-高可用的网站架构) - [3. 高可用的应用](#3-高可用的应用) - - [3.1. 通过负载均衡进行无状态服务的失效转移](#31-通过负载均衡进行无状态服务的失效转移) - - [3.2. 应用服务器集群的 Session 管理](#32-应用服务器集群的-session-管理) + - [3.1. 通过负载均衡进行无状态服务的失效转移](#31-通过负载均衡进行无状态服务的失效转移) + - [3.2. 应用服务器集群的 Session 管理](#32-应用服务器集群的-session-管理) - [4. 高可用的服务](#4-高可用的服务) - [5. 高可用的数据](#5-高可用的数据) - - [5.1. CAP 原理](#51-cap-原理) - - [5.2. 数据备份](#52-数据备份) - - [5.3. 失效转移](#53-失效转移) + - [5.1. CAP 原理](#51-cap-原理) + - [5.2. 数据备份](#52-数据备份) + - [5.3. 失效转移](#53-失效转移) - [6. 高可用网站的软件质量保证](#6-高可用网站的软件质量保证) - [7. 网站监控](#7-网站监控) - [8. 资料](#8-资料) @@ -77,11 +77,11 @@ Session 复制是指应用服务器开发 Web 容器的 Session 复制功能, 高可用的服务策略: -- 分级管理 - 将服务根据业务重要性进行分级管理,并在服务部署上进行隔离。 -- 超时设置 - 由于服务器宕机、线程死锁等原因,可能导致应用程序对服务端的调用失去响应。所以有必要引入超时机制,一旦调用超时,服务化框架抛出异常,应用程序根据服务调度策略,选择重试或请求转移到其他机器上。 -- 异步调用 - 对于需要即时响应的业务,应用在调用服务时可以通过消息队列等异步方式完成,避免长时间等待服务响应结果。 -- 服务降级 - 网站访问高峰期,服务可能因为大量并发调用而性能下降,严重时可能会导致宕机。为了保证核心功能的正常运行,需要对服务进行降级。降级有两种手段:拒绝服务和关闭服务。 -- 幂等性设计 - 为了避免服务重复调用,可以通过设置编号的方式进行服务调用有效性校验,有效的操作才能继续执行。 +- **分级管理** - 将服务根据业务重要性进行分级管理,并在服务部署上进行隔离。 +- **超时设置** - 由于服务器宕机、线程死锁等原因,可能导致应用程序对服务端的调用失去响应。所以有必要引入超时机制,一旦调用超时,服务化框架抛出异常,应用程序根据服务调度策略,选择重试或请求转移到其他机器上。 +- **异步调用** - 对于需要即时响应的业务,应用在调用服务时可以通过消息队列等异步方式完成,避免长时间等待服务响应结果。 +- **服务降级** - 网站访问高峰期,服务可能因为大量并发调用而性能下降,严重时可能会导致宕机。为了保证核心功能的正常运行,需要对服务进行降级。降级有两种手段:拒绝服务和关闭服务。 +- **幂等性设计** - 为了避免服务重复调用,可以通过设置编号的方式进行服务调用有效性校验,有效的操作才能继续执行。 ## 5. 高可用的数据 diff --git "a/docs/architecture/\347\275\221\347\253\231\347\232\204\351\253\230\346\200\247\350\203\275\346\236\266\346\236\204.md" "b/docs/architecture/\347\275\221\347\253\231\347\232\204\351\253\230\346\200\247\350\203\275\346\236\266\346\236\204.md" index fc241af5..00034034 100644 --- "a/docs/architecture/\347\275\221\347\253\231\347\232\204\351\253\230\346\200\247\350\203\275\346\236\266\346\236\204.md" +++ "b/docs/architecture/\347\275\221\347\253\231\347\232\204\351\253\230\346\200\247\350\203\275\346\236\266\346\236\204.md" @@ -30,10 +30,10 @@ 网站性能测试的主要指标有: -- 响应时间 - 响应时间(RT)是指从客户端发一个请求开始计时,到客户端接收到从服务器端返回的响应结果结束所经历的时间,响应时间由请求发送时间、网络传输时间和服务器处理时间三部分组成。 -- 并发数 - 系统同时处理的请求、事务数。 -- 吞吐量 - TPS(每秒事务数)、HPS(每秒 HTTP 请求数)、QPS(每秒查询数)。 -- 性能计数器 - 系统负载、对象与线程数、内存使用、CPU 使用、磁盘与网络 IO 等。这些指标也是系统监控的重要参数。 +- **响应时间** - 响应时间(RT)是指从客户端发一个请求开始计时,到客户端接收到从服务器端返回的响应结果结束所经历的时间,响应时间由请求发送时间、网络传输时间和服务器处理时间三部分组成。 +- **并发数** - 系统同时处理的请求、事务数。 +- **吞吐量** - TPS(每秒事务数)、HPS(每秒 HTTP 请求数)、QPS(每秒查询数)。 +- **性能计数器** - 系统负载、对象与线程数、内存使用、CPU 使用、磁盘与网络 IO 等。这些指标也是系统监控的重要参数。 ### 1.2. 性能测试方法 @@ -52,18 +52,18 @@ ### 1.4. 性能优化策略 -1. 性能分析 - 如果请求响应慢,存在性能问题。需要对请求经历的各个环节逐一分析,排查可能出现性能瓶颈的地方,定位问题。检查监控数据,分析影响性能的主要因素:内存、磁盘、网络、CPU,可能是代码或架构设计不合理,又或者是系统资源确实不足。 -2. 性能优化 - 性能优化根据网站分层架构,大致可分为前端性能优化、应用服务性能优化、存储服务性能优化。 +1. **性能分析** - 如果请求响应慢,存在性能问题。需要对请求经历的各个环节逐一分析,排查可能出现性能瓶颈的地方,定位问题。检查监控数据,分析影响性能的主要因素:内存、磁盘、网络、CPU,可能是代码或架构设计不合理,又或者是系统资源确实不足。 +2. **性能优化** - 性能优化根据网站分层架构,大致可分为前端性能优化、应用服务性能优化、存储服务性能优化。 ## 2. 前端性能优化 ### 2.1. 浏览器访问优化 -1. 减少 HTTP 请求 - HTTP 请求需要建立通信链路,进行数据传输,开销高昂,所以减少 HTTP 请求数可以有效提高访问性能。减少 HTTP 的主要手段是合并 Css、JavaScript、图片。 -2. 使用浏览器缓存 - 因为静态资源文件更新频率低,可以缓存浏览器中以提高性能。设置 HTTP 头中的 Cache-Control 和 Expires 属性,可以设定浏览器缓存。 -3. 启用压缩 - 在服务器端压缩静态资源文件,在浏览器端解压缩,可以有效减少传输的数据量。由于文本文件压缩率可达 80% 以上,所以可以对静态资源,如 Html、Css、JavaScrip 进行压缩。 -4. CSS 放在页面最上面,JavaScript 放在页面最下面 - 浏览器会在下载完全部的 Css 后才对整个页面进行渲染,所以最好的做法是将 Css 放在页面最上面,让浏览器尽快下载 Css;JavaScript 则相反,浏览器加载 JavaScript 后立即执行,可能会阻塞整个页面,造成页面显示缓慢,因此 JavaScript 最好放在页面最下面。 -5. 减少 Cookie 传输 - Cookie 包含在 HTTP 每次的请求和响应中,太大的 Cookie 会严重影响数据传输。 +1. **减少 HTTP 请求** - HTTP 请求需要建立通信链路,进行数据传输,开销高昂,所以减少 HTTP 请求数可以有效提高访问性能。减少 HTTP 的主要手段是合并 Css、JavaScript、图片。 +2. **使用浏览器缓存** - 因为静态资源文件更新频率低,可以缓存浏览器中以提高性能。设置 HTTP 头中的 Cache-Control 和 Expires 属性,可以设定浏览器缓存。 +3. **启用压缩** - 在服务器端压缩静态资源文件,在浏览器端解压缩,可以有效减少传输的数据量。由于文本文件压缩率可达 80% 以上,所以可以对静态资源,如 Html、Css、JavaScrip 进行压缩。 +4. **CSS 放在页面最上面,JavaScript 放在页面最下面** - 浏览器会在下载完全部的 Css 后才对整个页面进行渲染,所以最好的做法是将 Css 放在页面最上面,让浏览器尽快下载 Css;JavaScript 则相反,浏览器加载 JavaScript 后立即执行,可能会阻塞整个页面,造成页面显示缓慢,因此 JavaScript 最好放在页面最下面。 +5. **减少 Cookie 传输** - Cookie 包含在 HTTP 每次的请求和响应中,太大的 Cookie 会严重影响数据传输。 ### 2.2. CDN From cf1709f595c2342ad6e4a288be0e5d65ef1978fb Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Mon, 6 Aug 2018 15:03:38 +0800 Subject: [PATCH 07/75] :fire: Delete --- ...5\205\250-CSRF\346\224\273\345\207\273.png" | Bin 82317 -> 0 bytes ...50\345\205\245\346\224\273\345\207\273.png" | Bin 62493 -> 0 bytes ...345\236\213XSS\346\224\273\345\207\273.png" | Bin 89148 -> 0 bytes ...345\236\213XSS\346\224\273\345\207\273.png" | Bin 115402 -> 0 bytes 4 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 "images/architecture/\345\256\211\345\205\250-CSRF\346\224\273\345\207\273.png" delete mode 100644 "images/architecture/\345\256\211\345\205\250-SQL\346\263\250\345\205\245\346\224\273\345\207\273.png" delete mode 100644 "images/architecture/\345\256\211\345\205\250-\345\217\215\345\260\204\345\236\213XSS\346\224\273\345\207\273.png" delete mode 100644 "images/architecture/\345\256\211\345\205\250-\346\214\201\344\271\205\345\236\213XSS\346\224\273\345\207\273.png" diff --git "a/images/architecture/\345\256\211\345\205\250-CSRF\346\224\273\345\207\273.png" "b/images/architecture/\345\256\211\345\205\250-CSRF\346\224\273\345\207\273.png" deleted file mode 100644 index 6f3cdab6b793087823b88060b5f58cc14cf7daa9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 82317 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
BKhl;~Xvg z3W?y$ATQ70iNrQD;tK*T8HcdpZGj;Q6QAL)Ze)V$-+rzsKXO#z)7GDO|QAr8gSn5tYn`%{`Yi=UZ8MA$fkhPJeCBpcmj>F=1Abk=_BM$WQ0tQx8__cZ4 z@^OUG2Z~?4unaD)+LlLj#s}IeCT-;`+oB#*wZYpzgyyRNK*1PP1gFPeH9@Pe?P>F7d3lS+ud02+ytvc+mXCl31}fYd)m#{d}h%FWU^lO-0rU zItq35h_DY`!Yb#`7&_XWSuJ%B5R?$fKZFMikvyoX$S*X6IRrDIK(vgZ;Sun))*d$~ z(8iR+&<&slpr$}@R}jfZ2$BlfjpiRq{4qn;>A+4p9q*K8SC%-`%ll7v~TYD&^H9CVkTu$W>cPV;|gY=Vlg zF_QNUybQ`y>Y-iPjjptMLcoHL?0l` zh^Hk&xFx01td7aK^=-|S$$>9H2d;IRy#(#6RxT{7YODmYTo7~>yzZ^ z_W+#-PaEu5L6k#aDqx0eAW|5>-t0Xs6D<``6cOarp14qrX&ihh?&9fGl1~UMm@K4| zxYTam8r-7{^>jNBvv~E5r_ur#2>{G~gZM=lGh#`a+#vGHcU$LMJAqINs*0F-!>Bhe+?vOOL?8NX)pTWQRmQo5k_0-B>~)W z(B~>Vea&tfbD_fC)KdOMt2F-{0Ot-a3Iim&KddT=b10Z;9SFt1@Xky^roJ1*RC=d* zlqUfBf1R|{_;43{1z|4&%t^f$`h*(YqmGL}g!sFf>uba)9qb0l_w4A;?R3&dXa?~z z>?Fdd5~cXN>dHPqVjyuk2;jmG3g-10cAY*V!m@z(0b!iQ3oGZp#zbhy3ugdpIX1=> z?ja%=kQ`;^36_%Xb8D9ZdRIqvPf7$LL%|6k@2<1xqUI%=1weI(`2|(Hu1|iDR4)2oesR!l)oPqE?`n1yPd^J>Slm?tP_pr+s0 zbl|zHrL_#1#;hiH&t5`28UmS?DeXjtuUP=;Y8{vdA>lU_LoaEZz{rV+AM|^HA8&Dc zH~85IrI1Py19(JOM^x5Zq|X%T#Y#{aP1-XbTTwcWE^r;_EC0^YwxSn*Twv-TC$O)a zoF-+hn{Y&6ZiLZR(3pk6xmM8!vctS(U=DEG6_ACZ?&{>Lw;9|u*?1NO?V!LE;Gj;p zAtLw7{2RChHlW}jf+hN|e;~Z1RdJk1yUXY2PSzRGt4#pxS9MmOT$z~Ydspbzh-L$ zoxd`s%~&C+U3lG%^BoVyF}C0?@MT6yjqVPqr05_qu=!88E?V_z8jV0VjYoqa1IU*2 z=_qR+kXDGoyMTHSem5Yv*g&5=!4-i>-mWZE&0hR|4qydowtnJ6Vv z#XLT*_UrDDD4Na;`lf;`b{c81Q+Z;lLOC^%zX1Pp-3@wJ9TXbJ^rZtr8Lkkq;i=p~ z>X1??4x1z+*?-Xal&BzUB#exdXo;*w>7>xx9e$NAYY z3;6lLU%C4~=?6ZsW2Uh+xna(nTPvFrVi1#{pVvRjlg{~y#+KlERCh3VLc!6Ad3eZZ zoT~=YY@14VibE<^!IDtI=yOU%X4D9FVGGH!^tbh`3FF6YXkT^cEvO3`pQ$J3`scEf zgL#5FlhFhd8QzljkkYg(E?GT!X1VYr;4TB>>pIHY1d(Y^K21CbC^C=GXMPg#bQNO< z(>tq*{RS21T~I~2cnE_LsGIZbl|6MVE!yo0*+KGOJPWXhZas@H=$2u#!EXvzv{d=X zx@I01A|`s=I#P^Ckl_YJY-e!}7|RKq`|E~R5}JfRi;mv_AElS@2hc%Ayu#dtJLC>p zhY!SyX(UrR3xv%oJOu2K{@8?9{Od(vD=c%qFyGSj# zk36tpM)^Ljm0q7HJqclR0)0$FTSC&URwP;IoOFg=blyRK`4W@z%~X`_HQC8TvpUq} z)#m~c9$!AAqy!hHu1ugwxWshN@jRARwNYM2SPcYUBiRRQ?Df9CDOOTx-(jI~_qSPQ z*JRZi9gNTCm7nO`8;=xza5$uUCd#l}nSDe>&HY3PAfdF}ms-B?7M^vydvR8S5jgX* zewNk@2i+L(7!;{)q+W&bAUhB@B;JjHbB9P^zAZY+U^vQi|Tt<{T1(vA+H~hE<~CuyU<-j9yE< z0?;W%K?{;dExR8`yPDK~b)O*^&Nlh1{Z=>88pOha*5$m53l_p(S}}W$nz63~TOyB_ zUjgJZ?H=T&t#ap6xzzr9^lgQd10xn3PcpcWf^~?4^AL2LK zo$BEO1tlysSm0=VF<|Cl-ii1TZ&9Az*(Vk%&haa1dj?#^c}(Y@s&}_o4y5H(tnZf< z-|4RCBO=*>!=e=dBQ#p`+Y8DyW$T`tUYE*ENKjLTmOk)vm;(9^Q7@|8$+vp<*Vu#I z@F?Ri#<3OL$rol)#~}}Q3OY&m&0oGrj9u8_r9$j==`z%}$KPyz>eie(c5$lUHn$OO zh+`Z7X%b|*(hgtkgC!Xp>*e-e<%PC>_Jtp@^SDANVfmw1dieIk8Juqa;l-_S# zh~O%Jk29VK2XF?!D!~&G|9+=jOh~zQLs{%%e?&(kf(z5#4@I9Fk9(x_YahjT;#!0R5vw=zCj`|;>UQa9)#l2P(d_#aJ`N0w~ zvZWR#sx;0eH}}{N8h8u!6Pf+hXV{;zaK!|&hek4_#{2+#C4fyoz>GE`p#h#~+od3; zjlhbX0v&i<*W-j_c#ue<yPSeQqEvVL`}ois3bQln+uxb%f_;8pYnV7EMfnpl>FR6!gvV zy8_+QPC{hV=BHeV3{TY%4=RMsWrcV!l6|Sv;a|VGBY$4Qw5lyWheEV8F7ug=auXiH zvm)}#L|MI5U9!~jl=D+#Mlfu&6ec?*payFh3E~4OY~vTulk>$H*V$P)8uIA(b0UOMdg= zKBNxwE5wu)a7H`W-TSz+8f69g(fucRwFY0WzX7E>pY=^UCPn?i%@-ky3jF8NlWOYJ^B;evOL;%=@GUAv-w}6P?Rnb=vD9L23@grf6ApeW^sL(l6~CQ&619D+itNNK;4(%+>1_=wSz)*E6TCN+njq%@ zUS67FN`1Fl_$`T_&9|Gi+1bZ2)4-MI7dT8r_CXXZLDcXdhFl$?L3I}zdB=#JW!2{! z$>;D)FsVwA5TQYil;ow5^8yeqeg!sh6VJhSZnGiPv5iB{xU4oA1ehX8`+U*E(^2hPz` zYf$<09!};-?Z~0j(r9-gkLvPIN~Dc2V}%v&L@wU9G{SvoCfJ`@8%ZUzx?ioUt%p@s z?t&_p62wi4>sicNIUN=^!0>^AE*EFl!m+bV#pM<-B`eg{i~d|!DM`>Ey+(20bkJ~1 z>14iW54s+q`n@%>PpdDGNDK9&ViC11Bj<9UK}zCHb~{qDC4J;b9unrkAUr-&Z=n~v zmNT)>R%MF?kdY<|_O^hW566{w>)g!Lqzlld`5n;z=K`f92Mvz|ZTHTHIJ&27a;s75 zRH?X!r1uJ}QVFx%6jnJS#K>F(4C6y5;<1#!w~v4j{DEPS!q`B$<&`;>R@72{^)oU} zu4I{@Ml>heB4LhhmC=`jdwLNvuE7bDoU1Byul~J7WFcfi7vP=5v2usUf`!0Bbg~Az zx~%R@DoGoj@9G3ae2S*%L~Fa}YxnY=`8GII2olc6wZs1Vb zHns)SU%`#OPHJp zb?~@@4fou?KS+_l=1-MDS~EBOqo&>s$fd9sHkP~2dU!+7wcEF>y;s% zz1mCYW?(A5x!8Uj?CY!vA_(^lGCx8_t#i=G&oBP?sj=3!5Kx72pMm zM6mA{1J>_-p@qE-#hkzQkcx_a!b4%Q#P()*7dtD?qw1!asOyZr>&m(|`TNptU-+*? znQ9;RgKwwKcrOW(0ql6@;FF0tf_V2dhL3tJEU??Sj{_G?RBPmo=alQ*J@CqIAe9O8 zxpz35ak+5ZpyA4(Ww*|-I}RLJcvw|zWg;--R4NlekUBA*WuvcQqm_WNF7n{#hSWdJ zX+@(RQ%a+Ce(UwP2iYA2z$FlQ%TRhhekEX#`EdT4?``+G?u>bz>V%@c4c>^_IDdaR zZ}N`)>#7#2J-@F#7fL{ z>s75)MM;o!`6X7z%hIQiW$NB5TtDdJD)Wd4l)9n*(sGGKG@I-~fPDFfj!8|I^u0Na zA%nvQly=y5OAKrpYLHNL&^d ztB59XI4bKxR(1Pzikod|&oH;L>|MSncH6O!qiI@82sYQQYsZO1Jr?ln3PLVugLjvE_`|N$H)Gk~E;&C<~l9t-R z`5042X(Im-v-0=LGUT`%Qew?+SZh>0K8_;{2jXlZ21@GPVW9Bn)(xsl5#n}Hy&i-o zl$A58RHNC)1t0-2#;KQ>z)5lX&W2oKQf4$hl-7L$pk7dE5YL6>`^f&BC2%V?#){q= zoT7Q|kQl`)+z24C-ibq%%z42{^P0p)c2t)<;(oUa<`tuG3 zE)~gp+0yuFyd{}D4tGVDZIv+7=!Y*uB$7y(x5TYcM+!(V3i%J;QR-Yv9;n@w>hNI7 zIW|h(#+v?E?RMZ1ZDz)#cHw&My=-w7|2{rzd@ddLT}Y0|_0q%O!SCcZ@5H?5F0a@8 zEZr0SGF$R|yJM#*B``g|B^UWrBXSK#RoU`$oA!z@{)_BezdLsba(3W(*rF^KkcXqG z@BDhDo0mpAWUyNeHZmRsPpkWCNNmSS>a(w#Et)~gizI`HPgpp4R3}Yih_EUau%8=~ z=6=wyR*I-lk_bZ2?9tOujw0J#nj7{TWg_mO%*x^2Tlq$Q*@>4EoD}30Z{$E~1xykf%0kO`t^5muoT7HS0PGZ%ddlSLFU6hrI4?zl+ z^O%2lnnd!6r_5lc;~HAZ$kSCw8OfU(cFA;0!t-3c-?+^`-${GG^nQc>uDe4izw0y) zffuS{4CfG z@KRykR`~UyEOqC+&@2zlvL>Vyo_Uk8r1zR>bw4ErTkn{O_$KzH6qm8lOK*nc<3-$$ ztmK2tGoAK9EJRuKLHN>ge)o~uqZwJ4`OYv@c^SSR2=o`ZOH89h#VT22S$d7P>ZOvs zcBOu`HSn^35LX829>u24-5Z+1y5oy$uT;g8lqdZn@s&oL?mOIETFGELm1h5ZSV~=S zq0bb@FV7g{DPrm6M=lwlAVK%MpIfWxj^JMWX5PJWgj-e^=)5{Q`lAoDN^s|qTyiPz=n5^n{d5C7jHvTqPmZ9 z`fMbQvTGqc^bFkelI zHFU^Q%Ph+@drXfu$$s6Hr+XmwFt$^0@wI&>4lWvI@rO({aO(-ec5C9ozoP{fn1$2i z?+5k~~oM7h)R9WW}Hh=}uz`#ucInkB~7nrCv)lYm!pZ z(=)pWYk~IVlrOveWr_+b;C!^(-l3Qv4-De{CpLBgVgP^SxXEw*NWO@>M)lNB-r83= z&$q*){P0m11>hIMjP-(WIO3tuI*JUW^BbPOzNJfoKZ01oDIB7L$8kbzF0)y*>Bvt^ zJNqz$H)PtP4PV_PLY~B@>~x)ShX=+X8%HN#GnmjnOE>1kOx11w=+-Y8_UqMrFF$RJ z3f-u#iL-Y17u*&+wP;kp9NOmSPFAJtE!Mhb9(@!Q?M3xiN@*m_Kp~_$;Pq}s`Yw9s z&l2;9yjB4SK|pXHl10}gkVmTS%U#mSk?wf#@Q@+Y&z7cZj}-oI6E? zIZ(D^HHVHi2hXz~`QoWS#ypvGEJ*kxbTzo;g7)y(>nBLtr1TsUEGt)r7@S~UJP5Da z!%|2nV-vWp!zj_!Eog2}CMb0mJrGG0_fT*}#(%T$Dhd(Y0&72wak5i?C=Y_B@S!0l zvA$(w1O4pA?7v;RZhD|RZh#=vH{r!7u&WV~S%@cl0ziKF@yV7<$nB6T%CZXx2iNBU zTU9;3>+oT3mqO~HaD&-=#wki^+z!Oq-kXUD>t0C*(O4+yPje`{+sgZcxV{6*?IQ?v zk^WeUODe;$IbO1blw{F91&xWzSp*kh`yL@mRFsqgYXT#S2Ps}-_cix0_x;c*Ui~}; z{qEj6CzfJ+jSB@C988UbRrt6djR`B+5l8;0`_CR5>pm)D^vBa3rB^vcJ(Yb$Zz$R= z6^4wxY^uUt=qM#NFif8wbz~=!juR+MVo8j$*-H|g;{D!(ias?B=^{gVT37-zsa~z3 zZI!Wecs>H2O3l3H17eo*qL!*iE(G>1x}(elHgnseW?tS;So`3Q$?@1cpRnj^*ivcUtfJjUc3wtaO#9n$6utgBDq71jyag_qiF3 zU9uMJ87`LzMe<#CNZDx$v*zXW)cT=Z=?t4fb6l_Q|r|Umkc644&sQeu#!(3<3--CVP7;d5owA#%) z#Ey6~A2N4tsrV4)GNfFahl|J%s$sm(1D@oS6FaH)=5-ecYggTCQP#fc3ir zD~(A^_b$Qo;J|#ymD(R+AjYQ<+Q()F?C+2@~m|}u$>&=%{D*( z!Ckxt0#j>n{(}!H)$47m`%<_>O9&;Q*jy_!4KBrA+b=o{ZgVa6Ho=%jq-?y&4_a?e ztn1>=dWRGX)27MMoVcN43eMEAj1Yng%$3P3WwoD#DtjOF^s}kI^aGcC9IzDj!l(O zas52PH3H-k?@)y*|HFAO@PvR0*!tw;iySJq@29R?eckCwy#n(lnLogI2>wdFi9KlkKKEw6KP~d!(MkF1Y zb{{B#)jQOJaw>$hu2Brt$y4(cJx!XtspO0h4meLEOt1N>gqU#F!;k)ErDi#8)QVGN z3@&^xH1YFQqN~61c5Wylv8%Vm(@73Z*Ag%pD4}XeP9Vm)C=!^gXa=BQe+-&`Ku@_Q zNuf!D6)TS?&a<8?c`Y@0w89D$u_B-6o{YlnGT*?6ZOdJHv%Q?DKNBvvcD9}~gVbU( zCAh*iR!g&QtWxJ+dL-%?3%?aGFqz)85Ak!=8efTdLOQe>?Gx>qyEU(xm@gs{D?=Z= z83Dw&^e~|qi|DC|f5QMJX z7(t45-<>`Qj?(TF&MH3~J%_o{u~hQZ)q_ODCLe=MmYp-`6r**ixp0*%3O9Tu!5r1X|Fx;SVF3$0Pk%e;{YM znN^%+|E>i1ZJFlz-1L_@+Sj;ypIYUFzdUrXrcE{N4fOP3xFl|&JV71rl%`qMyFsI{ z@`mwWR|N_N{x0uZ@C#x)9&Oq2$LWBUVvIqnn#x3p=&0y)!UQ`_<*pU zy?Sf8_XFvGg~o(~?ER!GetkfgMf8ysN@pF&+2Q;o!ymFeUqcf+hnak!=gnE1+zEhI zHkPkP39piqU_%K_n;*JwiY0zNVYWo@Ia<25u`N4%`Y)hZ3fwZ+-kzd{QGuBm<8}EpoFXSp%xmU#2#~oKAJS z2Lx}ff^KN#hlt8gBiR5EhW~3Q2^mjjnBSh;|2XpcCphvy}FYofy*3kebug%WCR66SNoPv(m|SF-(Zvl3(dK28xmZ;ROj zF5E2|dXU5{BYXls@y}u7StK_M-6QUc=@lZ~^UP9Xv?iAZV_8V%62hf*K@^jbdzfus zfZ2NoHLjcIqJqrS9#t=sF3WvD#vzDOm(-bW?t}f@kWKz@=y?xZu$5C$Xv*!BpGEGB zTj{wfgN}j@$*469G(%30-#XK}(Ih)>bRpED(O>0FU;A#{lA}_RHMGaJ-4pcx${D>M zp8pd!??o~DWIW1{uj>R&T2;58Q11Gcm}hI^lU?Iy0f&@U)a;yF<0l`@tWmqOj}ODE zo^lYn?)lC>Z;`)XkQ1NOJvXA|?$q`-**KgC-BJDG^q8|8lbkQV{Pj3fL{4k3dIRdi z+@gOtA2>ntbennG?b1BX-M-BQ$EQp_Jbim##*p^klzoovJ$3eR*Qky*F3qu)MJZ)> z_@bx~0($V=RFK6}n!5;i2X&g>%MfN#6f`(#jh6msV-UW^S1Vu6t2oS`HYSpiH=kH~ zbX#0uzSedqZGJ+LC^WKiD)U*Tplkh@lUf5g6*o>-+ow#zaO0_Jx3`5JE_bhdVtC9% zIi-3lB?e16b9)oN629<$@br17s$ihs`fC5)J;6%a2=#uROQn1zM(ia!r}b=%vHMif zF~c}^VmG3EDkY?mmeR@_wbHd$+;k^g9zB~o{-k&TYlM-!%D6yoZ(ED0Ybw0n zle~?yRif1F0r`9481sbtQvB5~RFKPc~jf|2hsrQs}A3CY2tKJoK zxs9QPQ_R00iFWgb#&eW!Heb(>ySBV9_mhkFqy)cMhE4p92`;shJ~@nZ*|Z>feQM;eCLOmQU{xYIgyEobk53x;n7StGP$Y!u zgL{b3lvu&r1=so6cUvmxq!L~=$*HPsph!M>Aoi7#kI9tG3N8#;Y_j=nOBcRQf&^Qm zhuuC~1B)aw^4a7qQ~U>1Y<#_2UNtRV@f?REi9vn58*^NhQ6g4N{tMXGyVH~rN&_({ zJe{D<(dZe*iO|?cPA??zO5u!=Ty1-9?Z+^QN3i~~xP=>v*Z zPKAUEfAhSIKh)-0`8gp(Dv)IEE9s1%oF<@b^TSqxZI%etXN?(UVqu#2a7e%A?ob`I z3e~S*Cv3Zi=kZ7}Sl9=n2TNpba}B&Ws@>~+QWBv;r!h#=isNrd8jRVQnv@La5?}a+ zeatCbA(AP1j`Y3GXqr882jvi7W;M&PmDpX4vYpvdJ_}R+$kFz-!=1QwGZiE?aa;uw zR}2%*9oiJNDN1}sPcYPoaZ9YV48&-Z$eNOt+mHl(}?b zm~mdv*6rFiF)ML0WEYPFSUq_?v8v*kty$Nk98fS>`uWluvkXh66db-uqxCv-&^k-X zGT8cbrAB3G(OYYq%0_0g4_E3iSQ^OZCx)jVG)P2p#~blQS#r^n)<$YoEeo&$b;!YV_<4f5f?d~VxR^iVFjFf!Sr5wpaY+x8oMkU$luXp!gjZzhfWml@3 zrZT~k3qSgnxNvSDl2w(9*?=jb?OCz$+r@#dm)o~#ItkIdGcE6}z$tTjBdq)C@iohs z1Ct>|LP!dIsF^|WfHxC9b62kNQ%{Ty*YM>0FNVI}DNff8)r6e!I3cMCAzWK1oKH@w5-0)_xjc$#jXeWSfo6~(5q-(kysAFapfE4y)k?d;$t+xgDc}7S#J`DOPS_4} z^Nnt)V97M`DjUwfZ^H=#`&okBH0D5VRh$Xu=FlnK9!}vAJ61cxLTez@;5ldO0(G{W_OhL7hV=!-Jx>KU#fQD|Iu8x=QHs>dpSE?-JzPu5 zj^T_nT2-LkcP+MZt2`hQY@>JpM3Bzbv5b7<%@HOyhTj20x^d?vz9Wy(%7wR$EaeY1qELLha+O}|j!rw>*PYb3mwWOWk1$!uUb`T@N%zw*L-u`Imj2D`-H5E-DBa|IL z*Q{f&{DL(-)=Q8&@E8xLM=0|Tr8eu;Xi{;&P!^1vNP~Ki@mV6jItJx0`-a3uK$ z#5gbKGxzK}q_1#G_@oI=$*HC#E#;ig9X|G#q&nOxbwNI}1jQY&u z@wnQssDe&Ao4tPu7&Kk4)9E_VS-Q~sMWAG4U5MFwIl|3+(bb&su2;7?`sw&5xYdcx zN3&9n?BQWQ;&dQPVAZCO-FBE&8{DH<*6!qgX8($%Yim0@w0n_Q+kR3p+lO)3N|i7< ze}-o+!f8VZr@J8WRwt2(a0!D$>v~sUtP6`F5uv9h%P{?m@tc~Y(8ziTm4`mczPAPv zXB{b0wd3#I58)gVcYBLjAtiKOSs_NAQQ$zWpzobr66wt#GOJuZbZ;L2T>aOSgg=Ak zdRyX+bAmZ?E^EHxi5*h~$^8|f7KUO4k6FHG#`U1>?Igzl)XEobD>fH6p7JDOq;7Py z?g6K*sgD2-B*0Usb)_S zLmiPS`q(Wc<%H-E`I5S~58oF)XL1&kXUpkz8mQO^}=1KZN;*FIv zn68c0*y1{(WsZjW@08)ErqP_)TMZ)%5j4@WGVGW|e$g#kJ*R)ds5_omiAi7Lb5i4-?w4jfuMSEw)HZkvuPxE_ z-x#HJ$ba?l%3r3uyOI8w((Eixi>01Me)+eb}FJHww)*bF4rQH1#`YZwg)*{3>1x5gVq zmM@x@KGJr(CD61NC{>BZ+r0u2b2Xqe{4@ zMbCOrCxa`uFC&{NKaAz)6j=A^ZyY9uozdvcaP93DGNm35nWCE8?2)JuMWNzgl{~iL zqs7GYnot3=8tdO-M%j&d8RwtnmakwQN1-pe7nGy z@yw1$pM^~Mz}X=48x0R(I*$?NG%#x z&MD6LWzC3|UUW!8lEB%VzCMW=KS4U#Z3hGcRoqH#4tR#6-f_QVA>3hPoLAmw`aWlSRUK}|A5EmISEDapw~OnH2Yv2*HV$zNW<5-6i16ik zMmQ)Ht&B|D1g^hXYWY8TCDYz_yHJ{8UYVY?$$^REL-TnS(!E6Y25oAlWw>LC-#K}p z7JZiJjmuV9%WGt4`1xY`ImeeI;Q+H>IQ*XP4vzcks-19;^E^mSE_#Z&Oma8Hx`RgY z(RPRoUaOj$AC1f!Wnm23qld&#R-eFNACbvo1k~=6KJqwnB z*HrqXQ6w`iO=^CbZH3)Ffg`;*F3Hk921$1tj5)ED(JM5}O7TZfMqBF^_S^b&Wrx0O zCadF<90p0f;Q7nX26tw>1lh8!mG=xocJP7BJGC!gE8>m|C$G)q=Lw~T%Bsziv{Ok* zO86OhTmy3F9I+l#zQl`dixPIi2uz~6U&SkI{VCtUTb6Qm!L=O)Mam)S+ls8}n|d7! zkwh{W+4!V(z3Cfu$~EZmZH_bD%r1>Rzr8hIYvuhzL^k$(7?YfBq;`so`|?LXm+8j5 zH~+$L{Mjp7D;1oF)H?;gk3=`5Y*5hC3A!2FU0cwjOdnZT)OK%@V4<(O;(mqMqCH=f zY0AM=ocT*tRNQYO$)r{u6S<*@=B4O=J%85X^Wk+P=L_1Nf=V61N(ONm`u=#;5k=*M z;pqICd?^J>0y<2_{zhH*VV;EmVy|%1!oMD+2q%_RlUn3GJ4i?{4M z^<9UOsD*PAZVIq zO{2kG)<;b)u*~ghPl$&iKgiBqNa@%_6Ns}nSU*NkKpji<#8ca2;#QPnNWa6M&wqyL ze`8&2>B?qMMU@nqO8xZ+&$X>?9d(U`K_CXFN72Iu@2f~DwLNP~Md^%Lp6rV!bVb-( zBclRel$5UL&NSpGKZ-6*$Jwvmb#`_57EJA$7~3saV~67I)mYmv_&)1iR7&#UqixJ* z0qt0dDi&;yzD+fKC&>4zj73rO#a!W@{wC1gZuUyhMk#AM7VfI95*E&|Whe1bP3uYJz4YMQs2)p=dsI7}9Kc7n;_gVfF}6mMTq6e9x9 zOeda0pZhY{-skX;pTa9iz?M{~ME{F7y^BpXc+oXH!$#)YZ|X|LwoB}S#hVC94dhwI zrEjF)C`UJfW6a{8>oBt0j#E0zC%)-boiFJR{1TMbfg1=#Ldu=%N>K z@{{A&Cc-a|YGaXJQrC}C$}oQLtpL@c}T zooX^ex}fAcHpQ5IA)=~Cg2J;~KaU{9W^}d)w%aKRD*ZA^;t9SdGEl*I#nzl9_5=IR zSL*Av(lx3#JeQ<0j5_P;zKW6dEJLQLMdn;MC36If%FH7N?2=!r1qO2b{02j(C%)~n z#ZDdI>Qu@td#_d+U<#roPvI>UWSubWt^IPW(8mp#FOZvCAQclFare3>JB3|P;dk?t zjq5H}LHFa5HXqNOh-kCnG5zsd@#k_~UifJD%M#X575j6S%=k$s8F$STU#UzxXn0WX zICd)%S9&t#8!|YhIQhnNq7R8Zca-NrrH*uLa>;s6)cw`%jb0;-%Cq?kv(t;#b`?tt$t z_{RAwQ6WuM=$pZwT+AWTg+cML)5=g@P1T;jc}9u{bljx=UCk zzir8&Dplbj-N9kIPcEdwEIZsPfAEBeSyZv6_U5YJM#;qXTbz7+3t!Q_^pI1`rkG8m z5VXo|6BKpkyK;+@{qGDB;U);)n?H*oy*+Aqu z)@0tw5nV?~drArKf2`k~r-G=!V``p6=XCsY1+L-av zk8v$GRj)#ur(sn1MJ@@l&0zse{T9|a8SeLFy}4L3t--7W-;O@TXN;<`Y&M~Z%OTUE z{?1i5CB2AG~63zvQu@6ZGapP|Y zwIY{cUIR2bADBF!i`uH6EBu!e$1!V2h`{bo=5Tj{kh;8)R+=1KD2TuvjzoRyJ+~h~ zd!z#g#Q9{ijZwfqVLST9`ti*ZdHk<{d%aQXLi+FjUpe;2-VWd+dG~LeZ!4#mZ2XVS z`*HPlOTmYS1(JSnak&StqoVnj*89)z{^y@2j@@TlTtoo<5g4G951A=~!`T0!GbD0k z-#ym~KW`*&6b8ThzxHypMKTw-jmH4mQ}QdB?_h3W`>)Ac@wLf$QG`}5k0eMyw_Do& zHGf--nV7=#RqA8DV`a(jTBSsq|GMN2c5hDH0A2!D@@7{zkFMPQ68tZokttEx^k0av zGGKt||9wfs)_ctVv7{rM;fUuGHhD$BY_sqHo$a5{B1V>;3AKMs;{Tt&FVI+gyRM&=h^<=iF zT6^iII`CNujny{7HOXU`-wuDL#|6{>Fh##1=yiasm5BT0@CZPeSBStIfY5E5TL3R$ zJBdfZqF@3Zx_mT_DEQrruUpYh16eNBH za$%3J1O%j#{`VO!EP^`wfa*`T_kUAF{_*8ESap9vfWZ7s?)+(${NG1r^3);#;3WWL z`FjR2D@XsIrBM0WM8OnBsixMv;9x&$F#b=R6Di~F!Xh~6zJTrdKfp?L4qtfF5E9@C z2$gd!@_%1E;g`b((6S>uFc*Jpe^IO=;9|sy`0!(p-vZ7&;NssU!F9O~m-~6Iu2>&` z5(iogh!7DLq_nVYc=Z4O$qEV0*#B|@(0mg=;1&ZN0@@NW7-RsZ|Gm4n+5ZIw7+OCZ zk@G3~Z#EPHUeI9P@e!uIf4=0s3Qn2%AD{&3PyYbm3?Tq`|Gqa`coSesh2VksWrqJr zcTypWc`S@bg@Qk*U}v*X_TNVaRNVi?Km6~DG=SCpKeUK{g0Wh{uH6MlDjSSyXJCm? zg?;Aw7sEh=+?;5ORtz2z0BlKvW!$di|NLFjgyt{*rDMl4&6&vl`-S>HiyzyH%>Nr= zaMNh|0Q$7B-|HWI{}GY?^PzC~PKkuwT%qGp_W^fg4VFXbTQh{j^gsXg%b{ifBI3d2 zN#o_@|Lf-s@NU8F@dwz#rT-+2Fjlw#?*T@<-@uuG3XeG4;lCLUD)1alK>bQ1E0Sx1o}LH_XSd9@abMFF}?08N5VEcJ@})APJRCC!F3UnV5tb+==YuK zHnNn2{(ru(>X){Tr{%jQW5g|y>DZO`*_@lLy4wL252j_pU*P*GqE85>5qNxll=yUR z9FTmY6Qv3U9#4&=M@Xew>7x&+A}5(r2^9;EB&43>r1&49VZ4kLVq`OA5&5mSM*P$>ru+u7s6)~29(Lw<^YBR z-mW;*(HeFReQDX4kd%<_7j2v6&J+6M&C>iY{29_Sw7zk9A7=WA+gv#8w7)C;^BYKGwoR0|;_Y%ytx+np z(a6!3)*_lEHuI_*1*CpMEKVc%=xqV;1JsVkU{SXMOE1DW44&nmF!u!EHLB^;P_dZs zd0GTh5O5y|wnrGKN=gybc-a2H;VP^JxQE{WyO^D^hqI^*>>JRx1Hph>@t@U6N*AxL zm?iN7fQ6EdU^lpPV#6JR(8Q~P>_62_gNel&_=_z_s*U%~1| zSgzr9b%AyHp4)-PQd7Y)^!?Ko(Z`*YoWNr^eAg@PMB5!h>rl@)LY7Yw(CbB%-@ietUo_bsv60EgN!~1F0Jz#$fIMX;evI^_BKuMDX79%3jw7%1$vt&m=h%d zA`Rfp@ojTuc6c8_;gacuj|aG!%m{M=({p3l3(Wz}2u^kkd|Hk&@}{BJV5fJV>;SAP zt2B_PGoIbR8TpT*ry9za&mQ z7(g(Y5x`V99lhYIgCLHAe=l*UA8F_kpi>R$`ZGWbPB&L|L!u=!m}mjYglgi}B9FuK zhv;c-vj8K2oCK6uIT_)Ungi)ymNzgpK7sEC!A~o%AtaL;8ba)9-;g6z zt`b(5m?H9-Lm)2eS0OInt1DluXTJ_11}QyNtq=LIYnIe!{3|TYO8_oX@R*$ev;kQC zZV*X<0+4>d(n25uq&1yu0I;k`46m_%UKZSlW{;vq`L#?N-r?|dg@@9ifAK~)8~SNa zjfLzNbTF%Mj*x}GlWq?Cb3+^lI}mBAYi_Lpj4x$ z!n21~IW!OkRY%#el=W&)KuHdN@o&IfAf%Oj?sJvHWj7b>jskU8F+a|d|3O3@Qz%+p zgk&})Hyo09p8;TXa%wc?=5kby zdNXSub}7f#^q?()FL4UJ-0gKl{djka187t>SDQYt^zN`-Xg~#inE*hk=aj4yWZ5bO z7MfDcwn}ydf{w8h^#FGO)z}G`uMHqe5DX>=W(NX=Qt{!3Foon^d0ey|j~=54J#wDI zG5CW$fQSvj0bFTvfK1kdG7@r0Hh^M|Aeux#uG&QMB_7@@9&1iVK+AE{ol)D$>|x>_ z>6^%%r0NPRfxrwA$N+3t84%**e}gwy2 z1de=wwKLfIg`lG|v?6LN(00i{q6x@3np}>k3Y+ahHKL#xIRyeU)Hbk4+7=_N2Es}>TS8>ZIfInR>5=-RT51`~0TIoxMv2Wg z_Yhc1U=PS1g=~Np$k=o!>}h~?3y4x8Io=2&ut@%iK*1m;meXZn=MEhlfE5g@+QBLi zx5puLR1Boc2)4r(>&|NqkbBa$*IPVqFx_XwK*S&}Dt+X775FZ|)Fi`rARj?Z=r;+T zJO`YXC_I5r073J5bsfkVtLff>Yiv5^0X}0`0e<|z6M)JSfrLA1fB}1g5S-`Yp@{r5 zKZB11Qu>HWQ??b8ns8J;KIwzK4P@=;XHWe`2!h~otI)*Ez^`FuOtr-m#u3rxVdx3F z@xeEEvPkfA*qTcki9AHL9NW2Gim~ios8#@#3;5K z^}HR&!|ZYxfStidF!y_*JB7#>MLqQwj9Ll)6$+>sj~==@)YD8B9)9+73`ur8zSauv*Pk$d7wUpknkpi$cA1;kk5P#fP{&(3?Tm^;H5gI*6*Ol z!4^`2&&qN zv=K;W$K)@^A5=niytv2eTuwrSord@_!|tejC!!6Oxf`dJT|IpL@Mt9n84*cT2sZWknA^isE(Lxk??}XiiXm`NDkDzoyrkA{;i)#@sLJj8u zQBV0A<~7ZVy73L=rE4zCCwo*UCND!CuEt5dtN8x;!yhnm-+>-m0;!V{n0WwSnEA8) zV@JvJ%_eA^qaEk}YTI6&$un0RHG538kfs1AlJl^D!8kJ0m>d)u&G z8^@lIzxet#O}7$5e-n7?EC&w8&v79LAQtW56fZko&ez;sg&g)FZ$uUsphMQ+jE6Kl)cYR!N1EstKyj#7jKRKoU9V+kyrf%V zG%x%gNG)NQ2&=6iKJO@jg1G-p;)_Oyg4*A;0%4Hp!5cc zV2C8;?g}_v6TLYvK?EQ`;zGXa`CCA)!irq>RosJx9t2IK8P0YmHdrD!J5}SlDaj5+ z>140Yl$zMuGxWGi?j{S$q3x0?epi|sd{o12*VI~HD^(A2UNclb2Fm1lz@I`+@2_2+ zl4fRQ3q%?lTq8Z{K@gbXI8SaPl?U;C8`p$+!|S)3i1?5UD+KT!B$b)8_kic@TLw-H zz(T26KZo+O)PG*1t_?bLw|XE*A_t6Hb&bLP)&XrUg4=pP@>B8#RM4ZZnza@=R%Cu! z1=1jR(Uefpet}F0{Yjwge1}DbIO5@uJqC1RtnM%1B7Ib@y^@}{Jx49UbeB*%j4F#J zJy&ibsUhbvleYlo=L}>v`taOzOQ13@=}g)I8_Gq)?NgQ@G;YuJ?u~qytt4=*pp+u2 zzkB~ihwWLFy>)`DQy(>3vxN-$A5v5mtSC0sU!~Xj7p*sk6Q!Q-871d{qgEIF&o+5r zMJ5AM$WocV0`p5NO0pC!bGI;M1as}L?7)9r1FeJ|6vqio3Kg;Yw0AybM9~6?e2nL2 zq!`z!$n%BZd;w82(_?6WoH(FpdI>Cu%AOvO(axE`1Dwkt{tc&iv4|^3@`-5;)5-T7 zDG(2ZDl)m8G1IOPp4|iB;35hR@RY=)!rBi5@pNfCwgx6AB4ZdNvkW%%zK~p{YS0Wp zu!E4{Cil3{9@?6xcC2rj^CGHf(GcuUMcdEs|F(SY=EQ^r+6K{MMC2Ukxrg+m;PM>&t=}(JA@OA~f^f%)Wl01+B z`q|$IThQm^owC~x;dnLy!ossv*opx7Y`JI%Qjz35+j9EqH&90-)D0-4O#Z}yqcK2aTQET3>LrA^`_S`S%pjIK& zYv?=dC*~ZF3)LdZX-O-M4!)AsAfw&O`6BV}JeIfDqR}SNed75EGCb~? zb!)4Ib$LY435Pw@AN~oyts_?KO{$RqYq3PG*nH}C8O6eqYc3LMY?>R&mNt+Cm6y`h z9YUoR5@l5@zh2(M{^^S*lWR0=$iOE&WKmUc&?xBjoOYO9X7`d5`EO*Wx~IvAK+P0_ zVcKGL1Hlp1ZC57!dyNi`fk;yBjJN2jBSR(r>hNg27wcj8DqB`i^w$9dhQs1qyElr z^-A@k%ZfOJEUMdCl4rHTeNQ$0v#9uRuw&mNF3~C9PxD~`v1cVTN=SvIkuCH6;FX({ z-5D#6)b*5#W);;=JO5+363?D<60<6(3HCXv#L$cA!w@G7@^le?>z?e2dku>(~S zBAat3-=F-KJ;QWAy0zI5XV1~a5$0vSG>5LQo!_@$V)g95h-61-oc?^LG#a2L#H+2h zz10?~y7~QA0f%nLwS&xT4xWDDqR7GNJeMu4--%inK5l<##wxI#n^_WH8C9z`TXr@_ zTBP8P9Rg2P&;y1D#Dvkt5J`5Z6xcRZt)c?Enz^^hY|b)5pkDWF5lNu!(G?t*?1a|9 z3(k6KVh7Bza=!pOUnX{OIGSLAE1Ok>xa3=Zyst#dp2Bu99{3bxDd5@L*>XAc?Xyn8 z`UOwM{#$1W+IlnlrR5=QvWj;kx3n3f_msL05S0~&CCEFJZsIy&Jqiz-Y;}oLaY^Ns zBvjzf*omi4^Lny0_E-a`j|@p8odtk3rgg;c%*bq=mwsZnjJ+d@+J$B$^`ff#u^%s& z9g&tEA>YpD)FDMlrJtmKZJ{(vh3WfA`~TR2Z`ksVw@*XLsF*dGE-TM>=ro|kP`mxG zioJwRg9vZ0TP z>%T%?QA4mj8GGZXh1I+7bZQ=QA=;VjD8gFnx}F;fzfnILhF|xa9^pZHGE|F3B#_t5 zb<-X<*$%x*kNaE4vf>cE1yRK&Jui~^>ab3)cPmfrPzwCHsDG9xFUWym0S(pdAmKOJ zFMVd=qqzN>A4DHhYM&7G&n<$^h6aPE{KcfA{rcRJz`W<)_#;%3`ihl-pK#~#5VmdU zUg&cWsH#;N6pYb<{8FRqEB!1ZiG#$9dg_vxgNbwV)F?@5q$M3w<019^NxW=4K%6I= z#}4P7j-mgZm<36wWVSjNZ_!bjymln?B$&**7~(!>&^j(LFX;t1IhJ0%8Sh3nd^`4Q zQ2VDHrW$v@u>~W6LN`pS;JC{`HV-Gn$XWT+a3}RiYXCgISXFSnLzZ z!*PemUik;D#qy}5v^AUP5!x#btRXu&_13q)w^ja9`U8QveLZpZcyUR&(OmZ@?#_FQ z$m=;_O9{UIMEiHG@E4Zinix<0zG&r@smPYFmNarLtS|KYu$B<}$7( zNFWQHhk7ij@ATWPqZJbCRRUBfk#S;SBn;q5+)rt-a)OW4;{o z6oK9^o2!xz!|%MiJ9tl6u{3#stcI+t4SW1f1>s5Id3Rk2w#|mNk1DBjogyaI#)@*B*& zfB4d!3^_$yg8cf8-Me6PYr*>L0q(6&cFE%rrG{nDyWK)v>|vOx6Gi1Nl>)EC$U_8Op5x zy0({@H7U+T4fg>2C9csMY7z$Ijd_e*0x8 zIsJ#+Xv5# zaK9j|mxJ@(ZJtZ-vI^~t8Yi7$s{HwcNsmIJZp*!CQZsi)eab=7^SltLMAjsSCj<+p zC)mMXo3R*mom3EpGAg$RPUb0`ks7dTk{#2wQ?%?$Hj!e`CeVB?9+!-Z;xF9V|iEUiRl=(L`iwv?vC``j@n+A%rUeLpqKYLyPx zl9Rh0c&>~VmUf25ry(VUm`p4Yx)bXqC6)9Fam?pG86?SDn^)X$mwcRxV;b!Z`0fX# z&W8@&gQ~2drD>}Q(JptHulXv(IY|=Jrk7;q5?pu9{>^sxtF-4iO$EPj@YKXk^71`Yv?pCtZG`{dG&YOs^l2W)6JfK{Y-d`tKx#^9H z+_^qY%2-yRE{xlELpzjMs$lws=Jvro-YOzTUjgkJ?%^T5t7x2anSB={kqw9jqr-05 z>G{r7?<-$f9kuY$;8P9Z#PMa6G-c;JTHZ8AS48(ci@s*KVYqYmAa}%R*(S`4z<sMVl{=~2>|ZaMg5+A>YijiLS!O4EUe|7 z?kgQ_RlPG&@bmmUjzLW*#Ny!ZD0BOX#Wt-Cg!%&S?E&EZ=hPecthxv>>KxEG6+ZBI z(lGX$nrdy3R-_YV-eNR-NgH~A1{)2WpfS!qQZ`#){M)4{kX}!8Zzs-EjB7c#GMgeQP zhLEAC%{*>9pNCz3J?8n*a?9s|a#|zE0d%x3QYmimGndnoe;+uZmo9$a`%`m)`<hQZq1LBtK%x?_=d`PgoeZQO(K)G^-fE!d~{aQw-E`U{`j@{p`6^m zD)2-en-)%jvr>9 zuX>WM&h`GXZmKl0+=umVS>_cqUYQe1J5*EO`+{_PI4PbCFSyIyOa7k6F$FYF4EW)R zA}^Fi?cR78k0tzlw7mI2YzKYqmDa)7c_QU$pwAxMDSkMdF!M>v%AFZ{qdKJUag)`d zC;Z^1w@xwPH)kz*!e!PNYvsdlCW^aWm`!|QNtWu$Bm-m5m}IO~7Ezq)CI*XLf?q#E zT7E`RFTOnmMX9~a)(+eXeH07~0(U#)XBqSHB<#>8M8`gfQk70wKRv-n z%0rCvC%6A@Snuyzuw%pnK03YjJyUv%vN-y>!KM@Z64;fcPo?eZqZnZQHBY(yMIP? zos{iLG@H~gUom1DNa570Rv+7?HFn^$9VSw2W%gvHz1TnVLaoDphq+j>=bb#5$&i$m zk0Op^Qo({l6KfHzzF~(&q9Z6v+Un%lnJ}Y0ZPk}La52hZC?#03R0fmM90%Bm?PtrDwbS~an5CXeaBjd6;(o1Lj8dm^&B(AIvtgOYzX$@dff8%9aw z%|}Q-tGYRxD`=Fd#KpJG7-_KkVyWJ|y<*&s-SJLxs#A>ix7B^}ULYS0mxRXpVN7)S zJ)Cyc`b^Zx2yD&f(kDUffzwH6Kj8f2Rw8!0Y&~PWk+6MUpCP7O{hXxm&#mLQ=IvxO z=893Bg_^P%MXq^i#g5118DFJ5I4j2Xm?RC%i;A=TgmY2GuF_jJ!`cvch1Gv7J)a|@ zliQ(>`zye6Tdwzuh#mNi9e=@P*HZXpb$^PmK=@N@6r}il!2{@hbyCS5W}4LyR@E92 z3@<$qK7Z^pI@hy^nCM?gRN~ST>nP(@=(|QLqH_L-ev~PC)Bjg=aCd8XqeW+^-uiu# zYhoD%&CA@V$sp86k3VihW38r4SoD@0A_?yNmM-6zhpT7b50uKocEf-OvDB*SsOy@V zvo=r^s1$TV6U;-lTBQpVR@tpn4;D}yt`TFp>OLLn!Gd<&DNKQDiu(`o5*YjW(XvDo zuZUYTYFKPd_gahWN7T-$yA`ciJ^SM2HC%gxkG>44r!S2C&JGoFc#fR@`CwIGMI`oI zilxFRP(wWG)bxf!r$(|PS|NFV#D`W$?RRw%Z`WTM&6;6!{Cz9NrdY9K~$ta=FX-2L>`aQ>LLcO)CN&UT&Js4O_<`zu8-4om=Tlg~> z&#TPgmrxwz9`hG%r)Q5&qAQZ7zk@b<&{D|ap)}89Ur;nO8dSM>#K(5yk#DV>_nvq> zf6uIqSta1M`+DQG&++cxF&<;qcpXH7xvSQA_Z2*Ip3x4Ay^DdeG(*~lc&7xDz$egb zRbbo}W?L-v+pl4=om{)B8FHrai^FmoH`TC*t+|IM)A(cjOq3KT7D-lV%t;3 zp*WxQrYFv3Rh^Oy`{&^qL&Pm}=)B}t>=rYwyk8v3*L)q4 zu2E^G>LwFM;52%jCdn(HA1l{OC|0+~Ff4 zfATS3_&&bYgwGHII*|iQI9zkeE&oGCWH2D#+d$W%mY|(%O?Z|xNBAoJNO!)>LA70y zB8^^GGiz_z#n6x~d?F7`R-sIUnaYT|c}5k>K$~!{^?(31@rwwB=P>`TI{%WqY#r1K zV}kA@O^r%g?o|JE*H3Q+(jAD3p9FhWDstUCOHjj_AZHp{BzEW`dBx;y^YKp;vyQjg zU^@vlG6@!|uYB@MA94`f3(nq|^u~&?IiLO!Kb&`-Me=EanuolZNi|=Qd#f}y*Kbyc zo%IRoz&EjEuO(l8vwVBrQ5qc|+Vataj?5y5d_Pj0le~xh^=&lM_3Vv1uO*89vU3+x z%fjW0l|nx5Au6_=BBp!Wt4aUK^K+NE=dM(~lPzDK9?wbUJfTP8u(3srG$}DR{+g{6-}l8L!fgFeeZZq%l`zd^Fz>R@e?lj_Py*9tT6rdt-44Iw zB<7F$Xhwv2Tg$huUj0QPb(|*d(5m}mK~X|VH%R+q8oRy4vp&)kJJrg%NJTxGUb$!6 zBFqxymNSR=Qcek^?0I%r!y)gRpE}@vTpB0a6T!5}Jbl@F!$j%O@xx9hw)$TFkNXbS zvt)}h1%nBRM%f)2CDeipgeB*w_?~?s$`?MBio_^w(oee5agUC;VypJ+l3J3+Bo5)y zid<7dO}?F=-MB2h?0jCyKq-P%1GV2YoMe?(p{bmWL!F~3l5xY5s)urDo76IGM!E3x zs~rvJy*NdIOyuuO(O4N~6FPPn$-@Q3J!3AEjG5A|8I9XHVXdV!GoseG=G~ZzVKm(^ z;q^3cPsT#^;$ux%;=JvnBQrvYrMm@jn8X;>r=MzTaT4CJEYD;(C@2cK_i)ZcD{fg{ zk^~JY^v^J2%NX*`yKJ)=DP8!O&2yvO1-T8+smU-o_rwb;^tO2)5~tccryJGoY3v0r zgLG`yyPZM3UrDFmAsM_5P`q`QC{;sPo8VZmRM<(GMncklM){xwf(^B`AMtWujykEa`pP<@1Nr znX%TnHN5gxb*xyJU#i{m7k%dhjzKmdT-n2cb)G$0@^eb#P zv*{v$r6n6tTn~wI$bOcXaFuRC7hN3nw3yk*j_&s!4VUR1w}3*NmbG28!ZjxRX5!~6 zQnDisJP&GlMlw(6OQ}*q^wanSi6+IpHu7&~GG6pW#%475GBSI9Q0RD?#dP1(;1AtU zPk*nAjTwb@am$WfT$fSEB3*{IR=s4!UtH_2O-=}KKW$P@9Qx_9H#4mQ8KS^AKB!3D zhTlM%@F)FqDzO{w=t-6P6-S!2G!qef&p%Oos$j{>J+ABPmbxzuh{o>b6dLV^FDb$f zT#^rty2uDhiz9&aI%@le3(42WV=2W>T*_PPSCF&VNk0(!)#qzP;ol z?MsQb+R<=P7V2R&&VLb)KO%vw<;ee-?T!p|^T@fa+^Oo#-3-)(pOdri(VC|l%=M4$`q%O>+#eBF{YS1i+zbGsnb+#LcW&gb~R^Gy8#FNwZWnZ^f zR{*cutL7BLd8%%fu0*dR6kFC4J|&oFJ1`rQQ4RZC-$~pjtSlaAs$hJ#D-v%s)X$+@ zF!@N8SU2*3nc!^kHd)hz>|)i*(dAhpP6E!NJ*DnSCZAcJ8FE`!s6p($FwTOHH3s`4 zW?JHox7h@PSiQtKW-C?*P<%g0yKVf=Bhb{X>%(>>Jc%GF2whL0y8oi7}hGD z1|ty1Hsal%-i#7QHr4Fnnvppq9(_Uh-XkWSpTU#={uyJ#a z@wc|0W((fe-;!FzTWinn^9QyqD?)fHx=HR^dPA|Razih*16wTjM6dO5KEdl6h|eh} zr0@o#WAhSlDWp-q0ZV>%wr{oVQ$CBqF5m-Rm1Wq z#;yi1g_C8RUN)p6g=rU1f2%vRDQ9|!6|Ignapse#NJ#uRD)%-nYE!AuQCjTj_FK%^ zKK>9?^2eswPQ0ltac{c%+M)kopN1B&CUJs1x_aC*#J@BNaAz6L%55r?vn4tEGdPML=8<*zk4i=3fNs0&%CC?-evqrtho40ve?oi9Aa>> z?gz`q8R0#dt;aYNlIYoKX-hC+f3iigV#c@DxNlW>XpLNYo9~IZy3ZU*s-N4pH3RaV$=VE2vs8|of+&7eG~p$@S+yclvL##Wegylij5laXR(kyY}D zK$di`KIkBr{}Ux*Aeqdgy|aJgo{DJ!z)I?HS3lq_B*Q2{3&r>(t#!FYa9Kb&&Ez(v zdh}N+0%=F65Q~$JIm7IGV$-&=*7zCGbPTimfiCRS(i$RS+xehD#}ZH8hSihfV~&i= z{6R1|gz5VDx__>ovhaH9{rwzR+l>0~7(Whw>3PSXgl_$Ely?$b{%s2qnGJ@ynL05w zF4yF$G>5_;jIAA8sCl4r9-`JXQUrH~a4+Z>%-OZC?fqX+cv}2fP)TfrHX5~8@mO;S zj4HZDqnT;pE!_&@vnv{oYOAri*L{xG^(z(p?Xm4ABYWQ|&D3@r9G^6_IQ61;?^`6f zm%!&nT$+&m{wggGz7la~thk`W%u?vS4^*b*6|ohmBJGpho;jJpyw z$o*`c2p#<;7fUwY4EtT$Xjt^rtCALx4u6*Gxh|KAf}m(zs-8~fv=r0Fgy{RQWJL@O z_KzbOknLZwhZSYl|2%q+typT|jyG^*3DHiR3@*22DEy|dPOn%{ATNdLF+CqsRw!Jp zExnbI;o9^q;kh$9KANfFcv5XQI{7HIB{O3XC$ckkscc{*C31_+7DP=(maBGOd?it4 z#$Yz>qVr&v>?W>0OwT<^tRS;jOwD1ZR~K#fP&a2v;uBWt^hu&H z%LJSCP#Ptvjv?VF!dSE>dx+){up~<}WLgiUszfe=loJqjKLY-y9xJv9-ko3}1*=op z`>b+hGK$4_93&rKcIOQr3^`VNE6?&7ciGnW(t7om75Jvv}JI}`ZVmJlYjNCUD zMU`NeX8TAbKiWVzqFkfeB|~=G>ap1Fppn_gzT;I~qgeZv=<*x#EAg|jECD17J;L+K z9QBh(7~N4#FMssg=a9TXg4E?m@no?)1#=9Fk|hnrbI$uo%;zwBpW}wmm^~S5vyJC+ z3Gdo4Lk@(?){E#*pH5SwhUg*@dcT%F9jbuB;DOT>(bwVkt{VY#s+rFF z<@NBbEWgp*!k7fhS2vq}y^equs_mV$U8=Lie)>oF$p*p3cJ9jBDZU(Di;lU8)=Ene zmEw{@63^Z9DBr=Fqzcbm1Qj8PpP7asU^@H`qZK3LZl3_6M@rw&GIJK~)fq#|DRY!P z{Gpp5gQelYd#CQ2I!?}b5=$)WR3{l7wzf~Eu#Y?c)um=gE@bjBEsK}wA;Gq46)|>k z+pEXGivicq){xuI5sHJdXS*pKnSh`|DOXr@k~{de6u!JTVL6Z2F0o~J|IJ=@KS8wp zlJd2!^7g58tap2c$yzBWOB zoO3mtnWA2mIXA?A7vGoWq(a~%mpobF3*tD-p=`cPR`1QIop8y++1n|f;A0{`5~$jG zZ>7(pJrW+?#Y0}koFs#lsv-Ku>uip3qH3KPrJ(ZLoiKR`v_61X>C>P=xwvwBkfUQu zBu8kG+*Xw^x|5{H!ghjV94=F*rltW4B`>y(i68OWtN_RIMS@9eFoh0UYUaVh(yp;t z+6s18jX{%=p0xxh+ujR{G!zvaM&K>JV|@AeUh%AnMJs-C7c?Hm1+CCentZjledv%y zp$vP+rL`Y1EP;Ug{!z>)Cfaa|&C`#EX|M~osCayfR>K1$6(0>=(X}Zn#p+|^yfDeg zaQ!R(SyITY*c-7GvSw-B0QrUj9qD}sYslt z@?_O(9AplByP`r2d-+*@_^0lkcqooiJmONzkn3wE(e9dOsrEY6SK=lc_q(hUN=7j* z?~B3_I28p-bUkkj7hHU|B?(z(J{0fCx=D84^`a z^@Gm{J&=f*S6&g`ASGZCZWj|1=xV;ac*auF|vd5ANJEXK2ih7+J>rKh(H$sQyqB`VRI2VzWHi~b)% z9VL)a4kyl*?*b{)-yj0<2Bwx98oTZkryaZ*FFIUU`rB$^PgiqYbT9^gRBHR-zWomUvM<6 zFQVN=cZh@Ndt}M5Pp%+SbTra1PriDyNUw1kSW?OlfEdvromDoE&GX_|iSDZjhi2T3Ck)@ud{_e(mQ3Itgv@XKk*lcY-bq?MGg zn~WzkI{BW;4IZklKstFHD3|T}9%?|jJLm?z05^3W5ThXB+lc{bRIdj)7Z8NC`VC^u z*wkEolkdAUdlb-N0@fc&O#zkx8ZgX6h;EVY7}U@S0coLQR`$#bn&;lTxPe?n21}CJ za~1b9Md7P|L^DUhZ3)+lKbLNVeG-zr2{#!WK0O9GE`U};_ ze@BvmDH`Nmlv0n3bV12$s~7yF%x?h0<=P!^Y3*j8Y|y@^$n^j9*t*hGq)4jxl~M@p zO}lD~d*Q#daJ(~dj>8qXuef245%U~14>X=M4LnbdVseA za-)NW-(97!2%LTRsk z*5axAAXD{Vf{0C57=-y^hQKc5xr&uYZ;~Xi3v|nY!`PVrFNhC$14stMQ~!DPLY2Bo zBjoVfy9|$=s!l1U zZqrrBV@G;w{&_hA$~Wq2zT4AIB+NpIh@Rlms*P0yP$c*U4HN5thlXV;4x3`!_baHx zQ_gyC+~K$gvhYnUvT?XQ0EZk#d9gc|WAI|?P0Y(OKl`IFLMR{F4nRIOfD6cW1hB0n zZ?TA*%Xhd9PE#%IT1c+|M7vuJct_I%9<9$D!y%dX{wg{809m_!&SUMbF#-2kS%PMQ z<>-GjW$Z;aH-!Mw2C=S|B-f9C!P1cW=L6)!O~54UTNeEhz+hN+ZCCF=|g&*(siK>bLqBzAh@Yq5PQ<0G4JAMPAhivVx za7%6R)=C)+VOFcqBTVi@NJpT?LG-V61S{C(sJWxYE6%W8r01Zj;Fc@o+XetDE5$FD zq?kunKXy4Sp@=+_6VD_iNI_kQufZRrmGGMf9_E0Il_e7qk%kv(JSZH9ndbOEiuW|Vf@A`$D15Otuwl(X~Q2{kIwF}u!j&%GZWk^a-D^>hj zUe1|u6ls-|y}ScXe&7$eh8&7~6N$AfS&pJ70UF6M1%w8wuD0ev|3h3GFSL6)+q0n< z+^8_zGDP$;E8*DF+}GTaIaj+bk}y^cJ<_tuS3+NO1MqBfRHhNMphX=ZPn|RXMZLEa zsUM}b@n!!G4+JQ^hKUFtzvIP&iDep4%%dJ_38;%7Jra^?1pH!bH(KH(Z{}a@dey4h zW)6;_QWb$R3v6l ztpT!^&!8WXn`_-@YLpaEA#0K_P3WD*(>?=KN@M`Lu*WB~0`=V#qCPBI2YFHO) z90bx?jMZ?b0w8tlE@ySSs-qzhW4huoIh#S-1weuU5cw9D9;Zv&A@Jf-9HSzd4C=sR z%S;O0ngzw+d`kPj58$dx0CkMA3(QsHbLUMmM?&MrMyg&UIt|5$jq7xAfGABkpael| z5))0~O`O*=aS}%(y?ze4j4(bxjJ4o1+f!mTV$VeA0rFK@p*=T-gV|(us6HbX!k4`k z0NCnV;2U3ABQX!K0DUXk3nsj>cM57!9Pgx1MW2RZo}GUo9Sx;DKo`AenXFkvoFu#bC9%AbXA@ZgT8AazT4KBT zIgbCIE6%X>(d#)oyyB@B*pMM^RDPCgsoH0xxnGoqpGRI$AwHUe;b3s<+A2sAt%7Yz zGDDgW9k-ZvW;Oz+_?NX#2OE<2mj+5lglJs(dBi8Iq@xiDA}$b!A_+U$a*;f^y-!~| z9=W454HSqF`^tBH9#d~msGObAvsv3!OxgB%ZKrF?ogfS(mJM6&09y%`fnn?v`i$}W z)kS{q90|g{jD1NCxLv}dgluozfw4*lxEA#GAdp8+RhNXJ<42av;{T2ck8=DEWuT#J^#&hz{KP3kCe8B7B?{TwNS#{irR9_z^TJ!$ZtAR%UO5$#%4QNTNn1Z&_iqeT-i+zf&dyUo0^f4kANar2VQ~S;oltcDjejECg{}g-qn@lN2Q{t;A z7@GHx^aaabkrwA?mjfcKUbhy%>$k0#1TC`Vt0J61Q;n%*01{Y+Uw)3eYfJ%s>sB&{ zoEieEynFRSD3g+CJTrufEPpT9o)W)3daU2mj^5u--!{7BR@+tpy}dqN$+w-&cD^TK zhJnk5tou(tZVGau47{$&m{G~KV?Ew!Hq+uwBVu3UO)qWxc@@ypou=n}M8T5xuG1El z)+4_%{^$Y`F`T^PF4E|-^f+SfM24Jga``9-P$o8FA|Gd?CMrkey`4{;-MzwPruX;W z=ARuh{eCD-R)hu2r8%0(2-&uN5=cQuxVfM|a%(El5D|SThQ!-lG+dLPySq_re&lM{V>>>aj4V0s>%dLsD z_uH@|@!C5#`)4v)%^s1&-xEJb%D6dz+%BhtE8eM$vx}P`nV*OFTgTVThe$VWy~0#G zEZ4scZVZ3ij@?+hqcx3QiDOI>?qoiEY-r%rg={ERF>BFAH_D_E50CWoj;8h zUyri#oALaj2FX`T2i2W5#)*i#%qzXyam;1>YCCryfaaFNj@ZIMMR82Wq{DG&G`dtd z-A@F)Hg{CzAF3CZGNTgxjVgC_VJksp?S< zF4|vIGZ7VcGM@Cqwa&!QTrD&7;%>RkYCiyD_sCh~NAyUT*+K&Qx=u&GrksMl!X&VA ztLuJc6q##X@x|(m@(561SXC5Z%yaVL4kEh|VcuFh{rbdm)&6|AR`-@fz6?%yW0W@2 z-ObJR!%77{!v)++PKUT-TPla5P$40VoLaBJnYn-!DrmOH#LTHvmHc3AJQqhP#gz4T zM~UCk3C|+HPVVdLvUrlxIfy(4$<#mpYj6D}GzskeRZmVQC{4-mXbAnE|Fq519DK<4 z$%=S*$?wsA^GZ-EX$a>tLgT`DDi(*B$CnqfWZe^JkRp0`=-6mJ9*wZU|B?R5>!QOe z=7@b2Y<PvB*`!cJM~A?sQfCI)=fZ45B&}V3KuPK1P74LlriiCQ z&7+dro|c^$#@0C{^YuK~Ty|V;P?OUX>@$WZ=Z&I-BXMzfc;X`Gj_I}vWc0VmVd#2@ zLCI+vEx*=6y4~qVz>b*N19Q*$cYRZ@iXd5Ff{Io;UZ5PQ6&?HD^H&%Z&!2856 zenARFY|G*&Fsps;J+yDdQuaUfvV&m z;|ZE#_0yl8KRz6$&MHb&ijz&);Si#w^n#(j@aZY{aN0Qt@H5#%0*E`jx+chXY_{wjIiVg6F| z0WA4CabCKg95@2&SMEb@enfAFhqOKsTkD9VXQjEmMrl>SY2_bvQ78%%HVH8%spt~q zbzCR~*v)e{GXS2SwI?^}`zjbe0JC0cYQ&~ykM_VyE22v@f#f3}J8JAZHeaVEnML(m zHa-v6`!d3SBL>Q?_?FAez2}fP`7&DrEBRis1Sy#}i1y*apQ%>96mjH&3Tz9n2OIgZ4(*I!BQmc7%Y$>roq({kZ zpf6J&2T;W_=F^|--rg~QKNW7_X8iBXwAnz8?>=aHd?&lFI{s_e^o?(u$$a1bq%11h zU_dn^|B{A&dA>paZt`@74dpJ|+iZ8%Uce{OsHuP_J^#`LK{W`dd~8919R0^PW9kx} z8o;jjEK{h91{krY+sp$RW%pIkSU(epxMAV&h|zLHSX^2cW?oZ_ z)1~0%|7-#R*QCul*@)l3n^ps&{3n8XEZT=VSt?qy49~+bXBGZ-@VKdK^2toT054J2 zcNztPji8GQh;qIOT!(Sj*1QvR3j){@uys8OPjPV!xdRZF=8NV#_{~3;|A76(hpkbU z6+B^ZNK#q!KYC@|QcwLF$~T(2!C|p?kJ9 z3M`_V8`s^|wmTJG%>sx|3izJTL8 zKN^PadI%Qv)lR_#!5O*MgK}B}DN63&?maLz?9~BcWFHJ}k{Lryv*9z~Y1vMM6Ga8D zDXbjqAKT^Bxb3D1|DgH9=t3uu__I|44)+C~f?snH;lP&Phk;j0-Y1{LBClyyFsB)l zW_vz2V+#R#<+NBX&z}q#-y81-?u2IuBL}!3sgfCm`A7&B+rcaF4hyzgt5XeNJhnT{ zwUzv*#*`~)hp(IXvA7h$kCY~s=WZVh&oLszwTHK*gua&K>gr)gH*bXxp^bhWFfKrMEg=juHAk`;j02UBXLc!|guqUt^O-a|n2br?WrtKEZWV_Neghy}KX) z{VtO)$jC58|5f9@VbS0aad|zI_z@$cQ1BP&C-Bx#;(iD2`Db1q|NTIiL;OWu#`KrY zTX}`xFOVEozBNyzdu;rI$0Cgfqg+t{FXI`FYIeg;=N#O+y;{T8@)a%J2XaCk%$8y> z;=z7I`)vrS!GIouLeSL^%zuDPLgMdf`)a4A66R=m{|AeK&_r%15IreK(r}se`T+gb zURdkZO#*=zx2Fc<0^phj;O5TIf)i$yvOQ8Ku(6sf1|GpIifXJx`^A5FEKKY@J{Wnc zjGUBr$2QMslMn?D%{x8Ip8w|mF3rAFA+xjroOLVw>h59C%~rca9vXQBn5DJ=TZ5W( zl>y{U4sSmYz<>`x-}KWasnA77kh4+q9>EqW_0g-QxCPT?Rrq0rAIPb7;2)HU6X!ru zy7V3Gd1Z>`*9u7zozH6nA=0(thf^?cJ)9svc<<5r48u#JQz{U9by`rlj@F9EQ0x+I ze6`@CY&vt~Fzs#u9}8lqEwT^AAM&(;yyE`#gE;N@gHN>BlmlZ$n^_3%Z#w|70Sevi z+sFUJr`T;`k4ev+a==HlhW_!pg*f+J!6zP5%8g?A&Z)F{$^fzB>{uyq;FniyDZ;-K z2lOO1IX@|oQLGid2{l6tRw#HprF2_RfCEk#Gl2bq9yOX)SoMgHWiB9uC(b<|Mrlwm zn7~&b1_U0F0k*pc$bV5K@`PPhIh1EBl(UdhfXNEOziO2DS)ch;I~qX!^84c{eC&{y zNedRCVPK5z2eeBmzMw|rCwPEoHa-JJ&rczs^qG9_p9y7qj)P@xq0(<)4 ze|{-xu<5*f^^WC3V)PKGU^ao4v-bF9Y{j!MQjYS=u^eVXv*jum-O4lh83l5h zDr#W#UMKVda~9vu&rjObOTOLL@yyLydD%BheNFqfasI|AxUtisxl=OL8?egHfO_=C z7iXM3AI-&4V9PM)tW=SWexAWzf=Bu)aAi@bl3~HbfrF`p<^KA$L27hXZA=Q4WF%>D zOn%`jo*{TcgN&Jvl$PNMD7X@%iU!dCPTV~>y$mhK0i#sSd#xY@j<>jCP7Lv5!5=vRg8Rb--#sU^lpI;i--nM zQ!P1Tk?ic$N9>(-g%x>m5U-7Ya>A=yIWhCGKbLHMed@0RO_49gDP9Y|yR~`S@Jh+A zMIFB@C2do!>{W7WD8IN39-jP7bfdV93}XKZZEItq z>(gb?#KMEx&Kx2bcYZdI16NcC7KF)o$hlR$CR;DrFp;#D;I}lHRXS(49Z(6?VRB%t zk~ve*?Cuf|MglohTO)kyd$7@m?K#?}(flL+@Y=6Jz)U;Ot`iKlrcCT#=mZ?H(q%np z$>O79mpL@ldV`VcbF%R>Z_MCQzp~G%-CI`;KSgLIjGyV_VA?B*S_gq8n|^p8)e1Kg zm%aK_AgWv;RO(WUC@l!*~8uYd`_5(Fko5|HwZkIyJ_XHnC3kocjsvGasrZ# z%2Iw+O0k&OjBquvaz0EleiAF*w<6w0FhbkU#0lDh6m1AdmmPibt{4PkHIp7J6*a5L zf1MonlQ5sJxo5!U( z=eApsybI;IBNu8AzNbjUJs#j33>|2n7sY&qp-#j4I^4*w?1h7qu+sW!;09&##W@gd zO};!GpP7b4uQi#8A(5f-ECygY_pz4oVYj#K>mTF&ps=a9#AEis-O9+i&oh7uaoJ<5 zju{Qw7qa+kJ}OHtgDI|k@`=H7KL(b{dd%8BvDfP<^ zYn)5BjFb`rlh4Vnrgdx2-{*VwAcf4d`bPe1*|9^2qhe37gac}QNC z%N4Rp4@Dcy^MVoi-N4}ZAd`V^arxOVXVte2OfT>=)G%Jg;!sUXHVus?5d6oums12z z#b)a$5_4emntq43QQTm~_2&CZJ#$8!*fNS54lHkjwgCK<$4D%&knSD9lA3VANuU6z zU^~HrtBpCpS;o=2f&K3g1hSE)nm zjE{=dV}XS~dh)`+xgQK1jNt1!_3`u*W{6DazaJK2c>63gZ#Y0kdw=~MW()F?(ZU-- zrQxrC2L7xxpZ>VB)YTkAUiOgYIVEh$Nlq-=L8L#nE*{PXRy?LKbhg!nv|pz)YsKSv z!?*KWK((kpwuiFZY*E7KyN6HBAA8lEHC8xtf`;5r?}Hm!oX`H~KbGyNNkPmL57ALm z%Hv`vBz%u%P{gme{s)okD$(~Q7|PAX0`4^6#{3>;3R%a0n14%ZiS-dc{r8T z#Xdl_kgH9@!CI^dOS4C@Bu_uT!glolE zfFOoOANHY0o)#w>fu?`tWvWxJfYZ#8Zcc$1ld-RK;V$jhX+NvhHf^p?wER}6|4!lD zG7{S%FEDDfq+fjVgh!la-bv!r6lOFcRcQ;quInx3yi<_cLD4Y5y}64Q2kR|V)C_{X zj76QnKP=m<9nbHFjGh9Q@LKqgh+!vr*&%7=A87bLmR61Omhl>}#G(_`-K|kSm_gh_ zzkPf|mfjwnzlBp)!BMP9K@hyh{dkCc1kJ~>D<7JjA?%P(XJT_7ZSezRl?&7>f-NAg zNj{ak9~PD?A6Z`RN?nP!h4Lge2Bx@>;@T^p-(@g14t~KwcpHQC)6<;Lk*d$TFj;2s zLV)XXRhddIpfF%B;WH~yGSO&Vx0j+dW*{6D?u&v+`GDwi(){0zOu85O>KWrRU>@Zi z1?7O95N0R5yg2VWm(;K)VK6-<#1}9TruE0P6iW>JjV#ZaEGPLwMf5DGZWkq~2Ms|% zx0{fKZn)sqG_07&xY$soUpee-a-IXS?1nlN(-DyRgT@VGv@*4?1rv-!$8C_6^va;f z8sj&K5IG3iaA~$b#&AE%L_-6tRcu$dD+l8NxV@JzrbVR^mD(^!miC&$FD@3h!xi^A zm1cdp6370iGxsi3?@Vm>@1tCzDIyvMQY7_^(XY~H3(aV6tdtk!Bop^!`iH;TNC|X1 ze(&(J^gJdro9F8!Y1Uu*_kr&0PYg7$L{Ev6@0P__4BckI{&{cq_42+ldg|?vL$5rm z;W9@P)JGxGc*Tw$zC`rk#-ss`Cw^1;K`Q}ICmjkI?X?lc18OUV8%C0cA`2hnIylOV zbT;d40ySElU)gniDS2?zD>Yh(hIv0$$xcsc!X9{ZW7>CEab}8&eGr>(ZnQEEjPX%D zkBbms-IdM|B{nyosmg=Wd?RtYz+X9=6e#@gjA$)=+?_9rrCqNaA)S>SR$t>?kF;lp z+zr&FO>q6<{ITGg=}op}$x!`SK4$%lK-maqx60e^!;1AJpp+2|q7{Z&VJNqr6(Z~Q zI1SD#H=Z6zTUC3vo?#|)AR8U5Jz(~SzZCT}V5IM!{)objw|e}hA{DN183U(285a&W zHz68Hj1;Q~p2e~WhwpGc?CpURi1YHoGB2H6F(nA>FqE71`KryTYSJU+sQ**g5(H4Um4m=vs-83;d~ z02+Rg$dl@t4ChzV{R)pyPL^pXlVdiBWA?EDFR#h8Sj^uYt0nslb_)!nFS_ zynh~Sd9zj6KgmHE{q%ZKh9&$j6`8UU>TW}qkr$5oDE0%IVb35(D#0v$+?RB+^FB;0 zmVgEtyvft5cx}|`@Gj{+BLRX)MdLP%QAlnn+jZ}`upxJQ+6s3T=2$^@qLC(Rm=#{g zN%BLc1R9IKLq>gwJSi=agQv#`k}^v>=9F;(D(4|#-uNp#IKn8AZlgB|1Nm=FoX{4q zQjLrj33f*ElD%yeC>PjG9z6R0la5#!ln>FdPL9Hjqwm@O_;hAO+}ccv7&Oa;iXR|M zZF=z$r8M0Op8b-E)8^L#GjVHa8u1kSSNpN8)2hv4{?<j-~clSsNzO$wip}wv2=k=E(As(Wom5 zhj#po5l0mjt4!zS4(1N?9}4eerT_Zh=W#=QZ-sPJ4ogaG`LOJ=ds?)Gf7Z|1FnIJVl46xTbv(M81OZbi-j80A*e>dK@gF>5lgwLqr*gb-> zkeJ|zJP|=f=hH%CVB6^MA_EXYKumW#q`_$9yG9wg8)98$6! zF%^%bc2p8lU(;tUbNFLIMmp8@g^L0E=j}SOHt1;m2wc6Ha%-gdJS24r(HB%DJX+ZT zq+jG#k1!|8N3(f6Hh|XIQSok4Y!)h8x`UQ{!XfTrG>FO`asK?&YKs^U4xeA$MWbG{ zfz&VN>Z7Synpo&D*OBQL3TLcTpv966RtO4UDb@vp1JHLt29;H8r_Yc#+_NxdorEl$ z8dLP04k_(5H*J3vo{3|4uqM}}XGOu-zOJ50xJWP$t4-U0$q1_!*GzX=V8{oQ!^^^& zq!MF=`%_mE56C#nppiOK(tq+7T$Z3}Ie)CSHGtu~VeW1os-e$|Wf7F5E3CT5((8fs za!C_z9qMz5IaXnz9#Kh}nC8eHrd=?s9TFyNmph>*L4Xm&b1v_^XA!>6jjsp1O_Q&+V#-|}5zV!VIpdpT2Uz;qr^H9s>I8+W42oSc?`wmr zh^8xyGc)wSl)Egq*4LB*E(pKjxf9@yFSVjQd%Et31Es$MW2U9uYMcO9-u6O~6bRoi z;0?QjsZCS4-76o&nCyDs;q(uU+z*n*ABVo^YG<8--M+;5vhy0Kbr4QVi!JrKEeS0d z;t$K*ku{?h+k!E=mA=E#@dEq!Kho6UWZHT0%E9ldt||B}@9L9kD8H@vnhhn|F)IG7 zW@mn+q)|{Jl5fXsa11B^!y|Ovme#QuL(*IYx(u1wjp*pdv&u@_3oMCkGlEpH_Dxus z5*?$cgs0v$MWoT9|EFAeI1rE2N*3!oFZC2s@ZG_A+Jxr3mq;>Fm4=EKRf(4}H*MP? z?hrHXkg$@Q+t+V-PxL9p?txJs*6A3=c5hv zb`yjrm}le|6C4}%kxSN6{xWivYESw>JMGA?8r{;4>pQ%UiODGY9=)`=psgW!ojo_P zPy&CMwUOVxjK=g*)}kc=X14_lX<*uy(r6N0*8q}8L6o*EBfg-h1QHR&!vc>~)ZT`C zUEyi7Xur#PVsr!1YdU(-nN0Dww zGTXtAWWxOP=x03tJI$}A6T33x)=aw-KAWF;YngcoF2w6~^uISPrWfcEZGz0DD(f~a zyI5+9@Yu)g;}%LQaUCR$^X^Z5*&OsRKz zejI=$tVnz(f3(1T+rIZ5pKf+VEqgTd5gAB5xaBbv~2NiHuu${Ga| zvl$D{5IizOKui#5g)wl%^9vwf;Bk{%ZAHV!8(eiGyxemmdqlpAIMJGBse_&i-T_$R zV~ZM7r6eusSf@{h<$r%|`tHbI!88ZW_fP^!WrcQ;$wzx3(MBBcM5R%|^0M{lav!)N z4W)z?#J$weU!uiY!{*p;^MlZ`;CT(+l?~RLO#2whg<|{dZ?QGU6Mju?LM62eGl8RB zuxX*fPRE8*S^DBEwDBg_w9f#8N}l1I1%u9vp$^UyXbbyy9Q~?$#NHPQWV~Y7#7&{0 z(!_s7V3P-Z(GS$0`GOXCV~**Qc6`Na<-{2b^iI@1-zafArA6iJj4lyp;P`3zlJz#k zKl@b-v!+5mm&Oit5K-58PhUrV;HZK-q+R-ACF&9F-E*xJ!#LEGKua2;qfo?Vg^&%$ zGdQncrJ%S2InF^Fc{x3!j>LD}MpwpnYl-wtMA+*&fV)D|kmA4pjh9FPWMVBTRb@=9 zsp4G|qn2cDE&W@O7C0R(pzsC5+lMo6#o|Yef*6>Rh}fCL4;bkq)@sPR?JW>b4|AtB zsFs{9>wy|r=vxS1F|DauTsXHPUwy#k-i;z3N7pCGzbzs(=%k$jb5`Y4E9@Wy#XY+U zefy=QS@;9;^|%maRUgjfc@_T$Nvo1|<6%0tJtjOxeOcw|7M$A#tY*fx5UZC>@SS@y zr5G;^l&kDmd7R+BZK$gy3bDXfkrBtDLH#@igxUM|mDiW@7LI8W*=>+bqDaP*UyeYi zO_6lzNUThpYN$n3JP*5r5iR}gRjC10OxJNXw1aZJ>Idl7S)COgT znz4M9A)aqjyG_gVlsKAl?J$1J>%}3&5Il{CBhcP!7?_StTWej#6q*X|;x{9;*3WzLD>$-a@FlH-tK~CL<$P^hzt%VMfXfNRup)mv65SO>+%^ngkdd zuYPR`VIFUbiGmF#!@;~Ii4g8=0u~EO-xsTYAP!qbM2V-J4eu6CBxkHybWqM4Bb?qZ~D)fuB-<873bi7@qsl z85~8PgD+w;c+NMKQ)~_UV~mhTxxwS!w)B5kK%&k+d=#Ut;Qq?#s~x^4DJ}i)M)y4} zQQn~=afXp9`Lo|V$cxftK_BvykK99aij~QpOL0f;4~*yDt!!J_(wYsL>*J~~6h3miuv%oJ)UEBVN&XboVdudR-Q}zB;IRj?k?Sdphs2-X zOT%AG1TB}gGozn+NFQq-a(7qmWWUE-{4rIK2sh(}k=`a2;yEk%=8v?;^uE^e7;EX0 zCtAm$qk^D8k(I>}k)?J(QEKfU9|epFzn?f*o$+3Y!U9hi__nx01r1Ls#V){iH+PvuHib)>WcZTo0GUe{OoEoYj2K;~qtBO=UU! z8;cX5jz%_Xgu}&@pHb%OV6p1$2hAFU$Ntxso~IPBB-e^zPAU$Yex)nM8>#voZBK>y zO{mXCJQ+Q{N;ZpDg%X}}j$N4WdSLz-Tp2ywK`FoqQmrVX2eZF?CjSdnviDKA#C=Q7 zGr^^P+|NKRK8$=7Y)Y!3Lw!{Y+SI>Nz_Tt1$B&?^o5N)$3QIH~ zeM|C18^+O?6{nIY@}eexl7{#om@KzLD|@sv;}+NGs|+d?!>XMj!f&9IXv0fGD19hr z*J4+Yp^Jx?y)etu-YSv-i>zCZ%bi-WbGh;*iE6CF2!Yu5v72K`axOBg#oZ(t>QwL! zox<;ZckpMzh``1TlY=DJipvcOlTK~ z%{^NA@ILN;tE}CXG@OR>K7)qMItz#vSi?Db^Oq`jPyPH8!5XKXTt`^dL$u9>gkr=( zGauta8(T~)H0TVX2btub~K3MDd?z96C0eQ*(dLj z1=fvZ8DJlDT}S6}U=>xu-6xW5ImmB1Sj!s6Ak_TH9Za_Y3s$lwMm?KjCn5Z53d3Tj zpEM)mFvX2VoR2;i^2opMFZgjw8jBKi1B;mP<*oI_YJY2hg4-*PHA+t@6Q>#8MP6)v z52M%I0eb+PePLe2@653q@7h_M@FZm7iZ^e3LIf`twUddNuNG6{X_G^}|0^$}1_y1Q z!|KaT@>#o=1=wQjd3F znlv4E(J`NwDFD^KF^ulkv~&ikj+C%+oS6e6)|dmhn`bwJIg7;#>9_-G1U6~q2|Th` z0`k72PZETHW-LutmWt_bM;lgDkJs_(Vdm3H4zpAQHIt4&30TQz>MQvF_&NX3Nt94L z14%~GM`{g`DXRaT>8!l4Jg-ekjUnyRJoq1i>R#+q#iwWS<&_a^Qmi6CL_~xX`tIBw z0nJ^I2P$=Q@6RyI^;?$7**{VJ^;x-&+GJUDn>hKx=qlbqIa_u@50ou8MV$arT+YXo zxbGNTi5EdV(iUaoiU0b9nCAVrmi5V{H>vX0p3U)suGl%^bBP}|R@C@8^cM*5Unhiq z3OB|UOcStyNHo35brnwtJ!`=6n{_^fB!v3E6vdu2=J-mk!GG1Y-@nNdJ4$@n@y_AY zt_ao->&lYEiJWPsfh~ji`J@zN?r~*Nkt!YuYdZp$@j}mW;1iH zJT8b&g8P5B`js!_Ai-A(rz@X9&S17|oV@71k(l%!Xt;W2gNz+U69n6J%#y7BwDZNy~``ht2wu|9$c?#?ct-|Sm5 zr1fN_c%%ay*MEXMR%B)3K#ee+RT(rUb?zZwE~YcFad1#ea*0UjLMLQ`ktzUCl_1J7 z0S`kpz^l+JmqSn03b>3;>;!=UR4SkJ2dFHQ7w8I7<+cc10Ob4IW?)}U6ANclD4;}5Z_g?E-*Sgm0 zbv>n<_Ora2O|Z5dxk{#tPZ~;`s(i)6aaJUzkn-ODLNN>+)o{t=!nZf+hcq@-(&Thc zex#jdctG?@YZQvw_w|IN_w#JH2lvKOrSsFu?cH+6YKp1h-I=?#troj5U7Wt5K>u_X z3`DPrNp}EzLOmChio4>0rpJg%pU9cphVcq4Pt**>ziL4Z@w@l|PG4g3z4%W!x*7vZuqrL{IhukEt=v%$^W zDb$>8ei8+{X&g__m~bR3aBO{_kaX+ErNRQqF3mK8K~>+pTHeOjSt;hYVNub;s&zl# z!?Q}&9QIwW2sjM-JM)&La=Va|++YO3Ipg=CiZOJ&FHc#+o0w(kDYL0^fhiCAWA&g} zrw3Q6Kn&1WJRdSq@$2+Rb-Ni?RW>JOPH7EnokB=Z1TcCnOScD{8g2MZv?zWYMZMn!56t`O5MKuQQNqMXLJRL3< zN?PQz9M4O?d^lMUKTF$U`JUe~rIVl24=B>)q)hS0IOM3pudg%OkMy^E|0ccT`S&9! z6vvb&wXu{4l>~Z+t6#ZjPhH6OynkcARM$TYKLR_czyC`X)nAT1TI3P_&4y~1t`u+i zTiIVlo)Lp0aoVj3~1h<=^wk-g{=; z*7K%#yQDCyN1gW&L33o{ex?V0hAi1E&OYd4Ga9cI#xD7H2ml4Up|uVnq{AyeWv5 ziOW;u4iA9m&?( zuBG0V4Qc5yYEUn}#b}L}`NS<{&%`a(jRR;cASb?JdchGkwk-;^X$6KGox8!Kdu{+y zpU3i;XZR|!2{hnY+@9T;?CUw{$`2|7Xa1|+8ueKtH*Hb|4`MxSgMFl20^K7@6QdJw z0IMqto4jZBT^b?W6!LpQix#hkab9MGY!qk;$RC3lYQ+Z80Q^g9G?V*)wpcOIIyNKz zSp>%#sX^x&hG_-vHt1x`bVHfB&SVa+?GSW-2TzvB93kWhx!9)S@k8`%A<4o zwC8MyQyQt1{XZUjvaq)-v#LGEUruuvVbWP+Vq!-^yD{8^H9HO5%QY-Lx|kGG`N}XB zByR?!WKIH&N2TXn=0%NEU8kVs7u{#zYa#$MApBdRa4K~#YS!gO&X!tNkSQtqI|H}3 z=8B!L@U6}~WQ|*2@?=nT+tEY+k2AabbN3AUI`x=SESD3Mw)*R-!T7|IS*=EtGQ;d2@RM%nJq0N)8*7j>{9yAMZzW! zy0XV>%ijQuynU1&`q6XfUh;e*ETQH4dHacoT0Z8ZJ9eMw!K@k4RI#L#44;>)Uocv; zww{b*DCCLfRp*?!9|8?H-_y>bI3dAM}h{r5=*P;qi=ncrrMw81~eFE*G^$I+v4tC!%!4Mugq5 z=uMSE1U%2pOLCIV*}^GvxsX+TR?CFngGNc#yr>&?%{#xUIw9JOCo?~SCM_b3oS^M9 zTMiN3`4+YQ*r}6nt>mcs-uN+u>1I)4_G_sxZ|&|`*poKm%@V+pD!R^V=hl{onoAqO zI1>K-tad*9H6)jBxp_R!zxyVRicfi#2TnaFHuKr_^uI-Z7UN87Fgf9$G$@RZEh-P}nc@v=47%*H4o_UWcQ8 zoEtLVF!OhotFfqd>nd)9{89j&B|R;>BC}{r6Uj%66E>b+0T1I% z=2sl(`wWocb3rVEJ>vJuDLvx#e?FN9xwTBiq*Y)M?3)g z%r$UkTt^l~^`>qq2H=b{`6bG~y8|Xo9W0c};?f+Se_<6+tMo~ivHa)32Ra6G^rgY` zEv>VZf$ZFS8_Qq>5Rd6kHJ+P;D)NI<;}L$kWU^7@K(NTqkY|>zFdRD6p5#-^ak!?yFi35-*l)VdfxO^DP3uIXd1VfTi1l#d89FT?ok&3ea2X*yErx2V#$u zHioU7|NIJZ@BSa;bN6~o?k5y9y`q+ExpciU(SHhZ3H5dAPnM`WQ(3+2ZJX5@Ee^&+xX{1(;*GjmPaNW z20bfaZlR!cl}3iPttXIO8lgdbTko4xY`ohh?L*yS#95+k?0*-aq2@G38fn2Od+YM0 z_H^?pna_2{j%dd`@hN5fv?#R3?2J3c_$OII#m%H!5<+ch!9pUd$V^%A%+MV7XB$JPxu$Gx@uE|Y7tIWEzfYqZJ^4?r&o z#?TX!X_wQ~;_PG%z9ncaY&gVpbfoH$T>A!Y+t+Yam=sN8orje~xS1|0a>>{_A^s?I z;BP^170ha0yq8E2Y2Gl0_h#-7B-0s@(4G{EXh>Dk%R2R%gaeqq8flF$A*9sYg#+KY!}6|kN&$ED z19FT?An_1qk{CWSVi|c0xiIErEQK($!~l>qjaK1R_Bc;Y>zGf3QY22wtn`B!1d&$hTM^Mr={D3-(Rfvg4a~YroDtqn)k5p1s4o!5A_4V>L9rBq^oE7q&3(%go!cF~2pkdpq4COOAK47L=j-Krj&nPZr>xP@a z!XAL|Hrc66!hcPe7V?&kb6|~cM(d0;7m!k?hpXXXszM^BD6)T)EJ_|~uxNti0wk+2DXS!Q>IRiKjZK@Qky zz~<5=yqGs zi!K7(#ZzKcPPC~+!b-qJLod6gSxvthJSp)S^g38h4(S#;-lW`lg(cBYwcm}e$aSx? zhzZFB%(1T!UKf`dt)n!{{M6?Cgs5k}?0<3B$3F;!ND^T=oGDkE^SZtvOSHxdM!O4W zwK3pecGgc)Ujau1*etVK=4phX1>=EmYLKoOnl2dLkmH?>?g)53q`5>%RZx!Dp&S?` z#HjUehMWD>A`b$Ek>=QA_Jhnwe~*IXupp}7)Hq^`gmESkT}W}FULV#jEuJ?-&b6jR zI%y`J_6KsOWR8VOVxNK~cc!@FA-{dewMq*yI{&!EEH3x^V!CwxNVKpd^>6Qrc7ItN zYb-YTyi+Rfz(J*)zxT1mXm^Q}zwy=Ldi71*IDR@96FBOq%S{@xjk>H;Pm$Vprf)#-$^XvcGujg~ zMQrK69iH3-?K}Kyr#2tBOofxg>5eq-?$O&S?f%wsHYu+IIDe$^1yz4=T$|583=)=S z@rs_N`6gV6-JFQL3lF#d4vueWhTCNqc!)7nH1q`al3f<9SzHYclExT&S;$6#C&N_8dp$l`0Db-{VBFS zUo>Yj_{O>)(7ix1A=5^+D9hzs_E9s=M5KO;F6dR6BPdA;i z`qOSEb!Gp7k+iU?MCm#6h>tli4VC3{)?rgeA|ZXgQI_Ui>KlBe<>RYG=Qx2C_sLSH z5Gf+MJLDTO7Izh}B=inm73tr_cWcgS$2bGcvef$E%pKYw*}=#w7h=B#f0;;MjAUY& zjSXjJ&D}jL`)QBQ?XfLAuX%2o_V?+#Xcr0sQ0mr!CRiY?6~j~ZD|N;Z9tPjDUDsXc4qm5kKfDrf`9M+$YJ^G9LXqJI0dru>D8ro~;P!_E?SqYD$5 zm$3g1-K`pEst5c!KHC+MGyO+_{-+_eCZS_Yskw~(Tab%rfn*1T)A!#oHa!yuZI7zh z^VAHv$2{W4nE$R1VhnnJr0k=G(;sVIP4r*OUScqs+a>E5_L~zLFq!Zzu9CaNn* zmU*hD-rl`d-(&Ot{^D(p_jj(Fd4%P#ryU$`=BBVNJB**AZn`8_|CzH)H!`}HSue6f zlKl5>>7+xl$=A2&Y3pER&PIK{uDjy?wY+<=yKG64@<@VUTO+WmTg%GwRhhECeMg?U z3eo3cEF;<#^SJ(fum+LI2UgvkdV1k!V~;TMz#+ayhiZ7%!n!`7%~hbRtWK5{7tE$U zXU?-E+Xcgrph385k|Lb6OUlrTXBS^39*hvG+^;f#KPYfAQqBAjE}15<;}ifXoOENH9@aO z2(qw7Lu?&;G?cJMx@e-YL_zfMd0Vp?y?Jfg-h&710Gq$};Qcp=zU%!bw^U<^*}6l8 z`P!)8Qx-^heX#*SG)vn5V{gC!l54>O7k*6*$j&%M{O^&6JeqcB(6Ko5^754?><~o` z#v}+HICiriNLuveuonM?m@6!w=Gu)^ydHg)+6S>?RtsI6Me4E!2OSxWA3Dt9z)m2_ zmPnk0$w=4)jw)4Szk#Ts2`Db!V&uAomz8-cZ^A$i=foW?vdZ79yH%uP-r5+RD|G5L z6%;AW&CTuNefZ%L6Dq-tAUEe;_Hv=(*(0=#;nWbmPYbf$5pWy8cw3la0Z0YPp2G5)Z$Z35w0`;z zmQ#j4B!!E!Pq!{Wvu&KE1R-_nNo;)EgJtSaQz=KC0((O(uiIM7hKj`1w_Kdw+@Pa) zi(o@xW8^(hI)L;SMNI-j3$jCZqkC{vaf&9++3xpM#hHl$#6e;D0hIOPg!3XB(->4ULq1+?gdwo?%+@5TfHDMAj18lmNpm{c4P zRfVr!peM}1>3vv~U60&_`KTAS_wgQ{Pav{46e$pO!Ig5rs!HrJoTN>n0w?)?+je0By75aU&wYdC-{vU})n?GRk3hyA>)WocS! z>YdSIk|^bFgP97i$C&_2)UURmLo8UB85_HVgiECe4MtE!b?LFB*68YG{Jmf`_lwgN zWK>KZ1(oLZn~;3u9h<1Xha8_BcaY9pqkhqRrfC1{*X6D!&609Yxk(rGHeM&L zt9g9ndqBGN2hp{QNpofYoL?4+ee7E`q`GeHzbQ;%V@~3%1|dV$T-|;y%cbBeSC>GV zTSlwtG*^P4tuFDhs6L-H4_ar&Ot;DUQ>Tx2H6;a$2lgzuxcxew$|K~xO_suHSZ$;| z+qnkHl3T~k(7(@DQ+4D{0B2>UbCz9pS6DK8#e7rx%$M}{+GFv_CpH_k*omVu?sDNr zzSyHQgroBA*q!NL{X>WfB*jyDowi{)q?2dt)9Ru3yUj$RJL-JL1lKom*%jusWuihk zh{!jJUN{6|)2)X1lHKkm5xfuLdp-4S)4s_6c`o~~af~TwPmtv47fMz?zKE?K9+vUR zOt^v1<~^Jy{clwjGg#YW5Ri_aVLZ_N|N2|_pZVtXm311lkXFbzre#BeKL)x-50Q18 GF8v?7EG?q| diff --git "a/images/architecture/\345\256\211\345\205\250-\345\217\215\345\260\204\345\236\213XSS\346\224\273\345\207\273.png" "b/images/architecture/\345\256\211\345\205\250-\345\217\215\345\260\204\345\236\213XSS\346\224\273\345\207\273.png" deleted file mode 100644 index fc64692736432071be93838be4e44a97341d6b0f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 89148 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 diff --git "a/images/architecture/\345\256\211\345\205\250-\346\214\201\344\271\205\345\236\213XSS\346\224\273\345\207\273.png" "b/images/architecture/\345\256\211\345\205\250-\346\214\201\344\271\205\345\236\213XSS\346\224\273\345\207\273.png" deleted file mode 100644 index f1b165a49ef0f597cc153a36c7978b3e89371de2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 115402 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 !KLF?9pa72h( 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�Wj-<4y(cxS*;D6{-nKuAmw% z!)33x+7>TOVQI)Heug6Rpu=dwCkLZJY^@|GyGL$B<5K>FRF~=OSt?$msUTI4q{174 zf?Tf46^s>h6>*)Ytl&L(Y_gj*pcmVG^|JAt@I&y%R1+{PWb{dhixZ;^N5>mLd$Zx< z$(;huOE}8HzQ?92HmRKL-itEyii!%X6}@GN*9~Z-79%~vwgBHIFzUCI+0N7&E}~mZ z4;sJ!=k5K>iP9dh3vC4M{){yZY>owwi+RAlK<6?6y(NpO0cmil#n=+VrC!N<+?AKa z+$>hCIK#QMDQt$#_QaPrx*XrY^~Zv0yhy0O()X1et3CAE$J;v??m~^OlABcicWy;y zc(QnS*oLv~5Bv|%D>6{yz$>p7uK_aVvzQ5lzXITn$@wthB&HyY>DT;+qA09Zxek&i z*30`tu0(FxpAg(U<)Mx96yX+y4s{19_y&!99WYC{Ihu7WpGHPvNn2t>*;IvsWF9 zI44S*ci&sAKRS3o}Ek+OLKOyho(E_Xn5U?jhbK|VSx+Y-K8SFj^&d%qnTB+mC z$e;7GvnafButntN3~V{cHKXc#+h3p_0=GaNAzkqZ65sz>-T^%|I{ZdQ@d1AHE)Ngp zBS>CQq)$O1+2h2v&UxjvAP8#_z|d5jy&o|PQp(Y7U;{lk1v=GjD^E5QL6zZP5&&NBdGlrm zQHja2lLYczfUfCeXJ*czkI$nUU4a6#$*C5KGM?Q$=HuN`OnOS3i-piJy{BK$oq$s4LrG=N;4G}G98DBqq zu%0bsFs2=clKxf$31HnVHrSkbd#|vZ9qp^a$VQo0DtQNJ|$8MToasy@CS?6!^|063ZNn)%>)a|G%E6z4cdP+iLo@V zV)Xgi^2u=%up`1dQ4oV@$bVlh0cfzs#9bdYPlv`CQ-4y-$%~6Qfj1XfTMn@B0bsl8 zT$T{v?zT4k(TH8N<%+b@E$YN|F%@GLctv2VGEQ0RCm?lz#_lF2Cg$X90|ey6kED1N zCA|;ZuMgR5n_9svz$HP!s5C~5>HwQy3R+rLhO*WC3EFG~NvqYcGeL!k?_}r>t_$XS zg@uLw?>~P|^(amtcoN+LXr`c1Y&@}W{n(EG0^#4C-*4EJN@3!{yteT9HM{Pnw3m~Q z*caByE<^KAJYOd1uN%={?6CRx>un1Sjr~J18*g1 z48>PtV2N32FfmY&6g9>|A{tt~`O|M&PQaqCy;NIybmB^paPxK z3w(zblD7{D#su0J?6!vBBQ$jGr~pD-V!1wYKXRsZs`yPzXG6JuRPd#OVn8#4Cf5Rn z$on*B{I-KW`S;Jas6;V*)~U~*2OvmVw7V(0r0{3JVP^bqYM)e7FXf^;*eNzL4PBPtMK=Xg-2(hS0Yg6#FR;oJDlM?*FZi z-3cHPoBP0ugng*UZ#2~2sT$0#E@-|jDHx9aMwmjIF%F1(JC0a2A_{&E=tJkCo%rFa z;)wNaj33WV)!Yp5Qh067LB8^&yE_O$TcgOR;-9A6!I%iBcYfhC%ou$^%t6$7#=?8g zhP#uo0I>yz$M_@`RTpTRurl{2VktRt(g<1(*mg`N&}b~oNF)rx6_es*5I*a>T0j+J z{1F6MHyi~p6tckB%gO?Jw|jqNY}PD{s#{xuC}!v8t`HN8Ypugfx@c`!EYvN4hk`lb0N(B?FcM(8j>k)spoT~JnoPlq1Nw|M z;x(Qoej5^dUO~Zm{a|^>ZyrozYi>v4h}JZYGmIng22CU8cydSx^pcXBye4zyWpJC>t9l1bW~H#rqRGZ;p(hG8LMvljI*>-3 z%|nS~QCT_n;|C@@Xe6DvVCQ1P$~(wU9l5|oPr|-0*c9TZ==!_G7&0rYYEB{8WsS9cO!nNKSDYftP74cn#cbHl!%@HFajf+bb2bB zHu~C7RRAm|dQTv3zZVySm)kXDIZkP=7>`$`C?Ob*mQ4Js%jdGr=aRQdFWp;_9llqf z`qOW*Z)6>vzOJrA19icHrq_Fz35AtXbsL>qq@9cL9B};4+`jz-(K!d6^?>|bXnFBZ zOQvxvklz;3PhV2G(I0Ai8ZixE$#;8T}w~ z&Vb3IZ<~t#o5*@DA+o7lZ~c`W-=iiPCd@sIL&y`4whD-1sXB?oBBjqmy0BkRwL909 zD1cz^0KqQCjtufN?xS>Rw3mZKx2u(0D*Jv_;`vyFvX}CgY`+(A6vmI8hxpIVDNJ&G z6qs4K>6}xU0Zl*paElIaxS{t22CpKw<^q_Wg{%V&$(( zu-wsR5i4IrPDMMVgZngV%)zZ`1lLClUwF+2xca=5H4Fb=Wmg^#_5Qtwu}t=5lznY% zSE{ijB{Q-sONdsol)6NsQe>=6t~EqT2siEBh`2&hqzx@sWGjVmukFTI@_jz~eSfdt zzrW9)UdqhpectCh=Q+tx`zQLa%QNmi9kY9*M ziKdD1;_SH7*1XHQfFYYL{WD=YVxdp@pvKFi&I4uo4D?JEUUp!86q#nPOn}_jC@3G@ zcEDj954H?B&pY(Ty$urZoA@rd=61rXOnxc%;LPZd+ow{H8rbe2;sjT#5me z9B)Q%<^iEVtB%O~={K!jwF(>Wet7a-dpp(+XZpt{YaJEppR9uOLgI2RuPHG(*^ zTUTJ>mY2W@#mtN-3S~KutDQ{Cv!gFXvp(IIu6;Xys-8;C^K24Lv;`W!J+m$Jn(8eU|U3IlhO}qwk>JkVyX* z)C=V|h|{o07DSCx-fgZ-3MGT*Zc z>u5*`5VXH-dSjQjxX`nhbTa?u(gBf(t|+R_1GE_FqnJ9eKOi8PRHdS;dl<4I{EV@- z?(e&sPAnYG{`&Y~kB(WwYa-M)ctUl6 zw^X=S_Qzc9;v7`>Sh?$l6$UG#@81tacAY8+O@%x_@+C0I@P8^+MU?>FsOh8Ok^4Mt zHF)%2Q9#Bth0rVh$rvsdV zGeh_Cavk@(h@K#Qf{q>qCYJ^cxIcv5#BjoUA*8Cn!=4dF5u6EH7J-g(a!x{M9qZ=e zk}>sv4usQxiVI)rpOymhGUH4!Z5zX^2K7G~(?wDBU@}*&T7^O;)(&9BPD%Jc7}+*D z2QLP7tiOO2kr1LDL=2R~8^T7NcPJh~CoU?zhGfTKhL)KD_GX#0tN2I!X`}(3@8HEfwo|T}1u0WVPj#RQ+)-y(@E?W_3QAk)A>#B4; zY7*n~M-dqTZ_r5ll8vrrV&Gi)DQiL;pFj3*Dt5NAsIPcWMA zG-ioXV=(87!5~$PMe5HCcj{8ONrZX%{glRZLf(XC>lR@k=%P_jRiJX>XbDlYlk+o2 zf`jj2R3$uuljuzesP8MUT|0U5kP?Dq7vJH9s;9?Tc6kvdgI>t$#8*Mlbo2++x!v7$~Cxv7; z_4}5;ZG{G`bEflhJccs)ADUU#GvQA^QRVE5Z-q-aERC($SYVj=j*k%D;<>0EUv%i4}%oi`q? zDHRo$=<=Wh#`GV0{{0!L&&Bmdv`7c-Q&-sSQ`Iik3Fh*)PZQj>I+fXTo1=~i2wc$D zMMA|PHU=-@9`Zr@n9C{7F0vawH?MOWN9Gc-#sj%07%z!!wa5Qa%nGspzNvR#NGJ{) z#Fh%h=*v?uRF`sdpRC65VQHatb7FM71*Nm2gIIqK0OuPfN7-xHAhGXxcv0k%r{#Kd zzP-sQT5pR3t-t_4LsOI8WP@5i^@(Mpkv{W~BRe6X3fqbJRMj5XHhk5*TE;zP(+R+P zuCAzPrEwqClQlkU6Ry@D-c|SX?krL4=`3F*WNU0M{BMt_0$uWwkXxM!mIni9!}A4g*BbF7(Odq8zxPfwQfw&bLw;ymyO!Dqixm*hF#VMg0gO5D9W zr&f5}Uf%7!En$>QeuW6JmCL%QAy!wWAqWM#Yc9a}9!w=OB$FYhLQo3)i{wSWZuZwZ zpyr`|sM(P*Q3?e|{hlJ=;^^mARB6qua za;KM8R7Um|8Xq=hW*{STU|7ne%5-CqsEP{n(;DXG_*mb>XXA7_9W9$SW4(788XB;L z6YBMd$vce$;a&N2fBRy-klaS(s)*U8w6}M|?&VeqOFn{C%=oy1%*)3oBCF5-A--JbWiC;a zr=R1t`?&MXPU0~73K`OnQy7W=2@V-0GVGn#Gcdp^k8c3;@YBmm`}dgdq>`RC?2~@f z*+~r3nw_0RbpPEIMh&BFngR{EbuYikK7t4-J%^_3Om$-{{HKCRSCxe(IZP zUKT$}1@hjPZ70IP%s^^}4%XHyUVy>@@=Ro_6`{l1Ij_P&k->v;k3Sa`Rj1L!(+e(w zBE!I%maXJ6Vz+bUNBk~`oiaie?-W@@`vK1GK~2{l)|_0_f4 zCIiwRUb-Xa?sd%UGWE#p$OpY+Fqay?3j88qZ?LQ3zaF$9(aq)OUs$$w6=+tH|>nPc48S(e% zOWCgy`5B29!TmM(#-*V%v*=I*3aEhQ6K-)Em|#IP#H3vo_XeK}eVwQL3(6~ynV8k5 z)zm{m)>mjHf3~|Qtx2Tc3`d4=h1DP5ef^UjXz>B+D=<#To?0$~>mn4~fOl|d38sBp zvRiouyZ3UkSgKsx&NDBNx7nQZ*2<32u>h^Ssrj0{$>7(iv?jT9GQ zXH)Ott+hu-M;B*4izb7Qo?>+n<4yWQ(QG;DQo|cubC{L*!8?0fQ?}ZM<{^-|Xk2@x z+tJ>Rtzvv@XMANcRHS8F>+%U26z>M?E0&nbD=5gcf(3ws)Wm^OWOhm`TrE)X6<0oRe8UJ#o3ZxRASJ}+6m5};5%Lx zr254OSc7MQ$gn`Pwk5n#5i?4P0sJVx!SkWY)=^iN4?Yn(!fb3wtR^WW%B|DgxN*0l zlwJdTTY*Nb1^5un7P%d|0j=_hnua|BxPRf&Am0x>Zr@0~bmPV__z|E0-UFq-jOf46 z4iTI#%}u@Cax1({tk;;^T9*6w%_XueK(=j-jrX)qr1mUYli+g4-)7I6f5I2;q=P*Gn+eadaQmgA(gm^j5YGuMXvqN$@J zXHP|0*+@@qE!tjB@gaN=v=)2{01^RG(Ix>3)KbYGI0U?>W(-=$L*A1H1~Dy|#YOgu z>#jy_2hs_=g=6j&hcC=0wFfsTYyD%jbjy5Jh*iWHU_t{(X0AUsfB?_Vo|&DMn1u4k zhH0Z5`i~%4lN7);?Ho^WBty}kHt-TEQh;8g&~MZTGXecDG!vPrfPB7d10Uij?YIpU zowOBrz1@F!pJAsg9hEY@5^Vlr{&DMLI#UZ=303b8az-2Ak=C(tk^-?&J$@}go%lDT=fwhIn z2VW=KXf^)S*dYgWYO|}V?*<``dsuW0x}258K6i1mAkV?BR7z$}TCGgR68DKntW!6Q zEHo)4s zZO^kmRYH=zdR>GDQ=0*Q{cEKD_tTg&IAW%|?Jq=!`y@FFEOPP|mwh3B3;j|NMXO>e znt5XDaf(s?t}wrG3@FzwUhjKtz8ojM^z@Y~-*jM3?`AsR;=Ri7c{DsA6v(3XvblJ5 zO(K8D`zp_pygB)g@tXRt>tFqPfe&McEdTqDnSI^`L=z+N)Hj;_&lls3ONna(1^F|s z3l_^Q*#c(KEMFS7w{%E4|NZceGwzyQgx%vA`{V!fAD>yAUi@L2{( Date: Mon, 6 Aug 2018 16:26:30 +0800 Subject: [PATCH 08/75] :memo: Writing docs. --- README.md | 2 +- ...11\345\205\250\346\236\266\346\236\204.md" | 284 +++++++++++++++--- 2 files changed, 246 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index a698b880..89d15f73 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ - [网站的高可用架构](docs/architecture/网站的高可用架构.md) - [网站的伸缩性架构](docs/architecture/网站的伸缩性架构.md) - [网站的可扩展架构](docs/architecture/网站的可扩展架构.md) -- [网站的安全架构](docs/architecture/网站的安全架构.md) +- [网站的安全架构](docs/architecture/网站的安全架构.md) - 关键词:XSS、CSRF、SQL 注入、DoS、消息摘要、加密算法、证书 - [网站典型故障](docs/architecture/网站典型故障.md) ## :one: JavaEE diff --git "a/docs/architecture/\347\275\221\347\253\231\347\232\204\345\256\211\345\205\250\346\236\266\346\236\204.md" "b/docs/architecture/\347\275\221\347\253\231\347\232\204\345\256\211\345\205\250\346\236\266\346\236\204.md" index b8dac4c7..b427e869 100644 --- "a/docs/architecture/\347\275\221\347\253\231\347\232\204\345\256\211\345\205\250\346\236\266\346\236\204.md" +++ "b/docs/architecture/\347\275\221\347\253\231\347\232\204\345\256\211\345\205\250\346\236\266\346\236\204.md" @@ -1,83 +1,234 @@ +--- +title: 网站的安全架构 +date: 2018/08/06 +categories: +- javaweb +tags: +- java +- javaweb +- architecture +- security +--- + # 网站的安全架构 +> 关键词:XSS、CSRF、SQL 注入、DoS、消息摘要、加密算法、证书 + - [1. 网站安全的攻与防](#1-网站安全的攻与防) - - [1.1. XSS 攻击](#11-xss-攻击) - - [1.2. 注入攻击](#12-注入攻击) - - [1.3. CSRF 攻击](#13-csrf-攻击) + - [1.1. 跨站脚本攻击(XSS)](#11-跨站脚本攻击xss) + - [1.2. 跨站请求伪造(CSRF)](#12-跨站请求伪造csrf) + - [1.3. SQL 注入攻击](#13-sql-注入攻击) + - [1.4. 拒绝服务攻击(DoS)](#14-拒绝服务攻击dos) - [2. 加密技术及密钥安全管理](#2-加密技术及密钥安全管理) - [2.1. 消息摘要](#21-消息摘要) - - [2.2. 对称加密](#22-对称加密) - - [2.3. 非对称加密](#23-非对称加密) - - [2.4. 密钥安全管理](#24-密钥安全管理) + - [2.2. 加密算法](#22-加密算法) + - [2.3. 证书](#23-证书) - [3. 资料](#3-资料) ## 1. 网站安全的攻与防 -### 1.1. XSS 攻击 +互联网环境鱼龙混杂,网站被攻击是常见现象,所以了解一些常见的网站攻击手段十分必要。下面列举比较常见的 4 种攻击手段: + +### 1.1. 跨站脚本攻击(XSS) + +#### 概念 -XSS 攻击即跨站点脚本攻击(Cross Site Script),指黑客通过篡改网页,注入恶意 HTML 脚本,在用户浏览网页时,控制用户浏览器进行恶意操作的一种攻击方式。 +跨站脚本攻击(Cross-Site Scripting, XSS),是一种网站应用程序的安全漏洞攻击,是代码注入的一种。它允许恶意用户将代码注入到网页上,其他用户在观看网页时就会受到影响。这类攻击通常包含了 HTML 以及用户端脚本语言。 -XSS 攻击有两种类型: +XSS 攻击示例: -(1)反射型 XSS 攻击 +假如有下面一个 textbox -
- -
+```html + +``` -(2)持久型 XSS 攻击 +value1from 是来自用户的输入,如果用户不是输入 value1from,而是输入 `"/> - - +```html + +``` -应对手段: +嵌入的 JavaScript 代码将会被执行。攻击的威力,取决于用户输入了什么样的脚本。 -- **消毒** - XSS 攻击一般是通过在请求中嵌入恶意脚本达到攻击的目的,这些脚本是一般用户输入中不使用的。过滤和消毒处理,即对某些 html 危险字符转移,如 `>` 转义为 `>`、`<` 转义为 `<` 等,就可以防止大部分攻击。为了避免对不必要的内容错误转移,如 `3<5` 中的 `<` 需要进行文本匹配后再转移,如:` - - +- **过滤特殊字符** - 将用户所提供的内容进行过滤,从而避免 HTML 和 Jascript 代码的运行。如 `>` 转义为 `>`、`<` 转义为 `<` 等,就可以防止大部分攻击。为了避免对不必要的内容错误转移,如 `3<5` 中的 `<` 需要进行文本匹配后再转移,如:` :point_right: 参考阅读: +> +> - [Wiki 词条 - 跨站脚本](https://zh.wikipedia.org/wiki/%E8%B7%A8%E7%B6%B2%E7%AB%99%E6%8C%87%E4%BB%A4%E7%A2%BC) +> - [Web 安全测试之 XSS](https://www.cnblogs.com/TankXiao/archive/2012/03/21/2337194.html) -- **消毒** - 和防 XSS 攻击一样,请求参数消毒是一种比较简单粗暴又有效的手段。通过正则匹配,过滤请求数据中可能注入的 SQL。 -- **参数绑定** - 使用预编译手段,绑定参数是最好的防 SQL 注入方式。目前许多数据访问层框架,如 mybatis、hibernate 等,都实现 SQL 预编译和参数绑定,攻击者的恶意 SQL 会被当做 SQL 的参数,而不是 SQL 命令被执行。 +### 1.2. 跨站请求伪造(CSRF) -### 1.3. CSRF 攻击 +#### 概念 -CSRF 攻击即跨站点请求伪造(Cross Site Request Forgery),攻击者通过跨站请求,以合法用户的身份进行非法操作,如转账交易、发表评论等。CSRF 的主要手法是利用跨站请求,在用户不知情的情况下,以用户身份伪造请求。其核心是利用了浏览器 Cookie 或服务器 Session 策略,盗取用户身份。 +跨站请求伪造(Cross-site request forgery,CSRF),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF。它 是一种挟制用户在当前已登录的 Web 应用程序上执行非本意的操作的攻击方法。和跨站脚本(XSS)相比,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。 -
- -
+#### 攻击手段和目的 -应对手段: +可以如此理解 CSRF:攻击者盗用了你的身份,以你的名义发送恶意请求。 -- **表单 Token** - CSRF 是一个伪造用户请求的操作,所以需要构造用户请求的所有参数才可以。表单 Token 通过在请求参数中添加随机数的办法来组织攻击者获得所有请求参数。 +CSRF 能做的事太多: + +- 以你名义发送邮件,发消息 +- 用你的账号购买商品 +- 用你的名义完成虚拟货币转账 +- 泄露个人隐私 +- ... + +#### 应对手段 + +- **表单 Token** - CSRF 是一个伪造用户请求的操作,所以需要构造用户请求的所有参数才可以。表单 Token 通过在请求参数中添加随机数的办法来阻止攻击者获得所有请求参数。 - **验证码** - 请求提交是,需要用户输入验证码,以避免用户在不知情的情况下被攻击者伪造请求。 - **Referer check** - HTTP 请求头的 Referer 域中记录着请求资源,可通过检查请求来源,验证其是否合法。 +> :point_right: 参考阅读: +> +> - [Wiki 词条 - 跨站请求伪造](https://zh.wikipedia.org/wiki/%E8%B7%A8%E7%AB%99%E8%AF%B7%E6%B1%82%E4%BC%AA%E9%80%A0) +> - [浅谈 CSRF 攻击方式](http://www.cnblogs.com/hyddd/archive/2009/04/09/1432744.html) +> - [「每日一题」CSRF 是什么?](https://zhuanlan.zhihu.com/p/22521378)[「每日一题」CSRF 是什么?](https://zhuanlan.zhihu.com/p/22521378) +> - [WEB 安全之-CSRF(跨站请求伪造)](https://www.jianshu.com/p/855395f9603b) + +### 1.3. SQL 注入攻击 + +#### 概念 + +SQL 注入攻击(SQL injection),是发生于应用程序之数据层的安全漏洞。简而言之,是在输入的字符串之中注入 SQL 指令,在设计不良的程序当中忽略了检查,那么这些注入进去的指令就会被数据库服务器误认为是正常的 SQL 指令而运行,因此遭到破坏或是入侵。 + +攻击示例: + +考虑以下简单的登录表单: + +```html +
+

Username:

+

Password:

+

+
+``` + +我们的处理里面的 SQL 可能是这样的: + +```sql +username:=r.Form.Get("username") +password:=r.Form.Get("password") +sql:="SELECT * FROM user WHERE username='"+username+"' AND password='"+password+"'" +``` + +如果用户的输入的用户名如下,密码任意 + +```sql +myuser' or 'foo' = 'foo' -- +``` + +那么我们的 SQL 变成了如下所示: + +```sql +SELECT * FROM user WHERE username='myuser' or 'foo' = 'foo' --'' AND password='xxx' +``` + +在 SQL 里面 `--` 是注释标记,所以查询语句会在此中断。这就让攻击者在不知道任何合法用户名和密码的情况下成功登录了。 + +对于 MSSQL 还有更加危险的一种 SQL 注入,就是控制系统,下面这个可怕的例子将演示如何在某些版本的 MSSQL 数据库上执行系统命令。 + +```sql +sql:="SELECT * FROM products WHERE name LIKE '%"+prod+"%'" +Db.Exec(sql) +``` + +如果攻击提交 `a%' exec master..xp_cmdshell 'net user test testpass /ADD' --` 作为变量 prod 的值,那么 sql 将会变成 + +```sql +sql:="SELECT * FROM products WHERE name LIKE '%a%' exec master..xp_cmdshell 'net user test testpass /ADD'--%'" +``` + +MSSQL 服务器会执行这条 SQL 语句,包括它后面那个用于向系统添加新用户的命令。如果这个程序是以 sa 运行而 MSSQLSERVER 服务又有足够的权限的话,攻击者就可以获得一个系统帐号来访问主机了。 + +虽然以上的例子是针对某一特定的数据库系统的,但是这并不代表不能对其它数据库系统实施类似的攻击。针对这种安全漏洞,只要使用不同方法,各种数据库都有可能遭殃。 + +#### 攻击手段和目的 + +- 数据表中的数据外泄,例如个人机密数据,账户数据,密码等。 +- 数据结构被黑客探知,得以做进一步攻击(例如 `SELECT * FROM sys.tables`)。 +- 数据库服务器被攻击,系统管理员账户被窜改(例如 `ALTER LOGIN sa WITH PASSWORD='xxxxxx'`)。 +- 获取系统较高权限后,有可能得以在网页加入恶意链接、恶意代码以及 XSS 等。 +- 经由数据库服务器提供的操作系统支持,让黑客得以修改或控制操作系统(例如 xp_cmdshell "net stop iisadmin"可停止服务器的 IIS 服务)。 +- 破坏硬盘数据,瘫痪全系统(例如 xp_cmdshell "FORMAT C:")。 + +#### 应对手段 + +- **使用参数化查询** - 建议使用数据库提供的参数化查询接口,参数化的语句使用参数而不是将用户输入变量嵌入到 SQL 语句中,即不要直接拼接 SQL 语句。例如使用 database/sql 里面的查询函数 `Prepare` 和 `Query` ,或者 `Exec(query string, args ...interface{})`。 +- **单引号转换** - 在组合 SQL 字符串时,先针对所传入的参数作字符取代(将单引号字符取代为连续 2 个单引号字符)。 + +> :point_right: 参考阅读: +> +> - [Wiki 词条 - SQL 注入攻击](https://zh.wikipedia.org/wiki/SQL%E8%B3%87%E6%96%99%E9%9A%B1%E7%A2%BC%E6%94%BB%E6%93%8A) +> - [避免 SQL 注入](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/09.4.md) +> - [实例讲解 SQL 注入攻击](http://blog.jobbole.com/83092/) + +### 1.4. 拒绝服务攻击(DoS) + +拒绝服务攻击(denial-of-service attack, DoS)亦称洪水攻击,是一种网络攻击手法,其目的在于使目标电脑的网络或系统资源耗尽,使服务暂时中断或停止,导致其正常用户无法访问。 + +当黑客使用网络上两个或以上被攻陷的电脑作为“僵尸”向特定的目标发动“拒绝服务”式攻击时,称为分布式拒绝服务攻击(distributed denial-of-service attack,缩写:DDoS attack、DDoS)。 + +#### 攻击方式 + +- 带宽消耗型攻击 +- 资源消耗型攻击 + +#### 应对手段 + +- 防火墙 - 允许或拒绝特定通讯协议,端口或 IP 地址。当攻击从少数不正常的 IP 地址发出时,可以简单的使用拒绝规则阻止一切从攻击源 IP 发出的通信。 +- 路由器、交换机 - 具有速度限制和访问控制能力。 +- 流量清洗 - 通过采用抗 DDoS 软件处理,将正常流量和恶意流量区分开。 + +> :point_right: 参考阅读: +> +> - [拒绝服务攻击](https://zh.wikipedia.org/wiki/%E9%98%BB%E6%96%B7%E6%9C%8D%E5%8B%99%E6%94%BB%E6%93%8A) + ## 2. 加密技术及密钥安全管理 +对于网站来说,用户信息、账户等等敏感数据一旦泄漏,后果严重,所以为了保护数据,应对这些信息进行加密处理。 + +信息加密技术一般分为: + +- 消息摘要 +- 加密算法 + - 对称加密 + - 非对称加密 +- 证书 + ### 2.1. 消息摘要 常用数字签名算法:MD5、SHA 等。 应用场景:将用户密码以消息摘要形式保存到数据库中。 -### 2.2. 对称加密 +> :point_right: 参考阅读: +> +> - [消息摘要](https://github.com/dunwu/JavaCore/blob/master/docs/advanced/encode/java-message-digest.md) + +### 2.2. 加密算法 + +#### 对称加密 对称加密指加密和解密所使用的密钥是同一个密钥。 @@ -85,7 +236,7 @@ CSRF 攻击即跨站点请求伪造(Cross Site Request Forgery),攻击者 应用场景:Cookie 加密、通信机密等。 -### 2.3. 非对称加密 +#### 非对称加密 非对称加密指加密和解密所使用的不是同一个密钥,而是一个公私钥对。用公钥加密的信息必须用私钥才能解开;反之,用私钥加密的信息只有用公钥才能解开。 @@ -93,13 +244,68 @@ CSRF 攻击即跨站点请求伪造(Cross Site Request Forgery),攻击者 应用场景:HTTPS 传输中浏览器使用的数字证书实质上是经过权威机构认证的非对称加密公钥。 -### 2.4. 密钥安全管理 +> :point_right: 参考阅读: +> +> - [加密](https://github.com/dunwu/JavaCore/blob/master/docs/advanced/encode/java-encryption.md) + +#### 2.3. 密钥安全管理 保证密钥安全的方法: 1. 把密钥和算法放在一个独立的服务器上,对外提供加密和解密服务,应用系统通过调用这个服务,实现数据的加解密。 2. 把加解密算法放在应用系统中,密钥则放在独立服务器中,为了提高密钥的安全性,实际存储时,密钥被切分成数片,加密后分别保存在不同存储介质中。 +### 2.3. 证书 + +证书可以称为信息安全加密的终极手段。公开密钥认证(英语:Public key certificate),又称公开密钥证书、公钥证书、数字证书(digital certificate)、数字认证、身份证书(identity certificate)、电子证书或安全证书,是用于公开密钥基础建设的电子文件,用来证明公开密钥拥有者的身份。此文件包含了公钥信息、拥有者身份信息(主体)、以及数字证书认证机构(发行者)对这份文件的数字签名,以保证这个文件的整体内容正确无误。 + +透过信任权威数字证书认证机构的根证书、及其使用公开密钥加密作数字签名核发的公开密钥认证,形成信任链架构,已在 TLS 实现并在万维网的 HTTP 以 HTTPS、在电子邮件的 SMTP 以 STARTTLS 引入并广泛应用。 + +众所周知,常见的应用层协议 HTTP、FTP、Telnet 本身不保证信息安全。但是加入了 SSL/TLS 加密数据包机制的 HTTPS、FTPS、Telnets 是信息安全的。 + +#### 概念 + +传输层安全性协议(Transport Layer Security, TLS),及其前身安全套接层(Secure Sockets Layer, SSL)是一种安全协议,目的是为互联网通信,提供安全及数据完整性保障。 + +#### 证书原理 + +SSL/TLS 协议的基本思路是采用公钥加密法,也就是说,客户端先向服务器端索要公钥,然后用公钥加密信息,服务器收到密文后,用自己的私钥解密。 + +这里有两个问题: + +(1)**如何保证公钥不被篡改?** + +解决方法:将公钥放在数字证书中。只要证书是可信的,公钥就是可信的。 + +(2)**公钥加密计算量太大,如何减少耗用的时间?** + +解决方法:每一次对话(session),客户端和服务器端都生成一个"对话密钥"(session key),用它来加密信息。由于"对话密钥"是对称加密,所以运算速度非常快,而服务器公钥只用于加密"对话密钥"本身,这样就减少了加密运算的消耗时间。 + +SSL/TLS 协议的基本过程是这样的: + +1. 客户端向服务器端索要并验证公钥。 +2. 双方协商生成"对话密钥"。 +3. 双方采用"对话密钥"进行加密通信。 + +> :point_right: 参考阅读: +> +> - [传输层安全性协议](https://zh.wikipedia.org/wiki/%E5%82%B3%E8%BC%B8%E5%B1%A4%E5%AE%89%E5%85%A8%E6%80%A7%E5%8D%94%E5%AE%9A) +> - [公开密钥认证](https://zh.wikipedia.org/wiki/%E5%85%AC%E9%96%8B%E9%87%91%E9%91%B0%E8%AA%8D%E8%AD%89) +> - [SSL/TLS 协议运行机制的概述](http://www.ruanyifeng.com/blog/2014/02/ssl_tls.html) + ## 3. 资料 - [大型网站技术架构](https://item.jd.com/11322972.html) +- [Wiki 词条 - 跨站脚本](https://zh.wikipedia.org/wiki/%E8%B7%A8%E7%B6%B2%E7%AB%99%E6%8C%87%E4%BB%A4%E7%A2%BC) +- [Web 安全测试之 XSS](https://www.cnblogs.com/TankXiao/archive/2012/03/21/2337194.html) +- [Wiki 词条 - 跨站请求伪造](https://zh.wikipedia.org/wiki/%E8%B7%A8%E7%AB%99%E8%AF%B7%E6%B1%82%E4%BC%AA%E9%80%A0) +- [浅谈 CSRF 攻击方式](http://www.cnblogs.com/hyddd/archive/2009/04/09/1432744.html) +- [「每日一题」CSRF 是什么?](https://zhuanlan.zhihu.com/p/22521378)[「每日一题」CSRF 是什么?](https://zhuanlan.zhihu.com/p/22521378) +- [WEB 安全之-CSRF(跨站请求伪造)](https://www.jianshu.com/p/855395f9603b) +- [Wiki 词条 - SQL 注入攻击](https://zh.wikipedia.org/wiki/SQL%E8%B3%87%E6%96%99%E9%9A%B1%E7%A2%BC%E6%94%BB%E6%93%8A) +- [避免 SQL 注入](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/09.4.md) +- [实例讲解 SQL 注入攻击](http://blog.jobbole.com/83092/) +- [拒绝服务攻击](https://zh.wikipedia.org/wiki/%E9%98%BB%E6%96%B7%E6%9C%8D%E5%8B%99%E6%94%BB%E6%93%8A) +- [传输层安全性协议](https://zh.wikipedia.org/wiki/%E5%82%B3%E8%BC%B8%E5%B1%A4%E5%AE%89%E5%85%A8%E6%80%A7%E5%8D%94%E5%AE%9A) +- [公开密钥认证](https://zh.wikipedia.org/wiki/%E5%85%AC%E9%96%8B%E9%87%91%E9%91%B0%E8%AA%8D%E8%AD%89) +- [SSL/TLS 协议运行机制的概述](http://www.ruanyifeng.com/blog/2014/02/ssl_tls.html) From 0e45578fe2bc3632a1cef9a2a6a6b8a3a4cc6970 Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Tue, 4 Sep 2018 11:35:54 +0800 Subject: [PATCH 09/75] :memo: Writing docs. --- docs/distributed/mq/kafka-advanced.md | 76 +++++++++++++------------- docs/distributed/mq/kafka-basics.md | 78 +++++++++++++-------------- 2 files changed, 77 insertions(+), 77 deletions(-) diff --git a/docs/distributed/mq/kafka-advanced.md b/docs/distributed/mq/kafka-advanced.md index e0f7f5c5..31018bc9 100644 --- a/docs/distributed/mq/kafka-advanced.md +++ b/docs/distributed/mq/kafka-advanced.md @@ -2,12 +2,12 @@ title: Kafka date: 2018/06/13 categories: -- 分布式 + - 分布式 tags: -- java -- javaweb -- 分布式 -- mq + - java + - javaweb + - 分布式 + - mq --- # Kafka @@ -17,35 +17,35 @@ tags: - [1. 概述](#1-概述) - - [1.1. 分布式](#11-分布式) - - [1.2. 容错](#12-容错) - - [1.3. 提交日志](#13-提交日志) - - [1.4. 消息队列](#14-消息队列) - - [1.5. 为什么要使用消息系统](#15-为什么要使用消息系统) - - [1.6. Kafka 的关键功能](#16-kafka-的关键功能) - - [1.7. Kafka 基本概念](#17-kafka-基本概念) - - [1.8. Kafka 核心 API](#18-kafka-核心-api) - - [1.9. Topic 和日志](#19-topic-和日志) + - [1.1. 分布式](#11-分布式) + - [1.2. 容错](#12-容错) + - [1.3. 提交日志](#13-提交日志) + - [1.4. 消息队列](#14-消息队列) + - [1.5. 为什么要使用消息系统](#15-为什么要使用消息系统) + - [1.6. Kafka 的关键功能](#16-kafka-的关键功能) + - [1.7. Kafka 基本概念](#17-kafka-基本概念) + - [1.8. Kafka 核心 API](#18-kafka-核心-api) + - [1.9. Topic 和日志](#19-topic-和日志) - [2. Kafka 工作原理](#2-kafka-工作原理) - [3. 持久化](#3-持久化) - [4. 复制](#4-复制) - [5. 流处理](#5-流处理) - - [5.1. 无状态处理](#51-无状态处理) - - [5.2. 有状态处理](#52-有状态处理) + - [5.1. 无状态处理](#51-无状态处理) + - [5.2. 有状态处理](#52-有状态处理) - [6. Kafka 应用场景](#6-kafka-应用场景) - [7. 幂等性](#7-幂等性) - - [7.1. 幂等性实现](#71-幂等性实现) - - [7.2. 幂等性的应用实例](#72-幂等性的应用实例) + - [7.1. 幂等性实现](#71-幂等性实现) + - [7.2. 幂等性的应用实例](#72-幂等性的应用实例) - [8. 事务](#8-事务) - - [8.1. 事务属性理解](#81-事务属性理解) - - [8.2. 引入事务目的](#82-引入事务目的) - - [8.3. 事务操作的 API](#83-事务操作的-api) - - [8.4. 事务属性的应用实例](#84-事务属性的应用实例) - - [8.5. 生产者事务的实现](#85-生产者事务的实现) - - [8.6. 其他思考](#86-其他思考) + - [8.1. 事务属性理解](#81-事务属性理解) + - [8.2. 引入事务目的](#82-引入事务目的) + - [8.3. 事务操作的 API](#83-事务操作的-api) + - [8.4. 事务属性的应用实例](#84-事务属性的应用实例) + - [8.5. 生产者事务的实现](#85-生产者事务的实现) + - [8.6. 其他思考](#86-其他思考) - [9. 资料](#9-资料) - - [9.1. 官方资料](#91-官方资料) - - [9.2. 第三方资料](#92-第三方资料) + - [9.1. 官方资料](#91-官方资料) + - [9.2. 第三方资料](#92-第三方资料) @@ -280,7 +280,7 @@ Kafka 允许您轻松分离不同(微)服务之间的通信。利用 Streams ### 7.1. 幂等性实现 -#### PID 和 Sequence Number +#### 7.1.1. PID 和 Sequence Number 为了实现 Producer 的幂等性,Kafka 引入了 Producer ID(即 PID)和 Sequence Number。 @@ -295,7 +295,7 @@ Broker 端在缓存中保存了这 seq number,对于接收的每条消息, ![2](http://www.heartthinkdo.com/wp-content/uploads/2018/05/2.png) -#### 生成 PID 的流程 +#### 7.1.2. 生成 PID 的流程 在执行创建事务时,如下: @@ -470,7 +470,7 @@ producer 提供了 initTransactions, beginTransaction, sendOffsets, commitTransa 只有 consumer 消费消息,这种操作其实没有什么意义,跟使用手动提交效果一样,而且也不是事务属性引入的目的,所以一般不会使用这种情况 -#### 相关属性配置 +#### 8.4.1. 相关属性配置 使用 kafka 的事务 api 时的一些注意事项: @@ -479,7 +479,7 @@ producer 提供了 initTransactions, beginTransaction, sendOffsets, commitTransa - 生产者不需要再配置 enable.idempotence,因为如果配置了 transaction.id,则此时 enable.idempotence 会被设置为 true - 消费者需要配置 Isolation.level。在 consume-trnasform-produce 模式下使用事务时,必须设置为 READ_COMMITTED。 -#### 只有写 +#### 8.4.2. 只有写 创建一个事务,在这个事务操作中,只有生成消息操作。代码如下: @@ -573,7 +573,7 @@ producer 提供了 initTransactions, beginTransaction, sendOffsets, commitTransa } ``` -#### 消费-生产并存(consume-transform-produce) +#### 8.4.3. 消费-生产并存(consume-transform-produce) 在一个事务中,既有生产消息操作又有消费消息操作,即常说的 Consume-tansform-produce 模式。如下实例代码 @@ -664,7 +664,7 @@ producer 提供了 initTransactions, beginTransaction, sendOffsets, commitTransa } ``` -#### 只有读 +#### 8.4.4. 只有读 创建一个事务,在这个事务操作中,只有生成消息操作,如下代码。这种操作其实没有什么意义,跟使用手动提交效果一样,无法保证消费消息操作和提交偏移量操作在一个事务。 @@ -718,9 +718,9 @@ producer 提供了 initTransactions, beginTransaction, sendOffsets, commitTransa ### 8.5. 生产者事务的实现 -#### 相关配置 +#### 8.5.1. 相关配置 -#### 幂等性和事务性的关系 +#### 8.5.2. 幂等性和事务性的关系 ##### 两者关系 @@ -770,7 +770,7 @@ org.apache.kafka.common.errors.ProducerFencedException: Producer attempted an op - 跨 Session 的数据幂等发送。当具有相同 Transaction ID 的新的 Producer 实例被创建且工作时,旧的且拥有相同 Transaction ID 的 Producer 将不再工作【上面的实例可以验证】。kafka 保证了关联同一个事务的所有 producer(一个应用有多个实例)必须按照顺序初始化事务、和提交事务,否则就会有问题,这保证了同一事务 ID 中消息是有序的(不同实例得按顺序创建事务和提交事务)。 -#### 事务最佳实践-单实例的事务性 +#### 8.5.3. 事务最佳实践-单实例的事务性 通过上面实例中可以看到 kafka 是跨 Session 的数据幂等发送,即如果应用部署多个实例时常会遇到上面的问题“_org.apache.kafka.common.errors.ProducerFencedException: Producer attempted an operation with an old epoch. Either there is a newer producer with the same transactionalId, or the producer’s transaction has been expired by the broker_.”,必须保证这些实例生产者的提交事务顺序和创建顺序保持一致才可以,否则就无法成功。其实,在实践中,我们更多的是**如何实现对应用单实例的事务性**。可以通过 spring-kafaka 实现思路来学习,即**每次创建生产者都设置一个不同的 transactionId 的值**,如下代码: @@ -832,7 +832,7 @@ protected Producer createTransactionalProducer() { } ``` -#### Consume-transform-Produce 的流程 +#### 8.5.4. Consume-transform-Produce 的流程 ![Snip20180504_56](http://www.heartthinkdo.com/wp-content/uploads/2018/05/Snip20180504_56.png) @@ -969,7 +969,7 @@ Transaction Log 如下放置在“\_tranaction_state”主题下面,默认是 ![57646045](http://www.heartthinkdo.com/wp-content/uploads/2018/05/57646045.png) -#### 消费读取事务消息(READ_COMMITED) +#### 8.5.5. 消费读取事务消息(READ_COMMITED) Consumer 为了实现事务,新增了一个 isolation.level 配置,有两个值如下, @@ -1141,4 +1141,4 @@ Broker 在缓存中维护了所有处于运行状态的事务对应的 initial o - [Kafka Manager](https://github.com/yahoo/kafka-manager) - Kafka 管理工具 - [Kafka 剖析(一):Kafka 背景及架构介绍](http://www.infoq.com/cn/articles/kafka-analysis-part-1) - [Thorough Introduction to Apache Kafka](https://hackernoon.com/thorough-introduction-to-apache-kafka-6fbf2989bbc1) -- [Kafak(04) Kafka生产者事务和幂等](http://www.heartthinkdo.com/?p=2040#43) \ No newline at end of file +- [Kafak(04) Kafka 生产者事务和幂等](http://www.heartthinkdo.com/?p=2040#43) diff --git a/docs/distributed/mq/kafka-basics.md b/docs/distributed/mq/kafka-basics.md index facb9737..d1c4b979 100644 --- a/docs/distributed/mq/kafka-basics.md +++ b/docs/distributed/mq/kafka-basics.md @@ -2,12 +2,12 @@ title: Kafka 实战篇 date: 2018/07/12 categories: -- 分布式 + - 分布式 tags: -- java -- javaweb -- 分布式 -- mq + - java + - javaweb + - 分布式 + - mq --- # Kafka 实战篇 @@ -64,7 +64,7 @@ Kafka 是一个分布式的、可水平扩展的、基于发布/订阅模式的 ### 1.2. 系统结构和存储结构 -#### 系统结构 +#### 1.2.1. 系统结构 producer 采用 push 方式向 broker 发送消息;customer 采用 pull 方式从 broker 接受消息。 @@ -77,7 +77,7 @@ producer 采用 push 方式向 broker 发送消息;customer 采用 pull 方式 -#### Topic 的存储结构 +#### 1.2.2. Topic 的存储结构
@@ -88,7 +88,7 @@ producer 采用 push 方式向 broker 发送消息;customer 采用 pull 方式 - **Segment** - 是 Partition 目录下的文件,保存存储消息。 - **索引** - 方便查询 segment -#### Segment 文件格式 +#### 1.2.3. Segment 文件格式 可以把 topic 当做一个数据表,表中每一个记录都是 key,value 形式,如下图。 @@ -108,13 +108,13 @@ producer 采用 push 方式向 broker 发送消息;customer 采用 pull 方式 最新版本保存在 kafka 中,对应的主题是\_consumer_offsets。老版本是在 zookeeper 中。 -**(3)假设某一个消息处理业务逻辑失败了。是否还可以继续想下执行?如果可以的话,那么此时怎么保证这个消息还会继续被处理呢?** +**(3)假设某一个消息处理业务逻辑失败了。是否还可以继续向下执行?如果可以的话,那么此时怎么保证这个消息还会继续被处理呢?** 答案是:正常情况下无法再处理有问题的消息。 这里举一个例子,如 M1->M2->M3->M4,假设第一次 poll 时,得到 M1 和 M2,M1 处理成功,M2 处理失败,我们采用提交方式为处理一个消息就提交一次,此时我们提交偏移量是 offset1,但是当我们第二次执行 poll 时,此时只会获取到 M3 和 M4,因为 poll 的时候是根据本地偏移量来获取的,不是 kafka 中保存的初始偏移量。解决这个问题方法是通过 seek 操作定位到 M2 的位置,此时再执行 poll 时就会获取到 M2 和 M3。 -**(4)当一个消费者执行了 close 之后,此时会执行再均衡,那么在均衡是在哪里发生的呢?其他同组的消费者如何感知到?** +**(4)当一个消费者执行了 close 之后,此时会执行再均衡,那么再均衡是在哪里发生的呢?其他同组的消费者如何感知到?** 是通过群组中成为群主的消费者执行再均衡,执行完毕之后,通过群组协调器把每一个消费者负责分区信息发送给消费者,每一个消费者只能知道它负责的分区信息。 @@ -298,7 +298,7 @@ Stream API 的 maven 依赖: ### 4.2. 发送消息方式 -#### 发送并忘记(fire-and-forget) +#### 4.2.1. 发送并忘记(fire-and-forget) 代码如下,直接通过 send 方法来发送 @@ -312,7 +312,7 @@ ProducerRecord record = } ``` -#### 同步发送 +#### 4.2.2. 同步发送 代码如下,与“发送并忘记”的方式区别在于多了一个 get()方法,会一直阻塞等待 broker 返回结果: @@ -326,7 +326,7 @@ ProducerRecord record = } ``` -#### 异步发送 +#### 4.2.3. 异步发送 代码如下,异步方式相对于“发送并忘记”的方式的不同在于,在异步返回时可以执行一些操作,如记录错误或者成功日志。 @@ -351,7 +351,7 @@ ProducerRecord record = producer.send(record, new DemoProducerCallback()); ``` -#### 发送消息示例 +#### 4.2.4. 发送消息示例 ```java import java.util.Properties; @@ -404,7 +404,7 @@ public class ProducerDemo { ### 5.1. 消费者和消费者群组 -#### 消费者介绍 +#### 5.1.1. 消费者介绍 消费者以**pull 方式**从 broker 拉取消息,消费者可以订阅一个或多个主题,然后按照消息生成顺序(**kafka 只能保证分区中消息的顺序**)读取消息。 @@ -414,7 +414,7 @@ public class ProducerDemo {
-#### 消费者分区组 +#### 5.1.2. 消费者分区组 消费者群组可以实现并发的处理消息。一个消费者群组作为消费一个 topic 消息的单元,每一个 Partition 只能隶属于一个消费者群组中一个 customer,如下图 @@ -422,7 +422,7 @@ public class ProducerDemo { -#### 消费者区组的再均衡 +#### 5.1.3. 消费者区组的再均衡 当在群组里面 新增/移除消费者 或者 新增/移除 kafka 集群 broker 节点 时,群组协调器 Broker 会触发再均衡,重新为每一个 Partition 分配消费者。**再均衡期间,消费者无法读取消息,造成整个消费者群组一小段时间的不可用。** @@ -448,7 +448,7 @@ public class ProducerDemo { ### 5.2. 消费消息流程 -#### 消费流程 demo +#### 5.2.1. 消费流程 demo 具体步骤如下 @@ -504,7 +504,7 @@ public Consumer buildCustomer() { } ``` -#### 消费消息方式 +#### 5.2.2. 消费消息方式 分为订阅主题和指定分组两种方式: @@ -557,7 +557,7 @@ public void consumeMessageForIndependentConsumer(String topic){ } ``` -#### 轮询获取消息 +#### 5.2.3. 轮询获取消息 通过 poll 来获取消息,但是获取消息时并不是立刻返回结果,需要考虑两个因素: @@ -574,7 +574,7 @@ poll 处了获取消息外,还有其他作用,如下: ### 5.3. 提交偏移量 -#### 偏移量和提交 +#### 5.3.1. 偏移量和提交 (1)偏移量 @@ -624,7 +624,7 @@ b. 手动提交 除了自动提交,还可以进行手动提交,手动提交就是通过代码调用函数的方式提交,在使用手动提交时首先需要将 auto.commit.commit 设置为 false,目前有三种方式:同步提交、异步提交、同步和异步结合。 -#### 同步提交 +#### 5.3.2. 同步提交 可以通过 commitSync 来进行提交,**同步提交会一直提交直到成功**。如下 @@ -658,7 +658,7 @@ public void customerMessageWithSyncCommit(String topic) { } ``` -#### 异步提交 +#### 5.3.3. 异步提交 同步提交一个缺点是,在进行提交 commitAysnc()会阻塞整个下面流程。所以引入了异步提交 commitAsync(),如下代码,这里定义了 OffsetCommitCallback,也可以只进行 commitAsync(),不设置任何参数。 @@ -695,7 +695,7 @@ public void customerMessageWithAsyncCommit(String topic) { } ``` -#### 同步和异步提交 +#### 5.3.4. 同步和异步提交 代码如下: @@ -819,9 +819,9 @@ Zookeeper 保存的就是节点信息和节点状态,不会保存 kafka 的消 -#### 5.1. 1 broker +#### 7.1.1. broker -1、Topic 的注册信息 +(1)Topic 的注册信息 作用:在创建 zookeeper 时,注册 topic 的 Partition 信息,包括每一个分区的复制节点 id。 @@ -847,7 +847,7 @@ Example: } ``` -2、分区信息 +(2)分区信息 作用:记录分区信息,如分区的 leader 信息 @@ -880,7 +880,7 @@ Example: } ``` -3 broker 信息 +(3)broker 信息 作用:在 borker 启动时,向 zookeeper 注册节点信息 @@ -907,19 +907,19 @@ Example: } ``` -#### 5.1.2 controller 和 controller_epoch +#### 7.1.2. controller 和 controller_epoch -1、 控制器的 epoch: +(1)控制器的 epoch: /controller_epoch -> int (epoch) -2、控制器的注册信息: +(2)控制器的注册信息: /controller -> int (broker id of the controller) -#### 5.1.3 consumer +#### 7.1.3. consumer -1.消费者注册信息: +(1)消费者注册信息: 路径:/consumers/[groupId]/ids/[consumerId] @@ -958,9 +958,9 @@ A blacklist subscription: } ``` -#### 5.1.4 admin +#### 7.1.4. admin -\1. Re-assign partitions +(1)Re-assign partitions 路径:/admin/reassign_partitions @@ -1018,7 +1018,7 @@ Example: } ``` -\2. Preferred replication election +(2)Preferred replication election 路径:/admin/preferred_replica_election @@ -1074,13 +1074,13 @@ Example: } ``` -\3. Delete topics +(3)Delete topics /admin/delete_topics/[topic_to_be_deleted] (the value of the path in empty) -#### 5.1.5 config +#### 7.1.5. config -1 Topic Configuration +Topic Configuration /config/topics/[topic_name] From fdd06e6dc4d96aec15ce5bba58a07a53ee4238ae Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Thu, 25 Oct 2018 10:00:49 +0800 Subject: [PATCH 10/75] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E7=89=88=E6=9C=AC?= =?UTF-8?q?=EF=BC=9A=E4=BF=AE=E6=AD=A3=E5=AD=98=E5=9C=A8=E5=AE=89=E5=85=A8?= =?UTF-8?q?=E9=97=AE=E9=A2=98=E7=9A=84jar=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codes/javaee/filter/pom.xml | 2 +- codes/javaee/jsp/pom.xml | 2 +- codes/javaee/jstl/pom.xml | 2 +- codes/javaee/listener/pom.xml | 2 +- codes/javaee/oss/pom.xml | 6 +++--- codes/javaee/parent/pom.xml | 6 +++--- codes/javaee/servlet/pom.xml | 2 +- codes/javaee/session/pom.xml | 2 +- codes/javaee/taglib/pom.xml | 2 +- 9 files changed, 13 insertions(+), 13 deletions(-) diff --git a/codes/javaee/filter/pom.xml b/codes/javaee/filter/pom.xml index a748e82f..af038e2a 100644 --- a/codes/javaee/filter/pom.xml +++ b/codes/javaee/filter/pom.xml @@ -24,7 +24,7 @@ ${java.version} - 9.3.2.v20150730 + 9.4.12.v20180830 diff --git a/codes/javaee/jsp/pom.xml b/codes/javaee/jsp/pom.xml index eb12d448..8b2492d2 100644 --- a/codes/javaee/jsp/pom.xml +++ b/codes/javaee/jsp/pom.xml @@ -25,7 +25,7 @@ ${java.version} - 9.3.2.v20150730 + 9.4.12.v20180830 diff --git a/codes/javaee/jstl/pom.xml b/codes/javaee/jstl/pom.xml index 72783785..e395214a 100644 --- a/codes/javaee/jstl/pom.xml +++ b/codes/javaee/jstl/pom.xml @@ -25,7 +25,7 @@ ${java.version} - 9.3.2.v20150730 + 9.4.12.v20180830 diff --git a/codes/javaee/listener/pom.xml b/codes/javaee/listener/pom.xml index bf2c5117..73ceed34 100644 --- a/codes/javaee/listener/pom.xml +++ b/codes/javaee/listener/pom.xml @@ -24,7 +24,7 @@ ${java.version} - 9.3.2.v20150730 + 9.4.12.v20180830 diff --git a/codes/javaee/oss/pom.xml b/codes/javaee/oss/pom.xml index 4278f78a..38910f71 100644 --- a/codes/javaee/oss/pom.xml +++ b/codes/javaee/oss/pom.xml @@ -26,7 +26,7 @@ ${java.version} - 9.3.2.v20150730 + 9.4.12.v20180830 @@ -123,7 +123,7 @@ com.alibaba fastjson - 1.2.8 + 1.2.31 com.fasterxml.jackson.core @@ -133,7 +133,7 @@ com.fasterxml.jackson.core jackson-databind - 2.5.1 + 2.9.5 diff --git a/codes/javaee/parent/pom.xml b/codes/javaee/parent/pom.xml index c675bebe..a1f580d1 100644 --- a/codes/javaee/parent/pom.xml +++ b/codes/javaee/parent/pom.xml @@ -135,7 +135,7 @@ com.alibaba fastjson - 1.2.7 + 1.2.31 com.google.code.gson @@ -318,12 +318,12 @@ UTF-8 - 9.3.2.v20150730 + 9.4.12.v20180830 4.1.4.RELEASE 1.4.0 4.12 - 2.9.0 + 2.9.5 diff --git a/codes/javaee/servlet/pom.xml b/codes/javaee/servlet/pom.xml index e76194d3..cd316c59 100644 --- a/codes/javaee/servlet/pom.xml +++ b/codes/javaee/servlet/pom.xml @@ -25,7 +25,7 @@ ${java.version} - 9.3.2.v20150730 + 9.4.12.v20180830 diff --git a/codes/javaee/session/pom.xml b/codes/javaee/session/pom.xml index 4f7d81e6..78019c02 100644 --- a/codes/javaee/session/pom.xml +++ b/codes/javaee/session/pom.xml @@ -24,7 +24,7 @@ ${java.version} - 9.3.2.v20150730 + 9.4.12.v20180830 diff --git a/codes/javaee/taglib/pom.xml b/codes/javaee/taglib/pom.xml index ac265d5e..cbdac756 100644 --- a/codes/javaee/taglib/pom.xml +++ b/codes/javaee/taglib/pom.xml @@ -25,7 +25,7 @@ ${java.version} - 9.3.2.v20150730 + 9.4.12.v20180830 From 51c9c96e714ee631e309d2ac2ebcea9bb164bcac Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Sat, 29 Dec 2018 14:34:02 +0800 Subject: [PATCH 11/75] =?UTF-8?q?=E6=B6=88=E9=99=A4=20github=20=E5=AE=89?= =?UTF-8?q?=E5=85=A8=E5=91=8A=E8=AD=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codes/javaee/filter/pom.xml | 2 +- codes/javaee/jsp/pom.xml | 2 +- codes/javaee/jstl/pom.xml | 2 +- codes/javaee/listener/pom.xml | 2 +- codes/javaee/parent/pom.xml | 2 +- codes/javaee/servlet/pom.xml | 2 +- codes/javaee/session/pom.xml | 2 +- codes/javaee/taglib/pom.xml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/codes/javaee/filter/pom.xml b/codes/javaee/filter/pom.xml index af038e2a..261b3aa2 100644 --- a/codes/javaee/filter/pom.xml +++ b/codes/javaee/filter/pom.xml @@ -62,7 +62,7 @@ commons-fileupload commons-fileupload - 1.3.1 + 1.3.3 commons-io diff --git a/codes/javaee/jsp/pom.xml b/codes/javaee/jsp/pom.xml index 8b2492d2..e4c1a606 100644 --- a/codes/javaee/jsp/pom.xml +++ b/codes/javaee/jsp/pom.xml @@ -55,7 +55,7 @@ commons-fileupload commons-fileupload - 1.3.1 + 1.3.3 commons-io diff --git a/codes/javaee/jstl/pom.xml b/codes/javaee/jstl/pom.xml index e395214a..5b82b0bd 100644 --- a/codes/javaee/jstl/pom.xml +++ b/codes/javaee/jstl/pom.xml @@ -55,7 +55,7 @@ commons-fileupload commons-fileupload - 1.3.1 + 1.3.3 commons-io diff --git a/codes/javaee/listener/pom.xml b/codes/javaee/listener/pom.xml index 73ceed34..022a5a5a 100644 --- a/codes/javaee/listener/pom.xml +++ b/codes/javaee/listener/pom.xml @@ -54,7 +54,7 @@ commons-fileupload commons-fileupload - 1.3.1 + 1.3.3 commons-io diff --git a/codes/javaee/parent/pom.xml b/codes/javaee/parent/pom.xml index a1f580d1..9983883c 100644 --- a/codes/javaee/parent/pom.xml +++ b/codes/javaee/parent/pom.xml @@ -283,7 +283,7 @@ commons-fileupload commons-fileupload - 1.3.1 + 1.3.3 commons-io diff --git a/codes/javaee/servlet/pom.xml b/codes/javaee/servlet/pom.xml index cd316c59..58a1357d 100644 --- a/codes/javaee/servlet/pom.xml +++ b/codes/javaee/servlet/pom.xml @@ -89,7 +89,7 @@ commons-fileupload commons-fileupload - 1.3.1 + 1.3.3 diff --git a/codes/javaee/session/pom.xml b/codes/javaee/session/pom.xml index 78019c02..82313bcf 100644 --- a/codes/javaee/session/pom.xml +++ b/codes/javaee/session/pom.xml @@ -54,7 +54,7 @@ commons-fileupload commons-fileupload - 1.3.1 + 1.3.3 commons-io diff --git a/codes/javaee/taglib/pom.xml b/codes/javaee/taglib/pom.xml index cbdac756..332a5a20 100644 --- a/codes/javaee/taglib/pom.xml +++ b/codes/javaee/taglib/pom.xml @@ -55,7 +55,7 @@ commons-fileupload commons-fileupload - 1.3.1 + 1.3.3 commons-io From 9c561d1aab6e2b51cb12b32c2457a8fa42c58d05 Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Mon, 14 Jan 2019 18:09:44 +0800 Subject: [PATCH 12/75] update docs --- README.md | 43 +++-- .../mq/{ => kafka}/kafka-advanced.md | 0 .../mq/{ => kafka}/kafka-basics.md | 0 .../distributed/mq/kafka/kafka-cheat-sheet.md | 154 ++++++++++++++++++ 4 files changed, 184 insertions(+), 13 deletions(-) rename docs/distributed/mq/{ => kafka}/kafka-advanced.md (100%) rename docs/distributed/mq/{ => kafka}/kafka-basics.md (100%) create mode 100644 docs/distributed/mq/kafka/kafka-cheat-sheet.md diff --git a/README.md b/README.md index 89d15f73..d3fc6f8a 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,19 @@ > Java Web 开发之路经验总结 -| :recycle: | :one: | :two: | :three: | :four: | -| --------------------- | --------------------- | ----------------------------- | ------------------------------- | ------------------ | -| [架构设计](#recycle-架构设计) | [JavaEE](#one-javaee) | [单点式技术](#two-单点式技术) | [分布式技术](#three-分布式技术) | [工具](#four-工具) | +| :beginner: | :recycle: | :one: | :two: | :three: | :four: | +| ---------------------- | ----------------------------- | --------------------- | ----------------------------- | ------------------------------- | ------------------ | +| [准备](#beginner-准备) | [架构设计](#recycle-架构设计) | [JavaEE](#one-javaee) | [单点式技术](#two-单点式技术) | [分布式技术](#three-分布式技术) | [工具](#four-工具) | + +## :beginner: 准备 + +作为 Web 工程师,你应该多多少少掌握一些的知识: + +- [网络通信](https://github.com/dunwu/notes/tree/master/网络通信) - 你需要掌握一些网络通信协议知识。至少,你应该知道 HTTP、DNS 协议的工作机制。 +- [前端技术指南](https://github.com/dunwu/frontend-tutorial) - 即使是后端工程师,也难免会接触到前端技术。前端技术五花八门,如:React、Vue、Angular、Webpack、ES6、Babel、Node.js 等等。不说掌握,至少也应该知道这些技术是什么。 + ## :recycle: 架构设计 > [架构设计](docs/architecture/) 整理架构设计方面的一些学习总结和心得。 @@ -27,13 +35,19 @@ > [单点式技术(Standalone)](docs/standalone/),典型的技术如:SSM 框架、SSH 框架。 -- Platform - - [Spring](https://github.com/dunwu/spring-notes) - JavaSE/JavaEE 一站式开发框架。 -- ORM - - [Mybatis](docs/standalone/orm/mybatis.md) - 一个支持普通 SQL 查询,存储过程和高级映射的优秀持久层框架。 - - Hibernate - 待补充。。。 -- 安全 - - [Shiro](docs/standalone/security/shiro.md) - 安全框架,具有认证、授权、加密、会话管理功能。 +### Platform + +- [Spring](https://github.com/dunwu/spring-notes) - JavaSE/JavaEE 一站式开发框架。 +- [Spring Boot](https://github.com/dunwu/spring-boot-tutorial) - 简化*Spring*应用的初始搭建以及开发过程。 + +### ORM + +- [Mybatis](docs/standalone/orm/mybatis.md) - 一个支持普通 SQL 查询,存储过程和高级映射的优秀持久层框架。 +- Hibernate - 待补充。。。 + +### 安全 + +- [Shiro](docs/standalone/security/shiro.md) - 安全框架,具有认证、授权、加密、会话管理功能。 ## :three: 分布式技术 @@ -55,9 +69,12 @@ ### [分布式消息队列(MQ)](docs/distributed/mq) -- [分布式消息队列](docs/distributed/mq/分布式消息队列.md) -- [Kafka 实战篇](docs/distributed/mq/kafka-basics.md) -- [Kafka 原理篇](docs/distributed/mq/kafka-advanced.md) +- 原理 + - [分布式消息队列](docs/distributed/mq/分布式消息队列.md) +- Kafka + - [Kafka 实战篇](docs/distributed/mq/kafka/kafka-basics.md) + - [Kafka 原理篇](docs/distributed/mq/kafka/kafka-advanced.md) + - [Kafka Cheat Sheet](docs/distributed/mq/kafka/kafka-cheat-sheet.md) - [RocketMQ 实战篇](docs/distributed/mq/rocketmq-basics.md) - [RocketMQ 原理篇](docs/distributed/mq/rocketmq-basics.md) - [ActiveMQ 实战篇](docs/distributed/mq/ActiveMQ.md) diff --git a/docs/distributed/mq/kafka-advanced.md b/docs/distributed/mq/kafka/kafka-advanced.md similarity index 100% rename from docs/distributed/mq/kafka-advanced.md rename to docs/distributed/mq/kafka/kafka-advanced.md diff --git a/docs/distributed/mq/kafka-basics.md b/docs/distributed/mq/kafka/kafka-basics.md similarity index 100% rename from docs/distributed/mq/kafka-basics.md rename to docs/distributed/mq/kafka/kafka-basics.md diff --git a/docs/distributed/mq/kafka/kafka-cheat-sheet.md b/docs/distributed/mq/kafka/kafka-cheat-sheet.md new file mode 100644 index 00000000..7222c18a --- /dev/null +++ b/docs/distributed/mq/kafka/kafka-cheat-sheet.md @@ -0,0 +1,154 @@ +# Kafka Cheat Sheet + +## 安装 + +Run [landoop/fast-data-dev](https://github.com/Landoop/fast-data-dev) docker image. For example: + +``` +docker run --rm -it -p 2181:2181 -p 3030:3030 -p 8081:8081 -p 8082:8082 -p 8083:8083 -p 9092:9092 -p 9581:9581 -p 9582:9582 -p 9583:9583 -p 9584:9584 -e ADV_HOST=127.0.0.1 landoop/fast-data-dev:latest +``` +_Note: Follow the instructions on fast-data-dev README to customise the container._ + +Enter the container bash: + +``` +docker run --rm -it --net=host landoop/fast-data-dev bash +``` + +Kafka utilities are now available: + +## Topic + +创建 Topic +``` +kafka-topics --create --zookeeper localhost:2181 --replication-factor 1 --partitions 3 --topic my-topic +``` +查看所有 Topic +``` +kafka-topics --list --zookeeper localhost:2181 +``` +添加 Partition +``` +kafka-topics --zookeeper localhost:2181 --alter --topic my-topic --partitions 16 +``` +删除 Topic +``` +kafka-topics --zookeeper localhost:2181 --delete --topic my-topic +``` +查看 Topic 详细信息 +``` +kafka-topics --zookeeper localhost:2181/kafka-cluster --describe +``` +Show Under-replicated Partitions for topics +``` +kafka-topics --zookeeper localhost:2181/kafka-cluster --describe --under-replicated-partitions +``` + +## Producers +通过控制台向 Kafka Topic 输入生产消息 + +``` +kafka-console-producer --broker-list localhost:9092 --topic my-topic +``` +通过文件输入向 Kafka Topic 生产消息 + +``` +kafka-console-producer --broker-list localhost:9092 --topic test < messages.txt +``` +通过控制台向 Kafka Topic 输入 Avro 生产消息 +``` +kafka-avro-console-producer --broker-list localhost:9092 --topic my.Topic --property value.schema='{"type":"record","name":"myrecord","fields":[{"name":"f1","type":"string"}]}' --property schema.registry.url=http://localhost:8081 +``` +然后,可以选择输入部分 json key: +``` +{"f1": "value1"} +``` + +## Consumers + +### Consume messages + +启动消费者,读取历史未消费的数据 + +``` +kafka-console-consumer --bootstrap-server localhost:9092 --topic my-topic --from-beginning +``` +消费 1 条消息: +``` +kafka-console-consumer --bootstrap-server localhost:9092 --topic my-topic --max-messages 1 +``` + +从指定的 offset `__consumer_offsets` 消费一条消息: +``` +kafka-console-consumer --bootstrap-server localhost:9092 --topic __consumer_offsets --formatter 'kafka.coordinator.GroupMetadataManager$OffsetsMessageFormatter' --max-messages 1 +``` + +指定 Group 消费消息: + +``` +kafka-console-consumer --topic my-topic --new-consumer --bootstrap-server localhost:9092 --consumer-property group.id=my-group +``` + +### Consume Avro messages +``` +kafka-avro-console-consumer --topic position-reports --new-consumer --bootstrap-server localhost:9092 --from-beginning --property schema.registry.url=localhost:8081 --max-messages 10 +``` + +``` +kafka-avro-console-consumer --topic position-reports --new-consumer --bootstrap-server localhost:9092 --from-beginning --property schema.registry.url=localhost:8081 +``` + +### Consumers admin operations + +查看 Group +``` +kafka-consumer-groups --new-consumer --list --bootstrap-server localhost:9092 +``` +查看 Group 详细信息 +``` +kafka-consumer-groups --bootstrap-server localhost:9092 --describe --group testgroup +``` + +## Config + +设置 Topic 的保留时间 + +``` +kafka-configs --zookeeper localhost:2181 --alter --entity-type topics --entity-name my-topic --add-config retention.ms=3600000 +``` +展示一个 Topic 下的所有配置: + +``` +kafka-configs --zookeeper localhost:2181 --describe --entity-type topics --entity-name my-topic +``` +修改一条 Topic 的配置 + +``` +kafka-configs --zookeeper localhost:2181 --alter --entity-type topics --entity-name my-topic --delete-config retention.ms +``` + +## Performance + +Producer +``` +kafka-producer-perf-test --topic position-reports --throughput 10000 --record-size 300 --num-records 20000 --producer-props bootstrap.servers="localhost:9092" +``` + +## ACLs +``` +kafka-acls --authorizer-properties zookeeper.connect=localhost:2181 --add --allow-principal User:Bob --consumer --topic topicA --group groupA +``` + +``` +kafka-acls --authorizer-properties zookeeper.connect=localhost:2181 --add --allow-principal User:Bob --producer --topic topicA +``` +List the ACLs +``` + kafka-acls --authorizer-properties zookeeper.connect=localhost:2181 --list --topic topicA +``` + +## Zookeeper +zookeepr shell: +``` +zookeeper-shell localhost:2182 ls / +``` From 04e92aa538732dbae05253b02c08c1f29ccf8fd0 Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Fri, 24 May 2019 13:55:20 +0800 Subject: [PATCH 13/75] =?UTF-8?q?=E6=95=B4=E7=90=86=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codes/{javaee => javaee-tutorial}/README.md | 0 .../javaee-tutorial-filter}/pom.xml | 67 +++---- .../dunwu/javaee/filter/CacheFilter.java | 0 .../filter/CharacterEncodingFilter.java | 0 .../javaee/filter/ExceptionHandlerFilter.java | 0 .../dunwu/javaee/filter/FilterImpl.java | 0 .../dunwu/javaee/filter/GZipFilter.java | 0 .../javaee/filter/ImageRedirectFilter.java | 0 .../github/dunwu/javaee/filter/LogFilter.java | 0 .../github/dunwu/javaee/filter/MyFilter.java | 0 .../javaee/filter/OutputReplaceFilter.java | 0 .../dunwu/javaee/filter/PrivilegeFilter.java | 0 .../dunwu/javaee/filter/UploadFilter.java | 0 .../dunwu/javaee/filter/WaterMarkFilter.java | 0 .../dunwu/javaee/filter/XSLTFilter.java | 0 .../filter/exception/AccountException.java | 0 .../filter/exception/BusinessException.java | 0 .../dunwu/javaee/filter/test/Download.java | 0 .../dunwu/javaee/filter/test/GZipTest.java | 0 .../filter/wrapper/CacheResponseWrapper.java | 0 .../filter/wrapper/GZipOutputStream.java | 0 .../filter/wrapper/GZipResponseWrapper.java | 0 .../wrapper/HttpCharacterResponseWrapper.java | 0 .../filter/wrapper/UploadRequestWrapper.java | 0 .../filter/wrapper/WaterMarkOutputStream.java | 0 .../wrapper/WaterMarkResponseWrapper.java | 0 .../src/main/resources/logback.xml | 0 .../src/main/webapp/META-INF/MANIFEST.MF | 0 .../src/main/webapp/WEB-INF/logo.png | Bin .../main/webapp/WEB-INF/privilege.properties | 0 .../main/webapp/WEB-INF/sensitive.properties | 0 .../src/main/webapp/WEB-INF/web.xml | 0 .../src/main/webapp/views/images/error.gif | Bin .../src/main/webapp/views/images/mm.jpg | Bin .../src/main/webapp/views/images/sunset.jpg | Bin .../src/main/webapp/views/images/winter.jpg | Bin .../src/main/webapp/views/js/dojo.js | 0 .../webapp/views/jsp/accountException.jsp | 0 .../webapp/views/jsp/businessException.jsp | 0 .../src/main/webapp/views/jsp/dispatcher.jsp | 0 .../src/main/webapp/views/jsp/exception.jsp | 0 .../src/main/webapp/views/jsp/image.jsp | 0 .../src/main/webapp/views/jsp/index.jsp | 0 .../main/webapp/views/jsp/testCacheFilter.jsp | 0 .../views/jsp/testCharacterEncodingFilter.jsp | 0 .../views/jsp/testExceptionHandlerFilter.jsp | 0 .../main/webapp/views/jsp/testGZipFilter.jsp | 0 .../views/jsp/testOutputReplaceFilter.jsp | 0 .../webapp/views/jsp/testUploadFilter.jsp | 0 .../webapp/views/jsp/testWaterMarkFilter.jsp | 0 .../src/main/webapp/views/xml/book.xml | 0 .../src/main/webapp/views/xml/book.xsl | 0 .../src/main/webapp/views/xml/demo.xml | 0 .../src/main/webapp/views/xml/messageLog.xsl | Bin .../src/main/webapp/views/xml/thinkInJava.xml | 0 .../src/main/webapp/views/xml/xml.xsl | 0 .../dunwu/javaee/server/JettyFactory.java | 0 .../github/dunwu/javaee/server/Profiles.java | 0 .../dunwu/javaee/server/QuickStartServer.java | 0 .../src/test/resources/jetty/webdefault.xml | 0 .../javaee-tutorial-jsp/pom.xml | 96 +++++++++ .../com/sun/products/applet/demo/Graph.java | 0 .../github/dunwu/javaee/jsp/bean/Counter.java | 0 .../github/dunwu/javaee/jsp/bean/Message.java | 0 .../github/dunwu/javaee/jsp/bean/Person.java | 0 .../github/dunwu/javaee/jsp/util/IpUtil.java | 0 .../dunwu/javaee/jsp/util/ip/IPEntry.java | 0 .../dunwu/javaee/jsp/util/ip/IPSeeker.java | 0 .../github/dunwu/javaee/jsp/util/ip/QQWry.dat | Bin .../github/dunwu/javaee/jsp/util/ip/Test.java | 0 .../dunwu/javaee/jsp/util/ip/Utils.java | 0 .../src/main/resources/logback.xml | 0 .../src/main/webapp/META-INF/MANIFEST.MF | 0 .../main/webapp/WEB-INF/META-INF/MANIFEST.MF | 0 .../webapp/WEB-INF/resources/jsp/index.jsp | 0 .../src/main/webapp/WEB-INF/web.xml | 0 .../main/webapp/examples/02.grammar/break.jsp | 0 .../main/webapp/examples/02.grammar/for.jsp | 0 .../main/webapp/examples/02.grammar/if.jsp | 0 .../main/webapp/examples/02.grammar/if2.jsp | 0 .../webapp/examples/02.grammar/scriptlet.jsp | 0 .../webapp/examples/02.grammar/switch.jsp | 0 .../main/webapp/examples/02.grammar/while.jsp | 0 .../webapp/examples/03.directive/foot.jsp | 0 .../webapp/examples/03.directive/head.jsp | 0 .../webapp/examples/03.directive/include.jsp | 0 .../webapp/examples/03.directive/page.jsp | 0 .../main/webapp/examples/04.action/date.jsp | 0 .../webapp/examples/04.action/jspforward.jsp | 0 .../webapp/examples/04.action/jspinclude.jsp | 0 .../webapp/examples/04.action/jspplugin.jsp | 0 .../webapp/examples/04.action/jspuseBean.jsp | 0 .../webapp/examples/04.action/jspuseBean2.jsp | 0 .../webapp/examples/04.action/useBean.html | 0 .../examples/05.implicit_object/error.jsp | 0 .../examples/05.implicit_object/exception.jsp | 0 .../webapp/examples/cookie/readCookie.jsp | 0 .../main/webapp/examples/cookie/session.jsp | 0 .../webapp/examples/cookie/writeCookie.jsp | 0 .../main/webapp/examples/locale/language.jsp | 0 .../main/webapp/examples/locale/language2.jsp | 0 .../webapp/examples/locale/localeCurrency.jsp | 0 .../webapp/examples/locale/localeDate.jsp | 0 .../webapp/examples/locale/localePercent.jsp | 0 .../main/webapp/examples/practice/date.jsp | 0 .../examples/practice/form/checkbox.html | 0 .../examples/practice/form/checkbox.jsp | 0 .../examples/practice/form/checkbox2.html | 0 .../examples/practice/form/checkbox2.jsp | 0 .../examples/practice/form/formGet.html | 0 .../webapp/examples/practice/form/formGet.jsp | 0 .../examples/practice/form/formPost.html | 0 .../examples/practice/form/formPost.jsp | 0 .../webapp/examples/practice/hitCount.jsp | 0 .../examples/practice/mail/sendMail.jsp | 0 .../webapp/examples/practice/redirect.jsp | 0 .../main/webapp/examples/practice/refresh.jsp | 0 .../webapp/examples/uncheck/01.helloWorld.jsp | 0 .../examples/uncheck/01.helloWorld_zh.jsp | 0 .../main/webapp/examples/uncheck/02.life.jsp | 0 .../webapp/examples/uncheck/03.yourIp.jsp | 0 .../webapp/examples/uncheck/configuration.jsp | 0 .../src/main/webapp/images/bg-btn-blue.gif | Bin .../src/main/webapp/images/mail.gif | Bin .../src/main/webapp/images/vertical_line.gif | Bin .../src/main/webapp/views/css/style.css | 0 .../src/main/webapp/views/jsp/action.jsp | 0 .../src/main/webapp/views/jsp/action/date.jsp | 0 .../main/webapp/views/jsp/action/forward.jsp | 0 .../webapp/views/jsp/action/getProperty.jsp | 0 .../main/webapp/views/jsp/action/include.jsp | 0 .../main/webapp/views/jsp/action/useBean.html | 0 .../main/webapp/views/jsp/action/useBean.jsp | 0 .../src/main/webapp/views/jsp/application.jsp | 0 .../src/main/webapp/views/jsp/break.jsp | 0 .../src/main/webapp/views/jsp/comment.jsp | 0 .../src/main/webapp/views/jsp/counter.jsp | 0 .../src/main/webapp/views/jsp/directive.jsp | 0 .../views/jsp/directive/include/foot.jsp | 0 .../views/jsp/directive/include/head.jsp | 0 .../views/jsp/directive/include/include.jsp | 0 .../views/jsp/directive/page/contentType.jsp | 0 .../views/jsp/directive/page/errorPage.jsp | 0 .../views/jsp/directive/page/isErrorPage.jsp | 0 .../src/main/webapp/views/jsp/el.jsp | 0 .../src/main/webapp/views/jsp/error.jsp | 0 .../src/main/webapp/views/jsp/exception.jsp | 0 .../src/main/webapp/views/jsp/grammar/for.jsp | 0 .../src/main/webapp/views/jsp/grammar/if.jsp | 0 .../main/webapp/views/jsp/grammar/while.jsp | 0 .../src/main/webapp/views/jsp/greeting.jsp | 0 .../src/main/webapp/views/jsp/index.jsp | 0 .../src/main/webapp/views/jsp/life.jsp | 0 .../src/main/webapp/views/jsp/method.jsp | 0 .../src/main/webapp/views/jsp/plugin.jsp | 0 .../src/main/webapp/views/jsp/return.jsp | 0 .../src/main/webapp/views/jsp/scriptlet.jsp | 0 .../src/main/webapp/views/jsp/taglib.jsp | 0 .../dunwu/javaee/server/JettyFactory.java | 0 .../github/dunwu/javaee/server/Profiles.java | 0 .../dunwu/javaee/server/QuickStartServer.java | 0 .../src/test/resources/jetty/webdefault.xml | 0 .../src/test/resources/logback.xml | 0 .../javaee-tutorial-jstl/pom.xml | 122 ++++++++++++ .../io/github/dunwu/javaee/bean/Person.java | 0 .../io/github/dunwu/javaee/util/Example.java | 0 .../dunwu/javaee/util/Example_es_ES.java | 0 .../github/dunwu/javaee/util/Pagination.java | 0 .../src/main/resources/init.sql | 0 .../src/main/resources/logback.xml | 0 .../src/main/resources/messages.properties | 0 .../main/resources/messages_zh_CN.properties | 0 .../main/resources/sql/create_employees.sql | 0 .../main/resources/sql/create_students.sql | 0 .../src/main/webapp/WEB-INF/web.xml | 0 .../src/main/webapp/examples/core/c_catch.jsp | 0 .../main/webapp/examples/core/c_choose.jsp | 0 .../main/webapp/examples/core/c_forEach.jsp | 0 .../main/webapp/examples/core/c_forTokens.jsp | 0 .../src/main/webapp/examples/core/c_if.jsp | 0 .../main/webapp/examples/core/c_import.jsp | 0 .../src/main/webapp/examples/core/c_out.jsp | 0 .../src/main/webapp/examples/core/c_param.jsp | 0 .../main/webapp/examples/core/c_redirect.jsp | 0 .../main/webapp/examples/core/c_remove.jsp | 0 .../src/main/webapp/examples/core/c_set.jsp | 0 .../src/main/webapp/examples/core/c_url.jsp | 0 .../main/webapp/examples/fmt/fmt_bundle.jsp | 0 .../webapp/examples/fmt/fmt_formatDate.jsp | 0 .../webapp/examples/fmt/fmt_formatNumber.jsp | 0 .../main/webapp/examples/fmt/fmt_message.jsp | 0 .../webapp/examples/fmt/fmt_parseDate.jsp | 0 .../webapp/examples/fmt/fmt_parseNumber.jsp | 0 .../examples/fmt/fmt_requestEncoding.jsp | 0 .../webapp/examples/fmt/fmt_setLocale.jsp | 0 .../webapp/examples/fmt/fmt_setTimeZone.jsp | 0 .../main/webapp/examples/fmt/fmt_timeZone.jsp | 0 .../webapp/examples/function/fn_contains.jsp | 0 .../function/fn_containsIgnoreCase.jsp | 0 .../webapp/examples/function/fn_endsWith.jsp | 0 .../webapp/examples/function/fn_escapeXml.jsp | 0 .../webapp/examples/function/fn_indexOf.jsp | 0 .../main/webapp/examples/function/fn_join.jsp | 0 .../webapp/examples/function/fn_length.jsp | 0 .../webapp/examples/function/fn_replace.jsp | 0 .../webapp/examples/function/fn_split.jsp | 0 .../examples/function/fn_startsWith.jsp | 0 .../webapp/examples/function/fn_substring.jsp | 0 .../examples/function/fn_substringAfter.jsp | 0 .../examples/function/fn_substringBefore.jsp | 0 .../examples/function/fn_toLowerCase.jsp | 0 .../examples/function/fn_toUpperCase.jsp | 0 .../main/webapp/examples/function/fn_trim.jsp | 0 .../webapp/examples/sql/sql_dateParam.jsp | 0 .../main/webapp/examples/sql/sql_param.jsp | 0 .../main/webapp/examples/sql/sql_query.jsp | 0 .../webapp/examples/sql/sql_setDataSource.jsp | 0 .../webapp/examples/sql/sql_transaction.jsp | 0 .../main/webapp/examples/sql/sql_update.jsp | 0 .../main/webapp/examples/uncheck/bundle.jsp | 0 .../main/webapp/examples/uncheck/catch.jsp | 0 .../main/webapp/examples/uncheck/choose.jsp | 0 .../main/webapp/examples/uncheck/contains.jsp | 0 .../examples/uncheck/containsIgnoreCase.jsp | 0 .../webapp/examples/uncheck/dateParam.jsp | 0 .../main/webapp/examples/uncheck/endsWith.jsp | 0 .../webapp/examples/uncheck/escapeXml.jsp | 0 .../src/main/webapp/examples/uncheck/fmt.jsp | 0 .../src/main/webapp/examples/uncheck/fn.jsp | 0 .../main/webapp/examples/uncheck/forEach.jsp | 0 .../examples/uncheck/forEachWithList.jsp | 0 .../webapp/examples/uncheck/forTokens.jsp | 0 .../webapp/examples/uncheck/formatDate.jsp | 0 .../webapp/examples/uncheck/formatNumber.jsp | 0 .../src/main/webapp/examples/uncheck/if.jsp | 0 .../main/webapp/examples/uncheck/import.jsp | 0 .../main/webapp/examples/uncheck/index.jsp | 0 .../main/webapp/examples/uncheck/indexOf.jsp | 0 .../src/main/webapp/examples/uncheck/join.jsp | 0 .../main/webapp/examples/uncheck/length.jsp | 0 .../src/main/webapp/examples/uncheck/out.jsp | 0 .../main/webapp/examples/uncheck/param.jsp | 0 .../main/webapp/examples/uncheck/parse.jsp | 0 .../webapp/examples/uncheck/parseDate.jsp | 0 .../webapp/examples/uncheck/parseNumber.jsp | 0 .../main/webapp/examples/uncheck/query.jsp | 0 .../examples/uncheck/queryPagination.jsp | 0 .../webapp/examples/uncheck/queryReflect.jsp | 0 .../main/webapp/examples/uncheck/redirect.jsp | 0 .../main/webapp/examples/uncheck/remove.jsp | 0 .../examples/uncheck/requestEncoding.jsp | 0 .../src/main/webapp/examples/uncheck/set.jsp | 0 .../webapp/examples/uncheck/setBundle.jsp | 0 .../webapp/examples/uncheck/setDataSource.jsp | 0 .../webapp/examples/uncheck/setLocale.jsp | 0 .../webapp/examples/uncheck/setTimeZone.jsp | 0 .../src/main/webapp/examples/uncheck/sina.xml | 0 .../main/webapp/examples/uncheck/split.jsp | 0 .../src/main/webapp/examples/uncheck/sql.jsp | 0 .../webapp/examples/uncheck/substring.jsp | 0 .../main/webapp/examples/uncheck/timeZone.jsp | 0 .../webapp/examples/uncheck/transaction.jsp | 0 .../main/webapp/examples/uncheck/update.jsp | 0 .../src/main/webapp/examples/uncheck/url.jsp | 0 .../main/webapp/examples/uncheck/x_choose.jsp | 0 .../webapp/examples/uncheck/x_forEach.jsp | 0 .../src/main/webapp/examples/uncheck/x_if.jsp | 0 .../main/webapp/examples/uncheck/x_out.jsp | 0 .../main/webapp/examples/uncheck/x_set.jsp | 0 .../webapp/examples/uncheck/x_transform.jsp | 0 .../src/main/webapp/examples/uncheck/xml.jsp | 0 .../src/main/webapp/examples/xml/x_choose.jsp | 0 .../main/webapp/examples/xml/x_forEach.jsp | 0 .../src/main/webapp/examples/xml/x_if.jsp | 0 .../src/main/webapp/examples/xml/x_out.jsp | 0 .../src/main/webapp/examples/xml/x_param.jsp | 0 .../src/main/webapp/examples/xml/x_parse.jsp | 0 .../src/main/webapp/examples/xml/x_set.jsp | 0 .../main/webapp/examples/xml/x_transform.jsp | 0 .../src/main/webapp/xml/books.xml | 0 .../src/main/webapp/xml/style.xsl | 0 .../dunwu/javaee/server/JettyFactory.java | 0 .../github/dunwu/javaee/server/Profiles.java | 0 .../dunwu/javaee/server/QuickStartServer.java | 0 .../src/test/resources/jetty/webdefault.xml | 0 .../javaee-tutorial-listener}/pom.xml | 83 +++----- .../javaee/listener/LoginSessionListener.java | 0 .../MyHttpSessionActivationListener.java | 0 .../MyHttpSessionAttributeListener.java | 0 .../MyHttpSessionBindingListener.java | 0 .../listener/MyHttpSessionListener.java | 0 .../MyServletContextAttributeListener.java | 0 .../listener/MyServletContextListener.java | 0 .../MyServletRequestAttributeListener.java | 0 .../listener/MyServletRequestListener.java | 0 .../javaee/listener/PersonInfoListener.java | 0 .../SessionAttributeListenerTest.java | 0 .../javaee/listener/bean/PersonInfo.java | 0 .../listener/util/ApplicationConstants.java | 0 .../src/main/resources/logback.xml | 0 .../src/main/webapp/META-INF/MANIFEST.MF | 0 .../src/main/webapp/META-INF/context.xml | 0 .../webapp/WEB-INF/resources/jsp/index.jsp | 0 .../src/main/webapp/WEB-INF/web.xml | 0 .../src/main/webapp/a.gif | Bin .../src/main/webapp/aa.html | 0 .../src/main/webapp/active.jsp | 0 .../src/main/webapp/b.gif | Bin .../src/main/webapp/index.jsp | 0 .../src/main/webapp/listener.jsp | 0 .../src/main/webapp/online.jsp | 0 .../main/webapp/testLoginSessionListener.jsp | 0 .../dunwu/javaee/server/JettyFactory.java | 0 .../github/dunwu/javaee/server/Profiles.java | 0 .../dunwu/javaee/server/QuickStartServer.java | 0 .../src/test/resources/jetty/webdefault.xml | 0 .../javaee-tutorial-oss}/pom.xml | 87 +++------ .../javaee/oss/encode/digest/DsaCoder.java | 0 .../javaee/oss/encode/digest/HmacCoder.java | 0 .../javaee/oss/encode/digest/MdCoder.java | 0 .../javaee/oss/encode/digest/ShaCoder.java | 0 .../javaee/oss/encode/encrypt/AESCoder.java | 0 .../javaee/oss/encode/encrypt/Base64Demo.java | 0 .../javaee/oss/encode/encrypt/DESCoder.java | 0 .../oss/encode/encrypt/DESedeCoder.java | 0 .../javaee/oss/encode/encrypt/PBECoder.java | 0 .../javaee/oss/encode/encrypt/RSACoder.java | 0 .../oss/encode/sample/DownloadPolicy.java | 0 .../oss/encode/sample/ModifyPolicy.java | 0 .../javaee/oss/encode/sample/TokenUtil.java | 0 .../oss/encode/sample/UploadConstant.java | 0 .../oss/encode/sample/UploadPolicy.java | 0 .../dunwu/javaee/oss/html/CnblogParser.java | 0 .../dunwu/javaee/oss/html/XiamiParser.java | 0 .../dunwu/javaee/oss/image/ImageUtil.java | 0 .../dunwu/javaee/oss/image/QRCodeUtil.java | 0 .../javaee/oss/image/dto/BarcodeParamDTO.java | 0 .../javaee/oss/image/dto/ImageParamDTO.java | 0 .../dunwu/javaee/oss/jms/JMSHelloWorld.java | 0 .../dunwu/javaee/oss/jms/JMSReceiver.java | 0 .../dunwu/javaee/oss/jms/JMSSender.java | 0 .../dunwu/javaee/oss/logging/JclDemo.java | 0 .../dunwu/javaee/oss/logging/Slf4jDemo.java | 0 .../dunwu/javaee/oss/mail/ForwardMail.java | 0 .../dunwu/javaee/oss/mail/MailConfigDTO.java | 0 .../github/dunwu/javaee/oss/mail/MailDTO.java | 0 .../dunwu/javaee/oss/mail/MailUtil.java | 0 .../javaee/oss/mail/SendAttachmentMail.java | 0 .../javaee/oss/mail/SendTemplateMail.java | 0 .../dunwu/javaee/oss/mail/SendTextMail.java | 0 .../dunwu/javaee/oss/mail/StoreMail.java | 0 .../dunwu/javaee/oss/mail/sendHtmlMail.java | 0 .../javaee/oss/template/LoadVelocityDemo.java | 0 .../oss/template/VelocityHelloWorld.java | 0 .../oss/template/VelocityHelloWorld2.java | 0 .../javaee/oss/template/VelocityUtil.java | 0 .../src/main/resources/html/example.html | 0 .../src/main/resources/images/lion.jpg | Bin .../src/main/resources/images/lion2.jpg | Bin .../src/main/resources/log4j.xml | 0 .../src/main/resources/logback.xml | 0 .../src/main/resources/mail/mail.properties | 0 .../src/main/resources/template/footer.vm | 0 .../src/main/resources/template/header.vm | 0 .../src/main/resources/template/hello.vm | 0 .../src/main/resources/template/mail.vm | 0 .../resources/template/velocity.properties | 2 +- .../dunwu/javaee/oss/html/JsoupTest.java | 0 .../dunwu/javaee/oss/image/ImageUtilTest.java | 0 .../javaee/oss/image/QRCodeUtilTest.java | 0 .../javaee/oss/image/ThumbnailatorTest.java | 0 .../javaee/oss/template/VelocityUtilTest.java | 0 .../dunwu/javaee/oss/test/JUnitExecTest.java | 0 .../test/resources/html/jsoup-cookbook.html | 0 .../src/test/resources/images/lion.jpg | Bin .../src/test/resources/images/lion2.jpg | Bin .../test/resources/images/lion2_watermark.png | Bin .../src/test/resources/images/wartermark.png | Bin .../src/test/resources/logback.xml | 0 .../javaee-tutorial-servlet}/pom.xml | 70 +++---- .../javaee/servlet/AnnotationServlet.java | 0 .../javaee/servlet/ContextParamServlet.java | 0 .../dunwu/javaee/servlet/FirstServlet.java | 0 .../dunwu/javaee/servlet/ForwardServlet.java | 0 .../dunwu/javaee/servlet/HelloServlet.java | 0 .../dunwu/javaee/servlet/ImageServlet.java | 0 .../javaee/servlet/InitParamServlet.java | 0 .../javaee/servlet/InjectionServlet.java | 0 .../javaee/servlet/LifeCycleServlet.java | 0 .../dunwu/javaee/servlet/PostServlet.java | 0 .../dunwu/javaee/servlet/RedirectServlet.java | 0 .../dunwu/javaee/servlet/RequestServlet.java | 0 .../javaee/servlet/ThreadSafetyServlet.java | 0 .../dunwu/javaee/servlet/UploadServlet.java | 0 .../servlet/upload/ProgressUploadServlet.java | 0 .../javaee/servlet/upload/UploadListener.java | 0 .../javaee/servlet/upload/UploadStatus.java | 0 .../src/main/resources/logback.xml | 2 +- .../src/main/webapp/META-INF/MANIFEST.MF | 0 .../src/main/webapp/WEB-INF/notice.html | 0 .../src/main/webapp/WEB-INF/web.xml | 0 .../src/main/webapp/index.jsp | 0 .../views/jsp/postPersonalInformation.html | 0 .../dunwu/javaee/server/JettyFactory.java | 0 .../github/dunwu/javaee/server/Profiles.java | 0 .../dunwu/javaee/server/QuickStartServer.java | 0 .../src/test/resources/jetty/webdefault.xml | 0 .../src/test/resources/logback.xml | 0 .../javaee-tutorial-session/pom.xml | 93 +++++++++ .../dunwu/javaee/cookie/AddCookies.java | 0 .../dunwu/javaee/cookie/DeleteCookies.java | 0 .../dunwu/javaee/cookie/ReadCookies.java | 0 .../dunwu/javaee/cookie/bean/Person.java | 0 .../dunwu/javaee/cookie/bean/Topic.java | 0 .../javaee/session/SessionTrackServlet.java | 0 .../src/main/resources/logback.xml | 0 .../src/main/webapp/META-INF/MANIFEST.MF | 0 .../src/main/webapp/META-INF/context.xml | 0 .../src/main/webapp/WEB-INF/web.xml | 0 .../src/main/webapp/encodeURL.jsp | 0 .../src/main/webapp/index.jsp | 0 .../src/main/webapp/views/css/style.css | 0 .../main/webapp/views/images/bg-btn-blue.gif | Bin .../src/main/webapp/views/images/cookie.gif | Bin .../main/webapp/views/images/errorstate.gif | Bin .../src/main/webapp/views/images/mail.gif | Bin .../webapp/views/images/vertical_line.gif | Bin .../webapp/views/jsp/cookie/addCookies.jsp | 0 .../main/webapp/views/jsp/cookie/base64.jsp | 0 .../webapp/views/jsp/cookie/base64_decode.jsp | 0 .../main/webapp/views/jsp/cookie/cookie.gif | Bin .../main/webapp/views/jsp/cookie/cookie.jsp | 0 .../views/jsp/cookie/cookieAttribute.jsp | 0 .../webapp/views/jsp/cookie/cookieDomain.jsp | 0 .../main/webapp/views/jsp/cookie/encoding.jsp | 0 .../main/webapp/views/jsp/cookie/history.js | 0 .../main/webapp/views/jsp/cookie/history.jsp | 0 .../webapp/views/jsp/cookie/javascript.jsp | 0 .../main/webapp/views/jsp/cookie/login.jsp | 0 .../webapp/views/jsp/cookie/loginCookie.jsp | 0 .../main/webapp/views/jsp/cookie/maxAge.jsp | 0 .../webapp/views/jsp/cookie/setCookie.jsp | 0 .../main/webapp/views/jsp/session/session.jsp | 0 .../main/webapp/views/jsp/session/vote.jsp | 0 .../main/webapp/views/jsp/session/welcome.jsp | 0 .../dunwu/javaee/server/JettyFactory.java | 0 .../github/dunwu/javaee/server/Profiles.java | 0 .../dunwu/javaee/server/QuickStartServer.java | 0 .../src/test/resources/jetty/webdefault.xml | 0 .../javaee-tutorial-taglib}/pom.xml | 83 ++++---- .../github/dunwu/javaee/taglib/Copyright.java | 0 .../github/dunwu/javaee/taglib/HelloTag.java | 0 .../github/dunwu/javaee/taglib/HelloTag2.java | 0 .../github/dunwu/javaee/taglib/HelloTag3.java | 0 .../dunwu/javaee/taglib/bean/Person.java | 0 .../javaee/taglib/function/Function.java | 0 .../dunwu/javaee/taglib/tags/AddTag.java | 0 .../dunwu/javaee/taglib/tags/Copyright.java | 0 .../dunwu/javaee/taglib/tags/Copyright2.java | 0 .../taglib/tags/DynamicAttributeTag.java | 0 .../dunwu/javaee/taglib/tags/HelloTag.java | 0 .../dunwu/javaee/taglib/tags/IteratorTag.java | 0 .../dunwu/javaee/taglib/tags/LoopTag.java | 0 .../javaee/taglib/tags/ToLowerCaseTag.java | 0 .../javaee/taglib/tags/table/Column.java | 0 .../dunwu/javaee/taglib/tags/table/Table.java | 0 .../taglib/tags2/MultiAttributeTag.java | 0 .../dunwu/javaee/taglib/tags2/MultiTag.java | 0 .../dunwu/javaee/taglib/tags2/RepeatTag.java | 0 .../javaee/taglib/tags2/ToUpperCaseTag.java | 0 .../dunwu/javaee/taglib/test/Messages.java | 0 .../dunwu/javaee/taglib/test/TestMessage.java | 0 .../javaee/taglib/test/messages.properties | 0 .../src/main/resources/copyright.properties | 0 .../src/main/resources/logback.xml | 0 .../src/main/webapp/WEB-INF/tld/function.tld | 0 .../src/main/webapp/WEB-INF/tld/hello.tld | 0 .../src/main/webapp/WEB-INF/tld/hello2.tld | 0 .../src/main/webapp/WEB-INF/tld/hello3.tld | 0 .../src/main/webapp/WEB-INF/tld/taglib.tld | 0 .../src/main/webapp/WEB-INF/web.xml | 0 .../src/main/webapp/dynamic.jsp | 0 .../src/main/webapp/function.jsp | 0 .../src/main/webapp/hello.jsp | 0 .../src/main/webapp/images/I.png | Bin .../src/main/webapp/images/L.png | Bin .../src/main/webapp/images/Lminus.png | Bin .../src/main/webapp/images/Lplus.png | Bin .../src/main/webapp/images/T.png | Bin .../src/main/webapp/images/Thumbs.db | Bin .../src/main/webapp/images/Tminus.png | Bin .../src/main/webapp/images/Tplus.png | Bin .../src/main/webapp/images/asc.gif | Bin .../src/main/webapp/images/bg-btn-blue.gif | Bin .../src/main/webapp/images/bg_4.jpg | Bin .../src/main/webapp/images/blank.png | Bin .../src/main/webapp/images/bottom-left.gif | Bin .../src/main/webapp/images/bottom-right.gif | Bin .../src/main/webapp/images/btn-go-dark.gif | Bin .../src/main/webapp/images/delete.gif | Bin .../src/main/webapp/images/delimiter.gif | Bin .../src/main/webapp/images/desc.gif | Bin .../src/main/webapp/images/edit.gif | Bin .../src/main/webapp/images/element.gif | Bin .../src/main/webapp/images/file.png | Bin .../src/main/webapp/images/foldericon.png | Bin .../main/webapp/images/ibm-tab-background.gif | Bin .../src/main/webapp/images/ibm_logo.gif | Bin .../main/webapp/images/left-nav-corner.gif | Bin .../images/leftnav-overview-highlight.gif | Bin .../src/main/webapp/images/line01.gif | Bin .../webapp/images/masthead-links-gradient.gif | Bin .../src/main/webapp/images/memo.gif | Bin .../src/main/webapp/images/new.png | Bin .../src/main/webapp/images/next.gif | Bin .../src/main/webapp/images/openfoldericon.png | Bin .../main/webapp/images/pagetools-gradient.gif | Bin .../webapp/images/pagetools_gradient_a.gif | Bin .../src/main/webapp/images/password.gif | Bin .../src/main/webapp/images/prev.gif | Bin .../src/main/webapp/images/project.png | Bin .../src/main/webapp/images/pspbrwse.jbf | Bin .../src/main/webapp/images/rl-bullet.gif | Bin .../src/main/webapp/images/role.gif | Bin .../src/main/webapp/images/team.gif | Bin .../main/webapp/images/template-gradient.gif | Bin .../main/webapp/images/top-content-shadow.gif | Bin .../src/main/webapp/images/top-left.gif | Bin .../src/main/webapp/images/top-right.gif | Bin .../src/main/webapp/images/trans.gif | Bin .../src/main/webapp/index.jsp | 0 .../src/main/webapp/loop.jsp | 0 .../src/main/webapp/multi.jsp | 0 .../src/main/webapp/multiAttribute.jsp | 0 .../src/main/webapp/style/style.css | 0 .../src/main/webapp/table.jsp | 0 .../src/main/webapp/taglib/copyright.jsp | 0 .../src/main/webapp/taglib/helloTag.jsp | 0 .../src/main/webapp/taglib/helloTag2.jsp | 0 .../src/main/webapp/taglib/helloTag3.jsp | 0 .../src/main/webapp/test.jsp | 0 .../src/main/webapp/toLowerCase.jsp | 0 .../src/main/webapp/toUpperCase.jsp | 0 .../src/main/webapp/vote.gif | Bin .../dunwu/javaee/server/JettyFactory.java | 0 .../github/dunwu/javaee/server/Profiles.java | 0 .../dunwu/javaee/server/QuickStartServer.java | 0 .../src/test/resources/jetty/webdefault.xml | 0 .../javaee-tutorial-websocket}/pom.xml | 60 +++--- .../dunwu/javaee/servlet/SocketServlet.java | 0 .../javaee/websocket/WebSocketServer.java | 0 .../WebSocketServerConfigurator.java | 0 .../src/main/resources/logback.xml | 2 +- .../src/main/webapp/WEB-INF/web.xml | 0 .../src/main/webapp/websocketA.html | 0 .../src/main/webapp/websocketB.html | 0 .../github/dunwu/javaee/server/DevServer.java | 0 .../src/test/resources/jetty/webdefault.xml | 0 .../src/test/resources/logback.xml | 2 +- codes/javaee-tutorial/pom.xml | 121 ++++++++++++ codes/javaee/jstl/pom.xml | 184 ------------------ codes/javaee/listener/pom.xml | 183 ----------------- codes/javaee/pom.xml | 80 -------- codes/javaee/taglib/pom.xml | 181 ----------------- 564 files changed, 613 insertions(+), 905 deletions(-) rename codes/{javaee => javaee-tutorial}/README.md (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-filter}/pom.xml (70%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/java/io/github/dunwu/javaee/filter/CacheFilter.java (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/java/io/github/dunwu/javaee/filter/CharacterEncodingFilter.java (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/java/io/github/dunwu/javaee/filter/ExceptionHandlerFilter.java (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/java/io/github/dunwu/javaee/filter/FilterImpl.java (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/java/io/github/dunwu/javaee/filter/GZipFilter.java (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/java/io/github/dunwu/javaee/filter/ImageRedirectFilter.java (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/java/io/github/dunwu/javaee/filter/LogFilter.java (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/java/io/github/dunwu/javaee/filter/MyFilter.java (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/java/io/github/dunwu/javaee/filter/OutputReplaceFilter.java (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/java/io/github/dunwu/javaee/filter/PrivilegeFilter.java (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/java/io/github/dunwu/javaee/filter/UploadFilter.java (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/java/io/github/dunwu/javaee/filter/WaterMarkFilter.java (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/java/io/github/dunwu/javaee/filter/XSLTFilter.java (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/java/io/github/dunwu/javaee/filter/exception/AccountException.java (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/java/io/github/dunwu/javaee/filter/exception/BusinessException.java (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/java/io/github/dunwu/javaee/filter/test/Download.java (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/java/io/github/dunwu/javaee/filter/test/GZipTest.java (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/java/io/github/dunwu/javaee/filter/wrapper/CacheResponseWrapper.java (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/java/io/github/dunwu/javaee/filter/wrapper/GZipOutputStream.java (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/java/io/github/dunwu/javaee/filter/wrapper/GZipResponseWrapper.java (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/java/io/github/dunwu/javaee/filter/wrapper/HttpCharacterResponseWrapper.java (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/java/io/github/dunwu/javaee/filter/wrapper/UploadRequestWrapper.java (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/java/io/github/dunwu/javaee/filter/wrapper/WaterMarkOutputStream.java (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/java/io/github/dunwu/javaee/filter/wrapper/WaterMarkResponseWrapper.java (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/resources/logback.xml (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/webapp/META-INF/MANIFEST.MF (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/webapp/WEB-INF/logo.png (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/webapp/WEB-INF/privilege.properties (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/webapp/WEB-INF/sensitive.properties (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/webapp/WEB-INF/web.xml (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/webapp/views/images/error.gif (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/webapp/views/images/mm.jpg (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/webapp/views/images/sunset.jpg (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/webapp/views/images/winter.jpg (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/webapp/views/js/dojo.js (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/webapp/views/jsp/accountException.jsp (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/webapp/views/jsp/businessException.jsp (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/webapp/views/jsp/dispatcher.jsp (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/webapp/views/jsp/exception.jsp (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/webapp/views/jsp/image.jsp (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/webapp/views/jsp/index.jsp (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/webapp/views/jsp/testCacheFilter.jsp (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/webapp/views/jsp/testCharacterEncodingFilter.jsp (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/webapp/views/jsp/testExceptionHandlerFilter.jsp (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/webapp/views/jsp/testGZipFilter.jsp (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/webapp/views/jsp/testOutputReplaceFilter.jsp (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/webapp/views/jsp/testUploadFilter.jsp (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/webapp/views/jsp/testWaterMarkFilter.jsp (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/webapp/views/xml/book.xml (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/webapp/views/xml/book.xsl (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/webapp/views/xml/demo.xml (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/webapp/views/xml/messageLog.xsl (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/webapp/views/xml/thinkInJava.xml (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/main/webapp/views/xml/xml.xsl (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/test/java/io/github/dunwu/javaee/server/Profiles.java (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-filter}/src/test/resources/jetty/webdefault.xml (100%) create mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/pom.xml rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/java/com/sun/products/applet/demo/Graph.java (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/java/io/github/dunwu/javaee/jsp/bean/Counter.java (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/java/io/github/dunwu/javaee/jsp/bean/Message.java (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/java/io/github/dunwu/javaee/jsp/bean/Person.java (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/java/io/github/dunwu/javaee/jsp/util/IpUtil.java (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/java/io/github/dunwu/javaee/jsp/util/ip/IPEntry.java (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/java/io/github/dunwu/javaee/jsp/util/ip/IPSeeker.java (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/java/io/github/dunwu/javaee/jsp/util/ip/QQWry.dat (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/java/io/github/dunwu/javaee/jsp/util/ip/Test.java (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/java/io/github/dunwu/javaee/jsp/util/ip/Utils.java (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/resources/logback.xml (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/META-INF/MANIFEST.MF (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/WEB-INF/META-INF/MANIFEST.MF (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/WEB-INF/resources/jsp/index.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/WEB-INF/web.xml (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/02.grammar/break.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/02.grammar/for.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/02.grammar/if.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/02.grammar/if2.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/02.grammar/scriptlet.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/02.grammar/switch.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/02.grammar/while.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/03.directive/foot.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/03.directive/head.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/03.directive/include.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/03.directive/page.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/04.action/date.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/04.action/jspforward.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/04.action/jspinclude.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/04.action/jspplugin.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/04.action/jspuseBean.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/04.action/jspuseBean2.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/04.action/useBean.html (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/05.implicit_object/error.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/05.implicit_object/exception.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/cookie/readCookie.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/cookie/session.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/cookie/writeCookie.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/locale/language.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/locale/language2.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/locale/localeCurrency.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/locale/localeDate.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/locale/localePercent.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/practice/date.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/practice/form/checkbox.html (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/practice/form/checkbox.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/practice/form/checkbox2.html (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/practice/form/checkbox2.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/practice/form/formGet.html (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/practice/form/formGet.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/practice/form/formPost.html (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/practice/form/formPost.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/practice/hitCount.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/practice/mail/sendMail.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/practice/redirect.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/practice/refresh.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/uncheck/01.helloWorld.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/uncheck/01.helloWorld_zh.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/uncheck/02.life.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/uncheck/03.yourIp.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/examples/uncheck/configuration.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/images/bg-btn-blue.gif (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/images/mail.gif (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/images/vertical_line.gif (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/views/css/style.css (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/views/jsp/action.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/views/jsp/action/date.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/views/jsp/action/forward.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/views/jsp/action/getProperty.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/views/jsp/action/include.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/views/jsp/action/useBean.html (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/views/jsp/action/useBean.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/views/jsp/application.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/views/jsp/break.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/views/jsp/comment.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/views/jsp/counter.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/views/jsp/directive.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/views/jsp/directive/include/foot.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/views/jsp/directive/include/head.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/views/jsp/directive/include/include.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/views/jsp/directive/page/contentType.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/views/jsp/directive/page/errorPage.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/views/jsp/directive/page/isErrorPage.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/views/jsp/el.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/views/jsp/error.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/views/jsp/exception.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/views/jsp/grammar/for.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/views/jsp/grammar/if.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/views/jsp/grammar/while.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/views/jsp/greeting.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/views/jsp/index.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/views/jsp/life.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/views/jsp/method.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/views/jsp/plugin.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/views/jsp/return.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/views/jsp/scriptlet.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/main/webapp/views/jsp/taglib.jsp (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/test/java/io/github/dunwu/javaee/server/Profiles.java (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/test/resources/jetty/webdefault.xml (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-jsp}/src/test/resources/logback.xml (100%) create mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/pom.xml rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/java/io/github/dunwu/javaee/bean/Person.java (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/java/io/github/dunwu/javaee/util/Example.java (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/java/io/github/dunwu/javaee/util/Example_es_ES.java (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/java/io/github/dunwu/javaee/util/Pagination.java (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/resources/init.sql (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/resources/logback.xml (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/resources/messages.properties (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/resources/messages_zh_CN.properties (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/resources/sql/create_employees.sql (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/resources/sql/create_students.sql (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/WEB-INF/web.xml (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/core/c_catch.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/core/c_choose.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/core/c_forEach.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/core/c_forTokens.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/core/c_if.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/core/c_import.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/core/c_out.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/core/c_param.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/core/c_redirect.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/core/c_remove.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/core/c_set.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/core/c_url.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/fmt/fmt_bundle.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/fmt/fmt_formatDate.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/fmt/fmt_formatNumber.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/fmt/fmt_message.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/fmt/fmt_parseDate.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/fmt/fmt_parseNumber.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/fmt/fmt_requestEncoding.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/fmt/fmt_setLocale.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/fmt/fmt_setTimeZone.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/fmt/fmt_timeZone.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/function/fn_contains.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/function/fn_containsIgnoreCase.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/function/fn_endsWith.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/function/fn_escapeXml.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/function/fn_indexOf.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/function/fn_join.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/function/fn_length.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/function/fn_replace.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/function/fn_split.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/function/fn_startsWith.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/function/fn_substring.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/function/fn_substringAfter.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/function/fn_substringBefore.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/function/fn_toLowerCase.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/function/fn_toUpperCase.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/function/fn_trim.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/sql/sql_dateParam.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/sql/sql_param.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/sql/sql_query.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/sql/sql_setDataSource.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/sql/sql_transaction.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/sql/sql_update.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/bundle.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/catch.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/choose.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/contains.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/containsIgnoreCase.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/dateParam.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/endsWith.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/escapeXml.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/fmt.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/fn.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/forEach.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/forEachWithList.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/forTokens.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/formatDate.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/formatNumber.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/if.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/import.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/index.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/indexOf.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/join.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/length.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/out.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/param.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/parse.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/parseDate.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/parseNumber.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/query.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/queryPagination.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/queryReflect.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/redirect.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/remove.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/requestEncoding.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/set.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/setBundle.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/setDataSource.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/setLocale.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/setTimeZone.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/sina.xml (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/split.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/sql.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/substring.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/timeZone.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/transaction.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/update.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/url.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/x_choose.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/x_forEach.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/x_if.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/x_out.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/x_set.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/x_transform.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/uncheck/xml.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/xml/x_choose.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/xml/x_forEach.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/xml/x_if.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/xml/x_out.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/xml/x_param.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/xml/x_parse.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/xml/x_set.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/examples/xml/x_transform.jsp (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/xml/books.xml (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/main/webapp/xml/style.xsl (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/test/java/io/github/dunwu/javaee/server/Profiles.java (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java (100%) rename codes/{javaee/jstl => javaee-tutorial/javaee-tutorial-jstl}/src/test/resources/jetty/webdefault.xml (100%) rename codes/{javaee/filter => javaee-tutorial/javaee-tutorial-listener}/pom.xml (62%) rename codes/{javaee/listener => javaee-tutorial/javaee-tutorial-listener}/src/main/java/io/github/dunwu/javaee/listener/LoginSessionListener.java (100%) rename codes/{javaee/listener => javaee-tutorial/javaee-tutorial-listener}/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionActivationListener.java (100%) rename codes/{javaee/listener => javaee-tutorial/javaee-tutorial-listener}/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionAttributeListener.java (100%) rename codes/{javaee/listener => javaee-tutorial/javaee-tutorial-listener}/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionBindingListener.java (100%) rename codes/{javaee/listener => javaee-tutorial/javaee-tutorial-listener}/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionListener.java (100%) rename codes/{javaee/listener => javaee-tutorial/javaee-tutorial-listener}/src/main/java/io/github/dunwu/javaee/listener/MyServletContextAttributeListener.java (100%) rename codes/{javaee/listener => javaee-tutorial/javaee-tutorial-listener}/src/main/java/io/github/dunwu/javaee/listener/MyServletContextListener.java (100%) rename codes/{javaee/listener => javaee-tutorial/javaee-tutorial-listener}/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestAttributeListener.java (100%) rename codes/{javaee/listener => javaee-tutorial/javaee-tutorial-listener}/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestListener.java (100%) rename codes/{javaee/listener => javaee-tutorial/javaee-tutorial-listener}/src/main/java/io/github/dunwu/javaee/listener/PersonInfoListener.java (100%) rename codes/{javaee/listener => javaee-tutorial/javaee-tutorial-listener}/src/main/java/io/github/dunwu/javaee/listener/SessionAttributeListenerTest.java (100%) rename codes/{javaee/listener => javaee-tutorial/javaee-tutorial-listener}/src/main/java/io/github/dunwu/javaee/listener/bean/PersonInfo.java (100%) rename codes/{javaee/listener => javaee-tutorial/javaee-tutorial-listener}/src/main/java/io/github/dunwu/javaee/listener/util/ApplicationConstants.java (100%) rename codes/{javaee/listener => javaee-tutorial/javaee-tutorial-listener}/src/main/resources/logback.xml (100%) rename codes/{javaee/listener => javaee-tutorial/javaee-tutorial-listener}/src/main/webapp/META-INF/MANIFEST.MF (100%) rename codes/{javaee/listener => javaee-tutorial/javaee-tutorial-listener}/src/main/webapp/META-INF/context.xml (100%) rename codes/{javaee/listener => javaee-tutorial/javaee-tutorial-listener}/src/main/webapp/WEB-INF/resources/jsp/index.jsp (100%) rename codes/{javaee/listener => javaee-tutorial/javaee-tutorial-listener}/src/main/webapp/WEB-INF/web.xml (100%) rename codes/{javaee/listener => javaee-tutorial/javaee-tutorial-listener}/src/main/webapp/a.gif (100%) rename codes/{javaee/listener => javaee-tutorial/javaee-tutorial-listener}/src/main/webapp/aa.html (100%) rename codes/{javaee/listener => javaee-tutorial/javaee-tutorial-listener}/src/main/webapp/active.jsp (100%) rename codes/{javaee/listener => javaee-tutorial/javaee-tutorial-listener}/src/main/webapp/b.gif (100%) rename codes/{javaee/listener => javaee-tutorial/javaee-tutorial-listener}/src/main/webapp/index.jsp (100%) rename codes/{javaee/listener => javaee-tutorial/javaee-tutorial-listener}/src/main/webapp/listener.jsp (100%) rename codes/{javaee/listener => javaee-tutorial/javaee-tutorial-listener}/src/main/webapp/online.jsp (100%) rename codes/{javaee/listener => javaee-tutorial/javaee-tutorial-listener}/src/main/webapp/testLoginSessionListener.jsp (100%) rename codes/{javaee/listener => javaee-tutorial/javaee-tutorial-listener}/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java (100%) rename codes/{javaee/listener => javaee-tutorial/javaee-tutorial-listener}/src/test/java/io/github/dunwu/javaee/server/Profiles.java (100%) rename codes/{javaee/listener => javaee-tutorial/javaee-tutorial-listener}/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java (100%) rename codes/{javaee/listener => javaee-tutorial/javaee-tutorial-listener}/src/test/resources/jetty/webdefault.xml (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/pom.xml (68%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/encode/digest/DsaCoder.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/encode/digest/HmacCoder.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/encode/digest/MdCoder.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/encode/digest/ShaCoder.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/AESCoder.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/Base64Demo.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/DESCoder.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/DESedeCoder.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/PBECoder.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/RSACoder.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/encode/sample/DownloadPolicy.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/encode/sample/ModifyPolicy.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/encode/sample/TokenUtil.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/encode/sample/UploadConstant.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/encode/sample/UploadPolicy.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/html/CnblogParser.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/html/XiamiParser.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/image/ImageUtil.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/image/QRCodeUtil.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/image/dto/BarcodeParamDTO.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/image/dto/ImageParamDTO.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/jms/JMSHelloWorld.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/jms/JMSReceiver.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/jms/JMSSender.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/logging/JclDemo.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/logging/Slf4jDemo.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/mail/ForwardMail.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/mail/MailConfigDTO.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/mail/MailDTO.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/mail/MailUtil.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/mail/SendAttachmentMail.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/mail/SendTemplateMail.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/mail/SendTextMail.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/mail/StoreMail.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/mail/sendHtmlMail.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/template/LoadVelocityDemo.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/template/VelocityHelloWorld.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/template/VelocityHelloWorld2.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/java/io/github/dunwu/javaee/oss/template/VelocityUtil.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/resources/html/example.html (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/resources/images/lion.jpg (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/resources/images/lion2.jpg (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/resources/log4j.xml (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/resources/logback.xml (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/resources/mail/mail.properties (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/resources/template/footer.vm (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/resources/template/header.vm (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/resources/template/hello.vm (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/resources/template/mail.vm (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/main/resources/template/velocity.properties (76%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/test/java/io/github/dunwu/javaee/oss/html/JsoupTest.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/test/java/io/github/dunwu/javaee/oss/image/ImageUtilTest.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/test/java/io/github/dunwu/javaee/oss/image/QRCodeUtilTest.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/test/java/io/github/dunwu/javaee/oss/image/ThumbnailatorTest.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/test/java/io/github/dunwu/javaee/oss/template/VelocityUtilTest.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/test/java/io/github/dunwu/javaee/oss/test/JUnitExecTest.java (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/test/resources/html/jsoup-cookbook.html (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/test/resources/images/lion.jpg (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/test/resources/images/lion2.jpg (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/test/resources/images/lion2_watermark.png (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/test/resources/images/wartermark.png (100%) rename codes/{javaee/oss => javaee-tutorial/javaee-tutorial-oss}/src/test/resources/logback.xml (100%) rename codes/{javaee/servlet => javaee-tutorial/javaee-tutorial-servlet}/pom.xml (69%) rename codes/{javaee/servlet => javaee-tutorial/javaee-tutorial-servlet}/src/main/java/io/github/dunwu/javaee/servlet/AnnotationServlet.java (100%) rename codes/{javaee/servlet => javaee-tutorial/javaee-tutorial-servlet}/src/main/java/io/github/dunwu/javaee/servlet/ContextParamServlet.java (100%) rename codes/{javaee/servlet => javaee-tutorial/javaee-tutorial-servlet}/src/main/java/io/github/dunwu/javaee/servlet/FirstServlet.java (100%) rename codes/{javaee/servlet => javaee-tutorial/javaee-tutorial-servlet}/src/main/java/io/github/dunwu/javaee/servlet/ForwardServlet.java (100%) rename codes/{javaee/servlet => javaee-tutorial/javaee-tutorial-servlet}/src/main/java/io/github/dunwu/javaee/servlet/HelloServlet.java (100%) rename codes/{javaee/servlet => javaee-tutorial/javaee-tutorial-servlet}/src/main/java/io/github/dunwu/javaee/servlet/ImageServlet.java (100%) rename codes/{javaee/servlet => javaee-tutorial/javaee-tutorial-servlet}/src/main/java/io/github/dunwu/javaee/servlet/InitParamServlet.java (100%) rename codes/{javaee/servlet => javaee-tutorial/javaee-tutorial-servlet}/src/main/java/io/github/dunwu/javaee/servlet/InjectionServlet.java (100%) rename codes/{javaee/servlet => javaee-tutorial/javaee-tutorial-servlet}/src/main/java/io/github/dunwu/javaee/servlet/LifeCycleServlet.java (100%) rename codes/{javaee/servlet => javaee-tutorial/javaee-tutorial-servlet}/src/main/java/io/github/dunwu/javaee/servlet/PostServlet.java (100%) rename codes/{javaee/servlet => javaee-tutorial/javaee-tutorial-servlet}/src/main/java/io/github/dunwu/javaee/servlet/RedirectServlet.java (100%) rename codes/{javaee/servlet => javaee-tutorial/javaee-tutorial-servlet}/src/main/java/io/github/dunwu/javaee/servlet/RequestServlet.java (100%) rename codes/{javaee/servlet => javaee-tutorial/javaee-tutorial-servlet}/src/main/java/io/github/dunwu/javaee/servlet/ThreadSafetyServlet.java (100%) rename codes/{javaee/servlet => javaee-tutorial/javaee-tutorial-servlet}/src/main/java/io/github/dunwu/javaee/servlet/UploadServlet.java (100%) rename codes/{javaee/servlet => javaee-tutorial/javaee-tutorial-servlet}/src/main/java/io/github/dunwu/javaee/servlet/upload/ProgressUploadServlet.java (100%) rename codes/{javaee/servlet => javaee-tutorial/javaee-tutorial-servlet}/src/main/java/io/github/dunwu/javaee/servlet/upload/UploadListener.java (100%) rename codes/{javaee/servlet => javaee-tutorial/javaee-tutorial-servlet}/src/main/java/io/github/dunwu/javaee/servlet/upload/UploadStatus.java (100%) rename codes/{javaee/servlet => javaee-tutorial/javaee-tutorial-servlet}/src/main/resources/logback.xml (96%) rename codes/{javaee/servlet => javaee-tutorial/javaee-tutorial-servlet}/src/main/webapp/META-INF/MANIFEST.MF (100%) rename codes/{javaee/servlet => javaee-tutorial/javaee-tutorial-servlet}/src/main/webapp/WEB-INF/notice.html (100%) rename codes/{javaee/servlet => javaee-tutorial/javaee-tutorial-servlet}/src/main/webapp/WEB-INF/web.xml (100%) rename codes/{javaee/servlet => javaee-tutorial/javaee-tutorial-servlet}/src/main/webapp/index.jsp (100%) rename codes/{javaee/servlet => javaee-tutorial/javaee-tutorial-servlet}/src/main/webapp/views/jsp/postPersonalInformation.html (100%) rename codes/{javaee/servlet => javaee-tutorial/javaee-tutorial-servlet}/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java (100%) rename codes/{javaee/servlet => javaee-tutorial/javaee-tutorial-servlet}/src/test/java/io/github/dunwu/javaee/server/Profiles.java (100%) rename codes/{javaee/servlet => javaee-tutorial/javaee-tutorial-servlet}/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java (100%) rename codes/{javaee/servlet => javaee-tutorial/javaee-tutorial-servlet}/src/test/resources/jetty/webdefault.xml (100%) rename codes/{javaee/servlet => javaee-tutorial/javaee-tutorial-servlet}/src/test/resources/logback.xml (100%) create mode 100644 codes/javaee-tutorial/javaee-tutorial-session/pom.xml rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/java/io/github/dunwu/javaee/cookie/AddCookies.java (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/java/io/github/dunwu/javaee/cookie/DeleteCookies.java (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/java/io/github/dunwu/javaee/cookie/ReadCookies.java (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/java/io/github/dunwu/javaee/cookie/bean/Person.java (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/java/io/github/dunwu/javaee/cookie/bean/Topic.java (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/java/io/github/dunwu/javaee/session/SessionTrackServlet.java (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/resources/logback.xml (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/webapp/META-INF/MANIFEST.MF (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/webapp/META-INF/context.xml (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/webapp/WEB-INF/web.xml (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/webapp/encodeURL.jsp (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/webapp/index.jsp (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/webapp/views/css/style.css (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/webapp/views/images/bg-btn-blue.gif (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/webapp/views/images/cookie.gif (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/webapp/views/images/errorstate.gif (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/webapp/views/images/mail.gif (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/webapp/views/images/vertical_line.gif (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/webapp/views/jsp/cookie/addCookies.jsp (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/webapp/views/jsp/cookie/base64.jsp (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/webapp/views/jsp/cookie/base64_decode.jsp (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/webapp/views/jsp/cookie/cookie.gif (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/webapp/views/jsp/cookie/cookie.jsp (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/webapp/views/jsp/cookie/cookieAttribute.jsp (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/webapp/views/jsp/cookie/cookieDomain.jsp (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/webapp/views/jsp/cookie/encoding.jsp (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/webapp/views/jsp/cookie/history.js (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/webapp/views/jsp/cookie/history.jsp (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/webapp/views/jsp/cookie/javascript.jsp (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/webapp/views/jsp/cookie/login.jsp (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/webapp/views/jsp/cookie/loginCookie.jsp (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/webapp/views/jsp/cookie/maxAge.jsp (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/webapp/views/jsp/cookie/setCookie.jsp (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/webapp/views/jsp/session/session.jsp (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/webapp/views/jsp/session/vote.jsp (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/main/webapp/views/jsp/session/welcome.jsp (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/test/java/io/github/dunwu/javaee/server/Profiles.java (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java (100%) rename codes/{javaee/session => javaee-tutorial/javaee-tutorial-session}/src/test/resources/jetty/webdefault.xml (100%) rename codes/{javaee/jsp => javaee-tutorial/javaee-tutorial-taglib}/pom.xml (66%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/java/io/github/dunwu/javaee/taglib/Copyright.java (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/java/io/github/dunwu/javaee/taglib/HelloTag.java (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/java/io/github/dunwu/javaee/taglib/HelloTag2.java (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/java/io/github/dunwu/javaee/taglib/HelloTag3.java (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/java/io/github/dunwu/javaee/taglib/bean/Person.java (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/java/io/github/dunwu/javaee/taglib/function/Function.java (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/java/io/github/dunwu/javaee/taglib/tags/AddTag.java (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/java/io/github/dunwu/javaee/taglib/tags/Copyright.java (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/java/io/github/dunwu/javaee/taglib/tags/Copyright2.java (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/java/io/github/dunwu/javaee/taglib/tags/DynamicAttributeTag.java (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/java/io/github/dunwu/javaee/taglib/tags/HelloTag.java (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/java/io/github/dunwu/javaee/taglib/tags/IteratorTag.java (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/java/io/github/dunwu/javaee/taglib/tags/LoopTag.java (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/java/io/github/dunwu/javaee/taglib/tags/ToLowerCaseTag.java (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/java/io/github/dunwu/javaee/taglib/tags/table/Column.java (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/java/io/github/dunwu/javaee/taglib/tags/table/Table.java (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/java/io/github/dunwu/javaee/taglib/tags2/MultiAttributeTag.java (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/java/io/github/dunwu/javaee/taglib/tags2/MultiTag.java (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/java/io/github/dunwu/javaee/taglib/tags2/RepeatTag.java (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/java/io/github/dunwu/javaee/taglib/tags2/ToUpperCaseTag.java (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/java/io/github/dunwu/javaee/taglib/test/Messages.java (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/java/io/github/dunwu/javaee/taglib/test/TestMessage.java (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/java/io/github/dunwu/javaee/taglib/test/messages.properties (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/resources/copyright.properties (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/resources/logback.xml (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/WEB-INF/tld/function.tld (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/WEB-INF/tld/hello.tld (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/WEB-INF/tld/hello2.tld (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/WEB-INF/tld/hello3.tld (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/WEB-INF/tld/taglib.tld (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/WEB-INF/web.xml (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/dynamic.jsp (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/function.jsp (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/hello.jsp (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/I.png (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/L.png (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/Lminus.png (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/Lplus.png (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/T.png (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/Thumbs.db (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/Tminus.png (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/Tplus.png (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/asc.gif (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/bg-btn-blue.gif (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/bg_4.jpg (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/blank.png (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/bottom-left.gif (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/bottom-right.gif (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/btn-go-dark.gif (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/delete.gif (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/delimiter.gif (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/desc.gif (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/edit.gif (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/element.gif (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/file.png (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/foldericon.png (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/ibm-tab-background.gif (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/ibm_logo.gif (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/left-nav-corner.gif (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/leftnav-overview-highlight.gif (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/line01.gif (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/masthead-links-gradient.gif (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/memo.gif (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/new.png (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/next.gif (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/openfoldericon.png (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/pagetools-gradient.gif (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/pagetools_gradient_a.gif (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/password.gif (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/prev.gif (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/project.png (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/pspbrwse.jbf (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/rl-bullet.gif (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/role.gif (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/team.gif (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/template-gradient.gif (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/top-content-shadow.gif (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/top-left.gif (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/top-right.gif (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/images/trans.gif (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/index.jsp (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/loop.jsp (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/multi.jsp (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/multiAttribute.jsp (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/style/style.css (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/table.jsp (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/taglib/copyright.jsp (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/taglib/helloTag.jsp (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/taglib/helloTag2.jsp (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/taglib/helloTag3.jsp (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/test.jsp (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/toLowerCase.jsp (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/toUpperCase.jsp (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/main/webapp/vote.gif (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/test/java/io/github/dunwu/javaee/server/Profiles.java (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java (100%) rename codes/{javaee/taglib => javaee-tutorial/javaee-tutorial-taglib}/src/test/resources/jetty/webdefault.xml (100%) rename codes/{javaee/websocket => javaee-tutorial/javaee-tutorial-websocket}/pom.xml (79%) rename codes/{javaee/websocket => javaee-tutorial/javaee-tutorial-websocket}/src/main/java/io/github/dunwu/javaee/servlet/SocketServlet.java (100%) rename codes/{javaee/websocket => javaee-tutorial/javaee-tutorial-websocket}/src/main/java/io/github/dunwu/javaee/websocket/WebSocketServer.java (100%) rename codes/{javaee/websocket => javaee-tutorial/javaee-tutorial-websocket}/src/main/java/io/github/dunwu/javaee/websocket/WebSocketServerConfigurator.java (100%) rename codes/{javaee/websocket => javaee-tutorial/javaee-tutorial-websocket}/src/main/resources/logback.xml (95%) rename codes/{javaee/websocket => javaee-tutorial/javaee-tutorial-websocket}/src/main/webapp/WEB-INF/web.xml (100%) rename codes/{javaee/websocket => javaee-tutorial/javaee-tutorial-websocket}/src/main/webapp/websocketA.html (100%) rename codes/{javaee/websocket => javaee-tutorial/javaee-tutorial-websocket}/src/main/webapp/websocketB.html (100%) rename codes/{javaee/websocket => javaee-tutorial/javaee-tutorial-websocket}/src/test/java/io/github/dunwu/javaee/server/DevServer.java (100%) rename codes/{javaee/websocket => javaee-tutorial/javaee-tutorial-websocket}/src/test/resources/jetty/webdefault.xml (100%) rename codes/{javaee/websocket => javaee-tutorial/javaee-tutorial-websocket}/src/test/resources/logback.xml (95%) create mode 100644 codes/javaee-tutorial/pom.xml delete mode 100644 codes/javaee/jstl/pom.xml delete mode 100644 codes/javaee/listener/pom.xml delete mode 100644 codes/javaee/pom.xml delete mode 100644 codes/javaee/taglib/pom.xml diff --git a/codes/javaee/README.md b/codes/javaee-tutorial/README.md similarity index 100% rename from codes/javaee/README.md rename to codes/javaee-tutorial/README.md diff --git a/codes/javaee/session/pom.xml b/codes/javaee-tutorial/javaee-tutorial-filter/pom.xml similarity index 70% rename from codes/javaee/session/pom.xml rename to codes/javaee-tutorial/javaee-tutorial-filter/pom.xml index 82313bcf..665c39ea 100644 --- a/codes/javaee/session/pom.xml +++ b/codes/javaee-tutorial/javaee-tutorial-filter/pom.xml @@ -2,69 +2,55 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - io.github.dunwu - javaee-notes-session + javaee-tutorial-filter 1.0.0 war - + javaee-tutorial-filter + JavaEE 学习笔记之 Filter - - - javaee-notes-session - javaee 学习笔记之 session - - - - UTF-8 1.7 ${java.version} ${java.version} - - - 9.4.12.v20180830 + ch.qos.logback logback-classic - 1.1.3 ch.qos.logback logback-core - 1.1.3 - - - org.logback-extensions - logback-ext-spring - 0.1.2 org.slf4j jcl-over-slf4j - 1.7.12 + + + net.coobird + thumbnailator + + + commons-fileupload commons-fileupload - 1.3.3 commons-io commons-io - 2.5 org.apache.commons commons-lang3 - 3.4 @@ -72,7 +58,7 @@ javax.servlet javax.servlet-api - 3.1.0 + provided @@ -80,22 +66,22 @@ org.eclipse.jetty jetty-webapp - ${jetty.version} + test org.eclipse.jetty jetty-server - ${jetty.version} + test org.eclipse.jetty jetty-annotations - ${jetty.version} + test org.eclipse.jetty apache-jsp - ${jetty.version} + test @@ -103,20 +89,25 @@ junit junit - 4.12 + test org.assertj assertj-core - 3.4.1 + test - - - - - - + + + + io.github.dunwu + dunwu-dependencies + 0.4.0-SNAPSHOT + pom + import + + + diff --git a/codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/CacheFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/CacheFilter.java similarity index 100% rename from codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/CacheFilter.java rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/CacheFilter.java diff --git a/codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/CharacterEncodingFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/CharacterEncodingFilter.java similarity index 100% rename from codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/CharacterEncodingFilter.java rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/CharacterEncodingFilter.java diff --git a/codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/ExceptionHandlerFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/ExceptionHandlerFilter.java similarity index 100% rename from codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/ExceptionHandlerFilter.java rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/ExceptionHandlerFilter.java diff --git a/codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/FilterImpl.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/FilterImpl.java similarity index 100% rename from codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/FilterImpl.java rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/FilterImpl.java diff --git a/codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/GZipFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/GZipFilter.java similarity index 100% rename from codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/GZipFilter.java rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/GZipFilter.java diff --git a/codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/ImageRedirectFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/ImageRedirectFilter.java similarity index 100% rename from codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/ImageRedirectFilter.java rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/ImageRedirectFilter.java diff --git a/codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/LogFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/LogFilter.java similarity index 100% rename from codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/LogFilter.java rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/LogFilter.java diff --git a/codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/MyFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/MyFilter.java similarity index 100% rename from codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/MyFilter.java rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/MyFilter.java diff --git a/codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/OutputReplaceFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/OutputReplaceFilter.java similarity index 100% rename from codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/OutputReplaceFilter.java rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/OutputReplaceFilter.java diff --git a/codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/PrivilegeFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/PrivilegeFilter.java similarity index 100% rename from codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/PrivilegeFilter.java rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/PrivilegeFilter.java diff --git a/codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/UploadFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/UploadFilter.java similarity index 100% rename from codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/UploadFilter.java rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/UploadFilter.java diff --git a/codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/WaterMarkFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/WaterMarkFilter.java similarity index 100% rename from codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/WaterMarkFilter.java rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/WaterMarkFilter.java diff --git a/codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/XSLTFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/XSLTFilter.java similarity index 100% rename from codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/XSLTFilter.java rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/XSLTFilter.java diff --git a/codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/exception/AccountException.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/exception/AccountException.java similarity index 100% rename from codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/exception/AccountException.java rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/exception/AccountException.java diff --git a/codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/exception/BusinessException.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/exception/BusinessException.java similarity index 100% rename from codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/exception/BusinessException.java rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/exception/BusinessException.java diff --git a/codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/test/Download.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/test/Download.java similarity index 100% rename from codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/test/Download.java rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/test/Download.java diff --git a/codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/test/GZipTest.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/test/GZipTest.java similarity index 100% rename from codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/test/GZipTest.java rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/test/GZipTest.java diff --git a/codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/CacheResponseWrapper.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/CacheResponseWrapper.java similarity index 100% rename from codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/CacheResponseWrapper.java rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/CacheResponseWrapper.java diff --git a/codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/GZipOutputStream.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/GZipOutputStream.java similarity index 100% rename from codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/GZipOutputStream.java rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/GZipOutputStream.java diff --git a/codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/GZipResponseWrapper.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/GZipResponseWrapper.java similarity index 100% rename from codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/GZipResponseWrapper.java rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/GZipResponseWrapper.java diff --git a/codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/HttpCharacterResponseWrapper.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/HttpCharacterResponseWrapper.java similarity index 100% rename from codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/HttpCharacterResponseWrapper.java rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/HttpCharacterResponseWrapper.java diff --git a/codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/UploadRequestWrapper.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/UploadRequestWrapper.java similarity index 100% rename from codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/UploadRequestWrapper.java rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/UploadRequestWrapper.java diff --git a/codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/WaterMarkOutputStream.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/WaterMarkOutputStream.java similarity index 100% rename from codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/WaterMarkOutputStream.java rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/WaterMarkOutputStream.java diff --git a/codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/WaterMarkResponseWrapper.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/WaterMarkResponseWrapper.java similarity index 100% rename from codes/javaee/filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/WaterMarkResponseWrapper.java rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/WaterMarkResponseWrapper.java diff --git a/codes/javaee/filter/src/main/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/resources/logback.xml similarity index 100% rename from codes/javaee/filter/src/main/resources/logback.xml rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/resources/logback.xml diff --git a/codes/javaee/filter/src/main/webapp/META-INF/MANIFEST.MF b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/META-INF/MANIFEST.MF similarity index 100% rename from codes/javaee/filter/src/main/webapp/META-INF/MANIFEST.MF rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/META-INF/MANIFEST.MF diff --git a/codes/javaee/filter/src/main/webapp/WEB-INF/logo.png b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/WEB-INF/logo.png similarity index 100% rename from codes/javaee/filter/src/main/webapp/WEB-INF/logo.png rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/WEB-INF/logo.png diff --git a/codes/javaee/filter/src/main/webapp/WEB-INF/privilege.properties b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/WEB-INF/privilege.properties similarity index 100% rename from codes/javaee/filter/src/main/webapp/WEB-INF/privilege.properties rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/WEB-INF/privilege.properties diff --git a/codes/javaee/filter/src/main/webapp/WEB-INF/sensitive.properties b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/WEB-INF/sensitive.properties similarity index 100% rename from codes/javaee/filter/src/main/webapp/WEB-INF/sensitive.properties rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/WEB-INF/sensitive.properties diff --git a/codes/javaee/filter/src/main/webapp/WEB-INF/web.xml b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/WEB-INF/web.xml similarity index 100% rename from codes/javaee/filter/src/main/webapp/WEB-INF/web.xml rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/WEB-INF/web.xml diff --git a/codes/javaee/filter/src/main/webapp/views/images/error.gif b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/images/error.gif similarity index 100% rename from codes/javaee/filter/src/main/webapp/views/images/error.gif rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/images/error.gif diff --git a/codes/javaee/filter/src/main/webapp/views/images/mm.jpg b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/images/mm.jpg similarity index 100% rename from codes/javaee/filter/src/main/webapp/views/images/mm.jpg rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/images/mm.jpg diff --git a/codes/javaee/filter/src/main/webapp/views/images/sunset.jpg b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/images/sunset.jpg similarity index 100% rename from codes/javaee/filter/src/main/webapp/views/images/sunset.jpg rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/images/sunset.jpg diff --git a/codes/javaee/filter/src/main/webapp/views/images/winter.jpg b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/images/winter.jpg similarity index 100% rename from codes/javaee/filter/src/main/webapp/views/images/winter.jpg rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/images/winter.jpg diff --git a/codes/javaee/filter/src/main/webapp/views/js/dojo.js b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/js/dojo.js similarity index 100% rename from codes/javaee/filter/src/main/webapp/views/js/dojo.js rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/js/dojo.js diff --git a/codes/javaee/filter/src/main/webapp/views/jsp/accountException.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/accountException.jsp similarity index 100% rename from codes/javaee/filter/src/main/webapp/views/jsp/accountException.jsp rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/accountException.jsp diff --git a/codes/javaee/filter/src/main/webapp/views/jsp/businessException.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/businessException.jsp similarity index 100% rename from codes/javaee/filter/src/main/webapp/views/jsp/businessException.jsp rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/businessException.jsp diff --git a/codes/javaee/filter/src/main/webapp/views/jsp/dispatcher.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/dispatcher.jsp similarity index 100% rename from codes/javaee/filter/src/main/webapp/views/jsp/dispatcher.jsp rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/dispatcher.jsp diff --git a/codes/javaee/filter/src/main/webapp/views/jsp/exception.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/exception.jsp similarity index 100% rename from codes/javaee/filter/src/main/webapp/views/jsp/exception.jsp rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/exception.jsp diff --git a/codes/javaee/filter/src/main/webapp/views/jsp/image.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/image.jsp similarity index 100% rename from codes/javaee/filter/src/main/webapp/views/jsp/image.jsp rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/image.jsp diff --git a/codes/javaee/filter/src/main/webapp/views/jsp/index.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/index.jsp similarity index 100% rename from codes/javaee/filter/src/main/webapp/views/jsp/index.jsp rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/index.jsp diff --git a/codes/javaee/filter/src/main/webapp/views/jsp/testCacheFilter.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testCacheFilter.jsp similarity index 100% rename from codes/javaee/filter/src/main/webapp/views/jsp/testCacheFilter.jsp rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testCacheFilter.jsp diff --git a/codes/javaee/filter/src/main/webapp/views/jsp/testCharacterEncodingFilter.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testCharacterEncodingFilter.jsp similarity index 100% rename from codes/javaee/filter/src/main/webapp/views/jsp/testCharacterEncodingFilter.jsp rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testCharacterEncodingFilter.jsp diff --git a/codes/javaee/filter/src/main/webapp/views/jsp/testExceptionHandlerFilter.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testExceptionHandlerFilter.jsp similarity index 100% rename from codes/javaee/filter/src/main/webapp/views/jsp/testExceptionHandlerFilter.jsp rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testExceptionHandlerFilter.jsp diff --git a/codes/javaee/filter/src/main/webapp/views/jsp/testGZipFilter.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testGZipFilter.jsp similarity index 100% rename from codes/javaee/filter/src/main/webapp/views/jsp/testGZipFilter.jsp rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testGZipFilter.jsp diff --git a/codes/javaee/filter/src/main/webapp/views/jsp/testOutputReplaceFilter.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testOutputReplaceFilter.jsp similarity index 100% rename from codes/javaee/filter/src/main/webapp/views/jsp/testOutputReplaceFilter.jsp rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testOutputReplaceFilter.jsp diff --git a/codes/javaee/filter/src/main/webapp/views/jsp/testUploadFilter.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testUploadFilter.jsp similarity index 100% rename from codes/javaee/filter/src/main/webapp/views/jsp/testUploadFilter.jsp rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testUploadFilter.jsp diff --git a/codes/javaee/filter/src/main/webapp/views/jsp/testWaterMarkFilter.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testWaterMarkFilter.jsp similarity index 100% rename from codes/javaee/filter/src/main/webapp/views/jsp/testWaterMarkFilter.jsp rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testWaterMarkFilter.jsp diff --git a/codes/javaee/filter/src/main/webapp/views/xml/book.xml b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/book.xml similarity index 100% rename from codes/javaee/filter/src/main/webapp/views/xml/book.xml rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/book.xml diff --git a/codes/javaee/filter/src/main/webapp/views/xml/book.xsl b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/book.xsl similarity index 100% rename from codes/javaee/filter/src/main/webapp/views/xml/book.xsl rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/book.xsl diff --git a/codes/javaee/filter/src/main/webapp/views/xml/demo.xml b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/demo.xml similarity index 100% rename from codes/javaee/filter/src/main/webapp/views/xml/demo.xml rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/demo.xml diff --git a/codes/javaee/filter/src/main/webapp/views/xml/messageLog.xsl b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/messageLog.xsl similarity index 100% rename from codes/javaee/filter/src/main/webapp/views/xml/messageLog.xsl rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/messageLog.xsl diff --git a/codes/javaee/filter/src/main/webapp/views/xml/thinkInJava.xml b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/thinkInJava.xml similarity index 100% rename from codes/javaee/filter/src/main/webapp/views/xml/thinkInJava.xml rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/thinkInJava.xml diff --git a/codes/javaee/filter/src/main/webapp/views/xml/xml.xsl b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/xml.xsl similarity index 100% rename from codes/javaee/filter/src/main/webapp/views/xml/xml.xsl rename to codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/xml.xsl diff --git a/codes/javaee/filter/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java similarity index 100% rename from codes/javaee/filter/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java rename to codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java diff --git a/codes/javaee/filter/src/test/java/io/github/dunwu/javaee/server/Profiles.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/Profiles.java similarity index 100% rename from codes/javaee/filter/src/test/java/io/github/dunwu/javaee/server/Profiles.java rename to codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/Profiles.java diff --git a/codes/javaee/filter/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java similarity index 100% rename from codes/javaee/filter/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java rename to codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java diff --git a/codes/javaee/filter/src/test/resources/jetty/webdefault.xml b/codes/javaee-tutorial/javaee-tutorial-filter/src/test/resources/jetty/webdefault.xml similarity index 100% rename from codes/javaee/filter/src/test/resources/jetty/webdefault.xml rename to codes/javaee-tutorial/javaee-tutorial-filter/src/test/resources/jetty/webdefault.xml diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/pom.xml b/codes/javaee-tutorial/javaee-tutorial-jsp/pom.xml new file mode 100644 index 00000000..59d5c086 --- /dev/null +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/pom.xml @@ -0,0 +1,96 @@ + + + 4.0.0 + + io.github.dunwu + javaee-tutorial-jsp + 1.0.0 + war + javaee-tutorial-jsp + JavaEE 学习笔记之 JSP + + + UTF-8 + 1.7 + ${java.version} + ${java.version} + + + + + + ch.qos.logback + logback-classic + + + org.slf4j + jcl-over-slf4j + + + + + commons-fileupload + commons-fileupload + + + commons-io + commons-io + + + org.apache.commons + commons-lang3 + + + + + javax.servlet + javax.servlet-api + provided + + + org.eclipse.jetty + jetty-webapp + test + + + org.eclipse.jetty + jetty-server + test + + + org.eclipse.jetty + jetty-annotations + test + + + org.eclipse.jetty + apache-jsp + test + + + + + junit + junit + test + + + org.assertj + assertj-core + test + + + + + + + io.github.dunwu + dunwu-dependencies + 0.4.0-SNAPSHOT + pom + import + + + + diff --git a/codes/javaee/jsp/src/main/java/com/sun/products/applet/demo/Graph.java b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/com/sun/products/applet/demo/Graph.java similarity index 100% rename from codes/javaee/jsp/src/main/java/com/sun/products/applet/demo/Graph.java rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/com/sun/products/applet/demo/Graph.java diff --git a/codes/javaee/jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Counter.java b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Counter.java similarity index 100% rename from codes/javaee/jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Counter.java rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Counter.java diff --git a/codes/javaee/jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Message.java b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Message.java similarity index 100% rename from codes/javaee/jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Message.java rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Message.java diff --git a/codes/javaee/jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Person.java b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Person.java similarity index 100% rename from codes/javaee/jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Person.java rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Person.java diff --git a/codes/javaee/jsp/src/main/java/io/github/dunwu/javaee/jsp/util/IpUtil.java b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/IpUtil.java similarity index 100% rename from codes/javaee/jsp/src/main/java/io/github/dunwu/javaee/jsp/util/IpUtil.java rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/IpUtil.java diff --git a/codes/javaee/jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/IPEntry.java b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/IPEntry.java similarity index 100% rename from codes/javaee/jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/IPEntry.java rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/IPEntry.java diff --git a/codes/javaee/jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/IPSeeker.java b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/IPSeeker.java similarity index 100% rename from codes/javaee/jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/IPSeeker.java rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/IPSeeker.java diff --git a/codes/javaee/jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/QQWry.dat b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/QQWry.dat similarity index 100% rename from codes/javaee/jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/QQWry.dat rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/QQWry.dat diff --git a/codes/javaee/jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/Test.java b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/Test.java similarity index 100% rename from codes/javaee/jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/Test.java rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/Test.java diff --git a/codes/javaee/jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/Utils.java b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/Utils.java similarity index 100% rename from codes/javaee/jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/Utils.java rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/Utils.java diff --git a/codes/javaee/jsp/src/main/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/resources/logback.xml similarity index 100% rename from codes/javaee/jsp/src/main/resources/logback.xml rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/resources/logback.xml diff --git a/codes/javaee/jsp/src/main/webapp/META-INF/MANIFEST.MF b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/META-INF/MANIFEST.MF similarity index 100% rename from codes/javaee/jsp/src/main/webapp/META-INF/MANIFEST.MF rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/META-INF/MANIFEST.MF diff --git a/codes/javaee/jsp/src/main/webapp/WEB-INF/META-INF/MANIFEST.MF b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/WEB-INF/META-INF/MANIFEST.MF similarity index 100% rename from codes/javaee/jsp/src/main/webapp/WEB-INF/META-INF/MANIFEST.MF rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/WEB-INF/META-INF/MANIFEST.MF diff --git a/codes/javaee/jsp/src/main/webapp/WEB-INF/resources/jsp/index.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/WEB-INF/resources/jsp/index.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/WEB-INF/resources/jsp/index.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/WEB-INF/resources/jsp/index.jsp diff --git a/codes/javaee/jsp/src/main/webapp/WEB-INF/web.xml b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/WEB-INF/web.xml similarity index 100% rename from codes/javaee/jsp/src/main/webapp/WEB-INF/web.xml rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/WEB-INF/web.xml diff --git a/codes/javaee/jsp/src/main/webapp/examples/02.grammar/break.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/break.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/02.grammar/break.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/break.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/02.grammar/for.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/for.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/02.grammar/for.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/for.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/02.grammar/if.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/if.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/02.grammar/if.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/if.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/02.grammar/if2.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/if2.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/02.grammar/if2.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/if2.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/02.grammar/scriptlet.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/scriptlet.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/02.grammar/scriptlet.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/scriptlet.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/02.grammar/switch.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/switch.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/02.grammar/switch.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/switch.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/02.grammar/while.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/while.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/02.grammar/while.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/while.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/03.directive/foot.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/foot.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/03.directive/foot.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/foot.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/03.directive/head.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/head.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/03.directive/head.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/head.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/03.directive/include.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/include.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/03.directive/include.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/include.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/03.directive/page.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/page.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/03.directive/page.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/page.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/04.action/date.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/date.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/04.action/date.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/date.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/04.action/jspforward.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspforward.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/04.action/jspforward.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspforward.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/04.action/jspinclude.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspinclude.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/04.action/jspinclude.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspinclude.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/04.action/jspplugin.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspplugin.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/04.action/jspplugin.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspplugin.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/04.action/jspuseBean.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspuseBean.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/04.action/jspuseBean.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspuseBean.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/04.action/jspuseBean2.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspuseBean2.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/04.action/jspuseBean2.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspuseBean2.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/04.action/useBean.html b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/useBean.html similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/04.action/useBean.html rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/useBean.html diff --git a/codes/javaee/jsp/src/main/webapp/examples/05.implicit_object/error.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/05.implicit_object/error.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/05.implicit_object/error.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/05.implicit_object/error.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/05.implicit_object/exception.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/05.implicit_object/exception.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/05.implicit_object/exception.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/05.implicit_object/exception.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/cookie/readCookie.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/cookie/readCookie.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/cookie/readCookie.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/cookie/readCookie.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/cookie/session.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/cookie/session.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/cookie/session.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/cookie/session.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/cookie/writeCookie.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/cookie/writeCookie.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/cookie/writeCookie.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/cookie/writeCookie.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/locale/language.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/language.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/locale/language.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/language.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/locale/language2.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/language2.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/locale/language2.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/language2.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/locale/localeCurrency.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/localeCurrency.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/locale/localeCurrency.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/localeCurrency.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/locale/localeDate.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/localeDate.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/locale/localeDate.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/localeDate.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/locale/localePercent.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/localePercent.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/locale/localePercent.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/localePercent.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/practice/date.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/date.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/practice/date.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/date.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/practice/form/checkbox.html b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox.html similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/practice/form/checkbox.html rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox.html diff --git a/codes/javaee/jsp/src/main/webapp/examples/practice/form/checkbox.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/practice/form/checkbox.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/practice/form/checkbox2.html b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox2.html similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/practice/form/checkbox2.html rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox2.html diff --git a/codes/javaee/jsp/src/main/webapp/examples/practice/form/checkbox2.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox2.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/practice/form/checkbox2.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox2.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/practice/form/formGet.html b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formGet.html similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/practice/form/formGet.html rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formGet.html diff --git a/codes/javaee/jsp/src/main/webapp/examples/practice/form/formGet.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formGet.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/practice/form/formGet.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formGet.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/practice/form/formPost.html b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formPost.html similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/practice/form/formPost.html rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formPost.html diff --git a/codes/javaee/jsp/src/main/webapp/examples/practice/form/formPost.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formPost.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/practice/form/formPost.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formPost.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/practice/hitCount.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/hitCount.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/practice/hitCount.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/hitCount.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/practice/mail/sendMail.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/mail/sendMail.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/practice/mail/sendMail.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/mail/sendMail.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/practice/redirect.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/redirect.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/practice/redirect.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/redirect.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/practice/refresh.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/refresh.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/practice/refresh.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/refresh.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/uncheck/01.helloWorld.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/01.helloWorld.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/uncheck/01.helloWorld.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/01.helloWorld.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/uncheck/01.helloWorld_zh.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/01.helloWorld_zh.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/uncheck/01.helloWorld_zh.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/01.helloWorld_zh.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/uncheck/02.life.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/02.life.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/uncheck/02.life.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/02.life.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/uncheck/03.yourIp.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/03.yourIp.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/uncheck/03.yourIp.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/03.yourIp.jsp diff --git a/codes/javaee/jsp/src/main/webapp/examples/uncheck/configuration.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/configuration.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/examples/uncheck/configuration.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/configuration.jsp diff --git a/codes/javaee/jsp/src/main/webapp/images/bg-btn-blue.gif b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/images/bg-btn-blue.gif similarity index 100% rename from codes/javaee/jsp/src/main/webapp/images/bg-btn-blue.gif rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/images/bg-btn-blue.gif diff --git a/codes/javaee/jsp/src/main/webapp/images/mail.gif b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/images/mail.gif similarity index 100% rename from codes/javaee/jsp/src/main/webapp/images/mail.gif rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/images/mail.gif diff --git a/codes/javaee/jsp/src/main/webapp/images/vertical_line.gif b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/images/vertical_line.gif similarity index 100% rename from codes/javaee/jsp/src/main/webapp/images/vertical_line.gif rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/images/vertical_line.gif diff --git a/codes/javaee/jsp/src/main/webapp/views/css/style.css b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/css/style.css similarity index 100% rename from codes/javaee/jsp/src/main/webapp/views/css/style.css rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/css/style.css diff --git a/codes/javaee/jsp/src/main/webapp/views/jsp/action.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/views/jsp/action.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action.jsp diff --git a/codes/javaee/jsp/src/main/webapp/views/jsp/action/date.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/date.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/views/jsp/action/date.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/date.jsp diff --git a/codes/javaee/jsp/src/main/webapp/views/jsp/action/forward.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/forward.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/views/jsp/action/forward.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/forward.jsp diff --git a/codes/javaee/jsp/src/main/webapp/views/jsp/action/getProperty.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/getProperty.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/views/jsp/action/getProperty.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/getProperty.jsp diff --git a/codes/javaee/jsp/src/main/webapp/views/jsp/action/include.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/include.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/views/jsp/action/include.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/include.jsp diff --git a/codes/javaee/jsp/src/main/webapp/views/jsp/action/useBean.html b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/useBean.html similarity index 100% rename from codes/javaee/jsp/src/main/webapp/views/jsp/action/useBean.html rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/useBean.html diff --git a/codes/javaee/jsp/src/main/webapp/views/jsp/action/useBean.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/useBean.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/views/jsp/action/useBean.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/useBean.jsp diff --git a/codes/javaee/jsp/src/main/webapp/views/jsp/application.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/application.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/views/jsp/application.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/application.jsp diff --git a/codes/javaee/jsp/src/main/webapp/views/jsp/break.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/break.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/views/jsp/break.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/break.jsp diff --git a/codes/javaee/jsp/src/main/webapp/views/jsp/comment.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/comment.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/views/jsp/comment.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/comment.jsp diff --git a/codes/javaee/jsp/src/main/webapp/views/jsp/counter.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/counter.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/views/jsp/counter.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/counter.jsp diff --git a/codes/javaee/jsp/src/main/webapp/views/jsp/directive.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/views/jsp/directive.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive.jsp diff --git a/codes/javaee/jsp/src/main/webapp/views/jsp/directive/include/foot.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/include/foot.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/views/jsp/directive/include/foot.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/include/foot.jsp diff --git a/codes/javaee/jsp/src/main/webapp/views/jsp/directive/include/head.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/include/head.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/views/jsp/directive/include/head.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/include/head.jsp diff --git a/codes/javaee/jsp/src/main/webapp/views/jsp/directive/include/include.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/include/include.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/views/jsp/directive/include/include.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/include/include.jsp diff --git a/codes/javaee/jsp/src/main/webapp/views/jsp/directive/page/contentType.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/page/contentType.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/views/jsp/directive/page/contentType.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/page/contentType.jsp diff --git a/codes/javaee/jsp/src/main/webapp/views/jsp/directive/page/errorPage.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/page/errorPage.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/views/jsp/directive/page/errorPage.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/page/errorPage.jsp diff --git a/codes/javaee/jsp/src/main/webapp/views/jsp/directive/page/isErrorPage.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/page/isErrorPage.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/views/jsp/directive/page/isErrorPage.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/page/isErrorPage.jsp diff --git a/codes/javaee/jsp/src/main/webapp/views/jsp/el.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/el.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/views/jsp/el.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/el.jsp diff --git a/codes/javaee/jsp/src/main/webapp/views/jsp/error.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/error.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/views/jsp/error.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/error.jsp diff --git a/codes/javaee/jsp/src/main/webapp/views/jsp/exception.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/exception.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/views/jsp/exception.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/exception.jsp diff --git a/codes/javaee/jsp/src/main/webapp/views/jsp/grammar/for.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/grammar/for.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/views/jsp/grammar/for.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/grammar/for.jsp diff --git a/codes/javaee/jsp/src/main/webapp/views/jsp/grammar/if.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/grammar/if.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/views/jsp/grammar/if.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/grammar/if.jsp diff --git a/codes/javaee/jsp/src/main/webapp/views/jsp/grammar/while.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/grammar/while.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/views/jsp/grammar/while.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/grammar/while.jsp diff --git a/codes/javaee/jsp/src/main/webapp/views/jsp/greeting.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/greeting.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/views/jsp/greeting.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/greeting.jsp diff --git a/codes/javaee/jsp/src/main/webapp/views/jsp/index.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/index.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/views/jsp/index.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/index.jsp diff --git a/codes/javaee/jsp/src/main/webapp/views/jsp/life.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/life.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/views/jsp/life.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/life.jsp diff --git a/codes/javaee/jsp/src/main/webapp/views/jsp/method.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/method.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/views/jsp/method.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/method.jsp diff --git a/codes/javaee/jsp/src/main/webapp/views/jsp/plugin.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/plugin.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/views/jsp/plugin.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/plugin.jsp diff --git a/codes/javaee/jsp/src/main/webapp/views/jsp/return.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/return.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/views/jsp/return.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/return.jsp diff --git a/codes/javaee/jsp/src/main/webapp/views/jsp/scriptlet.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/scriptlet.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/views/jsp/scriptlet.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/scriptlet.jsp diff --git a/codes/javaee/jsp/src/main/webapp/views/jsp/taglib.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/taglib.jsp similarity index 100% rename from codes/javaee/jsp/src/main/webapp/views/jsp/taglib.jsp rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/taglib.jsp diff --git a/codes/javaee/jsp/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java b/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java similarity index 100% rename from codes/javaee/jsp/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java diff --git a/codes/javaee/jsp/src/test/java/io/github/dunwu/javaee/server/Profiles.java b/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/Profiles.java similarity index 100% rename from codes/javaee/jsp/src/test/java/io/github/dunwu/javaee/server/Profiles.java rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/Profiles.java diff --git a/codes/javaee/jsp/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java b/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java similarity index 100% rename from codes/javaee/jsp/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java diff --git a/codes/javaee/jsp/src/test/resources/jetty/webdefault.xml b/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/resources/jetty/webdefault.xml similarity index 100% rename from codes/javaee/jsp/src/test/resources/jetty/webdefault.xml rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/test/resources/jetty/webdefault.xml diff --git a/codes/javaee/jsp/src/test/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/resources/logback.xml similarity index 100% rename from codes/javaee/jsp/src/test/resources/logback.xml rename to codes/javaee-tutorial/javaee-tutorial-jsp/src/test/resources/logback.xml diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/pom.xml b/codes/javaee-tutorial/javaee-tutorial-jstl/pom.xml new file mode 100644 index 00000000..fd89e746 --- /dev/null +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/pom.xml @@ -0,0 +1,122 @@ + + + 4.0.0 + + + io.github.dunwu + javaee-tutorial-jstl + 1.0.0 + war + + + + + javaee-tutorial-jstl + JavaEE 学习笔记之 jstl + + + + + + UTF-8 + 1.7 + ${java.version} + ${java.version} + + + + + + ch.qos.logback + logback-classic + + + org.slf4j + jcl-over-slf4j + + + + + + org.apache.commons + commons-lang3 + + + + + + javax.servlet + javax.servlet-api + provided + + + javax.servlet.jsp + javax.servlet.jsp-api + provided + + + javax.servlet + jstl + 1.2 + + + + + + org.eclipse.jetty + jetty-webapp + test + + + org.eclipse.jetty + jetty-annotations + test + + + org.eclipse.jetty + apache-jsp + test + + + org.eclipse.jetty + apache-jstl + test + + + + + + org.assertj + assertj-core + test + + + + + + + xalan + xalan + 2.7.2 + + + xerces + xercesImpl + 2.11.0 + + + + + + + + io.github.dunwu + dunwu-dependencies + 0.4.0-SNAPSHOT + pom + import + + + + diff --git a/codes/javaee/jstl/src/main/java/io/github/dunwu/javaee/bean/Person.java b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/bean/Person.java similarity index 100% rename from codes/javaee/jstl/src/main/java/io/github/dunwu/javaee/bean/Person.java rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/bean/Person.java diff --git a/codes/javaee/jstl/src/main/java/io/github/dunwu/javaee/util/Example.java b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/util/Example.java similarity index 100% rename from codes/javaee/jstl/src/main/java/io/github/dunwu/javaee/util/Example.java rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/util/Example.java diff --git a/codes/javaee/jstl/src/main/java/io/github/dunwu/javaee/util/Example_es_ES.java b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/util/Example_es_ES.java similarity index 100% rename from codes/javaee/jstl/src/main/java/io/github/dunwu/javaee/util/Example_es_ES.java rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/util/Example_es_ES.java diff --git a/codes/javaee/jstl/src/main/java/io/github/dunwu/javaee/util/Pagination.java b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/util/Pagination.java similarity index 100% rename from codes/javaee/jstl/src/main/java/io/github/dunwu/javaee/util/Pagination.java rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/util/Pagination.java diff --git a/codes/javaee/jstl/src/main/resources/init.sql b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/init.sql similarity index 100% rename from codes/javaee/jstl/src/main/resources/init.sql rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/init.sql diff --git a/codes/javaee/jstl/src/main/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/logback.xml similarity index 100% rename from codes/javaee/jstl/src/main/resources/logback.xml rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/logback.xml diff --git a/codes/javaee/jstl/src/main/resources/messages.properties b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/messages.properties similarity index 100% rename from codes/javaee/jstl/src/main/resources/messages.properties rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/messages.properties diff --git a/codes/javaee/jstl/src/main/resources/messages_zh_CN.properties b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/messages_zh_CN.properties similarity index 100% rename from codes/javaee/jstl/src/main/resources/messages_zh_CN.properties rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/messages_zh_CN.properties diff --git a/codes/javaee/jstl/src/main/resources/sql/create_employees.sql b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/sql/create_employees.sql similarity index 100% rename from codes/javaee/jstl/src/main/resources/sql/create_employees.sql rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/sql/create_employees.sql diff --git a/codes/javaee/jstl/src/main/resources/sql/create_students.sql b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/sql/create_students.sql similarity index 100% rename from codes/javaee/jstl/src/main/resources/sql/create_students.sql rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/sql/create_students.sql diff --git a/codes/javaee/jstl/src/main/webapp/WEB-INF/web.xml b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/WEB-INF/web.xml similarity index 100% rename from codes/javaee/jstl/src/main/webapp/WEB-INF/web.xml rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/WEB-INF/web.xml diff --git a/codes/javaee/jstl/src/main/webapp/examples/core/c_catch.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_catch.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/core/c_catch.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_catch.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/core/c_choose.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_choose.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/core/c_choose.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_choose.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/core/c_forEach.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_forEach.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/core/c_forEach.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_forEach.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/core/c_forTokens.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_forTokens.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/core/c_forTokens.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_forTokens.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/core/c_if.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_if.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/core/c_if.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_if.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/core/c_import.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_import.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/core/c_import.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_import.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/core/c_out.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_out.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/core/c_out.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_out.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/core/c_param.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_param.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/core/c_param.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_param.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/core/c_redirect.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_redirect.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/core/c_redirect.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_redirect.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/core/c_remove.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_remove.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/core/c_remove.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_remove.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/core/c_set.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_set.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/core/c_set.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_set.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/core/c_url.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_url.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/core/c_url.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_url.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/fmt/fmt_bundle.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_bundle.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/fmt/fmt_bundle.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_bundle.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/fmt/fmt_formatDate.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_formatDate.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/fmt/fmt_formatDate.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_formatDate.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/fmt/fmt_formatNumber.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_formatNumber.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/fmt/fmt_formatNumber.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_formatNumber.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/fmt/fmt_message.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_message.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/fmt/fmt_message.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_message.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/fmt/fmt_parseDate.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_parseDate.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/fmt/fmt_parseDate.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_parseDate.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/fmt/fmt_parseNumber.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_parseNumber.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/fmt/fmt_parseNumber.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_parseNumber.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/fmt/fmt_requestEncoding.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_requestEncoding.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/fmt/fmt_requestEncoding.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_requestEncoding.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/fmt/fmt_setLocale.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_setLocale.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/fmt/fmt_setLocale.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_setLocale.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/fmt/fmt_setTimeZone.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_setTimeZone.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/fmt/fmt_setTimeZone.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_setTimeZone.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/fmt/fmt_timeZone.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_timeZone.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/fmt/fmt_timeZone.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_timeZone.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/function/fn_contains.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_contains.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/function/fn_contains.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_contains.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/function/fn_containsIgnoreCase.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_containsIgnoreCase.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/function/fn_containsIgnoreCase.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_containsIgnoreCase.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/function/fn_endsWith.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_endsWith.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/function/fn_endsWith.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_endsWith.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/function/fn_escapeXml.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_escapeXml.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/function/fn_escapeXml.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_escapeXml.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/function/fn_indexOf.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_indexOf.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/function/fn_indexOf.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_indexOf.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/function/fn_join.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_join.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/function/fn_join.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_join.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/function/fn_length.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_length.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/function/fn_length.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_length.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/function/fn_replace.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_replace.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/function/fn_replace.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_replace.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/function/fn_split.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_split.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/function/fn_split.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_split.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/function/fn_startsWith.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_startsWith.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/function/fn_startsWith.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_startsWith.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/function/fn_substring.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substring.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/function/fn_substring.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substring.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/function/fn_substringAfter.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substringAfter.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/function/fn_substringAfter.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substringAfter.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/function/fn_substringBefore.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substringBefore.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/function/fn_substringBefore.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substringBefore.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/function/fn_toLowerCase.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_toLowerCase.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/function/fn_toLowerCase.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_toLowerCase.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/function/fn_toUpperCase.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_toUpperCase.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/function/fn_toUpperCase.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_toUpperCase.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/function/fn_trim.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_trim.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/function/fn_trim.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_trim.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/sql/sql_dateParam.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_dateParam.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/sql/sql_dateParam.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_dateParam.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/sql/sql_param.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_param.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/sql/sql_param.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_param.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/sql/sql_query.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_query.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/sql/sql_query.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_query.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/sql/sql_setDataSource.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_setDataSource.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/sql/sql_setDataSource.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_setDataSource.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/sql/sql_transaction.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_transaction.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/sql/sql_transaction.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_transaction.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/sql/sql_update.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_update.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/sql/sql_update.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_update.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/bundle.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/bundle.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/bundle.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/bundle.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/catch.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/catch.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/catch.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/catch.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/choose.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/choose.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/choose.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/choose.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/contains.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/contains.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/contains.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/contains.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/containsIgnoreCase.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/containsIgnoreCase.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/containsIgnoreCase.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/containsIgnoreCase.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/dateParam.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/dateParam.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/dateParam.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/dateParam.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/endsWith.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/endsWith.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/endsWith.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/endsWith.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/escapeXml.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/escapeXml.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/escapeXml.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/escapeXml.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/fmt.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/fmt.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/fmt.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/fmt.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/fn.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/fn.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/fn.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/fn.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/forEach.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forEach.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/forEach.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forEach.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/forEachWithList.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forEachWithList.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/forEachWithList.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forEachWithList.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/forTokens.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forTokens.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/forTokens.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forTokens.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/formatDate.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/formatDate.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/formatDate.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/formatDate.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/formatNumber.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/formatNumber.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/formatNumber.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/formatNumber.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/if.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/if.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/if.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/if.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/import.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/import.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/import.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/import.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/index.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/index.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/index.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/index.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/indexOf.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/indexOf.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/indexOf.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/indexOf.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/join.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/join.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/join.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/join.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/length.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/length.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/length.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/length.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/out.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/out.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/out.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/out.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/param.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/param.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/param.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/param.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/parse.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parse.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/parse.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parse.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/parseDate.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parseDate.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/parseDate.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parseDate.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/parseNumber.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parseNumber.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/parseNumber.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parseNumber.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/query.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/query.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/query.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/query.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/queryPagination.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/queryPagination.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/queryPagination.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/queryPagination.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/queryReflect.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/queryReflect.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/queryReflect.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/queryReflect.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/redirect.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/redirect.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/redirect.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/redirect.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/remove.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/remove.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/remove.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/remove.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/requestEncoding.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/requestEncoding.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/requestEncoding.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/requestEncoding.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/set.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/set.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/set.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/set.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/setBundle.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setBundle.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/setBundle.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setBundle.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/setDataSource.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setDataSource.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/setDataSource.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setDataSource.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/setLocale.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setLocale.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/setLocale.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setLocale.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/setTimeZone.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setTimeZone.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/setTimeZone.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setTimeZone.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/sina.xml b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/sina.xml similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/sina.xml rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/sina.xml diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/split.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/split.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/split.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/split.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/sql.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/sql.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/sql.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/sql.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/substring.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/substring.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/substring.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/substring.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/timeZone.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/timeZone.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/timeZone.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/timeZone.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/transaction.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/transaction.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/transaction.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/transaction.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/update.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/update.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/update.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/update.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/url.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/url.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/url.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/url.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/x_choose.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_choose.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/x_choose.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_choose.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/x_forEach.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_forEach.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/x_forEach.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_forEach.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/x_if.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_if.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/x_if.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_if.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/x_out.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_out.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/x_out.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_out.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/x_set.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_set.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/x_set.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_set.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/x_transform.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_transform.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/x_transform.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_transform.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/uncheck/xml.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/xml.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/uncheck/xml.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/xml.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/xml/x_choose.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_choose.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/xml/x_choose.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_choose.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/xml/x_forEach.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_forEach.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/xml/x_forEach.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_forEach.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/xml/x_if.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_if.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/xml/x_if.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_if.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/xml/x_out.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_out.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/xml/x_out.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_out.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/xml/x_param.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_param.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/xml/x_param.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_param.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/xml/x_parse.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_parse.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/xml/x_parse.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_parse.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/xml/x_set.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_set.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/xml/x_set.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_set.jsp diff --git a/codes/javaee/jstl/src/main/webapp/examples/xml/x_transform.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_transform.jsp similarity index 100% rename from codes/javaee/jstl/src/main/webapp/examples/xml/x_transform.jsp rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_transform.jsp diff --git a/codes/javaee/jstl/src/main/webapp/xml/books.xml b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/xml/books.xml similarity index 100% rename from codes/javaee/jstl/src/main/webapp/xml/books.xml rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/xml/books.xml diff --git a/codes/javaee/jstl/src/main/webapp/xml/style.xsl b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/xml/style.xsl similarity index 100% rename from codes/javaee/jstl/src/main/webapp/xml/style.xsl rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/xml/style.xsl diff --git a/codes/javaee/jstl/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java b/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java similarity index 100% rename from codes/javaee/jstl/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java diff --git a/codes/javaee/jstl/src/test/java/io/github/dunwu/javaee/server/Profiles.java b/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/Profiles.java similarity index 100% rename from codes/javaee/jstl/src/test/java/io/github/dunwu/javaee/server/Profiles.java rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/Profiles.java diff --git a/codes/javaee/jstl/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java b/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java similarity index 100% rename from codes/javaee/jstl/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java diff --git a/codes/javaee/jstl/src/test/resources/jetty/webdefault.xml b/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/resources/jetty/webdefault.xml similarity index 100% rename from codes/javaee/jstl/src/test/resources/jetty/webdefault.xml rename to codes/javaee-tutorial/javaee-tutorial-jstl/src/test/resources/jetty/webdefault.xml diff --git a/codes/javaee/filter/pom.xml b/codes/javaee-tutorial/javaee-tutorial-listener/pom.xml similarity index 62% rename from codes/javaee/filter/pom.xml rename to codes/javaee-tutorial/javaee-tutorial-listener/pom.xml index 261b3aa2..3b55a73e 100644 --- a/codes/javaee/filter/pom.xml +++ b/codes/javaee-tutorial/javaee-tutorial-listener/pom.xml @@ -2,77 +2,44 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - io.github.dunwu - javaee-notes-filter + javaee-tutorial-listener 1.0.0 war - + javaee-tutorial-listener + JavaEE 学习笔记之 listener - - - javaee-notes-filter - javaee 学习笔记之 filter - - - - UTF-8 1.7 ${java.version} ${java.version} - - - 9.4.12.v20180830 + ch.qos.logback logback-classic - 1.1.3 - - - ch.qos.logback - logback-core - 1.1.3 - - - org.logback-extensions - logback-ext-spring - 0.1.2 org.slf4j jcl-over-slf4j - 1.7.12 - - - net.coobird - thumbnailator - [0.4, 0.5) - - - commons-fileupload commons-fileupload - 1.3.3 commons-io commons-io - 2.5 org.apache.commons commons-lang3 - 3.4 @@ -80,7 +47,12 @@ javax.servlet javax.servlet-api - 3.1.0 + provided + + + javax.servlet.jsp + javax.servlet.jsp-api + provided @@ -88,22 +60,22 @@ org.eclipse.jetty jetty-webapp - ${jetty.version} + test org.eclipse.jetty - jetty-server - ${jetty.version} + jetty-annotations + test org.eclipse.jetty - jetty-annotations - ${jetty.version} + apache-jsp + test org.eclipse.jetty - apache-jsp - ${jetty.version} + apache-jstl + test @@ -111,20 +83,25 @@ junit junit - 4.12 + test org.assertj assertj-core - 3.4.1 + test - - - - - - + + + + io.github.dunwu + dunwu-dependencies + 0.4.0-SNAPSHOT + pom + import + + + diff --git a/codes/javaee/listener/src/main/java/io/github/dunwu/javaee/listener/LoginSessionListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/LoginSessionListener.java similarity index 100% rename from codes/javaee/listener/src/main/java/io/github/dunwu/javaee/listener/LoginSessionListener.java rename to codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/LoginSessionListener.java diff --git a/codes/javaee/listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionActivationListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionActivationListener.java similarity index 100% rename from codes/javaee/listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionActivationListener.java rename to codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionActivationListener.java diff --git a/codes/javaee/listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionAttributeListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionAttributeListener.java similarity index 100% rename from codes/javaee/listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionAttributeListener.java rename to codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionAttributeListener.java diff --git a/codes/javaee/listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionBindingListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionBindingListener.java similarity index 100% rename from codes/javaee/listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionBindingListener.java rename to codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionBindingListener.java diff --git a/codes/javaee/listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionListener.java similarity index 100% rename from codes/javaee/listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionListener.java rename to codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionListener.java diff --git a/codes/javaee/listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextAttributeListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextAttributeListener.java similarity index 100% rename from codes/javaee/listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextAttributeListener.java rename to codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextAttributeListener.java diff --git a/codes/javaee/listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextListener.java similarity index 100% rename from codes/javaee/listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextListener.java rename to codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextListener.java diff --git a/codes/javaee/listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestAttributeListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestAttributeListener.java similarity index 100% rename from codes/javaee/listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestAttributeListener.java rename to codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestAttributeListener.java diff --git a/codes/javaee/listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestListener.java similarity index 100% rename from codes/javaee/listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestListener.java rename to codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestListener.java diff --git a/codes/javaee/listener/src/main/java/io/github/dunwu/javaee/listener/PersonInfoListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/PersonInfoListener.java similarity index 100% rename from codes/javaee/listener/src/main/java/io/github/dunwu/javaee/listener/PersonInfoListener.java rename to codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/PersonInfoListener.java diff --git a/codes/javaee/listener/src/main/java/io/github/dunwu/javaee/listener/SessionAttributeListenerTest.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/SessionAttributeListenerTest.java similarity index 100% rename from codes/javaee/listener/src/main/java/io/github/dunwu/javaee/listener/SessionAttributeListenerTest.java rename to codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/SessionAttributeListenerTest.java diff --git a/codes/javaee/listener/src/main/java/io/github/dunwu/javaee/listener/bean/PersonInfo.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/bean/PersonInfo.java similarity index 100% rename from codes/javaee/listener/src/main/java/io/github/dunwu/javaee/listener/bean/PersonInfo.java rename to codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/bean/PersonInfo.java diff --git a/codes/javaee/listener/src/main/java/io/github/dunwu/javaee/listener/util/ApplicationConstants.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/util/ApplicationConstants.java similarity index 100% rename from codes/javaee/listener/src/main/java/io/github/dunwu/javaee/listener/util/ApplicationConstants.java rename to codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/util/ApplicationConstants.java diff --git a/codes/javaee/listener/src/main/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/resources/logback.xml similarity index 100% rename from codes/javaee/listener/src/main/resources/logback.xml rename to codes/javaee-tutorial/javaee-tutorial-listener/src/main/resources/logback.xml diff --git a/codes/javaee/listener/src/main/webapp/META-INF/MANIFEST.MF b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/META-INF/MANIFEST.MF similarity index 100% rename from codes/javaee/listener/src/main/webapp/META-INF/MANIFEST.MF rename to codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/META-INF/MANIFEST.MF diff --git a/codes/javaee/listener/src/main/webapp/META-INF/context.xml b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/META-INF/context.xml similarity index 100% rename from codes/javaee/listener/src/main/webapp/META-INF/context.xml rename to codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/META-INF/context.xml diff --git a/codes/javaee/listener/src/main/webapp/WEB-INF/resources/jsp/index.jsp b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/WEB-INF/resources/jsp/index.jsp similarity index 100% rename from codes/javaee/listener/src/main/webapp/WEB-INF/resources/jsp/index.jsp rename to codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/WEB-INF/resources/jsp/index.jsp diff --git a/codes/javaee/listener/src/main/webapp/WEB-INF/web.xml b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/WEB-INF/web.xml similarity index 100% rename from codes/javaee/listener/src/main/webapp/WEB-INF/web.xml rename to codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/WEB-INF/web.xml diff --git a/codes/javaee/listener/src/main/webapp/a.gif b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/a.gif similarity index 100% rename from codes/javaee/listener/src/main/webapp/a.gif rename to codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/a.gif diff --git a/codes/javaee/listener/src/main/webapp/aa.html b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/aa.html similarity index 100% rename from codes/javaee/listener/src/main/webapp/aa.html rename to codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/aa.html diff --git a/codes/javaee/listener/src/main/webapp/active.jsp b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/active.jsp similarity index 100% rename from codes/javaee/listener/src/main/webapp/active.jsp rename to codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/active.jsp diff --git a/codes/javaee/listener/src/main/webapp/b.gif b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/b.gif similarity index 100% rename from codes/javaee/listener/src/main/webapp/b.gif rename to codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/b.gif diff --git a/codes/javaee/listener/src/main/webapp/index.jsp b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/index.jsp similarity index 100% rename from codes/javaee/listener/src/main/webapp/index.jsp rename to codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/index.jsp diff --git a/codes/javaee/listener/src/main/webapp/listener.jsp b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/listener.jsp similarity index 100% rename from codes/javaee/listener/src/main/webapp/listener.jsp rename to codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/listener.jsp diff --git a/codes/javaee/listener/src/main/webapp/online.jsp b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/online.jsp similarity index 100% rename from codes/javaee/listener/src/main/webapp/online.jsp rename to codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/online.jsp diff --git a/codes/javaee/listener/src/main/webapp/testLoginSessionListener.jsp b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/testLoginSessionListener.jsp similarity index 100% rename from codes/javaee/listener/src/main/webapp/testLoginSessionListener.jsp rename to codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/testLoginSessionListener.jsp diff --git a/codes/javaee/listener/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java similarity index 100% rename from codes/javaee/listener/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java rename to codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java diff --git a/codes/javaee/listener/src/test/java/io/github/dunwu/javaee/server/Profiles.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/Profiles.java similarity index 100% rename from codes/javaee/listener/src/test/java/io/github/dunwu/javaee/server/Profiles.java rename to codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/Profiles.java diff --git a/codes/javaee/listener/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java similarity index 100% rename from codes/javaee/listener/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java rename to codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java diff --git a/codes/javaee/listener/src/test/resources/jetty/webdefault.xml b/codes/javaee-tutorial/javaee-tutorial-listener/src/test/resources/jetty/webdefault.xml similarity index 100% rename from codes/javaee/listener/src/test/resources/jetty/webdefault.xml rename to codes/javaee-tutorial/javaee-tutorial-listener/src/test/resources/jetty/webdefault.xml diff --git a/codes/javaee/oss/pom.xml b/codes/javaee-tutorial/javaee-tutorial-oss/pom.xml similarity index 68% rename from codes/javaee/oss/pom.xml rename to codes/javaee-tutorial/javaee-tutorial-oss/pom.xml index 38910f71..c0c54c65 100644 --- a/codes/javaee/oss/pom.xml +++ b/codes/javaee-tutorial/javaee-tutorial-oss/pom.xml @@ -4,61 +4,29 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - io.github.dunwu - javaee-notes-oss + javaee-tutorial-oss 1.0.0 jar - + javaee-tutorial-oss + JavaEE 学习笔记之 OSS(Open Source Software) - - - javaee-notes-oss - javaee 学习笔记之 OSS(Open Source Software) - - - - UTF-8 1.7 ${java.version} ${java.version} - - - 9.4.12.v20180830 + ch.qos.logback logback-classic - 1.1.3 - - - ch.qos.logback - logback-core - 1.1.3 - - - org.logback-extensions - logback-ext-spring - 0.1.2 org.slf4j jcl-over-slf4j - 1.7.12 - - - commons-logging - commons-logging - 1.2 - - - log4j - log4j - 1.2.17 @@ -66,18 +34,13 @@ commons-codec commons-codec - 1.10 org.bouncycastle bcprov-jdk15on 1.54 - - javax.validation - validation-api - 1.1.0.Final - + @@ -102,7 +65,6 @@ net.coobird thumbnailator - [0.4, 0.5) net.sf.jmimemagic @@ -123,27 +85,27 @@ com.alibaba fastjson - 1.2.31 com.fasterxml.jackson.core jackson-core - 2.7.4 com.fasterxml.jackson.core jackson-databind - 2.9.5 - + javax.mail - mail - 1.4.7 + javax.mail-api - + + javax.validation + validation-api + + @@ -157,13 +119,11 @@ junit junit - 4.12 test org.assertj assertj-core - 3.5.2 test @@ -172,20 +132,23 @@ org.apache.commons commons-lang3 - 3.4 - commons-collections - commons-collections - 3.2.2 + org.apache.commons + commons-collections4 - - - - - - + + + + io.github.dunwu + dunwu-dependencies + 0.4.0-SNAPSHOT + pom + import + + + diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/DsaCoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/DsaCoder.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/DsaCoder.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/DsaCoder.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/HmacCoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/HmacCoder.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/HmacCoder.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/HmacCoder.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/MdCoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/MdCoder.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/MdCoder.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/MdCoder.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/ShaCoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/ShaCoder.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/ShaCoder.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/ShaCoder.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/AESCoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/AESCoder.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/AESCoder.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/AESCoder.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/Base64Demo.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/Base64Demo.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/Base64Demo.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/Base64Demo.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/DESCoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/DESCoder.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/DESCoder.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/DESCoder.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/DESedeCoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/DESedeCoder.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/DESedeCoder.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/DESedeCoder.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/PBECoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/PBECoder.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/PBECoder.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/PBECoder.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/RSACoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/RSACoder.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/RSACoder.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/RSACoder.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/DownloadPolicy.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/DownloadPolicy.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/DownloadPolicy.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/DownloadPolicy.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/ModifyPolicy.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/ModifyPolicy.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/ModifyPolicy.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/ModifyPolicy.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/TokenUtil.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/TokenUtil.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/TokenUtil.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/TokenUtil.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/UploadConstant.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/UploadConstant.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/UploadConstant.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/UploadConstant.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/UploadPolicy.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/UploadPolicy.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/UploadPolicy.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/UploadPolicy.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/html/CnblogParser.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/html/CnblogParser.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/html/CnblogParser.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/html/CnblogParser.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/html/XiamiParser.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/html/XiamiParser.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/html/XiamiParser.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/html/XiamiParser.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/image/ImageUtil.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/ImageUtil.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/image/ImageUtil.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/ImageUtil.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/image/QRCodeUtil.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/QRCodeUtil.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/image/QRCodeUtil.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/QRCodeUtil.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/image/dto/BarcodeParamDTO.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/dto/BarcodeParamDTO.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/image/dto/BarcodeParamDTO.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/dto/BarcodeParamDTO.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/image/dto/ImageParamDTO.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/dto/ImageParamDTO.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/image/dto/ImageParamDTO.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/dto/ImageParamDTO.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSHelloWorld.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSHelloWorld.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSHelloWorld.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSHelloWorld.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSReceiver.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSReceiver.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSReceiver.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSReceiver.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSSender.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSSender.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSSender.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSSender.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/logging/JclDemo.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/logging/JclDemo.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/logging/JclDemo.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/logging/JclDemo.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/logging/Slf4jDemo.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/logging/Slf4jDemo.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/logging/Slf4jDemo.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/logging/Slf4jDemo.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/mail/ForwardMail.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/ForwardMail.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/mail/ForwardMail.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/ForwardMail.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailConfigDTO.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailConfigDTO.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailConfigDTO.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailConfigDTO.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailDTO.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailDTO.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailDTO.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailDTO.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailUtil.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailUtil.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailUtil.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailUtil.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendAttachmentMail.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendAttachmentMail.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendAttachmentMail.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendAttachmentMail.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendTemplateMail.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendTemplateMail.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendTemplateMail.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendTemplateMail.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendTextMail.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendTextMail.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendTextMail.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendTextMail.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/mail/StoreMail.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/StoreMail.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/mail/StoreMail.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/StoreMail.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/mail/sendHtmlMail.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/sendHtmlMail.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/mail/sendHtmlMail.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/sendHtmlMail.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/template/LoadVelocityDemo.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/LoadVelocityDemo.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/template/LoadVelocityDemo.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/LoadVelocityDemo.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityHelloWorld.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityHelloWorld.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityHelloWorld.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityHelloWorld.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityHelloWorld2.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityHelloWorld2.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityHelloWorld2.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityHelloWorld2.java diff --git a/codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityUtil.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityUtil.java similarity index 100% rename from codes/javaee/oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityUtil.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityUtil.java diff --git a/codes/javaee/oss/src/main/resources/html/example.html b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/html/example.html similarity index 100% rename from codes/javaee/oss/src/main/resources/html/example.html rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/html/example.html diff --git a/codes/javaee/oss/src/main/resources/images/lion.jpg b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/images/lion.jpg similarity index 100% rename from codes/javaee/oss/src/main/resources/images/lion.jpg rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/images/lion.jpg diff --git a/codes/javaee/oss/src/main/resources/images/lion2.jpg b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/images/lion2.jpg similarity index 100% rename from codes/javaee/oss/src/main/resources/images/lion2.jpg rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/images/lion2.jpg diff --git a/codes/javaee/oss/src/main/resources/log4j.xml b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/log4j.xml similarity index 100% rename from codes/javaee/oss/src/main/resources/log4j.xml rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/log4j.xml diff --git a/codes/javaee/oss/src/main/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/logback.xml similarity index 100% rename from codes/javaee/oss/src/main/resources/logback.xml rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/logback.xml diff --git a/codes/javaee/oss/src/main/resources/mail/mail.properties b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/mail/mail.properties similarity index 100% rename from codes/javaee/oss/src/main/resources/mail/mail.properties rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/mail/mail.properties diff --git a/codes/javaee/oss/src/main/resources/template/footer.vm b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/footer.vm similarity index 100% rename from codes/javaee/oss/src/main/resources/template/footer.vm rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/footer.vm diff --git a/codes/javaee/oss/src/main/resources/template/header.vm b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/header.vm similarity index 100% rename from codes/javaee/oss/src/main/resources/template/header.vm rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/header.vm diff --git a/codes/javaee/oss/src/main/resources/template/hello.vm b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/hello.vm similarity index 100% rename from codes/javaee/oss/src/main/resources/template/hello.vm rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/hello.vm diff --git a/codes/javaee/oss/src/main/resources/template/mail.vm b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/mail.vm similarity index 100% rename from codes/javaee/oss/src/main/resources/template/mail.vm rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/mail.vm diff --git a/codes/javaee/oss/src/main/resources/template/velocity.properties b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/velocity.properties similarity index 76% rename from codes/javaee/oss/src/main/resources/template/velocity.properties rename to codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/velocity.properties index 0b0fdde7..c299635c 100644 --- a/codes/javaee/oss/src/main/resources/template/velocity.properties +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/velocity.properties @@ -1,4 +1,4 @@ resource.loader=file -file.resource.loader.path=D:/01_Workspace/Project/zp/java/javaee-notes/codes/oss/src/main/resources +file.resource.loader.path=D:/01_Workspace/Project/zp/java/javaee-tutorial/codes/oss/src/main/resources input.encoding=utf-8 output.encoding=utf-8 diff --git a/codes/javaee/oss/src/test/java/io/github/dunwu/javaee/oss/html/JsoupTest.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/html/JsoupTest.java similarity index 100% rename from codes/javaee/oss/src/test/java/io/github/dunwu/javaee/oss/html/JsoupTest.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/html/JsoupTest.java diff --git a/codes/javaee/oss/src/test/java/io/github/dunwu/javaee/oss/image/ImageUtilTest.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/ImageUtilTest.java similarity index 100% rename from codes/javaee/oss/src/test/java/io/github/dunwu/javaee/oss/image/ImageUtilTest.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/ImageUtilTest.java diff --git a/codes/javaee/oss/src/test/java/io/github/dunwu/javaee/oss/image/QRCodeUtilTest.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/QRCodeUtilTest.java similarity index 100% rename from codes/javaee/oss/src/test/java/io/github/dunwu/javaee/oss/image/QRCodeUtilTest.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/QRCodeUtilTest.java diff --git a/codes/javaee/oss/src/test/java/io/github/dunwu/javaee/oss/image/ThumbnailatorTest.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/ThumbnailatorTest.java similarity index 100% rename from codes/javaee/oss/src/test/java/io/github/dunwu/javaee/oss/image/ThumbnailatorTest.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/ThumbnailatorTest.java diff --git a/codes/javaee/oss/src/test/java/io/github/dunwu/javaee/oss/template/VelocityUtilTest.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/template/VelocityUtilTest.java similarity index 100% rename from codes/javaee/oss/src/test/java/io/github/dunwu/javaee/oss/template/VelocityUtilTest.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/template/VelocityUtilTest.java diff --git a/codes/javaee/oss/src/test/java/io/github/dunwu/javaee/oss/test/JUnitExecTest.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/test/JUnitExecTest.java similarity index 100% rename from codes/javaee/oss/src/test/java/io/github/dunwu/javaee/oss/test/JUnitExecTest.java rename to codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/test/JUnitExecTest.java diff --git a/codes/javaee/oss/src/test/resources/html/jsoup-cookbook.html b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/html/jsoup-cookbook.html similarity index 100% rename from codes/javaee/oss/src/test/resources/html/jsoup-cookbook.html rename to codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/html/jsoup-cookbook.html diff --git a/codes/javaee/oss/src/test/resources/images/lion.jpg b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/images/lion.jpg similarity index 100% rename from codes/javaee/oss/src/test/resources/images/lion.jpg rename to codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/images/lion.jpg diff --git a/codes/javaee/oss/src/test/resources/images/lion2.jpg b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/images/lion2.jpg similarity index 100% rename from codes/javaee/oss/src/test/resources/images/lion2.jpg rename to codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/images/lion2.jpg diff --git a/codes/javaee/oss/src/test/resources/images/lion2_watermark.png b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/images/lion2_watermark.png similarity index 100% rename from codes/javaee/oss/src/test/resources/images/lion2_watermark.png rename to codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/images/lion2_watermark.png diff --git a/codes/javaee/oss/src/test/resources/images/wartermark.png b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/images/wartermark.png similarity index 100% rename from codes/javaee/oss/src/test/resources/images/wartermark.png rename to codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/images/wartermark.png diff --git a/codes/javaee/oss/src/test/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/logback.xml similarity index 100% rename from codes/javaee/oss/src/test/resources/logback.xml rename to codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/logback.xml diff --git a/codes/javaee/servlet/pom.xml b/codes/javaee-tutorial/javaee-tutorial-servlet/pom.xml similarity index 69% rename from codes/javaee/servlet/pom.xml rename to codes/javaee-tutorial/javaee-tutorial-servlet/pom.xml index 58a1357d..094872fe 100644 --- a/codes/javaee/servlet/pom.xml +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/pom.xml @@ -3,100 +3,86 @@ xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 4.0.0 - io.github.dunwu - javaee-notes-servlet + javaee-tutorial-servlet 1.0.0 war - + javaee-tutorial-servlet + JavaEE 学习笔记之 servlet - - - javaee-notes-servlet - javaee 学习笔记之 servlet - - - - UTF-8 1.7 ${java.version} ${java.version} - - - 9.4.12.v20180830 + 0.4.0 - + + + ch.qos.logback + logback-classic + + + + + javax.servlet + javax.servlet-api + org.eclipse.jetty jetty-webapp - ${jetty.version} test org.eclipse.jetty jetty-annotations - ${jetty.version} test org.eclipse.jetty apache-jsp - ${jetty.version} test org.eclipse.jetty apache-jstl - ${jetty.version} test - - - ch.qos.logback - logback-classic - 1.1.3 - - - - - javax.servlet - javax.servlet-api - 3.1.0 - - junit junit - 4.12 + test org.assertj assertj-core - 3.5.2 + test org.apache.commons commons-lang3 - 3.4 commons-fileupload commons-fileupload - 1.3.3 - - - - - + + + + io.github.dunwu + dunwu-dependencies + 0.4.0-SNAPSHOT + pom + import + + + diff --git a/codes/javaee/servlet/src/main/java/io/github/dunwu/javaee/servlet/AnnotationServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/AnnotationServlet.java similarity index 100% rename from codes/javaee/servlet/src/main/java/io/github/dunwu/javaee/servlet/AnnotationServlet.java rename to codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/AnnotationServlet.java diff --git a/codes/javaee/servlet/src/main/java/io/github/dunwu/javaee/servlet/ContextParamServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ContextParamServlet.java similarity index 100% rename from codes/javaee/servlet/src/main/java/io/github/dunwu/javaee/servlet/ContextParamServlet.java rename to codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ContextParamServlet.java diff --git a/codes/javaee/servlet/src/main/java/io/github/dunwu/javaee/servlet/FirstServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/FirstServlet.java similarity index 100% rename from codes/javaee/servlet/src/main/java/io/github/dunwu/javaee/servlet/FirstServlet.java rename to codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/FirstServlet.java diff --git a/codes/javaee/servlet/src/main/java/io/github/dunwu/javaee/servlet/ForwardServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ForwardServlet.java similarity index 100% rename from codes/javaee/servlet/src/main/java/io/github/dunwu/javaee/servlet/ForwardServlet.java rename to codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ForwardServlet.java diff --git a/codes/javaee/servlet/src/main/java/io/github/dunwu/javaee/servlet/HelloServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/HelloServlet.java similarity index 100% rename from codes/javaee/servlet/src/main/java/io/github/dunwu/javaee/servlet/HelloServlet.java rename to codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/HelloServlet.java diff --git a/codes/javaee/servlet/src/main/java/io/github/dunwu/javaee/servlet/ImageServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ImageServlet.java similarity index 100% rename from codes/javaee/servlet/src/main/java/io/github/dunwu/javaee/servlet/ImageServlet.java rename to codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ImageServlet.java diff --git a/codes/javaee/servlet/src/main/java/io/github/dunwu/javaee/servlet/InitParamServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/InitParamServlet.java similarity index 100% rename from codes/javaee/servlet/src/main/java/io/github/dunwu/javaee/servlet/InitParamServlet.java rename to codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/InitParamServlet.java diff --git a/codes/javaee/servlet/src/main/java/io/github/dunwu/javaee/servlet/InjectionServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/InjectionServlet.java similarity index 100% rename from codes/javaee/servlet/src/main/java/io/github/dunwu/javaee/servlet/InjectionServlet.java rename to codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/InjectionServlet.java diff --git a/codes/javaee/servlet/src/main/java/io/github/dunwu/javaee/servlet/LifeCycleServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/LifeCycleServlet.java similarity index 100% rename from codes/javaee/servlet/src/main/java/io/github/dunwu/javaee/servlet/LifeCycleServlet.java rename to codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/LifeCycleServlet.java diff --git a/codes/javaee/servlet/src/main/java/io/github/dunwu/javaee/servlet/PostServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/PostServlet.java similarity index 100% rename from codes/javaee/servlet/src/main/java/io/github/dunwu/javaee/servlet/PostServlet.java rename to codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/PostServlet.java diff --git a/codes/javaee/servlet/src/main/java/io/github/dunwu/javaee/servlet/RedirectServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/RedirectServlet.java similarity index 100% rename from codes/javaee/servlet/src/main/java/io/github/dunwu/javaee/servlet/RedirectServlet.java rename to codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/RedirectServlet.java diff --git a/codes/javaee/servlet/src/main/java/io/github/dunwu/javaee/servlet/RequestServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/RequestServlet.java similarity index 100% rename from codes/javaee/servlet/src/main/java/io/github/dunwu/javaee/servlet/RequestServlet.java rename to codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/RequestServlet.java diff --git a/codes/javaee/servlet/src/main/java/io/github/dunwu/javaee/servlet/ThreadSafetyServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ThreadSafetyServlet.java similarity index 100% rename from codes/javaee/servlet/src/main/java/io/github/dunwu/javaee/servlet/ThreadSafetyServlet.java rename to codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ThreadSafetyServlet.java diff --git a/codes/javaee/servlet/src/main/java/io/github/dunwu/javaee/servlet/UploadServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/UploadServlet.java similarity index 100% rename from codes/javaee/servlet/src/main/java/io/github/dunwu/javaee/servlet/UploadServlet.java rename to codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/UploadServlet.java diff --git a/codes/javaee/servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/ProgressUploadServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/ProgressUploadServlet.java similarity index 100% rename from codes/javaee/servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/ProgressUploadServlet.java rename to codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/ProgressUploadServlet.java diff --git a/codes/javaee/servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/UploadListener.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/UploadListener.java similarity index 100% rename from codes/javaee/servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/UploadListener.java rename to codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/UploadListener.java diff --git a/codes/javaee/servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/UploadStatus.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/UploadStatus.java similarity index 100% rename from codes/javaee/servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/UploadStatus.java rename to codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/UploadStatus.java diff --git a/codes/javaee/servlet/src/main/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/resources/logback.xml similarity index 96% rename from codes/javaee/servlet/src/main/resources/logback.xml rename to codes/javaee-tutorial/javaee-tutorial-servlet/src/main/resources/logback.xml index c7c8cd2d..d0ebb62e 100644 --- a/codes/javaee/servlet/src/main/resources/logback.xml +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/resources/logback.xml @@ -3,7 +3,7 @@ - + diff --git a/codes/javaee/servlet/src/main/webapp/META-INF/MANIFEST.MF b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/META-INF/MANIFEST.MF similarity index 100% rename from codes/javaee/servlet/src/main/webapp/META-INF/MANIFEST.MF rename to codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/META-INF/MANIFEST.MF diff --git a/codes/javaee/servlet/src/main/webapp/WEB-INF/notice.html b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/WEB-INF/notice.html similarity index 100% rename from codes/javaee/servlet/src/main/webapp/WEB-INF/notice.html rename to codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/WEB-INF/notice.html diff --git a/codes/javaee/servlet/src/main/webapp/WEB-INF/web.xml b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/WEB-INF/web.xml similarity index 100% rename from codes/javaee/servlet/src/main/webapp/WEB-INF/web.xml rename to codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/WEB-INF/web.xml diff --git a/codes/javaee/servlet/src/main/webapp/index.jsp b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/index.jsp similarity index 100% rename from codes/javaee/servlet/src/main/webapp/index.jsp rename to codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/index.jsp diff --git a/codes/javaee/servlet/src/main/webapp/views/jsp/postPersonalInformation.html b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/views/jsp/postPersonalInformation.html similarity index 100% rename from codes/javaee/servlet/src/main/webapp/views/jsp/postPersonalInformation.html rename to codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/views/jsp/postPersonalInformation.html diff --git a/codes/javaee/servlet/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java similarity index 100% rename from codes/javaee/servlet/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java rename to codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java diff --git a/codes/javaee/servlet/src/test/java/io/github/dunwu/javaee/server/Profiles.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/Profiles.java similarity index 100% rename from codes/javaee/servlet/src/test/java/io/github/dunwu/javaee/server/Profiles.java rename to codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/Profiles.java diff --git a/codes/javaee/servlet/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java similarity index 100% rename from codes/javaee/servlet/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java rename to codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java diff --git a/codes/javaee/servlet/src/test/resources/jetty/webdefault.xml b/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/resources/jetty/webdefault.xml similarity index 100% rename from codes/javaee/servlet/src/test/resources/jetty/webdefault.xml rename to codes/javaee-tutorial/javaee-tutorial-servlet/src/test/resources/jetty/webdefault.xml diff --git a/codes/javaee/servlet/src/test/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/resources/logback.xml similarity index 100% rename from codes/javaee/servlet/src/test/resources/logback.xml rename to codes/javaee-tutorial/javaee-tutorial-servlet/src/test/resources/logback.xml diff --git a/codes/javaee-tutorial/javaee-tutorial-session/pom.xml b/codes/javaee-tutorial/javaee-tutorial-session/pom.xml new file mode 100644 index 00000000..0ff281b8 --- /dev/null +++ b/codes/javaee-tutorial/javaee-tutorial-session/pom.xml @@ -0,0 +1,93 @@ + + 4.0.0 + + io.github.dunwu + javaee-tutorial-session + 1.0.0 + war + javaee-tutorial-session + JavaEE 学习笔记之 session + + + UTF-8 + 1.7 + ${java.version} + ${java.version} + + + + + + ch.qos.logback + logback-classic + + + org.slf4j + jcl-over-slf4j + + + + + + org.apache.commons + commons-lang3 + + + + + + javax.servlet + javax.servlet-api + + + + + + org.eclipse.jetty + jetty-webapp + test + + + org.eclipse.jetty + jetty-annotations + test + + + org.eclipse.jetty + apache-jsp + test + + + org.eclipse.jetty + apache-jstl + test + + + + + + junit + junit + test + + + org.assertj + assertj-core + test + + + + + + + + io.github.dunwu + dunwu-dependencies + 0.4.0-SNAPSHOT + pom + import + + + + diff --git a/codes/javaee/session/src/main/java/io/github/dunwu/javaee/cookie/AddCookies.java b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/AddCookies.java similarity index 100% rename from codes/javaee/session/src/main/java/io/github/dunwu/javaee/cookie/AddCookies.java rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/AddCookies.java diff --git a/codes/javaee/session/src/main/java/io/github/dunwu/javaee/cookie/DeleteCookies.java b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/DeleteCookies.java similarity index 100% rename from codes/javaee/session/src/main/java/io/github/dunwu/javaee/cookie/DeleteCookies.java rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/DeleteCookies.java diff --git a/codes/javaee/session/src/main/java/io/github/dunwu/javaee/cookie/ReadCookies.java b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/ReadCookies.java similarity index 100% rename from codes/javaee/session/src/main/java/io/github/dunwu/javaee/cookie/ReadCookies.java rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/ReadCookies.java diff --git a/codes/javaee/session/src/main/java/io/github/dunwu/javaee/cookie/bean/Person.java b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Person.java similarity index 100% rename from codes/javaee/session/src/main/java/io/github/dunwu/javaee/cookie/bean/Person.java rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Person.java diff --git a/codes/javaee/session/src/main/java/io/github/dunwu/javaee/cookie/bean/Topic.java b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Topic.java similarity index 100% rename from codes/javaee/session/src/main/java/io/github/dunwu/javaee/cookie/bean/Topic.java rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Topic.java diff --git a/codes/javaee/session/src/main/java/io/github/dunwu/javaee/session/SessionTrackServlet.java b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/session/SessionTrackServlet.java similarity index 100% rename from codes/javaee/session/src/main/java/io/github/dunwu/javaee/session/SessionTrackServlet.java rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/session/SessionTrackServlet.java diff --git a/codes/javaee/session/src/main/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-session/src/main/resources/logback.xml similarity index 100% rename from codes/javaee/session/src/main/resources/logback.xml rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/resources/logback.xml diff --git a/codes/javaee/session/src/main/webapp/META-INF/MANIFEST.MF b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/META-INF/MANIFEST.MF similarity index 100% rename from codes/javaee/session/src/main/webapp/META-INF/MANIFEST.MF rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/META-INF/MANIFEST.MF diff --git a/codes/javaee/session/src/main/webapp/META-INF/context.xml b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/META-INF/context.xml similarity index 100% rename from codes/javaee/session/src/main/webapp/META-INF/context.xml rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/META-INF/context.xml diff --git a/codes/javaee/session/src/main/webapp/WEB-INF/web.xml b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/WEB-INF/web.xml similarity index 100% rename from codes/javaee/session/src/main/webapp/WEB-INF/web.xml rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/WEB-INF/web.xml diff --git a/codes/javaee/session/src/main/webapp/encodeURL.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/encodeURL.jsp similarity index 100% rename from codes/javaee/session/src/main/webapp/encodeURL.jsp rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/encodeURL.jsp diff --git a/codes/javaee/session/src/main/webapp/index.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/index.jsp similarity index 100% rename from codes/javaee/session/src/main/webapp/index.jsp rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/index.jsp diff --git a/codes/javaee/session/src/main/webapp/views/css/style.css b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/css/style.css similarity index 100% rename from codes/javaee/session/src/main/webapp/views/css/style.css rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/css/style.css diff --git a/codes/javaee/session/src/main/webapp/views/images/bg-btn-blue.gif b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/images/bg-btn-blue.gif similarity index 100% rename from codes/javaee/session/src/main/webapp/views/images/bg-btn-blue.gif rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/images/bg-btn-blue.gif diff --git a/codes/javaee/session/src/main/webapp/views/images/cookie.gif b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/images/cookie.gif similarity index 100% rename from codes/javaee/session/src/main/webapp/views/images/cookie.gif rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/images/cookie.gif diff --git a/codes/javaee/session/src/main/webapp/views/images/errorstate.gif b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/images/errorstate.gif similarity index 100% rename from codes/javaee/session/src/main/webapp/views/images/errorstate.gif rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/images/errorstate.gif diff --git a/codes/javaee/session/src/main/webapp/views/images/mail.gif b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/images/mail.gif similarity index 100% rename from codes/javaee/session/src/main/webapp/views/images/mail.gif rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/images/mail.gif diff --git a/codes/javaee/session/src/main/webapp/views/images/vertical_line.gif b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/images/vertical_line.gif similarity index 100% rename from codes/javaee/session/src/main/webapp/views/images/vertical_line.gif rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/images/vertical_line.gif diff --git a/codes/javaee/session/src/main/webapp/views/jsp/cookie/addCookies.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/addCookies.jsp similarity index 100% rename from codes/javaee/session/src/main/webapp/views/jsp/cookie/addCookies.jsp rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/addCookies.jsp diff --git a/codes/javaee/session/src/main/webapp/views/jsp/cookie/base64.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/base64.jsp similarity index 100% rename from codes/javaee/session/src/main/webapp/views/jsp/cookie/base64.jsp rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/base64.jsp diff --git a/codes/javaee/session/src/main/webapp/views/jsp/cookie/base64_decode.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/base64_decode.jsp similarity index 100% rename from codes/javaee/session/src/main/webapp/views/jsp/cookie/base64_decode.jsp rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/base64_decode.jsp diff --git a/codes/javaee/session/src/main/webapp/views/jsp/cookie/cookie.gif b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/cookie.gif similarity index 100% rename from codes/javaee/session/src/main/webapp/views/jsp/cookie/cookie.gif rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/cookie.gif diff --git a/codes/javaee/session/src/main/webapp/views/jsp/cookie/cookie.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/cookie.jsp similarity index 100% rename from codes/javaee/session/src/main/webapp/views/jsp/cookie/cookie.jsp rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/cookie.jsp diff --git a/codes/javaee/session/src/main/webapp/views/jsp/cookie/cookieAttribute.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/cookieAttribute.jsp similarity index 100% rename from codes/javaee/session/src/main/webapp/views/jsp/cookie/cookieAttribute.jsp rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/cookieAttribute.jsp diff --git a/codes/javaee/session/src/main/webapp/views/jsp/cookie/cookieDomain.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/cookieDomain.jsp similarity index 100% rename from codes/javaee/session/src/main/webapp/views/jsp/cookie/cookieDomain.jsp rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/cookieDomain.jsp diff --git a/codes/javaee/session/src/main/webapp/views/jsp/cookie/encoding.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/encoding.jsp similarity index 100% rename from codes/javaee/session/src/main/webapp/views/jsp/cookie/encoding.jsp rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/encoding.jsp diff --git a/codes/javaee/session/src/main/webapp/views/jsp/cookie/history.js b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/history.js similarity index 100% rename from codes/javaee/session/src/main/webapp/views/jsp/cookie/history.js rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/history.js diff --git a/codes/javaee/session/src/main/webapp/views/jsp/cookie/history.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/history.jsp similarity index 100% rename from codes/javaee/session/src/main/webapp/views/jsp/cookie/history.jsp rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/history.jsp diff --git a/codes/javaee/session/src/main/webapp/views/jsp/cookie/javascript.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/javascript.jsp similarity index 100% rename from codes/javaee/session/src/main/webapp/views/jsp/cookie/javascript.jsp rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/javascript.jsp diff --git a/codes/javaee/session/src/main/webapp/views/jsp/cookie/login.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/login.jsp similarity index 100% rename from codes/javaee/session/src/main/webapp/views/jsp/cookie/login.jsp rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/login.jsp diff --git a/codes/javaee/session/src/main/webapp/views/jsp/cookie/loginCookie.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/loginCookie.jsp similarity index 100% rename from codes/javaee/session/src/main/webapp/views/jsp/cookie/loginCookie.jsp rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/loginCookie.jsp diff --git a/codes/javaee/session/src/main/webapp/views/jsp/cookie/maxAge.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/maxAge.jsp similarity index 100% rename from codes/javaee/session/src/main/webapp/views/jsp/cookie/maxAge.jsp rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/maxAge.jsp diff --git a/codes/javaee/session/src/main/webapp/views/jsp/cookie/setCookie.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/setCookie.jsp similarity index 100% rename from codes/javaee/session/src/main/webapp/views/jsp/cookie/setCookie.jsp rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/setCookie.jsp diff --git a/codes/javaee/session/src/main/webapp/views/jsp/session/session.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/session/session.jsp similarity index 100% rename from codes/javaee/session/src/main/webapp/views/jsp/session/session.jsp rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/session/session.jsp diff --git a/codes/javaee/session/src/main/webapp/views/jsp/session/vote.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/session/vote.jsp similarity index 100% rename from codes/javaee/session/src/main/webapp/views/jsp/session/vote.jsp rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/session/vote.jsp diff --git a/codes/javaee/session/src/main/webapp/views/jsp/session/welcome.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/session/welcome.jsp similarity index 100% rename from codes/javaee/session/src/main/webapp/views/jsp/session/welcome.jsp rename to codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/session/welcome.jsp diff --git a/codes/javaee/session/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java b/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java similarity index 100% rename from codes/javaee/session/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java rename to codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java diff --git a/codes/javaee/session/src/test/java/io/github/dunwu/javaee/server/Profiles.java b/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/Profiles.java similarity index 100% rename from codes/javaee/session/src/test/java/io/github/dunwu/javaee/server/Profiles.java rename to codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/Profiles.java diff --git a/codes/javaee/session/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java b/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java similarity index 100% rename from codes/javaee/session/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java rename to codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java diff --git a/codes/javaee/session/src/test/resources/jetty/webdefault.xml b/codes/javaee-tutorial/javaee-tutorial-session/src/test/resources/jetty/webdefault.xml similarity index 100% rename from codes/javaee/session/src/test/resources/jetty/webdefault.xml rename to codes/javaee-tutorial/javaee-tutorial-session/src/test/resources/jetty/webdefault.xml diff --git a/codes/javaee/jsp/pom.xml b/codes/javaee-tutorial/javaee-tutorial-taglib/pom.xml similarity index 66% rename from codes/javaee/jsp/pom.xml rename to codes/javaee-tutorial/javaee-tutorial-taglib/pom.xml index e4c1a606..318861e2 100644 --- a/codes/javaee/jsp/pom.xml +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/pom.xml @@ -5,15 +5,15 @@ io.github.dunwu - javaee-notes-jsp + javaee-tutorial-taglib 1.0.0 war - javaee-notes-jsp - javaee 学习笔记之 jsp + javaee-tutorial-taglib + JavaEE 学习笔记之 taglib @@ -23,49 +23,24 @@ 1.7 ${java.version} ${java.version} - - - 9.4.12.v20180830 + ch.qos.logback logback-classic - 1.1.3 - - - ch.qos.logback - logback-core - 1.1.3 - - - org.logback-extensions - logback-ext-spring - 0.1.2 org.slf4j jcl-over-slf4j - 1.7.12 - - commons-fileupload - commons-fileupload - 1.3.3 - - - commons-io - commons-io - 2.5 - org.apache.commons commons-lang3 - 3.4 @@ -73,7 +48,12 @@ javax.servlet javax.servlet-api - 3.1.0 + provided + + + javax.servlet.jsp + javax.servlet.jsp-api + provided @@ -81,22 +61,22 @@ org.eclipse.jetty jetty-webapp - ${jetty.version} + test org.eclipse.jetty jetty-annotations - ${jetty.version} + test org.eclipse.jetty apache-jsp - ${jetty.version} + test org.eclipse.jetty apache-jstl - ${jetty.version} + test @@ -104,20 +84,39 @@ junit junit - 4.12 + test org.assertj assertj-core - 3.4.1 + test - - - - - - + + + + xalan + xalan + 2.7.2 + + + xerces + xercesImpl + 2.11.0 + + + + + + + io.github.dunwu + dunwu-dependencies + 0.4.0-SNAPSHOT + pom + import + + + diff --git a/codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/Copyright.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/Copyright.java similarity index 100% rename from codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/Copyright.java rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/Copyright.java diff --git a/codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag.java similarity index 100% rename from codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag.java rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag.java diff --git a/codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag2.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag2.java similarity index 100% rename from codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag2.java rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag2.java diff --git a/codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag3.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag3.java similarity index 100% rename from codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag3.java rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag3.java diff --git a/codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/bean/Person.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/bean/Person.java similarity index 100% rename from codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/bean/Person.java rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/bean/Person.java diff --git a/codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/function/Function.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/function/Function.java similarity index 100% rename from codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/function/Function.java rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/function/Function.java diff --git a/codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/AddTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/AddTag.java similarity index 100% rename from codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/AddTag.java rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/AddTag.java diff --git a/codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/Copyright.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/Copyright.java similarity index 100% rename from codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/Copyright.java rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/Copyright.java diff --git a/codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/Copyright2.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/Copyright2.java similarity index 100% rename from codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/Copyright2.java rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/Copyright2.java diff --git a/codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/DynamicAttributeTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/DynamicAttributeTag.java similarity index 100% rename from codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/DynamicAttributeTag.java rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/DynamicAttributeTag.java diff --git a/codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/HelloTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/HelloTag.java similarity index 100% rename from codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/HelloTag.java rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/HelloTag.java diff --git a/codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/IteratorTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/IteratorTag.java similarity index 100% rename from codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/IteratorTag.java rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/IteratorTag.java diff --git a/codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/LoopTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/LoopTag.java similarity index 100% rename from codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/LoopTag.java rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/LoopTag.java diff --git a/codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/ToLowerCaseTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/ToLowerCaseTag.java similarity index 100% rename from codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/ToLowerCaseTag.java rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/ToLowerCaseTag.java diff --git a/codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/table/Column.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/table/Column.java similarity index 100% rename from codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/table/Column.java rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/table/Column.java diff --git a/codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/table/Table.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/table/Table.java similarity index 100% rename from codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/table/Table.java rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/table/Table.java diff --git a/codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/MultiAttributeTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/MultiAttributeTag.java similarity index 100% rename from codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/MultiAttributeTag.java rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/MultiAttributeTag.java diff --git a/codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/MultiTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/MultiTag.java similarity index 100% rename from codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/MultiTag.java rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/MultiTag.java diff --git a/codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/RepeatTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/RepeatTag.java similarity index 100% rename from codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/RepeatTag.java rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/RepeatTag.java diff --git a/codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/ToUpperCaseTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/ToUpperCaseTag.java similarity index 100% rename from codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/ToUpperCaseTag.java rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/ToUpperCaseTag.java diff --git a/codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/test/Messages.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/Messages.java similarity index 100% rename from codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/test/Messages.java rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/Messages.java diff --git a/codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/test/TestMessage.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/TestMessage.java similarity index 100% rename from codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/test/TestMessage.java rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/TestMessage.java diff --git a/codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/test/messages.properties b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/messages.properties similarity index 100% rename from codes/javaee/taglib/src/main/java/io/github/dunwu/javaee/taglib/test/messages.properties rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/messages.properties diff --git a/codes/javaee/taglib/src/main/resources/copyright.properties b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/resources/copyright.properties similarity index 100% rename from codes/javaee/taglib/src/main/resources/copyright.properties rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/resources/copyright.properties diff --git a/codes/javaee/taglib/src/main/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/resources/logback.xml similarity index 100% rename from codes/javaee/taglib/src/main/resources/logback.xml rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/resources/logback.xml diff --git a/codes/javaee/taglib/src/main/webapp/WEB-INF/tld/function.tld b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/function.tld similarity index 100% rename from codes/javaee/taglib/src/main/webapp/WEB-INF/tld/function.tld rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/function.tld diff --git a/codes/javaee/taglib/src/main/webapp/WEB-INF/tld/hello.tld b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello.tld similarity index 100% rename from codes/javaee/taglib/src/main/webapp/WEB-INF/tld/hello.tld rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello.tld diff --git a/codes/javaee/taglib/src/main/webapp/WEB-INF/tld/hello2.tld b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello2.tld similarity index 100% rename from codes/javaee/taglib/src/main/webapp/WEB-INF/tld/hello2.tld rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello2.tld diff --git a/codes/javaee/taglib/src/main/webapp/WEB-INF/tld/hello3.tld b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello3.tld similarity index 100% rename from codes/javaee/taglib/src/main/webapp/WEB-INF/tld/hello3.tld rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello3.tld diff --git a/codes/javaee/taglib/src/main/webapp/WEB-INF/tld/taglib.tld b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/taglib.tld similarity index 100% rename from codes/javaee/taglib/src/main/webapp/WEB-INF/tld/taglib.tld rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/taglib.tld diff --git a/codes/javaee/taglib/src/main/webapp/WEB-INF/web.xml b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/web.xml similarity index 100% rename from codes/javaee/taglib/src/main/webapp/WEB-INF/web.xml rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/web.xml diff --git a/codes/javaee/taglib/src/main/webapp/dynamic.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/dynamic.jsp similarity index 100% rename from codes/javaee/taglib/src/main/webapp/dynamic.jsp rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/dynamic.jsp diff --git a/codes/javaee/taglib/src/main/webapp/function.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/function.jsp similarity index 100% rename from codes/javaee/taglib/src/main/webapp/function.jsp rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/function.jsp diff --git a/codes/javaee/taglib/src/main/webapp/hello.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/hello.jsp similarity index 100% rename from codes/javaee/taglib/src/main/webapp/hello.jsp rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/hello.jsp diff --git a/codes/javaee/taglib/src/main/webapp/images/I.png b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/I.png similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/I.png rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/I.png diff --git a/codes/javaee/taglib/src/main/webapp/images/L.png b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/L.png similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/L.png rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/L.png diff --git a/codes/javaee/taglib/src/main/webapp/images/Lminus.png b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/Lminus.png similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/Lminus.png rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/Lminus.png diff --git a/codes/javaee/taglib/src/main/webapp/images/Lplus.png b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/Lplus.png similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/Lplus.png rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/Lplus.png diff --git a/codes/javaee/taglib/src/main/webapp/images/T.png b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/T.png similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/T.png rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/T.png diff --git a/codes/javaee/taglib/src/main/webapp/images/Thumbs.db b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/Thumbs.db similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/Thumbs.db rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/Thumbs.db diff --git a/codes/javaee/taglib/src/main/webapp/images/Tminus.png b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/Tminus.png similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/Tminus.png rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/Tminus.png diff --git a/codes/javaee/taglib/src/main/webapp/images/Tplus.png b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/Tplus.png similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/Tplus.png rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/Tplus.png diff --git a/codes/javaee/taglib/src/main/webapp/images/asc.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/asc.gif similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/asc.gif rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/asc.gif diff --git a/codes/javaee/taglib/src/main/webapp/images/bg-btn-blue.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/bg-btn-blue.gif similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/bg-btn-blue.gif rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/bg-btn-blue.gif diff --git a/codes/javaee/taglib/src/main/webapp/images/bg_4.jpg b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/bg_4.jpg similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/bg_4.jpg rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/bg_4.jpg diff --git a/codes/javaee/taglib/src/main/webapp/images/blank.png b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/blank.png similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/blank.png rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/blank.png diff --git a/codes/javaee/taglib/src/main/webapp/images/bottom-left.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/bottom-left.gif similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/bottom-left.gif rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/bottom-left.gif diff --git a/codes/javaee/taglib/src/main/webapp/images/bottom-right.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/bottom-right.gif similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/bottom-right.gif rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/bottom-right.gif diff --git a/codes/javaee/taglib/src/main/webapp/images/btn-go-dark.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/btn-go-dark.gif similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/btn-go-dark.gif rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/btn-go-dark.gif diff --git a/codes/javaee/taglib/src/main/webapp/images/delete.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/delete.gif similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/delete.gif rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/delete.gif diff --git a/codes/javaee/taglib/src/main/webapp/images/delimiter.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/delimiter.gif similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/delimiter.gif rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/delimiter.gif diff --git a/codes/javaee/taglib/src/main/webapp/images/desc.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/desc.gif similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/desc.gif rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/desc.gif diff --git a/codes/javaee/taglib/src/main/webapp/images/edit.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/edit.gif similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/edit.gif rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/edit.gif diff --git a/codes/javaee/taglib/src/main/webapp/images/element.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/element.gif similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/element.gif rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/element.gif diff --git a/codes/javaee/taglib/src/main/webapp/images/file.png b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/file.png similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/file.png rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/file.png diff --git a/codes/javaee/taglib/src/main/webapp/images/foldericon.png b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/foldericon.png similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/foldericon.png rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/foldericon.png diff --git a/codes/javaee/taglib/src/main/webapp/images/ibm-tab-background.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/ibm-tab-background.gif similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/ibm-tab-background.gif rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/ibm-tab-background.gif diff --git a/codes/javaee/taglib/src/main/webapp/images/ibm_logo.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/ibm_logo.gif similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/ibm_logo.gif rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/ibm_logo.gif diff --git a/codes/javaee/taglib/src/main/webapp/images/left-nav-corner.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/left-nav-corner.gif similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/left-nav-corner.gif rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/left-nav-corner.gif diff --git a/codes/javaee/taglib/src/main/webapp/images/leftnav-overview-highlight.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/leftnav-overview-highlight.gif similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/leftnav-overview-highlight.gif rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/leftnav-overview-highlight.gif diff --git a/codes/javaee/taglib/src/main/webapp/images/line01.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/line01.gif similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/line01.gif rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/line01.gif diff --git a/codes/javaee/taglib/src/main/webapp/images/masthead-links-gradient.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/masthead-links-gradient.gif similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/masthead-links-gradient.gif rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/masthead-links-gradient.gif diff --git a/codes/javaee/taglib/src/main/webapp/images/memo.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/memo.gif similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/memo.gif rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/memo.gif diff --git a/codes/javaee/taglib/src/main/webapp/images/new.png b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/new.png similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/new.png rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/new.png diff --git a/codes/javaee/taglib/src/main/webapp/images/next.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/next.gif similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/next.gif rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/next.gif diff --git a/codes/javaee/taglib/src/main/webapp/images/openfoldericon.png b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/openfoldericon.png similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/openfoldericon.png rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/openfoldericon.png diff --git a/codes/javaee/taglib/src/main/webapp/images/pagetools-gradient.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/pagetools-gradient.gif similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/pagetools-gradient.gif rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/pagetools-gradient.gif diff --git a/codes/javaee/taglib/src/main/webapp/images/pagetools_gradient_a.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/pagetools_gradient_a.gif similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/pagetools_gradient_a.gif rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/pagetools_gradient_a.gif diff --git a/codes/javaee/taglib/src/main/webapp/images/password.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/password.gif similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/password.gif rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/password.gif diff --git a/codes/javaee/taglib/src/main/webapp/images/prev.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/prev.gif similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/prev.gif rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/prev.gif diff --git a/codes/javaee/taglib/src/main/webapp/images/project.png b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/project.png similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/project.png rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/project.png diff --git a/codes/javaee/taglib/src/main/webapp/images/pspbrwse.jbf b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/pspbrwse.jbf similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/pspbrwse.jbf rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/pspbrwse.jbf diff --git a/codes/javaee/taglib/src/main/webapp/images/rl-bullet.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/rl-bullet.gif similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/rl-bullet.gif rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/rl-bullet.gif diff --git a/codes/javaee/taglib/src/main/webapp/images/role.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/role.gif similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/role.gif rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/role.gif diff --git a/codes/javaee/taglib/src/main/webapp/images/team.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/team.gif similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/team.gif rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/team.gif diff --git a/codes/javaee/taglib/src/main/webapp/images/template-gradient.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/template-gradient.gif similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/template-gradient.gif rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/template-gradient.gif diff --git a/codes/javaee/taglib/src/main/webapp/images/top-content-shadow.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/top-content-shadow.gif similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/top-content-shadow.gif rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/top-content-shadow.gif diff --git a/codes/javaee/taglib/src/main/webapp/images/top-left.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/top-left.gif similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/top-left.gif rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/top-left.gif diff --git a/codes/javaee/taglib/src/main/webapp/images/top-right.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/top-right.gif similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/top-right.gif rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/top-right.gif diff --git a/codes/javaee/taglib/src/main/webapp/images/trans.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/trans.gif similarity index 100% rename from codes/javaee/taglib/src/main/webapp/images/trans.gif rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/trans.gif diff --git a/codes/javaee/taglib/src/main/webapp/index.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/index.jsp similarity index 100% rename from codes/javaee/taglib/src/main/webapp/index.jsp rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/index.jsp diff --git a/codes/javaee/taglib/src/main/webapp/loop.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/loop.jsp similarity index 100% rename from codes/javaee/taglib/src/main/webapp/loop.jsp rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/loop.jsp diff --git a/codes/javaee/taglib/src/main/webapp/multi.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/multi.jsp similarity index 100% rename from codes/javaee/taglib/src/main/webapp/multi.jsp rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/multi.jsp diff --git a/codes/javaee/taglib/src/main/webapp/multiAttribute.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/multiAttribute.jsp similarity index 100% rename from codes/javaee/taglib/src/main/webapp/multiAttribute.jsp rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/multiAttribute.jsp diff --git a/codes/javaee/taglib/src/main/webapp/style/style.css b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/style/style.css similarity index 100% rename from codes/javaee/taglib/src/main/webapp/style/style.css rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/style/style.css diff --git a/codes/javaee/taglib/src/main/webapp/table.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/table.jsp similarity index 100% rename from codes/javaee/taglib/src/main/webapp/table.jsp rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/table.jsp diff --git a/codes/javaee/taglib/src/main/webapp/taglib/copyright.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/taglib/copyright.jsp similarity index 100% rename from codes/javaee/taglib/src/main/webapp/taglib/copyright.jsp rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/taglib/copyright.jsp diff --git a/codes/javaee/taglib/src/main/webapp/taglib/helloTag.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/taglib/helloTag.jsp similarity index 100% rename from codes/javaee/taglib/src/main/webapp/taglib/helloTag.jsp rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/taglib/helloTag.jsp diff --git a/codes/javaee/taglib/src/main/webapp/taglib/helloTag2.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/taglib/helloTag2.jsp similarity index 100% rename from codes/javaee/taglib/src/main/webapp/taglib/helloTag2.jsp rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/taglib/helloTag2.jsp diff --git a/codes/javaee/taglib/src/main/webapp/taglib/helloTag3.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/taglib/helloTag3.jsp similarity index 100% rename from codes/javaee/taglib/src/main/webapp/taglib/helloTag3.jsp rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/taglib/helloTag3.jsp diff --git a/codes/javaee/taglib/src/main/webapp/test.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/test.jsp similarity index 100% rename from codes/javaee/taglib/src/main/webapp/test.jsp rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/test.jsp diff --git a/codes/javaee/taglib/src/main/webapp/toLowerCase.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/toLowerCase.jsp similarity index 100% rename from codes/javaee/taglib/src/main/webapp/toLowerCase.jsp rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/toLowerCase.jsp diff --git a/codes/javaee/taglib/src/main/webapp/toUpperCase.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/toUpperCase.jsp similarity index 100% rename from codes/javaee/taglib/src/main/webapp/toUpperCase.jsp rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/toUpperCase.jsp diff --git a/codes/javaee/taglib/src/main/webapp/vote.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/vote.gif similarity index 100% rename from codes/javaee/taglib/src/main/webapp/vote.gif rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/vote.gif diff --git a/codes/javaee/taglib/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java similarity index 100% rename from codes/javaee/taglib/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java diff --git a/codes/javaee/taglib/src/test/java/io/github/dunwu/javaee/server/Profiles.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/Profiles.java similarity index 100% rename from codes/javaee/taglib/src/test/java/io/github/dunwu/javaee/server/Profiles.java rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/Profiles.java diff --git a/codes/javaee/taglib/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java similarity index 100% rename from codes/javaee/taglib/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java diff --git a/codes/javaee/taglib/src/test/resources/jetty/webdefault.xml b/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/resources/jetty/webdefault.xml similarity index 100% rename from codes/javaee/taglib/src/test/resources/jetty/webdefault.xml rename to codes/javaee-tutorial/javaee-tutorial-taglib/src/test/resources/jetty/webdefault.xml diff --git a/codes/javaee/websocket/pom.xml b/codes/javaee-tutorial/javaee-tutorial-websocket/pom.xml similarity index 79% rename from codes/javaee/websocket/pom.xml rename to codes/javaee-tutorial/javaee-tutorial-websocket/pom.xml index cb21a2ae..1ffac1e2 100644 --- a/codes/javaee/websocket/pom.xml +++ b/codes/javaee-tutorial/javaee-tutorial-websocket/pom.xml @@ -3,23 +3,20 @@ xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 4.0.0 - - - - io.github.dunwu - javaee-notes-websocket + javaee-tutorial-websocket 1.0.0 war - + javaee-tutorial-websocket + JavaEE 学习笔记之 WebSocket + + + UTF-8 + 1.7 + ${java.version} + ${java.version} + - - - io.github.dunwu - javaee-notes-parent - 1.0.0 - ../parent - @@ -40,7 +37,7 @@ javax.servlet.jsp - jsp-api + javax.servlet.jsp-api provided @@ -84,26 +81,13 @@ - - - - UTF-8 - 1.7 - ${java.version} - ${java.version} - - - - - - - org.apache.tomcat.maven tomcat7-maven-plugin + 2.2 8089 /${artifactId} @@ -113,6 +97,7 @@ org.eclipse.jetty jetty-maven-plugin + 9.4.18.v20190429 8089 @@ -124,13 +109,16 @@ - - - - - javaee-notes-websocket - javaee 学习笔记之 websocket - - + + + + io.github.dunwu + dunwu-dependencies + 0.4.0-SNAPSHOT + pom + import + + + diff --git a/codes/javaee/websocket/src/main/java/io/github/dunwu/javaee/servlet/SocketServlet.java b/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/servlet/SocketServlet.java similarity index 100% rename from codes/javaee/websocket/src/main/java/io/github/dunwu/javaee/servlet/SocketServlet.java rename to codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/servlet/SocketServlet.java diff --git a/codes/javaee/websocket/src/main/java/io/github/dunwu/javaee/websocket/WebSocketServer.java b/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/websocket/WebSocketServer.java similarity index 100% rename from codes/javaee/websocket/src/main/java/io/github/dunwu/javaee/websocket/WebSocketServer.java rename to codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/websocket/WebSocketServer.java diff --git a/codes/javaee/websocket/src/main/java/io/github/dunwu/javaee/websocket/WebSocketServerConfigurator.java b/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/websocket/WebSocketServerConfigurator.java similarity index 100% rename from codes/javaee/websocket/src/main/java/io/github/dunwu/javaee/websocket/WebSocketServerConfigurator.java rename to codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/websocket/WebSocketServerConfigurator.java diff --git a/codes/javaee/websocket/src/main/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/resources/logback.xml similarity index 95% rename from codes/javaee/websocket/src/main/resources/logback.xml rename to codes/javaee-tutorial/javaee-tutorial-websocket/src/main/resources/logback.xml index 2b38c24b..3146d0f1 100644 --- a/codes/javaee/websocket/src/main/resources/logback.xml +++ b/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/resources/logback.xml @@ -3,7 +3,7 @@ - + diff --git a/codes/javaee/websocket/src/main/webapp/WEB-INF/web.xml b/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/webapp/WEB-INF/web.xml similarity index 100% rename from codes/javaee/websocket/src/main/webapp/WEB-INF/web.xml rename to codes/javaee-tutorial/javaee-tutorial-websocket/src/main/webapp/WEB-INF/web.xml diff --git a/codes/javaee/websocket/src/main/webapp/websocketA.html b/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/webapp/websocketA.html similarity index 100% rename from codes/javaee/websocket/src/main/webapp/websocketA.html rename to codes/javaee-tutorial/javaee-tutorial-websocket/src/main/webapp/websocketA.html diff --git a/codes/javaee/websocket/src/main/webapp/websocketB.html b/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/webapp/websocketB.html similarity index 100% rename from codes/javaee/websocket/src/main/webapp/websocketB.html rename to codes/javaee-tutorial/javaee-tutorial-websocket/src/main/webapp/websocketB.html diff --git a/codes/javaee/websocket/src/test/java/io/github/dunwu/javaee/server/DevServer.java b/codes/javaee-tutorial/javaee-tutorial-websocket/src/test/java/io/github/dunwu/javaee/server/DevServer.java similarity index 100% rename from codes/javaee/websocket/src/test/java/io/github/dunwu/javaee/server/DevServer.java rename to codes/javaee-tutorial/javaee-tutorial-websocket/src/test/java/io/github/dunwu/javaee/server/DevServer.java diff --git a/codes/javaee/websocket/src/test/resources/jetty/webdefault.xml b/codes/javaee-tutorial/javaee-tutorial-websocket/src/test/resources/jetty/webdefault.xml similarity index 100% rename from codes/javaee/websocket/src/test/resources/jetty/webdefault.xml rename to codes/javaee-tutorial/javaee-tutorial-websocket/src/test/resources/jetty/webdefault.xml diff --git a/codes/javaee/websocket/src/test/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-websocket/src/test/resources/logback.xml similarity index 95% rename from codes/javaee/websocket/src/test/resources/logback.xml rename to codes/javaee-tutorial/javaee-tutorial-websocket/src/test/resources/logback.xml index 1784e3bf..d3067f1e 100644 --- a/codes/javaee/websocket/src/test/resources/logback.xml +++ b/codes/javaee-tutorial/javaee-tutorial-websocket/src/test/resources/logback.xml @@ -3,7 +3,7 @@ - + diff --git a/codes/javaee-tutorial/pom.xml b/codes/javaee-tutorial/pom.xml new file mode 100644 index 00000000..ac104294 --- /dev/null +++ b/codes/javaee-tutorial/pom.xml @@ -0,0 +1,121 @@ + + + 4.0.0 + + io.github.dunwu + javaee-tutorial + 1.0.0 + pom + + + javaee-tutorial-servlet + javaee-tutorial-jsp + javaee-tutorial-session + javaee-tutorial-filter + javaee-tutorial-listener + javaee-tutorial-jstl + javaee-tutorial-taglib + javaee-tutorial-oss + javaee-tutorial-websocket + + + + UTF-8 + 1.6 + ${java.version} + ${java.version} + + + 9.4.12.v20180830 + 4.1.4.RELEASE + 1.4.0 + 4.12 + + 2.9.5 + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.12 + + + org.apache.tomcat.maven + tomcat7-maven-plugin + 2.2 + + + org.eclipse.jetty + jetty-maven-plugin + ${jetty.version} + + + org.codehaus.mojo + aspectj-maven-plugin + + 1.2 + + + + org.aspectj + aspectjrt + 1.8.1 + + + org.aspectj + aspectjtools + 1.8.1 + + + + + + compile + test-compile + + + + + true + ${java.version} + ${java.version} + + + + + org.mybatis.generator + mybatis-generator-maven-plugin + 1.3.5 + + + org.mybatis.generator + mybatis-generator-core + 1.3.5 + + + + + + + + 2016-2019 + + + Apache License, Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + repo + A business-friendly OSS license + + + + + Zhang Peng + forbreak@163.com + +8 + + + diff --git a/codes/javaee/jstl/pom.xml b/codes/javaee/jstl/pom.xml deleted file mode 100644 index 5b82b0bd..00000000 --- a/codes/javaee/jstl/pom.xml +++ /dev/null @@ -1,184 +0,0 @@ - - - 4.0.0 - - - io.github.dunwu - javaee-notes-jstl - 1.0.0 - war - - - - - javaee-notes-jstl - javaee 学习笔记之 jstl - - - - - - UTF-8 - 1.7 - ${java.version} - ${java.version} - - - 9.4.12.v20180830 - - - - - ch.qos.logback - logback-classic - 1.1.3 - - - ch.qos.logback - logback-core - 1.1.3 - - - org.logback-extensions - logback-ext-spring - 0.1.2 - - - org.slf4j - jcl-over-slf4j - 1.7.12 - - - - - - commons-fileupload - commons-fileupload - 1.3.3 - - - commons-io - commons-io - 2.5 - - - org.apache.commons - commons-lang3 - 3.4 - - - - - - javax.servlet - javax.servlet-api - 3.1.0 - - - javax.servlet.jsp - jsp-api - 2.2 - - - javax.servlet - jstl - 1.2 - - - - - javax.servlet.jsp.jstl - jstl-api - 1.2 - - - javax.servlet - servlet-api - - - javax.servlet.jsp - jsp-api - - - - - - org.glassfish.web - jstl-impl - 1.2 - - - javax.servlet - servlet-api - - - javax.servlet.jsp - jsp-api - - - javax.servlet.jsp.jstl - jstl-api - - - - - - - - org.eclipse.jetty - jetty-webapp - ${jetty.version} - - - org.eclipse.jetty - jetty-annotations - ${jetty.version} - - - org.eclipse.jetty - apache-jsp - ${jetty.version} - - - org.eclipse.jetty - apache-jstl - ${jetty.version} - - - - - - junit - junit - 4.12 - - - org.assertj - assertj-core - 3.4.1 - - - - - - - xalan - xalan - 2.7.2 - - - xerces - xercesImpl - 2.11.0 - - - - - - - - - - - diff --git a/codes/javaee/listener/pom.xml b/codes/javaee/listener/pom.xml deleted file mode 100644 index 022a5a5a..00000000 --- a/codes/javaee/listener/pom.xml +++ /dev/null @@ -1,183 +0,0 @@ - - 4.0.0 - - - io.github.dunwu - javaee-notes-listener - 1.0.0 - war - - - - - javaee-notes-listener - javaee 学习笔记之 listener - - - - - - UTF-8 - 1.7 - ${java.version} - ${java.version} - - - 9.4.12.v20180830 - - - - - ch.qos.logback - logback-classic - 1.1.3 - - - ch.qos.logback - logback-core - 1.1.3 - - - org.logback-extensions - logback-ext-spring - 0.1.2 - - - org.slf4j - jcl-over-slf4j - 1.7.12 - - - - - - commons-fileupload - commons-fileupload - 1.3.3 - - - commons-io - commons-io - 2.5 - - - org.apache.commons - commons-lang3 - 3.4 - - - - - - javax.servlet - javax.servlet-api - 3.1.0 - - - javax.servlet.jsp - jsp-api - 2.2 - - - javax.servlet - jstl - 1.2 - - - - - javax.servlet.jsp.jstl - jstl-api - 1.2 - - - javax.servlet - servlet-api - - - javax.servlet.jsp - jsp-api - - - - - - org.glassfish.web - jstl-impl - 1.2 - - - javax.servlet - servlet-api - - - javax.servlet.jsp - jsp-api - - - javax.servlet.jsp.jstl - jstl-api - - - - - - - - org.eclipse.jetty - jetty-webapp - ${jetty.version} - - - org.eclipse.jetty - jetty-annotations - ${jetty.version} - - - org.eclipse.jetty - apache-jsp - ${jetty.version} - - - org.eclipse.jetty - apache-jstl - ${jetty.version} - - - - - - junit - junit - 4.12 - - - org.assertj - assertj-core - 3.4.1 - - - - - - - xalan - xalan - 2.7.2 - - - xerces - xercesImpl - 2.11.0 - - - - - - - - - - - diff --git a/codes/javaee/pom.xml b/codes/javaee/pom.xml deleted file mode 100644 index f8ef2b5b..00000000 --- a/codes/javaee/pom.xml +++ /dev/null @@ -1,80 +0,0 @@ - - - 4.0.0 - - - - - - io.github.dunwu - javaee-notes - 1.0.0 - pom - - - - - parent - servlet - jsp - session - filter - listener - jstl - taglib - oss - websocket - - - - - - UTF-8 - - - - - - - - - - - - javaee-notes - javaee 学习笔记 - https://github.com/dunwu/javaee-notes - 2016-2017 - - - Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0.txt - repo - A business-friendly OSS license - - - - - Zhang Peng - forbreak@163.com - +8 - - - - - - - - Github - https://github.com/dunwu/javaee-notes/issues - - - https://github.com/dunwu/javaee-notes - scm:git:git://github.com/dunwu/javaee-notes.git - scm:git:ssh://git@github.com:dunwu/javaee-notes.git - - - - - diff --git a/codes/javaee/taglib/pom.xml b/codes/javaee/taglib/pom.xml deleted file mode 100644 index 332a5a20..00000000 --- a/codes/javaee/taglib/pom.xml +++ /dev/null @@ -1,181 +0,0 @@ - - - 4.0.0 - - - io.github.dunwu - javaee-notes-taglib - 1.0.0 - war - - - - - javaee-notes-taglib - javaee 学习笔记之 taglib - - - - - - UTF-8 - 1.7 - ${java.version} - ${java.version} - - - 9.4.12.v20180830 - - - - - ch.qos.logback - logback-classic - 1.1.3 - - - ch.qos.logback - logback-core - 1.1.3 - - - org.logback-extensions - logback-ext-spring - 0.1.2 - - - org.slf4j - jcl-over-slf4j - 1.7.12 - - - - - - commons-fileupload - commons-fileupload - 1.3.3 - - - commons-io - commons-io - 2.5 - - - org.apache.commons - commons-lang3 - 3.4 - - - - - - javax.servlet - javax.servlet-api - 3.1.0 - - - javax.servlet.jsp - jsp-api - 2.2 - - - javax.servlet - jstl - 1.2 - - - javax.servlet.jsp.jstl - jstl-api - 1.2 - - - javax.servlet - servlet-api - - - javax.servlet.jsp - jsp-api - - - - - org.glassfish.web - jstl-impl - 1.2 - - - javax.servlet - servlet-api - - - javax.servlet.jsp - jsp-api - - - javax.servlet.jsp.jstl - jstl-api - - - - - - - - org.eclipse.jetty - jetty-webapp - ${jetty.version} - - - org.eclipse.jetty - jetty-annotations - ${jetty.version} - - - org.eclipse.jetty - apache-jsp - ${jetty.version} - - - org.eclipse.jetty - apache-jstl - ${jetty.version} - - - - - - junit - junit - 4.12 - - - org.assertj - assertj-core - 3.4.1 - - - - - - - xalan - xalan - 2.7.2 - - - xerces - xercesImpl - 2.11.0 - - - - - - - - - - - From f4acf1e891b758c811db8d469b1c79b2e0b0b245 Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Mon, 27 May 2019 22:36:15 +0800 Subject: [PATCH 14/75] =?UTF-8?q?=E6=95=B4=E7=90=86=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/README.md | 93 + docs/distributed/cache/redis.md | 12 +- ...03\345\274\217\347\274\223\345\255\230.md" | 36 +- docs/distributed/mq/ActiveMQ.md | 19 +- docs/distributed/mq/kafka-advanced.md | 1132 ++++++++++ docs/distributed/mq/kafka-basics.md | 1094 ++++++++++ docs/distributed/mq/rocketmq-advanced.md | 15 +- docs/distributed/mq/rocketmq-basics.md | 11 - ...10\346\201\257\351\230\237\345\210\227.md" | 46 +- docs/distributed/rpc/dubbo.md | 10 - docs/distributed/rpc/zookeeper-advanced.md | 10 - docs/distributed/rpc/zookeeper-basics.md | 10 - docs/index.html | 62 + docs/javaee/README.md | 13 +- .../cookie.md => cookie-and-sesion.md} | 282 ++- docs/javaee/jsp.md | 1829 +++++++++++++++++ docs/javaee/jsp/jsp-action.md | 400 ---- docs/javaee/jsp/jsp-directive.md | 115 -- docs/javaee/jsp/jsp-el-expression.md | 217 -- docs/javaee/jsp/jsp-grammar.md | 383 ---- docs/javaee/jsp/jsp-implicit-objects.md | 113 - docs/javaee/jsp/jsp-introduction.md | 152 -- docs/javaee/jsp/jsp-locale.md | 62 - docs/javaee/jsp/taglib.md | 15 - docs/javaee/jstl/jstl-core.md | 350 ---- docs/javaee/jstl/jstl-format.md | 362 ---- docs/javaee/jstl/jstl-function.md | 35 - docs/javaee/jstl/jstl-sql.md | 135 -- docs/javaee/jstl/jstl-xml.md | 246 --- docs/javaee/{listener => }/listener.md | 27 +- .../servlet-and-status.md => servlet.md} | 211 +- docs/javaee/servlet/servlet-demo.md | 87 - docs/javaee/servlet/servlet-introduction.md | 141 -- docs/javaee/session/cookie-and-sesion.md | 48 - docs/javaee/session/session.md | 182 -- docs/javaee/taglib/taglib.md | 13 - docs/navbar.md | 5 + docs/orm/mybatis.md | 356 ++++ docs/package.json | 9 + docs/security/shiro.md | 68 + 40 files changed, 5183 insertions(+), 3223 deletions(-) create mode 100644 docs/README.md create mode 100644 docs/distributed/mq/kafka-advanced.md create mode 100644 docs/distributed/mq/kafka-basics.md create mode 100644 docs/index.html rename docs/javaee/{session/cookie.md => cookie-and-sesion.md} (53%) create mode 100644 docs/javaee/jsp.md delete mode 100644 docs/javaee/jsp/jsp-action.md delete mode 100644 docs/javaee/jsp/jsp-directive.md delete mode 100644 docs/javaee/jsp/jsp-el-expression.md delete mode 100644 docs/javaee/jsp/jsp-grammar.md delete mode 100644 docs/javaee/jsp/jsp-implicit-objects.md delete mode 100644 docs/javaee/jsp/jsp-introduction.md delete mode 100644 docs/javaee/jsp/jsp-locale.md delete mode 100644 docs/javaee/jsp/taglib.md delete mode 100644 docs/javaee/jstl/jstl-core.md delete mode 100644 docs/javaee/jstl/jstl-format.md delete mode 100644 docs/javaee/jstl/jstl-function.md delete mode 100644 docs/javaee/jstl/jstl-sql.md delete mode 100644 docs/javaee/jstl/jstl-xml.md rename docs/javaee/{listener => }/listener.md (87%) rename docs/javaee/{servlet/servlet-and-status.md => servlet.md} (50%) delete mode 100644 docs/javaee/servlet/servlet-demo.md delete mode 100644 docs/javaee/servlet/servlet-introduction.md delete mode 100644 docs/javaee/session/cookie-and-sesion.md delete mode 100644 docs/javaee/session/session.md delete mode 100644 docs/javaee/taglib/taglib.md create mode 100644 docs/navbar.md create mode 100644 docs/orm/mybatis.md create mode 100644 docs/package.json create mode 100644 docs/security/shiro.md diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..d3fc6f8a --- /dev/null +++ b/docs/README.md @@ -0,0 +1,93 @@ +# JavaWeb + +> Java Web 开发之路经验总结 + +| :beginner: | :recycle: | :one: | :two: | :three: | :four: | +| ---------------------- | ----------------------------- | --------------------- | ----------------------------- | ------------------------------- | ------------------ | +| [准备](#beginner-准备) | [架构设计](#recycle-架构设计) | [JavaEE](#one-javaee) | [单点式技术](#two-单点式技术) | [分布式技术](#three-分布式技术) | [工具](#four-工具) | + + + +## :beginner: 准备 + +作为 Web 工程师,你应该多多少少掌握一些的知识: + +- [网络通信](https://github.com/dunwu/notes/tree/master/网络通信) - 你需要掌握一些网络通信协议知识。至少,你应该知道 HTTP、DNS 协议的工作机制。 +- [前端技术指南](https://github.com/dunwu/frontend-tutorial) - 即使是后端工程师,也难免会接触到前端技术。前端技术五花八门,如:React、Vue、Angular、Webpack、ES6、Babel、Node.js 等等。不说掌握,至少也应该知道这些技术是什么。 + +## :recycle: 架构设计 + +> [架构设计](docs/architecture/) 整理架构设计方面的一些学习总结和心得。 + +- [大型网站架构概述](docs/architecture/大型网站架构概述.md) +- [网站的高性能架构](docs/architecture/网站的高性能架构.md) +- [网站的高可用架构](docs/architecture/网站的高可用架构.md) +- [网站的伸缩性架构](docs/architecture/网站的伸缩性架构.md) +- [网站的可扩展架构](docs/architecture/网站的可扩展架构.md) +- [网站的安全架构](docs/architecture/网站的安全架构.md) - 关键词:XSS、CSRF、SQL 注入、DoS、消息摘要、加密算法、证书 +- [网站典型故障](docs/architecture/网站典型故障.md) + +## :one: JavaEE + +> [JavaEE](docs/javaee/) 技术——Java Web 的基石 + +## :two: 单点式技术 + +> [单点式技术(Standalone)](docs/standalone/),典型的技术如:SSM 框架、SSH 框架。 + +### Platform + +- [Spring](https://github.com/dunwu/spring-notes) - JavaSE/JavaEE 一站式开发框架。 +- [Spring Boot](https://github.com/dunwu/spring-boot-tutorial) - 简化*Spring*应用的初始搭建以及开发过程。 + +### ORM + +- [Mybatis](docs/standalone/orm/mybatis.md) - 一个支持普通 SQL 查询,存储过程和高级映射的优秀持久层框架。 +- Hibernate - 待补充。。。 + +### 安全 + +- [Shiro](docs/standalone/security/shiro.md) - 安全框架,具有认证、授权、加密、会话管理功能。 + +## :three: 分布式技术 + +> [分布式技术(Distributed)](docs/distributed/),典型的技术如:分布式缓存、分布式消息队列、分布式服务、分布式搜索引擎等。 + +- [分布式技术面试题](docs/distributed/分布式技术面试题.md) + +### [分布式缓存(CACHE)](docs/distributed/cache) + +- [分布式缓存](docs/distributed/cache/分布式缓存.md) +- [Redis](docs/distributed/cache/redis.md) +- Memcached + +### [分布式服务(RPC)](docs/distributed/rpc) + +- [Dubbo](docs/distributed/rpc/dubbo.md) - 基于 Java 开发的高性能 RPC 框架。 +- [ZooKeeper 实战篇](docs/distributed/rpc/zookeeper-basics.md) +- [ZooKeeper 原理篇](docs/distributed/rpc/zookeeper-advanced.md) + +### [分布式消息队列(MQ)](docs/distributed/mq) + +- 原理 + - [分布式消息队列](docs/distributed/mq/分布式消息队列.md) +- Kafka + - [Kafka 实战篇](docs/distributed/mq/kafka/kafka-basics.md) + - [Kafka 原理篇](docs/distributed/mq/kafka/kafka-advanced.md) + - [Kafka Cheat Sheet](docs/distributed/mq/kafka/kafka-cheat-sheet.md) +- [RocketMQ 实战篇](docs/distributed/mq/rocketmq-basics.md) +- [RocketMQ 原理篇](docs/distributed/mq/rocketmq-basics.md) +- [ActiveMQ 实战篇](docs/distributed/mq/ActiveMQ.md) +- RabbitMQ - 待补充。。。 + +### 分布式搜索引擎 + +- ElasticSearch - 待补充。。。 + +## :four: 工具 + +> [工具](docs/tools/) 整理了 Java Web 领域常用软件。 + +- [Nginx](https://github.com/dunwu/Nginx) - 轻量级的 Web 服务器、反向代理服务器及电子邮件(IMAP/POP3)代理服务器,支持负载均衡。 +- [Tomcat](docs/tools/tomcat.md) - 轻量级的应用服务器 +- [Jetty](docs/tools/jetty.md) - 比 Tomcat 更轻量级的应用服务器 diff --git a/docs/distributed/cache/redis.md b/docs/distributed/cache/redis.md index 3675a43c..e380ac35 100644 --- a/docs/distributed/cache/redis.md +++ b/docs/distributed/cache/redis.md @@ -1,6 +1,6 @@ --- title: Redis -date: 2018/06/09 +date: 2018-06-09 categories: - database tags: @@ -107,7 +107,7 @@ Redis 与 Memcached 因为都可以用于缓存,所以常常被拿来做比较 ### 2.1. STRING
- +
命令: @@ -134,7 +134,7 @@ OK ### 2.2. LIST
- +
命令: @@ -171,7 +171,7 @@ OK ### 2.3. SET
- +
命令: @@ -218,7 +218,7 @@ OK ### 2.4. HASH
- +
命令: @@ -269,7 +269,7 @@ OK ### 2.5. ZSET
- +
命令: diff --git "a/docs/distributed/cache/\345\210\206\345\270\203\345\274\217\347\274\223\345\255\230.md" "b/docs/distributed/cache/\345\210\206\345\270\203\345\274\217\347\274\223\345\255\230.md" index 5dfaa459..1082b56a 100644 --- "a/docs/distributed/cache/\345\210\206\345\270\203\345\274\217\347\274\223\345\255\230.md" +++ "b/docs/distributed/cache/\345\210\206\345\270\203\345\274\217\347\274\223\345\255\230.md" @@ -1,6 +1,6 @@ --- title: 分布式缓存 -date: 2018/07/05 +date: 2018-07-05 categories: - 分布式 tags: @@ -110,7 +110,7 @@ CDN 的基本原理是广泛采用各种缓存服务器,将这些缓存服务 (1)未部署 CDN 应用前 -![img](https://images2015.cnblogs.com/blog/820332/201606/820332-20160606062828308-319014468.png) +
网络路径: @@ -121,7 +121,7 @@ CDN 的基本原理是广泛采用各种缓存服务器,将这些缓存服务 (2)部署 CDN 应用后 -![img](https://images2015.cnblogs.com/blog/820332/201606/820332-20160606062837558-517040681.png) +
网络路径: @@ -159,7 +159,7 @@ b. 如何保证数据的一致性和实时性需要权衡考虑; 摘自《云宙视频 CDN 系统》 -![img](https://images2015.cnblogs.com/blog/820332/201606/820332-20160606062848590-1014999706.jpg) +
### 2.4. CND 技术实践 @@ -167,7 +167,7 @@ b. 如何保证数据的一致性和实时性需要权衡考虑; 淘宝 CDN,如下图(来自网络): -![img](https://images2015.cnblogs.com/blog/820332/201606/820332-20160606062859808-1261244246.png) +
## 3. 反向代理缓存 @@ -177,7 +177,7 @@ b. 如何保证数据的一致性和实时性需要权衡考虑; 反向代理位于应用服务器机房,处理所有对 WEB 服务器的请求。如果用户请求的页面在代理服务器上有缓冲的话,代理服务器直接将缓冲内容发送给用户。如果没有缓冲则先向 WEB 服务器发出请求,取回数据,本地缓存后再发送给用户。通过降低向 WEB 服务器的请求数,从而降低了 WEB 服务器的负载。 -![img](https://images2015.cnblogs.com/blog/820332/201606/820332-20160606062912886-1223701582.png) +
​ 反向代理一般缓存静态资源,动态资源转发到应用服务器处理。常用的缓存应用服务器有 Varnish,Ngnix,Squid。 @@ -190,7 +190,7 @@ Squid 反向代理一般只缓存静态资源,动态程序默认不缓存。 - Cache-Control: 告诉反向代理页面是否应该被缓冲 - Pragma: 用来包含实现特定的指令,最常用的是 Pragma:no-cache -![img](https://images2015.cnblogs.com/blog/820332/201606/820332-20160606062921683-1401924233.jpg) +
Squid 反向代理加速网站实例 @@ -233,7 +233,7 @@ Memcache 特性: ### 4.2. Memcache 工作原理 -![img](https://images2015.cnblogs.com/blog/820332/201606/820332-20160606064023621-1066632858.png) +
Memcache 的工作流程如下: @@ -250,7 +250,7 @@ Memcached 虽然称为 “ 分布式 ” 缓存服务器,但服务器端并没 存取数据分二步走,第一步,选择服务器,第二步存取数据。 -![img](https://images2015.cnblogs.com/blog/820332/201606/820332-20160606064101183-532365936.png) +
**分布式算法(Consistent Hashing):** @@ -263,7 +263,7 @@ Memcached 虽然称为 “ 分布式 ” 缓存服务器,但服务器端并没 - 散列算法:(一致性 Hash) 先算出 Memcached 服务器的散列值,并将其分布到 0 到 2 的 32 次方的圆上,然后用同样的方法算出存储数据的键的散列值并映射至圆上,最后从数据映射到的位置开始顺时针查找,将数据保存到查找到的第一个服务器上,如果超过 2 的 32 次方,依然找不到服务器,就将数据保存到第一台 Memcached 服务器上。 -![img](https://images2015.cnblogs.com/blog/820332/201606/820332-20160606064109965-1633058692.jpg) +
如果添加了一台 Memcached 服务器,只在圆上增加服务器的逆时针方向的第一台服务器上的键会受到影响。 @@ -291,7 +291,7 @@ Redis 是一个开源(BSD 许可)的,基于内存的,多数据结构存 应用场景:以存储一个用户信息对象数据,为例: -![img](https://images2015.cnblogs.com/blog/820332/201606/820332-20160606064128371-891992227.png) +
实现方式: @@ -341,7 +341,7 @@ Redis sorted set 的内部使用 HashMap 和跳跃表(SkipList)来保证数据 (1)通过 keepalived 实现的高可用方案 -![img](https://images2015.cnblogs.com/blog/820332/201606/820332-20160606064149465-1648287265.png) +
切换流程: @@ -366,7 +366,7 @@ Redis sorted set 的内部使用 HashMap 和跳跃表(SkipList)来保证数据 特点:快、轻量级、减少后端 Cache Server 连接数、易配置、支持 ketama、modula、random、常用 hash 分片算法。 -![img](https://images2015.cnblogs.com/blog/820332/201606/820332-20160606064241496-1034024146.png) +
这里使用 keepalived 实现高可用主备方案,解决 proxy 单点问题; @@ -398,7 +398,7 @@ Redis sorted set 的内部使用 HashMap 和跳跃表(SkipList)来保证数据 ## 6. 缓存架构示例 -![img](https://images2015.cnblogs.com/blog/820332/201606/820332-20160606064322168-810312242.png) +
职责划分: @@ -429,7 +429,7 @@ Redis sorted set 的内部使用 HashMap 和跳跃表(SkipList)来保证数据 ​ 如下图: -![img](https://images2015.cnblogs.com/blog/820332/201606/820332-20160619110523914-514476589.jpg) +
假如缓存写成功,但写数据库失败或响应延迟,则下次读取(并发读)缓存时,就出现脏读; @@ -437,7 +437,7 @@ Redis sorted set 的内部使用 HashMap 和跳跃表(SkipList)来保证数据 ​ 如下图: -​ ![img](https://images2015.cnblogs.com/blog/820332/201606/820332-20160619110532039-1008665032.jpg) +
​ 假如写数据库成功,但写缓存失败,则下次读取(并发读)缓存时,则读不到数据; @@ -445,7 +445,7 @@ Redis sorted set 的内部使用 HashMap 和跳跃表(SkipList)来保证数据 ​ 指数据库操作和写缓存不在一个操作步骤中,比如在分布式场景下,无法做到同时写缓存或需要异步刷新(补救措施)时候。 -![img](https://images2015.cnblogs.com/blog/820332/201606/820332-20160619110540555-889329617.jpg) +
​ 此种情况,主要考虑数据写入和缓存刷新的时效性。比如多久内刷新缓存,不影响用户对数据的访问。 @@ -490,7 +490,7 @@ Redis sorted set 的内部使用 HashMap 和跳跃表(SkipList)来保证数据 ​ 缓存的高可用,一般通过分布式和复制实现。分布式实现数据的海量缓存,复制实现缓存数据节点的高可用。架构图如下: -​ ![img](https://images2015.cnblogs.com/blog/820332/201606/820332-20160619110556336-2133732642.jpg) +
​ 其中,分布式采用一致性 Hash 算法,复制采用异步复制。 diff --git a/docs/distributed/mq/ActiveMQ.md b/docs/distributed/mq/ActiveMQ.md index d3304742..1d99ab8e 100644 --- a/docs/distributed/mq/ActiveMQ.md +++ b/docs/distributed/mq/ActiveMQ.md @@ -1,16 +1,3 @@ ---- -title: ActiveMQ -date: 2017/8/18 -categories: -- javaweb -tags: -- java -- javaweb -- 分布式 -- mq -- jms ---- - # ActiveMQ @@ -37,7 +24,7 @@ JMS 有两种消息模型: #### P2P 的特点 -![jms-pointToPoint.gif](http://oyz7npk35.bkt.clouddn.com//image/java/libs/activemq/jms-pointToPoint.gif) +
在点对点的消息系统中,消息分发给一个单独的使用者。点对点消息往往与队列 `javax.jms.Queue` 相关联。 @@ -51,7 +38,7 @@ JMS 有两种消息模型: #### Pub/Sub 的特点 -![jms-publishSubscribe.gif](http://oyz7npk35.bkt.clouddn.com//image/java/libs/activemq/jms-publishSubscribe.gif) +
发布/订阅消息系统支持一个事件驱动模型,消息生产者和消费者都参与消息的传递。生产者发布事件,而使用者订阅感兴趣的事件,并使用事件。该类型消息一般与特定的主题 `javax.jms.Topic` 关联。 @@ -65,7 +52,7 @@ JMS 有两种消息模型: ### 1.2. JMS 编程模型 -![jms-publishSubscribe.gif](http://oyz7npk35.bkt.clouddn.com//image/java/libs/activemq/jms-publishSubscribe.gif) +
#### ConnectionFactory diff --git a/docs/distributed/mq/kafka-advanced.md b/docs/distributed/mq/kafka-advanced.md new file mode 100644 index 00000000..1c4d343d --- /dev/null +++ b/docs/distributed/mq/kafka-advanced.md @@ -0,0 +1,1132 @@ +# Kafka + +> Kafka 是一个分布式的、可水平扩展的、基于发布/订阅模式的、支持容错的消息系统。 + + + +- [1. 概述](#1-概述) + - [1.1. 分布式](#11-分布式) + - [1.2. 容错](#12-容错) + - [1.3. 提交日志](#13-提交日志) + - [1.4. 消息队列](#14-消息队列) + - [1.5. 为什么要使用消息系统](#15-为什么要使用消息系统) + - [1.6. Kafka 的关键功能](#16-kafka-的关键功能) + - [1.7. Kafka 基本概念](#17-kafka-基本概念) + - [1.8. Kafka 核心 API](#18-kafka-核心-api) + - [1.9. Topic 和日志](#19-topic-和日志) +- [2. Kafka 工作原理](#2-kafka-工作原理) +- [3. 持久化](#3-持久化) +- [4. 复制](#4-复制) +- [5. 流处理](#5-流处理) + - [5.1. 无状态处理](#51-无状态处理) + - [5.2. 有状态处理](#52-有状态处理) +- [6. Kafka 应用场景](#6-kafka-应用场景) +- [7. 幂等性](#7-幂等性) + - [7.1. 幂等性实现](#71-幂等性实现) + - [7.2. 幂等性的应用实例](#72-幂等性的应用实例) +- [8. 事务](#8-事务) + - [8.1. 事务属性理解](#81-事务属性理解) + - [8.2. 引入事务目的](#82-引入事务目的) + - [8.3. 事务操作的 API](#83-事务操作的-api) + - [8.4. 事务属性的应用实例](#84-事务属性的应用实例) + - [8.5. 生产者事务的实现](#85-生产者事务的实现) + - [8.6. 其他思考](#86-其他思考) +- [9. 资料](#9-资料) + - [9.1. 官方资料](#91-官方资料) + - [9.2. 第三方资料](#92-第三方资料) + + + +## 1. 概述 + +### 1.1. 分布式 + +分布式系统是一个由多个运行机器组成的系统,所有这些机器在一个集群中一起工作,对最终端用户表现为一个节点。 + +Kafka 的分布式意义在于:它在不同的节点上存储、接收和发送消息。 + +### 1.2. 容错 + +分布式系统一般都会设计容错机制,保证集群中几个节点出现故障时,仍能对外提供服务。 + +### 1.3. 提交日志 + +提交日志(也称为预写日志,事务日志)是仅支持附加的持久有序数据结构。您不能修改或删除记录。它从左到右读取并保证项目排序。 + +Kafka 实际上将所有的消息存储到磁盘,并在结构中对它们进行排序,以便利用顺序磁盘读取。 + +### 1.4. 消息队列 + +消息队列技术是分布式应用间交换信息的一种技术。消息队列可驻留在内存或磁盘上, 队列存储消息直到它们被应用程序读走。通过消息队列,应用程序可独立地执行--它们不需要知道彼此的位置、或在继续执行前不需要等待接收程序接收此消息。在分布式计算环境中,为了集成分布式应用,开发者需要对异构网络环境下的分布式应用提供有效的通信手段。为了管理需要共享的信息,对应用提供公共的信息交换机制是重要的。常用的消息队列技术是 Message Queue。 + +Message Queue 的通信模式: + +- **点对点**:点对点方式是最为传统和常见的通讯方式,它支持一对一、一对多、多对多、多对一等多种配置方式,支持树状、网状等多种拓扑结构。 +- **多点广播**:MQ 适用于不同类型的应用。其中重要的,也是正在发展中的是"多点广播"应用,即能够将消息发送到多个目标站点 (Destination List)。可以使用一条 MQ 指令将单一消息发送到多个目标站点,并确保为每一站点可靠地提供信息。MQ 不仅提供了多点广播的功能,而且还拥有智能消息分发功能,在将一条消息发送到同一系统上的多个用户时,MQ 将消息的一个复制版本和该系统上接收者的名单发送到目标 MQ 系统。目标 MQ 系统在本地复制这些消息,并将它们发送到名单上的队列,从而尽可能减少网络的传输量。 +- **发布/订阅 (Publish/Subscribe)**:发布/订阅功能使消息的分发可以突破目的队列地理指向的限制,使消息按照特定的主题甚至内容进行分发,用户或应用程序可以根据主题或内容接收到所需要的消息。发布/订阅功能使得发送者和接收者之间的耦合关系变得更为松散,发送者不必关心接收者的目的地址,而接收者也不必关心消息的发送地址,而只是根据消息的主题进行消息的收发。 +- **集群 (Cluster)**:为了简化点对点通讯模式中的系统配置,MQ 提供 Cluster(集群) 的解决方案。集群类似于一个域 (Domain),集群内部的队列管理器之间通讯时,不需要两两之间建立消息通道,而是采用集群 (Cluster) 通道与其它成员通讯,从而大大简化了系统配置。此外,集群中的队列管理器之间能够自动进行负载均衡,当某一队列管理器出现故障时,其它队列管理器可以接管它的工作,从而大大提高系统的高可靠性。 + +### 1.5. 为什么要使用消息系统 + +- 解耦 + 在项目启动之初来预测将来项目会碰到什么需求,是极其困难的。消息系统在处理过程中间插入了一个隐含的、基于数据的接口层,两边的处理过程都要实现这一接口。这允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。 +- 冗余 + 有些情况下,处理数据的过程会失败。除非数据被持久化,否则将造成丢失。消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险。许多消息队列所采用的"插入-获取-删除"范式中,在把一个消息从队列中删除之前,需要你的处理系统明确的指出该消息已经被处理完毕,从而确保你的数据被安全的保存直到你使用完毕。 +- 扩展性 + 因为消息队列解耦了你的处理过程,所以增大消息入队和处理的频率是很容易的,只要另外增加处理过程即可。不需要改变代码、不需要调节参数。扩展就像调大电力按钮一样简单。 +- 灵活性 & 峰值处理能力 + 在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见;如果为以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。 +- 可恢复性 + 系统的一部分组件失效时,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。 +- 顺序保证 + 在大多使用场景下,数据处理的顺序都很重要。大部分消息队列本来就是排序的,并且能保证数据会按照特定的顺序来处理。Kafka 保证一个 Partition 内的消息的有序性。 +- 缓冲 + 在任何重要的系统中,都会有需要不同的处理时间的元素。例如,加载一张图片比应用过滤器花费更少的时间。消息队列通过一个缓冲层来帮助任务最高效率的执行———写入队列的处理会尽可能的快速。该缓冲有助于控制和优化数据流经过系统的速度。 +- 异步通信 + 很多时候,用户不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。 + +### 1.6. Kafka 的关键功能 + +- 发布和订阅流记录,类似于消息队列或企业级消息系统。 +- 以容错、持久化的方式存储流记录。 +- 处理流记录。 + +### 1.7. Kafka 基本概念 + +- Kafka 作为一个集群运行在一台或多台可以跨越多个数据中心的服务器上。 +- Kafka 集群在称为 Topic 的类别中存储记录流。 +- Kafka 的每个记录由一个键,一个值和一个时间戳组成。 + +### 1.8. Kafka 核心 API + +
+ +
+ +- Producer - 允许应用程序将记录流发布到一个或多个 Kafka Topic。 +- Consumer - 允许应用程序订阅一个或多个 Topic 并处理为他们生成的记录流。 +- Streams - 允许应用程序充当流处理器,从一个或多个 Topic 中消费输入流,并将输出流生成为一个或多个输出 Topic,从而将输入流有效地转换为输出流。 +- Connector - 允许构建和运行可重复使用的生产者或消费者,将 Kafka Topic 连接到现有的应用程序或数据系统。例如,连接到关系数据库的连接器可能会捕获对表的每个更改。 + +在 Kafka 中,客户端和服务器之间的通信是采用 TCP 协议方式。 + +### 1.9. Topic 和日志 + +Topic 是一个目录名,它保存着发布记录。kafka 的 Topic 始终是多订阅者的,也就是说,一个主题可以有零个,一个或多个订阅写入数据的 Consumer。 + +在 Kafka 中,任意一个 Topic 维护一个 Partition 的日志,类似下图: + +
+ +
+ +每个 Partition 都是一个有序的,不可变的记录序列,不断追加到结构化的提交日志中。Partition 中的记录每个分配一个连续的 id 号,称为偏移量,用于唯一标识 Partition 内的每条记录。 + +Kafka 集群持久化保存(使用可配置的保留期限)所有发布记录——无论它们是否被消费。例如,如果保留期限被设置为两天,则在记录发布后的两天之内,它都可以被消费,超过时间后将被丢弃以释放空间。 + +
+ +
+ +实际上,保留在每个 Consumer 基础上的唯一元数据是该 Consumer 在日志中的抵消或位置。这个偏移量是由 Consumer 控制的:Consumer 通常会在读取记录时线性地推进其偏移量,但实际上,由于位置由 Consumer 控制,因此它可以按照喜欢的任何顺序消费记录。 + +这种功能组合意味着 Kafka Consumer 的开销很小——它们的出现对集群和其他 Consumer 没有多少影响。 + +日志中的 Partition 有多种目的。首先,它们允许日志的大小超出服务器限制的大小。每个单独的 Partition 必须适合承载它的服务器,但是一个主题可能有很多 Partition,因此它可以处理任意数量的数据。其次,它们作为并行的单位。 + +## 2. Kafka 工作原理 + +- **Broker** - Kafka 集群包含一个或多个服务器,这种服务器被称为 broker。 +- **Topic** - 每条发布到 Kafka 集群的消息都有一个类别,这个类别被称为 Topic。(物理上不同 Topic 的消息分开存储,逻辑上一个 Topic 的消息虽然保存于一个或多个 broker 上但用户只需指定消息的 Topic 即可生产或消费数据而不必关心数据存于何处)。 +- **Partition** - Parition 是物理上的概念,每个 Topic 包含一个或多个 Partition。 +- **Producer** - 负责发布消息到 Kafka broker。 +- **Consumer** - 消息消费者,向 Kafka broker 读取消息的客户端。 +- **Consumer Group** - 每个 Consumer 属于一个特定的 Consumer Group(可为每个 Consumer 指定 group name,若不指定 group name 则属于默认的 group)。 + +Producer 将消息(记录)发送到 Kafka 节点(Broker),消息由称为 Consumer 的其他应用程序处理。消息被存储在 Topic 中,并且 Consumer 订阅该主题以接收新消息。 + +随着 Topic 变得日益庞大,它们会被分割成更小的 Partition 以提高性能和可伸缩性。Kafka 保证 Partition 内的所有消息按照它们出现的顺序排序。区分特定消息的方式是通过它的偏移量,您可以将它看作普通数组索引,每个新消息都会增加一个序列号在一个 Partition 中。 + +
+ +
+ +Kafka 遵循发布/订阅模式。这意味着 Kafka 不会跟踪 Kafka 读取哪些记录并删除它们,而是将它们存储一段时间(例如一天)或直到满足某个大小阈值。Consumer 自己对 Kafka 进行新的消息调查并说出他们想要阅读的记录。这使得他们可以按照自己的意愿递增/递减偏移量,从而能够重播和重新处理事件。 + +Kafka 集群持久化保存(使用可配置的保留期限)所有发布记录——无论它们是否被消费。例如,如果保留期限被设置为两天,则在记录发布后的两天之内,它都可以被消费,超过时间后将被丢弃以释放空间。 + +值得注意的是,Consumer 实际上是内部拥有一个或多个 Consumer 流程的 Consumer 群体。为了避免两个进程读两次相同的消息,每个 Partition 仅与每个组的一个 Consumer 进程相关联。 + +
+ +
+ +## 3. 持久化 + +Kafka 实际上将其所有记录存储在磁盘中,并且不会将任何内容保留在 RAM 中。 + +- Kafka 有一个将消息分组在一起的协议。它允许网络请求将消息分组在一起以减少网络开销。服务器一气呵成的将消息的数据块持久化并立即获取较大的线性块。 +- 线性读取/写入磁盘速度很快。现代磁盘速度较慢的概念是由于大量的磁盘搜索,这在大型线性操作中不是问题。 +- 所说的线性操作由操作系统通过预读(预取大块数倍)和后写(将小的逻辑写入大物理写入)技术进行了大量优化。 +- 现代操作系统将磁盘缓存在可用 RAM 中。这被称为 pagecache。 +- 由于 Kafka 在整个流程(生产者 -> 经纪 -> 消费者)中以标准化的二进制格式存储未修改的消息,所以它可以利用零拷贝优化。这就是操作系统将数据从页面缓存直接复制到套接字时,完全绕过了 Kafka 经纪人应用程序。 + +所有这些优化都允许 Kafka 以接近网络速度传递消息。 + +## 4. 复制 + +分区数据在多个代理中复制,以便在一个代理死亡的情况下保存数据。 + +在任何时候,一个代理“拥有”一个分区,并且是应用程序通过该分区读写数据的节点。这被称为分区领导。它将它收到的数据复制到 N 个其他代理(称为追随者)。他们也存储数据,并准备在领导者节点死亡的情况下取代领导者。这就是典型的一主多从模式。 + +
+ +
+ +生产者/消费者如何知道分区的领导者是谁? + +对于生产者/消费者来说,从一个分区写入/读取,他们需要知道它的领导者,对吧?这些信息需要从某处获得。Kafka 将这种元数据存储在一个名为 Zookeeper 的服务中。 + +生产者和消费者都和 Zookeeper 连接并通信。Kafka 一直在摆脱这种耦合,自 0.8 和 0.9 版分别开始,客户端直接从 Kafka 经纪人那里获取元数据信息,他们自己与 Zookeeper 交谈。 + +
+ +
+ +## 5. 流处理 + +在 Kafka 中,流处理器是任何需要从输入主题中持续输入数据流,对该输入执行一些处理并生成输出主题的数据流(或外部服务,数据库,垃圾桶,无论哪里真的......) + +可以直接使用生产者/消费者 API 进行简单处理,但对于更复杂的转换(如将流连接在一起),Kafka 提供了一个集成的 Streams API 库。 + +此 API 旨在用于您自己的代码库中,它不在代理上运行。它与消费者 API 类似,可帮助您扩展多个应用程序的流处理工作(类似于消费者群体)。 + +
+ +
+ +### 5.1. 无状态处理 + +流的无状态处理是确定性处理,不依赖于任何外部。你知道,对于任何给定的数据,你将总是产生独立于其他任何东西的相同输出。 + +一个流可以被解释为一个表,一个表可以被解释为一个流。 + +流可以被解释为数据的一系列更新,其中聚合是表的最终结果。 + +如果您看看如何实现同步数据库复制,您会发现它是通过所谓的流式复制,其中表中的每个更改都发送到副本服务器。 + +Kafka 流可以用同样的方式解释 - 当从最终状态积累时的事件。这样的流聚合被保存在本地的 RocksDB 中(默认情况下),被称为 KTable。 + +
+ +
+ +可以将表格视为流中每个键的最新值的快照。以同样的方式,流记录可以产生一个表,表更新可以产生一个更新日志流。 + +
+ +
+ +### 5.2. 有状态处理 + +一些简单的操作,如 map() 或 filter() 是无状态的,并且不要求您保留有关处理的任何数据。但是,在现实生活中,你要做的大多数操作都是有状态的(例如 count()),因此需要存储当前的累积状态。 + +维护流处理器上的状态的问题是流处理器可能会失败!你需要在哪里保持这个状态才能容错? + +一种天真的做法是简单地将所有状态存储在远程数据库中,并通过网络连接到该存储。问题在于没有数据的地方和大量的网络往返,这两者都会显著减慢你的应用程序。一个更微妙但重要的问题是,您的流处理作业的正常运行时间将与远程数据库紧密耦合,并且作业不会自成体系(数据库中来自另一个团队的更改可能会破坏您的处理过程)。 + +那么更好的方法是什么? + +回想一下表和流的双重性。这使我们能够将数据流转换为与我们的处理共处一地的表格。它还为我们提供了处理容错的机制 - 通过将流存储在 Kafka 代理中。 + +流处理器可以将其状态保存在本地表(例如 RocksDB)中,该表将从输入流更新(可能是某种任意转换之后)。当进程失败时,它可以通过重放流来恢复其数据。 + +您甚至可以让远程数据库成为流的生产者,从而有效地广播更新日志,以便在本地重建表。 + +
+ +
+ +## 6. Kafka 应用场景 + +- 构建实时的流数据管道,在系统或应用间获取可靠数据。 +- 构建实时的流应用程序,用于转换或响应数据流。 + +正如我们已经介绍的那样,Kafka 允许您将大量消息通过集中介质存储并存储,而不用担心性能或数据丢失等问题。 + +这意味着它非常适合用作系统架构的核心,充当连接不同应用程序的集中介质。 Kafka 可以成为事件驱动架构的核心部分,并允许您真正将应用程序彼此分离。 + +
+ +
+ +Kafka 允许您轻松分离不同(微)服务之间的通信。利用 Streams API,现在比以往更容易编写业务逻辑,丰富了 Kafka 主题数据以便服务消费。 + +## 7. 幂等性 + +幂等性引入目的:生产者重复生产消息。生产者进行 retry 会产生重试时,会重复产生消息。有了幂等性之后,在进行 retry 重试时,只会生成一个消息。 + +### 7.1. 幂等性实现 + +#### 7.1.1. PID 和 Sequence Number + +为了实现 Producer 的幂等性,Kafka 引入了 Producer ID(即 PID)和 Sequence Number。 + +- PID。每个新的 Producer 在初始化的时候会被分配一个唯一的 PID,这个 PID 对用户是不可见的。 +- Sequence Numbler。(对于每个 PID,该 Producer 发送数据的每个都对应一个从 0 开始单调递增的 Sequence Number。 + +Broker 端在缓存中保存了这 seq number,对于接收的每条消息,如果其序号比 Broker 缓存中序号大于 1 则接受它,否则将其丢弃。这样就可以实现了消息重复提交了。但是,只能保证单个 Producer 对于同一个的 Exactly Once 语义。不能保证同一个 Producer 一个 topic 不同的 partion 幂等。 + +
+ +实现幂等之后 + +
+ +#### 7.1.2. 生成 PID 的流程 + +在执行创建事务时,如下: + +```java +Producer producer = new KafkaProducer(props); +``` + +会创建一个 Sender,并启动线程,执行如下 run 方法,在 maybeWaitForProducerId()中生成一个 producerId,如下: + +```java +==================================== +类名:Sender +==================================== + +void run(long now) { + if (transactionManager != null) { + try { + ........ + if (!transactionManager.isTransactional()) { + // 为idempotent producer生成一个producer id + maybeWaitForProducerId(); + } else if (transactionManager.hasUnresolvedSequences() && !transactionManager.hasFatalError()) { + ........ +``` + +### 7.2. 幂等性的应用实例 + +(1)配置属性 + +需要设置: + +- enable.idempotence,需要设置为 ture,此时就会默认把 acks 设置为 all,所以不需要再设置 acks 属性了。 + +```java +private Producer buildIdempotProducer(){ + + // create instance for properties to access producer configs + Properties props = new Properties(); + + // bootstrap.servers是Kafka集群的IP地址。多个时,使用逗号隔开 + props.put("bootstrap.servers", "localhost:9092"); + + props.put("enable.idempotence",true); + + //If the request fails, the producer can automatically retry, + props.put("retries", 3); + + //Reduce the no of requests less than 0 + props.put("linger.ms", 1); + + //The buffer.memory controls the total amount of memory available to the producer for buffering. + props.put("buffer.memory", 33554432); + + // Kafka消息是以键值对的形式发送,需要设置key和value类型序列化器 + props.put("key.serializer", + "org.apache.kafka.common.serialization.StringSerializer"); + + props.put("value.serializer", + "org.apache.kafka.common.serialization.StringSerializer"); + + Producer producer = new KafkaProducer(props); + + + return producer; +} +``` + +(2)发送消息 + +跟一般生产者一样,如下 + +```java +public void produceIdempotMessage(String topic, String message) { + // 创建Producer + Producer producer = buildIdempotProducer(); + // 发送消息 + producer.send(new ProducerRecord(topic, message)); + producer.flush(); +} +``` + +此时,因为我们并没有配置 transaction.id 属性,所以不能使用事务相关 API,如下 + +```java +producer.initTransactions(); +``` + +否则会出现如下错误: + +```java +Exception in thread “main” java.lang.IllegalStateException: Transactional method invoked on a non-transactional producer. + at org.apache.kafka.clients.producer.internals.TransactionManager.ensureTransactional(TransactionManager.java:777) + at org.apache.kafka.clients.producer.internals.TransactionManager.initializeTransactions(TransactionManager.java:202) + at org.apache.kafka.clients.producer.KafkaProducer.initTransactions(KafkaProducer.java:544) +``` + +## 8. 事务 + +### 8.1. 事务属性理解 + +事务属性是 2017 年 Kafka 0.11.0.0 引入的新特性。类似于数据库事务,只是这里的数据源是 Kafka,**kafka 事务属性是指一系列的生产者生产消息和消费者提交偏移量的操作在一个事务,或者说是是一个原子操作),同时成功或者失败**。 + +注意:在理解消息的事务时,一直处于一个错误理解就是如下代码中,把操作 db 的业务逻辑跟操作消息当成是一个事务。其实这个是有问题的,操作 DB 数据库的数据源是 DB,消息数据源是 kfaka,这是完全不同两个数据,一种数据源(如 mysql,kafka)对应一个事务,所以它们是两个独立的事务:kafka 事务指 kafka 一系列 生产、消费消息等操作组成一个原子操作;db 事务是指操作数据库的一系列增删改操作组成一个原子操作。 + +```java +void kakfa_in_tranction() { + // 1.kafa的操作:读取消息或者生产消息 + kafkaOperation(); + // 2.db操作 + dbOperation(); +} +``` + +### 8.2. 引入事务目的 + +在事务属性之前先引入了生产者幂等性,它的作用为: + +- 生产者多次发送消息可以封装成一个原子操作,要么都成功,要么失败 +- consumer-transform-producer 模式下,因为消费者提交偏移量出现问题,导致在重复消费消息时,生产者重复生产消息。需要将这个模式下消费者提交偏移量操作和生产者一系列生成消息的操作封装成一个原子操作。 + +**消费者提交偏移量导致重复消费消息的场景**:消费者在消费消息完成提交便宜量 o2 之前挂掉了(假设它最近提交的偏移量是 o1),此时执行再均衡时,其它消费者会重复消费消息(o1 到 o2 之间的消息)。 + +### 8.3. 事务操作的 API + +producer 提供了 initTransactions, beginTransaction, sendOffsets, commitTransaction, abortTransaction 五个事务方法。 + +```java + /** + * 初始化事务。需要注意的有: + * 1、前提 + * 需要保证transation.id属性被配置。 + * 2、这个方法执行逻辑是: + * (1)Ensures any transactions initiated by previous instances of the producer with the same + * transactional.id are completed. If the previous instance had failed with a transaction in + * progress, it will be aborted. If the last transaction had begun completion, + * but not yet finished, this method awaits its completion. + * (2)Gets the internal producer id and epoch, used in all future transactional + * messages issued by the producer. + * + */ + public void initTransactions(); + + /** + * 开启事务 + */ + public void beginTransaction() throws ProducerFencedException ; + + /** + * 为消费者提供的在事务内提交偏移量的操作 + */ + public void sendOffsetsToTransaction(Map offsets, + String consumerGroupId) throws ProducerFencedException ; + + /** + * 提交事务 + */ + public void commitTransaction() throws ProducerFencedException; + + /** + * 放弃事务,类似回滚事务的操作 + */ + public void abortTransaction() throws ProducerFencedException ; +``` + +### 8.4. 事务属性的应用实例 + +在一个原子操作中,根据包含的操作类型,可以分为三种情况,**前两种情况是事务引入的场景**,最后一种情况没有使用价值。 + +只有 Producer 生产消息; + +消费消息和生产消息并存,**这个是事务场景中最常用的情况**,就是我们常说的“consume-transform-produce ”模式 + +只有 consumer 消费消息,这种操作其实没有什么意义,跟使用手动提交效果一样,而且也不是事务属性引入的目的,所以一般不会使用这种情况 + +#### 8.4.1. 相关属性配置 + +使用 kafka 的事务 api 时的一些注意事项: + +- 需要消费者的自动模式设置为 false,并且不能子再手动的进行执行 consumer#commitSync 或者 consumer#commitAsyc +- 生产者配置 transaction.id 属性 +- 生产者不需要再配置 enable.idempotence,因为如果配置了 transaction.id,则此时 enable.idempotence 会被设置为 true +- 消费者需要配置 Isolation.level。在 consume-trnasform-produce 模式下使用事务时,必须设置为 READ_COMMITTED。 + +#### 8.4.2. 只有写 + +创建一个事务,在这个事务操作中,只有生成消息操作。代码如下: + +```java + /** + * 在一个事务只有生产消息操作 + */ + public void onlyProduceInTransaction() { + Producer producer = buildProducer(); + + // 1.初始化事务 + producer.initTransactions(); + + // 2.开启事务 + producer.beginTransaction(); + + try { + // 3.kafka写操作集合 + // 3.1 do业务逻辑 + + // 3.2 发送消息 + producer.send(new ProducerRecord("test", "transaction-data-1")); + + producer.send(new ProducerRecord("test", "transaction-data-2")); + // 3.3 do其他业务逻辑,还可以发送其他topic的消息。 + + // 4.事务提交 + producer.commitTransaction(); + + + } catch (Exception e) { + // 5.放弃事务 + producer.abortTransaction(); + } + + } +``` + +创建生产者,代码如下,需要: + +- 配置 transactional.id 属性 +- 配置 enable.idempotence 属性 + +```java + /** + * 需要: + * 1、设置transactional.id + * 2、设置enable.idempotence + * @return + */ + private Producer buildProducer() { + + // create instance for properties to access producer configs + Properties props = new Properties(); + + // bootstrap.servers是Kafka集群的IP地址。多个时,使用逗号隔开 + props.put("bootstrap.servers", "localhost:9092"); + + // 设置事务id + props.put("transactional.id", "first-transactional"); + + // 设置幂等性 + props.put("enable.idempotence",true); + + //Set acknowledgements for producer requests. + props.put("acks", "all"); + + //If the request fails, the producer can automatically retry, + props.put("retries", 1); + + //Specify buffer size in config,这里不进行设置这个属性,如果设置了,还需要执行producer.flush()来把缓存中消息发送出去 + //props.put("batch.size", 16384); + + //Reduce the no of requests less than 0 + props.put("linger.ms", 1); + + //The buffer.memory controls the total amount of memory available to the producer for buffering. + props.put("buffer.memory", 33554432); + + // Kafka消息是以键值对的形式发送,需要设置key和value类型序列化器 + props.put("key.serializer", + "org.apache.kafka.common.serialization.StringSerializer"); + + props.put("value.serializer", + "org.apache.kafka.common.serialization.StringSerializer"); + + + Producer producer = new KafkaProducer(props); + + return producer; + } +``` + +#### 8.4.3. 消费-生产并存(consume-transform-produce) + +在一个事务中,既有生产消息操作又有消费消息操作,即常说的 Consume-tansform-produce 模式。如下实例代码 + +```java + /** + * 在一个事务内,即有生产消息又有消费消息 + */ + public void consumeTransferProduce() { + // 1.构建上产者 + Producer producer = buildProducer(); + // 2.初始化事务(生成productId),对于一个生产者,只能执行一次初始化事务操作 + producer.initTransactions(); + + // 3.构建消费者和订阅主题 + Consumer consumer = buildConsumer(); + consumer.subscribe(Arrays.asList("test")); + while (true) { + // 4.开启事务 + producer.beginTransaction(); + + // 5.1 接受消息 + ConsumerRecords records = consumer.poll(500); + + try { + // 5.2 do业务逻辑; + System.out.println("customer Message---"); + Map commits = Maps.newHashMap(); + for (ConsumerRecord record : records) { + // 5.2.1 读取消息,并处理消息。print the offset,key and value for the consumer records. + System.out.printf("offset = %d, key = %s, value = %s\n", + record.offset(), record.key(), record.value()); + + // 5.2.2 记录提交的偏移量 + commits.put(new TopicPartition(record.topic(), record.partition()), + new OffsetAndMetadata(record.offset())); + + + // 6.生产新的消息。比如外卖订单状态的消息,如果订单成功,则需要发送跟商家结转消息或者派送员的提成消息 + producer.send(new ProducerRecord("test", "data2")); + } + + // 7.提交偏移量 + producer.sendOffsetsToTransaction(commits, "group0323"); + + // 8.事务提交 + producer.commitTransaction(); + + } catch (Exception e) { + // 7.放弃事务 + producer.abortTransaction(); + } + } + } +``` + +创建消费者代码,需要: + +- 将配置中的自动提交属性(auto.commit)进行关闭 +- 而且在代码里面也不能使用手动提交 commitSync( )或者 commitAsync( ) +- 设置 isolation.level + +```java + /** + * 需要: + * 1、关闭自动提交 enable.auto.commit + * 2、isolation.level为 + * @return + */ + public Consumer buildConsumer() { + Properties props = new Properties(); + // bootstrap.servers是Kafka集群的IP地址。多个时,使用逗号隔开 + props.put("bootstrap.servers", "localhost:9092"); + // 消费者群组 + props.put("group.id", "group0323"); + // 设置隔离级别 + props.put("isolation.level","read_committed"); + // 关闭自动提交 + props.put("enable.auto.commit", "false"); + props.put("session.timeout.ms", "30000"); + props.put("key.deserializer", + "org.apache.kafka.common.serialization.StringDeserializer"); + props.put("value.deserializer", + "org.apache.kafka.common.serialization.StringDeserializer"); + KafkaConsumer consumer = new KafkaConsumer + (props); + + return consumer; + } +``` + +#### 8.4.4. 只有读 + +创建一个事务,在这个事务操作中,只有生成消息操作,如下代码。这种操作其实没有什么意义,跟使用手动提交效果一样,无法保证消费消息操作和提交偏移量操作在一个事务。 + +```java + /** + * 在一个事务只有消息操作 + */ + public void onlyConsumeInTransaction() { + Producer producer = buildProducer(); + + // 1.初始化事务 + producer.initTransactions(); + + // 2.开启事务 + producer.beginTransaction(); + + // 3.kafka读消息的操作集合 + Consumer consumer = buildConsumer(); + while (true) { + // 3.1 接受消息 + ConsumerRecords records = consumer.poll(500); + + try { + // 3.2 do业务逻辑; + System.out.println("customer Message---"); + Map commits = Maps.newHashMap(); + for (ConsumerRecord record : records) { + // 3.2.1 处理消息 print the offset,key and value for the consumer records. + System.out.printf("offset = %d, key = %s, value = %s\n", + record.offset(), record.key(), record.value()); + + // 3.2.2 记录提交偏移量 + commits.put(new TopicPartition(record.topic(), record.partition()), + new OffsetAndMetadata(record.offset())); + } + + // 4.提交偏移量 + producer.sendOffsetsToTransaction(commits, "group0323"); + + // 5.事务提交 + producer.commitTransaction(); + + } catch (Exception e) { + // 6.放弃事务 + producer.abortTransaction(); + } + } + + } +``` + +### 8.5. 生产者事务的实现 + +#### 8.5.1. 相关配置 + +#### 8.5.2. 幂等性和事务性的关系 + +##### 两者关系 + +事务属性实现前提是幂等性,即在配置事务属性 transaction id 时,必须还得配置幂等性;但是幂等性是可以独立使用的,不需要依赖事务属性。 + +- 幂等性引入了 Porducer ID +- 事务属性引入了 Transaction Id 属性。、 + +设置 + +- enable.idempotence = true,transactional.id 不设置:只支持幂等性。 +- enable.idempotence = true,transactional.id 设置:支持事务属性和幂等性 +- enable.idempotence = false,transactional.id 不设置:没有事务属性和幂等性的 kafka +- enable.idempotence = false,transactional.id 设置:无法获取到 PID,此时会报错 + +##### tranaction id 、productid 和 epoch + +**一个 app 有一个 tid,同一个应用的不同实例 PID 是一样的,只是 epoch 的值不同**。如: + +
+ +同一份代码运行两个实例,分步执行如下:_在实例 1 没有进行提交事务前,开始执行实例 2 的初始化事务_ + +
+ +**step1 实例 1-初始化事务**。的打印出对应 productId 和 epoch,信息如下: + +[2018-04-21 20:56:23,106] INFO [TransactionCoordinator id=0] Initialized transactionalId first-transactional with producerId 8000 and producer epoch 123 on partition \_\_transaction_state-12 (kafka.coordinator.transaction.TransactionCoordinator) + +**step2 实例 1-发送消息。** + +**step3 实例 2-初始化事务**。初始化事务时的打印出对应 productId 和 epoch,信息如下: + +18-04-21 20:56:48,373] INFO [TransactionCoordinator id=0] Initialized transactionalId first-transactional with producerId 8000 and producer epoch 124 on partition \_\_transaction_state-12 (kafka.coordinator.transaction.TransactionCoordinator) + +**step4 实例 1-提交事务**,此时报错 + +org.apache.kafka.common.errors.ProducerFencedException: Producer attempted an operation with an old epoch. Either there is a newer producer with the same transactionalId, or the producer’s transaction has been expired by the broker. + +**step5 实例 2-提交事务** + +为了避免这种错误,同一个事务 ID,只有保证如下顺序 epch 小 producer 执行 init-transaction 和 committransaction,然后 epoch 较大的 procuder 才能开始执行 init-transaction 和 commit-transaction,如下顺序: + +
+ +有了 transactionId 后,Kafka 可保证: + +- 跨 Session 的数据幂等发送。当具有相同 Transaction ID 的新的 Producer 实例被创建且工作时,旧的且拥有相同 Transaction ID 的 Producer 将不再工作【上面的实例可以验证】。kafka 保证了关联同一个事务的所有 producer(一个应用有多个实例)必须按照顺序初始化事务、和提交事务,否则就会有问题,这保证了同一事务 ID 中消息是有序的(不同实例得按顺序创建事务和提交事务)。 + +#### 8.5.3. 事务最佳实践-单实例的事务性 + +通过上面实例中可以看到 kafka 是跨 Session 的数据幂等发送,即如果应用部署多个实例时常会遇到上面的问题“_org.apache.kafka.common.errors.ProducerFencedException: Producer attempted an operation with an old epoch. Either there is a newer producer with the same transactionalId, or the producer’s transaction has been expired by the broker_.”,必须保证这些实例生产者的提交事务顺序和创建顺序保持一致才可以,否则就无法成功。其实,在实践中,我们更多的是**如何实现对应用单实例的事务性**。可以通过 spring-kafaka 实现思路来学习,即**每次创建生产者都设置一个不同的 transactionId 的值**,如下代码: + +在 spring-kafka 中,对于一个线程创建一个 producer,事务提交之后,还会关闭这个 producer 并清除,后续同一个线程或者新的线程重新执行事务时,此时就会重新创建 producer。 + +```java +==================================== +类名:ProducerFactoryUtils +==================================== +/** + * Obtain a Producer that is synchronized with the current transaction, if any. + * @param producerFactory the ConnectionFactory to obtain a Channel for + * @param the key type. + * @param the value type. + * @return the resource holder. + */ +public static KafkaResourceHolder getTransactionalResourceHolder( + final ProducerFactory producerFactory) { + + Assert.notNull(producerFactory, "ProducerFactory must not be null"); + + // 1.对于每一个线程会生成一个唯一key,然后根据key去查找resourceHolder + @SuppressWarnings("unchecked") + KafkaResourceHolder resourceHolder = (KafkaResourceHolder) TransactionSynchronizationManager + .getResource(producerFactory); + if (resourceHolder == null) { + // 2.创建一个消费者 + Producer producer = producerFactory.createProducer(); + // 3.开启事务 + producer.beginTransaction(); + resourceHolder = new KafkaResourceHolder(producer); + bindResourceToTransaction(resourceHolder, producerFactory); + } + return resourceHolder; +} +``` + +创建消费者代码 + +```java +==================================== +类名:DefaultKafkaProducerFactory +==================================== +protected Producer createTransactionalProducer() { + Producer producer = this.cache.poll(); + if (producer == null) { + Map configs = new HashMap<>(this.configs); + // 对于每一次生成producer时,都设置一个不同的transactionId + configs.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG, + this.transactionIdPrefix + this.transactionIdSuffix.getAndIncrement()); + producer = new KafkaProducer(configs, this.keySerializer, this.valueSerializer); + // 1.初始化话事务。 + producer.initTransactions(); + return new CloseSafeProducer(producer, this.cache); + } + else { + return producer; + } +} +``` + +#### 8.5.4. Consume-transform-Produce 的流程 + +
+ +**流程 1** **:**查找 Tranaction Corordinator。 + +Producer 向任意一个 brokers 发送 FindCoordinatorRequest 请求来获取 Transaction Coordinator 的地址。 + +**流程 2:**初始化事务 initTransaction + +Producer 发送 InitpidRequest 给事务协调器,获取一个 Pid**。InitpidRequest 的处理过程是同步阻塞的,一旦该调用正确返回,Producer 就可以开始新的事务**。TranactionalId 通过 InitpidRequest 发送给 Tranciton Corordinator,然后在 Tranaciton Log 中记录这的映射关系。除了返回 PID 之外,还具有如下功能: + +- 对 PID 对应的 epoch 进行递增,这样可以保证同一个 app 的不同实例对应的 PID 是一样的,但是 epoch 是不同的。 +- 回滚之前的 Producer 未完成的事务(如果有)。 + +**流程 3:** 开始事务 beginTransaction + +执行 Producer 的 beginTransacion(),它的作用是 Producer 在本地记录下这个 transaction 的状态为开始状态。 + +注意:这个操作并没有通知 Transaction Coordinator。 + +**流程 4:** Consume-transform-produce loop + +**流程 4.0:** 通过 Consumtor 消费消息,处理业务逻辑 + +**流程 4.1:** producer 向 TransactionCordinantro 发送 AddPartitionsToTxnRequest + +在 producer 执行 send 操作时,如果是第一次给发送数据,此时会向 Trasaction Corrdinator 发送一个 AddPartitionsToTxnRequest 请求,Transaction Corrdinator 会在 transaction log 中记录下 tranasactionId 和一个映射关系,并将状态改为 begin。AddPartionsToTxnRequest 的数据结构如下: + +``` +AddPartitionsToTxnRequest => TransactionalId PID Epoch [Topic [Partition]] + TransactionalId => string + PID => int64 + Epoch => int16 + Topic => string + Partition => int32 +``` + +**流程 4.2:** producer#send 发送 ProduceRequst + +生产者发送数据,虽然没有还没有执行 commit 或者 absrot,但是此时消息已经保存到 kafka 上,可以参考如下图断点位置处,此时已经可以查看到消息了,而且即使后面执行 abort,消息也不会删除,只是更改状态字段标识消息为 abort 状态。 + +
+ +**流程 4.3:** AddOffsetCommitsToTxnRequest + +Producer 通过 KafkaProducer.sendOffsetsToTransaction 向事务协调器器发送一个 AddOffesetCommitsToTxnRequests: + +``` +AddOffsetsToTxnRequest => TransactionalId PID Epoch ConsumerGroupID + TransactionalId => string + PID => int64 + Epoch => int16 + ConsumerGroupID => string +``` + +在执行事务提交时,可以根据 ConsumerGroupID 来推断\_customer_offsets 主题中相应的 TopicPartions 信息。这样在 + +**流程 4.4:** TxnOffsetCommitRequest + +Producer 通过 KafkaProducer.sendOffsetsToTransaction 还会向消费者协调器 Cosumer Corrdinator 发送一个 TxnOffsetCommitRequest,在主题\_consumer_offsets 中保存消费者的偏移量信息。 + +``` +TxnOffsetCommitRequest => ConsumerGroupID + PID + Epoch + RetentionTime + OffsetAndMetadata + ConsumerGroupID => string + PID => int64 + Epoch => int32 + RetentionTime => int64 + OffsetAndMetadata => [TopicName [Partition Offset Metadata]] + TopicName => string + Partition => int32 + Offset => int64 + Metadata => string +``` + +**流程 5:** 事务提交和事务终结(放弃事务) + +通过生产者的 commitTransaction 或 abortTransaction 方法来提交事务和终结事务,这两个操作都会发送一个 EndTxnRequest 给 Transaction Coordinator。 + +**流程 5.1**:EndTxnRequest。Producer 发送一个 EndTxnRequest 给 Transaction Coordinator,然后执行如下操作: + +- Transaction Coordinator 会把 PREPARE_COMMIT or PREPARE_ABORT 消息写入到 transaction log 中记录 +- 执行流程 5.2 +- 执行流程 5.3 + +**流程 5.2**:WriteTxnMarkerRequest + +``` +WriteTxnMarkersRequest => [CoorinadorEpoch PID Epoch Marker [Topic [Partition]]] + CoordinatorEpoch => int32 + PID => int64 + Epoch => int16 + Marker => boolean (false(0) means ABORT, true(1) means COMMIT) + Topic => string + Partition => int32 +``` + +- 对于 Producer 生产的消息。Tranaction Coordinator 会发送 WriteTxnMarkerRequest 给当前事务涉及到每个的 leader,leader 收到请求后,会写入一个 COMMIT(PID) 或者 ABORT(PID)的控制信息到 data log 中 +- 对于消费者偏移量信息,如果在这个事务里面包含\_consumer-offsets 主题。Tranaction Coordinator 会发送 WriteTxnMarkerRequest 给 Transaction Coordinartor,Transaction Coordinartor 收到请求后,会写入一个 COMMIT(PID) 或者 ABORT(PID)的控制信息到 data log 中。 + +**流程 5.3:**Transaction Coordinator 会将最终的 COMPLETE_COMMIT 或 COMPLETE_ABORT 消息写入 Transaction Log 中以标明该事务结束。 + +- 只会保留这个事务对应的 PID 和 timstamp。然后把当前事务其他相关消息删除掉,包括 PID 和 tranactionId 的映射关系。 + +##### 文件类型和查看命令 + +kafka 文件主要包括 broker 的 data(主题:test)、事务协调器对应的 transaction_log(主题:\_\_tranaction_state)、偏移量信息(主题:\_consumer_offsets)三种类型。如下图 + +
+ +这三种文件类型其实都是 topic 的分区,所以对于每一个目录都包含*.log、*.index、_.timeindex、_.txnindex 文件(仅这个文件是为了实现事务属性引入的)。segment 和 segmengt 对应 index、timeindex、txnindex 文件命名中序号表示的是第几个消息。如下图中,00000000000000368769.index 和 00000000000000568769.log 中“368969”就是表示文件中存储的第一个消息是 468969 个消息。 + +对于索引文案包含两部分: + +- baseOffset:索引对应 segment 文件中的第几条 message。 +- position:在 segment 中的绝对位置。 + +
+ +查看文件内容: + +bin/kafka-run-class.sh kafka.tools.DumpLogSegments –files /Users/wuzhonghu/data/kafka-logs/firtstopic-0/00000000000000000002.log –print-data-log + +##### ControlMessage 和 Transaction markers + +Trasaction markers 就是 kafka 为了实现事务定义的 Controll Message。这个消息和数据消息都存放在 log 中,在 Consumer 读取事务消息时有用,可以参考下面章节-4.5.1 老版本-读取事务消息顺序。 + +##### Transaction Coordinator 和 Transaction Log + +Transaction Log 如下放置在“\_tranaction_state”主题下面,默认是 50 个分区,每一个分区中文件格式和 broker 存储消息是一样的,都有 log/index/timeindex 文件,如下: + +
+ +#### 8.5.5. 消费读取事务消息(READ_COMMITED) + +Consumer 为了实现事务,新增了一个 isolation.level 配置,有两个值如下, + +- READ_UNCOMMITTED,类似于没有事务属性的消费者。 +- READ_COMMITED,只获取执行了事务提交的消息。 + +在本小节中我们主要讲 READ_COMMITED 模式下读取消息的流程的两种版本的演化 + +##### 老版本-读取事务消息顺序 + +如下图中,按顺序保存到 broker 中消息有:事务 1 消息 T1-M1、对于事务 2 的消息有 T2-M1、事务 1 消息 T1-M2、非事务消息 M1,最终到达 client 端的循序是 M1-> T2-M1 -> T1-M1 -> T1-M2。 + +
+ +具体步骤如下: + +- **step1** Consumer 接受到事务消息 T1-M1、T2-M2、T1-M2 和非事务消息 M1,因为没有收到事务 T1 和 T2 的控制消息,所以此时把事务相关消息 T1-M1、T2-M2、T1-M2 保存到内存,然后只把非事务消息 M1 返回给 client。 +- **step2** Consumer 接受到事务 2 的控制消息 T2-C,此时就把事务消息 T2-M1 发送给 Clinet。 +- **step3** C onsumer 接受到事务 1 的控制消息 T1-C,此时就把事务消息 T1-M1 和 T1-M2 发送给 Client + +##### 新版本-读取事务消息顺序 + +第一种方式,需要在 consumer 客户端缓存消息,当存在耗时比较长的事务时,占用客户端大量的内存资源。为了解决这个问题,通过 LSO 和 Abort Index 文件来解决这个问题,参考: + + + +(1) LSO,Last stable offset。Broker 在缓存中维护了所有处于运行状态的事务对应的 initial offsets,LSO 的值就是这些 offsets 中最小值-1。这样在 LSO 之前数据都是已经 commit 或者 abort 的数据,只有这些数据才对 Consumer 可见,即 consumer 读取数据只能读取到 LSO 的位置。 + +- LSO 并没有持久化某一个位置,而是实时计算出来的,并保存在缓存中。 + +(2)Absort Index 文件 + +Conusmer 发送 FetchRequest 中,新增了 Isolation 字段,表示是那种模式 + +``` +ReplicaId MaxWaitTime MinBytes [TopicName [Partition FetchOffset MaxBytes]] + + ReplicaId => int32 + MaxWaitTime => int32 + MinBytes => int32 + TopicName => string + Partition => int32 + FetchOffset => int64 + MaxBytes => int32 + Isolation => READ_COMMITTED | READ_UNCOMMITTED +``` + +返回数据类型为 FetchResponse 的格式为: + +ThrottleTime [TopicName [Partition ErrorCode HighwaterMarkOffset AbortedTransactions MessageSetSize MessageSet]] + +对应各个给字段类型为 + +``` + ThrottleTime => int32 + TopicName => string + Partition => int32 + ErrorCode => int16 + HighwaterMarkOffset => int64 + AbortedTransactions => [PID FirstOffset] + PID => int64 + FirstOffset => int64 + MessageSetSize => int32 +``` + +- 设置成 READ_UNCOMMITTED 模式时, the AbortedTransactions array is null. +- 设置为 READ_COMMITTED 时,the Last Stable Offset(LSO),当事务提交之后,LSO 向前移动 offset + +数据如下: + +- 存放数据的 log + +
+ +- 存放 Absort Index 的内容如下: + +
+ +执行读取数据流程如下: + +**step1:** 假设 consumer 读取数据的 fetched offsets 的区间是 0 到 4。 + +- 首先,broker 读取 data log 中数据 + +
+ +- 然后,broker 依次读取 abort index 的内容,发现 LSO 大于等于 4 就停止。如上可以获取到 P2 对应的 offset 从 2 到 5 的消息都是被丢弃的: + +
+ +- 最后,broker 将上面 data log 和 abort index 中满足条件的数据返回给 consumer。 + +**step2 :**在 consumer 端根据 absrot index 中返回的内容,过滤丢弃的消息,最终给用户消息为 + +
+ +##### Absorted Transaction Index + +在 broker 中数据中新增一个索引文件,保存 aborted tranasation 对应的 offsets,只有事务执行 abort 时,才会往这个文件新增一个记录,初始这个文件是不存在的,只有第一条 abort 时,才会创建这个文件。 + +
+ +这个索引文件结构的每一行结构是 TransactionEntry: + +``` +Version => int16 + PID => int64 + FirstOffset => int64 + LastOffset => int64 + LastStableOffset => int64 +``` + +当 broker 接受到控制消息(producer 执行 commitTransaction()或者 abortTransaction())时, 执行如下操作: + +(1)计算 LSO。 + +Broker 在缓存中维护了所有处于运行状态的事务对应的 initial offsets,LSO 的值就是这些 offsets 中最小值-1。 + +举例说明下 LSO 的计算,对于一个 data log 中内如如下 + +
+ +对应的 abort index 文件中内如如下:**LSO 是递增的** + +
+ +(2)第二步 如果事务是提交状态,则在索引文件中新增 TransactionEntry。 + +(3)第三步 从 active 的 tranaction set 中移除这个 transaton,然后更新 LSO。 + +##### 问题 + +1、问题 1:producer 通过事务提交消息时抛异常了, 对于使用非事务的消费者,是否可以获取此消息? + +对于事务消息,必须是执行 commit 或者 abstort 之后,消息才对消费者可见,即使是非事务的消费者。只是非事务消费者相比事务消费者区别,在于可以读取执行了 absort 的消息。 + +### 8.6. 其他思考 + +1、如何保证消息不丢。 + +(1)在消费端可以建立一个日志表,和业务处理在一个事务 + +定时扫描没有表发送没有被处理的消息 + +(2)消费端,消费消息之后,修改消息表的中消息状态为已处理成功。 + +2、如何保证消息提交和业务处理在同一个事务内完成 + +在消费端可以建立一个日志表,和业务处理在一个事务 + +3、消费者角度,如何保证消息不被重复消费。 + +(1)通过 seek 操作 + +(2)通过 kafka 事务操作。 + +4、生产者角度,如何保证消息不重复生产 + +(1)kakfka 幂等性 + +## 9. 资料 + +### 9.1. 官方资料 + +[Github](https://github.com/apache/kafka) | [官方文档](https://kafka.apache.org/documentation/) + +### 9.2. 第三方资料 + +- [Kafka Manager](https://github.com/yahoo/kafka-manager) - Kafka 管理工具 +- [Kafka 剖析(一):Kafka 背景及架构介绍](http://www.infoq.com/cn/articles/kafka-analysis-part-1) +- [Thorough Introduction to Apache Kafka](https://hackernoon.com/thorough-introduction-to-apache-kafka-6fbf2989bbc1) +- [Kafak(04) Kafka 生产者事务和幂等](http://www.heartthinkdo.com/?p=2040#43) diff --git a/docs/distributed/mq/kafka-basics.md b/docs/distributed/mq/kafka-basics.md new file mode 100644 index 00000000..f8159ac7 --- /dev/null +++ b/docs/distributed/mq/kafka-basics.md @@ -0,0 +1,1094 @@ +# Kafka 实战篇 + +> Kafka 是一个分布式的、可水平扩展的、基于发布/订阅模式的、支持容错的消息系统。 +> +> | [**官网**](http://kafka.apache.org/) | [**官方文档**](https://kafka.apache.org/documentation/) | [**Github**](https://github.com/apache/kafka) | + + + +- [1. 概述](#1-概述) + - [1.1. 简介](#11-简介) + - [1.2. 系统结构和存储结构](#12-系统结构和存储结构) + - [1.3. 小结](#13-小结) +- [2. 安装部署](#2-安装部署) + - [2.1. 下载解压](#21-下载解压) + - [2.2. 启动服务器](#22-启动服务器) + - [2.3. 停止服务器](#23-停止服务器) + - [2.4. 创建主题](#24-创建主题) + - [2.5. 生产者生产消息](#25-生产者生产消息) + - [2.6. 消费者消费消息](#26-消费者消费消息) + - [2.7. 集群部署](#27-集群部署) +- [3. API](#3-api) +- [4. 生产者(Producer)](#4-生产者producer) + - [4.1. 发送消息流程](#41-发送消息流程) + - [4.2. 发送消息方式](#42-发送消息方式) +- [5. 消费者(Consumer)](#5-消费者consumer) + - [5.1. 消费者和消费者群组](#51-消费者和消费者群组) + - [5.2. 消费消息流程](#52-消费消息流程) + - [5.3. 提交偏移量](#53-提交偏移量) + - [5.4. 从指定偏移量获取数据](#54-从指定偏移量获取数据) +- [6. Broker](#6-broker) + - [6.1. 集群控制器](#61-集群控制器) + - [6.2. 分区 leader 和 follower](#62-分区-leader-和-follower) + - [6.3. 群组协调器](#63-群组协调器) +- [7. Zookeeper 集群](#7-zookeeper-集群) + - [7.1. 节点信息](#71-节点信息) + - [7.2. zookeeper 一些总结](#72-zookeeper-一些总结) +- [8. 资料](#8-资料) + + + +## 1. 概述 + +### 1.1. 简介 + +Kafka 是一个分布式的、可水平扩展的、基于发布/订阅模式的、支持容错的 MQ 中间件。具有如下特点: + +- 伸缩性。随着数据量增长,可以通过对 broker 集群水平扩展来提高系统性能。 +- 高性能。通过横向扩展生产者、消费者(通过消费者群组实现)和 broker(通过扩展实现系统伸缩性)可以轻松处理巨大的消息流。 +- 消息持久化。基于磁盘的数据存储,消息不会丢失。 + +通过 Partition 来实现多个生产者(同一个 topic 消息根据 key 放置到一个 Partition 中)和多个消费者(一个 Partition 由消费者群组中每一个消费者来负责)。 + +### 1.2. 系统结构和存储结构 + +#### 1.2.1. 系统结构 + +producer 采用 push 方式向 broker 发送消息;customer 采用 pull 方式从 broker 接受消息。 + +- **Producer** - 消息生产者,负责发布消息到 Kafka Broker。 +- **Consumer** - 消息消费者,从 Kafka Broker 读取消息的客户端。 +- **Consumer Group** - 每个 Consumer 属于一个特定的 Consumer Group,若不指定 group name 则属于默认的 group。**在同一个 Group 中,每一个 customer 可以消费多个 Partition,但是一个 Partition 只能指定给一个这个 Group 中一个 Customer**。 +- **Broker** - Kafka 集群包含一个或多个服务器,这种服务器被称为 broker + +
+ +
+ +#### 1.2.2. Topic 的存储结构 + +
+ +
+ +- **Topic** - 每条发布到 Kafka 集群的消息都有一个类别,这个类别被称为 Topic。(物理上不同 Topic 的消息分开存储,逻辑上一个 Topic 的消息虽然保存于一个或多个 broker 上,但用户只需指定消息的 Topic 即可生产或消费数据而不必关心数据存于何处)。 +- **Partition** - Partition 是物理上的概念,每个 Topic 包含一个或多个 Partition。为了使得 Kafka 的吞吐率可以线性提高,物理上把 Topic 分成一个或多个 Partition,每个 Partition 在物理上对应一个文件夹,该文件夹下存储这个 Partition 的所有消息和索引文件。 +- **Segment** - 是 Partition 目录下的文件,保存存储消息。 +- **索引** - 方便查询 segment + +#### 1.2.3. Segment 文件格式 + +可以把 topic 当做一个数据表,表中每一个记录都是 key,value 形式,如下图。 + +注意: 必须要有一个 key,如果没有,则默认会生成一个 key,可以把 key 当做一个消息的标识,同一个 key 可能有多条数据。 + +
+ +
+ +### 1.3. 小结 + +**(1)一个主题存在多个分区,每一分区属于哪个 leader broker?** + +在任意一个 broker 机器都有每一个分区所属 leader 的信息,所以可以通过访问任意一个 broker 获取这些信息。 + +**(2)每个消费者群组对应的分区偏移量的元数据存储在哪里。** + +最新版本保存在 kafka 中,对应的主题是\_consumer_offsets。老版本是在 zookeeper 中。 + +**(3)假设某一个消息处理业务逻辑失败了。是否还可以继续向下执行?如果可以的话,那么此时怎么保证这个消息还会继续被处理呢?** + +答案是:正常情况下无法再处理有问题的消息。 + +这里举一个例子,如 M1->M2->M3->M4,假设第一次 poll 时,得到 M1 和 M2,M1 处理成功,M2 处理失败,我们采用提交方式为处理一个消息就提交一次,此时我们提交偏移量是 offset1,但是当我们第二次执行 poll 时,此时只会获取到 M3 和 M4,因为 poll 的时候是根据本地偏移量来获取的,不是 kafka 中保存的初始偏移量。解决这个问题方法是通过 seek 操作定位到 M2 的位置,此时再执行 poll 时就会获取到 M2 和 M3。 + +**(4)当一个消费者执行了 close 之后,此时会执行再均衡,那么再均衡是在哪里发生的呢?其他同组的消费者如何感知到?** + +是通过群组中成为群主的消费者执行再均衡,执行完毕之后,通过群组协调器把每一个消费者负责分区信息发送给消费者,每一个消费者只能知道它负责的分区信息。 + +**(5)如何保证时序性** + +因为 kafkaf 只保证一个分区内的消息才有时序性,所以只要消息属于同一个 topic 且在同一个分区内,就可以保证 kafka 消费消息是有顺序的了。 + +## 2. 安装部署 + +环境要求:JDK8、ZooKeeper + +### 2.1. 下载解压 + +进入官方下载地址:http://kafka.apache.org/downloads,选择合适版本。 + +解压到本地: + +``` +> tar -xzf kafka_2.11-1.1.0.tgz +> cd kafka_2.11-1.1.0 +``` + +现在您已经在您的机器上下载了最新版本的 Kafka。 + +### 2.2. 启动服务器 + +由于 Kafka 依赖于 ZooKeeper,所以运行前需要先启动 ZooKeeper + +``` +> bin/zookeeper-server-start.sh config/zookeeper.properties +[2013-04-22 15:01:37,495] INFO Reading configuration from: config/zookeeper.properties (org.apache.zookeeper.server.quorum.QuorumPeerConfig) +... +``` + +然后,启动 Kafka + +``` +> bin/kafka-server-start.sh config/server.properties +[2013-04-22 15:01:47,028] INFO Verifying properties (kafka.utils.VerifiableProperties) +[2013-04-22 15:01:47,051] INFO Property socket.send.buffer.bytes is overridden to 1048576 (kafka.utils.VerifiableProperties) +... +``` + +### 2.3. 停止服务器 + +执行所有操作后,可以使用以下命令停止服务器 + +``` +$ bin/kafka-server-stop.sh config/server.properties +``` + +### 2.4. 创建主题 + +创建一个名为 test 的 Topic,这个 Topic 只有一个分区以及一个备份: + +``` +> bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test +``` + +### 2.5. 生产者生产消息 + +运行生产者,然后可以在控制台中输入一些消息,这些消息会发送到服务器: + +``` +> bin/kafka-console-producer.sh --broker-list localhost:9092 --topic test +This is a message +This is another message +``` + +### 2.6. 消费者消费消息 + +启动消费者,然后获得服务器中 Topic 下的消息: + +``` +> bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test --from-beginning +This is a message +This is another message +``` + +### 2.7. 集群部署 + +复制配置为多份(Windows 使用 copy 命令代理): + +``` +> cp config/server.properties config/server-1.properties +> cp config/server.properties config/server-2.properties +``` + +修改配置: + +``` +config/server-1.properties: + broker.id=1 + listeners=PLAINTEXT://:9093 + log.dir=/tmp/kafka-logs-1 + +config/server-2.properties: + broker.id=2 + listeners=PLAINTEXT://:9094 + log.dir=/tmp/kafka-logs-2 +``` + +其中,broker.id 这个参数必须是唯一的。 + +端口故意配置的不一致,是为了可以在一台机器启动多个应用节点。 + +根据这两份配置启动三个服务器节点: + +``` +> bin/kafka-server-start.sh config/server.properties & +... +> bin/kafka-server-start.sh config/server-1.properties & +... +> bin/kafka-server-start.sh config/server-2.properties & +... +``` + +创建一个新的 Topic 使用 三个备份: + +``` +> bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 3 --partitions 1 --topic my-replicated-topic +``` + +查看主题: + +``` +> bin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic my-replicated-topic +Topic:my-replicated-topic PartitionCount:1 ReplicationFactor:3 Configs: + Topic: my-replicated-topic Partition: 0 Leader: 1 Replicas: 1,2,0 Isr: 1,2,0 +``` + +- leader - 负责指定分区的所有读取和写入的节点。每个节点将成为随机选择的分区部分的领导者。 +- replicas - 是复制此分区日志的节点列表,无论它们是否为领导者,或者即使它们当前处于活动状态。 +- isr - 是“同步”复制品的集合。这是副本列表的子集,该列表当前处于活跃状态并且已经被领导者捕获。 + +## 3. API + +Stream API 的 maven 依赖: + +```xml + + org.apache.kafka + kafka-streams + 1.1.0 + +``` + +其他 API 的 maven 依赖: + +```xml + + org.apache.kafka + kafka-clients + 1.1.0 + +``` + +## 4. 生产者(Producer) + +### 4.1. 发送消息流程 + +发送消息流程如下图,需要注意的有: + +- 分区器 Partitioner,分区器决定了一个消息被分配到哪个分区。在我们创建消息时,我们可以选择性指定一个键值 key 或者分区 Partition,如果传入的是 key,则通过图中的分区器 Partitioner 选择一个分区来保存这个消息;如果 key 和 Partition 都没有指定,则会默认生成一个 key。 +- 批次传输。**批次,就是一组消息,这些消息属于同一个主题和分区**。发送时,会把消息分成批次 Batch 传输,如果每一个消息发送一次,会导致大量的网路开销, +- 如果消息成功写入 kafka,就返回一个 RecoredMetaData 对象,它包含了主题和分区信息,以及记录在分区里的偏移量。 +- 如果消息发送失败,可以进行重试,重试次数可以在配置中指定。 + +
+ +
+ +生产者在向 broker 发送消息时是怎么确定向哪一个 broker 发送消息? + +- 生产者客户端会向任一个 broker 发送一个元数据请求(MetadataRequest),获取到每一个分区对应的 leader 信息,并缓存到本地。 +- step2:生产者在发送消息时,会指定 Partition 或者通过 key 得到到一个 Partition,然后根据 Partition 从缓存中获取相应的 leader 信息。 + +
+ +
+ +### 4.2. 发送消息方式 + +#### 4.2.1. 发送并忘记(fire-and-forget) + +代码如下,直接通过 send 方法来发送 + +```java +ProducerRecord record = + new ProducerRecord<>("CustomerCountry", "Precision Products", "France"); + try { + producer.send(record); + } catch (Exception e) { + e.printStackTrace(); +} +``` + +#### 4.2.2. 同步发送 + +代码如下,与“发送并忘记”的方式区别在于多了一个 get()方法,会一直阻塞等待 broker 返回结果: + +```java +ProducerRecord record = + new ProducerRecord<>("CustomerCountry", "Precision Products", "France"); + try { + producer.send(record).get(); + } catch (Exception e) { + e.printStackTrace(); +} +``` + +#### 4.2.3. 异步发送 + +代码如下,异步方式相对于“发送并忘记”的方式的不同在于,在异步返回时可以执行一些操作,如记录错误或者成功日志。 + +首先,定义一个 callback + +```java +private class DemoProducerCallback implements Callback { + @Override + public void onCompletion(RecordMetadata recordMetadata, Exception e) { + if (e != null) { + e.printStackTrace(); + } + } +} +``` + +然后,使用这个 callback + +```java +ProducerRecord record = + new ProducerRecord<>("CustomerCountry", "Biomedical Materials", "USA"); +producer.send(record, new DemoProducerCallback()); +``` + +#### 4.2.4. 发送消息示例 + +```java +import java.util.Properties; +import org.apache.kafka.clients.producer.KafkaProducer; +import org.apache.kafka.clients.producer.Producer; +import org.apache.kafka.clients.producer.ProducerConfig; +import org.apache.kafka.clients.producer.ProducerRecord; + +/** + * Kafka 生产者生产消息示例 生产者配置参考:https://kafka.apache.org/documentation/#producerconfigs + */ +public class ProducerDemo { + private static final String HOST = "localhost:9092"; + + public static void main(String[] args) { + // 1. 指定生产者的配置 + Properties properties = new Properties(); + properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, HOST); + properties.put(ProducerConfig.ACKS_CONFIG, "all"); + properties.put(ProducerConfig.RETRIES_CONFIG, 0); + properties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384); + properties.put(ProducerConfig.LINGER_MS_CONFIG, 1); + properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432); + properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.StringSerializer"); + properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.StringSerializer"); + + // 2. 使用配置初始化 Kafka 生产者 + Producer producer = new KafkaProducer<>(properties); + + try { + // 3. 使用 send 方法发送异步消息 + for (int i = 0; i < 100; i++) { + String msg = "Message " + i; + producer.send(new ProducerRecord<>("HelloWorld", msg)); + System.out.println("Sent:" + msg); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + // 4. 关闭生产者 + producer.close(); + } + } +} +``` + +## 5. 消费者(Consumer) + +### 5.1. 消费者和消费者群组 + +#### 5.1.1. 消费者介绍 + +消费者以**pull 方式**从 broker 拉取消息,消费者可以订阅一个或多个主题,然后按照消息生成顺序(**kafka 只能保证分区中消息的顺序**)读取消息。 + +**一个消息消息只有在所有跟随者节点都进行了同步,才会被消费者获取到**。如下图,只能消费 Message0、Message1、Message2: + +
+ +
+ +#### 5.1.2. 消费者分区组 + +消费者群组可以实现并发的处理消息。一个消费者群组作为消费一个 topic 消息的单元,每一个 Partition 只能隶属于一个消费者群组中一个 customer,如下图 + +
+ +
+ +#### 5.1.3. 消费者区组的再均衡 + +当在群组里面 新增/移除消费者 或者 新增/移除 kafka 集群 broker 节点 时,群组协调器 Broker 会触发再均衡,重新为每一个 Partition 分配消费者。**再均衡期间,消费者无法读取消息,造成整个消费者群组一小段时间的不可用。** + +- 新增消费者。customer 订阅主题之后,第一次执行 poll 方法 +- 移除消费者。执行 customer.close()操作或者消费客户端宕机,就不再通过 poll 向群组协调器发送心跳了,当群组协调器检测次消费者没有心跳,就会触发再均衡。 +- 新增 broker。如重启 broker 节点 +- 移除 broker。如 kill 掉 broker 节点。 + +**再均衡是是通过消费者群组中的称为“群主”消费者客户端进行的**。什么是群主呢?“群主”就是第一个加入群组的消费者。消费者第一次加入群组时,它会向群组协调器发送一个 JoinGroup 的请求,如果是第一个,则此消费者被指定为“群主”(群主是不是和 qq 群很想啊,就是那个第一个进群的人)。 + +群主分配分区的过程如下: + +1. 群主从群组协调器获取群组成员列表,然后给每一个消费者进行分配分区 Partition。 +2. 两个分配策略:Range 和 RoundRobin。 + - Range 策略,就是把若干个连续的分区分配给消费者,如存在分区 1-5,假设有 3 个消费者,则消费者 1 负责分区 1-2,消费者 2 负责分区 3-4,消费者 3 负责分区 5。 + - RoundRoin 策略,就是把所有分区逐个分给消费者,如存在分区 1-5,假设有 3 个消费者,则分区 1->消费 1,分区 2->消费者 2,分区 3>消费者 3,分区 4>消费者 1,分区 5->消费者 2。 +3. 群主分配完成之后,把分配情况发送给群组协调器。 +4. 群组协调器再把这些信息发送给消费者。**每一个消费者只能看到自己的分配信息,只有群主知道所有消费者的分配信息**。 + +
+ +
+ +### 5.2. 消费消息流程 + +#### 5.2.1. 消费流程 demo + +具体步骤如下 + +- step1 创建消费者。 +- step2 订阅主题。除了订阅主题方式外还有使用指定分组的模式,但是常用方式都是订阅主题方式 +- stpe3 轮询消息。通过 poll 方法轮询。 +- stpe4 关闭消费者。在不用消费者之后,会执行 close 操作。close 操作会关闭 socket,并触发当前消费者群组的再均衡。 + +```java + // 1.构建KafkaCustomer + Consumer consumer = buildCustomer(); + + // 2.设置主题 + consumer.subscribe(Arrays.asList(topic)); + + // 3.接受消息 + try { + while (true) { + ConsumerRecords records = consumer.poll(500); + System.out.println("customer Message---"); + for (ConsumerRecord record : records) + + // print the offset,key and value for the consumer records. + System.out.printf("offset = %d, key = %s, value = %s\n", + record.offset(), record.key(), record.value()); + } + } finally { + // 4.关闭消息 + consumer.close(); + } +``` + +创建消费者的代码如下: + +```java +public Consumer buildCustomer() { + Properties props = new Properties(); + // bootstrap.servers是Kafka集群的IP地址。多个时,使用逗号隔开 + props.put("bootstrap.servers", "localhost:9092"); + // 消费者群组 + props.put("group.id", "test"); + props.put("enable.auto.commit", "true"); + props.put("auto.commit.interval.ms", "1000"); + props.put("session.timeout.ms", "30000"); + props.put("key.deserializer", + "org.apache.kafka.common.serialization.StringDeserializer"); + props.put("value.deserializer", + "org.apache.kafka.common.serialization.StringDeserializer"); + KafkaConsumer consumer = new KafkaConsumer + (props); + + return consumer; +} +``` + +#### 5.2.2. 消费消息方式 + +分为订阅主题和指定分组两种方式: + +- 消费者分组模式。通过订阅主题方式时,消费者必须加入到消费者群组中,即消费者必须有一个自己的分组; +- 独立消费者模式。这种模式就是消费者是独立的不属于任何消费者分组,自己指定消费那些 Partition。 + +1、订阅主题方式 + +```java +consumer.subscribe(Arrays.asList(topic)); +``` + +2、独立消费者模式 + +通过 consumer 的 assign(Collection partitions)方法来为消费者指定分区。 + +```java +public void consumeMessageForIndependentConsumer(String topic){ + // 1.构建KafkaCustomer + Consumer consumer = buildCustomer(); + + // 2.指定分区 + // 2.1获取可用分区 + List partitionInfoList = buildCustomer().partitionsFor(topic); + // 2.2指定分区,这里是指定了所有分区,也可以指定个别的分区 + if(null != partitionInfoList){ + List partitions = Lists.newArrayList(); + for(PartitionInfo partitionInfo : partitionInfoList){ + partitions.add(new TopicPartition(partitionInfo.topic(),partitionInfo.partition())); + } + consumer.assign(partitions); + } + + // 3.接受消息 + while (true) { + ConsumerRecords records = consumer.poll(500); + System.out.println("consume Message---"); + for (ConsumerRecord record : records) { + + // print the offset,key and value for the consumer records. + System.out.printf("offset = %d, key = %s, value = %s\n", + record.offset(), record.key(), record.value()); + + // 异步提交 + consumer.commitAsync(); + + + } + } +} +``` + +#### 5.2.3. 轮询获取消息 + +通过 poll 来获取消息,但是获取消息时并不是立刻返回结果,需要考虑两个因素: + +- 消费者通过 customer.poll(time)中设置的等待时间 +- broker 会等待累计一定量数据,然后发送给消费者。这样可以减少网络开销。 + +
+ +
+ +poll 处了获取消息外,还有其他作用,如下: + +- 发送心跳信息。消费者通过向被指派为群组协调器的 broker 发送心跳来维护他和群组的从属关系,当机器宕掉后,群组协调器触发再分配 + +### 5.3. 提交偏移量 + +#### 5.3.1. 偏移量和提交 + +(1)偏移量 + +偏移量 offeset,是指一个消息在分区中位置。在通过生产者向 kafka 推送消息返回的结果中包含了这个偏移量值,或者在消费者拉取信息时,也会包含消息的偏移量信息。 + +(2)提交的解释 + +我们把消息的偏移量提交到 kafka 的操作叫做**提交或提交偏移量。** + +(3)偏移量的应用 + +目前会有两个位置记录这个偏移量: + +a. Kafka Broker 保存。消费者通过提交操作,把读取分区中最新消息的偏移量更新到 kafka 服务器端(老版本的 kafka 是保存在 zookeeper 中),即消费者往一个叫做\_consumer_offset 的特殊主题发送消息,消息里面包消息的偏移量信息,**并且该主题配置清理策略是 compact,即对于每一个 key 只保存最新的值(key 由 groupId、topic 和 partition 组成)**。关于提交操作在本节进行讨论。 + +如果消费者一直处于运行状态,这个偏移量是没有起到作用,只有当加入或者删除一个群组里消费者,然后进行再均衡操作只有,此时为了可以继续之前工作,新的消费者需要知道上一个消费者处理这个分区的位置信息。 + +b. 消费者客户端保存。消费者客户端会保存 poll()每一次执行后的最后一个消息的偏移量,这样每次执行轮询操作 poll 时,都从这个位置获取信息。这个信息修改可以通过后续小节中三个 seek 方法来修改。 + +(4)提交时会遇到两个问题 + +a. 重复处理 + +当提交的偏移量小于客户端处理的最后一个消息的偏移量时,会出现重复处理消息的问题,如下图 + +
+ +
+ +b. 消息丢失 + +当提交的偏移量大于客户端处理的最后端最后一个消息的偏移量,会出现消息丢失的问题,如下图: + +
+ +
+ +(5)提交方式 + +主要分为:自动提交和手动提交。 + +a. 自动提交 + +auto.commit.commit ,默认为 true 自动提交,自动提交时通过轮询方式来做,时间间通过 auto.commit.interval.ms 属性来进行设置。 + +b. 手动提交 + +除了自动提交,还可以进行手动提交,手动提交就是通过代码调用函数的方式提交,在使用手动提交时首先需要将 auto.commit.commit 设置为 false,目前有三种方式:同步提交、异步提交、同步和异步结合。 + +#### 5.3.2. 同步提交 + +可以通过 commitSync 来进行提交,**同步提交会一直提交直到成功**。如下 + +```java +public void customerMessageWithSyncCommit(String topic) { + // 1.构建KafkaCustomer + Consumer consumer = buildCustomer(); + + // 2.设置主题 + consumer.subscribe(Arrays.asList(topic)); + + // 3.接受消息 + while (true) { + ConsumerRecords records = consumer.poll(500); + System.out.println("customer Message---"); + for (ConsumerRecord record : records) { + + // print the offset,key and value for the consumer records. + System.out.printf("offset = %d, key = %s, value = %s\n", + record.offset(), record.key(), record.value()); + + + // 同步提交 + try { + consumer.commitSync(); + } catch (Exception e) { + logger.error("commit error"); + } + } + } +} +``` + +#### 5.3.3. 异步提交 + +同步提交一个缺点是,在进行提交 commitAysnc()会阻塞整个下面流程。所以引入了异步提交 commitAsync(),如下代码,这里定义了 OffsetCommitCallback,也可以只进行 commitAsync(),不设置任何参数。 + +```java +public void customerMessageWithAsyncCommit(String topic) { + // 1.构建KafkaCustomer + Consumer consumer = buildCustomer(); + + // 2.设置主题 + consumer.subscribe(Arrays.asList(topic)); + + // 3.接受消息 + while (true) { + ConsumerRecords records = consumer.poll(500); + System.out.println("customer Message---"); + for (ConsumerRecord record : records) { + + // print the offset,key and value for the consumer records. + System.out.printf("offset = %d, key = %s, value = %s\n", + record.offset(), record.key(), record.value()); + + // 异步提交 + consumer.commitAsync(new OffsetCommitCallback() { + public void onComplete(Map offsets, Exception e) { + if (e != null) { + logger.error("Commit failed for offsets{}", offsets, e); + } + } + }); + + + } + } +} +``` + +#### 5.3.4. 同步和异步提交 + +代码如下: + +```java +public void customerMessageWithSyncAndAsyncCommit(String topic) { + // 1.构建KafkaCustomer + Consumer consumer = buildCustomer(); + + // 2.设置主题 + consumer.subscribe(Arrays.asList(topic)); + + // 3.接受消息 + try { + while (true) { + ConsumerRecords records = consumer.poll(500); + System.out.println("customer Message---"); + for (ConsumerRecord record : records) { + + // print the offset,key and value for the consumer records. + System.out.printf("offset = %d, key = %s, value = %s\n", + record.offset(), record.key(), record.value()); + + // 异步提交 + consumer.commitAsync(); + } + } + } catch (Exception e) { + + } finally { + // 同步提交 + try { + consumer.commitSync(); + } finally { + consumer.close(); + } + } +} +``` + +### 5.4. 从指定偏移量获取数据 + +我们读取消息是通过 poll 方法。它根据消费者客户端本地保存的当前偏移量来获取消息。如果我们需要从指定偏移量位置获取数据,此时就需要修改这个值为我们想要读取消息开始的地方,目前有如下三个方法: + +- seekToBeginning(Collection partitions)。可以修改分区当前偏移量为分区的起始位置、 +- seekToEnd(Collection partitions)。可以修改分区当前偏移量为分区的末尾位置 +- seek(TopicPartition partition, long offset); 可以修改分区当前偏移量为分区的起始位置 + +通过 seek(TopicPartition partition, long offset)可以实现处理消息和提交偏移量在一个事务中完成。思路就是需要在可短建立一个数据表,保证处理消息和和消息偏移量位置写入到这个数据表在一个事务中,此时就可以保证处理消息和记录偏移量要么同时成功,要么同时失败。代码如下: + +```java + consumer.subscribe(topic); + // 1.第一次调用pool,加入消费者群组 + consumer.poll(0); + // 2.获取负责的分区,并从本地数据库读取改分区最新偏移量,并通过seek方法修改poll获取消息的位置 + for (TopicPartition partition: consumer.assignment()) + consumer.seek(partition, getOffsetFromDB(partition)); + + while (true) { + ConsumerRecords records = + consumer.poll(100); + for (ConsumerRecord record : records) + { + processRecord(record); + storeRecordInDB(record); + storeOffsetInDB(record.topic(), record.partition(), + record.offset()); + } + commitDBTransaction(); + } +``` + +## 6. Broker + +### 6.1. 集群控制器 + +控制器除了具有一般 broker 的功能,还负责分区 leader 的选举。 + +### 6.2. 分区 leader 和 follower + +Kafka 在 0.8 以前的版本中,如果一个 broker 机器宕机了,其上面的 Partition 都不能用了。为了实现 High Availablity,引入了复制功能,即一个 Partition 还会在其他的 broker 上面进行备份。为了实现复制功能,引入了分区 leader 和 follower: + +(1)Leader 作用 + +生产者和消费者请求都会经过这个 leader。Producer 和 Concumer 往一个 Partition 写入和读取消息时,都会首先查找这个 Partition 的 leader + +保存那些 follower 节点的状态与自己是一致的。 + +(2)follower 作用: + +定时通过类似消费者的 poll 方法从 leader 中获取消息,进行备份 + +同一个 topic 的不同 Partition 会分布在多个 broker 上,而且一个 Partition 还会在其他的 broker 上面进行备份,Producer 在发布消息到某个 Partition 时,先找到该 Partition 的 Leader,然后向这个 leader 推送消息;每个 Follower 都从 Leader 拉取消息,拉取消息成功之后,向 leader 发送一个 ack 确认。如下一个流程图: + +
+ +
+ +### 6.3. 群组协调器 + +群组协调器,顾名思义就是维护消费者群组 ,消费者通过向被指派为群组协调器的 broker(不同的群组可以有不同的协调器)发送心跳来维护它们和群组的从属关系,以及它们对分区的所有权。 + +1、触发再均衡 + +只要消费者以正常的时间间隔发送心跳,就会被认为是活跃的。消费者是通过 poll 获取消息时,发送的心跳的,当消费者客户端宕机之后,群组协调器在一段内没有收到心跳,则此时会认为消费者已死亡,然后触发一次再均衡。具体再均衡流程,可以参考上面的“再均衡”小节。 + +## 7. Zookeeper 集群 + +### 7.1. 节点信息 + +参考: + +Zookeeper 保存的就是节点信息和节点状态,不会保存 kafka 的消息信息,节点信息包括: + +- broker。broer 启动时在 zookeeper 注册、并通过 watcher 监听 broker 节点变化;并且还记录 topic 和 Partition 的信息。 +- consumers,消费者节点信息 +- admin +- config +- controller 和 controloer_epoch + +
+ +
+ +#### 7.1.1. broker + +(1)Topic 的注册信息 + +作用:在创建 zookeeper 时,注册 topic 的 Partition 信息,包括每一个分区的复制节点 id。 + +路径:/brokers/topics/[topic] + +数据格式: + +```json +Schema: +{ "fields" : + [ {"name": "version", "type": "int", "doc": "version id"}, + {"name": "partitions", + "type": {"type": "map", + "values": {"type": "array", "items": "int", "doc": "a list of replica ids"}, + "doc": "a map from partition id to replica list"}, + } + ] +} +Example: +{ + "version": 1, + "partitions": {"0": [0, 1, 3] } } # 分区0的对应的复制节点是0、1、3. +} +``` + +(2)分区信息 + +作用:记录分区信息,如分区的 leader 信息 + +路径信息:/brokers/topics/[topic]/partitions/[partitionId]/state + +格式: + +```json +Schema: +{ "fields": + [ {"name": "version", "type": "int", "doc": "version id"}, + {"name": "isr", + "type": {"type": "array", + "items": "int", + "doc": "an array of the id of replicas in isr"} + }, + {"name": "leader", "type": "int", "doc": "id of the leader replica"}, + {"name": "controller_epoch", "type": "int", "doc": "epoch of the controller that last updated the leader and isr info"}, + {"name": "leader_epoch", "type": "int", "doc": "epoch of the leader"} + ] +} + +Example: +{ + "version": 1, + "isr": [0,1], + "leader": 0, + "controller_epoch": 1, + "leader_epoch": 0 +} +``` + +(3)broker 信息 + +作用:在 borker 启动时,向 zookeeper 注册节点信息 + +路径:/brokers/ids/[brokerId] + +数据格式: + +```json +Schema: +{ "fields": + [ {"name": "version", "type": "int", "doc": "version id"}, + {"name": "host", "type": "string", "doc": "ip address or host name of the broker"}, + {"name": "port", "type": "int", "doc": "port of the broker"}, + {"name": "jmx_port", "type": "int", "doc": "port for jmx"} + ] +} + +Example: +{ + "version": 1, + "host": "192.168.1.148", + "port": 9092, + "jmx_port": 9999 +} +``` + +#### 7.1.2. controller 和 controller_epoch + +(1)控制器的 epoch: + +/controller_epoch -> int (epoch) + +(2)控制器的注册信息: + +/controller -> int (broker id of the controller) + +#### 7.1.3. consumer + +(1)消费者注册信息: + +路径:/consumers/[groupId]/ids/[consumerId] + +数据格式: + +```json +Schema: +{ "fields": + [ {"name": "version", "type": "int", "doc": "version id"}, + {"name": "pattern", "type": "string", "doc": "can be of static, white_list or black_list"}, + {"name": "subscription", "type" : {"type": "map", "values": {"type": "int"}, + "doc": "a map from a topic or a wildcard pattern to the number of streams"} } ] +} + +Example: +A static subscription: +{ + "version": 1, + "pattern": "static", + "subscription": {"topic1": 1, "topic2": 2} +} + + +A whitelist subscription: +{ + "version": 1, + "pattern": "white_list", + "subscription": {"abc": 1} +} + +A blacklist subscription: +{ + "version": 1, + "pattern": "black_list", + "subscription": {"abc": 1} +} +``` + +#### 7.1.4. admin + +(1)Re-assign partitions + +路径:/admin/reassign_partitions + +数据格式: + +```json +{ + "fields":[ + { + "name":"version", + "type":"int", + "doc":"version id" + }, + { + "name":"partitions", + "type":{ + "type":"array", + "items":{ + "fields":[ + { + "name":"topic", + "type":"string", + "doc":"topic of the partition to be reassigned" + }, + { + "name":"partition", + "type":"int", + "doc":"the partition to be reassigned" + }, + { + "name":"replicas", + "type":"array", + "items":"int", + "doc":"a list of replica ids" + } + ], + } + "doc":"an array of partitions to be reassigned to new replicas" + } + } + ] +} + +Example: +{ + "version": 1, + "partitions": + [ + { + "topic": "Foo", + "partition": 1, + "replicas": [0, 1, 3] + } + ] +} +``` + +(2)Preferred replication election + +路径:/admin/preferred_replica_election + +数据格式: + +```json +{ + "fields":[ + { + "name":"version", + "type":"int", + "doc":"version id" + }, + { + "name":"partitions", + "type":{ + "type":"array", + "items":{ + "fields":[ + { + "name":"topic", + "type":"string", + "doc":"topic of the partition for which preferred replica election should be triggered" + }, + { + "name":"partition", + "type":"int", + "doc":"the partition for which preferred replica election should be triggered" + } + ], + } + "doc":"an array of partitions for which preferred replica election should be triggered" + } + } + ] +} + +Example: + +{ + "version": 1, + "partitions": + [ + { + "topic": "Foo", + "partition": 1 + }, + { + "topic": "Bar", + "partition": 0 + } + ] +} +``` + +(3)Delete topics + +/admin/delete_topics/[topic_to_be_deleted] (the value of the path in empty) + +#### 7.1.5. config + +Topic Configuration + +/config/topics/[topic_name] + +数据格式: + +```json +{ + "version": 1, + "config": { + "config.a": "x", + "config.b": "y", + ... + } +} +``` + +### 7.2. zookeeper 一些总结 + +离开了 Zookeeper, Kafka 不能对 Topic 进行新增操作, 但是仍然可以 produce 和 consume 消息. + +## 8. 资料 + +- [Kafka(03) Kafka 介绍](http://www.heartthinkdo.com/?p=2006#233) diff --git a/docs/distributed/mq/rocketmq-advanced.md b/docs/distributed/mq/rocketmq-advanced.md index 3d84c76a..e09138dc 100644 --- a/docs/distributed/mq/rocketmq-advanced.md +++ b/docs/distributed/mq/rocketmq-advanced.md @@ -1,14 +1,3 @@ ---- -title: RocketMQ 原理篇 -date: 2018/07/17 -categories: -- javaweb -tags: -- java -- javaweb -- mq ---- - # RocketMQ 原理篇 @@ -200,11 +189,11 @@ RocketMQ 除了支持普通消息,顺序消息,另外还支持事务消息 假设这样的场景: -![image.png](https://upload-images.jianshu.io/upload_images/3101171-253d8bd65736694f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +
图中执行本地事务(Bob 账户扣款)和发送异步消息应该保证同时成功或者同时失败,也就是扣款成功了,发送消息一定要成功,如果扣款失败了,就不能再发送消息。那问题是:我们是先扣款还是先发送消息呢? -![image](http://upload-images.jianshu.io/upload_images/3101171-088dc074c4ecd192?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +
RocketMQ 分布式事务步骤: diff --git a/docs/distributed/mq/rocketmq-basics.md b/docs/distributed/mq/rocketmq-basics.md index ea2573cb..d693e1bf 100644 --- a/docs/distributed/mq/rocketmq-basics.md +++ b/docs/distributed/mq/rocketmq-basics.md @@ -1,14 +1,3 @@ ---- -title: RocketMQ 实战篇 -date: 2018/07/17 -categories: -- javaweb -tags: -- java -- javaweb -- mq ---- - # RocketMQ 实战篇 diff --git "a/docs/distributed/mq/\345\210\206\345\270\203\345\274\217\346\266\210\346\201\257\351\230\237\345\210\227.md" "b/docs/distributed/mq/\345\210\206\345\270\203\345\274\217\346\266\210\346\201\257\351\230\237\345\210\227.md" index dbfb7080..c0cbe411 100644 --- "a/docs/distributed/mq/\345\210\206\345\270\203\345\274\217\346\266\210\346\201\257\351\230\237\345\210\227.md" +++ "b/docs/distributed/mq/\345\210\206\345\270\203\345\274\217\346\266\210\346\201\257\351\230\237\345\210\227.md" @@ -1,13 +1,3 @@ ---- -title: 分布式消息队列 -date: 2018/07/05 -categories: -- 分布式 -tags: -- 分布式 -- mq ---- - # 分布式消息队列 > 消息队列是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量削锋等问题。实现高性能,高可用,可伸缩和最终一致性架构。是大型分布式系统不可缺少的中间件。 @@ -46,11 +36,11 @@ tags: (1)串行方式:将注册信息写入数据库成功后,发送注册邮件,再发送注册短信。以上三个任务全部完成后,返回给客户端。 -![image](http://upload-images.jianshu.io/upload_images/3101171-c60d8f5be3a7a3f2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +
(2)并行方式:将注册信息写入数据库成功后,发送注册邮件的同时,发送注册短信。以上三个任务完成后,返回给客户端。与串行的差别是,并行的方式可以提高处理的时间。 -![image](http://upload-images.jianshu.io/upload_images/3101171-1a78d88cfbfebcd2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +
假设三个业务节点每个使用 50 毫秒钟,不考虑网络等其他开销,则串行方式的时间是 150 毫秒,并行的时间可能是 100 毫秒。 @@ -60,7 +50,7 @@ tags: 引入消息队列,将不是必须的业务逻辑,异步处理。改造后的架构如下: -![image](http://upload-images.jianshu.io/upload_images/3101171-105299d1fd6f3093.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +
按照以上约定,用户的响应时间相当于是注册信息写入数据库的时间,也就是 50 毫秒。注册邮件,发送短信写入消息队列后,直接返回,因此写入消息队列的速度很快,基本可以忽略,因此用户的响应时间可能是 50 毫秒。因此架构改变后,系统的吞吐量提高到每秒 20 QPS。比串行提高了 3 倍,比并行提高了两倍。 @@ -68,17 +58,17 @@ tags: 场景说明:用户下单后,订单系统需要通知库存系统。传统的做法是,订单系统调用库存系统的接口。如下图: -![image](http://upload-images.jianshu.io/upload_images/3101171-872f21e480fa4026.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +
传统模式的缺点: -1)  假如库存系统无法访问,则订单减库存将失败,从而导致订单失败; +1) 假如库存系统无法访问,则订单减库存将失败,从而导致订单失败; -2)  订单系统与库存系统耦合; +2) 订单系统与库存系统耦合; 如何解决以上问题呢?引入应用消息队列后的方案,如下图: -![image](http://upload-images.jianshu.io/upload_images/3101171-81e965d35df99238.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +
- 订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功。 - 库存系统:订阅下单的消息,采用拉/推的方式,获取下单信息,库存系统根据下单信息,进行库存操作。 @@ -93,7 +83,7 @@ tags: 1. 可以控制活动的人数; 2. 可以缓解短时间内高流量压垮应用; -![image](http://upload-images.jianshu.io/upload_images/3101171-26394776da461cbf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +
1. 用户的请求,服务器接收后,首先写入消息队列。假如消息队列长度超过最大数量,则直接抛弃用户请求或跳转到错误页面; 2. 秒杀业务根据消息队列中的请求信息,再做后续处理。 @@ -102,7 +92,7 @@ tags: 日志处理是指将消息队列用在日志处理中,比如 Kafka 的应用,解决大量日志传输的问题。架构简化如下: -![image](http://upload-images.jianshu.io/upload_images/3101171-54d35d48b2fb643b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +
- 日志采集客户端,负责日志数据采集,定时写入 Kafka 队列; - Kafka 消息队列,负责日志数据的接收,存储和转发; @@ -112,7 +102,7 @@ tags: 转自(http://cloud.51cto.com/art/201507/484338.htm) -![image](http://upload-images.jianshu.io/upload_images/3101171-46e0f421a0670f4b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +
- Kafka - 接收用户日志的消息队列。 - Logstash - 负责日志传输和解析,统一成 JSON 输出给 Elasticsearch。 @@ -125,13 +115,13 @@ tags: 点对点通讯: -![image](http://upload-images.jianshu.io/upload_images/3101171-ba662aaae7331dee.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +
客户端 A 和客户端 B 使用同一队列,进行消息通讯。 聊天室通讯: -![image](http://upload-images.jianshu.io/upload_images/3101171-07ef81ffec631fd1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +
客户端 A,客户端 B,客户端 N 订阅同一主题,进行消息发布和接收。实现类似聊天室效果。 @@ -152,7 +142,7 @@ tags: #### 2.1.1. P2P 模式 -![image](http://upload-images.jianshu.io/upload_images/3101171-2adc66e2367cd2c2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +
P2P 模式包含三个角色:消息队列(Queue),发送者(Sender),接收者(Receiver)。每个消息都被发送到一个特定的队列,接收者从队列中获取消息。队列保留着消息,直到他们被消费或超时。 @@ -166,7 +156,7 @@ P2P 的特点 #### 2.1.2. Pub/sub 模式 -![image](http://upload-images.jianshu.io/upload_images/3101171-12afe9581da889ea.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +
包含三个角色主题(Topic),发布者(Publisher),订阅者(Subscriber) 。多个发布者将消息发送到 Topic,系统将这些消息传递给多个订阅者。 @@ -266,7 +256,7 @@ RabbitMQ 是流行的开源消息队列系统,用 erlang 语言开发。Rabbit 结构图如下: -![image](http://upload-images.jianshu.io/upload_images/3101171-1109f074e8445c6d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +
几个重要概念: @@ -375,7 +365,7 @@ Parition 是物理上的概念,每个 Topic 包含一个或多个 Partition. ### 4.1. 电商系统 -![image](http://upload-images.jianshu.io/upload_images/3101171-1bbed6d1a2274ba1.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +
消息队列采用高可用,可持久化的消息中间件。比如 Active MQ,Rabbit MQ,Rocket Mq。 @@ -387,7 +377,7 @@ Parition 是物理上的概念,每个 Topic 包含一个或多个 Partition. ### 4.2. 日志收集系统 -![image](http://upload-images.jianshu.io/upload_images/3101171-4275c9f0e9c8c463.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +
分为 Zookeeper 注册中心,日志收集客户端,Kafka 集群和 Storm 集群(OtherApp)四部分组成。 @@ -402,4 +392,4 @@ Storm 集群:与 OtherApp 处于同一级别,采用拉的方式消费队列 - [大型网站架构系列:分布式消息队列(一)](https://www.cnblogs.com/itfly8/p/5155983.html) - [大型网站架构系列:消息队列(二)](https://www.cnblogs.com/itfly8/p/5156155.html) - [分布式开放消息系统(RocketMQ)的原理与实践](https://www.jianshu.com/p/453c6e7ff81c) -- [阿里 RocketMQ 优势对比](https://juejin.im/entry/5a0abfb5f265da43062a4a91) \ No newline at end of file +- [阿里 RocketMQ 优势对比](https://juejin.im/entry/5a0abfb5f265da43062a4a91) diff --git a/docs/distributed/rpc/dubbo.md b/docs/distributed/rpc/dubbo.md index 3db29e2b..81261c00 100644 --- a/docs/distributed/rpc/dubbo.md +++ b/docs/distributed/rpc/dubbo.md @@ -1,13 +1,3 @@ ---- -title: Dubbo -date: 2018/06/12 -categories: -- 分布式 -tags: -- 分布式 -- rpc ---- - # Dubbo > Dubbo 是一个基于 Java 开发的高性能 RPC 框架。 diff --git a/docs/distributed/rpc/zookeeper-advanced.md b/docs/distributed/rpc/zookeeper-advanced.md index 0e236165..6fbe0b64 100644 --- a/docs/distributed/rpc/zookeeper-advanced.md +++ b/docs/distributed/rpc/zookeeper-advanced.md @@ -1,13 +1,3 @@ ---- -title: ZooKeeper 高级篇 -date: 2018/07/10 -categories: -- 分布式 -tags: -- 分布式 -- rpc ---- - # ZooKeeper 高级篇 > ZooKeeper 是一个分布式应用协调系统,已经用到了许多分布式项目中,用来完成统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等工作。 diff --git a/docs/distributed/rpc/zookeeper-basics.md b/docs/distributed/rpc/zookeeper-basics.md index 0d89c0e7..7d8dffee 100644 --- a/docs/distributed/rpc/zookeeper-basics.md +++ b/docs/distributed/rpc/zookeeper-basics.md @@ -1,13 +1,3 @@ ---- -title: ZooKeeper 基础篇 -date: 2018/07/12 -categories: -- 分布式 -tags: -- 分布式 -- rpc ---- - # ZooKeeper 基础篇 > ZooKeeper 是一个针对大型分布式系统的可靠协调系统,提供的功能包括:配置维护、名字服务、分布式同步、组服务等。 diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 00000000..e26f425f --- /dev/null +++ b/docs/index.html @@ -0,0 +1,62 @@ + + + + + JavaWeb + + + + + + + + + + +
正在加载...
+ + + + + + + + + + + + + diff --git a/docs/javaee/README.md b/docs/javaee/README.md index 73f369c9..44b42327 100644 --- a/docs/javaee/README.md +++ b/docs/javaee/README.md @@ -1,6 +1,11 @@ -# JavaEE +# JavaEE 教程 -> [JavaEE](http://www.oracle.com/technetwork/java/javaee/overview/index.html) 即 Java 的企业级应用程序版本。这个版本以前称为 J2EE。能够帮助我们开发和部署可移植、健壮、可伸缩且安全的服务器端 Java 应用程序。 -> -> JavaEE 是在 JavaSE 的基础上构建的,它提供 Web 服务、组件模型、管理和通信 API,可以用来实现企业级的 SOA 和 Web 3.0 应用程序。 +## 学习资料 +- 书 + - [Java Web 整合开发王者归来](https://book.douban.com/subject/4189495/) + - [Head First Servlets & JSP](https://book.douban.com/subject/1942934/) +- 教程 + - [博客园孤傲苍狼 JavaWeb 学习总结](https://www.cnblogs.com/xdp-gacl/tag/JavaWeb%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93/) + - [Servlet 教程](https://www.runoob.com/servlet/servlet-tutorial.html) + - [JSP 教程](https://www.runoob.com/jsp/jsp-tutorial.html) diff --git a/docs/javaee/session/cookie.md b/docs/javaee/cookie-and-sesion.md similarity index 53% rename from docs/javaee/session/cookie.md rename to docs/javaee/cookie-and-sesion.md index 61da1135..c05953db 100644 --- a/docs/javaee/session/cookie.md +++ b/docs/javaee/cookie-and-sesion.md @@ -1,19 +1,48 @@ --- -title: JavaEE Cookie -date: 2017/11/08 -categories: -- javaee -tags: -- javaee -- cookie -- session +title: Cookie 和 Session +categories: ['java', 'javaweb', 'javaee'] +tags: ['java', 'javaweb', 'javaee', 'cookie', 'session'] +date: 2017-11-08 --- +# Cookie 和 Session + + + +- [Cookie](#cookie) + - [Cookie 是什么?](#cookie-是什么) + - [Cookie 剖析](#cookie-剖析) + - [Cookie 类中的方法](#cookie-类中的方法) + - [Cookie 的有效期](#cookie-的有效期) + - [Cookie 的域名](#cookie-的域名) + - [Cookie 的路径](#cookie-的路径) + - [Cookie 的安全属性](#cookie-的安全属性) + - [实例](#实例) +- [Session](#session) + - [Session 是什么?](#session-是什么) + - [Session 类中的方法](#session-类中的方法) + - [Session 的有效期](#session-的有效期) + - [Session 对浏览器的要求](#session-对浏览器的要求) + - [URL 地址重写](#url-地址重写) + - [Session 中禁用 Cookie](#session-中禁用-cookie) + - [实例](#实例-1) +- [Cookie vs Session](#cookie-vs-session) + - [存取方式](#存取方式) + - [隐私安全](#隐私安全) + - [有效期](#有效期) + - [服务器的开销](#服务器的开销) + - [浏览器的支持](#浏览器的支持) + - [跨域名](#跨域名) + + + +## Cookie + 由于 Http 是一种无状态的协议,服务器单从网络连接上无从知道客户身份。 会话跟踪是 Web 程序中常用的技术,用来跟踪用户的整个会话。常用会话跟踪技术是 Cookie 与 Session。 -## Cookie 是什么? +### Cookie 是什么? Cookie 实际上是存储在客户端上的文本信息,并保留了各种跟踪的信息。 @@ -27,7 +56,7 @@ Cookie 实际上是存储在客户端上的文本信息,并保留了各种跟 Java 中把 Cookie 封装成了`javax.servlet.http.Cookie`类。 -## Cookie 剖析 +### Cookie 剖析 Cookies 通常设置在 HTTP 头信息中(虽然 JavaScript 也可以直接在浏览器上设置一个 Cookie)。 @@ -59,7 +88,7 @@ Accept-Charset: iso-8859-1,*,utf-8 Cookie: name=xyz ``` -## Cookie 类中的方法 +### Cookie 类中的方法 | 方法 | 功能 | | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | @@ -76,7 +105,7 @@ Cookie: name=xyz | public void setComment(String purpose) | 该方法规定了描述 cookie 目的的注释。该注释在浏览器向用户呈现 cookie 时非常有用。 | | public String getComment() | 该方法返回了描述 cookie 目的的注释,如果 cookie 没有注释则返回 null。 | -## Cookie 的有效期 +### Cookie 的有效期 `Cookie`的`maxAge`决定着 Cookie 的有效期,单位为秒。 @@ -86,7 +115,7 @@ Cookie: name=xyz Cookie 中提供`getMaxAge()`**和**`setMaxAge(int expiry)`方法来读写`maxAge`属性。 -## Cookie 的域名 +### Cookie 的域名 Cookie 是不可以跨域名的。域名 www.google.com 颁发的 Cookie 不会被提交到域名 www.baidu.com 去。这是由 Cookie 的隐私安全机制决定的。隐私安全机制能够禁止网站非法获取其他网站的 Cookie。 @@ -94,13 +123,13 @@ Cookie 是不可以跨域名的。域名 www.google.com 颁发的 Cookie 不会 Java 中使用`setDomain(Stringdomain)`和`getDomain()`方法来设置、获取 domain。 -## Cookie 的路径 +### Cookie 的路径 Path 属性决定允许访问 Cookie 的路径。 Java 中使用`setPath(Stringuri)`和`getPath()`方法来设置、获取 path。 -## Cookie 的安全属性 +### Cookie 的安全属性 HTTP 协议不仅是无状态的,而且是不安全的。 @@ -108,9 +137,9 @@ HTTP 协议不仅是无状态的,而且是不安全的。 Java 中使用`setSecure(booleanflag)`和`getSecure ()`方法来设置、获取 Secure。 -## 实例 +### 实例 -### 添加 Cookie +#### 添加 Cookie 通过 Servlet 添加 Cookies 包括三个步骤: @@ -207,7 +236,7 @@ addCookies.jsp ``` -### 显示 Cookie +#### 显示 Cookie 要读取 Cookies,您需要通过调用 `HttpServletRequest` 的 `getCookies()` 方法创建一个 `javax.servlet.http.Cookie` 对象的数组。然后循环遍历数组,并使用 `getName()` 和 `getValue()` 方法来访问每个 cookie 和关联的值。 @@ -284,7 +313,7 @@ public class ReadCookies extends HttpServlet { } ``` -### 删除 Cookie +#### 删除 Cookie Java 中并没有提供直接删除 Cookie 的方法,如果想要删除一个 Cookie,直接将这个 Cookie 的有效期设为 0 就可以了。步骤如下: @@ -365,3 +394,218 @@ public class DeleteCookies extends HttpServlet { } ``` + +## Session + +### Session 是什么? + +不同于 Cookie 保存在客户端浏览器中,Session 保存在服务器上。 + +如果说 Cookie 机制是通过检查客户身上的“通行证”来确定客户身份的话,那么 Session 机制就是通过检查服务器上的“客户明细表”来确认客户身份。 + +Session 对应的类为 `javax.servlet.http.HttpSession` 类。Session 对象是在客户第一次请求服务器时创建的。 + +### Session 类中的方法 + +`javax.servlet.http.HttpSession` 类中的方法: + +| **方法** | **功能** | +| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | +| public Object getAttribute(String name) | 该方法返回在该 session 会话中具有指定名称的对象,如果没有指定名称的对象,则返回 null。 | +| public Enumeration getAttributeNames() | 该方法返回 String 对象的枚举,String 对象包含所有绑定到该 session 会话的对象的名称。 | +| public long getCreationTime() | 该方法返回该 session 会话被创建的时间,自格林尼治标准时间 1970 年 1 月 1 日午夜算起,以毫秒为单位。 | +| public String getId() | 该方法返回一个包含分配给该 session 会话的唯一标识符的字符串。 | +| public long getLastAccessedTime() | 该方法返回客户端最后一次发送与该 session 会话相关的请求的时间自格林尼治标准时间 1970 年 1 月 1 日午夜算起,以毫秒为单位。 | +| public int getMaxInactiveInterval() | 该方法返回 Servlet 容器在客户端访问时保持 session 会话打开的最大时间间隔,以秒为单位。 | +| public void invalidate() | 该方法指示该 session 会话无效,并解除绑定到它上面的任何对象。 | +| public boolean isNew() | 如果客户端还不知道该 session 会话,或者如果客户选择不参入该 session 会话,则该方法返回 true。 | +| public void removeAttribute(String name) | 该方法将从该 session 会话移除指定名称的对象。 | +| public void setAttribute(String name, Object value) | 该方法使用指定的名称绑定一个对象到该 session 会话。 | +| public void setMaxInactiveInterval(int interval) | 该方法在 Servlet 容器指示该 session 会话无效之前,指定客户端请求之间的时间,以秒为单位。 | + +### Session 的有效期 + +由于会有越来越多的用户访问服务器,因此 Session 也会越来越多。为防止内存溢出,服务器会把长时间没有活跃的 Session 从内存中删除。 + +Session 的超时时间为`maxInactiveInterval`属性,可以通过`getMaxInactiveInterval()`、`setMaxInactiveInterval(longinterval)`来读写这个属性。 + +Tomcat 中 Session 的默认超时时间为 20 分钟。可以修改 web.xml 改变 Session 的默认超时时间。 + +例: + +```xml + + 60 + +``` + +### Session 对浏览器的要求 + +HTTP 协议是无状态的,Session 不能依据 HTTP 连接来判断是否为同一客户。因此服务器向客户端浏览器发送一个名为 JESSIONID 的 Cookie,他的值为该 Session 的 id(也就是 HttpSession.getId()的返回值)。Session 依据该 Cookie 来识别是否为同一用户。 + +该 Cookie 为服务器自动生成的,它的`maxAge`属性一般为-1,表示仅当前浏览器内有效,并且各浏览器窗口间不共享,关闭浏览器就会失效。 + +### URL 地址重写 + +URL 地址重写的原理是将该用户 Session 的 id 信息重写到 URL 地址中。服务器能够解析重写后的 URL 获取 Session 的 id。这样即使客户端不支持 Cookie,也可以使用 Session 来记录用户状态。 + +`HttpServletResponse`类提供了`encodeURL(Stringurl)`实现 URL 地址重写。 + +### Session 中禁用 Cookie + +在`META-INF/context.xml`中编辑如下: + +```xml + + +``` + +部署后,TOMCAT 便不会自动生成名 JESSIONID 的 Cookie,Session 也不会以 Cookie 为识别标志,而仅仅以重写后的 URL 地址为识别标志了。 + +### 实例 + +#### Session 跟踪 + +SessionTrackServlet.java + +```java +import java.io.IOException; +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.Date; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +@WebServlet("/servlet/SessionTrackServlet") +public class SessionTrackServlet extends HttpServlet { + private static final long serialVersionUID = 1L; + + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + // 如果不存在 session 会话,则创建一个 session 对象 + HttpSession session = request.getSession(true); + // 获取 session 创建时间 + Date createTime = new Date(session.getCreationTime()); + // 获取该网页的最后一次访问时间 + Date lastAccessTime = new Date(session.getLastAccessedTime()); + + // 设置日期输出的格式 + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + String title = "Servlet Session 实例"; + Integer visitCount = new Integer(0); + String visitCountKey = new String("visitCount"); + String userIDKey = new String("userID"); + String userID = new String("admin"); + + // 检查网页上是否有新的访问者 + if (session.isNew()) { + session.setAttribute(userIDKey, userID); + } else { + visitCount = (Integer) session.getAttribute(visitCountKey); + visitCount = visitCount + 1; + userID = (String) session.getAttribute(userIDKey); + } + session.setAttribute(visitCountKey, visitCount); + + // 设置响应内容类型 + response.setContentType("text/html;charset=UTF-8"); + PrintWriter out = response.getWriter(); + + String docType = "\n"; + out.println(docType + "\n" + "" + title + "\n" + + "\n" + "

" + title + + "

\n" + "

Session 信息

\n" + + "\n" + "\n" + + " \n" + "\n" + " \n" + + " \n" + "\n" + + " \n" + " \n" + + "\n" + " \n" + " \n" + "\n" + " \n" + " \n" + "\n" + " \n" + " \n" + "
Session 信息
id" + session.getId() + "
创建时间" + df.format(createTime) + "
最后访问时间" + df.format(lastAccessTime) + + "
用户 ID" + userID + + "
访问统计:" + visitCount + + "
\n" + ""); + } +} +``` + +web.xml + +```xml + + SessionTrackServlet + SessionTrackServlet + + + SessionTrackServlet + /servlet/SessionTrackServlet + +``` + +#### 删除 Session 会话数据 + +当您完成了一个用户的 session 会话数据,您有以下几种选择: + +**移除一个特定的属性:**您可以调用 `removeAttribute(String name)` 方法来删除与特定的键相关联的值。 + +**删除整个 session 会话:**您可以调用 `invalidate()` 方法来丢弃整个 session 会话。 + +**设置 session 会话过期时间:**您可以调用 `setMaxInactiveInterval(int interval)` 方法来单独设置 session 会话超时。 + +**注销用户:**如果使用的是支持 servlet 2.4 的服务器,您可以调用 `logout` 来注销 Web 服务器的客户端,并把属于所有用户的所有 session 会话设置为无效。 + +**web.xml 配置:**如果您使用的是 Tomcat,除了上述方法,您还可以在 web.xml 文件中配置 session 会话超时,如下所示: + +```xml + + 15 + +``` + +上面实例中的超时时间是以分钟为单位,将覆盖 Tomcat 中默认的 30 分钟超时时间。 + +在一个 Servlet 中的 `getMaxInactiveInterval()` 方法会返回 session 会话的超时时间,以秒为单位。所以,如果在 web.xml 中配置 session 会话超时时间为 15 分钟,那么`getMaxInactiveInterval()` 会返回 900。 + +## Cookie vs Session + +### 存取方式 + +Cookie 只能保存`ASCII`字符串,如果需要存取 Unicode 字符或二进制数据,需要进行`UTF-8`、`GBK`或`BASE64`等方式的编码。 + +Session 可以存取任何类型的数据,甚至是任何 Java 类。可以将 Session 看成是一个 Java 容器类。 + +### 隐私安全 + +Cookie 存于客户端浏览器,一些客户端的程序可能会窥探、复制或修改 Cookie 内容。 + +Session 存于服务器,对客户端是透明的,不存在敏感信息泄露的危险。 + +### 有效期 + +使用 Cookie 可以保证长时间登录有效,只要设置 Cookie 的`maxAge`属性为一个很大的数字。 + +而 Session 虽然理论上也可以通过设置很大的数值来保持长时间登录有效,但是,由于 Session 依赖于名为`JESSIONID`的 Cookie,而 Cookie `JESSIONID`的`maxAge`默认为-1,只要关闭了浏览器该 Session 就会失效,因此,Session 不能实现信息永久有效的效果。使用 URL 地址重写也不能实现。 + +### 服务器的开销 + +由于 Session 是保存在服务器的,每个用户都会产生一个 Session,如果并发访问的用户非常多,会产生很多的 Session,消耗大量的内存。 + +而 Cookie 由于保存在客户端浏览器上,所以不占用服务器资源。 + +### 浏览器的支持 + +Cookie 需要浏览器支持才能使用。 + +如果浏览器不支持 Cookie,需要使用 Session 以及 URL 地址重写。 + +需要注意的事所有的用到 Session 程序的 URL 都要使用`response.encodeURL(StringURL)` 或`response.encodeRediretURL(String URL)`进行 URL 地址重写,否则导致 Session 会话跟踪失效。 + +### 跨域名 + +Cookie 支持跨域名。 + +Session 不支持跨域名。 diff --git a/docs/javaee/jsp.md b/docs/javaee/jsp.md new file mode 100644 index 00000000..38556b96 --- /dev/null +++ b/docs/javaee/jsp.md @@ -0,0 +1,1829 @@ +# JSP 指南 + + + +- [简介](#简介) + - [什么是 Java Server Pages?](#什么是-java-server-pages) + - [为什么使用 JSP?](#为什么使用-jsp) + - [JSP 的优势](#jsp-的优势) +- [JSP 工作原理](#jsp-工作原理) + - [JSP 工作流程](#jsp-工作流程) + - [JSP 生命周期](#jsp-生命周期) +- [语法](#语法) + - [脚本](#脚本) + - [JSP 声明](#jsp-声明) + - [JSP 表达式](#jsp-表达式) + - [JSP 注释](#jsp-注释) + - [控制语句](#控制语句) + - [JSP 字面量](#jsp-字面量) +- [指令](#指令) + - [Page 指令](#page-指令) + - [Include 指令](#include-指令) + - [Taglib 指令](#taglib-指令) +- [JSP 动作元素](#jsp-动作元素) + - [常见的属性](#常见的属性) + - [``](#jspinclude) + - [``](#jspusebean) + - [``](#jspsetproperty) + - [``](#jspgetproperty) + - [``](#jspforward) + - [``](#jspplugin) + - [`` 、 ``、``](#jspelement--jspattributejspbody) + - [``](#jsptext) +- [JSP 隐式对象](#jsp-隐式对象) + - [request 对象](#request-对象) + - [response 对象](#response-对象) + - [out 对象](#out-对象) + - [session 对象](#session-对象) + - [application 对象](#application-对象) + - [config 对象](#config-对象) + - [pageContext 对象](#pagecontext-对象) + - [page 对象](#page-对象) + - [exception 对象](#exception-对象) +- [EL 表达式](#el-表达式) + - [一个简单的语法](#一个简单的语法) + - [EL 中的基础操作符](#el-中的基础操作符) + - [JSP EL 中的函数](#jsp-el-中的函数) + - [JSP EL 隐含对象](#jsp-el-隐含对象) + - [pageContext 对象](#pagecontext-对象-1) + - [Scope 对象](#scope-对象) + - [param 和 paramValues 对象](#param-和-paramvalues-对象) + - [header 和 headerValues 对象](#header-和-headervalues-对象) +- [JSTL](#jstl) + - [JSTL 库安装](#jstl-库安装) + - [核心标签](#核心标签) + - [格式化标签](#格式化标签) + - [SQL 标签](#sql-标签) + - [XML 标签](#xml-标签) + - [JSTL 函数](#jstl-函数) +- [Taglib](#taglib) + - [JSP 自定义标签](#jsp-自定义标签) + - [创建"Hello"标签](#创建hello标签) + - [访问标签体](#访问标签体) + - [自定义标签属性](#自定义标签属性) + + + +## 简介 + +### 什么是 Java Server Pages? + +`JSP`全称`Java Server Pages`,是一种动态网页开发技术。 + +它使用 JSP 标签在 HTML 网页中插入 Java 代码。标签通常以`<%`开头以`%>`结束。 + +JSP 是一种 Java servlet,主要用于实现 Java web 应用程序的用户界面部分。网页开发者们通过结合 HTML 代码、XHTML 代码、XML 元素以及嵌入 JSP 操作和命令来编写 JSP。 + +JSP 通过网页表单获取用户输入数据、访问数据库及其他数据源,然后动态地创建网页。 + +JSP 标签有多种功能,比如访问数据库、记录用户选择信息、访问 JavaBeans 组件等,还可以在不同的网页中传递控制信息和共享信息。 + +### 为什么使用 JSP? + +JSP 也是一种 Servlet,因此 JSP 能够完成 Servlet 能完成的任何工作。 + +JSP 程序与 CGI 程序有着相似的功能,但和 CGI 程序相比,JSP 程序有如下优势: + +- 性能更加优越,因为 JSP 可以直接在 HTML 网页中动态嵌入元素而不需要单独引用 CGI 文件。 +- 服务器调用的是已经编译好的 JSP 文件,而不像 CGI/Perl 那样必须先载入解释器和目标脚本。 +- JSP 基于 Java Servlets API,因此,JSP 拥有各种强大的企业级 Java API,包括 JDBC,JNDI,EJB,JAXP 等等。 +- JSP 页面可以与处理业务逻辑的 servlets 一起使用,这种模式被 Java servlet 模板引擎所支持。 + +最后,JSP 是 Java EE 不可或缺的一部分,是一个完整的企业级应用平台。这意味着 JSP 可以用最简单的方式来实现最复杂的应用。 + +### JSP 的优势 + +以下列出了使用 JSP 带来的其他好处: + +- 与 ASP 相比:JSP 有两大优势。首先,动态部分用 Java 编写,而不是 VB 或其他 MS 专用语言,所以更加强大与易用。第二点就是 JSP 易于移植到非 MS 平台上。 +- 与纯 Servlets 相比:JSP 可以很方便的编写或者修改 HTML 网页而不用去面对大量的 println 语句。 +- 与 SSI 相比:SSI 无法使用表单数据、无法进行数据库链接。 +- 与 JavaScript 相比:虽然 JavaScript 可以在客户端动态生成 HTML,但是很难与服务器交互,因此不能提供复杂的服务,比如访问数据库和图像处理等等。 +- 与静态 HTML 相比:静态 HTML 不包含动态信息。 + +## JSP 工作原理 + +**JSP 是一种 Servlet**,但工作方式和 Servlet 有所差别。 + +Servlet 是先将源代码编译为 class 文件后部署到服务器下的,**先编译后部署**。 + +Jsp 是先将源代码部署到服务器再编译,**先部署后编译**。 + +Jsp 会在客户端第一次请求 Jsp 文件时被编译为 HttpJspPage 类(Servlet 的一个子类)。该类会被服务器临时存放在服务器工作目录里。所以,第一次请求 Jsp 后,访问速度会变快就是这个道理。 + +### JSP 工作流程 + +网络服务器需要一个 JSP 引擎,也就是一个容器来处理 JSP 页面。容器负责截获对 JSP 页面的请求。本教程使用内嵌 JSP 容器的 Apache 来支持 JSP 开发。 + +JSP 容器与 Web 服务器协同合作,为 JSP 的正常运行提供必要的运行环境和其他服务,并且能够正确识别专属于 JSP 网页的特殊元素。 + +下图显示了 JSP 容器和 JSP 文件在 Web 应用中所处的位置。 + +
+ +#### 工作步骤 + +以下步骤表明了 Web 服务器是如何使用 JSP 来创建网页的: + +- 就像其他普通的网页一样,您的浏览器发送一个 HTTP 请求给服务器。 +- Web 服务器识别出这是一个对 JSP 网页的请求,并且将该请求传递给 JSP 引擎。通过使用 URL 或者.jsp 文件来完成。 +- JSP 引擎从磁盘中载入 JSP 文件,然后将它们转化为 servlet。这种转化只是简单地将所有模板文本改用 println()语句,并且将所有的 JSP 元素转化成 Java 代码。 +- JSP 引擎将 servlet 编译成可执行类,并且将原始请求传递给 servlet 引擎。 +- Web 服务器的某组件将会调用 servlet 引擎,然后载入并执行 servlet 类。在执行过程中,servlet 产生 HTML 格式的输出并将其内嵌于 HTTP response 中上交给 Web 服务器。 +- Web 服务器以静态 HTML 网页的形式将 HTTP response 返回到您的浏览器中。 +- 最终,Web 浏览器处理 HTTP response 中动态产生的 HTML 网页,就好像在处理静态网页一样。 + +以上提及到的步骤可以用下图来表示: + +一般情况下,JSP 引擎会检查 JSP 文件对应的 servlet 是否已经存在,并且检查 JSP 文件的修改日期是否早于 servlet。如果 JSP 文件的修改日期早于对应的 servlet,那么容器就可以确定 JSP 文件没有被修改过并且 servlet 有效。这使得整个流程与其他脚本语言(比如 PHP)相比要高效快捷一些。 + +### JSP 生命周期 + +理解 JSP 底层功能的关键就是去理解它们所遵守的生命周期。 + +JSP 生命周期就是从创建到销毁的整个过程,类似于 servlet 生命周期,区别在于 JSP 生命周期还包括将 JSP 文件编译成 servlet。 + +以下是 JSP 生命周期中所走过的几个阶段: + +- **编译阶段:**servlet 容器编译 servlet 源文件,生成 servlet 类 +- **初始化阶段:**加载与 JSP 对应的 servlet 类,创建其实例,并调用它的初始化方法 +- **执行阶段:**调用与 JSP 对应的 servlet 实例的服务方法 +- **销毁阶段:**调用与 JSP 对应的 servlet 实例的销毁方法,然后销毁 servlet 实例 + +很明显,JSP 生命周期的四个主要阶段和 servlet 生命周期非常相似,下面给出图示: + +
+ +#### JSP 编译 + +当浏览器请求 JSP 页面时,JSP 引擎会首先去检查是否需要编译这个文件。如果这个文件没有被编译过,或者在上次编译后被更改过,则编译这个 JSP 文件。 + +编译的过程包括三个步骤: + +- 解析 JSP 文件。 +- 将 JSP 文件转为 servlet。 +- 编译 servlet。 + +#### JSP 初始化 + +容器载入 JSP 文件后,它会在为请求提供任何服务前调用 jspInit()方法。如果您需要执行自定义的 JSP 初始化任务,复写 jspInit()方法就行了,就像下面这样: + +``` +public void jspInit(){ + // 初始化代码 +} +``` + +一般来讲程序只初始化一次,servlet 也是如此。通常情况下您可以在 jspInit()方法中初始化数据库连接、打开文件和创建查询表。 + +#### JSP 执行 + +这一阶段描述了 JSP 生命周期中一切与请求相关的交互行为,直到被销毁。 + +当 JSP 网页完成初始化后,JSP 引擎将会调用\_jspService()方法。 + +\_jspService()方法需要一个 HttpServletRequest 对象和一个 HttpServletResponse 对象作为它的参数,就像下面这样: + +``` +void _jspService(HttpServletRequest request, + HttpServletResponse response) +{ + // 服务端处理代码 +} +``` + +`_jspService()` 方法在每个 request 中被调用一次并且负责产生与之相对应的 response,并且它还负责产生所有 7 个 HTTP 方法的回应,比如 GET、POST、DELETE 等等。 + +#### JSP 清理 + +JSP 生命周期的销毁阶段描述了当一个 JSP 网页从容器中被移除时所发生的一切。 + +jspDestroy()方法在 JSP 中等价于 servlet 中的销毁方法。当您需要执行任何清理工作时复写 jspDestroy()方法,比如释放数据库连接或者关闭文件夹等等。 + +jspDestroy()方法的格式如下: + +``` +public void jspDestroy() +{ + // 清理代码 +} +``` + +## 语法 + +### 脚本 + +脚本程序可以包含任意量的 Java 语句、变量、方法或表达式,只要它们在脚本语言中是有效的。 + +脚本程序的语法格式: + +```jsp +<% 代码片段 %> +``` + +或者,您也可以编写与其等价的 XML 语句,就像下面这样: + +```jsp + + 代码片段 + +``` + +任何文本、HTML 标签、JSP 元素必须写在脚本程序的外面。 + +下面给出一个示例,同时也是本教程的第一个 JSP 示例: + +```jsp + +Hello World + +Hello World!
+<% +out.println("Your IP address is " + request.getRemoteAddr()); +%> + + +``` + +**注意:**请确保 Apache Tomcat 已经安装在 C:\apache-tomcat-7.0.2 目录下并且运行环境已经正确设置。 + +将以上代码保存在 hello.jsp 中,然后将它放置在 C:\apache-tomcat-7.0.2\webapps\ROOT 目录下,打开浏览器并在地址栏中输入http://localhost:8080/hello.jsp。运行后得到以下结果: + +
+ +#### 中文编码问题 + +如果我们要在页面正常显示中文,我们需要在 JSP 文件头部添加以下代码:<> + +```jsp +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> +``` + +接下来我们将以上程序修改为: + +```jsp +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> + + + + +菜鸟教程(runoob.com) + + +Hello World!
+<% +out.println("你的 IP 地址 " + request.getRemoteAddr()); +%> + + +``` + +这样中文就可以正常显示了。 + +### JSP 声明 + +一个声明语句可以声明一个或多个变量、方法,供后面的 Java 代码使用。在 JSP 文件中,您必须先声明这些变量和方法然后才能使用它们。 + +JSP 声明的语法格式: + +```jsp +<%! declaration; [ declaration; ]+ ... %> +``` + +或者,您也可以编写与其等价的 XML 语句,就像下面这样: + +```jsp + + 代码片段 + +``` + +程序示例: + +```jsp +<%! int i = 0; %> +<%! int a, b, c; %> +<%! Circle a = new Circle(2.0); %> +``` + +### JSP 表达式 + +一个 JSP 表达式中包含的脚本语言表达式,先被转化成 String,然后插入到表达式出现的地方。 + +由于表达式的值会被转化成 String,所以您可以在一个文本行中使用表达式而不用去管它是否是 HTML 标签。 + +表达式元素中可以包含任何符合 Java 语言规范的表达式,但是不能使用分号来结束表达式。 + +JSP 表达式的语法格式: + +``` +<%= 表达式 %> +``` + +同样,您也可以编写与之等价的 XML 语句: + +``` + + 表达式 + +``` + +程序示例: + +``` +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> + + + + +菜鸟教程(runoob.com) + + +

+ 今天的日期是: <%= (new java.util.Date()).toLocaleString()%> +

+ + +``` + +运行后得到以下结果: + +``` +今天的日期是: 2016-6-25 13:40:07 +``` + +--- + +### JSP 注释 + +JSP 注释主要有两个作用:为代码作注释以及将某段代码注释掉。 + +JSP 注释的语法格式: + +```jsp +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> + + + + +JSP注释示例 + + +<%-- 该部分注释在网页中不会被显示--%> +

+ 今天的日期是: <%= (new java.util.Date()).toLocaleString()%> +

+ + +``` + +运行后得到以下结果: + +``` +今天的日期是: 2016-6-25 13:41:26 +``` + +不同情况下使用注释的语法规则: + +| **语法** | 描述 | +| -------------- | ----------------------------------------------------- | +| <%-- 注释 --%> | JSP 注释,注释内容不会被发送至浏览器甚至不会被编译 | +| | HTML 注释,通过浏览器查看网页源代码时可以看见注释内容 | +| <\% | 代表静态 <%常量 | +| %\> | 代表静态 %> 常量 | +| \' | 在属性中使用的单引号 | +| \" | 在属性中使用的双引号 | + +### 控制语句 + +JSP 提供对 Java 语言的全面支持。您可以在 JSP 程序中使用 Java API 甚至建立 Java 代码块,包括判断语句和循环语句等等。 + +#### if…else 语句 + +`If…else`块,请看下面这个例子: + +```jsp +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> +<%! int day = 1; %> + + + + + 02.JSP语法 - if...else示例 + + +

IF...ELSE 实例

+<% if (day == 1 | day == 7) { %> +

今天是周末

+<% } else { %> +

今天不是周末

+<% } %> + + +``` + +运行后得到以下结果: + +``` +IF...ELSE 实例 +今天不是周末 +``` + +#### switch…case 语句 + +现在来看看 switch…case 块,与 if…else 块有很大的不同,它使用 out.println(),并且整个都装在脚本程序的标签中,就像下面这样: + +```jsp +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> +<%! int day = 3; %> + + + + + 02.JSP语法 - switch...case示例 + + +

Sswitch...case示例

+<% + switch(day) { + case 0: + out.println("星期天"); + break; + case 1: + out.println("星期一"); + break; + case 2: + out.println("星期二"); + break; + case 3: + out.println("星期三"); + break; + case 4: + out.println("星期四"); + break; + case 5: + out.println("星期五"); + break; + default: + out.println("星期六"); + } +%> + + +``` + +浏览器访问,运行后得出以下结果: + +``` +SWITCH...CASE 实例 + +星期三 +``` + +#### 循环语句 + +在 JSP 程序中可以使用 Java 的三个基本循环类型:for,while,和 do…while。 + +让我们来看看 for 循环的例子,以下输出的不同字体大小的"菜鸟教程": + +``` +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> +<%! int fontSize; %> + + + + +菜鸟教程(runoob.com) + + +

For 循环实例

+<%for ( fontSize = 1; fontSize <= 3; fontSize++){ %> + + 菜鸟教程 +
+<%}%> + + +``` + +运行后得到以下结果: + +
+ +将上例改用 while 循环来写: + +``` +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> +<%! int fontSize; %> + + + + +菜鸟教程(runoob.com) + + +

While 循环实例

+<%while ( fontSize <= 3){ %> + + 菜鸟教程 +
+<%fontSize++;%> +<%}%> + + +``` + +浏览器访问,输出结果为(fontSize 初始化为 0,所以多输出了一行): + +
+ +JSP 运算符 + +JSP 支持所有 Java 逻辑和算术运算符。 + +下表罗列出了 JSP 常见运算符,优先级从高到底: + +| **类别** | **操作符** | **结合性** | +| --------- | ----------------------------------- | ---------- | +| 后缀 | () [] . (点运算符) | 左到右 | +| 一元 | ++ - - ! \~ | 右到左 | +| 可乘性 | \* / % | 左到右 | +| 可加性 | + - | 左到右 | +| 移位 | >> >>> << | 左到右 | +| 关系 | > >= < <= | 左到右 | +| 相等/不等 | == != | 左到右 | +| 位与 | & | 左到右 | +| 位异或 | ^ | 左到右 | +| 位或 | \| | 左到右 | +| 逻辑与 | && | 左到右 | +| 逻辑或 | \|\| | 左到右 | +| 条件判断 | ?: | 右到左 | +| 赋值 | = += -= \*= /= %= >>= <<= &= ^= \|= | 右到左 | +| 逗号 | , | 左到右 | + +### JSP 字面量 + +JSP 语言定义了以下几个字面量: + +- 布尔值(boolean):true 和 false; +- 整型(int):与 Java 中的一样; +- 浮点型(float):与 Java 中的一样; +- 字符串(string):以单引号或双引号开始和结束; +- Null:null。 + +## 指令 + +JSP 指令用来设置整个 JSP 页面相关的属性,如网页的编码方式和脚本语言。 + +JSP 指令以开`<%@`开始,以`%>`结束。 + +JSP 指令语法格式如下: + +```jsp +<%@ directive attribute="value" %> +``` + +指令可以有很多个属性,它们以键值对的形式存在,并用逗号隔开。 + +JSP 中的三种指令标签: + +| **指令** | **描述** | +| -------------------- | -------------------------------------------------------- | +| `<%@ page ... %>` | 定义网页依赖属性,比如脚本语言、error 页面、缓存需求等等 | +| `<%@ include ... %>` | 包含其他文件 | +| `<%@ taglib ... %>` | 引入标签库的定义,可以是自定义标签 | + +### Page 指令 + +Page 指令为容器提供当前页面的使用说明。一个 JSP 页面可以包含多个`page`指令。 + +Page 指令的语法格式: + +```jsp +<%@ page attribute="value" %> +``` + +等价的 XML 格式: + +```jsp + +``` + +例: + +```jsp +<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> +``` + +#### 属性 + +下表列出与 Page 指令相关的属性: + +| **属性** | **描述** | +| ------------------ | ----------------------------------------------------- | +| buffer | 指定 out 对象使用缓冲区的大小 | +| autoFlush | 控制 out 对象的 缓存区 | +| contentType | 指定当前 JSP 页面的 MIME 类型和字符编码 | +| errorPage | 指定当 JSP 页面发生异常时需要转向的错误处理页面 | +| isErrorPage | 指定当前页面是否可以作为另一个 JSP 页面的错误处理页面 | +| extends | 指定 servlet 从哪一个类继承 | +| import | 导入要使用的 Java 类 | +| info | 定义 JSP 页面的描述信息 | +| isThreadSafe | 指定对 JSP 页面的访问是否为线程安全 | +| language | 定义 JSP 页面所用的脚本语言,默认是 Java | +| session | 指定 JSP 页面是否使用 session | +| isELIgnored | 指定是否执行 EL 表达式 | +| isScriptingEnabled | 确定脚本元素能否被使用 | + +### Include 指令 + +JSP 可以通过`include`指令来包含其他文件。 + +被包含的文件可以是 JSP 文件、HTML 文件或文本文件。包含的文件就好像是该 JSP 文件的一部分,会被同时编译执行。 + +Include 指令的语法格式如下: + +```jsp +<%@ include file="文件相对 url 地址" %> +``` + +**include** 指令中的文件名实际上是一个相对的 URL 地址。 + +如果您没有给文件关联一个路径,JSP 编译器默认在当前路径下寻找。 + +等价的 XML 语法: + +```jsp + +``` + +### Taglib 指令 + +JSP 允许用户自定义标签,一个自定义标签库就是自定义标签的集合。 + +`taglib`指令引入一个自定义标签集合的定义,包括库路径、自定义标签。 + +`taglib`指令的语法: + +```jsp +<%@ taglib uri="uri" prefix="prefixOfTag" %> +``` + +uri 属性确定标签库的位置,prefix 属性指定标签库的前缀。 + +等价的 XML 语法: + +```jsp + +``` + +## JSP 动作元素 + +JSP 动作元素是一组 JSP 内置的标签,只需要书写很少的标记代码就能使用 JSP 提供的丰富功能。JSP 动作元素是对常用的 JSP 功能的抽象与封装,包括两种,自定义 JSP 动作元素与标准 JSP 动作元素。 + +与 JSP 指令元素不同的是,JSP 动作元素在请求处理阶段起作用。JSP 动作元素是用 XML 语法写成的。 + +利用 JSP 动作可以动态地插入文件、重用 JavaBean 组件、把用户重定向到另外的页面、为 Java 插件生成 HTML 代码。 + +动作元素只有一种语法,它符合 XML 标准: + +```jsp + +``` + +动作元素基本上都是预定义的函数,JSP 规范定义了一系列的标准动作,它用 JSP 作为前缀,可用的标准动作元素如下: + +| 语法 | 描述 | +| --------------- | ----------------------------------------------------- | +| jsp:include | 在页面被请求的时候引入一个文件。 | +| jsp:useBean | 寻找或者实例化一个 JavaBean。 | +| jsp:setProperty | 设置 JavaBean 的属性。 | +| jsp:getProperty | 输出某个 JavaBean 的属性。 | +| jsp:forward | 把请求转到一个新的页面。 | +| jsp:plugin | 根据浏览器类型为 Java 插件生成 OBJECT 或 EMBED 标记。 | +| jsp:element | 定义动态 XML 元素 | +| jsp:attribute | 设置动态定义的 XML 元素属性。 | +| jsp:body | 设置动态定义的 XML 元素内容。 | +| jsp:text | 在 JSP 页面和文档中使用写入文本的模板 | + +### 常见的属性 + +所有的动作要素都有两个属性:id 属性和 scope 属性。 + +- **id 属性:**id 属性是动作元素的唯一标识,可以在 JSP 页面中引用。动作元素创建的 id 值可以通过 PageContext 来调用。 +- **scope 属性:**该属性用于识别动作元素的生命周期。 id 属性和 scope 属性有直接关系,scope 属性定义了相关联 id 对象的寿命。 scope 属性有四个可能的值: (a) page, (b)request, (c)session, 和 (d) application。 + +### `` + +`` 用来包含静态和动态的文件。该动作把指定文件插入正在生成的页面。 + +如果被包含的文件为 JSP 程序,则会先执行 JSP 程序,再将执行结果包含进来。 + +语法格式如下: + +``` + +``` + +前面已经介绍过 include 指令,它是在 JSP 文件被转换成 Servlet 的时候引入文件,而这里的 jsp:include 动作不同,插入文件的时间是在页面被请求的时候。 + +以下是 include 动作相关的属性列表。 + +| 属性 | 描述 | +| ----- | ------------------------------------------ | +| page | 包含在页面中的相对 URL 地址。 | +| flush | 布尔属性,定义在包含资源前是否刷新缓存区。 | + +**例** + +以下我们定义了两个文件 **date.jsp** 和 **main.jsp**,代码如下所示: + +date.jsp 文件代码: + +```c++ +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> +

+ 今天的日期是: <%= (new java.util.Date())%> +

+``` + +main.jsp 文件代码: + +```jsp +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> + + + + +菜鸟教程(runoob.com) + + + +

include 动作实例

+ + + + +``` + +现在将以上两个文件放在服务器的根目录下,访问 main.jsp 文件。显示结果如下: + +``` +include 动作实例 + +今天的日期是: 2016-6-25 14:08:17 +``` + +### `` + +**jsp:useBean** 动作用来加载一个将在 JSP 页面中使用的 JavaBean。 + +这个功能非常有用,因为它使得我们可以发挥 Java 组件复用的优势。 + +jsp:useBean 动作最简单的语法为: + +``` + +``` + +在类载入后,我们既可以通过 jsp:setProperty 和 jsp:getProperty 动作来修改和检索 bean 的属性。 + +以下是 useBean 动作相关的属性列表。 + +| 属性 | 描述 | +| -------- | ------------------------------------------------------------- | +| class | 指定 Bean 的完整包名。 | +| type | 指定将引用该对象变量的类型。 | +| beanName | 通过 java.beans.Beans 的 instantiate() 方法指定 Bean 的名字。 | + +在给出具体实例前,让我们先来看下 jsp:setProperty 和 jsp:getProperty 动作元素: + +### `` + +jsp:setProperty 用来设置已经实例化的 Bean 对象的属性,有两种用法。首先,你可以在 jsp:useBean 元素的外面(后面)使用 jsp:setProperty,如下所示: + +``` + +... + +``` + +此时,不管 jsp:useBean 是找到了一个现有的 Bean,还是新创建了一个 Bean 实例,jsp:setProperty 都会执行。第二种用法是把 jsp:setProperty 放入 jsp:useBean 元素的内部,如下所示: + +``` + +... + + +``` + +此时,jsp:setProperty 只有在新建 Bean 实例时才会执行,如果是使用现有实例则不执行 jsp:setProperty。 + +jsp:setProperty 动作有下面四个属性,如下表: + +| 属性 | 描述 | +| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| name | name 属性是必需的。它表示要设置属性的是哪个 Bean。 | +| property | property 属性是必需的。它表示要设置哪个属性。有一个特殊用法:如果 property 的值是"\*",表示所有名字和 Bean 属性名字匹配的请求参数都将被传递给相应的属性 set 方法。 | +| value | value 属性是可选的。该属性用来指定 Bean 属性的值。字符串数据会在目标类中通过标准的 valueOf 方法自动转换成数字、boolean、Boolean、 byte、Byte、char、Character。例如,boolean 和 Boolean 类型的属性值(比如"true")通过 Boolean.valueOf 转换,int 和 Integer 类型的属性值(比如"42")通过 Integer.valueOf 转换。    value 和 param 不能同时使用,但可以使用其中任意一个。 | +| param | param 是可选的。它指定用哪个请求参数作为 Bean 属性的值。如果当前请求没有参数,则什么事情也不做,系统不会把 null 传递给 Bean 属性的 set 方法。因此,你可以让 Bean 自己提供默认属性值,只有当请求参数明确指定了新值时才修改默认属性值。 | + +### `` + +jsp:getProperty 动作提取指定 Bean 属性的值,转换成字符串,然后输出。语法格式如下: + +``` + +... + +``` + +下表是与 getProperty 相关联的属性: + +| 属性 | 描述 | +| -------- | ----------------------------------------- | +| name | 要检索的 Bean 属性名称。Bean 必须已定义。 | +| property | 表示要提取 Bean 属性的值 | + +实例 + +以下实例我们使用了 Bean: + +``` +package com.runoob.main; + +public class TestBean { + private String message = "菜鸟教程"; + + public String getMessage() { + return(message); + } + public void setMessage(String message) { + this.message = message; + } +} +``` + +编译以上实例文件 TestBean.java : + +``` +$ javac TestBean.java +``` + +编译完成后会在当前目录下生成一个 **TestBean.class** 文件, 将该文件拷贝至当前 JSP 项目的 **WebContent/WEB-INF/classes/com/runoob/main** 下( com/runoob/main 包路径,没有需要手动创建)。 + +下面是一个 Eclipse 中目录结构图: + +
+ +下面是一个很简单的例子,它的功能是装载一个 Bean,然后设置/读取它的 message 属性。 + +现在让我们在 main.jsp 文件中调用该 Bean: + +``` +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> + + + + +菜鸟教程(runoob.com) + + + +

Jsp 使用 JavaBean 实例

+ + + + +

输出信息....

+ + + + + +``` + +浏览器访问,执行以上文件,输出如下所示: + +
+ +### `` + +jsp:forward 动作把请求转到另外的页面。jsp:forward 标记只有一个属性 page。语法格式如下所示: + +``` + +``` + +以下是 forward 相关联的属性: + +| 属性 | 描述 | +| ---- | ----------------------------------------------------------------------------------------------------------------------------- | +| page | page 属性包含的是一个相对 URL。page 的值既可以直接给出,也可以在请求的时候动态计算,可以是一个 JSP 页面或者一个 Java Servlet. | + +实例 + +以下实例我们使用了两个文件,分别是: date.jsp 和 main.jsp。 + +date.jsp 文件代码如下: + +``` +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> +

+ 今天的日期是: <%= (new java.util.Date()).toLocaleString()%> +

+``` + +main.jsp 文件代码: + +``` +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> + + + + +菜鸟教程(runoob.com) + + + +

forward 动作实例

+ + + +``` + +现在将以上两个文件放在服务器的根目录下,访问 main.jsp 文件。显示结果如下: + +``` +今天的日期是: 2016-6-25 14:37:25 +``` + +### `` + +jsp:plugin 动作用来根据浏览器的类型,插入通过 Java 插件 运行 Java Applet 所必需的 OBJECT 或 EMBED 元素。 + +如果需要的插件不存在,它会下载插件,然后执行 Java 组件。 Java 组件可以是一个 applet 或一个 JavaBean。 + +plugin 动作有多个对应 HTML 元素的属性用于格式化 Java 组件。param 元素可用于向 Applet 或 Bean 传递参数。 + +以下是使用 plugin 动作元素的典型实例: + +``` + + + + + + Unable to initialize Java Plugin + + + +``` + +如果你有兴趣可以尝试使用 applet 来测试 jsp:plugin 动作元素,元素是一个新元素,在组件出现故障的错误是发送给用户错误信息。 + +### `` 、 ``、`` + +动作元素动态定义 XML 元素。动态是非常重要的,这就意味着 XML 元素在编译时是动态生成的而非静态。 + +以下实例动态定义了 XML 元素: + +``` +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> + + + + +菜鸟教程(runoob.com) + + + + + 属性值 + + + XML 元素的主体 + + + + +``` + +浏览器访问以下页面,输出结果如下所示: + +
+ +### `` + +动作元素允许在 JSP 页面和文档中使用写入文本的模板,语法格式如下: + +``` +模板数据 +``` + +以上文本模板不能包含其他元素,只能只能包含文本和 EL 表达式(注:EL 表达式将在后续章节中介绍)。请注意,在 XML 文件中,您不能使用表达式如 ${whatever > 0},因为>符号是非法的。 你可以使用 ${whatever gt 0}表达式或者嵌入在一个 CDATA 部分的值。 + +``` +]]> +``` + +如果你需要在 XHTML 中声明 DOCTYPE,必须使用到动作元素,实例如下: + +``` +]]> + +jsp:text action + + + + Welcome to JSP Programming + + + + +``` + +你可以对以上实例尝试使用及不使用该动作元素执行结果的区别。 + +## JSP 隐式对象 + +JSP 隐式对象是 JSP 容器为每个页面提供的 Java 对象,开发者可以直接使用它们而不用显式声明。JSP 隐式对象也被称为预定义变量。 + +JSP 所支持的九大隐式对象: + +| **对象** | **描述** | +| ----------- | ------------------------------------------------------------------ | +| request | **HttpServletRequest**类的实例 | +| response | **HttpServletResponse**类的实例 | +| out | **PrintWriter**类的实例,用于把结果输出至网页上 | +| session | **HttpSession**类的实例 | +| application | **ServletContext**类的实例,与应用上下文有关 | +| config | **ServletConfig**类的实例 | +| pageContext | **PageContext**类的实例,提供对 JSP 页面所有对象以及命名空间的访问 | +| page | 类似于 Java 类中的 this 关键字 | +| Exception | **Exception**类的对象,代表发生错误的 JSP 页面中对应的异常对象 | + +### request 对象 + +`request`对象是`javax.servlet.http.HttpServletRequest` 类的实例。 + +每当客户端请求一个 JSP 页面时,JSP 引擎就会制造一个新的`request`对象来代表这个请求。 + +`request`对象提供了一系列方法来获取 HTTP 头信息,cookies,HTTP 方法等等。 + +### response 对象 + +`response`对象是`javax.servlet.http.HttpServletResponse`类的实例。 + +当服务器创建`request`对象时会同时创建用于响应这个客户端的`response`对象。 + +`response`对象也定义了处理 HTTP 头模块的接口。通过这个对象,开发者们可以添加新的 cookies,时间戳,HTTP 状态码等等。 + +### out 对象 + +`out`对象是`javax.servlet.jsp.JspWriter`类的实例,用来在`response`对象中写入内容。 + +最初的`JspWriter`类对象根据页面是否有缓存来进行不同的实例化操作。可以在`page`指令中使用`buffered='false'`属性来轻松关闭缓存。 + +`JspWriter`类包含了大部分`java.io.PrintWriter`类中的方法。不过,`JspWriter`新增了一些专为处理缓存而设计的方法。还有就是,`JspWriter`类会抛出`IOExceptions`异常,而`PrintWriter`不会。 + +下表列出了我们将会用来输出`boolean`,`char`,`int`,`double`,`String`,`object`等类型数据的重要方法: + +| **方法** | **描述** | +| ---------------------------- | -------------------------- | +| **out.print(dataType dt)** | 输出 Type 类型的值 | +| **out.println(dataType dt)** | 输出 Type 类型的值然后换行 | +| **out.flush()** | 刷新输出流 | + +### session 对象 + +`session`对象是`javax.servlet.http.HttpSession`类的实例。和 Java Servlets 中的`session`对象有一样的行为。 + +`session`对象用来跟踪在各个客户端请求间的会话。 + +### application 对象 + +`application`对象直接包装了 servlet 的`ServletContext`类的对象,是`javax.servlet.ServletContext`类的实例。 + +这个对象在 JSP 页面的整个生命周期中都代表着这个 JSP 页面。这个对象在 JSP 页面初始化时被创建,随着`jspDestroy()`方法的调用而被移除。 + +通过向`application`中添加属性,则所有组成您 web 应用的 JSP 文件都能访问到这些属性。 + +### config 对象 + +`config`对象是`javax.servlet.ServletConfig`类的实例,直接包装了 servlet 的`ServletConfig`类的对象。 + +这个对象允许开发者访问 Servlet 或者 JSP 引擎的初始化参数,比如文件路径等。 + +以下是 config 对象的使用方法,不是很重要,所以不常用: + +```jsp +config.getServletName(); +``` + +它返回包含在``元素中的 servlet 名字,注意,``元素在`WEB-INF\web.xml`文件中定义。 + +### pageContext 对象 + +`pageContext`对象是`javax.servlet.jsp.PageContext`类的实例,用来代表整个 JSP 页面。 + +这个对象主要用来访问页面信息,同时过滤掉大部分实现细节。 + +这个对象存储了`request`对象和`response`对象的引用。`application`对象,`config`对象,`session`对象,`out`对象可以通过访问这个对象的属性来导出。 + +`pageContext`对象也包含了传给 JSP 页面的指令信息,包括缓存信息,ErrorPage URL,页面 scope 等。 + +`PageContext`类定义了一些字段,包括 PAGE_SCOPE,REQUEST_SCOPE,SESSION_SCOPE, APPLICATION_SCOPE。它也提供了 40 余种方法,有一半继承自`javax.servlet.jsp.JspContext` 类。 + +其中一个重要的方法就是`removeArribute()`,它可接受一个或两个参数。比如,pageContext.removeArribute("attrName")移除四个 scope 中相关属性,但是下面这种方法只移除特定 scope 中的相关属性: + +```jsp +pageContext.removeAttribute("attrName", PAGE_SCOPE); +``` + +### page 对象 + +这个对象就是页面实例的引用。它可以被看做是整个 JSP 页面的代表。 + +`page`对象就是`this`对象的同义词。 + +### exception 对象 + +`exception`对象包装了从先前页面中抛出的异常信息。它通常被用来产生对出错条件的适当响应。 + +## EL 表达式 + +EL 表达式是用`${}`括起来的脚本,用来更方便地读取对象。EL 表达式写在 JSP 的 HTML 代码中,而不能写在`<%`与`%>`引起的 JSP 脚本中。 + +JSP 表达式语言(EL)使得访问存储在 JavaBean 中的数据变得非常简单。JSP EL 既可以用来创建算术表达式也可以用来创建逻辑表达式。在 JSP EL 表达式内可以使用整型数,浮点数,字符串,常量 true、false,还有 null。 + +### 一个简单的语法 + +典型的,当您需要在 JSP 标签中指定一个属性值时,只需要简单地使用字符串即可: + +```jsp + +``` + +JSP EL 允许您指定一个表达式来表示属性值。一个简单的表达式语法如下: + +```jsp +${expr} +``` + +其中,expr 指的是表达式。在 JSP EL 中通用的操作符是"."和"[]"。这两个操作符允许您通过内嵌的 JSP 对象访问各种各样的 JavaBean 属性。 + +举例来说,上面的标签可以使用表达式语言改写成如下形式: + +```jsp + +``` + +当 JSP 编译器在属性中见到"\${}"格式后,它会产生代码来计算这个表达式,并且产生一个替代品来代替表达式的值。 + +您也可以在标签的模板文本中使用表达式语言。比如标签简单地将其主体中的文本插入到 JSP 输出中: + +```jsp + +

Hello JSP!

+
+``` + +现在,在标签主体中使用表达式,就像这样: + +```jsp + +Box Perimeter is: ${2*box.width + 2*box.height} + +``` + +在 EL 表达式中可以使用圆括号来组织子表达式。比如 `${(1 + 2) _ 3}` 等于 9,但是 `${1 + (2 _ 3)}` 等于 7。 + +想要停用对 EL 表达式的评估的话,需要使用 page 指令将 isELIgnored 属性值设为 true: + +```jsp +<%@ page isELIgnored ="true|false" %> +``` + +这样,EL 表达式就会被忽略。若设为 false,则容器将会计算 EL 表达式。 + +### EL 中的基础操作符 + +EL 表达式支持大部分 Java 所提供的算术和逻辑操作符: + +| **操作符** | **描述** | +| ---------- | ---------------------------------- | +| . | 访问一个 Bean 属性或者一个映射条目 | +| [] | 访问一个数组或者链表的元素 | +| ( ) | 组织一个子表达式以改变优先级 | +| + | 加 | +| - | 减或负 | +| \* | 乘 | +| / or div | 除 | +| % or mod | 取模 | +| == or eq | 测试是否相等 | +| != or ne | 测试是否不等 | +| < or lt | 测试是否小于 | +| > or gt | 测试是否大于 | +| <= or le | 测试是否小于等于 | +| >= or ge | 测试是否大于等于 | +| && or and | 测试逻辑与 | +| \|\| or or | 测试逻辑或 | +| ! or not | 测试取反 | +| empty | 测试是否空值 | + +### JSP EL 中的函数 + +JSP EL 允许您在表达式中使用函数。这些函数必须被定义在自定义标签库中。函数的使用语法如下: + +```jsp +${ns:func(param1, param2, ...)} +``` + +ns 指的是命名空间(namespace),func 指的是函数的名称,param1 指的是第一个参数,param2 指的是第二个参数,以此类推。比如,有函数 fn:length,在 JSTL 库中定义,可以像下面这样来获取一个字符串的长度: + +```jsp +${fn:length("Get my length")} +``` + +要使用任何标签库中的函数,您需要将这些库安装在服务器中,然后使用 `` 标签在 JSP 文件中包含这些库。 + +### JSP EL 隐含对象 + +JSP EL 支持下表列出的隐含对象: + +| **隐含对象** | **描述** | +| ---------------- | ------------------------------ | +| pageScope | page 作用域 | +| requestScope | request 作用域 | +| sessionScope | session 作用域 | +| applicationScope | application 作用域 | +| param | Request 对象的参数,字符串 | +| paramValues | Request 对象的参数,字符串集合 | +| header | HTTP 信息头,字符串 | +| headerValues | HTTP 信息头,字符串集合 | +| initParam | 上下文初始化参数 | +| cookie | Cookie 值 | +| pageContext | 当前页面的 pageContext | + +您可以在表达式中使用这些对象,就像使用变量一样。接下来会给出几个例子来更好的理解这个概念。 + +### pageContext 对象 + +pageContext 对象是 JSP 中 pageContext 对象的引用。通过 pageContext 对象,您可以访问 request 对象。比如,访问 request 对象传入的查询字符串,就像这样: + +```jsp +${pageContext.request.queryString} +``` + +### Scope 对象 + +pageScope,requestScope,sessionScope,applicationScope 变量用来访问存储在各个作用域层次的变量。 + +举例来说,如果您需要显式访问在 applicationScope 层的 box 变量,可以这样来访问:applicationScope.box。 + +### param 和 paramValues 对象 + +param 和 paramValues 对象用来访问参数值,通过使用 request.getParameter 方法和 request.getParameterValues 方法。 + +举例来说,访问一个名为 order 的参数,可以这样使用表达式:`${param.order}`,或者`${param["order"]}`。 + +接下来的例子表明了如何访问 request 中的 username 参数: + +```jsp +<%@ page import="java.io.*,java.util.*" %> +<% + String title = "Accessing Request Param"; +%> + + +<% out.print(title); %> + + +
+

<% out.print(title); %>

+
+
+

${param["username"]}

+
+ + +``` + +param 对象返回单一的字符串,而 paramValues 对象则返回一个字符串数组。 + +### header 和 headerValues 对象 + +header 和 headerValues 对象用来访问信息头,通过使用 request.getHeader 方法和 request.getHeaders 方法。 + +举例来说,要访问一个名为 user-agent 的信息头,可以这样使用表达式:`${header.user-agent}`,或者 `${header["user-agent"]}`。 + +接下来的例子表明了如何访问 user-agent 信息头: + +```jsp +<%@ page import="java.io.*,java.util.*" %> +<% + String title = "User Agent Example"; +%> + + +<% out.print(title); %> + + +
+

<% out.print(title); %>

+
+
+

${header["user-agent"]}

+
+ + +``` + +运行结果如下: + +
+ +header 对象返回单一值,而 headerValues 则返回一个字符串数组。 + +## JSTL + +JSP 标准标签库(JSTL)是一个 JSP 标签集合,它封装了 JSP 应用的通用核心功能。 + +JSTL 支持通用的、结构化的任务,比如迭代,条件判断,XML 文档操作,国际化标签,SQL 标签。 除了这些,它还提供了一个框架来使用集成 JSTL 的自定义标签。 + +根据 JSTL 标签所提供的功能,可以将其分为 5 个类别。 + +- **核心标签** +- **格式化标签** +- **SQL 标签** +- **XML 标签** +- **JSTL 函数** + +### JSTL 库安装 + +Apache Tomcat 安装 JSTL 库步骤如下: + +从 Apache 的标准标签库中下载的二进包(jakarta-taglibs-standard-current.zip)。 + +- 官方下载地址: +- 本站下载地址:[jakarta-taglibs-standard-1.1.2.zip](http://static.runoob.com/download/jakarta-taglibs-standard-1.1.2.tar.gz) + +下载 **jakarta-taglibs-standard-1.1.2.zip** 包并解压,将 **jakarta-taglibs-standard-1.1.2/lib/** 下的两个 jar 文件:**standard.jar** 和 **jstl.jar** 文件拷贝到 **/WEB-INF/lib/** 下。 + +将 tld 下的需要引入的 tld 文件复制到 WEB-INF 目录下。 + +接下来我们在 web.xml 文件中添加以下配置: + +``` + + + + + http://java.sun.com/jsp/jstl/fmt + /WEB-INF/fmt.tld + + + http://java.sun.com/jsp/jstl/fmt-rt + /WEB-INF/fmt-rt.tld + + + http://java.sun.com/jsp/jstl/core + /WEB-INF/c.tld + + + http://java.sun.com/jsp/jstl/core-rt + /WEB-INF/c-rt.tld + + + http://java.sun.com/jsp/jstl/sql + /WEB-INF/sql.tld + + + http://java.sun.com/jsp/jstl/sql-rt + /WEB-INF/sql-rt.tld + + + http://java.sun.com/jsp/jstl/x + /WEB-INF/x.tld + + + http://java.sun.com/jsp/jstl/x-rt + /WEB-INF/x-rt.tld + + + +``` + +使用任何库,你必须在每个 JSP 文件中的头部包含 **** 标签。 + +### 核心标签 + +核心标签是最常用的 JSTL 标签。引用核心标签库的语法如下: + +``` +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +``` + +| 标签 | 描述 | +| :---------------------------------------------------------------------- | :-------------------------------------------------------------------------- | +| [``](http://www.runoob.com/jsp/jstl-core-out-tag.html) | 用于在 JSP 中显示数据,就像<%= ... > | +| [``](http://www.runoob.com/jsp/jstl-core-set-tag.html) | 用于保存数据 | +| [``](http://www.runoob.com/jsp/jstl-core-remove-tag.html) | 用于删除数据 | +| [``](http://www.runoob.com/jsp/jstl-core-catch-tag.html) | 用来处理产生错误的异常状况,并且将错误信息储存起来 | +| [``](http://www.runoob.com/jsp/jstl-core-if-tag.html) | 与我们在一般程序中用的 if 一样 | +| [``](http://www.runoob.com/jsp/jstl-core-choose-tag.html) | 本身只当做的父标签 | +| [``](http://www.runoob.com/jsp/jstl-core-choose-tag.html) | 的子标签,用来判断条件是否成立 | +| [``](http://www.runoob.com/jsp/jstl-core-choose-tag.html) | 的子标签,接在标签后,当标签判断为 false 时被执行 | +| [``](http://www.runoob.com/jsp/jstl-core-import-tag.html) | 检索一个绝对或相对 URL,然后将其内容暴露给页面 | +| [``](http://www.runoob.com/jsp/jstl-core-foreach-tag.html) | 基础迭代标签,接受多种集合类型 | +| [``](http://www.runoob.com/jsp/jstl-core-foreach-tag.html) | 根据指定的分隔符来分隔内容并迭代输出 | +| [``](http://www.runoob.com/jsp/jstl-core-param-tag.html) | 用来给包含或重定向的页面传递参数 | +| [``](http://www.runoob.com/jsp/jstl-core-redirect-tag.html) | 重定向至一个新的 URL. | +| [``](http://www.runoob.com/jsp/jstl-core-url-tag.html) | 使用可选的查询参数来创造一个 URL | + +### 格式化标签 + +JSTL 格式化标签用来格式化并输出文本、日期、时间、数字。引用格式化标签库的语法如下: + +``` +<%@ taglib prefix="fmt" + uri="http://java.sun.com/jsp/jstl/fmt" %> +``` + +| 标签 | 描述 | +| :---------------------------------------------------------------------------------------- | :--------------------------------------- | +| [``](http://www.runoob.com/jsp/jstl-format-formatnumber-tag.html) | 使用指定的格式或精度格式化数字 | +| [``](http://www.runoob.com/jsp/jstl-format-parsenumber-tag.html) | 解析一个代表着数字,货币或百分比的字符串 | +| [``](http://www.runoob.com/jsp/jstl-format-formatdate-tag.html) | 使用指定的风格或模式格式化日期和时间 | +| [``](http://www.runoob.com/jsp/jstl-format-parsedate-tag.html) | 解析一个代表着日期或时间的字符串 | +| [``](http://www.runoob.com/jsp/jstl-format-bundle-tag.html) | 绑定资源 | +| [``](http://www.runoob.com/jsp/jstl-format-setlocale-tag.html) | 指定地区 | +| [``](http://www.runoob.com/jsp/jstl-format-setbundle-tag.html) | 绑定资源 | +| [``](http://www.runoob.com/jsp/jstl-format-timezone-tag.html) | 指定时区 | +| [``](http://www.runoob.com/jsp/jstl-format-settimezone-tag.html) | 指定时区 | +| [``](http://www.runoob.com/jsp/jstl-format-message-tag.html) | 显示资源配置文件信息 | +| [``](http://www.runoob.com/jsp/jstl-format-requestencoding-tag.html) | 设置 request 的字符编码 | + +### SQL 标签 + +JSTL SQL 标签库提供了与关系型数据库(Oracle,MySQL,SQL Server 等等)进行交互的标签。引用 SQL 标签库的语法如下: + +``` +<%@ taglib prefix="sql" + uri="http://java.sun.com/jsp/jstl/sql" %> +``` + +| 标签 | 描述 | +| :--------------------------------------------------------------------------------- | :--------------------------------------------------------------------------- | +| [``](http://www.runoob.com/jsp/jstl-sql-setdatasource-tag.html) | 指定数据源 | +| [``](http://www.runoob.com/jsp/jstl-sql-query-tag.html) | 运行 SQL 查询语句 | +| [``](http://www.runoob.com/jsp/jstl-sql-update-tag.html) | 运行 SQL 更新语句 | +| [``](http://www.runoob.com/jsp/jstl-sql-param-tag.html) | 将 SQL 语句中的参数设为指定值 | +| [``](http://www.runoob.com/jsp/jstl-sql-dateparam-tag.html) | 将 SQL 语句中的日期参数设为指定的 java.util.Date 对象值 | +| [``](http://www.runoob.com/jsp/jstl-sql-transaction-tag.html) | 在共享数据库连接中提供嵌套的数据库行为元素,将所有语句以一个事务的形式来运行 | + +### XML 标签 + +JSTL XML 标签库提供了创建和操作 XML 文档的标签。引用 XML 标签库的语法如下: + +``` +<%@ taglib prefix="x" + uri="http://java.sun.com/jsp/jstl/xml" %> +``` + +在使用 xml 标签前,你必须将 XML 和 XPath 的相关包拷贝至你的\lib 下: + +- XercesImpl.jar + + 下载地址: + +- xalan.jar + + 下载地址: + +| 标签 | 描述 | +| :----------------------------------------------------------------------- | :---------------------------------------------------------- | +| [``](http://www.runoob.com/jsp/jstl-xml-out-tag.html) | 与<%= ... >,类似,不过只用于 XPath 表达式 | +| [``](http://www.runoob.com/jsp/jstl-xml-parse-tag.html) | 解析 XML 数据 | +| [``](http://www.runoob.com/jsp/jstl-xml-set-tag.html) | 设置 XPath 表达式 | +| [``](http://www.runoob.com/jsp/jstl-xml-if-tag.html) | 判断 XPath 表达式,若为真,则执行本体中的内容,否则跳过本体 | +| [``](http://www.runoob.com/jsp/jstl-xml-foreach-tag.html) | 迭代 XML 文档中的节点 | +| [``](http://www.runoob.com/jsp/jstl-xml-choose-tag.html) | 的父标签 | +| [``](http://www.runoob.com/jsp/jstl-xml-choose-tag.html) | 的子标签,用来进行条件判断 | +| [``](http://www.runoob.com/jsp/jstl-xml-choose-tag.html) | 的子标签,当判断为 false 时被执行 | +| [``](http://www.runoob.com/jsp/jstl-xml-transform-tag.html) | 将 XSL 转换应用在 XML 文档中 | +| [``](http://www.runoob.com/jsp/jstl-xml-param-tag.html) | 与共同使用,用于设置 XSL 样式表 | + +### JSTL 函数 + +JSTL 包含一系列标准函数,大部分是通用的字符串处理函数。引用 JSTL 函数库的语法如下: + +``` +<%@ taglib prefix="fn" + uri="http://java.sun.com/jsp/jstl/functions" %> +``` + +| 函数 | 描述 | +| :----------------------------------------------------------------------------------------- | :------------------------------------------------------- | +| [fn:contains()](http://www.runoob.com/jsp/jstl-function-contains.html) | 测试输入的字符串是否包含指定的子串 | +| [fn:containsIgnoreCase()](http://www.runoob.com/jsp/jstl-function-containsignoreCase.html) | 测试输入的字符串是否包含指定的子串,大小写不敏感 | +| [fn:endsWith()](http://www.runoob.com/jsp/jstl-function-endswith.html) | 测试输入的字符串是否以指定的后缀结尾 | +| [fn:escapeXml()](http://www.runoob.com/jsp/jstl-function-escapexml.html) | 跳过可以作为 XML 标记的字符 | +| [fn:indexOf()](http://www.runoob.com/jsp/jstl-function-indexof.html) | 返回指定字符串在输入字符串中出现的位置 | +| [fn:join()](http://www.runoob.com/jsp/jstl-function-join.html) | 将数组中的元素合成一个字符串然后输出 | +| [fn:length()](http://www.runoob.com/jsp/jstl-function-length.html) | 返回字符串长度 | +| [fn:replace()](http://www.runoob.com/jsp/jstl-function-replace.html) | 将输入字符串中指定的位置替换为指定的字符串然后返回 | +| [fn:split()](http://www.runoob.com/jsp/jstl-function-split.html) | 将字符串用指定的分隔符分隔然后组成一个子字符串数组并返回 | +| [fn:startsWith()](http://www.runoob.com/jsp/jstl-function-startswith.html) | 测试输入字符串是否以指定的前缀开始 | +| [fn:substring()](http://www.runoob.com/jsp/jstl-function-substring.html) | 返回字符串的子集 | +| [fn:substringAfter()](http://www.runoob.com/jsp/jstl-function-substringafter.html) | 返回字符串在指定子串之后的子集 | +| [fn:substringBefore()](http://www.runoob.com/jsp/jstl-function-substringbefore.html) | 返回字符串在指定子串之前的子集 | +| [fn:toLowerCase()](http://www.runoob.com/jsp/jstl-function-tolowercase.html) | 将字符串中的字符转为小写 | +| [fn:toUpperCase()](http://www.runoob.com/jsp/jstl-function-touppercase.html) | 将字符串中的字符转为大写 | +| [fn:trim()](http://www.runoob.com/jsp/jstl-function-trim.html) | 移除首尾的空白符 | + +## Taglib + +### JSP 自定义标签 + +自定义标签是用户定义的 JSP 语言元素。当 JSP 页面包含一个自定义标签时将被转化为 servlet,标签转化为对被 称为 tag handler 的对象的操作,即当 servlet 执行时 Web container 调用那些操作。 + +JSP 标签扩展可以让你创建新的标签并且可以直接插入到一个 JSP 页面。 JSP 2.0 规范中引入 Simple Tag Handlers 来编写这些自定义标记。 + +你可以继承 SimpleTagSupport 类并重写的 doTag()方法来开发一个最简单的自定义标签。 + +### 创建"Hello"标签 + +接下来,我们想创建一个自定义标签叫作,标签格式为: + +``` + +``` + +要创建自定义的 JSP 标签,你首先必须创建处理标签的 Java 类。所以,让我们创建一个 HelloTag 类,如下所示: + +``` +package com.runoob; + +import javax.servlet.jsp.tagext.*; +import javax.servlet.jsp.*; +import java.io.*; + +public class HelloTag extends SimpleTagSupport { + + public void doTag() throws JspException, IOException { + JspWriter out = getJspContext().getOut(); + out.println("Hello Custom Tag!"); + } +} +``` + +以下代码重写了 doTag()方法,方法中使用了 getJspContext()方法来获取当前的 JspContext 对象,并将"Hello Custom Tag!"传递给 JspWriter 对象。 + +编译以上类,并将其复制到环境变量 CLASSPATH 目录中。最后创建如下标签库:`webapps\ROOT\WEB-INF\custom.tld`。 + +``` + + 1.0 + 2.0 + Example TLD + + Hello + com.runoob.HelloTag + empty + + +``` + +接下来,我们就可以在 JSP 文件中使用 Hello 标签: + +``` +<%@ taglib prefix="ex" uri="WEB-INF/custom.tld"%> + + + A sample custom tag + + + + + +``` + +以上程序输出结果为: + +``` +Hello Custom Tag! +``` + +### 访问标签体 + +你可以像标准标签库一样在标签中包含消息内容。如我们要在我们自定义的 Hello 中包含内容,格式如下: + +``` + + This is message body + +``` + +我们可以修改标签处理类文件,代码如下: + +``` +package com.runoob; + +import javax.servlet.jsp.tagext.*; +import javax.servlet.jsp.*; +import java.io.*; + +public class HelloTag extends SimpleTagSupport { + + StringWriter sw = new StringWriter(); + public void doTag() + throws JspException, IOException + { + getJspBody().invoke(sw); + getJspContext().getOut().println(sw.toString()); + } + +} +``` + +接下来我们需要修改 TLD 文件,如下所示: + +``` + + 1.0 + 2.0 + Example TLD with Body + + Hello + com.runoob.HelloTag + scriptless + + +``` + +现在我们可以在 JSP 使用修改后的标签,如下所示: + +``` +<%@ taglib prefix="ex" uri="WEB-INF/custom.tld"%> + + + A sample custom tag + + + + This is message body + + + +``` + +以上程序输出结果如下所示: + +``` +This is message body +``` + +### 自定义标签属性 + +你可以在自定义标准中设置各种属性,要接收属性,值自定义标签类必须实现 setter 方法, JavaBean 中的 setter 方法如下所示: + +``` +package com.runoob; + +import javax.servlet.jsp.tagext.*; +import javax.servlet.jsp.*; +import java.io.*; + +public class HelloTag extends SimpleTagSupport { + + private String message; + + public void setMessage(String msg) { + this.message = msg; + } + + StringWriter sw = new StringWriter(); + + public void doTag() + throws JspException, IOException + { + if (message != null) { + /* 从属性中使用消息 */ + JspWriter out = getJspContext().getOut(); + out.println( message ); + } + else { + /* 从内容体中使用消息 */ + getJspBody().invoke(sw); + getJspContext().getOut().println(sw.toString()); + } + } + +} +``` + +属性的名称是"message",所以 setter 方法是的 setMessage()。现在让我们在 TLD 文件中使用的元素添加此属性: + +``` + + 1.0 + 2.0 + Example TLD with Body + + Hello + com.runoob.HelloTag + scriptless + + message + + + +``` + +现在我们就可以在 JSP 文件中使用 message 属性了,如下所示: + +``` +<%@ taglib prefix="ex" uri="WEB-INF/custom.tld"%> + + + A sample custom tag + + + + + +``` + +以上实例数据输出结果为: + +``` +This is custom tag +``` + +你还可以包含以下属性: + +| 属性 | 描述 | +| :---------- | :------------------------------------------------------- | +| name | 定义属性的名称。每个标签的是属性名称必须是唯一的。 | +| required | 指定属性是否是必须的或者可选的,如果设置为 false 为可选。 | +| rtexprvalue | 声明在运行表达式时,标签属性是否有效。 | +| type | 定义该属性的 Java 类类型 。默认指定为 **String** | +| description | 描述信息 | +| fragment | 如果声明了该属性,属性值将被视为一个 **JspFragment**。 | + +以下是指定相关的属性实例: + +``` +..... + + attribute_name + false + java.util.Date + false + +..... +``` + +如果你使用了两个属性,修改 TLD 文件,如下所示: + +``` +..... + + attribute_name1 + false + java.util.Boolean + false + + + attribute_name2 + true + java.util.Date + +..... +``` diff --git a/docs/javaee/jsp/jsp-action.md b/docs/javaee/jsp/jsp-action.md deleted file mode 100644 index 5c31f695..00000000 --- a/docs/javaee/jsp/jsp-action.md +++ /dev/null @@ -1,400 +0,0 @@ ---- -title: JSP 行为 -date: 2017/11/08 -categories: -- javaee -tags: -- javaee -- jsp ---- - -JSP 行为是一组 JSP 内置的标签,只需要书写很少的标记代码就能使用 JSP 提供的丰富功能。JSP 行为是对常用的 JSP 功能的抽象与封装,包括两种,自定义 JSP 行为与标准 JSP 行为。 - -与 JSP 指令元素不同的是,JSP 动作元素在请求处理阶段起作用。JSP 动作元素是用 XML 语法写成的。 - -利用 JSP 动作可以动态地插入文件、重用 JavaBean 组件、把用户重定向到另外的页面、为 Java 插件生成 HTML 代码。 - -动作元素只有一种语法,它符合 XML 标准: - -``` - -``` - -动作元素基本上都是预定义的函数,JSP 规范定义了一系列的标准动作,它用 JSP 作为前缀,可用的标准动作元素如下: - -| 语法 | 描述 | -| --------------- | ----------------------------------------------------- | -| jsp:include | 在页面被请求的时候引入一个文件。 | -| jsp:useBean | 寻找或者实例化一个 JavaBean。 | -| jsp:setProperty | 设置 JavaBean 的属性。 | -| jsp:getProperty | 输出某个 JavaBean 的属性。 | -| jsp:forward | 把请求转到一个新的页面。 | -| jsp:plugin | 根据浏览器类型为 Java 插件生成 OBJECT 或 EMBED 标记。 | -| jsp:element | 定义动态 XML 元素 | -| jsp:attribute | 设置动态定义的 XML 元素属性。 | -| jsp:body | 设置动态定义的 XML 元素内容。 | -| jsp:text | 在 JSP 页面和文档中使用写入文本的模板 | - -## 常见的属性 - -所有的动作要素都有两个属性:id 属性和 scope 属性。 - -- **id 属性:**id 属性是动作元素的唯一标识,可以在 JSP 页面中引用。动作元素创建的 id 值可以通过 PageContext 来调用。 -- **scope 属性:**该属性用于识别动作元素的生命周期。 id 属性和 scope 属性有直接关系,scope 属性定义了相关联 id 对象的寿命。 scope 属性有四个可能的值: (a) page, (b)request, (c)session, 和 (d) application。 - -## - -``用来包含静态和动态的文件。该动作把指定文件插入正在生成的页面。 - -如果被包含的文件为 JSP 程序,则会先执行 JSP 程序,再将执行结果包含进来。 - -语法格式如下: - -``` - -``` - -前面已经介绍过 include 指令,它是在 JSP 文件被转换成 Servlet 的时候引入文件,而这里的 jsp:include 动作不同,插入文件的时间是在页面被请求的时候。 - -以下是 include 动作相关的属性列表。 - -| 属性 | 描述 | -| ----- | ------------------------------------------ | -| page | 包含在页面中的相对 URL 地址。 | -| flush | 布尔属性,定义在包含资源前是否刷新缓存区。 | - -**例** - -以下我们定义了两个文件  **date.jsp**  和  **main.jsp**,代码如下所示: - -date.jsp 文件代码: - -```c++ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> -

- 今天的日期是: <%= (new java.util.Date())%> -

-``` - -main.jsp 文件代码: - -```jsp -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> - - - - -菜鸟教程(runoob.com) - - - -

include 动作实例

- - - - -``` - -现在将以上两个文件放在服务器的根目录下,访问 main.jsp 文件。显示结果如下: - -``` -include 动作实例 - -今天的日期是: 2016-6-25 14:08:17 -``` - ---- - -## - -**jsp:useBean**  动作用来加载一个将在 JSP 页面中使用的 JavaBean。 - -这个功能非常有用,因为它使得我们可以发挥 Java 组件复用的优势。 - -jsp:useBean 动作最简单的语法为: - -``` - -``` - -在类载入后,我们既可以通过 jsp:setProperty 和 jsp:getProperty 动作来修改和检索 bean 的属性。 - -以下是 useBean 动作相关的属性列表。 - -| 属性 | 描述 | -| -------- | ------------------------------------------------------------- | -| class | 指定 Bean 的完整包名。 | -| type | 指定将引用该对象变量的类型。 | -| beanName | 通过 java.beans.Beans 的 instantiate() 方法指定 Bean 的名字。 | - -在给出具体实例前,让我们先来看下 jsp:setProperty 和 jsp:getProperty 动作元素: - ---- - -## - -jsp:setProperty 用来设置已经实例化的 Bean 对象的属性,有两种用法。首先,你可以在 jsp:useBean 元素的外面(后面)使用 jsp:setProperty,如下所示: - -``` - -... - -``` - -此时,不管 jsp:useBean 是找到了一个现有的 Bean,还是新创建了一个 Bean 实例,jsp:setProperty 都会执行。第二种用法是把 jsp:setProperty 放入 jsp:useBean 元素的内部,如下所示: - -``` - -... - - -``` - -此时,jsp:setProperty 只有在新建 Bean 实例时才会执行,如果是使用现有实例则不执行 jsp:setProperty。 - -jsp:setProperty 动作有下面四个属性,如下表: - -| 属性 | 描述 | -| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| name | name 属性是必需的。它表示要设置属性的是哪个 Bean。 | -| property | property 属性是必需的。它表示要设置哪个属性。有一个特殊用法:如果 property 的值是"\*",表示所有名字和 Bean 属性名字匹配的请求参数都将被传递给相应的属性 set 方法。 | -| value | value 属性是可选的。该属性用来指定 Bean 属性的值。字符串数据会在目标类中通过标准的 valueOf 方法自动转换成数字、boolean、Boolean、 byte、Byte、char、Character。例如,boolean 和 Boolean 类型的属性值(比如"true")通过 Boolean.valueOf 转换,int 和 Integer 类型的属性值(比如"42")通过 Integer.valueOf 转换。    value 和 param 不能同时使用,但可以使用其中任意一个。 | -| param | param 是可选的。它指定用哪个请求参数作为 Bean 属性的值。如果当前请求没有参数,则什么事情也不做,系统不会把 null 传递给 Bean 属性的 set 方法。因此,你可以让 Bean 自己提供默认属性值,只有当请求参数明确指定了新值时才修改默认属性值。 | - ---- - -## - -jsp:getProperty 动作提取指定 Bean 属性的值,转换成字符串,然后输出。语法格式如下: - -``` - -... - -``` - -下表是与 getProperty 相关联的属性: - -| 属性 | 描述 | -| -------- | ----------------------------------------- | -| name | 要检索的 Bean 属性名称。Bean 必须已定义。 | -| property | 表示要提取 Bean 属性的值 | - -实例 - -以下实例我们使用了 Bean: - -``` -package com.runoob.main; - -public class TestBean { - private String message = "菜鸟教程"; - - public String getMessage() { - return(message); - } - public void setMessage(String message) { - this.message = message; - } -} -``` - -编译以上实例文件 TestBean.java : - -``` -$ javac TestBean.java -``` - -编译完成后会在当前目录下生成一个  **TestBean.class**  文件, 将该文件拷贝至当前 JSP 项目的  **WebContent/WEB-INF/classes/com/runoob/main**  下( com/runoob/main 包路径,没有需要手动创建)。 - -下面是一个 Eclipse 中目录结构图: - -![img](http://www.runoob.com/wp-content/uploads/2014/01/6AC33FBA-0B76-4BFD-A690-E856E9E01900.jpg) - -下面是一个很简单的例子,它的功能是装载一个 Bean,然后设置/读取它的 message 属性。 - -现在让我们在 main.jsp 文件中调用该 Bean: - -``` -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> - - - - -菜鸟教程(runoob.com) - - - -

Jsp 使用 JavaBean 实例

- - - - -

输出信息....

- - - - - -``` - -浏览器访问,执行以上文件,输出如下所示: - -![img](http://www.runoob.com/wp-content/uploads/2014/01/D7AD87A8-3392-4D4E-8731-18806B0644CD.jpg) - ---- - -## - -jsp:forward 动作把请求转到另外的页面。jsp:forward 标记只有一个属性 page。语法格式如下所示: - -``` - -``` - -以下是 forward 相关联的属性: - -| 属性 | 描述 | -| ---- | ----------------------------------------------------------------------------------------------------------------------------- | -| page | page 属性包含的是一个相对 URL。page 的值既可以直接给出,也可以在请求的时候动态计算,可以是一个 JSP 页面或者一个 Java Servlet. | - -实例 - -以下实例我们使用了两个文件,分别是: date.jsp 和 main.jsp。 - -date.jsp 文件代码如下: - -``` -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> -

- 今天的日期是: <%= (new java.util.Date()).toLocaleString()%> -

-``` - -main.jsp 文件代码: - -``` -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> - - - - -菜鸟教程(runoob.com) - - - -

forward 动作实例

- - - -``` - -现在将以上两个文件放在服务器的根目录下,访问 main.jsp 文件。显示结果如下: - -``` -今天的日期是: 2016-6-25 14:37:25 -``` - ---- - -## - -jsp:plugin 动作用来根据浏览器的类型,插入通过 Java 插件 运行 Java Applet 所必需的 OBJECT 或 EMBED 元素。 - -如果需要的插件不存在,它会下载插件,然后执行 Java 组件。 Java 组件可以是一个 applet 或一个 JavaBean。 - -plugin 动作有多个对应 HTML 元素的属性用于格式化 Java 组件。param 元素可用于向 Applet 或 Bean 传递参数。 - -以下是使用 plugin 动作元素的典型实例: - -``` - - - - - - Unable to initialize Java Plugin - - - -``` - -如果你有兴趣可以尝试使用 applet 来测试 jsp:plugin 动作元素,元素是一个新元素,在组件出现故障的错误是发送给用户错误信息。 - ---- - -## - -动作元素动态定义 XML 元素。动态是非常重要的,这就意味着 XML 元素在编译时是动态生成的而非静态。 - -以下实例动态定义了 XML 元素: - -``` -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> - - - - -菜鸟教程(runoob.com) - - - - - 属性值 - - - XML 元素的主体 - - - - -``` - -浏览器访问以下页面,输出结果如下所示: - -![img](http://www.runoob.com/wp-content/uploads/2014/01/7D8C47F0-0DDE-4F1D-8BE1-B2C9C955683E.jpg) - ---- - -## - -动作元素允许在 JSP 页面和文档中使用写入文本的模板,语法格式如下: - -``` -模板数据 -``` - -以上文本模板不能包含其他元素,只能只能包含文本和 EL 表达式(注:EL 表达式将在后续章节中介绍)。请注意,在 XML 文件中,您不能使用表达式如 ${whatever > 0},因为>符号是非法的。 你可以使用 ${whatever gt 0}表达式或者嵌入在一个 CDATA 部分的值。 - -``` -]]> -``` - -如果你需要在 XHTML 中声明 DOCTYPE,必须使用到动作元素,实例如下: - -``` -]]> - -jsp:text action - - - - Welcome to JSP Programming - - - - -``` - -你可以对以上实例尝试使用及不使用该动作元素执行结果的区别。 diff --git a/docs/javaee/jsp/jsp-directive.md b/docs/javaee/jsp/jsp-directive.md deleted file mode 100644 index 084d1a77..00000000 --- a/docs/javaee/jsp/jsp-directive.md +++ /dev/null @@ -1,115 +0,0 @@ ---- -title: JSP 指令 -date: 2017/11/08 -categories: -- javaee -tags: -- javaee -- jsp ---- - -## 概述 - -JSP 指令用来设置整个 JSP 页面相关的属性,如网页的编码方式和脚本语言。 - -JSP 指令以开`<%@`开始,以`%>`结束。 - -JSP 指令语法格式如下: - -```jsp -<%@ directive attribute="value" %> -``` - -指令可以有很多个属性,它们以键值对的形式存在,并用逗号隔开。 - -JSP 中的三种指令标签: - -| **指令** | **描述** | -| ------------------ | -------------------------------------------------------- | -| <%@ page ... %> | 定义网页依赖属性,比如脚本语言、error 页面、缓存需求等等 | -| <%@ include ... %> | 包含其他文件 | -| <%@ taglib ... %> | 引入标签库的定义,可以是自定义标签 | - -## Page 指令 - -Page 指令为容器提供当前页面的使用说明。一个 JSP 页面可以包含多个`page`指令。 - -Page 指令的语法格式: - -```jsp -<%@ page attribute="value" %> -``` - -等价的 XML 格式: - -```jsp - -``` - -例: - -```jsp -<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> -``` - -### 属性 - -下表列出与 Page 指令相关的属性: - -| **属性** | **描述** | -| ------------------ | ----------------------------------------------------- | -| buffer | 指定 out 对象使用缓冲区的大小 | -| autoFlush | 控制 out 对象的   缓存区 | -| contentType | 指定当前 JSP 页面的 MIME 类型和字符编码 | -| errorPage | 指定当 JSP 页面发生异常时需要转向的错误处理页面 | -| isErrorPage | 指定当前页面是否可以作为另一个 JSP 页面的错误处理页面 | -| extends | 指定 servlet 从哪一个类继承 | -| import | 导入要使用的 Java 类 | -| info | 定义 JSP 页面的描述信息 | -| isThreadSafe | 指定对 JSP 页面的访问是否为线程安全 | -| language | 定义 JSP 页面所用的脚本语言,默认是 Java | -| session | 指定 JSP 页面是否使用 session | -| isELIgnored | 指定是否执行 EL 表达式 | -| isScriptingEnabled | 确定脚本元素能否被使用 | - -## Include 指令 - -JSP 可以通过`include`指令来包含其他文件。 - -被包含的文件可以是 JSP 文件、HTML 文件或文本文件。包含的文件就好像是该 JSP 文件的一部分,会被同时编译执行。 - -Include 指令的语法格式如下: - -```jsp -<%@ include file="文件相对 url 地址" %> -``` - -**include**  指令中的文件名实际上是一个相对的 URL 地址。 - -如果您没有给文件关联一个路径,JSP 编译器默认在当前路径下寻找。 - -等价的 XML 语法: - -```jsp - -``` - -## Taglib 指令 - -JSP 允许用户自定义标签,一个自定义标签库就是自定义标签的集合。 - -`taglib`指令引入一个自定义标签集合的定义,包括库路径、自定义标签。 - -`taglib`指令的语法: - -```jsp -<%@ taglib uri="uri" prefix="prefixOfTag" %> -``` - -uri 属性确定标签库的位置,prefix 属性指定标签库的前缀。 - -等价的 XML 语法: - -```jsp - -``` diff --git a/docs/javaee/jsp/jsp-el-expression.md b/docs/javaee/jsp/jsp-el-expression.md deleted file mode 100644 index 6deeea0f..00000000 --- a/docs/javaee/jsp/jsp-el-expression.md +++ /dev/null @@ -1,217 +0,0 @@ ---- -title: JSP 表达式语言 -date: 2017/11/08 -categories: -- javaee -tags: -- javaee -- jsp ---- - -EL 表达式是用`${}`括起来的脚本,用来更方便地读取对象。EL 表达式写在 JSP 的 HTML 代码中,而不能写在`<%`与`%>`引起的 JSP 脚本中。 - -JSP 表达式语言(EL)使得访问存储在 JavaBean 中的数据变得非常简单。JSP EL 既可以用来创建算术表达式也可以用来创建逻辑表达式。在 JSP EL 表达式内可以使用整型数,浮点数,字符串,常量 true、false,还有 null。 - -## 一个简单的语法 - -典型的,当您需要在 JSP 标签中指定一个属性值时,只需要简单地使用字符串即可: - -```jsp - -``` - -JSP EL 允许您指定一个表达式来表示属性值。一个简单的表达式语法如下: - -```jsp -${expr} -``` - -其中,expr 指的是表达式。在 JSP EL 中通用的操作符是"."和"[]"。这两个操作符允许您通过内嵌的 JSP 对象访问各种各样的 JavaBean 属性。 - -举例来说,上面的标签可以使用表达式语言改写成如下形式: - -```jsp - -``` - -当 JSP 编译器在属性中见到"${}"格式后,它会产生代码来计算这个表达式,并且产生一个替代品来代替表达式的值。 - -您也可以在标签的模板文本中使用表达式语言。比如标签简单地将其主体中的文本插入到 JSP 输出中: - -```jsp - -

Hello JSP!

-
-``` - -现在,在标签主体中使用表达式,就像这样: - -```jsp - -Box Perimeter is: ${2*box.width + 2*box.height} - -``` - -在 EL 表达式中可以使用圆括号来组织子表达式。比如${(1 + 2) _ 3}等于 9,但是${1 + (2 _ 3)} 等于 7。 - -想要停用对 EL 表达式的评估的话,需要使用 page 指令将 isELIgnored 属性值设为 true: - -```jsp -<%@ page isELIgnored ="true|false" %> -``` - -这样,EL 表达式就会被忽略。若设为 false,则容器将会计算 EL 表达式。 - ---- - -## EL 中的基础操作符 - -EL 表达式支持大部分 Java 所提供的算术和逻辑操作符: - -| **操作符** | **描述** | -| ---------- | ---------------------------------- | -| . | 访问一个 Bean 属性或者一个映射条目 | -| [] | 访问一个数组或者链表的元素 | -| ( ) | 组织一个子表达式以改变优先级 | -| + | 加 | -| - | 减或负 | -| \* | 乘 | -| / or div | 除 | -| % or mod | 取模 | -| == or eq | 测试是否相等 | -| != or ne | 测试是否不等 | -| < or lt | 测试是否小于 | -| > or gt | 测试是否大于 | -| <= or le | 测试是否小于等于 | -| >= or ge | 测试是否大于等于 | -| && or and | 测试逻辑与 | -| \|\| or or | 测试逻辑或 | -| ! or not | 测试取反 | -| empty | 测试是否空值 | - ---- - -## JSP EL 中的函数 - -JSP EL 允许您在表达式中使用函数。这些函数必须被定义在自定义标签库中。函数的使用语法如下: - -```jsp -${ns:func(param1, param2, ...)} -``` - -ns 指的是命名空间(namespace),func 指的是函数的名称,param1 指的是第一个参数,param2 指的是第二个参数,以此类推。比如,有函数 fn:length,在 JSTL 库中定义,可以像下面这样来获取一个字符串的长度: - -```jsp -${fn:length("Get my length")} -``` - -要使用任何标签库中的函数,您需要将这些库安装在服务器中,然后使用标签在 JSP 文件中包含这些库。 - ---- - -## JSP EL 隐含对象 - -JSP EL 支持下表列出的隐含对象: - -| **隐含对象** | **描述** | -| ---------------- | ------------------------------ | -| pageScope | page 作用域 | -| requestScope | request 作用域 | -| sessionScope | session 作用域 | -| applicationScope | application 作用域 | -| param | Request 对象的参数,字符串 | -| paramValues | Request 对象的参数,字符串集合 | -| header | HTTP 信息头,字符串 | -| headerValues | HTTP 信息头,字符串集合 | -| initParam | 上下文初始化参数 | -| cookie | Cookie 值 | -| pageContext | 当前页面的 pageContext | - -您可以在表达式中使用这些对象,就像使用变量一样。接下来会给出几个例子来更好的理解这个概念。 - ---- - -## pageContext 对象 - -pageContext 对象是 JSP 中 pageContext 对象的引用。通过 pageContext 对象,您可以访问 request 对象。比如,访问 request 对象传入的查询字符串,就像这样: - -```jsp -${pageContext.request.queryString} -``` - ---- - -## Scope 对象 - -pageScope,requestScope,sessionScope,applicationScope 变量用来访问存储在各个作用域层次的变量。 - -举例来说,如果您需要显式访问在 applicationScope 层的 box 变量,可以这样来访问:applicationScope.box。 - ---- - -## param 和 paramValues 对象 - -param 和 paramValues 对象用来访问参数值,通过使用 request.getParameter 方法和 request.getParameterValues 方法。 - -举例来说,访问一个名为 order 的参数,可以这样使用表达式:${param.order},或者${param["order"]}。 - -接下来的例子表明了如何访问 request 中的 username 参数: - -```jsp -<%@ page import="java.io.*,java.util.*" %> -<% - String title = "Accessing Request Param"; -%> - - -<% out.print(title); %> - - -
-

<% out.print(title); %>

-
-
-

${param["username"]}

-
- - -``` - -param 对象返回单一的字符串,而 paramValues 对象则返回一个字符串数组。 - ---- - -## header 和 headerValues 对象 - -header 和 headerValues 对象用来访问信息头,通过使用 request.getHeader 方法和 request.getHeaders 方法。 - -举例来说,要访问一个名为 user-agent 的信息头,可以这样使用表达式:${header.user-agent},或者${header["user-agent"]}。 - -接下来的例子表明了如何访问 user-agent 信息头: - -```jsp -<%@ page import="java.io.*,java.util.*" %> -<% - String title = "User Agent Example"; -%> - - -<% out.print(title); %> - - -
-

<% out.print(title); %>

-
-
-

${header["user-agent"]}

-
- - -``` - -运行结果如下: - -![jsp-expression-language](http://www.runoob.com/wp-content/uploads/2014/01/jsp-expression-language.jpg) - -header 对象返回单一值,而 headerValues 则返回一个字符串数组。 diff --git a/docs/javaee/jsp/jsp-grammar.md b/docs/javaee/jsp/jsp-grammar.md deleted file mode 100644 index 1b36efbc..00000000 --- a/docs/javaee/jsp/jsp-grammar.md +++ /dev/null @@ -1,383 +0,0 @@ ---- -title: JSP 语法 -date: 2017/11/08 -categories: -- javaee -tags: -- javaee -- jsp ---- - -本小节将会简单地介绍一下 JSP 开发中的基础语法。 - ---- - -## 脚本 - -脚本程序可以包含任意量的 Java 语句、变量、方法或表达式,只要它们在脚本语言中是有效的。 - -脚本程序的语法格式: - -```jsp -<% 代码片段 %> -``` - -或者,您也可以编写与其等价的 XML 语句,就像下面这样: - -```jsp - - 代码片段 - -``` - -任何文本、HTML 标签、JSP 元素必须写在脚本程序的外面。 - -下面给出一个示例,同时也是本教程的第一个 JSP 示例: - -```jsp - -Hello World - -Hello World!
-<% -out.println("Your IP address is " + request.getRemoteAddr()); -%> - - -``` - -**注意:**请确保 Apache Tomcat 已经安装在 C:\apache-tomcat-7.0.2 目录下并且运行环境已经正确设置。 - -将以上代码保存在 hello.jsp 中,然后将它放置在 C:\apache-tomcat-7.0.2\webapps\ROOT 目录下,打开浏览器并在地址栏中输入http://localhost:8080/hello.jsp。运行后得到以下结果: - -![img](http://www.runoob.com/wp-content/uploads/2014/01/jsp_hello_world.jpg) - -### 中文编码问题 - -如果我们要在页面正常显示中文,我们需要在 JSP 文件头部添加以下代码:<> - -```jsp -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> -``` - -接下来我们将以上程序修改为: - -```jsp -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> - - - - -菜鸟教程(runoob.com) - - -Hello World!
-<% -out.println("你的 IP 地址 " + request.getRemoteAddr()); -%> - - -``` - -这样中文就可以正常显示了。 - -## JSP 声明 - -一个声明语句可以声明一个或多个变量、方法,供后面的 Java 代码使用。在 JSP 文件中,您必须先声明这些变量和方法然后才能使用它们。 - -JSP 声明的语法格式: - -```jsp -<%! declaration; [ declaration; ]+ ... %> -``` - -或者,您也可以编写与其等价的 XML 语句,就像下面这样: - -```jsp - - 代码片段 - -``` - -程序示例: - -```jsp -<%! int i = 0; %> -<%! int a, b, c; %> -<%! Circle a = new Circle(2.0); %> -``` - -## JSP 表达式 - -一个 JSP 表达式中包含的脚本语言表达式,先被转化成 String,然后插入到表达式出现的地方。 - -由于表达式的值会被转化成 String,所以您可以在一个文本行中使用表达式而不用去管它是否是 HTML 标签。 - -表达式元素中可以包含任何符合 Java 语言规范的表达式,但是不能使用分号来结束表达式。 - -JSP 表达式的语法格式: - -``` -<%= 表达式 %> -``` - -同样,您也可以编写与之等价的 XML 语句: - -``` - - 表达式 - -``` - -程序示例: - -``` -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> - - - - -菜鸟教程(runoob.com) - - -

- 今天的日期是: <%= (new java.util.Date()).toLocaleString()%> -

- - -``` - -运行后得到以下结果: - -``` -今天的日期是: 2016-6-25 13:40:07 -``` - ---- - -## JSP 注释 - -JSP 注释主要有两个作用:为代码作注释以及将某段代码注释掉。 - -JSP 注释的语法格式: - -```jsp -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> - - - - -JSP注释示例 - - -<%-- 该部分注释在网页中不会被显示--%> -

- 今天的日期是: <%= (new java.util.Date()).toLocaleString()%> -

- - -``` - -运行后得到以下结果: - -``` -今天的日期是: 2016-6-25 13:41:26 -``` - -不同情况下使用注释的语法规则: - -| **语法** | 描述 | -| -------------- | ----------------------------------------------------- | -| <%-- 注释 --%> | JSP 注释,注释内容不会被发送至浏览器甚至不会被编译 | -| | HTML 注释,通过浏览器查看网页源代码时可以看见注释内容 | -| <\% | 代表静态 <%常量 | -| %\> | 代表静态 %> 常量 | -| \' | 在属性中使用的单引号 | -| \" | 在属性中使用的双引号 | - -## 控制语句 - -JSP 提供对 Java 语言的全面支持。您可以在 JSP 程序中使用 Java API 甚至建立 Java 代码块,包括判断语句和循环语句等等。 - -### if…else 语句 - -`If…else`块,请看下面这个例子: - -```jsp -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> -<%! int day = 1; %> - - - - - 02.JSP语法 - if...else示例 - - -

IF...ELSE 实例

-<% if (day == 1 | day == 7) { %> -

今天是周末

-<% } else { %> -

今天不是周末

-<% } %> - - -``` - -运行后得到以下结果: - -``` -IF...ELSE 实例 -今天不是周末 -``` - -### switch…case 语句 - -现在来看看 switch…case 块,与 if…else 块有很大的不同,它使用 out.println(),并且整个都装在脚本程序的标签中,就像下面这样: - -```jsp -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> -<%! int day = 3; %> - - - - - 02.JSP语法 - switch...case示例 - - -

Sswitch...case示例

-<% - switch(day) { - case 0: - out.println("星期天"); - break; - case 1: - out.println("星期一"); - break; - case 2: - out.println("星期二"); - break; - case 3: - out.println("星期三"); - break; - case 4: - out.println("星期四"); - break; - case 5: - out.println("星期五"); - break; - default: - out.println("星期六"); - } -%> - - -``` - -浏览器访问,运行后得出以下结果: - -``` -SWITCH...CASE 实例 - -星期三 -``` - -### 循环语句 - -在 JSP 程序中可以使用 Java 的三个基本循环类型:for,while,和 do…while。 - -让我们来看看 for 循环的例子,以下输出的不同字体大小的"菜鸟教程": - -``` -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> -<%! int fontSize; %> - - - - -菜鸟教程(runoob.com) - - -

For 循环实例

-<%for ( fontSize = 1; fontSize <= 3; fontSize++){ %> - - 菜鸟教程 -
-<%}%> - - -``` - -运行后得到以下结果: - -![img](http://www.runoob.com/wp-content/uploads/2014/01/7B4B85CF-FE4B-43CB-AAFF-F8594AD4342C.jpg) - -将上例改用 while 循环来写: - -``` -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> -<%! int fontSize; %> - - - - -菜鸟教程(runoob.com) - - -

While 循环实例

-<%while ( fontSize <= 3){ %> - - 菜鸟教程 -
-<%fontSize++;%> -<%}%> - - -``` - -浏览器访问,输出结果为(fontSize 初始化为 0,所以多输出了一行): - -![img](http://www.runoob.com/wp-content/uploads/2014/01/4F744CC9-E484-45BA-AF18-27AFCF4AD45C.jpg) - -JSP 运算符 - -JSP 支持所有 Java 逻辑和算术运算符。 - -下表罗列出了 JSP 常见运算符,优先级从高到底: - -| **类别** | **操作符** | **结合性** | -| --------- | ----------------------------------- | ---------- | -| 后缀 | () [] . (点运算符) | 左到右 | -| 一元 | ++ - - ! ~ | 右到左 | -| 可乘性 | \* / % | 左到右 | -| 可加性 | + - | 左到右 | -| 移位 | >> >>> << | 左到右 | -| 关系 | > >= < <= | 左到右 | -| 相等/不等 | == != | 左到右 | -| 位与 | & | 左到右 | -| 位异或 | ^ | 左到右 | -| 位或 | \| | 左到右 | -| 逻辑与 | && | 左到右 | -| 逻辑或 | \|\| | 左到右 | -| 条件判断 | ?: | 右到左 | -| 赋值 | = += -= \*= /= %= >>= <<= &= ^= \|= | 右到左 | -| 逗号 | , | 左到右 | - ---- - -## JSP 字面量 - -JSP 语言定义了以下几个字面量: - -- 布尔值(boolean):true 和 false; -- 整型(int):与 Java 中的一样; -- 浮点型(float):与 Java 中的一样; -- 字符串(string):以单引号或双引号开始和结束; -- Null:null。 diff --git a/docs/javaee/jsp/jsp-implicit-objects.md b/docs/javaee/jsp/jsp-implicit-objects.md deleted file mode 100644 index 0cd99495..00000000 --- a/docs/javaee/jsp/jsp-implicit-objects.md +++ /dev/null @@ -1,113 +0,0 @@ ---- -title: JSP 隐式对象 -date: 2017/11/08 -categories: -- javaee -tags: -- javaee -- jsp ---- - -JSP 隐式对象是 JSP 容器为每个页面提供的 Java 对象,开发者可以直接使用它们而不用显式声明。JSP 隐式对象也被称为预定义变量。 - -JSP 所支持的九大隐式对象: - -| **对象** | **描述** | -| ----------- | ------------------------------------------------------------------ | -| request | **HttpServletRequest**类的实例 | -| response | **HttpServletResponse**类的实例 | -| out | **PrintWriter**类的实例,用于把结果输出至网页上 | -| session | **HttpSession**类的实例 | -| application | **ServletContext**类的实例,与应用上下文有关 | -| config | **ServletConfig**类的实例 | -| pageContext | **PageContext**类的实例,提供对 JSP 页面所有对象以及命名空间的访问 | -| page | 类似于 Java 类中的 this 关键字 | -| Exception | **Exception**类的对象,代表发生错误的 JSP 页面中对应的异常对象 | - -## request 对象 - -`request`对象是`javax.servlet.http.HttpServletRequest` 类的实例。 - -每当客户端请求一个 JSP 页面时,JSP 引擎就会制造一个新的`request`对象来代表这个请求。 - -`request`对象提供了一系列方法来获取 HTTP 头信息,cookies,HTTP 方法等等。 - -## response 对象 - -`response`对象是`javax.servlet.http.HttpServletResponse`类的实例。 - -当服务器创建`request`对象时会同时创建用于响应这个客户端的`response`对象。 - -`response`对象也定义了处理 HTTP 头模块的接口。通过这个对象,开发者们可以添加新的 cookies,时间戳,HTTP 状态码等等。 - -## out 对象 - -`out`对象是`javax.servlet.jsp.JspWriter`类的实例,用来在`response`对象中写入内容。 - -最初的`JspWriter`类对象根据页面是否有缓存来进行不同的实例化操作。可以在`page`指令中使用`buffered='false'`属性来轻松关闭缓存。 - -`JspWriter`类包含了大部分`java.io.PrintWriter`类中的方法。不过,`JspWriter`新增了一些专为处理缓存而设计的方法。还有就是,`JspWriter`类会抛出`IOExceptions`异常,而`PrintWriter`不会。 - -下表列出了我们将会用来输出`boolean`,`char`,`int`,`double`,`String`,`object`等类型数据的重要方法: - -| **方法** | **描述** | -| ---------------------------- | -------------------------- | -| **out.print(dataType dt)** | 输出 Type 类型的值 | -| **out.println(dataType dt)** | 输出 Type 类型的值然后换行 | -| **out.flush()** | 刷新输出流 | - -## session 对象 - -`session`对象是`javax.servlet.http.HttpSession`类的实例。和 Java Servlets 中的`session`对象有一样的行为。 - -`session`对象用来跟踪在各个客户端请求间的会话。 - -## application 对象 - -`application`对象直接包装了 servlet 的`ServletContext`类的对象,是`javax.servlet.ServletContext`类的实例。 - -这个对象在 JSP 页面的整个生命周期中都代表着这个 JSP 页面。这个对象在 JSP 页面初始化时被创建,随着`jspDestroy()`方法的调用而被移除。 - -通过向`application`中添加属性,则所有组成您 web 应用的 JSP 文件都能访问到这些属性。 - -## config 对象 - -`config`对象是`javax.servlet.ServletConfig`类的实例,直接包装了 servlet 的`ServletConfig`类的对象。 - -这个对象允许开发者访问 Servlet 或者 JSP 引擎的初始化参数,比如文件路径等。 - -以下是 config 对象的使用方法,不是很重要,所以不常用: - -```jsp -config.getServletName(); -``` - -它返回包含在``元素中的 servlet 名字,注意,``元素在`WEB-INF\web.xml`文件中定义。 - -## pageContext 对象 - -`pageContext`对象是`javax.servlet.jsp.PageContext`类的实例,用来代表整个 JSP 页面。 - -这个对象主要用来访问页面信息,同时过滤掉大部分实现细节。 - -这个对象存储了`request`对象和`response`对象的引用。`application`对象,`config`对象,`session`对象,`out`对象可以通过访问这个对象的属性来导出。 - -`pageContext`对象也包含了传给 JSP 页面的指令信息,包括缓存信息,ErrorPage URL,页面 scope 等。 - -`PageContext`类定义了一些字段,包括 PAGE_SCOPE,REQUEST_SCOPE,SESSION_SCOPE, APPLICATION_SCOPE。它也提供了 40 余种方法,有一半继承自`javax.servlet.jsp.JspContext` 类。 - -其中一个重要的方法就是`removeArribute()`,它可接受一个或两个参数。比如,pageContext.removeArribute("attrName")移除四个 scope 中相关属性,但是下面这种方法只移除特定 scope 中的相关属性: - -```jsp -pageContext.removeAttribute("attrName", PAGE_SCOPE); -``` - -## page 对象 - -这个对象就是页面实例的引用。它可以被看做是整个 JSP 页面的代表。 - -`page`对象就是`this`对象的同义词。 - -## exception 对象 - -`exception`对象包装了从先前页面中抛出的异常信息。它通常被用来产生对出错条件的适当响应。 diff --git a/docs/javaee/jsp/jsp-introduction.md b/docs/javaee/jsp/jsp-introduction.md deleted file mode 100644 index 4b9e0a8f..00000000 --- a/docs/javaee/jsp/jsp-introduction.md +++ /dev/null @@ -1,152 +0,0 @@ ---- -title: JSP 概述 -date: 2017/11/08 -categories: -- javaee -tags: -- javaee -- jsp ---- - -## 什么是 Java Server Pages? - -`JSP`全称`Java Server Pages`,是一种动态网页开发技术。 - -它使用 JSP 标签在 HTML 网页中插入 Java 代码。标签通常以`<%`开头以`%>`结束。 - -JSP 是一种 Java servlet,主要用于实现 Java web 应用程序的用户界面部分。网页开发者们通过结合 HTML 代码、XHTML 代码、XML 元素以及嵌入 JSP 操作和命令来编写 JSP。 - -JSP 通过网页表单获取用户输入数据、访问数据库及其他数据源,然后动态地创建网页。 - -JSP 标签有多种功能,比如访问数据库、记录用户选择信息、访问 JavaBeans 组件等,还可以在不同的网页中传递控制信息和共享信息。 - -## 为什么使用 JSP? - -JSP 也是一种 Servlet,因此 JSP 能够完成 Servlet 能完成的任何工作。 - -JSP 程序与 CGI 程序有着相似的功能,但和 CGI 程序相比,JSP 程序有如下优势: - -- 性能更加优越,因为 JSP 可以直接在 HTML 网页中动态嵌入元素而不需要单独引用 CGI 文件。 -- 服务器调用的是已经编译好的 JSP 文件,而不像 CGI/Perl 那样必须先载入解释器和目标脚本。 -- JSP 基于 Java Servlets API,因此,JSP 拥有各种强大的企业级 Java API,包括 JDBC,JNDI,EJB,JAXP 等等。 -- JSP 页面可以与处理业务逻辑的 servlets 一起使用,这种模式被 Java servlet 模板引擎所支持。 - -最后,JSP 是 Java EE 不可或缺的一部分,是一个完整的企业级应用平台。这意味着 JSP 可以用最简单的方式来实现最复杂的应用。 - -## JSP 的优势 - -以下列出了使用 JSP 带来的其他好处: - -- 与 ASP 相比:JSP 有两大优势。首先,动态部分用 Java 编写,而不是 VB 或其他 MS 专用语言,所以更加强大与易用。第二点就是 JSP 易于移植到非 MS 平台上。 -- 与纯 Servlets 相比:JSP 可以很方便的编写或者修改 HTML 网页而不用去面对大量的 println 语句。 -- 与 SSI 相比:SSI 无法使用表单数据、无法进行数据库链接。 -- 与 JavaScript 相比:虽然 JavaScript 可以在客户端动态生成 HTML,但是很难与服务器交互,因此不能提供复杂的服务,比如访问数据库和图像处理等等。 -- 与静态 HTML 相比:静态 HTML 不包含动态信息。 - -# JSP 工作原理 - -**JSP 是一种 Servlet**,但工作方式和 Servlet 有所差别。 - -Servlet 是先将源代码编译为 class 文件后部署到服务器下的,**先编译后部署**。 - -Jsp 是先将源代码部署到服务器再编译,**先部署后编译**。 - -Jsp 会在客户端第一次请求 Jsp 文件时被编译为 HttpJspPage 类(Servlet 的一个子类)。该类会被服务器临时存放在服务器工作目录里。所以,第一次请求 Jsp 后,访问速度会变快就是这个道理。 - -## JSP 工作流程 - -网络服务器需要一个 JSP 引擎,也就是一个容器来处理 JSP 页面。容器负责截获对 JSP 页面的请求。本教程使用内嵌 JSP 容器的 Apache 来支持 JSP 开发。 - -JSP 容器与 Web 服务器协同合作,为 JSP 的正常运行提供必要的运行环境和其他服务,并且能够正确识别专属于 JSP 网页的特殊元素。 - -下图显示了 JSP 容器和 JSP 文件在 Web 应用中所处的位置。 - -![img](http://www.runoob.com/wp-content/uploads/2014/01/jsp-arch.jpg) - -### 工作步骤 - -以下步骤表明了 Web 服务器是如何使用 JSP 来创建网页的: - -- 就像其他普通的网页一样,您的浏览器发送一个 HTTP 请求给服务器。 -- Web 服务器识别出这是一个对 JSP 网页的请求,并且将该请求传递给 JSP 引擎。通过使用 URL 或者.jsp 文件来完成。 -- JSP 引擎从磁盘中载入 JSP 文件,然后将它们转化为 servlet。这种转化只是简单地将所有模板文本改用 println()语句,并且将所有的 JSP 元素转化成 Java 代码。 -- JSP 引擎将 servlet 编译成可执行类,并且将原始请求传递给 servlet 引擎。 -- Web 服务器的某组件将会调用 servlet 引擎,然后载入并执行 servlet 类。在执行过程中,servlet 产生 HTML 格式的输出并将其内嵌于 HTTP response 中上交给 Web 服务器。 -- Web 服务器以静态 HTML 网页的形式将 HTTP response 返回到您的浏览器中。 -- 最终,Web 浏览器处理 HTTP response 中动态产生的 HTML 网页,就好像在处理静态网页一样。 - -以上提及到的步骤可以用下图来表示: - -一般情况下,JSP 引擎会检查 JSP 文件对应的 servlet 是否已经存在,并且检查 JSP 文件的修改日期是否早于 servlet。如果 JSP 文件的修改日期早于对应的 servlet,那么容器就可以确定 JSP 文件没有被修改过并且 servlet 有效。这使得整个流程与其他脚本语言(比如 PHP)相比要高效快捷一些。 - -# JSP  生命周期 - -理解 JSP 底层功能的关键就是去理解它们所遵守的生命周期。 - -JSP 生命周期就是从创建到销毁的整个过程,类似于 servlet 生命周期,区别在于 JSP 生命周期还包括将 JSP 文件编译成 servlet。 - -以下是 JSP 生命周期中所走过的几个阶段: - -- **编译阶段:**servlet 容器编译 servlet 源文件,生成 servlet 类 -- **初始化阶段:**加载与 JSP 对应的 servlet 类,创建其实例,并调用它的初始化方法 -- **执行阶段:**调用与 JSP 对应的 servlet 实例的服务方法 -- **销毁阶段:**调用与 JSP 对应的 servlet 实例的销毁方法,然后销毁 servlet 实例 - -很明显,JSP 生命周期的四个主要阶段和 servlet 生命周期非常相似,下面给出图示: - -![img](http://www.runoob.com/wp-content/uploads/2014/01/jsp_life_cycle.jpg) - -## JSP 编译 - -当浏览器请求 JSP 页面时,JSP 引擎会首先去检查是否需要编译这个文件。如果这个文件没有被编译过,或者在上次编译后被更改过,则编译这个 JSP 文件。 - -编译的过程包括三个步骤: - -- 解析 JSP 文件。 -- 将 JSP 文件转为 servlet。 -- 编译 servlet。 - -## JSP 初始化 - -容器载入 JSP 文件后,它会在为请求提供任何服务前调用 jspInit()方法。如果您需要执行自定义的 JSP 初始化任务,复写 jspInit()方法就行了,就像下面这样: - -``` -public void jspInit(){ - // 初始化代码 -} -``` - -一般来讲程序只初始化一次,servlet 也是如此。通常情况下您可以在 jspInit()方法中初始化数据库连接、打开文件和创建查询表。 - -## JSP 执行 - -这一阶段描述了 JSP 生命周期中一切与请求相关的交互行为,直到被销毁。 - -当 JSP 网页完成初始化后,JSP 引擎将会调用\_jspService()方法。 - -\_jspService()方法需要一个 HttpServletRequest 对象和一个 HttpServletResponse 对象作为它的参数,就像下面这样: - -``` -void _jspService(HttpServletRequest request, - HttpServletResponse response) -{ - // 服务端处理代码 -} -``` - -\_jspService()方法在每个 request 中被调用一次并且负责产生与之相对应的 response,并且它还负责产生所有 7 个 HTTP 方法的回应,比如 GET、POST、DELETE 等等。 - -## JSP 清理 - -JSP 生命周期的销毁阶段描述了当一个 JSP 网页从容器中被移除时所发生的一切。 - -jspDestroy()方法在 JSP 中等价于 servlet 中的销毁方法。当您需要执行任何清理工作时复写 jspDestroy()方法,比如释放数据库连接或者关闭文件夹等等。 - -jspDestroy()方法的格式如下: - -``` -public void jspDestroy() -{ - // 清理代码 -} -``` diff --git a/docs/javaee/jsp/jsp-locale.md b/docs/javaee/jsp/jsp-locale.md deleted file mode 100644 index 8a200ec5..00000000 --- a/docs/javaee/jsp/jsp-locale.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -title: JSP 国际化 -date: 2017/11/08 -categories: -- javaee -tags: -- javaee -- jsp ---- - -在开始前,需要解释几个重要的概念: - -- 国际化(i18n):表明一个页面根据访问者的语言或国家来呈现不同的翻译版本。 -- 本地化(l10n):向网站添加资源,以使它适应不同的地区和文化。比如网站的印度语版本。 -- 区域:这是一个特定的区域或文化,通常认为是一个语言标志和国家标志通过下划线连接起来。比如"en_US"代表美国英语地区。 - -如果想要建立一个全球化的网站,就需要关心一系列项目。本章将会详细告诉您如何处理国际化问题,并给出了一些例子来加深理解。 - -JSP 容器能够根据 request 的 locale 属性来提供正确地页面版本。接下来给出了如何通过 request 对象来获得 Locale 对象的语法: - -``` -java.util.Locale request.getLocale() -``` - -## 检测 Locale - -下表列举出了 Locale 对象中比较重要的方法,用于检测 request 对象的地区,语言,和区域。所有这些方法都会在浏览器中显示国家名称和语言名称: - -| **序号** | **方法** & **描述** | -| -------- | --------------------------------------------------------------------------------- | -| 1 | **String getCountry()**返回国家/地区码的英文大写,或 ISO 3166 2-letter 格式的区域 | -| 2 | **String getDisplayCountry()**返回要显示给用户的国家名称 | -| 3 | **String getLanguage()**返回语言码的英文小写,或 ISO 639 格式的区域 | -| 4 | **String getDisplayLanguage()**返回要给用户看的语言名称 | -| 5 | **String getISO3Country()**返回国家名称的 3 字母缩写 | -| 6 | **String getISO3Language()**返回语言名称的 3 字母缩写 | - -## 语言设置 - -JSP 可以使用西欧语言来输出一个页面,比如英语,西班牙语,德语,法语,意大利语等等。由此可见,设置`Content-Language`信息头来正确显示所有字符是很重要的。 - -第二点就是,需要使用 HTML 字符实体来显示特殊字符,比如"ñ" 代表的是"?","¡"代表的是 "?" : - -## 区域特定日期 - -可以使用`java.text.DateFormat`类和它的静态方法`getDateTimeInstance()`来格式化日期和时间。接下来的这个例子显示了如何根据指定的区域来格式化日期和时间: - -## 区域特定货币 - -可以使用`java.text.NumberFormat`类和它的静态方法`getCurrencyInstance()`来格式化数字。比如在区域特定货币中的 long 型和 double 型。接下来的例子显示了如何根据指定的区域来格式化货币: - -## 区域特定百分比 - -可以使用`java.text.NumberFormat`类和它的静态方法`getPercentInstance()`来格式化百分比。接下来的例子告诉我们如何根据指定的区域来格式化百分比: - -# 参考资料 - -参考代码见: - -``` -javaee-notes\javaee-jsp\src\main\webapp\examples\locale -``` diff --git a/docs/javaee/jsp/taglib.md b/docs/javaee/jsp/taglib.md deleted file mode 100644 index 493fa6ab..00000000 --- a/docs/javaee/jsp/taglib.md +++ /dev/null @@ -1,15 +0,0 @@ - - -自定义标签是用户定义的JSP语言元素。 - -当JSP页面包含一个自定义标签时将被转化为servlet,标签转化为对被称为tag handler的对象的操作,即当servlet执行时Web container调用那些操作。 - -JSP标签扩展可以让你创建新的标签并且可以直接插入到一个JSP页面。 JSP 2.0规范中引入Simple Tag Handlers来编写这些自定义标记。 - -编写自定义步骤的方法 - -任何一个标签都对应一个Java类,该类必须实现Tag接口。 - -声明一个tld标签库描述文件 - -如果tld文件位于/WEB-INF目录下面,Tomcat会自动加载tld文件中的标签库。如果是某些不支持自动加载或者放于其他位置的tld文件,可以在web.xml中配置。 \ No newline at end of file diff --git a/docs/javaee/jstl/jstl-core.md b/docs/javaee/jstl/jstl-core.md deleted file mode 100644 index c51aa609..00000000 --- a/docs/javaee/jstl/jstl-core.md +++ /dev/null @@ -1,350 +0,0 @@ ---- -title: JSTL 核心标签 -date: 2017/11/08 -categories: -- javaee -tags: -- javaee -- jstl ---- - -核心标签是最常用的 JSTL 标签。引用核心标签库的语法如下: - -```jsp -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -``` - -| 标签 | 描述 | -| --------------- | --------------------------------------------------------------------------------- | -| `` | 用于在 JSP 中显示数据,就像<%= ... > | -| `` | 用于保存数据 | -| `` | 用于删除数据 | -| `` | 用来处理产生错误的异常状况,并且将错误信息储存起来 | -| `` | 与我们在一般程序中用的 if 一样 | -| `` | 本身只当做``和``的父标签 | -| `` | ``的子标签,用来判断条件是否成立 | -| `` | ``的子标签,接在``标签后,当``标签判断为 false 时被执行 | -| `` | 检索一个绝对或相对 URL,然后将其内容暴露给页面 | -| `` | 基础迭代标签,接受多种集合类型 | -| `` | 根据指定的分隔符来分隔内容并迭代输出 | -| `` | 用来给包含或重定向的页面传递参数 | -| `` | 重定向至一个新的 URL. | -| `` | 使用可选的查询参数来创造一个 URL | - -## `` 标签 - -``标签用来显示一个表达式的结果,与`<%= %>`作用相似,它们的区别就是``标签可以直接通过"."操作符来访问属性。 - -举例来说,如果想要访问 customer.address.street,只需要这样写: - -```jsp - -``` - -``标签会自动忽略 XML 标记字符,所以它们不会被当做标签来处理。 - -### 语法格式 - -```jsp - -``` - -### 属性 - -``标签有如下属性: - -| **属性** | **描述** | **是否必要** | **默认值** | -| --------- | --------------------- | ------------ | ------------ | -| value | 要输出的内容 | 是 | 无 | -| default | 输出的默认值 | 否 | 主体中的内容 | -| escapeXml | 是否忽略 XML 特殊字符 | 否 | true | - -## `` 标签 - -``标签用于设置变量值和对象属性。 - -``标签就是``行为标签的孪生兄弟。 - -这个标签之所以很有用呢,是因为它会计算表达式的值,然后使用计算结果来设置 JavaBean 对象或 java.util.Map 对象的值。 - -### 语法格式 - -```jsp - -``` - -### 属性 - -``标签有如下属性: - -| **属性** | **描述** | **是否必要** | **默认值** | -| -------- | ---------------------- | ------------ | ---------- | -| value | 要存储的值 | 否 | 主体的内容 | -| target | 要修改的属性所属的对象 | 否 | 无 | -| property | 要修改的属性 | 否 | 无 | -| var | 存储信息的变量 | 否 | 无 | -| scope | var 属性的作用域 | 否 | Page | - -如果指定了 target 属性,那么 property 属性也需要被指定。 - -## `` 标签 - -``标签用于移除一个变量,可以指定这个变量的作用域,若未指定,则默认为变量第一次出现的作用域。 - -这个标签不是特别有用,不过可以用来确保 JSP 完成清理工作。 - -### 语法格式 - -```jsp - -``` - -### 属性 - -`c:remove>`标签有如下属性: - -| **属性** | **描述** | **是否必要** | **默认值** | -| -------- | ---------------- | ------------ | ---------- | -| var | 要移除的变量名称 | 是 | 无 | -| scope | 变量所属的作用域 | 否 | 所有作用域 | - -## `` 标签 - -`` 标签主要用来处理产生错误的异常状况,并且将错误信息储存起来。 - -### 语法格式 - -```jsp - -... - -``` - -### 属性 - -``标签有如下属性: - -| 属性 | 描述 | 是否必要 | 默认值 | -| ---- | ---------------------- | -------- | ------ | -| var | 用来储存错误信息的变量 | 否 | None | - -## `` 标签 - -``标签判断表达式的值,如果表达式的值为 true 则执行其主体内容。 - -### 语法格式 - -```jsp - - ... - -``` - -### 属性 - -``标签有如下属性: - -| **属性** | **描述** | **是否必要** | **默认值** | -| -------- | ---------------------- | ------------ | ---------- | -| test | 条件 | 是 | 无 | -| var | 用于存储条件结果的变量 | 否 | 无 | -| scope | var 属性的作用域 | 否 | page | - -## ``, ``, `` 标签 - -``标签与 Java switch 语句的功能一样,用于在众多选项中做出选择。 - -switch 语句中有 case,而``标签中对应有``,switch 语句中有 default,而``标签中有``。 - -### 语法格式 - -```jsp - - - ... - - - ... - - ... - ... - - ... - - -``` - -### 属性 - -- ``标签没有属性。 -- ``标签只有一个属性,在下表中有给出。 -- ``标签没有属性。 - -``标签的属性如下: - -| **属性** | **描述** | **是否必要** | **默认值** | -| -------- | -------- | ------------ | ---------- | -| test | 条件 | 是 | 无 | - -## `` 标签 - -``标签提供了所有``行为标签所具有的功能,同时也允许包含绝对 URL。 - -举例来说,使用``标签可以包含一个 FTP 服务器中不同的网页内容。 - -### 语法格式 - -```jsp - -``` - -### 属性 - -``标签有如下属性: - -| **属性** | **描述** | **是否必要** | **默认值** | -| ------------ | --------------------------------------------------------------------- | ------------ | ------------ | -| url | 待导入资源的 URL,可以是相对路径和绝对路径,并且可以导入其他主机资源 | 是 | 无 | -| context | 当使用相对路径访问外部 context 资源时,context 指定了这个资源的名字。 | 否 | 当前应用程序 | -| charEncoding | 所引入的数据的字符编码集 | 否 | ISO-8859-1 | -| var | 用于存储所引入的文本的变量 | 否 | 无 | -| scope | var 属性的作用域 | 否 | page | -| varReader | 可选的用于提供 java.io.Reader 对象的变量 | 否 | 无 | - -## ``, `` 标签 - -[![JSP 标准标签库](http://www.runoob.com/images/up.gif)JSP 标准标签库](http://www.runoob.com/jsp/jsp-jstl.html) - -这些标签封装了 Java 中的 for,while,do-while 循环。 - -相比而言,``标签是更加通用的标签,因为它迭代一个集合中的对象。 - -``标签通过指定分隔符将字符串分隔为一个数组然后迭代它们。 - -### `forEach` 语法格式 - -```jsp - - - ... -``` - -### `forTokens` 语法格式 - -```jsp - -``` - -### 属性 - -``标签有如下属性: - -| **属性** | **描述** | **是否必要** | **默认值** | -| --------- | ------------------------------------------ | ------------ | ------------ | -| items | 要被循环的信息 | 否 | 无 | -| begin | 开始的元素(0=第一个元素,1=第二个元素) | 否 | 0 | -| end | 最后一个元素(0=第一个元素,1=第二个元素) | 否 | Last element | -| step | 每一次迭代的步长 | 否 | 1 | -| var | 代表当前条目的变量名称 | 否 | 无 | -| varStatus | 代表循环状态的变量名称 | 否 | 无 | - -``标签与``标签有相似的属性,不过``还有另一个属性: - -| **属性** | **描述** | **是否必要** | **默认值** | -| -------- | -------- | ------------ | ---------- | -| delims | 分隔符 | 是 | 无 | - -## `` 标签 - -``标签用于在``标签中指定参数,而且与 URL 编码相关。 - -在``标签内,name 属性表明参数的名称,value 属性表明参数的值。 - -### 语法格式 - -```jsp - -``` - -### 属性 - -``标签有如下属性: - -| **属性** | **描述** | **是否必要** | **默认值** | -| -------- | ------------------------ | ------------ | ---------- | -| name | URL 中要设置的参数的名称 | 是 | 无 | -| value | 参数的值 | 否 | Body | - -## `` 标签 - -``标签通过自动重写 URL 来将浏览器重定向至一个新的 URL,它提供内容相关的 URL,并且支持 c:param 标签。 - -### 语法格式 - -``` - -``` - -### 属性 - -``标签有如下属性: - -| **属性** | **描述** | **是否必要** | **默认值** | -| -------- | -------------------------------- | ------------ | ------------ | -| url | 目标 URL | 是 | 无 | -| context | 紧接着一个本地网络应用程序的名称 | 否 | 当前应用程序 | - -## `` 标签 - -``标签将 URL 格式化为一个字符串,然后存储在一个变量中。 - -这个标签在需要的时候会自动重写 URL。 - -var 属性用于存储格式化后的 URL。 - -``标签只是用于调用`response.encodeURL()`方法的一种可选的方法。它真正的优势在于提供了合适的 URL 编码,包括``中指定的参数。 - -### 语法格式 - -```jsp - -``` - -### 属性 - -``标签有如下属性: - -| **属性** | **描述** | **是否必要** | **默认值** | -| -------- | ---------------------- | ------------ | ------------- | -| value | 基础 URL | 是 | 无 | -| context | 本地网络应用程序的名称 | 否 | 当前应用程序 | -| var | 代表 URL 的变量名 | 否 | Print to page | -| scope | var 属性的作用域 | 否 | Page | diff --git a/docs/javaee/jstl/jstl-format.md b/docs/javaee/jstl/jstl-format.md deleted file mode 100644 index 1f450358..00000000 --- a/docs/javaee/jstl/jstl-format.md +++ /dev/null @@ -1,362 +0,0 @@ ---- -title: JSTL 格式化标签 -date: 2017/11/08 -categories: -- javaee -tags: -- javaee -- jstl ---- - -JSTL 格式化标签用来格式化并输出文本、日期、时间、数字。引用格式化标签库的语法如下: - -```jsp -<%@ taglib prefix="fmt" - uri="http://java.sun.com/jsp/jstl/fmt" %> -``` - -| 标签 | 描述 | -| ----------------------- | ---------------------------------------- | -| `` | 使用指定的格式或精度格式化数字 | -| `` | 解析一个代表着数字,货币或百分比的字符串 | -| `` | 使用指定的风格或模式格式化日期和时间 | -| `` | 解析一个代表着日期或时间的字符串 | -| `` | 绑定资源 | -| `` | 指定地区 | -| `` | 绑定资源 | -| `` | 指定时区 | -| `` | 指定时区 | -| `` | 显示资源配置文件信息 | -| `` | 设置 request 的字符编码 | - -## ``标签 - -``标签用于格式化数字,百分比,货币。 - -### 语法格式 - -```jsp - -``` - -### 属性 - -``标签有如下属性: - -| **属性** | **描述** | **是否必要** | **默认值** | -| ----------------- | ---------------------------------- | ------------ | -------------- | -| value | 要显示的数字 | 是 | 无 | -| type | NUMBER,CURRENCY,或 PERCENT 类型 | 否 | Number | -| pattern | 指定一个自定义的格式化模式用与输出 | 否 | 无 | -| currencyCode | 货币码(当 type="currency"时) | 否 | 取决于默认区域 | -| currencySymbol | 货币符号 (当 type="currency"时) | 否 | 取决于默认区域 | -| groupingUsed | 是否对数字分组 (TRUE 或 FALSE) | 否 | true | -| maxIntegerDigits | 整型数最大的位数 | 否 | 无 | -| minIntegerDigits | 整型数最小的位数 | 否 | 无 | -| maxFractionDigits | 小数点后最大的位数 | 否 | 无 | -| minFractionDigits | 小数点后最小的位数 | 否 | 无 | -| var | 存储格式化数字的变量 | 否 | Print to page | -| scope | var 属性的作用域 | 否 | page | - -如果`type`属性为 percent 或 number,那么您就可以使用其它几个格式化数字属性。 - -`maxIntegerDigits`属性和`minIntegerDigits`属性允许您指定整数的长度。若实际数字超过了`maxIntegerDigits`所指定的最大值,则数字将会被截断。 - -有一些属性允许您指定小数点后的位数。`minFractionalDigits`属性和`maxFractionalDigits`属性允许您指定小数点后的位数。若实际的数字超出了所指定的范围,则这个数字会被截断。 - -数字分组可以用来在每三个数字中插入一个逗号。`groupingIsUsed`属性用来指定是否使用数字分组。当与`minIntegerDigits`属性一同使用时,就必须要很小心地来获取预期的结果了。 - -您或许会使用`pattern`属性。这个属性可以让您在对数字编码时包含指定的字符。接下来的表格中列出了这些字符。 - -| **符号** | **描述** | -| -------- | -------------------------------- | -| 0 | 代表一位数字 | -| E | 使用指数格式 | -| # | 代表一位数字,若没有则显示 0 | -| . | 小数点 | -| , | 数字分组分隔符 | -| ; | 分隔格式 | -| - | 使用默认负数前缀 | -| % | 百分数 | -| ? | 千分数 | -| ¤ | 货币符号,使用实际的货币符号代替 | -| X | 指定可以作为前缀或后缀的字符 | -| ' | 在前缀或后缀中引用特殊字符 | - -## `` 标签 - -``标签用来解析数字,百分数,货币。 - -### 语法格式 - -```jsp - -``` - -### 属性 - -``标签有如下属性: - -| **属性** | **描述** | **是否必要** | **默认值** | -| ----------- | ----------------------------------------- | ------------ | ------------- | -| value | 要解析的数字 | 否 | Body | -| type | NUMBER,,CURRENCY,或 PERCENT | 否 | number | -| parseLocale | 解析数字时所用的区域 | 否 | 默认区域 | -| integerOnly | 是否只解析整型数(true)或浮点数(false) | 否 | false | -| pattern | 自定义解析模式 | 否 | 无 | -| timeZone | 要显示的日期的时区 | 否 | 默认时区 | -| var | 存储待解析数字的变量 | 否 | Print to page | -| scope | var 属性的作用域 | 否 | page | - -`pattern`属性与``标签中的`pattern`有相同的作用。在解析时,`pattern`属性告诉解析器期望的格式。 - -## `` 标签 - -``标签用于使用不同的方式格式化日期。 - -### 语法格式 - -```jsp - -``` - -### 属性 - -``标签有如下属性: - -| 属性 | 描述 | 是否必要 | 默认值 | -| --------- | ------------------------------------- | -------- | ---------- | -| value | 要显示的日期 | 是 | 无 | -| type | DATE, TIME, 或 BOTH | 否 | date | -| dateStyle | FULL, LONG, MEDIUM, SHORT, 或 DEFAULT | 否 | default | -| timeStyle | FULL, LONG, MEDIUM, SHORT, 或 DEFAULT | 否 | default | -| pattern | 自定义格式模式 | 否 | 无 | -| timeZone | 显示日期的时区 | 否 | 默认时区 | -| var | 存储格式化日期的变量名 | 否 | 显示在页面 | -| scope | 存储格式化日志变量的范围 | 否 | 页面 | - -`` 标签格式模式 - -| 代码 | 描述 | 实例 | -| ---- | ------------------------------------------------------------------------- | -------------------------- | -| G | 时代标志 | AD | -| y | 不包含纪元的年份。如果不包含纪元的年份小于 10,则显示不具有前导零的年份。 | 2002 | -| M | 月份数字。一位数的月份没有前导零。 | April & 04 | -| d | 月中的某一天。一位数的日期没有前导零。 | 20 | -| h | 12 小时制的小时。一位数的小时数没有前导零。 | 12 | -| H | 24 小时制的小时。一位数的小时数没有前导零。 | 0 | -| m | 分钟。一位数的分钟数没有前导零。 | 45 | -| s | 秒。一位数的秒数没有前导零。 | 52 | -| S | 毫秒 | 970 | -| E | 周几 | Tuesday | -| D | 一年中的第几天 | 180 | -| F | 一个月中的第几个周几 | 2 (一个月中的第二个星期三) | -| w | 一年中的第几周 r | 27 | -| W | 一个月中的第几周 | 2 | -| a | a.m./p.m. 指示符 | PM | -| k | 小时(12 小时制的小时) | 24 | -| K | 小时(24 小时制的小时) | 0 | -| z | 时区 | 中部标准时间 | -| ' | | 转义文本 | -| '' | | 单引号 | - -## `` 标签 - -`` 标签用于解析日期。 - -### 语法格式 - -```jsp - -``` - -### 属性 - -``标签有如下属性: - -| 属性 | 描述 | 是否必要 | 默认值 | -| --------- | ------------------------------------- | -------- | ---------- | -| value | 要显示的日期 | 是 | 无 | -| type | DATE, TIME, 或 BOTH | 否 | date | -| dateStyle | FULL, LONG, MEDIUM, SHORT, 或 DEFAULT | 否 | default | -| timeStyle | FULL, LONG, MEDIUM, SHORT, 或 DEFAULT | 否 | default | -| pattern | 自定义格式模式 | 否 | 无 | -| timeZone | 显示日期的时区 | 否 | 默认时区 | -| var | 存储格式化日期的变量名 | 否 | 显示在页面 | -| scope | 存储格式化日志变量的范围 | 否 | 页面 | - -属性设置我们需要的输出的时间格式。 - -## `` 标签 - -``标签将指定的资源束对出现在``标签中的``标签可用。这可以使您省去为每个``标签指定资源束的很多步骤。 - -举例来说,下面的两个``块将产生同样的输出: - -```jsp - - - - - - - -``` - -### 语法格式 - -```jsp - -``` - -### 属性 - -``标签有如下属性: - -| **属性** | **描述** | **是否必要** | **默认值** | -| -------- | -------------------------------------- | ------------ | ---------- | -| basename | 指定被载入的资源束的基础名称 | 是 | 无 | -| prefix | 指定``标签 key 属性的前缀 | 否 | 无 | - -## `` 标签 - -``标签用来将给定的区域存储在 locale 配置变量中。 - -### 语法格式 - -```jsp - -``` - -### 属性 - -``标签有如下属性: - -| **属性** | **描述** | **是否必要** | **默认值** | -| -------- | ------------------------------------- | ------------ | ---------- | -| value | 指定 ISO-639 语言码和 ISO-3166 国家码 | 是 | en_US | -| variant | 特定浏览器变体 | 否 | 无 | -| scope | Locale 配置变量的作用域 | 否 | Page | - -## `` 标签 - -``标签用来指定时区,供其它标签使用。 - -### 语法格式 - -```jsp - -``` - -### 属性 - -``标签有如下属性: - -| **属性** | **描述** | **是否必要** | **默认值** | -| -------- | -------- | ------------ | ---------- | -| value | 时区 | 是 | 无 | - -## `` 标签 - -``标签用来复制一个时区对象至指定的作用域。 - -### 语法格式 - -```jsp - -``` - -### 属性 - -``标签有如下属性: - -| **属性** | **描述** | **是否必要** | **默认值** | -| -------- | ------------------ | ------------ | --------------- | -| value | 时区 | 是 | 无 | -| var | 存储新时区的变量名 | 否 | Replace default | -| scope | 变量的作用域 | 否 | Page | - -## `` 标签 - -``标签映射一个关键字给局部消息,然后执行参数替换。 - -### 语法格式 - -```jsp - -``` - -### 属性 - -``标签有如下属性: - -| **属性** | **描述** | **是否必要** | **默认值** | -| -------- | -------------------- | ------------ | ------------- | -| key | 要检索的消息关键字 | 否 | Body | -| bundle | 要使用的资源束 | 否 | 默认资源束 | -| var | 存储局部消息的变量名 | 否 | Print to page | -| scope | var 属性的作用域 | 否 | Page | - -## `` 标签 - -``标签用来指定返回给 Web 应用程序的表单编码类型。 - -### 语法格式 - -```jsp - -``` - -### 属性 - -``标签有如下属性: - -| **属性** | **描述** | **是否必要** | **默认值** | -| -------- | --------------------------------------- | ------------ | ---------- | -| key | 字符编码集的名称,用于解码 request 参数 | 是 | 无 | - -使用``标签来指定字符集,用于解码来自表单的数据。在字符集不是 ISO-8859-1 时必须使用这个标签。由于大多数浏览器在它们的请求中不包含 Content-Type 头,所以需要这个标签。 - -``标签的目的就是用来指定请求的 Content-Type。您必须指定一个 Content-Type,就算 response 是通过 Page 指令的 contentType 属性来编码。这是因为 response 的实际区域可能与 Page 指令所指定的不同。 - -如果页面包含 I18N-capable 格式行为用于设置 response 的 locale 属性(通过调用 ServletResponse.setLocale()方法),任何在页面中指定的编码集将会被覆盖。 diff --git a/docs/javaee/jstl/jstl-function.md b/docs/javaee/jstl/jstl-function.md deleted file mode 100644 index 1675bc1b..00000000 --- a/docs/javaee/jstl/jstl-function.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -title: JSTL 函数 -date: 2017/11/08 -categories: -- javaee -tags: -- javaee -- jstl ---- - -JSTL 包含一系列标准函数,大部分是通用的字符串处理函数。引用 JSTL 函数库的语法如下: - -```jsp -<%@ taglib prefix="fn" - uri="http://java.sun.com/jsp/jstl/functions" %> -``` - -| 函数 | 描述 | -| ------------------------------------------------------------------------------------------ | -------------------------------------------------------- | -| [fn:contains()](http://www.runoob.com/jsp/jstl-function-contains.html) | 测试输入的字符串是否包含指定的子串 | -| [fn:containsIgnoreCase()](http://www.runoob.com/jsp/jstl-function-containsignoreCase.html) | 测试输入的字符串是否包含指定的子串,大小写不敏感 | -| [fn:endsWith()](http://www.runoob.com/jsp/jstl-function-endswith.html) | 测试输入的字符串是否以指定的后缀结尾 | -| [fn:escapeXml()](http://www.runoob.com/jsp/jstl-function-escapexml.html) | 跳过可以作为 XML 标记的字符 | -| [fn:indexOf()](http://www.runoob.com/jsp/jstl-function-indexof.html) | 返回指定字符串在输入字符串中出现的位置 | -| [fn:join()](http://www.runoob.com/jsp/jstl-function-join.html) | 将数组中的元素合成一个字符串然后输出 | -| [fn:length()](http://www.runoob.com/jsp/jstl-function-length.html) | 返回字符串长度 | -| [fn:replace()](http://www.runoob.com/jsp/jstl-function-replace.html) | 将输入字符串中指定的位置替换为指定的字符串然后返回 | -| [fn:split()](http://www.runoob.com/jsp/jstl-function-split.html) | 将字符串用指定的分隔符分隔然后组成一个子字符串数组并返回 | -| [fn:startsWith()](http://www.runoob.com/jsp/jstl-function-startswith.html) | 测试输入字符串是否以指定的前缀开始 | -| [fn:substring()](http://www.runoob.com/jsp/jstl-function-substring.html) | 返回字符串的子集 | -| [fn:substringAfter()](http://www.runoob.com/jsp/jstl-function-substringafter.html) | 返回字符串在指定子串之后的子集 | -| [fn:substringBefore()](http://www.runoob.com/jsp/jstl-function-substringbefore.html) | 返回字符串在指定子串之前的子集 | -| [fn:toLowerCase()](http://www.runoob.com/jsp/jstl-function-tolowercase.html) | 将字符串中的字符转为小写 | -| [fn:toUpperCase()](http://www.runoob.com/jsp/jstl-function-touppercase.html) | 将字符串中的字符转为大写 | -| [fn:trim()](http://www.runoob.com/jsp/jstl-function-trim.html) | 移除首位的空白符 | diff --git a/docs/javaee/jstl/jstl-sql.md b/docs/javaee/jstl/jstl-sql.md deleted file mode 100644 index 928f23c6..00000000 --- a/docs/javaee/jstl/jstl-sql.md +++ /dev/null @@ -1,135 +0,0 @@ ---- -title: JSTL SQL标签 -date: 2017/11/08 -categories: -- javaee -tags: -- javaee -- jstl ---- - -JSTL SQL 标签库提供了与关系型数据库(Oracle,MySQL,SQL Server 等等)进行交互的标签。引用 SQL 标签库的语法如下: - -```jsp -<%@ taglib prefix="sql" - uri="http://java.sun.com/jsp/jstl/sql" %> -``` - -| 标签 | 描述 | -| --------------------- | ---------------------------------------------------------------------------- | -| `` | 指定数据源 | -| `` | 运行 SQL 查询语句 | -| `` | 运行 SQL 更新语句 | -| `` | 将 SQL 语句中的参数设为指定值 | -| `` | 将 SQL 语句中的日期参数设为指定的 java.util.Date  对象值 | -| `` | 在共享数据库连接中提供嵌套的数据库行为元素,将所有语句以一个事务的形式来运行 | - -## `` 标签 - -``标签用来配置数据源或者将数据源信息存储在某作用域的变量中,用来作为其它 JSTL 数据库操作的数据源。 - -### 语法格式 - -```jsp - -``` - -### 属性 - -``标签有如下属性: - -| **属性** | **描述** | **是否必要** | **默认值** | -| ---------- | --------------------- | ------------ | ---------- | -| driver | 要注册的 JDBC 驱动 | 否 | 无 | -| url | 数据库连接的 JDBC URL | 否 | 无 | -| user | 数据库用户名 | 否 | 无 | -| password | 数据库密码 | 否 | 无 | -| dataSource | 事先准备好的数据库 | 否 | 无 | -| var | 代表数据库的变量 | 否 | 默认设置 | -| scope | var 属性的作用域 | 否 | Page | - -## `` 标签 - -``标签用来执行一个没有返回值的 SQL 语句,比如 SQL INSERT,UPDATE,DELETE 语句。 - -### 语法格式 - -```jsp - -``` - -### 属性 - -``标签有如下属性: - -| **属性** | **描述** | **是否必要** | **默认值** | -| ---------- | -------------------------------------------- | ------------ | ---------- | -| sql | 需要执行的 SQL 命令(不返回 ResultSet 对象) | 否 | Body | -| dataSource | 所使用的数据库连接(覆盖默认值) | 否 | 默认数据库 | -| var | 用来存储所影响行数的变量 | 否 | 无 | -| scope | var 属性的作用域 | 否 | Page | - -## `` 标签 - -``标签与``标签和``标签嵌套使用,用来提供一个值占位符。如果是一个 null 值,则将占位符设为 SQL NULL。 - -### 语法格式 - -```jsp - -``` - -### 属性 - -``标签有如下属性: - -| **属性** | **描述** | **是否必要** | **默认值** | -| -------- | ---------------- | ------------ | ---------- | -| value | 需要设置的参数值 | 否 | Body | - -## `` 标签 - -``标签与``标签和``标签嵌套使用,用来提供日期和时间的占位符。如果是一个 null 值,则将占位符设为 SQL NULL。 - -### 语法格式 - -```jsp - -``` - -### 属性 - -``标签有如下属性: - -| **属性** | **描述** | **是否必要** | **默认值** | -| -------- | -------------------------------------------------------------- | ------------ | ---------- | -| value | 需要设置的日期参数(java.util.Date) | 否 | Body | -| type | DATE (只有日期),TIME(只有时间), TIMESTAMP (日期和时间) | 否 | TIMESTAMP | - -## `` 标签 - -``标签用来将``标签和``标签封装至事务中。可以将大量的``和``操作装入``中,使它们成为单一的事务。 - -它确保对数据库的修改不是被提交就是被回滚。 - -### 语法格式 - -```jsp - -``` - -### 属性 - -``标签有如下属性: - -| **属性** | **描述** | **是否必要** | **默认值** | -| ---------- | ------------------------------------------------------------------------------------ | ------------ | ---------- | -| dataSource | 所使用的数据库(覆盖默认值) | 否 | 默认数据库 | -| isolation | 事务隔离等级 (READ_COMMITTED,,READ_UNCOMMITTED, REPEATABLE_READ 或 SERIALIZABLE) | 否 | 数据库默认 | diff --git a/docs/javaee/jstl/jstl-xml.md b/docs/javaee/jstl/jstl-xml.md deleted file mode 100644 index 3572e27c..00000000 --- a/docs/javaee/jstl/jstl-xml.md +++ /dev/null @@ -1,246 +0,0 @@ ---- -title: JSTL XML标签 -date: 2017/11/08 -categories: -- javaee -tags: -- javaee -- jstl ---- - -JSTL XML 标签库提供了创建和操作 XML 文档的标签。引用 XML 标签库的语法如下: - -```jsp -<%@ taglib prefix="x" - uri="http://java.sun.com/jsp/jstl/xml" %> -``` - -在使用 xml 标签前,你必须将 XML 和 XPath 的相关包拷贝至你的`\lib`下: - -- **XercesImpl.jar**下载地址: [http://www.apache.org/dist/xerces/j/](http://www.apache.org/dist/xerces/j/) - -- **xalan.jar**下载地址: [http://xml.apache.org/xalan-j/index.html](http://xml.apache.org/xalan-j/index.html) - -| 标签 | 描述 | -| --------------- | ----------------------------------------------------------- | -| `` | 与`<%= ... >`,类似,不过只用于 XPath 表达式 | -| `` | 解析 XML 数据 | -| `` | 设置 XPath 表达式 | -| `` | 判断 XPath 表达式,若为真,则执行本体中的内容,否则跳过本体 | -| `` | 迭代 XML 文档中的节点 | -| `` | ``和``的父标签 | -| `` | ``的子标签,用来进行条件判断 | -| `` | ``的子标签,当``判断为 false 时被执行 | -| `` | 将 XSL 转换应用在 XML 文档中 | -| `` | 与``共同使用,用于设置 XSL 样式表 | - -## `` 标签 - -``标签显示 XPath 表达式的结果,与`<%= %>`功能相似。 - -### 语法格式 - -```jsp - -``` - -### 属性 - -``标签有如下属性: - -| **属性** | **描述** | **是否必要** | **默认值** | -| --------- | -------------------------------------------- | ------------ | ---------- | -| select | 需要计算的 XPath 表达式,通常使用 XPath 变量 | 是 | 无 | -| escapeXml | 是否忽略 XML 特殊字符 | 否 | true | - -## `` 标签 - -``标签用来解析属性中或标签主体中的 XML 数据。 - -### 语法格式 - -```jsp - -``` - -### 属性 - -``标签有如下属性: - -| **属性** | **描述** | **是否必要** | **默认值** | -| -------- | -------------------------------------------- | ------------ | ---------- | -| var | 包含已解析 XML 数据的变量 | 否 | 无 | -| xml | 需要解析的文档的文本内容(String 或 Reader) | 否 | Body | -| systemId | 系统标识符 URI,用来解析文档 | 否 | 无 | -| filter | 应用于源文档的过滤器 | 否 | 无 | -| doc | 需要解析的 XML 文档 | 否 | Page | -| scope | var 属性的作用域 | 否 | Page | -| varDom | 包含已解析 XML 数据的变量 | 否 | Page | -| scopeDom | varDom 属性的作用域 | 否 | Page | - -## `` 标签 - -``标签为 XPath 表达式的值设置一个变量。 - -如果 XPath 表达式的值是 boolean 类型,则``将会设置一个 java.lang.Boolean 对象,若是字符串,则设置一个 java.lang.String 对象,若是数字,则设置一个 java.lang.Number 对象。 - -### 语法格式 - -```jsp - -``` - -### 属性 - -``标签有如下属性: - -| **属性** | **描述** | **是否必要** | **默认值** | -| -------- | ------------------------- | ------------ | ---------- | -| var | 代表 XPath 表达式值得变量 | 是 | Body | -| select | 需要计算的 XPath 表达式 | 否 | 无 | -| scope | var 属性的作用域 | 否 | Page | - -## `` 标签 - -``标签用于判断一个 XPath 表达式的值,若为真,则执行其主体中的内容,若为假则其主体的内容将会被忽略。 - -### 语法格式 - -```jsp - - ... - -``` - -### 属性 - -``标签有如下属性: - -| **属性** | **描述** | **是否必要** | **默认值** | -| -------- | ----------------------- | ------------ | ---------- | -| select | 需要计算的 XPath 表达式 | 是 | 无 | -| var | 存储条件结果的变量 | 否 | 无 | -| scope | var 属性的作用域 | 否 | Page | - -## `` 标签 - -``标签用来循环遍历 XML 文档的节点。 - -### 语法格式 - -```jsp - -``` - -### 属性 - -``标签有如下属性: - -| **属性** | **描述** | **是否必要** | **默认值** | -| --------- | ---------------------------- | ------------ | ---------- | -| select | 需要计算的 XPath 表达式 | 是 | 无 | -| var | 用于存储当前项目的变量 | 否 | 无 | -| begin | 迭代器的开始索引 | 否 | 无 | -| end | 迭代器的结束索引 | 否 | 无 | -| step | 迭代的步长 | 否 | 无 | -| varStatus | 代表迭代器所存储的状态的变量 | 否 | 无 | - -## ``, ``, `` 标签 - -``标签与 Java switch 语句有相同的功能。switch 语句有 case 语句,而``标签有``标签。switch 语句有 default 语句,而``标签有``标签。 - -### 语法格式 - -```jsp - - - ... - - - ... - - ... - ... - - ... - -` -``` - -### 属性 - -- ``没有属性。 -- ``的属性在下表中给出。 -- ``没有属性。 - -``标签的属性: - -| **属性** | **描述** | **是否必要** | **默认值** | -| -------- | -------- | ------------ | ---------- | -| select | 条件 | 是 | 无 | - -## `` 标签 - -``标签在 XML 文档中应用 XSL。 - -### 语法格式 - -```jsp - -``` - -### 属性 - -``标签有如下属性: - -| **属性** | **描述** | **是否必要** | **默认值** | -| ------------ | --------------------------- | ------------ | ------------- | -| doc | 源 XML 文档 | 否 | Body | -| docSystemId | 源 XML 文档的 URI | 否 | 无 | -| xslt | XSLT 样式表 | 是 | 无 | -| xsltSystemId | 源 XSLT 文档的 URI | 否 | 无 | -| result | 接收转换结果的对象 | 否 | Print to page | -| var | 代表被转换的 XML 文档的变量 | 否 | Print to page | -| scope | var 属性的作用域 | 否 | 无 | - -## `` 标签 - -``标签与``标签一同使用,用于设置 XSLT 样式表的参数。 - -### 语法格式 - -```jsp - -``` - -### 属性 - -``标签有如下属性: - -| **属性** | **描述** | **是否必要** | **默认值** | -| -------- | --------------- | ------------ | ---------- | -| name | XSLT 参数的名称 | 是 | Body | -| value | XSLT 参数的值 | 否 | 无 | diff --git a/docs/javaee/listener/listener.md b/docs/javaee/listener.md similarity index 87% rename from docs/javaee/listener/listener.md rename to docs/javaee/listener.md index 36c702ad..9fd55642 100644 --- a/docs/javaee/listener/listener.md +++ b/docs/javaee/listener.md @@ -1,16 +1,25 @@ --- -title: JavaEE 监听器 -date: 2017/11/08 -categories: -- javaee -tags: -- javaee -- listener +title: Servlet 指南 +categories: ['java', 'javaweb', 'javaee'] +tags: ['java', 'javaweb', 'javaee', 'listener'] +date: 2017-11-08 --- -监听器是一个专门用于对其他对象身上发生的事件或状态改变进行监听和相应处理的对象,当被监视的对象发生情况时,立即采取相应的行动。监听器其实就是一个实现特定接口的普通 java 程序,这个程序专门用于监听另一个 java 对象的方法调用或属性改变,当被监听对象发生上述事件后,监听器某个方法立即被执行。 +# 监听器 -## 概述 +> 监听器是一个专门用于对其他对象身上发生的事件或状态改变进行监听和相应处理的对象,当被监视的对象发生情况时,立即采取相应的行动。监听器其实就是一个实现特定接口的普通 java 程序,这个程序专门用于监听另一个 java 对象的方法调用或属性改变,当被监听对象发生上述事件后,监听器某个方法立即被执行。 + + + +- [简介](#简介) +- [监听器的分类](#监听器的分类) + - [监听对象的创建和销毁](#监听对象的创建和销毁) + - [监听对象的属性变化](#监听对象的属性变化) + - [监听 Session 内的对象](#监听-session-内的对象) + + + +## 简介 JavaWeb 中的监听器是 Servlet 规范中定义的一种特殊类,它用于监听 web 应用程序中的`ServletContext`, `HttpSession`和 `ServletRequest`等域对象的创建与销毁事件,以及监听这些域对象中的属性发生修改的事件。 diff --git a/docs/javaee/servlet/servlet-and-status.md b/docs/javaee/servlet.md similarity index 50% rename from docs/javaee/servlet/servlet-and-status.md rename to docs/javaee/servlet.md index 4e706a5f..ba15d4ac 100644 --- a/docs/javaee/servlet/servlet-and-status.md +++ b/docs/javaee/servlet.md @@ -1,15 +1,200 @@ ---- +# Servlet 指南 + + + +- [JavaWeb 简介](#javaweb-简介) + - [Web 应用程序](#web-应用程序) + - [常见 Web 服务器](#常见-web-服务器) +- [Servlet 简介](#servlet-简介) + - [Servlet 是什么?](#servlet-是什么) + - [Servlet 架构](#servlet-架构) + - [Servlet 任务](#servlet-任务) + - [Servlet 包](#servlet-包) +- [Servlet 生命周期](#servlet-生命周期) + - [init() 方法](#init-方法) + - [service() 方法](#service-方法) + - [doGet() 方法](#doget-方法) + - [doPost() 方法](#dopost-方法) + - [destroy() 方法](#destroy-方法) + - [架构图](#架构图) +- [Servlet 和 HTTP 状态码](#servlet-和-http-状态码) + - [HTTP 状态码](#http-状态码) + - [设置 HTTP 状态码的方法](#设置-http-状态码的方法) + - [HTTP 状态码实例](#http-状态码实例) + + + +## JavaWeb 简介 + +### Web 应用程序 + +Web,在英语中 web 即表示网页的意思,它用于表示 Internet 主机上供外界访问的资源。 + +Web 应用程序是一种可以通过 Web 访问的应用程序,程序的最大好处是用户很容易访问应用程序,用户只需要有浏览器即可,不需要再安装其他软件。 + +Internet 上供外界访问的 Web 资源分为: + +- 静态 web 资源:指 web 页面中供人们浏览的数据始终是不变。常见静态资源文件:html、css、各种图片类型(jpg、png) +- 动态 web 资源:指 web 页面中供人们浏览的数据是由程序产生的,不同时间点访问 web 页面看到的内容各不相同。常见动态资源技术:JSP/Servlet、ASP、PHP + +### 常见 Web 服务器 + +- [Tomcat](http://tomcat.apache.org/) +- [Jetty](http://www.eclipse.org/jetty/) +- [Resin](https://caucho.com/) +- [Apache](http://httpd.apache.org/) +- [Nginx](http://nginx.org/en/) +- [WebSphere](https://www.ibm.com/cloud/websphere-application-platform) +- [WebLogic](https://www.oracle.com/middleware/technologies/weblogic.html) +- JBoss + +## Servlet 简介 + +### Servlet 是什么? + +Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。 + +使用 Servlet,您可以收集来自网页表单的用户输入,呈现来自数据库或者其他源的记录,还可以动态创建网页。 + +Java Servlet 通常情况下与使用 CGI(Common Gateway Interface,公共网关接口)实现的程序可以达到异曲同工的效果。但是相比于 CGI,Servlet 有以下几点优势: + +- 性能明显更好。 +- Servlet 在 Web 服务器的地址空间内执行。这样它就没有必要再创建一个单独的进程来处理每个客户端请求。 +- Servlet 是独立于平台的,因为它们是用 Java 编写的。 +- 服务器上的 Java 安全管理器执行了一系列限制,以保护服务器计算机上的资源。因此,Servlet 是可信的。 +- Java 类库的全部功能对 Servlet 来说都是可用的。它可以通过 sockets 和 RMI 机制与 applets、数据库或其他软件进行交互。 + +### Servlet 架构 + +下图显示了 Servlet 在 Web 应用程序中的位置。 + +
+ +### Servlet 任务 + +Servlet 执行以下主要任务: + +- 读取客户端(浏览器)发送的显式的数据。这包括网页上的 HTML 表单,或者也可以是来自 applet 或自定义的 HTTP 客户端程序的表单。 +- 读取客户端(浏览器)发送的隐式的 HTTP 请求数据。这包括 cookies、媒体类型和浏览器能理解的压缩格式等等。 +- 处理数据并生成结果。这个过程可能需要访问数据库,执行 RMI 或 CORBA 调用,调用 Web 服务,或者直接计算得出对应的响应。 +- 发送显式的数据(即文档)到客户端(浏览器)。该文档的格式可以是多种多样的,包括文本文件(HTML 或 XML)、二进制文件(GIF 图像)、Excel 等。 +- 发送隐式的 HTTP 响应到客户端(浏览器)。这包括告诉浏览器或其他客户端被返回的文档类型(例如 HTML),设置 cookies 和缓存参数,以及其他类似的任务。 + +### Servlet 包 + +Java Servlet 是运行在带有支持 Java Servlet 规范的解释器的 web 服务器上的 Java 类。 + +Servlet 可以使用 **javax.servlet** 和 **javax.servlet.http** 包创建,它是 Java 企业版的标准组成部分,Java 企业版是支持大型开发项目的 Java 类库的扩展版本。 + +这些类实现 Java Servlet 和 JSP 规范。在写本教程的时候,二者相应的版本分别是 Java Servlet 2.5 和 JSP 2.1。 + +Java Servlet 就像任何其他的 Java 类一样已经被创建和编译。在您安装 Servlet 包并把它们添加到您的计算机上的 Classpath 类路径中之后,您就可以通过 JDK 的 Java 编译器或任何其他编译器来编译 Servlet。 + +## Servlet 生命周期 + +Servlet 生命周期可被定义为从创建直到毁灭的整个过程。以下是 Servlet 遵循的过程: + +- Servlet 通过调用 **init ()** 方法进行初始化。 +- Servlet 调用 **service()** 方法来处理客户端的请求。 +- Servlet 通过调用 **destroy()** 方法终止(结束)。 +- 最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。 + +现在让我们详细讨论生命周期的方法。 + +### init() 方法 + +init 方法被设计成只调用一次。它在第一次创建 Servlet 时被调用,在后续每次用户请求时不再调用。因此,它是用于一次性初始化,就像 Applet 的 init 方法一样。 + +Servlet 创建于用户第一次调用对应于该 Servlet 的 URL 时,但是您也可以指定 Servlet 在服务器第一次启动时被加载。 + +当用户调用一个 Servlet 时,就会创建一个 Servlet 实例,每一个用户请求都会产生一个新的线程,适当的时候移交给 doGet 或 doPost 方法。init() 方法简单地创建或加载一些数据,这些数据将被用于 Servlet 的整个生命周期。 + +init 方法的定义如下: + +```java +public void init() throws ServletException { + // 初始化代码... +} +``` + +### service() 方法 + +service() 方法是执行实际任务的主要方法。Servlet 容器(即 Web 服务器)调用 service() 方法来处理来自客户端(浏览器)的请求,并把格式化的响应写回给客户端。 + +每次服务器接收到一个 Servlet 请求时,服务器会产生一个新的线程并调用服务。service() 方法检查 HTTP 请求类型(GET、POST、PUT、DELETE 等),并在适当的时候调用 doGet、doPost、doPut,doDelete 等方法。 + +下面是该方法的特征: + +```java +public void service(ServletRequest request, + ServletResponse response) + throws ServletException, IOException{ +} +``` + +service() 方法由容器调用,service 方法在适当的时候调用 doGet、doPost、doPut、doDelete 等方法。所以,您不用对 service() 方法做任何动作,您只需要根据来自客户端的请求类型来重写 doGet() 或 doPost() 即可。 + +doGet() 和 doPost() 方法是每次服务请求中最常用的方法。下面是这两种方法的特征。 + +### doGet() 方法 + +GET 请求来自于一个 URL 的正常请求,或者来自于一个未指定 METHOD 的 HTML 表单,它由 doGet() 方法处理。 + +```java +public void doGet(HttpServletRequest request, + HttpServletResponse response) + throws ServletException, IOException { + // Servlet 代码 +} +``` + +### doPost() 方法 + +POST 请求来自于一个特别指定了 METHOD 为 POST 的 HTML 表单,它由 doPost() 方法处理。 + +```java +public void doPost(HttpServletRequest request, + HttpServletResponse response) + throws ServletException, IOException { + // Servlet 代码 +} +``` + +### destroy() 方法 + +destroy() 方法只会被调用一次,在 Servlet 生命周期结束时被调用。destroy() 方法可以让您的 Servlet 关闭数据库连接、停止后台线程、把 Cookie 列表或点击计数器写入到磁盘,并执行其他类似的清理活动。 + +在调用 destroy() 方法之后,servlet 对象被标记为垃圾回收。destroy 方法定义如下所示: + +```java + public void destroy() { + // 终止化代码... + } +``` + +### 架构图 + +下图显示了一个典型的 Servlet 生命周期方案。 + +- 第一个到达服务器的 HTTP 请求被委派到 Servlet 容器。 +- Servlet 容器在调用 service() 方法之前加载 Servlet。 +- 然后 Servlet 容器处理由多个线程产生的多个请求,每个线程执行一个单一的 Servlet 实例的 service() 方法。 + +
+ +## Servlet 和 HTTP 状态码 + title: JavaEE Servlet HTTP 状态码 -date: 2017/11/08 +date: 2017-11-08 categories: + - javaee -tags: + tags: - javaee - servlet - http ---- -## HTTP 状态码 +### HTTP 状态码 HTTP 请求和 HTTP 响应消息的格式是类似的,结构如下: @@ -83,17 +268,17 @@ HeaderN: ... | 504 | Gateway Timeout | 网关超时。 | | 505 | HTTP Version Not Supported | 服务器不支持"HTTP 协议"版本。 | -## 设置 HTTP 状态码的方法 +### 设置 HTTP 状态码的方法 -下面的方法可用于在 Servlet 程序中设置 HTTP 状态码。这些方法通过  `HttpServletResponse`  对象可用。 +下面的方法可用于在 Servlet 程序中设置 HTTP 状态码。这些方法通过 `HttpServletResponse` 对象可用。 -| 序号 | 方法 & 描述 | -| ---- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| 1 | **public void setStatus ( int statusCode )**该方法设置一个任意的状态码。setStatus 方法接受一个 int(状态码)作为参数。如果您的反应包含了一个特殊的状态码和文档,请确保在使用  *PrintWriter*  实际返回任何内容之前调用 setStatus。 | -| 2 | **public void sendRedirect(String url)**该方法生成一个 302 响应,连同一个带有新文档 URL 的  *Location*  头。 | -| 3 | **public void sendError(int code, String message)**该方法发送一个状态码(通常为 404),连同一个在 HTML 文档内部自动格式化并发送到客户端的短消息。 | +| 序号 | 方法 & 描述 | +| ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 1 | **public void setStatus ( int statusCode )**该方法设置一个任意的状态码。setStatus 方法接受一个 int(状态码)作为参数。如果您的反应包含了一个特殊的状态码和文档,请确保在使用 _PrintWriter_ 实际返回任何内容之前调用 setStatus。 | +| 2 | **public void sendRedirect(String url)**该方法生成一个 302 响应,连同一个带有新文档 URL 的 _Location_ 头。 | +| 3 | **public void sendError(int code, String message)**该方法发送一个状态码(通常为 404),连同一个在 HTML 文档内部自动格式化并发送到客户端的短消息。 | -## HTTP 状态码实例 +### HTTP 状态码实例 下面的例子把 407 错误代码发送到客户端浏览器,浏览器会显示 "Need authentication!!!" 消息。 diff --git a/docs/javaee/servlet/servlet-demo.md b/docs/javaee/servlet/servlet-demo.md deleted file mode 100644 index 25f29464..00000000 --- a/docs/javaee/servlet/servlet-demo.md +++ /dev/null @@ -1,87 +0,0 @@ ---- -title: JavaEE Servlet 示例 -date: 2017/11/08 -categories: -- javaee -tags: -- javaee -- servlet ---- - -## 编写 Servlet - -### 部署 Servlet - -### web.xml 是什么 - -### servlet 元素 - -``元素用于注册 Servlet。 - -``包含有两个主要的子元素:``和``,分别用于设置 Servlet 的注册名称和 Servlet 的完整类名。 - -如果在``元素中配置了一个``元素,那么 WEB 应用程序在启动时,就会装载并创建 Servlet 的实例对象、以及调用 Servlet 实例对象的 init()方法。 - -```xml - - HelloServlet - org.zp.notes.javaee.servlet.HelloServlet - 1 - -``` - -### servlet-mapping 元素 - -``元素用于映射一个已注册的 Servlet 的一个对外访问路径。 - -``包含有两个子元素:``和``,分别用于指定 Servlet 的注册名称和 Servlet 的对外访问路径。 - -**注:同一个 Servlet 可以被映射到多个 URL 上。**例: - -```xml - - HelloServlet - org.zp.notes.javaee.servlet.HelloServlet - - - HelloServlet - /servlet/HelloServlet - /servlet/HelloServlet.asp - /servlet/HelloServlet.jsp - /servlet/HelloServlet.php - /servlet/HelloServlet.aspx - -``` - -### url-pattern 的通配符 - -`/` - -如果某个 Servlet 的映射路径仅仅为一个正斜杠`/`,那么这个 Servlet 就成为当前 Web 应用程序的缺省 Servlet。  凡是在`web.xml`文件中找不到匹配的``元素的 URL,它们的访问请求都将交给缺省 Servlet 处理,也就是说,缺省 Servlet 用于处理所有其他 Servlet 都不处理的访问请求。 - -`*` - -`*`可以匹配任意的字符。 - -### init-param 元素和 context-param 元素 - -``标签用于为当前 servlet 配置一些初始化参数。 - -``标签用于配置全局的初始化参数,所有 servlet 都可以使用。 - -```xml - - - - - - datasource - jdbc:mysql://localhost:3306/test - - - - index.jsp - - -``` diff --git a/docs/javaee/servlet/servlet-introduction.md b/docs/javaee/servlet/servlet-introduction.md deleted file mode 100644 index 10b23e0c..00000000 --- a/docs/javaee/servlet/servlet-introduction.md +++ /dev/null @@ -1,141 +0,0 @@ ---- -title: JavaEE Servlet 简介 -date: 2017/11/08 -categories: -- javaee -tags: -- javaee -- servlet ---- - -## Servlet 是什么? - -Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。 - -使用 Servlet,您可以收集来自网页表单的用户输入,呈现来自数据库或者其他源的记录,还可以动态创建网页。 - -Java Servlet 通常情况下与使用 CGI(Common Gateway Interface,公共网关接口)实现的程序可以达到异曲同工的效果。但是相比于 CGI,Servlet 有以下几点优势: - -- 性能明显更好。 -- Servlet 在 Web 服务器的地址空间内执行。这样它就没有必要再创建一个单独的进程来处理每个客户端请求。 -- Servlet 是独立于平台的,因为它们是用 Java 编写的。 -- 服务器上的 Java 安全管理器执行了一系列限制,以保护服务器计算机上的资源。因此,Servlet 是可信的。 -- Java 类库的全部功能对 Servlet 来说都是可用的。它可以通过 sockets 和 RMI 机制与 applets、数据库或其他软件进行交互。 - -## Servlet 架构 - -下图显示了 Servlet 在 Web 应用程序中的位置。 - -![Servlet 架构](http://www.runoob.com/wp-content/uploads/2014/07/servlet-arch.jpg) - -## Servlet 任务 - -Servlet 执行以下主要任务: - -- 读取客户端(浏览器)发送的显式的数据。这包括网页上的 HTML 表单,或者也可以是来自 applet 或自定义的 HTTP 客户端程序的表单。 -- 读取客户端(浏览器)发送的隐式的 HTTP 请求数据。这包括 cookies、媒体类型和浏览器能理解的压缩格式等等。 -- 处理数据并生成结果。这个过程可能需要访问数据库,执行 RMI 或 CORBA 调用,调用 Web 服务,或者直接计算得出对应的响应。 -- 发送显式的数据(即文档)到客户端(浏览器)。该文档的格式可以是多种多样的,包括文本文件(HTML 或 XML)、二进制文件(GIF 图像)、Excel 等。 -- 发送隐式的 HTTP 响应到客户端(浏览器)。这包括告诉浏览器或其他客户端被返回的文档类型(例如 HTML),设置 cookies 和缓存参数,以及其他类似的任务。 - -## Servlet 包 - -Java Servlet 是运行在带有支持 Java Servlet 规范的解释器的 web 服务器上的 Java 类。 - -Servlet 可以使用  **javax.servlet**  和  **javax.servlet.http**  包创建,它是 Java 企业版的标准组成部分,Java 企业版是支持大型开发项目的 Java 类库的扩展版本。 - -这些类实现 Java Servlet 和 JSP 规范。在写本教程的时候,二者相应的版本分别是 Java Servlet 2.5 和 JSP 2.1。 - -Java Servlet 就像任何其他的 Java 类一样已经被创建和编译。在您安装 Servlet 包并把它们添加到您的计算机上的 Classpath 类路径中之后,您就可以通过 JDK 的 Java 编译器或任何其他编译器来编译 Servlet。 - -# Servlet  生命周期 - -Servlet 生命周期可被定义为从创建直到毁灭的整个过程。以下是 Servlet 遵循的过程: - -- Servlet 通过调用  **init ()**  方法进行初始化。 -- Servlet 调用  **service()**  方法来处理客户端的请求。 -- Servlet 通过调用  **destroy()**  方法终止(结束)。 -- 最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。 - -现在让我们详细讨论生命周期的方法。 - -## init() 方法 - -init 方法被设计成只调用一次。它在第一次创建 Servlet 时被调用,在后续每次用户请求时不再调用。因此,它是用于一次性初始化,就像 Applet 的 init 方法一样。 - -Servlet 创建于用户第一次调用对应于该 Servlet 的 URL 时,但是您也可以指定 Servlet 在服务器第一次启动时被加载。 - -当用户调用一个 Servlet 时,就会创建一个 Servlet 实例,每一个用户请求都会产生一个新的线程,适当的时候移交给 doGet 或 doPost 方法。init() 方法简单地创建或加载一些数据,这些数据将被用于 Servlet 的整个生命周期。 - -init 方法的定义如下: - -```java -public void init() throws ServletException { - // 初始化代码... -} -``` - -## service() 方法 - -service() 方法是执行实际任务的主要方法。Servlet 容器(即 Web 服务器)调用 service() 方法来处理来自客户端(浏览器)的请求,并把格式化的响应写回给客户端。 - -每次服务器接收到一个 Servlet 请求时,服务器会产生一个新的线程并调用服务。service() 方法检查 HTTP 请求类型(GET、POST、PUT、DELETE 等),并在适当的时候调用 doGet、doPost、doPut,doDelete 等方法。 - -下面是该方法的特征: - -```java -public void service(ServletRequest request, - ServletResponse response) - throws ServletException, IOException{ -} -``` - -service() 方法由容器调用,service 方法在适当的时候调用 doGet、doPost、doPut、doDelete 等方法。所以,您不用对 service() 方法做任何动作,您只需要根据来自客户端的请求类型来重写 doGet() 或 doPost() 即可。 - -doGet() 和 doPost() 方法是每次服务请求中最常用的方法。下面是这两种方法的特征。 - -## doGet() 方法 - -GET 请求来自于一个 URL 的正常请求,或者来自于一个未指定 METHOD 的 HTML 表单,它由 doGet() 方法处理。 - -```java -public void doGet(HttpServletRequest request, - HttpServletResponse response) - throws ServletException, IOException { - // Servlet 代码 -} -``` - -## doPost() 方法 - -POST 请求来自于一个特别指定了 METHOD 为 POST 的 HTML 表单,它由 doPost() 方法处理。 - -```java -public void doPost(HttpServletRequest request, - HttpServletResponse response) - throws ServletException, IOException { - // Servlet 代码 -} -``` - -## destroy() 方法 - -destroy() 方法只会被调用一次,在 Servlet 生命周期结束时被调用。destroy() 方法可以让您的 Servlet 关闭数据库连接、停止后台线程、把 Cookie 列表或点击计数器写入到磁盘,并执行其他类似的清理活动。 - -在调用 destroy() 方法之后,servlet 对象被标记为垃圾回收。destroy 方法定义如下所示: - -```java - public void destroy() { - // 终止化代码... - } -``` - -## 架构图 - -下图显示了一个典型的 Servlet 生命周期方案。 - -- 第一个到达服务器的 HTTP 请求被委派到 Servlet 容器。 -- Servlet 容器在调用 service() 方法之前加载 Servlet。 -- 然后 Servlet 容器处理由多个线程产生的多个请求,每个线程执行一个单一的 Servlet 实例的 service() 方法。 - -![Servlet 生命周期](http://www.runoob.com/wp-content/uploads/2014/07/Servlet-LifeCycle.jpg) diff --git a/docs/javaee/session/cookie-and-sesion.md b/docs/javaee/session/cookie-and-sesion.md deleted file mode 100644 index cf7abfd6..00000000 --- a/docs/javaee/session/cookie-and-sesion.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -title: JavaEE Cookie vs. Session -date: 2017/11/08 -categories: -- javaee -tags: -- javaee -- cookie -- session ---- - -## 存取方式 - -Cookie 只能保存`ASCII`字符串,如果需要存取 Unicode 字符或二进制数据,需要进行`UTF-8`、`GBK`或`BASE64`等方式的编码。 - -Session 可以存取任何类型的数据,甚至是任何 Java 类。可以将 Session 看成是一个 Java 容器类。 - -## 隐私安全 - -Cookie 存于客户端浏览器,一些客户端的程序可能会窥探、复制或修改 Cookie 内容。 - -Session 存于服务器,对客户端是透明的,不存在敏感信息泄露的危险。 - -## 有效期 - -使用 Cookie 可以保证长时间登录有效,只要设置 Cookie 的`maxAge`属性为一个很大的数字。 - -而 Session 虽然理论上也可以通过设置很大的数值来保持长时间登录有效,但是,由于 Session 依赖于名为`JESSIONID`的 Cookie,而 Cookie `JESSIONID`的`maxAge`默认为-1,只要关闭了浏览器该 Session 就会失效,因此,Session 不能实现信息永久有效的效果。使用 URL 地址重写也不能实现。 - -## 服务器的开销 - -由于 Session 是保存在服务器的,每个用户都会产生一个 Session,如果并发访问的用户非常多,会产生很多的 Session,消耗大量的内存。 - -而 Cookie 由于保存在客户端浏览器上,所以不占用服务器资源。 - -## 浏览器的支持 - -Cookie 需要浏览器支持才能使用。 - -如果浏览器不支持 Cookie,需要使用 Session 以及 URL 地址重写。 - -需要注意的事所有的用到 Session 程序的 URL 都要使用`response.encodeURL(StringURL)` 或`response.encodeRediretURL(String URL)`进行 URL 地址重写,否则导致 Session 会话跟踪失效。 - -## 跨域名 - -Cookie 支持跨域名。 - -Session 不支持跨域名。 diff --git a/docs/javaee/session/session.md b/docs/javaee/session/session.md deleted file mode 100644 index 29a13e0a..00000000 --- a/docs/javaee/session/session.md +++ /dev/null @@ -1,182 +0,0 @@ ---- -title: JavaEE Session -date: 2017/11/08 -categories: -- javaee -tags: -- javaee -- session ---- - -## Session 是什么? - -不同于 Cookie 保存在客户端浏览器中,Session 保存在服务器上。 - -如果说 Cookie 机制是通过检查客户身上的“通行证”来确定客户身份的话,那么 Session 机制就是通过检查服务器上的“客户明细表”来确认客户身份。 - -Session 对应的类为 `javax.servlet.http.HttpSession` 类。Session 对象是在客户第一次请求服务器时创建的。 - -## Session 类中的方法 - -`javax.servlet.http.HttpSession` 类中的方法: - -| **方法** | **功能** | -| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | -| public Object getAttribute(String name) | 该方法返回在该 session 会话中具有指定名称的对象,如果没有指定名称的对象,则返回 null。 | -| public Enumeration getAttributeNames() | 该方法返回 String 对象的枚举,String 对象包含所有绑定到该 session 会话的对象的名称。 | -| public long getCreationTime() | 该方法返回该 session 会话被创建的时间,自格林尼治标准时间 1970 年 1 月 1 日午夜算起,以毫秒为单位。 | -| public String getId() | 该方法返回一个包含分配给该 session 会话的唯一标识符的字符串。 | -| public long getLastAccessedTime() | 该方法返回客户端最后一次发送与该 session 会话相关的请求的时间自格林尼治标准时间 1970 年 1 月 1 日午夜算起,以毫秒为单位。 | -| public int getMaxInactiveInterval() | 该方法返回 Servlet 容器在客户端访问时保持 session 会话打开的最大时间间隔,以秒为单位。 | -| public void invalidate() | 该方法指示该 session 会话无效,并解除绑定到它上面的任何对象。 | -| public boolean isNew() | 如果客户端还不知道该 session 会话,或者如果客户选择不参入该 session 会话,则该方法返回 true。 | -| public void removeAttribute(String name) | 该方法将从该 session 会话移除指定名称的对象。 | -| public void setAttribute(String name, Object value) | 该方法使用指定的名称绑定一个对象到该 session 会话。 | -| public void setMaxInactiveInterval(int interval) | 该方法在 Servlet 容器指示该 session 会话无效之前,指定客户端请求之间的时间,以秒为单位。 | - -## Session 的有效期 - -由于会有越来越多的用户访问服务器,因此 Session 也会越来越多。为防止内存溢出,服务器会把长时间没有活跃的 Session 从内存中删除。 - -Session 的超时时间为`maxInactiveInterval`属性,可以通过`getMaxInactiveInterval()`、`setMaxInactiveInterval(longinterval)`来读写这个属性。 - -Tomcat 中 Session 的默认超时时间为 20 分钟。可以修改 web.xml 改变 Session 的默认超时时间。 - -例: - -```xml - - 60 - -``` - -## Session 对浏览器的要求 - -HTTP 协议是无状态的,Session 不能依据 HTTP 连接来判断是否为同一客户。因此服务器向客户端浏览器发送一个名为 JESSIONID 的 Cookie,他的值为该 Session 的 id(也就是 HttpSession.getId()的返回值)。Session 依据该 Cookie 来识别是否为同一用户。 - -该 Cookie 为服务器自动生成的,它的`maxAge`属性一般为-1,表示仅当前浏览器内有效,并且各浏览器窗口间不共享,关闭浏览器就会失效。 - -## URL 地址重写 - -URL 地址重写的原理是将该用户 Session 的 id 信息重写到 URL 地址中。服务器能够解析重写后的 URL 获取 Session 的 id。这样即使客户端不支持 Cookie,也可以使用 Session 来记录用户状态。 - -`HttpServletResponse`类提供了`encodeURL(Stringurl)`实现 URL 地址重写。 - -## Session 中禁用 Cookie - -在`META-INF/context.xml`中编辑如下: - -```xml - - -``` - -部署后,TOMCAT 便不会自动生成名 JESSIONID 的 Cookie,Session 也不会以 Cookie 为识别标志,而仅仅以重写后的 URL 地址为识别标志了。 - -## 实例 - -### Session 跟踪 - -SessionTrackServlet.java - -```java -import java.io.IOException; -import java.io.PrintWriter; -import java.text.SimpleDateFormat; -import java.util.Date; - -import javax.servlet.ServletException; -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; - -@WebServlet("/servlet/SessionTrackServlet") -public class SessionTrackServlet extends HttpServlet { - private static final long serialVersionUID = 1L; - - public void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - // 如果不存在 session 会话,则创建一个 session 对象 - HttpSession session = request.getSession(true); - // 获取 session 创建时间 - Date createTime = new Date(session.getCreationTime()); - // 获取该网页的最后一次访问时间 - Date lastAccessTime = new Date(session.getLastAccessedTime()); - - // 设置日期输出的格式 - SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - - String title = "Servlet Session 实例"; - Integer visitCount = new Integer(0); - String visitCountKey = new String("visitCount"); - String userIDKey = new String("userID"); - String userID = new String("admin"); - - // 检查网页上是否有新的访问者 - if (session.isNew()) { - session.setAttribute(userIDKey, userID); - } else { - visitCount = (Integer) session.getAttribute(visitCountKey); - visitCount = visitCount + 1; - userID = (String) session.getAttribute(userIDKey); - } - session.setAttribute(visitCountKey, visitCount); - - // 设置响应内容类型 - response.setContentType("text/html;charset=UTF-8"); - PrintWriter out = response.getWriter(); - - String docType = "\n"; - out.println(docType + "\n" + "" + title + "\n" - + "\n" + "

" + title - + "

\n" + "

Session 信息

\n" - + "\n" + "\n" - + " \n" + "\n" + " \n" - + " \n" + "\n" - + " \n" + " \n" - + "\n" + " \n" + " \n" + "\n" + " \n" + " \n" + "\n" + " \n" + " \n" + "
Session 信息
id" + session.getId() + "
创建时间" + df.format(createTime) + "
最后访问时间" + df.format(lastAccessTime) - + "
用户 ID" + userID - + "
访问统计:" + visitCount - + "
\n" + ""); - } -} -``` - -web.xml - -```xml - - SessionTrackServlet - SessionTrackServlet - - - SessionTrackServlet - /servlet/SessionTrackServlet - -``` - -### 删除 Session 会话数据 - -当您完成了一个用户的 session 会话数据,您有以下几种选择: - -**移除一个特定的属性:**您可以调用  `removeAttribute(String name)` 方法来删除与特定的键相关联的值。 - -**删除整个 session 会话:**您可以调用  `invalidate()`  方法来丢弃整个 session 会话。 - -**设置 session 会话过期时间:**您可以调用 `setMaxInactiveInterval(int interval)`  方法来单独设置 session 会话超时。 - -**注销用户:**如果使用的是支持 servlet 2.4 的服务器,您可以调用  `logout`  来注销 Web 服务器的客户端,并把属于所有用户的所有 session 会话设置为无效。 - -**web.xml 配置:**如果您使用的是 Tomcat,除了上述方法,您还可以在 web.xml 文件中配置 session 会话超时,如下所示: - -```xml - - 15 - -``` - -上面实例中的超时时间是以分钟为单位,将覆盖 Tomcat 中默认的 30 分钟超时时间。 - -在一个 Servlet 中的 `getMaxInactiveInterval()` 方法会返回 session 会话的超时时间,以秒为单位。所以,如果在 web.xml 中配置 session 会话超时时间为 15 分钟,那么`getMaxInactiveInterval()` 会返回 900。 diff --git a/docs/javaee/taglib/taglib.md b/docs/javaee/taglib/taglib.md deleted file mode 100644 index 9329d315..00000000 --- a/docs/javaee/taglib/taglib.md +++ /dev/null @@ -1,13 +0,0 @@ -自定义标签是用户定义的 JSP 语言元素。 - -当 JSP 页面包含一个自定义标签时将被转化为 servlet,标签转化为对被称为 tag handler 的对象的操作,即当 servlet 执行时 Web container 调用那些操作。 - -JSP 标签扩展可以让你创建新的标签并且可以直接插入到一个 JSP 页面。 JSP 2.0 规范中引入 Simple Tag Handlers 来编写这些自定义标记。 - -编写自定义步骤的方法 - -任何一个标签都对应一个 Java 类,该类必须实现 Tag 接口。 - -声明一个 tld 标签库描述文件 - -如果 tld 文件位于/WEB-INF 目录下面,Tomcat 会自动加载 tld 文件中的标签库。如果是某些不支持自动加载或者放于其他位置的 tld 文件,可以在 web.xml 中配置。 diff --git a/docs/navbar.md b/docs/navbar.md new file mode 100644 index 00000000..734ef3fe --- /dev/null +++ b/docs/navbar.md @@ -0,0 +1,5 @@ +- [JavaEE](javaee/README.md) + - [Servlet 指南](javaee/servlet.md) + - [Jsp 指南](javaee/jsp.md) + - [Listener 指南](javaee/listener.md) + - [Filter 指南](javaee/filter.md) diff --git a/docs/orm/mybatis.md b/docs/orm/mybatis.md new file mode 100644 index 00000000..31886d59 --- /dev/null +++ b/docs/orm/mybatis.md @@ -0,0 +1,356 @@ +--- +title: MyBatis 指南 +categories: ['java', 'javaweb'] +tags: ['java', 'javaweb', 'orm'] +date: 2019-03-06 +--- + +# MyBatis 指南 + +> MyBatis 的前身就是 iBatis ,是一个作用在数据持久层的对象关系映射(Object Relational Mapping,简称 ORM)框架。 + + + +- [简介](#简介) + - [什么是 MyBatis?](#什么是-mybatis) + - [MyBatis vs. Hibernate](#mybatis-vs-hibernate) +- [入门](#入门) +- [核心 API](#核心-api) + - [SqlSessionFactoryBuilder](#sqlsessionfactorybuilder) + - [SqlSessionFactory](#sqlsessionfactory) + - [SqlSession](#sqlsession) + - [映射器](#映射器) +- [原理](#原理) + - [MyBatis 的架构](#mybatis-的架构) + - [接口层](#接口层) + - [数据处理层](#数据处理层) + - [框架支撑层](#框架支撑层) + - [引导层](#引导层) + - [主要组件](#主要组件) +- [配置](#配置) +- [SQL XML 映射文件](#sql-xml-映射文件) +- [Mybatis 扩展](#mybatis-扩展) +- [资料](#资料) + + + +## 简介 + +### 什么是 MyBatis? + +MyBatis 是一款持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。 + +### MyBatis vs. Hibernate + +MyBatis 和 Hibernate 都是 Java 世界中比较流行的 ORM 框架。我们应该了解其各自的优势,根据项目的需要去抉择在开发中使用哪个框架。 + +**Mybatis 优势** + +- MyBatis 可以进行更为细致的 SQL 优化,可以减少查询字段。 +- MyBatis 容易掌握,而 Hibernate 门槛较高。 + +**Hibernate 优势** + +- Hibernate 的 DAO 层开发比 MyBatis 简单,Mybatis 需要维护 SQL 和结果映射。 +- Hibernate 对对象的维护和缓存要比 MyBatis 好,对增删改查的对象的维护要方便。 +- Hibernate 数据库移植性很好,MyBatis 的数据库移植性不好,不同的数据库需要写不同 SQL。 +- Hibernate 有更好的二级缓存机制,可以使用第三方缓存。MyBatis 本身提供的缓存机制不佳。 + +## 入门 + +> [Mybatis 官方文档之入门](http://www.mybatis.org/mybatis-3/zh/getting-started.html) 已经写得很简洁易懂,不再赘述。 + +## 核心 API + +> 核心 API 请参考:「 [Mybatis 官方文档之 Java API](http://www.mybatis.org/mybatis-3/zh/java-api.html) 」 + +
+ +### SqlSessionFactoryBuilder + +- **SqlSessionFactoryBuilder 职责** + +**`SqlSessionFactoryBuilder` 负责创建 `SqlSessionFactory` 实例。而 `SqlSessionFactoryBuilder` 则可以从 XML 配置文件或一个预先定制的 `Configuration` 的实例构建出 `SqlSessionFactory` 的实例。** + +
+ +- **SqlSessionFactoryBuilder 生命周期** + +这个类可以被实例化、使用和丢弃,一旦创建了 `SqlSessionFactory`,就不再需要它了。 因此 `SqlSessionFactoryBuilder` 实例的最佳作用域是方法作用域(也就是局部方法变量)。 + +### SqlSessionFactory + +- **SqlSessionFactory 职责** + +**SqlSessionFactory 负责创建 SqlSession 实例。** + +
+ +- **SqlSessionFactory 生命周期** + +`SqlSessionFactory` 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。最简单的方法就是使用单例模式或者静态单例模式。 + +### SqlSession + +
+ +- **SqlSession 职责** + +**MyBatis 的主要 Java 接口就是 `SqlSession`。你可以通过这个接口来执行命令,获取映射器和管理事务。** + +> 详细内容可以参考:「 [Mybatis 官方文档之 sqlSessions](http://www.mybatis.org/mybatis-3/zh/java-api.html#sqlSessions) 」 。 + +- **SqlSession 生命周期** + +> 注意:当 Mybatis 与一些依赖注入框架(如 Spring 或者 Guice)同时使用时,`SqlSessions` 将被依赖注入框架所创建,所以你不需要使用 `SqlSessionFactoryBuilder` 或者 `SqlSessionFactory`。 + +**每个线程都应该有它自己的 `SqlSession` 实例。** + +`SqlSession` 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 `SqlSession` 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 `SqlSession` 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 `HttpSession`。 正确在 Web 中使用 `SqlSession` 的场景是:每次收到的 HTTP 请求,就可以打开一个 `SqlSession`,返回一个响应,就关闭它。 + +编程模式: + +```java +SqlSession session = sqlSessionFactory.openSession(); +try { + // 你的应用逻辑代码 +} finally { + session.close(); +} +``` + +### 映射器 + +- **映射器职责** + +映射器是一些由你创建的、绑定你映射的语句的接口。 + +各个 insert、update、delete 和 select 方法都很强大,但也有些繁琐。更通用的方式是使用映射器类来执行映射语句。一个映射器类就是一个仅需声明与 SqlSession 方法相匹配的方法的接口类。 + +下面的示例展示了一些方法签名以及它们是如何映射到 SqlSession 上的。 + +```java +public interface AuthorMapper { + // (Author) selectOne("selectAuthor",5); + Author selectAuthor(int id); + // (List) selectList(“selectAuthors”) + List selectAuthors(); + // (Map) selectMap("selectAuthors", "id") + @MapKey("id") + Map selectAuthors(); + // insert("insertAuthor", author) + int insertAuthor(Author author); + // updateAuthor("updateAuthor", author) + int updateAuthor(Author author); + // delete("deleteAuthor",5) + int deleteAuthor(int id); +} +``` + +> **注意** +> +> - 映射器接口不需要去实现任何接口或继承自任何类。只要方法可以被唯一标识对应的映射语句就可以了。 +> - 映射器接口可以继承自其他接口。当使用 XML 来构建映射器接口时要保证语句被包含在合适的命名空间中。而且,唯一的限制就是你不能在两个继承关系的接口中拥有相同的方法签名(潜在的危险做法不可取)。 + +- **映射器生命周期** + +映射器接口的实例是从 `SqlSession` 中获得的。因此从技术层面讲,任何映射器实例的最大作用域是和请求它们的 `SqlSession` 相同的。尽管如此,映射器实例的最佳作用域是方法作用域。 也就是说,映射器实例应该在调用它们的方法中被请求,用过之后即可丢弃。 + +编程模式: + +```java +SqlSession session = sqlSessionFactory.openSession(); +try { + BlogMapper mapper = session.getMapper(BlogMapper.class); + // 你的应用逻辑代码 +} finally { + session.close(); +} +``` + +- **映射器注解** + +MyBatis 是一个 XML 驱动的框架。配置信息是基于 XML 的,而且映射语句也是定义在 XML 中的。MyBatis 3 以后,支持注解配置。注解配置基于配置 API;而配置 API 基于 XML 配置。 + +Mybatis 支持诸如 @Insert、@Update、@Delete、@Select、@Result 等注解。 + +> 详细内容请参考:[Mybatis 官方文档之 sqlSessions](http://www.mybatis.org/mybatis-3/zh/java-api.html#sqlSessions),其中列举了 Mybatis 支持的注解清单,以及基本用法。 + +## 原理 + +### MyBatis 的架构 + +
+ +### 接口层 + +接口层负责和数据库交互的方式 + +MyBatis 和数据库的交互有两种方式: + +1. 使用传统的 MyBatis 提供的 API; +2. 使用 Mapper 接口 + +#### 使用传统的 MyBatis 提供的 API + +这是传统的传递 Statement Id 和查询参数给 SqlSession 对象,使用 SqlSession 对象完成和数据库的交互;MyBatis 提供了非常方便和简单的 API,供用户实现对数据库的增删改查数据操作,以及对数据库连接信息和 MyBatis 自身配置信息的维护操作。 + +
+ +
+ +上述使用 MyBatis 的方法,是创建一个和数据库打交道的 SqlSession 对象,然后根据 Statement Id 和参数来操作数据库,这种方式固然很简单和实用,但是它不符合面向对象语言的概念和面向接口编程的编程习惯。由于面向接口的编程是面向对象的大趋势,MyBatis 为了适应这一趋势,增加了第二种使用 MyBatis 支持接口(Interface)调用方式。 + +#### 使用 Mapper 接口 + +MyBatis 将配置文件中的每一个 `` 节点抽象为一个 Mapper 接口,而这个接口中声明的方法和跟 `` 节点中的 `` 节点相对应,即 `` 节点的 id 值为 Mapper 接口中的方法名称,parameterType 值表示 Mapper 对应方法的入参类型,而 resultMap 值则对应了 Mapper 接口表示的返回值类型或者返回结果集的元素类型。 + +
+ +
+ +根据 MyBatis 的配置规范配置好后,通过 `SqlSession.getMapper(XXXMapper.class)` 方法,MyBatis 会根据相应的接口声明的方法信息,通过动态代理机制生成一个 Mapper 实例,我们使用 Mapper 接口的某一个方法时,MyBatis 会根据这个方法的方法名和参数类型,确定 Statement Id,底层还是通过`SqlSession.select("statementId",parameterObject);` 或者 `SqlSession.update("statementId",parameterObject);` 等等来实现对数据库的操作。 + +MyBatis 引用 Mapper 接口这种调用方式,纯粹是为了满足面向接口编程的需要。(其实还有一个原因是在于,面向接口的编程,使得用户在接口上可以使用注解来配置 SQL 语句,这样就可以脱离 XML 配置文件,实现“0 配置”)。 + +### 数据处理层 + +数据处理层可以说是 MyBatis 的核心,从大的方面上讲,它要完成两个功能: + +1. 通过传入参数构建动态 SQL 语句; +2. SQL 语句的执行以及封装查询结果集成 `List` + +#### 参数映射和动态 SQL 语句生成 + +动态语句生成可以说是 MyBatis 框架非常优雅的一个设计,MyBatis 通过传入的参数值,**使用 Ognl 来动态地构造 SQL 语句**,使得 MyBatis 有很强的灵活性和扩展性。 + +参数映射指的是对于 java 数据类型和 jdbc 数据类型之间的转换:这里有包括两个过程:查询阶段,我们要将 java 类型的数据,转换成 jdbc 类型的数据,通过 `preparedStatement.setXXX()` 来设值;另一个就是对 resultset 查询结果集的 jdbcType 数据转换成 java 数据类型。 + +#### SQL 语句的执行以及封装查询结果集成 `List` + +动态 SQL 语句生成之后,MyBatis 将执行 SQL 语句,并将可能返回的结果集转换成 `List` 列表。MyBatis 在对结果集的处理中,支持结果集关系一对多和多对一的转换,并且有两种支持方式,一种为嵌套查询语句的查询,还有一种是嵌套结果集的查询。 + +### 框架支撑层 + +#### 事务管理机制 + +对数据库的事务而言,应该具有以下几点:创建(create)、提交(commit)、回滚(rollback)、关闭(close)。对应地,MyBatis 将事务抽象成了 Transaction 接口。 + +MyBatis 的事务管理分为两种形式: + +1. 使用 JDBC 的事务管理机制:即利用 java.sql.Connection 对象完成对事务的提交(commit())、回滚(rollback())、关闭(close())等。 +2. 使用 MANAGED 的事务管理机制:这种机制 MyBatis 自身不会去实现事务管理,而是让程序的容器如(JBOSS,Weblogic)来实现对事务的管理。 + +#### 连接池管理机制 + +由于创建一个数据库连接所占用的资源比较大, 对于数据吞吐量大和访问量非常大的应用而言,连接池的设计就显得非常重要,对于连接池管理机制我已经在我的博文《深入理解 mybatis 原理》 Mybatis 数据源与连接池 中有非常详细的讨论,感兴趣的读者可以点击查看。 + +#### 缓存机制 + +MyBatis 将数据缓存设计成两级结构,分为一级缓存、二级缓存: + +- **一级缓存是 Session 会话级别的缓存**,位于表示一次数据库会话的 SqlSession 对象之中,又被称之为本地缓存。一级缓存是 MyBatis 内部实现的一个特性,用户不能配置,默认情况下自动支持的缓存,用户没有定制它的权利(不过这也不是绝对的,可以通过开发插件对它进行修改); +- **二级缓存是 Application 应用级别的缓存**,它的是生命周期很长,跟 Application 的声明周期一样,也就是说它的作用范围是整个 Application 应用。 + +
+ +
+ +##### 一级缓存的工作机制 + +一级缓存是 Session 会话级别的,一般而言,一个 SqlSession 对象会使用一个 Executor 对象来完成会话操作,Executor 对象会维护一个 Cache 缓存,以提高查询性能。 + +
+ +
+ +##### 二级缓存的工作机制 + +如上所言,一个 SqlSession 对象会使用一个 Executor 对象来完成会话操作,MyBatis 的二级缓存机制的关键就是对这个 Executor 对象做文章。如果用户配置了 `"cacheEnabled=true"`,那么 MyBatis 在为 SqlSession 对象创建 Executor 对象时,会对 Executor 对象加上一个装饰者:CachingExecutor,这时 SqlSession 使用 CachingExecutor 对象来完成操作请求。CachingExecutor 对于查询请求,会先判断该查询请求在 Application 级别的二级缓存中是否有缓存结果,如果有查询结果,则直接返回缓存结果;如果缓存中没有,再交给真正的 Executor 对象来完成查询操作,之后 CachingExecutor 会将真正 Executor 返回的查询结果放置到缓存中,然后在返回给用户。 + +
+ +
+ +#### SQL 语句的配置方式 + +传统的 MyBatis 配置 SQL 语句方式就是使用 XML 文件进行配置的,但是这种方式不能很好地支持面向接口编程的理念,为了支持面向接口的编程,MyBatis 引入了 Mapper 接口的概念,面向接口的引入,对使用注解来配置 SQL 语句成为可能,用户只需要在接口上添加必要的注解即可,不用再去配置 XML 文件了,但是,目前的 MyBatis 只是对注解配置 SQL 语句提供了有限的支持,某些高级功能还是要依赖 XML 配置文件配置 SQL 语句。 + +### 引导层 + +引导层是配置和启动 MyBatis 配置信息的方式。MyBatis 提供两种方式来引导 MyBatis : + +1. 基于 XML 配置文件的方式 +2. 基于 Java API 的方式 + +### 主要组件 + +从 MyBatis 代码实现的角度来看,MyBatis 的主要组件有以下几个: + +- **SqlSession** - 作为 MyBatis 工作的主要顶层 API,表示和数据库交互的会话,完成必要数据库增删改查功能。 +- **Executor** - MyBatis 执行器,是 MyBatis 调度的核心,负责 SQL 语句的生成和查询缓存的维护。 +- **StatementHandler** - 封装了 JDBC Statement 操作,负责对 JDBC statement 的操作,如设置参数、将 Statement 结果集转换成 List 集合。 +- **ParameterHandler** - 负责对用户传递的参数转换成 JDBC Statement 所需要的参数。 +- **ResultSetHandler** - 负责将 JDBC 返回的 ResultSet 结果集对象转换成 List 类型的集合。 +- **TypeHandler** - 负责 java 数据类型和 jdbc 数据类型之间的映射和转换。 +- **MappedStatement** - MappedStatement 维护了一条 `` 节点的封装。 +- **SqlSource** - 负责根据用户传递的 parameterObject,动态地生成 SQL 语句,将信息封装到 BoundSql 对象中,并返回。 +- **BoundSql** - 表示动态生成的 SQL 语句以及相应的参数信息。 +- **Configuration** - MyBatis 所有的配置信息都维持在 Configuration 对象之中。 + +它们的关系如下图所示: + +
+ +
+## 配置 + +> 配置的详细内容请参考:「 [Mybatis 官方文档之配置](http://www.mybatis.org/mybatis-3/zh/configuration.html) 」 。 + +MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。主要配置项有以下: + +- [properties(属性)](http://www.mybatis.org/mybatis-3/zh/configuration.html#properties) +- [settings(设置)](http://www.mybatis.org/mybatis-3/zh/configuration.html#settings) +- [typeAliases(类型别名)](http://www.mybatis.org/mybatis-3/zh/configuration.html#typeAliases) +- [typeHandlers(类型处理器)](http://www.mybatis.org/mybatis-3/zh/configuration.html#typeHandlers) +- [objectFactory(对象工厂)](http://www.mybatis.org/mybatis-3/zh/configuration.html#objectFactory) +- [plugins(插件)](http://www.mybatis.org/mybatis-3/zh/configuration.html#plugins) +- [environments(环境配置)](http://www.mybatis.org/mybatis-3/zh/configuration.html#environments) + - environment(环境变量) + - transactionManager(事务管理器) + - dataSource(数据源) +- [databaseIdProvider(数据库厂商标识)](http://www.mybatis.org/mybatis-3/zh/configuration.html#databaseIdProvider) +- [mappers(映射器)](http://www.mybatis.org/mybatis-3/zh/configuration.html#mappers) + +## SQL XML 映射文件 + +> SQL XML 映射文件详细内容请参考:「 [Mybatis 官方文档之 XML 映射文件](http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html) 」。 + +XML 映射文件只有几个顶级元素: + +- `cache` – 对给定命名空间的缓存配置。 +- `cache-ref` – 对其他命名空间缓存配置的引用。 +- `resultMap` – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。 +- \~\~`parameterMap` – 已被废弃!老式风格的参数映射。更好的办法是使用内联参数,此元素可能在将来被移除。文档中不会介绍此元素。\~\~ +- `sql` – 可被其他语句引用的可重用语句块。 +- `insert` – 映射插入语句 +- `update` – 映射更新语句 +- `delete` – 映射删除语句 +- `select` – 映射查询语句 + +## Mybatis 扩展 + +- [mybatis-plus](https://github.com/baomidou/mybatis-plus) - CRUD、代码生成器、分页器等多功能 +- [Mapper](https://github.com/abel533/Mapper) - CRUD +- [Mybatis-PageHelper](https://github.com/pagehelper/Mybatis-PageHelper) - Mybatis 通用分页插件 + +## 资料 + +- **官方** + +| [Github](https://github.com/mybatis/mybatis-3) | [官网](http://www.mybatis.org/mybatis-3/) | [MyBatis Generator](https://github.com/mybatis/generator) | [Spring 集成](https://github.com/mybatis/spring) | [Spring Boot 集成](https://github.com/mybatis/spring-boot-starter) | + +- **文章** + - [深入理解 mybatis 原理](https://blog.csdn.net/luanlouis/article/details/40422941) + - [mybatis 源码中文注释](https://github.com/tuguangquan/mybatis) - mybatis 源码解读 + - [MyBatis Generator 详解](https://blog.csdn.net/isea533/article/details/42102297) diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 00000000..c7ce2694 --- /dev/null +++ b/docs/package.json @@ -0,0 +1,9 @@ +{ + "name": "JavaWeb", + "version": "1.0.0", + "scripts": { + "start": "docsify serve ./" + }, + "dependencies": {}, + "devDependencies": {} +} diff --git a/docs/security/shiro.md b/docs/security/shiro.md new file mode 100644 index 00000000..59638ecb --- /dev/null +++ b/docs/security/shiro.md @@ -0,0 +1,68 @@ +# Shiro + +> Shiro 是一个安全框架,具有认证、授权、加密、会话管理功能。 + + + +- [概述](#概述) + - [Shiro 功能](#shiro-功能) + - [Shiro 架构](#shiro-架构) +- [认证](#认证) +- [授权](#授权) +- [资料](#资料) + + + +## 概述 + +### Shiro 功能 + +

+ +

+ +- **Authentication** - 身份认证/登录,验证用户是不是拥有相应的身份; +- **Authorization** - 授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限; +- **Session Manager** - 会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的; +- **Cryptography** - 加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储; +- **Web Support** - Web 支持,可以非常容易的集成到 Web 环境; +- **Caching** - 缓存,比如用户登录后,其用户信息、拥有的角色 / 权限不必每次去查,这样可以提高效率; +- **Concurrency** - shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去; +- **Testing** - 提供测试支持; +- **Run As** - 允许一个用户假装为另一个用户(如果他们允许)的身份进行访问; +- **Remember Me** - 记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。 + +记住一点,Shiro 不会去维护用户、维护权限;这些需要我们自己去提供;然后通过相应的接口注入给 Shiro 即可。 + +### Shiro 架构 + +

+ +

+ +- **Subject** - 主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等;即一个抽象概念;所有 Subject 都绑定到 SecurityManager,与 Subject 的所有交互都会委托给 SecurityManager;可以把 Subject 认为是一个门面;SecurityManager 才是实际的执行者; +- **SecurityManager** - 安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject;可以看出它是 Shiro 的核心,它负责与后边介绍的其他组件进行交互,如果学习过 SpringMVC,你可以把它看成 DispatcherServlet 前端控制器; +- **Realm** - 域,Shiro 从从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色/权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。 + +

+ +

+ +- **Subject** - 主体,可以看到主体可以是任何可以与应用交互的“用户”; +- **SecurityManager** - 相当于 SpringMVC 中的 DispatcherServlet 或者 Struts2 中的 FilterDispatcher;是 Shiro 的心脏;所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理。 +- **Authenticator** - 认证器,负责主体认证的,这是一个扩展点,如果用户觉得 Shiro 默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了; +- **Authrizer** - 授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能; +- **Realm** - 可以有 1 个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是 JDBC 实现,也可以是 LDAP 实现,或者内存实现等等;由用户提供;注意 - Shiro 不知道你的用户/权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的 Realm; +- **SessionManager** - 如果写过 Servlet 就应该知道 Session 的概念,Session 呢需要有人去管理它的生命周期,这个组件就是 SessionManager;而 Shiro 并不仅仅可以用在 Web 环境,也可以用在如普通的 JavaSE 环境、EJB 等环境;所有呢,Shiro 就抽象了一个自己的 Session 来管理主体与应用之间交互的数据;这样的话,比如我们在 Web 环境用,刚开始是一台 Web 服务器;接着又上了台 EJB 服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到 Memcached 服务器); +- **SessionDAO** - DAO 大家都用过,数据访问对象,用于会话的 CRUD,比如我们想把 Session 保存到数据库,那么可以实现自己的 SessionDAO,通过如 JDBC 写到数据库;比如想把 Session 放到 Memcached 中,可以实现自己的 Memcached SessionDAO;另外 SessionDAO 中可以使用 Cache 进行缓存,以提高性能; +- **CacheManager** - 缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能 +- **Cryptography** - 密码模块,Shiro 提高了一些常见的加密组件用于如密码加密/解密的。 + +## 认证 + +## 授权 + +## 资料 + +- [shiro 官方文档](http://shiro.apache.org/reference.html) +- [跟我学 Shiro](http://jinnianshilongnian.iteye.com/category/305053) From d5aed7950ceabfdd47a8013323808a77f9088a8a Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Mon, 27 May 2019 23:42:37 +0800 Subject: [PATCH 15/75] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dunwu/javaee/filter/CacheFilter.java | 2 +- .../filter/CharacterEncodingFilter.java | 2 +- .../javaee/filter/ExceptionHandlerFilter.java | 2 +- .../dunwu/javaee/filter/FilterImpl.java | 2 +- .../dunwu/javaee/filter/GZipFilter.java | 2 +- .../javaee/filter/ImageRedirectFilter.java | 2 +- .../github/dunwu/javaee/filter/LogFilter.java | 2 +- .../github/dunwu/javaee/filter/MyFilter.java | 2 +- .../javaee/filter/OutputReplaceFilter.java | 2 +- .../dunwu/javaee/filter/PrivilegeFilter.java | 2 +- .../dunwu/javaee/filter/UploadFilter.java | 4 +- .../dunwu/javaee/filter/WaterMarkFilter.java | 2 +- .../dunwu/javaee/filter/XSLTFilter.java | 2 +- .../filter/wrapper/CacheResponseWrapper.java | 2 +- .../filter/wrapper/GZipOutputStream.java | 2 +- .../filter/wrapper/GZipResponseWrapper.java | 2 +- .../wrapper/HttpCharacterResponseWrapper.java | 2 +- .../filter/wrapper/UploadRequestWrapper.java | 2 +- .../filter/wrapper/WaterMarkOutputStream.java | 2 +- .../wrapper/WaterMarkResponseWrapper.java | 2 +- .../dunwu/javaee/server/JettyFactory.java | 2 +- .../dunwu/javaee/server/QuickStartServer.java | 2 +- .../dunwu/javaee/server/JettyFactory.java | 2 +- .../dunwu/javaee/server/QuickStartServer.java | 2 +- .../dunwu/javaee/server/JettyFactory.java | 2 +- .../dunwu/javaee/server/QuickStartServer.java | 2 +- .../javaee-tutorial-listener/pom.xml | 5 - .../javaee/listener/LoginSessionListener.java | 4 +- .../MyHttpSessionActivationListener.java | 14 +- .../MyHttpSessionAttributeListener.java | 34 +- .../MyHttpSessionBindingListener.java | 5 + .../listener/MyHttpSessionListener.java | 8 +- .../MyServletContextAttributeListener.java | 14 +- .../listener/MyServletContextListener.java | 18 +- .../MyServletRequestAttributeListener.java | 14 +- .../listener/MyServletRequestListener.java | 8 +- .../javaee/listener/PersonInfoListener.java | 14 +- .../SessionAttributeListenerTest.java | 35 -- .../src/main/webapp/WEB-INF/web.xml | 94 ++-- .../dunwu/javaee/server/JettyFactory.java | 51 ++- ...erver.java => ListenerDemosBootstrap.java} | 4 +- .../github/dunwu/javaee/server/Profiles.java | 29 -- .../dunwu/javaee/servlet/HelloServlet.java | 2 +- .../dunwu/javaee/server/JettyFactory.java | 2 +- .../dunwu/javaee/server/QuickStartServer.java | 2 +- .../dunwu/javaee/cookie/AddCookies.java | 2 +- .../dunwu/javaee/cookie/DeleteCookies.java | 2 +- .../dunwu/javaee/cookie/ReadCookies.java | 2 +- .../dunwu/javaee/cookie/bean/Person.java | 2 +- .../dunwu/javaee/cookie/bean/Topic.java | 2 +- .../javaee/session/SessionTrackServlet.java | 2 +- .../dunwu/javaee/server/JettyFactory.java | 2 +- .../dunwu/javaee/server/QuickStartServer.java | 2 +- .../github/dunwu/javaee/taglib/Copyright.java | 2 +- .../github/dunwu/javaee/taglib/HelloTag.java | 2 +- .../github/dunwu/javaee/taglib/HelloTag2.java | 2 +- .../github/dunwu/javaee/taglib/HelloTag3.java | 2 +- .../dunwu/javaee/server/JettyFactory.java | 2 +- .../dunwu/javaee/server/QuickStartServer.java | 2 +- .../javaee/websocket/WebSocketServer.java | 2 +- .../WebSocketServerConfigurator.java | 2 +- .../github/dunwu/javaee/server/DevServer.java | 2 +- codes/javaee/parent/pom.xml | 409 ------------------ 63 files changed, 185 insertions(+), 669 deletions(-) delete mode 100644 codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/SessionAttributeListenerTest.java rename codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/{QuickStartServer.java => ListenerDemosBootstrap.java} (89%) delete mode 100644 codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/Profiles.java delete mode 100644 codes/javaee/parent/pom.xml diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/CacheFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/CacheFilter.java index cc79cb30..32208c5c 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/CacheFilter.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/CacheFilter.java @@ -22,7 +22,7 @@ import io.github.dunwu.javaee.filter.wrapper.CacheResponseWrapper; /** - * @author Zhang Peng + * @author
Zhang Peng * @date 2017/3/27. */ public class CacheFilter extends MyFilter { diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/CharacterEncodingFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/CharacterEncodingFilter.java index 5fecbb22..bf1c7993 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/CharacterEncodingFilter.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/CharacterEncodingFilter.java @@ -14,7 +14,7 @@ import javax.servlet.http.HttpServletRequest; /** - * @author Zhang Peng + * @author Zhang Peng * @date 2017/3/27. */ public class CharacterEncodingFilter extends MyFilter { diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/ExceptionHandlerFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/ExceptionHandlerFilter.java index d0c9e166..28b4d2c9 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/ExceptionHandlerFilter.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/ExceptionHandlerFilter.java @@ -11,7 +11,7 @@ import io.github.dunwu.javaee.filter.exception.BusinessException; /** - * @author Zhang Peng + * @author Zhang Peng * @date 2017/3/27. */ public class ExceptionHandlerFilter extends MyFilter { diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/FilterImpl.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/FilterImpl.java index b47466e2..731e2d9e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/FilterImpl.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/FilterImpl.java @@ -10,7 +10,7 @@ import javax.servlet.ServletResponse; /** - * @author Zhang Peng + * @author Zhang Peng * @date 2017/3/27. */ public class FilterImpl implements Filter { diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/GZipFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/GZipFilter.java index 8b272274..e8b7ed94 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/GZipFilter.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/GZipFilter.java @@ -12,7 +12,7 @@ import io.github.dunwu.javaee.filter.wrapper.GZipResponseWrapper; /** - * @author Zhang Peng + * @author Zhang Peng * @date 2017/3/27. */ public class GZipFilter extends MyFilter { diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/ImageRedirectFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/ImageRedirectFilter.java index b71387fa..74d29db5 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/ImageRedirectFilter.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/ImageRedirectFilter.java @@ -10,7 +10,7 @@ import javax.servlet.http.HttpServletResponse; /** - * @author Zhang Peng + * @author Zhang Peng * @date 2017/3/27. */ public class ImageRedirectFilter extends MyFilter { diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/LogFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/LogFilter.java index 7f0b2feb..e20bd597 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/LogFilter.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/LogFilter.java @@ -10,7 +10,7 @@ import javax.servlet.http.HttpServletResponse; /** - * @author Zhang Peng + * @author Zhang Peng * @date 2017/3/27. */ public class LogFilter extends MyFilter { diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/MyFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/MyFilter.java index e328b019..96eb9caf 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/MyFilter.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/MyFilter.java @@ -16,7 +16,7 @@ import org.slf4j.LoggerFactory; /** - * @author Zhang Peng + * @author Zhang Peng * @date 2017/3/27. */ public abstract class MyFilter implements Filter { diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/OutputReplaceFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/OutputReplaceFilter.java index b3a18566..9b602a1d 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/OutputReplaceFilter.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/OutputReplaceFilter.java @@ -15,7 +15,7 @@ import io.github.dunwu.javaee.filter.wrapper.HttpCharacterResponseWrapper; /** - * @author Zhang Peng + * @author Zhang Peng * @date 2017/3/27. */ public class OutputReplaceFilter extends MyFilter { diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/PrivilegeFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/PrivilegeFilter.java index af1b09cf..46a06fe4 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/PrivilegeFilter.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/PrivilegeFilter.java @@ -14,7 +14,7 @@ import io.github.dunwu.javaee.filter.exception.AccountException; /** - * @author Zhang Peng + * @author Zhang Peng * @date 2017/3/27. */ public class PrivilegeFilter extends MyFilter { diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/UploadFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/UploadFilter.java index 715d450c..4bf93b69 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/UploadFilter.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/UploadFilter.java @@ -11,8 +11,8 @@ import io.github.dunwu.javaee.filter.wrapper.UploadRequestWrapper; /** - * @author Zhang Peng - * @date 2017/4/4. + * @author Zhang Peng + * @since 2017-04-04 */ public class UploadFilter extends MyFilter { @Override diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/WaterMarkFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/WaterMarkFilter.java index 3cbc577e..de4ee857 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/WaterMarkFilter.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/WaterMarkFilter.java @@ -13,7 +13,7 @@ import io.github.dunwu.javaee.filter.wrapper.WaterMarkResponseWrapper; /** - * @author Zhang Peng + * @author Zhang Peng * @date 2017/3/27. */ public class WaterMarkFilter extends MyFilter { diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/XSLTFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/XSLTFilter.java index 4e56cab9..cd953897 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/XSLTFilter.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/XSLTFilter.java @@ -19,7 +19,7 @@ import javax.xml.transform.stream.StreamSource; /** - * @author Zhang Peng + * @author Zhang Peng * @date 2017/3/28. */ public class XSLTFilter extends MyFilter { diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/CacheResponseWrapper.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/CacheResponseWrapper.java index 5024bd34..a3cb3858 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/CacheResponseWrapper.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/CacheResponseWrapper.java @@ -8,7 +8,7 @@ import javax.servlet.http.HttpServletResponseWrapper; /** - * @author Zhang Peng + * @author Zhang Peng * @date 2017/3/27. */ public class CacheResponseWrapper extends HttpServletResponseWrapper { diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/GZipOutputStream.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/GZipOutputStream.java index 2a025044..e8721027 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/GZipOutputStream.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/GZipOutputStream.java @@ -9,7 +9,7 @@ import javax.servlet.http.HttpServletResponse; /** - * @author Zhang Peng + * @author Zhang Peng * @date 2017/3/27. */ public class GZipOutputStream extends ServletOutputStream { diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/GZipResponseWrapper.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/GZipResponseWrapper.java index e564c82e..43257eca 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/GZipResponseWrapper.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/GZipResponseWrapper.java @@ -9,7 +9,7 @@ import javax.servlet.http.HttpServletResponseWrapper; /** - * @author Zhang Peng + * @author Zhang Peng * @date 2017/3/27. */ public class GZipResponseWrapper extends HttpServletResponseWrapper { diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/HttpCharacterResponseWrapper.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/HttpCharacterResponseWrapper.java index 07e1ecdf..7a7878a5 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/HttpCharacterResponseWrapper.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/HttpCharacterResponseWrapper.java @@ -8,7 +8,7 @@ import javax.servlet.http.HttpServletResponseWrapper; /** - * @author Zhang Peng + * @author Zhang Peng * @date 2017/3/27. */ public class HttpCharacterResponseWrapper extends HttpServletResponseWrapper { diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/UploadRequestWrapper.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/UploadRequestWrapper.java index 654e34fe..e79483a0 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/UploadRequestWrapper.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/UploadRequestWrapper.java @@ -15,7 +15,7 @@ import org.apache.commons.fileupload.FileItem; /** - * @author Zhang Peng + * @author Zhang Peng * @date 2017/3/27. */ public class UploadRequestWrapper extends HttpServletRequestWrapper { diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/WaterMarkOutputStream.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/WaterMarkOutputStream.java index 50c56bf3..6c37bb32 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/WaterMarkOutputStream.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/WaterMarkOutputStream.java @@ -7,7 +7,7 @@ import javax.servlet.WriteListener; /** - * @author Zhang Peng + * @author Zhang Peng * @date 2017/3/27. */ public class WaterMarkOutputStream extends ServletOutputStream { diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/WaterMarkResponseWrapper.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/WaterMarkResponseWrapper.java index e3f6589b..b4e121b1 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/WaterMarkResponseWrapper.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/WaterMarkResponseWrapper.java @@ -13,7 +13,7 @@ import net.coobird.thumbnailator.geometry.Positions; /** - * @author Zhang Peng + * @author Zhang Peng * @date 2017/3/27. */ public class WaterMarkResponseWrapper extends HttpServletResponseWrapper { diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java index 6d275e8c..a3085499 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java @@ -12,7 +12,7 @@ * JettyFactory 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij * 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 * - * @author Zhang Peng + * @author Zhang Peng */ @SuppressWarnings("unused") public class JettyFactory { diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java index 2936b162..dd0ac658 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java @@ -5,7 +5,7 @@ /** * 快速启动 jetty 服务器,方便测试 * - * @author Zhang Peng + * @author Zhang Peng */ public class QuickStartServer { // private static int STARTUP_TYPE = JettyFactory.IDE_ECLIPSE; diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java b/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java index 2c85755f..5aa9d247 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java @@ -12,7 +12,7 @@ * JettyFactory 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij * 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 * - * @author Zhang Peng + * @author Zhang Peng */ @SuppressWarnings("unused") public class JettyFactory { diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java b/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java index 2936b162..dd0ac658 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java @@ -5,7 +5,7 @@ /** * 快速启动 jetty 服务器,方便测试 * - * @author Zhang Peng + * @author Zhang Peng */ public class QuickStartServer { // private static int STARTUP_TYPE = JettyFactory.IDE_ECLIPSE; diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java b/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java index f20d87b3..26e801e6 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java @@ -12,7 +12,7 @@ * JettyFactory 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij * 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 * - * @author Zhang Peng + * @author Zhang Peng */ @SuppressWarnings("unused") public class JettyFactory { diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java b/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java index 2936b162..dd0ac658 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java @@ -5,7 +5,7 @@ /** * 快速启动 jetty 服务器,方便测试 * - * @author Zhang Peng + * @author Zhang Peng */ public class QuickStartServer { // private static int STARTUP_TYPE = JettyFactory.IDE_ECLIPSE; diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/pom.xml b/codes/javaee-tutorial/javaee-tutorial-listener/pom.xml index 3b55a73e..ca830201 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/pom.xml +++ b/codes/javaee-tutorial/javaee-tutorial-listener/pom.xml @@ -85,11 +85,6 @@ junit test - - org.assertj - assertj-core - test - diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/LoginSessionListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/LoginSessionListener.java index 9d0ee026..21bbcac4 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/LoginSessionListener.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/LoginSessionListener.java @@ -14,8 +14,8 @@ /** * 单点登录处理类 * - * @author Zhang Peng - * @date 2017/4/4. + * @author Zhang Peng + * @since 2017-04-04 */ public class LoginSessionListener implements HttpSessionAttributeListener { diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionActivationListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionActivationListener.java index bbd38402..24c1a9ea 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionActivationListener.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionActivationListener.java @@ -1,7 +1,3 @@ -/** - * The Apache License 2.0 - * Copyright (c) 2017 Zhang Peng - */ package io.github.dunwu.javaee.listener; import org.slf4j.Logger; @@ -12,8 +8,10 @@ import java.io.Serializable; /** - * @author Zhang Peng - * @date 2017/4/4. + * HttpSessionActivationListener 接口的 JavaBean 对象可以感知自己被序列化或反序列化的事件。 + * + * @author Zhang Peng + * @since 2017-04-04 */ public class MyHttpSessionActivationListener implements HttpSessionActivationListener, Serializable { @@ -25,12 +23,12 @@ public class MyHttpSessionActivationListener implements HttpSessionActivationLis @Override public void sessionWillPassivate(HttpSessionEvent se) { - logger.debug(name + "和session一起被序列化(钝化)到硬盘了,session的id是:" + se.getSession().getId()); + logger.debug(name + "和session一起被序列化到硬盘了,session的id是:" + se.getSession().getId()); } @Override public void sessionDidActivate(HttpSessionEvent se) { - logger.debug(name + "和session一起从硬盘反序列化(活化)回到内存了,session的id是:" + se.getSession().getId()); + logger.debug(name + "和session一起从硬盘反序列化回到内存了,session的id是:" + se.getSession().getId()); } public String getName() { diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionAttributeListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionAttributeListener.java index 250095f6..19a3e1a5 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionAttributeListener.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionAttributeListener.java @@ -1,42 +1,44 @@ package io.github.dunwu.javaee.listener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionAttributeListener; import javax.servlet.http.HttpSessionBindingEvent; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - +/** + * HttpSessionAttributeListener 用于监听 HttpSession 中的属性变化 + * + * @author Zhang Peng + * @since 2017-04-04 + */ public class MyHttpSessionAttributeListener implements HttpSessionAttributeListener { private final Logger logger = LoggerFactory.getLogger(getClass()); - /** - * 添加属性 - */ + // 添加属性 @Override public void attributeAdded(HttpSessionBindingEvent se) { + HttpSession session = se.getSession(); String name = se.getName(); - logger.debug("新建session属性:key={}, value={}", name, se.getValue()); + logger.info("新建session属性:" + name + ", 值为:" + se.getValue()); } - /** - * 删除属性 - */ + // 删除属性 @Override public void attributeRemoved(HttpSessionBindingEvent se) { + HttpSession session = se.getSession(); String name = se.getName(); - logger.debug("新建session属性:key={}, value={}", name, se.getValue()); + logger.info("删除session属性:" + name + ", 值为:" + se.getValue()); } - /** - * 修改属性 - */ + // 修改属性 @Override public void attributeReplaced(HttpSessionBindingEvent se) { HttpSession session = se.getSession(); String name = se.getName(); - logger.debug("新建session属性:key={}, 原value={}, 现value={}", name, se.getValue(), - session.getAttribute(name)); + Object oldValue = se.getValue(); + logger.info("修改session属性:" + name + ", 原值:" + oldValue + ", 新值:" + session.getAttribute(name)); } } diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionBindingListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionBindingListener.java index e61c85d7..e6a190ec 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionBindingListener.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionBindingListener.java @@ -6,6 +6,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * HttpSessionBindingListener 接口的 JavaBean 对象可以感知自己被绑定或解绑定到 Session 中的事件。 + * @author Zhang Peng + * @since 2017-04-04 + */ public class MyHttpSessionBindingListener implements HttpSessionBindingListener { private final Logger logger = LoggerFactory.getLogger(getClass()); diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionListener.java index 37817bec..f1b21bc9 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionListener.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionListener.java @@ -1,6 +1,3 @@ -/** - * The Apache License 2.0 Copyright (c) 2017 Zhang Peng - */ package io.github.dunwu.javaee.listener; import java.util.Date; @@ -14,8 +11,9 @@ import org.slf4j.LoggerFactory; /** - * @author Zhang Peng - * @date 2017/4/4. + * HttpSessionListener 接口用于监听 HttpSession 对象的创建和销毁。 + * @author Zhang Peng + * @since 2017-04-04 */ public class MyHttpSessionListener implements HttpSessionListener { private final Logger logger = LoggerFactory.getLogger(getClass()); diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextAttributeListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextAttributeListener.java index 8a35d16f..cbfe7716 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextAttributeListener.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextAttributeListener.java @@ -1,19 +1,16 @@ -/** - * The Apache License 2.0 Copyright (c) 2017 Zhang Peng - */ package io.github.dunwu.javaee.listener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.text.MessageFormat; - import javax.servlet.ServletContextAttributeEvent; import javax.servlet.ServletContextAttributeListener; /** - * @author Zhang Peng - * @date 2017/4/4. + * ServletContextAttributeListener 用于监听 ServletContext 中的属性变化 + * + * @author Zhang Peng + * @since 2017-04-04 */ public class MyServletContextAttributeListener implements ServletContextAttributeListener { @@ -31,7 +28,6 @@ public void attributeRemoved(ServletContextAttributeEvent scab) { @Override public void attributeReplaced(ServletContextAttributeEvent scab) { - logger.debug("ServletContext域对象中替换了属性:{},原值是:{}, 现值是:{}", - scab.getName(), scab.getSource(), scab.getValue()); + logger.debug("ServletContext域对象中替换了属性:{},原值是:{}, 现值是:{}", scab.getName(), scab.getSource(), scab.getValue()); } } diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextListener.java index 2ed2324b..2f0b96f9 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextListener.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextListener.java @@ -1,23 +1,19 @@ -/** - * The Apache License 2.0 Copyright (c) 2017 Zhang Peng - */ package io.github.dunwu.javaee.listener; -import java.util.Date; +import io.github.dunwu.javaee.listener.util.ApplicationConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; - -import io.github.dunwu.javaee.listener.util.ApplicationConstants; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import java.util.Date; /** - * ServletContextListener接口用于监听ServletContext对象的创建和销毁事件。 + * ServletContextListener 接口用于监听 ServletContext 对象的创建和销毁事件。 * - * @author Zhang Peng - * @date 2017/4/4. + * @author Zhang Peng + * @since 2017-04-04 */ public class MyServletContextListener implements ServletContextListener { diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestAttributeListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestAttributeListener.java index 28816c9a..3693736b 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestAttributeListener.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestAttributeListener.java @@ -1,19 +1,16 @@ -/** - * The Apache License 2.0 Copyright (c) 2017 Zhang Peng - */ package io.github.dunwu.javaee.listener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.text.MessageFormat; - import javax.servlet.ServletRequestAttributeEvent; import javax.servlet.ServletRequestAttributeListener; /** - * @author Zhang Peng - * @date 2017/4/4. + * ServletContextAttributeListener 用于监听 ServletRequest 中的属性变化 + * + * @author Zhang Peng + * @since 2017-04-04 */ public class MyServletRequestAttributeListener implements ServletRequestAttributeListener { @@ -31,7 +28,6 @@ public void attributeRemoved(ServletRequestAttributeEvent srae) { @Override public void attributeReplaced(ServletRequestAttributeEvent srae) { - logger.debug("ServletRequest域对象中替换了属性:{},原值是:{}, 现值是:{}", - srae.getName(), srae.getSource(), srae.getValue()); + logger.debug("ServletRequest域对象中替换了属性:{},原值是:{}, 现值是:{}", srae.getName(), srae.getSource(), srae.getValue()); } } diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestListener.java index 8bdd9655..578e83ed 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestListener.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestListener.java @@ -1,6 +1,3 @@ -/** - * The Apache License 2.0 Copyright (c) 2017 Zhang Peng - */ package io.github.dunwu.javaee.listener; import javax.servlet.ServletRequestEvent; @@ -12,8 +9,9 @@ import org.slf4j.LoggerFactory; /** - * @author Zhang Peng - * @date 2017/4/4. + * ServletRequestListener 接口用于监听 ServletRequest 对象的创建和销毁。 + * @author Zhang Peng + * @since 2017-04-04 */ public class MyServletRequestListener implements ServletRequestListener { diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/PersonInfoListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/PersonInfoListener.java index 458f8a02..da0c4cf6 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/PersonInfoListener.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/PersonInfoListener.java @@ -12,12 +12,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * @author Zhang Peng + * @since 2017-04-04 + */ public class PersonInfoListener implements HttpSessionActivationListener, HttpSessionBindingListener, Serializable { private static final long serialVersionUID = -4780592776386225973L; - Logger log = LoggerFactory.getLogger(getClass()); + private final Logger logger = LoggerFactory.getLogger(getClass()); private String name; @@ -42,20 +46,20 @@ public void setDateCreated(Date dateCreated) { // 从硬盘加载后 public void sessionDidActivate(HttpSessionEvent se) { HttpSession session = se.getSession(); - log.info(this + "已经成功从硬盘中加载。sessionId: " + session.getId()); + logger.info(this + "已经成功从硬盘中加载。sessionId: " + session.getId()); } // 即将被钝化到硬盘时 public void sessionWillPassivate(HttpSessionEvent se) { HttpSession session = se.getSession(); - log.info(this + "即将保存到硬盘。sessionId: " + session.getId()); + logger.info(this + "即将保存到硬盘。sessionId: " + session.getId()); } // 被放进session前 public void valueBound(HttpSessionBindingEvent event) { HttpSession session = event.getSession(); String name = event.getName(); - log.info(this + "被绑定到session \"" + session.getId() + "\"的" + name + logger.info(this + "被绑定到session \"" + session.getId() + "\"的" + name + "属性上"); // 记录放到session中的时间 @@ -66,7 +70,7 @@ public void valueBound(HttpSessionBindingEvent event) { public void valueUnbound(HttpSessionBindingEvent event) { HttpSession session = event.getSession(); String name = event.getName(); - log.info(this + "被从session \"" + session.getId() + "\"的" + name + logger.info(this + "被从session \"" + session.getId() + "\"的" + name + "属性上移除"); } diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/SessionAttributeListenerTest.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/SessionAttributeListenerTest.java deleted file mode 100644 index 1d2a4cb2..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/SessionAttributeListenerTest.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.github.dunwu.javaee.listener; - -import javax.servlet.http.HttpSession; -import javax.servlet.http.HttpSessionAttributeListener; -import javax.servlet.http.HttpSessionBindingEvent; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class SessionAttributeListenerTest implements HttpSessionAttributeListener { - - Logger logger = LoggerFactory.getLogger(getClass()); - - // 添加属性 - public void attributeAdded(HttpSessionBindingEvent se) { - HttpSession session = se.getSession(); - String name = se.getName(); - logger.info("新建session属性:" + name + ", 值为:" + se.getValue()); - } - - // 删除属性 - public void attributeRemoved(HttpSessionBindingEvent se) { - HttpSession session = se.getSession(); - String name = se.getName(); - logger.info("删除session属性:" + name + ", 值为:" + se.getValue()); - } - - // 修改属性 - public void attributeReplaced(HttpSessionBindingEvent se) { - HttpSession session = se.getSession(); - String name = se.getName(); - Object oldValue = se.getValue(); - logger.info("修改session属性:" + name + ", 原值:" + oldValue + ", 新值:" + session.getAttribute(name)); - } -} diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/WEB-INF/web.xml b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/WEB-INF/web.xml index 98a6b99b..36c01cd6 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/WEB-INF/web.xml +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/WEB-INF/web.xml @@ -3,67 +3,55 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> - - contextPath - value. sdf - + + contextPath + value. sdf + - - - - - io.github.dunwu.javaee.listener.MyHttpSessionListener - - - - - io.github.dunwu.javaee.listener.MyServletContextListener - - - - - io.github.dunwu.javaee.listener.MyServletRequestListener - - + + + io.github.dunwu.javaee.listener.MyHttpSessionListener + + + io.github.dunwu.javaee.listener.MyServletContextListener + + + io.github.dunwu.javaee.listener.MyServletRequestListener + - - - - io.github.dunwu.javaee.listener.MyHttpSessionAttributeListener - - - - - io.github.dunwu.javaee.listener.MyServletContextAttributeListener - - - - - io.github.dunwu.javaee.listener.MyServletRequestAttributeListener - - + + + io.github.dunwu.javaee.listener.MyHttpSessionAttributeListener + + + io.github.dunwu.javaee.listener.MyServletContextAttributeListener + + + io.github.dunwu.javaee.listener.MyServletRequestAttributeListener + - - - - io.github.dunwu.javaee.listener.MyHttpSessionBindingListener - - + + + io.github.dunwu.javaee.listener.MyHttpSessionBindingListener + + + io.github.dunwu.javaee.listener.MyHttpSessionActivationListener + - - + + io.github.dunwu.javaee.listener.LoginSessionListener - + - - + + io.github.dunwu.javaee.listener.PersonInfoListener - - + + - - index.jsp - + + index.jsp + diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java index 6d275e8c..f9eb3106 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java @@ -1,35 +1,44 @@ package io.github.dunwu.javaee.server; -import java.util.ArrayList; - import org.apache.commons.lang3.StringUtils; -import org.assertj.core.util.Lists; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.webapp.WebAppClassLoader; import org.eclipse.jetty.webapp.WebAppContext; +import java.util.ArrayList; +import java.util.List; + /** - * JettyFactory 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij - * 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 + * JettyFactory 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 * - * @author Zhang Peng + * @author Zhang Peng */ -@SuppressWarnings("unused") +@SuppressWarnings("all") public class JettyFactory { - private static final int PORT = 8080; + private static final int PORT = 9527; private static final String CONTEXT = "/"; private static final String RESOURCE_BASE_PATH = "src/main/webapp"; private static final String WEB_XML_PATH = "/WEB-INF/web.xml"; - private static final String[] TLD_JAR_NAMES = - new String[]{"sitemesh", "spring-webmvc", "shiro-web", "tiles"}; + private static final String[] TLD_JAR_NAMES = new String[] {"sitemesh", "spring-webmvc", "tiles"}; private static final String WINDOWS_WEBDEFAULT_PATH = "jetty/webdefault.xml"; public static final int IDE_ECLIPSE = 0; public static final int IDE_INTELLIJ = 1; + public static final String ACTIVE_PROFILE = "spring.profiles.active"; + public static final String DEFAULT_PROFILE = "spring.profiles.default"; + public static final String DEVELOPMENT = "development"; + + /** + * 在Spring启动前,设置profile的环境变量。 + */ + public static void setProfileAsSystemProperty(String profile) { + System.setProperty(ACTIVE_PROFILE, profile); + } + public static Server initServer() { - Profiles.setProfileAsSystemProperty(Profiles.DEVELOPMENT); + setProfileAsSystemProperty(DEVELOPMENT); WebAppContext webAppContext = new WebAppContext(); Server server = new Server(PORT); server.setHandler(webAppContext); @@ -75,8 +84,8 @@ public static void startServer(Server server) throws Exception { public static String getAbsolutePath() { String path = null; - String folderPath = JettyFactory.class.getProtectionDomain().getCodeSource().getLocation() - .getPath().substring(1); + String folderPath = + JettyFactory.class.getProtectionDomain().getCodeSource().getLocation().getPath().substring(1); if (folderPath.indexOf("target") > 0) { path = folderPath.substring(0, folderPath.indexOf("target")); } @@ -88,21 +97,25 @@ public static void supportJspAndSetTldJarNames(Server server, String... jarNames // This webapp will use jsps and jstl. We need to enable the AnnotationConfiguration in // order to correctly set up the jsp container org.eclipse.jetty.webapp.Configuration.ClassList classlist = - org.eclipse.jetty.webapp.Configuration.ClassList.setServerDefault(server); + org.eclipse.jetty.webapp.Configuration.ClassList.setServerDefault(server); classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", - "org.eclipse.jetty.annotations.AnnotationConfiguration"); + "org.eclipse.jetty.annotations.AnnotationConfiguration"); // Set the ContainerIncludeJarPattern so that jetty examines these container-path jars for // tlds, web-fragments etc. // If you omit the jar that contains the jstl .tlds, the jsp engine will scan for them // instead. - ArrayList jarNameExprssions = Lists.newArrayList(".*/[^/]*servlet-api-[^/]*\\.jar$", - ".*/javax.servlet.jsp.jstl-.*\\.jar$", ".*/[^/]*taglibs.*\\.jar$"); + + List list = new ArrayList<>(); + list.add(".*/[^/]*servlet-api-[^/]*\\.jar$"); + list.add(".*/javax.servlet.jsp.jstl-.*\\.jar$"); + list.add(".*/[^/]*taglibs.*\\.jar$"); for (String jarName : jarNames) { - jarNameExprssions.add(".*/" + jarName + "-[^/]*\\.jar$"); + String str = ".*/" + jarName + "-[^/]*\\.jar$"; + list.add(str); } context.setAttribute("org.eclipse.jetty.io.github.dunwu.javaee.server.webapp.ContainerIncludeJarPattern", - StringUtils.join(jarNameExprssions, '|')); + StringUtils.join(list, '|')); } } diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/ListenerDemosBootstrap.java similarity index 89% rename from codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java rename to codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/ListenerDemosBootstrap.java index 2936b162..b0c8d3da 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/ListenerDemosBootstrap.java @@ -5,9 +5,9 @@ /** * 快速启动 jetty 服务器,方便测试 * - * @author Zhang Peng + * @author Zhang Peng */ -public class QuickStartServer { +public class ListenerDemosBootstrap { // private static int STARTUP_TYPE = JettyFactory.IDE_ECLIPSE; private static int STARTUP_TYPE = JettyFactory.IDE_INTELLIJ; diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/Profiles.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/Profiles.java deleted file mode 100644 index 3a8ee7c4..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/Profiles.java +++ /dev/null @@ -1,29 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2005, 2014 springside.github.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - *******************************************************************************/ -package io.github.dunwu.javaee.server; - -/** - * Spring profile 常用方法与profile名称。 - * - * @author calvin - */ -public class Profiles { - - public static final String ACTIVE_PROFILE = "spring.profiles.active"; - public static final String DEFAULT_PROFILE = "spring.profiles.default"; - - public static final String PRODUCTION = "production"; - public static final String DEVELOPMENT = "development"; - public static final String UNIT_TEST = "test"; - public static final String FUNCTIONAL_TEST = "functional"; - - /** - * 在Spring启动前,设置profile的环境变量。 - */ - public static void setProfileAsSystemProperty(String profile) { - System.setProperty(ACTIVE_PROFILE, profile); - } -} diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/HelloServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/HelloServlet.java index b653b59e..41084cc6 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/HelloServlet.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/HelloServlet.java @@ -12,7 +12,7 @@ import java.io.PrintWriter; /** - * @author Zhang Peng + * @author Zhang Peng * @date 2016/12/23. */ public class HelloServlet extends HttpServlet { diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java index 373e9e8c..721835ae 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java @@ -12,7 +12,7 @@ * JettyFactory 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij * 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 * - * @author Zhang Peng + * @author Zhang Peng */ @SuppressWarnings("unused") public class JettyFactory { diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java index 2936b162..dd0ac658 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java @@ -5,7 +5,7 @@ /** * 快速启动 jetty 服务器,方便测试 * - * @author Zhang Peng + * @author Zhang Peng */ public class QuickStartServer { // private static int STARTUP_TYPE = JettyFactory.IDE_ECLIPSE; diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/AddCookies.java b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/AddCookies.java index b41be2cd..bd34fb53 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/AddCookies.java +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/AddCookies.java @@ -15,7 +15,7 @@ import javax.servlet.http.HttpServletResponse; /** - * @author Zhang Peng + * @author Zhang Peng * @date 2017/3/26. */ @WebServlet("/servlet/AddCookies") diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/DeleteCookies.java b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/DeleteCookies.java index 8d5d7338..5f7dfb64 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/DeleteCookies.java +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/DeleteCookies.java @@ -14,7 +14,7 @@ import javax.servlet.http.HttpServletResponse; /** - * @author Zhang Peng + * @author Zhang Peng * @date 2017/3/26. */ @WebServlet("/servlet/DeleteCookies") diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/ReadCookies.java b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/ReadCookies.java index df12236c..f2d63937 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/ReadCookies.java +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/ReadCookies.java @@ -15,7 +15,7 @@ import javax.servlet.http.HttpServletResponse; /** - * @author Zhang Peng + * @author Zhang Peng * @date 2017/3/26. */ @WebServlet("/servlet/ReadCookies") diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Person.java b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Person.java index fdaee7ed..a8308691 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Person.java +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Person.java @@ -7,7 +7,7 @@ import java.util.Date; /** - * @author Zhang Peng + * @author Zhang Peng * @date 2017/3/26. */ public class Person implements Serializable { diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Topic.java b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Topic.java index 8867ba75..9f08813a 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Topic.java +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Topic.java @@ -4,7 +4,7 @@ package io.github.dunwu.javaee.cookie.bean; /** - * @author Zhang Peng + * @author Zhang Peng * @date 2017/3/26. */ public class Topic { diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/session/SessionTrackServlet.java b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/session/SessionTrackServlet.java index b341ff93..2940d7b6 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/session/SessionTrackServlet.java +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/session/SessionTrackServlet.java @@ -16,7 +16,7 @@ import javax.servlet.http.HttpSession; /** - * @author Zhang Peng + * @author Zhang Peng * @date 2017/3/26. */ @WebServlet("/servlet/SessionTrackServlet") diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java b/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java index ba3db0e5..bd66a4e1 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java @@ -12,7 +12,7 @@ * JettyFactory 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij * 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 * - * @author Zhang Peng + * @author Zhang Peng */ @SuppressWarnings("unused") public class JettyFactory { diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java b/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java index 2936b162..dd0ac658 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java @@ -5,7 +5,7 @@ /** * 快速启动 jetty 服务器,方便测试 * - * @author Zhang Peng + * @author Zhang Peng */ public class QuickStartServer { // private static int STARTUP_TYPE = JettyFactory.IDE_ECLIPSE; diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/Copyright.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/Copyright.java index 9574545a..dbae07e3 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/Copyright.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/Copyright.java @@ -12,7 +12,7 @@ import javax.servlet.jsp.tagext.Tag; /** - * @author Zhang Peng + * @author Zhang Peng * @date 2017/4/3. */ public class Copyright implements Tag { diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag.java index a60cf4e4..52d8232d 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag.java @@ -4,7 +4,7 @@ package io.github.dunwu.javaee.taglib; /** - * @author Zhang Peng + * @author Zhang Peng * @date 2017/4/3. */ diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag2.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag2.java index 9355e4d9..9f3f6af9 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag2.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag2.java @@ -10,7 +10,7 @@ import javax.servlet.jsp.tagext.SimpleTagSupport; /** - * @author Zhang Peng + * @author Zhang Peng * @date 2017/4/3. */ public class HelloTag2 extends SimpleTagSupport { diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag3.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag3.java index 85516fd7..f8caaf72 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag3.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag3.java @@ -11,7 +11,7 @@ import javax.servlet.jsp.tagext.SimpleTagSupport; /** - * @author Zhang Peng + * @author Zhang Peng * @date 2017/4/3. */ public class HelloTag3 extends SimpleTagSupport { diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java index f20d87b3..26e801e6 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java @@ -12,7 +12,7 @@ * JettyFactory 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij * 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 * - * @author Zhang Peng + * @author Zhang Peng */ @SuppressWarnings("unused") public class JettyFactory { diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java index 2936b162..dd0ac658 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java @@ -5,7 +5,7 @@ /** * 快速启动 jetty 服务器,方便测试 * - * @author Zhang Peng + * @author Zhang Peng */ public class QuickStartServer { // private static int STARTUP_TYPE = JettyFactory.IDE_ECLIPSE; diff --git a/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/websocket/WebSocketServer.java b/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/websocket/WebSocketServer.java index e54635c2..a1b40405 100644 --- a/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/websocket/WebSocketServer.java +++ b/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/websocket/WebSocketServer.java @@ -24,7 +24,7 @@ /** * Websocket 消息处理中心 * @see https://github.com/jetty-project/embedded-jetty-websocket-examples - * @author Zhang Peng + * @author Zhang Peng */ @ServerEndpoint(value = "/auth/user/{id}", configurator = WebSocketServerConfigurator.class) public class WebSocketServer { diff --git a/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/websocket/WebSocketServerConfigurator.java b/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/websocket/WebSocketServerConfigurator.java index 6b421f59..36f15097 100644 --- a/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/websocket/WebSocketServerConfigurator.java +++ b/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/websocket/WebSocketServerConfigurator.java @@ -6,7 +6,7 @@ import javax.websocket.server.ServerEndpointConfig; /** - * @author Zhang Peng + * @author Zhang Peng */ public class WebSocketServerConfigurator extends ServerEndpointConfig.Configurator { @Override diff --git a/codes/javaee-tutorial/javaee-tutorial-websocket/src/test/java/io/github/dunwu/javaee/server/DevServer.java b/codes/javaee-tutorial/javaee-tutorial-websocket/src/test/java/io/github/dunwu/javaee/server/DevServer.java index 6e6408a6..bc049622 100644 --- a/codes/javaee-tutorial/javaee-tutorial-websocket/src/test/java/io/github/dunwu/javaee/server/DevServer.java +++ b/codes/javaee-tutorial/javaee-tutorial-websocket/src/test/java/io/github/dunwu/javaee/server/DevServer.java @@ -16,7 +16,7 @@ /** * DevServer 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 * - * @author Zhang Peng + * @author Zhang Peng */ @SuppressWarnings("unused") public class DevServer { diff --git a/codes/javaee/parent/pom.xml b/codes/javaee/parent/pom.xml deleted file mode 100644 index 9983883c..00000000 --- a/codes/javaee/parent/pom.xml +++ /dev/null @@ -1,409 +0,0 @@ - - - 4.0.0 - - - - - - io.github.dunwu - javaee-notes-parent - 1.0.0 - pom - - - - - - - - org.mongodb - mongo-java-driver - 3.3.0 - - - org.mongodb - mongodb-driver-async - 3.3.0 - - - redis.clients - jedis - 2.7.3 - - - com.alibaba - druid - 1.0.19 - - - mysql - mysql-connector-java - 5.1.39 - - - com.h2database - h2 - 1.4.196 - - - - - - org.jsoup - jsoup - 1.9.2 - - - - - - javax.servlet - javax.servlet-api - 3.1.0 - provided - - - javax.servlet.jsp - jsp-api - 2.2 - provided - - - javax.servlet - jstl - 1.2 - provided - - - javax.websocket - javax.websocket-api - 1.1 - provided - - - - - - org.eclipse.jetty - jetty-webapp - ${jetty.version} - test - - - org.eclipse.jetty - jetty-annotations - ${jetty.version} - test - - - org.eclipse.jetty - apache-jsp - ${jetty.version} - test - - - org.eclipse.jetty - apache-jstl - ${jetty.version} - test - - - org.eclipse.jetty.websocket - javax-websocket-server-impl - ${jetty.version} - - - - - - com.fasterxml.jackson.core - jackson-core - ${fasterxml.jackson} - - - com.fasterxml.jackson.core - jackson-databind - ${fasterxml.jackson} - - - org.codehaus.jackson - jackson-mapper-asl - 1.9.13 - - - com.alibaba - fastjson - 1.2.31 - - - com.google.code.gson - gson - 2.3.1 - - - - - - commons-logging - commons-logging - 1.2 - - - log4j - log4j - 1.2.17 - - - ch.qos.logback - logback-classic - 1.1.3 - - - ch.qos.logback - logback-core - 1.1.3 - - - org.logback-extensions - logback-ext-spring - 0.1.2 - - - org.slf4j - jcl-over-slf4j - 1.7.12 - - - - - - net.sf.dozer - dozer - 5.4.0 - - - org.slf4j - jcl-over-slf4j - - - org.slf4j - slf4j-log4j12 - - - - - - - - org.mybatis - mybatis - 3.4.2 - - - org.mybatis - mybatis-spring - 1.3.0 - - - org.mybatis.generator - mybatis-generator-core - 1.3.5 - - - - - - org.apache.shiro - shiro-core - ${shiro.version} - - - org.apache.shiro - shiro-web - ${shiro.version} - - - org.apache.shiro - shiro-ehcache - ${shiro.version} - - - org.apache.shiro - shiro-quartz - ${shiro.version} - - - org.apache.shiro - shiro-spring - ${shiro.version} - - - - - - org.apache.velocity - velocity - 1.7 - - - - - - junit - junit - 4.12 - test - - - org.assertj - assertj-core - 3.4.1 - test - - - - - - commons-codec - commons-codec - 1.9 - - - commons-collections - commons-collections - 3.2.1 - - - commons-dbcp - commons-dbcp - 1.2.2 - - - commons-fileupload - commons-fileupload - 1.3.3 - - - commons-io - commons-io - 2.4 - - - org.apache.commons - commons-lang3 - 3.4 - - - com.google.guava - guava - 19.0 - - - - - - dom4j - dom4j - 1.6 - - - - - - - - - UTF-8 - - - 9.4.12.v20180830 - 4.1.4.RELEASE - 1.4.0 - 4.12 - - 2.9.5 - - - - - - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.12 - - - org.apache.tomcat.maven - tomcat7-maven-plugin - 2.2 - - - org.eclipse.jetty - jetty-maven-plugin - ${jetty.version} - - - org.codehaus.mojo - aspectj-maven-plugin - - 1.2 - - - - org.aspectj - aspectjrt - 1.8.1 - - - org.aspectj - aspectjtools - 1.8.1 - - - - - - compile - test-compile - - - - - true - ${java.version} - ${java.version} - - - - - org.mybatis.generator - mybatis-generator-maven-plugin - 1.3.5 - - - org.mybatis.generator - mybatis-generator-core - 1.3.5 - - - - - - - - - - - javaee-notes-parent - javaee-notes 项目的父POM,管理所有子项目的依赖版本 - - - - From ccd09f59172e904ac0ffc0199b48d1469f267290 Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Mon, 27 May 2019 23:43:35 +0800 Subject: [PATCH 16/75] =?UTF-8?q?=E6=95=B4=E7=90=86=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/javaee/listener.md | 107 +++++++++++++++++++++------------------- docs/navbar.md | 8 +-- 2 files changed, 59 insertions(+), 56 deletions(-) diff --git a/docs/javaee/listener.md b/docs/javaee/listener.md index 9fd55642..1ebbfc71 100644 --- a/docs/javaee/listener.md +++ b/docs/javaee/listener.md @@ -1,11 +1,4 @@ ---- -title: Servlet 指南 -categories: ['java', 'javaweb', 'javaee'] -tags: ['java', 'javaweb', 'javaee', 'listener'] -date: 2017-11-08 ---- - -# 监听器 +# JavaEE 之 Listener 指南 > 监听器是一个专门用于对其他对象身上发生的事件或状态改变进行监听和相应处理的对象,当被监视的对象发生情况时,立即采取相应的行动。监听器其实就是一个实现特定接口的普通 java 程序,这个程序专门用于监听另一个 java 对象的方法调用或属性改变,当被监听对象发生上述事件后,监听器某个方法立即被执行。 @@ -16,12 +9,15 @@ date: 2017-11-08 - [监听对象的创建和销毁](#监听对象的创建和销毁) - [监听对象的属性变化](#监听对象的属性变化) - [监听 Session 内的对象](#监听-session-内的对象) +- [参考资料](#参考资料) ## 简介 -JavaWeb 中的监听器是 Servlet 规范中定义的一种特殊类,它用于监听 web 应用程序中的`ServletContext`, `HttpSession`和 `ServletRequest`等域对象的创建与销毁事件,以及监听这些域对象中的属性发生修改的事件。 +> 监听器(`Listener`)用于监听 web 应用程序中的`ServletContext`, `HttpSession`和 `ServletRequest`等域对象的创建与销毁事件,以及监听这些域对象中的属性发生修改的事件。 + +使用 Listener 不需要关注该类事件时怎样触发或者怎么调用相应的 Listener,只要记住该类事件触发时一定会调用相应的 Listener,遵循 Servlet 规范的服务器会自动完成相应工作。 ## 监听器的分类 @@ -36,104 +32,111 @@ Servlet 规范针对这三个对象上的操作,又把多种类型的监听器 #### HttpSessionListener -`HttpSessionListener` 接口用于监听`HttpSession`对象的创建和销毁 - -创建一个`Session`时,激发`sessionCreated (HttpSessionEvent se)` 方法 +**`HttpSessionListener` 接口用于监听 `HttpSession` 对象的创建和销毁。** -销毁一个`Session`时,激发`sessionDestroyed (HttpSessionEvent se)` 方法。 +- 创建一个 `Session` 时,激发 `sessionCreated (HttpSessionEvent se)` 方法 +- 销毁一个 `Session` 时,激发 `sessionDestroyed (HttpSessionEvent se)` 方法。 #### ServletContextListener -`ServletContextListener`接口用于监听`ServletContext`对象的创建和销毁事件。 +**`ServletContextListener` 接口用于监听 `ServletContext` 对象的创建和销毁事件。** -实现了`ServletContextListener`接口的类都可以对`ServletContext`对象的创建和销毁进行监听。 +实现了 `ServletContextListener` 接口的类都可以对 `ServletContext` 对象的创建和销毁进行监听。 -当`ServletContext`对象被创建时,激发`contextInitialized (ServletContextEvent sce)`方法。 +- 当 `ServletContext` 对象被创建时,激发 `contextInitialized (ServletContextEvent sce)` 方法。 +- 当 `ServletContext` 对象被销毁时,激发 `contextDestroyed(ServletContextEvent sce)` 方法。 -当`ServletContext`对象被销毁时,激发`contextDestroyed(ServletContextEvent sce)`方法。 +`ServletContext` 域对象创建和销毁时机: -`ServletContext`域对象创建和销毁时机: - -- 创建:服务器启动针对每一个 Web 应用创建`ServletContext` -- 销毁:服务器关闭前先关闭代表每一个 web 应用的`ServletContext` +- 创建:服务器启动针对每一个 Web 应用创建 `ServletContext` +- 销毁:服务器关闭前先关闭代表每一个 web 应用的 `ServletContext` #### ServletRequestListener -`ServletRequestListener`接口用于监听`ServletRequest` 对象的创建和销毁 - -`Request`对象被创建时,监听器的`requestInitialized(ServletRequestEvent sre)`方法将会被调用 - -`Request`对象被销毁时,监听器的`requestDestroyed(ServletRequestEvent sre)`方法将会被调用 +**`ServletRequestListener` 接口用于监听 `ServletRequest` 对象的创建和销毁。** -`ServletRequest`域对象创建和销毁时机: +- `Request` 对象被创建时,监听器的 `requestInitialized(ServletRequestEvent sre)` 方法将会被调用 +- `Request` 对象被销毁时,监听器的 `requestDestroyed(ServletRequestEvent sre)` 方法将会被调用 -创建:用户每一次访问都会创建 request 对象 +`ServletRequest` 域对象创建和销毁时机: -销毁:当前访问结束,request 对象就会销毁 +- 创建:用户每一次访问都会创建 request 对象 +- 销毁:当前访问结束,request 对象就会销毁 ### 监听对象的属性变化 -域对象中属性的变更的事件监听器就是用来监听 ServletContext, HttpSession, HttpServletRequest 这三个对象中的属性变更信息事件的监听器。 -这三个监听器接口分别是 ServletContextAttributeListener, HttpSessionAttributeListener 和 ServletRequestAttributeListener,这三个接口中都定义了三个方法来处理被监听对象中的属性的增加,删除和替换的事件,同一个事件在这三个接口中对应的方法名称完全相同,只是接受的参数类型不同。 +域对象中属性的变更的事件监听器就是用来监听 `ServletContext`、`HttpSession`、`HttpServletRequest` 这三个对象中的属性变更信息事件的监听器。 +这三个监听器接口分别是 `ServletContextAttributeListener`、`HttpSessionAttributeListener` `和 ServletRequestAttributeListener`,这三个接口中都定义了三个方法来处理被监听对象中的属性的增加,删除和替换的事件,同一个事件在这三个接口中对应的方法名称完全相同,只是接受的参数类型不同。 #### attributeAdded 方法 -当向被监听对象中增加一个属性时,web 容器就调用事件监听器的`attributeAdded`方法进行响应,这个方法接收一个事件类型的参数,监听器可以通过这个参数来获得正在增加属性的域对象和被保存到域中的属性对象 +当向被监听对象中增加一个属性时,web 容器就调用事件监听器的 `attributeAdded` 方法进行响应,这个方法接收一个事件类型的参数,监听器可以通过这个参数来获得正在增加属性的域对象和被保存到域中的属性对象 各个域属性监听器中的完整语法定义为: ```java public void attributeAdded(ServletContextAttributeEvent scae) -public void attributeReplaced(HttpSessionBindingEvent hsbe) +public void attributeReplaced(HttpSessionBindingEvent hsbe) public void attributeRmoved(ServletRequestAttributeEvent srae) ``` #### attributeRemoved 方法 -当删除被监听对象中的一个属性时,web 容器调用事件监听器的`attributeRemoved`方法进行响应 +当删除被监听对象中的一个属性时,web 容器调用事件监听器的 `attributeRemoved` 方法进行响应 各个域属性监听器中的完整语法定义为: ```java public void attributeRemoved(ServletContextAttributeEvent scae) -public void attributeRemoved (HttpSessionBindingEvent hsbe) -public void attributeRemoved (ServletRequestAttributeEvent srae) +public void attributeRemoved(HttpSessionBindingEvent hsbe) +public void attributeRemoved(ServletRequestAttributeEvent srae) ``` #### attributeReplaced 方法 -当监听器的域对象中的某个属性被替换时,web 容器调用事件监听器的`attributeReplaced`方法进行响应 +当监听器的域对象中的某个属性被替换时,web 容器调用事件监听器的 `attributeReplaced` 方法进行响应 各个域属性监听器中的完整语法定义为: ```java public void attributeReplaced(ServletContextAttributeEvent scae) -public void attributeReplaced (HttpSessionBindingEvent hsbe) -public void attributeReplaced (ServletRequestAttributeEvent srae) +public void attributeReplaced(HttpSessionBindingEvent hsbe) +public void attributeReplaced(ServletRequestAttributeEvent srae) ``` ### 监听 Session 内的对象 保存在 Session 域中的对象可以有多种状态: -- 绑定(session.setAttribute("bean",Object))到 Session 中; -- 从 Session 域中解除(session.removeAttribute("bean"))绑定; -- 随 Session 对象持久化到一个存储设备中; -- 随 Session 对象从一个存储设备中恢复 +- 绑定(`session.setAttribute("bean",Object)`)到 `Session` 中; +- 从 `Session` 域中解除绑定(`session.removeAttribute("bean")`); +- 随 `Session` 对象持久化到一个存储设备中; +- 随 `Session` 对象从一个存储设备中恢复。 -Servlet 规范中定义了两个特殊的监听器接口`HttpSessionBindingListener`和`HttpSessionActivationListener`来帮助 JavaBean 对象了解自己在 Session 域中的这些状态。 +Servlet 规范中定义了两个特殊的监听器接口 `HttpSessionBindingListener` 和`HttpSessionActivationListener` 来帮助 JavaBean 对象了解自己在 Session 域中的这些状态。 -实现这两个接口的类不需要 web.xml 文件中进行注册。 +实现这两个接口的类不需要 `web.xml` 文件中进行注册。 #### HttpSessionBindingListener -实现了`HttpSessionBindingListener`接口的 JavaBean 对象可以感知自己被绑定到 Session 中和 Session 中删除的事件。 +`HttpSessionBindingListener` 接口的 JavaBean 对象可以感知自己被绑定或解绑定到 `Session` 中的事件。 -- 当对象被绑定到`HttpSession`对象中时,web 服务器调用该对象的`valueBound(HttpSessionBindingEvent event)`方法。 - -- 当对象从`HttpSession`对象中解除绑定时,web 服务器调用该对象的`valueUnbound(HttpSessionBindingEvent event)`方法。 +- 当对象被绑定到 `HttpSession` 对象中时,web 服务器调用该对象的 `valueBound(HttpSessionBindingEvent event)` 方法。 +- 当对象从 `HttpSession` 对象中解除绑定时,web 服务器调用该对象的 `valueUnbound(HttpSessionBindingEvent event)` 方法。 #### HttpSessionActivationListener -实现了`HttpSessionActivationListener`接口的 JavaBean 对象可以感知自己被活化(反序列化)和钝化(序列化)的事件。 +实现了 `HttpSessionActivationListener` 接口的 JavaBean 对象可以感知自己被活化(反序列化)和钝化(序列化)的事件。 + +- 当绑定到 `HttpSession` 对象中的 JavaBean 对象将要随 `HttpSession` 对象被序列化之前,web 服务器调用该 JavaBean 对象的 `sessionWillPassivate(HttpSessionEvent event)` 方法。这样 JavaBean 对象就可以知道自己将要和 `HttpSession` 对象一起被序列化到硬盘中. +- 当绑定到 `HttpSession` 对象中的 JavaBean 对象将要随 `HttpSession` 对象被反序列化之后,web 服务器调用该 JavaBean 对象的 `sessionDidActive(HttpSessionEvent event)` 方法。这样 JavaBean 对象就可以知道自己将要和 `HttpSession` 对象一起被反序列化回到内存中 + +## 示例代码 + +以上各种 `Listener` 的示例源码:[源码](https://github.com/dunwu/javaweb/tree/master/codes/javaee-tutorial/javaee-tutorial-listener) + +使用方法: + +1. 运行 `io.github.dunwu.javaee.server.ListenerDemosBootstrap#main` +2. 访问 http://localhost:9527/ -当绑定到 HttpSession 对象中的 javabean 对象将要随 HttpSession 对象被钝化(序列化)之前,web 服务器调用该 javabean 对象的`sessionWillPassivate(HttpSessionEvent event)` 方法。这样 javabean 对象就可以知道自己将要和 HttpSession 对象一起被序列化(钝化)到硬盘中. +## 参考资料 -当绑定到 HttpSession 对象中的 javabean 对象将要随 HttpSession 对象被活化(反序列化)之后,web 服务器调用该 javabean 对象的`sessionDidActive(HttpSessionEvent event)`方法。这样 javabean 对象就可以知道自己将要和 HttpSession 对象一起被反序列化(活化)回到内存中 +- Java Web 整合开发 diff --git a/docs/navbar.md b/docs/navbar.md index 734ef3fe..7524e190 100644 --- a/docs/navbar.md +++ b/docs/navbar.md @@ -1,5 +1,5 @@ - [JavaEE](javaee/README.md) - - [Servlet 指南](javaee/servlet.md) - - [Jsp 指南](javaee/jsp.md) - - [Listener 指南](javaee/listener.md) - - [Filter 指南](javaee/filter.md) + - [JavaEE 之 Servlet 指南](javaee/servlet.md) + - [JavaEE 之 Jsp 指南](javaee/jsp.md) + - [JavaEE 之 Listener 指南](javaee/listener.md) + - [JavaEE 之 Filter 指南](javaee/filter.md) From fdaf5e7ee983add7a01f0cdf9c19ee28c431364c Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Tue, 28 May 2019 23:45:25 +0800 Subject: [PATCH 17/75] =?UTF-8?q?=E6=95=B4=E7=90=86=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/webapp/WEB-INF/web.xml | 322 +++++++++--------- ...tServer.java => FilterDemosBootstrap.java} | 2 +- .../dunwu/javaee/server/JettyFactory.java | 49 ++- .../github/dunwu/javaee/server/Profiles.java | 29 -- 4 files changed, 193 insertions(+), 209 deletions(-) rename codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/{QuickStartServer.java => FilterDemosBootstrap.java} (96%) delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/Profiles.java diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/WEB-INF/web.xml b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/WEB-INF/web.xml index fc153548..1be12b89 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/WEB-INF/web.xml +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/WEB-INF/web.xml @@ -3,180 +3,180 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> - javaee-filter - - - - index.jsp - /views/jsp/index.jsp - - - index.jsp - / - - - - dispatcherServlet - /views/jsp/dispatcher.jsp - - - dispatcherServlet - *.do - - - - - - CacheFilter - + javaee-filter + + + + index.jsp + /views/jsp/index.jsp + + + index.jsp + / + + + + dispatcherServlet + /views/jsp/dispatcher.jsp + + + dispatcherServlet + *.do + + + + + + CacheFilter + io.github.dunwu.javaee.filter.CacheFilter - - cache - true - - - cacheTime - 1000000 - - - - CacheFilter - *.jsp - *.html - *.do - REQUEST - - - - characterEncodingFilter - + + cache + true + + + cacheTime + 1000000 + + + + CacheFilter + *.jsp + *.html + *.do + REQUEST + + + + characterEncodingFilter + io.github.dunwu.javaee.filter.CharacterEncodingFilter - - characterEncoding - UTF-8 - - - enable - true - - - - characterEncodingFilter - /* - - - - ExceptionHandlerFilter - + + characterEncoding + UTF-8 + + + enable + true + + + + characterEncodingFilter + /* + + + + ExceptionHandlerFilter + io.github.dunwu.javaee.filter.ExceptionHandlerFilter - - - ExceptionHandlerFilter - /* - - - - GZipFilter - io.github.dunwu.javaee.filter.GZipFilter - - - GZipFilter - /* - - - - ImageRedirectFilter - + + + ExceptionHandlerFilter + /* + + + + GZipFilter + io.github.dunwu.javaee.filter.GZipFilter + + + GZipFilter + /* + + + + ImageRedirectFilter + io.github.dunwu.javaee.filter.ImageRedirectFilter - - - ImageRedirectFilter - /views/images/* - - - - LogFilter - io.github.dunwu.javaee.filter.LogFilter - - - LogFilter - /* - - - - OutputReplaceFilter - + + + ImageRedirectFilter + /views/images/* + + + + LogFilter + io.github.dunwu.javaee.filter.LogFilter + + + LogFilter + /* + + + + OutputReplaceFilter + io.github.dunwu.javaee.filter.OutputReplaceFilter - - file - /WEB-INF/sensitive.properties - - - - OutputReplaceFilter - *.jsp - - - - UploadFilter - io.github.dunwu.javaee.filter.UploadFilter - - - UploadFilter - /* - - - - PrivilegeFilter - + + file + /WEB-INF/sensitive.properties + + + + OutputReplaceFilter + *.jsp + + + + UploadFilter + io.github.dunwu.javaee.filter.UploadFilter + + + UploadFilter + /* + + + + PrivilegeFilter + io.github.dunwu.javaee.filter.PrivilegeFilter - - file - /WEB-INF/privilege.properties - - - - PrivilegeFilter - *.do - - - - WaterMarkFilter - + + file + /WEB-INF/privilege.properties + + + + PrivilegeFilter + *.do + + + + WaterMarkFilter + io.github.dunwu.javaee.filter.WaterMarkFilter - - waterMarkFile - /WEB-INF/logo.png - - - - WaterMarkFilter - *.jpg - *.png - *.bmp - - - - XSLTFilter - io.github.dunwu.javaee.filter.XSLTFilter - - - XSLTFilter - /views/xml/* - - - - - /views/jsp/index.html - /views/jsp/index.htm - /views/jsp/index.jsp - + + waterMarkFile + /WEB-INF/logo.png + + + + WaterMarkFilter + *.jpg + *.png + *.bmp + + + + XSLTFilter + io.github.dunwu.javaee.filter.XSLTFilter + + + XSLTFilter + /views/xml/* + + + + + /views/jsp/index.html + /views/jsp/index.htm + /views/jsp/index.jsp + diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/FilterDemosBootstrap.java similarity index 96% rename from codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java rename to codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/FilterDemosBootstrap.java index dd0ac658..ba827cab 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/FilterDemosBootstrap.java @@ -7,7 +7,7 @@ * * @author Zhang Peng */ -public class QuickStartServer { +public class FilterDemosBootstrap { // private static int STARTUP_TYPE = JettyFactory.IDE_ECLIPSE; private static int STARTUP_TYPE = JettyFactory.IDE_INTELLIJ; diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java index a3085499..f9eb3106 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java @@ -1,35 +1,44 @@ package io.github.dunwu.javaee.server; -import java.util.ArrayList; - import org.apache.commons.lang3.StringUtils; -import org.assertj.core.util.Lists; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.webapp.WebAppClassLoader; import org.eclipse.jetty.webapp.WebAppContext; +import java.util.ArrayList; +import java.util.List; + /** - * JettyFactory 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij - * 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 + * JettyFactory 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 * * @author Zhang Peng */ -@SuppressWarnings("unused") +@SuppressWarnings("all") public class JettyFactory { - private static final int PORT = 8080; + private static final int PORT = 9527; private static final String CONTEXT = "/"; private static final String RESOURCE_BASE_PATH = "src/main/webapp"; private static final String WEB_XML_PATH = "/WEB-INF/web.xml"; - private static final String[] TLD_JAR_NAMES = - new String[]{"sitemesh", "spring-webmvc", "shiro-web", "tiles"}; + private static final String[] TLD_JAR_NAMES = new String[] {"sitemesh", "spring-webmvc", "tiles"}; private static final String WINDOWS_WEBDEFAULT_PATH = "jetty/webdefault.xml"; public static final int IDE_ECLIPSE = 0; public static final int IDE_INTELLIJ = 1; + public static final String ACTIVE_PROFILE = "spring.profiles.active"; + public static final String DEFAULT_PROFILE = "spring.profiles.default"; + public static final String DEVELOPMENT = "development"; + + /** + * 在Spring启动前,设置profile的环境变量。 + */ + public static void setProfileAsSystemProperty(String profile) { + System.setProperty(ACTIVE_PROFILE, profile); + } + public static Server initServer() { - Profiles.setProfileAsSystemProperty(Profiles.DEVELOPMENT); + setProfileAsSystemProperty(DEVELOPMENT); WebAppContext webAppContext = new WebAppContext(); Server server = new Server(PORT); server.setHandler(webAppContext); @@ -75,8 +84,8 @@ public static void startServer(Server server) throws Exception { public static String getAbsolutePath() { String path = null; - String folderPath = JettyFactory.class.getProtectionDomain().getCodeSource().getLocation() - .getPath().substring(1); + String folderPath = + JettyFactory.class.getProtectionDomain().getCodeSource().getLocation().getPath().substring(1); if (folderPath.indexOf("target") > 0) { path = folderPath.substring(0, folderPath.indexOf("target")); } @@ -88,21 +97,25 @@ public static void supportJspAndSetTldJarNames(Server server, String... jarNames // This webapp will use jsps and jstl. We need to enable the AnnotationConfiguration in // order to correctly set up the jsp container org.eclipse.jetty.webapp.Configuration.ClassList classlist = - org.eclipse.jetty.webapp.Configuration.ClassList.setServerDefault(server); + org.eclipse.jetty.webapp.Configuration.ClassList.setServerDefault(server); classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", - "org.eclipse.jetty.annotations.AnnotationConfiguration"); + "org.eclipse.jetty.annotations.AnnotationConfiguration"); // Set the ContainerIncludeJarPattern so that jetty examines these container-path jars for // tlds, web-fragments etc. // If you omit the jar that contains the jstl .tlds, the jsp engine will scan for them // instead. - ArrayList jarNameExprssions = Lists.newArrayList(".*/[^/]*servlet-api-[^/]*\\.jar$", - ".*/javax.servlet.jsp.jstl-.*\\.jar$", ".*/[^/]*taglibs.*\\.jar$"); + + List list = new ArrayList<>(); + list.add(".*/[^/]*servlet-api-[^/]*\\.jar$"); + list.add(".*/javax.servlet.jsp.jstl-.*\\.jar$"); + list.add(".*/[^/]*taglibs.*\\.jar$"); for (String jarName : jarNames) { - jarNameExprssions.add(".*/" + jarName + "-[^/]*\\.jar$"); + String str = ".*/" + jarName + "-[^/]*\\.jar$"; + list.add(str); } context.setAttribute("org.eclipse.jetty.io.github.dunwu.javaee.server.webapp.ContainerIncludeJarPattern", - StringUtils.join(jarNameExprssions, '|')); + StringUtils.join(list, '|')); } } diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/Profiles.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/Profiles.java deleted file mode 100644 index 3a8ee7c4..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/Profiles.java +++ /dev/null @@ -1,29 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2005, 2014 springside.github.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - *******************************************************************************/ -package io.github.dunwu.javaee.server; - -/** - * Spring profile 常用方法与profile名称。 - * - * @author calvin - */ -public class Profiles { - - public static final String ACTIVE_PROFILE = "spring.profiles.active"; - public static final String DEFAULT_PROFILE = "spring.profiles.default"; - - public static final String PRODUCTION = "production"; - public static final String DEVELOPMENT = "development"; - public static final String UNIT_TEST = "test"; - public static final String FUNCTIONAL_TEST = "functional"; - - /** - * 在Spring启动前,设置profile的环境变量。 - */ - public static void setProfileAsSystemProperty(String profile) { - System.setProperty(ACTIVE_PROFILE, profile); - } -} From aa863fc179daf5c143f9979355797252a02a94b3 Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Tue, 28 May 2019 23:49:13 +0800 Subject: [PATCH 18/75] update docs --- .../{listener.md => filter-and-listener.md} | 89 ++++++++++++++++--- docs/navbar.md | 3 +- 2 files changed, 77 insertions(+), 15 deletions(-) rename docs/javaee/{listener.md => filter-and-listener.md} (67%) diff --git a/docs/javaee/listener.md b/docs/javaee/filter-and-listener.md similarity index 67% rename from docs/javaee/listener.md rename to docs/javaee/filter-and-listener.md index 1ebbfc71..76badfa5 100644 --- a/docs/javaee/listener.md +++ b/docs/javaee/filter-and-listener.md @@ -1,25 +1,92 @@ -# JavaEE 之 Listener 指南 - -> 监听器是一个专门用于对其他对象身上发生的事件或状态改变进行监听和相应处理的对象,当被监视的对象发生情况时,立即采取相应的行动。监听器其实就是一个实现特定接口的普通 java 程序,这个程序专门用于监听另一个 java 对象的方法调用或属性改变,当被监听对象发生上述事件后,监听器某个方法立即被执行。 +# JavaEE 之 Filter 和 Listener -- [简介](#简介) -- [监听器的分类](#监听器的分类) +- [Filter](#filter) + - [过滤器方法](#过滤器方法) + - [过滤器配置](#过滤器配置) +- [Listener](#listener) + - [监听器的分类](#监听器的分类) - [监听对象的创建和销毁](#监听对象的创建和销毁) - [监听对象的属性变化](#监听对象的属性变化) - [监听 Session 内的对象](#监听-session-内的对象) +- [示例代码](#示例代码) - [参考资料](#参考资料) -## 简介 +## Filter + +过滤器(Filter)用于在 Servlet 之外对 request 或 response 进行修改。Filter 提供了过滤链(Filter Chain)的概念,一个过滤链包括多个 Filter。客户端请求 request 在抵达 Servlet 之前会经过过滤链的所有 Filter,服务器响应 response 从 Servlet 抵达客户端浏览器之前也会经过过滤链的所有 FIlter。 + +
+ +### 过滤器方法 + +Filter 接口有三个方法: + +- init +- destroy +- doFilter + +`init` 和 `destroy` 方法只会被调用一次;doFilter 每次有客户端请求都会被调用一次。 + +```java +public interface Filter { + + /** + * web 程序启动时调用此方法, 用于初始化该 Filter + * @param config + * 可以从该参数中获取初始化参数以及ServletContext信息等 + * @throws ServletException + */ + public void init(FilterConfig config) throws ServletException; + + /** + * 客户请求服务器时会经过 + * + * @param request + * 客户请求 + * @param response + * 服务器响应 + * @param chain + * 过滤链, 通过 chain.doFilter(request, response) 将请求传给下个 Filter 或 + * Servlet + * @throws ServletException + * @throws IOException + */ + public void doFilter(ServletRequest request, ServletResponse response, + FilterChain chain) throws ServletException, IOException; + + /** + * web 程序关闭时调用此方法, 用于销毁一些资源 + */ + public void destroy(); + +} +``` + +### 过滤器配置 + +`Filter` 需要配置在 `web.xml` 中才能生效。一个 `Filter` 需要配置 `` 与 `` 标签。 + +- `` 配置 Filter 名称,实现类以及初始化参数。 +- `` 配置什么规则下使用该 Filter。 +- `` 的 filterName 与 `` 的 filterName 必须匹配。 +- `` 配置 URL 的规则,可以配置多个,可以使用通配符(`*`)。 +- `` 配置到达 Servlet 的方式,有 4 种取值:REQUEST、FORWARD、INCLUDE、ERROR。可以同时配置多个 ``。如果没有配置任何 ``,默认为 REQUEST。 + - REQUEST - 表示仅当直接请求 Servlet 时才生效。 + - FORWARD - 表示仅当某 Servlet 通过 FORWARD 到该 Servlet 时才生效。 + - INCLUDE - JSP 中可以通过 `` 请求某 Servlet。仅在这种情况表有效。 + - ERROR - JSP 中可以通过 `<%@ page errorPage="error.jsp" %>` 指定错误处理页面。仅在这种情况表有效。 + +## Listener > 监听器(`Listener`)用于监听 web 应用程序中的`ServletContext`, `HttpSession`和 `ServletRequest`等域对象的创建与销毁事件,以及监听这些域对象中的属性发生修改的事件。 使用 Listener 不需要关注该类事件时怎样触发或者怎么调用相应的 Listener,只要记住该类事件触发时一定会调用相应的 Listener,遵循 Servlet 规范的服务器会自动完成相应工作。 -## 监听器的分类 +### 监听器的分类 在 Servlet 规范中定义了多种类型的监听器,它们用于监听的事件源分别为`ServletContext`,`HttpSession`和`ServletRequest`这三个域对象 Servlet 规范针对这三个对象上的操作,又把多种类型的监听器划分为三种类型: @@ -130,12 +197,8 @@ Servlet 规范中定义了两个特殊的监听器接口 `HttpSessionBindingList ## 示例代码 -以上各种 `Listener` 的示例源码:[源码](https://github.com/dunwu/javaweb/tree/master/codes/javaee-tutorial/javaee-tutorial-listener) - -使用方法: - -1. 运行 `io.github.dunwu.javaee.server.ListenerDemosBootstrap#main` -2. 访问 http://localhost:9527/ +- `Filter` 的示例源码:[源码](https://github.com/dunwu/javaweb/tree/master/codes/javaee-tutorial/javaee-tutorial-filter) +- `Listener` 的示例源码:[源码](https://github.com/dunwu/javaweb/tree/master/codes/javaee-tutorial/javaee-tutorial-listener) ## 参考资料 diff --git a/docs/navbar.md b/docs/navbar.md index 7524e190..6348a85e 100644 --- a/docs/navbar.md +++ b/docs/navbar.md @@ -1,5 +1,4 @@ - [JavaEE](javaee/README.md) - [JavaEE 之 Servlet 指南](javaee/servlet.md) - [JavaEE 之 Jsp 指南](javaee/jsp.md) - - [JavaEE 之 Listener 指南](javaee/listener.md) - - [JavaEE 之 Filter 指南](javaee/filter.md) + - [JavaEE 之 Filter 和 Listener](javaee/filter-and-listener.md) From 145736c03d1c10748418e64a0c1d9c6cd1af9544 Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Wed, 29 May 2019 10:06:51 +0800 Subject: [PATCH 19/75] update docs --- README.md | 108 +++--- docs/README.md | 124 ++++--- docs/architecture/README.md | 1 + docs/javaee/cookie-and-sesion.md | 9 +- docs/navbar.md | 12 + docs/orm/mybatis.md | 356 -------------------- docs/security/shiro.md | 68 ---- docs/{tools => server}/jetty.md | 13 +- docs/{tools => server}/tomcat.md | 13 +- docs/standalone/README.md | 1 + docs/standalone/mvc/spring.md | 238 -------------- docs/standalone/orm/mybatis.md | 267 ++++++++++++--- docs/tools/nginx.md | 545 ------------------------------- 13 files changed, 337 insertions(+), 1418 deletions(-) create mode 100644 docs/architecture/README.md delete mode 100644 docs/orm/mybatis.md delete mode 100644 docs/security/shiro.md rename docs/{tools => server}/jetty.md (98%) rename docs/{tools => server}/tomcat.md (99%) delete mode 100644 docs/standalone/mvc/spring.md delete mode 100644 docs/tools/nginx.md diff --git a/README.md b/README.md index d3fc6f8a..c0289a17 100644 --- a/README.md +++ b/README.md @@ -2,20 +2,20 @@ > Java Web 开发之路经验总结 -| :beginner: | :recycle: | :one: | :two: | :three: | :four: | -| ---------------------- | ----------------------------- | --------------------- | ----------------------------- | ------------------------------- | ------------------ | -| [准备](#beginner-准备) | [架构设计](#recycle-架构设计) | [JavaEE](#one-javaee) | [单点式技术](#two-单点式技术) | [分布式技术](#three-分布式技术) | [工具](#four-工具) | +| :beginner: | :recycle: | :coffee: | :eye: | :eyes: | :computer: | +| ------------- | --------------------- | ----------------- | --------------------- | ------------------------- | ------------- | +| [准备](#准备) | [架构设计](#架构设计) | [JavaEE](#javaee) | [主流技术](#主流技术) | [分布式技术](#分布式技术) | [服务器](#服务器) | -## :beginner: 准备 +## 准备 -作为 Web 工程师,你应该多多少少掌握一些的知识: +作为 Java Web 工程师,你应该多多少少掌握一些的知识: -- [网络通信](https://github.com/dunwu/notes/tree/master/网络通信) - 你需要掌握一些网络通信协议知识。至少,你应该知道 HTTP、DNS 协议的工作机制。 +- [JavaCore](https://dunwu.github.io/javacore/) - Java 核心技术 - [前端技术指南](https://github.com/dunwu/frontend-tutorial) - 即使是后端工程师,也难免会接触到前端技术。前端技术五花八门,如:React、Vue、Angular、Webpack、ES6、Babel、Node.js 等等。不说掌握,至少也应该知道这些技术是什么。 -## :recycle: 架构设计 +## 架构设计 > [架构设计](docs/architecture/) 整理架构设计方面的一些学习总结和心得。 @@ -27,67 +27,57 @@ - [网站的安全架构](docs/architecture/网站的安全架构.md) - 关键词:XSS、CSRF、SQL 注入、DoS、消息摘要、加密算法、证书 - [网站典型故障](docs/architecture/网站典型故障.md) -## :one: JavaEE +## JavaEE > [JavaEE](docs/javaee/) 技术——Java Web 的基石 -## :two: 单点式技术 +- [JavaEE 之 Servlet 指南](docs/javaee/servlet.md) +- [JavaEE 之 Jsp 指南](docs/javaee/jsp.md) +- [JavaEE 之 Filter 和 Listener](docs/javaee/filter-and-listener.md) +- [JavaEE 之 Cookie 和 Session](docs/javaee/cookie-and-sesion.md) -> [单点式技术(Standalone)](docs/standalone/),典型的技术如:SSM 框架、SSH 框架。 +## 主流技术 -### Platform +> [主流技术](docs/standalone/),典型的技术如:SSM 框架、SSH 框架。 -- [Spring](https://github.com/dunwu/spring-notes) - JavaSE/JavaEE 一站式开发框架。 -- [Spring Boot](https://github.com/dunwu/spring-boot-tutorial) - 简化*Spring*应用的初始搭建以及开发过程。 +- MVC + - [spring-tutorial](https://dunwu.gitbooks.io/spring-tutorial/) - Spring 教程 + - [spring-boot-tutorial](https://dunwu.github.io/spring-boot-tutorial/) - Spring Boot 教程 +- ORM + - [Mybatis](docs/standalone/orm/mybatis.md) - 一个支持普通 SQL 查询,存储过程和高级映射的优秀持久层框架。 +- Security + - [Shiro](docs/standalone/security/shiro.md) - 安全框架,具有认证、授权、加密、会话管理功能。 -### ORM - -- [Mybatis](docs/standalone/orm/mybatis.md) - 一个支持普通 SQL 查询,存储过程和高级映射的优秀持久层框架。 -- Hibernate - 待补充。。。 - -### 安全 - -- [Shiro](docs/standalone/security/shiro.md) - 安全框架,具有认证、授权、加密、会话管理功能。 - -## :three: 分布式技术 +## 分布式技术 > [分布式技术(Distributed)](docs/distributed/),典型的技术如:分布式缓存、分布式消息队列、分布式服务、分布式搜索引擎等。 - [分布式技术面试题](docs/distributed/分布式技术面试题.md) -### [分布式缓存(CACHE)](docs/distributed/cache) - -- [分布式缓存](docs/distributed/cache/分布式缓存.md) -- [Redis](docs/distributed/cache/redis.md) -- Memcached - -### [分布式服务(RPC)](docs/distributed/rpc) - -- [Dubbo](docs/distributed/rpc/dubbo.md) - 基于 Java 开发的高性能 RPC 框架。 -- [ZooKeeper 实战篇](docs/distributed/rpc/zookeeper-basics.md) -- [ZooKeeper 原理篇](docs/distributed/rpc/zookeeper-advanced.md) - -### [分布式消息队列(MQ)](docs/distributed/mq) - -- 原理 - - [分布式消息队列](docs/distributed/mq/分布式消息队列.md) -- Kafka - - [Kafka 实战篇](docs/distributed/mq/kafka/kafka-basics.md) - - [Kafka 原理篇](docs/distributed/mq/kafka/kafka-advanced.md) - - [Kafka Cheat Sheet](docs/distributed/mq/kafka/kafka-cheat-sheet.md) -- [RocketMQ 实战篇](docs/distributed/mq/rocketmq-basics.md) -- [RocketMQ 原理篇](docs/distributed/mq/rocketmq-basics.md) -- [ActiveMQ 实战篇](docs/distributed/mq/ActiveMQ.md) -- RabbitMQ - 待补充。。。 - -### 分布式搜索引擎 - -- ElasticSearch - 待补充。。。 - -## :four: 工具 - -> [工具](docs/tools/) 整理了 Java Web 领域常用软件。 - -- [Nginx](https://github.com/dunwu/Nginx) - 轻量级的 Web 服务器、反向代理服务器及电子邮件(IMAP/POP3)代理服务器,支持负载均衡。 -- [Tomcat](docs/tools/tomcat.md) - 轻量级的应用服务器 -- [Jetty](docs/tools/jetty.md) - 比 Tomcat 更轻量级的应用服务器 +- [分布式缓存(CACHE)](docs/distributed/cache) + - [分布式缓存](docs/distributed/cache/分布式缓存.md) + - [Redis](docs/distributed/cache/redis.md) + - Memcached +- [分布式服务(RPC)](docs/distributed/rpc) + - [Dubbo](docs/distributed/rpc/dubbo.md) - 基于 Java 开发的高性能 RPC 框架。 + - [ZooKeeper 实战篇](docs/distributed/rpc/zookeeper-basics.md) + - [ZooKeeper 原理篇](docs/distributed/rpc/zookeeper-advanced.md) +- [分布式消息队列(MQ)](docs/distributed/mq) + - 原理 + - [分布式消息队列](docs/distributed/mq/分布式消息队列.md) + - Kafka + - [Kafka 实战篇](docs/distributed/mq/kafka/kafka-basics.md) + - [Kafka 原理篇](docs/distributed/mq/kafka/kafka-advanced.md) + - [Kafka Cheat Sheet](docs/distributed/mq/kafka/kafka-cheat-sheet.md) + - [RocketMQ 实战篇](docs/distributed/mq/rocketmq-basics.md) + - [RocketMQ 原理篇](docs/distributed/mq/rocketmq-basics.md) + - [ActiveMQ 实战篇](docs/distributed/mq/ActiveMQ.md) + - RabbitMQ - 待补充。。。 + +## 服务器 + +> [服务器](docs/server/) 章节总结 Java Web 领域主流服务器技术。 + +- [Nginx 简易教程](https://github.com/dunwu/nginx-tutorial) - 轻量级的 Web 服务器、反向代理服务器及电子邮件(IMAP/POP3)代理服务器,支持负载均衡。 +- [Tomcat 应用指南](docs/server/tomcat.md) - 轻量级的应用服务器 +- [Jetty 应用指南](docs/server/jetty.md) - 比 Tomcat 更轻量级的应用服务器 diff --git a/docs/README.md b/docs/README.md index d3fc6f8a..30ba5ce9 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,92 +2,82 @@ > Java Web 开发之路经验总结 -| :beginner: | :recycle: | :one: | :two: | :three: | :four: | -| ---------------------- | ----------------------------- | --------------------- | ----------------------------- | ------------------------------- | ------------------ | -| [准备](#beginner-准备) | [架构设计](#recycle-架构设计) | [JavaEE](#one-javaee) | [单点式技术](#two-单点式技术) | [分布式技术](#three-分布式技术) | [工具](#four-工具) | +| :beginner: | :recycle: | :coffee: | :eye: | :eyes: | :computer: | +| ------------- | --------------------- | ----------------- | --------------------- | ------------------------- | ------------- | +| [准备](#准备) | [架构设计](#架构设计) | [JavaEE](#javaee) | [主流技术](#主流技术) | [分布式技术](#分布式技术) | [服务器](#服务器) | -## :beginner: 准备 +## 准备 -作为 Web 工程师,你应该多多少少掌握一些的知识: +作为 Java Web 工程师,你应该多多少少掌握一些的知识: -- [网络通信](https://github.com/dunwu/notes/tree/master/网络通信) - 你需要掌握一些网络通信协议知识。至少,你应该知道 HTTP、DNS 协议的工作机制。 +- [JavaCore](https://dunwu.github.io/javacore/) - Java 核心技术 - [前端技术指南](https://github.com/dunwu/frontend-tutorial) - 即使是后端工程师,也难免会接触到前端技术。前端技术五花八门,如:React、Vue、Angular、Webpack、ES6、Babel、Node.js 等等。不说掌握,至少也应该知道这些技术是什么。 -## :recycle: 架构设计 +## 架构设计 -> [架构设计](docs/architecture/) 整理架构设计方面的一些学习总结和心得。 +> [架构设计](architecture/) 整理架构设计方面的一些学习总结和心得。 -- [大型网站架构概述](docs/architecture/大型网站架构概述.md) -- [网站的高性能架构](docs/architecture/网站的高性能架构.md) -- [网站的高可用架构](docs/architecture/网站的高可用架构.md) -- [网站的伸缩性架构](docs/architecture/网站的伸缩性架构.md) -- [网站的可扩展架构](docs/architecture/网站的可扩展架构.md) -- [网站的安全架构](docs/architecture/网站的安全架构.md) - 关键词:XSS、CSRF、SQL 注入、DoS、消息摘要、加密算法、证书 -- [网站典型故障](docs/architecture/网站典型故障.md) +- [大型网站架构概述](architecture/大型网站架构概述.md) +- [网站的高性能架构](architecture/网站的高性能架构.md) +- [网站的高可用架构](architecture/网站的高可用架构.md) +- [网站的伸缩性架构](architecture/网站的伸缩性架构.md) +- [网站的可扩展架构](architecture/网站的可扩展架构.md) +- [网站的安全架构](architecture/网站的安全架构.md) - 关键词:XSS、CSRF、SQL 注入、DoS、消息摘要、加密算法、证书 +- [网站典型故障](architecture/网站典型故障.md) -## :one: JavaEE +## JavaEE -> [JavaEE](docs/javaee/) 技术——Java Web 的基石 +> [JavaEE](javaee/) 技术——Java Web 的基石 -## :two: 单点式技术 +- [JavaEE 之 Servlet 指南](javaee/servlet.md) +- [JavaEE 之 Jsp 指南](javaee/jsp.md) +- [JavaEE 之 Filter 和 Listener](javaee/filter-and-listener.md) +- [JavaEE 之 Cookie 和 Session](javaee/cookie-and-sesion.md) -> [单点式技术(Standalone)](docs/standalone/),典型的技术如:SSM 框架、SSH 框架。 +## 主流技术 -### Platform +> [主流技术](standalone/),典型的技术如:SSM 框架、SSH 框架。 -- [Spring](https://github.com/dunwu/spring-notes) - JavaSE/JavaEE 一站式开发框架。 -- [Spring Boot](https://github.com/dunwu/spring-boot-tutorial) - 简化*Spring*应用的初始搭建以及开发过程。 +- MVC + - [spring-tutorial](https://dunwu.gitbooks.io/spring-tutorial/) - Spring 教程 + - [spring-boot-tutorial](https://dunwu.github.io/spring-boot-tutorial/) - Spring Boot 教程 +- ORM + - [Mybatis](standalone/orm/mybatis.md) - 一个支持普通 SQL 查询,存储过程和高级映射的优秀持久层框架。 +- Security + - [Shiro](standalone/security/shiro.md) - 安全框架,具有认证、授权、加密、会话管理功能。 -### ORM +## 分布式技术 -- [Mybatis](docs/standalone/orm/mybatis.md) - 一个支持普通 SQL 查询,存储过程和高级映射的优秀持久层框架。 -- Hibernate - 待补充。。。 +> [分布式技术(Distributed)](distributed/),典型的技术如:分布式缓存、分布式消息队列、分布式服务、分布式搜索引擎等。 -### 安全 +- [分布式技术面试题](distributed/分布式技术面试题.md) -- [Shiro](docs/standalone/security/shiro.md) - 安全框架,具有认证、授权、加密、会话管理功能。 +- [分布式缓存(CACHE)](distributed/cache) + - [分布式缓存](distributed/cache/分布式缓存.md) + - [Redis](distributed/cache/redis.md) + - Memcached +- [分布式服务(RPC)](distributed/rpc) + - [Dubbo](distributed/rpc/dubbo.md) - 基于 Java 开发的高性能 RPC 框架。 + - [ZooKeeper 实战篇](distributed/rpc/zookeeper-basics.md) + - [ZooKeeper 原理篇](distributed/rpc/zookeeper-advanced.md) +- [分布式消息队列(MQ)](distributed/mq) + - 原理 + - [分布式消息队列](distributed/mq/分布式消息队列.md) + - Kafka + - [Kafka 实战篇](distributed/mq/kafka/kafka-basics.md) + - [Kafka 原理篇](distributed/mq/kafka/kafka-advanced.md) + - [Kafka Cheat Sheet](distributed/mq/kafka/kafka-cheat-sheet.md) + - [RocketMQ 实战篇](distributed/mq/rocketmq-basics.md) + - [RocketMQ 原理篇](distributed/mq/rocketmq-basics.md) + - [ActiveMQ 实战篇](distributed/mq/ActiveMQ.md) + - RabbitMQ - 待补充。。。 -## :three: 分布式技术 +## 服务器 -> [分布式技术(Distributed)](docs/distributed/),典型的技术如:分布式缓存、分布式消息队列、分布式服务、分布式搜索引擎等。 +> [服务器](server/) 章节总结 Java Web 领域主流服务器技术。 -- [分布式技术面试题](docs/distributed/分布式技术面试题.md) - -### [分布式缓存(CACHE)](docs/distributed/cache) - -- [分布式缓存](docs/distributed/cache/分布式缓存.md) -- [Redis](docs/distributed/cache/redis.md) -- Memcached - -### [分布式服务(RPC)](docs/distributed/rpc) - -- [Dubbo](docs/distributed/rpc/dubbo.md) - 基于 Java 开发的高性能 RPC 框架。 -- [ZooKeeper 实战篇](docs/distributed/rpc/zookeeper-basics.md) -- [ZooKeeper 原理篇](docs/distributed/rpc/zookeeper-advanced.md) - -### [分布式消息队列(MQ)](docs/distributed/mq) - -- 原理 - - [分布式消息队列](docs/distributed/mq/分布式消息队列.md) -- Kafka - - [Kafka 实战篇](docs/distributed/mq/kafka/kafka-basics.md) - - [Kafka 原理篇](docs/distributed/mq/kafka/kafka-advanced.md) - - [Kafka Cheat Sheet](docs/distributed/mq/kafka/kafka-cheat-sheet.md) -- [RocketMQ 实战篇](docs/distributed/mq/rocketmq-basics.md) -- [RocketMQ 原理篇](docs/distributed/mq/rocketmq-basics.md) -- [ActiveMQ 实战篇](docs/distributed/mq/ActiveMQ.md) -- RabbitMQ - 待补充。。。 - -### 分布式搜索引擎 - -- ElasticSearch - 待补充。。。 - -## :four: 工具 - -> [工具](docs/tools/) 整理了 Java Web 领域常用软件。 - -- [Nginx](https://github.com/dunwu/Nginx) - 轻量级的 Web 服务器、反向代理服务器及电子邮件(IMAP/POP3)代理服务器,支持负载均衡。 -- [Tomcat](docs/tools/tomcat.md) - 轻量级的应用服务器 -- [Jetty](docs/tools/jetty.md) - 比 Tomcat 更轻量级的应用服务器 +- [Nginx 简易教程](https://github.com/dunwu/nginx-tutorial) - 轻量级的 Web 服务器、反向代理服务器及电子邮件(IMAP/POP3)代理服务器,支持负载均衡。 +- [Tomcat 应用指南](server/tomcat.md) - 轻量级的应用服务器 +- [Jetty 应用指南](server/jetty.md) - 比 Tomcat 更轻量级的应用服务器 diff --git a/docs/architecture/README.md b/docs/architecture/README.md new file mode 100644 index 00000000..260054c5 --- /dev/null +++ b/docs/architecture/README.md @@ -0,0 +1 @@ +# 系统架构 diff --git a/docs/javaee/cookie-and-sesion.md b/docs/javaee/cookie-and-sesion.md index c05953db..1df06ec1 100644 --- a/docs/javaee/cookie-and-sesion.md +++ b/docs/javaee/cookie-and-sesion.md @@ -1,11 +1,4 @@ ---- -title: Cookie 和 Session -categories: ['java', 'javaweb', 'javaee'] -tags: ['java', 'javaweb', 'javaee', 'cookie', 'session'] -date: 2017-11-08 ---- - -# Cookie 和 Session +# JavaEE 之 Cookie 和 Session diff --git a/docs/navbar.md b/docs/navbar.md index 6348a85e..ce3daf86 100644 --- a/docs/navbar.md +++ b/docs/navbar.md @@ -1,4 +1,16 @@ +- [系统架构](docs/architecture/README.md) + - [大型网站架构概述](docs/architecture/大型网站架构概述.md) + - [网站的高性能架构](docs/architecture/网站的高性能架构.md) + - [网站的高可用架构](docs/architecture/网站的高可用架构.md) + - [网站的伸缩性架构](docs/architecture/网站的伸缩性架构.md) + - [网站的可扩展架构](docs/architecture/网站的可扩展架构.md) + - [网站的安全架构](docs/architecture/网站的安全架构.md) + - [网站典型故障](docs/architecture/网站典型故障.md) - [JavaEE](javaee/README.md) - [JavaEE 之 Servlet 指南](javaee/servlet.md) - [JavaEE 之 Jsp 指南](javaee/jsp.md) - [JavaEE 之 Filter 和 Listener](javaee/filter-and-listener.md) +- [Server](server/README.md) + - [Tomcat 应用指南](server/tomcat.md) + - [Jetty 应用指南](server/jetty.md) + diff --git a/docs/orm/mybatis.md b/docs/orm/mybatis.md deleted file mode 100644 index 31886d59..00000000 --- a/docs/orm/mybatis.md +++ /dev/null @@ -1,356 +0,0 @@ ---- -title: MyBatis 指南 -categories: ['java', 'javaweb'] -tags: ['java', 'javaweb', 'orm'] -date: 2019-03-06 ---- - -# MyBatis 指南 - -> MyBatis 的前身就是 iBatis ,是一个作用在数据持久层的对象关系映射(Object Relational Mapping,简称 ORM)框架。 - - - -- [简介](#简介) - - [什么是 MyBatis?](#什么是-mybatis) - - [MyBatis vs. Hibernate](#mybatis-vs-hibernate) -- [入门](#入门) -- [核心 API](#核心-api) - - [SqlSessionFactoryBuilder](#sqlsessionfactorybuilder) - - [SqlSessionFactory](#sqlsessionfactory) - - [SqlSession](#sqlsession) - - [映射器](#映射器) -- [原理](#原理) - - [MyBatis 的架构](#mybatis-的架构) - - [接口层](#接口层) - - [数据处理层](#数据处理层) - - [框架支撑层](#框架支撑层) - - [引导层](#引导层) - - [主要组件](#主要组件) -- [配置](#配置) -- [SQL XML 映射文件](#sql-xml-映射文件) -- [Mybatis 扩展](#mybatis-扩展) -- [资料](#资料) - - - -## 简介 - -### 什么是 MyBatis? - -MyBatis 是一款持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。 - -### MyBatis vs. Hibernate - -MyBatis 和 Hibernate 都是 Java 世界中比较流行的 ORM 框架。我们应该了解其各自的优势,根据项目的需要去抉择在开发中使用哪个框架。 - -**Mybatis 优势** - -- MyBatis 可以进行更为细致的 SQL 优化,可以减少查询字段。 -- MyBatis 容易掌握,而 Hibernate 门槛较高。 - -**Hibernate 优势** - -- Hibernate 的 DAO 层开发比 MyBatis 简单,Mybatis 需要维护 SQL 和结果映射。 -- Hibernate 对对象的维护和缓存要比 MyBatis 好,对增删改查的对象的维护要方便。 -- Hibernate 数据库移植性很好,MyBatis 的数据库移植性不好,不同的数据库需要写不同 SQL。 -- Hibernate 有更好的二级缓存机制,可以使用第三方缓存。MyBatis 本身提供的缓存机制不佳。 - -## 入门 - -> [Mybatis 官方文档之入门](http://www.mybatis.org/mybatis-3/zh/getting-started.html) 已经写得很简洁易懂,不再赘述。 - -## 核心 API - -> 核心 API 请参考:「 [Mybatis 官方文档之 Java API](http://www.mybatis.org/mybatis-3/zh/java-api.html) 」 - -
- -### SqlSessionFactoryBuilder - -- **SqlSessionFactoryBuilder 职责** - -**`SqlSessionFactoryBuilder` 负责创建 `SqlSessionFactory` 实例。而 `SqlSessionFactoryBuilder` 则可以从 XML 配置文件或一个预先定制的 `Configuration` 的实例构建出 `SqlSessionFactory` 的实例。** - -
- -- **SqlSessionFactoryBuilder 生命周期** - -这个类可以被实例化、使用和丢弃,一旦创建了 `SqlSessionFactory`,就不再需要它了。 因此 `SqlSessionFactoryBuilder` 实例的最佳作用域是方法作用域(也就是局部方法变量)。 - -### SqlSessionFactory - -- **SqlSessionFactory 职责** - -**SqlSessionFactory 负责创建 SqlSession 实例。** - -
- -- **SqlSessionFactory 生命周期** - -`SqlSessionFactory` 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。最简单的方法就是使用单例模式或者静态单例模式。 - -### SqlSession - -
- -- **SqlSession 职责** - -**MyBatis 的主要 Java 接口就是 `SqlSession`。你可以通过这个接口来执行命令,获取映射器和管理事务。** - -> 详细内容可以参考:「 [Mybatis 官方文档之 sqlSessions](http://www.mybatis.org/mybatis-3/zh/java-api.html#sqlSessions) 」 。 - -- **SqlSession 生命周期** - -> 注意:当 Mybatis 与一些依赖注入框架(如 Spring 或者 Guice)同时使用时,`SqlSessions` 将被依赖注入框架所创建,所以你不需要使用 `SqlSessionFactoryBuilder` 或者 `SqlSessionFactory`。 - -**每个线程都应该有它自己的 `SqlSession` 实例。** - -`SqlSession` 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 `SqlSession` 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 `SqlSession` 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 `HttpSession`。 正确在 Web 中使用 `SqlSession` 的场景是:每次收到的 HTTP 请求,就可以打开一个 `SqlSession`,返回一个响应,就关闭它。 - -编程模式: - -```java -SqlSession session = sqlSessionFactory.openSession(); -try { - // 你的应用逻辑代码 -} finally { - session.close(); -} -``` - -### 映射器 - -- **映射器职责** - -映射器是一些由你创建的、绑定你映射的语句的接口。 - -各个 insert、update、delete 和 select 方法都很强大,但也有些繁琐。更通用的方式是使用映射器类来执行映射语句。一个映射器类就是一个仅需声明与 SqlSession 方法相匹配的方法的接口类。 - -下面的示例展示了一些方法签名以及它们是如何映射到 SqlSession 上的。 - -```java -public interface AuthorMapper { - // (Author) selectOne("selectAuthor",5); - Author selectAuthor(int id); - // (List) selectList(“selectAuthors”) - List selectAuthors(); - // (Map) selectMap("selectAuthors", "id") - @MapKey("id") - Map selectAuthors(); - // insert("insertAuthor", author) - int insertAuthor(Author author); - // updateAuthor("updateAuthor", author) - int updateAuthor(Author author); - // delete("deleteAuthor",5) - int deleteAuthor(int id); -} -``` - -> **注意** -> -> - 映射器接口不需要去实现任何接口或继承自任何类。只要方法可以被唯一标识对应的映射语句就可以了。 -> - 映射器接口可以继承自其他接口。当使用 XML 来构建映射器接口时要保证语句被包含在合适的命名空间中。而且,唯一的限制就是你不能在两个继承关系的接口中拥有相同的方法签名(潜在的危险做法不可取)。 - -- **映射器生命周期** - -映射器接口的实例是从 `SqlSession` 中获得的。因此从技术层面讲,任何映射器实例的最大作用域是和请求它们的 `SqlSession` 相同的。尽管如此,映射器实例的最佳作用域是方法作用域。 也就是说,映射器实例应该在调用它们的方法中被请求,用过之后即可丢弃。 - -编程模式: - -```java -SqlSession session = sqlSessionFactory.openSession(); -try { - BlogMapper mapper = session.getMapper(BlogMapper.class); - // 你的应用逻辑代码 -} finally { - session.close(); -} -``` - -- **映射器注解** - -MyBatis 是一个 XML 驱动的框架。配置信息是基于 XML 的,而且映射语句也是定义在 XML 中的。MyBatis 3 以后,支持注解配置。注解配置基于配置 API;而配置 API 基于 XML 配置。 - -Mybatis 支持诸如 @Insert、@Update、@Delete、@Select、@Result 等注解。 - -> 详细内容请参考:[Mybatis 官方文档之 sqlSessions](http://www.mybatis.org/mybatis-3/zh/java-api.html#sqlSessions),其中列举了 Mybatis 支持的注解清单,以及基本用法。 - -## 原理 - -### MyBatis 的架构 - -
- -### 接口层 - -接口层负责和数据库交互的方式 - -MyBatis 和数据库的交互有两种方式: - -1. 使用传统的 MyBatis 提供的 API; -2. 使用 Mapper 接口 - -#### 使用传统的 MyBatis 提供的 API - -这是传统的传递 Statement Id 和查询参数给 SqlSession 对象,使用 SqlSession 对象完成和数据库的交互;MyBatis 提供了非常方便和简单的 API,供用户实现对数据库的增删改查数据操作,以及对数据库连接信息和 MyBatis 自身配置信息的维护操作。 - -
- -
- -上述使用 MyBatis 的方法,是创建一个和数据库打交道的 SqlSession 对象,然后根据 Statement Id 和参数来操作数据库,这种方式固然很简单和实用,但是它不符合面向对象语言的概念和面向接口编程的编程习惯。由于面向接口的编程是面向对象的大趋势,MyBatis 为了适应这一趋势,增加了第二种使用 MyBatis 支持接口(Interface)调用方式。 - -#### 使用 Mapper 接口 - -MyBatis 将配置文件中的每一个 `` 节点抽象为一个 Mapper 接口,而这个接口中声明的方法和跟 `` 节点中的 `` 节点相对应,即 `` 节点的 id 值为 Mapper 接口中的方法名称,parameterType 值表示 Mapper 对应方法的入参类型,而 resultMap 值则对应了 Mapper 接口表示的返回值类型或者返回结果集的元素类型。 - -
- -
- -根据 MyBatis 的配置规范配置好后,通过 `SqlSession.getMapper(XXXMapper.class)` 方法,MyBatis 会根据相应的接口声明的方法信息,通过动态代理机制生成一个 Mapper 实例,我们使用 Mapper 接口的某一个方法时,MyBatis 会根据这个方法的方法名和参数类型,确定 Statement Id,底层还是通过`SqlSession.select("statementId",parameterObject);` 或者 `SqlSession.update("statementId",parameterObject);` 等等来实现对数据库的操作。 - -MyBatis 引用 Mapper 接口这种调用方式,纯粹是为了满足面向接口编程的需要。(其实还有一个原因是在于,面向接口的编程,使得用户在接口上可以使用注解来配置 SQL 语句,这样就可以脱离 XML 配置文件,实现“0 配置”)。 - -### 数据处理层 - -数据处理层可以说是 MyBatis 的核心,从大的方面上讲,它要完成两个功能: - -1. 通过传入参数构建动态 SQL 语句; -2. SQL 语句的执行以及封装查询结果集成 `List` - -#### 参数映射和动态 SQL 语句生成 - -动态语句生成可以说是 MyBatis 框架非常优雅的一个设计,MyBatis 通过传入的参数值,**使用 Ognl 来动态地构造 SQL 语句**,使得 MyBatis 有很强的灵活性和扩展性。 - -参数映射指的是对于 java 数据类型和 jdbc 数据类型之间的转换:这里有包括两个过程:查询阶段,我们要将 java 类型的数据,转换成 jdbc 类型的数据,通过 `preparedStatement.setXXX()` 来设值;另一个就是对 resultset 查询结果集的 jdbcType 数据转换成 java 数据类型。 - -#### SQL 语句的执行以及封装查询结果集成 `List` - -动态 SQL 语句生成之后,MyBatis 将执行 SQL 语句,并将可能返回的结果集转换成 `List` 列表。MyBatis 在对结果集的处理中,支持结果集关系一对多和多对一的转换,并且有两种支持方式,一种为嵌套查询语句的查询,还有一种是嵌套结果集的查询。 - -### 框架支撑层 - -#### 事务管理机制 - -对数据库的事务而言,应该具有以下几点:创建(create)、提交(commit)、回滚(rollback)、关闭(close)。对应地,MyBatis 将事务抽象成了 Transaction 接口。 - -MyBatis 的事务管理分为两种形式: - -1. 使用 JDBC 的事务管理机制:即利用 java.sql.Connection 对象完成对事务的提交(commit())、回滚(rollback())、关闭(close())等。 -2. 使用 MANAGED 的事务管理机制:这种机制 MyBatis 自身不会去实现事务管理,而是让程序的容器如(JBOSS,Weblogic)来实现对事务的管理。 - -#### 连接池管理机制 - -由于创建一个数据库连接所占用的资源比较大, 对于数据吞吐量大和访问量非常大的应用而言,连接池的设计就显得非常重要,对于连接池管理机制我已经在我的博文《深入理解 mybatis 原理》 Mybatis 数据源与连接池 中有非常详细的讨论,感兴趣的读者可以点击查看。 - -#### 缓存机制 - -MyBatis 将数据缓存设计成两级结构,分为一级缓存、二级缓存: - -- **一级缓存是 Session 会话级别的缓存**,位于表示一次数据库会话的 SqlSession 对象之中,又被称之为本地缓存。一级缓存是 MyBatis 内部实现的一个特性,用户不能配置,默认情况下自动支持的缓存,用户没有定制它的权利(不过这也不是绝对的,可以通过开发插件对它进行修改); -- **二级缓存是 Application 应用级别的缓存**,它的是生命周期很长,跟 Application 的声明周期一样,也就是说它的作用范围是整个 Application 应用。 - -
- -
- -##### 一级缓存的工作机制 - -一级缓存是 Session 会话级别的,一般而言,一个 SqlSession 对象会使用一个 Executor 对象来完成会话操作,Executor 对象会维护一个 Cache 缓存,以提高查询性能。 - -
- -
- -##### 二级缓存的工作机制 - -如上所言,一个 SqlSession 对象会使用一个 Executor 对象来完成会话操作,MyBatis 的二级缓存机制的关键就是对这个 Executor 对象做文章。如果用户配置了 `"cacheEnabled=true"`,那么 MyBatis 在为 SqlSession 对象创建 Executor 对象时,会对 Executor 对象加上一个装饰者:CachingExecutor,这时 SqlSession 使用 CachingExecutor 对象来完成操作请求。CachingExecutor 对于查询请求,会先判断该查询请求在 Application 级别的二级缓存中是否有缓存结果,如果有查询结果,则直接返回缓存结果;如果缓存中没有,再交给真正的 Executor 对象来完成查询操作,之后 CachingExecutor 会将真正 Executor 返回的查询结果放置到缓存中,然后在返回给用户。 - -
- -
- -#### SQL 语句的配置方式 - -传统的 MyBatis 配置 SQL 语句方式就是使用 XML 文件进行配置的,但是这种方式不能很好地支持面向接口编程的理念,为了支持面向接口的编程,MyBatis 引入了 Mapper 接口的概念,面向接口的引入,对使用注解来配置 SQL 语句成为可能,用户只需要在接口上添加必要的注解即可,不用再去配置 XML 文件了,但是,目前的 MyBatis 只是对注解配置 SQL 语句提供了有限的支持,某些高级功能还是要依赖 XML 配置文件配置 SQL 语句。 - -### 引导层 - -引导层是配置和启动 MyBatis 配置信息的方式。MyBatis 提供两种方式来引导 MyBatis : - -1. 基于 XML 配置文件的方式 -2. 基于 Java API 的方式 - -### 主要组件 - -从 MyBatis 代码实现的角度来看,MyBatis 的主要组件有以下几个: - -- **SqlSession** - 作为 MyBatis 工作的主要顶层 API,表示和数据库交互的会话,完成必要数据库增删改查功能。 -- **Executor** - MyBatis 执行器,是 MyBatis 调度的核心,负责 SQL 语句的生成和查询缓存的维护。 -- **StatementHandler** - 封装了 JDBC Statement 操作,负责对 JDBC statement 的操作,如设置参数、将 Statement 结果集转换成 List 集合。 -- **ParameterHandler** - 负责对用户传递的参数转换成 JDBC Statement 所需要的参数。 -- **ResultSetHandler** - 负责将 JDBC 返回的 ResultSet 结果集对象转换成 List 类型的集合。 -- **TypeHandler** - 负责 java 数据类型和 jdbc 数据类型之间的映射和转换。 -- **MappedStatement** - MappedStatement 维护了一条 `` 节点的封装。 -- **SqlSource** - 负责根据用户传递的 parameterObject,动态地生成 SQL 语句,将信息封装到 BoundSql 对象中,并返回。 -- **BoundSql** - 表示动态生成的 SQL 语句以及相应的参数信息。 -- **Configuration** - MyBatis 所有的配置信息都维持在 Configuration 对象之中。 - -它们的关系如下图所示: - -
- -
-## 配置 - -> 配置的详细内容请参考:「 [Mybatis 官方文档之配置](http://www.mybatis.org/mybatis-3/zh/configuration.html) 」 。 - -MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。主要配置项有以下: - -- [properties(属性)](http://www.mybatis.org/mybatis-3/zh/configuration.html#properties) -- [settings(设置)](http://www.mybatis.org/mybatis-3/zh/configuration.html#settings) -- [typeAliases(类型别名)](http://www.mybatis.org/mybatis-3/zh/configuration.html#typeAliases) -- [typeHandlers(类型处理器)](http://www.mybatis.org/mybatis-3/zh/configuration.html#typeHandlers) -- [objectFactory(对象工厂)](http://www.mybatis.org/mybatis-3/zh/configuration.html#objectFactory) -- [plugins(插件)](http://www.mybatis.org/mybatis-3/zh/configuration.html#plugins) -- [environments(环境配置)](http://www.mybatis.org/mybatis-3/zh/configuration.html#environments) - - environment(环境变量) - - transactionManager(事务管理器) - - dataSource(数据源) -- [databaseIdProvider(数据库厂商标识)](http://www.mybatis.org/mybatis-3/zh/configuration.html#databaseIdProvider) -- [mappers(映射器)](http://www.mybatis.org/mybatis-3/zh/configuration.html#mappers) - -## SQL XML 映射文件 - -> SQL XML 映射文件详细内容请参考:「 [Mybatis 官方文档之 XML 映射文件](http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html) 」。 - -XML 映射文件只有几个顶级元素: - -- `cache` – 对给定命名空间的缓存配置。 -- `cache-ref` – 对其他命名空间缓存配置的引用。 -- `resultMap` – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。 -- \~\~`parameterMap` – 已被废弃!老式风格的参数映射。更好的办法是使用内联参数,此元素可能在将来被移除。文档中不会介绍此元素。\~\~ -- `sql` – 可被其他语句引用的可重用语句块。 -- `insert` – 映射插入语句 -- `update` – 映射更新语句 -- `delete` – 映射删除语句 -- `select` – 映射查询语句 - -## Mybatis 扩展 - -- [mybatis-plus](https://github.com/baomidou/mybatis-plus) - CRUD、代码生成器、分页器等多功能 -- [Mapper](https://github.com/abel533/Mapper) - CRUD -- [Mybatis-PageHelper](https://github.com/pagehelper/Mybatis-PageHelper) - Mybatis 通用分页插件 - -## 资料 - -- **官方** - -| [Github](https://github.com/mybatis/mybatis-3) | [官网](http://www.mybatis.org/mybatis-3/) | [MyBatis Generator](https://github.com/mybatis/generator) | [Spring 集成](https://github.com/mybatis/spring) | [Spring Boot 集成](https://github.com/mybatis/spring-boot-starter) | - -- **文章** - - [深入理解 mybatis 原理](https://blog.csdn.net/luanlouis/article/details/40422941) - - [mybatis 源码中文注释](https://github.com/tuguangquan/mybatis) - mybatis 源码解读 - - [MyBatis Generator 详解](https://blog.csdn.net/isea533/article/details/42102297) diff --git a/docs/security/shiro.md b/docs/security/shiro.md deleted file mode 100644 index 59638ecb..00000000 --- a/docs/security/shiro.md +++ /dev/null @@ -1,68 +0,0 @@ -# Shiro - -> Shiro 是一个安全框架,具有认证、授权、加密、会话管理功能。 - - - -- [概述](#概述) - - [Shiro 功能](#shiro-功能) - - [Shiro 架构](#shiro-架构) -- [认证](#认证) -- [授权](#授权) -- [资料](#资料) - - - -## 概述 - -### Shiro 功能 - -

- -

- -- **Authentication** - 身份认证/登录,验证用户是不是拥有相应的身份; -- **Authorization** - 授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限; -- **Session Manager** - 会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的; -- **Cryptography** - 加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储; -- **Web Support** - Web 支持,可以非常容易的集成到 Web 环境; -- **Caching** - 缓存,比如用户登录后,其用户信息、拥有的角色 / 权限不必每次去查,这样可以提高效率; -- **Concurrency** - shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去; -- **Testing** - 提供测试支持; -- **Run As** - 允许一个用户假装为另一个用户(如果他们允许)的身份进行访问; -- **Remember Me** - 记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。 - -记住一点,Shiro 不会去维护用户、维护权限;这些需要我们自己去提供;然后通过相应的接口注入给 Shiro 即可。 - -### Shiro 架构 - -

- -

- -- **Subject** - 主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等;即一个抽象概念;所有 Subject 都绑定到 SecurityManager,与 Subject 的所有交互都会委托给 SecurityManager;可以把 Subject 认为是一个门面;SecurityManager 才是实际的执行者; -- **SecurityManager** - 安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject;可以看出它是 Shiro 的核心,它负责与后边介绍的其他组件进行交互,如果学习过 SpringMVC,你可以把它看成 DispatcherServlet 前端控制器; -- **Realm** - 域,Shiro 从从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色/权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。 - -

- -

- -- **Subject** - 主体,可以看到主体可以是任何可以与应用交互的“用户”; -- **SecurityManager** - 相当于 SpringMVC 中的 DispatcherServlet 或者 Struts2 中的 FilterDispatcher;是 Shiro 的心脏;所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理。 -- **Authenticator** - 认证器,负责主体认证的,这是一个扩展点,如果用户觉得 Shiro 默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了; -- **Authrizer** - 授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能; -- **Realm** - 可以有 1 个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是 JDBC 实现,也可以是 LDAP 实现,或者内存实现等等;由用户提供;注意 - Shiro 不知道你的用户/权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的 Realm; -- **SessionManager** - 如果写过 Servlet 就应该知道 Session 的概念,Session 呢需要有人去管理它的生命周期,这个组件就是 SessionManager;而 Shiro 并不仅仅可以用在 Web 环境,也可以用在如普通的 JavaSE 环境、EJB 等环境;所有呢,Shiro 就抽象了一个自己的 Session 来管理主体与应用之间交互的数据;这样的话,比如我们在 Web 环境用,刚开始是一台 Web 服务器;接着又上了台 EJB 服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到 Memcached 服务器); -- **SessionDAO** - DAO 大家都用过,数据访问对象,用于会话的 CRUD,比如我们想把 Session 保存到数据库,那么可以实现自己的 SessionDAO,通过如 JDBC 写到数据库;比如想把 Session 放到 Memcached 中,可以实现自己的 Memcached SessionDAO;另外 SessionDAO 中可以使用 Cache 进行缓存,以提高性能; -- **CacheManager** - 缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能 -- **Cryptography** - 密码模块,Shiro 提高了一些常见的加密组件用于如密码加密/解密的。 - -## 认证 - -## 授权 - -## 资料 - -- [shiro 官方文档](http://shiro.apache.org/reference.html) -- [跟我学 Shiro](http://jinnianshilongnian.iteye.com/category/305053) diff --git a/docs/tools/jetty.md b/docs/server/jetty.md similarity index 98% rename from docs/tools/jetty.md rename to docs/server/jetty.md index df9e258d..542513e3 100644 --- a/docs/tools/jetty.md +++ b/docs/server/jetty.md @@ -1,15 +1,4 @@ ---- -title: Jetty 使用小结 -date: 2017/11/08 -categories: -- javatool -tags: -- java -- javatool -- server ---- - -# Jetty 使用小结 +# Jetty 应用指南 diff --git a/docs/tools/tomcat.md b/docs/server/tomcat.md similarity index 99% rename from docs/tools/tomcat.md rename to docs/server/tomcat.md index 04726962..204d3a2e 100644 --- a/docs/tools/tomcat.md +++ b/docs/server/tomcat.md @@ -1,15 +1,4 @@ ---- -title: Tomcat 快速入门 -date: 2018/01/08 -categories: -- javatool -tags: -- java -- javatool -- server ---- - -# Tomcat 快速入门 +# Tomcat 应用指南 > 版本说明 > diff --git a/docs/standalone/README.md b/docs/standalone/README.md index 2f00b454..67e03b8e 100644 --- a/docs/standalone/README.md +++ b/docs/standalone/README.md @@ -1,3 +1,4 @@ # 单机 > Web 领域的单机技术 + diff --git a/docs/standalone/mvc/spring.md b/docs/standalone/mvc/spring.md deleted file mode 100644 index 2e45adc2..00000000 --- a/docs/standalone/mvc/spring.md +++ /dev/null @@ -1,238 +0,0 @@ ---- -title: Spring面试题 -date: 2018/07/02 -categories: -- javaweb -tags: -- java -- javaweb -- mvc - ---- - -# Spring 面试题 - - - -- [IoC](#ioc) - - [什么是控制反转(IoC)?什么是依赖注入?](#什么是控制反转ioc什么是依赖注入) - - [Spring 中的 IoC](#spring-中的-ioc) - - [BeanFactory 和 ApplicationContext 有什么区别?](#beanfactory-和-applicationcontext-有什么区别) - - [Spring 有几种配置方式](#spring-有几种配置方式) - - [Spring Bean 的生命周期](#spring-bean-的生命周期) - - [Spring Bean 的作用域](#spring-bean-的作用域) - - [Spring 框架中的单例 Beans 是线程安全的么?](#spring-框架中的单例-beans-是线程安全的么) - - [Spring 中注入一个 Java Collection?](#spring-中注入一个-java-collection) - - [自动装配模式](#自动装配模式) -- [AOP](#aop) - - [什么是 AOP](#什么是-aop) - - [AOP 的原理](#aop-的原理) - - [动态代理](#动态代理) - - [静态代理](#静态代理) - - - -## IoC - -### 什么是控制反转(IoC)?什么是依赖注入? - -IoC,是 Inversion of Control 的缩写,即控制反转。 - -- 上层模块不应该依赖于下层模块,它们共同依赖于一个抽象 -- 抽象不能依赖于具体实现,具体实现依赖于抽象。 - -注:又称为依赖倒置原则。这是设计模式六大原则之一。 - -DI,是 Dependency Injection 的缩写,即依赖注入。 - -- 依赖注入是 IoC 的最常见形式。 -- 容器全权负责的组件的装配,它会把符合依赖关系的对象通过 JavaBean 属性或者构造函数传递给需要的对象。 - -依赖注入三种形式: - -1. 构造器注入 -2. Setter 方法注入 -3. 接口注入 - -### Spring 中的 IoC - -BeanFactory 是 Spring IoC 容器的具体实现,用来包装和管理前面提到的各种 bean。 - -BeanFactory 接口是 Spring IoC 容器的核心接口。 - -IOC:把对象的创建、初始化、销毁交给 spring 来管理,而不是由开发者控制,实现控制反转。 - -### BeanFactory 和 ApplicationContext 有什么区别? - -BeanFactory 包含了种 bean 的定义,以便在接收到客户端请求时将对应的 bean 实例化。 - -BeanFactory 还能在实例化对象的时生成协作类之间的关系。 - -BeanFactory 还包含了 bean 生命周期的控制,调用客户端的初始化方法(initialization methods)和销毁方法(destruction methods)。 - -ApplicationContext 扩展了 BeanFactory: - -1. 提供了支持国际化的文本消息 -2. 统一的资源文件读取方式 -3. 已在监听器中注册的 bean 的事件 - -三种较常见的 ApplicationContext 实现: - -1. ClassPathXmlApplicationContext - 从 classpath 的 XML 配置文件中读取上下文。 -2. FileSystemXmlApplicationContext - 由文件系统中的 XML 配置文件读取上下文。 -3. XmlWebApplicationContext - 由 Web 应用的 XML 文件读取上下文。 - -### Spring 有几种配置方式 - -1. 基于 XML 的配置 -2. 基于注解的配置 -3. 基于 Java 的配置 - -#### xml 配置方式 - -在 `` 元素下指定 schema 命令空间,如:context、beans、jdbc、tx、aop、mvc 等。 - -然后声明 bean 或命名空间下提供的能力。 - -#### Java 配置方式 - -使用 @Configuration - -```java -@Configuration -public class AppConfig{ - @Bean - public MyService myService() { - return new MyServiceImpl(); - } -} -``` - -利用 AnnotationConfigApplicationContext 类进行实例化 - -```java -public static void main(String[] args) { - ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); - MyService myService = ctx.getBean(MyService.class); - myService.doStuff(); -} -``` - -#### 注解方式配置 - -开启注解扫描 - -```xml - - - - -``` - -引入 JavaBean 的注解 - -- @Required - 该注解应用于设值方法。 -- @Autowired - 该注解应用于有值设值方法、非设值方法、构造方法和变量。 -- @Qualifier - 该注解和@Autowired 注解搭配使用,用于消除特定 bean 自动装配的歧义。 -- JSR-250 Annotations - Spring 支持基于 JSR-250 注解的以下注解,@Resource、@PostConstruct 和 @PreDestroy - -### Spring Bean 的生命周期 - -BeanFactory 负责管理在 spring 容器中被创建的 bean 的生命周期。Bean 的生命周期由两组回调(call back)方法组成。 - -1. 初始化之后调用的回调方法。 -2. 销毁之前调用的回调方法。 - -Spring 框架提供了以下四种方式来管理 bean 的生命周期事件: - -1. InitializingBean 和 DisposableBean 回调接口 -2. 针对特殊行为的其他 Aware 接口 -3. Bean 配置文件中的 Custom init()方法和 destroy()方法 -4. @PostConstruct 和@PreDestroy 注解方式 - -### Spring Bean 的作用域 - -- singleton:这种 bean 范围是默认的,这种范围确保不管接受到多少个请求,每个容器中只有一个 bean 的实例,单例的模式由 bean factory 自身来维护。 -- prototype:原形范围与单例范围相反,为每一个 bean 请求提供一个实例。 -- request:在请求 bean 范围内会每一个来自客户端的网络请求创建一个实例,在请求完成以后,bean 会失效并被垃圾回收器回收。 -- Session:与请求范围类似,确保每个 session 中有一个 bean 的实例,在 session 过期后,bean 会随之失效。 -- global-session:global-session 和 Portlet 应用相关。当你的应用部署在 Portlet 容器中工作时,它包含很多 portlet。如果你想要声明让所有的 portlet 共用全局的存储变量的话,那么这全局变量需要存储在 global-session 中。 - -### Spring 框架中的单例 Beans 是线程安全的么? - -Spring 框架并没有对单例 bean 进行任何多线程的封装处理。关于单例 bean 的线程安全和并发问题需要开发者自行去搞定。 - -最浅显的解决办法就是将多态 bean 的作用域由“singleton”变更为“prototype”。 - -### Spring 中注入一个 Java Collection? - -Spring 提供了以下四种集合类的配置元素: - -1. `` - 该标签用来装配可重复的 list 值。 -2. `` - 该标签用来装配没有重复的 set 值。 -3. `` - 该标签可用来注入键和值可以为任何类型的键值对。 -4. `` - 该标签支持注入键和值都是字符串类型的键值对。 - -### 自动装配模式 - -- no:这是 Spring 框架的默认设置,在该设置下自动装配是关闭的,开发者需要自行在 bean 定义中用标签明确的设置依赖关系。 -- byName:该选项可以根据 bean 名称设置依赖关系。当向一个 bean 中自动装配一个属性时,容器将根据 bean 的名称自动在在配置文件中查询一个匹配的 bean。如果找到的话,就装配这个属性,如果没找到的话就报错。 -- byType:该选项可以根据 bean 类型设置依赖关系。当向一个 bean 中自动装配一个属性时,容器将根据 bean 的类型自动在在配置文件中查询一个匹配的 bean。如果找到的话,就装配这个属性,如果没找到的话就报错。 -- constructor:造器的自动装配和 byType 模式类似,但是仅仅适用于与有构造器相同参数的 bean,如果在容器中没有找到与构造器参数类型一致的 bean,那么将会抛出异常。 -- autodetect:该模式自动探测使用构造器自动装配或者 byType 自动装配。首先,首先会尝试找合适的带参数的构造器,如果找到的话就是用构造器自动装配,如果在 bean 内部没有找到相应的构造器或者是无参构造器,容器就会自动选择 byTpe 的自动装配方式。 - -## AOP - -### 什么是 AOP - -AOP 把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。 - -Aop 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。正如 Avanade 公司的高级方案构架师 Adam Magee 所说,AOP 的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。” - -### AOP 的原理 - -实现 AOP 的技术,主要分为两大类: - -1. 采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行; -2. 采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。 - -### 动态代理 - -#### JDK - -需要实现 InvocationHandler 接口。 - -#### CGLIB - -### 静态代理 - -```java -public interface IPerson { - public void doSomething(); -} - -public class Person implements IPerson { - public void doSomething(){ - System.out.println("I want wo sell this house"); - } -} - -public class PersonProxy { - private IPerson iPerson; - private final static Logger logger = LoggerFactory.getLogger(PersonProxy.class); - - public PersonProxy(IPerson iPerson) { - this.iPerson = iPerson; - } - public void doSomething() { - logger.info("Before Proxy"); - iPerson.doSomething(); - logger.info("After Proxy"); - } - - public static void main(String[] args) { - PersonProxy personProxy = new PersonProxy(new Person()); - personProxy.doSomething(); - } -} -``` diff --git a/docs/standalone/orm/mybatis.md b/docs/standalone/orm/mybatis.md index b97f0b2f..31886d59 100644 --- a/docs/standalone/orm/mybatis.md +++ b/docs/standalone/orm/mybatis.md @@ -1,64 +1,188 @@ --- -title: MyBatis -date: 2018/06/19 -categories: -- orm -tags: -- java -- orm +title: MyBatis 指南 +categories: ['java', 'javaweb'] +tags: ['java', 'javaweb', 'orm'] +date: 2019-03-06 --- -# MyBatis +# MyBatis 指南 > MyBatis 的前身就是 iBatis ,是一个作用在数据持久层的对象关系映射(Object Relational Mapping,简称 ORM)框架。 -- [概述](#概述) +- [简介](#简介) + - [什么是 MyBatis?](#什么是-mybatis) - [MyBatis vs. Hibernate](#mybatis-vs-hibernate) +- [入门](#入门) +- [核心 API](#核心-api) + - [SqlSessionFactoryBuilder](#sqlsessionfactorybuilder) + - [SqlSessionFactory](#sqlsessionfactory) + - [SqlSession](#sqlsession) + - [映射器](#映射器) +- [原理](#原理) - [MyBatis 的架构](#mybatis-的架构) -- [接口层](#接口层) - - [使用传统的 MyBatis 提供的 API](#使用传统的-mybatis-提供的-api) - - [使用 Mapper 接口](#使用-mapper-接口) -- [数据处理层](#数据处理层) - - [参数映射和动态 SQL 语句生成](#参数映射和动态-sql-语句生成) - - [SQL 语句的执行以及封装查询结果集成 `List`](#sql-语句的执行以及封装查询结果集成-liste) -- [框架支撑层](#框架支撑层) - - [事务管理机制](#事务管理机制) - - [连接池管理机制](#连接池管理机制) - - [缓存机制](#缓存机制) - - [SQL 语句的配置方式](#sql-语句的配置方式) -- [引导层](#引导层) -- [主要组件](#主要组件) + - [接口层](#接口层) + - [数据处理层](#数据处理层) + - [框架支撑层](#框架支撑层) + - [引导层](#引导层) + - [主要组件](#主要组件) +- [配置](#配置) +- [SQL XML 映射文件](#sql-xml-映射文件) +- [Mybatis 扩展](#mybatis-扩展) - [资料](#资料) - - [官方](#官方) - - [第三方](#第三方) -## 概述 +## 简介 + +### 什么是 MyBatis? + +MyBatis 是一款持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。 ### MyBatis vs. Hibernate -Mybatis 优势 +MyBatis 和 Hibernate 都是 Java 世界中比较流行的 ORM 框架。我们应该了解其各自的优势,根据项目的需要去抉择在开发中使用哪个框架。 + +**Mybatis 优势** - MyBatis 可以进行更为细致的 SQL 优化,可以减少查询字段。 - MyBatis 容易掌握,而 Hibernate 门槛较高。 -Hibernate 优势 +**Hibernate 优势** - Hibernate 的 DAO 层开发比 MyBatis 简单,Mybatis 需要维护 SQL 和结果映射。 - Hibernate 对对象的维护和缓存要比 MyBatis 好,对增删改查的对象的维护要方便。 - Hibernate 数据库移植性很好,MyBatis 的数据库移植性不好,不同的数据库需要写不同 SQL。 - Hibernate 有更好的二级缓存机制,可以使用第三方缓存。MyBatis 本身提供的缓存机制不佳。 +## 入门 + +> [Mybatis 官方文档之入门](http://www.mybatis.org/mybatis-3/zh/getting-started.html) 已经写得很简洁易懂,不再赘述。 + +## 核心 API + +> 核心 API 请参考:「 [Mybatis 官方文档之 Java API](http://www.mybatis.org/mybatis-3/zh/java-api.html) 」 + +
+ +### SqlSessionFactoryBuilder + +- **SqlSessionFactoryBuilder 职责** + +**`SqlSessionFactoryBuilder` 负责创建 `SqlSessionFactory` 实例。而 `SqlSessionFactoryBuilder` 则可以从 XML 配置文件或一个预先定制的 `Configuration` 的实例构建出 `SqlSessionFactory` 的实例。** + +
+ +- **SqlSessionFactoryBuilder 生命周期** + +这个类可以被实例化、使用和丢弃,一旦创建了 `SqlSessionFactory`,就不再需要它了。 因此 `SqlSessionFactoryBuilder` 实例的最佳作用域是方法作用域(也就是局部方法变量)。 + +### SqlSessionFactory + +- **SqlSessionFactory 职责** + +**SqlSessionFactory 负责创建 SqlSession 实例。** + +
+ +- **SqlSessionFactory 生命周期** + +`SqlSessionFactory` 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。最简单的方法就是使用单例模式或者静态单例模式。 + +### SqlSession + +
+ +- **SqlSession 职责** + +**MyBatis 的主要 Java 接口就是 `SqlSession`。你可以通过这个接口来执行命令,获取映射器和管理事务。** + +> 详细内容可以参考:「 [Mybatis 官方文档之 sqlSessions](http://www.mybatis.org/mybatis-3/zh/java-api.html#sqlSessions) 」 。 + +- **SqlSession 生命周期** + +> 注意:当 Mybatis 与一些依赖注入框架(如 Spring 或者 Guice)同时使用时,`SqlSessions` 将被依赖注入框架所创建,所以你不需要使用 `SqlSessionFactoryBuilder` 或者 `SqlSessionFactory`。 + +**每个线程都应该有它自己的 `SqlSession` 实例。** + +`SqlSession` 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 `SqlSession` 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 `SqlSession` 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 `HttpSession`。 正确在 Web 中使用 `SqlSession` 的场景是:每次收到的 HTTP 请求,就可以打开一个 `SqlSession`,返回一个响应,就关闭它。 + +编程模式: + +```java +SqlSession session = sqlSessionFactory.openSession(); +try { + // 你的应用逻辑代码 +} finally { + session.close(); +} +``` + +### 映射器 + +- **映射器职责** + +映射器是一些由你创建的、绑定你映射的语句的接口。 + +各个 insert、update、delete 和 select 方法都很强大,但也有些繁琐。更通用的方式是使用映射器类来执行映射语句。一个映射器类就是一个仅需声明与 SqlSession 方法相匹配的方法的接口类。 + +下面的示例展示了一些方法签名以及它们是如何映射到 SqlSession 上的。 + +```java +public interface AuthorMapper { + // (Author) selectOne("selectAuthor",5); + Author selectAuthor(int id); + // (List) selectList(“selectAuthors”) + List selectAuthors(); + // (Map) selectMap("selectAuthors", "id") + @MapKey("id") + Map selectAuthors(); + // insert("insertAuthor", author) + int insertAuthor(Author author); + // updateAuthor("updateAuthor", author) + int updateAuthor(Author author); + // delete("deleteAuthor",5) + int deleteAuthor(int id); +} +``` + +> **注意** +> +> - 映射器接口不需要去实现任何接口或继承自任何类。只要方法可以被唯一标识对应的映射语句就可以了。 +> - 映射器接口可以继承自其他接口。当使用 XML 来构建映射器接口时要保证语句被包含在合适的命名空间中。而且,唯一的限制就是你不能在两个继承关系的接口中拥有相同的方法签名(潜在的危险做法不可取)。 + +- **映射器生命周期** + +映射器接口的实例是从 `SqlSession` 中获得的。因此从技术层面讲,任何映射器实例的最大作用域是和请求它们的 `SqlSession` 相同的。尽管如此,映射器实例的最佳作用域是方法作用域。 也就是说,映射器实例应该在调用它们的方法中被请求,用过之后即可丢弃。 + +编程模式: + +```java +SqlSession session = sqlSessionFactory.openSession(); +try { + BlogMapper mapper = session.getMapper(BlogMapper.class); + // 你的应用逻辑代码 +} finally { + session.close(); +} +``` + +- **映射器注解** + +MyBatis 是一个 XML 驱动的框架。配置信息是基于 XML 的,而且映射语句也是定义在 XML 中的。MyBatis 3 以后,支持注解配置。注解配置基于配置 API;而配置 API 基于 XML 配置。 + +Mybatis 支持诸如 @Insert、@Update、@Delete、@Select、@Result 等注解。 + +> 详细内容请参考:[Mybatis 官方文档之 sqlSessions](http://www.mybatis.org/mybatis-3/zh/java-api.html#sqlSessions),其中列举了 Mybatis 支持的注解清单,以及基本用法。 + +## 原理 + ### MyBatis 的架构 -
- -
+
-## 接口层 +### 接口层 接口层负责和数据库交互的方式 @@ -67,7 +191,7 @@ MyBatis 和数据库的交互有两种方式: 1. 使用传统的 MyBatis 提供的 API; 2. 使用 Mapper 接口 -### 使用传统的 MyBatis 提供的 API +#### 使用传统的 MyBatis 提供的 API 这是传统的传递 Statement Id 和查询参数给 SqlSession 对象,使用 SqlSession 对象完成和数据库的交互;MyBatis 提供了非常方便和简单的 API,供用户实现对数据库的增删改查数据操作,以及对数据库连接信息和 MyBatis 自身配置信息的维护操作。 @@ -77,7 +201,7 @@ MyBatis 和数据库的交互有两种方式: 上述使用 MyBatis 的方法,是创建一个和数据库打交道的 SqlSession 对象,然后根据 Statement Id 和参数来操作数据库,这种方式固然很简单和实用,但是它不符合面向对象语言的概念和面向接口编程的编程习惯。由于面向接口的编程是面向对象的大趋势,MyBatis 为了适应这一趋势,增加了第二种使用 MyBatis 支持接口(Interface)调用方式。 -### 使用 Mapper 接口 +#### 使用 Mapper 接口 MyBatis 将配置文件中的每一个 `` 节点抽象为一个 Mapper 接口,而这个接口中声明的方法和跟 `` 节点中的 `` 节点相对应,即 `` 节点的 id 值为 Mapper 接口中的方法名称,parameterType 值表示 Mapper 对应方法的入参类型,而 resultMap 值则对应了 Mapper 接口表示的返回值类型或者返回结果集的元素类型。 @@ -89,26 +213,26 @@ MyBatis 将配置文件中的每一个 `` 节点抽象为一个 Mapper MyBatis 引用 Mapper 接口这种调用方式,纯粹是为了满足面向接口编程的需要。(其实还有一个原因是在于,面向接口的编程,使得用户在接口上可以使用注解来配置 SQL 语句,这样就可以脱离 XML 配置文件,实现“0 配置”)。 -## 数据处理层 +### 数据处理层 数据处理层可以说是 MyBatis 的核心,从大的方面上讲,它要完成两个功能: 1. 通过传入参数构建动态 SQL 语句; 2. SQL 语句的执行以及封装查询结果集成 `List` -### 参数映射和动态 SQL 语句生成 +#### 参数映射和动态 SQL 语句生成 动态语句生成可以说是 MyBatis 框架非常优雅的一个设计,MyBatis 通过传入的参数值,**使用 Ognl 来动态地构造 SQL 语句**,使得 MyBatis 有很强的灵活性和扩展性。 参数映射指的是对于 java 数据类型和 jdbc 数据类型之间的转换:这里有包括两个过程:查询阶段,我们要将 java 类型的数据,转换成 jdbc 类型的数据,通过 `preparedStatement.setXXX()` 来设值;另一个就是对 resultset 查询结果集的 jdbcType 数据转换成 java 数据类型。 -### SQL 语句的执行以及封装查询结果集成 `List` +#### SQL 语句的执行以及封装查询结果集成 `List` 动态 SQL 语句生成之后,MyBatis 将执行 SQL 语句,并将可能返回的结果集转换成 `List` 列表。MyBatis 在对结果集的处理中,支持结果集关系一对多和多对一的转换,并且有两种支持方式,一种为嵌套查询语句的查询,还有一种是嵌套结果集的查询。 -## 框架支撑层 +### 框架支撑层 -### 事务管理机制 +#### 事务管理机制 对数据库的事务而言,应该具有以下几点:创建(create)、提交(commit)、回滚(rollback)、关闭(close)。对应地,MyBatis 将事务抽象成了 Transaction 接口。 @@ -117,11 +241,11 @@ MyBatis 的事务管理分为两种形式: 1. 使用 JDBC 的事务管理机制:即利用 java.sql.Connection 对象完成对事务的提交(commit())、回滚(rollback())、关闭(close())等。 2. 使用 MANAGED 的事务管理机制:这种机制 MyBatis 自身不会去实现事务管理,而是让程序的容器如(JBOSS,Weblogic)来实现对事务的管理。 -### 连接池管理机制 +#### 连接池管理机制 由于创建一个数据库连接所占用的资源比较大, 对于数据吞吐量大和访问量非常大的应用而言,连接池的设计就显得非常重要,对于连接池管理机制我已经在我的博文《深入理解 mybatis 原理》 Mybatis 数据源与连接池 中有非常详细的讨论,感兴趣的读者可以点击查看。 -### 缓存机制 +#### 缓存机制 MyBatis 将数据缓存设计成两级结构,分为一级缓存、二级缓存: @@ -132,7 +256,7 @@ MyBatis 将数据缓存设计成两级结构,分为一级缓存、二级缓存 -#### 一级缓存的工作机制 +##### 一级缓存的工作机制 一级缓存是 Session 会话级别的,一般而言,一个 SqlSession 对象会使用一个 Executor 对象来完成会话操作,Executor 对象会维护一个 Cache 缓存,以提高查询性能。 @@ -140,8 +264,7 @@ MyBatis 将数据缓存设计成两级结构,分为一级缓存、二级缓存 - -#### 二级缓存的工作机制 +##### 二级缓存的工作机制 如上所言,一个 SqlSession 对象会使用一个 Executor 对象来完成会话操作,MyBatis 的二级缓存机制的关键就是对这个 Executor 对象做文章。如果用户配置了 `"cacheEnabled=true"`,那么 MyBatis 在为 SqlSession 对象创建 Executor 对象时,会对 Executor 对象加上一个装饰者:CachingExecutor,这时 SqlSession 使用 CachingExecutor 对象来完成操作请求。CachingExecutor 对于查询请求,会先判断该查询请求在 Application 级别的二级缓存中是否有缓存结果,如果有查询结果,则直接返回缓存结果;如果缓存中没有,再交给真正的 Executor 对象来完成查询操作,之后 CachingExecutor 会将真正 Executor 返回的查询结果放置到缓存中,然后在返回给用户。 @@ -149,19 +272,18 @@ MyBatis 将数据缓存设计成两级结构,分为一级缓存、二级缓存 - -### SQL 语句的配置方式 +#### SQL 语句的配置方式 传统的 MyBatis 配置 SQL 语句方式就是使用 XML 文件进行配置的,但是这种方式不能很好地支持面向接口编程的理念,为了支持面向接口的编程,MyBatis 引入了 Mapper 接口的概念,面向接口的引入,对使用注解来配置 SQL 语句成为可能,用户只需要在接口上添加必要的注解即可,不用再去配置 XML 文件了,但是,目前的 MyBatis 只是对注解配置 SQL 语句提供了有限的支持,某些高级功能还是要依赖 XML 配置文件配置 SQL 语句。 -## 引导层 +### 引导层 引导层是配置和启动 MyBatis 配置信息的方式。MyBatis 提供两种方式来引导 MyBatis : 1. 基于 XML 配置文件的方式 2. 基于 Java API 的方式 -## 主要组件 +### 主要组件 从 MyBatis 代码实现的角度来看,MyBatis 的主要组件有以下几个: @@ -181,15 +303,54 @@ MyBatis 将数据缓存设计成两级结构,分为一级缓存、二级缓存
+## 配置 -## 资料 +> 配置的详细内容请参考:「 [Mybatis 官方文档之配置](http://www.mybatis.org/mybatis-3/zh/configuration.html) 」 。 + +MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。主要配置项有以下: + +- [properties(属性)](http://www.mybatis.org/mybatis-3/zh/configuration.html#properties) +- [settings(设置)](http://www.mybatis.org/mybatis-3/zh/configuration.html#settings) +- [typeAliases(类型别名)](http://www.mybatis.org/mybatis-3/zh/configuration.html#typeAliases) +- [typeHandlers(类型处理器)](http://www.mybatis.org/mybatis-3/zh/configuration.html#typeHandlers) +- [objectFactory(对象工厂)](http://www.mybatis.org/mybatis-3/zh/configuration.html#objectFactory) +- [plugins(插件)](http://www.mybatis.org/mybatis-3/zh/configuration.html#plugins) +- [environments(环境配置)](http://www.mybatis.org/mybatis-3/zh/configuration.html#environments) + - environment(环境变量) + - transactionManager(事务管理器) + - dataSource(数据源) +- [databaseIdProvider(数据库厂商标识)](http://www.mybatis.org/mybatis-3/zh/configuration.html#databaseIdProvider) +- [mappers(映射器)](http://www.mybatis.org/mybatis-3/zh/configuration.html#mappers) -### 官方 +## SQL XML 映射文件 -| [Github](https://github.com/mybatis/mybatis-3) | [官网](http://www.mybatis.org/mybatis-3/) | [MyBatis Generator](https://github.com/mybatis/generator) +> SQL XML 映射文件详细内容请参考:「 [Mybatis 官方文档之 XML 映射文件](http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html) 」。 -### 第三方 +XML 映射文件只有几个顶级元素: +- `cache` – 对给定命名空间的缓存配置。 +- `cache-ref` – 对其他命名空间缓存配置的引用。 +- `resultMap` – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。 +- \~\~`parameterMap` – 已被废弃!老式风格的参数映射。更好的办法是使用内联参数,此元素可能在将来被移除。文档中不会介绍此元素。\~\~ +- `sql` – 可被其他语句引用的可重用语句块。 +- `insert` – 映射插入语句 +- `update` – 映射更新语句 +- `delete` – 映射删除语句 +- `select` – 映射查询语句 + +## Mybatis 扩展 + +- [mybatis-plus](https://github.com/baomidou/mybatis-plus) - CRUD、代码生成器、分页器等多功能 +- [Mapper](https://github.com/abel533/Mapper) - CRUD - [Mybatis-PageHelper](https://github.com/pagehelper/Mybatis-PageHelper) - Mybatis 通用分页插件 -- [MyBatis Spring Adapter](https://github.com/mybatis/spring) - Mybatis 和 Spring 的桥接包 -- [深入理解 mybatis 原理](https://blog.csdn.net/luanlouis/article/details/40422941) + +## 资料 + +- **官方** + +| [Github](https://github.com/mybatis/mybatis-3) | [官网](http://www.mybatis.org/mybatis-3/) | [MyBatis Generator](https://github.com/mybatis/generator) | [Spring 集成](https://github.com/mybatis/spring) | [Spring Boot 集成](https://github.com/mybatis/spring-boot-starter) | + +- **文章** + - [深入理解 mybatis 原理](https://blog.csdn.net/luanlouis/article/details/40422941) + - [mybatis 源码中文注释](https://github.com/tuguangquan/mybatis) - mybatis 源码解读 + - [MyBatis Generator 详解](https://blog.csdn.net/isea533/article/details/42102297) diff --git a/docs/tools/nginx.md b/docs/tools/nginx.md deleted file mode 100644 index 4325d03f..00000000 --- a/docs/tools/nginx.md +++ /dev/null @@ -1,545 +0,0 @@ ---- -title: Nginx 快速入门 -date: 2016-10-10 -categories: -- web -tags: -- server -- nginx -- load balance ---- - -# Nginx 快速入门 - - - -- [概述](#概述) -- [安装与使用](#安装与使用) - - [安装](#安装) - - [从源代码编译 Nginx](#从源代码编译-nginx) - - [Windows 安装](#windows-安装) - - [使用](#使用) -- [nginx 配置实战](#nginx-配置实战) - - [http 反向代理配置](#http-反向代理配置) - - [负载均衡配置](#负载均衡配置) - - [网站有多个 webapp 的配置](#网站有多个-webapp-的配置) - - [https 反向代理配置](#https-反向代理配置) - - [静态站点配置](#静态站点配置) - - [跨域解决方案](#跨域解决方案) -- [参考](#参考) - - - -## 概述 - -**什么是 Nginx?** - -**Nginx (engine x)** 是一款轻量级的 Web 服务器 、反向代理服务器及电子邮件(IMAP/POP3)代理服务器。 - -
- -
- -**什么是反向代理?** - -反向代理(Reverse Proxy)方式是指以代理服务器来接受 internet 上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给 internet 上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。 - -
- -
- -## 安装与使用 - -### 安装 - -[nginx 官网下载地址](http://nginx.org/) - -发布版本分为 Linux 和 windows 版本。 - -也可以下载源码,编译后运行。 - -#### 从源代码编译 Nginx - -把源码解压缩之后,在终端里运行如下命令: - -```sh -$ ./configure -$ make -$ sudo make install -``` - -默认情况下,Nginx 会被安装在 `/usr/local/nginx`。通过设定[编译选项](http://tool.oschina.net/uploads/apidocs/nginx-zh/NginxChsInstallOptions.htm),你可以改变这个设定。 - -#### Windows 安装 - -为了安装 Nginx / Win32,需先下载它。然后解压之,然后运行即可。下面以 C 盘根目录为例说明下: - -```sh -cd C: -cd C:\nginx-0.8.54 start nginx -``` - -Nginx / Win32 是运行在一个控制台程序,而非 windows 服务方式的。服务器方式目前还是开发尝试中。 - -### 使用 - -nginx 的使用比较简单,就是几条命令。 - -常用到的命令如下: - -```sh -nginx -s stop 快速关闭Nginx,可能不保存相关信息,并迅速终止web服务。 -nginx -s quit 平稳关闭Nginx,保存相关信息,有安排的结束web服务。 -nginx -s reload 因改变了Nginx相关配置,需要重新加载配置而重载。 -nginx -s reopen 重新打开日志文件。 -nginx -c filename 为 Nginx 指定一个配置文件,来代替缺省的。 -nginx -t 不运行,而仅仅测试配置文件。nginx 将检查配置文件的语法的正确性,并尝试打开配置文件中所引用到的文件。 -nginx -v 显示 nginx 的版本。 -nginx -V 显示 nginx 的版本,编译器版本和配置参数。 -``` - -如果不想每次都敲命令,可以在 nginx 安装目录下新添一个启动批处理文件**startup.bat**,双击即可运行。内容如下: - -```sh -@echo off -rem 如果启动前已经启动nginx并记录下pid文件,会kill指定进程 -nginx.exe -s stop - -rem 测试配置文件语法正确性 -nginx.exe -t -c conf/nginx.conf - -rem 显示版本信息 -nginx.exe -v - -rem 按照指定配置去启动nginx -nginx.exe -c conf/nginx.conf -``` - -如果是运行在 Linux 下,写一个 shell 脚本,大同小异。 - -## nginx 配置实战 - -我始终认为,各种开发工具的配置还是结合实战来讲述,会让人更易理解。 - -### http 反向代理配置 - -我们先实现一个小目标:不考虑复杂的配置,仅仅是完成一个 http 反向代理。 - -nginx.conf 配置文件如下: -**_注:conf / nginx.conf 是 nginx 的默认配置文件。你也可以使用 nginx -c 指定你的配置文件_** - -``` -#运行用户 -#user somebody; - -#启动进程,通常设置成和cpu的数量相等 -worker_processes 1; - -#全局错误日志 -error_log D:/Tools/nginx-1.10.1/logs/error.log; -error_log D:/Tools/nginx-1.10.1/logs/notice.log notice; -error_log D:/Tools/nginx-1.10.1/logs/info.log info; - -#PID文件,记录当前启动的nginx的进程ID -pid D:/Tools/nginx-1.10.1/logs/nginx.pid; - -#工作模式及连接数上限 -events { - worker_connections 1024; #单个后台worker process进程的最大并发链接数 -} - -#设定http服务器,利用它的反向代理功能提供负载均衡支持 -http { - #设定mime类型(邮件支持类型),类型由mime.types文件定义 - include D:/Tools/nginx-1.10.1/conf/mime.types; - default_type application/octet-stream; - - #设定日志 - log_format main '[$remote_addr] - [$remote_user] [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for"'; - - access_log D:/Tools/nginx-1.10.1/logs/access.log main; - rewrite_log on; - - #sendfile 指令指定 nginx 是否调用 sendfile 函数(zero copy 方式)来输出文件,对于普通应用, - #必须设为 on,如果用来进行下载等应用磁盘IO重负载应用,可设置为 off,以平衡磁盘与网络I/O处理速度,降低系统的uptime. - sendfile on; - #tcp_nopush on; - - #连接超时时间 - keepalive_timeout 120; - tcp_nodelay on; - - #gzip压缩开关 - #gzip on; - - #设定实际的服务器列表 - upstream zp_server1{ - server 127.0.0.1:8089; - } - - #HTTP服务器 - server { - #监听80端口,80端口是知名端口号,用于HTTP协议 - listen 80; - - #定义使用www.xx.com访问 - server_name www.helloworld.com; - - #首页 - index index.html - - #指向webapp的目录 - root D:\01_Workspace\Project\github\zp\SpringNotes\spring-security\spring-shiro\src\main\webapp; - - #编码格式 - charset utf-8; - - #代理配置参数 - proxy_connect_timeout 180; - proxy_send_timeout 180; - proxy_read_timeout 180; - proxy_set_header Host $host; - proxy_set_header X-Forwarder-For $remote_addr; - - #反向代理的路径(和upstream绑定),location 后面设置映射的路径 - location / { - proxy_pass http://zp_server1; - } - - #静态文件,nginx自己处理 - location ~ ^/(images|javascript|js|css|flash|media|static)/ { - root D:\01_Workspace\Project\github\zp\SpringNotes\spring-security\spring-shiro\src\main\webapp\views; - #过期30天,静态文件不怎么更新,过期可以设大一点,如果频繁更新,则可以设置得小一点。 - expires 30d; - } - - #设定查看Nginx状态的地址 - location /NginxStatus { - stub_status on; - access_log on; - auth_basic "NginxStatus"; - auth_basic_user_file conf/htpasswd; - } - - #禁止访问 .htxxx 文件 - location ~ /\.ht { - deny all; - } - - #错误处理页面(可选择性配置) - #error_page 404 /404.html; - #error_page 500 502 503 504 /50x.html; - #location = /50x.html { - # root html; - #} - } -} -``` - -好了,让我们来试试吧: - -1. 启动 webapp,注意启动绑定的端口要和 nginx 中的 `upstream` 设置的端口保持一致。 -2. 更改 host:在 C:\Windows\System32\drivers\etc 目录下的 host 文件中添加一条 DNS 记录 - -``` -127.0.0.1 www.helloworld.com -``` - -3. 启动前文中 startup.bat 的命令 -4. 在浏览器中访问 www.helloworld.com,不出意外,已经可以访问了。 - -### 负载均衡配置 - -上一个例子中,代理仅仅指向一个服务器。 - -但是,网站在实际运营过程中,多半都是有多台服务器运行着同样的 app,这时需要使用负载均衡来分流。 - -nginx 也可以实现简单的负载均衡功能。 - -假设这样一个应用场景:将应用部署在 192.168.1.11:80、192.168.1.12:80、192.168.1.13:80 三台 linux 环境的服务器上。网站域名叫 www.helloworld.com,公网 IP 为 192.168.1.11。在公网 IP 所在的服务器上部署 nginx,对所有请求做负载均衡处理。 - -nginx.conf 配置如下: - -``` -http { - #设定mime类型,类型由mime.type文件定义 - include /etc/nginx/mime.types; - default_type application/octet-stream; - #设定日志格式 - access_log /var/log/nginx/access.log; - - #设定负载均衡的服务器列表 - upstream load_balance_server { - #weigth参数表示权值,权值越高被分配到的几率越大 - server 192.168.1.11:80 weight=5; - server 192.168.1.12:80 weight=1; - server 192.168.1.13:80 weight=6; - } - - #HTTP服务器 - server { - #侦听80端口 - listen 80; - - #定义使用www.xx.com访问 - server_name www.helloworld.com; - - #对所有请求进行负载均衡请求 - location / { - root /root; #定义服务器的默认网站根目录位置 - index index.html index.htm; #定义首页索引文件的名称 - proxy_pass http://load_balance_server ;#请求转向load_balance_server 定义的服务器列表 - - #以下是一些反向代理的配置(可选择性配置) - #proxy_redirect off; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - #后端的Web服务器可以通过X-Forwarded-For获取用户真实IP - proxy_set_header X-Forwarded-For $remote_addr; - proxy_connect_timeout 90; #nginx跟后端服务器连接超时时间(代理连接超时) - proxy_send_timeout 90; #后端服务器数据回传时间(代理发送超时) - proxy_read_timeout 90; #连接成功后,后端服务器响应时间(代理接收超时) - proxy_buffer_size 4k; #设置代理服务器(nginx)保存用户头信息的缓冲区大小 - proxy_buffers 4 32k; #proxy_buffers缓冲区,网页平均在32k以下的话,这样设置 - proxy_busy_buffers_size 64k; #高负荷下缓冲大小(proxy_buffers*2) - proxy_temp_file_write_size 64k; #设定缓存文件夹大小,大于这个值,将从upstream服务器传 - - client_max_body_size 10m; #允许客户端请求的最大单文件字节数 - client_body_buffer_size 128k; #缓冲区代理缓冲用户端请求的最大字节数 - } - } -} -``` - -### 网站有多个 webapp 的配置 - -当一个网站功能越来越丰富时,往往需要将一些功能相对独立的模块剥离出来,独立维护。这样的话,通常,会有多个 webapp。 - -举个例子:假如 www.helloworld.com 站点有好几个 webapp,finance(金融)、product(产品)、admin(用户中心)。访问这些应用的方式通过上下文(context)来进行区分: - -www.helloworld.com/finance/ - -www.helloworld.com/product/ - -www.helloworld.com/admin/ - -我们知道,http 的默认端口号是 80,如果在一台服务器上同时启动这 3 个 webapp 应用,都用 80 端口,肯定是不成的。所以,这三个应用需要分别绑定不同的端口号。 - -那么,问题来了,用户在实际访问 www.helloworld.com 站点时,访问不同 webapp,总不会还带着对应的端口号去访问吧。所以,你再次需要用到反向代理来做处理。 - -配置也不难,来看看怎么做吧: - -``` -http { - #此处省略一些基本配置 - - upstream product_server{ - server www.helloworld.com:8081; - } - - upstream admin_server{ - server www.helloworld.com:8082; - } - - upstream finance_server{ - server www.helloworld.com:8083; - } - - server { - #此处省略一些基本配置 - #默认指向product的server - location / { - proxy_pass http://product_server; - } - - location /product/{ - proxy_pass http://product_server; - } - - location /admin/ { - proxy_pass http://admin_server; - } - - location /finance/ { - proxy_pass http://finance_server; - } - } -} -``` - -### https 反向代理配置 - -一些对安全性要求比较高的站点,可能会使用 HTTPS(一种使用 ssl 通信标准的安全 HTTP 协议)。 - -这里不科普 HTTP 协议和 SSL 标准。但是,使用 nginx 配置 https 需要知道几点: - -* HTTPS 的固定端口号是 443,不同于 HTTP 的 80 端口 -* SSL 标准需要引入安全证书,所以在 nginx.conf 中你需要指定证书和它对应的 key - -其他和 http 反向代理基本一样,只是在 `Server` 部分配置有些不同。 - -``` - #HTTP服务器 - server { - #监听443端口。443为知名端口号,主要用于HTTPS协议 - listen 443 ssl; - - #定义使用www.xx.com访问 - server_name www.helloworld.com; - - #ssl证书文件位置(常见证书文件格式为:crt/pem) - ssl_certificate cert.pem; - #ssl证书key位置 - ssl_certificate_key cert.key; - - #ssl配置参数(选择性配置) - ssl_session_cache shared:SSL:1m; - ssl_session_timeout 5m; - #数字签名,此处使用MD5 - ssl_ciphers HIGH:!aNULL:!MD5; - ssl_prefer_server_ciphers on; - - location / { - root /root; - index index.html index.htm; - } - } -``` - -### 静态站点配置 - -有时候,我们需要配置静态站点(即 html 文件和一堆静态资源)。 - -举例来说:如果所有的静态资源都放在了 `/app/dist` 目录下,我们只需要在 `nginx.conf` 中指定首页以及这个站点的 host 即可。 - -配置如下: - -``` -worker_processes 1; - -events { - worker_connections 1024; -} - -http { - include mime.types; - default_type application/octet-stream; - sendfile on; - keepalive_timeout 65; - - gzip on; - gzip_types text/plain application/x-javascript text/css application/xml text/javascript application/javascript image/jpeg image/gif image/png; - gzip_vary on; - - server { - listen 80; - server_name static.zp.cn; - - location / { - root /app/dist; - index index.html; - #转发任何请求到 index.html - } - } -} -``` - -然后,添加 HOST: - -127.0.0.1 static.zp.cn - -此时,在本地浏览器访问 static.zp.cn ,就可以访问静态站点了。 - -### 跨域解决方案 - -web 领域开发中,经常采用前后端分离模式。这种模式下,前端和后端分别是独立的 web 应用程序,例如:后端是 Java 程序,前端是 React 或 Vue 应用。 - -各自独立的 web app 在互相访问时,势必存在跨域问题。解决跨域问题一般有两种思路: - -1. **CORS** - -在后端服务器设置 HTTP 响应头,把你需要运行访问的域名加入加入 `Access-Control-Allow-Origin` 中。 - -2. **jsonp** - -把后端根据请求,构造 json 数据,并返回,前端用 jsonp 跨域。 - -这两种思路,本文不展开讨论。 - -需要说明的是,nginx 根据第一种思路,也提供了一种解决跨域的解决方案。 - -举例:www.helloworld.com 网站是由一个前端 app ,一个后端 app 组成的。前端端口号为 9000, 后端端口号为 8080。 - -前端和后端如果使用 http 进行交互时,请求会被拒绝,因为存在跨域问题。来看看,nginx 是怎么解决的吧: - -首先,在 enable-cors.conf 文件中设置 cors : - -``` -# allow origin list -set $ACAO '*'; - -# set single origin -if ($http_origin ~* (www.helloworld.com)$) { - set $ACAO $http_origin; -} - -if ($cors = "trueget") { - add_header 'Access-Control-Allow-Origin' "$http_origin"; - add_header 'Access-Control-Allow-Credentials' 'true'; - add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; - add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'; -} - -if ($request_method = 'OPTIONS') { - set $cors "${cors}options"; -} - -if ($request_method = 'GET') { - set $cors "${cors}get"; -} - -if ($request_method = 'POST') { - set $cors "${cors}post"; -} -``` - -接下来,在你的服务器中 `include enable-cors.conf` 来引入跨域配置: - -``` -# ---------------------------------------------------- -# 此文件为项目 nginx 配置片段 -# 可以直接在 nginx config 中 include(推荐) -# 或者 copy 到现有 nginx 中,自行配置 -# www.helloworld.com 域名需配合 dns hosts 进行配置 -# 其中,api 开启了 cors,需配合本目录下另一份配置文件 -# ---------------------------------------------------- -upstream front_server{ - server www.helloworld.com:9000; -} -upstream api_server{ - server www.helloworld.com:8080; -} - -server { - listen 80; - server_name www.helloworld.com; - - location ~ ^/api/ { - include enable-cors.conf; - proxy_pass http://api_server; - rewrite "^/api/(.*)$" /$1 break; - } - - location ~ ^/ { - proxy_pass http://front_server; - } -} -``` - -到此,就完成了。 - -## 参考 - -[Nginx 的中文维基](http://tool.oschina.net/apidocs/apidoc?api=nginx-zh) From 5781307830d946c4292cb215ed5bf39cc8c1b927 Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Wed, 29 May 2019 10:17:41 +0800 Subject: [PATCH 20/75] update docs. --- README.md | 2 ++ docs/README.md | 2 ++ docs/server/README.md | 5 +++++ docs/tools/README.md | 3 --- scripts/gitpush.sh | 5 +++++ 5 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 docs/server/README.md delete mode 100644 docs/tools/README.md create mode 100644 scripts/gitpush.sh diff --git a/README.md b/README.md index c0289a17..d7a6826f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # JavaWeb > Java Web 开发之路经验总结 +> +> 电子书阅读:[Github Pages](https://dunwu.github.io/javaweb/) | [Gitee Pages](https://turnon.gitee.io/javaweb/) | :beginner: | :recycle: | :coffee: | :eye: | :eyes: | :computer: | | ------------- | --------------------- | ----------------- | --------------------- | ------------------------- | ------------- | diff --git a/docs/README.md b/docs/README.md index 30ba5ce9..daea987c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,8 @@ # JavaWeb > Java Web 开发之路经验总结 +> +> 电子书阅读:[Github Pages](https://dunwu.github.io/javaweb/) | [Gitee Pages](https://turnon.gitee.io/javaweb/) | :beginner: | :recycle: | :coffee: | :eye: | :eyes: | :computer: | | ------------- | --------------------- | ----------------- | --------------------- | ------------------------- | ------------- | diff --git a/docs/server/README.md b/docs/server/README.md new file mode 100644 index 00000000..4745bc24 --- /dev/null +++ b/docs/server/README.md @@ -0,0 +1,5 @@ +# 服务器 + +- [Nginx 简易教程](https://github.com/dunwu/nginx-tutorial) - 轻量级的 Web 服务器、反向代理服务器及电子邮件(IMAP/POP3)代理服务器,支持负载均衡。 +- [Tomcat 应用指南](tomcat.md) - 轻量级的应用服务器 +- [Jetty 应用指南](jetty.md) - 比 Tomcat 更轻量级的应用服务器 diff --git a/docs/tools/README.md b/docs/tools/README.md deleted file mode 100644 index d10c34fc..00000000 --- a/docs/tools/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# 分布式 - -> Java Web 领域常用工具。 \ No newline at end of file diff --git a/scripts/gitpush.sh b/scripts/gitpush.sh new file mode 100644 index 00000000..ec14d5a9 --- /dev/null +++ b/scripts/gitpush.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +cd .. +git push github master +git push gitee master From 30c1d3fbe59cccbc020309a4a477ef8396e44216 Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Wed, 29 May 2019 13:07:04 +0800 Subject: [PATCH 21/75] update docs --- README.md | 9 +- config/git-config | 15 + docs/README.md | 9 +- docs/javaee/README.md | 8 + ...-and-sesion.md => javaee-cookie-sesion.md} | 0 ...-listener.md => javaee-filter-listener.md} | 0 docs/javaee/javaee-interview.md | 282 ++++++++++++++++++ docs/javaee/{jsp.md => javaee-jsp.md} | 28 +- docs/javaee/{servlet.md => javaee-servlet.md} | 156 ++++------ docs/navbar.md | 24 +- 10 files changed, 408 insertions(+), 123 deletions(-) create mode 100644 config/git-config rename docs/javaee/{cookie-and-sesion.md => javaee-cookie-sesion.md} (100%) rename docs/javaee/{filter-and-listener.md => javaee-filter-listener.md} (100%) create mode 100644 docs/javaee/javaee-interview.md rename docs/javaee/{jsp.md => javaee-jsp.md} (99%) rename docs/javaee/{servlet.md => javaee-servlet.md} (50%) diff --git a/README.md b/README.md index d7a6826f..aa9894af 100644 --- a/README.md +++ b/README.md @@ -33,10 +33,11 @@ > [JavaEE](docs/javaee/) 技术——Java Web 的基石 -- [JavaEE 之 Servlet 指南](docs/javaee/servlet.md) -- [JavaEE 之 Jsp 指南](docs/javaee/jsp.md) -- [JavaEE 之 Filter 和 Listener](docs/javaee/filter-and-listener.md) -- [JavaEE 之 Cookie 和 Session](docs/javaee/cookie-and-sesion.md) +- [JavaEE 面经](docs/javaee/javaee-interview.md) +- [JavaEE 之 Servlet 指南](docs/javaee/javaee-servlet.md) +- [JavaEE 之 Jsp 指南](docs/javaee/javaee-jsp.md) +- [JavaEE 之 Filter 和 Listener](docs/javaee/javaee-filter-listener.md) +- [JavaEE 之 Cookie 和 Session](docs/javaee/javaee-cookie-sesion.md) ## 主流技术 diff --git a/config/git-config b/config/git-config new file mode 100644 index 00000000..441dfc92 --- /dev/null +++ b/config/git-config @@ -0,0 +1,15 @@ +[core] + repositoryformatversion = 0 + filemode = false + bare = false + logallrefupdates = true + symlinks = false + ignorecase = true +[submodule] + active = . +[remote "github"] + url = git@github.com:dunwu/javaweb.git + fetch = +refs/heads/*:refs/remotes/github/* +[remote "gitee"] + url = git@gitee.com:turnon/javaweb.git + fetch = +refs/heads/*:refs/remotes/gitee/* diff --git a/docs/README.md b/docs/README.md index daea987c..ac8b0ab2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -33,10 +33,11 @@ > [JavaEE](javaee/) 技术——Java Web 的基石 -- [JavaEE 之 Servlet 指南](javaee/servlet.md) -- [JavaEE 之 Jsp 指南](javaee/jsp.md) -- [JavaEE 之 Filter 和 Listener](javaee/filter-and-listener.md) -- [JavaEE 之 Cookie 和 Session](javaee/cookie-and-sesion.md) +- [JavaEE 面经](javaee/javaee-interview.md) +- [JavaEE 之 Servlet 指南](javaee/javaee-servlet.md) +- [JavaEE 之 Jsp 指南](javaee/javaee-jsp.md) +- [JavaEE 之 Filter 和 Listener](javaee/javaee-filter-listener.md) +- [JavaEE 之 Cookie 和 Session](javaee/javaee-cookie-sesion.md) ## 主流技术 diff --git a/docs/javaee/README.md b/docs/javaee/README.md index 44b42327..fe37eab1 100644 --- a/docs/javaee/README.md +++ b/docs/javaee/README.md @@ -1,5 +1,13 @@ # JavaEE 教程 +## 知识点 + +- [JavaEE 面经](javaee-interview.md) +- [JavaEE 之 Servlet 指南](javaee-servlet.md) +- [JavaEE 之 Jsp 指南](javaee-jsp.md) +- [JavaEE 之 Filter 和 Listener](javaee-filter-listener.md) +- [JavaEE 之 Cookie 和 Session](javaee-cookie-sesion.md) + ## 学习资料 - 书 diff --git a/docs/javaee/cookie-and-sesion.md b/docs/javaee/javaee-cookie-sesion.md similarity index 100% rename from docs/javaee/cookie-and-sesion.md rename to docs/javaee/javaee-cookie-sesion.md diff --git a/docs/javaee/filter-and-listener.md b/docs/javaee/javaee-filter-listener.md similarity index 100% rename from docs/javaee/filter-and-listener.md rename to docs/javaee/javaee-filter-listener.md diff --git a/docs/javaee/javaee-interview.md b/docs/javaee/javaee-interview.md new file mode 100644 index 00000000..19e33510 --- /dev/null +++ b/docs/javaee/javaee-interview.md @@ -0,0 +1,282 @@ +# JavaEE 面经 + + + +- [1. Servlet](#1-servlet) + - [1.1. 什么是 Servlet?](#11-什么是-servlet) + - [1.2. Servlet 和 CGI 的区别?](#12-servlet-和-cgi-的区别) + - [1.3. Servlet 版本以及主要特性?](#13-servlet-版本以及主要特性) + - [1.4. Servlet 和 JSP 的区别?](#14-servlet-和-jsp-的区别) + - [1.5. 简述 Servlet 生命周期](#15-简述-servlet-生命周期) + - [1.6. 如何现实 servlet 的单线程模式?](#16-如何现实-servlet-的单线程模式) + - [1.7. Servlet 中如何获取用户提交的查询参数或者表单数据](#17-servlet-中如何获取用户提交的查询参数或者表单数据) + - [1.8. request 的主要方法](#18-request-的主要方法) +- [2. JSP](#2-jsp) + - [2.1. JSP 的内置对象?](#21-jsp-的内置对象) + - [2.2. JSP 的作用域?](#22-jsp-的作用域) + - [2.3. JSP 中 7 个动作指令和作用?](#23-jsp-中-7-个动作指令和作用) + - [2.4. JSP 中动态 INCLUDE 和静态 INCLUDE 有什么区别?](#24-jsp-中动态-include-和静态-include-有什么区别) +- [3. 原理](#3-原理) + - [3.1. 请求转发(forward)和重定向(redirect)的区别?](#31-请求转发forward和重定向redirect的区别) + - [3.2. get 请求和 post 请求的区别?](#32-get-请求和-post-请求的区别) + - [3.3. 用户在浏览器中输入 URL 之后,发什么了什么?写出请求和响应的流程](#33-用户在浏览器中输入-url-之后发什么了什么写出请求和响应的流程) + - [3.4. 什么是 Web Service?](#34-什么是-web-service) + - [3.5. 会话跟踪技术有哪些?](#35-会话跟踪技术有哪些) + - [3.6. 响应结果状态码有哪些,并给出中文含义?](#36-响应结果状态码有哪些并给出中文含义) + - [3.7. XML 文档定义有几种形式?它们之间有何本质区别?解析 XML 文档有哪几种方式?](#37-xml-文档定义有几种形式它们之间有何本质区别解析-xml-文档有哪几种方式) +- [4. 参考资料](#4-参考资料) + + + +## 1. Servlet + +### 1.1. 什么是 Servlet? + +Servlet(Server Applet),即小服务程序或服务连接器。Servlet 是 Java 编写的服务器端程序,具有独立于平台和协议的特性,主要功能在于交互式地浏览和生成数据,生成动态 Web 内容。 + +- 狭义的 Servlet 是指 Java 实现的一个接口。 +- 广义的 Servlet 是指任何实现了这个 Servlet 接口的类。 + +Servlet 运行于支持 Java 的应用服务器中。从原理上讲,Servlet 可以响应任何类型的请求,但绝大多数情况下 Servlet 只用来扩展基于 HTTP 协议的 Web 服务器。 + +### 1.2. Servlet 和 CGI 的区别? + +Servlet 技术出现之前,Web 主要使用 CGI 技术。它们的区别如下: + +- Servlet 是基于 Java 编写的,处于服务器进程中,他能够通过多线程方式运行 service() 方法,一个实例可以服务于多个请求,而且一般不会销毁; +- CGI(Common Gateway Interface),即通用网关接口。它会为每个请求产生新的进程,服务完成后销毁,所以效率上低于 Servlet。 + +### 1.3. Servlet 版本以及主要特性? + +| 版本 | 日期 | JAVA EE/JDK 版本 | 特性 | +| ----------- | ------------- | ------------------ | --------------------------------------------------------------------- | +| Servlet 4.0 | 2017 年 10 月 | JavaEE 8 | HTTP2 | +| Servlet 3.1 | 2013 年 5 月 | JavaEE 7 | 非阻塞 I/O,HTTP 协议升级机制 | +| Servlet 3.0 | 2009 年 12 月 | JavaEE 6, JavaSE 6 | 可插拔性,易于开发,异步 Servlet,安全性,文件上传 | +| Servlet 2.5 | 2005 年 10 月 | JavaEE 5, JavaSE 5 | 依赖 JavaSE 5,支持注解 | +| Servlet 2.4 | 2003 年 11 月 | J2EE 1.4, J2SE 1.3 | web.xml 使用 XML Schema | +| Servlet 2.3 | 2001 年 8 月 | J2EE 1.3, J2SE 1.2 | Filter | +| Servlet 2.2 | 1999 年 8 月 | J2EE 1.2, J2SE 1.2 | 成为 J2EE 标准 | +| Servlet 2.1 | 1998 年 11 月 | 未指定 | First official specification, added RequestDispatcher, ServletContext | +| Servlet 2.0 | | JDK 1.1 | Part of Java Servlet Development Kit 2.0 | +| Servlet 1.0 | 1997 年 6 月 | | | + +### 1.4. Servlet 和 JSP 的区别? + +1. Servlet 是一个运行在服务器上的 Java 类,依靠服务器支持向浏览器传输数据。 +2. **JSP 本质上就是 Servlet**,每次运行的时候 JSP 都会被编译成 .java 文件,然后再被编译成 .class 文件。 +3. 有了 JSP,Servlet 不再负责动态生成页面,转而去负责控制程序逻辑的作用,控制 JSP 与 JavaBean 之间的流转。 +4. JSP 侧重于视图,而 Servlet 侧重于控制逻辑,在 MVC 架构模式中,JSP 适合充当视图 View,Servlet 适合充当控制器 Controller。 + +### 1.5. 简述 Servlet 生命周期 + +
+ +Servlet 生命周期如下: + +1. **加载** - 第一个到达服务器的 HTTP 请求被委派到 Servlet 容器。容器通过类加载器使用 Servlet 类对应的文件加载 servlet; +2. **初始化** - Servlet 通过调用 **init ()** 方法进行初始化。 +3. **服务** - Servlet 调用 **service()** 方法来处理客户端的请求。 +4. **销毁** - Servlet 通过调用 **destroy()** 方法终止(结束)。 +5. **卸载** - Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。 + +### 1.6. 如何现实 servlet 的单线程模式? + +```jsp +<%@ page isThreadSafe="false" %> +``` + +### 1.7. Servlet 中如何获取用户提交的查询参数或者表单数据 + +- HttpServletRequest 的 getParameter() 方法。 +- HttpServletRequest 的 getParameterValues() 方法。 +- HttpServletRequest 的 getParameterMap() 方法。 + +### 1.8. request 的主要方法 + +- setAttribute(String name,Object):设置名字为 name 的 request 的参数值 +- getAttribute(String name):返回由 name 指定的属性值 +- getAttributeNames():返回 request 对象所有属性的名字集合,结果是一个枚举的实例 +- getCookies():返回客户端的所有 Cookie 对象,结果是一个 Cookie 数组 +- getCharacterEncoding():返回请求中的字符编码方式 +- getContentLength():返回请求的 Body 的长度 +- getHeader(String name):获得 HTTP 协议定义的文件头信息 +- getHeaders(String name):返回指定名字的 request Header 的所有值,结果是一个枚举的实例 +- getHeaderNames():返回所以 request Header 的名字,结果是一个枚举的实例 +- getInputStream():返回请求的输入流,用于获得请求中的数据 getMethod():获得客户端向服务器端传送数据的方法 +- getParameter(String name):获得客户端传送给服务器端的有 name 指定的参数值 +- getParameterNames():获得客户端传送给服务器端的所有参数的名字,结果是一个枚举的实例 +- getParameterValues(String name):获得有 name 指定的参数的所有值 +- getProtocol():获取客户端向服务器端传送数据所依据的协议名称 +- getQueryString():获得查询字符串 +- getRequestURI():获取发出请求字符串的客户端地址 +- getRemoteAddr():获取客户端的 IP 地址 +- getRemoteHost():获取客户端的名字 +- getSession([Boolean create]):返回和请求相关 +- Session getServerName():获取服务器的名字 +- getServletPath():获取客户端所请求的脚本文件的路径 +- getServerPort():获取服务器的端口号 +- removeAttribute(String name):删除请求中的一个属性 + +## 2. JSP + +### 2.1. JSP 的内置对象? + +1. **request**:包含**客户端请求的信息**; +2. **response**:包含**服务器传回客户端的响应信息**; +3. **session**:主要用来**区分每个用户信息和会话状态**; +4. **pageContext**:管理**页面属性**; +5. **application**:服务器启动时创建,服务器关闭时停止,**保存所有应用系统中的共有数据**,一个共享的内置对象(即一个容器中的多个用户共享一个 application 对象); +6. **out**:向客户端**输出数据**; +7. **config**:代码片段配置对象,用于**初始化 Servlet 的配置参数**; +8. **page**:指**网页本身**; +9. **exception**:处理 JSP 文件执行时发生的错误和异常,只要在**错误页面**里才能使用。 + +### 2.2. JSP 的作用域? + +1. **page**:一个页面; +2. **request**:一次请求; +3. **session**:一次会话; +4. **application**:服务器从启动到停止。 + +### 2.3. JSP 中 7 个动作指令和作用? + +1. **jsp:forward** - 执行页面转向,把请求转发到下一个页面; +2. **jsp:param** - 用于传递参数,必须与其他支持参数的标签一起使用; +3. **jsp:include** - 用于**动态引入一个 JSP 页面**; +4. **jsp:plugin** - 用于**下载 JavaBean 或 Applet 到客户端执行**; +5. **jsp:useBean** - 寻求或者实例化一个 JavaBean; +6. **jsp:setProperty** - 设置 JavaBean 的属性值; +7. **jsp:getProperty** - 获取 JavaBean 的属性值。 + +### 2.4. JSP 中动态 INCLUDE 和静态 INCLUDE 有什么区别? + +- **静态 INCLUDE**:用 include 伪码实现,**不会检查所含文件的变化**,适用于包含**静态页面<%@ include file="页面名称.html" %>**。**先合并再编译**。 +- **动态 INCLUDE**:用 jsp:include 动作实现 **** 它总是**会检查文件中的变化**,适用于包含**动态页面**,并且可以**带参数**。**先编译再合并**。 + +## 3. 原理 + +### 3.1. 请求转发(forward)和重定向(redirect)的区别? + +- 效率上 + - 转发(forward) > 重定向(redirect) +- 显示上 + - 重定向(redirect):显示新的 URL + - 转发(forward):地址栏不变 +- 数据上 + - 转发(forward):可以共享 request 里面的数据 + - 重定向(redirect):不能 +- 请求次数 + - 重定向(redirect)是两次 + - 转发(forward)是一次 + +### 3.2. get 请求和 post 请求的区别? + +![img](https://upload-images.jianshu.io/upload_images/7779232-5be5ae990207f9d2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/814/format/webp) + +- GET: + - 从服务器上获取数据,一般不能使用在写操作接口 + - 由 URL 所限制,GET 方式传输的数据大小有所限制,传送的数据量不超过 2KB + - 请求的数据会附加在 URL 之后,以?分隔 URL 和传输数据,多个参数用&连接 + - 安全性差 +- POST: + - 向服务器提交数据,一般处理写业务 + - POST 方式传送的数据量比较大,一般被默认为没有限制 + - 安全性高 + - 请的求的数据内容放置在 HTML HEADER 中 + +### 3.3. 用户在浏览器中输入 URL 之后,发什么了什么?写出请求和响应的流程 + +1. 域名解析 +2. TCP 三次握手 +3. 浏览器向服务器发送 http 请求 +4. 浏览器发送请求头信息 +5. 服务器处理请求 +6. 服务器做出应答 +7. 服务器发送应答头信息 +8. 服务器发送数据 +9. TCP 连接关闭 + +### 3.4. 什么是 Web Service? + +1. WebService 就是一个应用程序,它向外界暴露出一个能够通过 Web 进行调用的 API。 +2. 它是基于 HTTP 协议传输数据,这使得运行在不同机上的不同应用程序,无须借助附加的、专门的第三方 软件或硬件,就可以相互交换数据或集成。 + +### 3.5. 会话跟踪技术有哪些? + +由于 HTTP 协议本身是无状态的,服务器为了区分不同的用户,就需要对用户会话进行跟踪,简单的说就是为用户进行登记,为用户分配唯一的 ID,下一次用户在请求中包含此 ID,服务器根据此判断到底是哪一个用户。 + +- URL 重写:在 URL 中添加会话信息作为请求的参数,或者将唯一的会话 ID 添加到 URL 结尾,以表示一个会话。设置表单隐藏域:将和会话跟踪相关的字段添加到隐藏域中,这些信息不会在浏览器显示,但是提交表单时会提交给服务器。 +- cookie:cookie 有两种: + - 一种是基于窗口的,浏览器关闭后,cookie 就没有了; + - 另一种是将信息存储在一个临时文件中,并设置其有效路径和最大存活时间。当用户通过浏览器和服务器建立一次会话后,会话 ID 就会随相应信息储存在基于窗口的 cookie 中,那就意味着只要浏览器没有关闭,会话没有超时,下一次请求时这个会话 ID 又会提交给服务器,让服务器识别用户身份。 + - 在使用 cookie 时要注意几点: + - 首先不要在 cookie 中存放敏 感信息; + - 其次 cookie 存储的数据量有限(4k),不能将过多的内容存储 cookie 中; + - 再者浏览器通常只允许一个站点最多存放 20 个 cookie。 + - 当然,和用户会话相关的其他信息(除了会话 ID)也可以存在 cookie 方便进行会话 跟踪; +- HttpSession:在所有会话跟踪技术中,HttpSession 对象是最强大也是功能最多的。当一个用户第一次访问某个网站时会自动创建 HttpSession,每个用户可以访问他自己的 HttpSession。可以通过 HttpServletRequest 对象的 getSession 方法获得 HttpSession,通过 HttpSession 的 setAttribute 方法可以将一个值放在 HttpSession 中,通过调用 HttpSession 对象的 getAttribute 方法,同时传入属性名就可以获取保存在 HttpSession 中的对象。 + - 与上面三种方式不同的是,HttpSession 放在服务器的内存中,因此不要将过大的对象放在里面,即使目前的 Servlet 容器可以在内存将满时将 HttpSession 中的对象移到其他存储设备中,但是这样势必影响性能。 + - 添加到 HttpSession 中 的值可以是任意 Java 对象,这个对象最好实现了 Serializable 接口,这样 Servlet 容器在必要的时候可以将其序列 化到文件中,否则在序列化时就会出现异常。 + +### 3.6. 响应结果状态码有哪些,并给出中文含义? + +- `1**`:信息性状态码 +- `2**`:成功状态码 + - 200:请求正常成功 + - 204:指示请求成功但没有返回新信息 + - 206:指示服务器已完成对资源的部分 GET 请求 +- `3**`:重定向状态码 + - 301:永久性重定向 + - 302:临时性重定向 + - 304:服务器端允许请求访问资源,但未满足条件 +- `4**`:客户端错误状态码 + - 400:请求报文中存在语法错误 + - 401:发送的请求需要有通过 HTTP 认证的认证信息 + - 403:对请求资源的访问被服务器拒绝了 + - 404:服务器上无法找到请求的资源 +- `5**`:服务器错误状态码 + - 500:服务器端在执行请求时发生了错误 + - 503:服务器暂时处于超负载或正在进行停机维护,现在无法处理请求 + +### 3.7. XML 文档定义有几种形式?它们之间有何本质区别?解析 XML 文档有哪几种方式? + +(1)XML 文档有两种约束方式: + +1. DTD 约束 +2. Schema 约束 + +(2)XML 文档区别: +1 DTD 不符合 XML 的语法结构,schema 符合 XML 的语法结构; +2 DTD 的约束扩展性比较差,XML 文档只能引入一个 DTD 的文件。schema 可以引入多个文件; +3 DTD 不支持名称空间(理解包结构),schema 支持名称空间; +4 DTD 支持数据比较少,schema 支持更多的数据类型; + +(3)解析方式主要有三种: + +- DOM 解析: + - (a)加载整个 xml 的文档到内存中,形成树状结构,生成对象; + - (b)容易产生内存溢出; + - (c)可以做增删改 +- SAX 解析 + - (a)边读边解析; + - (b)不可以做增删改 +- DOM4J 解析(hibernate 底层采用) + - (a)可让 SAX 解析也产生树状结构。 + - (b)主要 api 开发步骤: + - 1)SAXReader.read(xxx.xml)代表解析 xml 的文档,返回对象是 Document; + - 2)Document.getRootElement(),返回的是文档的根节点,是 Element 对象; + - 3)Element: + - .element(...)-- 获得指定名称第一个子元素。可以不指定名称; + - .elements(...)-- 获得指定名称的所有子元素。可以不指定名称; + - .getText()-- 获得当前元素的文本内容; + - .elementText(...)-- 获得指定名称子元素的文本值 + - .addElement()-- 添加子节点 + - .setText()-- 设置子标签内容 + - 4)XMLWriter.write("..")-- 写出 + - 5)XMLWriter.close()-- 关闭输出流 + +## 4. 参考资料 + +- https://blog.csdn.net/YM_IlY/article/details/81266959 +- https://www.jianshu.com/p/f073dde56262 diff --git a/docs/javaee/jsp.md b/docs/javaee/javaee-jsp.md similarity index 99% rename from docs/javaee/jsp.md rename to docs/javaee/javaee-jsp.md index 38556b96..8c4c636f 100644 --- a/docs/javaee/jsp.md +++ b/docs/javaee/javaee-jsp.md @@ -1395,7 +1395,7 @@ Apache Tomcat 安装 JSTL 库步骤如下: 接下来我们在 web.xml 文件中添加以下配置: -``` +```xml ``` @@ -1581,13 +1581,13 @@ JSP 标签扩展可以让你创建新的标签并且可以直接插入到一个 接下来,我们想创建一个自定义标签叫作,标签格式为: -``` +```jsp ``` 要创建自定义的 JSP 标签,你首先必须创建处理标签的 Java 类。所以,让我们创建一个 HelloTag 类,如下所示: -``` +```java package com.runoob; import javax.servlet.jsp.tagext.*; @@ -1607,7 +1607,7 @@ public class HelloTag extends SimpleTagSupport { 编译以上类,并将其复制到环境变量 CLASSPATH 目录中。最后创建如下标签库:`webapps\ROOT\WEB-INF\custom.tld`。 -``` +```xml 1.0 2.0 @@ -1622,7 +1622,7 @@ public class HelloTag extends SimpleTagSupport { 接下来,我们就可以在 JSP 文件中使用 Hello 标签: -``` +```jsp <%@ taglib prefix="ex" uri="WEB-INF/custom.tld"%> @@ -1652,7 +1652,7 @@ Hello Custom Tag! 我们可以修改标签处理类文件,代码如下: -``` +```java package com.runoob; import javax.servlet.jsp.tagext.*; @@ -1674,7 +1674,7 @@ public class HelloTag extends SimpleTagSupport { 接下来我们需要修改 TLD 文件,如下所示: -``` +```xml 1.0 2.0 @@ -1689,7 +1689,7 @@ public class HelloTag extends SimpleTagSupport { 现在我们可以在 JSP 使用修改后的标签,如下所示: -``` +```jsp <%@ taglib prefix="ex" uri="WEB-INF/custom.tld"%> @@ -1713,7 +1713,7 @@ This is message body 你可以在自定义标准中设置各种属性,要接收属性,值自定义标签类必须实现 setter 方法, JavaBean 中的 setter 方法如下所示: -``` +```java package com.runoob; import javax.servlet.jsp.tagext.*; @@ -1750,7 +1750,7 @@ public class HelloTag extends SimpleTagSupport { 属性的名称是"message",所以 setter 方法是的 setMessage()。现在让我们在 TLD 文件中使用的元素添加此属性: -``` +```xml 1.0 2.0 @@ -1768,7 +1768,7 @@ public class HelloTag extends SimpleTagSupport { 现在我们就可以在 JSP 文件中使用 message 属性了,如下所示: -``` +```jsp <%@ taglib prefix="ex" uri="WEB-INF/custom.tld"%> @@ -1799,7 +1799,7 @@ This is custom tag 以下是指定相关的属性实例: -``` +```xml ..... attribute_name @@ -1812,7 +1812,7 @@ This is custom tag 如果你使用了两个属性,修改 TLD 文件,如下所示: -``` +```xml ..... attribute_name1 diff --git a/docs/javaee/servlet.md b/docs/javaee/javaee-servlet.md similarity index 50% rename from docs/javaee/servlet.md rename to docs/javaee/javaee-servlet.md index ba15d4ac..a2038c8e 100644 --- a/docs/javaee/servlet.md +++ b/docs/javaee/javaee-servlet.md @@ -6,17 +6,14 @@ - [Web 应用程序](#web-应用程序) - [常见 Web 服务器](#常见-web-服务器) - [Servlet 简介](#servlet-简介) - - [Servlet 是什么?](#servlet-是什么) - - [Servlet 架构](#servlet-架构) + - [什么是 Servlet?](#什么是-servlet) + - [Servlet 和 CGI 的区别?](#servlet-和-cgi-的区别) + - [Servlet 版本以及主要特性?](#servlet-版本以及主要特性) - [Servlet 任务](#servlet-任务) + - [Servlet 生命周期](#servlet-生命周期) +- [Servlet API](#servlet-api) - [Servlet 包](#servlet-包) -- [Servlet 生命周期](#servlet-生命周期) - - [init() 方法](#init-方法) - - [service() 方法](#service-方法) - - [doGet() 方法](#doget-方法) - - [doPost() 方法](#dopost-方法) - - [destroy() 方法](#destroy-方法) - - [架构图](#架构图) + - [Servlet 接口](#servlet-接口) - [Servlet 和 HTTP 状态码](#servlet-和-http-状态码) - [HTTP 状态码](#http-状态码) - [设置 HTTP 状态码的方法](#设置-http-状态码的方法) @@ -50,25 +47,36 @@ Internet 上供外界访问的 Web 资源分为: ## Servlet 简介 -### Servlet 是什么? +### 什么是 Servlet? -Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。 +Servlet(Server Applet),即小服务程序或服务连接器。Servlet 是 Java 编写的服务器端程序,具有独立于平台和协议的特性,主要功能在于交互式地浏览和生成数据,生成动态 Web 内容。 -使用 Servlet,您可以收集来自网页表单的用户输入,呈现来自数据库或者其他源的记录,还可以动态创建网页。 +- 狭义的 Servlet 是指 Java 实现的一个接口。 +- 广义的 Servlet 是指任何实现了这个 Servlet 接口的类。 -Java Servlet 通常情况下与使用 CGI(Common Gateway Interface,公共网关接口)实现的程序可以达到异曲同工的效果。但是相比于 CGI,Servlet 有以下几点优势: +Servlet 运行于支持 Java 的应用服务器中。从原理上讲,Servlet 可以响应任何类型的请求,但绝大多数情况下 Servlet 只用来扩展基于 HTTP 协议的 Web 服务器。 -- 性能明显更好。 -- Servlet 在 Web 服务器的地址空间内执行。这样它就没有必要再创建一个单独的进程来处理每个客户端请求。 -- Servlet 是独立于平台的,因为它们是用 Java 编写的。 -- 服务器上的 Java 安全管理器执行了一系列限制,以保护服务器计算机上的资源。因此,Servlet 是可信的。 -- Java 类库的全部功能对 Servlet 来说都是可用的。它可以通过 sockets 和 RMI 机制与 applets、数据库或其他软件进行交互。 +### Servlet 和 CGI 的区别? -### Servlet 架构 +Servlet 技术出现之前,Web 主要使用 CGI 技术。它们的区别如下: -下图显示了 Servlet 在 Web 应用程序中的位置。 +- Servlet 是基于 Java 编写的,处于服务器进程中,他能够通过多线程方式运行 service() 方法,一个实例可以服务于多个请求,而且一般不会销毁; +- CGI(Common Gateway Interface),即通用网关接口。它会为每个请求产生新的进程,服务完成后销毁,所以效率上低于 Servlet。 -
+### Servlet 版本以及主要特性? + +| 版本 | 日期 | JAVA EE/JDK 版本 | 特性 | +| ----------- | ------------- | ------------------ | --------------------------------------------------------------------- | +| Servlet 4.0 | 2017 年 10 月 | JavaEE 8 | HTTP2 | +| Servlet 3.1 | 2013 年 5 月 | JavaEE 7 | 非阻塞 I/O,HTTP 协议升级机制 | +| Servlet 3.0 | 2009 年 12 月 | JavaEE 6, JavaSE 6 | 可插拔性,易于开发,异步 Servlet,安全性,文件上传 | +| Servlet 2.5 | 2005 年 10 月 | JavaEE 5, JavaSE 5 | 依赖 JavaSE 5,支持注解 | +| Servlet 2.4 | 2003 年 11 月 | J2EE 1.4, J2SE 1.3 | web.xml 使用 XML Schema | +| Servlet 2.3 | 2001 年 8 月 | J2EE 1.3, J2SE 1.2 | Filter | +| Servlet 2.2 | 1999 年 8 月 | J2EE 1.2, J2SE 1.2 | 成为 J2EE 标准 | +| Servlet 2.1 | 1998 年 11 月 | 未指定 | First official specification, added RequestDispatcher, ServletContext | +| Servlet 2.0 | | JDK 1.1 | Part of Java Servlet Development Kit 2.0 | +| Servlet 1.0 | 1997 年 6 月 | | | ### Servlet 任务 @@ -80,28 +88,31 @@ Servlet 执行以下主要任务: - 发送显式的数据(即文档)到客户端(浏览器)。该文档的格式可以是多种多样的,包括文本文件(HTML 或 XML)、二进制文件(GIF 图像)、Excel 等。 - 发送隐式的 HTTP 响应到客户端(浏览器)。这包括告诉浏览器或其他客户端被返回的文档类型(例如 HTML),设置 cookies 和缓存参数,以及其他类似的任务。 -### Servlet 包 +### Servlet 生命周期 -Java Servlet 是运行在带有支持 Java Servlet 规范的解释器的 web 服务器上的 Java 类。 +
-Servlet 可以使用 **javax.servlet** 和 **javax.servlet.http** 包创建,它是 Java 企业版的标准组成部分,Java 企业版是支持大型开发项目的 Java 类库的扩展版本。 +Servlet 生命周期如下: -这些类实现 Java Servlet 和 JSP 规范。在写本教程的时候,二者相应的版本分别是 Java Servlet 2.5 和 JSP 2.1。 +1. **加载** - 第一个到达服务器的 HTTP 请求被委派到 Servlet 容器。容器通过类加载器使用 Servlet 类对应的文件加载 servlet; +2. **初始化** - Servlet 通过调用 **init ()** 方法进行初始化。 +3. **服务** - Servlet 调用 **service()** 方法来处理客户端的请求。 +4. **销毁** - Servlet 通过调用 **destroy()** 方法终止(结束)。 +5. **卸载** - Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。 -Java Servlet 就像任何其他的 Java 类一样已经被创建和编译。在您安装 Servlet 包并把它们添加到您的计算机上的 Classpath 类路径中之后,您就可以通过 JDK 的 Java 编译器或任何其他编译器来编译 Servlet。 +## Servlet API + +### Servlet 包 -## Servlet 生命周期 +Java Servlet 是运行在带有支持 Java Servlet 规范的解释器的 web 服务器上的 Java 类。 -Servlet 生命周期可被定义为从创建直到毁灭的整个过程。以下是 Servlet 遵循的过程: +Servlet 可以使用 **javax.servlet** 和 **javax.servlet.http** 包创建,它是 Java 企业版的标准组成部分,Java 企业版是支持大型开发项目的 Java 类库的扩展版本。 -- Servlet 通过调用 **init ()** 方法进行初始化。 -- Servlet 调用 **service()** 方法来处理客户端的请求。 -- Servlet 通过调用 **destroy()** 方法终止(结束)。 -- 最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。 +Java Servlet 就像任何其他的 Java 类一样已经被创建和编译。在您安装 Servlet 包并把它们添加到您的计算机上的 Classpath 类路径中之后,您就可以通过 JDK 的 Java 编译器或任何其他编译器来编译 Servlet。 -现在让我们详细讨论生命周期的方法。 +### Servlet 接口 -### init() 方法 +#### init() 方法 init 方法被设计成只调用一次。它在第一次创建 Servlet 时被调用,在后续每次用户请求时不再调用。因此,它是用于一次性初始化,就像 Applet 的 init 方法一样。 @@ -117,7 +128,7 @@ public void init() throws ServletException { } ``` -### service() 方法 +#### service() 方法 service() 方法是执行实际任务的主要方法。Servlet 容器(即 Web 服务器)调用 service() 方法来处理来自客户端(浏览器)的请求,并把格式化的响应写回给客户端。 @@ -136,7 +147,7 @@ service() 方法由容器调用,service 方法在适当的时候调用 doGet doGet() 和 doPost() 方法是每次服务请求中最常用的方法。下面是这两种方法的特征。 -### doGet() 方法 +#### doGet() 方法 GET 请求来自于一个 URL 的正常请求,或者来自于一个未指定 METHOD 的 HTML 表单,它由 doGet() 方法处理。 @@ -148,7 +159,7 @@ public void doGet(HttpServletRequest request, } ``` -### doPost() 方法 +#### doPost() 方法 POST 请求来自于一个特别指定了 METHOD 为 POST 的 HTML 表单,它由 doPost() 方法处理。 @@ -160,7 +171,7 @@ public void doPost(HttpServletRequest request, } ``` -### destroy() 方法 +#### destroy() 方法 destroy() 方法只会被调用一次,在 Servlet 生命周期结束时被调用。destroy() 方法可以让您的 Servlet 关闭数据库连接、停止后台线程、把 Cookie 列表或点击计数器写入到磁盘,并执行其他类似的清理活动。 @@ -172,16 +183,6 @@ destroy() 方法只会被调用一次,在 Servlet 生命周期结束时被调 } ``` -### 架构图 - -下图显示了一个典型的 Servlet 生命周期方案。 - -- 第一个到达服务器的 HTTP 请求被委派到 Servlet 容器。 -- Servlet 容器在调用 service() 方法之前加载 Servlet。 -- 然后 Servlet 容器处理由多个线程产生的多个请求,每个线程执行一个单一的 Servlet 实例的 service() 方法。 - -
- ## Servlet 和 HTTP 状态码 title: JavaEE Servlet HTTP 状态码 @@ -225,48 +226,23 @@ HeaderN: ... 以下是可能从 Web 服务器返回的 HTTP 状态码和相关的信息列表: -| 代码 | 消息 | 描述 | -| ---- | ----------------------------- | ------------------------------------------------------------------------------------------------------ | -| 100 | Continue | 只有请求的一部分已经被服务器接收,但只要它没有被拒绝,客户端应继续该请求。 | -| 101 | Switching Protocols | 服务器切换协议。 | -| 200 | OK | 请求成功。 | -| 201 | Created | 该请求是完整的,并创建一个新的资源。 | -| 202 | Accepted | 该请求被接受处理,但是该处理是不完整的。 | -| 203 | Non-authoritative Information | | -| 204 | No Content | | -| 205 | Reset Content | | -| 206 | Partial Content | | -| 300 | Multiple Choices | 链接列表。用户可以选择一个链接,进入到该位置。最多五个地址。 | -| 301 | Moved Permanently | 所请求的页面已经转移到一个新的 URL。 | -| 302 | Found | 所请求的页面已经临时转移到一个新的 URL。 | -| 303 | See Other | 所请求的页面可以在另一个不同的 URL 下被找到。 | -| 304 | Not Modified | | -| 305 | Use Proxy | | -| 306 | _Unused_ | 在以前的版本中使用该代码。现在已不再使用它,但代码仍被保留。 | -| 307 | Temporary Redirect | 所请求的页面已经临时转移到一个新的 URL。 | -| 400 | Bad Request | 服务器不理解请求。 | -| 401 | Unauthorized | 所请求的页面需要用户名和密码。 | -| 402 | Payment Required | _您还不能使用该代码。_ | -| 403 | Forbidden | 禁止访问所请求的页面。 | -| 404 | Not Found | 服务器无法找到所请求的页面。. | -| 405 | Method Not Allowed | 在请求中指定的方法是不允许的。 | -| 406 | Not Acceptable | 服务器只生成一个不被客户端接受的响应。 | -| 407 | Proxy Authentication Required | 在请求送达之前,您必须使用代理服务器的验证。 | -| 408 | Request Timeout | 请求需要的时间比服务器能够等待的时间长,超时。 | -| 409 | Conflict | 请求因为冲突无法完成。 | -| 410 | Gone | 所请求的页面不再可用。 | -| 411 | Length Required | "Content-Length" 未定义。服务器无法处理客户端发送的不带 Content-Length 的请求信息。 | -| 412 | Precondition Failed | 请求中给出的先决条件被服务器评估为 false。 | -| 413 | Request Entity Too Large | 服务器不接受该请求,因为请求实体过大。 | -| 414 | Request-url Too Long | 服务器不接受该请求,因为 URL 太长。当您转换一个 "post" 请求为一个带有长的查询信息的 "get" 请求时发生。 | -| 415 | Unsupported Media Type | 服务器不接受该请求,因为媒体类型不被支持。 | -| 417 | Expectation Failed | | -| 500 | Internal Server Error | 未完成的请求。服务器遇到了一个意外的情况。 | -| 501 | Not Implemented | 未完成的请求。服务器不支持所需的功能。 | -| 502 | Bad Gateway | 未完成的请求。服务器从上游服务器收到无效响应。 | -| 503 | Service Unavailable | 未完成的请求。服务器暂时超载或死机。 | -| 504 | Gateway Timeout | 网关超时。 | -| 505 | HTTP Version Not Supported | 服务器不支持"HTTP 协议"版本。 | +- `1**`:信息性状态码 +- `2**`:成功状态码 + - 200:请求正常成功 + - 204:指示请求成功但没有返回新信息 + - 206:指示服务器已完成对资源的部分 GET 请求 +- `3**`:重定向状态码 + - 301:永久性重定向 + - 302:临时性重定向 + - 304:服务器端允许请求访问资源,但未满足条件 +- `4**`:客户端错误状态码 + - 400:请求报文中存在语法错误 + - 401:发送的请求需要有通过 HTTP 认证的认证信息 + - 403:对请求资源的访问被服务器拒绝了 + - 404:服务器上无法找到请求的资源 +- `5**`:服务器错误状态码 + - 500:服务器端在执行请求时发生了错误 + - 503:服务器暂时处于超负载或正在进行停机维护,现在无法处理请求 ### 设置 HTTP 状态码的方法 diff --git a/docs/navbar.md b/docs/navbar.md index ce3daf86..1ddf2528 100644 --- a/docs/navbar.md +++ b/docs/navbar.md @@ -1,15 +1,17 @@ -- [系统架构](docs/architecture/README.md) - - [大型网站架构概述](docs/architecture/大型网站架构概述.md) - - [网站的高性能架构](docs/architecture/网站的高性能架构.md) - - [网站的高可用架构](docs/architecture/网站的高可用架构.md) - - [网站的伸缩性架构](docs/architecture/网站的伸缩性架构.md) - - [网站的可扩展架构](docs/architecture/网站的可扩展架构.md) - - [网站的安全架构](docs/architecture/网站的安全架构.md) - - [网站典型故障](docs/architecture/网站典型故障.md) +- [系统架构](architecture/README.md) + - [大型网站架构概述](architecture/大型网站架构概述.md) + - [网站的高性能架构](architecture/网站的高性能架构.md) + - [网站的高可用架构](architecture/网站的高可用架构.md) + - [网站的伸缩性架构](architecture/网站的伸缩性架构.md) + - [网站的可扩展架构](architecture/网站的可扩展架构.md) + - [网站的安全架构](architecture/网站的安全架构.md) + - [网站典型故障](architecture/网站典型故障.md) - [JavaEE](javaee/README.md) - - [JavaEE 之 Servlet 指南](javaee/servlet.md) - - [JavaEE 之 Jsp 指南](javaee/jsp.md) - - [JavaEE 之 Filter 和 Listener](javaee/filter-and-listener.md) + - [JavaEE 面经](javaee/javaee-interview.md) + - [JavaEE 之 Servlet 指南](javaee/javaee-servlet.md) + - [JavaEE 之 Jsp 指南](javaee/javaee-jsp.md) + - [JavaEE 之 Filter 和 Listener](javaee/javaee-filter-listener.md) + - [JavaEE 之 Cookie 和 Session](javaee/javaee-cookie-sesion.md) - [Server](server/README.md) - [Tomcat 应用指南](server/tomcat.md) - [Jetty 应用指南](server/jetty.md) From f596102f070a0c212517f984c09e5c8a67b9d10c Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Wed, 29 May 2019 18:35:53 +0800 Subject: [PATCH 22/75] update docs --- README.md | 23 ++- docs/README.md | 23 ++- docs/navbar.md | 11 +- docs/network/README.md | 15 ++ docs/network/network-application.md | 160 +++++++++++++++++ docs/network/network-data-link.md | 152 ++++++++++++++++ docs/network/network-guide.md | 126 +++++++++++++ docs/network/network-network.md | 264 ++++++++++++++++++++++++++++ docs/network/network-physical.md | 94 ++++++++++ docs/network/network-transport.md | 168 ++++++++++++++++++ docs/network/protocol/dns.md | 133 ++++++++++++++ docs/network/protocol/http.md | 212 ++++++++++++++++++++++ docs/network/protocol/icmp.md | 242 +++++++++++++++++++++++++ 13 files changed, 1614 insertions(+), 9 deletions(-) create mode 100644 docs/network/README.md create mode 100644 docs/network/network-application.md create mode 100644 docs/network/network-data-link.md create mode 100644 docs/network/network-guide.md create mode 100644 docs/network/network-network.md create mode 100644 docs/network/network-physical.md create mode 100644 docs/network/network-transport.md create mode 100644 docs/network/protocol/dns.md create mode 100644 docs/network/protocol/http.md create mode 100644 docs/network/protocol/icmp.md diff --git a/README.md b/README.md index aa9894af..cccdf342 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # JavaWeb > Java Web 开发之路经验总结 -> +> > 电子书阅读:[Github Pages](https://dunwu.github.io/javaweb/) | [Gitee Pages](https://turnon.gitee.io/javaweb/) -| :beginner: | :recycle: | :coffee: | :eye: | :eyes: | :computer: | -| ------------- | --------------------- | ----------------- | --------------------- | ------------------------- | ------------- | -| [准备](#准备) | [架构设计](#架构设计) | [JavaEE](#javaee) | [主流技术](#主流技术) | [分布式技术](#分布式技术) | [服务器](#服务器) | +| :beginner: | :recycle: | :coffee: | :eye: | :eyes: | :globe_with_meridians: | :computer: | +| ------------- | --------------------- | ----------------- | --------------------- | ------------------------- | ------------------------- | ----------------- | +| [准备](#准备) | [架构设计](#架构设计) | [JavaEE](#javaee) | [主流技术](#主流技术) | [分布式技术](#分布式技术) | [计算机网络](#计算机网络) | [服务器](#服务器) | @@ -66,6 +66,7 @@ - [ZooKeeper 实战篇](docs/distributed/rpc/zookeeper-basics.md) - [ZooKeeper 原理篇](docs/distributed/rpc/zookeeper-advanced.md) - [分布式消息队列(MQ)](docs/distributed/mq) + - 原理 - [分布式消息队列](docs/distributed/mq/分布式消息队列.md) - Kafka @@ -77,6 +78,20 @@ - [ActiveMQ 实战篇](docs/distributed/mq/ActiveMQ.md) - RabbitMQ - 待补充。。。 +## 计算机网络 + +- **网络分层** + - [计算机网络概述](docs/network/network-guide.md) + - [计算机网络之物理层](docs/network/network-physical.md) + - [计算机网络之链路层](docs/network/network-data-link.md) + - [计算机网络之网络层](docs/network/network-network.md) + - [计算机网络之传输层](docs/network/network-transport.md) + - [计算机网络之应用层](docs/network/network-application.md) +- **重要协议** + - [网络协议之 HTTP](docs/network/protocol/http.md) + - [网络协议之 DNS](docs/network/protocol/dns.md) + - [网络协议之 ICMP](docs/network/protocol/icmp.md) + ## 服务器 > [服务器](docs/server/) 章节总结 Java Web 领域主流服务器技术。 diff --git a/docs/README.md b/docs/README.md index ac8b0ab2..ff3ae428 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,12 +1,12 @@ # JavaWeb > Java Web 开发之路经验总结 -> +> > 电子书阅读:[Github Pages](https://dunwu.github.io/javaweb/) | [Gitee Pages](https://turnon.gitee.io/javaweb/) -| :beginner: | :recycle: | :coffee: | :eye: | :eyes: | :computer: | -| ------------- | --------------------- | ----------------- | --------------------- | ------------------------- | ------------- | -| [准备](#准备) | [架构设计](#架构设计) | [JavaEE](#javaee) | [主流技术](#主流技术) | [分布式技术](#分布式技术) | [服务器](#服务器) | +| :beginner: | :recycle: | :coffee: | :eye: | :eyes: | :globe_with_meridians: | :computer: | +| ------------- | --------------------- | ----------------- | --------------------- | ------------------------- | ------------------------- | ----------------- | +| [准备](#准备) | [架构设计](#架构设计) | [JavaEE](#javaee) | [主流技术](#主流技术) | [分布式技术](#分布式技术) | [计算机网络](#计算机网络) | [服务器](#服务器) | @@ -66,6 +66,7 @@ - [ZooKeeper 实战篇](distributed/rpc/zookeeper-basics.md) - [ZooKeeper 原理篇](distributed/rpc/zookeeper-advanced.md) - [分布式消息队列(MQ)](distributed/mq) + - 原理 - [分布式消息队列](distributed/mq/分布式消息队列.md) - Kafka @@ -77,6 +78,20 @@ - [ActiveMQ 实战篇](distributed/mq/ActiveMQ.md) - RabbitMQ - 待补充。。。 +## 计算机网络 + +- **网络分层** + - [计算机网络概述](network/network-guide.md) + - [计算机网络之物理层](network/network-physical.md) + - [计算机网络之链路层](network/network-data-link.md) + - [计算机网络之网络层](network/network-network.md) + - [计算机网络之传输层](network/network-transport.md) + - [计算机网络之应用层](network/network-application.md) +- **重要协议** + - [网络协议之 HTTP](network/protocol/http.md) + - [网络协议之 DNS](network/protocol/dns.md) + - [网络协议之 ICMP](network/protocol/icmp.md) + ## 服务器 > [服务器](server/) 章节总结 Java Web 领域主流服务器技术。 diff --git a/docs/navbar.md b/docs/navbar.md index 1ddf2528..8615fe00 100644 --- a/docs/navbar.md +++ b/docs/navbar.md @@ -12,7 +12,16 @@ - [JavaEE 之 Jsp 指南](javaee/javaee-jsp.md) - [JavaEE 之 Filter 和 Listener](javaee/javaee-filter-listener.md) - [JavaEE 之 Cookie 和 Session](javaee/javaee-cookie-sesion.md) +- [计算机网络](network/README.md) + - [计算机网络概述](network/network-guide.md) + - [计算机网络之物理层](network/network-physical.md) + - [计算机网络之链路层](network/network-data-link.md) + - [计算机网络之网络层](network/network-network.md) + - [计算机网络之传输层](network/network-transport.md) + - [计算机网络之应用层](network/network-application.md) + - [网络协议之 HTTP](network/protocol/http.md) + - [网络协议之 DNS](network/protocol/dns.md) + - [网络协议之 ICMP](network/protocol/icmp.md) - [Server](server/README.md) - [Tomcat 应用指南](server/tomcat.md) - [Jetty 应用指南](server/jetty.md) - diff --git a/docs/network/README.md b/docs/network/README.md new file mode 100644 index 00000000..56b2fb6b --- /dev/null +++ b/docs/network/README.md @@ -0,0 +1,15 @@ +# 计算机网络 + +## 知识点 + +- **网络分层** + - [计算机网络概述](network-guide.md) + - [计算机网络之物理层](network-physical.md) + - [计算机网络之链路层](network-data-link.md) + - [计算机网络之网络层](network-network.md) + - [计算机网络之传输层](network-transport.md) + - [计算机网络之应用层](network-application.md) +- **重要协议** + - [网络协议之 HTTP](protocol/http.md) + - [网络协议之 DNS](protocol/dns.md) + - [网络协议之 ICMP](protocol/icmp.md) diff --git a/docs/network/network-application.md b/docs/network/network-application.md new file mode 100644 index 00000000..5d0b1a2e --- /dev/null +++ b/docs/network/network-application.md @@ -0,0 +1,160 @@ +# 计算机网络之应用层 + + + +- [HTTP](#http) +- [DNS](#dns) +- [FTP](#ftp) +- [DHCP](#dhcp) +- [TELNET](#telnet) +- [电子邮件协议](#电子邮件协议) + - [1. SMTP](#1-smtp) + - [2. POP3](#2-pop3) + - [3. IMAP](#3-imap) +- [常用端口](#常用端口) +- [Web 页面请求过程](#web-页面请求过程) + - [1. DHCP 配置主机信息](#1-dhcp-配置主机信息) + - [2. ARP 解析 MAC 地址](#2-arp-解析-mac-地址) + - [3. DNS 解析域名](#3-dns-解析域名) + - [4. HTTP 请求页面](#4-http-请求页面) + + + +## HTTP + +
+ +超文本传输协议(英语:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP 是万维网的数据通信的基础。 + +设计 HTTP 最初的目的是为了提供一种发布和接收 HTML 页面的方法。通过 HTTP 或者 HTTPS 协议请求的资源由统一资源标识符(Uniform Resource Identifiers,URI)来标识。 + +安全套接字层超文本传输协议 HTTPS 为了数据传输的安全,HTTPS 在 HTTP 的基础上加入了 SSL 协议,SSL 依靠证书来验证服务器的身份,并为浏览器和服务器之间的通信加密。 + +> :point_right: 扩展阅读:[HTTP](HTTP.md) + +## DNS + +域名服务器 DNS 是一个分布式数据库,提供了主机名和 IP 地址之间相互转换的服务。这里的分布式数据库是指,每个站点只保留它自己的那部分数据。 + +
+ +域名具有层次结构,从上到下依次为:根域名、顶级域名、二级域名。 + +
+ +DNS 可以使用 UDP 或者 TCP 进行传输,使用的端口号都为 53。大多数情况下 DNS 使用 UDP 进行传输,这就要求域名解析器和域名服务器都必须自己处理超时和重传来保证可靠性。在两种情况下会使用 TCP 进行传输: + +- 如果返回的响应超过的 512 字节(UDP 最大只支持 512 字节的数据)。 +- 区域传送(区域传送是主域名服务器向辅助域名服务器传送变化的那部分数据)。 + +> :point_right: 扩展阅读:[DNS](DNS.md) + +## FTP + +文件传送协议 FTP 使用 TCP 进行连接,它需要两个连接来传送一个文件: + +- 控制连接:服务器打开端口号 21 等待客户端的连接,客户端主动建立连接后,使用这个连接将客户端的命令传送给服务器,并传回服务器的应答。 +- 数据连接:用来传送一个文件数据。 + +根据数据连接是否是服务器端主动建立,FTP 有主动和被动两种模式: + +
+ +- 主动模式:服务器端主动建立数据连接,其中服务器端的端口号为 20,客户端的端口号随机,但是必须大于 1024,因为 0\~1023 是熟知端口号。 +- 被动模式:客户端主动建立数据连接,其中客户端的端口号由客户端自己指定,服务器端的端口号随机。 + +主动模式要求客户端开放端口号给服务器端,需要去配置客户端的防火墙。被动模式只需要服务器端开放端口号即可,无需客户端配置防火墙。但是被动模式会导致服务器端的安全性减弱,因为开放了过多的端口号。 + +FTPS 是一种对常用的文件传输协议(FTP)添加传输层安全(TLS)和安全套接层(SSL)加密协议支持的扩展协议。 + +## DHCP + +动态主机配置协议 DHCP (Dynamic Host Configuration Protocol) 提供了即插即用的连网方式,用户不再需要去手动配置 IP 地址等信息。 + +DHCP 配置的内容不仅是 IP 地址,还包括子网掩码、网关 IP 地址。 + +DHCP 工作过程如下: + +1. 客户端发送 Discover 报文,该报文的目的地址为 255.255.255.255:67,源地址为 0.0.0.0:68,被放入 UDP 中,该报文被广播到同一个子网的所有主机上。如果客户端和 DHCP 服务器不在同一个子网,就需要使用中继代理。 +2. DHCP 服务器收到 Discover 报文之后,发送 Offer 报文给客户端,该报文包含了客户端所需要的信息。因为客户端可能收到多个 DHCP 服务器提供的信息,因此客户端需要进行选择。 +3. 如果客户端选择了某个 DHCP 服务器提供的信息,那么就发送 Request 报文给该 DHCP 服务器。 +4. DHCP 服务器发送 Ack 报文,表示客户端此时可以使用提供给它的信息。 + +
+ +## TELNET + +远程登录协议 TELNET 用于登录到远程主机上,并且远程主机上的输出也会返回。 + +TELNET 可以适应许多计算机和操作系统的差异,例如不同操作系统系统的换行符定义。 + +## 电子邮件协议 + +一个电子邮件系统由三部分组成:用户代理、邮件服务器以及邮件协议。 + +邮件协议包含发送协议和读取协议,发送协议常用 SMTP,读取协议常用 POP3 和 IMAP。 + +### 1. SMTP + +SMTP 只能发送 ASCII 码,而互联网邮件扩充 MIME 可以发送二进制文件。MIME 并没有改动或者取代 SMTP,而是增加邮件主体的结构,定义了非 ASCII 码的编码规则。 + +### 2. POP3 + +POP3 的特点是只要用户从服务器上读取了邮件,就把该邮件删除。 + +### 3. IMAP + +IMAP 协议中客户端和服务器上的邮件保持同步,如果不手动删除邮件,那么服务器上的邮件也不会被删除。IMAP 这种做法可以让用户随时随地去访问服务器上的邮件。 + +## 常用端口 + +| 应用 | 应用层协议 | 端口号 | 传输层协议 | 备注 | +| :--------------: | :--------: | :-----: | :--------: | :-------------------------: | +| 域名解析 | DNS | 53 | UDP/TCP | 长度超过 512 字节时使用 TCP | +| 动态主机配置协议 | DHCP | 67/68 | UDP | | +| 简单网络管理协议 | SNMP | 161/162 | UDP | | +| 文件传送协议 | FTP | 20/21 | TCP | 控制连接 21,数据连接 20 | +| 远程终端协议 | TELNET | 23 | TCP | | +| 超文本传送协议 | HTTP | 80 | TCP | | +| 简单邮件传送协议 | SMTP | 25 | TCP | | +| 邮件读取协议 | POP3 | 110 | TCP | | +| 网际报文存取协议 | IMAP | 143 | TCP | | + +## Web 页面请求过程 + +### 1. DHCP 配置主机信息 + +- 假设主机最开始没有 IP 地址以及其它信息,那么就需要先使用 DHCP 来获取。 +- 主机生成一个 DHCP 请求报文,并将这个报文放入具有目的端口 67 和源端口 68 的 UDP 报文段中。 +- 该报文段则被放入在一个具有广播 IP 目的地址(255.255.255.255) 和源 IP 地址(0.0.0.0)的 IP 数据报中。 +- 该数据报则被放置在 MAC 帧中,该帧具有目的地址 FF:FF:FF:FF:FF:FF,将广播到与交换机连接的所有设备。 +- 连接在交换机的 DHCP 服务器收到广播帧之后,不断地向上分解得到 IP 数据报、UDP 报文段、DHCP 请求报文,之后生成 DHCP ACK 报文,该报文包含以下信息:IP 地址、DNS 服务器的 IP 地址、默认网关路由器的 IP 地址和子网掩码。该报文被放入 UDP 报文段中,UDP 报文段有被放入 IP 数据报中,最后放入 MAC 帧中。 +- 该帧的目的地址是请求主机的 MAC 地址,因为交换机具有自学习能力,之前主机发送了广播帧之后就记录了 MAC 地址到其转发接口的交换表项,因此现在交换机就可以直接知道应该向哪个接口发送该帧。 +- 主机收到该帧后,不断分解得到 DHCP 报文。之后就配置它的 IP 地址、子网掩码和 DNS 服务器的 IP 地址,并在其 IP 转发表中安装默认网关。 + +### 2. ARP 解析 MAC 地址 + +- 主机通过浏览器生成一个 TCP 套接字,套接字向 HTTP 服务器发送 HTTP 请求。为了生成该套接字,主机需要知道网站的域名对应的 IP 地址。 +- 主机生成一个 DNS 查询报文,该报文具有 53 号端口,因为 DNS 服务器的端口号是 53。 +- 该 DNS 查询报文被放入目的地址为 DNS 服务器 IP 地址的 IP 数据报中。 +- 该 IP 数据报被放入一个以太网帧中,该帧将发送到网关路由器。 +- DHCP 过程只知道网关路由器的 IP 地址,为了获取网关路由器的 MAC 地址,需要使用 ARP 协议。 +- 主机生成一个包含目的地址为网关路由器 IP 地址的 ARP 查询报文,将该 ARP 查询报文放入一个具有广播目的地址(FF:FF:FF:FF:FF:FF)的以太网帧中,并向交换机发送该以太网帧,交换机将该帧转发给所有的连接设备,包括网关路由器。 +- 网关路由器接收到该帧后,不断向上分解得到 ARP 报文,发现其中的 IP 地址与其接口的 IP 地址匹配,因此就发送一个 ARP 回答报文,包含了它的 MAC 地址,发回给主机。 + +### 3. DNS 解析域名 + +- 知道了网关路由器的 MAC 地址之后,就可以继续 DNS 的解析过程了。 +- 网关路由器接收到包含 DNS 查询报文的以太网帧后,抽取出 IP 数据报,并根据转发表决定该 IP 数据报应该转发的路由器。 +- 因为路由器具有内部网关协议(RIP、OSPF)和外部网关协议(BGP)这两种路由选择协议,因此路由表中已经配置了网关路由器到达 DNS 服务器的路由表项。 +- 到达 DNS 服务器之后,DNS 服务器抽取出 DNS 查询报文,并在 DNS 数据库中查找待解析的域名。 +- 找到 DNS 记录之后,发送 DNS 回答报文,将该回答报文放入 UDP 报文段中,然后放入 IP 数据报中,通过路由器反向转发回网关路由器,并经过以太网交换机到达主机。 + +### 4. HTTP 请求页面 + +- 有了 HTTP 服务器的 IP 地址之后,主机就能够生成 TCP 套接字,该套接字将用于向 Web 服务器发送 HTTP GET 报文。 +- 在生成 TCP 套接字之前,必须先与 HTTP 服务器进行三次握手来建立连接。生成一个具有目的端口 80 的 TCP SYN 报文段,并向 HTTP 服务器发送该报文段。 +- HTTP 服务器收到该报文段之后,生成 TCP SYN ACK 报文段,发回给主机。 +- 连接建立之后,浏览器生成 HTTP GET 报文,并交付给 HTTP 服务器。 +- HTTP 服务器从 TCP 套接字读取 HTTP GET 报文,生成一个 HTTP 响应报文,将 Web 页面内容放入报文主体中,发回给主机。 +- 浏览器收到 HTTP 响应报文后,抽取出 Web 页面内容,之后进行渲染,显示 Web 页面。 diff --git a/docs/network/network-data-link.md b/docs/network/network-data-link.md new file mode 100644 index 00000000..d8e95f74 --- /dev/null +++ b/docs/network/network-data-link.md @@ -0,0 +1,152 @@ +# 计算机网络之数据链路层 + +> **数据链路层(Data Link Layer)** - 网络层针对的还是主机之间的数据传输服务,而主机之间可以有很多链路,链路层协议就是为同一链路的主机提供数据传输服务。数据链路层把网络层传下来的分组封装成帧。 +> +> - 主要协议:`PPP`、`CSMA/CD` 等。 +> - 数据单元:帧(frame)。 +> - 典型设备:二层交换机、网桥、网卡。 + + + +- [简介](#简介) +- [基本问题](#基本问题) + - [封装成帧](#封装成帧) + - [透明传输](#透明传输) + - [差错检测](#差错检测) +- [点对点信道](#点对点信道) + - [PPP 协议](#ppp-协议) +- [广播信道](#广播信道) + - [CSMA/CD 协议](#csmacd-协议) +- [局域网](#局域网) +- [以太网](#以太网) +- [MAC 地址](#mac-地址) +- [设备](#设备) + - [适配器](#适配器) + - [集线器](#集线器) + - [网桥](#网桥) + - [以太网交换机](#以太网交换机) + + + +## 简介 + +- 链路是从一个节点到相邻节点的一段物理线路,数据链路则是在链路的基础上增加了一些必要的硬件(网络适配器)和软件(协议)。 +- 数据链路层三个基本问题:封装成帧、透明传输、差错检测。 +- 数据链路层有两种信道类型:点对点信道(主要使用 `PPP`)和广播信道(主要使用 `CSMA/CD`)。 +- 以太网 MAC 层的地址。 +- 适配器、转发器、集线器、网桥、以太网交换机的作用及使用场合。 + +## 基本问题 + +### 封装成帧 + +为网络层传下来的 IP 数据报添加首部和尾部,用于标记帧的开始和结束。 + +为了提高传输效率,应该让数据部分长度尽可能大于首部和尾部。但是,每种链路层协议都限制了帧的数据部分长度上线——最大传送单元 MTU(Maximum Transfer Unit) + +
+ +### 透明传输 + +**透明**表示:某一个实际存在的事物看起来好像不存在一样。 + +帧使用首部和尾部进行定界,如果帧的数据部分含有和首部尾部相同的内容,那么帧的开始和结束位置就会被错误的判定。需要在数据部分出现首部尾部相同的内容前面插入转义字符。如果数据部分出现转义字符,那么就在转义字符前面再加个转义字符。在接收端进行处理之后可以还原出原始数据。这个过程透明传输的内容是转义字符,用户察觉不到转义字符的存在。 + +
+ +### 差错检测 + +目前数据链路层广泛使用了[**循环冗余检验 CRC(Cyclic redundancy check)**](https://zh.wikipedia.org/wiki/循环冗余检验)来检查比特差错。 + +## 点对点信道 + +点对点信道使用一对一的点对点通信方式。 + +对于点对点的链路,点对点协议 PPP(Point-to-Point Protocol)是使用最广泛的数据链路层协议。 + +### PPP 协议 + +互联网用户通常都要连接到某个 ISP 之后才能接入到互联网,PPP 协议是用户计算机和 ISP 进行通信时所使用的数据链路层协议。 + +
+ +PPP(点到点协议)是为在同等单元之间传输数据包这样的简单链路设计的链路层协议。这种链路提供全双工操作,并按照顺序传递数据包。设计目的主要是用来通过拨号或专线方式建立点对点连接发送数据,使其成为各种主机、网桥和路由器之间简单连接的一种共通的解决方案。 + +
+ +PPP 的帧格式: + +- F 字段为帧的定界符 +- A 和 C 字段暂时没有意义 +- FCS 字段是使用 CRC 的检验序列 +- 信息部分的长度不超过 1500 + +## 广播信道 + +广播信道(broadcast channel)是通过广播的方式传输信息的信息通道。 + +所有的节点都在同一个广播信道上发送数据,因此需要有专门的控制方法进行协调,避免发生冲突(冲突也叫碰撞)。 + +主要有两种控制方法进行协调,一个是使用信道复用技术,一是使用 CSMA/CD 协议。 + +### CSMA/CD 协议 + +CSMA/CD(Carrier Sense Multiple Access with Collision Detection)即带冲突检测的载波监听多路访问技术(载波监听多点接入/碰撞检测)。 + +- **多点接入** :说明这是总线型网络,许多计算机以多点接入的方式连接在一根总线上。 +- **载波监听** :每个主机都必须不停地监听信道。发送前监听,如果忙则等待,如果空闲则发送。 +- **碰撞检测** :即边发送边检测。若检测到信道有干扰信号,则表示产生了碰撞,于是就要停止发送数据,计算出退避等待时间,然后使用 CSMA 方法继续尝试发送。计算退避等待时间采用的是[二进制指数退避算法](https://baike.baidu.com/item/二进制指数退避算法)。 + +## 局域网 + +局域网 LAN(Local Area Network)是指在某一区域内由多台计算机互联成的计算机组。 + +局域网的拓扑结构通常为总线型和环型。 + +局域网技术主要有:以太网、令牌环网、FDDI 网和无线局域网等。 + +## 以太网 + +以太网(Ethernet)是一种星型拓扑结构局域网。 + +以太网是目前应用最广泛的局域网。 + +以太网使用 CSMA/CD 协议。 + +## MAC 地址 + +MAC 地址(Media Access Control Address),也称为以太网地址或物理地址,它是一个用来确认网上设备位置的地址。 + +MAC 地址长度为 6 字节(48 位),用于唯一标识网络适配器(网卡)。 + +一台主机拥有多少个网络适配器就有多少个 MAC 地址。 + +
+ +## 设备 + +### 适配器 + +网络适配器一般指网卡。 + +网卡是工作在链路层的网络组件,是局域网中连接计算机和传输介质的接口,不仅能实现与局域网传输介质之间的物理连接和电信号匹配,还涉及帧的发送与接收、帧的封装与拆封、介质访问控制、数据的编码与解码以及数据缓存的功能等。 + +网卡和局域网之间的通信是通过电缆或双绞线以串行传输方式进行的。而网卡和计算机之间的通信则是通过计算机主板上的 I/O 总线以并行传输方式进行。 + +### 集线器 + +集线器(Hub)的主要功能是对接收到的信号进行再生整形放大,以扩大网络的传输距离,同时把所有节点集中在以它为中心的节点上。 + +使用集线器可以在物理层扩展以太网。 + +### 网桥 + +网桥(Bridge)是早期的两端口二层网络设备,用来连接不同网段。网桥的两个端口分别有一条独立的交换信道,不是共享一条背板总线,可隔离冲突域。网桥比集线器(Hub)性能更好,集线器上各端口都是共享同一条背板总线的。后来,网桥被具有更多端口、同时也可隔离冲突域的交换机(Switch)所取代。 + +### 以太网交换机 + +以太网交换机是基于以太网传输数据的交换机,以太网采用共享总线型传输媒体方式的局域网。以太网交换机的结构是每个端口都直接与主机相连,并且一般都工作在全双工方式。交换机能同时连通许多对端口,使每一对相互通信的主机都能像独占通信媒体那样,进行无冲突地传输数据。 + +- 以太网交换机的每个端口都直接与主机相连,并且一般都工作在全双工方式。 +- 交换机能同时连通许多对的端口,使每一对相互通信的主机都能像独占通信媒体那样,进行无冲突地传输数据。 +- 用户独占传输媒体的带宽,若一个接口到主机的带宽是 10Mbit 每秒,那么有 10 个接口的交换机的总容量是 100Mbit 每秒。这是交换机的最大优点。 diff --git a/docs/network/network-guide.md b/docs/network/network-guide.md new file mode 100644 index 00000000..e6feb74e --- /dev/null +++ b/docs/network/network-guide.md @@ -0,0 +1,126 @@ +--- +title: 计算机网络概述 +categories: ['communication'] +tags: ['communication', 'network'] +date: 2019-02-20 22:26 +--- + +# 计算机网络概述 + +> 计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。 + +
+ +## :bulb: 指南 + +学习之前,先看一下入门三问: + +> **一、什么是计算机网络?** +> +> 计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。 +> +> ——摘自百度百科 + +> **二、为什么学习计算机网络?** +> +> 计算机网络是计算机科学的基础课程,也是计算机专业考研必考科目,可见其重要性。作为一名程序员,了解计算机网络,对于 Web 领域,通信领域的开发有莫大的帮助。 +> +> 在浏览器中访问网页的原理是什么?Wifi 是如何工作的?防火墙是如何保障网络安全的?什么是安全证书?Cookie 和 Session 是什么东西?。。。 +> +> 如果你接触过这些技术,如果你想了解这些技术的原理,那么你就有必要学习一下计算机网络了。 + +> **三、如何学习计算机网络?** +> +> 本人有 2 年通信领域开发经验,从事通信设备上的协议开发。就我个人的学习经验来看,学习计算机网络可以分为以下阶段: +> +> - **基础阶段——一般性的了解网络协议分层及各层功能** +> - 了解计算机网络协议分层(OSI)有哪些层,分层的依据是什么(即每层的功能是什么) +> - 了解每层的主要通信设备有哪些; +> - 了解每层有哪些重要网络协议,这些协议有什么作用,基本原理是什么? +> - 了解每层的传输数据形式(如:报文、帧等) +> - **进阶阶段——系统学习计算机网络知识,将各层主要协议功能串联起来** +> - 学习 TCP/IP 详解 卷 1、卷 2、卷 3(内容详实,但文字较为晦涩,不适合初学者) +> - **专业阶段——根据业务领域,有针对性的学习** +> - 网络协议很多,而且专业性非常强。精通所有协议,几乎是不可能的,所以有必要根据自己的业务领域,有针对性的深入学习协议。如果你是做 web 开发,那么你很有必要认真学习一下 HTTP、DNS 协议;如果你是做路由器、交换机领域通信开发,那么你应该更深入学习一下 IP/TCP/UDP 协议。。。 +> - 如何深入学习协议,最好的学习方式,就是深入学习 RFC,并结合实际的协议报文去了解。 + +### 核心概念 + +- **计算机网络** - 计算机网络(computer network),通常也简称网络,是利用通信设备和线路将地理位置不同的、功能独立的多个[计算机](https://zh.wikipedia.org/wiki/%E9%9B%BB%E5%AD%90%E8%A8%88%E7%AE%97%E6%A9%9F)系统连接起来,以功能完善的网络[软件](https://zh.wikipedia.org/wiki/%E8%BD%AF%E4%BB%B6)实现网络的[硬件](https://zh.wikipedia.org/wiki/%E7%A1%AC%E4%BB%B6)、[软件](https://zh.wikipedia.org/wiki/%E8%BB%9F%E4%BB%B6)及资源[共享](https://zh.wikipedia.org/wiki/%E5%85%B1%E4%BA%AB)和[信息](https://zh.wikipedia.org/wiki/%E4%BF%A1%E6%81%AF)传递的系统。简单的说即连接两台或多台计算机进行[通信](https://zh.wikipedia.org/wiki/%E9%80%9A%E4%BF%A1)的系统。 +- **互联网** - 互联网(Internet),即网络的网络。 + +### 拓扑结构 + +计算机网络的拓扑结构可分为: + +
+ +- 网型拓扑网型网(Mesh network) +- 环型拓扑环型网(Ring network) +- 星型拓扑星型网(Star network) +- 树状拓扑树型网(Tree network) +- 总线拓扑总线网(Bus network) + +### 作用范围 + +- 广域网 WAN(Wide Area Network) +- 城域网 MAN(Metropolitan Area Network) +- 局域网 LAN(Local Area Network) +- 个人区域网 PAN(Personal Area Network) + +### 性能指标 + +- **速率** - 速率的单位是 bit/s(比特每秒)。 +- **带宽(bandwidth)** - 带宽有以下两种不同的意义。 + - 信号的带宽是指该信号所包含的各种不同频率成分所占据的频率范围。这种意义的带宽的单位是赫 (或千赫,兆赫,吉赫等)。 + - 网络的带宽表示在单位时间内从网络中的某一点到另一点所能通过的**最高数据率**。这种意义的带宽的单位是 bit/s(比特每秒)。 +- **吞吐量(throughput)** - 吞吐量表示在单位时间内通过某个网络(或信道、接口)的数据量。例如,对于一个 100 Mbit/s 的以太网,其额定速率是 100 Mbit/s。 +- **时延(delay)** + - 总时延 = 排队时延 + 处理时延 + 传输时延 + 传播时延 + +### 体系结构 + +
+ +- [物理层](physical) - 关键词:调制、解调、数字信号、模拟信号、通信媒介、信道复用 +- [数据链路层](data-link-layer.md) - 关键词:点对点信道、广播信道、`PPP`、`CSMA/CD`、局域网、以太网、`MAC`、适配器、集线器、网桥、交换机 +- [网络层](network) - 关键词:`IP`、`ICMP`、`ARP`、路由 +- [传输层](transport) - 关键词:`UDP`、`TCP`、滑动窗口、拥塞控制、三次握手 +- [应用层](application) - 关键词:`HTTP`、`DNS`、`FTP`、`TELNET`、`DHCP` + +- **[物理层(Physical Layer)](network-physical.md)** - 物理层只接收和发送一串比特(bit)流,不考虑信息的意义和信息结构。 + - 数据单元:比特流。 + - 典型设备:光纤、同轴电缆、双绞线、中继器和集线器。 +- **[数据链路层(Data Link Layer)](network-data-link.md)** - 网络层针对的还是主机之间的数据传输服务,而主机之间可以有很多链路,链路层协议就是为同一链路的主机提供数据传输服务。数据链路层把网络层传下来的分组封装成帧。 + - 主要协议:`PPP`、`CSMA/CD` 等。 + - 数据单元:帧(frame)。 + - 典型设备:二层交换机、网桥、网卡。 +- **[网络层(network layer)](network-network.md)** - 为分组交换网上的不同主机提供通信服务。在发送数据时,网络层把运输层产生的报文段或用户数据报封装成分组或包进行传送。 + - 主要协议:`IP`。 + - 数据单元:IP 数据报(packet)。 + - 典型设备:网关、路由器。 +- **[传输层(transport layer)](network-transport.md)** - 为两台主机中进程间的通信提供通用的数据传输服务。 + - 主要协议:`TCP`、`UDP`。 + - 数据单元:报文段(segment)或用户数据报。 +- **会话层(Session Layer)** - 会话层不参与具体的传输,它提供包括访问验证和会话管理在内的建立和维护应用之间通信的机制。 +- **表示层(Presentation Layer)** - 表示层是为在应用过程之间传送的信息提供表示方法的服务,它关心的只是发出信息的语法与语义。表示层要完成某些特定的功能,主要有不同数据编码格式的转换,提供数据压缩、解压缩服务,对数据进行加密、解密。 +- **[应用层(application layer)](network-application.md)** - 通过应用进程间的交互来完成特定网络应用。应用层协议定义的是应用进程间通信和交互的规则。 + - 主要协议:`HTTP`、`DNS`、`SMTP`、`Telnet`、`FTP`、`SNMP` 等。 + - 数据单元:报文(message)。 + +## :books: 学习资源 + +### 书 + +- 谢希仁, 计算机网络 - 国内很多大学将其作为计算机网络课程的指定教材,通俗易懂,适合作为入门教材。 +- W·Richard Stevens, [TCP/IP 详解 卷 1:协议](https://book.douban.com/subject/1088054/) - TCP/IP 详解三部曲,适合作为进阶教材 +- W·Richard Stevens, [TCP/IP 详解 卷 2:实现](https://book.douban.com/subject/1087767/) +- W·Richard Stevens, [TCP/IP 详解 卷 3:TCP 事务协议、HTTP、NNTP 和 UNIX 域协议](https://book.douban.com/subject/1088054/) + +### 站点 + +- https://www.rfc-editor.org/ - 在线查阅、下载 RFC 文档 + +## :door: 传送门 + +| [回首頁](https://github.com/dunwu/blog) | diff --git a/docs/network/network-network.md b/docs/network/network-network.md new file mode 100644 index 00000000..da571943 --- /dev/null +++ b/docs/network/network-network.md @@ -0,0 +1,264 @@ +# 计算机网络之网络层 + +> **网络层(network layer)** - 为分组交换网上的不同主机提供通信服务。在发送数据时,网络层把运输层产生的报文段或用户数据报封装成分组或包进行传送。 +> +> - 主要协议:`IP`、`ICMP`。 +> - 数据单元:IP 数据报(packet)。 +> - 典型设备:网关、路由器。 + + + +- [概述](#概述) +- [IP 协议](#ip-协议) + - [相关协议](#相关协议) + - [分类的 IP 地址](#分类的-ip-地址) + - [IP 地址与物理地址](#ip-地址与物理地址) + - [IP 数据报格式](#ip-数据报格式) +- [地址解析协议 ARP](#地址解析协议-arp) +- [网际控制报文协议 ICMP](#网际控制报文协议-icmp) + - [1. Ping](#1-ping) + - [2. Traceroute](#2-traceroute) +- [虚拟专用网 VPN](#虚拟专用网-vpn) +- [网络地址转换 NAT](#网络地址转换-nat) +- [路由器的结构](#路由器的结构) +- [路由器分组转发流程](#路由器分组转发流程) +- [路由选择协议](#路由选择协议) + - [1. 内部网关协议 RIP](#1-内部网关协议-rip) + - [2. 内部网关协议 OSPF](#2-内部网关协议-ospf) + - [3. 外部网关协议 BGP](#3-外部网关协议-bgp) + + + +## 概述 + +网络层向上只提供简单灵活的、无连接的、尽最大努力交付的数据报服务。网络层不提供服务质量的承诺,不保证分组交付的时限,所传送的分组可能出错、丢失、重复和失序。进程间通信的可靠性由运输层负责。 + +## IP 协议 + +网际协议 IP (Internet Protocol) 定义了三种功能: + +1. IP 定义了在 TCP/IP 互联网上数据传送的基本单元和数据格式。 +2. IP 软件完成路由选择功能,选择数据传送的路径。 +3. IP 包含了一组不可靠分组传送的规则,指明了分组处理、差错信息发生以及分组的规则。 + +### 相关协议 + +与 IP 协议配套使用的还有三个协议: + +- 地址解析协议 ARP(Address Resolution Protocol) +- 网际控制报文协议 ICMP(Internet Control Message Protocol) +- 网际组管理协议 IGMP(Internet Group Management Protocol) + +
+ +### 分类的 IP 地址 + +IP 地址的编址方式经历了三个历史阶段: + +- 分类 +- 子网划分 +- 无分类 + +#### 1. 分类 + +由两部分组成,网络号和主机号,其中不同分类具有不同的网络号长度,并且是固定的。 + +``` +IP 地址 ::= {< 网络号 >, < 主机号 >} +``` + +
+ +#### 2. 子网划分 + +通过在主机号字段中拿一部分作为子网号,把两级 IP 地址划分为三级 IP 地址。 + +``` +IP 地址 ::= {< 网络号 >, < 子网号 >, < 主机号 >} +``` + +要使用子网,必须配置子网掩码。一个 B 类地址的默认子网掩码为 255.255.0.0,如果 B 类地址的子网占两个比特,那么子网掩码为 11111111 11111111 11000000 00000000,也就是 255.255.192.0。 + +注意,外部网络看不到子网的存在。 + +#### 3. 无分类 + +无分类编址 CIDR 消除了传统 A 类、B 类和 C 类地址以及划分子网的概念,使用网络前缀和主机号来对 IP 地址进行编码,网络前缀的长度可以根据需要变化。 + +``` +IP 地址 ::= {< 网络前缀号 >, < 主机号 >} +``` + +CIDR 的记法上采用在 IP 地址后面加上网络前缀长度的方法,例如 128.14.35.7/20 表示前 20 位为网络前缀。 + +CIDR 的地址掩码可以继续称为子网掩码,子网掩码首 1 长度为网络前缀的长度。 + +一个 CIDR 地址块中有很多地址,一个 CIDR 表示的网络就可以表示原来的很多个网络,并且在路由表中只需要一个路由就可以代替原来的多个路由,减少了路由表项的数量。把这种通过使用网络前缀来减少路由表项的方式称为路由聚合,也称为 **构成超网** 。 + +在路由表中的项目由“网络前缀”和“下一跳地址”组成,在查找时可能会得到不止一个匹配结果,应当采用最长前缀匹配来确定应该匹配哪一个。 + +### IP 地址与物理地址 + +
+ +- 物理地址是数据链路层和物理层使用的地址。 +- IP 地址是网络层和以上各层使用的地址,是一种逻辑地址。 + +
+ +### IP 数据报格式 + +
+ +- **版本** - 有 4(IPv4)和 6(IPv6)两个值。 +- **首部长度** - 占 4 位,因此最大十进制数值为 15。值为 1 表示的是 1 个 32 位字的长度,也就是 4 字节。因为首部固定长度为 20 字节,因此该值最小为 5。如果可选字段的长度不是 4 字节的整数倍,就用尾部的填充部分来填充。 +- **区分服务** - 用来获得更好的服务,一般情况下不使用。 +- **总长度** - 包括首部长度和数据部分长度。占 16 位,因此数据报的最大长度为 2 16 - 1 = 65535 字节。 +- **生存时间** - TTL,它的存在是为了防止无法交付的数据报在互联网中不断兜圈子。以路由器跳数为单位,当 TTL 为 0 时就丢弃数据报。 +- **协议** - 指出携带的数据应该上交给哪个协议进行处理,例如 ICMP、TCP、UDP 等。 +- **首部检验和** - 因为数据报每经过一个路由器,都要重新计算检验和,因此检验和不包含数据部分可以减少计算的工作量。 +- **标识** - 在数据报长度过长从而发生分片的情况下,相同数据报的不同分片具有相同的标识符。 +- **片偏移** - 和标识符一起,用于发生分片的情况。片偏移的单位为 8 字节。 + +
+ +## 地址解析协议 ARP + +网络层实现主机之间的通信,而链路层实现具体每段链路之间的通信。因此在通信过程中,IP 数据报的源地址和目的地址始终不变,而 MAC 地址随着链路的改变而改变。 + +
+ +ARP 实现由 IP 地址得到 MAC 地址。 + +
+ +每个主机都有一个 ARP 高速缓存,里面有本局域网上的各主机和路由器的 IP 地址到 MAC 地址的映射表。 + +如果主机 A 知道主机 B 的 IP 地址,但是 ARP 高速缓存中没有该 IP 地址到 MAC 地址的映射,此时主机 A 通过广播的方式发送 ARP 请求分组,主机 B 收到该请求后会发送 ARP 响应分组给主机 A 告知其 MAC 地址,随后主机 A 向其高速缓存中写入主机 B 的 IP 地址到 MAC 地址的映射。 + +
+ +## 网际控制报文协议 ICMP + +ICMP 是为了更有效地转发 IP 数据报和提高交付成功的机会。它封装在 IP 数据报中,但是不属于高层协议。 + +
+ +ICMP 报文分为差错报告报文和询问报文。 + +
+ +### 1. Ping + +Ping 是 ICMP 的一个重要应用,主要用来测试两台主机之间的连通性。 + +Ping 的原理是通过向目的主机发送 ICMP Echo 请求报文,目的主机收到之后会发送 Echo 回答报文。Ping 会根据时间和成功响应的次数估算出数据包往返时间以及丢包率。 + +### 2. Traceroute + +Traceroute 是 ICMP 的另一个应用,用来跟踪一个分组从源点到终点的路径。 + +Traceroute 发送的 IP 数据报封装的是无法交付的 UDP 用户数据报,并由目的主机发送终点不可达差错报告报文。 + +- 源主机向目的主机发送一连串的 IP 数据报。第一个数据报 P1 的生存时间 TTL 设置为 1,当 P1 到达路径上的第一个路由器 R1 时,R1 收下它并把 TTL 减 1,此时 TTL 等于 0,R1 就把 P1 丢弃,并向源主机发送一个 ICMP 时间超过差错报告报文; +- 源主机接着发送第二个数据报 P2,并把 TTL 设置为 2。P2 先到达 R1,R1 收下后把 TTL 减 1 再转发给 R2,R2 收下后也把 TTL 减 1,由于此时 TTL 等于 0,R2 就丢弃 P2,并向源主机发送一个 ICMP 时间超过差错报文。 +- 不断执行这样的步骤,直到最后一个数据报刚刚到达目的主机,主机不转发数据报,也不把 TTL 值减 1。但是因为数据报封装的是无法交付的 UDP,因此目的主机要向源主机发送 ICMP 终点不可达差错报告报文。 +- 之后源主机知道了到达目的主机所经过的路由器 IP 地址以及到达每个路由器的往返时间。 + +## 虚拟专用网 VPN + +由于 IP 地址的紧缺,一个机构能申请到的 IP 地址数往往远小于本机构所拥有的主机数。并且一个机构并不需要把所有的主机接入到外部的互联网中,机构内的计算机可以使用仅在本机构有效的 IP 地址(专用地址)。 + +有三个专用地址块: + +- 10.0.0.0 \~ 10.255.255.255 +- 172.16.0.0 \~ 172.31.255.255 +- 192.168.0.0 \~ 192.168.255.255 + +VPN 使用公用的互联网作为本机构各专用网之间的通信载体。专用指机构内的主机只与本机构内的其它主机通信;虚拟指好像是,而实际上并不是,它有经过公用的互联网。 + +下图中,场所 A 和 B 的通信经过互联网,如果场所 A 的主机 X 要和另一个场所 B 的主机 Y 通信,IP 数据报的源地址是 10.1.0.1,目的地址是 10.2.0.3。数据报先发送到与互联网相连的路由器 R1,R1 对内部数据进行加密,然后重新加上数据报的首部,源地址是路由器 R1 的全球地址 125.1.2.3,目的地址是路由器 R2 的全球地址 194.4.5.6。路由器 R2 收到数据报后将数据部分进行解密,恢复原来的数据报,此时目的地址为 10.2.0.3,就交付给 Y。 + +
+ +## 网络地址转换 NAT + +专用网内部的主机使用本地 IP 地址又想和互联网上的主机通信时,可以使用 NAT 来将本地 IP 转换为全球 IP。 + +在以前,NAT 将本地 IP 和全球 IP 一一对应,这种方式下拥有 n 个全球 IP 地址的专用网内最多只可以同时有 n 台主机接入互联网。为了更有效地利用全球 IP 地址,现在常用的 NAT 转换表把传输层的端口号也用上了,使得多个专用网内部的主机共用一个全球 IP 地址。使用端口号的 NAT 也叫做网络地址与端口转换 NAPT。 + +## 路由器的结构 + +路由器从功能上可以划分为:路由选择和分组转发。 + +分组转发结构由三个部分组成:交换结构、一组输入端口和一组输出端口。 + +
+ +## 路由器分组转发流程 + +- 从数据报的首部提取目的主机的 IP 地址 D,得到目的网络地址 N。 +- 若 N 就是与此路由器直接相连的某个网络地址,则进行直接交付; +- 若路由表中有目的地址为 D 的特定主机路由,则把数据报传送给表中所指明的下一跳路由器; +- 若路由表中有到达网络 N 的路由,则把数据报传送给路由表中所指明的下一跳路由器; +- 若路由表中有一个默认路由,则把数据报传送给路由表中所指明的默认路由器; +- 报告转发分组出错。 + +
+ +## 路由选择协议 + +路由选择协议都是自适应的,能随着网络通信量和拓扑结构的变化而自适应地进行调整。 + +互联网可以划分为许多较小的自治系统 AS,一个 AS 可以使用一种和别的 AS 不同的路由选择协议。 + +可以把路由选择协议划分为两大类: + +- 自治系统内部的路由选择:RIP 和 OSPF +- 自治系统间的路由选择:BGP + +### 1. 内部网关协议 RIP + +RIP 是一种基于距离向量的路由选择协议。距离是指跳数,直接相连的路由器跳数为 1。跳数最多为 15,超过 15 表示不可达。 + +RIP 按固定的时间间隔仅和相邻路由器交换自己的路由表,经过若干次交换之后,所有路由器最终会知道到达本自治系统中任何一个网络的最短距离和下一跳路由器地址。 + +距离向量算法: + +- 对地址为 X 的相邻路由器发来的 RIP 报文,先修改报文中的所有项目,把下一跳字段中的地址改为 X,并把所有的距离字段加 1; +- 对修改后的 RIP 报文中的每一个项目,进行以下步骤: +- 若原来的路由表中没有目的网络 N,则把该项目添加到路由表中; +- 否则:若下一跳路由器地址是 X,则把收到的项目替换原来路由表中的项目;否则:若收到的项目中的距离 d 小于路由表中的距离,则进行更新(例如原始路由表项为 Net2, 5, P,新表项为 Net2, 4, X,则更新);否则什么也不做。 +- 若 3 分钟还没有收到相邻路由器的更新路由表,则把该相邻路由器标为不可达,即把距离置为 16。 + +RIP 协议实现简单,开销小。但是 RIP 能使用的最大距离为 15,限制了网络的规模。并且当网络出现故障时,要经过比较长的时间才能将此消息传送到所有路由器。 + +### 2. 内部网关协议 OSPF + +开放最短路径优先 OSPF,是为了克服 RIP 的缺点而开发出来的。 + +开放表示 OSPF 不受某一家厂商控制,而是公开发表的;最短路径优先表示使用了 Dijkstra 提出的最短路径算法 SPF。 + +OSPF 具有以下特点: + +- 向本自治系统中的所有路由器发送信息,这种方法是洪泛法。 +- 发送的信息就是与相邻路由器的链路状态,链路状态包括与哪些路由器相连以及链路的度量,度量用费用、距离、时延、带宽等来表示。 +- 只有当链路状态发生变化时,路由器才会发送信息。 + +所有路由器都具有全网的拓扑结构图,并且是一致的。相比于 RIP,OSPF 的更新过程收敛的很快。 + +### 3. 外部网关协议 BGP + +BGP(Border Gateway Protocol,边界网关协议) + +AS 之间的路由选择很困难,主要是由于: + +- 互联网规模很大; +- 各个 AS 内部使用不同的路由选择协议,无法准确定义路径的度量; +- AS 之间的路由选择必须考虑有关的策略,比如有些 AS 不愿意让其它 AS 经过。 + +BGP 只能寻找一条比较好的路由,而不是最佳路由。 + +每个 AS 都必须配置 BGP 发言人,通过在两个相邻 BGP 发言人之间建立 TCP 连接来交换路由信息。 + +
diff --git a/docs/network/network-physical.md b/docs/network/network-physical.md new file mode 100644 index 00000000..ad13de03 --- /dev/null +++ b/docs/network/network-physical.md @@ -0,0 +1,94 @@ +# 计算机网络之物理层 + +> **摘要** +> +> **物理层(Physical Layer)** - 物理层只接收和发送一串比特(bit)流,不考虑信息的意义和信息结构。 +> +> - 数据单元:比特流。 +> - 典型设备:光纤、同轴电缆、双绞线、中继器和集线器。 + + + +- [通信系统模型](#通信系统模型) +- [通信方式](#通信方式) +- [通信信号](#通信信号) +- [调制解调](#调制解调) + - [基本带通调制方法](#基本带通调制方法) +- [通信媒介](#通信媒介) +- [信道复用](#信道复用) + + + +## 通信系统模型 + +
+ +通信系统模型分为三大部分:源系统(包括信源和发送器)、传输系统、目的系统(包括信宿接收器)。 + +重要概念: + +- **信源** - 也叫源点。产生各类信息的实体。 +- **信道** - 通信的通道,是信号传输的媒介。 +- **信宿** - 传输信息的归宿。 +- **码元** - 在数字通信中常常用时间间隔相同的符号来表示一个二进制数字,这样的时间间隔内的信号称为(二进制)码元。 + +## 通信方式 + +
+ +有三种通信方式: + +- 单工通信:单向传输 +- 半双工通信:双向交替传输 +- 全双工通信:双向同时传输 + +## 通信信号 + +通信的目的是传送消息。如语音、文字、图像、视频都是消息。数据时传送消息的实体。信号是数据的电气或电磁的表现。 + +模拟信号和数字信号 + +- **模拟信号** - 模拟信号是连续的信号。 +- **数字信号** - 数字信号是离散的信号。 + +## 调制解调 + +重要概念: + +- **基带信号** - 来自信源的信号叫做基带信号。 +- **调制** - 将各种数字基带信号转换成适于信道传输的数字调制信号(已调信号或频带信号)。简单来说:调制即,数字 -> 模拟。 +- **解调** - 在接收端将收到的数字频带信号还原成数字基带信号。简单来说:解调即,模拟 -> 数字。 + +> :pushpin: 提示:我们上网时所用到的调制解调器(俗称“猫”),指的就是转换数字和模拟信号的机器。 + +信号要在信道上传输就要经过调制。 + +调制分为:基带调制和带通调制 + +### 基本带通调制方法 + +
+ +如果你收听过广播,一定经常听到 AM、FM 这两个关键词,这是什么意思呢?答案如下: + +- **调幅(AM)** - 即载波的**振幅**随基带数字信号而变化。 +- **调频(FM)** - 即载波的**频率**随基带数字信号而变化。 +- **调相(PM)** - 即载波的**初始相位**随基带数字信号而变化。 + +> :pushpin: 提示:我们收听广播时,为了接收不同广播台的信号,就要调整 AM 或 FM,指的就是这里的调制方法。 + +## 通信媒介 + +通信媒介分为两大类: + +- 导引型 - 双绞线、电缆、光纤 +- 非导引型 - 无线、红外线、大气、激光 + +## 信道复用 + +信道复用就是将用于传输信道的总带宽划分成若干个子频带(或称子信道),每一个子信道传输一路信号。 + +- 频分复用 +- 时分复用 +- 波分复用 +- 码分复用 diff --git a/docs/network/network-transport.md b/docs/network/network-transport.md new file mode 100644 index 00000000..ebd92612 --- /dev/null +++ b/docs/network/network-transport.md @@ -0,0 +1,168 @@ +# 计算机网络之传输层 + +> 网络层只把分组发送到目的主机,但是真正通信的并不是主机而是主机中的进程。传输层提供了进程间的逻辑通信,传输层向高层用户屏蔽了下面网络层的核心细节,使应用程序看起来像是在两个传输层实体之间有一条端到端的逻辑通信信道。 + + + +- [UDP 和 TCP 的特点](#udp-和-tcp-的特点) +- [UDP 首部格式](#udp-首部格式) +- [TCP 首部格式](#tcp-首部格式) +- [TCP 的三次握手](#tcp-的三次握手) +- [TCP 的四次挥手](#tcp-的四次挥手) +- [TCP 可靠传输](#tcp-可靠传输) +- [TCP 滑动窗口](#tcp-滑动窗口) +- [TCP 流量控制](#tcp-流量控制) +- [TCP 拥塞控制](#tcp-拥塞控制) + - [1. 慢开始与拥塞避免](#1-慢开始与拥塞避免) + - [2. 快重传与快恢复](#2-快重传与快恢复) + + + +## UDP 和 TCP 的特点 + +- 用户数据报协议 UDP(User Datagram Protocol)是无连接的,尽最大可能交付,没有拥塞控制,面向报文(对于应用程序传下来的报文不合并也不拆分,只是添加 UDP 首部),支持一对一、一对多、多对一和多对多的交互通信。 + +- 传输控制协议 TCP(Transmission Control Protocol)是面向连接的,提供可靠交付,有流量控制,拥塞控制,提供全双工通信,面向字节流(把应用层传下来的报文看成字节流,把字节流组织成大小不等的数据块),每一条 TCP 连接只能是点对点的(一对一)。 + +## UDP 首部格式 + +
+ +首部字段只有 8 个字节,包括源端口、目的端口、长度、检验和。12 字节的伪首部是为了计算检验和临时添加的。 + +## TCP 首部格式 + +
+ +- **序号** :用于对字节流进行编号,例如序号为 301,表示第一个字节的编号为 301,如果携带的数据长度为 100 字节,那么下一个报文段的序号应为 401。 + +- **确认号** :期望收到的下一个报文段的序号。例如 B 正确收到 A 发送来的一个报文段,序号为 501,携带的数据长度为 200 字节,因此 B 期望下一个报文段的序号为 701,B 发送给 A 的确认报文段中确认号就为 701。 + +- **数据偏移** :指的是数据部分距离报文段起始处的偏移量,实际上指的是首部的长度。 + +- **确认 ACK** :当 ACK=1 时确认号字段有效,否则无效。TCP 规定,在连接建立后所有传送的报文段都必须把 ACK 置 1。 + +- **同步 SYN** :在连接建立时用来同步序号。当 SYN=1,ACK=0 时表示这是一个连接请求报文段。若对方同意建立连接,则响应报文中 SYN=1,ACK=1。 + +- **终止 FIN** :用来释放一个连接,当 FIN=1 时,表示此报文段的发送方的数据已发送完毕,并要求释放连接。 + +- **窗口** :窗口值作为接收方让发送方设置其发送窗口的依据。之所以要有这个限制,是因为接收方的数据缓存空间是有限的。 + +## TCP 的三次握手 + +
+ +假设 A 为客户端,B 为服务器端。 + +- 首先 B 处于 LISTEN(监听)状态,等待客户的连接请求。 + +- A 向 B 发送连接请求报文,SYN=1,ACK=0,选择一个初始的序号 x。 + +- B 收到连接请求报文,如果同意建立连接,则向 A 发送连接确认报文,SYN=1,ACK=1,确认号为 x+1,同时也选择一个初始的序号 y。 + +- A 收到 B 的连接确认报文后,还要向 B 发出确认,确认号为 y+1,序号为 x+1。 + +- B 收到 A 的确认后,连接建立。 + +**三次握手的原因** + +第三次握手是为了防止失效的连接请求到达服务器,让服务器错误打开连接。 + +客户端发送的连接请求如果在网络中滞留,那么就会隔很长一段时间才能收到服务器端发回的连接确认。客户端等待一个超时重传时间之后,就会重新请求连接。但是这个滞留的连接请求最后还是会到达服务器,如果不进行三次握手,那么服务器就会打开两个连接。如果有第三次握手,客户端会忽略服务器之后发送的对滞留连接请求的连接确认,不进行第三次握手,因此就不会再次打开连接。 + +## TCP 的四次挥手 + +
+ +以下描述不讨论序号和确认号,因为序号和确认号的规则比较简单。并且不讨论 ACK,因为 ACK 在连接建立之后都为 1。 + +- A 发送连接释放报文,FIN=1。 + +- B 收到之后发出确认,此时 TCP 属于半关闭状态,B 能向 A 发送数据但是 A 不能向 B 发送数据。 + +- 当 B 不再需要连接时,发送连接释放报文,FIN=1。 + +- A 收到后发出确认,进入 TIME-WAIT 状态,等待 2 MSL(最大报文存活时间)后释放连接。 + +- B 收到 A 的确认后释放连接。 + +**四次挥手的原因** + +客户端发送了 FIN 连接释放报文之后,服务器收到了这个报文,就进入了 CLOSE-WAIT 状态。这个状态是为了让服务器端发送还未传送完毕的数据,传送完毕之后,服务器会发送 FIN 连接释放报文。 + +**TIME_WAIT** + +客户端接收到服务器端的 FIN 报文后进入此状态,此时并不是直接进入 CLOSED 状态,还需要等待一个时间计时器设置的时间 2MSL。这么做有两个理由: + +- 确保最后一个确认报文能够到达。如果 B 没收到 A 发送来的确认报文,那么就会重新发送连接释放请求报文,A 等待一段时间就是为了处理这种情况的发生。 + +- 等待一段时间是为了让本连接持续时间内所产生的所有报文都从网络中消失,使得下一个新的连接不会出现旧的连接请求报文。 + +## TCP 可靠传输 + +TCP 使用超时重传来实现可靠传输:如果一个已经发送的报文段在超时时间内没有收到确认,那么就重传这个报文段。 + +一个报文段从发送再到接收到确认所经过的时间称为往返时间 RTT,加权平均往返时间 RTTs 计算如下: + +

+ +其中,0 ≤ a < 1,RTTs 随着 a 的增加更容易受到 RTT 的影响。 + +超时时间 RTO 应该略大于 RTTs,TCP 使用的超时时间计算如下: + +

+ +其中 RTTd 为偏差的加权平均值。 + +## TCP 滑动窗口 + +
+ +窗口是缓存的一部分,用来暂时存放字节流。发送方和接收方各有一个窗口,接收方通过 TCP 报文段中的窗口字段告诉发送方自己的窗口大小,发送方根据这个值和其它信息设置自己的窗口大小。 + +发送窗口内的字节都允许被发送,接收窗口内的字节都允许被接收。如果发送窗口左部的字节已经发送并且收到了确认,那么就将发送窗口向右滑动一定距离,直到左部第一个字节不是已发送并且已确认的状态;接收窗口的滑动类似,接收窗口左部字节已经发送确认并交付主机,就向右滑动接收窗口。 + +接收窗口只会对窗口内最后一个按序到达的字节进行确认,例如接收窗口已经收到的字节为 {31, 34, 35},其中 {31} 按序到达,而 {34, 35} 就不是,因此只对字节 31 进行确认。发送方得到一个字节的确认之后,就知道这个字节之前的所有字节都已经被接收。 + +## TCP 流量控制 + +流量控制是为了控制发送方发送速率,保证接收方来得及接收。 + +接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。将窗口字段设置为 0,则发送方不能发送数据。 + +## TCP 拥塞控制 + +如果网络出现拥塞,分组将会丢失,此时发送方会继续重传,从而导致网络拥塞程度更高。因此当出现拥塞时,应当控制发送方的速率。这一点和流量控制很像,但是出发点不同。流量控制是为了让接收方能来得及接收,而拥塞控制是为了降低整个网络的拥塞程度。 + +
+ +TCP 主要通过四个算法来进行拥塞控制:慢开始、拥塞避免、快重传、快恢复。 + +发送方需要维护一个叫做拥塞窗口(cwnd)的状态变量,注意拥塞窗口与发送方窗口的区别:拥塞窗口只是一个状态变量,实际决定发送方能发送多少数据的是发送方窗口。 + +为了便于讨论,做如下假设: + +- 接收方有足够大的接收缓存,因此不会发生流量控制; +- 虽然 TCP 的窗口基于字节,但是这里设窗口的大小单位为报文段。 + +
+ +### 1. 慢开始与拥塞避免 + +发送的最初执行慢开始,令 cwnd = 1,发送方只能发送 1 个报文段;当收到确认后,将 cwnd 加倍,因此之后发送方能够发送的报文段数量为:2、4、8 ... + +注意到慢开始每个轮次都将 cwnd 加倍,这样会让 cwnd 增长速度非常快,从而使得发送方发送的速度增长速度过快,网络拥塞的可能性也就更高。设置一个慢开始门限 ssthresh,当 cwnd >= ssthresh 时,进入拥塞避免,每个轮次只将 cwnd 加 1。 + +如果出现了超时,则令 ssthresh = cwnd / 2,然后重新执行慢开始。 + +### 2. 快重传与快恢复 + +在接收方,要求每次接收到报文段都应该对最后一个已收到的有序报文段进行确认。例如已经接收到 M1 和 M2,此时收到 M4,应当发送对 M2 的确认。 + +在发送方,如果收到三个重复确认,那么可以知道下一个报文段丢失,此时执行快重传,立即重传下一个报文段。例如收到三个 M2,则 M3 丢失,立即重传 M3。 + +在这种情况下,只是丢失个别报文段,而不是网络拥塞。因此执行快恢复,令 ssthresh = cwnd / 2 ,cwnd = ssthresh,注意到此时直接进入拥塞避免。 + +慢开始和快恢复的快慢指的是 cwnd 的设定值,而不是 cwnd 的增长速率。慢开始 cwnd 设定为 1,而快恢复 cwnd 设定为 ssthresh。 + +
diff --git a/docs/network/protocol/dns.md b/docs/network/protocol/dns.md new file mode 100644 index 00000000..764e0d82 --- /dev/null +++ b/docs/network/protocol/dns.md @@ -0,0 +1,133 @@ +# 网络协议之 DNS + +> 域名系统(英文:Domain Name System,缩写:DNS)是互联网的一项服务。它作为将域名和 IP 地址相互映射的一个分布式数据库,能够使人更方便地访问互联网。DNS 使用 TCP 和 UDP 端口 53。当前,对于每一级域名长度的限制是 63 个字符,域名总长度则不能超过 253 个字符。 +> +> 关键词:DNS, 域名解析 + + + +- [简介](#简介) + - [什么是 DNS?](#什么是-dns) + - [什么是域名?](#什么是域名) + - [DNS 的分层](#dns-的分层) + - [DNS 服务类型](#dns-服务类型) + - [记录类型](#记录类型) +- [域名解析](#域名解析) +- [Linux 上的域名相关命令](#linux-上的域名相关命令) + - [hostname](#hostname) + - [nslookup](#nslookup) +- [更多内容](#更多内容) + + + +## 简介 + +### 什么是 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](http://www.example.com/) )或映射到一个 `A`记录。 +- **PTR 记录(反向记录)** ─ PTR 记录用于定义与 IP 地址相关联的名称。 PTR 记录是 A 或 AAAA 记录的逆。 PTR 记录是唯一的,因为它们以 .arpa 根开始并被委派给 IP 地址的所有者。 + +> 详细可以参考:[维基百科 - 域名服务器记录类型列表](https://zh.wikipedia.org/wiki/域名服务器记录类型列表) + +## 域名解析 + +主机名到 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` 命令,在其中添加下面的内容来配置域名服务器地址: +> +> ``` +> nameserver 218.2.135.1 +> ``` + +## Linux 上的域名相关命令 + +### hostname + +> hostname 命令用于查看和设置系统的主机名称。环境变量 HOSTNAME 也保存了当前的主机名。在使用 hostname 命令设置主机名后,系统并不会永久保存新的主机名,重新启动机器之后还是原来的主机名。如果需要永久修改主机名,需要同时修改 `/etc/hosts` 和 `/etc/sysconfig/network` 的相关内容。 +> +> 参考:http://man.linuxde.net/hostname + +示例: + +```sh +$ hostname +AY1307311912260196fcZ +``` + +### nslookup + +> nslookup 命令是常用域名查询工具,就是查 DNS 信息用的命令。 +> +> 参考:http://man.linuxde.net/nslookup + +示例: + +```sh +[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 +``` + +## 更多内容 + +- [维基百科 - 域名](https://zh.wikipedia.org/wiki/域名) +- [维基百科 - 域名系统](https://zh.wikipedia.org/wiki/域名系统) +- [维基百科 - 域名服务器记录类型列表](https://zh.wikipedia.org/wiki/域名服务器记录类型列表) +- [什么是 DNS?](https://aws.amazon.com/cn/route53/what-is-dns/) +- [RFC 1034](https://tools.ietf.org/html/rfc1034) +- [RFC 1035](https://tools.ietf.org/html/rfc1035) diff --git a/docs/network/protocol/http.md b/docs/network/protocol/http.md new file mode 100644 index 00000000..cee3d989 --- /dev/null +++ b/docs/network/protocol/http.md @@ -0,0 +1,212 @@ +# 网络协议之 HTTP + + + +- [HTTP 是什么?](#http-是什么) +- [实例](#实例) +- [工作原理](#工作原理) +- [特点](#特点) +- [客户端请求消息](#客户端请求消息) +- [服务器响应消息](#服务器响应消息) +- [HTTP 状态码](#http-状态码) +- [更多内容](#更多内容) + + + +## HTTP 是什么? + +HTTP(HyperText Transfer Protocol,超文本传输协议)是 WWW (World Wide Web)实现数据通信的基石。 + +HTTP 是由 **IETF**(Internet Engineering Task Force,互联网工程工作小组) 和 **W3C**(World Wide Web Consortium,万维网协会) 共同合作制订的,它们发布了一系列的**RFC**(Request For Comments),其中最著名的是 RFC 2616,它定义了**HTTP /1.1**。 + +它是一种应用层协议(OSI 七层模型的最顶层),它基于 TCP/IP 通信协议来传递数据(HTML 文件, 图片文件, 查询结果等)。 + +## 实例 + +如果你学习过计算机网络,熟悉 OSI 模型,那么你可以跳过这个实例了。 + +而不了解 OSI 模型的朋友,不妨通过一个实例来对 HTTP 报文有一个感性的认识。 + +以下是使用 wireshark 抓取的一个实际访问百度首页的 HTTP GET 报文: + +
+ +可以清楚的看到它的层级结构如下图,经过了层层的包装。 + +
+ +## 工作原理 + +HTTP 工作于 **Client/Server** 模型上。 + +客户端和服务器之间的通信采用 **request/response** 机制。 + +客户端是终端(可以是浏览器、爬虫程序等),服务器是网站的 Web 服务器。 + +一次 HTTP 操作称为一个事务,其工作过程大致可分为四步: + +1. **建立连接** - 首先,客户端和服务器需要建立一个到服务器指定端口(默认端口号为 **80**)的 TCP 连接(注:虽然 HTTP 采用 TCP 连接是最流行的方式,但是 RFC 并没有指定一定要采用这种网络传输方式。)。 +2. **发送请求信息** - 客户端向服务器发送请求。请求方式的格式为,统一资源标识符、协议版本号,后边是 MIME 信息包括请求修饰符 +3. **发送响应信息** - 服务器监听指定接口是否收到请求,一旦发现请求,处理后,返回响应结果给客户端。其格式为一个状态行包括信息的协议版本号、一个成功或错误的代码,后边是 MIME 信息包括服务器信息、实体信息和可能的内容。 +4. **关闭连接** - 客户端根据响应,显示结果给用户,最后关闭连接。 + +## 特点 + +- **无连接的** - 无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。 +- **无状态的** - HTTP 协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。 +- **媒体独立的** - 这意味着,只要客户端和服务器知道如何处理的数据内容,任何类型的数据都可以通过 HTTP 发送。客户端以及服务器指定使用适合的 MIME-type 内容类型。 +- **C/S 模型的** - 基于 Client/Server 模型工作。 + +# HTTP 消息结构 + +HTTP 是基于客户端/服务端(C/S)的架构模型,通过一个可靠的链接来交换信息,是一个无状态的请求/响应协议。 + +一个 HTTP"客户端"是一个应用程序(Web 浏览器或其他任何客户端),通过连接到服务器达到向服务器发送一个或多个 HTTP 的请求的目的。 + +一个 HTTP"服务器"同样也是一个应用程序(通常是一个 Web 服务,如 Apache Web 服务器或 IIS 服务器等),通过接收客户端的请求并向客户端发送 HTTP 响应数据。 + +HTTP 使用统一资源标识符(Uniform Resource Identifiers, URI)来传输数据和建立连接。 + +一旦建立连接后,数据消息就通过类似 Internet 邮件所使用的格式[RFC5322]和多用途 Internet 邮件扩展(MIME)[RFC2045]来传送。 + +
+ +## 客户端请求消息 + +客户端发送一个 HTTP 请求到服务器的请求消息包括以下格式:请求行(request line)、请求头部(header)、空行和请求数据四个部分组成,下图给出了请求报文的一般格式。 + +
+ +## 服务器响应消息 + +HTTP 响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。 + +
+ +# HTTP 请求 + +根据 HTTP 标准,HTTP 请求可以使用多种请求方法。 + +**HTTP1.0**定义了三种请求方法: **GET**, **POST** 和 **HEAD**方法。 + +**HTTP1.1**新增了五种请求方法:**OPTIONS**, **PUT**, **DELETE**, **TRACE** 和 **CONNECT**方法。 + +| 方法 | 描述 | +| ------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| GET | 请求指定的页面信息,并返回实体主体。 | +| HEAD | 类似于 get 请求,只不过返回的响应中没有具体的内容,用于获取报头 | +| POST | 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST 请求可能会导致新的资源的建立和/或已有资源的修改。 | +| PUT | 从客户端向服务器传送的数据取代指定的文档的内容。 | +| DELETE | 请求服务器删除指定的页面。 | +| CONNECT | HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器。 | +| OPTIONS | 允许客户端查看服务器的性能。 | +| TRACE | 回显服务器收到的请求,主要用于测试或诊断。 | + +**HTTP** **请求消息头** + +| **请求消息头** | **说明** | +| --------------- | -------------------------------------------- | +| Accept | 浏览器支持的格式 | +| Accept-Encoding | 支持的编码格式,如(UTF-8,GBK) | +| Accept-Language | 支持的语言类型 | +| User-Agent | 浏览器信息 | +| Cookie | 记录的是用户当前的状态 | +| Referer | 指从哪个页面单击链接进入的页面 | +| HOST | 目的地址对应的主机名 | +| Connection | 连接类型。如 Keep-Alive 表示长连接,不会断开 | +| Content-Length | 内容长度 | +| Content-Type | 内容类型 | + +# HTTP 响应 + +| **响应消息头** | **说明** | +| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Allow | 服务器支持哪些请求方法(如 GET、POST 等)。 | +| Content-Encoding | 文档的编码(Encode)方法。只有在解码之后才可以得到 Content-Type 头指定的内容类型。利用 gzip 压缩文档能够显著地减少 HTML 文档的下载时间。Java 的 GZIPOutputStream 可以很方便地进行 gzip 压缩,但只有 Unix 上的 Netscape 和 Windows 上的 IE 4、IE 5 才支持它。因此,Servlet 应该通过查看 Accept-Encoding 头(即 request.getHeader("Accept-Encoding"))检查浏览器是否支持 gzip,为支持 gzip 的浏览器返回经 gzip 压缩的 HTML 页面,为其他浏览器返回普通页面。 | +| Content-Length | 表示内容长度。只有当浏览器使用持久 HTTP 连接时才需要这个数据。如果你想要利用持久连接的优势,可以把输出文档写入 ByteArrayOutputStram,完成后查看其大小,然后把该值放入 Content-Length 头,最后通过`byteArrayStream.writeTo(response.getOutputStream()` 发送内容。 | +| Content-Type | 表示后面的文档属于什么 MIME 类型。Servlet 默认为 `text/plain`,但通常需要显式地指定为 text/html。由于经常要设置 Content-Type,因此 HttpServletResponse 提供了一个专用的方法 setContentType。 | +| Date | 当前的 GMT 时间。你可以用 setDateHeader 来设置这个头以避免转换时间格式的麻烦。 | +| Expires | 应该在什么时候认为文档已经过期,从而不再缓存它? | +| Last-Modified | 文档的最后改动时间。客户可以通过 If-Modified-Since 请求头提供一个日期,该请求将被视为一个条件 GET,只有改动时间迟于指定时间的文档才会返回,否则返回一个 304(Not Modified)状态。Last-Modified 也可用 setDateHeader 方法来设置。 | +| Location | 表示客户应当到哪里去提取文档。Location 通常不是直接设置的,而是通过 HttpServletResponse 的 sendRedirect 方法,该方法同时设置状态代码为 302。 | +| Refresh | 表示浏览器应该在多少时间之后刷新文档,以秒计。除了刷新当前文档之外,你还可以通过 `response.setHeader("Refresh", "5;URL=http://host/path")`让浏览器读取指定的页面。 注意这种功能通常是通过设置 HTML 页面 HEAD 区的 ``实现,这是因为,自动刷新或重定向对于那些不能使用 CGI 或 Servlet 的 HTML 编写者十分重要。但是,对于 Servlet 来说,直接设置 Refresh 头更加方便。 注意 Refresh 的意义是"N 秒之后刷新本页面或访问指定页面",而不是"每隔 N 秒刷新本页面或访问指定页面"。因此,连续刷新要求每次都发送一个 Refresh 头,而发送 204 状态代码则可以阻止浏览器继续刷新,不管是使用 Refresh 头还是 ``。 注意 Refresh 头不属于 HTTP 1.1 正式规范的一部分,而是一个扩展,但 Netscape 和 IE 都支持它。 | +| Server | 服务器名字。Servlet 一般不设置这个值,而是由 Web 服务器自己设置。 | +| Set-Cookie | 设置和页面关联的 Cookie。Servlet 不应使用`response.setHeader("Set-Cookie", ...)`,而是应使用 HttpServletResponse 提供的专用方法 addCookie。参见下文有关 Cookie 设置的讨论。 | +| WWW-Authenticate | 客户应该在 Authorization 头中提供什么类型的授权信息?在包含 401(Unauthorized)状态行的应答中这个头是必需的。例如,`response.setHeader("WWW-Authenticate", "BASIC realm=\"executives\"")`。 注意 Servlet 一般不进行这方面的处理,而是让 Web 服务器的专门机制来控制受密码保护页面的访问(例如.htaccess)。 | + +## HTTP 状态码 + +当浏览者访问一个网页时,浏览者的浏览器会向网页所在服务器发出请求。当浏览器接收并显示网页前,此网页所在的服务器会返回一个包含 HTTP 状态码的信息头(server header)用以响应浏览器的请求。 + +HTTP 状态码的英文为 HTTP Status Code。 + +下面是常见的 HTTP 状态码: + +- 200 - 请求成功 +- 301 - 资源(网页等)被永久转移到其它 URL +- 404 - 请求的资源(网页等)不存在 +- 500 - 内部服务器错误 + +HTTP 状态码分类 + +HTTP 状态码由三个十进制数字组成,第一个十进制数字定义了状态码的类型,后两个数字没有分类的作用。HTTP 状态码共分为 5 种类型: + +| **分类** | **分类描述** | +| -------- | ---------------------------------------------- | +| 1 | 信息,服务器收到请求,需要请求者继续执行操作 | +| 2 | 成功,操作被成功接收并处理 | +| 3 | 重定向,需要进一步的操作以完成请求 | +| 4 | 客户端错误,请求包含语法错误或无法完成请求 | +| 5 | 服务器错误,服务器在处理请求的过程中发生了错误 | + +**HTTP 状态列表:** + +| 状态码 | 状态码英文名称 | +| ------ | ------------------------------- | +| 100 | Continue | +| 101 | Switching Protocols | +| 200 | OK | +| 201 | Created | +| 202 | Accepted | +| 203 | Non-Authoritative Information | +| 204 | No Content | +| 205 | Reset Content | +| 206 | Partial Content | +| 300 | Multiple Choices | +| 301 | Moved Permanently | +| 302 | Found | +| 303 | See Other | +| 304 | Not Modified | +| 305 | Use Proxy | +| 306 | Unused | +| 307 | Temporary Redirect | +| 400 | Bad Request | +| 401 | Unauthorized | +| 402 | Payment Required | +| 403 | Forbidden | +| 404 | Not Found | +| 405 | Method Not Allowed | +| 406 | Not Acceptable | +| 407 | Proxy Authentication Required | +| 408 | Request Time-out | +| 409 | Conflict | +| 410 | Gone | +| 411 | Length Required | +| 412 | Precondition Failed | +| 413 | Request Entity Too Large | +| 414 | Request-URI Too Large | +| 415 | Unsupported Media Type | +| 416 | Requested range not satisfiable | +| 417 | Expectation Failed | +| 500 | Internal Server Error | +| 501 | Not Implemented | +| 502 | Bad Gateway | +| 503 | Service Unavailable | +| 504 | Gateway Time-out | +| 505 | HTTP Version not supported | + +## 更多内容 + +- http://blog.csdn.net/gueter/article/details/1524447 +- http://www.runoob.com/http/http-intro.html +- https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol diff --git a/docs/network/protocol/icmp.md b/docs/network/protocol/icmp.md new file mode 100644 index 00000000..e2889d2c --- /dev/null +++ b/docs/network/protocol/icmp.md @@ -0,0 +1,242 @@ +# 网络协议之 ICMP + + + +- [ICMP 简介](#icmp-简介) +- [ICMP 类型](#icmp-类型) +- [目的不可达(Destination Unreachable Message)](#目的不可达destination-unreachable-message) +- [超时(Time Exceeded Message)](#超时time-exceeded-message) +- [参数错误报文(Parameter Problem Message)](#参数错误报文parameter-problem-message) +- [源冷却(Source Quench Message)](#源冷却source-quench-message) +- [重定向(Redirect Message)](#重定向redirect-message) +- [请求回显或回显应答(Echo or Echo Reply Message)](#请求回显或回显应答echo-or-echo-reply-message) +- [时间戳或时间戳请求(Timestamp or Timestamp Reply Message)](#时间戳或时间戳请求timestamp-or-timestamp-reply-message) +- [信息请求或信息响应](#信息请求或信息响应) +- [总结](#总结) +- [参考](#参考) + + + +## ICMP 简介 + +ICMP 全名为(INTERNET CONTROL MESSAGE PROTOCOL)网络控制消息协议。 + +ICMP 的协议号为**1**。 + +ICMP 报文就像是 IP 报文的小弟,总顶着 IP 报文的名头出来混。因为 ICMP 报文是在 IP 报文内部的,如图: + +
+ +图:IP 数据报 + +## ICMP 类型 + +ICMP 报文主要有两大功能:查询报文和差错报文。 + +## 目的不可达(Destination Unreachable Message) + +0 1 2 3 + +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +| Type | Code | Checksum | + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +| unused | + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +| Internet Header + 64 bits of Original Data Datagram | + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +日常生活中,邮寄包裹会经过多个传递环节,任意一环如果无法传下去,都会返回寄件人,并附上无法邮寄的原因。同理,当路由器收到一个无法传递下去的 IP 报文时,会发送 ICMP**目的不可达报文**(Type 为 3)给 IP 报文的源发送方。报文中的 Code 就表示发送失败的原因。 + +Code + +0 = net unreachable; + +1 = host unreachable; + +2 = protocol unreachable; + +3 = port unreachable; + +4 = fragmentation needed and DF set; + +5 = source route failed. + +## 超时(Time Exceeded Message) + +0 1 2 3 + +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +| Type | Code | Checksum | + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +| unused | + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +| Internet Header + 64 bits of Original Data Datagram | + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +网络传输 IP 数据报的过程中,如果 IP 数据包的 TTL 值逐渐递减为 0 时,需要丢弃数据报。这时,路由器需要向源发送方发送 **ICMP 超时报文**(Type 为 11),Code 为 0,表示传输过程中超时了。 + +一个 IP 数据报可能会因为过大而被分片,然后在目的主机侧把所有的分片重组。如果主机迟迟没有等到所有的分片报文,就会向源发送方发送一个 ICMP 超时报文,Code 为 1,表示分片重组超时了。 + +## 参数错误报文(Parameter Problem Message) + +0 1 2 3 + +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +| Type | Code | Checksum | + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +| Pointer | unused | + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +| Internet Header + 64 bits of Original Data Datagram | + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +当路由器或主机处理数据报时,发现因为报文头的参数错误而不得不丢弃报文时,需要向源发送方发送**参数错误报文**(Type 为 12)。当 Code 为 0 时,报文中的 Pointer 表示错误的字节位置。 + +## 源冷却(Source Quench Message) + +0 1 2 3 + +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +| Type | Code | Checksum | + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +| unused | + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +| Internet Header + 64 bits of Original Data Datagram | + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +路由器在处理报文时会有一个缓存队列。如果超过最大缓存队列,将无法处理,从而丢弃报文。并向源发送方发一个 **ICMP 源冷却报文**(Type 为 4),告诉对方:“嘿,我这里客满了,你迟点再来。” + +## 重定向(Redirect Message) + +0 1 2 3 + +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +| Type | Code | Checksum | + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +| Gateway Internet Address | + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +| Internet Header + 64 bits of Original Data Datagram | + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +想像一下,在公司中,有人来你的项目组问你某某某在哪儿。你一想,我们组没有这人啊。你肯定就会说,我们组没有这号人,你去其他组看看。当路由收到 IP 数据报,发现数据报的目的地址在路由表上没有,它就会发 **ICMP 重定向报文**(Type 为 5)给源发送方,提醒它想要发送的地址不在,去其他地方找找吧。 + +## 请求回显或回显应答(Echo or Echo Reply Message) + +0 1 2 3 + +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +| Type | Code | Checksum | + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +| Identifier | Sequence Number | + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +| Data ... + ++-+-+-+-+- + +**Type(8)**是**请求回显报文(Echo)**;Type(0)是**回显应答报文(Echo Reply)**。 + +请求回显或回显应答报文属于查询报文。Ping 就是用这种报文进行查询和回应。 + +## 时间戳或时间戳请求(Timestamp or Timestamp Reply Message) + +0 1 2 3 + +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +| Type | Code | Checksum | + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +| Identifier | Sequence Number | + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +| Originate Timestamp | + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +| Receive Timestamp | + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +| Transmit Timestamp | + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +时间戳报文是用来记录收发以及传输时间的报文。Originate Timestamp 记录的是发送方发送报文的时刻;**Receive Timestamp**记录的是接收方收到报文的时刻;**Transmit Timestamp**表示回显这最后发送报文的时刻。 + +## 信息请求或信息响应 + +0 1 2 3 + +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +| Type | Code | Checksum | + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +| Identifier | Sequence Number | + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +这种报文是用来找出一个主机所在的网络个数(一个主机可能会在多个网络中)。报文的 IP 消息头的目的地址会填为全 0,表示 this,源地址会填为源 IP 所在的网络 IP。 + +## 总结 + +**图:ICMP 知识点思维导图** + +
+ +## 参考 + +- [RFC792](http://www.rfc-editor.org/info/rfc792) From b0434049f9c023b2f63e79f7103c0d6135199ea8 Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Wed, 29 May 2019 23:19:39 +0800 Subject: [PATCH 23/75] update docs --- README.md | 22 ++--- docs/README.md | 22 ++--- docs/index.html | 2 +- docs/navbar.md | 9 ++- docs/network/README.md | 10 ++- docs/network/cdn.md | 119 ++++++++++++++++++++++++++++ docs/network/{protocol => }/dns.md | 0 docs/network/{protocol => }/http.md | 0 docs/network/{protocol => }/icmp.md | 0 9 files changed, 157 insertions(+), 27 deletions(-) create mode 100644 docs/network/cdn.md rename docs/network/{protocol => }/dns.md (100%) rename docs/network/{protocol => }/http.md (100%) rename docs/network/{protocol => }/icmp.md (100%) diff --git a/README.md b/README.md index cccdf342..a6dd97f0 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ # JavaWeb -> Java Web 开发之路经验总结 +> JavaWeb 开发之路经验总结。 > -> 电子书阅读:[Github Pages](https://dunwu.github.io/javaweb/) | [Gitee Pages](https://turnon.gitee.io/javaweb/) +> - :repeat: 项目同步维护:[Github](https://github.com/dunwu/javaweb/) | [Gitee](https://gitee.com/turnon/javaweb/) +> - :book: 电子书阅读:[Github Pages](https://dunwu.github.io/javaweb/) | [Gitee Pages](http://turnon.gitee.io/javaweb/) -| :beginner: | :recycle: | :coffee: | :eye: | :eyes: | :globe_with_meridians: | :computer: | -| ------------- | --------------------- | ----------------- | --------------------- | ------------------------- | ------------------------- | ----------------- | -| [准备](#准备) | [架构设计](#架构设计) | [JavaEE](#javaee) | [主流技术](#主流技术) | [分布式技术](#分布式技术) | [计算机网络](#计算机网络) | [服务器](#服务器) | +| :beginner: | :recycle: | :coffee: | :eye: | :eyes: | :globe_with_meridians: | :computer: | +| ------------- | --------------------- | ----------------- | --------------------- | ------------------------- | ---------------------- | ----------------- | +| [准备](#准备) | [架构设计](#架构设计) | [JavaEE](#javaee) | [主流技术](#主流技术) | [分布式技术](#分布式技术) | [网络通信](#网络通信) | [服务器](#服务器) | @@ -78,7 +79,7 @@ - [ActiveMQ 实战篇](docs/distributed/mq/ActiveMQ.md) - RabbitMQ - 待补充。。。 -## 计算机网络 +## 网络通信 - **网络分层** - [计算机网络概述](docs/network/network-guide.md) @@ -88,9 +89,12 @@ - [计算机网络之传输层](docs/network/network-transport.md) - [计算机网络之应用层](docs/network/network-application.md) - **重要协议** - - [网络协议之 HTTP](docs/network/protocol/http.md) - - [网络协议之 DNS](docs/network/protocol/dns.md) - - [网络协议之 ICMP](docs/network/protocol/icmp.md) + - [网络协议之 HTTP](docs/network/http.md) + - [网络协议之 DNS](docs/network/dns.md) + - [网络协议之 ICMP](docs/network/icmp.md) +- **网络技术** + + - [CDN 详解](docs/network/cdn.md) ## 服务器 diff --git a/docs/README.md b/docs/README.md index ff3ae428..18843a09 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,12 +1,13 @@ # JavaWeb -> Java Web 开发之路经验总结 +> JavaWeb 开发之路经验总结。 > -> 电子书阅读:[Github Pages](https://dunwu.github.io/javaweb/) | [Gitee Pages](https://turnon.gitee.io/javaweb/) +> - :repeat: 项目同步维护:[Github](https://github.com/dunwu/javaweb/) | [Gitee](https://gitee.com/turnon/javaweb/) +> - :book: 电子书阅读:[Github Pages](https://dunwu.github.io/javaweb/) | [Gitee Pages](http://turnon.gitee.io/javaweb/) -| :beginner: | :recycle: | :coffee: | :eye: | :eyes: | :globe_with_meridians: | :computer: | -| ------------- | --------------------- | ----------------- | --------------------- | ------------------------- | ------------------------- | ----------------- | -| [准备](#准备) | [架构设计](#架构设计) | [JavaEE](#javaee) | [主流技术](#主流技术) | [分布式技术](#分布式技术) | [计算机网络](#计算机网络) | [服务器](#服务器) | +| :beginner: | :recycle: | :coffee: | :eye: | :eyes: | :globe_with_meridians: | :computer: | +| ------------- | --------------------- | ----------------- | --------------------- | ------------------------- | ---------------------- | ----------------- | +| [准备](#准备) | [架构设计](#架构设计) | [JavaEE](#javaee) | [主流技术](#主流技术) | [分布式技术](#分布式技术) | [网络通信](#网络通信) | [服务器](#服务器) | @@ -78,7 +79,7 @@ - [ActiveMQ 实战篇](distributed/mq/ActiveMQ.md) - RabbitMQ - 待补充。。。 -## 计算机网络 +## 网络通信 - **网络分层** - [计算机网络概述](network/network-guide.md) @@ -88,9 +89,12 @@ - [计算机网络之传输层](network/network-transport.md) - [计算机网络之应用层](network/network-application.md) - **重要协议** - - [网络协议之 HTTP](network/protocol/http.md) - - [网络协议之 DNS](network/protocol/dns.md) - - [网络协议之 ICMP](network/protocol/icmp.md) + - [网络协议之 HTTP](network/http.md) + - [网络协议之 DNS](network/dns.md) + - [网络协议之 ICMP](network/icmp.md) +- **网络技术** + + - [CDN 详解](network/cdn.md) ## 服务器 diff --git a/docs/index.html b/docs/index.html index e26f425f..acd7cb0b 100644 --- a/docs/index.html +++ b/docs/index.html @@ -40,7 +40,7 @@ // 完整配置参数 search: { maxAge: 86400000, // 过期时间,单位毫秒,默认一天 - paths: ["/", "linux/cli/", "linux/ops/", "linux/soft/", "git/"], // or 'auto' + paths: ["/", "javaee/", "network/", "server/"], // or 'auto' placeholder: "搜索", noData: "没有结果!", // 搜索标题的最大程级, 1 - 6 diff --git a/docs/navbar.md b/docs/navbar.md index 8615fe00..2ae12b8b 100644 --- a/docs/navbar.md +++ b/docs/navbar.md @@ -12,16 +12,17 @@ - [JavaEE 之 Jsp 指南](javaee/javaee-jsp.md) - [JavaEE 之 Filter 和 Listener](javaee/javaee-filter-listener.md) - [JavaEE 之 Cookie 和 Session](javaee/javaee-cookie-sesion.md) -- [计算机网络](network/README.md) +- [网络通信](network/README.md) - [计算机网络概述](network/network-guide.md) - [计算机网络之物理层](network/network-physical.md) - [计算机网络之链路层](network/network-data-link.md) - [计算机网络之网络层](network/network-network.md) - [计算机网络之传输层](network/network-transport.md) - [计算机网络之应用层](network/network-application.md) - - [网络协议之 HTTP](network/protocol/http.md) - - [网络协议之 DNS](network/protocol/dns.md) - - [网络协议之 ICMP](network/protocol/icmp.md) + - [网络协议之 HTTP](network/http.md) + - [网络协议之 DNS](network/dns.md) + - [网络协议之 ICMP](network/icmp.md) + - [CDN 详解](network/cdn.md) - [Server](server/README.md) - [Tomcat 应用指南](server/tomcat.md) - [Jetty 应用指南](server/jetty.md) diff --git a/docs/network/README.md b/docs/network/README.md index 56b2fb6b..a751ddb6 100644 --- a/docs/network/README.md +++ b/docs/network/README.md @@ -1,4 +1,4 @@ -# 计算机网络 +# 网络通信 ## 知识点 @@ -10,6 +10,8 @@ - [计算机网络之传输层](network-transport.md) - [计算机网络之应用层](network-application.md) - **重要协议** - - [网络协议之 HTTP](protocol/http.md) - - [网络协议之 DNS](protocol/dns.md) - - [网络协议之 ICMP](protocol/icmp.md) + - [网络协议之 HTTP](http.md) + - [网络协议之 DNS](dns.md) + - [网络协议之 ICMP](icmp.md) +- **网络技术** + - [CDN 详解](cdn.md) diff --git a/docs/network/cdn.md b/docs/network/cdn.md new file mode 100644 index 00000000..29aef8ed --- /dev/null +++ b/docs/network/cdn.md @@ -0,0 +1,119 @@ +# CDN 详解 + +
+ +## 简介 + +### 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 服务节点分布式缓存和终端本地缓存。任意一个点的存储崩溃或失效,并不影响系统服务的可用性。 + +
+ +如 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-内容推送网络](https://www.cnblogs.com/skynet/archive/2012/12/18/2824141.html) +> - [闲话 CDN](https://zhuanlan.zhihu.com/p/39028766) + +## CDN 访问流程 + +
+ +1. 用户在浏览器中访问域名,域名解析的请求被发往网站的 DNS 域名解析服务器; +2. 由于网站的 DNS 域名解析服务器对此域名的解析设置了 CNAME,请求被指向 CDN 网络中的智能 DNS 负载均衡系统; +3. 智能 DNS 负载均衡系统对域名进行智能解析,将响应速度最快的节点 IP 返回给用户;浏览器在得到速度最快节点的 IP 地址以后,向 CDN 节点发出访问请求; +4. 由于是第一次访问,CDN 节点将回到源站取用户请求的数据并发给用户; +5. 当有其他用户再次访问同样内容时,CDN 将直接将数据返回给客户,完成请求/服务过程。 + +同时,它还与分布在不同地点的所有 CDN 节点保持通信,搜集各节点的健康状态,确保不将用户的请求分配到任何一个已经不可用的节点上。 + +> 参考: +> +> - [CDN-内容推送网络](https://www.cnblogs.com/skynet/archive/2012/12/18/2824141.html) +> - [闲话 CDN](https://zhuanlan.zhihu.com/p/39028766) + +## 推送和拉取 + +CDN 服务有推送和拉取两种方式: + +### CDN 推送 + +当你服务器上内容发生变动时,推送 CDN 接受新内容。直接推送给 CDN 并重写 URL 地址以指向你的内容的 CDN 地址。你可以配置内容到期时间及何时更新。内容只有在更改或新增是才推送,流量最小化,但储存最大化。 + +优点在于节省源站带宽,提前将要分发的内容放到 CDN 节点上了,当某个流量高峰来临时,不会把你的源站带宽占满(源站还要留点带宽提供动态 HTML 啊)。 + +缺点是需要针对 CDN 做接口开发,在被分发内容生成时主动上传给 CDN。 + +### CDN 拉取 + +CDN 拉取是当第一个用户请求该资源时,从服务器上拉取资源。你将内容留在自己的服务器上并重写 URL 指向 CDN 地址。直到内容被缓存在 CDN 上为止,这样请求只会更慢, + +[存活时间(TTL)](https://en.wikipedia.org/wiki/Time_to_live)决定缓存多久时间。CDN 拉取方式最小化 CDN 上的储存空间,但如果过期文件并在实际更改之前被拉取,则会导致冗余的流量。 + +高流量站点使用 CDN 拉取效果不错,因为只有最近请求的内容保存在 CDN 中,流量才能更平衡地分散。 + +优点在于实现简单。 + +> 参考:[推送式与拉取式CDN服务的优劣问题](https://segmentfault.com/q/1010000000119794) + +## 资源 + +- 文章 + - [Wikipedia - CDN](https://en.wikipedia.org/wiki/Content_delivery_network) + - [CDN-内容推送网络](https://www.cnblogs.com/skynet/archive/2012/12/18/2824141.html) + - [闲话 CDN](https://zhuanlan.zhihu.com/p/39028766) + - [CDN 技术详解](https://www.cnblogs.com/losbyday/p/5843960.html) + - [推送式与拉取式CDN服务的优劣问题](https://segmentfault.com/q/1010000000119794) +- CDN 资源 + - https://staticfile.org/ + - https://unpkg.com/ + - https://cdnjs.com/ + - https://github.com/ossrs/srs + - https://github.com/jsdelivr/jsdelivr diff --git a/docs/network/protocol/dns.md b/docs/network/dns.md similarity index 100% rename from docs/network/protocol/dns.md rename to docs/network/dns.md diff --git a/docs/network/protocol/http.md b/docs/network/http.md similarity index 100% rename from docs/network/protocol/http.md rename to docs/network/http.md diff --git a/docs/network/protocol/icmp.md b/docs/network/icmp.md similarity index 100% rename from docs/network/protocol/icmp.md rename to docs/network/icmp.md From b594d0cfd8c8f46d9239dd8692ec352e49c8603b Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Thu, 30 May 2019 21:13:31 +0800 Subject: [PATCH 24/75] update docs --- README.md | 9 +- docs/README.md | 23 ++-- docs/coverpage.md | 8 ++ docs/distributed/mq/rocketmq-basics.md | 14 +- docs/index.html | 181 +++++++++++++++++++++---- docs/javaee/javaee-filter-listener.md | 2 +- docs/network/cdn.md | 6 +- docs/network/dns.md | 4 +- docs/package.json | 2 +- docs/server/tomcat.md | 2 +- docs/standalone/orm/mybatis.md | 10 +- 11 files changed, 198 insertions(+), 63 deletions(-) create mode 100644 docs/coverpage.md diff --git a/README.md b/README.md index a6dd97f0..5c069a8b 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,9 @@ > - :repeat: 项目同步维护:[Github](https://github.com/dunwu/javaweb/) | [Gitee](https://gitee.com/turnon/javaweb/) > - :book: 电子书阅读:[Github Pages](https://dunwu.github.io/javaweb/) | [Gitee Pages](http://turnon.gitee.io/javaweb/) -| :beginner: | :recycle: | :coffee: | :eye: | :eyes: | :globe_with_meridians: | :computer: | -| ------------- | --------------------- | ----------------- | --------------------- | ------------------------- | ---------------------- | ----------------- | -| [准备](#准备) | [架构设计](#架构设计) | [JavaEE](#javaee) | [主流技术](#主流技术) | [分布式技术](#分布式技术) | [网络通信](#网络通信) | [服务器](#服务器) | - - +| 🔰 | 🏗 | ☕ | ⭐️ | ✨ | 🕸 | 💻 | +| :-----------: | :-------------------: | :---------------: | :-------------------: | :-----------------------: | :-------------------: | :---------------: | +| [准备](#准备) | [架构设计](#架构设计) | [JavaEE](#javaee) | [主流技术](#主流技术) | [分布式技术](#分布式技术) | [网络通信](#网络通信) | [服务器](#服务器) | ## 准备 @@ -93,7 +91,6 @@ - [网络协议之 DNS](docs/network/dns.md) - [网络协议之 ICMP](docs/network/icmp.md) - **网络技术** - - [CDN 详解](docs/network/cdn.md) ## 服务器 diff --git a/docs/README.md b/docs/README.md index 18843a09..c205ba15 100644 --- a/docs/README.md +++ b/docs/README.md @@ -5,20 +5,18 @@ > - :repeat: 项目同步维护:[Github](https://github.com/dunwu/javaweb/) | [Gitee](https://gitee.com/turnon/javaweb/) > - :book: 电子书阅读:[Github Pages](https://dunwu.github.io/javaweb/) | [Gitee Pages](http://turnon.gitee.io/javaweb/) -| :beginner: | :recycle: | :coffee: | :eye: | :eyes: | :globe_with_meridians: | :computer: | -| ------------- | --------------------- | ----------------- | --------------------- | ------------------------- | ---------------------- | ----------------- | -| [准备](#准备) | [架构设计](#架构设计) | [JavaEE](#javaee) | [主流技术](#主流技术) | [分布式技术](#分布式技术) | [网络通信](#网络通信) | [服务器](#服务器) | +| 🔰 | 🏗 | ☕ | ⭐️ | ✨ | 🕸 | 💻 | +| :---------------: | :---------------------: | :------------------: | :-----------------------: | :--------------------------: | :---------------------: | :------------------: | +| [准备](#🔰️-准备) | [架构设计](#🏗-架构设计) | [JavaEE](#☕-javaee) | [主流技术](#⭐️-主流技术) | [分布式技术](#✨-分布式技术) | [网络通信](#🕸-网络通信) | [服务器](#💻-服务器) | - - -## 准备 +## 🔰️ 准备 作为 Java Web 工程师,你应该多多少少掌握一些的知识: - [JavaCore](https://dunwu.github.io/javacore/) - Java 核心技术 - [前端技术指南](https://github.com/dunwu/frontend-tutorial) - 即使是后端工程师,也难免会接触到前端技术。前端技术五花八门,如:React、Vue、Angular、Webpack、ES6、Babel、Node.js 等等。不说掌握,至少也应该知道这些技术是什么。 -## 架构设计 +## 🏗 架构设计 > [架构设计](architecture/) 整理架构设计方面的一些学习总结和心得。 @@ -30,7 +28,7 @@ - [网站的安全架构](architecture/网站的安全架构.md) - 关键词:XSS、CSRF、SQL 注入、DoS、消息摘要、加密算法、证书 - [网站典型故障](architecture/网站典型故障.md) -## JavaEE +## ☕ JavaEE > [JavaEE](javaee/) 技术——Java Web 的基石 @@ -40,7 +38,7 @@ - [JavaEE 之 Filter 和 Listener](javaee/javaee-filter-listener.md) - [JavaEE 之 Cookie 和 Session](javaee/javaee-cookie-sesion.md) -## 主流技术 +## ⭐️ 主流技术 > [主流技术](standalone/),典型的技术如:SSM 框架、SSH 框架。 @@ -52,7 +50,7 @@ - Security - [Shiro](standalone/security/shiro.md) - 安全框架,具有认证、授权、加密、会话管理功能。 -## 分布式技术 +## ✨ 分布式技术 > [分布式技术(Distributed)](distributed/),典型的技术如:分布式缓存、分布式消息队列、分布式服务、分布式搜索引擎等。 @@ -79,7 +77,7 @@ - [ActiveMQ 实战篇](distributed/mq/ActiveMQ.md) - RabbitMQ - 待补充。。。 -## 网络通信 +## 🕸 网络通信 - **网络分层** - [计算机网络概述](network/network-guide.md) @@ -93,10 +91,9 @@ - [网络协议之 DNS](network/dns.md) - [网络协议之 ICMP](network/icmp.md) - **网络技术** - - [CDN 详解](network/cdn.md) -## 服务器 +## 💻 服务器 > [服务器](server/) 章节总结 Java Web 领域主流服务器技术。 diff --git a/docs/coverpage.md b/docs/coverpage.md new file mode 100644 index 00000000..bb93f599 --- /dev/null +++ b/docs/coverpage.md @@ -0,0 +1,8 @@ +
+ +# JavaWeb + +> JavaWeb 开发之路经验总结 + +[Java Tutorial](https://dunwu.github.io/java-tutorial/) +[开始阅读](README.md) diff --git a/docs/distributed/mq/rocketmq-basics.md b/docs/distributed/mq/rocketmq-basics.md index d693e1bf..955b6c8f 100644 --- a/docs/distributed/mq/rocketmq-basics.md +++ b/docs/distributed/mq/rocketmq-basics.md @@ -66,14 +66,14 @@ RocketMQ 有以下核心概念: 解压到本地: -```sh +```bash > unzip rocketmq-all-4.2.0-source-release.zip > cd rocketmq-all-4.2.0/ ``` ### 启动 Name Server -```sh +```bash > nohup sh bin/mqnamesrv & > tail -f ~/logs/rocketmqlogs/namesrv.log The Name Server boot success... @@ -81,7 +81,7 @@ The Name Server boot success... ### 启动 Broker -```sh +```bash > nohup sh bin/mqbroker -n localhost:9876 -c conf/broker.conf & > tail -f ~/logs/rocketmqlogs/broker.log The broker[%s, 172.30.30.233:10911] boot success... @@ -91,7 +91,7 @@ The broker[%s, 172.30.30.233:10911] boot success... 执行收发消息操作之前,不许告诉客户端命名服务器的位置。在 RocketMQ 中有多种方法来实现这个目的。这里,我们使用最简单的方法——设置环境变量 `NAMESRV_ADDR` : -```sh +```bash > export NAMESRV_ADDR=localhost:9876 > sh bin/tools.sh org.apache.rocketmq.example.quickstart.Producer SendResult [sendStatus=SEND_OK, msgId= ... @@ -102,7 +102,7 @@ ConsumeMessageThread_%d Receive New Messages: [MessageExt... ### 关闭服务器 -```sh +```bash > sh bin/mqshutdown broker The mqbroker(36695) is running... Send shutdown request to mqbroker(36695) OK @@ -277,7 +277,7 @@ public class OrderedConsumer { ### FAQ -#### connect to <172.17.0.1:10909> failed +#### connect to `<172.17.0.1:10909>` failed 启动后,Producer 客户端连接 RocketMQ 时报错: @@ -313,7 +313,7 @@ brokerIP1 = 10.10.30.63 启动时需要指定配置文件 -```sh +```bash nohup sh bin/mqbroker -n localhost:9876 -c conf/broker.conf & ``` diff --git a/docs/index.html b/docs/index.html index acd7cb0b..93a84aff 100644 --- a/docs/index.html +++ b/docs/index.html @@ -9,13 +9,154 @@ name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" /> - - - - + + + + + @@ -23,27 +164,19 @@
正在加载...
- + + - diff --git a/docs/javaee/javaee-filter-listener.md b/docs/javaee/javaee-filter-listener.md index 76badfa5..19d7d94c 100644 --- a/docs/javaee/javaee-filter-listener.md +++ b/docs/javaee/javaee-filter-listener.md @@ -19,7 +19,7 @@ 过滤器(Filter)用于在 Servlet 之外对 request 或 response 进行修改。Filter 提供了过滤链(Filter Chain)的概念,一个过滤链包括多个 Filter。客户端请求 request 在抵达 Servlet 之前会经过过滤链的所有 Filter,服务器响应 response 从 Servlet 抵达客户端浏览器之前也会经过过滤链的所有 FIlter。 -
+
### 过滤器方法 diff --git a/docs/network/cdn.md b/docs/network/cdn.md index 29aef8ed..1faee4ea 100644 --- a/docs/network/cdn.md +++ b/docs/network/cdn.md @@ -1,6 +1,6 @@ # CDN 详解 -
+
## 简介 @@ -39,7 +39,7 @@ CDN 是一个策略性部署的整体系统,包括**分布式存储**、**负 CDN 网络将存储资源分布到各个地理位置、各个网段。存储系统作为 CDN 系统密不可分的一部分,将 CDN 分发的文件和数据库表记录内容存储起来,提供持续服务。存储系统采用三级存储架构,包括核心存储、CDN 服务节点分布式缓存和终端本地缓存。任意一个点的存储崩溃或失效,并不影响系统服务的可用性。 -
+
如 CDN 系统在 5 大运营商(中国电信、中国网通、中国铁通、中国移动、中国联通)以及 2 大专有网络(中国教育和科研计算机网、中国科技网)都布有 CDN 节点。**这样就消除了不同运营商之间互联的瓶颈造成的影响,实现了跨运营商的网络加速,保证不同网络中的用户都能得到良好的访问质量。** @@ -64,7 +64,7 @@ CDN 负载均衡系统实现 CDN 的**内容路由功能**。它的作用是将 ## CDN 访问流程 -
+
1. 用户在浏览器中访问域名,域名解析的请求被发往网站的 DNS 域名解析服务器; 2. 由于网站的 DNS 域名解析服务器对此域名的解析设置了 CNAME,请求被指向 CDN 网络中的智能 DNS 负载均衡系统; diff --git a/docs/network/dns.md b/docs/network/dns.md index 764e0d82..587c7d30 100644 --- a/docs/network/dns.md +++ b/docs/network/dns.md @@ -99,7 +99,7 @@ DNS 中,常见的资源记录类型有: 示例: -```sh +```bash $ hostname AY1307311912260196fcZ ``` @@ -112,7 +112,7 @@ AY1307311912260196fcZ 示例: -```sh +```bash [root@localhost ~]# nslookup www.jsdig.com Server: 202.96.104.15 Address: 202.96.104.15#53 diff --git a/docs/package.json b/docs/package.json index c7ce2694..cd779280 100644 --- a/docs/package.json +++ b/docs/package.json @@ -2,7 +2,7 @@ "name": "JavaWeb", "version": "1.0.0", "scripts": { - "start": "docsify serve ./" + "start": "docsify serve ./ --port 4000" }, "dependencies": {}, "devDependencies": {} diff --git a/docs/server/tomcat.md b/docs/server/tomcat.md index 204d3a2e..ee3164d4 100644 --- a/docs/server/tomcat.md +++ b/docs/server/tomcat.md @@ -94,7 +94,7 @@ Tomcat 8.5 要求 JDK 版本为 1.7 以上。 下面的示例以 8.5.24 版本为例,包含了下载、解压、启动操作。 -```sh +```bash # 下载解压到本地 wget http://mirrors.hust.edu.cn/apache/tomcat/tomcat-8/v8.5.24/bin/apache-tomcat-8.5.24.tar.gz tar -zxf apache-tomcat-8.5.24.tar.gz diff --git a/docs/standalone/orm/mybatis.md b/docs/standalone/orm/mybatis.md index 31886d59..5b998fd6 100644 --- a/docs/standalone/orm/mybatis.md +++ b/docs/standalone/orm/mybatis.md @@ -64,7 +64,7 @@ MyBatis 和 Hibernate 都是 Java 世界中比较流行的 ORM 框架。我们 > 核心 API 请参考:「 [Mybatis 官方文档之 Java API](http://www.mybatis.org/mybatis-3/zh/java-api.html) 」 -
+
### SqlSessionFactoryBuilder @@ -72,7 +72,7 @@ MyBatis 和 Hibernate 都是 Java 世界中比较流行的 ORM 框架。我们 **`SqlSessionFactoryBuilder` 负责创建 `SqlSessionFactory` 实例。而 `SqlSessionFactoryBuilder` 则可以从 XML 配置文件或一个预先定制的 `Configuration` 的实例构建出 `SqlSessionFactory` 的实例。** -
+
- **SqlSessionFactoryBuilder 生命周期** @@ -84,7 +84,7 @@ MyBatis 和 Hibernate 都是 Java 世界中比较流行的 ORM 框架。我们 **SqlSessionFactory 负责创建 SqlSession 实例。** -
+
- **SqlSessionFactory 生命周期** @@ -92,7 +92,7 @@ MyBatis 和 Hibernate 都是 Java 世界中比较流行的 ORM 框架。我们 ### SqlSession -
+
- **SqlSession 职责** @@ -180,7 +180,7 @@ Mybatis 支持诸如 @Insert、@Update、@Delete、@Select、@Result 等注解 ### MyBatis 的架构 -
+
### 接口层 From 58b95fc62dc78b931808acbd378bc696afcf4391 Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Fri, 31 May 2019 11:51:24 +0800 Subject: [PATCH 25/75] update docs --- README.md | 12 +- docs/README.md | 12 +- docs/network/README.md | 28 ++- docs/network/dns.md | 2 +- docs/network/tcp.md | 110 +++++++++++ docs/network/udp.md | 49 +++++ docs/network/websocket.md | 380 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 577 insertions(+), 16 deletions(-) create mode 100644 docs/network/tcp.md create mode 100644 docs/network/udp.md create mode 100644 docs/network/websocket.md diff --git a/README.md b/README.md index 5c069a8b..f8277fbf 100644 --- a/README.md +++ b/README.md @@ -86,11 +86,13 @@ - [计算机网络之网络层](docs/network/network-network.md) - [计算机网络之传输层](docs/network/network-transport.md) - [计算机网络之应用层](docs/network/network-application.md) -- **重要协议** - - [网络协议之 HTTP](docs/network/http.md) - - [网络协议之 DNS](docs/network/dns.md) - - [网络协议之 ICMP](docs/network/icmp.md) -- **网络技术** +- **重要技术** + - [超文本传输协议 HTTP](docs/network/http.md) + - [域名系统 DNS](docs/network/dns.md) + - [传输控制协议 TCP](docs/network/tcp.md) + - [用户数据报协议 UDP](docs/network/udp.md) + - [网际控制报文协议 ICMP](docs/network/icmp.md) + - [网络协议之 WebSocket](docs/network/websocket.md) - [CDN 详解](docs/network/cdn.md) ## 服务器 diff --git a/docs/README.md b/docs/README.md index c205ba15..68f510f5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -86,11 +86,13 @@ - [计算机网络之网络层](network/network-network.md) - [计算机网络之传输层](network/network-transport.md) - [计算机网络之应用层](network/network-application.md) -- **重要协议** - - [网络协议之 HTTP](network/http.md) - - [网络协议之 DNS](network/dns.md) - - [网络协议之 ICMP](network/icmp.md) -- **网络技术** +- **重要技术** + - [超文本传输协议 HTTP](network/http.md) + - [域名系统 DNS](network/dns.md) + - [传输控制协议 TCP](network/tcp.md) + - [用户数据报协议 UDP](network/udp.md) + - [网际控制报文协议 ICMP](network/icmp.md) + - [网络协议之 WebSocket](network/websocket.md) - [CDN 详解](network/cdn.md) ## 💻 服务器 diff --git a/docs/network/README.md b/docs/network/README.md index a751ddb6..50b8a1b6 100644 --- a/docs/network/README.md +++ b/docs/network/README.md @@ -9,9 +9,27 @@ - [计算机网络之网络层](network-network.md) - [计算机网络之传输层](network-transport.md) - [计算机网络之应用层](network-application.md) -- **重要协议** - - [网络协议之 HTTP](http.md) - - [网络协议之 DNS](dns.md) - - [网络协议之 ICMP](icmp.md) -- **网络技术** +- **重要技术** + - [超文本传输协议 HTTP](http.md) + - [域名系统 DNS](dns.md) + - [传输控制协议 TCP](tcp.md) + - [用户数据报协议 UDP](udp.md) + - [网际控制报文协议 ICMP](icmp.md) + - [网络协议之 WebSocket](websocket.md) - [CDN 详解](cdn.md) + - [ ] Socket 详解 + +## 资源 + +- 书 + - [HTTP 权威指南](https://book.douban.com/subject/10746113/) + - [TCP/IP 详解 卷 1:协议](https://book.douban.com/subject/1088054/) + - [TCP/IP 详解 卷 2:实现](https://book.douban.com/subject/1087767/) + - [TCP/IP 详解 卷 3:TCP 事务协议、HTTP、NNTP 和 UNIX 域协议](https://book.douban.com/subject/1058634/) + - [Web性能权威指南](https://book.douban.com/subject/25856314/) + - [图解 HTTP](https://book.douban.com/subject/25863515/) + - [图解TCP/IP](https://book.douban.com/subject/24737674/) + - [计算机网络(第7版)](https://book.douban.com/subject/26960678/) - 谢希仁 +- 工具 + - [WireShark](https://www.wireshark.org/) + - [Postman](https://www.getpostman.com/) diff --git a/docs/network/dns.md b/docs/network/dns.md index 587c7d30..7db52072 100644 --- a/docs/network/dns.md +++ b/docs/network/dns.md @@ -1,4 +1,4 @@ -# 网络协议之 DNS +# 域名系统 DNS > 域名系统(英文:Domain Name System,缩写:DNS)是互联网的一项服务。它作为将域名和 IP 地址相互映射的一个分布式数据库,能够使人更方便地访问互联网。DNS 使用 TCP 和 UDP 端口 53。当前,对于每一级域名长度的限制是 63 个字符,域名总长度则不能超过 253 个字符。 > diff --git a/docs/network/tcp.md b/docs/network/tcp.md new file mode 100644 index 00000000..af7ade17 --- /dev/null +++ b/docs/network/tcp.md @@ -0,0 +1,110 @@ +# 传输控制协议 TCP + +## 简介 + +
+ +TCP 是通过 [IP 网络](https://en.wikipedia.org/wiki/Internet_Protocol)的面向连接的协议。 使用[握手](https://en.wikipedia.org/wiki/Handshaking)建立和断开连接。 发送的所有数据包保证以原始顺序到达目的地,用以下措施保证数据包不被损坏: + +- 每个数据包的序列号和[校验码](https://en.wikipedia.org/wiki/Transmission_Control_Protocol#Checksum_computation)。 +- [确认包]()和自动重传 + +如果发送者没有收到正确的响应,它将重新发送数据包。如果多次超时,连接就会断开。TCP 实行[流量控制]()和[拥塞控制](https://en.wikipedia.org/wiki/Network_congestion#Congestion_control)。这些确保措施会导致延迟,而且通常导致传输效率比 UDP 低。 + +为了确保高吞吐量,Web 服务器可以保持大量的 TCP 连接,从而导致高内存使用。在 Web 服务器线程间拥有大量开放连接可能开销巨大,消耗资源过多,也就是说,一个 [memcached](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#memcached) 服务器。[连接池](https://en.wikipedia.org/wiki/Connection_pool) 可以帮助除了在适用的情况下切换到 UDP。 + +TCP 对于需要高可靠性但时间紧迫的应用程序很有用。比如包括 Web 服务器,数据库信息,SMTP,FTP 和 SSH。 + +以下情况使用 TCP 代替 UDP: + +- 你需要数据完好无损。 +- 你想对网络吞吐量自动进行最佳评估。 + +## TCP 报文 + +
+ +- TCP 的包是没有 IP 地址的,那是 IP 层上的事。但是有源端口和目标端口。 +- 一个 TCP 连接需要四个元组来表示是同一个连接(src_ip, src_port, dst_ip, dst_port)准确说是五元组,还有一个是协议。但因为这里只是说 TCP 协议,所以,这里我只说四元组。 +- 注意上图中的四个非常重要的东西: + - **Sequence Number**是包的序号,**用来解决网络包乱序(reordering)问题。** + - **Acknowledgement Number**就是 ACK——用于确认收到,**用来解决不丢包的问题**。 + - **Window 又叫 Advertised-Window**,也就是著名的滑动窗口(Sliding Window),**用于解决流控的**。 + - **TCP Flag** ,也就是包的类型,**主要是用于操控 TCP 的状态机的**。 + +
+ +## TCP 通信流程 + +
+ +### 三次握手 + +1. 第一次握手 - client 发送一个 **SYN(J)** 包给 server,然后等待 server 的 ACK 回复,进入`SYN-SENT`状态。p.s: SYN 为 synchronize 的缩写,ACK 为 acknowledgment 的缩写。 +2. 第二次握手 - server 接收到 SYN(seq=J)包后就返回一个 **ACK(J+1)** 包以及一个自己的 **SYN(K)** 包,然后等待 client 的 ACK 回复,server 进入 `SYN-RECIVED` 状态。 +3. 第三次握手 - client 接收到 server 发回的 ACK(J+1)包后,进入 `ESTABLISHED` 状态。然后根据 server 发来的 SYN(K)包,返回给等待中的 server 一个 **ACK(K+1)** 包。等待中的 server 收到 ACK 回复,也把自己的状态设置为 `ESTABLISHED`。到此 TCP 三次握手完成,client 与 server 可以正常进行通信了。 + +> 🤔 **思考** +> +> 为什么要进行三次握手? +> +> 我们来看一下为什么需要进行三次握手,两次握手难道不行么?这里我们用一个生活中的具体例子来解释就很好理解了。我们可以将三次握手中的客户端和服务器之间的握手过程比喻成 A 和 B 通信的过程: +> +> - 在第一次通信过程中,A 向 B 发送信息之后,B 收到信息后可以确认自己的收信能力和 A 的发信能力没有问题。 +> - 在第二次通信中,B 向 A 发送信息之后,A 可以确认自己的发信能力和 B 的收信能力没有问题,**但是 B 不知道自己的发信能力到底如何**,所以就需要第三次通信。 +> - 在第三次通信中,A 向 B 发送信息之后,B 就可以确认自己的发信能力没有问题。 + +### 四次挥手 + +1. 第一次挥手 - client 发送一个 **FIN(M)** 包,此时 client 进入 `FIN-WAIT-1` 状态,这表明 client 已经没有数据要发送了。 +2. 第二次挥手 - server 收到了 client 发来的 FIN(M)包后,向 client 发回一个 **ACK(M+1)** 包,此时 server 进入 `CLOSE-WAIT` 状态,client 进入 `FIN-WAIT-2` 状态。 +3. 第三次挥手 - server 向 client 发送 **FIN(N)** 包,请求关闭连接,同时 server 进入 `LAST-ACK` 状态。 +4. 第四次挥手 - client 收到 server 发送的 **FIN(N)** 包,进入 `TIME-WAIT` 状态。向 server 发送 **ACK(N+1)** 包,server 收到 client 的 **ACK(N+1)** 包以后,进入 `CLOSE` 状态;client 等待一段时间还没有得到回复后判断 server 已正式关闭,进入 `CLOSE` 状态。 + +## TCP 滑动窗口 + +**TCP 必需要解决的可靠传输以及包乱序(reordering)的问题**,所以,TCP 必需要知道网络实际的数据处理带宽或是数据处理速度,这样才不会引起网络拥塞,导致丢包。 + +所以,TCP 引入了一些技术和设计来做网络流控,Sliding Window 是其中一个技术。 前面我们说过,**TCP 头里有一个字段叫 Window,又叫 Advertised-Window,这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据**。**于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来**。 为了说明滑动窗口,我们需要先看一下 TCP 缓冲区的一些数据结构 + +
+ +- \#1 已收到 ack 确认的数据。 +- \#2 发还没收到 ack 的。 +- \#3 在窗口中还没有发出的(接收方还有空间)。 +- \#4 窗口以外的数据(接收方没空间) + +下面是个滑动后的示意图(收到 36 的 ack,并发出了 46-51 的字节): + +
+ +## TCP 重传机制 + +TCP 要保证所有的数据包都可以到达,所以,必需要有重传机制。 + +### 超时重传机制 + +一种是不回 ack,死等 3,当发送方发现收不到 3 的 ack 超时后,会重传 3。一旦接收方收到 3 后,会 ack 回 4——意味着 3 和 4 都收到了。 + +但是,这种方式会有比较严重的问题,那就是因为要死等 3,所以会导致 4 和 5 即便已经收到了,而发送方也完全不知道发生了什么事,因为没有收到 Ack,所以,发送方可能会悲观地认为也丢了,所以有可能也会导致 4 和 5 的重传。 + +对此有两种选择: + +1. 一种是仅重传 timeout 的包。也就是第 3 份数据。 +2. 另一种是重传 timeout 后所有的数据,也就是第 3,4,5 这三份数据。 + +这两种方式有好也有不好。第一种会节省带宽,但是慢,第二种会快一点,但是会浪费带宽,也可能会有无用功。但总体来说都不好。因为都在等 timeout,timeout 可能会很长。 + +### 快速重传机制 + +于是,TCP 引入了一种叫 Fast Retransmit 的算法,不以时间驱动,而以数据驱动重传。也就是说,如果,包没有连续到达,就 ack 最后那个可能被丢了的包,如果发送方连续收到 3 次相同的 ack,就重传。Fast Retransmit 的好处是不用等 timeout 了再重传。 + +比如:如果发送方发出了 1,2,3,4,5 份数据,第一份先到送了,于是就 ack 回 2,结果 2 因为某些原因没收到,3 到达了,于是还是 ack 回 2,后面的 4 和 5 都到了,但是还是 ack 回 2,因为 2 还是没有收到,于是发送端收到了三个 ack=2 的确认,知道了 2 还没有到,于是就马上重转 2。然后,接收端收到了 2,此时因为 3,4,5 都收到了,于是 ack 回 6。 + +Fast Retransmit 只解决了一个问题,就是 timeout 的问题,它依然面临一个艰难的选择,就是,是重传之前的一个还是重传所有的问题。对于上面的示例来说,是重传#2 呢还是重传#2,#3,#4,#5 呢?因为发送端并不清楚这连续的 3 个 ack(2)是谁传回来的?也许发送端发了 20 份数据,是#6,#10,#20 传来的呢。这样,发送端很有可能要重传从 2 到 20 的这堆数据(这就是某些 TCP 的实际的实现)。可见,这是一把双刃剑。 + +## 参考资料 + +- [TCP 的那些事儿(上)](https://coolshell.cn/articles/11564.html) +- [TCP 的那些事儿(下)](https://coolshell.cn/articles/11609.html) +- [图解 TCP 三次握手与四次分手](https://juejin.im/post/5a7835a46fb9a063606eb801) diff --git a/docs/network/udp.md b/docs/network/udp.md new file mode 100644 index 00000000..71b1736c --- /dev/null +++ b/docs/network/udp.md @@ -0,0 +1,49 @@ +# 用户数据报协议(UDP) + +## 简介 + +
+ +UDP 是无连接的。数据报(类似于数据包)只在数据报级别有保证。数据报可能会无序的到达目的地,也有可能会遗失。UDP 不支持拥塞控制。虽然不如 TCP 那样有保证,但 UDP 通常效率更高。 + +UDP 可以通过广播将数据报发送至子网内的所有设备。这对 [DHCP](https://en.wikipedia.org/wiki/Dynamic_Host_Configuration_Protocol) 很有用,因为子网内的设备还没有分配 IP 地址,而 IP 对于 TCP 是必须的。 + +UDP 可靠性更低但适合用在网络电话、视频聊天,流媒体和实时多人游戏上。 + +以下情况使用 UDP 代替 TCP: + +- 你需要低延迟 +- 相对于数据丢失更糟的是数据延迟 +- 你想实现自己的错误校正方法 + +### UDP 特点 + +1. **无连接的**,即发送数据之前不需要建立连接,因此减少了开销和发送数据之前的时延。 +2. **不保证可靠交付**,因此主机不需要为此复杂的连接状态表 +3. **面向报文的**,意思是 UDP 对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界,在添加首部后向下交给 IP 层。 +4. **没有阻塞控制**,因此网络出现的拥塞不会使发送方的发送速率降低。 +5. **支持一对一、一对多、多对一和多对多的交互通信**,也即是提供广播和多播的功能。 +6. **首部开销小**,首部只有 8 个字节,分为四部分。 + +### UDP 应用场景 + +1. 名字转换(DNS) +2. 文件传送(TFTP) +3. 路由选择协议(RIP) +4. IP 地址配置(BOOTP,DHTP) +5. 网络管理(SNMP) +6. 远程文件服务(NFS) +7. IP 电话 +8. 流式多媒体通信 + +## UDP 报文 + +UDP 数据报分为数据字段和首部字段。 +首部字段只有 8 个字节,由四个字段组成,每个字段的长度是 2 个字节。 + +**首部各字段意义**: + +1. **源端口**:源端口号,在需要对方回信时选用,不需要时可全 0. +2. **目的端口**:目的端口号,在终点交付报文时必须要使用到。 +3. **长度**:UDP 用户数据报的长度,在只有首部的情况,其最小值是 8 。 +4. **检验和**:检测 UDP 用户数据报在传输中是否有错,有错就丢弃。 \ No newline at end of file diff --git a/docs/network/websocket.md b/docs/network/websocket.md new file mode 100644 index 00000000..0475b1ed --- /dev/null +++ b/docs/network/websocket.md @@ -0,0 +1,380 @@ +# Websocket 应用指南 + +## 概述 + +### WebSocket 是什么? + +[WebSocket](http://websocket.org/) 是一种网络通信协议。[RFC6455](https://tools.ietf.org/html/rfc6455) 定义了它的通信标准。 + +WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。 + +### 为什么需要 WebSocket ? + +了解计算机网络协议的人,应该都知道:HTTP 协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理。 + +这种通信模型有一个弊端:HTTP 协议无法实现服务器主动向客户端发起消息。 + +这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数 Web 应用程序将通过频繁的异步 JavaScript 和 XML(AJAX)请求实现长轮询。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。 + +
+ +因此,工程师们一直在思考,有没有更好的方法。WebSocket 就是这样发明的。WebSocket 连接允许客户端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端。WebSocket 只需要建立一次连接,就可以一直保持连接状态。这相比于轮询方式的不停建立连接显然效率要大大提高。 + +
+ +### WebSocket 如何工作? + +Web 浏览器和服务器都必须实现 WebSockets 协议来建立和维护连接。由于 WebSockets 连接长期存在,与典型的 HTTP 连接不同,对服务器有重要的影响。 + +基于多线程或多进程的服务器无法适用于 WebSockets,因为它旨在打开连接,尽可能快地处理请求,然后关闭连接。任何实际的 WebSockets 服务器端实现都需要一个异步服务器。 + +## WebSocket 客户端 + +在客户端,没有必要为 WebSockets 使用 JavaScript 库。实现 WebSockets 的 Web 浏览器将通过 WebSockets 对象公开所有必需的客户端功能(主要指支持 Html5 的浏览器)。 + +### 客户端 API + +以下 API 用于创建 WebSocket 对象。 + +``` +var Socket = new WebSocket(url, [protocol] ); +``` + +以上代码中的第一个参数 url, 指定连接的 URL。第二个参数 protocol 是可选的,指定了可接受的子协议。 + +#### WebSocket 属性 + +以下是 WebSocket 对象的属性。假定我们使用了以上代码创建了 Socket 对象: + +| 属性 | 描述 | +| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Socket.readyState | 只读属性 **readyState** 表示连接状态,可以是以下值:0 - 表示连接尚未建立。1 - 表示连接已建立,可以进行通信。2 - 表示连接正在进行关闭。3 - 表示连接已经关闭或者连接不能打开。 | +| Socket.bufferedAmount | 只读属性 **bufferedAmount** 已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数。 | + +#### WebSocket 事件 + +以下是 WebSocket 对象的相关事件。假定我们使用了以上代码创建了 Socket 对象: + +| 事件 | 事件处理程序 | 描述 | +| ------- | ---------------- | -------------------------- | +| open | Socket.onopen | 连接建立时触发 | +| message | Socket.onmessage | 客户端接收服务端数据时触发 | +| error | Socket.onerror | 通信发生错误时触发 | +| close | Socket.onclose | 连接关闭时触发 | + +#### WebSocket 方法 + +以下是 WebSocket 对象的相关方法。假定我们使用了以上代码创建了 Socket 对象: + +| 方法 | 描述 | +| -------------- | ---------------- | +| Socket.send() | 使用连接发送数据 | +| Socket.close() | 关闭连接 | + +**示例** + +```js +// 初始化一个 WebSocket 对象 +var ws = new WebSocket('ws://localhost:9998/echo'); + +// 建立 web socket 连接成功触发事件 +ws.onopen = function() { + // 使用 send() 方法发送数据 + ws.send('发送数据'); + alert('数据发送中...'); +}; + +// 接收服务端数据时触发事件 +ws.onmessage = function(evt) { + var received_msg = evt.data; + alert('数据已接收...'); +}; + +// 断开 web socket 连接成功触发事件 +ws.onclose = function() { + alert('连接已关闭...'); +}; +``` + +## WebSocket 服务端 + +WebSocket 在服务端的实现非常丰富。Node.js、Java、C++、Python 等多种语言都有自己的解决方案。 + +以下,介绍我在学习 WebSocket 过程中接触过的 WebSocket 服务端解决方案。 + +### Node.js + +常用的 Node 实现有以下三种。 + +- [µWebSockets](https://github.com/uWebSockets/uWebSockets) +- [Socket.IO](http://socket.io/) +- [WebSocket-Node](https://github.com/theturtle32/WebSocket-Node) + +### Java + +Java 的 web 一般都依托于 servlet 容器。 + +我使用过的 servlet 容器有:Tomcat、Jetty、Resin。其中 Tomcat7、Jetty7 及以上版本均开始支持 WebSocket(推荐较新的版本,因为随着版本的更迭,对 WebSocket 的支持可能有变更)。 + +此外,Spring 框架对 WebSocket 也提供了支持。 + +虽然,以上应用对于 WebSocket 都有各自的实现。但是,它们都遵循[RFC6455](https://tools.ietf.org/html/rfc6455) 的通信标准,并且 Java API 统一遵循 [JSR 356 - JavaTM API for WebSocket ](http://www.jcp.org/en/jsr/detail?id=356) 规范。所以,在实际编码中,API 差异不大。 + +#### Spring + +Spring 对于 WebSocket 的支持基于下面的 jar 包: + +```xml + + org.springframework + spring-websocket + ${spring.version} + +``` + +在 Spring 实现 WebSocket 服务器大概分为以下几步: + +**创建 WebSocket 处理器** + +扩展 `TextWebSocketHandler` 或 `BinaryWebSocketHandler` ,你可以覆写指定的方法。Spring 在收到 WebSocket 事件时,会自动调用事件对应的方法。 + +```java +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.TextMessage; + +public class MyHandler extends TextWebSocketHandler { + + @Override + public void handleTextMessage(WebSocketSession session, TextMessage message) { + // ... + } + +} +``` + +`WebSocketHandler` 源码如下,这意味着你的处理器大概可以处理哪些 WebSocket 事件: + +```java +public interface WebSocketHandler { + + /** + * 建立连接后触发的回调 + */ + void afterConnectionEstablished(WebSocketSession session) throws Exception; + + /** + * 收到消息时触发的回调 + */ + void handleMessage(WebSocketSession session, WebSocketMessage message) throws Exception; + + /** + * 传输消息出错时触发的回调 + */ + void handleTransportError(WebSocketSession session, Throwable exception) throws Exception; + + /** + * 断开连接后触发的回调 + */ + void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception; + + /** + * 是否处理分片消息 + */ + boolean supportsPartialMessages(); + +} +``` + +**配置 WebSocket** + +配置有两种方式:注解和 xml 。其作用就是将 WebSocket 处理器添加到注册中心。 + +1. 实现 `WebSocketConfigurer` + +```java +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.config.annotation.WebSocketConfigurer; +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; + +@Configuration +@EnableWebSocket +public class WebSocketConfig implements WebSocketConfigurer { + + @Override + public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { + registry.addHandler(myHandler(), "/myHandler"); + } + + @Bean + public WebSocketHandler myHandler() { + return new MyHandler(); + } + +} +``` + +2. xml 方式 + +```xml + + + + + + + + + +``` + +> 更多配置细节可以参考:[Spring WebSocket 文档](https://docs.spring.io/spring/docs/4.3.12.RELEASE/spring-framework-reference/htmlsingle/#websocket) + +#### javax.websocket + +如果不想使用 Spring 框架的 WebSocket API,你也可以选择基本的 javax.websocket。 + +首先,需要引入 API jar 包。 + +``` + + + javax.websocket + javax.websocket-api + 1.0 + +``` + +如果使用嵌入式 jetty,你还需要引入它的实现包: + +```xml + + + org.eclipse.jetty.websocket + javax-websocket-server-impl + ${jetty-version} + + + + org.eclipse.jetty.websocket + javax-websocket-client-impl + ${jetty-version} + +``` + +**@ServerEndpoint** + +这个注解用来标记一个类是 WebSocket 的处理器。 + +然后,你可以在这个类中使用下面的注解来表明所修饰的方法是触发事件的回调 + +```java +// 收到消息触发事件 +@OnMessage +public void onMessage(String message, Session session) throws IOException, InterruptedException { + ... +} + +// 打开连接触发事件 +@OnOpen +public void onOpen(Session session, EndpointConfig config, @PathParam("id") String id) { + ... +} + +// 关闭连接触发事件 +@OnClose +public void onClose(Session session, CloseReason closeReason) { + ... +} + +// 传输消息错误触发事件 +@OnError +public void onError(Throwable error) { + ... +} +``` + +**ServerEndpointConfig.Configurator** + +编写完处理器,你需要扩展 ServerEndpointConfig.Configurator 类完成配置: + +```java +public class WebSocketServerConfigurator extends ServerEndpointConfig.Configurator { + @Override + public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) { + HttpSession httpSession = (HttpSession) request.getHttpSession(); + sec.getUserProperties().put(HttpSession.class.getName(), httpSession); + } +} +``` + +然后就没有然后了,就是这么简单。 + +## WebSocket 代理 + +如果把 WebSocket 的通信看成是电话连接,Nginx 的角色则像是电话接线员,负责将发起电话连接的电话转接到指定的客服。 + +Nginx 从 [1.3 版](http://nginx.com/blog/websocket-nginx/)开始正式支持 WebSocket 代理。如果你的 web 应用使用了代理服务器 Nginx,那么你还需要为 Nginx 做一些配置,使得它开启 WebSocket 代理功能。 + +以下为参考配置: + +```nginx +server { + # this section is specific to the WebSockets proxying + location /socket.io { + proxy_pass http://app_server_wsgiapp/socket.io; + proxy_redirect off; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_read_timeout 600; + } +} +``` + +> 更多配置细节可以参考:[Nginx 官方的 websocket 文档](http://nginx.org/en/docs/http/websocket.html) + +## FAQ + +### HTTP 和 WebSocket 有什么关系? + +Websocket 其实是一个新协议,跟 HTTP 协议基本没有关系,只是为了兼容现有浏览器的握手规范而已,也就是说它是 HTTP 协议上的一种补充。 + +### Html 和 HTTP 有什么关系? + +Html 是超文本标记语言,是一种用于创建网页的标准标记语言。它是一种技术标准。Html5 是它的最新版本。 + +Http 是一种网络通信协议。其本身和 Html 没有直接关系。 + +## 完整示例 + +如果需要完整示例代码,可以参考我的 Github 代码: + +- [Spring 对 WebSocket 支持的示例](https://github.com/dunwu/spring-notes/tree/master/codes/web/websocket) +- [嵌入式 Jetty 服务器的 WebSocket 示例](https://github.com/dunwu/javaee-notes/tree/master/codes/websocket) + +spring-websocket 和 jetty 9.3 版本似乎存在兼容性问题,Tomcat 则木有问题。 + +我尝试了好几次,没有找到解决方案,只好使用 Jetty 官方的嵌入式示例在 Jetty 中使用 WebSocket 。 + +## 资料 + +- [知乎高票答案——WebSocket 是什么原理](https://www.zhihu.com/question/20215561) - 对 WebSocket 原理的阐述简单易懂。 +- [WebSocket 教程](http://www.ruanyifeng.com/blog/2017/05/websocket.html) - 阮一峰大神的科普一如既往的浅显易懂。 +- [WebSockets](https://www.fullstackpython.com/websockets.html) - by _fullstackpython_ +- [Nginx 官方的 websocket 文档](http://nginx.org/en/docs/http/websocket.html) +- [Spring WebSocket 文档](https://docs.spring.io/spring/docs/4.3.12.RELEASE/spring-framework-reference/htmlsingle/#websocket) +- [Tomcat7 WebSocket 文档](http://tomcat.apache.org/tomcat-7.0-doc/web-socket-howto.html) +- [Jetty WebSocket 文档](https://www.eclipse.org/jetty/documentation/9.4.7.v20170914/websocket-intro.html) From 2057d5dfc0d290a193db3e36f1dcfce4c8e972a2 Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Sun, 2 Jun 2019 22:03:39 +0800 Subject: [PATCH 26/75] update docs --- docs/README.md | 2 + docs/distributed/cache/redis.md | 10 +- docs/distributed/mq/ActiveMQ.md | 6 +- docs/index.html | 61 +++++++- docs/network/README.md | 4 + docs/network/dns.md | 4 +- docs/network/http.md | 3 +- docs/network/network-application.md | 10 +- docs/network/network-data-link.md | 10 +- docs/network/network-guide.md | 120 +++++++++------ docs/network/network-interview.md | 229 ++++++++++++++++++++++++++++ docs/network/network-network.md | 30 ++-- docs/network/network-physical.md | 6 +- docs/network/network-transport.md | 16 +- docs/network/tcp.md | 140 +++++++++++------ docs/network/websocket.md | 4 +- 16 files changed, 511 insertions(+), 144 deletions(-) create mode 100644 docs/network/network-interview.md diff --git a/docs/README.md b/docs/README.md index 68f510f5..243abdb2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -79,6 +79,8 @@ ## 🕸 网络通信 +- **面试** + - [网络通信面经](network/network-interview.md) - **网络分层** - [计算机网络概述](network/network-guide.md) - [计算机网络之物理层](network/network-physical.md) diff --git a/docs/distributed/cache/redis.md b/docs/distributed/cache/redis.md index e380ac35..527e9a36 100644 --- a/docs/distributed/cache/redis.md +++ b/docs/distributed/cache/redis.md @@ -107,7 +107,7 @@ Redis 与 Memcached 因为都可以用于缓存,所以常常被拿来做比较 ### 2.1. STRING
- +
命令: @@ -134,7 +134,7 @@ OK ### 2.2. LIST
- +
命令: @@ -171,7 +171,7 @@ OK ### 2.3. SET
- +
命令: @@ -218,7 +218,7 @@ OK ### 2.4. HASH
- +
命令: @@ -269,7 +269,7 @@ OK ### 2.5. ZSET
- +
命令: diff --git a/docs/distributed/mq/ActiveMQ.md b/docs/distributed/mq/ActiveMQ.md index 1d99ab8e..ff1e0fc8 100644 --- a/docs/distributed/mq/ActiveMQ.md +++ b/docs/distributed/mq/ActiveMQ.md @@ -24,7 +24,7 @@ JMS 有两种消息模型: #### P2P 的特点 -
+
在点对点的消息系统中,消息分发给一个单独的使用者。点对点消息往往与队列 `javax.jms.Queue` 相关联。 @@ -38,7 +38,7 @@ JMS 有两种消息模型: #### Pub/Sub 的特点 -
+
发布/订阅消息系统支持一个事件驱动模型,消息生产者和消费者都参与消息的传递。生产者发布事件,而使用者订阅感兴趣的事件,并使用事件。该类型消息一般与特定的主题 `javax.jms.Topic` 关联。 @@ -52,7 +52,7 @@ JMS 有两种消息模型: ### 1.2. JMS 编程模型 -
+
#### ConnectionFactory diff --git a/docs/index.html b/docs/index.html index 93a84aff..3702e0ab 100644 --- a/docs/index.html +++ b/docs/index.html @@ -52,41 +52,86 @@ } .cover-main p a:hover { - color: white !important; + text-align: center; + background-image: -webkit-linear-gradient( + left, + #ffdcb4, + #b96972 25%, + #e88a57 50%, + #804170 75%, + #a596cd + ); + -webkit-text-fill-color: transparent; + -webkit-background-clip: text; + -webkit-background-size: 200% 100%; + -webkit-animation: masked-animation 1.5s infinite linear; } /* content 样式内容 */ .sidebar a, .content a { color: #399ab2 !important; - text-decoration: none; + text-decoration: none !important; } .sidebar a:hover, .content a:hover { color: #fe4165 !important; + text-decoration: underline !important; + } + + .content h1 :hover, + .content h2 :hover, + .content h3 :hover, + .content h4 :hover { + text-align: center; + background-image: -webkit-linear-gradient( + left, + #ffdcb4, + #b96972 25%, + #e88a57 50%, + #804170 75%, + #a596cd + ); + -webkit-text-fill-color: transparent; + -webkit-background-clip: text; + -webkit-background-size: 200% 100%; + -webkit-animation: masked-animation 1.5s infinite linear; + } + + @-webkit-keyframes masked-animation { + 0% { + background-position: 0 0; + } + 100% { + background-position: -100% 0; + } } + .content h1 a, .content h1 span { color: #399ab2 !important; - font-size: 24px; + font-size: 30px; text-shadow: 0 -1px 5px rgba(0, 0, 0, 0.4); } + .content h2 a, .content h2 span { color: #60497c !important; - font-size: 20px; + font-size: 26px; text-shadow: 0 -1px 5px rgba(0, 0, 0, 0.4); } + .content h3 a, .content h3 span { color: #346093 !important; - font-size: 18px; + font-size: 22px; text-shadow: 0 -1px 5px rgba(0, 0, 0, 0.4); } + .content h4 a, .content h4 span { - font-size: 16px; + font-size: 18px; color: #78943a; text-shadow: 0 -1px 5px rgba(0, 0, 0, 0.4); } @@ -166,11 +211,11 @@ window.$docsify = { name: "Java Web", repo: "https://github.com/dunwu/javaweb", - auto2top: true, logo: "http://dunwu.test.upcdn.net/images/others/zp_100_100.png", + auto2top: true, coverpage: "coverpage.md", - subMaxLevel: 4, maxLevel: 4, + subMaxLevel: 4, formatUpdated: "{MM}/{DD} {HH}:{mm}", search: { maxAge: 86400000, diff --git a/docs/network/README.md b/docs/network/README.md index 50b8a1b6..88cc17e9 100644 --- a/docs/network/README.md +++ b/docs/network/README.md @@ -2,6 +2,8 @@ ## 知识点 +- **面试** + - [网络通信面经](network-interview.md) - **网络分层** - [计算机网络概述](network-guide.md) - [计算机网络之物理层](network-physical.md) @@ -30,6 +32,8 @@ - [图解 HTTP](https://book.douban.com/subject/25863515/) - [图解TCP/IP](https://book.douban.com/subject/24737674/) - [计算机网络(第7版)](https://book.douban.com/subject/26960678/) - 谢希仁 +- 站点 + - https://www.rfc-editor.org/ - 在线查阅、下载 RFC 文档 - 工具 - [WireShark](https://www.wireshark.org/) - [Postman](https://www.getpostman.com/) diff --git a/docs/network/dns.md b/docs/network/dns.md index 7db52072..8bebf48b 100644 --- a/docs/network/dns.md +++ b/docs/network/dns.md @@ -32,7 +32,7 @@ DNS 是一个应用层协议。 域名是由一串用点分隔符 `.` 组成的互联网上某一台计算机或计算机组的名称,用于在数据传输时标识计算机的方位。域名可以说是一个 IP 地址的代称,目的是为了便于记忆后者。例如,wikipedia.org 是一个域名,和 IP 地址 208.80.152.2 相对应。人们可以直接访问 wikipedia.org 来代替 IP 地址,然后域名系统(DNS)就会将它转化成便于机器识别的 IP 地址。这样,人们只需要记忆 wikipedia.org 这一串带有特殊含义的字符,而不需要记忆没有含义的数字。 -
+
### DNS 的分层 @@ -67,7 +67,7 @@ DNS 中,常见的资源记录类型有: 通过域名去查询域名服务器,得到 IP 地址的过程叫做域名解析。在解析域名时,一般先静态域名解析,再动态解析域名。可以将一些常用的域名放入静态域名解析表中,这样可以大大提高域名解析效率。 -
+
上图展示了一个动态域名解析的流程,步骤如下: diff --git a/docs/network/http.md b/docs/network/http.md index cee3d989..1a8555af 100644 --- a/docs/network/http.md +++ b/docs/network/http.md @@ -15,7 +15,7 @@ ## HTTP 是什么? -HTTP(HyperText Transfer Protocol,超文本传输协议)是 WWW (World Wide Web)实现数据通信的基石。 +HTTP(HyperText Transfer Protocol)即超文本传输协议,是互联网上应用最为广泛的一种网络协议。所有的 WWW(万维网) 文件都必须遵守这个标准。设计 HTTP 最初的目的是为了提供一种发布和接收 HTML 页面的方法。 HTTP 是由 **IETF**(Internet Engineering Task Force,互联网工程工作小组) 和 **W3C**(World Wide Web Consortium,万维网协会) 共同合作制订的,它们发布了一系列的**RFC**(Request For Comments),其中最著名的是 RFC 2616,它定义了**HTTP /1.1**。 @@ -207,6 +207,7 @@ HTTP 状态码由三个十进制数字组成,第一个十进制数字定义了 ## 更多内容 +- [图解 HTTP](https://book.douban.com/subject/25863515/) - http://blog.csdn.net/gueter/article/details/1524447 - http://www.runoob.com/http/http-intro.html - https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol diff --git a/docs/network/network-application.md b/docs/network/network-application.md index 5d0b1a2e..62e127dd 100644 --- a/docs/network/network-application.md +++ b/docs/network/network-application.md @@ -22,7 +22,7 @@ ## HTTP -
+
超文本传输协议(英语:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP 是万维网的数据通信的基础。 @@ -36,11 +36,11 @@ 域名服务器 DNS 是一个分布式数据库,提供了主机名和 IP 地址之间相互转换的服务。这里的分布式数据库是指,每个站点只保留它自己的那部分数据。 -
+
域名具有层次结构,从上到下依次为:根域名、顶级域名、二级域名。 -
+
DNS 可以使用 UDP 或者 TCP 进行传输,使用的端口号都为 53。大多数情况下 DNS 使用 UDP 进行传输,这就要求域名解析器和域名服务器都必须自己处理超时和重传来保证可靠性。在两种情况下会使用 TCP 进行传输: @@ -58,7 +58,7 @@ DNS 可以使用 UDP 或者 TCP 进行传输,使用的端口号都为 53。大 根据数据连接是否是服务器端主动建立,FTP 有主动和被动两种模式: -
+
- 主动模式:服务器端主动建立数据连接,其中服务器端的端口号为 20,客户端的端口号随机,但是必须大于 1024,因为 0\~1023 是熟知端口号。 - 被动模式:客户端主动建立数据连接,其中客户端的端口号由客户端自己指定,服务器端的端口号随机。 @@ -80,7 +80,7 @@ DHCP 工作过程如下: 3. 如果客户端选择了某个 DHCP 服务器提供的信息,那么就发送 Request 报文给该 DHCP 服务器。 4. DHCP 服务器发送 Ack 报文,表示客户端此时可以使用提供给它的信息。 -
+
## TELNET diff --git a/docs/network/network-data-link.md b/docs/network/network-data-link.md index d8e95f74..2045ac02 100644 --- a/docs/network/network-data-link.md +++ b/docs/network/network-data-link.md @@ -44,7 +44,7 @@ 为了提高传输效率,应该让数据部分长度尽可能大于首部和尾部。但是,每种链路层协议都限制了帧的数据部分长度上线——最大传送单元 MTU(Maximum Transfer Unit) -
+
### 透明传输 @@ -52,7 +52,7 @@ 帧使用首部和尾部进行定界,如果帧的数据部分含有和首部尾部相同的内容,那么帧的开始和结束位置就会被错误的判定。需要在数据部分出现首部尾部相同的内容前面插入转义字符。如果数据部分出现转义字符,那么就在转义字符前面再加个转义字符。在接收端进行处理之后可以还原出原始数据。这个过程透明传输的内容是转义字符,用户察觉不到转义字符的存在。 -
+
### 差错检测 @@ -68,11 +68,11 @@ 互联网用户通常都要连接到某个 ISP 之后才能接入到互联网,PPP 协议是用户计算机和 ISP 进行通信时所使用的数据链路层协议。 -
+
PPP(点到点协议)是为在同等单元之间传输数据包这样的简单链路设计的链路层协议。这种链路提供全双工操作,并按照顺序传递数据包。设计目的主要是用来通过拨号或专线方式建立点对点连接发送数据,使其成为各种主机、网桥和路由器之间简单连接的一种共通的解决方案。 -
+
PPP 的帧格式: @@ -121,7 +121,7 @@ MAC 地址长度为 6 字节(48 位),用于唯一标识网络适配器( 一台主机拥有多少个网络适配器就有多少个 MAC 地址。 -
+
## 设备 diff --git a/docs/network/network-guide.md b/docs/network/network-guide.md index e6feb74e..2156b467 100644 --- a/docs/network/network-guide.md +++ b/docs/network/network-guide.md @@ -9,7 +9,7 @@ date: 2019-02-20 22:26 > 计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。 -
+
## :bulb: 指南 @@ -53,7 +53,7 @@ date: 2019-02-20 22:26 计算机网络的拓扑结构可分为: -
+
- 网型拓扑网型网(Mesh network) - 环型拓扑环型网(Ring network) @@ -78,48 +78,80 @@ date: 2019-02-20 22:26 - **时延(delay)** - 总时延 = 排队时延 + 处理时延 + 传输时延 + 传播时延 -### 体系结构 - -
- -- [物理层](physical) - 关键词:调制、解调、数字信号、模拟信号、通信媒介、信道复用 -- [数据链路层](data-link-layer.md) - 关键词:点对点信道、广播信道、`PPP`、`CSMA/CD`、局域网、以太网、`MAC`、适配器、集线器、网桥、交换机 -- [网络层](network) - 关键词:`IP`、`ICMP`、`ARP`、路由 -- [传输层](transport) - 关键词:`UDP`、`TCP`、滑动窗口、拥塞控制、三次握手 -- [应用层](application) - 关键词:`HTTP`、`DNS`、`FTP`、`TELNET`、`DHCP` - -- **[物理层(Physical Layer)](network-physical.md)** - 物理层只接收和发送一串比特(bit)流,不考虑信息的意义和信息结构。 - - 数据单元:比特流。 - - 典型设备:光纤、同轴电缆、双绞线、中继器和集线器。 -- **[数据链路层(Data Link Layer)](network-data-link.md)** - 网络层针对的还是主机之间的数据传输服务,而主机之间可以有很多链路,链路层协议就是为同一链路的主机提供数据传输服务。数据链路层把网络层传下来的分组封装成帧。 - - 主要协议:`PPP`、`CSMA/CD` 等。 - - 数据单元:帧(frame)。 - - 典型设备:二层交换机、网桥、网卡。 -- **[网络层(network layer)](network-network.md)** - 为分组交换网上的不同主机提供通信服务。在发送数据时,网络层把运输层产生的报文段或用户数据报封装成分组或包进行传送。 - - 主要协议:`IP`。 - - 数据单元:IP 数据报(packet)。 - - 典型设备:网关、路由器。 -- **[传输层(transport layer)](network-transport.md)** - 为两台主机中进程间的通信提供通用的数据传输服务。 - - 主要协议:`TCP`、`UDP`。 - - 数据单元:报文段(segment)或用户数据报。 -- **会话层(Session Layer)** - 会话层不参与具体的传输,它提供包括访问验证和会话管理在内的建立和维护应用之间通信的机制。 -- **表示层(Presentation Layer)** - 表示层是为在应用过程之间传送的信息提供表示方法的服务,它关心的只是发出信息的语法与语义。表示层要完成某些特定的功能,主要有不同数据编码格式的转换,提供数据压缩、解压缩服务,对数据进行加密、解密。 -- **[应用层(application layer)](network-application.md)** - 通过应用进程间的交互来完成特定网络应用。应用层协议定义的是应用进程间通信和交互的规则。 - - 主要协议:`HTTP`、`DNS`、`SMTP`、`Telnet`、`FTP`、`SNMP` 等。 - - 数据单元:报文(message)。 - -## :books: 学习资源 - -### 书 - -- 谢希仁, 计算机网络 - 国内很多大学将其作为计算机网络课程的指定教材,通俗易懂,适合作为入门教材。 -- W·Richard Stevens, [TCP/IP 详解 卷 1:协议](https://book.douban.com/subject/1088054/) - TCP/IP 详解三部曲,适合作为进阶教材 -- W·Richard Stevens, [TCP/IP 详解 卷 2:实现](https://book.douban.com/subject/1087767/) -- W·Richard Stevens, [TCP/IP 详解 卷 3:TCP 事务协议、HTTP、NNTP 和 UNIX 域协议](https://book.douban.com/subject/1088054/) - -### 站点 - -- https://www.rfc-editor.org/ - 在线查阅、下载 RFC 文档 +## 网络分层 + +> 计算机网络如何分层?各层的作用是什么?各层的主要协议、设备分别是什么? +> +> 这是学习计算机网络知识宏观层面必须要了解的核心点。知道了这些,对于网络的体系结构就基本上了解了。 + +
+ +计算机网络分层一般有三种划分体系:OSI 分层;五层协议分层;TCP/IP 协议分层。 + +- OSI 的七层体系结构概念清楚,理论完整,但是比较复杂且不实用,所以并不流行。 +- 五层协议分层是一种折中方案,在现实中更为流行。 + +
+ +### 物理层 + +> 物理层(Physical Layer)只接收和发送一串比特(bit)流,不考虑信息的意义和信息结构。 +> +> 扩展阅读:[计算机网络之物理层](network-physical.md) + +- 关键词:调制、解调、数字信号、模拟信号、通信媒介、信道复用 +- 数据单元:比特流。 +- 典型设备:光纤、同轴电缆、双绞线、中继器和集线器。 + +### 数据链路层 + +> 网络层针对的还是主机之间的数据传输服务,而主机之间可以有很多链路,数据链路层(Data Link Layer)就是为同一链路的主机提供数据传输服务。数据链路层把网络层传下来的分组封装成帧。 +> +> 扩展阅读:[计算机网络之数据链路层](network-data-link.md) + +- 关键词:点对点信道、广播信道、`PPP`、`CSMA/CD`、局域网、以太网、`MAC`、适配器、集线器、网桥、交换机 +- 主要协议:`PPP`、`CSMA/CD` 等。 +- 数据单元:帧(frame)。 +- 典型设备:二层交换机、网桥、网卡。 + +### 网络层 + +> 网络层(network layer)为分组交换网上的不同主机提供通信服务。在发送数据时,网络层把运输层产生的报文段或用户数据报封装成分组或包进行传送。 +> +> 扩展阅读:[计算机网络之网络层](network-network.md) + +- 关键词:`IP`、`ICMP`、`ARP`、路由 +- 主要协议:`IP`。 +- 数据单元:IP 数据报(packet)。 +- 典型设备:网关、路由器。 + +### 传输层 + +> 传输层(transport layer)为两台主机中进程间的通信提供通用的数据传输服务。 +> +> 扩展阅读:[计算机网络之网络层](network-network.md) + +- 关键词:`UDP`、`TCP`、滑动窗口、拥塞控制、三次握手 +- 主要协议:`TCP`、`UDP`。 +- 数据单元:报文段(segment)或用户数据报。 + +### ~~会话层~~ + +> ~~会话层(Session Layer)不参与具体的传输,它提供包括访问验证和会话管理在内的建立和维护应用之间通信的机制。~~ + +### ~~表示层~~ + +> ~~表示层(Presentation Layer)是为在应用过程之间传送的信息提供表示方法的服务,它关心的只是发出信息的语法与语义。表示层要完成某些特定的功能,主要有不同数据编码格式的转换,提供数据压缩、解压缩服务,对数据进行加密、解密。~~ + +### 应用层 + +> 应用层(application layer)通过应用进程间的交互来完成特定网络应用。应用层协议定义的是应用进程间通信和交互的规则。 +> +> 扩展阅读:[计算机网络之应用层](network-application.md) + +- 关键词:`HTTP`、`DNS`、`FTP`、`TELNET`、`DHCP` +- 主要协议:`HTTP`、`DNS`、`SMTP`、`Telnet`、`FTP`、`SNMP` 等。 +- 数据单元:报文(message)。 ## :door: 传送门 diff --git a/docs/network/network-interview.md b/docs/network/network-interview.md new file mode 100644 index 00000000..a7ada2ce --- /dev/null +++ b/docs/network/network-interview.md @@ -0,0 +1,229 @@ +# 网络通信面经 + + + +- [1. 综合](#1-综合) + - [1.1. 计算机网络如何分层?](#11-计算机网络如何分层) +- [2. HTTP](#2-http) +- [3. DNS](#3-dns) +- [4. TCP/UDP](#4-tcpudp) + - [4.1. 什么是 TCP?](#41-什么是-tcp) + - [4.2. TCP 的特性是什么?](#42-tcp-的特性是什么) + - [4.3. TCP 三次握手](#43-tcp-三次握手) + - [4.4. TCP 四次挥手](#44-tcp-四次挥手) + - [4.5. TCP 滑动窗口](#45-tcp-滑动窗口) + - [4.6. TCP 重传机制](#46-tcp-重传机制) + + + +## 1. 综合 + +### 1.1. 计算机网络如何分层? + +> ❓ 问题:计算机网络如何分层?各层的作用是什么?各层的主要协议、设备分别是什么? +> +> 这是学习计算机网络知识宏观层面必须要了解的核心点。知道了这些,对于网络的体系结构就基本上了解了。 + +
+ +计算机网络分层一般有三种划分体系:OSI 分层;五层协议分层;TCP/IP 协议分层。 + +- OSI 的七层体系结构概念清楚,理论完整,但是比较复杂且不实用,所以并不流行。 +- 五层协议分层是一种折中方案,在现实中更为流行。 + +
+ +**物理层** + +> 物理层(Physical Layer)只接收和发送一串比特(bit)流,不考虑信息的意义和信息结构。 +> +> 扩展阅读:[计算机网络之物理层](network-physical.md) + +- 关键词:调制、解调、数字信号、模拟信号、通信媒介、信道复用 +- 数据单元:比特流。 +- 典型设备:光纤、同轴电缆、双绞线、中继器和集线器。 + +**数据链路层** + +> 网络层针对的还是主机之间的数据传输服务,而主机之间可以有很多链路,数据链路层(Data Link Layer)就是为同一链路的主机提供数据传输服务。数据链路层把网络层传下来的分组封装成帧。 +> +> 扩展阅读:[计算机网络之数据链路层](network-data-link.md) + +- 关键词:点对点信道、广播信道、`PPP`、`CSMA/CD`、局域网、以太网、`MAC`、适配器、集线器、网桥、交换机 +- 主要协议:`PPP`、`CSMA/CD` 等。 +- 数据单元:帧(frame)。 +- 典型设备:二层交换机、网桥、网卡。 + +**网络层** + +> 网络层(network layer)为分组交换网上的不同主机提供通信服务。在发送数据时,网络层把运输层产生的报文段或用户数据报封装成分组或包进行传送。 +> +> 扩展阅读:[计算机网络之网络层](network-network.md) + +- 关键词:`IP`、`ICMP`、`ARP`、路由 +- 主要协议:`IP`。 +- 数据单元:IP 数据报(packet)。 +- 典型设备:网关、路由器。 + +**传输层** + +> 传输层(transport layer)为两台主机中进程间的通信提供通用的数据传输服务。 +> +> 扩展阅读:[计算机网络之网络层](network-network.md) + +- 关键词:`UDP`、`TCP`、滑动窗口、拥塞控制、三次握手 +- 主要协议:`TCP`、`UDP`。 +- 数据单元:报文段(segment)或用户数据报。 + +~~**会话层**~~ + +> ~~会话层(Session Layer)不参与具体的传输,它提供包括访问验证和会话管理在内的建立和维护应用之间通信的机制。~~ + +~~**表示层**~~ + +> ~~表示层(Presentation Layer)是为在应用过程之间传送的信息提供表示方法的服务,它关心的只是发出信息的语法与语义。表示层要完成某些特定的功能,主要有不同数据编码格式的转换,提供数据压缩、解压缩服务,对数据进行加密、解密。~~ + +**应用层** + +> 应用层(application layer)通过应用进程间的交互来完成特定网络应用。应用层协议定义的是应用进程间通信和交互的规则。 +> +> 扩展阅读:[计算机网络之应用层](network-application.md) + +- 关键词:`HTTP`、`DNS`、`FTP`、`TELNET`、`DHCP` +- 主要协议:`HTTP`、`DNS`、`SMTP`、`Telnet`、`FTP`、`SNMP` 等。 +- 数据单元:报文(message)。 + +## 2. HTTP + +> 扩展阅读:[超文本传输协议 HTTP](http.md) + +## 3. DNS + +> 扩展阅读:[域名系统 DNS](dns.md) + +## 4. TCP/UDP + +> 扩展阅读:[传输控制协议 TCP](tcp.md),[用户数据报协议 UDP](udp.md) + +### 4.1. 什么是 TCP? + +**TCP(Transmission Control Protocol),即传输控制协议,它是一种`面向连接的`、`可靠的`、`基于字节流的`传输层通信协议**。 + +### 4.2. TCP 的特性是什么? + +- `面向连接的` - 面向连接是指 TCP 需要通过三次握手、四次挥手原则建立和断开双向连接。 +- `可靠的` - 可靠是指 TCP 传输的数据包保证以原始顺序到达目的地,且数据包不被损坏。为了实现这点,TCP 通过以下技术来保证: + - 数据包的序列号和校验码 + - 确认包和自动重传 + - 如果发送者没有收到正确的响应,它将重新发送数据包。如果多次超时,连接就会断开。 + - TCP 实行流量控制和拥塞控制。这些确保措施会导致延迟,而且通常导致传输效率比 UDP 低。 +- `基于字节流的` + - 虽然应用程序和 TCP 的交互是一次一个数据块(大小不等),但 TCP 把应用程序看成是一连串的无结构的字节流。TCP 有一个缓冲,当应用程序传送的数据块太长,TCP 就可以把它划分短一些再传送。如果应用程序一次只发送一个字节,TCP 也可以等待积累有足够多的字节后再构成报文段发送出去。 + - 在 TCP 建立连接前两次握手的 SYN 报文中选项字段的 MSS 值,通信双方商定通信的最大报文长度。如果应用层交付下来的数据过大,就会对数据分段,然后发送;否则通过滑动窗口来控制通信双发的数据。 + +### 4.3. TCP 三次握手 + +> ❓ 问题:三次握手有什么用?什么是三次握手?为什么需要三次握手? + +(1)三次握手有什么用? + +- 三次握手负责建立 TCP 双向连接。 + +(2)什么是三次握手? + +
+ +如上图所示,三次握手流程如下: + +1. 第一次握手 - 客户端向服务端发送带有 SYN 标志的数据包。 +2. 第二次握手 - 服务端向客户端发送带有 SYN/ACK 标志的数据包。 +3. 第三次握手 - 客户端向服务端发送带有带有 ACK 标志的数据包。 + +至此,TCP 三次握手完成,客户端与服务端已建立双向连接。 + +> 💡 说明:SYN 为 synchronize 的缩写,ACK 为 acknowledgment 的缩写。 + +(3)为什么需要三次握手? + +为了便于说明,假设客户端为 A, 服务端为 B。 + +1. 第一次握手,A 向 B 发同步消息。B 收到消息后,B 认为:A 发消息没问题;B 收消息没问题。 +2. 第二次握手,B 向 A 发同步消息和确认消息。A 收到消息后,A 认为:A 发消息、收消息都没问题;B 发消息、收消息都没问题。**但是,此时 B 不确定自己发消息是否没问题**,所以就需要第三次握手。 +3. 第三次握手,A 向 B 发确认消息。B 收到消息后。B 认为:B 发消息没问题。 + +### 4.4. TCP 四次挥手 + +> ❓ 问题:四次挥手有什么用?什么是四次挥手?为什么建立连接是三次握手,关闭连接确是四次挥手呢? + +(1)四次挥手有什么用? + +- 四次挥手负责断开 TCP 连接。 + +(2)什么是四次挥手? + +如上图所示,四次挥手流程如下: + +
+ +1. 第一次挥手 - 客户端向服务端发送一个 FIN 包,用来关闭客户端到服务端的数据传送。 +2. 第二次挥手 - 服务端收到这个 FIN 包,向客户端发送一个 ACK 包,确认序号为收到的序号加 1。和 SYN 一样,一个 FIN 将占用一个序号。 +3. 第三次挥手 - 服务端关闭与客户端的连接,向客户端发送一个 FIN 包。 +4. 第四次挥手 - 客户端向服务端发送 ACK 包,并将确认序号设置为收到序号加 1。 + +(3)为什么建立连接是三次握手,关闭连接确是四次挥手呢? + +- 建立连接的时候, 服务器在 LISTEN 状态下,收到建立连接请求的 SYN 报文后,把 ACK 和 SYN 放在一个报文里发送给客户端。 +- 而关闭连接时,服务器收到对方的 FIN 报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以己方可以立即关闭,也可以发送一些数据给对方后,再发送 FIN 报文给对方来表示同意现在关闭连接,因此,己方 ACK 和 FIN 一般都会分开发送,从而导致多了一次。 + +### 4.5. TCP 滑动窗口 + +> ❓ 问题:什么是滑动窗口?滑动窗口原理是什么? + +什么是滑动窗口? + +**滑动窗口是 TCP 的一种控制网络流量的技术。** + +**TCP 必需要解决的可靠传输以及包乱序(reordering)的问题**,所以,TCP 必需要知道网络实际的数据处理带宽或是数据处理速度,这样才不会引起网络拥塞,导致丢包。 + +TCP 头里有一个字段叫 Window,又叫 Advertised-Window,这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据。**于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来**。 + +滑动窗口原理是什么? + +
+ +1. 已发送已确认 - 数据流中最早的字节已经发送并得到确认。这些数据是站在发送端的角度来看的。上图中的 31 个字节已经发送并确认。 +2. 已发送但尚未确认 - 已发送但尚未得到确认的字节。发送方在确认之前,不认为这些数据已经被处理。上图中的 32 ~ 45 字节为第 2 类。 +3. 未发送而接收方已 Ready - 设备尚未将数据发出 ,但接收方根据最近一次关于发送方一次要发送多少字节确认自己有足够空间。发送方会立即尝试发送。上图中的 46 ~ 51 字节为第 3 类。 +4. 未发送而接收方 Not Ready - 由于接收方 not ready,还不允许将这部分数据发出。上图中的 52 以后的字节为第 4 类。 + +
+ +这张图片相对于上一张图片,滑动窗口偏移了 5 个字节,意味着有 5 个已发送的字节得到了确认。 + +### 4.6. TCP 重传机制 + +> ❓ 问题:为什么需要重传机制?TCP 有哪些重传机制,原理是什么? + +TCP 要保证所有的数据包都可以到达,所以,必需要有重传机制。 + +TCP 重传机制主要有两种: + +- 超时重传机制 +- 快速重传机制 + +(1)超时重传机制 + +超时重传机制是指:发送数据包在一定的时间周期内没有收到相应的 ACK,等待一定的时间,超时之后就认为这个数据包丢失,就会重新发送。这个等待时间被称为 RTO(Retransmission TimeOut),即重传超时时间。 + +没有确认的数据包不会从窗口中移走,定时器在重传时间到期内,每个片段的位置不变。 + +这种机制的重点是 RTO 的设置: + +- RTO 设长了,重发就慢,丢了老半天才重发,没有效率,性能差; +- RTO 设短了,会导致可能并没有丢就重发。于是重发的就快,会增加网络拥塞,导致更多的超时,更多的超时导致更多的重发 + +(2)快速重传机制 + +快速重传机制,实现了另外的一种丢包评定标准,即如果连续收到 3 次重复 ACK,发送方就认为这个 seq 的包丢失了,立刻进行重传。 + +当接收方收到乱序片段时,需要重复发送 ACK。 diff --git a/docs/network/network-network.md b/docs/network/network-network.md index da571943..54fd8c85 100644 --- a/docs/network/network-network.md +++ b/docs/network/network-network.md @@ -49,7 +49,7 @@ - 网际控制报文协议 ICMP(Internet Control Message Protocol) - 网际组管理协议 IGMP(Internet Group Management Protocol) -
+
### 分类的 IP 地址 @@ -67,7 +67,7 @@ IP 地址的编址方式经历了三个历史阶段: IP 地址 ::= {< 网络号 >, < 主机号 >} ``` -
+
#### 2. 子网划分 @@ -99,16 +99,16 @@ CIDR 的地址掩码可以继续称为子网掩码,子网掩码首 1 长度为 ### IP 地址与物理地址 -
+
- 物理地址是数据链路层和物理层使用的地址。 - IP 地址是网络层和以上各层使用的地址,是一种逻辑地址。 -
+
### IP 数据报格式 -
+
- **版本** - 有 4(IPv4)和 6(IPv6)两个值。 - **首部长度** - 占 4 位,因此最大十进制数值为 15。值为 1 表示的是 1 个 32 位字的长度,也就是 4 字节。因为首部固定长度为 20 字节,因此该值最小为 5。如果可选字段的长度不是 4 字节的整数倍,就用尾部的填充部分来填充。 @@ -120,33 +120,33 @@ CIDR 的地址掩码可以继续称为子网掩码,子网掩码首 1 长度为 - **标识** - 在数据报长度过长从而发生分片的情况下,相同数据报的不同分片具有相同的标识符。 - **片偏移** - 和标识符一起,用于发生分片的情况。片偏移的单位为 8 字节。 -
+
## 地址解析协议 ARP 网络层实现主机之间的通信,而链路层实现具体每段链路之间的通信。因此在通信过程中,IP 数据报的源地址和目的地址始终不变,而 MAC 地址随着链路的改变而改变。 -
+
ARP 实现由 IP 地址得到 MAC 地址。 -
+
每个主机都有一个 ARP 高速缓存,里面有本局域网上的各主机和路由器的 IP 地址到 MAC 地址的映射表。 如果主机 A 知道主机 B 的 IP 地址,但是 ARP 高速缓存中没有该 IP 地址到 MAC 地址的映射,此时主机 A 通过广播的方式发送 ARP 请求分组,主机 B 收到该请求后会发送 ARP 响应分组给主机 A 告知其 MAC 地址,随后主机 A 向其高速缓存中写入主机 B 的 IP 地址到 MAC 地址的映射。 -
+
## 网际控制报文协议 ICMP ICMP 是为了更有效地转发 IP 数据报和提高交付成功的机会。它封装在 IP 数据报中,但是不属于高层协议。 -
+
ICMP 报文分为差错报告报文和询问报文。 -
+
### 1. Ping @@ -179,7 +179,7 @@ VPN 使用公用的互联网作为本机构各专用网之间的通信载体。 下图中,场所 A 和 B 的通信经过互联网,如果场所 A 的主机 X 要和另一个场所 B 的主机 Y 通信,IP 数据报的源地址是 10.1.0.1,目的地址是 10.2.0.3。数据报先发送到与互联网相连的路由器 R1,R1 对内部数据进行加密,然后重新加上数据报的首部,源地址是路由器 R1 的全球地址 125.1.2.3,目的地址是路由器 R2 的全球地址 194.4.5.6。路由器 R2 收到数据报后将数据部分进行解密,恢复原来的数据报,此时目的地址为 10.2.0.3,就交付给 Y。 -
+
## 网络地址转换 NAT @@ -193,7 +193,7 @@ VPN 使用公用的互联网作为本机构各专用网之间的通信载体。 分组转发结构由三个部分组成:交换结构、一组输入端口和一组输出端口。 -
+
## 路由器分组转发流程 @@ -204,7 +204,7 @@ VPN 使用公用的互联网作为本机构各专用网之间的通信载体。 - 若路由表中有一个默认路由,则把数据报传送给路由表中所指明的默认路由器; - 报告转发分组出错。 -
+
## 路由选择协议 @@ -261,4 +261,4 @@ BGP 只能寻找一条比较好的路由,而不是最佳路由。 每个 AS 都必须配置 BGP 发言人,通过在两个相邻 BGP 发言人之间建立 TCP 连接来交换路由信息。 -
+
diff --git a/docs/network/network-physical.md b/docs/network/network-physical.md index ad13de03..9811a42c 100644 --- a/docs/network/network-physical.md +++ b/docs/network/network-physical.md @@ -21,7 +21,7 @@ ## 通信系统模型 -
+
通信系统模型分为三大部分:源系统(包括信源和发送器)、传输系统、目的系统(包括信宿接收器)。 @@ -34,7 +34,7 @@ ## 通信方式 -
+
有三种通信方式: @@ -67,7 +67,7 @@ ### 基本带通调制方法 -
+
如果你收听过广播,一定经常听到 AM、FM 这两个关键词,这是什么意思呢?答案如下: diff --git a/docs/network/network-transport.md b/docs/network/network-transport.md index ebd92612..32d09cd8 100644 --- a/docs/network/network-transport.md +++ b/docs/network/network-transport.md @@ -26,13 +26,13 @@ ## UDP 首部格式 -
+
首部字段只有 8 个字节,包括源端口、目的端口、长度、检验和。12 字节的伪首部是为了计算检验和临时添加的。 ## TCP 首部格式 -
+
- **序号** :用于对字节流进行编号,例如序号为 301,表示第一个字节的编号为 301,如果携带的数据长度为 100 字节,那么下一个报文段的序号应为 401。 @@ -50,7 +50,7 @@ ## TCP 的三次握手 -
+
假设 A 为客户端,B 为服务器端。 @@ -72,7 +72,7 @@ ## TCP 的四次挥手 -
+
以下描述不讨论序号和确认号,因为序号和确认号的规则比较简单。并且不讨论 ACK,因为 ACK 在连接建立之后都为 1。 @@ -116,7 +116,7 @@ TCP 使用超时重传来实现可靠传输:如果一个已经发送的报文 ## TCP 滑动窗口 -
+
窗口是缓存的一部分,用来暂时存放字节流。发送方和接收方各有一个窗口,接收方通过 TCP 报文段中的窗口字段告诉发送方自己的窗口大小,发送方根据这个值和其它信息设置自己的窗口大小。 @@ -134,7 +134,7 @@ TCP 使用超时重传来实现可靠传输:如果一个已经发送的报文 如果网络出现拥塞,分组将会丢失,此时发送方会继续重传,从而导致网络拥塞程度更高。因此当出现拥塞时,应当控制发送方的速率。这一点和流量控制很像,但是出发点不同。流量控制是为了让接收方能来得及接收,而拥塞控制是为了降低整个网络的拥塞程度。 -
+
TCP 主要通过四个算法来进行拥塞控制:慢开始、拥塞避免、快重传、快恢复。 @@ -145,7 +145,7 @@ TCP 主要通过四个算法来进行拥塞控制:慢开始、拥塞避免、 - 接收方有足够大的接收缓存,因此不会发生流量控制; - 虽然 TCP 的窗口基于字节,但是这里设窗口的大小单位为报文段。 -
+
### 1. 慢开始与拥塞避免 @@ -165,4 +165,4 @@ TCP 主要通过四个算法来进行拥塞控制:慢开始、拥塞避免、 慢开始和快恢复的快慢指的是 cwnd 的设定值,而不是 cwnd 的增长速率。慢开始 cwnd 设定为 1,而快恢复 cwnd 设定为 ssthresh。 -
+
diff --git a/docs/network/tcp.md b/docs/network/tcp.md index af7ade17..4a3088ad 100644 --- a/docs/network/tcp.md +++ b/docs/network/tcp.md @@ -2,16 +2,26 @@ ## 简介 -
+### 什么是 TCP? + +**TCP(Transmission Control Protocol),即传输控制协议,它是一种`面向连接的`、`可靠的`、`基于字节流的`传输层通信协议**。TCP 由 RFC 793 定义。 -TCP 是通过 [IP 网络](https://en.wikipedia.org/wiki/Internet_Protocol)的面向连接的协议。 使用[握手](https://en.wikipedia.org/wiki/Handshaking)建立和断开连接。 发送的所有数据包保证以原始顺序到达目的地,用以下措施保证数据包不被损坏: +
+### TCP 的特性 -- 每个数据包的序列号和[校验码](https://en.wikipedia.org/wiki/Transmission_Control_Protocol#Checksum_computation)。 -- [确认包]()和自动重传 +- `面向连接的` - 面向连接是指 TCP 需要通过三次握手、四次挥手原则建立和断开双向连接。 +- `可靠的` - 可靠是指 TCP 传输的数据包保证以原始顺序到达目的地,且数据包不被损坏。为了实现这点,TCP 通过以下技术来保证: + - 数据包的序列号和校验码 + - 确认包和自动重传 + - 如果发送者没有收到正确的响应,它将重新发送数据包。如果多次超时,连接就会断开。 + - TCP 实行流量控制和拥塞控制。这些确保措施会导致延迟,而且通常导致传输效率比 UDP 低。 +- `基于字节流的` + - 虽然应用程序和 TCP 的交互是一次一个数据块(大小不等),但 TCP 把应用程序看成是一连串的无结构的字节流。TCP 有一个缓冲,当应用程序传送的数据块太长,TCP 就可以把它划分短一些再传送。如果应用程序一次只发送一个字节,TCP 也可以等待积累有足够多的字节后再构成报文段发送出去。 + - 在 TCP 建立连接前两次握手的 SYN 报文中选项字段的 MSS 值,通信双方商定通信的最大报文长度。如果应用层交付下来的数据过大,就会对数据分段,然后发送;否则通过滑动窗口来控制通信双发的数据。 -如果发送者没有收到正确的响应,它将重新发送数据包。如果多次超时,连接就会断开。TCP 实行[流量控制]()和[拥塞控制](https://en.wikipedia.org/wiki/Network_congestion#Congestion_control)。这些确保措施会导致延迟,而且通常导致传输效率比 UDP 低。 +### TCP 的适用场景 -为了确保高吞吐量,Web 服务器可以保持大量的 TCP 连接,从而导致高内存使用。在 Web 服务器线程间拥有大量开放连接可能开销巨大,消耗资源过多,也就是说,一个 [memcached](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#memcached) 服务器。[连接池](https://en.wikipedia.org/wiki/Connection_pool) 可以帮助除了在适用的情况下切换到 UDP。 +基于以上特性,为了确保高吞吐量,Web 服务器可以保持大量的 TCP 连接,从而导致高内存使用。但要注意的是,在 Web 服务器线程间拥有大量开放连接可能开销巨大,消耗资源过多,这时可以考虑在适用情况下切换到 UDP。 TCP 对于需要高可靠性但时间紧迫的应用程序很有用。比如包括 Web 服务器,数据库信息,SMTP,FTP 和 SSH。 @@ -20,17 +30,19 @@ TCP 对于需要高可靠性但时间紧迫的应用程序很有用。比如包 - 你需要数据完好无损。 - 你想对网络吞吐量自动进行最佳评估。 -## TCP 报文 +### TCP 报文
+报文字段不一一阐述,重点关注以下几点: + - TCP 的包是没有 IP 地址的,那是 IP 层上的事。但是有源端口和目标端口。 - 一个 TCP 连接需要四个元组来表示是同一个连接(src_ip, src_port, dst_ip, dst_port)准确说是五元组,还有一个是协议。但因为这里只是说 TCP 协议,所以,这里我只说四元组。 - 注意上图中的四个非常重要的东西: - **Sequence Number**是包的序号,**用来解决网络包乱序(reordering)问题。** - **Acknowledgement Number**就是 ACK——用于确认收到,**用来解决不丢包的问题**。 - **Window 又叫 Advertised-Window**,也就是著名的滑动窗口(Sliding Window),**用于解决流控的**。 - - **TCP Flag** ,也就是包的类型,**主要是用于操控 TCP 的状态机的**。 + - **TCP Flag**,也就是包的类型,**主要是用于操控 TCP 的状态机的**。
@@ -38,73 +50,115 @@ TCP 对于需要高可靠性但时间紧迫的应用程序很有用。比如包
+TCP 完整的通信分为三块: + +1. 三次握手建立连接 +2. 数据传输 +3. 四次挥手端口连接 + ### 三次握手 -1. 第一次握手 - client 发送一个 **SYN(J)** 包给 server,然后等待 server 的 ACK 回复,进入`SYN-SENT`状态。p.s: SYN 为 synchronize 的缩写,ACK 为 acknowledgment 的缩写。 -2. 第二次握手 - server 接收到 SYN(seq=J)包后就返回一个 **ACK(J+1)** 包以及一个自己的 **SYN(K)** 包,然后等待 client 的 ACK 回复,server 进入 `SYN-RECIVED` 状态。 -3. 第三次握手 - client 接收到 server 发回的 ACK(J+1)包后,进入 `ESTABLISHED` 状态。然后根据 server 发来的 SYN(K)包,返回给等待中的 server 一个 **ACK(K+1)** 包。等待中的 server 收到 ACK 回复,也把自己的状态设置为 `ESTABLISHED`。到此 TCP 三次握手完成,client 与 server 可以正常进行通信了。 +(1)三次握手有什么用? + +- 三次握手负责建立 TCP 双向连接。 + +(2)什么是三次握手? -> 🤔 **思考** -> -> 为什么要进行三次握手? -> -> 我们来看一下为什么需要进行三次握手,两次握手难道不行么?这里我们用一个生活中的具体例子来解释就很好理解了。我们可以将三次握手中的客户端和服务器之间的握手过程比喻成 A 和 B 通信的过程: -> -> - 在第一次通信过程中,A 向 B 发送信息之后,B 收到信息后可以确认自己的收信能力和 A 的发信能力没有问题。 -> - 在第二次通信中,B 向 A 发送信息之后,A 可以确认自己的发信能力和 B 的收信能力没有问题,**但是 B 不知道自己的发信能力到底如何**,所以就需要第三次通信。 -> - 在第三次通信中,A 向 B 发送信息之后,B 就可以确认自己的发信能力没有问题。 +
+ +如上图所示,三次握手流程如下: + +1. 第一次握手 - 客户端向服务端发送带有 SYN 标志的数据包。 +2. 第二次握手 - 服务端向客户端发送带有 SYN/ACK 标志的数据包。 +3. 第三次握手 - 客户端向服务端发送带有带有 ACK 标志的数据包。 + +至此,TCP 三次握手完成,客户端与服务端已建立双向连接。 + +> 💡 说明:SYN 为 synchronize 的缩写,ACK 为 acknowledgment 的缩写。 + +(3)为什么需要三次握手? + +为了便于说明,假设客户端为 A, 服务端为 B。 + +1. 第一次握手,A 向 B 发同步消息。B 收到消息后,B 认为:A 发消息没问题;B 收消息没问题。 +2. 第二次握手,B 向 A 发同步消息和确认消息。A 收到消息后,A 认为:A 发消息、收消息都没问题;B 发消息、收消息都没问题。**但是,此时 B 不确定自己发消息是否没问题**,所以就需要第三次握手。 +3. 第三次握手,A 向 B 发确认消息。B 收到消息后。B 认为:B 发消息没问题。 ### 四次挥手 -1. 第一次挥手 - client 发送一个 **FIN(M)** 包,此时 client 进入 `FIN-WAIT-1` 状态,这表明 client 已经没有数据要发送了。 -2. 第二次挥手 - server 收到了 client 发来的 FIN(M)包后,向 client 发回一个 **ACK(M+1)** 包,此时 server 进入 `CLOSE-WAIT` 状态,client 进入 `FIN-WAIT-2` 状态。 -3. 第三次挥手 - server 向 client 发送 **FIN(N)** 包,请求关闭连接,同时 server 进入 `LAST-ACK` 状态。 -4. 第四次挥手 - client 收到 server 发送的 **FIN(N)** 包,进入 `TIME-WAIT` 状态。向 server 发送 **ACK(N+1)** 包,server 收到 client 的 **ACK(N+1)** 包以后,进入 `CLOSE` 状态;client 等待一段时间还没有得到回复后判断 server 已正式关闭,进入 `CLOSE` 状态。 +(1)四次挥手有什么用? + +- 四次挥手负责断开 TCP 连接。 -## TCP 滑动窗口 +(2)什么是四次挥手? + +如上图所示,四次挥手流程如下: + +
+ +1. 第一次挥手 - 客户端向服务端发送一个 FIN 包,用来关闭客户端到服务端的数据传送。 +2. 第二次挥手 - 服务端收到这个 FIN 包,向客户端发送一个 ACK 包,确认序号为收到的序号加 1。和 SYN 一样,一个 FIN 将占用一个序号。 +3. 第三次挥手 - 服务端关闭与客户端的连接,向客户端发送一个 FIN 包。 +4. 第四次挥手 - 客户端向服务端发送 ACK 包,并将确认序号设置为收到序号加 1。 + +(3)为什么建立连接是三次握手,关闭连接确是四次挥手呢? + +- 建立连接的时候, 服务器在 LISTEN 状态下,收到建立连接请求的 SYN 报文后,把 ACK 和 SYN 放在一个报文里发送给客户端。 +- 而关闭连接时,服务器收到对方的 FIN 报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以己方可以立即关闭,也可以发送一些数据给对方后,再发送 FIN 报文给对方来表示同意现在关闭连接,因此,己方 ACK 和 FIN 一般都会分开发送,从而导致多了一次。 + +## 滑动窗口 + +什么是滑动窗口? + +**滑动窗口是 TCP 的一种控制网络流量的技术。** **TCP 必需要解决的可靠传输以及包乱序(reordering)的问题**,所以,TCP 必需要知道网络实际的数据处理带宽或是数据处理速度,这样才不会引起网络拥塞,导致丢包。 -所以,TCP 引入了一些技术和设计来做网络流控,Sliding Window 是其中一个技术。 前面我们说过,**TCP 头里有一个字段叫 Window,又叫 Advertised-Window,这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据**。**于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来**。 为了说明滑动窗口,我们需要先看一下 TCP 缓冲区的一些数据结构 +TCP 头里有一个字段叫 Window,又叫 Advertised-Window,这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据。**于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来**。 -
+滑动窗口原理是什么? -- \#1 已收到 ack 确认的数据。 -- \#2 发还没收到 ack 的。 -- \#3 在窗口中还没有发出的(接收方还有空间)。 -- \#4 窗口以外的数据(接收方没空间) +
-下面是个滑动后的示意图(收到 36 的 ack,并发出了 46-51 的字节): +1. 已发送已确认 - 数据流中最早的字节已经发送并得到确认。这些数据是站在发送端的角度来看的。上图中的 31 个字节已经发送并确认。 +2. 已发送但尚未确认 - 已发送但尚未得到确认的字节。发送方在确认之前,不认为这些数据已经被处理。上图中的 32 ~ 45 字节为第 2 类。 +3. 未发送而接收方已 Ready - 设备尚未将数据发出 ,但接收方根据最近一次关于发送方一次要发送多少字节确认自己有足够空间。发送方会立即尝试发送。上图中的 46 ~ 51 字节为第 3 类。 +4. 未发送而接收方 Not Ready - 由于接收方 not ready,还不允许将这部分数据发出。上图中的 52 以后的字节为第 4 类。
+这张图片相对于上一张图片,滑动窗口偏移了 5 个字节,意味着有 5 个已发送的字节得到了确认。 + ## TCP 重传机制 TCP 要保证所有的数据包都可以到达,所以,必需要有重传机制。 -### 超时重传机制 +TCP 重传机制主要有两种: -一种是不回 ack,死等 3,当发送方发现收不到 3 的 ack 超时后,会重传 3。一旦接收方收到 3 后,会 ack 回 4——意味着 3 和 4 都收到了。 +- 超时重传机制 +- 快速重传机制 -但是,这种方式会有比较严重的问题,那就是因为要死等 3,所以会导致 4 和 5 即便已经收到了,而发送方也完全不知道发生了什么事,因为没有收到 Ack,所以,发送方可能会悲观地认为也丢了,所以有可能也会导致 4 和 5 的重传。 +### 超时重传机制 -对此有两种选择: +超时重传机制是指:发送数据包在一定的时间周期内没有收到相应的 ACK,等待一定的时间,超时之后就认为这个数据包丢失,就会重新发送。这个等待时间被称为 RTO(Retransmission TimeOut),即重传超时时间。 -1. 一种是仅重传 timeout 的包。也就是第 3 份数据。 -2. 另一种是重传 timeout 后所有的数据,也就是第 3,4,5 这三份数据。 +没有确认的数据包不会从窗口中移走,定时器在重传时间到期内,每个片段的位置不变。 -这两种方式有好也有不好。第一种会节省带宽,但是慢,第二种会快一点,但是会浪费带宽,也可能会有无用功。但总体来说都不好。因为都在等 timeout,timeout 可能会很长。 +这种机制的重点是 RTO 的设置: -### 快速重传机制 +- RTO 设长了,重发就慢,丢了老半天才重发,没有效率,性能差; +- RTO 设短了,会导致可能并没有丢就重发。于是重发的就快,会增加网络拥塞,导致更多的超时,更多的超时导致更多的重发 -于是,TCP 引入了一种叫 Fast Retransmit 的算法,不以时间驱动,而以数据驱动重传。也就是说,如果,包没有连续到达,就 ack 最后那个可能被丢了的包,如果发送方连续收到 3 次相同的 ack,就重传。Fast Retransmit 的好处是不用等 timeout 了再重传。 +### 快速重传机制 -比如:如果发送方发出了 1,2,3,4,5 份数据,第一份先到送了,于是就 ack 回 2,结果 2 因为某些原因没收到,3 到达了,于是还是 ack 回 2,后面的 4 和 5 都到了,但是还是 ack 回 2,因为 2 还是没有收到,于是发送端收到了三个 ack=2 的确认,知道了 2 还没有到,于是就马上重转 2。然后,接收端收到了 2,此时因为 3,4,5 都收到了,于是 ack 回 6。 +快速重传机制,实现了另外的一种丢包评定标准,即如果连续收到 3 次重复 ACK,发送方就认为这个 seq 的包丢失了,立刻进行重传。 -Fast Retransmit 只解决了一个问题,就是 timeout 的问题,它依然面临一个艰难的选择,就是,是重传之前的一个还是重传所有的问题。对于上面的示例来说,是重传#2 呢还是重传#2,#3,#4,#5 呢?因为发送端并不清楚这连续的 3 个 ack(2)是谁传回来的?也许发送端发了 20 份数据,是#6,#10,#20 传来的呢。这样,发送端很有可能要重传从 2 到 20 的这堆数据(这就是某些 TCP 的实际的实现)。可见,这是一把双刃剑。 +当接收方收到乱序片段时,需要重复发送 ACK。 ## 参考资料 - [TCP 的那些事儿(上)](https://coolshell.cn/articles/11564.html) - [TCP 的那些事儿(下)](https://coolshell.cn/articles/11609.html) - [图解 TCP 三次握手与四次分手](https://juejin.im/post/5a7835a46fb9a063606eb801) +- [TCP 的三次握手与四次挥手(详解+动图)](https://blog.csdn.net/qzcsu/article/details/72861891) +- [TCP 详解](https://blog.csdn.net/sinat_36629696/article/details/80740678) diff --git a/docs/network/websocket.md b/docs/network/websocket.md index 0475b1ed..69625620 100644 --- a/docs/network/websocket.md +++ b/docs/network/websocket.md @@ -16,11 +16,11 @@ WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工 这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数 Web 应用程序将通过频繁的异步 JavaScript 和 XML(AJAX)请求实现长轮询。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。 -
+
因此,工程师们一直在思考,有没有更好的方法。WebSocket 就是这样发明的。WebSocket 连接允许客户端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端。WebSocket 只需要建立一次连接,就可以一直保持连接状态。这相比于轮询方式的不停建立连接显然效率要大大提高。 -
+
### WebSocket 如何工作? From 180aefdee0af843a19f61bd80e8624fe695d8e44 Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Sun, 2 Jun 2019 22:32:26 +0800 Subject: [PATCH 27/75] update docs --- ...03\345\274\217\346\236\266\346\236\204.md" | 12 +++---- ...66\346\236\204\346\246\202\350\277\260.md" | 20 ++++++------ ...51\346\200\247\346\236\266\346\236\204.md" | 10 +++--- ...51\345\261\225\346\236\266\346\236\204.md" | 2 +- ...57\347\224\250\346\236\266\346\236\204.md" | 4 +-- ...47\350\203\275\346\236\266\346\236\204.md" | 4 +-- docs/distributed/mq/kafka-advanced.md | 24 +++++++------- docs/distributed/mq/kafka-basics.md | 6 ++-- docs/distributed/mq/kafka/kafka-advanced.md | 24 +++++++------- docs/distributed/mq/kafka/kafka-basics.md | 6 ++-- docs/distributed/mq/rocketmq-advanced.md | 4 +-- docs/distributed/mq/rocketmq-basics.md | 2 +- docs/distributed/rpc/dubbo.md | 14 ++++---- docs/distributed/rpc/zookeeper-advanced.md | 10 +++--- ...03\345\274\217\345\216\237\347\220\206.md" | 30 ++++++++--------- ...00\346\234\257\345\256\236\347\216\260.md" | 32 +++++++++---------- ...57\351\235\242\350\257\225\351\242\230.md" | 6 ++-- docs/server/tomcat.md | 32 +++++++++---------- docs/standalone/orm/mybatis.md | 12 +++---- docs/standalone/security/shiro.md | 6 ++-- 20 files changed, 130 insertions(+), 130 deletions(-) diff --git "a/docs/architecture/\345\210\206\345\270\203\345\274\217\346\236\266\346\236\204.md" "b/docs/architecture/\345\210\206\345\270\203\345\274\217\346\236\266\346\236\204.md" index 53cc6454..83b07707 100644 --- "a/docs/architecture/\345\210\206\345\270\203\345\274\217\346\236\266\346\236\204.md" +++ "b/docs/architecture/\345\210\206\345\270\203\345\274\217\346\236\266\346\236\204.md" @@ -59,13 +59,13 @@ tags: #### 消息队列框架
- +
#### 消息队列原理
- +
### 服务化 @@ -81,13 +81,13 @@ tags: #### 服务化框架
- +
#### 服务化原理
- +
#### 服务治理 @@ -109,13 +109,13 @@ tags: #### 服务总线框架
- +
#### 服务总线原理
- +
## 分布式架构的通信模式 diff --git "a/docs/architecture/\345\244\247\345\236\213\347\275\221\347\253\231\346\236\266\346\236\204\346\246\202\350\277\260.md" "b/docs/architecture/\345\244\247\345\236\213\347\275\221\347\253\231\346\236\266\346\236\204\346\246\202\350\277\260.md" index 2cdbde7c..f5b36948 100644 --- "a/docs/architecture/\345\244\247\345\236\213\347\275\221\347\253\231\346\236\266\346\236\204\346\246\202\350\277\260.md" +++ "b/docs/architecture/\345\244\247\345\236\213\347\275\221\347\253\231\346\236\266\346\236\204\346\246\202\350\277\260.md" @@ -55,7 +55,7 @@ 描述:通常服务器操作系统使用 linux,应用程序使用 PHP 开发,然后部署在 Apache 上,数据库使用 Mysql,通俗称为 LAMP。汇集各种免费开源软件以及一台廉价服务器就可以开始系统的发展之路了。
- +
### 2.2. 应用服务和数据服务分离 @@ -67,7 +67,7 @@ 描述:三台服务器对性能要求各不相同:应用服务器要处理大量业务逻辑,因此需要更快更强大的 CPU;数据库服务器需要快速磁盘检索和数据缓存,因此需要更快的硬盘和更大的内存;文件服务器需要存储大量文件,因此需要更大容量的硬盘。
- +
### 2.3. 使用缓存改善性能 @@ -79,7 +79,7 @@ 描述:缓存分为两种:应用服务器上的本地缓存和分布式缓存服务器上的远程缓存,本地缓存访问速度更快,但缓存数据量有限,同时存在与应用程序争用内存的情况。分布式缓存可以采用集群方式,理论上可以做到不受内存容量限制的缓存服务。
- +
### 2.4. 使用应用服务器集群 @@ -91,7 +91,7 @@ 描述:使用集群是系统解决高并发、海量数据问题的常用手段。通过向集群中追加资源,提升系统的并发处理能力,使得服务器的负载压力不再成为整个系统的瓶颈。
- +
### 2.5. 数据库读写分离 @@ -103,7 +103,7 @@ 描述:应用服务器在写操作的时候,访问主数据库,主数据库通过主从复制机制将数据更新同步到从数据库。这样当应用服务器在读操作的时候,访问从数据库获得数据。为了便于应用程序访问读写分离后的数据库,通常在应用服务器端使用专门的数据访问模块,使数据库读写分离的对应用透明。
- +
### 2.6. 反向代理和 CDN 加速 @@ -115,7 +115,7 @@ 描述:CDN 和反向代理的基本原理都是缓存,区别在于 CDN 部署在网络提供商的机房,使用户在请求网站服务时,可以从距离自己最近的网络提供商机房获取数据;而反向代理则部署在网站的中心机房,当用户请求到达中心机房后,首先访问的服务器时反向代理服务器,如果反向代理服务器中缓存着用户请求的资源,就将其直接返回给用户。
- +
### 2.7. 分布式文件系统和分布式数据库 @@ -127,7 +127,7 @@ 描述:分布式数据库是数据库拆分的最后方法,只有在单表数据规模非常庞大的时候才使用。不到不得已时,更常用的数据库拆分手段是业务分库,将不同的业务数据库部署在不同的物理服务器上。
- +
### 2.8. 使用 NoSQL 和搜索引擎 @@ -139,7 +139,7 @@ 描述:NoSQL 数据库及搜索引擎对可伸缩的分布式特性具有更好的支持。应用服务器通过统一数据访问模块访问各种数据,减轻应用程序管理诸多数据源的麻烦。
- +
### 2.9. 业务拆分 @@ -155,7 +155,7 @@ 横向拆分:将复用的业务拆分出来,独立部署为分布式服务,新增业务只需要调用这些分布式服务横向拆分需要识别可复用的业务,设计服务接口,规范服务依赖关系。
- +
### 2.10. 分布式服务 @@ -165,7 +165,7 @@ 特征:**公共业务提取出来,独立部署。由这些可复用的业务连接数据库,通过分布式服务提供共用业务服务。**
- +
## 3. 大型网站架构模式 diff --git "a/docs/architecture/\347\275\221\347\253\231\347\232\204\344\274\270\347\274\251\346\200\247\346\236\266\346\236\204.md" "b/docs/architecture/\347\275\221\347\253\231\347\232\204\344\274\270\347\274\251\346\200\247\346\236\266\346\236\204.md" index 5cff88d0..06d6a086 100644 --- "a/docs/architecture/\347\275\221\347\253\231\347\232\204\344\274\270\347\274\251\346\200\247\346\236\266\346\236\204.md" +++ "b/docs/architecture/\347\275\221\347\253\231\347\232\204\344\274\270\347\274\251\346\200\247\346\236\266\346\236\204.md" @@ -37,7 +37,7 @@ ### 2.1. HTTP 重定向负载均衡
- +
利用 HTTP 重定向协议实现负载均衡。 @@ -47,7 +47,7 @@ ### 2.2. DNS 域名解析负载均衡
- +
利用 DNS 处理域名解析请求的同时进行负载均衡处理的一种方案。 @@ -75,7 +75,7 @@ DNS 域名解析负载均衡的缺点: ### 2.3. 反向代理负载均衡
- +
大多数反向代理服务器同时提供反向代理和负载均衡的功能。 @@ -85,7 +85,7 @@ DNS 域名解析负载均衡的缺点: ### 2.4. IP 负载均衡
- +
在网络层通过修改请求目标地址进行负载均衡。负载均衡服务器(网关服务器)在操作系统内核获取网络数据包,根据负载均衡算法计算得到一台真实 Web 服务器 10.0.0.1,然后将目的 IP 地址修改为 10.0.0.1,不需要通过用户进程。真实 Web 服务器处理完成后,响应数据包回到负载均衡服务器,负载均衡服务器再将数据包原地址修改为自身的 IP 地址(114.100.80.10)发送给浏览器。 @@ -95,7 +95,7 @@ IP 负载均衡在内核完成数据分发,所以处理性能优于反向代 ### 2.5. 数据链路层负载均衡
- +
数据链路层负载均衡是指在通信协议的数据链路层修改 mac 地址进行负载均衡。 diff --git "a/docs/architecture/\347\275\221\347\253\231\347\232\204\345\217\257\346\211\251\345\261\225\346\236\266\346\236\204.md" "b/docs/architecture/\347\275\221\347\253\231\347\232\204\345\217\257\346\211\251\345\261\225\346\236\266\346\236\204.md" index e871da9f..3aa85e08 100644 --- "a/docs/architecture/\347\275\221\347\253\231\347\232\204\345\217\257\346\211\251\345\261\225\346\236\266\346\236\204.md" +++ "b/docs/architecture/\347\275\221\347\253\231\347\232\204\345\217\257\346\211\251\345\261\225\346\236\266\346\236\204.md" @@ -37,7 +37,7 @@ 消息生产者应用程序通过远程访问接口将消息推送给消息队列服务器,消息队列服务器将消息写入本地内存队列后立即返回成功响应给消息生产者。消息队列服务器根据消息订阅列表查找订阅该消息的消息消费者应用程序,将消息队列中的消息按照先进先出(FIFO)的原则将消息通过远程通信接口发送给消息消费者程序。
- +
在伸缩性方面,由于消息队列服务器上的数据可以看作是即时处理的,因此类似于无状态的服务器,伸缩性设计比较简单。将新服务器加入分布式消息队列集群中,通知生产者服务器更改消息队列服务器列表即可。 diff --git "a/docs/architecture/\347\275\221\347\253\231\347\232\204\351\253\230\345\217\257\347\224\250\346\236\266\346\236\204.md" "b/docs/architecture/\347\275\221\347\253\231\347\232\204\351\253\230\345\217\257\347\224\250\346\236\266\346\236\204.md" index b134583c..e130015b 100644 --- "a/docs/architecture/\347\275\221\347\253\231\347\232\204\351\253\230\345\217\257\347\224\250\346\236\266\346\236\204.md" +++ "b/docs/architecture/\347\275\221\347\253\231\347\232\204\351\253\230\345\217\257\347\224\250\346\236\266\346\236\204.md" @@ -90,7 +90,7 @@ Session 复制是指应用服务器开发 Web 容器的 Session 复制功能, 分布式系统不可能同时满足一致性(C:Consistency)、可用性(A:Availability)和分区容忍性(P:Partition Tolerance),最多只能同时满足其中两项。
- +
#### 可用性 @@ -137,7 +137,7 @@ Session 复制是指应用服务器开发 Web 容器的 Session 复制功能, #### 失效确认
- +
判断服务器宕机的手段有两种:心跳检测和访问失败报告。 diff --git "a/docs/architecture/\347\275\221\347\253\231\347\232\204\351\253\230\346\200\247\350\203\275\346\236\266\346\236\204.md" "b/docs/architecture/\347\275\221\347\253\231\347\232\204\351\253\230\346\200\247\350\203\275\346\236\266\346\236\204.md" index 00034034..5da22108 100644 --- "a/docs/architecture/\347\275\221\347\253\231\347\232\204\351\253\230\346\200\247\350\203\275\346\236\266\346\236\204.md" +++ "b/docs/architecture/\347\275\221\347\253\231\347\232\204\351\253\230\346\200\247\350\203\275\346\236\266\346\236\204.md" @@ -72,7 +72,7 @@ CDN 一般缓存的是静态资源。 CDN 的本质仍然是一个缓存,而且将数据缓存在离用户最近的地方,使用户已最快速度获取数据,即所谓网络访问第一跳。
- +
### 2.3. 反向代理 @@ -80,7 +80,7 @@ CDN 的本质仍然是一个缓存,而且将数据缓存在离用户最近的 传统代理服务器位于浏览器一侧,代理浏览器将 HTTP 请求发送到互联网上,而反向代理服务器位于网站机房一侧,代理网站服务器接收 HTTP 请求。
- +
反向代理服务器可以配置缓存功能加速 Web 请求,当用户第一次访问静态内容时,静态内容就会被缓存在反向代理服务器上。 diff --git a/docs/distributed/mq/kafka-advanced.md b/docs/distributed/mq/kafka-advanced.md index 1c4d343d..f9013e2a 100644 --- a/docs/distributed/mq/kafka-advanced.md +++ b/docs/distributed/mq/kafka-advanced.md @@ -100,7 +100,7 @@ Message Queue 的通信模式: ### 1.8. Kafka 核心 API
- +
- Producer - 允许应用程序将记录流发布到一个或多个 Kafka Topic。 @@ -117,7 +117,7 @@ Topic 是一个目录名,它保存着发布记录。kafka 的 Topic 始终是 在 Kafka 中,任意一个 Topic 维护一个 Partition 的日志,类似下图:
- +
每个 Partition 都是一个有序的,不可变的记录序列,不断追加到结构化的提交日志中。Partition 中的记录每个分配一个连续的 id 号,称为偏移量,用于唯一标识 Partition 内的每条记录。 @@ -125,7 +125,7 @@ Topic 是一个目录名,它保存着发布记录。kafka 的 Topic 始终是 Kafka 集群持久化保存(使用可配置的保留期限)所有发布记录——无论它们是否被消费。例如,如果保留期限被设置为两天,则在记录发布后的两天之内,它都可以被消费,超过时间后将被丢弃以释放空间。
- +
实际上,保留在每个 Consumer 基础上的唯一元数据是该 Consumer 在日志中的抵消或位置。这个偏移量是由 Consumer 控制的:Consumer 通常会在读取记录时线性地推进其偏移量,但实际上,由于位置由 Consumer 控制,因此它可以按照喜欢的任何顺序消费记录。 @@ -148,7 +148,7 @@ Producer 将消息(记录)发送到 Kafka 节点(Broker),消息由称 随着 Topic 变得日益庞大,它们会被分割成更小的 Partition 以提高性能和可伸缩性。Kafka 保证 Partition 内的所有消息按照它们出现的顺序排序。区分特定消息的方式是通过它的偏移量,您可以将它看作普通数组索引,每个新消息都会增加一个序列号在一个 Partition 中。
- +
Kafka 遵循发布/订阅模式。这意味着 Kafka 不会跟踪 Kafka 读取哪些记录并删除它们,而是将它们存储一段时间(例如一天)或直到满足某个大小阈值。Consumer 自己对 Kafka 进行新的消息调查并说出他们想要阅读的记录。这使得他们可以按照自己的意愿递增/递减偏移量,从而能够重播和重新处理事件。 @@ -158,7 +158,7 @@ Kafka 集群持久化保存(使用可配置的保留期限)所有发布记 值得注意的是,Consumer 实际上是内部拥有一个或多个 Consumer 流程的 Consumer 群体。为了避免两个进程读两次相同的消息,每个 Partition 仅与每个组的一个 Consumer 进程相关联。
- +
## 3. 持久化 @@ -180,7 +180,7 @@ Kafka 实际上将其所有记录存储在磁盘中,并且不会将任何内 在任何时候,一个代理“拥有”一个分区,并且是应用程序通过该分区读写数据的节点。这被称为分区领导。它将它收到的数据复制到 N 个其他代理(称为追随者)。他们也存储数据,并准备在领导者节点死亡的情况下取代领导者。这就是典型的一主多从模式。
- +
生产者/消费者如何知道分区的领导者是谁? @@ -190,7 +190,7 @@ Kafka 实际上将其所有记录存储在磁盘中,并且不会将任何内 生产者和消费者都和 Zookeeper 连接并通信。Kafka 一直在摆脱这种耦合,自 0.8 和 0.9 版分别开始,客户端直接从 Kafka 经纪人那里获取元数据信息,他们自己与 Zookeeper 交谈。
- +
## 5. 流处理 @@ -202,7 +202,7 @@ Kafka 实际上将其所有记录存储在磁盘中,并且不会将任何内 此 API 旨在用于您自己的代码库中,它不在代理上运行。它与消费者 API 类似,可帮助您扩展多个应用程序的流处理工作(类似于消费者群体)。
- +
### 5.1. 无状态处理 @@ -218,13 +218,13 @@ Kafka 实际上将其所有记录存储在磁盘中,并且不会将任何内 Kafka 流可以用同样的方式解释 - 当从最终状态积累时的事件。这样的流聚合被保存在本地的 RocksDB 中(默认情况下),被称为 KTable。
- +
可以将表格视为流中每个键的最新值的快照。以同样的方式,流记录可以产生一个表,表更新可以产生一个更新日志流。
- +
### 5.2. 有状态处理 @@ -244,7 +244,7 @@ Kafka 流可以用同样的方式解释 - 当从最终状态积累时的事件 您甚至可以让远程数据库成为流的生产者,从而有效地广播更新日志,以便在本地重建表。
- +
## 6. Kafka 应用场景 @@ -257,7 +257,7 @@ Kafka 流可以用同样的方式解释 - 当从最终状态积累时的事件 这意味着它非常适合用作系统架构的核心,充当连接不同应用程序的集中介质。 Kafka 可以成为事件驱动架构的核心部分,并允许您真正将应用程序彼此分离。
- +
Kafka 允许您轻松分离不同(微)服务之间的通信。利用 Streams API,现在比以往更容易编写业务逻辑,丰富了 Kafka 主题数据以便服务消费。 diff --git a/docs/distributed/mq/kafka-basics.md b/docs/distributed/mq/kafka-basics.md index f8159ac7..4061b54a 100644 --- a/docs/distributed/mq/kafka-basics.md +++ b/docs/distributed/mq/kafka-basics.md @@ -62,13 +62,13 @@ producer 采用 push 方式向 broker 发送消息;customer 采用 pull 方式 - **Broker** - Kafka 集群包含一个或多个服务器,这种服务器被称为 broker
- +
#### 1.2.2. Topic 的存储结构
- +
- **Topic** - 每条发布到 Kafka 集群的消息都有一个类别,这个类别被称为 Topic。(物理上不同 Topic 的消息分开存储,逻辑上一个 Topic 的消息虽然保存于一个或多个 broker 上,但用户只需指定消息的 Topic 即可生产或消费数据而不必关心数据存于何处)。 @@ -83,7 +83,7 @@ producer 采用 push 方式向 broker 发送消息;customer 采用 pull 方式 注意: 必须要有一个 key,如果没有,则默认会生成一个 key,可以把 key 当做一个消息的标识,同一个 key 可能有多条数据。
- +
### 1.3. 小结 diff --git a/docs/distributed/mq/kafka/kafka-advanced.md b/docs/distributed/mq/kafka/kafka-advanced.md index 31018bc9..bc0826c3 100644 --- a/docs/distributed/mq/kafka/kafka-advanced.md +++ b/docs/distributed/mq/kafka/kafka-advanced.md @@ -112,7 +112,7 @@ Message Queue 的通信模式: ### 1.8. Kafka 核心 API
- +
- Producer - 允许应用程序将记录流发布到一个或多个 Kafka Topic。 @@ -129,7 +129,7 @@ Topic 是一个目录名,它保存着发布记录。kafka 的 Topic 始终是 在 Kafka 中,任意一个 Topic 维护一个 Partition 的日志,类似下图:
- +
每个 Partition 都是一个有序的,不可变的记录序列,不断追加到结构化的提交日志中。Partition 中的记录每个分配一个连续的 id 号,称为偏移量,用于唯一标识 Partition 内的每条记录。 @@ -137,7 +137,7 @@ Topic 是一个目录名,它保存着发布记录。kafka 的 Topic 始终是 Kafka 集群持久化保存(使用可配置的保留期限)所有发布记录——无论它们是否被消费。例如,如果保留期限被设置为两天,则在记录发布后的两天之内,它都可以被消费,超过时间后将被丢弃以释放空间。
- +
实际上,保留在每个 Consumer 基础上的唯一元数据是该 Consumer 在日志中的抵消或位置。这个偏移量是由 Consumer 控制的:Consumer 通常会在读取记录时线性地推进其偏移量,但实际上,由于位置由 Consumer 控制,因此它可以按照喜欢的任何顺序消费记录。 @@ -160,7 +160,7 @@ Producer 将消息(记录)发送到 Kafka 节点(Broker),消息由称 随着 Topic 变得日益庞大,它们会被分割成更小的 Partition 以提高性能和可伸缩性。Kafka 保证 Partition 内的所有消息按照它们出现的顺序排序。区分特定消息的方式是通过它的偏移量,您可以将它看作普通数组索引,每个新消息都会增加一个序列号在一个 Partition 中。
- +
Kafka 遵循发布/订阅模式。这意味着 Kafka 不会跟踪 Kafka 读取哪些记录并删除它们,而是将它们存储一段时间(例如一天)或直到满足某个大小阈值。Consumer 自己对 Kafka 进行新的消息调查并说出他们想要阅读的记录。这使得他们可以按照自己的意愿递增/递减偏移量,从而能够重播和重新处理事件。 @@ -170,7 +170,7 @@ Kafka 集群持久化保存(使用可配置的保留期限)所有发布记 值得注意的是,Consumer 实际上是内部拥有一个或多个 Consumer 流程的 Consumer 群体。为了避免两个进程读两次相同的消息,每个 Partition 仅与每个组的一个 Consumer 进程相关联。
- +
## 3. 持久化 @@ -192,7 +192,7 @@ Kafka 实际上将其所有记录存储在磁盘中,并且不会将任何内 在任何时候,一个代理“拥有”一个分区,并且是应用程序通过该分区读写数据的节点。这被称为分区领导。它将它收到的数据复制到 N 个其他代理(称为追随者)。他们也存储数据,并准备在领导者节点死亡的情况下取代领导者。这就是典型的一主多从模式。
- +
生产者/消费者如何知道分区的领导者是谁? @@ -202,7 +202,7 @@ Kafka 实际上将其所有记录存储在磁盘中,并且不会将任何内 生产者和消费者都和 Zookeeper 连接并通信。Kafka 一直在摆脱这种耦合,自 0.8 和 0.9 版分别开始,客户端直接从 Kafka 经纪人那里获取元数据信息,他们自己与 Zookeeper 交谈。
- +
## 5. 流处理 @@ -214,7 +214,7 @@ Kafka 实际上将其所有记录存储在磁盘中,并且不会将任何内 此 API 旨在用于您自己的代码库中,它不在代理上运行。它与消费者 API 类似,可帮助您扩展多个应用程序的流处理工作(类似于消费者群体)。
- +
### 5.1. 无状态处理 @@ -230,13 +230,13 @@ Kafka 实际上将其所有记录存储在磁盘中,并且不会将任何内 Kafka 流可以用同样的方式解释 - 当从最终状态积累时的事件。这样的流聚合被保存在本地的 RocksDB 中(默认情况下),被称为 KTable。
- +
可以将表格视为流中每个键的最新值的快照。以同样的方式,流记录可以产生一个表,表更新可以产生一个更新日志流。
- +
### 5.2. 有状态处理 @@ -256,7 +256,7 @@ Kafka 流可以用同样的方式解释 - 当从最终状态积累时的事件 您甚至可以让远程数据库成为流的生产者,从而有效地广播更新日志,以便在本地重建表。
- +
## 6. Kafka 应用场景 @@ -269,7 +269,7 @@ Kafka 流可以用同样的方式解释 - 当从最终状态积累时的事件 这意味着它非常适合用作系统架构的核心,充当连接不同应用程序的集中介质。 Kafka 可以成为事件驱动架构的核心部分,并允许您真正将应用程序彼此分离。
- +
Kafka 允许您轻松分离不同(微)服务之间的通信。利用 Streams API,现在比以往更容易编写业务逻辑,丰富了 Kafka 主题数据以便服务消费。 diff --git a/docs/distributed/mq/kafka/kafka-basics.md b/docs/distributed/mq/kafka/kafka-basics.md index d1c4b979..c93aaa2e 100644 --- a/docs/distributed/mq/kafka/kafka-basics.md +++ b/docs/distributed/mq/kafka/kafka-basics.md @@ -74,13 +74,13 @@ producer 采用 push 方式向 broker 发送消息;customer 采用 pull 方式 - **Broker** - Kafka 集群包含一个或多个服务器,这种服务器被称为 broker
- +
#### 1.2.2. Topic 的存储结构
- +
- **Topic** - 每条发布到 Kafka 集群的消息都有一个类别,这个类别被称为 Topic。(物理上不同 Topic 的消息分开存储,逻辑上一个 Topic 的消息虽然保存于一个或多个 broker 上,但用户只需指定消息的 Topic 即可生产或消费数据而不必关心数据存于何处)。 @@ -95,7 +95,7 @@ producer 采用 push 方式向 broker 发送消息;customer 采用 pull 方式 注意: 必须要有一个 key,如果没有,则默认会生成一个 key,可以把 key 当做一个消息的标识,同一个 key 可能有多条数据。
- +
### 1.3. 小结 diff --git a/docs/distributed/mq/rocketmq-advanced.md b/docs/distributed/mq/rocketmq-advanced.md index e09138dc..763cbdba 100644 --- a/docs/distributed/mq/rocketmq-advanced.md +++ b/docs/distributed/mq/rocketmq-advanced.md @@ -18,7 +18,7 @@ ## 架构
- +
- + ### Producer diff --git a/docs/distributed/mq/rocketmq-basics.md b/docs/distributed/mq/rocketmq-basics.md index 955b6c8f..5f1566e2 100644 --- a/docs/distributed/mq/rocketmq-basics.md +++ b/docs/distributed/mq/rocketmq-basics.md @@ -31,7 +31,7 @@ RocketMQ 被阿里巴巴捐赠给 Apache,成为 Apache 的孵化项目。 ### 核心概念
- +
RocketMQ 有以下核心概念: diff --git a/docs/distributed/rpc/dubbo.md b/docs/distributed/rpc/dubbo.md index 81261c00..32fbe575 100644 --- a/docs/distributed/rpc/dubbo.md +++ b/docs/distributed/rpc/dubbo.md @@ -163,7 +163,7 @@ Dubbo 支持多种配置方式: - Properties 最后,相当于缺省值,只有 XML 没有配置时,dubbo.properties 的相应配置项才会生效,通常用于共享公共配置,比如应用名。
- +
#### xml 配置 @@ -223,7 +223,7 @@ dubbo.registry.address=10.20.153.10:9090 #### 配置之间的关系
- +
#### 配置覆盖关系 @@ -236,7 +236,7 @@ dubbo.registry.address=10.20.153.10:9090 其中,服务提供方配置,通过 URL 经由注册中心传递给消费方。
- +
## Dubbo 支持的协议 @@ -271,7 +271,7 @@ dubbo 协议是 dubbo 默认的协议。dubbo 协议采用单一长连接和 NIO ### 集群容错
- +
- **Failover** - 失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries="2" 来设置重试次数(不含第一次)。 @@ -353,7 +353,7 @@ registry.register(URL.valueOf("override://0.0.0.0/com.foo.BarService?category=co 在开发及测试环境下,经常需要绕过注册中心,只测试指定服务提供者,这时候可能需要点对点直连,点对点直联方式,将以服务接口为单位,忽略注册中心的提供者列表,A 接口配置点对点,不影响 B 接口从注册中心获取列表。
- +
配置方式: @@ -489,7 +489,7 @@ override://0.0.0.0/com.foo.BarService?category=configurators&dynamic=false&appli ## Dubbo 架构
- +
节点角色: @@ -514,7 +514,7 @@ override://0.0.0.0/com.foo.BarService?category=configurators&dynamic=false&appli ### 整体设计
- +
图例说明: diff --git a/docs/distributed/rpc/zookeeper-advanced.md b/docs/distributed/rpc/zookeeper-advanced.md index 6fbe0b64..f105a04e 100644 --- a/docs/distributed/rpc/zookeeper-advanced.md +++ b/docs/distributed/rpc/zookeeper-advanced.md @@ -37,7 +37,7 @@ ZooKeeper 作为一个分布式的服务框架,主要用来解决分布式集群中应用系统的一致性问题,它能提供基于类似于文件系统的目录节点树方式的数据存储,但是 ZooKeeper 并不是用来专门存储数据的,它的作用主要是用来维护和监控你存储的数据的状态变化。通过监控这些数据状态的变化,从而可以达到基于数据的集群管理。
- +
### 1.2. ZooKeeper 提供了什么? @@ -73,7 +73,7 @@ ZooKeeper 的核心是原子广播,这个机制保证了各个 Server 之间 ZooKeeper 会维护一个具有层次关系的数据结构,它非常类似于一个标准的文件系统,如下图所示:
- +
ZooKeeper 这种数据结构有如下这些特点: @@ -111,7 +111,7 @@ Name Service 已经是 ZooKeeper 内置的功能,你只要调用 ZooKeeper 的 像这样的配置信息完全可以交给 ZooKeeper 来管理,将配置信息保存在 ZooKeeper 的某个目录节点中,然后将所有需要修改的应用机器监控配置信息的状态,一旦配置信息发生变化,每台应用机器就会收到 ZooKeeper 的通知,然后从 ZooKeeper 获取新的配置信息应用到系统中。
- +
### 4.3. 集群管理(Group Membership) @@ -125,7 +125,7 @@ ZooKeeper 不仅能够帮你维护当前的集群中机器的服务状态,而 ZooKeeper 如何实现 Leader Election,也就是选出一个 Master Server。和前面的一样每台 Server 创建一个 EPHEMERAL 目录节点,不同的是它还是一个 SEQUENTIAL 目录节点,所以它是个 EPHEMERAL_SEQUENTIAL 目录节点。之所以它是 EPHEMERAL_SEQUENTIAL 目录节点,是因为我们可以给每台 Server 编号,我们可以选择当前是最小编号的 Server 为 Master,假如这个最小编号的 Server 死去,由于是 EPHEMERAL 节点,死去的 Server 对应的节点也被删除,所以当前的节点列表中又出现一个最小编号的节点,我们就选择这个节点为当前 Master。这样就实现了动态选择 Master,避免了传统意义上单 Master 容易出现单点故障的问题。
- +
### 4.4. 分布式锁 @@ -179,7 +179,7 @@ ZooKeeper 作为一个集群提供一致的数据服务,自然,它要在所 ZooKeeper 选举流程基于 Paxos 算法。
- +
1. 选举线程由当前 Server 发起选举的线程担任,其主要功能是对投票结果进行统计,并选出推荐的 Server; diff --git "a/docs/distributed/\345\210\206\345\270\203\345\274\217\345\216\237\347\220\206.md" "b/docs/distributed/\345\210\206\345\270\203\345\274\217\345\216\237\347\220\206.md" index d00459d3..68502a9d 100644 --- "a/docs/distributed/\345\210\206\345\270\203\345\274\217\345\216\237\347\220\206.md" +++ "b/docs/distributed/\345\210\206\345\270\203\345\274\217\345\216\237\347\220\206.md" @@ -137,7 +137,7 @@ Distributed Hash Table(DHT):对于哈希空间 [0, 2n-1],将 分布式系统不可能同时满足一致性(C:Consistency)、可用性(A:Availability)和分区容忍性(P:Partition Tolerance),最多只能同时满足其中两项。
- +
#### 一致性 @@ -176,7 +176,7 @@ BASE 是基本可用(Basically Available)、软状态(Soft State)和最 BASE 理论是对 CAP 中一致性和可用性权衡的结果,它的理论的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。
- +
#### 基本可用 @@ -214,7 +214,7 @@ ACID 要求强一致性,通常运用在传统的数据库系统上。而 BASE 协调者询问参与者事务是否执行成功,参与者发回事务执行结果。
- +
##### 提交阶段 @@ -222,7 +222,7 @@ ACID 要求强一致性,通常运用在传统的数据库系统上。而 BASE 如果事务在每个参与者上都执行成功,事务协调者发送通知让参与者提交事务;否则,协调者发送通知让参与者回滚事务。
- +
需要注意的是,在准备阶段,参与者执行了事务,但是还未提交。只有在提交阶段接收到协调者发来的通知后,才进行提交或者回滚。 @@ -277,7 +277,7 @@ ACID 要求强一致性,通常运用在传统的数据库系统上。而 BASE 3. 在分布式事务操作的另一方从消息队列中读取一个消息,并执行消息中的操作。
- +
这种方案遵循 BASE 理论,采用的是最终一致性。 @@ -376,25 +376,25 @@ _注:此处 log 并非是指日志消息,而是各种事件的发生记录 - 下图表示一个分布式系统的最初阶段,此时只有 Follower,没有 Leader。Follower A 等待一个随机的竞选超时时间之后,没收到 Leader 发来的心跳包,因此进入竞选阶段。
- +
- 此时 A 发送投票请求给其它所有节点。
- +
- 其它节点会对请求进行回复,如果超过一半的节点回复了,那么该 Candidate 就会变成 Leader。
- +
- 之后 Leader 会周期性地发送心跳包给 Follower,Follower 接收到心跳包,会重新开始计时。
- +
#### 多个 Candidate 竞选 @@ -402,13 +402,13 @@ _注:此处 log 并非是指日志消息,而是各种事件的发生记录 - 如果有多个 Follower 成为 Candidate,并且所获得票数相同,那么就需要重新开始投票,例如下图中 Candidate B 和 Candidate D 都获得两票,因此需要重新开始投票。
- +
- 当重新开始投票时,由于每个节点设置的随机竞选超时时间不同,因此能下一次再次出现多个 Candidate 并获得同样票数的概率很低。
- +
#### 同步日志 @@ -416,25 +416,25 @@ _注:此处 log 并非是指日志消息,而是各种事件的发生记录 - 来自客户端的修改都会被传入 Leader。注意该修改还未被提交,只是写入日志中。
- +
- Leader 会把修改复制到所有 Follower。
- +
- Leader 会等待大多数的 Follower 也进行了修改,然后才将修改提交。
- +
- 此时 Leader 会通知的所有 Follower 让它们也提交修改,此时所有节点的值达成一致。
- +
## 6. 分布式缓存问题 diff --git "a/docs/distributed/\345\210\206\345\270\203\345\274\217\346\212\200\346\234\257\345\256\236\347\216\260.md" "b/docs/distributed/\345\210\206\345\270\203\345\274\217\346\212\200\346\234\257\345\256\236\347\216\260.md" index 7fed708d..0f1fddae 100644 --- "a/docs/distributed/\345\210\206\345\270\203\345\274\217\346\212\200\346\234\257\345\256\236\347\216\260.md" +++ "b/docs/distributed/\345\210\206\345\270\203\345\274\217\346\212\200\346\234\257\345\256\236\347\216\260.md" @@ -165,7 +165,7 @@ ZooKeeper 版本的分布式锁问题相对比较来说少。 缺点:当服务器节点宕机时,将丢失该服务器节点上的所有 Session。
- +
### 3.2. Session Replication @@ -175,7 +175,7 @@ ZooKeeper 版本的分布式锁问题相对比较来说少。 缺点:占用过多内存;同步过程占用网络带宽以及服务器处理器时间。
- +
### 3.3. Session Server @@ -185,7 +185,7 @@ ZooKeeper 版本的分布式锁问题相对比较来说少。 缺点:需要去实现存取 Session 的代码。
- +
## 4. 分布式存储 @@ -229,13 +229,13 @@ ZooKeeper 版本的分布式锁问题相对比较来说少。 轮询算法把每个请求轮流发送到每个服务器上。下图中,一共有 6 个客户端产生了 6 个请求,这 6 个请求按 (1, 2, 3, 4, 5, 6) 的顺序发送。最后,(1, 3, 5) 的请求会被发送到服务器 1,(2, 4, 6) 的请求会被发送到服务器 2。
- +
该算法比较适合每个服务器的性能差不多的场景,如果有性能存在差异的情况下,那么性能较差的服务器可能无法承担过大的负载(下图的 Server 2)。
- +
#### 加权轮询(Weighted Round Robbin) @@ -243,7 +243,7 @@ ZooKeeper 版本的分布式锁问题相对比较来说少。 加权轮询是在轮询的基础上,根据服务器的性能差异,为服务器赋予一定的权值。例如下图中,服务器 1 被赋予的权值为 5,服务器 2 被赋予的权值为 1,那么 (1, 2, 3, 4, 5) 请求会被发送到服务器 1,(6) 请求会被发送到服务器 2。
- +
#### 最少连接(least Connections) @@ -251,13 +251,13 @@ ZooKeeper 版本的分布式锁问题相对比较来说少。 由于每个请求的连接时间不一样,使用轮询或者加权轮询算法的话,可能会让一台服务器当前连接数过大,而另一台服务器的连接过小,造成负载不均衡。例如下图中,(1, 3, 5) 请求会被发送到服务器 1,但是 (1, 3) 很快就断开连接,此时只有 (5) 请求连接服务器 1;(2, 4, 6) 请求被发送到服务器 2,只有 (2) 的连接断开。该系统继续运行时,服务器 2 会承担过大的负载。
- +
最少连接算法就是将请求发送给当前最少连接数的服务器上。例如下图中,服务器 1 当前连接数最小,那么新到来的请求 6 就会被发送到服务器 1 上。
- +
#### 加权最少连接(Weighted Least Connection) @@ -265,7 +265,7 @@ ZooKeeper 版本的分布式锁问题相对比较来说少。 在最少连接的基础上,根据服务器的性能为每台服务器分配权重,再根据权重计算出每台服务器能处理的连接数。
- +
#### 随机算法(Random) @@ -273,7 +273,7 @@ ZooKeeper 版本的分布式锁问题相对比较来说少。 把请求随机发送到服务器上。和轮询算法类似,该算法比较适合服务器性能差不多的场景。
- +
#### 源地址哈希法 (IP Hash) @@ -284,7 +284,7 @@ ZooKeeper 版本的分布式锁问题相对比较来说少。 - 缺点:不利于集群扩展,后台服务器数量变更都会影响 hash 结果。可以采用一致性 Hash 改进。
- +
### 7.2. 实现 @@ -299,7 +299,7 @@ HTTP 重定向负载均衡服务器收到 HTTP 请求之后会返回服务器的 - 如果负载均衡器宕机,就无法访问该站点。
- +
#### DNS 重定向 @@ -311,7 +311,7 @@ HTTP 重定向负载均衡服务器收到 HTTP 请求之后会返回服务器的 - DNS 查找表可能会被客户端缓存起来,那么之后的所有请求都会被重定向到同一个服务器。
- +
#### 修改 MAC 地址 @@ -319,7 +319,7 @@ HTTP 重定向负载均衡服务器收到 HTTP 请求之后会返回服务器的 使用 LVS(Linux Virtual Server)这种链路层负载均衡器,根据负载情况修改请求的 MAC 地址。
- +
#### 修改 IP 地址 @@ -327,7 +327,7 @@ HTTP 重定向负载均衡服务器收到 HTTP 请求之后会返回服务器的 在网络层修改请求的目的 IP 地址。
- +
#### 代理自动配置 @@ -340,7 +340,7 @@ HTTP 重定向负载均衡服务器收到 HTTP 请求之后会返回服务器的 PAC 服务器是用来判断一个请求是否要经过代理。
- +
## 8. 资料 diff --git "a/docs/distributed/\345\210\206\345\270\203\345\274\217\346\212\200\346\234\257\351\235\242\350\257\225\351\242\230.md" "b/docs/distributed/\345\210\206\345\270\203\345\274\217\346\212\200\346\234\257\351\235\242\350\257\225\351\242\230.md" index e1771e10..2a1f3867 100644 --- "a/docs/distributed/\345\210\206\345\270\203\345\274\217\346\212\200\346\234\257\351\235\242\350\257\225\351\242\230.md" +++ "b/docs/distributed/\345\210\206\345\270\203\345\274\217\346\212\200\346\234\257\351\235\242\350\257\225\351\242\230.md" @@ -322,7 +322,7 @@ MQ 的常见问题有: ### 3.1. Dubbo 的实现过程?
- +
节点角色: @@ -371,7 +371,7 @@ MQ 的常见问题有: ### 3.3. Dubbo 集群容错策略 ? -![img](https://raw.githubusercontent.com/dunwu/JavaWeb/master/images/distributed/rpc/dubbo/dubbo%E9%9B%86%E7%BE%A4%E5%AE%B9%E9%94%99.jpg) +![img](https://gitee.com/turnon/javaweb/raw/master/images/distributed/rpc/dubbo/dubbo%E9%9B%86%E7%BE%A4%E5%AE%B9%E9%94%99.jpg) - **Failover** - 失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries="2" 来设置重试次数(不含第一次)。 - **Failfast** - 快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。 @@ -442,7 +442,7 @@ Protocol Buffer 的数据压缩效果好(即序列化后的数据量体积小 ZooKeeper 是一个分布式应用协调系统,已经用到了许多分布式项目中,用来完成统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等工作。
- +
1. 每个 Server 在内存中存储了一份数据; diff --git a/docs/server/tomcat.md b/docs/server/tomcat.md index ee3164d4..e49f3d28 100644 --- a/docs/server/tomcat.md +++ b/docs/server/tomcat.md @@ -104,7 +104,7 @@ tar -zxf apache-tomcat-8.5.24.tar.gz 启动后,访问 http://localhost:8080 ,可以看到 Tomcat 安装成功的测试页面。 -![tomcat.png](https://raw.githubusercontent.com/dunwu/JavaWeb/master/images/tools/tomcat/tomcat.png) +![tomcat.png](https://gitee.com/turnon/javaweb/raw/master/images/tools/tomcat/tomcat.png) ### 配置 @@ -357,7 +357,7 @@ public class SimpleTomcatServer { - 设置启动应用的端口、JVM 参数、启动浏览器等。 - 成功后,可以访问 http://localhost:8080/(当然,你也可以在 url 中设置上下文名称)。 -![](https://raw.githubusercontent.com/dunwu/JavaWeb/master/images/tools/tomcat/tomcat-intellij-run-config.png) +![](https://gitee.com/turnon/javaweb/raw/master/images/tools/tomcat/tomcat-intellij-run-config.png) > **说明** > @@ -370,7 +370,7 @@ public class SimpleTomcatServer { ### Tomcat 主要组件
- +
- **Server** - 指的就是整个 Tomcat 服 务器,包含多组服务,负责管理和 启动各个 Service,同时监听 8005 端口发过来的 shutdown 命令,用 于关闭整个容器。 @@ -387,7 +387,7 @@ public class SimpleTomcatServer { #### Container 组件
- +
- **Engine** - Servlet 的顶层容器,包含一 个或多个 Host 子容器; @@ -404,7 +404,7 @@ Tomcat 为了方便管理组件和容器的生命周期,定义了从创建、 比如执行初始化操作时,会判断当前状态是否 New,如果不是则抛出生命周期异常;是的 话则设置当前状态为 Initializing,并执行 initInternal 方法,由子类实现,方法执行成功则设置当 前状态为 Initialized,执行失败则设置为 Failed 状态;
- +
Tomcat 的生命周期管理引入了事件机制,在组件或容器的生命周期状态发生变化时会通 知事件监听器,监听器通过判断事件的类型来进行相应的操作。 @@ -419,7 +419,7 @@ Tomcat 各类容器的配置过程就是通过添加 listener 的方式来进行 #### Tomcat 的启动过程
- +
启动从 Tomcat 提供的 start.sh 脚本开始,shell 脚本会调用 Bootstrap 的 main 方法,实际 调用了 Catalina 相应的 load、start 方法。 @@ -486,7 +486,7 @@ ContextConfig 解析 web.xml 顺序: #### 请求处理过程
- +
1. 根据 server.xml 配置的指定的 connector 以及端口监听 http、或者 ajp 请求 @@ -497,7 +497,7 @@ ContextConfig 解析 web.xml 顺序: #### Pipeline 与 Valve
- +
Pipeline 可以理解为现实中的管道,Valve 为管道中的阀门,Request 和 Response 对象在管道中 经过各个阀门的处理和控制。 @@ -511,25 +511,25 @@ Valve 中主要的三个方法:setNext、getNext、invoke;valve 之间的关系 ### Connector
- +
#### 阻塞 IO
- +
#### 非阻塞 IO
- +
#### IO 多路复用
- +
阻塞与非阻塞的区别在于进行读操作和写操作的系统调用时,如果此时内核态没有数据可读或者没有缓冲空间可写时,是否阻塞。 @@ -539,7 +539,7 @@ IO 多路复用的好处在于可同时监听多个 socket 的可读和可写事 #### Tomcat 各类 Connector 对比
- +
- JIO:用 java.io 编写的 TCP 模块,阻塞 IO @@ -560,7 +560,7 @@ Apache Portable Runtime 是一个高度可移植的库,它是 Apache HTTP Serv **NIO 处理相关类**
- +
Poller 线程从 EventQueue 获取 PollerEvent,并执行 PollerEvent 的 run 方法,调用 Selector 的 select 方法,如果有可读的 Socket 则创建 Http11NioProcessor,放入到线程池中执行; @@ -593,7 +593,7 @@ Note: ### 异步 Servlet
- +
传统流程: @@ -603,7 +603,7 @@ Note: - 最后,根据处理的结果提交响应,Servlet 线程结束
- +
异步处理流程: diff --git a/docs/standalone/orm/mybatis.md b/docs/standalone/orm/mybatis.md index 5b998fd6..da254368 100644 --- a/docs/standalone/orm/mybatis.md +++ b/docs/standalone/orm/mybatis.md @@ -196,7 +196,7 @@ MyBatis 和数据库的交互有两种方式: 这是传统的传递 Statement Id 和查询参数给 SqlSession 对象,使用 SqlSession 对象完成和数据库的交互;MyBatis 提供了非常方便和简单的 API,供用户实现对数据库的增删改查数据操作,以及对数据库连接信息和 MyBatis 自身配置信息的维护操作。
- +
上述使用 MyBatis 的方法,是创建一个和数据库打交道的 SqlSession 对象,然后根据 Statement Id 和参数来操作数据库,这种方式固然很简单和实用,但是它不符合面向对象语言的概念和面向接口编程的编程习惯。由于面向接口的编程是面向对象的大趋势,MyBatis 为了适应这一趋势,增加了第二种使用 MyBatis 支持接口(Interface)调用方式。 @@ -206,7 +206,7 @@ MyBatis 和数据库的交互有两种方式: MyBatis 将配置文件中的每一个 `` 节点抽象为一个 Mapper 接口,而这个接口中声明的方法和跟 `` 节点中的 `` 节点相对应,即 `` 节点的 id 值为 Mapper 接口中的方法名称,parameterType 值表示 Mapper 对应方法的入参类型,而 resultMap 值则对应了 Mapper 接口表示的返回值类型或者返回结果集的元素类型。
- +
根据 MyBatis 的配置规范配置好后,通过 `SqlSession.getMapper(XXXMapper.class)` 方法,MyBatis 会根据相应的接口声明的方法信息,通过动态代理机制生成一个 Mapper 实例,我们使用 Mapper 接口的某一个方法时,MyBatis 会根据这个方法的方法名和参数类型,确定 Statement Id,底层还是通过`SqlSession.select("statementId",parameterObject);` 或者 `SqlSession.update("statementId",parameterObject);` 等等来实现对数据库的操作。 @@ -253,7 +253,7 @@ MyBatis 将数据缓存设计成两级结构,分为一级缓存、二级缓存 - **二级缓存是 Application 应用级别的缓存**,它的是生命周期很长,跟 Application 的声明周期一样,也就是说它的作用范围是整个 Application 应用。
- +
##### 一级缓存的工作机制 @@ -261,7 +261,7 @@ MyBatis 将数据缓存设计成两级结构,分为一级缓存、二级缓存 一级缓存是 Session 会话级别的,一般而言,一个 SqlSession 对象会使用一个 Executor 对象来完成会话操作,Executor 对象会维护一个 Cache 缓存,以提高查询性能。
- +
##### 二级缓存的工作机制 @@ -269,7 +269,7 @@ MyBatis 将数据缓存设计成两级结构,分为一级缓存、二级缓存 如上所言,一个 SqlSession 对象会使用一个 Executor 对象来完成会话操作,MyBatis 的二级缓存机制的关键就是对这个 Executor 对象做文章。如果用户配置了 `"cacheEnabled=true"`,那么 MyBatis 在为 SqlSession 对象创建 Executor 对象时,会对 Executor 对象加上一个装饰者:CachingExecutor,这时 SqlSession 使用 CachingExecutor 对象来完成操作请求。CachingExecutor 对于查询请求,会先判断该查询请求在 Application 级别的二级缓存中是否有缓存结果,如果有查询结果,则直接返回缓存结果;如果缓存中没有,再交给真正的 Executor 对象来完成查询操作,之后 CachingExecutor 会将真正 Executor 返回的查询结果放置到缓存中,然后在返回给用户。
- +
#### SQL 语句的配置方式 @@ -301,7 +301,7 @@ MyBatis 将数据缓存设计成两级结构,分为一级缓存、二级缓存 它们的关系如下图所示:
- +
## 配置 diff --git a/docs/standalone/security/shiro.md b/docs/standalone/security/shiro.md index b85ee7a7..86319159 100644 --- a/docs/standalone/security/shiro.md +++ b/docs/standalone/security/shiro.md @@ -29,7 +29,7 @@ tags: ### Shiro 功能

- +

- **Authentication** - 身份认证/登录,验证用户是不是拥有相应的身份; @@ -48,7 +48,7 @@ tags: ### Shiro 架构

- +

- **Subject** - 主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等;即一个抽象概念;所有 Subject 都绑定到 SecurityManager,与 Subject 的所有交互都会委托给 SecurityManager;可以把 Subject 认为是一个门面;SecurityManager 才是实际的执行者; @@ -56,7 +56,7 @@ tags: - **Realm** - 域,Shiro 从从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色/权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。

- +

- **Subject** - 主体,可以看到主体可以是任何可以与应用交互的“用户”; From 44661f14922e2d9883acc6531f73396425e651fd Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Tue, 4 Jun 2019 23:42:35 +0800 Subject: [PATCH 28/75] update docs --- README.md | 2 +- docs/README.md | 23 +- docs/distributed/README.md | 6 +- docs/distributed/cache/cache-interview.md | 571 ++++++++++++++++++++ docs/distributed/distributed-lock.md | 341 ++++++++++++ docs/distributed/distributed-session.md | 128 +++++ docs/distributed/distributed-transaction.md | 91 ++++ docs/distributed/mq/mq-interview.md | 375 +++++++++++++ docs/index.html | 2 +- 9 files changed, 1524 insertions(+), 15 deletions(-) create mode 100644 docs/distributed/cache/cache-interview.md create mode 100644 docs/distributed/distributed-lock.md create mode 100644 docs/distributed/distributed-session.md create mode 100644 docs/distributed/distributed-transaction.md create mode 100644 docs/distributed/mq/mq-interview.md diff --git a/README.md b/README.md index f8277fbf..4ceca8cb 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ > [主流技术](docs/standalone/),典型的技术如:SSM 框架、SSH 框架。 - MVC - - [spring-tutorial](https://dunwu.gitbooks.io/spring-tutorial/) - Spring 教程 + - [spring-tutorial](https://dunwu.github.io/spring-tutorial/) - Spring 教程 - [spring-boot-tutorial](https://dunwu.github.io/spring-boot-tutorial/) - Spring Boot 教程 - ORM - [Mybatis](docs/standalone/orm/mybatis.md) - 一个支持普通 SQL 查询,存储过程和高级映射的优秀持久层框架。 diff --git a/docs/README.md b/docs/README.md index 243abdb2..069f7fa5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -43,7 +43,7 @@ > [主流技术](standalone/),典型的技术如:SSM 框架、SSH 框架。 - MVC - - [spring-tutorial](https://dunwu.gitbooks.io/spring-tutorial/) - Spring 教程 + - [spring-tutorial](https://dunwu.github.io/spring-tutorial/) - Spring 教程 - [spring-boot-tutorial](https://dunwu.github.io/spring-boot-tutorial/) - Spring Boot 教程 - ORM - [Mybatis](standalone/orm/mybatis.md) - 一个支持普通 SQL 查询,存储过程和高级映射的优秀持久层框架。 @@ -56,26 +56,27 @@ - [分布式技术面试题](distributed/分布式技术面试题.md) -- [分布式缓存(CACHE)](distributed/cache) +- [缓存(CACHE)](distributed/cache) - [分布式缓存](distributed/cache/分布式缓存.md) - [Redis](distributed/cache/redis.md) - Memcached -- [分布式服务(RPC)](distributed/rpc) +- [微服务(RPC)](distributed/rpc) - [Dubbo](distributed/rpc/dubbo.md) - 基于 Java 开发的高性能 RPC 框架。 - [ZooKeeper 实战篇](distributed/rpc/zookeeper-basics.md) - [ZooKeeper 原理篇](distributed/rpc/zookeeper-advanced.md) -- [分布式消息队列(MQ)](distributed/mq) - - - 原理 - - [分布式消息队列](distributed/mq/分布式消息队列.md) - - Kafka - - [Kafka 实战篇](distributed/mq/kafka/kafka-basics.md) - - [Kafka 原理篇](distributed/mq/kafka/kafka-advanced.md) - - [Kafka Cheat Sheet](distributed/mq/kafka/kafka-cheat-sheet.md) +- [消息队列(MQ)](distributed/mq) + - [消息队列面试夺命连环问](distributed/mq/mq-interview.md) + - [分布式消息队列](distributed/mq/分布式消息队列.md) + - [Kafka 实战篇](distributed/mq/kafka/kafka-basics.md) + - [Kafka 原理篇](distributed/mq/kafka/kafka-advanced.md) + - [Kafka Cheat Sheet](distributed/mq/kafka/kafka-cheat-sheet.md) - [RocketMQ 实战篇](distributed/mq/rocketmq-basics.md) - [RocketMQ 原理篇](distributed/mq/rocketmq-basics.md) - [ActiveMQ 实战篇](distributed/mq/ActiveMQ.md) - RabbitMQ - 待补充。。。 +- [分布式锁](distributed/distributed-lock.md) +- [分布式会话](distributed/distributed-session.md) +- [分布式事务](distributed/distributed-transaction.md) ## 🕸 网络通信 diff --git a/docs/distributed/README.md b/docs/distributed/README.md index 70644ae5..430e2560 100644 --- a/docs/distributed/README.md +++ b/docs/distributed/README.md @@ -1,3 +1,5 @@ -# 分布式 +# 分布式系统 -> 分布式技术 \ No newline at end of file +- [分布式锁](distributed-lock.md) +- [分布式会话](distributed-session.md) +- [分布式事务](distributed-transaction.md) diff --git a/docs/distributed/cache/cache-interview.md b/docs/distributed/cache/cache-interview.md new file mode 100644 index 00000000..7693d5b0 --- /dev/null +++ b/docs/distributed/cache/cache-interview.md @@ -0,0 +1,571 @@ +# 缓存夺命连环问 + +## 为什么要用缓存? + +用缓存,主要有两个用途:**高性能**、**高并发**。 + +### 高性能 + +假设这么个场景,你有个操作,一个请求过来,吭哧吭哧你各种乱七八糟操作 mysql,半天查出来一个结果,耗时 600ms。但是这个结果可能接下来几个小时都不会变了,或者变了也可以不用立即反馈给用户。那么此时咋办? + +缓存啊,折腾 600ms 查出来的结果,扔缓存里,一个 key 对应一个 value,下次再有人查,别走 mysql 折腾 600ms 了,直接从缓存里,通过一个 key 查出来一个 value,2ms 搞定。性能提升 300 倍。 + +就是说对于一些需要复杂操作耗时查出来的结果,且确定后面不怎么变化,但是有很多读请求,那么直接将查询出来的结果放在缓存中,后面直接读缓存就好。 + +### 高并发 + +mysql 这么重的数据库,压根儿设计不是让你玩儿高并发的,虽然也可以玩儿,但是天然支持不好。mysql 单机支撑到 `2000QPS` 也开始容易报警了。 + +所以要是你有个系统,高峰期一秒钟过来的请求有 1万,那一个 mysql 单机绝对会死掉。你这个时候就只能上缓存,把很多数据放缓存,别放 mysql。缓存功能简单,说白了就是 `key-value` 式操作,单机支撑的并发量轻松一秒几万十几万,支撑高并发 so easy。单机承载并发量是 mysql 单机的几十倍。 + +> 缓存是走内存的,内存天然就支撑高并发。 + +## 用了缓存之后会有什么不良后果? + +常见的缓存问题有以下几个: + +- [缓存与数据库双写不一致](https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/redis-consistence.md) +- [缓存雪崩、缓存穿透](https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md) +- [缓存并发竞争](https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/redis-cas.md) + +后面再详细说明。 + +## redis 和 memcached 有啥区别? + +redis 支持复杂的数据结构 + +redis 相比 memcached 来说,拥有[更多的数据结构](https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/redis-data-types.md),能支持更丰富的数据操作。如果需要缓存能够支持更复杂的结构和操作, redis 会是不错的选择。 + +redis 原生支持集群模式 + +在 redis3.x 版本中,便能支持 cluster 模式,而 memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据。 + +性能对比 + +由于 redis 只使用**单核**,而 memcached 可以使用**多核**,所以平均每一个核上 redis 在存储小数据时比 memcached 性能更高。而在 100k 以上的数据中,memcached 性能要高于 redis。虽然 redis 最近也在存储大数据的性能上进行优化,但是比起 memcached,还是稍有逊色。 + +## 为啥 redis 单线程模型也能效率这么高? + +### redis 的线程模型 + +redis 内部使用文件事件处理器 `file event handler`,这个文件事件处理器是单线程的,所以 redis 才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 socket,将产生事件的 socket 压入内存队列中,事件分派器根据 socket 上的事件类型来选择对应的事件处理器进行处理。 + +文件事件处理器的结构包含 4 个部分: + +- 多个 socket +- IO 多路复用程序 +- 文件事件分派器 +- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器) + +多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 socket,会将产生事件的 socket 放入队列中排队,事件分派器每次从队列中取出一个 socket,根据 socket 的事件类型交给对应的事件处理器进行处理。 + +来看客户端与 redis 的一次通信过程: + +[![redis-single-thread-model](https://github.com/doocs/advanced-java/raw/master/images/redis-single-thread-model.png)](https://github.com/doocs/advanced-java/blob/master/images/redis-single-thread-model.png) + +要明白,通信是通过 socket 来完成的,不懂的同学可以先去看一看 socket 网络编程。 + +首先,redis 服务端进程初始化的时候,会将 server socket 的 `AE_READABLE` 事件与连接应答处理器关联。 + +客户端 socket01 向 redis 进程的 server socket 请求建立连接,此时 server socket 会产生一个 `AE_READABLE` 事件,IO 多路复用程序监听到 server socket 产生的事件后,将该 socket 压入队列中。文件事件分派器从队列中获取 socket,交给**连接应答处理器**。连接应答处理器会创建一个能与客户端通信的 socket01,并将该 socket01 的 `AE_READABLE` 事件与命令请求处理器关联。 + +假设此时客户端发送了一个 `set key value` 请求,此时 redis 中的 socket01 会产生 `AE_READABLE` 事件,IO 多路复用程序将 socket01 压入队列,此时事件分派器从队列中获取到 socket01 产生的 `AE_READABLE` 事件,由于前面 socket01 的 `AE_READABLE` 事件已经与命令请求处理器关联,因此事件分派器将事件交给命令请求处理器来处理。命令请求处理器读取 socket01 的 `key value` 并在自己内存中完成 `key value` 的设置。操作完成后,它会将 socket01 的 `AE_WRITABLE` 事件与命令回复处理器关联。 + +如果此时客户端准备好接收返回结果了,那么 redis 中的 socket01 会产生一个 `AE_WRITABLE` 事件,同样压入队列中,事件分派器找到相关联的命令回复处理器,由命令回复处理器对 socket01 输入本次操作的一个结果,比如 `ok`,之后解除 socket01 的 `AE_WRITABLE` 事件与命令回复处理器的关联。 + +这样便完成了一次通信。 + +### 为啥 redis 单线程模型也能效率这么高? + +- 纯内存操作。 +- 核心是基于非阻塞的 IO 多路复用机制。 +- C 语言实现,一般来说,C 语言实现的程序“距离”操作系统更近,执行速度相对会更快。 +- 单线程反而避免了多线程的频繁上下文切换问题,预防了多线程可能产生的竞争问题。 + +## Redis 有哪些数据类型 + +redis 主要有以下几种数据类型: + +- string +- hash +- list +- set +- sorted set + +### string + +这是最简单的类型,就是普通的 set 和 get,做简单的 KV 缓存。 + +``` +set college szu +``` + +### hash + +这个是类似 map 的一种结构,这个一般就是可以将结构化的数据,比如一个对象(前提是**这个对象没嵌套其他的对象**)给缓存在 redis 里,然后每次读写缓存的时候,可以就操作 hash 里的**某个字段**。 + +```bash +hset person name bingo +hset person age 20 +hset person id 1 +hget person name +person = { + "name": "bingo", + "age": 20, + "id": 1 +} +``` + +### list + +list 是有序列表,这个可以玩儿出很多花样。 + +比如可以通过 list 存储一些列表型的数据结构,类似粉丝列表、文章的评论列表之类的东西。 + +比如可以通过 lrange 命令,读取某个闭区间内的元素,可以基于 list 实现分页查询,这个是很棒的一个功能,基于 redis 实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西,性能高,就一页一页走。 + +``` +# 0开始位置,-1结束位置,结束位置为-1时,表示列表的最后一个位置,即查看所有。 +lrange mylist 0 -1 +``` + +比如可以搞个简单的消息队列,从 list 头怼进去,从 list 尾巴那里弄出来。 + +``` +lpush mylist 1 +lpush mylist 2 +lpush mylist 3 4 5 + +# 1 +rpop mylist +``` + +### set + +set 是无序集合,自动去重。 + +直接基于 set 将系统里需要去重的数据扔进去,自动就给去重了,如果你需要对一些数据进行快速的全局去重,你当然也可以基于 jvm 内存里的 HashSet 进行去重,但是如果你的某个系统部署在多台机器上呢?得基于 redis 进行全局的 set 去重。 + +可以基于 set 玩儿交集、并集、差集的操作,比如交集吧,可以把两个人的粉丝列表整一个交集,看看俩人的共同好友是谁?对吧。 + +把两个大 V 的粉丝都放在两个 set 中,对两个 set 做交集。 + +``` +#-------操作一个set------- +# 添加元素 +sadd mySet 1 + +# 查看全部元素 +smembers mySet + +# 判断是否包含某个值 +sismember mySet 3 + +# 删除某个/些元素 +srem mySet 1 +srem mySet 2 4 + +# 查看元素个数 +scard mySet + +# 随机删除一个元素 +spop mySet + +#-------操作多个set------- +# 将一个set的元素移动到另外一个set +smove yourSet mySet 2 + +# 求两set的交集 +sinter yourSet mySet + +# 求两set的并集 +sunion yourSet mySet + +# 求在yourSet中而不在mySet中的元素 +sdiff yourSet mySet +``` + +### sorted set + +sorted set 是排序的 set,去重但可以排序,写进去的时候给一个分数,自动根据分数排序。 + +``` +zadd board 85 zhangsan +zadd board 72 lisi +zadd board 96 wangwu +zadd board 63 zhaoliu + +# 获取排名前三的用户(默认是升序,所以需要 rev 改为降序) +zrevrange board 0 3 + +# 获取某用户的排名 +zrank board zhaoliu +``` + +## 如何保证 redis 的高并发和高可用?redis 的主从复制原理能介绍一下么?redis 的哨兵原理能介绍一下么? + +如果你用 redis 缓存技术的话,肯定要考虑如何用 redis 来加多台机器,保证 redis 是高并发的,还有就是如何让 redis 保证自己不是挂掉以后就直接死掉了,即 redis 高可用。 + +由于此节内容较多,因此,会分为两个小节进行讲解。 + +- [redis 主从架构](https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/redis-master-slave.md) +- [redis 基于哨兵实现高可用](https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/redis-sentinel.md) + +redis 实现**高并发**主要依靠**主从架构**,一主多从,一般来说,很多项目其实就足够了,单主用来写入数据,单机几万 QPS,多从用来查询数据,多个从实例可以提供每秒 10w 的 QPS。 + +如果想要在实现高并发的同时,容纳大量的数据,那么就需要 redis 集群,使用 redis 集群之后,可以提供每秒几十万的读写并发。 + +redis 高可用,如果是做主从架构部署,那么加上哨兵就可以了,就可以实现,任何一个实例宕机,可以进行主备切换。 + +## redis 的过期策略都有哪些?内存淘汰机制都有哪些?手写一下 LRU 代码实现? + +### redis 过期策略 + +redis 过期策略是:**定期删除+惰性删除**。 + +所谓**定期删除**,指的是 redis 默认是每隔 100ms 就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除。 + +假设 redis 里放了 10w 个 key,都设置了过期时间,你每隔几百毫秒,就检查 10w 个 key,那 redis 基本上就死了,cpu 负载会很高的,消耗在你的检查过期 key 上了。注意,这里可不是每隔 100ms 就遍历所有的设置过期时间的 key,那样就是一场性能上的**灾难**。实际上 redis 是每隔 100ms **随机抽取**一些 key 来检查和删除的。 + +但是问题是,定期删除可能会导致很多过期 key 到了时间并没有被删除掉,那咋整呢?所以就是惰性删除了。这就是说,在你获取某个 key 的时候,redis 会检查一下 ,这个 key 如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西。 + +> 获取 key 的时候,如果此时 key 已经过期,就删除,不会返回任何东西。 + +但是实际上这还是有问题的,如果定期删除漏掉了很多过期 key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期 key 堆积在内存里,导致 redis 内存块耗尽了,咋整? + +答案是:**走内存淘汰机制**。 + +### 内存淘汰机制 + +redis 内存淘汰机制有以下几个: + +- noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了。 +- **allkeys-lru**:当内存不足以容纳新写入数据时,在**键空间**中,移除最近最少使用的 key(这个是**最常用**的)。 +- allkeys-random:当内存不足以容纳新写入数据时,在**键空间**中,随机移除某个 key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的 key 给干掉啊。 +- volatile-lru:当内存不足以容纳新写入数据时,在**设置了过期时间的键空间**中,移除最近最少使用的 key(这个一般不太合适)。 +- volatile-random:当内存不足以容纳新写入数据时,在**设置了过期时间的键空间**中,**随机移除**某个 key。 +- volatile-ttl:当内存不足以容纳新写入数据时,在**设置了过期时间的键空间**中,有**更早过期时间**的 key 优先移除。 + +### 手写一个 LRU 算法 + +你可以现场手写最原始的 LRU 算法,那个代码量太大了,似乎不太现实。 + +不求自己纯手工从底层开始打造出自己的 LRU,但是起码要知道如何利用已有的 JDK 数据结构实现一个 Java 版的 LRU。 + +```java +class LRUCache extends LinkedHashMap { + private final int CACHE_SIZE; + + /** + * 传递进来最多能缓存多少数据 + * + * @param cacheSize 缓存大小 + */ + public LRUCache(int cacheSize) { + // true 表示让 linkedHashMap 按照访问顺序来进行排序,最近访问的放在头部,最老访问的放在尾部。 + super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true); + CACHE_SIZE = cacheSize; + } + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + // 当 map中的数据量大于指定的缓存个数的时候,就自动删除最老的数据。 + return size() > CACHE_SIZE; + } +} +``` + +## redis 集群模式的工作原理能说一下么?在集群模式下,redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗? + +在前几年,redis 如果要搞几个节点,每个节点存储一部分的数据,得**借助一些中间件**来实现,比如说有 `codis`,或者 `twemproxy`,都有。有一些 redis 中间件,你读写 redis 中间件,redis 中间件负责将你的数据分布式存储在多台机器上的 redis 实例中。 + +这两年,redis 不断在发展,redis 也不断有新的版本,现在的 redis 集群模式,可以做到在多台机器上,部署多个 redis 实例,每个实例存储一部分的数据,同时每个 redis 主实例可以挂 redis 从实例,自动确保说,如果 redis 主实例挂了,会自动切换到 redis 从实例上来。 + +现在 redis 的新版本,大家都是用 redis cluster 的,也就是 redis 原生支持的 redis 集群模式,那么面试官肯定会就 redis cluster 对你来个几连炮。要是你没用过 redis cluster,正常,以前很多人用 codis 之类的客户端来支持集群,但是起码你得研究一下 redis cluster 吧。 + +如果你的数据量很少,主要是承载高并发高性能的场景,比如你的缓存一般就几个 G,单机就足够了,可以使用 replication,一个 master 多个 slaves,要几个 slave 跟你要求的读吞吐量有关,然后自己搭建一个 sentinel 集群去保证 redis 主从架构的高可用性。 + +redis cluster,主要是针对**海量数据+高并发+高可用**的场景。redis cluster 支撑 N 个 redis master node,每个 master node 都可以挂载多个 slave node。这样整个 redis 就可以横向扩容了。如果你要支撑更大数据量的缓存,那就横向扩容更多的 master 节点,每个 master 节点就能存放更多的数据了。 + +### 面试题剖析 + +### redis cluster 介绍 + +- 自动将数据进行分片,每个 master 上放一部分数据 +- 提供内置的高可用支持,部分 master 不可用时,还是可以继续工作的 + +在 redis cluster 架构下,每个 redis 要放开两个端口号,比如一个是 6379,另外一个就是 加1w 的端口号,比如 16379。 + +16379 端口号是用来进行节点间通信的,也就是 cluster bus 的东西,cluster bus 的通信,用来进行故障检测、配置更新、故障转移授权。cluster bus 用了另外一种二进制的协议,`gossip` 协议,用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间。 + +### 节点间的内部通信机制 + +#### 基本通信原理 + +集群元数据的维护有两种方式:集中式、Gossip 协议。redis cluster 节点间采用 gossip 协议进行通信。 + +**集中式**是将集群元数据(节点信息、故障等等)几种存储在某个节点上。集中式元数据集中存储的一个典型代表,就是大数据领域的 `storm`。它是分布式的大数据实时计算引擎,是集中式的元数据存储的结构,底层基于 zookeeper(分布式协调的中间件)对所有元数据进行存储维护。 + +[![zookeeper-centralized-storage](https://github.com/doocs/advanced-java/raw/master/images/zookeeper-centralized-storage.png)](https://github.com/doocs/advanced-java/blob/master/images/zookeeper-centralized-storage.png) + +redis 维护集群元数据采用另一个方式, `gossip` 协议,所有节点都持有一份元数据,不同的节点如果出现了元数据的变更,就不断将元数据发送给其它的节点,让其它节点也进行元数据的变更。 + +[![redis-gossip](https://github.com/doocs/advanced-java/raw/master/images/redis-gossip.png)](https://github.com/doocs/advanced-java/blob/master/images/redis-gossip.png) + +**集中式**的**好处**在于,元数据的读取和更新,时效性非常好,一旦元数据出现了变更,就立即更新到集中式的存储中,其它节点读取的时候就可以感知到;**不好**在于,所有的元数据的更新压力全部集中在一个地方,可能会导致元数据的存储有压力。 + +gossip 好处在于,元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续打到所有节点上去更新,降低了压力;不好在于,元数据的更新有延时,可能导致集群中的一些操作会有一些滞后。 + +- 10000 端口:每个节点都有一个专门用于节点间通信的端口,就是自己提供服务的端口号+10000,比如 7001,那么用于节点间通信的就是 17001 端口。每个节点每隔一段时间都会往另外几个节点发送 `ping` 消息,同时其它几个节点接收到 `ping` 之后返回 `pong`。 +- 交换的信息:信息包括故障信息,节点的增加和删除,hash slot 信息等等。 + +#### gossip 协议 + +gossip 协议包含多种消息,包含 `ping`,`pong`,`meet`,`fail` 等等。 + +- meet:某个节点发送 meet 给新加入的节点,让新节点加入集群中,然后新节点就会开始与其它节点进行通信。 + +``` +redis-trib.rb add-node +``` + +其实内部就是发送了一个 gossip meet 消息给新加入的节点,通知那个节点去加入我们的集群。 + +- ping:每个节点都会频繁给其它节点发送 ping,其中包含自己的状态还有自己维护的集群元数据,互相通过 ping 交换元数据。 +- pong:返回 ping 和 meeet,包含自己的状态和其它信息,也用于信息广播和更新。 +- fail:某个节点判断另一个节点 fail 之后,就发送 fail 给其它节点,通知其它节点说,某个节点宕机啦。 + +#### ping 消息深入 + +ping 时要携带一些元数据,如果很频繁,可能会加重网络负担。 + +每个节点每秒会执行 10 次 ping,每次会选择 5 个最久没有通信的其它节点。当然如果发现某个节点通信延时达到了 `cluster_node_timeout / 2`,那么立即发送 ping,避免数据交换延时过长,落后的时间太长了。比如说,两个节点之间都 10 分钟没有交换数据了,那么整个集群处于严重的元数据不一致的情况,就会有问题。所以 `cluster_node_timeout` 可以调节,如果调得比较大,那么会降低 ping 的频率。 + +每次 ping,会带上自己节点的信息,还有就是带上 1/10 其它节点的信息,发送出去,进行交换。至少包含 `3` 个其它节点的信息,最多包含 `总节点数减 2` 个其它节点的信息。 + +### 分布式寻址算法 + +- hash 算法(大量缓存重建) +- 一致性 hash 算法(自动缓存迁移)+ 虚拟节点(自动负载均衡) +- redis cluster 的 hash slot 算法 + +#### hash 算法 + +来了一个 key,首先计算 hash 值,然后对节点数取模。然后打在不同的 master 节点上。一旦某一个 master 节点宕机,所有请求过来,都会基于最新的剩余 master 节点数去取模,尝试去取数据。这会导致**大部分的请求过来,全部无法拿到有效的缓存**,导致大量的流量涌入数据库。 + +[![hash](https://github.com/doocs/advanced-java/raw/master/images/hash.png)](https://github.com/doocs/advanced-java/blob/master/images/hash.png) + +#### 一致性 hash 算法 + +一致性 hash 算法将整个 hash 值空间组织成一个虚拟的圆环,整个空间按顺时针方向组织,下一步将各个 master 节点(使用服务器的 ip 或主机名)进行 hash。这样就能确定每个节点在其哈希环上的位置。 + +来了一个 key,首先计算 hash 值,并确定此数据在环上的位置,从此位置沿环**顺时针“行走”**,遇到的第一个 master 节点就是 key 所在位置。 + +在一致性哈希算法中,如果一个节点挂了,受影响的数据仅仅是此节点到环空间前一个节点(沿着逆时针方向行走遇到的第一个节点)之间的数据,其它不受影响。增加一个节点也同理。 + +燃鹅,一致性哈希算法在节点太少时,容易因为节点分布不均匀而造成**缓存热点**的问题。为了解决这种热点问题,一致性 hash 算法引入了虚拟节点机制,即对每一个节点计算多个 hash,每个计算结果位置都放置一个虚拟节点。这样就实现了数据的均匀分布,负载均衡。 + +[![consistent-hashing-algorithm](https://github.com/doocs/advanced-java/raw/master/images/consistent-hashing-algorithm.png)](https://github.com/doocs/advanced-java/blob/master/images/consistent-hashing-algorithm.png) + +#### redis cluster 的 hash slot 算法 + +redis cluster 有固定的 `16384` 个 hash slot,对每个 `key` 计算 `CRC16` 值,然后对 `16384` 取模,可以获取 key 对应的 hash slot。 + +redis cluster 中每个 master 都会持有部分 slot,比如有 3 个 master,那么可能每个 master 持有 5000 多个 hash slot。hash slot 让 node 的增加和移除很简单,增加一个 master,就将其他 master 的 hash slot 移动部分过去,减少一个 master,就将它的 hash slot 移动到其他 master 上去。移动 hash slot 的成本是非常低的。客户端的 api,可以对指定的数据,让他们走同一个 hash slot,通过 `hash tag` 来实现。 + +任何一台机器宕机,另外两个节点,不影响的。因为 key 找的是 hash slot,不是机器。 + +[![hash-slot](https://github.com/doocs/advanced-java/raw/master/images/hash-slot.png)](https://github.com/doocs/advanced-java/blob/master/images/hash-slot.png) + +### redis cluster 的高可用与主备切换原理 + +redis cluster 的高可用的原理,几乎跟哨兵是类似的。 + +#### 判断节点宕机 + +如果一个节点认为另外一个节点宕机,那么就是 `pfail`,**主观宕机**。如果多个节点都认为另外一个节点宕机了,那么就是 `fail`,**客观宕机**,跟哨兵的原理几乎一样,sdown,odown。 + +在 `cluster-node-timeout` 内,某个节点一直没有返回 `pong`,那么就被认为 `pfail`。 + +如果一个节点认为某个节点 `pfail` 了,那么会在 `gossip ping` 消息中,`ping` 给其他节点,如果**超过半数**的节点都认为 `pfail` 了,那么就会变成 `fail`。 + +#### 从节点过滤 + +对宕机的 master node,从其所有的 slave node 中,选择一个切换成 master node。 + +检查每个 slave node 与 master node 断开连接的时间,如果超过了 `cluster-node-timeout * cluster-slave-validity-factor`,那么就**没有资格**切换成 `master`。 + +#### 从节点选举 + +每个从节点,都根据自己对 master 复制数据的 offset,来设置一个选举时间,offset 越大(复制数据越多)的从节点,选举时间越靠前,优先进行选举。 + +所有的 master node 开始 slave 选举投票,给要进行选举的 slave 进行投票,如果大部分 master node`(N/2 + 1)`都投票给了某个从节点,那么选举通过,那个从节点可以切换成 master。 + +从节点执行主备切换,从节点切换为主节点。 + +#### 与哨兵比较 + +整个流程跟哨兵相比,非常类似,所以说,redis cluster 功能强大,直接集成了 replication 和 sentinel 的功能。 + +## 如何保证缓存与数据库的双写一致性? + +一般来说,如果允许缓存可以稍微的跟数据库偶尔有不一致的情况,也就是说如果你的系统**不是严格要求** “缓存+数据库” 必须保持一致性的话,最好不要做这个方案,即:**读请求和写请求串行化**,串到一个**内存队列**里去。 + +串行化可以保证一定不会出现不一致的情况,但是它也会导致系统的吞吐量大幅度降低,用比正常情况下多几倍的机器去支撑线上的一个请求。 + +### Cache Aside Pattern + +最经典的缓存+数据库读写的模式,就是 Cache Aside Pattern。 + +- 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。 +- 更新的时候,**先更新数据库,然后再删除缓存**。 + +**为什么是删除缓存,而不是更新缓存?** + +原因很简单,很多时候,在复杂点的缓存场景,缓存不单单是数据库中直接取出来的值。 + +比如可能更新了某个表的一个字段,然后其对应的缓存,是需要查询另外两个表的数据并进行运算,才能计算出缓存最新的值的。 + +另外更新缓存的代价有时候是很高的。是不是说,每次修改数据库的时候,都一定要将其对应的缓存更新一份?也许有的场景是这样,但是对于**比较复杂的缓存数据计算的场景**,就不是这样了。如果你频繁修改一个缓存涉及的多个表,缓存也频繁更新。但是问题在于,**这个缓存到底会不会被频繁访问到?** + +举个栗子,一个缓存涉及的表的字段,在 1 分钟内就修改了 20 次,或者是 100 次,那么缓存更新 20 次、100 次;但是这个缓存在 1 分钟内只被读取了 1 次,有**大量的冷数据**。实际上,如果你只是删除缓存的话,那么在 1 分钟内,这个缓存不过就重新计算一次而已,开销大幅度降低。**用到缓存才去算缓存。** + +其实删除缓存,而不是更新缓存,就是一个 lazy 计算的思想,不要每次都重新做复杂的计算,不管它会不会用到,而是让它到需要被使用的时候再重新计算。像 mybatis,hibernate,都有懒加载思想。查询一个部门,部门带了一个员工的 list,没有必要说每次查询部门,都里面的 1000 个员工的数据也同时查出来啊。80% 的情况,查这个部门,就只是要访问这个部门的信息就可以了。先查部门,同时要访问里面的员工,那么这个时候只有在你要访问里面的员工的时候,才会去数据库里面查询 1000 个员工。 + +### 最初级的缓存不一致问题及解决方案 + +问题:先更新数据库,再删除缓存。如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据就出现了不一致。 + +[![redis-junior-inconsistent](https://github.com/doocs/advanced-java/raw/master/images/redis-junior-inconsistent.png)](https://github.com/doocs/advanced-java/blob/master/images/redis-junior-inconsistent.png) + +解决思路:先删除缓存,再更新数据库。如果数据库更新失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致。因为读的时候缓存没有,所以去读了数据库中的旧数据,然后更新到缓存中。 + +### 比较复杂的数据不一致问题分析 + +数据发生了变更,先删除了缓存,然后要去修改数据库,此时还没修改。一个请求过来,去读缓存,发现缓存空了,去查询数据库,**查到了修改前的旧数据**,放到了缓存中。随后数据变更的程序完成了数据库的修改。完了,数据库和缓存中的数据不一样了... + +**为什么上亿流量高并发场景下,缓存会出现这个问题?** + +只有在对一个数据在并发的进行读写的时候,才可能会出现这种问题。其实如果说你的并发量很低的话,特别是读并发很低,每天访问量就 1 万次,那么很少的情况下,会出现刚才描述的那种不一致的场景。但是问题是,如果每天的是上亿的流量,每秒并发读是几万,每秒只要有数据更新的请求,就**可能会出现上述的数据库+缓存不一致的情况**。 + +**解决方案如下:** + +更新数据的时候,根据**数据的唯一标识**,将操作路由之后,发送到一个 jvm 内部队列中。读取数据的时候,如果发现数据不在缓存中,那么将重新读取数据+更新缓存的操作,根据唯一标识路由之后,也发送同一个 jvm 内部队列中。 + +一个队列对应一个工作线程,每个工作线程**串行**拿到对应的操作,然后一条一条的执行。这样的话,一个数据变更的操作,先删除缓存,然后再去更新数据库,但是还没完成更新。此时如果一个读请求过来,没有读到缓存,那么可以先将缓存更新的请求发送到队列中,此时会在队列中积压,然后同步等待缓存更新完成。 + +这里有一个**优化点**,一个队列中,其实**多个更新缓存请求串在一起是没意义的**,因此可以做过滤,如果发现队列中已经有一个更新缓存的请求了,那么就不用再放个更新请求操作进去了,直接等待前面的更新操作请求完成即可。 + +待那个队列对应的工作线程完成了上一个操作的数据库的修改之后,才会去执行下一个操作,也就是缓存更新的操作,此时会从数据库中读取最新的值,然后写入缓存中。 + +如果请求还在等待时间范围内,不断轮询发现可以取到值了,那么就直接返回;如果请求等待的时间超过一定时长,那么这一次直接从数据库中读取当前的旧值。 + +高并发的场景下,该解决方案要注意的问题: + +- 读请求长时阻塞 + +由于读请求进行了非常轻度的异步化,所以一定要注意读超时的问题,每个读请求必须在超时时间范围内返回。 + +该解决方案,最大的风险点在于说,**可能数据更新很频繁**,导致队列中积压了大量更新操作在里面,然后**读请求会发生大量的超时**,最后导致大量的请求直接走数据库。务必通过一些模拟真实的测试,看看更新数据的频率是怎样的。 + +另外一点,因为一个队列中,可能会积压针对多个数据项的更新操作,因此需要根据自己的业务情况进行测试,可能需要**部署多个服务**,每个服务分摊一些数据的更新操作。如果一个内存队列里居然会挤压 100 个商品的库存修改操作,每隔库存修改操作要耗费 10ms 去完成,那么最后一个商品的读请求,可能等待 10 * 100 = 1000ms = 1s 后,才能得到数据,这个时候就导致**读请求的长时阻塞**。 + +一定要做根据实际业务系统的运行情况,去进行一些压力测试,和模拟线上环境,去看看最繁忙的时候,内存队列可能会挤压多少更新操作,可能会导致最后一个更新操作对应的读请求,会 hang 多少时间,如果读请求在 200ms 返回,如果你计算过后,哪怕是最繁忙的时候,积压 10 个更新操作,最多等待 200ms,那还可以的。 + +**如果一个内存队列中可能积压的更新操作特别多**,那么你就要**加机器**,让每个机器上部署的服务实例处理更少的数据,那么每个内存队列中积压的更新操作就会越少。 + +其实根据之前的项目经验,一般来说,数据的写频率是很低的,因此实际上正常来说,在队列中积压的更新操作应该是很少的。像这种针对读高并发、读缓存架构的项目,一般来说写请求是非常少的,每秒的 QPS 能到几百就不错了。 + +我们来**实际粗略测算一下**。 + +如果一秒有 500 的写操作,如果分成 5 个时间片,每 200ms 就 100 个写操作,放到 20 个内存队列中,每个内存队列,可能就积压 5 个写操作。每个写操作性能测试后,一般是在 20ms 左右就完成,那么针对每个内存队列的数据的读请求,也就最多 hang 一会儿,200ms 以内肯定能返回了。 + +经过刚才简单的测算,我们知道,单机支撑的写 QPS 在几百是没问题的,如果写 QPS 扩大了 10 倍,那么就扩容机器,扩容 10 倍的机器,每个机器 20 个队列。 + +- 读请求并发量过高 + +这里还必须做好压力测试,确保恰巧碰上上述情况的时候,还有一个风险,就是突然间大量读请求会在几十毫秒的延时 hang 在服务上,看服务能不能扛的住,需要多少机器才能扛住最大的极限情况的峰值。 + +但是因为并不是所有的数据都在同一时间更新,缓存也不会同一时间失效,所以每次可能也就是少数数据的缓存失效了,然后那些数据对应的读请求过来,并发量应该也不会特别大。 + +- 多服务实例部署的请求路由 + +可能这个服务部署了多个实例,那么必须**保证**说,执行数据更新操作,以及执行缓存更新操作的请求,都通过 Nginx 服务器**路由到相同的服务实例上**。 + +比如说,对同一个商品的读写请求,全部路由到同一台机器上。可以自己去做服务间的按照某个请求参数的 hash 路由,也可以用 Nginx 的 hash 路由功能等等。 + +- 热点商品的路由问题,导致请求的倾斜 + +万一某个商品的读写请求特别高,全部打到相同的机器的相同的队列里面去了,可能会造成某台机器的压力过大。就是说,因为只有在商品数据更新的时候才会清空缓存,然后才会导致读写并发,所以其实要根据业务系统去看,如果更新频率不是太高的话,这个问题的影响并不是特别大,但是的确可能某些机器的负载会高一些。 + +## 了解什么是 redis 的雪崩、穿透和击穿?redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 redis 的穿透? + +### 缓存雪崩 + +对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意外发生了全盘宕机。缓存挂了,此时 1 秒 5000 个请求全部落数据库,数据库必然扛不住,它会报一下警,然后就挂了。此时,如果没有采用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。 + +这就是缓存雪崩。 + +[![redis-caching-avalanche](https://github.com/doocs/advanced-java/raw/master/images/redis-caching-avalanche.png)](https://github.com/doocs/advanced-java/blob/master/images/redis-caching-avalanche.png) + +大约在 3 年前,国内比较知名的一个互联网公司,曾因为缓存事故,导致雪崩,后台系统全部崩溃,事故从当天下午持续到晚上凌晨 3~4 点,公司损失了几千万。 + +缓存雪崩的事前事中事后的解决方案如下。 + +- 事前:redis 高可用,主从+哨兵,redis cluster,避免全盘崩溃。 +- 事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。 +- 事后:redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。 + +[![redis-caching-avalanche-solution](https://github.com/doocs/advanced-java/raw/master/images/redis-caching-avalanche-solution.png)](https://github.com/doocs/advanced-java/blob/master/images/redis-caching-avalanche-solution.png) + +用户发送一个请求,系统 A 收到请求后,先查本地 ehcache 缓存,如果没查到再查 redis。如果 ehcache 和 redis 都没有,再查数据库,将数据库中的结果,写入 ehcache 和 redis 中。 + +限流组件,可以设置每秒的请求,有多少能通过组件,剩余的未通过的请求,怎么办?**走降级**!可以返回一些默认的值,或者友情提示,或者空白的值。 + +好处: + +- 数据库绝对不会死,限流组件确保了每秒只有多少个请求能通过。 +- 只要数据库不死,就是说,对用户来说,2/5 的请求都是可以被处理的。 +- 只要有 2/5 的请求可以被处理,就意味着你的系统没死,对用户来说,可能就是点击几次刷不出来页面,但是多点几次,就可以刷出来一次。 + +### 缓存穿透 + +对于系统A,假设一秒 5000 个请求,结果其中 4000 个请求是黑客发出的恶意攻击。 + +黑客发出的那 4000 个攻击,缓存中查不到,每次你去数据库里查,也查不到。 + +举个栗子。数据库 id 是从 1 开始的,结果黑客发过来的请求 id 全部都是负数。这样的话,缓存中不会有,请求每次都“视缓存于无物”,直接查询数据库。这种恶意攻击场景的缓存穿透就会直接把数据库给打死。 + +[![redis-caching-penetration](https://github.com/doocs/advanced-java/raw/master/images/redis-caching-penetration.png)](https://github.com/doocs/advanced-java/blob/master/images/redis-caching-penetration.png) + +解决方式很简单,每次系统 A 从数据库中只要没查到,就写一个空值到缓存里去,比如 `set -999 UNKNOWN`。然后设置一个过期时间,这样的话,下次有相同的 key 来访问的时候,在缓存失效之前,都可以直接从缓存中取数据。 + +### 缓存击穿 + +缓存击穿,就是说某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。 + +解决方式也很简单,可以将热点数据设置为永远不过期;或者基于 redis or zookeeper 实现互斥锁,等待第一个请求构建完缓存之后,再释放锁,进而其它请求才能通过该 key 访问数据。 + +## redis 的并发竞争问题是什么?如何解决这个问题?了解 redis 事务的 CAS 方案吗? + +某个时刻,多个系统实例都去更新某个 key。可以基于 zookeeper 实现分布式锁。每个系统通过 zookeeper 获取分布式锁,确保同一时间,只能有一个系统实例在操作某个 key,别人都不允许读和写。 + +[![zookeeper-distributed-lock](https://github.com/doocs/advanced-java/raw/master/images/zookeeper-distributed-lock.png)](https://github.com/doocs/advanced-java/blob/master/images/zookeeper-distributed-lock.png) + +你要写入缓存的数据,都是从 mysql 里查出来的,都得写入 mysql 中,写入 mysql 中的时候必须保存一个时间戳,从 mysql 查出来的时候,时间戳也查出来。 + +每次要**写之前,先判断**一下当前这个 value 的时间戳是否比缓存里的 value 的时间戳要新。如果是的话,那么可以写,否则,就不能用旧的数据覆盖新的数据。 + +## 生产环境中的 redis 是怎么部署的? + +redis cluster,10 台机器,5 台机器部署了 redis 主实例,另外 5 台机器部署了 redis 的从实例,每个主实例挂了一个从实例,5 个节点对外提供读写服务,每个节点的读写高峰qps可能可以达到每秒 5 万,5 台机器最多是 25 万读写请求/s。 + +机器是什么配置?32G 内存+ 8 核 CPU + 1T 磁盘,但是分配给 redis 进程的是10g内存,一般线上生产环境,redis 的内存尽量不要超过 10g,超过 10g 可能会有问题。 + +5 台机器对外提供读写,一共有 50g 内存。 + +因为每个主实例都挂了一个从实例,所以是高可用的,任何一个主实例宕机,都会自动故障迁移,redis 从实例会自动变成主实例继续提供读写服务。 + +你往内存里写的是什么数据?每条数据的大小是多少?商品数据,每条数据是 10kb。100 条数据是 1mb,10 万条数据是 1g。常驻内存的是 200 万条商品数据,占用内存是 20g,仅仅不到总内存的 50%。目前高峰期每秒就是 3500 左右的请求量。 + +其实大型的公司,会有基础架构的 team 负责缓存集群的运维。 \ No newline at end of file diff --git a/docs/distributed/distributed-lock.md b/docs/distributed/distributed-lock.md new file mode 100644 index 00000000..8ae7b2c1 --- /dev/null +++ b/docs/distributed/distributed-lock.md @@ -0,0 +1,341 @@ +# 分布式锁 + +## redis 分布式锁 + +官方叫做 `RedLock` 算法,是 redis 官方支持的分布式锁算法。 + +这个分布式锁有 3 个重要的考量点: + +- 互斥(只能有一个客户端获取锁) +- 不能死锁 +- 容错(只要大部分 redis 节点创建了这把锁就可以) + +### redis 最普通的分布式锁 + +第一个最普通的实现方式,就是在 redis 里使用 `setnx` 命令创建一个 key,这样就算加锁。 + +``` +SET resource_name my_random_value NX PX 30000 +``` + +执行这个命令就 ok。 + +- `NX`:表示只有 `key` 不存在的时候才会设置成功。(如果此时 redis 中存在这个 key,那么设置失败,返回 `nil`) +- `PX 30000`:意思是 30s 后锁自动释放。别人创建的时候如果发现已经有了就不能加锁了。 + +释放锁就是删除 key ,但是一般可以用 `lua` 脚本删除,判断 value 一样才删除: + +``` +-- 删除锁的时候,找到 key 对应的 value,跟自己传过去的 value 做比较,如果是一样的才删除。 +if redis.call("get",KEYS[1]) == ARGV[1] then + return redis.call("del",KEYS[1]) +else + return 0 +end +``` + +为啥要用 `random_value` 随机值呢?因为如果某个客户端获取到了锁,但是阻塞了很长时间才执行完,比如说超过了 30s,此时可能已经自动释放锁了,此时可能别的客户端已经获取到了这个锁,要是你这个时候直接删除 key 的话会有问题,所以得用随机值加上面的 `lua` 脚本来释放锁。 + +但是这样是肯定不行的。因为如果是普通的 redis 单实例,那就是单点故障。或者是 redis 普通主从,那 redis 主从异步复制,如果主节点挂了(key 就没有了),key 还没同步到从节点,此时从节点切换为主节点,别人就可以 set key,从而拿到锁。 + +### RedLock 算法 + +这个场景是假设有一个 redis cluster,有 5 个 redis master 实例。然后执行如下步骤获取一把锁: + +1. 获取当前时间戳,单位是毫秒; +2. 跟上面类似,轮流尝试在每个 master 节点上创建锁,过期时间较短,一般就几十毫秒; +3. 尝试在**大多数节点**上建立一个锁,比如 5 个节点就要求是 3 个节点 `n / 2 + 1`; +4. 客户端计算建立好锁的时间,如果建立锁的时间小于超时时间,就算建立成功了; +5. 要是锁建立失败了,那么就依次之前建立过的锁删除; +6. 只要别人建立了一把分布式锁,你就得**不断轮询去尝试获取锁**。 + +[![redis-redlock](https://github.com/doocs/advanced-java/raw/master/images/redis-redlock.png)](https://github.com/doocs/advanced-java/blob/master/images/redis-redlock.png) + +[Redis 官方](https://redis.io/)给出了以上两种基于 Redis 实现分布式锁的方法,详细说明可以查看: 。 + +## zk 分布式锁 + +zk 分布式锁,其实可以做的比较简单,就是某个节点尝试创建临时 znode,此时创建成功了就获取了这个锁;这个时候别的客户端来创建锁会失败,只能**注册个监听器**监听这个锁。释放锁就是删除这个 znode,一旦释放掉就会通知客户端,然后有一个等待着的客户端就可以再次重新加锁。 + +```java +/** + * ZooKeeperSession + * + * @author bingo + * @since 2018/11/29 + * + */ +public class ZooKeeperSession { + + private static CountDownLatch connectedSemaphore = new CountDownLatch(1); + + private ZooKeeper zookeeper; + private CountDownLatch latch; + + public ZooKeeperSession() { + try { + this.zookeeper = new ZooKeeper("192.168.31.187:2181,192.168.31.19:2181,192.168.31.227:2181", 50000, new ZooKeeperWatcher()); + try { + connectedSemaphore.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + System.out.println("ZooKeeper session established......"); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 获取分布式锁 + * + * @param productId + */ + public Boolean acquireDistributedLock(Long productId) { + String path = "/product-lock-" + productId; + + try { + zookeeper.create(path, "".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL); + return true; + } catch (Exception e) { + while (true) { + try { + // 相当于是给node注册一个监听器,去看看这个监听器是否存在 + Stat stat = zk.exists(path, true); + + if (stat != null) { + this.latch = new CountDownLatch(1); + this.latch.await(waitTime, TimeUnit.MILLISECONDS); + this.latch = null; + } + zookeeper.create(path, "".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL); + return true; + } catch (Exception ee) { + continue; + } + } + + } + return true; + } + + /** + * 释放掉一个分布式锁 + * + * @param productId + */ + public void releaseDistributedLock(Long productId) { + String path = "/product-lock-" + productId; + try { + zookeeper.delete(path, -1); + System.out.println("release the lock for product[id=" + productId + "]......"); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 建立zk session的watcher + * + * @author bingo + * @since 2018/11/29 + * + */ + private class ZooKeeperWatcher implements Watcher { + + public void process(WatchedEvent event) { + System.out.println("Receive watched event: " + event.getState()); + + if (KeeperState.SyncConnected == event.getState()) { + connectedSemaphore.countDown(); + } + + if (this.latch != null) { + this.latch.countDown(); + } + } + + } + + /** + * 封装单例的静态内部类 + * + * @author bingo + * @since 2018/11/29 + * + */ + private static class Singleton { + + private static ZooKeeperSession instance; + + static { + instance = new ZooKeeperSession(); + } + + public static ZooKeeperSession getInstance() { + return instance; + } + + } + + /** + * 获取单例 + * + * @return + */ + public static ZooKeeperSession getInstance() { + return Singleton.getInstance(); + } + + /** + * 初始化单例的便捷方法 + */ + public static void init() { + getInstance(); + } + +} +``` + +也可以采用另一种方式,创建临时顺序节点: + +如果有一把锁,被多个人给竞争,此时多个人会排队,第一个拿到锁的人会执行,然后释放锁;后面的每个人都会去监听**排在自己前面**的那个人创建的 node 上,一旦某个人释放了锁,排在自己后面的人就会被 zookeeper 给通知,一旦被通知了之后,就 ok 了,自己就获取到了锁,就可以执行代码了。 + +```java +public class ZooKeeperDistributedLock implements Watcher { + + private ZooKeeper zk; + private String locksRoot = "/locks"; + private String productId; + private String waitNode; + private String lockNode; + private CountDownLatch latch; + private CountDownLatch connectedLatch = new CountDownLatch(1); + private int sessionTimeout = 30000; + + public ZooKeeperDistributedLock(String productId) { + this.productId = productId; + try { + String address = "192.168.31.187:2181,192.168.31.19:2181,192.168.31.227:2181"; + zk = new ZooKeeper(address, sessionTimeout, this); + connectedLatch.await(); + } catch (IOException e) { + throw new LockException(e); + } catch (KeeperException e) { + throw new LockException(e); + } catch (InterruptedException e) { + throw new LockException(e); + } + } + + public void process(WatchedEvent event) { + if (event.getState() == KeeperState.SyncConnected) { + connectedLatch.countDown(); + return; + } + + if (this.latch != null) { + this.latch.countDown(); + } + } + + public void acquireDistributedLock() { + try { + if (this.tryLock()) { + return; + } else { + waitForLock(waitNode, sessionTimeout); + } + } catch (KeeperException e) { + throw new LockException(e); + } catch (InterruptedException e) { + throw new LockException(e); + } + } + + public boolean tryLock() { + try { + // 传入进去的locksRoot + “/” + productId + // 假设productId代表了一个商品id,比如说1 + // locksRoot = locks + // /locks/10000000000,/locks/10000000001,/locks/10000000002 + lockNode = zk.create(locksRoot + "/" + productId, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); + + // 看看刚创建的节点是不是最小的节点 + // locks:10000000000,10000000001,10000000002 + List locks = zk.getChildren(locksRoot, false); + Collections.sort(locks); + + if(lockNode.equals(locksRoot+"/"+ locks.get(0))){ + //如果是最小的节点,则表示取得锁 + return true; + } + + //如果不是最小的节点,找到比自己小1的节点 + int previousLockIndex = -1; + for(int i = 0; i < locks.size(); i++) { + if(lockNode.equals(locksRoot + “/” + locks.get(i))) { + previousLockIndex = i - 1; + break; + } + } + + this.waitNode = locks.get(previousLockIndex); + } catch (KeeperException e) { + throw new LockException(e); + } catch (InterruptedException e) { + throw new LockException(e); + } + return false; + } + + private boolean waitForLock(String waitNode, long waitTime) throws InterruptedException, KeeperException { + Stat stat = zk.exists(locksRoot + "/" + waitNode, true); + if (stat != null) { + this.latch = new CountDownLatch(1); + this.latch.await(waitTime, TimeUnit.MILLISECONDS); + this.latch = null; + } + return true; + } + + public void unlock() { + try { + // 删除/locks/10000000000节点 + // 删除/locks/10000000001节点 + System.out.println("unlock " + lockNode); + zk.delete(lockNode, -1); + lockNode = null; + zk.close(); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (KeeperException e) { + e.printStackTrace(); + } + } + + public class LockException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public LockException(String e) { + super(e); + } + + public LockException(Exception e) { + super(e); + } + } +} +``` + +## redis 分布式锁和 zk 分布式锁的对比 + +- redis 分布式锁,其实**需要自己不断去尝试获取锁**,比较消耗性能。 +- zk 分布式锁,获取不到锁,注册个监听器即可,不需要不断主动尝试获取锁,性能开销较小。 + +另外一点就是,如果是 redis 获取锁的那个客户端出现 bug 挂了,那么只能等待超时时间之后才能释放锁;而 zk 的话,因为创建的是临时 znode,只要客户端挂了,znode 就没了,此时就自动释放锁。 + +redis 分布式锁大家没发现好麻烦吗?遍历上锁,计算时间等等......zk 的分布式锁语义清晰实现简单。 + +所以先不分析太多的东西,就说这两点,我个人实践认为 zk 的分布式锁比 redis 的分布式锁牢靠、而且模型简单易用。 \ No newline at end of file diff --git a/docs/distributed/distributed-session.md b/docs/distributed/distributed-session.md new file mode 100644 index 00000000..e13014e6 --- /dev/null +++ b/docs/distributed/distributed-session.md @@ -0,0 +1,128 @@ +# 分布式会话 + +session 是啥?浏览器有个 cookie,在一段时间内这个 cookie 都存在,然后每次发请求过来都带上一个特殊的 `jsessionid cookie`,就根据这个东西,在服务端可以维护一个对应的 session 域,里面可以放点数据。 + +一般的话只要你没关掉浏览器,cookie 还在,那么对应的那个 session 就在,但是如果 cookie 没了,session 也就没了。常见于什么购物车之类的东西,还有登录状态保存之类的。 + +这个不多说了,懂 Java 的都该知道这个。 + +单块系统的时候这么玩儿 session 没问题,但是你要是分布式系统呢,那么多的服务,session 状态在哪儿维护啊? + +其实方法很多,但是常见常用的是以下几种: + +## 完全不用 session + +使用 JWT Token 储存用户身份,然后再从数据库或者 cache 中获取其他的信息。这样无论请求分配到哪个服务器都无所谓。 + +## tomcat + redis + +这个其实还挺方便的,就是使用 session 的代码,跟以前一样,还是基于 tomcat 原生的 session 支持即可,然后就是用一个叫做 `Tomcat RedisSessionManager` 的东西,让所有我们部署的 tomcat 都将 session 数据存储到 redis 即可。 + +在 tomcat 的配置文件中配置: + +``` + + + +``` + +然后指定 redis 的 host 和 port 就 ok 了。 + +``` + + +``` + +还可以用上面这种方式基于 redis 哨兵支持的 redis 高可用集群来保存 session 数据,都是 ok 的。 + +## spring session + redis + +上面所说的第二种方式会与 tomcat 容器重耦合,如果我要将 web 容器迁移成 jetty,难道还要重新把 jetty 都配置一遍? + +因为上面那种 tomcat + redis 的方式好用,但是会**严重依赖于web容器**,不好将代码移植到其他 web 容器上去,尤其是你要是换了技术栈咋整?比如换成了 spring cloud 或者是 spring boot 之类的呢? + +所以现在比较好的还是基于 Java 一站式解决方案,也就是 spring。人家 spring 基本上承包了大部分我们需要使用的框架,spirng cloud 做微服务,spring boot 做脚手架,所以用 sping session 是一个很好的选择。 + +在 pom.xml 中配置: + +``` + + org.springframework.session + spring-session-data-redis + 1.2.1.RELEASE + + + redis.clients + jedis + 2.8.1 + +``` + +在 spring 配置文件中配置: + +``` + + + + + + + + + + + + + + + + + +``` + +在 web.xml 中配置: + +``` + + springSessionRepositoryFilter + org.springframework.web.filter.DelegatingFilterProxy + + + springSessionRepositoryFilter + /* + +``` + +示例代码: + +``` +@RestController +@RequestMapping("/test") +public class TestController { + + @RequestMapping("/putIntoSession") + public String putIntoSession(HttpServletRequest request, String username) { + request.getSession().setAttribute("name", "leo"); + return "ok"; + } + + @RequestMapping("/getFromSession") + public String getFromSession(HttpServletRequest request, Model model){ + String name = request.getSession().getAttribute("name"); + return name; + } +} +``` + +上面的代码就是 ok 的,给 sping session 配置基于 redis 来存储 session 数据,然后配置了一个 spring session 的过滤器,这样的话,session 相关操作都会交给 spring session 来管了。接着在代码中,就用原生的 session 操作,就是直接基于 spring sesion 从 redis 中获取数据了。 + +实现分布式的会话有很多种方式,我说的只不过是比较常见的几种方式,tomcat + redis 早期比较常用,但是会重耦合到 tomcat 中;近些年,通过 spring session 来实现。 diff --git a/docs/distributed/distributed-transaction.md b/docs/distributed/distributed-transaction.md new file mode 100644 index 00000000..f80eae9d --- /dev/null +++ b/docs/distributed/distributed-transaction.md @@ -0,0 +1,91 @@ +# 分布式事务 + +分布式事务的实现主要有以下 5 种方案: + +- XA 方案 +- TCC 方案 +- 本地消息表 +- 可靠消息最终一致性方案 +- 最大努力通知方案 + +### 两阶段提交方案/XA方案 + +所谓的 XA 方案,即:两阶段提交,有一个**事务管理器**的概念,负责协调多个数据库(资源管理器)的事务,事务管理器先问问各个数据库你准备好了吗?如果每个数据库都回复 ok,那么就正式提交事务,在各个数据库上执行操作;如果任何其中一个数据库回答不 ok,那么就回滚事务。 + +这种分布式事务方案,比较适合单块应用里,跨多个库的分布式事务,而且因为严重依赖于数据库层面来搞定复杂的事务,效率很低,绝对不适合高并发的场景。如果要玩儿,那么基于 `Spring + JTA` 就可以搞定,自己随便搜个 demo 看看就知道了。 + +这个方案,我们很少用,一般来说**某个系统内部如果出现跨多个库**的这么一个操作,是**不合规**的。我可以给大家介绍一下, 现在微服务,一个大的系统分成几十个甚至几百个服务。一般来说,我们的规定和规范,是要求**每个服务只能操作自己对应的一个数据库**。 + +如果你要操作别的服务对应的库,不允许直连别的服务的库,违反微服务架构的规范,你随便交叉胡乱访问,几百个服务的话,全体乱套,这样的一套服务是没法管理的,没法治理的,可能会出现数据被别人改错,自己的库被别人写挂等情况。 + +如果你要操作别人的服务的库,你必须是通过**调用别的服务的接口**来实现,绝对不允许交叉访问别人的数据库。 + +[![distributed-transacion-XA](https://github.com/doocs/advanced-java/raw/master/images/distributed-transaction-XA.png)](https://github.com/doocs/advanced-java/blob/master/images/distributed-transaction-XA.png) + +### TCC 方案 + +TCC 的全称是:`Try`、`Confirm`、`Cancel`。 + +- Try 阶段:这个阶段说的是对各个服务的资源做检测以及对资源进行**锁定或者预留**。 +- Confirm 阶段:这个阶段说的是在各个服务中**执行实际的操作**。 +- Cancel 阶段:如果任何一个服务的业务方法执行出错,那么这里就需要**进行补偿**,就是执行已经执行成功的业务逻辑的回滚操作。(把那些执行成功的回滚) + +这种方案说实话几乎很少人使用,我们用的也比较少,但是也有使用的场景。因为这个**事务回滚**实际上是**严重依赖于你自己写代码来回滚和补偿**了,会造成补偿代码巨大,非常之恶心。 + +比如说我们,一般来说跟**钱**相关的,跟钱打交道的,**支付**、**交易**相关的场景,我们会用 TCC,严格保证分布式事务要么全部成功,要么全部自动回滚,严格保证资金的正确性,保证在资金上不会出现问题。 + +而且最好是你的各个业务执行的时间都比较短。 + +但是说实话,一般尽量别这么搞,自己手写回滚逻辑,或者是补偿逻辑,实在太恶心了,那个业务代码是很难维护的。 + +[![distributed-transacion-TCC](https://github.com/doocs/advanced-java/raw/master/images/distributed-transaction-TCC.png)](https://github.com/doocs/advanced-java/blob/master/images/distributed-transaction-TCC.png) + +### 本地消息表 + +本地消息表其实是国外的 ebay 搞出来的这么一套思想。 + +这个大概意思是这样的: + +1. A 系统在自己本地一个事务里操作同时,插入一条数据到消息表; +2. 接着 A 系统将这个消息发送到 MQ 中去; +3. B 系统接收到消息之后,在一个事务里,往自己本地消息表里插入一条数据,同时执行其他的业务操作,如果这个消息已经被处理过了,那么此时这个事务会回滚,这样**保证不会重复处理消息**; +4. B 系统执行成功之后,就会更新自己本地消息表的状态以及 A 系统消息表的状态; +5. 如果 B 系统处理失败了,那么就不会更新消息表状态,那么此时 A 系统会定时扫描自己的消息表,如果有未处理的消息,会再次发送到 MQ 中去,让 B 再次处理; +6. 这个方案保证了最终一致性,哪怕 B 事务失败了,但是 A 会不断重发消息,直到 B 那边成功为止。 + +这个方案说实话最大的问题就在于**严重依赖于数据库的消息表来管理事务**啥的,如果是高并发场景咋办呢?咋扩展呢?所以一般确实很少用。 + +[![distributed-transaction-local-message-table](https://github.com/doocs/advanced-java/raw/master/images/distributed-transaction-local-message-table.png)](https://github.com/doocs/advanced-java/blob/master/images/distributed-transaction-local-message-table.png) + +### 可靠消息最终一致性方案 + +这个的意思,就是干脆不要用本地的消息表了,直接基于 MQ 来实现事务。比如阿里的 RocketMQ 就支持消息事务。 + +大概的意思就是: + +1. A 系统先发送一个 prepared 消息到 mq,如果这个 prepared 消息发送失败那么就直接取消操作别执行了; +2. 如果这个消息发送成功过了,那么接着执行本地事务,如果成功就告诉 mq 发送确认消息,如果失败就告诉 mq 回滚消息; +3. 如果发送了确认消息,那么此时 B 系统会接收到确认消息,然后执行本地的事务; +4. mq 会自动**定时轮询**所有 prepared 消息回调你的接口,问你,这个消息是不是本地事务处理失败了,所有没发送确认的消息,是继续重试还是回滚?一般来说这里你就可以查下数据库看之前本地事务是否执行,如果回滚了,那么这里也回滚吧。这个就是避免可能本地事务执行成功了,而确认消息却发送失败了。 +5. 这个方案里,要是系统 B 的事务失败了咋办?重试咯,自动不断重试直到成功,如果实在是不行,要么就是针对重要的资金类业务进行回滚,比如 B 系统本地回滚后,想办法通知系统 A 也回滚;或者是发送报警由人工来手工回滚和补偿。 +6. 这个还是比较合适的,目前国内互联网公司大都是这么玩儿的,要不你举用 RocketMQ 支持的,要不你就自己基于类似 ActiveMQ?RabbitMQ?自己封装一套类似的逻辑出来,总之思路就是这样子的。 + +[![distributed-transaction-reliable-message](https://github.com/doocs/advanced-java/raw/master/images/distributed-transaction-reliable-message.png)](https://github.com/doocs/advanced-java/blob/master/images/distributed-transaction-reliable-message.png) + +### 最大努力通知方案 + +这个方案的大致意思就是: + +1. 系统 A 本地事务执行完之后,发送个消息到 MQ; +2. 这里会有个专门消费 MQ 的**最大努力通知服务**,这个服务会消费 MQ 然后写入数据库中记录下来,或者是放入个内存队列也可以,接着调用系统 B 的接口; +3. 要是系统 B 执行成功就 ok 了;要是系统 B 执行失败了,那么最大努力通知服务就定时尝试重新调用系统 B,反复 N 次,最后还是不行就放弃。 + +### 你们公司是如何处理分布式事务的? + +如果你真的被问到,可以这么说,我们某某特别严格的场景,用的是 TCC 来保证强一致性;然后其他的一些场景基于阿里的 RocketMQ 来实现分布式事务。 + +你找一个严格资金要求绝对不能错的场景,你可以说你是用的 TCC 方案;如果是一般的分布式事务场景,订单插入之后要调用库存服务更新库存,库存数据没有资金那么的敏感,可以用可靠消息最终一致性方案。 + +友情提示一下,RocketMQ 3.2.6 之前的版本,是可以按照上面的思路来的,但是之后接口做了一些改变,我这里不再赘述了。 + +当然如果你愿意,你可以参考可靠消息最终一致性方案来自己实现一套分布式事务,比如基于 RocketMQ 来玩儿。 \ No newline at end of file diff --git a/docs/distributed/mq/mq-interview.md b/docs/distributed/mq/mq-interview.md new file mode 100644 index 00000000..b1f09610 --- /dev/null +++ b/docs/distributed/mq/mq-interview.md @@ -0,0 +1,375 @@ +# 消息队列面试夺命连环问 + + + +- [1. 为什么使用消息队列?](#1-为什么使用消息队列) +- [2. 消息队列有什么优缺点](#2-消息队列有什么优缺点) +- [3. Kafka、ActiveMQ、RabbitMQ、RocketMQ 有什么优缺点?](#3-kafkaactivemqrabbitmqrocketmq-有什么优缺点) +- [4. 如何保证消息队列的高可用?](#4-如何保证消息队列的高可用) +- [5. 如何保证消息不被重复消费?(如何保证消息消费的幂等性)](#5-如何保证消息不被重复消费如何保证消息消费的幂等性) +- [6. 如何保证消息的可靠性传输?(如何处理消息丢失的问题)](#6-如何保证消息的可靠性传输如何处理消息丢失的问题) +- [7. 如何保证消息的顺序性?](#7-如何保证消息的顺序性) +- [8. 如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决?](#8-如何解决消息队列的延时以及过期失效问题消息队列满了以后该怎么处理有几百万消息持续积压几小时说说怎么解决) +- [9. 如果让你写一个消息队列,该如何进行架构设计啊?说一下你的思路。](#9-如果让你写一个消息队列该如何进行架构设计啊说一下你的思路) + + + +## 1. 为什么使用消息队列? + +### 解耦 + +看这么个场景。A 系统发送数据到 BCD 三个系统,通过接口调用发送。如果 E 系统也要这个数据呢?那如果 C 系统现在不需要了呢?A 系统负责人几乎崩溃...... + +[![mq-1](https://github.com/doocs/advanced-java/raw/master/images/mq-1.png)](https://github.com/doocs/advanced-java/blob/master/images/mq-1.png) + +在这个场景中,A 系统跟其它各种乱七八糟的系统严重耦合,A 系统产生一条比较关键的数据,很多系统都需要 A 系统将这个数据发送过来。A 系统要时时刻刻考虑 BCDE 四个系统如果挂了该咋办?要不要重发,要不要把消息存起来?头发都白了啊! + +如果使用 MQ,A 系统产生一条数据,发送到 MQ 里面去,哪个系统需要数据自己去 MQ 里面消费。如果新系统需要数据,直接从 MQ 里消费即可;如果某个系统不需要这条数据了,就取消对 MQ 消息的消费即可。这样下来,A 系统压根儿不需要去考虑要给谁发送数据,不需要维护这个代码,也不需要考虑人家是否调用成功、失败超时等情况。 + +[![mq-2](https://github.com/doocs/advanced-java/raw/master/images/mq-2.png)](https://github.com/doocs/advanced-java/blob/master/images/mq-2.png) + +**总结**:通过一个 MQ,Pub/Sub 发布订阅消息这么一个模型,A 系统就跟其它系统彻底解耦了。 + +**面试技巧**:你需要去考虑一下你负责的系统中是否有类似的场景,就是一个系统或者一个模块,调用了多个系统或者模块,互相之间的调用很复杂,维护起来很麻烦。但是其实这个调用是不需要直接同步调用接口的,如果用 MQ 给它异步化解耦,也是可以的,你就需要去考虑在你的项目里,是不是可以运用这个 MQ 去进行系统的解耦。在简历中体现出来这块东西,用 MQ 作解耦。 + +### 异步 + +再来看一个场景,A 系统接收一个请求,需要在自己本地写库,还需要在 BCD 三个系统写库,自己本地写库要 3ms,BCD 三个系统分别写库要 300ms、450ms、200ms。最终请求总延时是 3 + 300 + 450 + 200 = 953ms,接近 1s,用户感觉搞个什么东西,慢死了慢死了。用户通过浏览器发起请求,等待个 1s,这几乎是不可接受的。 + +[![mq-3](https://github.com/doocs/advanced-java/raw/master/images/mq-3.png)](https://github.com/doocs/advanced-java/blob/master/images/mq-3.png) + +一般互联网类的企业,对于用户直接的操作,一般要求是每个请求都必须在 200 ms 以内完成,对用户几乎是无感知的。 + +如果**使用 MQ**,那么 A 系统连续发送 3 条消息到 MQ 队列中,假如耗时 5ms,A 系统从接受一个请求到返回响应给用户,总时长是 3 + 5 = 8ms,对于用户而言,其实感觉上就是点个按钮,8ms 以后就直接返回了,爽!网站做得真好,真快! + +[![mq-4](https://github.com/doocs/advanced-java/raw/master/images/mq-4.png)](https://github.com/doocs/advanced-java/blob/master/images/mq-4.png) + +### 削峰 + +每天 0:00 到 12:00,A 系统风平浪静,每秒并发请求数量就 50 个。结果每次一到 12:00 ~ 13:00 ,每秒并发请求数量突然会暴增到 5k+ 条。但是系统是直接基于 MySQL 的,大量的请求涌入 MySQL,每秒钟对 MySQL 执行约 5k 条 SQL。 + +一般的 MySQL,扛到每秒 2k 个请求就差不多了,如果每秒请求到 5k 的话,可能就直接把 MySQL 给打死了,导致系统崩溃,用户也就没法再使用系统了。 + +但是高峰期一过,到了下午的时候,就成了低峰期,可能也就 1w 的用户同时在网站上操作,每秒中的请求数量可能也就 50 个请求,对整个系统几乎没有任何的压力。 + +[![mq-5](https://github.com/doocs/advanced-java/raw/master/images/mq-5.png)](https://github.com/doocs/advanced-java/blob/master/images/mq-5.png) + +如果使用 MQ,每秒 5k 个请求写入 MQ,A 系统每秒钟最多处理 2k 个请求,因为 MySQL 每秒钟最多处理 2k 个。A 系统从 MQ 中慢慢拉取请求,每秒钟就拉取 2k 个请求,不要超过自己每秒能处理的最大请求数量就 ok,这样下来,哪怕是高峰期的时候,A 系统也绝对不会挂掉。而 MQ 每秒钟 5k 个请求进来,就 2k 个请求出去,结果就导致在中午高峰期(1 个小时),可能有几十万甚至几百万的请求积压在 MQ 中。 + +[![mq-6](https://github.com/doocs/advanced-java/raw/master/images/mq-6.png)](https://github.com/doocs/advanced-java/blob/master/images/mq-6.png) + +这个短暂的高峰期积压是 ok 的,因为高峰期过了之后,每秒钟就 50 个请求进 MQ,但是 A 系统依然会按照每秒 2k 个请求的速度在处理。所以说,只要高峰期一过,A 系统就会快速将积压的消息给解决掉。 + +## 2. 消息队列有什么优缺点 + +优点上面已经说了,就是**在特殊场景下有其对应的好处**,**解耦**、**异步**、**削峰**。 + +缺点有以下几个: + +- 系统可用性降低 + 系统引入的外部依赖越多,越容易挂掉。本来你就是 A 系统调用 BCD 三个系统的接口就好了,人 ABCD 四个系统好好的,没啥问题,你偏加个 MQ 进来,万一 MQ 挂了咋整,MQ 一挂,整套系统崩溃的,你不就完了?如何保证消息队列的高可用,可以[点击这里查看](https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/how-to-ensure-high-availability-of-message-queues.md)。 +- 系统复杂度提高 + 硬生生加个 MQ 进来,你怎么[保证消息没有重复消费](https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/how-to-ensure-that-messages-are-not-repeatedly-consumed.md)?怎么[处理消息丢失的情况](https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/how-to-ensure-the-reliable-transmission-of-messages.md)?怎么保证消息传递的顺序性?头大头大,问题一大堆,痛苦不已。 +- 一致性问题 + A 系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是 BCD 三个系统那里,BD 两个系统写库成功了,结果 C 系统写库失败了,咋整?你这数据就不一致了。 + +所以消息队列实际是一种非常复杂的架构,你引入它有很多好处,但是也得针对它带来的坏处做各种额外的技术方案和架构来规避掉,做好之后,你会发现,妈呀,系统复杂度提升了一个数量级,也许是复杂了 10 倍。但是关键时刻,用,还是得用的。 + +## 3. Kafka、ActiveMQ、RabbitMQ、RocketMQ 有什么优缺点? + +| 特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafka | +| ------------------------ | ------------------------------------- | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | +| 单机吞吐量 | 万级,比 RocketMQ、Kafka 低一个数量级 | 同 ActiveMQ | 10 万级,支撑高吞吐 | 10 万级,高吞吐,一般配合大数据类的系统来进行实时数据计算、日志采集等场景 | +| topic 数量对吞吐量的影响 | | | topic 可以达到几百/几千的级别,吞吐量会有较小幅度的下降,这是 RocketMQ 的一大优势,在同等机器下,可以支撑大量的 topic | topic 从几十到几百个时候,吞吐量会大幅度下降,在同等机器下,Kafka 尽量保证 topic 数量不要过多,如果要支撑大规模的 topic,需要增加更多的机器资源 | +| 时效性 | ms 级 | 微秒级,这是 RabbitMQ 的一大特点,延迟最低 | ms 级 | 延迟在 ms 级以内 | +| 可用性 | 高,基于主从架构实现高可用 | 同 ActiveMQ | 非常高,分布式架构 | 非常高,分布式,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用 | +| 消息可靠性 | 有较低的概率丢失数据 | 基本不丢 | 经过参数优化配置,可以做到 0 丢失 | 同 RocketMQ | +| 功能支持 | MQ 领域的功能极其完备 | 基于 erlang 开发,并发能力很强,性能极好,延时很低 | MQ 功能较为完善,还是分布式的,扩展性好 | 功能较为简单,主要支持简单的 MQ 功能,在大数据领域的实时计算以及日志采集被大规模使用 | + +综上,各种对比之后,有如下建议: + +- 一般的业务系统要引入 MQ,最早大家都用 ActiveMQ,但是现在确实大家用的不多了,没经过大规模吞吐量场景的验证,社区也不是很活跃,所以大家还是算了吧,我个人不推荐用这个了; +- 后来大家开始用 RabbitMQ,但是确实 erlang 语言阻止了大量的 Java 工程师去深入研究和掌控它,对公司而言,几乎处于不可控的状态,但是确实人家是开源的,比较稳定的支持,活跃度也高; +- 不过现在确实越来越多的公司会去用 RocketMQ,确实很不错,毕竟是阿里出品,但社区可能有突然黄掉的风险(目前 RocketMQ 已捐给 [Apache](https://github.com/apache/rocketmq),但 GitHub 上的活跃度其实不算高)对自己公司技术实力有绝对自信的,推荐用 RocketMQ,否则回去老老实实用 RabbitMQ 吧,人家有活跃的开源社区,绝对不会黄。 +- 所以**中小型公司**,技术实力较为一般,技术挑战不是特别高,用 RabbitMQ 是不错的选择;**大型公司**,基础架构研发实力较强,用 RocketMQ 是很好的选择。 +- 如果是**大数据领域**的实时计算、日志采集等场景,用 Kafka 是业内标准的,绝对没问题,社区活跃度很高,绝对不会黄,何况几乎是全世界这个领域的事实性规范。 + +## 4. 如何保证消息队列的高可用? + +### RabbitMQ 的高可用性 + +RabbitMQ 是比较有代表性的,因为是**基于主从**(非分布式)做高可用性的,我们就以 RabbitMQ 为例子讲解第一种 MQ 的高可用性怎么实现。 + +RabbitMQ 有三种模式:单机模式、普通集群模式、镜像集群模式。 + +#### 单机模式 + +单机模式,就是 Demo 级别的,一般就是你本地启动了玩玩儿的 😄,没人生产用单机模式。 + +#### 普通集群模式(无高可用性) + +普通集群模式,意思就是在多台机器上启动多个 RabbitMQ 实例,每个机器启动一个。你**创建的 queue,只会放在一个 RabbitMQ 实例上**,但是每个实例都同步 queue 的元数据(元数据可以认为是 queue 的一些配置信息,通过元数据,可以找到 queue 所在实例)。你消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从 queue 所在实例上拉取数据过来。 + +[![mq-7](https://github.com/doocs/advanced-java/raw/master/images/mq-7.png)](https://github.com/doocs/advanced-java/blob/master/images/mq-7.png) + +这种方式确实很麻烦,也不怎么好,**没做到所谓的分布式**,就是个普通集群。因为这导致你要么消费者每次随机连接一个实例然后拉取数据,要么固定连接那个 queue 所在实例消费数据,前者有**数据拉取的开销**,后者导致**单实例性能瓶颈**。 + +而且如果那个放 queue 的实例宕机了,会导致接下来其他实例就无法从那个实例拉取,如果你**开启了消息持久化**,让 RabbitMQ 落地存储消息的话,**消息不一定会丢**,得等这个实例恢复了,然后才可以继续从这个 queue 拉取数据。 + +所以这个事儿就比较尴尬了,这就**没有什么所谓的高可用性**,**这方案主要是提高吞吐量的**,就是说让集群中多个节点来服务某个 queue 的读写操作。 + +#### 镜像集群模式(高可用性) + +这种模式,才是所谓的 RabbitMQ 的高可用模式。跟普通集群模式不一样的是,在镜像集群模式下,你创建的 queue,无论元数据还是 queue 里的消息都会**存在于多个实例上**,就是说,每个 RabbitMQ 节点都有这个 queue 的一个**完整镜像**,包含 queue 的全部数据的意思。然后每次你写消息到 queue 的时候,都会自动把**消息同步**到多个实例的 queue 上。 + +[![mq-8](https://github.com/doocs/advanced-java/raw/master/images/mq-8.png)](https://github.com/doocs/advanced-java/blob/master/images/mq-8.png) + +那么**如何开启这个镜像集群模式**呢?其实很简单,RabbitMQ 有很好的管理控制台,就是在后台新增一个策略,这个策略是**镜像集群模式的策略**,指定的时候是可以要求数据同步到所有节点的,也可以要求同步到指定数量的节点,再次创建 queue 的时候,应用这个策略,就会自动将数据同步到其他的节点上去了。 + +这样的话,好处在于,你任何一个机器宕机了,没事儿,其它机器(节点)还包含了这个 queue 的完整数据,别的 consumer 都可以到其它节点上去消费数据。坏处在于,第一,这个性能开销也太大了吧,消息需要同步到所有机器上,导致网络带宽压力和消耗很重!第二,这么玩儿,不是分布式的,就**没有扩展性可言**了,如果某个 queue 负载很重,你加机器,新增的机器也包含了这个 queue 的所有数据,并**没有办法线性扩展**你的 queue。你想,如果这个 queue 的数据量很大,大到这个机器上的容量无法容纳了,此时该怎么办呢? + +### Kafka 的高可用性 + +Kafka 一个最基本的架构认识:由多个 broker 组成,每个 broker 是一个节点;你创建一个 topic,这个 topic 可以划分为多个 partition,每个 partition 可以存在于不同的 broker 上,每个 partition 就放一部分数据。 + +这就是**天然的分布式消息队列**,就是说一个 topic 的数据,是**分散放在多个机器上的,每个机器就放一部分数据**。 + +实际上 RabbmitMQ 之类的,并不是分布式消息队列,它就是传统的消息队列,只不过提供了一些集群、HA(High Availability, 高可用性) 的机制而已,因为无论怎么玩儿,RabbitMQ 一个 queue 的数据都是放在一个节点里的,镜像集群下,也是每个节点都放这个 queue 的完整数据。 + +Kafka 0.8 以前,是没有 HA 机制的,就是任何一个 broker 宕机了,那个 broker 上的 partition 就废了,没法写也没法读,没有什么高可用性可言。 + +比如说,我们假设创建了一个 topic,指定其 partition 数量是 3 个,分别在三台机器上。但是,如果第二台机器宕机了,会导致这个 topic 的 1/3 的数据就丢了,因此这个是做不到高可用的。 + +[![kafka-before](https://github.com/doocs/advanced-java/raw/master/images/kafka-before.png)](https://github.com/doocs/advanced-java/blob/master/images/kafka-before.png) + +Kafka 0.8 以后,提供了 HA 机制,就是 replica(复制品) 副本机制。每个 partition 的数据都会同步到其它机器上,形成自己的多个 replica 副本。所有 replica 会选举一个 leader 出来,那么生产和消费都跟这个 leader 打交道,然后其他 replica 就是 follower。写的时候,leader 会负责把数据同步到所有 follower 上去,读的时候就直接读 leader 上的数据即可。只能读写 leader?很简单,**要是你可以随意读写每个 follower,那么就要 care 数据一致性的问题**,系统复杂度太高,很容易出问题。Kafka 会均匀地将一个 partition 的所有 replica 分布在不同的机器上,这样才可以提高容错性。 + +[![kafka-after](https://github.com/doocs/advanced-java/raw/master/images/kafka-after.png)](https://github.com/doocs/advanced-java/blob/master/images/kafka-after.png) + +这么搞,就有所谓的**高可用性**了,因为如果某个 broker 宕机了,没事儿,那个 broker 上面的 partition 在其他机器上都有副本的。如果这个宕机的 broker 上面有某个 partition 的 leader,那么此时会从 follower 中**重新选举**一个新的 leader 出来,大家继续读写那个新的 leader 即可。这就有所谓的高可用性了。 + +**写数据**的时候,生产者就写 leader,然后 leader 将数据落地写本地磁盘,接着其他 follower 自己主动从 leader 来 pull 数据。一旦所有 follower 同步好数据了,就会发送 ack 给 leader,leader 收到所有 follower 的 ack 之后,就会返回写成功的消息给生产者。(当然,这只是其中一种模式,还可以适当调整这个行为) + +**消费**的时候,只会从 leader 去读,但是只有当一个消息已经被所有 follower 都同步成功返回 ack 的时候,这个消息才会被消费者读到。 + +看到这里,相信你大致明白了 Kafka 是如何保证高可用机制的了,对吧?不至于一无所知,现场还能给面试官画画图。要是遇上面试官确实是 Kafka 高手,深挖了问,那你只能说不好意思,太深入的你没研究过。 + +## 5. 如何保证消息不被重复消费?(如何保证消息消费的幂等性) + +首先,比如 RabbitMQ、RocketMQ、Kafka,都有可能会出现消息重复消费的问题,正常。因为这问题通常不是 MQ 自己保证的,是由我们开发来保证的。挑一个 Kafka 来举个例子,说说怎么重复消费吧。 + +Kafka 实际上有个 offset 的概念,就是每个消息写进去,都有一个 offset,代表消息的序号,然后 consumer 消费了数据之后,**每隔一段时间**(定时定期),会把自己消费过的消息的 offset 提交一下,表示“我已经消费过了,下次我要是重启啥的,你就让我继续从上次消费到的 offset 来继续消费吧”。 + +但是凡事总有意外,比如我们之前生产经常遇到的,就是你有时候重启系统,看你怎么重启了,如果碰到点着急的,直接 kill 进程了,再重启。这会导致 consumer 有些消息处理了,但是没来得及提交 offset,尴尬了。重启之后,少数消息会再次消费一次。 + +举个栗子。 + +有这么个场景。数据 1/2/3 依次进入 kafka,kafka 会给这三条数据每条分配一个 offset,代表这条数据的序号,我们就假设分配的 offset 依次是 152/153/154。消费者从 kafka 去消费的时候,也是按照这个顺序去消费。假如当消费者消费了 `offset=153` 的这条数据,刚准备去提交 offset 到 zookeeper,此时消费者进程被重启了。那么此时消费过的数据 1/2 的 offset 并没有提交,kafka 也就不知道你已经消费了 `offset=153` 这条数据。那么重启之后,消费者会找 kafka 说,嘿,哥儿们,你给我接着把上次我消费到的那个地方后面的数据继续给我传递过来。由于之前的 offset 没有提交成功,那么数据 1/2 会再次传过来,如果此时消费者没有去重的话,那么就会导致重复消费。 + +[![mq-10](https://github.com/doocs/advanced-java/raw/master/images/mq-10.png)](https://github.com/doocs/advanced-java/blob/master/images/mq-10.png) + +如果消费者干的事儿是拿一条数据就往数据库里写一条,会导致说,你可能就把数据 1/2 在数据库里插入了 2 次,那么数据就错啦。 + +其实重复消费不可怕,可怕的是你没考虑到重复消费之后,**怎么保证幂等性**。 + +举个例子吧。假设你有个系统,消费一条消息就往数据库里插入一条数据,要是你一个消息重复两次,你不就插入了两条,这数据不就错了?但是你要是消费到第二次的时候,自己判断一下是否已经消费过了,若是就直接扔了,这样不就保留了一条数据,从而保证了数据的正确性。 + +一条数据重复出现两次,数据库里就只有一条数据,这就保证了系统的幂等性。 + +幂等性,通俗点说,就一个数据,或者一个请求,给你重复来多次,你得确保对应的数据是不会改变的,**不能出错**。 + +所以第二个问题来了,怎么保证消息队列消费的幂等性? + +其实还是得结合业务来思考,我这里给几个思路: + +- 比如你拿个数据要写库,你先根据主键查一下,如果这数据都有了,你就别插入了,update 一下好吧。 +- 比如你是写 Redis,那没问题了,反正每次都是 set,天然幂等性。 +- 比如你不是上面两个场景,那做的稍微复杂一点,你需要让生产者发送每条数据的时候,里面加一个全局唯一的 id,类似订单 id 之类的东西,然后你这里消费到了之后,先根据这个 id 去比如 Redis 里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个 id 写 Redis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可。 +- 比如基于数据库的唯一键来保证重复数据不会重复插入多条。因为有唯一键约束了,重复数据插入只会报错,不会导致数据库中出现脏数据。 + +[![mq-11](https://github.com/doocs/advanced-java/raw/master/images/mq-11.png)](https://github.com/doocs/advanced-java/blob/master/images/mq-11.png) + +当然,如何保证 MQ 的消费是幂等性的,需要结合具体的业务来看。 + +## 6. 如何保证消息的可靠性传输?(如何处理消息丢失的问题) + +数据的丢失问题,可能出现在生产者、MQ、消费者中,咱们从 RabbitMQ 和 Kafka 分别来分析一下吧。 + +### RabbitMQ + +[![rabbitmq-message-lose](https://github.com/doocs/advanced-java/raw/master/images/rabbitmq-message-lose.png)](https://github.com/doocs/advanced-java/blob/master/images/rabbitmq-message-lose.png) + +#### 生产者弄丢了数据 + +生产者将数据发送到 RabbitMQ 的时候,可能数据就在半路给搞丢了,因为网络问题啥的,都有可能。 + +此时可以选择用 RabbitMQ 提供的事务功能,就是生产者**发送数据之前**开启 RabbitMQ 事务`channel.txSelect`,然后发送消息,如果消息没有成功被 RabbitMQ 接收到,那么生产者会收到异常报错,此时就可以回滚事务`channel.txRollback`,然后重试发送消息;如果收到了消息,那么可以提交事务`channel.txCommit`。 + +```java +// 开启事务 +channel.txSelect +try { + // 这里发送消息 +} catch (Exception e) { + channel.txRollback + + // 这里再次重发这条消息 +} + +// 提交事务 +channel.txCommit +``` + +但是问题是,RabbitMQ 事务机制(同步)一搞,基本上**吞吐量会下来,因为太耗性能**。 + +所以一般来说,如果你要确保说写 RabbitMQ 的消息别丢,可以开启 `confirm` 模式,在生产者那里设置开启 `confirm`模式之后,你每次写的消息都会分配一个唯一的 id,然后如果写入了 RabbitMQ 中,RabbitMQ 会给你回传一个 `ack` 消息,告诉你说这个消息 ok 了。如果 RabbitMQ 没能处理这个消息,会回调你的一个 `nack` 接口,告诉你这个消息接收失败,你可以重试。而且你可以结合这个机制自己在内存里维护每个消息 id 的状态,如果超过一定时间还没接收到这个消息的回调,那么你可以重发。 + +事务机制和 `confirm` 机制最大的不同在于,**事务机制是同步的**,你提交一个事务之后会**阻塞**在那儿,但是 `confirm` 机制是**异步**的,你发送个消息之后就可以发送下一个消息,然后那个消息 RabbitMQ 接收了之后会异步回调你的一个接口通知你这个消息接收到了。 + +所以一般在生产者这块**避免数据丢失**,都是用 `confirm` 机制的。 + +#### RabbitMQ 弄丢了数据 + +就是 RabbitMQ 自己弄丢了数据,这个你必须**开启 RabbitMQ 的持久化**,就是消息写入之后会持久化到磁盘,哪怕是 RabbitMQ 自己挂了,**恢复之后会自动读取之前存储的数据**,一般数据不会丢。除非极其罕见的是,RabbitMQ 还没持久化,自己就挂了,**可能导致少量数据丢失**,但是这个概率较小。 + +设置持久化有**两个步骤**: + +- 创建 queue 的时候将其设置为持久化 + 这样就可以保证 RabbitMQ 持久化 queue 的元数据,但是它是不会持久化 queue 里的数据的。 +- 第二个是发送消息的时候将消息的 `deliveryMode` 设置为 2 + 就是将消息设置为持久化的,此时 RabbitMQ 就会将消息持久化到磁盘上去。 + +必须要同时设置这两个持久化才行,RabbitMQ 哪怕是挂了,再次重启,也会从磁盘上重启恢复 queue,恢复这个 queue 里的数据。 + +注意,哪怕是你给 RabbitMQ 开启了持久化机制,也有一种可能,就是这个消息写到了 RabbitMQ 中,但是还没来得及持久化到磁盘上,结果不巧,此时 RabbitMQ 挂了,就会导致内存里的一点点数据丢失。 + +所以,持久化可以跟生产者那边的 `confirm` 机制配合起来,只有消息被持久化到磁盘之后,才会通知生产者 `ack` 了,所以哪怕是在持久化到磁盘之前,RabbitMQ 挂了,数据丢了,生产者收不到 `ack`,你也是可以自己重发的。 + +#### 消费端弄丢了数据 + +RabbitMQ 如果丢失了数据,主要是因为你消费的时候,**刚消费到,还没处理,结果进程挂了**,比如重启了,那么就尴尬了,RabbitMQ 认为你都消费了,这数据就丢了。 + +这个时候得用 RabbitMQ 提供的 `ack` 机制,简单来说,就是你必须关闭 RabbitMQ 的自动 `ack`,可以通过一个 api 来调用就行,然后每次你自己代码里确保处理完的时候,再在程序里 `ack` 一把。这样的话,如果你还没处理完,不就没有 `ack` 了?那 RabbitMQ 就认为你还没处理完,这个时候 RabbitMQ 会把这个消费分配给别的 consumer 去处理,消息是不会丢的。 + +[![rabbitmq-message-lose-solution](https://github.com/doocs/advanced-java/raw/master/images/rabbitmq-message-lose-solution.png)](https://github.com/doocs/advanced-java/blob/master/images/rabbitmq-message-lose-solution.png) + +### Kafka + +#### 消费端弄丢了数据 + +唯一可能导致消费者弄丢数据的情况,就是说,你消费到了这个消息,然后消费者那边**自动提交了 offset**,让 Kafka 以为你已经消费好了这个消息,但其实你才刚准备处理这个消息,你还没处理,你自己就挂了,此时这条消息就丢咯。 + +这不是跟 RabbitMQ 差不多吗,大家都知道 Kafka 会自动提交 offset,那么只要**关闭自动提交** offset,在处理完之后自己手动提交 offset,就可以保证数据不会丢。但是此时确实还是**可能会有重复消费**,比如你刚处理完,还没提交 offset,结果自己挂了,此时肯定会重复消费一次,自己保证幂等性就好了。 + +生产环境碰到的一个问题,就是说我们的 Kafka 消费者消费到了数据之后是写到一个内存的 queue 里先缓冲一下,结果有的时候,你刚把消息写入内存 queue,然后消费者会自动提交 offset。然后此时我们重启了系统,就会导致内存 queue 里还没来得及处理的数据就丢失了。 + +#### Kafka 弄丢了数据 + +这块比较常见的一个场景,就是 Kafka 某个 broker 宕机,然后重新选举 partition 的 leader。大家想想,要是此时其他的 follower 刚好还有些数据没有同步,结果此时 leader 挂了,然后选举某个 follower 成 leader 之后,不就少了一些数据?这就丢了一些数据啊。 + +生产环境也遇到过,我们也是,之前 Kafka 的 leader 机器宕机了,将 follower 切换为 leader 之后,就会发现说这个数据就丢了。 + +所以此时一般是要求起码设置如下 4 个参数: + +- 给 topic 设置 `replication.factor` 参数:这个值必须大于 1,要求每个 partition 必须有至少 2 个副本。 +- 在 Kafka 服务端设置 `min.insync.replicas` 参数:这个值必须大于 1,这个是要求一个 leader 至少感知到有至少一个 follower 还跟自己保持联系,没掉队,这样才能确保 leader 挂了还有一个 follower 吧。 +- 在 producer 端设置 `acks=all`:这个是要求每条数据,必须是**写入所有 replica 之后,才能认为是写成功了**。 +- 在 producer 端设置 `retries=MAX`(很大很大很大的一个值,无限次重试的意思):这个是**要求一旦写入失败,就无限重试**,卡在这里了。 + +我们生产环境就是按照上述要求配置的,这样配置之后,至少在 Kafka broker 端就可以保证在 leader 所在 broker 发生故障,进行 leader 切换时,数据不会丢失。 + +#### 生产者会不会弄丢数据? + +如果按照上述的思路设置了 `acks=all`,一定不会丢,要求是,你的 leader 接收到消息,所有的 follower 都同步到了消息之后,才认为本次写成功了。如果没满足这个条件,生产者会自动不断的重试,重试无限次。 + +## 7. 如何保证消息的顺序性? + +我举个例子,我们以前做过一个 mysql `binlog` 同步的系统,压力还是非常大的,日同步数据要达到上亿,就是说数据从一个 mysql 库原封不动地同步到另一个 mysql 库里面去(mysql -> mysql)。常见的一点在于说比如大数据 team,就需要同步一个 mysql 库过来,对公司的业务系统的数据做各种复杂的操作。 + +你在 mysql 里增删改一条数据,对应出来了增删改 3 条 `binlog` 日志,接着这三条 `binlog` 发送到 MQ 里面,再消费出来依次执行,起码得保证人家是按照顺序来的吧?不然本来是:增加、修改、删除;你楞是换了顺序给执行成删除、修改、增加,不全错了么。 + +本来这个数据同步过来,应该最后这个数据被删除了;结果你搞错了这个顺序,最后这个数据保留下来了,数据同步就出错了。 + +先看看顺序会错乱的俩场景: + +- **RabbitMQ**:一个 queue,多个 consumer。比如,生产者向 RabbitMQ 里发送了三条数据,顺序依次是 data1/data2/data3,压入的是 RabbitMQ 的一个内存队列。有三个消费者分别从 MQ 中消费这三条数据中的一条,结果消费者2先执行完操作,把 data2 存入数据库,然后是 data1/data3。这不明显乱了。 + +[![rabbitmq-order-01](https://github.com/doocs/advanced-java/raw/master/images/rabbitmq-order-01.png)](https://github.com/doocs/advanced-java/blob/master/images/rabbitmq-order-01.png) + +- **Kafka**:比如说我们建了一个 topic,有三个 partition。生产者在写的时候,其实可以指定一个 key,比如说我们指定了某个订单 id 作为 key,那么这个订单相关的数据,一定会被分发到同一个 partition 中去,而且这个 partition 中的数据一定是有顺序的。 + 消费者从 partition 中取出来数据的时候,也一定是有顺序的。到这里,顺序还是 ok 的,没有错乱。接着,我们在消费者里可能会搞**多个线程来并发处理消息**。因为如果消费者是单线程消费处理,而处理比较耗时的话,比如处理一条消息耗时几十 ms,那么 1 秒钟只能处理几十条消息,这吞吐量太低了。而多个线程并发跑的话,顺序可能就乱掉了。 + +[![kafka-order-01](https://github.com/doocs/advanced-java/raw/master/images/kafka-order-01.png)](https://github.com/doocs/advanced-java/blob/master/images/kafka-order-01.png) + +### 解决方案 + +#### RabbitMQ + +拆分多个 queue,每个 queue 一个 consumer,就是多一些 queue 而已,确实是麻烦点;或者就一个 queue 但是对应一个 consumer,然后这个 consumer 内部用内存队列做排队,然后分发给底层不同的 worker 来处理。 [![rabbitmq-order-02](https://github.com/doocs/advanced-java/raw/master/images/rabbitmq-order-02.png)](https://github.com/doocs/advanced-java/blob/master/images/rabbitmq-order-02.png) + +#### Kafka + +- 一个 topic,一个 partition,一个 consumer,内部单线程消费,单线程吞吐量太低,一般不会用这个。 +- 写 N 个内存 queue,具有相同 key 的数据都到同一个内存 queue;然后对于 N 个线程,每个线程分别消费一个内存 queue 即可,这样就能保证顺序性。 + +[![kafka-order-02](https://github.com/doocs/advanced-java/raw/master/images/kafka-order-02.png)](https://github.com/doocs/advanced-java/blob/master/images/kafka-order-02.png) + +## 8. 如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决? + +你看这问法,其实本质针对的场景,都是说,可能你的消费端出了问题,不消费了;或者消费的速度极其慢。接着就坑爹了,可能你的消息队列集群的磁盘都快写满了,都没人消费,这个时候怎么办?或者是这整个就积压了几个小时,你这个时候怎么办?或者是你积压的时间太长了,导致比如 RabbitMQ 设置了消息过期时间后就没了怎么办? + +所以就这事儿,其实线上挺常见的,一般不出,一出就是大 case。一般常见于,举个例子,消费端每次消费之后要写 mysql,结果 mysql 挂了,消费端 hang 那儿了,不动了;或者是消费端出了个什么岔子,导致消费速度极其慢。 + +### 面试题剖析 + +关于这个事儿,我们一个一个来梳理吧,先假设一个场景,我们现在消费端出故障了,然后大量消息在 mq 里积压,现在出事故了,慌了。 + +### 大量消息在 mq 里积压了几个小时了还没解决 + +几千万条数据在 MQ 里积压了七八个小时,从下午 4 点多,积压到了晚上 11 点多。这个是我们真实遇到过的一个场景,确实是线上故障了,这个时候要不然就是修复 consumer 的问题,让它恢复消费速度,然后傻傻的等待几个小时消费完毕。这个肯定不能在面试的时候说吧。 + +一个消费者一秒是 1000 条,一秒 3 个消费者是 3000 条,一分钟就是 18 万条。所以如果你积压了几百万到上千万的数据,即使消费者恢复了,也需要大概 1 小时的时间才能恢复过来。 + +一般这个时候,只能临时紧急扩容了,具体操作步骤和思路如下: + +- 先修复 consumer 的问题,确保其恢复消费速度,然后将现有 consumer 都停掉。 +- 新建一个 topic,partition 是原来的 10 倍,临时建立好原先 10 倍的 queue 数量。 +- 然后写一个临时的分发数据的 consumer 程序,这个程序部署上去消费积压的数据,**消费之后不做耗时的处理**,直接均匀轮询写入临时建立好的 10 倍数量的 queue。 +- 接着临时征用 10 倍的机器来部署 consumer,每一批 consumer 消费一个临时 queue 的数据。这种做法相当于是临时将 queue 资源和 consumer 资源扩大 10 倍,以正常的 10 倍速度来消费数据。 +- 等快速消费完积压数据之后,**得恢复原先部署的架构**,**重新**用原先的 consumer 机器来消费消息。 + +### mq 中的消息过期失效了 + +假设你用的是 RabbitMQ,RabbtiMQ 是可以设置过期时间的,也就是 TTL。如果消息在 queue 中积压超过一定的时间就会被 RabbitMQ 给清理掉,这个数据就没了。那这就是第二个坑了。这就不是说数据会大量积压在 mq 里,而是**大量的数据会直接搞丢**。 + +这个情况下,就不是说要增加 consumer 消费积压的消息,因为实际上没啥积压,而是丢了大量的消息。我们可以采取一个方案,就是**批量重导**,这个我们之前线上也有类似的场景干过。就是大量积压的时候,我们当时就直接丢弃数据了,然后等过了高峰期以后,比如大家一起喝咖啡熬夜到晚上12点以后,用户都睡觉了。这个时候我们就开始写程序,将丢失的那批数据,写个临时程序,一点一点的查出来,然后重新灌入 mq 里面去,把白天丢的数据给他补回来。也只能是这样了。 + +假设 1 万个订单积压在 mq 里面,没有处理,其中 1000 个订单都丢了,你只能手动写程序把那 1000 个订单给查出来,手动发到 mq 里去再补一次。 + +### mq 都快写满了 + +如果消息积压在 mq 里,你很长时间都没有处理掉,此时导致 mq 都快写满了,咋办?这个还有别的办法吗?没有,谁让你第一个方案执行的太慢了,你临时写程序,接入数据来消费,**消费一个丢弃一个,都不要了**,快速消费掉所有的消息。然后走第二个方案,到了晚上再补数据吧。 + +## 9. 如果让你写一个消息队列,该如何进行架构设计啊?说一下你的思路。 + +### 面试官心理分析 + +其实聊到这个问题,一般面试官要考察两块: + +- 你有没有对某一个消息队列做过较为深入的原理的了解,或者从整体了解把握住一个消息队列的架构原理。 +- 看看你的设计能力,给你一个常见的系统,就是消息队列系统,看看你能不能从全局把握一下整体架构设计,给出一些关键点出来。 + +说实话,问类似问题的时候,大部分人基本都会蒙,因为平时从来没有思考过类似的问题,大多数人就是平时埋头用,从来不去思考背后的一些东西。类似的问题,比如,如果让你来设计一个 Spring 框架你会怎么做?如果让你来设计一个 Dubbo 框架你会怎么做?如果让你来设计一个 MyBatis 框架你会怎么做? + +### 面试题剖析 + +其实回答这类问题,说白了,不求你看过那技术的源码,起码你要大概知道那个技术的基本原理、核心组成部分、基本架构构成,然后参照一些开源的技术把一个系统设计出来的思路说一下就好。 + +比如说这个消息队列系统,我们从以下几个角度来考虑一下: + +- 首先这个 mq 得支持可伸缩性吧,就是需要的时候快速扩容,就可以增加吞吐量和容量,那怎么搞?设计个分布式的系统呗,参照一下 kafka 的设计理念,broker -> topic -> partition,每个 partition 放一个机器,就存一部分数据。如果现在资源不够了,简单啊,给 topic 增加 partition,然后做数据迁移,增加机器,不就可以存放更多数据,提供更高的吞吐量了? +- 其次你得考虑一下这个 mq 的数据要不要落地磁盘吧?那肯定要了,落磁盘才能保证别进程挂了数据就丢了。那落磁盘的时候怎么落啊?顺序写,这样就没有磁盘随机读写的寻址开销,磁盘顺序读写的性能是很高的,这就是 kafka 的思路。 +- 其次你考虑一下你的 mq 的可用性啊?这个事儿,具体参考之前可用性那个环节讲解的 kafka 的高可用保障机制。多副本 -> leader & follower -> broker 挂了重新选举 leader 即可对外服务。 +- 能不能支持数据 0 丢失啊?可以的,参考我们之前说的那个 kafka 数据零丢失方案。 + +mq 肯定是很复杂的,面试官问你这个问题,其实是个开放题,他就是看看你有没有从架构角度整体构思和设计的思维以及能力。确实这个问题可以刷掉一大批人,因为大部分人平时不思考这些东西。 diff --git a/docs/index.html b/docs/index.html index 3702e0ab..ed66fa21 100644 --- a/docs/index.html +++ b/docs/index.html @@ -28,7 +28,7 @@ .sidebar img { border: none; border-radius: 8px; - box-shadow: 3px 3px 3px #ddd; + box-shadow: 0 0 8px #333333; } .content, From ee7da1719278daf853a6cb70b1f038f09721d33b Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Thu, 27 Jun 2019 13:34:14 +0800 Subject: [PATCH 29/75] =?UTF-8?q?=E6=95=B4=E7=90=86=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kafka => javaweb-mq/javaweb-mq-kafka}/pom.xml | 9 ++++----- .../github/dunwu/javaweb/kafka/ConsumerAOC.java | 0 .../dunwu/javaweb/kafka/ConsumerManual.java | 0 .../javaweb/kafka/ConsumerManualPartition.java | 0 .../github/dunwu/javaweb/kafka/ProducerDemo.java | 7 ++++--- .../javaweb/kafka/ProducerInTransaction.java | 0 .../io/github/dunwu/javaweb/kafka/StreamDemo.java | 0 .../src/main/resources/logback.xml | 0 .../javaweb-mq-rocketmq}/pom.xml | 6 +++--- .../dunwu/javaweb/rocketmq/AsyncProducer.java | 0 .../dunwu/javaweb/rocketmq/BatchProducer.java | 0 .../dunwu/javaweb/rocketmq/BroadcastConsumer.java | 0 .../dunwu/javaweb/rocketmq/BroadcastProducer.java | 0 .../dunwu/javaweb/rocketmq/OnewayProducer.java | 0 .../dunwu/javaweb/rocketmq/OrderedConsumer.java | 0 .../dunwu/javaweb/rocketmq/OrderedProducer.java | 0 .../dunwu/javaweb/rocketmq/RocketConfig.java | 0 .../rocketmq/ScheduledMessageConsumer.java | 0 .../rocketmq/ScheduledMessageProducer.java | 0 .../dunwu/javaweb/rocketmq/SyncProducer.java | 0 .../src/main/resources/logback.xml | 0 codes/javaweb/javaweb-mq/pom.xml | 15 +++++++++++++++ .../javaweb-rpc-zookeeper}/pom.xml | 8 +++++--- .../dunwu/javaweb/zookeeper/ZKConnection.java | 0 .../github/dunwu/javaweb/zookeeper/ZKCreate.java | 0 .../github/dunwu/javaweb/zookeeper/ZKDelete.java | 0 .../github/dunwu/javaweb/zookeeper/ZKExists.java | 0 .../dunwu/javaweb/zookeeper/ZKGetChildren.java | 0 .../github/dunwu/javaweb/zookeeper/ZKGetData.java | 0 .../github/dunwu/javaweb/zookeeper/ZKSetData.java | 0 codes/javaweb/javaweb-rpc/pom.xml | 14 ++++++++++++++ codes/javaweb/pom.xml | 15 +++++++++++++++ 32 files changed, 60 insertions(+), 14 deletions(-) rename codes/javaweb/{mq/kafka => javaweb-mq/javaweb-mq-kafka}/pom.xml (83%) rename codes/javaweb/{mq/kafka => javaweb-mq/javaweb-mq-kafka}/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerAOC.java (100%) rename codes/javaweb/{mq/kafka => javaweb-mq/javaweb-mq-kafka}/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManual.java (100%) rename codes/javaweb/{mq/kafka => javaweb-mq/javaweb-mq-kafka}/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManualPartition.java (100%) rename codes/javaweb/{mq/kafka => javaweb-mq/javaweb-mq-kafka}/src/main/java/io/github/dunwu/javaweb/kafka/ProducerDemo.java (91%) rename codes/javaweb/{mq/kafka => javaweb-mq/javaweb-mq-kafka}/src/main/java/io/github/dunwu/javaweb/kafka/ProducerInTransaction.java (100%) rename codes/javaweb/{mq/kafka => javaweb-mq/javaweb-mq-kafka}/src/main/java/io/github/dunwu/javaweb/kafka/StreamDemo.java (100%) rename codes/javaweb/{mq/kafka => javaweb-mq/javaweb-mq-kafka}/src/main/resources/logback.xml (100%) rename codes/javaweb/{mq/rocketmq => javaweb-mq/javaweb-mq-rocketmq}/pom.xml (84%) rename codes/javaweb/{mq/rocketmq => javaweb-mq/javaweb-mq-rocketmq}/src/main/java/io/github/dunwu/javaweb/rocketmq/AsyncProducer.java (100%) rename codes/javaweb/{mq/rocketmq => javaweb-mq/javaweb-mq-rocketmq}/src/main/java/io/github/dunwu/javaweb/rocketmq/BatchProducer.java (100%) rename codes/javaweb/{mq/rocketmq => javaweb-mq/javaweb-mq-rocketmq}/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastConsumer.java (100%) rename codes/javaweb/{mq/rocketmq => javaweb-mq/javaweb-mq-rocketmq}/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastProducer.java (100%) rename codes/javaweb/{mq/rocketmq => javaweb-mq/javaweb-mq-rocketmq}/src/main/java/io/github/dunwu/javaweb/rocketmq/OnewayProducer.java (100%) rename codes/javaweb/{mq/rocketmq => javaweb-mq/javaweb-mq-rocketmq}/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedConsumer.java (100%) rename codes/javaweb/{mq/rocketmq => javaweb-mq/javaweb-mq-rocketmq}/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedProducer.java (100%) rename codes/javaweb/{mq/rocketmq => javaweb-mq/javaweb-mq-rocketmq}/src/main/java/io/github/dunwu/javaweb/rocketmq/RocketConfig.java (100%) rename codes/javaweb/{mq/rocketmq => javaweb-mq/javaweb-mq-rocketmq}/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageConsumer.java (100%) rename codes/javaweb/{mq/rocketmq => javaweb-mq/javaweb-mq-rocketmq}/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageProducer.java (100%) rename codes/javaweb/{mq/rocketmq => javaweb-mq/javaweb-mq-rocketmq}/src/main/java/io/github/dunwu/javaweb/rocketmq/SyncProducer.java (100%) rename codes/javaweb/{mq/rocketmq => javaweb-mq/javaweb-mq-rocketmq}/src/main/resources/logback.xml (100%) create mode 100644 codes/javaweb/javaweb-mq/pom.xml rename codes/javaweb/{rpc/zookeeper => javaweb-rpc/javaweb-rpc-zookeeper}/pom.xml (82%) rename codes/javaweb/{rpc/zookeeper => javaweb-rpc/javaweb-rpc-zookeeper}/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKConnection.java (100%) rename codes/javaweb/{rpc/zookeeper => javaweb-rpc/javaweb-rpc-zookeeper}/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKCreate.java (100%) rename codes/javaweb/{rpc/zookeeper => javaweb-rpc/javaweb-rpc-zookeeper}/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKDelete.java (100%) rename codes/javaweb/{rpc/zookeeper => javaweb-rpc/javaweb-rpc-zookeeper}/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKExists.java (100%) rename codes/javaweb/{rpc/zookeeper => javaweb-rpc/javaweb-rpc-zookeeper}/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKGetChildren.java (100%) rename codes/javaweb/{rpc/zookeeper => javaweb-rpc/javaweb-rpc-zookeeper}/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKGetData.java (100%) rename codes/javaweb/{rpc/zookeeper => javaweb-rpc/javaweb-rpc-zookeeper}/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKSetData.java (100%) create mode 100644 codes/javaweb/javaweb-rpc/pom.xml create mode 100644 codes/javaweb/pom.xml diff --git a/codes/javaweb/mq/kafka/pom.xml b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/pom.xml similarity index 83% rename from codes/javaweb/mq/kafka/pom.xml rename to codes/javaweb/javaweb-mq/javaweb-mq-kafka/pom.xml index 1fcbff96..d3847e69 100644 --- a/codes/javaweb/mq/kafka/pom.xml +++ b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/pom.xml @@ -1,12 +1,12 @@ + xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 4.0.0 - io.github.dunwu - javaweb-mq-Kafka + + io.github.dunwu.javaweb + javaweb-mq-kafka 1.0.0 jar - Javaweb :: MQ :: Kafka UTF-8 @@ -36,5 +36,4 @@ ${project.artifactId} - diff --git a/codes/javaweb/mq/kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerAOC.java b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerAOC.java similarity index 100% rename from codes/javaweb/mq/kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerAOC.java rename to codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerAOC.java diff --git a/codes/javaweb/mq/kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManual.java b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManual.java similarity index 100% rename from codes/javaweb/mq/kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManual.java rename to codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManual.java diff --git a/codes/javaweb/mq/kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManualPartition.java b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManualPartition.java similarity index 100% rename from codes/javaweb/mq/kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManualPartition.java rename to codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManualPartition.java diff --git a/codes/javaweb/mq/kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerDemo.java b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerDemo.java similarity index 91% rename from codes/javaweb/mq/kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerDemo.java rename to codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerDemo.java index 133def52..01772342 100644 --- a/codes/javaweb/mq/kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerDemo.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerDemo.java @@ -1,11 +1,12 @@ package io.github.dunwu.javaweb.kafka; -import java.util.Properties; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.Producer; import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.clients.producer.ProducerRecord; +import java.util.Properties; + /** * Kafka 生产者生产消息示例 生产者配置参考:https://kafka.apache.org/documentation/#producerconfigs */ @@ -22,9 +23,9 @@ public static void main(String[] args) { properties.put(ProducerConfig.LINGER_MS_CONFIG, 1); properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432); properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringSerializer"); + "org.apache.kafka.common.serialization.StringSerializer"); properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringSerializer"); + "org.apache.kafka.common.serialization.StringSerializer"); // 2. 使用配置初始化 Kafka 生产者 Producer producer = new KafkaProducer<>(properties); diff --git a/codes/javaweb/mq/kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerInTransaction.java b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerInTransaction.java similarity index 100% rename from codes/javaweb/mq/kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerInTransaction.java rename to codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerInTransaction.java diff --git a/codes/javaweb/mq/kafka/src/main/java/io/github/dunwu/javaweb/kafka/StreamDemo.java b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/StreamDemo.java similarity index 100% rename from codes/javaweb/mq/kafka/src/main/java/io/github/dunwu/javaweb/kafka/StreamDemo.java rename to codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/StreamDemo.java diff --git a/codes/javaweb/mq/kafka/src/main/resources/logback.xml b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/resources/logback.xml similarity index 100% rename from codes/javaweb/mq/kafka/src/main/resources/logback.xml rename to codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/resources/logback.xml diff --git a/codes/javaweb/mq/rocketmq/pom.xml b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/pom.xml similarity index 84% rename from codes/javaweb/mq/rocketmq/pom.xml rename to codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/pom.xml index 81edd3ba..01ac0736 100644 --- a/codes/javaweb/mq/rocketmq/pom.xml +++ b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/pom.xml @@ -1,12 +1,12 @@ + xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 4.0.0 - io.github.dunwu + + io.github.dunwu.javaweb javaweb-mq-rocketmq 1.0.0 jar - Javaweb :: MQ :: RocketMQ UTF-8 diff --git a/codes/javaweb/mq/rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/AsyncProducer.java b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/AsyncProducer.java similarity index 100% rename from codes/javaweb/mq/rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/AsyncProducer.java rename to codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/AsyncProducer.java diff --git a/codes/javaweb/mq/rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BatchProducer.java b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BatchProducer.java similarity index 100% rename from codes/javaweb/mq/rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BatchProducer.java rename to codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BatchProducer.java diff --git a/codes/javaweb/mq/rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastConsumer.java b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastConsumer.java similarity index 100% rename from codes/javaweb/mq/rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastConsumer.java rename to codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastConsumer.java diff --git a/codes/javaweb/mq/rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastProducer.java b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastProducer.java similarity index 100% rename from codes/javaweb/mq/rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastProducer.java rename to codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastProducer.java diff --git a/codes/javaweb/mq/rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OnewayProducer.java b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OnewayProducer.java similarity index 100% rename from codes/javaweb/mq/rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OnewayProducer.java rename to codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OnewayProducer.java diff --git a/codes/javaweb/mq/rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedConsumer.java b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedConsumer.java similarity index 100% rename from codes/javaweb/mq/rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedConsumer.java rename to codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedConsumer.java diff --git a/codes/javaweb/mq/rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedProducer.java b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedProducer.java similarity index 100% rename from codes/javaweb/mq/rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedProducer.java rename to codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedProducer.java diff --git a/codes/javaweb/mq/rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/RocketConfig.java b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/RocketConfig.java similarity index 100% rename from codes/javaweb/mq/rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/RocketConfig.java rename to codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/RocketConfig.java diff --git a/codes/javaweb/mq/rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageConsumer.java b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageConsumer.java similarity index 100% rename from codes/javaweb/mq/rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageConsumer.java rename to codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageConsumer.java diff --git a/codes/javaweb/mq/rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageProducer.java b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageProducer.java similarity index 100% rename from codes/javaweb/mq/rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageProducer.java rename to codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageProducer.java diff --git a/codes/javaweb/mq/rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/SyncProducer.java b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/SyncProducer.java similarity index 100% rename from codes/javaweb/mq/rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/SyncProducer.java rename to codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/SyncProducer.java diff --git a/codes/javaweb/mq/rocketmq/src/main/resources/logback.xml b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/resources/logback.xml similarity index 100% rename from codes/javaweb/mq/rocketmq/src/main/resources/logback.xml rename to codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/resources/logback.xml diff --git a/codes/javaweb/javaweb-mq/pom.xml b/codes/javaweb/javaweb-mq/pom.xml new file mode 100644 index 00000000..c70d3bdf --- /dev/null +++ b/codes/javaweb/javaweb-mq/pom.xml @@ -0,0 +1,15 @@ + + + 4.0.0 + + io.github.dunwu.javaweb + javaweb-mq + 1.0.0 + pom + + + javaweb-mq-kafka + javaweb-mq-rocketmq + + diff --git a/codes/javaweb/rpc/zookeeper/pom.xml b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/pom.xml similarity index 82% rename from codes/javaweb/rpc/zookeeper/pom.xml rename to codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/pom.xml index 525dfdbd..0b32e2d5 100644 --- a/codes/javaweb/rpc/zookeeper/pom.xml +++ b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/pom.xml @@ -1,18 +1,20 @@ + xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 4.0.0 - io.github.dunwu + + io.github.dunwu.javaweb javaweb-rpc-zookeeper 1.0.0 jar - Javaweb :: Rpc :: Zookeeper + UTF-8 1.8 ${java.version} ${java.version} + org.apache.zookeeper diff --git a/codes/javaweb/rpc/zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKConnection.java b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKConnection.java similarity index 100% rename from codes/javaweb/rpc/zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKConnection.java rename to codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKConnection.java diff --git a/codes/javaweb/rpc/zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKCreate.java b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKCreate.java similarity index 100% rename from codes/javaweb/rpc/zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKCreate.java rename to codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKCreate.java diff --git a/codes/javaweb/rpc/zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKDelete.java b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKDelete.java similarity index 100% rename from codes/javaweb/rpc/zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKDelete.java rename to codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKDelete.java diff --git a/codes/javaweb/rpc/zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKExists.java b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKExists.java similarity index 100% rename from codes/javaweb/rpc/zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKExists.java rename to codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKExists.java diff --git a/codes/javaweb/rpc/zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKGetChildren.java b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKGetChildren.java similarity index 100% rename from codes/javaweb/rpc/zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKGetChildren.java rename to codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKGetChildren.java diff --git a/codes/javaweb/rpc/zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKGetData.java b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKGetData.java similarity index 100% rename from codes/javaweb/rpc/zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKGetData.java rename to codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKGetData.java diff --git a/codes/javaweb/rpc/zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKSetData.java b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKSetData.java similarity index 100% rename from codes/javaweb/rpc/zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKSetData.java rename to codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKSetData.java diff --git a/codes/javaweb/javaweb-rpc/pom.xml b/codes/javaweb/javaweb-rpc/pom.xml new file mode 100644 index 00000000..d98845bf --- /dev/null +++ b/codes/javaweb/javaweb-rpc/pom.xml @@ -0,0 +1,14 @@ + + + 4.0.0 + + io.github.dunwu.javaweb + javaweb-rpc + 1.0.0 + pom + + + javaweb-rpc-zookeeper + + diff --git a/codes/javaweb/pom.xml b/codes/javaweb/pom.xml new file mode 100644 index 00000000..434ef8ba --- /dev/null +++ b/codes/javaweb/pom.xml @@ -0,0 +1,15 @@ + + + 4.0.0 + + io.github.dunwu.javaweb + javaweb + 1.0.0 + pom + + + javaweb-mq + javaweb-rpc + + From f16b7266101da5c1c6d429e75f9aed1fcf4fe3a2 Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Thu, 27 Jun 2019 15:36:02 +0800 Subject: [PATCH 30/75] update docs --- README.md | 110 ++-- docs/README.md | 111 ++-- docs/distributed/README.md | 5 - docs/distributed/rpc/zookeeper-advanced.md | 212 ------- ...00\346\234\257\345\256\236\347\216\260.md" | 355 ------------ docs/index.html | 77 +-- docs/navbar.md | 28 - docs/network/http.md | 1 + docs/standalone/README.md | 4 - .../cache/cache-interview.md | 0 .../technology/cache/cache-theory.md | 0 .../cache/redis.md | 0 docs/technology/monitor/README.md | 5 + .../mq/ActiveMQ.md | 0 .../mq/kafka-advanced.md | 0 .../mq/kafka-basics.md | 0 .../mq/kafka/kafka-advanced.md | 0 .../mq/kafka/kafka-basics.md | 0 .../mq/kafka/kafka-cheat-sheet.md | 0 .../mq/mq-interview.md | 0 .../technology/mq/mq-theory.md | 0 .../mq/rocketmq-advanced.md | 0 .../mq/rocketmq-basics.md | 0 .../{standalone => technology}/orm/mybatis.md | 0 docs/{distributed => technology}/rpc/dubbo.md | 0 .../rpc/zookeeper.md} | 308 ++++++++-- docs/technology/security/security-theory.md | 325 +++++++++++ .../security/shiro.md | 0 docs/technology/security/spring-security.md | 23 + docs/{ => technology}/server/README.md | 0 docs/{ => technology}/server/jetty.md | 0 docs/{ => technology}/server/tomcat.md | 0 docs/theory/README.md | 9 + .../theory/distributed-base.md | 237 +------- docs/theory/distributed-cache.md | 75 +++ .../theory/distributed-interview.md | 0 .../distributed-lock.md | 139 ++++- .../distributed-session.md | 74 ++- .../distributed-transaction.md | 126 ++++- .../theory/load-balance.md | 524 ++++++++++-------- 40 files changed, 1470 insertions(+), 1278 deletions(-) delete mode 100644 docs/distributed/README.md delete mode 100644 docs/distributed/rpc/zookeeper-advanced.md delete mode 100644 "docs/distributed/\345\210\206\345\270\203\345\274\217\346\212\200\346\234\257\345\256\236\347\216\260.md" delete mode 100644 docs/navbar.md delete mode 100644 docs/standalone/README.md rename docs/{distributed => technology}/cache/cache-interview.md (100%) rename "docs/distributed/cache/\345\210\206\345\270\203\345\274\217\347\274\223\345\255\230.md" => docs/technology/cache/cache-theory.md (100%) rename docs/{distributed => technology}/cache/redis.md (100%) create mode 100644 docs/technology/monitor/README.md rename docs/{distributed => technology}/mq/ActiveMQ.md (100%) rename docs/{distributed => technology}/mq/kafka-advanced.md (100%) rename docs/{distributed => technology}/mq/kafka-basics.md (100%) rename docs/{distributed => technology}/mq/kafka/kafka-advanced.md (100%) rename docs/{distributed => technology}/mq/kafka/kafka-basics.md (100%) rename docs/{distributed => technology}/mq/kafka/kafka-cheat-sheet.md (100%) rename docs/{distributed => technology}/mq/mq-interview.md (100%) rename "docs/distributed/mq/\345\210\206\345\270\203\345\274\217\346\266\210\346\201\257\351\230\237\345\210\227.md" => docs/technology/mq/mq-theory.md (100%) rename docs/{distributed => technology}/mq/rocketmq-advanced.md (100%) rename docs/{distributed => technology}/mq/rocketmq-basics.md (100%) rename docs/{standalone => technology}/orm/mybatis.md (100%) rename docs/{distributed => technology}/rpc/dubbo.md (100%) rename docs/{distributed/rpc/zookeeper-basics.md => technology/rpc/zookeeper.md} (59%) create mode 100644 docs/technology/security/security-theory.md rename docs/{standalone => technology}/security/shiro.md (100%) create mode 100644 docs/technology/security/spring-security.md rename docs/{ => technology}/server/README.md (100%) rename docs/{ => technology}/server/jetty.md (100%) rename docs/{ => technology}/server/tomcat.md (100%) create mode 100644 docs/theory/README.md rename "docs/distributed/\345\210\206\345\270\203\345\274\217\345\216\237\347\220\206.md" => docs/theory/distributed-base.md (58%) create mode 100644 docs/theory/distributed-cache.md rename "docs/distributed/\345\210\206\345\270\203\345\274\217\346\212\200\346\234\257\351\235\242\350\257\225\351\242\230.md" => docs/theory/distributed-interview.md (100%) rename docs/{distributed => theory}/distributed-lock.md (61%) rename docs/{distributed => theory}/distributed-session.md (66%) rename docs/{distributed => theory}/distributed-transaction.md (50%) rename "docs/distributed/\350\264\237\350\275\275\345\235\207\350\241\241.md" => docs/theory/load-balance.md (59%) diff --git a/README.md b/README.md index 4ceca8cb..9e8f1761 100644 --- a/README.md +++ b/README.md @@ -5,20 +5,20 @@ > - :repeat: 项目同步维护:[Github](https://github.com/dunwu/javaweb/) | [Gitee](https://gitee.com/turnon/javaweb/) > - :book: 电子书阅读:[Github Pages](https://dunwu.github.io/javaweb/) | [Gitee Pages](http://turnon.gitee.io/javaweb/) -| 🔰 | 🏗 | ☕ | ⭐️ | ✨ | 🕸 | 💻 | -| :-----------: | :-------------------: | :---------------: | :-------------------: | :-----------------------: | :-------------------: | :---------------: | -| [准备](#准备) | [架构设计](#架构设计) | [JavaEE](#javaee) | [主流技术](#主流技术) | [分布式技术](#分布式技术) | [网络通信](#网络通信) | [服务器](#服务器) | +| 🔰 | 🏗 | ☕ | ✨ | ⭐️ | 🕸 | +| :---------------: | :---------------------: | :------------------: | :----------------------: | :-------------------------: | :---------------------: | +| [准备](#🔰️-准备) | [架构设计](#🏗-架构设计) | [JavaEE](#☕-javaee) | [系统原理](#✨-系统原理) | [分布式技术](#⭐️-主流技术) | [网络通信](#🕸-网络通信) | -## 准备 +## 🔰️ 准备 作为 Java Web 工程师,你应该多多少少掌握一些的知识: - [JavaCore](https://dunwu.github.io/javacore/) - Java 核心技术 - [前端技术指南](https://github.com/dunwu/frontend-tutorial) - 即使是后端工程师,也难免会接触到前端技术。前端技术五花八门,如:React、Vue、Angular、Webpack、ES6、Babel、Node.js 等等。不说掌握,至少也应该知道这些技术是什么。 -## 架构设计 +## 🏗 架构设计 -> [架构设计](docs/architecture/) 整理架构设计方面的一些学习总结和心得。 +> [架构设计](docs/architecture) 整理架构设计方面的一些学习总结和心得。 - [大型网站架构概述](docs/architecture/大型网站架构概述.md) - [网站的高性能架构](docs/architecture/网站的高性能架构.md) @@ -28,9 +28,9 @@ - [网站的安全架构](docs/architecture/网站的安全架构.md) - 关键词:XSS、CSRF、SQL 注入、DoS、消息摘要、加密算法、证书 - [网站典型故障](docs/architecture/网站典型故障.md) -## JavaEE +## ☕ JavaEE -> [JavaEE](docs/javaee/) 技术——Java Web 的基石 +> [JavaEE](docs/javaee) 技术——Java Web 的基石 - [JavaEE 面经](docs/javaee/javaee-interview.md) - [JavaEE 之 Servlet 指南](docs/javaee/javaee-servlet.md) @@ -38,47 +38,69 @@ - [JavaEE 之 Filter 和 Listener](docs/javaee/javaee-filter-listener.md) - [JavaEE 之 Cookie 和 Session](docs/javaee/javaee-cookie-sesion.md) -## 主流技术 +## ✨ 系统原理 -> [主流技术](docs/standalone/),典型的技术如:SSM 框架、SSH 框架。 +> [系统原理](docs/theory) - 理论联系实际,分析系统技术层面问题 -- MVC - - [spring-tutorial](https://dunwu.github.io/spring-tutorial/) - Spring 教程 - - [spring-boot-tutorial](https://dunwu.github.io/spring-boot-tutorial/) - Spring Boot 教程 -- ORM - - [Mybatis](docs/standalone/orm/mybatis.md) - 一个支持普通 SQL 查询,存储过程和高级映射的优秀持久层框架。 -- Security - - [Shiro](docs/standalone/security/shiro.md) - 安全框架,具有认证、授权、加密、会话管理功能。 +- [分布式技术面试题](docs/theory/distributed-interview.md) +- [分布式基础理论](docs/theory/distributed-base.md) +- [分布式会话](docs/theory/distributed-session.md) +- [分布式事务](docs/theory/distributed-transaction.md) +- [分布式缓存](docs/theory/distributed-cache.md) +- [分布式锁](docs/theory/distributed-lock.md) +- [负载均衡](docs/theory/load-balance.md) -## 分布式技术 +## ⭐️ 主流技术 -> [分布式技术(Distributed)](docs/distributed/),典型的技术如:分布式缓存、分布式消息队列、分布式服务、分布式搜索引擎等。 +### Web 框架 -- [分布式技术面试题](docs/distributed/分布式技术面试题.md) +- [Spring 教程](https://dunwu.github.io/spring-tutorial/) 📚 +- [Spring Boot 教程](https://dunwu.github.io/spring-boot-tutorial/) 📚 -- [分布式缓存(CACHE)](docs/distributed/cache) - - [分布式缓存](docs/distributed/cache/分布式缓存.md) - - [Redis](docs/distributed/cache/redis.md) - - Memcached -- [分布式服务(RPC)](docs/distributed/rpc) - - [Dubbo](docs/distributed/rpc/dubbo.md) - 基于 Java 开发的高性能 RPC 框架。 - - [ZooKeeper 实战篇](docs/distributed/rpc/zookeeper-basics.md) - - [ZooKeeper 原理篇](docs/distributed/rpc/zookeeper-advanced.md) -- [分布式消息队列(MQ)](docs/distributed/mq) +### 服务器 - - 原理 - - [分布式消息队列](docs/distributed/mq/分布式消息队列.md) - - Kafka - - [Kafka 实战篇](docs/distributed/mq/kafka/kafka-basics.md) - - [Kafka 原理篇](docs/distributed/mq/kafka/kafka-advanced.md) - - [Kafka Cheat Sheet](docs/distributed/mq/kafka/kafka-cheat-sheet.md) - - [RocketMQ 实战篇](docs/distributed/mq/rocketmq-basics.md) - - [RocketMQ 原理篇](docs/distributed/mq/rocketmq-basics.md) - - [ActiveMQ 实战篇](docs/distributed/mq/ActiveMQ.md) - - RabbitMQ - 待补充。。。 +- [Tomcat 应用指南](docs/technology/server/tomcat.md) +- [Jetty 应用指南](docs/technology/server/jetty.md) +- [Nginx 简易教程](https://github.com/dunwu/nginx-tutorial) 📚 -## 网络通信 +### 消息队列 +- [消息队列原理](docs/technology/mq/mq-theory.md) +- Kafka + - [Kafka 实战篇](docs/technology/mq/kafka/kafka-basics.md) + - [Kafka 原理篇](docs/technology/mq/kafka/kafka-advanced.md) + - [Kafka Cheat Sheet](docs/technology/mq/kafka/kafka-cheat-sheet.md) +- [RocketMQ 实战篇](docs/technology/mq/rocketmq-basics.md) +- [RocketMQ 原理篇](docs/technology/mq/rocketmq-basics.md) +- [ActiveMQ 实战篇](docs/technology/mq/ActiveMQ.md) + +### 缓存 + +- [缓存面经](docs/technology/cache/cache-interview.md) +- [缓存原理](docs/technology/cache/cache-theory.md) +- [Redis](https://github.com/dunwu/db-tutorial/tree/master/docs/nosql/redis) +- Ehcache + +### RPC + +- [ZooKeeper 应用指南](docs/technology/rpc/zookeeper.md) +- [Dubbo 应用指南](docs/technology/rpc/dubbo.md) +- Spring Cloud + +### 安全 + +- [系统安全原理](docs/technology/security/security-theory.md) +- [Shiro 应用指南](docs/technology/security/shiro.md) +- [Spring Security 应用指南](docs/technology/security/spring-security.md) + +### ORM + +- [Mybatis 应用指南](docs/technology/orm/mybatis.md) + +## 🕸 网络通信 + +- **面试** + - [网络通信面经](docs/network/network-interview.md) - **网络分层** - [计算机网络概述](docs/network/network-guide.md) - [计算机网络之物理层](docs/network/network-physical.md) @@ -94,11 +116,3 @@ - [网际控制报文协议 ICMP](docs/network/icmp.md) - [网络协议之 WebSocket](docs/network/websocket.md) - [CDN 详解](docs/network/cdn.md) - -## 服务器 - -> [服务器](docs/server/) 章节总结 Java Web 领域主流服务器技术。 - -- [Nginx 简易教程](https://github.com/dunwu/nginx-tutorial) - 轻量级的 Web 服务器、反向代理服务器及电子邮件(IMAP/POP3)代理服务器,支持负载均衡。 -- [Tomcat 应用指南](docs/server/tomcat.md) - 轻量级的应用服务器 -- [Jetty 应用指南](docs/server/jetty.md) - 比 Tomcat 更轻量级的应用服务器 diff --git a/docs/README.md b/docs/README.md index 069f7fa5..780d73c6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -5,9 +5,9 @@ > - :repeat: 项目同步维护:[Github](https://github.com/dunwu/javaweb/) | [Gitee](https://gitee.com/turnon/javaweb/) > - :book: 电子书阅读:[Github Pages](https://dunwu.github.io/javaweb/) | [Gitee Pages](http://turnon.gitee.io/javaweb/) -| 🔰 | 🏗 | ☕ | ⭐️ | ✨ | 🕸 | 💻 | -| :---------------: | :---------------------: | :------------------: | :-----------------------: | :--------------------------: | :---------------------: | :------------------: | -| [准备](#🔰️-准备) | [架构设计](#🏗-架构设计) | [JavaEE](#☕-javaee) | [主流技术](#⭐️-主流技术) | [分布式技术](#✨-分布式技术) | [网络通信](#🕸-网络通信) | [服务器](#💻-服务器) | +| 🔰 | 🏗 | ☕ | ✨ | ⭐️ | 🕸 | +| :---------------: | :---------------------: | :------------------: | :----------------------: | :-------------------------: | :---------------------: | +| [准备](#🔰️-准备) | [架构设计](#🏗-架构设计) | [JavaEE](#☕-javaee) | [系统原理](#✨-系统原理) | [分布式技术](#⭐️-主流技术) | [网络通信](#🕸-网络通信) | ## 🔰️ 准备 @@ -18,7 +18,7 @@ ## 🏗 架构设计 -> [架构设计](architecture/) 整理架构设计方面的一些学习总结和心得。 +> [架构设计](architecture) 整理架构设计方面的一些学习总结和心得。 - [大型网站架构概述](architecture/大型网站架构概述.md) - [网站的高性能架构](architecture/网站的高性能架构.md) @@ -30,7 +30,7 @@ ## ☕ JavaEE -> [JavaEE](javaee/) 技术——Java Web 的基石 +> [JavaEE](javaee) 技术——Java Web 的基石 - [JavaEE 面经](javaee/javaee-interview.md) - [JavaEE 之 Servlet 指南](javaee/javaee-servlet.md) @@ -38,45 +38,64 @@ - [JavaEE 之 Filter 和 Listener](javaee/javaee-filter-listener.md) - [JavaEE 之 Cookie 和 Session](javaee/javaee-cookie-sesion.md) +## ✨ 系统原理 + +> [分布式技术(Distributed)](theory),典型的技术如:分布式缓存、分布式消息队列、分布式服务、分布式搜索引擎等。 + +- [分布式技术面试题](theory/distributed-interview.md) +- [分布式基础理论](theory/distributed-base.md) +- [分布式会话](theory/distributed-session.md) +- [分布式事务](theory/distributed-transaction.md) +- [分布式缓存](theory/distributed-cache.md) +- [分布式锁](theory/distributed-lock.md) +- [负载均衡](theory/load-balance.md) + ## ⭐️ 主流技术 -> [主流技术](standalone/),典型的技术如:SSM 框架、SSH 框架。 - -- MVC - - [spring-tutorial](https://dunwu.github.io/spring-tutorial/) - Spring 教程 - - [spring-boot-tutorial](https://dunwu.github.io/spring-boot-tutorial/) - Spring Boot 教程 -- ORM - - [Mybatis](standalone/orm/mybatis.md) - 一个支持普通 SQL 查询,存储过程和高级映射的优秀持久层框架。 -- Security - - [Shiro](standalone/security/shiro.md) - 安全框架,具有认证、授权、加密、会话管理功能。 - -## ✨ 分布式技术 - -> [分布式技术(Distributed)](distributed/),典型的技术如:分布式缓存、分布式消息队列、分布式服务、分布式搜索引擎等。 - -- [分布式技术面试题](distributed/分布式技术面试题.md) - -- [缓存(CACHE)](distributed/cache) - - [分布式缓存](distributed/cache/分布式缓存.md) - - [Redis](distributed/cache/redis.md) - - Memcached -- [微服务(RPC)](distributed/rpc) - - [Dubbo](distributed/rpc/dubbo.md) - 基于 Java 开发的高性能 RPC 框架。 - - [ZooKeeper 实战篇](distributed/rpc/zookeeper-basics.md) - - [ZooKeeper 原理篇](distributed/rpc/zookeeper-advanced.md) -- [消息队列(MQ)](distributed/mq) - - [消息队列面试夺命连环问](distributed/mq/mq-interview.md) - - [分布式消息队列](distributed/mq/分布式消息队列.md) - - [Kafka 实战篇](distributed/mq/kafka/kafka-basics.md) - - [Kafka 原理篇](distributed/mq/kafka/kafka-advanced.md) - - [Kafka Cheat Sheet](distributed/mq/kafka/kafka-cheat-sheet.md) - - [RocketMQ 实战篇](distributed/mq/rocketmq-basics.md) - - [RocketMQ 原理篇](distributed/mq/rocketmq-basics.md) - - [ActiveMQ 实战篇](distributed/mq/ActiveMQ.md) - - RabbitMQ - 待补充。。。 -- [分布式锁](distributed/distributed-lock.md) -- [分布式会话](distributed/distributed-session.md) -- [分布式事务](distributed/distributed-transaction.md) +### Web 框架 + +- [Spring 教程](https://dunwu.github.io/spring-tutorial/) 📚 +- [Spring Boot 教程](https://dunwu.github.io/spring-boot-tutorial/) 📚 + +### 服务器 + +- [Tomcat 应用指南](technology/server/tomcat.md) +- [Jetty 应用指南](technology/server/jetty.md) +- [Nginx 简易教程](https://github.com/dunwu/nginx-tutorial) 📚 + +### 消息队列 + +- [消息队列原理](technology/mq/mq-theory.md) +- Kafka + - [Kafka 实战篇](technology/mq/kafka/kafka-basics.md) + - [Kafka 原理篇](technology/mq/kafka/kafka-advanced.md) + - [Kafka Cheat Sheet](technology/mq/kafka/kafka-cheat-sheet.md) +- [RocketMQ 实战篇](technology/mq/rocketmq-basics.md) +- [RocketMQ 原理篇](technology/mq/rocketmq-basics.md) +- [ActiveMQ 实战篇](technology/mq/ActiveMQ.md) + +### 缓存 + +- [缓存面经](technology/cache/cache-interview.md) +- [缓存原理](technology/cache/cache-theory.md) +- [Redis](https://github.com/dunwu/db-tutorial/tree/master/docs/nosql/redis) +- Ehcache + +### RPC + +- [ZooKeeper 应用指南](technology/rpc/zookeeper.md) +- [Dubbo 应用指南](technology/rpc/dubbo.md) +- Spring Cloud + +### 安全 + +- [系统安全原理](technology/security/security-theory.md) +- [Shiro 应用指南](technology/security/shiro.md) +- [Spring Security 应用指南](technology/security/spring-security.md) + +### ORM + +- [Mybatis 应用指南](technology/orm/mybatis.md) ## 🕸 网络通信 @@ -97,11 +116,3 @@ - [网际控制报文协议 ICMP](network/icmp.md) - [网络协议之 WebSocket](network/websocket.md) - [CDN 详解](network/cdn.md) - -## 💻 服务器 - -> [服务器](server/) 章节总结 Java Web 领域主流服务器技术。 - -- [Nginx 简易教程](https://github.com/dunwu/nginx-tutorial) - 轻量级的 Web 服务器、反向代理服务器及电子邮件(IMAP/POP3)代理服务器,支持负载均衡。 -- [Tomcat 应用指南](server/tomcat.md) - 轻量级的应用服务器 -- [Jetty 应用指南](server/jetty.md) - 比 Tomcat 更轻量级的应用服务器 diff --git a/docs/distributed/README.md b/docs/distributed/README.md deleted file mode 100644 index 430e2560..00000000 --- a/docs/distributed/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# 分布式系统 - -- [分布式锁](distributed-lock.md) -- [分布式会话](distributed-session.md) -- [分布式事务](distributed-transaction.md) diff --git a/docs/distributed/rpc/zookeeper-advanced.md b/docs/distributed/rpc/zookeeper-advanced.md deleted file mode 100644 index f105a04e..00000000 --- a/docs/distributed/rpc/zookeeper-advanced.md +++ /dev/null @@ -1,212 +0,0 @@ -# ZooKeeper 高级篇 - -> ZooKeeper 是一个分布式应用协调系统,已经用到了许多分布式项目中,用来完成统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等工作。 -> -> 本文侧重于总结 ZooKeeper 工作原理。 - - - -- [1. 概述](#1-概述) - - [1.1. ZooKeeper 是什么?](#11-zookeeper-是什么) - - [1.2. ZooKeeper 提供了什么?](#12-zookeeper-提供了什么) - - [1.3. Zookeeper 的特性](#13-zookeeper-的特性) - - [1.4. 工作原理](#14-工作原理) - - [1.5. Server 工作状态](#15-server-工作状态) -- [2. 文件系统](#2-文件系统) - - [2.1. znode 类型](#21-znode-类型) -- [3. 通知机制](#3-通知机制) -- [4. 应用场景](#4-应用场景) - - [4.1. 统一命名服务(Name Service)](#41-统一命名服务name-service) - - [4.2. 配置管理(Configuration Management)](#42-配置管理configuration-management) - - [4.3. 集群管理(Group Membership)](#43-集群管理group-membership) - - [4.4. 分布式锁](#44-分布式锁) - - [4.5. 队列管理](#45-队列管理) -- [5. 复制](#5-复制) -- [6. 选举流程](#6-选举流程) -- [7. 同步流程](#7-同步流程) -- [8. 资源](#8-资源) - - [8.1. 官方资源](#81-官方资源) - - [8.2. 文章](#82-文章) - - - -## 1. 概述 - -### 1.1. ZooKeeper 是什么? - -ZooKeeper 作为一个分布式的服务框架,主要用来解决分布式集群中应用系统的一致性问题,它能提供基于类似于文件系统的目录节点树方式的数据存储,但是 ZooKeeper 并不是用来专门存储数据的,它的作用主要是用来维护和监控你存储的数据的状态变化。通过监控这些数据状态的变化,从而可以达到基于数据的集群管理。 - -
- -
- -### 1.2. ZooKeeper 提供了什么? - -1. 文件系统 -2. 通知机制 - -### 1.3. Zookeeper 的特性 - -- 最终一致性:client 不论连接到哪个 Server,展示给它都是同一个视图,这是 zookeeper 最重要的性能。 -- 可靠性:具有简单、健壮、良好的性能,如果消息被到一台服务器接受,那么它将被所有的服务器接受。 -- 实时性:Zookeeper 保证客户端将在一个时间间隔范围内获得服务器的更新信息,或者服务器失效的信息。但由于网络延时等原因,Zookeeper 不能保证两个客户端能同时得到刚更新的数据,如果需要最新数据,应该在读数据之前调用 sync()接口。 -- 等待无关(wait-free):慢的或者失效的 client 不得干预快速的 client 的请求,使得每个 client 都能有效的等待。 -- 原子性:更新只能成功或者失败,没有中间状态。 -- 顺序性:包括全局有序和偏序两种:全局有序是指如果在一台服务器上消息 a 在消息 b 前发布,则在所有 Server 上消息 a 都将在消息 b 前被发布;偏序是指如果一个消息 b 在消息 a 后被同一个发送者发布,a 必将排在 b 前面。 - -### 1.4. 工作原理 - -ZooKeeper 的核心是原子广播,这个机制保证了各个 Server 之间的同步。实现这个机制的协议叫做 Zab 协议。Zab 协议有两种模式,它们分别是恢复模式(选主)和广播模式(同步)。当服务启动或者在领导者崩溃后,Zab 就进入了恢复模式,当领导者被选举出来,且大多数 Server 完成了和 leader 的状态同步以后,恢复模式就结束了。状态同步保证了 leader 和 Server 具有相同的系统状态。 - -为了保证事务的顺序一致性,ZooKeeper 采用了递增的事务 id 号(zxid)来标识事务。所有的提议(proposal)都在被提出的时候加上了 zxid。实现中 zxid 是一个 64 位的数字,它高 32 位是 epoch 用来标识 leader 关系是否改变,每次一个 leader 被选出来,它都会有一个新的 epoch,标识当前属于那个 leader 的统治时期。低 32 位用于递增计数。 - -### 1.5. Server 工作状态 - -每个 Server 在工作过程中有三种状态: - -- LOOKING - 当前 Server 不知道 leader 是谁,正在搜寻 -- LEADING - 当前 Server 即为选举出来的 leader -- FOLLOWING - leader 已经选举出来,当前 Server 与之同步 - -## 2. 文件系统 - -ZooKeeper 会维护一个具有层次关系的数据结构,它非常类似于一个标准的文件系统,如下图所示: - -
- -
- -ZooKeeper 这种数据结构有如下这些特点: - -- 每个子目录项如 NameService 都被称作为 znode,这个 znode 是被它所在的路径唯一标识,如 Server1 这个 znode 的标识为 /NameService/Server1 -- znode 可以有子节点目录,并且每个 znode 可以存储数据,注意 EPHEMERAL 类型的目录节点不能有子节点目录 -- znode 是有版本的,每个 znode 中存储的数据可以有多个版本,也就是一个访问路径中可以存储多份数据 -- znode 可以是临时节点,一旦创建这个 znode 的客户端与服务器失去联系,这个 znode 也将自动删除,ZooKeeper 的客户端和服务器通信采用长连接方式,每个客户端和服务器通过心跳来保持连接,这个连接状态称为 session,如果 znode 是临时节点,这个 session 失效,znode 也就删除了 -- znode 的目录名可以自动编号,如 App1 已经存在,再创建的话,将会自动命名为 App2 -- znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个是 ZooKeeper 的核心特性,ZooKeeper 的很多功能都是基于这个特性实现的,后面在典型的应用场景中会有实例介绍 - -### 2.1. znode 类型 - -1. PERSISTENT(持久化目录节点) - 客户端与 zookeeper 断开连接后,该节点依旧存在 -2. PERSISTENT_SEQUENTIAL(持久化顺序编号目录节点) - 客户端与 zookeeper 断开连接后,该节点依旧存在,只是 Zookeeper 给该节点名称进行顺序编号 -3. EPHEMERAL(临时目录节点) - 客户端与 zookeeper 断开连接后,该节点被删除 -4. EPHEMERAL_SEQUENTIAL(临时顺序编号目录节点) - 客户端与 zookeeper 断开连接后,该节点被删除,只是 Zookeeper 给该节点名称进行顺序编号 - -## 3. 通知机制 - -客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变、被删除、子目录节点增加删除)时,zookeeper 会通知客户端。 - -## 4. 应用场景 - -### 4.1. 统一命名服务(Name Service) - -分布式应用中,通常需要有一套完整的命名规则,既能够产生唯一的名称又便于人识别和记住,通常情况下用树形的名称结构是一个理想的选择,树形的名称结构是一个有层次的目录结构,既对人友好又不会重复。说到这里你可能想到了 JNDI,没错 ZooKeeper 的 Name Service 与 JNDI 能够完成的功能是差不多的,它们都是将有层次的目录结构关联到一定资源上,但是 ZooKeeper 的 Name Service 更加是广泛意义上的关联,也许你并不需要将名称关联到特定资源上,你可能只需要一个不会重复名称,就像数据库中产生一个唯一的数字主键一样。 - -Name Service 已经是 ZooKeeper 内置的功能,你只要调用 ZooKeeper 的 API 就能实现。如调用 create 接口就可以很容易创建一个目录节点。 - -### 4.2. 配置管理(Configuration Management) - -配置的管理在分布式应用环境中很常见,例如同一个应用系统需要多台 PC Server 运行,但是它们运行的应用系统的某些配置项是相同的,如果要修改这些相同的配置项,那么就必须同时修改每台运行这个应用系统的 PC Server,这样非常麻烦而且容易出错。 - -像这样的配置信息完全可以交给 ZooKeeper 来管理,将配置信息保存在 ZooKeeper 的某个目录节点中,然后将所有需要修改的应用机器监控配置信息的状态,一旦配置信息发生变化,每台应用机器就会收到 ZooKeeper 的通知,然后从 ZooKeeper 获取新的配置信息应用到系统中。 - -
- -
- -### 4.3. 集群管理(Group Membership) - -ZooKeeper 能够很容易的实现集群管理的功能,如有多台 Server 组成一个服务集群,那么必须要一个“总管”知道当前集群中每台机器的服务状态,一旦有机器不能提供服务,集群中其它集群必须知道,从而做出调整重新分配服务策略。同样当增加集群的服务能力时,就会增加一台或多台 Server,同样也必须让“总管”知道。 - -ZooKeeper 不仅能够帮你维护当前的集群中机器的服务状态,而且能够帮你选出一个“总管”,让这个总管来管理集群,这就是 ZooKeeper 的另一个功能 Leader Election。 - -它们的实现方式都是在 ZooKeeper 上创建一个 EPHEMERAL 类型的目录节点,然后每个 Server 在它们创建目录节点的父目录节点上调用 getChildren(String path, boolean watch) 方法并设置 watch 为 true,由于是 EPHEMERAL 目录节点,当创建它的 Server 死去,这个目录节点也随之被删除,所以 Children 将会变化,这时 getChildren 上的 Watch 将会被调用,所以其它 Server 就知道已经有某台 Server 死去了。新增 Server 也是同样的原理。 - -ZooKeeper 如何实现 Leader Election,也就是选出一个 Master Server。和前面的一样每台 Server 创建一个 EPHEMERAL 目录节点,不同的是它还是一个 SEQUENTIAL 目录节点,所以它是个 EPHEMERAL_SEQUENTIAL 目录节点。之所以它是 EPHEMERAL_SEQUENTIAL 目录节点,是因为我们可以给每台 Server 编号,我们可以选择当前是最小编号的 Server 为 Master,假如这个最小编号的 Server 死去,由于是 EPHEMERAL 节点,死去的 Server 对应的节点也被删除,所以当前的节点列表中又出现一个最小编号的节点,我们就选择这个节点为当前 Master。这样就实现了动态选择 Master,避免了传统意义上单 Master 容易出现单点故障的问题。 - -
- -
- -### 4.4. 分布式锁 - -ZooKeeper 实现分布式锁的步骤: - -1. 创建一个目录 mylock; -2. 线程 A 想获取锁就在 mylock 目录下创建临时顺序节点; -3. 获取 mylock 目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁; -4. 线程 B 获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点; -5. 线程 A 处理完,删除自己的节点,线程 B 监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。 - -ZooKeeper 版本的分布式锁问题相对比较来说少。 - -- 锁的占用时间限制:redis 就有占用时间限制,而 ZooKeeper 则没有,最主要的原因是 redis 目前没有办法知道已经获取锁的客户端的状态,是已经挂了呢还是正在执行耗时较长的业务逻辑。而 ZooKeeper 通过临时节点就能清晰知道,如果临时节点存在说明还在执行业务逻辑,如果临时节点不存在说明已经执行完毕释放锁或者是挂了。由此看来 redis 如果能像 ZooKeeper 一样添加一些与客户端绑定的临时键,也是一大好事。 -- 是否单点故障:redis 本身有很多中玩法,如客户端一致性 hash,服务器端 sentinel 方案或者 cluster 方案,很难做到一种分布式锁方式能应对所有这些方案。而 ZooKeeper 只有一种玩法,多台机器的节点数据是一致的,没有 redis 的那么多的麻烦因素要考虑。 - -总体上来说 ZooKeeper 实现分布式锁更加的简单,可靠性更高。但 ZooKeeper 因为需要频繁的创建和删除节点,性能上不如 Redis 方式。 - -### 4.5. 队列管理 - -ZooKeeper 可以处理两种类型的队列: - -1. 当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达,这种是同步队列。 -2. 队列按照 FIFO 方式进行入队和出队操作,例如实现生产者和消费者模型。 - -同步队列用 ZooKeeper 实现的实现思路如下: - -创建一个父目录 /synchronizing,每个成员都监控标志(Set Watch)位目录 /synchronizing/start 是否存在,然后每个成员都加入这个队列,加入队列的方式就是创建 /synchronizing/member_i 的临时目录节点,然后每个成员获取 / synchronizing 目录的所有目录节点,也就是 member_i。判断 i 的值是否已经是成员的个数,如果小于成员个数等待 /synchronizing/start 的出现,如果已经相等就创建 /synchronizing/start。 - -## 5. 复制 - -ZooKeeper 作为一个集群提供一致的数据服务,自然,它要在所有机器间做数据复制。 - -从客户端读写访问的透明度来看,数据复制集群系统分下面两种: - -- 写主(WriteMaster) :对数据的修改提交给指定的节点。读无此限制,可以读取任何一个节点。这种情况下客户端需要对读与写进行区别,俗称读写分离; -- 写任意(Write Any):对数据的修改可提交给任意的节点,跟读一样。这种情况下,客户端对集群节点的角色与变化透明。 - -对 ZooKeeper 来说,它采用的方式是写任意。通过增加机器,它的读吞吐能力和响应能力扩展性非常好,而写,随着机器的增多吞吐能力肯定下降(这也是它建立 observer 的原因),而响应能力则取决于具体实现方式,是延迟复制保持最终一致性,还是立即复制快速响应。 - -## 6. 选举流程 - -选举状态: - -- LOOKING,竞选状态。 -- FOLLOWING,随从状态,同步 leader 状态,参与投票。 -- OBSERVING,观察状态,同步 leader 状态,不参与投票。 -- LEADING,领导者状态。 - -ZooKeeper 选举流程基于 Paxos 算法。 - -
- -
- -1. 选举线程由当前 Server 发起选举的线程担任,其主要功能是对投票结果进行统计,并选出推荐的 Server; -2. 选举线程首先向所有 Server 发起一次询问(包括自己); -3. 选举线程收到回复后,验证是否是自己发起的询问(验证 zxid 是否一致),然后获取对方的 id(myid),并存储到当前询问对象列表中,最后获取对方提议的 leader 相关信息(id,zxid),并将这些信息存储到当次选举的投票记录表中; -4. 收到所有 Server 回复以后,就计算出 zxid 最大的那个 Server,并将这个 Server 相关信息设置成下一次要投票的 Server; -5. 线程将当前 zxid 最大的 Server 设置为当前 Server 要推荐的 Leader,如果此时获胜的 Server 获得 n/2 + 1 的 Server 票数,设置当前推荐的 leader 为获胜的 Server,将根据获胜的 Server 相关信息设置自己的状态,否则,继续这个过程,直到 leader 被选举出来。 通过流程分析我们可以得出:要使 Leader 获得多数 Server 的支持,则 Server 总数必须是奇数 2n+1,且存活的 Server 的数目不得少于 n+1. 每个 Server 启动后都会重复以上流程。在恢复模式下,如果是刚从崩溃状态恢复的或者刚启动的 server 还会从磁盘快照中恢复数据和会话信息,zk 会记录事务日志并定期进行快照,方便在恢复时进行状态恢复。 - -述 Leader 选择过程中的状态变化,这是假设全部实例中均没有数据,假设服务器启动顺序分别为:A,B,C。 - -## 7. 同步流程 - -选完 Leader 以后,zk 就进入状态同步过程。 - -1. Leader 等待 server 连接; -2. Follower 连接 leader,将最大的 zxid 发送给 leader; -3. Leader 根据 follower 的 zxid 确定同步点; -4. 完成同步后通知 follower 已经成为 uptodate 状态; -5. Follower 收到 uptodate 消息后,又可以重新接受 client 的请求进行服务了。 - -## 8. 资源 - -### 8.1. 官方资源 - -| [官网](http://zookeeper.apache.org/) | [官网文档](https://cwiki.apache.org/confluence/display/ZOOKEEPER) | [Github](https://github.com/apache/zookeeper) | - -### 8.2. 文章 - -[分布式服务框架 ZooKeeper -- 管理分布式环境中的数据](https://www.ibm.com/developerworks/cn/opensource/os-cn-zookeeper/index.html) -[ZooKeeper 的功能以及工作原理](https://www.cnblogs.com/felixzh/p/5869212.html) diff --git "a/docs/distributed/\345\210\206\345\270\203\345\274\217\346\212\200\346\234\257\345\256\236\347\216\260.md" "b/docs/distributed/\345\210\206\345\270\203\345\274\217\346\212\200\346\234\257\345\256\236\347\216\260.md" deleted file mode 100644 index 0f1fddae..00000000 --- "a/docs/distributed/\345\210\206\345\270\203\345\274\217\346\212\200\346\234\257\345\256\236\347\216\260.md" +++ /dev/null @@ -1,355 +0,0 @@ ---- -title: 分布式技术实现 -date: 2018/07/09 -categories: -- 分布式 -tags: -- 分布式 ---- - -# 分布式技术实现 - - - -- [1. 分布式事务](#1-分布式事务) -- [2. 分布式锁](#2-分布式锁) - - [2.1. 基于数据库实现分布式锁](#21-基于数据库实现分布式锁) - - [2.2. 基于 Redis 实现分布式锁](#22-基于-redis-实现分布式锁) - - [2.3. 基于 ZooKeeper 实现分布式锁](#23-基于-zookeeper-实现分布式锁) -- [3. 分布式 Session](#3-分布式-session) - - [3.1. Sticky Sessions](#31-sticky-sessions) - - [3.2. Session Replication](#32-session-replication) - - [3.3. Session Server](#33-session-server) -- [4. 分布式存储](#4-分布式存储) -- [5. 分布式缓存](#5-分布式缓存) -- [6. 分布式计算](#6-分布式计算) -- [7. 负载均衡](#7-负载均衡) - - [7.1. 算法](#71-算法) - - [7.2. 实现](#72-实现) -- [8. 资料](#8-资料) - - - -## 1. 分布式事务 - -> 参考:[分布式原理#4-分布式事务问题](分布式原理.md#4-分布式事务问题) - -## 2. 分布式锁 - -Java 原生 API 虽然有并发锁,但并没有提供分布式锁的能力,所以针对分布式场景中的锁需要解决的方案。 - -分布式锁的解决方案大致有以下几种: - -- 基于数据库实现 -- 基于缓存(redis,memcached 等)实现 -- 基于 Zookeeper 实现 - -### 2.1. 基于数据库实现分布式锁 - -#### 实现 - -##### 1. 创建表 - -```sql -CREATE TABLE `methodLock` ( - `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', - `method_name` varchar(64) NOT NULL DEFAULT '' COMMENT '锁定的方法名', - `desc` varchar(1024) NOT NULL DEFAULT '备注信息', - `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '保存数据时间,自动生成', - PRIMARY KEY (`id`), - UNIQUE KEY `uidx_method_name` (`method_name `) USING BTREE -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='锁定中的方法'; -``` - -##### 2. 获取锁 - -想要锁住某个方法时,执行以下 SQL: - -```sql -insert into methodLock(method_name,desc) values (‘method_name’,‘desc’) -``` - -因为我们对 `method_name` 做了唯一性约束,这里如果有多个请求同时提交到数据库的话,数据库会保证只有一个操作可以成功,那么我们就可以认为操作成功的那个线程获得了该方法的锁,可以执行方法体内容。 - -成功插入则获取锁。 - -##### 3. 释放锁 - -当方法执行完毕之后,想要释放锁的话,需要执行以下 Sql: - -```sql -delete from methodLock where method_name ='method_name' -``` - -#### 问题 - -1. 这把锁强依赖数据库的可用性。如果数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用。 -2. 这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在数据库中,其他线程无法再获得到锁。 -3. 这把锁只能是非阻塞的,因为数据的 insert 操作,一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁操作。 -4. 这把锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁。因为数据中数据已经存在了。 - -#### 解决办法 - -1. 单点问题可以用多数据库实例,同时塞 N 个表,N/2+1 个成功就任务锁定成功 -2. 写一个定时任务,隔一段时间清除一次过期的数据。 -3. 写一个 while 循环,不断的重试插入,直到成功。 -4. 在数据库表中加个字段,记录当前获得锁的机器的主机信息和线程信息,那么下次再获取锁的时候先查询数据库,如果当前机器的主机信息和线程信息在数据库可以查到的话,直接把锁分配给他就可以了。 - -#### 小结 - -- 优点: 直接借助数据库,容易理解。 -- 缺点: 会有各种各样的问题,在解决问题的过程中会使整个方案变得越来越复杂。操作数据库需要一定的开销,性能问题需要考虑。 - -### 2.2. 基于 Redis 实现分布式锁 - -相比于用数据库来实现分布式锁,基于缓存实现的分布式锁的性能会更好一些。目前有很多成熟的分布式产品,包括 Redis、memcache、Tair 等。这里以 Redis 举例。 - -#### Redis 命令 - -- setnx - setnx key val:当且仅当 key 不存在时,set 一个 key 为 val 的字符串,返回 1;若 key 存在,则什么都不做,返回 0。 -- expire - expire key timeout:为 key 设置一个超时时间,单位为 second,超过这个时间锁会自动释放,避免死锁。 -- delete - delete key:删除 key - -#### 实现 - -单点实现步骤: - -1. 获取锁的使用,使用 setnx 加锁,锁的 value 值为一个随机生成的 UUID,再使用 expire 设置一个过期值。 -2. 获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。 -3. 释放锁的时候,通过 UUID 判断是不是该锁,若是该锁,则执行 delete 进行锁释放。 - -#### 问题 - -- 单点问题。如果单机 redis 挂掉了,那么程序会跟着出错。 -- 如果转移使用 slave 节点,复制不是同步复制,会出现多个程序获取锁的情况 - -#### 小结 - -可以考虑使用 [redisson 的解决方案](https://github.com/redisson/redisson/wiki/8.-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%92%8C%E5%90%8C%E6%AD%A5%E5%99%A8)。 - -### 2.3. 基于 ZooKeeper 实现分布式锁 - -#### 实现 - -这也是 ZooKeeper 客户端 curator 的分布式锁实现。 - -1. 创建一个目录 mylock; -2. 线程 A 想获取锁就在 mylock 目录下创建临时顺序节点; -3. 获取 mylock 目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁; -4. 线程 B 获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点; -5. 线程 A 处理完,删除自己的节点,线程 B 监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。 - -#### 小结 - -ZooKeeper 版本的分布式锁问题相对比较来说少。 - -- 锁的占用时间限制:redis 就有占用时间限制,而 ZooKeeper 则没有,最主要的原因是 redis 目前没有办法知道已经获取锁的客户端的状态,是已经挂了呢还是正在执行耗时较长的业务逻辑。而 ZooKeeper 通过临时节点就能清晰知道,如果临时节点存在说明还在执行业务逻辑,如果临时节点不存在说明已经执行完毕释放锁或者是挂了。由此看来 redis 如果能像 ZooKeeper 一样添加一些与客户端绑定的临时键,也是一大好事。 -- 是否单点故障:redis 本身有很多中玩法,如客户端一致性 hash,服务器端 sentinel 方案或者 cluster 方案,很难做到一种分布式锁方式能应对所有这些方案。而 ZooKeeper 只有一种玩法,多台机器的节点数据是一致的,没有 redis 的那么多的麻烦因素要考虑。 - -总体上来说 ZooKeeper 实现分布式锁更加的简单,可靠性更高。但 ZooKeeper 因为需要频繁的创建和删除节点,性能上不如 Redis 方式。 - -## 3. 分布式 Session - -在分布式场景下,一个用户的 Session 如果只存储在一个服务器上,那么当负载均衡器把用户的下一个请求转发到另一个服务器上,该服务器没有用户的 Session,就可能导致用户需要重新进行登录等操作。 - -分布式 Session 的几种实现策略: - -1. 粘性 session -2. 应用服务器间的 session 复制共享 -3. 基于 cache DB 缓存的 session 共享 - -### 3.1. Sticky Sessions - -需要配置负载均衡器,使得一个用户的所有请求都路由到一个服务器节点上,这样就可以把用户的 Session 存放在该服务器节点中。 - -缺点:当服务器节点宕机时,将丢失该服务器节点上的所有 Session。 - -
- -
- -### 3.2. Session Replication - -在服务器节点之间进行 Session 同步操作,这样的话用户可以访问任何一个服务器节点。 - -缺点:占用过多内存;同步过程占用网络带宽以及服务器处理器时间。 - -
- -
- -### 3.3. Session Server - -使用一个单独的服务器存储 Session 数据,可以存在 MySQL 数据库上,也可以存在 Redis 或者 Memcached 这种内存型数据库。 - -缺点:需要去实现存取 Session 的代码。 - -
- -
- -## 4. 分布式存储 - -通常有两种解决方案: - -1. 数据分布:就是把数据分块存在不同的服务器上(分库分表)。 -2. 数据复制:让所有的服务器都有相同的数据,提供相当的服务。 - -> 参考:[分布式原理.md#2-数据分布](分布式原理.md#2-数据分布) - -## 5. 分布式缓存 - -使用缓存的好处: - -- 提升数据读取速度 -- 提升系统扩展能力,通过扩展缓存,提升系统承载能力 -- 降低存储成本,Cache+DB 的方式可以承担原有需要多台 DB 才能承担的请求量,节省机器成本 - -根据业务场景,通常缓存有以下几种使用方式 - -- 懒汉式(读时触发):写入 DB 后, 然后把相关的数据也写入 Cache -- 饥饿式(写时触发):先查询 DB 里的数据, 然后把相关的数据写入 Cache -- 定期刷新:适合周期性的跑数据的任务,或者列表型的数据,而且不要求绝对实时性 - -缓存分类: - -- 应用内缓存:如:EHCache -- 分布式缓存:如:Memached、Redis - -> 参考:[分布式原理.md#6-分布式缓存问题](分布式原理.md#6-分布式缓存问题) - -## 6. 分布式计算 - -## 7. 负载均衡 - -### 7.1. 算法 - -#### 轮询(Round Robin) - -轮询算法把每个请求轮流发送到每个服务器上。下图中,一共有 6 个客户端产生了 6 个请求,这 6 个请求按 (1, 2, 3, 4, 5, 6) 的顺序发送。最后,(1, 3, 5) 的请求会被发送到服务器 1,(2, 4, 6) 的请求会被发送到服务器 2。 - -
- -
- -该算法比较适合每个服务器的性能差不多的场景,如果有性能存在差异的情况下,那么性能较差的服务器可能无法承担过大的负载(下图的 Server 2)。 - -
- -
- -#### 加权轮询(Weighted Round Robbin) - -加权轮询是在轮询的基础上,根据服务器的性能差异,为服务器赋予一定的权值。例如下图中,服务器 1 被赋予的权值为 5,服务器 2 被赋予的权值为 1,那么 (1, 2, 3, 4, 5) 请求会被发送到服务器 1,(6) 请求会被发送到服务器 2。 - -
- -
- -#### 最少连接(least Connections) - -由于每个请求的连接时间不一样,使用轮询或者加权轮询算法的话,可能会让一台服务器当前连接数过大,而另一台服务器的连接过小,造成负载不均衡。例如下图中,(1, 3, 5) 请求会被发送到服务器 1,但是 (1, 3) 很快就断开连接,此时只有 (5) 请求连接服务器 1;(2, 4, 6) 请求被发送到服务器 2,只有 (2) 的连接断开。该系统继续运行时,服务器 2 会承担过大的负载。 - -
- -
- -最少连接算法就是将请求发送给当前最少连接数的服务器上。例如下图中,服务器 1 当前连接数最小,那么新到来的请求 6 就会被发送到服务器 1 上。 - -
- -
- -#### 加权最少连接(Weighted Least Connection) - -在最少连接的基础上,根据服务器的性能为每台服务器分配权重,再根据权重计算出每台服务器能处理的连接数。 - -
- -
- -#### 随机算法(Random) - -把请求随机发送到服务器上。和轮询算法类似,该算法比较适合服务器性能差不多的场景。 - -
- -
- -#### 源地址哈希法 (IP Hash) - -源地址哈希通过对客户端 IP 哈希计算得到的一个数值,用该数值对服务器数量进行取模运算,取模结果便是目标服务器的序号。 - -- 优点:保证同一 IP 的客户端都会被 hash 到同一台服务器上。 -- 缺点:不利于集群扩展,后台服务器数量变更都会影响 hash 结果。可以采用一致性 Hash 改进。 - -
- -
- -### 7.2. 实现 - -#### HTTP 重定向 - -HTTP 重定向负载均衡服务器收到 HTTP 请求之后会返回服务器的地址,并将该地址写入 HTTP 重定向响应中返回给浏览器,浏览器收到后需要再次发送请求。 - -缺点: - -- 用户访问的延迟会增加; -- 如果负载均衡器宕机,就无法访问该站点。 - -
- -
- -#### DNS 重定向 - -使用 DNS 作为负载均衡器,根据负载情况返回不同服务器的 IP 地址。大型网站基本使用了这种方式做为第一级负载均衡手段,然后在内部使用其它方式做第二级负载均衡。 - -缺点: - -- DNS 查找表可能会被客户端缓存起来,那么之后的所有请求都会被重定向到同一个服务器。 - -
- -
- -#### 修改 MAC 地址 - -使用 LVS(Linux Virtual Server)这种链路层负载均衡器,根据负载情况修改请求的 MAC 地址。 - -
- -
- -#### 修改 IP 地址 - -在网络层修改请求的目的 IP 地址。 - -
- -
- -#### 代理自动配置 - -正向代理与反向代理的区别: - -- 正向代理:发生在客户端,是由用户主动发起的。比如翻墙,客户端通过主动访问代理服务器,让代理服务器获得需要的外网数据,然后转发回客户端。 -- 反向代理:发生在服务器端,用户不知道代理的存在。 - -PAC 服务器是用来判断一个请求是否要经过代理。 - -
- -
- -## 8. 资料 - -- https://www.cnblogs.com/savorboard/p/distributed-system-transaction-consistency.html -- https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E5%88%86%E5%B8%83%E5%BC%8F%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90.md -- https://www.jianshu.com/p/453c6e7ff81c -- https://juejin.im/post/5a20cd8bf265da43163cdd9a -- https://github.com/redisson/redisson/wiki/8.-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%92%8C%E5%90%8C%E6%AD%A5%E5%99%A8 -- https://github.com/L316476844/distributed-session -- [分布式缓存架构基础](https://juejin.im/entry/57e39e320e3dd90058021bff) -- [阿里 P8 技术专家细究分布式缓存问题](https://www.toutiao.com/i6533812974807679495/?tt_from=weixin&utm_campaign=client_share&from=singlemessage×tamp=1521281305&app=news_article&utm_source=weixin&iid=28128279343&utm_medium=toutiao_android&weixin_list=1&wxshare_count=2&pbid=6517746516513195523) diff --git a/docs/index.html b/docs/index.html index ed66fa21..13fa4648 100644 --- a/docs/index.html +++ b/docs/index.html @@ -9,16 +9,8 @@ name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" /> - - + + + + + businessException +
-${ message } 返回 + ${ message } 返回
- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/exception.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/exception.jsp index dbc981ef..246fd973 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/exception.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/exception.jsp @@ -2,23 +2,23 @@ - - exception - + + exception +
- ${ message } 返回 + ${ message } 返回
- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testCacheFilter.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testCacheFilter.jsp index 0ab5e289..1adb0f45 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testCacheFilter.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testCacheFilter.jsp @@ -2,69 +2,70 @@ - - Insert title here - + + Insert title here + - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testCharacterEncodingFilter.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testCharacterEncodingFilter.jsp index 5c7f2561..767495dd 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testCharacterEncodingFilter.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testCharacterEncodingFilter.jsp @@ -2,18 +2,18 @@ - - Insert title here - + + Insert title here +
@@ -21,8 +21,8 @@
 ${ param.text }
 

-
+ name="text">${ param.text }
+ - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testExceptionHandlerFilter.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testExceptionHandlerFilter.jsp index ef1f3304..37478dd6 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testExceptionHandlerFilter.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testExceptionHandlerFilter.jsp @@ -2,22 +2,22 @@ <%@ page import="io.github.dunwu.javaee.filter.exception.AccountException" %> <%@ page import="io.github.dunwu.javaee.filter.exception.BusinessException" %> <% - String action = request.getParameter("action"); + String action = request.getParameter("action"); - if ("businessException".equals(action)) { - throw new BusinessException("业务操作失败. "); - } else if ("accountException".equals(action)) { - throw new AccountException("您需要登陆后再进行此项操作. "); - } else if ("exception".equals(action)) { - Integer.parseInt(""); - } + if ("businessException".equals(action)) { + throw new BusinessException("业务操作失败. "); + } else if ("accountException".equals(action)) { + throw new AccountException("您需要登陆后再进行此项操作. "); + } else if ("exception".equals(action)) { + Integer.parseInt(""); + } %> - - exceptionHandler + + exceptionHandler diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testGZipFilter.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testGZipFilter.jsp index c7991dfd..cc39119d 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testGZipFilter.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testGZipFilter.jsp @@ -5,67 +5,62 @@ <%@page import="java.text.NumberFormat" %> - - Insert title here - + + Insert title here + <%! - public void test(JspWriter out, String url) throws Exception { - - // 模拟支持 GZIP 的浏览器 - URLConnection connGzip = new URL(url).openConnection(); - connGzip.setRequestProperty("Accept-Encoding", "gzip"); - int lengthGzip = connGzip.getContentLength(); - - // 模拟不支持 GZIP 的浏览器 - URLConnection connCommon = new URL(url).openConnection(); - int lengthCommon = connCommon.getContentLength(); - - double rate = new Double(lengthGzip) / lengthCommon; - - out.println(""); - out.println(" "); - out.println(" "); - out.println(" "); - out.println(" "); - out.println(" "); - out.println(" "); - out.println(" "); - out.println(" "); - out.println("
网址: " + url + "
压缩后:" + lengthGzip + " byte压缩前:" + lengthCommon + " byte比率:" + NumberFormat.getPercentInstance().format(rate) + "
"); - } + public void test(JspWriter out, String url) throws Exception { + + // 模拟支持 GZIP 的浏览器 + URLConnection connGzip = new URL(url).openConnection(); + connGzip.setRequestProperty("Accept-Encoding", "gzip"); + int lengthGzip = connGzip.getContentLength(); + + // 模拟不支持 GZIP 的浏览器 + URLConnection connCommon = new URL(url).openConnection(); + int lengthCommon = connCommon.getContentLength(); + + double rate = new Double(lengthGzip) / lengthCommon; + + out.println(""); + out.println(" "); + out.println(" "); + out.println(" "); + out.println(" "); + out.println(" "); + out.println(" "); + out.println(" "); + out.println(" "); + out.println("
网址: " + url + "
压缩后:" + lengthGzip + " byte压缩前:" + lengthCommon + " byte比率:" + NumberFormat.getPercentInstance().format(rate) + "
"); + } %> <% - String[] urls = { - "http://localhost:9899/views/js/dojo.js", - "http://localhost:9899/views/images/image.jsp", - "http://localhost:9899/views/images/winter.jpg", - "http://www.baidu.com", - "http://www.google.cn", - }; - for (String url : urls) { - test(out, url); - } + String[] urls = {"http://localhost:9899/views/js/dojo.js", "http://localhost:9899/views/images/image.jsp", + "http://localhost:9899/views/images/winter.jpg", "http://www.baidu.com", "http://www.google.cn",}; + for (String url : urls) { + test(out, url); + } %> - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testOutputReplaceFilter.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testOutputReplaceFilter.jsp index 2f922496..30dfd3ef 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testOutputReplaceFilter.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testOutputReplaceFilter.jsp @@ -2,8 +2,8 @@ - - Insert title here + + Insert title here @@ -16,4 +16,4 @@ Chna
www.baidu.com.cn
- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testWaterMarkFilter.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testWaterMarkFilter.jsp index e0bb8d34..f088941e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testWaterMarkFilter.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testWaterMarkFilter.jsp @@ -2,8 +2,8 @@ - - 图片过滤器示例 + + 图片过滤器示例 @@ -13,4 +13,4 @@ ${ header } - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/book.xsl b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/book.xsl index 30da7f7e..97ee95f6 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/book.xsl +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/book.xsl @@ -1,6 +1,6 @@ - + diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/messageLog.xsl b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/messageLog.xsl index 5b236407f3c7e5c45092ecfe9ab20d0ea741c401..ba812790504f6d8b5848c9e8a3868d4504ace2ea 100644 GIT binary patch delta 1902 zcmcgtJ!lhQ7``-ry-RvtlQd~+a`}3;7n7!GD)Fy`BUl9KP@i-?10Cr42S2NBdkTtw7K97=Q&7k$6)&NJ00og|RF-~0bQ?@vA)__X(Op;0s% z$LfvAlx#@~eVD`=uQvJxfeW5-)I3G^b;_m2v8AKrzP4kjBk#V(!Mx?5;|%+}gRUfp zJKmxzdGCErGzF9x?)kC-n?~KEw-_-0M9b@{f5tVx;K`vJsJ2Mw1G6in+k!Qjr(qX! zJ-E@M;B{~)=>)YyOBHEaIwBpE4oen1MnXO+oso_a$HIb~z*MJ>MLCXoq=1{9eS)6X z%Ftwn4YENVN97*8OLn3e$_r`JRPaPTOmrV672Bej^jPuag}**oIo*UW~&x&n^oau?T(!%{c78tr)2nm_g0 z@s_&=plF)o{bVeFs@5rTn9&0G64ym;k23?PMa!_dHQ2OIME@Mmz{yYOa(lJAgvk0y zT#Dock0WCdpZhu)Q`+dIuEf+;saASc?Gj6IJJc$|(=@AoL*jtj1=$IUhA^LZ)@_6j z*LuS)R6acGtqQ34aEv8Yl5qQHDFqjkB~j75rr=|;Mt~GBmuAE>2~Y)Z`p&n|DLhyk zgQ*(=)JK^S)G}OG=ir9UiSeD0Xnh_3W3KN?I@ZQ_6<_rjcIX_fpYi?~1Gh6VQCAuz z%D$OV#gK9AbQY#z?p2NjEqo)Gz|^{e^$o^cmK7z1kRf(%BEN~ z)2@W``b14DEBi0mhW1FO=?KnB2F>?q?gSQd6X+P`y;;ch;KuL(to+YI{d=uxSp5~= zFVg&$A0t~6`-gbHcwd{L2<{J+2+f!{oZ&)IAY6|Waj9Sml$+~Pq}`uNI>+r^_gWD- zivz>GoO1ijd-ZUbW@7uI}2MeUDQY_{(b1%?w&JAMl{+gX|;Ou TFDvJq|3ZVjHb&DvJ`z6wCwJs! literal 12130 zcmeHN-EJF26do?PLE;(Mh9IXBX_BJ0BAB*-T&J?q`s^Zz$z=AXZt=7BjeeRE(kb7Z<^WX5LS z49&oNXqL=|xnJ zi8;i*HGGck=N5k71Lh&9>EZ5%dEb84%r|DsJjI=DJZXW(vDvk=`hespbX49jtN3J~ zZ)n<>c>tb%0F52oBjqFf9{~0k|2Z?y-4Wj5oR-UvfN>+r=Y z+X_84Peo|c+u+gJ`W7tMc{XKD{~F-VDz4}O`>^~|csh0sf`$p8 z?PB%`@N{ZOM&nqyR6en=N&N^M`*Jx3whr#>0rPFkU9~>yV)bK6h;;|tj;7IO75W;2 z0?MWjDmH9vV`L&_nbl?s(tKV+9=<03^>Nn!IyyF#+Sabg^^Li<)8cuZ;UBNsus>$er=X%2+*ZYQo;;&fOkih9^&!m zx@_ihliZiFiMRIgW!8_+keOlJ%%=3YC^52B`B{;Ci3=h`ZnXx z=ir*TZV9okoC3A5eG8RRqYemh8F^Y_X@)x)Y?k#yW>&l>Hv>x{lsHEEeO@lUR7EE9 zP!xls@W!wRRza?&aWR|xX)F$7Hg)d!rro%Ttq-ZB;`at3+d3k+)K-EyHTqKh8t}E2 zEazhlG&oM;5*B>4SXUX$RRPlx4&E8LLp%i<^)JBOKrK z-&Xo2fG-szqcDB#BiJ<|Yldy)uq>G$VUzk?4Nxk`DyDs;-KdF(`!zhtM z)9+kNtt4HoR8ZwYe-`f z{c)XDUgs^>=&RJv4nom<+L>1?^H|YU%2j$t*K&3CqBZ*Y>{a?>*K%E~T-gD zo=?8BHJ-DMDq%y>kULg8%MY&vi75wCawk>qW{mk6p2pSyUarh*{LNSGZmAMQYIZ zij=L@iNijeV%EyriTvkVnp5MJ?dC|wGn~U`l97u~xa-e73HO9t_*CX|g3fX?Wlfo% zm){l1S2lS%()|ea7PlLD5Xn^j{^^okyY|5ocPT==Um!y86oO=So;zSySE6uChpcDV zr{?~Ra9eI$u6EEYw`61uvS!?l-Dp}l6DD;leQ?pZj1IR@CwFCB1ZAw^FNUg^ES2(l zy9_#CCC^<}?s0PeomLm>p0%6XeW|-sQp8cc-rNRW?xC;Q`b9COG$lA>^gWCfOvq8_ z8}d$mRBNnHZ}p0Tl$GlyZn5sJtR;N@@{wWQ2=WBpd<4C--s2u0z55JZzv5l)|B?sJ zU=@F6KEpMu#xv|+avj5UNZvl^O+tTK)>j#WXkDbNX=KGBaiwfUQYVHKAu}$V7izg3 zH!JKct%IV+T*rX_M-0&H8QPY51oxDA;--Z!ci315@S3YZcRrCBgZ*!wvUE9wD{-zK z!ZrR28#{PHLQf@%O=|2d?&31;@Vtq{s1 - Bruce - Java 编程思想 - 计算机经典书籍系列丛书 - $100.00 + Bruce + Java 编程思想 + 计算机经典书籍系列丛书 + $100.00 diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/xml.xsl b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/xml.xsl index edd374d8..2364687a 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/xml.xsl +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/xml.xsl @@ -1,6 +1,6 @@ - + diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/FilterDemosBootstrap.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/FilterDemosBootstrap.java index ba827cab..aaa9c2cc 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/FilterDemosBootstrap.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/FilterDemosBootstrap.java @@ -8,26 +8,29 @@ * @author Zhang Peng */ public class FilterDemosBootstrap { - // private static int STARTUP_TYPE = JettyFactory.IDE_ECLIPSE; - private static int STARTUP_TYPE = JettyFactory.IDE_INTELLIJ; - public static void main(String[] args) throws Exception { - Server server = JettyFactory.initServer(); - JettyFactory.initWebAppContext(server, STARTUP_TYPE); + // private static int STARTUP_TYPE = JettyFactory.IDE_ECLIPSE; + private static int STARTUP_TYPE = JettyFactory.IDE_INTELLIJ; - try { - JettyFactory.startServer(server); + public static void main(String[] args) throws Exception { + Server server = JettyFactory.initServer(); + JettyFactory.initWebAppContext(server, STARTUP_TYPE); + + try { + JettyFactory.startServer(server); + + // 等待用户输入回车重载应用 + while (true) { + char c = (char) System.in.read(); + if (c == '\n') { + JettyFactory.reloadWebAppContext(server); + } + } + } + catch (Exception e) { + e.printStackTrace(); + System.exit(-1); + } + } - // 等待用户输入回车重载应用 - while (true) { - char c = (char) System.in.read(); - if (c == '\n') { - JettyFactory.reloadWebAppContext(server); - } - } - } catch (Exception e) { - e.printStackTrace(); - System.exit(-1); - } - } } diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java index f9eb3106..b04f2c2b 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java @@ -9,113 +9,116 @@ import java.util.List; /** - * JettyFactory 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 + * JettyFactory 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij + * 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 * * @author Zhang Peng */ @SuppressWarnings("all") public class JettyFactory { - private static final int PORT = 9527; - private static final String CONTEXT = "/"; - private static final String RESOURCE_BASE_PATH = "src/main/webapp"; - private static final String WEB_XML_PATH = "/WEB-INF/web.xml"; - private static final String[] TLD_JAR_NAMES = new String[] {"sitemesh", "spring-webmvc", "tiles"}; - private static final String WINDOWS_WEBDEFAULT_PATH = "jetty/webdefault.xml"; - - public static final int IDE_ECLIPSE = 0; - public static final int IDE_INTELLIJ = 1; - - public static final String ACTIVE_PROFILE = "spring.profiles.active"; - public static final String DEFAULT_PROFILE = "spring.profiles.default"; - public static final String DEVELOPMENT = "development"; - - /** - * 在Spring启动前,设置profile的环境变量。 - */ - public static void setProfileAsSystemProperty(String profile) { - System.setProperty(ACTIVE_PROFILE, profile); - } - - - public static Server initServer() { - setProfileAsSystemProperty(DEVELOPMENT); - WebAppContext webAppContext = new WebAppContext(); - Server server = new Server(PORT); - server.setHandler(webAppContext); - return server; - } - - public static void initWebAppContext(Server server, int type) throws Exception { - System.out.println("[INFO] Application loading"); - WebAppContext webAppContext = (WebAppContext) server.getHandler(); - webAppContext.setContextPath(CONTEXT); - webAppContext.setResourceBase(getAbsolutePath() + RESOURCE_BASE_PATH); - webAppContext.setDescriptor(getAbsolutePath() + RESOURCE_BASE_PATH + WEB_XML_PATH); - - if (IDE_INTELLIJ == type) { - webAppContext.setDefaultsDescriptor(WINDOWS_WEBDEFAULT_PATH); - supportJspAndSetTldJarNames(server, TLD_JAR_NAMES); - } else { - webAppContext.setParentLoaderPriority(true); - } - - System.out.println("[INFO] Application loaded"); - } - - public static void reloadWebAppContext(Server server) throws Exception { - WebAppContext webAppContext = (WebAppContext) server.getHandler(); - System.out.println("[INFO] Application reloading"); - webAppContext.stop(); - WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext); - classLoader.addClassPath(getAbsolutePath() + "target/classes"); - classLoader.addClassPath(getAbsolutePath() + "target/test-classes"); - webAppContext.setClassLoader(classLoader); - webAppContext.start(); - System.out.println("[INFO] Application reloaded"); - } - - - public static void startServer(Server server) throws Exception { - System.out.println("[HINT] Don't forget to set -XX:MaxPermSize=128m"); - server.start(); - System.out.println("Server running at http://localhost:" + PORT + CONTEXT); - System.out.println("[HINT] Hit Enter to reload the application quickly"); - } - - public static String getAbsolutePath() { - String path = null; - String folderPath = - JettyFactory.class.getProtectionDomain().getCodeSource().getLocation().getPath().substring(1); - if (folderPath.indexOf("target") > 0) { - path = folderPath.substring(0, folderPath.indexOf("target")); - } - return path; - } - - public static void supportJspAndSetTldJarNames(Server server, String... jarNames) { - WebAppContext context = (WebAppContext) server.getHandler(); - // This webapp will use jsps and jstl. We need to enable the AnnotationConfiguration in - // order to correctly set up the jsp container - org.eclipse.jetty.webapp.Configuration.ClassList classlist = - org.eclipse.jetty.webapp.Configuration.ClassList.setServerDefault(server); - classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", - "org.eclipse.jetty.annotations.AnnotationConfiguration"); - // Set the ContainerIncludeJarPattern so that jetty examines these container-path jars for - // tlds, web-fragments etc. - // If you omit the jar that contains the jstl .tlds, the jsp engine will scan for them - // instead. - - List list = new ArrayList<>(); - list.add(".*/[^/]*servlet-api-[^/]*\\.jar$"); - list.add(".*/javax.servlet.jsp.jstl-.*\\.jar$"); - list.add(".*/[^/]*taglibs.*\\.jar$"); - - for (String jarName : jarNames) { - String str = ".*/" + jarName + "-[^/]*\\.jar$"; - list.add(str); - } - - context.setAttribute("org.eclipse.jetty.io.github.dunwu.javaee.server.webapp.ContainerIncludeJarPattern", - StringUtils.join(list, '|')); - } + + public static final int IDE_ECLIPSE = 0; + public static final int IDE_INTELLIJ = 1; + public static final String ACTIVE_PROFILE = "spring.profiles.active"; + public static final String DEFAULT_PROFILE = "spring.profiles.default"; + public static final String DEVELOPMENT = "development"; + private static final int PORT = 9527; + private static final String CONTEXT = "/"; + private static final String RESOURCE_BASE_PATH = "src/main/webapp"; + private static final String WEB_XML_PATH = "/WEB-INF/web.xml"; + private static final String[] TLD_JAR_NAMES = new String[] { "sitemesh", "spring-webmvc", "tiles" }; + private static final String WINDOWS_WEBDEFAULT_PATH = "jetty/webdefault.xml"; + + /** + * 在Spring启动前,设置profile的环境变量。 + */ + public static void setProfileAsSystemProperty(String profile) { + System.setProperty(ACTIVE_PROFILE, profile); + } + + public static Server initServer() { + setProfileAsSystemProperty(DEVELOPMENT); + WebAppContext webAppContext = new WebAppContext(); + Server server = new Server(PORT); + server.setHandler(webAppContext); + return server; + } + + public static void initWebAppContext(Server server, int type) throws Exception { + System.out.println("[INFO] Application loading"); + WebAppContext webAppContext = (WebAppContext) server.getHandler(); + webAppContext.setContextPath(CONTEXT); + webAppContext.setResourceBase(getAbsolutePath() + RESOURCE_BASE_PATH); + webAppContext.setDescriptor(getAbsolutePath() + RESOURCE_BASE_PATH + WEB_XML_PATH); + + if (IDE_INTELLIJ == type) { + webAppContext.setDefaultsDescriptor(WINDOWS_WEBDEFAULT_PATH); + supportJspAndSetTldJarNames(server, TLD_JAR_NAMES); + } + else { + webAppContext.setParentLoaderPriority(true); + } + + System.out.println("[INFO] Application loaded"); + } + + public static void reloadWebAppContext(Server server) throws Exception { + WebAppContext webAppContext = (WebAppContext) server.getHandler(); + System.out.println("[INFO] Application reloading"); + webAppContext.stop(); + WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext); + classLoader.addClassPath(getAbsolutePath() + "target/classes"); + classLoader.addClassPath(getAbsolutePath() + "target/test-classes"); + webAppContext.setClassLoader(classLoader); + webAppContext.start(); + System.out.println("[INFO] Application reloaded"); + } + + public static void startServer(Server server) throws Exception { + System.out.println("[HINT] Don't forget to set -XX:MaxPermSize=128m"); + server.start(); + System.out.println("Server running at http://localhost:" + PORT + CONTEXT); + System.out.println("[HINT] Hit Enter to reload the application quickly"); + } + + public static String getAbsolutePath() { + String path = null; + String folderPath = JettyFactory.class.getProtectionDomain().getCodeSource().getLocation().getPath() + .substring(1); + if (folderPath.indexOf("target") > 0) { + path = folderPath.substring(0, folderPath.indexOf("target")); + } + return path; + } + + public static void supportJspAndSetTldJarNames(Server server, String... jarNames) { + WebAppContext context = (WebAppContext) server.getHandler(); + // This webapp will use jsps and jstl. We need to enable the + // AnnotationConfiguration in + // order to correctly set up the jsp container + org.eclipse.jetty.webapp.Configuration.ClassList classlist = org.eclipse.jetty.webapp.Configuration.ClassList + .setServerDefault(server); + classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", + "org.eclipse.jetty.annotations.AnnotationConfiguration"); + // Set the ContainerIncludeJarPattern so that jetty examines these container-path + // jars for + // tlds, web-fragments etc. + // If you omit the jar that contains the jstl .tlds, the jsp engine will scan for + // them + // instead. + + List list = new ArrayList<>(); + list.add(".*/[^/]*servlet-api-[^/]*\\.jar$"); + list.add(".*/javax.servlet.jsp.jstl-.*\\.jar$"); + list.add(".*/[^/]*taglibs.*\\.jar$"); + + for (String jarName : jarNames) { + String str = ".*/" + jarName + "-[^/]*\\.jar$"; + list.add(str); + } + + context.setAttribute("org.eclipse.jetty.io.github.dunwu.javaee.server.webapp.ContainerIncludeJarPattern", + StringUtils.join(list, '|')); + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/pom.xml b/codes/javaee-tutorial/javaee-tutorial-jsp/pom.xml index 6c60565d..ad266ffb 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/pom.xml +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/pom.xml @@ -1,6 +1,6 @@ - + 4.0.0 io.github.dunwu diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/com/sun/products/applet/demo/Graph.java b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/com/sun/products/applet/demo/Graph.java index e8126fee..03cf53dd 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/com/sun/products/applet/demo/Graph.java +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/com/sun/products/applet/demo/Graph.java @@ -30,423 +30,423 @@ */ import java.applet.Applet; -import java.awt.BorderLayout; -import java.awt.Button; -import java.awt.Checkbox; -import java.awt.Color; -import java.awt.Dimension; -import java.awt.FontMetrics; -import java.awt.Graphics; -import java.awt.Image; -import java.awt.Panel; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.awt.event.MouseMotionListener; +import java.awt.*; +import java.awt.event.*; import java.util.StringTokenizer; - class Node { - double x; - double y; - double dx; - double dy; + double x; - boolean fixed; + double y; - String lbl; -} + double dx; + + double dy; + + boolean fixed; + String lbl; + +} class Edge { - int from; - int to; - double len; + int from; + + int to; + + double len; + } +class GraphPanel extends Panel implements Runnable, MouseListener, MouseMotionListener { -class GraphPanel extends Panel - implements Runnable, MouseListener, MouseMotionListener { - /** - * + /** + * */ private static final long serialVersionUID = -6414075534397495418L; + final Color fixedColor = Color.red; + final Color selectColor = Color.pink; + final Color edgeColor = Color.black; + final Color nodeColor = new Color(250, 220, 100); + final Color stressColor = Color.darkGray; + final Color arcColor1 = Color.black; + final Color arcColor2 = Color.pink; + final Color arcColor3 = Color.red; Graph graph; - int nnodes; - Node nodes[] = new Node[100]; - - int nedges; - Edge edges[] = new Edge[200]; - - Thread relaxer; - boolean stress; - boolean random; - - GraphPanel(Graph graph) { - this.graph = graph; - addMouseListener(this); - } - - int findNode(String lbl) { - for (int i = 0 ; i < nnodes ; i++) { - if (nodes[i].lbl.equals(lbl)) { - return i; - } + int nnodes; + Node nodes[] = new Node[100]; + int nedges; + Edge edges[] = new Edge[200]; + Thread relaxer; + boolean stress; + boolean random; + Node pick; + boolean pickfixed; + Image offscreen; + Dimension offscreensize; + Graphics offgraphics; + + GraphPanel(Graph graph) { + this.graph = graph; + addMouseListener(this); } - return addNode(lbl); - } - int addNode(String lbl) { - Node n = new Node(); - n.x = 10 + 380*Math.random(); - n.y = 10 + 380*Math.random(); - n.lbl = lbl; - nodes[nnodes] = n; - return nnodes++; - } - void addEdge(String from, String to, int len) { - Edge e = new Edge(); - e.from = findNode(from); - e.to = findNode(to); - e.len = len; - edges[nedges++] = e; - } - - public void run() { - Thread me = Thread.currentThread(); - while (relaxer == me) { - relax(); - if (random && (Math.random() < 0.03)) { - Node n = nodes[(int)(Math.random() * nnodes)]; - if (!n.fixed) { - n.x += 100*Math.random() - 50; - n.y += 100*Math.random() - 50; + + int findNode(String lbl) { + for (int i = 0; i < nnodes; i++) { + if (nodes[i].lbl.equals(lbl)) { + return i; + } } - graph.play(graph.getCodeBase(), "audio/drip.au"); - } - try { - Thread.sleep(100); - } catch (InterruptedException e) { - break; - } + return addNode(lbl); + } + + int addNode(String lbl) { + Node n = new Node(); + n.x = 10 + 380 * Math.random(); + n.y = 10 + 380 * Math.random(); + n.lbl = lbl; + nodes[nnodes] = n; + return nnodes++; + } + + void addEdge(String from, String to, int len) { + Edge e = new Edge(); + e.from = findNode(from); + e.to = findNode(to); + e.len = len; + edges[nedges++] = e; } - } - - synchronized void relax() { - for (int i = 0 ; i < nedges ; i++) { - Edge e = edges[i]; - double vx = nodes[e.to].x - nodes[e.from].x; - double vy = nodes[e.to].y - nodes[e.from].y; - double len = Math.sqrt(vx * vx + vy * vy); - len = (len == 0) ? .0001 : len; - double f = (edges[i].len - len) / (len * 3); - double dx = f * vx; - double dy = f * vy; - - nodes[e.to].dx += dx; - nodes[e.to].dy += dy; - nodes[e.from].dx += -dx; - nodes[e.from].dy += -dy; + + public void run() { + Thread me = Thread.currentThread(); + while (relaxer == me) { + relax(); + if (random && (Math.random() < 0.03)) { + Node n = nodes[(int) (Math.random() * nnodes)]; + if (!n.fixed) { + n.x += 100 * Math.random() - 50; + n.y += 100 * Math.random() - 50; + } + graph.play(graph.getCodeBase(), "audio/drip.au"); + } + try { + Thread.sleep(100); + } + catch (InterruptedException e) { + break; + } + } } - for (int i = 0 ; i < nnodes ; i++) { - Node n1 = nodes[i]; - double dx = 0; - double dy = 0; + synchronized void relax() { + for (int i = 0; i < nedges; i++) { + Edge e = edges[i]; + double vx = nodes[e.to].x - nodes[e.from].x; + double vy = nodes[e.to].y - nodes[e.from].y; + double len = Math.sqrt(vx * vx + vy * vy); + len = (len == 0) ? .0001 : len; + double f = (edges[i].len - len) / (len * 3); + double dx = f * vx; + double dy = f * vy; + + nodes[e.to].dx += dx; + nodes[e.to].dy += dy; + nodes[e.from].dx += -dx; + nodes[e.from].dy += -dy; + } - for (int j = 0 ; j < nnodes ; j++) { - if (i == j) { - continue; + for (int i = 0; i < nnodes; i++) { + Node n1 = nodes[i]; + double dx = 0; + double dy = 0; + + for (int j = 0; j < nnodes; j++) { + if (i == j) { + continue; + } + Node n2 = nodes[j]; + double vx = n1.x - n2.x; + double vy = n1.y - n2.y; + double len = vx * vx + vy * vy; + if (len == 0) { + dx += Math.random(); + dy += Math.random(); + } + else if (len < 100 * 100) { + dx += vx / len; + dy += vy / len; + } + } + double dlen = dx * dx + dy * dy; + if (dlen > 0) { + dlen = Math.sqrt(dlen) / 2; + n1.dx += dx / dlen; + n1.dy += dy / dlen; + } } - Node n2 = nodes[j]; - double vx = n1.x - n2.x; - double vy = n1.y - n2.y; - double len = vx * vx + vy * vy; - if (len == 0) { - dx += Math.random(); - dy += Math.random(); - } else if (len < 100*100) { - dx += vx / len; - dy += vy / len; + + Dimension d = getSize(); + for (int i = 0; i < nnodes; i++) { + Node n = nodes[i]; + if (!n.fixed) { + n.x += Math.max(-5, Math.min(5, n.dx)); + n.y += Math.max(-5, Math.min(5, n.dy)); + } + if (n.x < 0) { + n.x = 0; + } + else if (n.x > d.width) { + n.x = d.width; + } + if (n.y < 0) { + n.y = 0; + } + else if (n.y > d.height) { + n.y = d.height; + } + n.dx /= 2; + n.dy /= 2; } - } - double dlen = dx * dx + dy * dy; - if (dlen > 0) { - dlen = Math.sqrt(dlen) / 2; - n1.dx += dx / dlen; - n1.dy += dy / dlen; - } + repaint(); } - Dimension d = getSize(); - for (int i = 0 ; i < nnodes ; i++) { - Node n = nodes[i]; - if (!n.fixed) { - n.x += Math.max(-5, Math.min(5, n.dx)); - n.y += Math.max(-5, Math.min(5, n.dy)); - } - if (n.x < 0) { - n.x = 0; - } else if (n.x > d.width) { - n.x = d.width; - } - if (n.y < 0) { - n.y = 0; - } else if (n.y > d.height) { - n.y = d.height; - } - n.dx /= 2; - n.dy /= 2; + public void paintNode(Graphics g, Node n, FontMetrics fm) { + int x = (int) n.x; + int y = (int) n.y; + g.setColor((n == pick) ? selectColor : (n.fixed ? fixedColor : nodeColor)); + int w = fm.stringWidth(n.lbl) + 10; + int h = fm.getHeight() + 4; + g.fillRect(x - w / 2, y - h / 2, w, h); + g.setColor(Color.black); + g.drawRect(x - w / 2, y - h / 2, w - 1, h - 1); + g.drawString(n.lbl, x - (w - 10) / 2, (y - (h - 4) / 2) + fm.getAscent()); } - repaint(); - } - - Node pick; - boolean pickfixed; - Image offscreen; - Dimension offscreensize; - Graphics offgraphics; - - final Color fixedColor = Color.red; - final Color selectColor = Color.pink; - final Color edgeColor = Color.black; - final Color nodeColor = new Color(250, 220, 100); - final Color stressColor = Color.darkGray; - final Color arcColor1 = Color.black; - final Color arcColor2 = Color.pink; - final Color arcColor3 = Color.red; - - public void paintNode(Graphics g, Node n, FontMetrics fm) { - int x = (int)n.x; - int y = (int)n.y; - g.setColor((n == pick) ? selectColor : (n.fixed ? fixedColor : nodeColor)); - int w = fm.stringWidth(n.lbl) + 10; - int h = fm.getHeight() + 4; - g.fillRect(x - w/2, y - h / 2, w, h); - g.setColor(Color.black); - g.drawRect(x - w/2, y - h / 2, w-1, h-1); - g.drawString(n.lbl, x - (w-10)/2, (y - (h-4)/2) + fm.getAscent()); - } - - public synchronized void update(Graphics g) { - Dimension d = getSize(); - if ((offscreen == null) || (d.width != offscreensize.width) || (d.height != offscreensize.height)) { - offscreen = createImage(d.width, d.height); - offscreensize = d; - if (offgraphics != null) { - offgraphics.dispose(); - } - offgraphics = offscreen.getGraphics(); - offgraphics.setFont(getFont()); + + public synchronized void update(Graphics g) { + Dimension d = getSize(); + if ((offscreen == null) || (d.width != offscreensize.width) || (d.height != offscreensize.height)) { + offscreen = createImage(d.width, d.height); + offscreensize = d; + if (offgraphics != null) { + offgraphics.dispose(); + } + offgraphics = offscreen.getGraphics(); + offgraphics.setFont(getFont()); + } + + offgraphics.setColor(getBackground()); + offgraphics.fillRect(0, 0, d.width, d.height); + for (int i = 0; i < nedges; i++) { + Edge e = edges[i]; + int x1 = (int) nodes[e.from].x; + int y1 = (int) nodes[e.from].y; + int x2 = (int) nodes[e.to].x; + int y2 = (int) nodes[e.to].y; + int len = (int) Math.abs(Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)) - e.len); + offgraphics.setColor((len < 10) ? arcColor1 : (len < 20 ? arcColor2 : arcColor3)); + offgraphics.drawLine(x1, y1, x2, y2); + if (stress) { + String lbl = String.valueOf(len); + offgraphics.setColor(stressColor); + offgraphics.drawString(lbl, x1 + (x2 - x1) / 2, y1 + (y2 - y1) / 2); + offgraphics.setColor(edgeColor); + } + } + + FontMetrics fm = offgraphics.getFontMetrics(); + for (int i = 0; i < nnodes; i++) { + paintNode(offgraphics, nodes[i], fm); + } + g.drawImage(offscreen, 0, 0, null); } - offgraphics.setColor(getBackground()); - offgraphics.fillRect(0, 0, d.width, d.height); - for (int i = 0 ; i < nedges ; i++) { - Edge e = edges[i]; - int x1 = (int)nodes[e.from].x; - int y1 = (int)nodes[e.from].y; - int x2 = (int)nodes[e.to].x; - int y2 = (int)nodes[e.to].y; - int len = (int)Math.abs(Math.sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2)) - e.len); - offgraphics.setColor((len < 10) ? arcColor1 : (len < 20 ? arcColor2 : arcColor3)) ; - offgraphics.drawLine(x1, y1, x2, y2); - if (stress) { - String lbl = String.valueOf(len); - offgraphics.setColor(stressColor); - offgraphics.drawString(lbl, x1 + (x2-x1)/2, y1 + (y2-y1)/2); - offgraphics.setColor(edgeColor); - } + // 1.1 event handling + public void mouseClicked(MouseEvent e) { } - FontMetrics fm = offgraphics.getFontMetrics(); - for (int i = 0 ; i < nnodes ; i++) { - paintNode(offgraphics, nodes[i], fm); + public void mousePressed(MouseEvent e) { + addMouseMotionListener(this); + double bestdist = Double.MAX_VALUE; + int x = e.getX(); + int y = e.getY(); + for (int i = 0; i < nnodes; i++) { + Node n = nodes[i]; + double dist = (n.x - x) * (n.x - x) + (n.y - y) * (n.y - y); + if (dist < bestdist) { + pick = n; + bestdist = dist; + } + } + pickfixed = pick.fixed; + pick.fixed = true; + pick.x = x; + pick.y = y; + repaint(); + e.consume(); } - g.drawImage(offscreen, 0, 0, null); - } - - //1.1 event handling - public void mouseClicked(MouseEvent e) { - } - - public void mousePressed(MouseEvent e) { - addMouseMotionListener(this); - double bestdist = Double.MAX_VALUE; - int x = e.getX(); - int y = e.getY(); - for (int i = 0 ; i < nnodes ; i++) { - Node n = nodes[i]; - double dist = (n.x - x) * (n.x - x) + (n.y - y) * (n.y - y); - if (dist < bestdist) { - pick = n; - bestdist = dist; - } + + public void mouseReleased(MouseEvent e) { + removeMouseMotionListener(this); + if (pick != null) { + pick.x = e.getX(); + pick.y = e.getY(); + pick.fixed = pickfixed; + pick = null; + } + repaint(); + e.consume(); } - pickfixed = pick.fixed; - pick.fixed = true; - pick.x = x; - pick.y = y; - repaint(); - e.consume(); - } - - public void mouseReleased(MouseEvent e) { - removeMouseMotionListener(this); - if (pick != null) { - pick.x = e.getX(); - pick.y = e.getY(); - pick.fixed = pickfixed; - pick = null; - } - repaint(); - e.consume(); - } - - public void mouseEntered(MouseEvent e) { - } - - public void mouseExited(MouseEvent e) { - } - - public void mouseDragged(MouseEvent e) { - pick.x = e.getX(); - pick.y = e.getY(); - repaint(); - e.consume(); - } - - public void mouseMoved(MouseEvent e) { - } - - public void start() { - relaxer = new Thread(this); - relaxer.start(); - } - - public void stop() { - relaxer = null; - } -} + public void mouseEntered(MouseEvent e) { + } + + public void mouseExited(MouseEvent e) { + } + + public void mouseDragged(MouseEvent e) { + pick.x = e.getX(); + pick.y = e.getY(); + repaint(); + e.consume(); + } + + public void mouseMoved(MouseEvent e) { + } + public void start() { + relaxer = new Thread(this); + relaxer.start(); + } + + public void stop() { + relaxer = null; + } + +} public class Graph extends Applet implements ActionListener, ItemListener { - /** - * + /** + * */ private static final long serialVersionUID = 9208137208697128121L; + GraphPanel panel; - Panel controlPanel; - - Button scramble = new Button("Scramble"); - Button shake = new Button("Shake"); - Checkbox stress = new Checkbox("Stress"); - Checkbox random = new Checkbox("Random"); - - public void init() { - setLayout(new BorderLayout()); - - panel = new GraphPanel(this); - add("Center", panel); - controlPanel = new Panel(); - add("South", controlPanel); - - controlPanel.add(scramble); scramble.addActionListener(this); - controlPanel.add(shake); shake.addActionListener(this); - controlPanel.add(stress); stress.addItemListener(this); - controlPanel.add(random); random.addItemListener(this); - - String edges = getParameter("edges"); - for (StringTokenizer t = new StringTokenizer(edges, ",") ; t.hasMoreTokens() ; ) { - String str = t.nextToken(); - int i = str.indexOf('-'); - if (i > 0) { - int len = 50; - int j = str.indexOf('/'); - if (j > 0) { - len = Integer.valueOf(str.substring(j+1)).intValue(); - str = str.substring(0, j); + + Panel controlPanel; + + Button scramble = new Button("Scramble"); + + Button shake = new Button("Shake"); + + Checkbox stress = new Checkbox("Stress"); + + Checkbox random = new Checkbox("Random"); + + public void init() { + setLayout(new BorderLayout()); + + panel = new GraphPanel(this); + add("Center", panel); + controlPanel = new Panel(); + add("South", controlPanel); + + controlPanel.add(scramble); + scramble.addActionListener(this); + controlPanel.add(shake); + shake.addActionListener(this); + controlPanel.add(stress); + stress.addItemListener(this); + controlPanel.add(random); + random.addItemListener(this); + + String edges = getParameter("edges"); + for (StringTokenizer t = new StringTokenizer(edges, ","); t.hasMoreTokens();) { + String str = t.nextToken(); + int i = str.indexOf('-'); + if (i > 0) { + int len = 50; + int j = str.indexOf('/'); + if (j > 0) { + len = Integer.valueOf(str.substring(j + 1)).intValue(); + str = str.substring(0, j); + } + panel.addEdge(str.substring(0, i), str.substring(i + 1), len); + } + } + Dimension d = getSize(); + String center = getParameter("center"); + if (center != null) { + Node n = panel.nodes[panel.findNode(center)]; + n.x = d.width / 2; + n.y = d.height / 2; + n.fixed = true; } - panel.addEdge(str.substring(0,i), str.substring(i+1), len); - } } - Dimension d = getSize(); - String center = getParameter("center"); - if (center != null){ - Node n = panel.nodes[panel.findNode(center)]; - n.x = d.width / 2; - n.y = d.height / 2; - n.fixed = true; + + public void destroy() { + remove(panel); + remove(controlPanel); } - } - - public void destroy() { - remove(panel); - remove(controlPanel); - } - - public void start() { - panel.start(); - } - - public void stop() { - panel.stop(); - } - - public void actionPerformed(ActionEvent e) { - Object src = e.getSource(); - - if (src == scramble) { - play(getCodeBase(), "audio/computer.au"); - Dimension d = getSize(); - for (int i = 0 ; i < panel.nnodes ; i++) { - Node n = panel.nodes[i]; - if (!n.fixed) { - n.x = 10 + (d.width-20)*Math.random(); - n.y = 10 + (d.height-20)*Math.random(); - } - } - return; + + public void start() { + panel.start(); } - if (src == shake) { - play(getCodeBase(), "audio/gong.au"); -// Dimension d = getSize(); - for (int i = 0 ; i < panel.nnodes ; i++) { - Node n = panel.nodes[i]; - if (!n.fixed) { - n.x += 80*Math.random() - 40; - n.y += 80*Math.random() - 40; - } - } + public void stop() { + panel.stop(); } - } + public void actionPerformed(ActionEvent e) { + Object src = e.getSource(); + + if (src == scramble) { + play(getCodeBase(), "audio/computer.au"); + Dimension d = getSize(); + for (int i = 0; i < panel.nnodes; i++) { + Node n = panel.nodes[i]; + if (!n.fixed) { + n.x = 10 + (d.width - 20) * Math.random(); + n.y = 10 + (d.height - 20) * Math.random(); + } + } + return; + } + + if (src == shake) { + play(getCodeBase(), "audio/gong.au"); + // Dimension d = getSize(); + for (int i = 0; i < panel.nnodes; i++) { + Node n = panel.nodes[i]; + if (!n.fixed) { + n.x += 80 * Math.random() - 40; + n.y += 80 * Math.random() - 40; + } + } + } - public void itemStateChanged(ItemEvent e) { - Object src = e.getSource(); - boolean on = e.getStateChange() == ItemEvent.SELECTED; - if (src == stress) panel.stress = on; - else if (src == random) panel.random = on; - } + } - public String getAppletInfo() { - return "Title: GraphLayout \nAuthor: "; - } + public void itemStateChanged(ItemEvent e) { + Object src = e.getSource(); + boolean on = e.getStateChange() == ItemEvent.SELECTED; + if (src == stress) + panel.stress = on; + else if (src == random) + panel.random = on; + } - public String[][] getParameterInfo() { - String[][] info = { - {"edges", "delimited string", "A comma-delimited list of all the edges. It takes the form of 'C-N1,C-N2,C-N3,C-NX,N1-N2/M12,N2-N3/M23,N3-NX/M3X,...' where C is the name of center node (see 'center' parameter) and NX is a node attached to the center node. For the edges connecting nodes to eachother (and not to the center node) you may (optionally) specify a length MXY separated from the edge name by a forward slash."}, - {"center", "string", "The name of the center node."} - }; - return info; - } + public String getAppletInfo() { + return "Title: GraphLayout \nAuthor: "; + } -} + public String[][] getParameterInfo() { + String[][] info = { { "edges", "delimited string", + "A comma-delimited list of all the edges. It takes the form of 'C-N1,C-N2,C-N3,C-NX,N1-N2/M12,N2-N3/M23,N3-NX/M3X,...' where C is the name of center node (see 'center' parameter) and NX is a node attached to the center node. For the edges connecting nodes to eachother (and not to the center node) you may (optionally) specify a length MXY separated from the edge name by a forward slash." }, + { "center", "string", "The name of the center node." } }; + return info; + } +} diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Counter.java b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Counter.java index fbfe6bf5..2b4108d5 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Counter.java +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Counter.java @@ -1,13 +1,15 @@ package io.github.dunwu.javaee.jsp.bean; public class Counter { - private int count; - public int getCount() { - return ++count; - } + private int count; + + public int getCount() { + return ++count; + } + + public void setCount(int count) { + this.count = count; + } - public void setCount(int count) { - this.count = count; - } } diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Message.java b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Message.java index b7465c53..a15011f0 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Message.java +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Message.java @@ -1,14 +1,15 @@ package io.github.dunwu.javaee.jsp.bean; public class Message { - private String content; - public String getContent() { - return content; - } + private String content; - public void setContent(String content) { - this.content = content; - } + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } } diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Person.java b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Person.java index 043dc81a..a699495f 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Person.java +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Person.java @@ -1,31 +1,35 @@ package io.github.dunwu.javaee.jsp.bean; public class Person { - private String name; - private int age; - private String sex; - public String getName() { - return name; - } + private String name; - public void setName(String name) { - this.name = name; - } + private int age; - public int getAge() { - return age; - } + private String sex; - public void setAge(int age) { - this.age = age; - } + public String getName() { + return name; + } - public String getSex() { - return sex; - } + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public String getSex() { + return sex; + } + + public void setSex(String sex) { + this.sex = sex; + } - public void setSex(String sex) { - this.sex = sex; - } } diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/IpUtil.java b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/IpUtil.java index eb35a60c..73c30cf1 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/IpUtil.java +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/IpUtil.java @@ -5,9 +5,10 @@ public class IpUtil { public static String getIpAddress(String ip) { - try{ + try { return IPSeeker.getInstance().getAddress(ip); - }catch(Exception e){ + } + catch (Exception e) { e.printStackTrace(); } return "δ֪����"; diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/IPEntry.java b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/IPEntry.java index 7b00299b..133cff2e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/IPEntry.java +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/IPEntry.java @@ -20,8 +20,6 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - - /** *
  * һ��IP��Χ��¼�������������Һ�����Ҳ������ʼIP�ͽ���IP
@@ -30,22 +28,24 @@
  * @author �����
  */
 public class IPEntry {
-    public String beginIp;
-    public String endIp;
-    public String country;
-    public String area;
-
-    /**
-     * ���캯��
-     */
-    public IPEntry() {
-        beginIp = endIp = country = area = "";
-    }
-
-    public String toString(){
-    	return this.area+"  "+this.country+"  IP��Χ:"+this.beginIp+"-"+this.endIp;
-    }
-}
 
+	public String beginIp;
+
+	public String endIp;
 
+	public String country;
 
+	public String area;
+
+	/**
+	 * ���캯��
+	 */
+	public IPEntry() {
+		beginIp = endIp = country = area = "";
+	}
+
+	public String toString() {
+		return this.area + "  " + this.country + "  IP��Χ:" + this.beginIp + "-" + this.endIp;
+	}
+
+}
diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/IPSeeker.java b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/IPSeeker.java
index 523cc25e..499741cc 100644
--- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/IPSeeker.java
+++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/IPSeeker.java
@@ -20,8 +20,6 @@
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
 
-
-
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -33,9 +31,6 @@
 import java.util.Hashtable;
 import java.util.List;
 
-
-
-
 /**
  * 
  * ������ȡQQwry.dat�ļ����Ը���ip��ú���λ�ã�QQwry.dat�ĸ�ʽ��
@@ -66,44 +61,22 @@
  * @author �����
  */
 public class IPSeeker {
-	/**
-	 * 
-	 * ������װip�����Ϣ��Ŀǰֻ�������ֶΣ�ip���ڵĹ��Һ͵���
-	 * 
- * - * @author ����� - */ - private class IPLocation { - public String country; - public String area; - - public IPLocation() { - country = area = ""; - } - - public IPLocation getCopy() { - IPLocation ret = new IPLocation(); - ret.country = country; - ret.area = area; - return ret; - } - } private static final File IP_FILE = new File(IPSeeker.class.getResource("").getFile(), "QQWry.dat"); - // һЩ�̶������������¼���ȵȵ� private static final int IP_RECORD_LENGTH = 7; private static final byte AREA_FOLLOWED = 0x01; private static final byte NO_AREA = 0x2; - - // ������Ϊcache����ѯһ��ipʱ���Ȳ鿴cache���Լ��ٲ���Ҫ���ظ����� + // ��һģʽʵ�� + private static IPSeeker instance = new IPSeeker(); + // ������Ϊcache����ѯһ��ipʱ���Ȳ鿴cache���Լ��ٲ���Ҫ���ظ����� private Hashtable ipCache; + // ����ļ������� private RandomAccessFile ipFile; + // �ڴ�ӳ���ļ� private MappedByteBuffer mbb; - // ��һģʽʵ�� - private static IPSeeker instance = new IPSeeker(); // ��ʼ�����Ŀ�ʼ�ͽ����ľ���ƫ�� private long ipBegin, ipEnd; // Ϊ���Ч�ʶ����õ���ʱ���� @@ -115,7 +88,7 @@ public IPLocation getCopy() { /** * ˽�й��캯�� */ - private IPSeeker() { + private IPSeeker() { ipCache = new Hashtable(); loc = new IPLocation(); buf = new byte[100]; @@ -123,23 +96,25 @@ private IPSeeker() { b3 = new byte[3]; try { ipFile = new RandomAccessFile(IP_FILE, "r"); - } catch (FileNotFoundException e) { - System.out.println(IP_FILE.getAbsolutePath()); - System.out.println(IP_FILE); + } + catch (FileNotFoundException e) { + System.out.println(IP_FILE.getAbsolutePath()); + System.out.println(IP_FILE); System.out.println("IP��ַ��Ϣ�ļ�û���ҵ���IP��ʾ���ܽ��޷�ʹ��"); ipFile = null; } // ������ļ��ɹ�����ȡ�ļ�ͷ��Ϣ - if(ipFile != null) { + if (ipFile != null) { try { ipBegin = readLong4(0); ipEnd = readLong4(4); - if(ipBegin == -1 || ipEnd == -1) { + if (ipBegin == -1 || ipEnd == -1) { ipFile.close(); ipFile = null; } - } catch (IOException e) { + } + catch (IOException e) { System.out.println("IP��ַ��Ϣ�ļ���ʽ�д���IP��ʾ���ܽ��޷�ʹ��"); ipFile = null; } @@ -159,31 +134,31 @@ public static IPSeeker getInstance() { * @return ����IPEntry���͵�List */ public List getIPEntriesDebug(String s) { - List ret = new ArrayList(); - long endOffset = ipEnd + 4; - for(long offset = ipBegin + 4; offset <= endOffset; offset += IP_RECORD_LENGTH) { - // ��ȡ����IPƫ�� - long temp = readLong3(offset); - // ���temp������-1����ȡIP�ĵص���Ϣ - if(temp != -1) { - IPLocation loc = getIPLocation(temp); - // �ж��Ƿ�����ص����������s�Ӵ�����������ˣ���������¼��List�У����û�У����� - if(loc.country.indexOf(s) != -1 || loc.area.indexOf(s) != -1) { - IPEntry entry = new IPEntry(); - entry.country = loc.country; - entry.area = loc.area; - // �õ���ʼIP - readIP(offset - 4, b4); - entry.beginIp = Utils.getIpStringFromBytes(b4); - // �õ�����IP - readIP(temp, b4); - entry.endIp = Utils.getIpStringFromBytes(b4); - // ��Ӹü�¼ - ret.add(entry); - } - } - } - return ret; + List ret = new ArrayList(); + long endOffset = ipEnd + 4; + for (long offset = ipBegin + 4; offset <= endOffset; offset += IP_RECORD_LENGTH) { + // ��ȡ����IPƫ�� + long temp = readLong3(offset); + // ���temp������-1����ȡIP�ĵص���Ϣ + if (temp != -1) { + IPLocation loc = getIPLocation(temp); + // �ж��Ƿ�����ص����������s�Ӵ�����������ˣ���������¼��List�У����û�У����� + if (loc.country.indexOf(s) != -1 || loc.area.indexOf(s) != -1) { + IPEntry entry = new IPEntry(); + entry.country = loc.country; + entry.area = loc.area; + // �õ���ʼIP + readIP(offset - 4, b4); + entry.beginIp = Utils.getIpStringFromBytes(b4); + // �õ�����IP + readIP(temp, b4); + entry.endIp = Utils.getIpStringFromBytes(b4); + // ��Ӹü�¼ + ret.add(entry); + } + } + } + return ret; } /** @@ -192,40 +167,41 @@ public List getIPEntriesDebug(String s) { * @return ����IPEntry���͵�List */ public List getIPEntries(String s) { - List ret = new ArrayList(); - try { - // ӳ��IP��Ϣ�ļ����ڴ��� - if(mbb == null) { - FileChannel fc = ipFile.getChannel(); - mbb = fc.map(FileChannel.MapMode.READ_ONLY, 0, ipFile.length()); - mbb.order(ByteOrder.LITTLE_ENDIAN); - } - - int endOffset = (int)ipEnd; - for(int offset = (int)ipBegin + 4; offset <= endOffset; offset += IP_RECORD_LENGTH) { - int temp = readInt3(offset); - if(temp != -1) { - IPLocation loc = getIPLocation(temp); - // �ж��Ƿ�����ص����������s�Ӵ�����������ˣ���������¼��List�У����û�У����� - if(loc.country.indexOf(s) != -1 || loc.area.indexOf(s) != -1) { - IPEntry entry = new IPEntry(); - entry.country = loc.country; - entry.area = loc.area; - // �õ���ʼIP - readIP(offset - 4, b4); - entry.beginIp = Utils.getIpStringFromBytes(b4); - // �õ�����IP - readIP(temp, b4); - entry.endIp = Utils.getIpStringFromBytes(b4); - // ��Ӹü�¼ - ret.add(entry); - } - } - } - } catch (IOException e) { - System.out.println(e.getMessage()); - } - return ret; + List ret = new ArrayList(); + try { + // ӳ��IP��Ϣ�ļ����ڴ��� + if (mbb == null) { + FileChannel fc = ipFile.getChannel(); + mbb = fc.map(FileChannel.MapMode.READ_ONLY, 0, ipFile.length()); + mbb.order(ByteOrder.LITTLE_ENDIAN); + } + + int endOffset = (int) ipEnd; + for (int offset = (int) ipBegin + 4; offset <= endOffset; offset += IP_RECORD_LENGTH) { + int temp = readInt3(offset); + if (temp != -1) { + IPLocation loc = getIPLocation(temp); + // �ж��Ƿ�����ص����������s�Ӵ�����������ˣ���������¼��List�У����û�У����� + if (loc.country.indexOf(s) != -1 || loc.area.indexOf(s) != -1) { + IPEntry entry = new IPEntry(); + entry.country = loc.country; + entry.area = loc.area; + // �õ���ʼIP + readIP(offset - 4, b4); + entry.beginIp = Utils.getIpStringFromBytes(b4); + // �õ�����IP + readIP(temp, b4); + entry.endIp = Utils.getIpStringFromBytes(b4); + // ��Ӹü�¼ + ret.add(entry); + } + } + } + } + catch (IOException e) { + System.out.println(e.getMessage()); + } + return ret; } /** @@ -234,8 +210,8 @@ public List getIPEntries(String s) { * @return */ private int readInt3(int offset) { - mbb.position(offset); - return mbb.getInt() & 0x00FFFFFF; + mbb.position(offset); + return mbb.getInt() & 0x00FFFFFF; } /** @@ -243,7 +219,7 @@ private int readInt3(int offset) { * @return */ private int readInt3() { - return mbb.getInt() & 0x00FFFFFF; + return mbb.getInt() & 0x00FFFFFF; } /** @@ -253,14 +229,16 @@ private int readInt3() { */ public String getCountry(byte[] ip) { // ���ip��ַ�ļ��Ƿ����� - if(ipFile == null) return "�����IP���ݿ��ļ�"; + if (ipFile == null) + return "�����IP���ݿ��ļ�"; // ����ip��ת��ip�ֽ�����Ϊ�ַ�����ʽ String ipStr = Utils.getIpStringFromBytes(ip); // �ȼ��cache���Ƿ��Ѿ����������ip�Ľ����û���������ļ� - if(ipCache.containsKey(ipStr)) { - IPLocation loc = (IPLocation)ipCache.get(ipStr); + if (ipCache.containsKey(ipStr)) { + IPLocation loc = (IPLocation) ipCache.get(ipStr); return loc.country; - } else { + } + else { IPLocation loc = getIPLocation(ip); ipCache.put(ipStr, loc.getCopy()); return loc.country; @@ -273,7 +251,7 @@ public String getCountry(byte[] ip) { * @return �������ַ��� */ public String getCountry(String ip) { - return getCountry(Utils.getIpByteArrayFromString(ip)); + return getCountry(Utils.getIpByteArrayFromString(ip)); } /** @@ -283,14 +261,16 @@ public String getCountry(String ip) { */ public String getArea(byte[] ip) { // ���ip��ַ�ļ��Ƿ����� - if(ipFile == null) return "�����IP���ݿ��ļ�"; + if (ipFile == null) + return "�����IP���ݿ��ļ�"; // ����ip��ת��ip�ֽ�����Ϊ�ַ�����ʽ String ipStr = Utils.getIpStringFromBytes(ip); // �ȼ��cache���Ƿ��Ѿ����������ip�Ľ����û���������ļ� - if(ipCache.containsKey(ipStr)) { - IPLocation loc = (IPLocation)ipCache.get(ipStr); + if (ipCache.containsKey(ipStr)) { + IPLocation loc = (IPLocation) ipCache.get(ipStr); return loc.area; - } else { + } + else { IPLocation loc = getIPLocation(ip); ipCache.put(ipStr, loc.getCopy()); return loc.area; @@ -303,7 +283,7 @@ public String getArea(byte[] ip) { * @return �������ַ��� */ public String getArea(String ip) { - return getArea(Utils.getIpByteArrayFromString(ip)); + return getArea(Utils.getIpByteArrayFromString(ip)); } /** @@ -314,9 +294,9 @@ public String getArea(String ip) { private IPLocation getIPLocation(byte[] ip) { IPLocation info = null; long offset = locateIP(ip); - if(offset != -1) + if (offset != -1) info = getIPLocation(offset); - if(info == null) { + if (info == null) { info = new IPLocation(); info.country = "δ֪����"; info.area = "δ֪����"; @@ -325,8 +305,7 @@ private IPLocation getIPLocation(byte[] ip) { } /** - * ��offsetλ�ö�ȡ4���ֽ�Ϊһ��long����ΪjavaΪbig-endian��ʽ������û�취 - * ������ôһ����������ת�� + * ��offsetλ�ö�ȡ4���ֽ�Ϊһ��long����ΪjavaΪbig-endian��ʽ������û�취 ������ôһ����������ת�� * @param offset * @return ��ȡ��longֵ������-1��ʾ��ȡ�ļ�ʧ�� */ @@ -339,14 +318,14 @@ private long readLong4(long offset) { ret |= ((ipFile.readByte() << 16) & 0xFF0000); ret |= ((ipFile.readByte() << 24) & 0xFF000000); return ret; - } catch (IOException e) { + } + catch (IOException e) { return -1; } } /** - * ��offsetλ�ö�ȡ3���ֽ�Ϊһ��long����ΪjavaΪbig-endian��ʽ������û�취 - * ������ôһ����������ת�� + * ��offsetλ�ö�ȡ3���ֽ�Ϊһ��long����ΪjavaΪbig-endian��ʽ������û�취 ������ôһ����������ת�� * @param offset * @return ��ȡ��longֵ������-1��ʾ��ȡ�ļ�ʧ�� */ @@ -359,7 +338,8 @@ private long readLong3(long offset) { ret |= ((b3[1] << 8) & 0xFF00); ret |= ((b3[2] << 16) & 0xFF0000); return ret; - } catch (IOException e) { + } + catch (IOException e) { return -1; } } @@ -376,7 +356,8 @@ private long readLong3() { ret |= ((b3[1] << 8) & 0xFF00); ret |= ((b3[2] << 16) & 0xFF0000); return ret; - } catch (IOException e) { + } + catch (IOException e) { return -1; } } @@ -397,8 +378,9 @@ private void readIP(long offset, byte[] ip) { temp = ip[1]; ip[1] = ip[2]; ip[2] = temp; - } catch (IOException e) { - System.out.println(e.getMessage()); + } + catch (IOException e) { + System.out.println(e.getMessage()); } } @@ -409,8 +391,8 @@ private void readIP(long offset, byte[] ip) { * @param ip */ private void readIP(int offset, byte[] ip) { - mbb.position(offset); - mbb.get(ip); + mbb.position(offset); + mbb.get(ip); byte temp = ip[0]; ip[0] = ip[3]; ip[3] = temp; @@ -426,9 +408,9 @@ private void readIP(int offset, byte[] ip) { * @return ��ȷ���0��ip����beginIp�򷵻�1��С�ڷ���-1�� */ private int compareIP(byte[] ip, byte[] beginIp) { - for(int i = 0; i < 4; i++) { + for (int i = 0; i < 4; i++) { int r = compareByte(ip[i], beginIp[i]); - if(r != 0) + if (r != 0) return r; } return 0; @@ -441,17 +423,16 @@ private int compareIP(byte[] ip, byte[] beginIp) { * @return ��b1����b2�򷵻�1����ȷ���0��С�ڷ���-1 */ private int compareByte(byte b1, byte b2) { - if((b1 & 0xFF) > (b2 & 0xFF)) // �Ƚ��Ƿ���� + if ((b1 & 0xFF) > (b2 & 0xFF)) // �Ƚ��Ƿ���� return 1; - else if((b1 ^ b2) == 0)// �ж��Ƿ���� + else if ((b1 ^ b2) == 0)// �ж��Ƿ���� return 0; else return -1; } /** - * �������������ip�����ݣ���λ���������ip���ҵ����ļ�¼��������һ������ƫ�� - * ����ʹ�ö��ַ����ҡ� + * �������������ip�����ݣ���λ���������ip���ҵ����ļ�¼��������һ������ƫ�� ����ʹ�ö��ַ����ҡ� * @param ip Ҫ��ѯ��IP * @return ����ҵ��ˣ����ؽ���IP��ƫ�ƣ����û���ҵ�������-1 */ @@ -461,32 +442,38 @@ private long locateIP(byte[] ip) { // �Ƚϵ�һ��ip�� readIP(ipBegin, b4); r = compareIP(ip, b4); - if(r == 0) return ipBegin; - else if(r < 0) return -1; + if (r == 0) + return ipBegin; + else if (r < 0) + return -1; // ��ʼ�������� - for(long i = ipBegin, j = ipEnd; i < j; ) { + for (long i = ipBegin, j = ipEnd; i < j;) { m = getMiddleOffset(i, j); readIP(m, b4); r = compareIP(ip, b4); // log.debug(Utils.getIpStringFromBytes(b)); - if(r > 0) + if (r > 0) i = m; - else if(r < 0) { - if(m == j) { + else if (r < 0) { + if (m == j) { j -= IP_RECORD_LENGTH; m = j; - } else + } + else j = m; - } else + } + else return readLong3(m + 4); } // ���ѭ�������ˣ���ôi��j�ض�����ȵģ������¼Ϊ����ܵļ�¼�����Dz��� - // �϶����ǣ���Ҫ���һ�£�����ǣ��ͷ��ؽ�����ַ���ľ���ƫ�� + // �϶����ǣ���Ҫ���һ�£�����ǣ��ͷ��ؽ�����ַ���ľ���ƫ�� m = readLong3(m + 4); readIP(m, b4); r = compareIP(ip, b4); - if(r <= 0) return m; - else return -1; + if (r <= 0) + return m; + else + return -1; } /** @@ -498,7 +485,8 @@ else if(r < 0) { private long getMiddleOffset(long begin, long end) { long records = (end - begin) / IP_RECORD_LENGTH; records >>= 1; - if(records == 0) records = 1; + if (records == 0) + records = 1; return begin + records * IP_RECORD_LENGTH; } @@ -513,29 +501,33 @@ private IPLocation getIPLocation(long offset) { ipFile.seek(offset + 4); // ��ȡ��һ���ֽ��ж��Ƿ��־�ֽ� byte b = ipFile.readByte(); - if(b == AREA_FOLLOWED) { + if (b == AREA_FOLLOWED) { // ��ȡ����ƫ�� long countryOffset = readLong3(); // ��ת��ƫ�ƴ� ipFile.seek(countryOffset); // �ټ��һ�α�־�ֽڣ���Ϊ���ʱ������ط���Ȼ�����Ǹ��ض��� b = ipFile.readByte(); - if(b == NO_AREA) { + if (b == NO_AREA) { loc.country = readString(readLong3()); ipFile.seek(countryOffset + 4); - } else + } + else loc.country = readString(countryOffset); // ��ȡ������־ loc.area = readArea(ipFile.getFilePointer()); - } else if(b == NO_AREA) { + } + else if (b == NO_AREA) { loc.country = readString(readLong3()); loc.area = readArea(offset + 8); - } else { + } + else { loc.country = readString(ipFile.getFilePointer() - 1); loc.area = readArea(ipFile.getFilePointer()); } return loc; - } catch (IOException e) { + } + catch (IOException e) { return null; } } @@ -546,27 +538,30 @@ private IPLocation getIPLocation(long offset) { */ private IPLocation getIPLocation(int offset) { // ����4�ֽ�ip - mbb.position(offset + 4); + mbb.position(offset + 4); // ��ȡ��һ���ֽ��ж��Ƿ��־�ֽ� byte b = mbb.get(); - if(b == AREA_FOLLOWED) { + if (b == AREA_FOLLOWED) { // ��ȡ����ƫ�� int countryOffset = readInt3(); // ��ת��ƫ�ƴ� mbb.position(countryOffset); // �ټ��һ�α�־�ֽڣ���Ϊ���ʱ������ط���Ȼ�����Ǹ��ض��� b = mbb.get(); - if(b == NO_AREA) { + if (b == NO_AREA) { loc.country = readString(readInt3()); mbb.position(countryOffset + 4); - } else + } + else loc.country = readString(countryOffset); // ��ȡ������־ loc.area = readArea(mbb.position()); - } else if(b == NO_AREA) { + } + else if (b == NO_AREA) { loc.country = readString(readInt3()); loc.area = readArea(offset + 8); - } else { + } + else { loc.country = readString(mbb.position() - 1); loc.area = readArea(mbb.position()); } @@ -582,13 +577,14 @@ private IPLocation getIPLocation(int offset) { private String readArea(long offset) throws IOException { ipFile.seek(offset); byte b = ipFile.readByte(); - if(b == 0x01 || b == 0x02) { + if (b == 0x01 || b == 0x02) { long areaOffset = readLong3(offset + 1); - if(areaOffset == 0) + if (areaOffset == 0) return "δ֪����"; else return readString(areaOffset); - } else + } + else return readString(offset); } @@ -599,13 +595,14 @@ private String readArea(long offset) throws IOException { private String readArea(int offset) { mbb.position(offset); byte b = mbb.get(); - if(b == 0x01 || b == 0x02) { + if (b == 0x01 || b == 0x02) { int areaOffset = readInt3(); - if(areaOffset == 0) + if (areaOffset == 0) return "δ֪����"; else return readString(areaOffset); - } else + } + else return readString(offset); } @@ -618,11 +615,13 @@ private String readString(long offset) { try { ipFile.seek(offset); int i; - for(i = 0, buf[i] = ipFile.readByte(); buf[i] != 0; buf[++i] = ipFile.readByte()); - if(i != 0) - return Utils.getString(buf, 0, i, "GBK"); - } catch (IOException e) { - System.out.println(e.getMessage()); + for (i = 0, buf[i] = ipFile.readByte(); buf[i] != 0; buf[++i] = ipFile.readByte()) + ; + if (i != 0) + return Utils.getString(buf, 0, i, "GBK"); + } + catch (IOException e) { + System.out.println(e.getMessage()); } return ""; } @@ -633,26 +632,52 @@ private String readString(long offset) { * @return */ private String readString(int offset) { - try { + try { mbb.position(offset); int i; - for(i = 0, buf[i] = mbb.get(); buf[i] != 0; buf[++i] = mbb.get()); - if(i != 0) - return Utils.getString(buf, 0, i, "GBK"); - } catch (IllegalArgumentException e) { - System.out.println(e.getMessage()); - } - return ""; + for (i = 0, buf[i] = mbb.get(); buf[i] != 0; buf[++i] = mbb.get()) + ; + if (i != 0) + return Utils.getString(buf, 0, i, "GBK"); + } + catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + return ""; } - public String getAddress(String ip){ - String country = getCountry(ip).equals(" CZ88.NET")?"":getCountry(ip); - String area = getArea(ip).equals(" CZ88.NET")?"":getArea(ip); - String address = country+" "+area; + public String getAddress(String ip) { + String country = getCountry(ip).equals(" CZ88.NET") ? "" : getCountry(ip); + String area = getArea(ip).equals(" CZ88.NET") ? "" : getArea(ip); + String address = country + " " + area; return address.trim(); } -} + /** + *
+	 * ������װip�����Ϣ��Ŀǰֻ�������ֶΣ�ip���ڵĹ��Һ͵���
+	 * 
+ * + * @author ����� + */ + private class IPLocation { + public String country; + public String area; + + public IPLocation() { + country = area = ""; + } + + public IPLocation getCopy() { + IPLocation ret = new IPLocation(); + ret.country = country; + ret.area = area; + return ret; + } + + } + +} diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/Test.java b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/Test.java index 2b1516f4..8d5dc84f 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/Test.java +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/Test.java @@ -1,34 +1,38 @@ package io.github.dunwu.javaee.jsp.util.ip; + /** * @author LJ-silver */ -import java.util.*; + + public class Test { public static void main(String[] args) { - args = new String[]{"ip", "9.128.2.1"}; + args = new String[] { "ip", "9.128.2.1" }; IPSeeker seeker = IPSeeker.getInstance(); - if(args.length==2){ - if("ip".equals(args[0])){ - System.out.println(args[0]+"�����ڵ�ַ��:"+seeker.getAddress(args[1])); - System.out.println(args[0]+"�����ڵ�ַ������:"+seeker.getCountry(args[1])); - }else if("address".equals(args[0])){ + if (args.length == 2) { + if ("ip".equals(args[0])) { + System.out.println(args[0] + "�����ڵ�ַ��:" + seeker.getAddress(args[1])); + System.out.println(args[0] + "�����ڵ�ַ������:" + seeker.getCountry(args[1])); + } + else if ("address".equals(args[0])) { List a = seeker.getIPEntries(args[1]); -System.out.println(args[0]+":"); - for(int i=0;i - + @@ -33,13 +33,13 @@ - - + + - - + + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/WEB-INF/resources/jsp/index.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/WEB-INF/resources/jsp/index.jsp index 74319429..bde68afd 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/WEB-INF/resources/jsp/index.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/WEB-INF/resources/jsp/index.jsp @@ -1,16 +1,16 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> -My JSP 'index.jsp' starting page - - - - + My JSP 'index.jsp' starting page + + + + -

This is my JSP page.

+

This is my JSP page.

diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/WEB-INF/web.xml b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/WEB-INF/web.xml index d65f0eaa..ae92ddac 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/WEB-INF/web.xml +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/WEB-INF/web.xml @@ -1,25 +1,25 @@ - - - HelloServlet - /examples/configuration.jsp - - message - welcome to jsp - - 1 - - - HelloServlet - /config - /config.jsp - + + HelloServlet + /examples/configuration.jsp + + message + welcome to jsp + + 1 + + + HelloServlet + /config + /config.jsp + - - - /WEB-INF/views/jsp/index.jsp - + + + /WEB-INF/views/jsp/index.jsp + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/break.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/break.jsp index d97e9fb9..adcf059b 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/break.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/break.jsp @@ -1,22 +1,24 @@ -<%@ page language="java" contentType="text/html; charset=utf-8"%> +<%@ page language="java" contentType="text/html; charset=utf-8" %> JSP Scriptlets <% - for(int i=0; i<5; i++){ -%> break 所在的循环, i = <%= i %>。
+ for (int i = 0; i < 5; i++) { +%> break 所在的循环, i = <%= i %>。
<% - if(i == 2) break; - } -%> break 循环完毕,
+ if (i == 2) + break; + } +%> break 循环完毕,
<% - for(int i=0; i<5; i++){ -%> return 所在的循环, i = <%= i %>。
+ for (int i = 0; i < 5; i++) { +%> return 所在的循环, i = <%= i %>。
<% - if(i == 2) return; - } -%> return 循环完毕,
+ if (i == 2) + return; + } +%> return 循环完毕,
diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/for.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/for.jsp index 93273521..f3f92e99 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/for.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/for.jsp @@ -7,12 +7,10 @@
<% - Object[][] letters = { - {true, "恭喜您注册的信息已经生效", "e_inn@163.com", "forbreak@163.com", "2007-8-8"}, + Object[][] letters = {{true, "恭喜您注册的信息已经生效", "e_inn@163.com", "forbreak@163.com", "2007-8-8"}, {true, "JavaEE 7.0 release!", "admin@sun.com", "forbreak@163.com", "2007-6-24"}, {false, "来信已经收到,下周来面谈", "foo@bar.com", "forbreak@163.com", "2007-5-20"}, - {false, "您有新的邮件", "blog@foo.bar.com", "forbreak@163.com", "2007-3-2"}, - }; + {false, "您有新的邮件", "blog@foo.bar.com", "forbreak@163.com", "2007-3-2"},}; String[] colors = {"#DDDDDD", "#AAAAAA",}; %> diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/if2.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/if2.jsp index 1c574813..37c7debc 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/if2.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/if2.jsp @@ -1,4 +1,4 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <%! int day = 1; %> @@ -14,4 +14,4 @@

今天不是周末

<% } %> - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/scriptlet.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/scriptlet.jsp index 9764f28d..467b29da 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/scriptlet.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/scriptlet.jsp @@ -1,16 +1,16 @@ -<%@ page language="java" contentType="text/html; charset=utf-8"%> +<%@ page language="java" contentType="text/html; charset=utf-8" %> JSP Scriptlets <% - int num = 10; - int result = 1; - for(int i=1; i<=num; i++){ - result *= i; - } - out.println("
"); - out.println("数字 " + num + " 的阶乘为:" + result); + int num = 10; + int result = 1; + for (int i = 1; i <= num; i++) { + result *= i; + } + out.println("
"); + out.println("数字 " + num + " 的阶乘为:" + result); %> diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/switch.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/switch.jsp index 3b077b8a..7079bdb7 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/switch.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/switch.jsp @@ -1,5 +1,5 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> + pageEncoding="UTF-8" %> <%! int day = 3; %> @@ -10,7 +10,7 @@

Sswitch...case示例

<% - switch(day) { + switch (day) { case 0: out.println("星期天"); break; @@ -34,4 +34,4 @@ } %> - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/foot.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/foot.jsp index d17f0460..21080f9e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/foot.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/foot.jsp @@ -1,12 +1,12 @@ -<%@ page language="java" contentType="text/html; charset=utf-8"%> +<%@ page language="java" contentType="text/html; charset=utf-8" %>
- - - - + + + +
- Copyright 2017 &Zhang Peng -
+ Copyright 2017 &Zhang Peng +
diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/head.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/head.jsp index 3210fdb6..4455211c 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/head.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/head.jsp @@ -1,19 +1,20 @@ -<%@ page language="java" contentType="text/html; charset=utf-8"%> +<%@ page language="java" contentType="text/html; charset=utf-8" %> -JSP 示例 - + JSP 示例 + - - - - - - - - -
JSP 示例
首页资源文档
\ No newline at end of file + + JSP 示例 + + + + 首页 + 资源 + 文档 + + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/include.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/include.jsp index 5699a431..7234c39f 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/include.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/include.jsp @@ -1,4 +1,4 @@ -<%@ page language="java" contentType="text/html; charset=utf-8"%> +<%@ page language="java" contentType="text/html; charset=utf-8" %> <%@ include file="head.jsp" %> @@ -15,4 +15,4 @@

人生如梦,一樽还酹江月。

-<%@ include file="foot.jsp" %> + <%@ include file="foot.jsp" %> diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspforward.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspforward.jsp index aec8a0dd..1430aa8e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspforward.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspforward.jsp @@ -1,22 +1,22 @@ - - + + <% - out.clear(); - if("1".equals(request.getParameter("param"))){ + out.clear(); + if ("1".equals(request.getParameter("param"))) { %> - - + + <% - } + } %> -闀ㄤ讲鍓ラ弪镡煎禌?/title> -<link rel='stylesheet' type='text/css' href='css/style.css'> -</head> -<body> + <title>闀ㄤ讲鍓ラ弪镡煎禌?/title> + <link rel='stylesheet' type='text/css' href='css/style.css'> + </head> +<body> i闀ㄥ洷娓规繛锲噭閵娿储鍕鹃柛褉锅挞梹妞ょ箰瀵剟寮?param=1 闀ㄤ礁娼″Λ鍓佹嫚閵夆敛钪妫婚ⅳ顑藉亾? </body> </html> diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspplugin.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspplugin.jsp index 6916eb6e..05320036 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspplugin.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspplugin.jsp @@ -1,27 +1,32 @@ <%@ page language="java" contentType="text/html; charset=utf-8" %> <html> <head> -<title>plugin - + plugin +
- + +
+ + +
- - - - - - 您的浏览器不支持 Java Applet - + + + + + + 您的浏览器不支持 Java Applet + -
+
diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspuseBean2.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspuseBean2.jsp index 56dbc752..efa5137a 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspuseBean2.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspuseBean2.jsp @@ -2,8 +2,8 @@ 计数器 - - + +
<%-- 定义一个 session 范围内的计数器,记录个人的访问信息 --%> diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/useBean.html b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/useBean.html index 4e5ce1e5..ed22e1f2 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/useBean.html +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/useBean.html @@ -1,41 +1,41 @@ - - Java Bean Actions - + + Java Bean Actions +

-
-
- 请填写 Person 信息 - - - - - - - - - - - - - - - - - + +
+ 请填写 Person 信息 +
姓名:
年龄:
性别: - Male - Female -
- -
+ + + + + + + + + + + + + + + + -
姓名:
年龄:
性别: + Male + Female +
+ +
-
-
+ + +
diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/05.implicit_object/error.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/05.implicit_object/error.jsp index cc800f12..9528c599 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/05.implicit_object/error.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/05.implicit_object/error.jsp @@ -4,7 +4,7 @@ <% - out.println("程序拋出了一个异常:"+ exception); + out.println("程序拋出了一个异常:" + exception); %> diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/05.implicit_object/exception.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/05.implicit_object/exception.jsp index 3edbf87e..b5187acd 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/05.implicit_object/exception.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/05.implicit_object/exception.jsp @@ -1,9 +1,9 @@ <%@ page language="java" contentType="text/html; charset=utf-8" errorPage="error.jsp" %> <% - out.clear(); - String str = null; - // length() 会抛出 NullPointerException - int length = str.length(); + out.clear(); + String str = null; + // length() 会抛出 NullPointerException + int length = str.length(); %> JSP diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/cookie/readCookie.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/cookie/readCookie.jsp index d44a4435..4d73c103 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/cookie/readCookie.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/cookie/readCookie.jsp @@ -1,31 +1,31 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> + pageEncoding="UTF-8" %> <%@ page import="java.net.URLDecoder" %> - - 获取 Cookie + + 获取 Cookie <% - Cookie cookie = null; - Cookie[] cookies = null; - // 获取cookies的数据,是一个数组 - cookies = request.getCookies(); - if( cookies != null ){ - out.println("

查找 Cookie 名与值

"); - for (int i = 0; i < cookies.length; i++){ - cookie = cookies[i]; - - out.print("参数名 : " + cookie.getName()); - out.print("
"); - out.print("参数值: " + URLDecoder.decode(cookie.getValue(), "utf-8") +"
"); - out.print("------------------------------------
"); - } - }else{ - out.println("

没有发现 Cookie

"); - } + Cookie cookie = null; + Cookie[] cookies = null; + // 获取cookies的数据,是一个数组 + cookies = request.getCookies(); + if (cookies != null) { + out.println("

查找 Cookie 名与值

"); + for (int i = 0; i < cookies.length; i++) { + cookie = cookies[i]; + + out.print("参数名 : " + cookie.getName()); + out.print("
"); + out.print("参数值: " + URLDecoder.decode(cookie.getValue(), "utf-8") + "
"); + out.print("------------------------------------
"); + } + } else { + out.println("

没有发现 Cookie

"); + } %> - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/cookie/session.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/cookie/session.jsp index f7b8c735..7b71ff01 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/cookie/session.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/cookie/session.jsp @@ -2,38 +2,38 @@ pageEncoding="UTF-8" %> <%@ page import="java.util.Date" %> <% - // 获取session创建时间 - Date createTime = new Date(session.getCreationTime()); - // 获取最后访问页面的时间 - Date lastAccessTime = new Date(session.getLastAccessedTime()); - - String note = "再次访问"; - Integer visitCount = 0; - String visitCountKey = "visitCount"; - String userIDKey = "userID"; - String userID = "ABCD"; - - // 检测网页是否由新的访问用户 - if (session.isNew()) { - note = "第一次访问"; - session.setAttribute(userIDKey, userID); - session.setAttribute(visitCountKey, visitCount); - } else { - visitCount = (Integer) session.getAttribute(visitCountKey); - if (null == visitCount) { - visitCount = 0; - } - visitCount += 1; - userID = (String) session.getAttribute(userIDKey); - if (null == userID) { - userID = "ABCD"; - } - session.setAttribute(visitCountKey, visitCount); - } + // 获取session创建时间 + Date createTime = new Date(session.getCreationTime()); + // 获取最后访问页面的时间 + Date lastAccessTime = new Date(session.getLastAccessedTime()); + + String note = "再次访问"; + Integer visitCount = 0; + String visitCountKey = "visitCount"; + String userIDKey = "userID"; + String userID = "ABCD"; + + // 检测网页是否由新的访问用户 + if (session.isNew()) { + note = "第一次访问"; + session.setAttribute(userIDKey, userID); + session.setAttribute(visitCountKey, visitCount); + } else { + visitCount = (Integer) session.getAttribute(visitCountKey); + if (null == visitCount) { + visitCount = 0; + } + visitCount += 1; + userID = (String) session.getAttribute(userIDKey); + if (null == userID) { + userID = "ABCD"; + } + session.setAttribute(visitCountKey, visitCount); + } %> - Session 示例 + Session 示例 @@ -42,30 +42,30 @@

<% out.print(note); %>

- - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + +
Session 信息
id<% out.print(session.getId()); %>
创建时间<% out.print(createTime); %>
最后访问时间<% out.print(lastAccessTime); %>
用户 ID<% out.print(userID); %>
访问次数<% out.print(visitCount); %>
Session 信息
id<% out.print(session.getId()); %>
创建时间<% out.print(createTime); %>
最后访问时间<% out.print(lastAccessTime); %>
用户 ID<% out.print(userID); %>
访问次数<% out.print(visitCount); %>
- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/cookie/writeCookie.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/cookie/writeCookie.jsp index 1c50ae3d..62aae68c 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/cookie/writeCookie.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/cookie/writeCookie.jsp @@ -6,36 +6,36 @@ pageEncoding="UTF-8" %> <%@ page import="java.net.URLEncoder" %> <% - // 编码,解决中文乱码 - String name = URLEncoder.encode(request.getParameter("name"), "utf-8"); - - // 设置 nameCookie 和 urlCookie cookie - Cookie nameCookie = new Cookie("name", name); - Cookie urlCookie = new Cookie("url", request.getParameter("url")); - - // 设置cookie过期时间为24小时。 - nameCookie.setMaxAge(60 * 60 * 24); - urlCookie.setMaxAge(60 * 60 * 24); - - // 在响应头部添加cookie - response.addCookie(nameCookie); - response.addCookie(urlCookie); + // 编码,解决中文乱码 + String name = URLEncoder.encode(request.getParameter("name"), "utf-8"); + + // 设置 nameCookie 和 urlCookie cookie + Cookie nameCookie = new Cookie("name", name); + Cookie urlCookie = new Cookie("url", request.getParameter("url")); + + // 设置cookie过期时间为24小时。 + nameCookie.setMaxAge(60 * 60 * 24); + urlCookie.setMaxAge(60 * 60 * 24); + + // 在响应头部添加cookie + response.addCookie(nameCookie); + response.addCookie(urlCookie); %> - 设置 Cookie + 设置 Cookie

设置 Cookie

    -
  • 网站名: - <%= request.getParameter("name")%> -

  • -
  • 网址: - <%= request.getParameter("url")%> -

  • +
  • 网站名: + <%= request.getParameter("name")%> +

  • +
  • 网址: + <%= request.getParameter("url")%> +

- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/language.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/language.jsp index d60eeb99..641be6db 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/language.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/language.jsp @@ -1,23 +1,23 @@ <%@ page import="java.util.Locale" %> <% - //获取客户端本地化信息 - Locale locale = request.getLocale(); - String language = locale.getLanguage(); - String country = locale.getCountry(); + //获取客户端本地化信息 + Locale locale = request.getLocale(); + String language = locale.getLanguage(); + String country = locale.getCountry(); %> - Detecting Locale + Detecting Locale
-

Detecting Locale

+

Detecting Locale

- <% - out.println("Language : " + language + "
"); - out.println("Country : " + country + "
"); - %> + <% + out.println("Language : " + language + "
"); + out.println("Country : " + country + "
"); + %>

- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/language2.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/language2.jsp index 13b6eade..c9ce8204 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/language2.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/language2.jsp @@ -1,20 +1,20 @@ <% - // Set response content type - response.setContentType("text/html"); - // Set spanish language code. - response.setHeader("Content-Language", "es"); - String title = "En Espa?ol"; + // Set response content type + response.setContentType("text/html"); + // Set spanish language code. + response.setHeader("Content-Language", "es"); + String title = "En Espa?ol"; %> - <% out.print(title); %> + <% out.print(title); %>

<% out.print(title); %>

-

En Espa?ol

-

?Hola Mundo!

+

En Espa?ol

+

?Hola Mundo!

- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/localeCurrency.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/localeCurrency.jsp index 466b6382..5d90985b 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/localeCurrency.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/localeCurrency.jsp @@ -1,20 +1,20 @@ <%@ page import="java.text.NumberFormat,java.util.Locale" %> <% - String title = "Locale Specific Currency"; - //Get the client's Locale - Locale locale = request.getLocale(); - NumberFormat nft = NumberFormat.getCurrencyInstance(locale); - String formattedCurr = nft.format(1000000); + String title = "Locale Specific Currency"; + //Get the client's Locale + Locale locale = request.getLocale(); + NumberFormat nft = NumberFormat.getCurrencyInstance(locale); + String formattedCurr = nft.format(1000000); %> - <% out.print(title); %> + <% out.print(title); %>

<% out.print(title); %>

-

Formatted Currency: <% out.print(formattedCurr); %>

+

Formatted Currency: <% out.print(formattedCurr); %>

- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/localeDate.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/localeDate.jsp index 42485237..08792be1 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/localeDate.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/localeDate.jsp @@ -2,22 +2,19 @@ <%@ page import="java.util.Locale " %> <% - String title = "Locale Specific Dates"; - //Get the client's Locale - Locale locale = request.getLocale(); - String date = DateFormat.getDateTimeInstance( - DateFormat.FULL, - DateFormat.SHORT, - locale).format(new Date()); + String title = "Locale Specific Dates"; + //Get the client's Locale + Locale locale = request.getLocale(); + String date = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.SHORT, locale).format(new Date()); %> - <% out.print(title); %> + <% out.print(title); %>

<% out.print(title); %>

-

Local Date: <% out.print(date); %>

+

Local Date: <% out.print(date); %>

- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/localePercent.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/localePercent.jsp index f635f617..6bc4957b 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/localePercent.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/localePercent.jsp @@ -1,20 +1,20 @@ <%@ page import="java.text.NumberFormat,java.util.Locale" %> <% - String title = "Locale Specific Percentage"; - //Get the client's Locale - Locale locale = request.getLocale(); - NumberFormat nft = NumberFormat.getPercentInstance(locale); - String formattedPerc = nft.format(0.51); + String title = "Locale Specific Percentage"; + //Get the client's Locale + Locale locale = request.getLocale(); + NumberFormat nft = NumberFormat.getPercentInstance(locale); + String formattedPerc = nft.format(0.51); %> - <% out.print(title); %> + <% out.print(title); %>

<% out.print(title); %>

-

Formatted Percentage: <% out.print(formattedPerc); %>

+

Formatted Percentage: <% out.print(formattedPerc); %>

- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/date.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/date.jsp index 4c250187..57310aab 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/date.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/date.jsp @@ -3,23 +3,22 @@ <%@ page import="java.text.SimpleDateFormat,java.util.Date" %> - 显示当前时间与日期 + 显示当前时间与日期

显示当前时间与日期

<% - Date date = new Date(); - out.print("

" + date.toString() + "

"); + Date date = new Date(); + out.print("

" + date.toString() + "

"); %>

使用SimpleDateFormat格式化日期

<% - Date dNow = new Date(); - SimpleDateFormat ft = - new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - out.print("

" + ft.format(dNow) + "

"); + Date dNow = new Date(); + SimpleDateFormat ft = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + out.print("

" + ft.format(dNow) + "

"); %> - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox.html b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox.html index d7efbff7..4b9f950c 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox.html +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox.html @@ -7,11 +7,11 @@
- 谷歌 - 百度 - 淘宝 + 谷歌 + 百度 + 淘宝
- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox.jsp index 60966b82..b835e91b 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox.jsp @@ -3,21 +3,21 @@ - - Checkbox + + Checkbox

从复选框中读取数据

    -
  • 谷歌是否选中: - <%= request.getParameter("google")%> -

  • -
  • 百度是否选中: - <%= request.getParameter("baidu")%> -

  • -
  • 淘宝是否选中: - <%= request.getParameter("taobao")%> -

  • +
  • 谷歌是否选中: + <%= request.getParameter("google")%> +

  • +
  • 百度是否选中: + <%= request.getParameter("baidu")%> +

  • +
  • 淘宝是否选中: + <%= request.getParameter("taobao")%> +

- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox2.html b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox2.html index 0c4b6fd0..fc2faab7 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox2.html +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox2.html @@ -7,11 +7,11 @@
- 谷歌 - 百度 - 淘宝 + 谷歌 + 百度 + 淘宝
- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox2.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox2.jsp index 2a40276a..bbb64c78 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox2.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox2.jsp @@ -4,26 +4,26 @@ - - Checkbox2 + + Checkbox2

读取所有表单参数

- - - - - <% - Enumeration paramNames = request.getParameterNames(); - - while (paramNames.hasMoreElements()) { - String paramName = (String) paramNames.nextElement(); - out.print("\n"); - String paramValue = request.getParameter(paramName); - out.println("\n"); - } - %> + + + + + <% + Enumeration paramNames = request.getParameterNames(); + + while (paramNames.hasMoreElements()) { + String paramName = (String) paramNames.nextElement(); + out.print("\n"); + String paramValue = request.getParameter(paramName); + out.println("\n"); + } + %>
参数名参数值
" + paramName + " " + paramValue + "
参数名参数值
" + paramName + " " + paramValue + "
- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formGet.html b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formGet.html index 1572d4ba..0dafa642 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formGet.html +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formGet.html @@ -7,11 +7,11 @@
- 站点名: + 站点名:
- 网址: + 网址:
- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formGet.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formGet.jsp index 2dc4baa4..fc41d491 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formGet.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formGet.jsp @@ -3,18 +3,18 @@ - - 表单 - Get操作 + + 表单 - Get操作

使用 GET 方法读取数据

    -
  • 站点名: - <%= request.getParameter("name")%> -

  • -
  • 网址: - <%= request.getParameter("url")%> -

  • +
  • 站点名: + <%= request.getParameter("name")%> +

  • +
  • 网址: + <%= request.getParameter("url")%> +

- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formPost.html b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formPost.html index c624cc0a..bf60e986 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formPost.html +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formPost.html @@ -7,11 +7,11 @@
- 站点名: + 站点名:
- 网址: + 网址:
- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formPost.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formPost.jsp index 8a4ccd7a..28721541 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formPost.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formPost.jsp @@ -3,22 +3,22 @@ - - 表单 - Post操作 + + 表单 - Post操作

使用 POST 方法读取数据

    -
  • 站点名: - <% - // 解决中文乱码的问题 - String name = new String((request.getParameter("name")).getBytes("ISO-8859-1"), "UTF-8"); - %> - <%=name%> -

  • -
  • 网址: - <%= request.getParameter("url")%> -

  • +
  • 站点名: + <% + // 解决中文乱码的问题 + String name = new String((request.getParameter("name")).getBytes("ISO-8859-1"), "UTF-8"); + %> + <%=name%> +

  • +
  • 网址: + <%= request.getParameter("url")%> +

- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/hitCount.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/hitCount.jsp index dcbd6e36..4f14c1f7 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/hitCount.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/hitCount.jsp @@ -3,26 +3,25 @@ - 访问量统计 + 访问量统计 <% - Integer hitsCount = - (Integer) application.getAttribute("hitCounter"); - if (hitsCount == null || hitsCount == 0) { - /* 第一次访问 */ - out.println("欢迎访问!"); - hitsCount = 1; - } else { - /* 返回访问值 */ - out.println("欢迎再次访问!"); - hitsCount += 1; - } - application.setAttribute("hitCounter", hitsCount); + Integer hitsCount = (Integer) application.getAttribute("hitCounter"); + if (hitsCount == null || hitsCount == 0) { + /* 第一次访问 */ + out.println("欢迎访问!"); + hitsCount = 1; + } else { + /* 返回访问值 */ + out.println("欢迎再次访问!"); + hitsCount += 1; + } + application.setAttribute("hitCounter", hitsCount); %>

页面访问量为: <%= hitsCount%>

- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/mail/sendMail.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/mail/sendMail.jsp index f07a2c2e..265a8475 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/mail/sendMail.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/mail/sendMail.jsp @@ -1,56 +1,55 @@ -<%@ page import="javax.mail.*,javax.mail.internet.*,java.util.Properties" %> +<%@ page import="java.util.Properties" %> <% - String result; - // 收件人的电子邮件 - String to = "abcd@gmail.com"; - - // 发件人的电子邮件 - String from = "mcmohd@gmail.com"; - - // 假设你是从本地主机发送电子邮件 - String host = "localhost"; - - // 获取系统属性对象 - Properties properties = System.getProperties(); - - // 设置邮件服务器 - properties.setProperty("mail.smtp.host", host); - - // 获取默认的Session对象。 - Session mailSession = Session.getDefaultInstance(properties); - - try { - // 创建一个默认的MimeMessage对象。 - MimeMessage message = new MimeMessage(mailSession); - // 设置 From: 头部的header字段 - message.setFrom(new InternetAddress(from)); - // 设置 To: 头部的header字段 - message.addRecipient(Message.RecipientType.TO, - new InternetAddress(to)); - // 设置 Subject: header字段 - message.setSubject("This is the Subject Line!"); - // 现在设置的实际消息 - message.setText("This is actual message"); - // 发送消息 - Transport.send(message); - result = "Sent message successfully...."; - } catch (MessagingException mex) { - mex.printStackTrace(); - result = "Error: unable to send message...."; - } + String result; + // 收件人的电子邮件 + String to = "abcd@gmail.com"; + + // 发件人的电子邮件 + String from = "mcmohd@gmail.com"; + + // 假设你是从本地主机发送电子邮件 + String host = "localhost"; + + // 获取系统属性对象 + Properties properties = System.getProperties(); + + // 设置邮件服务器 + properties.setProperty("mail.smtp.host", host); + + // 获取默认的Session对象。 + Session mailSession = Session.getDefaultInstance(properties); + + try { + // 创建一个默认的MimeMessage对象。 + MimeMessage message = new MimeMessage(mailSession); + // 设置 From: 头部的header字段 + message.setFrom(new InternetAddress(from)); + // 设置 To: 头部的header字段 + message.addRecipient(Message.RecipientType.TO, new InternetAddress(to)); + // 设置 Subject: header字段 + message.setSubject("This is the Subject Line!"); + // 现在设置的实际消息 + message.setText("This is actual message"); + // 发送消息 + Transport.send(message); + result = "Sent message successfully...."; + } catch (MessagingException mex) { + mex.printStackTrace(); + result = "Error: unable to send message...."; + } %> - Send Email using JSP + Send Email using JSP
-

Send Email using JSP

+

Send Email using JSP

- <% - out.println("Result: " + result + "\n"); - %> + <% + out.println("Result: " + result + "\n"); + %>

- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/redirect.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/redirect.jsp index bc4c0165..63c7e272 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/redirect.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/redirect.jsp @@ -3,18 +3,18 @@ - 页面重定向 + 页面重定向

页面重定向

<% - // 重定向到新地址 - String site = new String("http://www.baidu.com"); - response.setStatus(response.SC_MOVED_TEMPORARILY); - response.setHeader("Location", site); + // 重定向到新地址 + String site = new String("http://www.baidu.com"); + response.setStatus(response.SC_MOVED_TEMPORARILY); + response.setHeader("Location", site); %> - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/refresh.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/refresh.jsp index 86bf4444..8a10dc89 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/refresh.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/refresh.jsp @@ -3,27 +3,27 @@ <%@ page import="java.util.Calendar,java.util.GregorianCalendar" %> - 自动刷新实例 + 自动刷新实例

每秒刷新时间

<% - // 设置每秒刷新一次 - response.setIntHeader("Refresh", 1); - // 获取当前时间 - Calendar calendar = new GregorianCalendar(); - String am_pm; - int hour = calendar.get(Calendar.HOUR); - int minute = calendar.get(Calendar.MINUTE); - int second = calendar.get(Calendar.SECOND); - if (calendar.get(Calendar.AM_PM) == 0) - am_pm = "AM"; - else - am_pm = "PM"; - String CT = hour + ":" + minute + ":" + second + " " + am_pm; - out.println("当前时间为: " + CT + "\n"); + // 设置每秒刷新一次 + response.setIntHeader("Refresh", 1); + // 获取当前时间 + Calendar calendar = new GregorianCalendar(); + String am_pm; + int hour = calendar.get(Calendar.HOUR); + int minute = calendar.get(Calendar.MINUTE); + int second = calendar.get(Calendar.SECOND); + if (calendar.get(Calendar.AM_PM) == 0) + am_pm = "AM"; + else + am_pm = "PM"; + String CT = hour + ":" + minute + ":" + second + " " + am_pm; + out.println("当前时间为: " + CT + "\n"); %> - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/01.helloWorld.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/01.helloWorld.jsp index 88b3b2fa..7490aff3 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/01.helloWorld.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/01.helloWorld.jsp @@ -1,10 +1,10 @@ - First Jsp Programe + First Jsp Programe <% - out.println("Hello World!"); + out.println("Hello World!"); %> - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/01.helloWorld_zh.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/01.helloWorld_zh.jsp index 20fc14fa..747c2796 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/01.helloWorld_zh.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/01.helloWorld_zh.jsp @@ -3,11 +3,11 @@ - 第一个Jsp程序 + 第一个Jsp程序 <% - out.println("你好!"); + out.println("你好!"); %> - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/02.life.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/02.life.jsp index 9a50ba4c..88f1df22 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/02.life.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/02.life.jsp @@ -1,39 +1,42 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> + pageEncoding="UTF-8" %> - life.jsp + life.jsp <%! - private int initVar=0; - private int serviceVar=0; - private int destroyVar=0; + private int initVar = 0; + private int serviceVar = 0; + private int destroyVar = 0; %> <%! - public void jspInit(){ - initVar++; - System.out.println("jspInit(): JSP被初始化了"+initVar+"次"); - } - public void jspDestroy(){ - destroyVar++; - System.out.println("jspDestroy(): JSP被销毁了"+destroyVar+"次"); - } + public void jspInit() { + initVar++; + System.out.println("jspInit(): JSP被初始化了" + initVar + "次"); + } + public void jspDestroy() { + destroyVar++; + System.out.println("jspDestroy(): JSP被销毁了" + destroyVar + "次"); + } %> <% - serviceVar++; - System.out.println("_jspService(): JSP共响应了"+serviceVar+"次请求"); - - String content1="初始化次数 : "+initVar; - String content2="响应客户请求次数 : "+serviceVar; - String content3="销毁次数 : "+destroyVar; + serviceVar++; + System.out.println("_jspService(): JSP共响应了" + serviceVar + "次请求"); + + String content1 = "初始化次数 : " + initVar; + String content2 = "响应客户请求次数 : " + serviceVar; + String content3 = "销毁次数 : " + destroyVar; %>

菜鸟教程 JSP 测试实例

-

<%=content1 %>

-

<%=content2 %>

-

<%=content3 %>

+

<%=content1 %> +

+

<%=content2 %> +

+

<%=content3 %> +

- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/03.yourIp.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/03.yourIp.jsp index dda307dd..5f84217e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/03.yourIp.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/03.yourIp.jsp @@ -1,15 +1,15 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> + pageEncoding="UTF-8" %> - - 菜鸟教程(runoob.com) + + 菜鸟教程(runoob.com) Hello World!
<% - out.println("你的 IP 地址 " + request.getRemoteAddr()); + out.println("你的 IP 地址 " + request.getRemoteAddr()); %> - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/css/style.css b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/css/style.css index c97394dc..8e2eb97a 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/css/style.css +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/css/style.css @@ -1,18 +1,51 @@ -body, div, td, input {font-size:12px; margin:0px; } -select {height:20px; width:300px; } -.title {font-size: 16px; padding: 10px; margin:10px; width:80%; } -.text {height:20px; width:300px; border:1px solid #AAAAAA; } -.line {margin:2px; } -.leftDiv {width:110px; float:left; height:22px; line-height:22px; font-weight:bold; } -.rightDiv {height:22px; line-height:22px; } +body, div, td, input { + font-size: 12px; + margin: 0px; +} + +select { + height: 20px; + width: 300px; +} + +.title { + font-size: 16px; + padding: 10px; + margin: 10px; + width: 80%; +} + +.text { + height: 20px; + width: 300px; + border: 1px solid #AAAAAA; +} + +.line { + margin: 2px; +} + +.leftDiv { + width: 110px; + float: left; + height: 22px; + line-height: 22px; + font-weight: bold; +} + +.rightDiv { + height: 22px; + line-height: 22px; +} + .button { - color:#fff; - font-weight:bold; - font-size: 11px; - text-align:center; - padding:.17em 0 .2em .17em; - border-style:solid; - border-width:1px; - border-color:#9cf #159 #159 #9cf; - background:#69c url(../images/bg-btn-blue.gif) repeat-x; -} \ No newline at end of file + color: #FFFFFF; + font-weight: bold; + font-size: 11px; + text-align: center; + padding: .17em 0 .2em .17em; + border-style: solid; + border-width: 1px; + border-color: #99CCFF #115599 #115599 #99CCFF; + background: #6699CC url(../images/bg-btn-blue.gif) repeat-x; +} diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/date.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/date.jsp index 42913eab..19c75df9 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/date.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/date.jsp @@ -1,4 +1,4 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>

今天的日期是: <%=(new java.util.Date()).toLocaleString()%> -

\ No newline at end of file +

diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/forward.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/forward.jsp index 3f2f7181..d62ea475 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/forward.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/forward.jsp @@ -1,12 +1,12 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> - -jsp:forward范例 + + jsp:forward范例 -

jsp:forward范例

- +

jsp:forward范例

+ - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/getProperty.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/getProperty.jsp index ec8c48a4..ffa1ea0a 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/getProperty.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/getProperty.jsp @@ -1,19 +1,19 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> -jsp:setProperty和jsp:getProperty使用范例 + jsp:setProperty和jsp:getProperty使用范例 -

jsp:setProperty和jsp:getProperty使用范例

- +

jsp:setProperty和jsp:getProperty使用范例

+ - + -

输出信息....

+

输出信息....

- + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/include.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/include.jsp index 55db92e9..2c4aadc5 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/include.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/include.jsp @@ -1,12 +1,12 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> - -jsp:include范例 + + jsp:include范例 -

include 动作实例

- +

include 动作实例

+ - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/useBean.html b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/useBean.html index 528dd91d..221c8494 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/useBean.html +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/useBean.html @@ -1,37 +1,38 @@ -Java Bean Actions - - + Java Bean Actions + + -
-
-
-
- 请填写 Person 信息 - - - - - - - - - - - - - - - - - -
姓名:
年龄:
性别:Male Female
-
-
-
-
+
+
+
+
+ 请填写 Person 信息 + + + + + + + + + + + + + + + + + +
姓名:
年龄:
性别:Male Female +
+
+
+
+
- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/useBean.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/useBean.jsp index 0213f300..e2a62a6b 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/useBean.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/useBean.jsp @@ -1,50 +1,53 @@ -<%@ page language="java" contentType="text/html; charset=utf-8"%> +<%@ page language="java" contentType="text/html; charset=utf-8" %> -Java Bean Actions - + Java Bean Actions + -
+
- <%-- 声明 Person 类对象 person --%> - +<%-- 声明 Person 类对象 person --%> + - <%-- 设置 person 的所有属性,所有的属性值从 request 中自动取得 --%> - +<%-- 设置 person 的所有属性,所有的属性值从 request 中自动取得 --%> + -
-
-
- 请填写 Person 信息 - - - - - - - - - - - - - - - - - -
姓名: - <%-- 获取 person 的 name 属性 --%> -
年龄: - <%-- 获取 person 的 age 属性 --%> -
性别: - <%-- 获取 person 的 sex 属性 --%> -
-
-
-
-
+
+
+
+ 请填写 Person 信息 + + + + + + + + + + + + + + + + + +
姓名: + <%-- 获取 person 的 name 属性 --%> + +
年龄: + <%-- 获取 person 的 age 属性 --%> + +
性别: + <%-- 获取 person 的 sex 属性 --%> + +
+
+
+
+
diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/application.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/application.jsp index ab926c25..da0d5a78 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/application.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/application.jsp @@ -1,11 +1,10 @@ -<%@ page language="java" contentType="text/html; charset=utf-8"%> +<%@ page language="java" contentType="text/html; charset=utf-8" %> <% - out.getBufferSize(); - - out.println(request); - - - + out.getBufferSize(); + + out.println(request); + + %> JSP Scriptlets diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/break.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/break.jsp index b7de724b..18052841 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/break.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/break.jsp @@ -1,23 +1,25 @@ -<%@ page language="java" contentType="text/html; charset=utf-8"%> +<%@ page language="java" contentType="text/html; charset=utf-8" %> JSP Scriptlets <% - for(int i=0; i<5; i++){ -%> break 所在的循环, i = <%= i %>.
+ for (int i = 0; i < 5; i++) { +%> break 所在的循环, i = <%= i %>.
<% - if(i == 2) break; - } -%> break 循环完毕.
+ if (i == 2) + break; + } +%> break 循环完毕.
<% - for(int i=0; i<5; i++){ -%> return 所在的循环, i = <%= i %>.
+ for (int i = 0; i < 5; i++) { +%> return 所在的循环, i = <%= i %>.
<% - if(i == 2) return; - } -%> return 循环完毕.
+ if (i == 2) + return; + } +%> return 循环完毕.
diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/comment.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/comment.jsp index 6006ae8b..796788dc 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/comment.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/comment.jsp @@ -1,21 +1,21 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" %> <% - // 这是 Java 行注释 - String path = request.getContextPath(); - + // 这是 Java 行注释 + String path = request.getContextPath(); + /* 这是 Java 多行注释 */ %> - - JSP 注释 - + + JSP 注释 + <%-- 这是 JSP 注释,可以添加多行注释 --%> - - This is my JSP page.
- + +This is my JSP page.
+ diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/counter.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/counter.jsp index 6757ece3..cb062d93 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/counter.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/counter.jsp @@ -1,39 +1,41 @@ <%@ page language="java" contentType="text/html; charset=utf-8" %> -计数器 - + 计数器 +
<%-- 定义一个 session 范围内的计数器 记录个人访问信息 --%> - + <%-- 定义一个 application 范围内的计数器 记录所有人的访问信息 --%> - +
-
-
- 计数器 - - - - - - - - - -
您的访问次数: - <%-- 获取个人的 访问次数 --%> - 次 -
总共的访问次数: - <%-- 获取所有人的 访问次数 --%> - 次 -
-
-
+
+
+ 计数器 + + + + + + + + + +
您的访问次数: + <%-- 获取个人的 访问次数 --%> + + 次 +
总共的访问次数: + <%-- 获取所有人的 访问次数 --%> + + 次 +
+
+
diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive.jsp index 40aa1215..21f32e35 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive.jsp @@ -1,4 +1,4 @@ -<%@page contentType="image/jpeg"%> +<%@page contentType="image/jpeg" %> - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/include/foot.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/include/foot.jsp index 1673b0b2..5e55bd71 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/include/foot.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/include/foot.jsp @@ -1,9 +1,10 @@ -<%@ page language="java" contentType="text/html; charset=utf-8"%> +<%@ page language="java" contentType="text/html; charset=utf-8" %> + Copyright 2007-2010 ©Helloweenvsfei +
- Copyright 2007-2010 ©Helloweenvsfei
diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/include/head.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/include/head.jsp index 30670c4f..fc829110 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/include/head.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/include/head.jsp @@ -1,23 +1,23 @@ -<%@ page language="java" contentType="text/html; charset=utf-8"%> +<%@ page language="java" contentType="text/html; charset=utf-8" %> -JSP指令include范例 - + JSP指令include范例 + - - - - - - - - - - - - - -
JSP指令include范例
首页资源文档下载关于邮件社区
\ No newline at end of file + + + + + + + + + + + + + +
JSP指令include范例
首页资源文档下载关于邮件社区
diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/include/include.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/include/include.jsp index 0169c331..743daec0 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/include/include.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/include/include.jsp @@ -1,8 +1,15 @@ -<%@ page language="java" contentType="text/html; charset=utf-8"%> +<%@ page language="java" contentType="text/html; charset=utf-8" %> <%@ include file="head.jsp" %>

-

如今且说林黛玉自在荣府以来,贾母万般怜爱,寝食起居,一如宝玉,迎春,探春, 惜春三个亲孙女倒且靠后, 便是宝玉和黛玉二人之亲密友爱处,亦自较别个不同,日则同行同坐,夜则同息同止,真是言和意顺,略无参商.不想如今忽然来了一个薛宝钗 ,年岁虽大不多,然品格端方,容貌丰美,人多谓黛玉所不及.而且宝钗行为豁达,随分从时, 不比黛玉孤高自许,目无下尘,故比黛玉大得下人之心.便是那些小丫头子们, 亦多喜与宝钗去顽. 因此黛玉心中便有些悒郁不忿之意,宝钗却浑然不觉.那宝玉亦在孩提之间, 况自天性所禀来的一片愚拙偏僻,视姊妹弟兄皆出一意,并无亲疏远近之别.其中因与黛玉同随贾母一处坐卧,故略比别个姊妹熟惯些.既熟惯,则更觉亲密 , 既亲密,则不免一时有求全之毁,不虞之隙.这日不知为何,他二人言语有些不合起来,黛玉又气的独在房中垂泪,宝玉又自悔言语冒撞,前去俯就,那黛玉方渐渐的回转来. 因东边宁府中花园内梅花盛开,贾珍之妻尤氏乃治酒,请贾母,邢夫人,王夫人等赏花. 是日先携了贾蓉之妻,二人来面请.贾母等于早饭后过来,就在会芳园游顽,先茶后酒,不过皆是宁荣二府女眷家宴小集,并无别样新文趣事可记. -

一时宝玉倦怠,欲睡中觉,贾母命人好生哄着,歇一回再来.贾蓉之妻秦氏便忙笑回道:"我们这里有给宝叔收拾下的屋子,老祖宗放心,只管交与我就是了."又向宝玉的奶娘丫鬟等道:"嬷嬷,姐姐们,请宝叔随我这里来."贾母素知秦氏是个极妥当的人 , 生的袅娜纤巧,行事又温柔和平,乃重孙媳中第一个得意之人,见他去安置宝玉,自是安稳的. +

如今且说林黛玉自在荣府以来,贾母万般怜爱,寝食起居,一如宝玉,迎春,探春, 惜春三个亲孙女倒且靠后, + 便是宝玉和黛玉二人之亲密友爱处,亦自较别个不同,日则同行同坐,夜则同息同止,真是言和意顺,略无参商.不想如今忽然来了一个薛宝钗 ,年岁虽大不多,然品格端方,容貌丰美,人多谓黛玉所不及.而且宝钗行为豁达,随分从时, + 不比黛玉孤高自许,目无下尘,故比黛玉大得下人之心.便是那些小丫头子们, 亦多喜与宝钗去顽. 因此黛玉心中便有些悒郁不忿之意,宝钗却浑然不觉.那宝玉亦在孩提之间, + 况自天性所禀来的一片愚拙偏僻,视姊妹弟兄皆出一意,并无亲疏远近之别.其中因与黛玉同随贾母一处坐卧,故略比别个姊妹熟惯些.既熟惯,则更觉亲密 , + 既亲密,则不免一时有求全之毁,不虞之隙.这日不知为何,他二人言语有些不合起来,黛玉又气的独在房中垂泪,宝玉又自悔言语冒撞,前去俯就,那黛玉方渐渐的回转来. 因东边宁府中花园内梅花盛开,贾珍之妻尤氏乃治酒,请贾母,邢夫人,王夫人等赏花. + 是日先携了贾蓉之妻,二人来面请.贾母等于早饭后过来,就在会芳园游顽,先茶后酒,不过皆是宁荣二府女眷家宴小集,并无别样新文趣事可记.

-<%@ include file="foot.jsp" %> \ No newline at end of file + 一时宝玉倦怠,欲睡中觉,贾母命人好生哄着,歇一回再来.贾蓉之妻秦氏便忙笑回道:"我们这里有给宝叔收拾下的屋子,老祖宗放心,只管交与我就是了."又向宝玉的奶娘丫鬟等道:"嬷嬷,姐姐们,请宝叔随我这里来."贾母素知秦氏是个极妥当的人 , + 生的袅娜纤巧,行事又温柔和平,乃重孙媳中第一个得意之人,见他去安置宝玉,自是安稳的. +

+<%@ include file="foot.jsp" %> diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/page/contentType.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/page/contentType.jsp index 2e888d21..2dc4cc51 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/page/contentType.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/page/contentType.jsp @@ -1,12 +1,12 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> -JSP指令page-属性contentType + JSP指令page-属性contentType -

你好啊

-

contentType="text/html; charset=UTF-8"

+

你好啊

+

contentType="text/html; charset=UTF-8"

- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/page/errorPage.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/page/errorPage.jsp index 509bfe41..a4dfa89a 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/page/errorPage.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/page/errorPage.jsp @@ -1,13 +1,13 @@ -<%@ page language="java" errorPage="isErrorPage.jsp" pageEncoding="UTF-8"%> +<%@ page language="java" errorPage="isErrorPage.jsp" pageEncoding="UTF-8" %> -JSP指令page-属性errorPage + JSP指令page-属性errorPage - <% - //这行代码肯定会出错,因为除数是0,一运行就会抛出异常 - int x = 1 / 0; - %> +<% + //这行代码肯定会出错,因为除数是0,一运行就会抛出异常 + int x = 1 / 0; +%> - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/page/isErrorPage.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/page/isErrorPage.jsp index f4f884aa..55f76a2b 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/page/isErrorPage.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/page/isErrorPage.jsp @@ -1,12 +1,12 @@ -<%@ page language="java" pageEncoding="UTF-8" isErrorPage="true"%> +<%@ page language="java" pageEncoding="UTF-8" isErrorPage="true" %> -JSP + JSP - <% - out.println("程序抛出了一个异常:" + exception); - %> +<% + out.println("程序抛出了一个异常:" + exception); +%> - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/el.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/el.jsp index 9cb21fc4..66bd6eee 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/el.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/el.jsp @@ -1,26 +1,26 @@ -<%@ page language="java" pageEncoding="UTF-8" %> +<%@ page language="java" pageEncoding="UTF-8" %> <%! - String ss1 = "ss String"; + String ss1 = "ss String"; %> <% %> - - My JSP 'el.jsp' starting page - - - - - - - + + My JSP 'el.jsp' starting page + + + + + + + - + - ${not (1==2) } +${not (1==2) } - + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/error.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/error.jsp index 59893f09..1b0f9de6 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/error.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/error.jsp @@ -1,11 +1,11 @@ -<%@ page language="java" contentType="text/html; charset=utf-8" isErrorPage="true"%> +<%@ page language="java" contentType="text/html; charset=utf-8" isErrorPage="true" %> -JSP + JSP - <% - out.println("程序抛出了一个异常:" + exception); - %> +<% + out.println("程序抛出了一个异常:" + exception); +%> diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/exception.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/exception.jsp index 4abf68c2..1cdb1f5d 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/exception.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/exception.jsp @@ -1,9 +1,9 @@ <%@ page language="java" contentType="text/html; charset=utf-8" errorPage="/error.jsp" %> <% - out.clear(); - String str = null; - // length() 操作会抛出 NullPointerException - int length = str.length(); + out.clear(); + String str = null; + // length() 操作会抛出 NullPointerException + int length = str.length(); %> JSP diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/grammar/for.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/grammar/for.jsp index c733f75d..5b06acd4 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/grammar/for.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/grammar/for.jsp @@ -1,52 +1,53 @@ -<%@ page language="java" contentType="text/html; charset=utf-8"%> +<%@ page language="java" contentType="text/html; charset=utf-8" %> JSP Scriptlets - +
<% - Object[][] letters = { - {true, "恭喜您注册的信息已经生效", "e_inn@163.com", "helloweenvsfei@gmail.com", "2007-8-8"}, - {true, "Java EE 5.0 release!!", "admin@sun.com", "helloweenvsfei@gmail.com", "2007-6-24"}, - {false, "来信已经收到,下周末见面商谈", "foo@bar.com", "helloweenvsfei@gmail.com", "2007-5-20"}, - {false, "您的博客有新的留言", "blog@foo.bar.com", "helloweenvsfei@gmail.com", "2007-3-2"}, - }; - String[] colors = {"#DDDDDD", "#AAAAAA", }; + Object[][] letters = {{true, "恭喜您注册的信息已经生效", "e_inn@163.com", "helloweenvsfei@gmail.com", "2007-8-8"}, + {true, "Java EE 5.0 release!!", "admin@sun.com", "helloweenvsfei@gmail.com", "2007-6-24"}, + {false, "来信已经收到,下周末见面商谈", "foo@bar.com", "helloweenvsfei@gmail.com", "2007-5-20"}, + {false, "您的博客有新的留言", "blog@foo.bar.com", "helloweenvsfei@gmail.com", "2007-3-2"},}; + String[] colors = {"#DDDDDD", "#AAAAAA",}; %> - - - - - - - - -<% - for(int i=0; i - - - - - - - -<% - } -%> -
 标题 发信人 收信人 时间 
- <% - if(letter[0] == Boolean.TRUE){ - %> - - <% - } - else{ - out.println(" "); - } - %> - <%= letter[1] %><%= letter[2] %><%= letter[3] %><%= letter[4] %>
+ + + + + + + + + <% + for (int i = 0; i < letters.length; i++) { + Object[] letter = letters[i]; + %> + + + + + + + + <% + } + %> +
 标题 发信人 收信人 时间 
+ <% + if (letter[0] == Boolean.TRUE) { + %> + + <% + } else { + out.println(" "); + } + %> + <%= letter[1] %> + <%= letter[2] %> + <%= letter[3] %> + <%= letter[4] %> +
diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/grammar/if.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/grammar/if.jsp index 05140e7b..70851000 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/grammar/if.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/grammar/if.jsp @@ -1,12 +1,12 @@ -<%@ page language="java" contentType="text/html; charset=utf-8"%> +<%@ page language="java" contentType="text/html; charset=utf-8" %> JSP Scriptlets <% - String param = request.getParameter("param"); - - if("1".equals(param)){ + String param = request.getParameter("param"); + + if ("1".equals(param)) { %> 关雎·周南·诗经
关关雎鸠,在河之洲。窈窕淑女,君子好逑。
@@ -14,10 +14,9 @@ 求之不得,寤寐思服。悠哉悠哉,辗转反侧。
参差荇菜,左右采之。窈窕淑女,琴瑟友之。
参差荇菜,左右芼之。窈窕淑女,钟鼓乐之。
-<% - } - else if("2".equals(param)){ -%> +<% +} else if ("2".equals(param)) { +%> 蒹葭·秦风·诗经
蒹葭苍苍,白露为霜。所谓伊人,在水一方。
溯洄从之,道阻且长。溯游从之,宛在水中央。
@@ -25,9 +24,8 @@ 溯洄从之,道阻且跻。溯游从之,宛在水中坻。
蒹葭采采,白露未已。所谓伊人,在水之涘。
溯洄从之,道阻且右。溯游从之,宛在水中沚。
-<% - } - else if("3".equals(param)){ +<% +} else if ("3".equals(param)) { %> 子衿·国风·郑风
青青子衿,悠悠我心。
@@ -36,16 +34,15 @@ 纵我不往,子宁不来?
挑兮达兮,在城阙兮。
一日不见,如三月兮。
-<% - } - else{ +<% +} else { %> 请使用参数 param=1, 2, 3 选择要显示的诗歌

if.jsp?param=1
if.jsp?param=2
if.jsp?param=3
-<% - } +<% + } %> diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/grammar/while.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/grammar/while.jsp index 5b93d0db..7436b4bc 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/grammar/while.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/grammar/while.jsp @@ -1,23 +1,23 @@ -<%@ page language="java" contentType="text/html; charset=utf-8"%> +<%@ page language="java" contentType="text/html; charset=utf-8" %> JSP Scriptlets <% - java.util.List list = new java.util.ArrayList(); - - list.add("茕茕白兔"); - list.add("东走西顾"); - list.add("衣不如新"); - list.add("人不如故"); - - java.util.Iterator it = list.iterator(); - - while(it.hasNext()){ -%> <%= it.next() %>
+ java.util.List list = new java.util.ArrayList(); + + list.add("茕茕白兔"); + list.add("东走西顾"); + list.add("衣不如新"); + list.add("人不如故"); + + java.util.Iterator it = list.iterator(); + + while (it.hasNext()) { +%> <%= it.next() %>
<% - } - + } + %> diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/greeting.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/greeting.jsp index 5703e327..4669db23 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/greeting.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/greeting.jsp @@ -1,42 +1,44 @@ -<%@ page language="java" import="java.util.*" contentType="text/html; charset=utf-8"%> +<%@ page language="java" import="java.util.Calendar" contentType="text/html; charset=utf-8" %> +<%@ page import="java.util.Locale" %> <% - Locale locale = request.getLocale(); + Locale locale = request.getLocale(); - Calendar calendar = Calendar.getInstance(locale); + Calendar calendar = Calendar.getInstance(locale); - int hour = calendar.get(Calendar.HOUR_OF_DAY); + int hour = calendar.get(Calendar.HOUR_OF_DAY); - String greeting = ""; + String greeting = ""; - if (hour <= 6) { - greeting = "凌晨好,您该睡觉了。良好的睡眠是美好一天的开始。"; - } else if (hour <= 9) { - greeting = "早上好。早餐应该注意营养。"; - } else if (hour <= 12) { - greeting = "上午好。工作时注意保护眼睛。"; - } else if (hour <= 18) { - greeting = "下午好。小心工作中打瞌睡。"; - } else if (hour <= 24) { - greeting = "晚上好。放松一下自己,好好休息。睡觉不要太晚啊~~"; - } else { + if (hour <= 6) { + greeting = "凌晨好,您该睡觉了。良好的睡眠是美好一天的开始。"; + } else if (hour <= 9) { + greeting = "早上好。早餐应该注意营养。"; + } else if (hour <= 12) { + greeting = "上午好。工作时注意保护眼睛。"; + } else if (hour <= 18) { + greeting = "下午好。小心工作中打瞌睡。"; + } else if (hour <= 24) { + greeting = "晚上好。放松一下自己,好好休息。睡觉不要太晚啊~~"; + } else { - } + } %> - 欢迎页面 - - - - - + 欢迎页面 + + + + + - - - - -
<%= greeting %>
+ + + + +
<%= greeting %> +
diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/index.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/index.jsp index 7dd27143..0ce44df2 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/index.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/index.jsp @@ -1,22 +1,22 @@ -<%@ page language="java" import="java.util.*" pageEncoding="GB18030" %> +<%@ page language="java" pageEncoding="GB18030" %> <% - String path = request.getContextPath(); - String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; + String path = request.getContextPath(); + String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; %> - - javaee-jsp ҳ - - - - - - + + javaee-jsp ҳ + + + + + + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/life.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/life.jsp index 6ddc9416..35457789 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/life.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/life.jsp @@ -6,10 +6,10 @@ - -Insert title here + + Insert title here - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/method.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/method.jsp index 98d7415c..25887b58 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/method.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/method.jsp @@ -1,81 +1,83 @@ -<%@ page language="java" contentType="text/html; charset=utf-8"%> +<%@ page language="java" contentType="text/html; charset=utf-8" %> <%@ page import="com.helloweenvsfei.util.ip.IPSeeker" %> <%! - // 全局变量 - private IPSeeker ipSeeker = IPSeeker.getInstance(); - - // 方法一 - public String getArea(String ip){ - return ipSeeker.getArea(ip); - } - - //方法二 - public String getCountry(String ip){ - return ipSeeker.getCountry(ip); - } - - // 方法三 正则表达式判断是否合法 IP 地址 - public boolean isValidIp(String ip){ - return ip!=null - && ip.trim().matches("^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$"); - } + // 全局变量 + private IPSeeker ipSeeker = IPSeeker.getInstance(); + + // 方法一 + public String getArea(String ip) { + return ipSeeker.getArea(ip); + } + + //方法二 + public String getCountry(String ip) { + return ipSeeker.getCountry(ip); + } + + // 方法三 正则表达式判断是否合法 IP 地址 + public boolean isValidIp(String ip) { + return ip != null && ip.trim().matches("^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$"); + } %> -IP 地址查询 - + IP 地址查询 +
<% - String ip = request.getParameter("ip"); - String area = ""; - String country = ""; - - // 如果是合法的 IP 地址 - if(isValidIp(ip)){ - // 调用方法一 - country = getCountry(ip); - // 调用方法二 - area = getArea(ip); - } - + String ip = request.getParameter("ip"); + String area = ""; + String country = ""; + + // 如果是合法的 IP 地址 + if (isValidIp(ip)) { + // 调用方法一 + country = getCountry(ip); + // 调用方法二 + area = getArea(ip); + } + %>
-
-
- IP 地址查询 - - <% - if( isValidIp(ip) ){ - %> - - - - - - - - - - - - - <% - } - %> - - - - - - - - -
IP 地址:<%= ip %>
国家:<%= country %>
地区:<%= area %>
请输入要查询的 IP 地址:
- -
-
-
+
+
+ IP 地址查询 + + <% + if (isValidIp(ip)) { + %> + + + + + + + + + + + + + <% + } + %> + + + + + + + + +
IP 地址:<%= ip %> +
国家:<%= country %> +
地区:<%= area %> +
请输入要查询的 IP 地址:
+ +
+
+
diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/plugin.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/plugin.jsp index 6916eb6e..05320036 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/plugin.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/plugin.jsp @@ -1,27 +1,32 @@ <%@ page language="java" contentType="text/html; charset=utf-8" %> -plugin - + plugin +
- + +
+ + +
- - - - - - 您的浏览器不支持 Java Applet - + + + + + + 您的浏览器不支持 Java Applet + -
+
diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/return.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/return.jsp index 381d415e..2e0df96b 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/return.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/return.jsp @@ -1,19 +1,19 @@ -<%@ page language="java" contentType="text/html; charset=utf-8"%> +<%@ page language="java" contentType="text/html; charset=utf-8" %> JSP Scriptlets <% - String param = request.getParameter("param"); + String param = request.getParameter("param"); %> 昔我往矣,
杨柳依依。
今我来思,
雨雪霏霏。
<% - if("return".equals(param)){ - return; - } + if ("return".equals(param)) { + return; + } %> 青青子衿,
悠悠我心,
diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/scriptlet.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/scriptlet.jsp index 749b6006..cffbf3b1 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/scriptlet.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/scriptlet.jsp @@ -1,17 +1,17 @@ -<%@ page language="java" contentType="text/html; charset=utf-8"%> +<%@ page language="java" contentType="text/html; charset=utf-8" %> JSP Scriptlets <% - int num = 10; - int result = 1; - for(int i=1; i<=num; i++){ - result *= i; - out.println("第" + i + "步运算:" + result + "
"); - } - out.println("
"); - out.println("数字 " + num + " 的阶乘为: " + result); + int num = 10; + int result = 1; + for (int i = 1; i <= num; i++) { + result *= i; + out.println("第" + i + "步运算:" + result + "
"); + } + out.println("
"); + out.println("数字 " + num + " 的阶乘为: " + result); %> diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/taglib.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/taglib.jsp index 9d05cc47..6a09ea9f 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/taglib.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/taglib.jsp @@ -3,12 +3,12 @@ <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -My JSP 'taglib.jsp' starting page + My JSP 'taglib.jsp' starting page - - - + + + diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java b/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java index 5aa9d247..f4a1a2e9 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java @@ -16,93 +16,95 @@ */ @SuppressWarnings("unused") public class JettyFactory { - private static final int PORT = 9798; - private static final String CONTEXT = "/"; - private static final String RESOURCE_BASE_PATH = "src/main/webapp"; - private static final String WEB_XML_PATH = "/WEB-INF/web.xml"; - private static final String[] TLD_JAR_NAMES = - new String[]{"sitemesh", "spring-webmvc", "shiro-web", "tiles"}; - private static final String WINDOWS_WEBDEFAULT_PATH = "jetty/webdefault.xml"; - public static final int IDE_ECLIPSE = 0; - public static final int IDE_INTELLIJ = 1; + public static final int IDE_ECLIPSE = 0; + public static final int IDE_INTELLIJ = 1; + private static final int PORT = 9798; + private static final String CONTEXT = "/"; + private static final String RESOURCE_BASE_PATH = "src/main/webapp"; + private static final String WEB_XML_PATH = "/WEB-INF/web.xml"; + private static final String[] TLD_JAR_NAMES = new String[] { "sitemesh", "spring-webmvc", "shiro-web", "tiles" }; + private static final String WINDOWS_WEBDEFAULT_PATH = "jetty/webdefault.xml"; + + public static Server initServer() { + Profiles.setProfileAsSystemProperty(Profiles.DEVELOPMENT); + WebAppContext webAppContext = new WebAppContext(); + Server server = new Server(PORT); + server.setHandler(webAppContext); + return server; + } + + public static void initWebAppContext(Server server, int type) throws Exception { + System.out.println("[INFO] Application loading"); + WebAppContext webAppContext = (WebAppContext) server.getHandler(); + webAppContext.setContextPath(CONTEXT); + webAppContext.setResourceBase(getAbsolutePath() + RESOURCE_BASE_PATH); + webAppContext.setDescriptor(getAbsolutePath() + RESOURCE_BASE_PATH + WEB_XML_PATH); + + if (IDE_INTELLIJ == type) { + webAppContext.setDefaultsDescriptor(WINDOWS_WEBDEFAULT_PATH); + supportJspAndSetTldJarNames(server, TLD_JAR_NAMES); + } + else { + webAppContext.setParentLoaderPriority(true); + } + + System.out.println("[INFO] Application loaded"); + } + + public static void reloadWebAppContext(Server server) throws Exception { + WebAppContext webAppContext = (WebAppContext) server.getHandler(); + System.out.println("[INFO] Application reloading"); + webAppContext.stop(); + WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext); + classLoader.addClassPath(getAbsolutePath() + "target/classes"); + classLoader.addClassPath(getAbsolutePath() + "target/test-classes"); + webAppContext.setClassLoader(classLoader); + webAppContext.start(); + System.out.println("[INFO] Application reloaded"); + } + + public static void startServer(Server server) throws Exception { + System.out.println("[HINT] Don't forget to set -XX:MaxPermSize=128m"); + server.start(); + System.out.println("Server running at http://localhost:" + PORT + CONTEXT); + System.out.println("[HINT] Hit Enter to reload the application quickly"); + } + + public static String getAbsolutePath() { + String path = null; + String folderPath = JettyFactory.class.getProtectionDomain().getCodeSource().getLocation().getPath() + .substring(1); + if (folderPath.indexOf("target") > 0) { + path = folderPath.substring(0, folderPath.indexOf("target")); + } + return path; + } + + public static void supportJspAndSetTldJarNames(Server server, String... jarNames) { + WebAppContext context = (WebAppContext) server.getHandler(); + // This webapp will use jsps and jstl. We need to enable the + // AnnotationConfiguration in + // order to correctly set up the jsp container + org.eclipse.jetty.webapp.Configuration.ClassList classlist = org.eclipse.jetty.webapp.Configuration.ClassList + .setServerDefault(server); + classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", + "org.eclipse.jetty.annotations.AnnotationConfiguration"); + // Set the ContainerIncludeJarPattern so that jetty examines these container-path + // jars for + // tlds, web-fragments etc. + // If you omit the jar that contains the jstl .tlds, the jsp engine will scan for + // them + // instead. + ArrayList jarNameExprssions = Lists.newArrayList(".*/[^/]*servlet-api-[^/]*\\.jar$", + ".*/javax.servlet.jsp.jstl-.*\\.jar$", ".*/[^/]*taglibs.*\\.jar$"); + + for (String jarName : jarNames) { + jarNameExprssions.add(".*/" + jarName + "-[^/]*\\.jar$"); + } + + context.setAttribute("org.eclipse.jetty.io.github.dunwu.javaee.server.webapp.ContainerIncludeJarPattern", + StringUtils.join(jarNameExprssions, '|')); + } - - public static Server initServer() { - Profiles.setProfileAsSystemProperty(Profiles.DEVELOPMENT); - WebAppContext webAppContext = new WebAppContext(); - Server server = new Server(PORT); - server.setHandler(webAppContext); - return server; - } - - public static void initWebAppContext(Server server, int type) throws Exception { - System.out.println("[INFO] Application loading"); - WebAppContext webAppContext = (WebAppContext) server.getHandler(); - webAppContext.setContextPath(CONTEXT); - webAppContext.setResourceBase(getAbsolutePath() + RESOURCE_BASE_PATH); - webAppContext.setDescriptor(getAbsolutePath() + RESOURCE_BASE_PATH + WEB_XML_PATH); - - if (IDE_INTELLIJ == type) { - webAppContext.setDefaultsDescriptor(WINDOWS_WEBDEFAULT_PATH); - supportJspAndSetTldJarNames(server, TLD_JAR_NAMES); - } else { - webAppContext.setParentLoaderPriority(true); - } - - System.out.println("[INFO] Application loaded"); - } - - public static void reloadWebAppContext(Server server) throws Exception { - WebAppContext webAppContext = (WebAppContext) server.getHandler(); - System.out.println("[INFO] Application reloading"); - webAppContext.stop(); - WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext); - classLoader.addClassPath(getAbsolutePath() + "target/classes"); - classLoader.addClassPath(getAbsolutePath() + "target/test-classes"); - webAppContext.setClassLoader(classLoader); - webAppContext.start(); - System.out.println("[INFO] Application reloaded"); - } - - - public static void startServer(Server server) throws Exception { - System.out.println("[HINT] Don't forget to set -XX:MaxPermSize=128m"); - server.start(); - System.out.println("Server running at http://localhost:" + PORT + CONTEXT); - System.out.println("[HINT] Hit Enter to reload the application quickly"); - } - - public static String getAbsolutePath() { - String path = null; - String folderPath = JettyFactory.class.getProtectionDomain().getCodeSource().getLocation() - .getPath().substring(1); - if (folderPath.indexOf("target") > 0) { - path = folderPath.substring(0, folderPath.indexOf("target")); - } - return path; - } - - public static void supportJspAndSetTldJarNames(Server server, String... jarNames) { - WebAppContext context = (WebAppContext) server.getHandler(); - // This webapp will use jsps and jstl. We need to enable the AnnotationConfiguration in - // order to correctly set up the jsp container - org.eclipse.jetty.webapp.Configuration.ClassList classlist = - org.eclipse.jetty.webapp.Configuration.ClassList.setServerDefault(server); - classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", - "org.eclipse.jetty.annotations.AnnotationConfiguration"); - // Set the ContainerIncludeJarPattern so that jetty examines these container-path jars for - // tlds, web-fragments etc. - // If you omit the jar that contains the jstl .tlds, the jsp engine will scan for them - // instead. - ArrayList jarNameExprssions = Lists.newArrayList(".*/[^/]*servlet-api-[^/]*\\.jar$", - ".*/javax.servlet.jsp.jstl-.*\\.jar$", ".*/[^/]*taglibs.*\\.jar$"); - - for (String jarName : jarNames) { - jarNameExprssions.add(".*/" + jarName + "-[^/]*\\.jar$"); - } - - context.setAttribute("org.eclipse.jetty.io.github.dunwu.javaee.server.webapp.ContainerIncludeJarPattern", - StringUtils.join(jarNameExprssions, '|')); - } } diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/Profiles.java b/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/Profiles.java index 3a8ee7c4..dcfd3e39 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/Profiles.java +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/Profiles.java @@ -13,11 +13,15 @@ public class Profiles { public static final String ACTIVE_PROFILE = "spring.profiles.active"; + public static final String DEFAULT_PROFILE = "spring.profiles.default"; public static final String PRODUCTION = "production"; + public static final String DEVELOPMENT = "development"; + public static final String UNIT_TEST = "test"; + public static final String FUNCTIONAL_TEST = "functional"; /** @@ -26,4 +30,5 @@ public class Profiles { public static void setProfileAsSystemProperty(String profile) { System.setProperty(ACTIVE_PROFILE, profile); } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java b/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java index dd0ac658..c09f0fc1 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java +++ b/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java @@ -8,26 +8,29 @@ * @author Zhang Peng */ public class QuickStartServer { - // private static int STARTUP_TYPE = JettyFactory.IDE_ECLIPSE; - private static int STARTUP_TYPE = JettyFactory.IDE_INTELLIJ; - public static void main(String[] args) throws Exception { - Server server = JettyFactory.initServer(); - JettyFactory.initWebAppContext(server, STARTUP_TYPE); + // private static int STARTUP_TYPE = JettyFactory.IDE_ECLIPSE; + private static int STARTUP_TYPE = JettyFactory.IDE_INTELLIJ; - try { - JettyFactory.startServer(server); + public static void main(String[] args) throws Exception { + Server server = JettyFactory.initServer(); + JettyFactory.initWebAppContext(server, STARTUP_TYPE); + + try { + JettyFactory.startServer(server); + + // 等待用户输入回车重载应用 + while (true) { + char c = (char) System.in.read(); + if (c == '\n') { + JettyFactory.reloadWebAppContext(server); + } + } + } + catch (Exception e) { + e.printStackTrace(); + System.exit(-1); + } + } - // 等待用户输入回车重载应用 - while (true) { - char c = (char) System.in.read(); - if (c == '\n') { - JettyFactory.reloadWebAppContext(server); - } - } - } catch (Exception e) { - e.printStackTrace(); - System.exit(-1); - } - } } diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/pom.xml b/codes/javaee-tutorial/javaee-tutorial-jstl/pom.xml index 9e825bad..99579528 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/pom.xml +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/pom.xml @@ -1,6 +1,6 @@ - + 4.0.0 diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/bean/Person.java b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/bean/Person.java index d7b16ba4..36943d29 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/bean/Person.java +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/bean/Person.java @@ -2,105 +2,105 @@ public class Person { - private int id; + private int id; - private String name; + private String name; - private String sex; + private String sex; - private int age; + private int age; - private String telephone; + private String telephone; - private String birthday; + private String birthday; - private String mobile; + private String mobile; - private String address; + private String address; - private String city; + private String city; - private boolean deleted; + private boolean deleted; - public int getId() { - return id; - } + public int getId() { + return id; + } - public void setId(int id) { - this.id = id; - } + public void setId(int id) { + this.id = id; + } - public String getName() { - return name; - } + public String getName() { + return name; + } - public void setName(String name) { - this.name = name; - } + public void setName(String name) { + this.name = name; + } - public String getSex() { - return sex; - } + public String getSex() { + return sex; + } - public void setSex(String sex) { - this.sex = sex; - } + public void setSex(String sex) { + this.sex = sex; + } - public int getAge() { - return age; - } + public int getAge() { + return age; + } - public void setAge(int age) { - this.age = age; - } + public void setAge(int age) { + this.age = age; + } - public boolean isDeleted() { - return deleted; - } + public boolean isDeleted() { + return deleted; + } - public void setDeleted(boolean deleted) { - this.deleted = deleted; - } + public void setDeleted(boolean deleted) { + this.deleted = deleted; + } - public String getTelephone() { - return telephone; - } + public String getTelephone() { + return telephone; + } - public void setTelephone(String telephone) { - this.telephone = telephone; - } + public void setTelephone(String telephone) { + this.telephone = telephone; + } - public String getBirthday() { - return birthday; - } + public String getBirthday() { + return birthday; + } - public void setBirthday(String birthday) { - this.birthday = birthday; - } + public void setBirthday(String birthday) { + this.birthday = birthday; + } - public String getMobile() { - return mobile; - } + public String getMobile() { + return mobile; + } - public void setMobile(String mobile) { - this.mobile = mobile; - } + public void setMobile(String mobile) { + this.mobile = mobile; + } - public String getAddress() { - return address; - } + public String getAddress() { + return address; + } - public void setAddress(String address) { - this.address = address; - } + public void setAddress(String address) { + this.address = address; + } - public String getCity() { - return city; - } + public String getCity() { + return city; + } - public void setCity(String city) { - this.city = city; - } + public void setCity(String city) { + this.city = city; + } } diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/util/Example.java b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/util/Example.java index 87758b27..daa6a76e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/util/Example.java +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/util/Example.java @@ -3,10 +3,11 @@ import java.util.ListResourceBundle; public class Example extends ListResourceBundle { - public Object[][] getContents() { - return contents; - } - static final Object[][] contents = - {{"count.one", "一"}, {"count.two", "二"}, {"count.three", "三"},}; + static final Object[][] contents = { { "count.one", "一" }, { "count.two", "二" }, { "count.three", "三" }, }; + + public Object[][] getContents() { + return contents; + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/util/Example_es_ES.java b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/util/Example_es_ES.java index 48f2366e..95e191c2 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/util/Example_es_ES.java +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/util/Example_es_ES.java @@ -3,10 +3,11 @@ import java.util.ListResourceBundle; public class Example_es_ES extends ListResourceBundle { - public Object[][] getContents() { - return contents; - } - static final Object[][] contents = - {{"count.one", "one"}, {"count.two", "two"}, {"count.three", "three"},}; + static final Object[][] contents = { { "count.one", "one" }, { "count.two", "two" }, { "count.three", "three" }, }; + + public Object[][] getContents() { + return contents; + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/util/Pagination.java b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/util/Pagination.java index 29561325..67a3080e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/util/Pagination.java +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/util/Pagination.java @@ -1,208 +1,208 @@ package io.github.dunwu.javaee.util; -import java.net.URLEncoder; - import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.net.URLEncoder; public class Pagination { - private int pageSize = 20; - private int pageNum = 1; + private int pageSize = 20; + + private int pageNum = 1; + + private int recordCount; - private int recordCount; + private int pageCount; - private int pageCount; + private int firstResult; - private int firstResult; + private String pageUrl; - private String pageUrl; + public Pagination(HttpServletRequest request, HttpServletResponse response) { + try { + pageNum = Integer.parseInt(request.getParameter("pageNum")); + } + catch (Exception e) { + } - public Pagination(HttpServletRequest request, HttpServletResponse response) { - try { - pageNum = Integer.parseInt(request.getParameter("pageNum")); - } catch (Exception e) {} + for (Cookie cookie : request.getCookies()) { + if ("pageSize".equals(cookie.getName())) { + try { + pageSize = Integer.parseInt(cookie.getValue()); + } + catch (Exception e) { + } + } + } - for (Cookie cookie : request.getCookies()) { - if ("pageSize".equals(cookie.getName())) { - try { - pageSize = Integer.parseInt(cookie.getValue()); - } catch (Exception e) {} - } - } + try { + pageSize = Integer.parseInt(request.getParameter("pageSize")); + } + catch (Exception e) { + } - try { - pageSize = Integer.parseInt(request.getParameter("pageSize")); - } catch (Exception e) {} + Cookie cookie = new Cookie("pageSize", Integer.toString(pageSize)); + cookie.setMaxAge(Integer.MAX_VALUE); - Cookie cookie = new Cookie("pageSize", Integer.toString(pageSize)); - cookie.setMaxAge(Integer.MAX_VALUE); + response.addCookie(cookie); - response.addCookie(cookie); + StringBuffer queryString = new StringBuffer(); - StringBuffer queryString = new StringBuffer(); + for (Object parameterName : request.getParameterMap().keySet()) { + String name = (String) parameterName; - for (Object parameterName : request.getParameterMap().keySet()) { - String name = (String) parameterName; + if ("pageNum".equals(name) || "pageSize".equals(name)) { + continue; + } - if ("pageNum".equals(name) || "pageSize".equals(name)) { - continue; - } + for (String value : request.getParameterValues(name)) { + if (queryString.length() > 0) { + queryString.append("&"); + } - for (String value : request.getParameterValues(name)) { - if (queryString.length() > 0) { - queryString.append("&"); - } - - try { - queryString.append(name + "=" + URLEncoder.encode(value, "UTF-8")); - } catch (Exception e) { - queryString.append(name + "=" + value); - } - } - } + try { + queryString.append(name + "=" + URLEncoder.encode(value, "UTF-8")); + } + catch (Exception e) { + queryString.append(name + "=" + value); + } + } + } - pageUrl = request.getRequestURI() + "?" + queryString.toString(); - } + pageUrl = request.getRequestURI() + "?" + queryString.toString(); + } - private void calculate() { - pageCount = (recordCount + pageSize - 1) / pageSize; + private void calculate() { + pageCount = (recordCount + pageSize - 1) / pageSize; - firstResult = (pageNum - 1) * pageSize; - } + firstResult = (pageNum - 1) * pageSize; + } - /** - * 生成分页信息 包括第一页,上一页,下一页,最后一页等等。 - */ - public String toString() { - calculate(); + /** + * 生成分页信息 包括第一页,上一页,下一页,最后一页等等。 + */ + public String toString() { + calculate(); - String url = pageUrl.contains("?") ? pageUrl : pageUrl + "?"; + String url = pageUrl.contains("?") ? pageUrl : pageUrl + "?"; - StringBuffer buffer = new StringBuffer(); + StringBuffer buffer = new StringBuffer(); - buffer.append("每页 "); + buffer.append("每页 "); - buffer.append(" 条记录 "); + buffer.append(" 条记录 "); - buffer.append(" 总记录数: " + recordCount); + buffer.append(" 总记录数: " + recordCount); - buffer.append(" 页数/总页数: " + pageNum + "/" + pageCount + " "); + buffer.append(" 页数/总页数: " + pageNum + "/" + pageCount + " "); - buffer.append(" "); + buffer.append(" "); - buffer.append(pageCount == 0 || pageNum == 1 - ? " 第一页 " - : " 第一页 "); + buffer.append(pageCount == 0 || pageNum == 1 ? " 第一页 " : " 第一页 "); - buffer.append("   "); + buffer.append("   "); - buffer.append(pageCount == 0 || pageNum == 1 - ? " 上一页 " - : " 上一页 "); + buffer.append(pageCount == 0 || pageNum == 1 ? " 上一页 " + : " 上一页 "); - buffer.append("   "); + buffer.append("   "); - buffer.append(pageCount == 0 || pageNum == pageCount - ? " 下一页 " - : " 下一页 "); + buffer.append(pageCount == 0 || pageNum == pageCount ? " 下一页 " + : " 下一页 "); - buffer.append("   "); + buffer.append("   "); - buffer.append(pageCount == 0 || pageNum == pageCount - ? " 最后一页 " - : " 最后一页 "); + buffer.append(pageCount == 0 || pageNum == pageCount ? " 最后一页 " + : " 最后一页 "); - buffer.append("   转到第页 "); + buffer.append("   转到第页 "); - buffer.append(" "); + buffer.append(" "); - buffer.append(""); + buffer.append(""); - return buffer.toString(); + return buffer.toString(); - } + } - public int getPageSize() { - calculate(); + public int getPageSize() { + calculate(); - return pageSize; - } + return pageSize; + } - public void setPageSize(int pageSize) { - calculate(); + public void setPageSize(int pageSize) { + calculate(); - this.pageSize = pageSize; - } + this.pageSize = pageSize; + } - public int getRecordCount() { - calculate(); + public int getRecordCount() { + calculate(); - return recordCount; - } + return recordCount; + } - public void setRecordCount(int recordCount) { - calculate(); + public void setRecordCount(int recordCount) { + calculate(); - this.recordCount = recordCount; - } + this.recordCount = recordCount; + } - public int getFirstResult() { - calculate(); + public int getFirstResult() { + calculate(); - return firstResult; - } + return firstResult; + } - public void setFirstResult(int firstResult) { - calculate(); + public void setFirstResult(int firstResult) { + calculate(); - this.firstResult = firstResult; - } + this.firstResult = firstResult; + } - public String getPageUrl() { - return pageUrl + "&pageNum=" + pageNum; - } + public String getPageUrl() { + return pageUrl + "&pageNum=" + pageNum; + } - public void setPageUrl(String pageUrl) { - this.pageUrl = pageUrl; - } + public void setPageUrl(String pageUrl) { + this.pageUrl = pageUrl; + } } diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/logback.xml index 2298f01e..f5e516ab 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/logback.xml +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/logback.xml @@ -2,7 +2,7 @@ - + @@ -33,13 +33,13 @@ - - + + - - + + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/messages.properties b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/messages.properties index 12e36995..3a9590dd 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/messages.properties +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/messages.properties @@ -1,2 +1,2 @@ -prompt.hello=Hello, "{0}". -prompt.greeting=Nice to meet you. \ No newline at end of file +prompt.hello = Hello, "{0}". +prompt.greeting = Nice to meet you. diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/messages_zh_CN.properties b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/messages_zh_CN.properties index 46054c07..8ef7827c 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/messages_zh_CN.properties +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/messages_zh_CN.properties @@ -1,2 +1,2 @@ -prompt.hello=\u4f60\u597d, "{0}". -prompt.greeting=\u5f88\u9ad8\u5174\u89c1\u5230\u4f60. \ No newline at end of file +prompt.hello = \u4f60\u597d, "{0}". +prompt.greeting = \u5f88\u9ad8\u5174\u89c1\u5230\u4f60. diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/WEB-INF/web.xml b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/WEB-INF/web.xml index aa1943d6..ae92ddac 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/WEB-INF/web.xml +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/WEB-INF/web.xml @@ -1,25 +1,25 @@ - - - HelloServlet - /examples/configuration.jsp - - message - welcome to jsp - - 1 - - - HelloServlet - /config - /config.jsp - + + HelloServlet + /examples/configuration.jsp + + message + welcome to jsp + + 1 + + + HelloServlet + /config + /config.jsp + - - - /WEB-INF/views/jsp/index.jsp - + + + /WEB-INF/views/jsp/index.jsp + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_forEach.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_forEach.jsp index 8f0df0d3..2562e5c4 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_forEach.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_forEach.jsp @@ -8,6 +8,6 @@ Item

- + - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_forTokens.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_forTokens.jsp index 5c5abb19..3d2470b4 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_forTokens.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_forTokens.jsp @@ -8,6 +8,6 @@

- + - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_if.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_if.jsp index 60bc9091..91b904ac 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_if.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_if.jsp @@ -9,6 +9,6 @@

我的工资为:

- + - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_param.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_param.jsp index 40661d86..78fb0860 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_param.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_param.jsp @@ -8,7 +8,7 @@

<c:param> 实例

- + <%-- @@ -18,4 +18,4 @@ "> 使用 <c:param> 为指定URL发送两个参数。--%> - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_set.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_set.jsp index ec1fe9b8..66f5f045 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_set.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_set.jsp @@ -1,5 +1,5 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> @@ -9,4 +9,4 @@ - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_contains.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_contains.jsp index 760abc1a..33bb67f7 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_contains.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_contains.jsp @@ -1,5 +1,5 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> @@ -11,12 +11,14 @@ -

找到 china

+

找到 china +

-

找到 CHINA

+

找到 CHINA +

- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_containsIgnoreCase.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_containsIgnoreCase.jsp index b23f215c..d80a5b2e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_containsIgnoreCase.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_containsIgnoreCase.jsp @@ -1,5 +1,5 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> @@ -11,12 +11,14 @@ -

找到 china

+

找到 china +

-

找到 CHINA

+

找到 CHINA +

- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_endsWith.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_endsWith.jsp index 0084cfdf..5b04d5cc 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_endsWith.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_endsWith.jsp @@ -1,5 +1,5 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> @@ -11,12 +11,14 @@ -

字符串以 123 结尾

+

字符串以 123 结尾 +

-

字符串以 china 结尾

+

字符串以 china 结尾 +

- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_escapeXml.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_escapeXml.jsp index 9faf2408..33651ba9 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_escapeXml.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_escapeXml.jsp @@ -1,5 +1,5 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> @@ -20,4 +20,4 @@

string (2) : ${string2}

- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_indexOf.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_indexOf.jsp index 9fc8dbd4..87f0ebf3 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_indexOf.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_indexOf.jsp @@ -1,5 +1,5 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> @@ -15,4 +15,4 @@

Index (2) : ${fn:indexOf(string2, "second")}

- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_join.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_join.jsp index 244362ce..8860a092 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_join.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_join.jsp @@ -1,5 +1,5 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> @@ -9,10 +9,10 @@ - - + +

字符串为 : ${string3}

- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_length.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_length.jsp index 9dcfaf91..28b6f0f8 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_length.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_length.jsp @@ -1,5 +1,5 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> @@ -9,11 +9,11 @@ - +

字符串长度 (1) : ${fn:length(string1)}

字符串长度 (2) : ${fn:length(string2)}

- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_replace.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_replace.jsp index 5a3f7b9f..91f7aa2a 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_replace.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_replace.jsp @@ -1,5 +1,5 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> @@ -10,10 +10,10 @@ + 'google', 'china')}"/>

替换后的字符串 : ${string2}

- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_split.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_split.jsp index 832e4fac..cba38eca 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_split.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_split.jsp @@ -1,5 +1,5 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> @@ -9,15 +9,15 @@ - - + +

string3 字符串 : ${string3}

- - + +

string5 字符串: ${string5}

- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_startsWith.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_startsWith.jsp index 27417ed5..61fa5d0f 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_startsWith.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_startsWith.jsp @@ -1,5 +1,5 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> @@ -12,10 +12,10 @@

字符串以 Google 开头

-
+

字符串以 China 开头

- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substring.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substring.jsp index 410be03a..20a2f441 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substring.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substring.jsp @@ -1,5 +1,5 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> @@ -9,9 +9,9 @@ - +

生成的子字符串为 : ${string2}

- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substringAfter.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substringAfter.jsp index bea1d206..33e92370 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substringAfter.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substringAfter.jsp @@ -1,5 +1,5 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> @@ -9,9 +9,9 @@ - +

生成的子字符串 : ${string2}

- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substringBefore.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substringBefore.jsp index 03f82ef8..aec080dd 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substringBefore.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substringBefore.jsp @@ -1,5 +1,5 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> @@ -9,9 +9,9 @@ - +

生成的子字符串 : ${string2}

- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_toLowerCase.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_toLowerCase.jsp index 832e9215..4d3fdc21 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_toLowerCase.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_toLowerCase.jsp @@ -1,5 +1,5 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> @@ -9,9 +9,9 @@ - +

字符串为 : ${string2}

- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_toUpperCase.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_toUpperCase.jsp index 656d58c6..c2f5a9cb 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_toUpperCase.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_toUpperCase.jsp @@ -1,5 +1,5 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> @@ -9,9 +9,9 @@ - +

字符串为 : ${string2}

- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_trim.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_trim.jsp index 8302b9fa..38a372d5 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_trim.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_trim.jsp @@ -1,5 +1,5 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> @@ -11,9 +11,9 @@

string1 长度 : ${fn:length(string1)}

- +

string2 长度 : ${fn:length(string2)}

字符串为 : ${string2}

- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/bundle.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/bundle.jsp index fc8d78ce..6076fd10 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/bundle.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/bundle.jsp @@ -1,11 +1,11 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> - -Insert title here + + Insert title here @@ -13,15 +13,15 @@ - - -
- - - + + +
+ + +
- - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/catch.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/catch.jsp index cf69926c..1b5aa361 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/catch.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/catch.jsp @@ -1,25 +1,26 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - -Insert title here - + + Insert title here + - + - 程序抛出了异常 ${ e.class.name },原因: ${ e.message } + 程序抛出了异常 ${ e.class.name },原因: ${ e.message } - - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/choose.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/choose.jsp index bb09fa4a..8e213de2 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/choose.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/choose.jsp @@ -1,21 +1,21 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - -Insert title here + + Insert title here - - when 标签的输出 - - - otherwise 标签的输出 - + + when 标签的输出 + + + otherwise 标签的输出 + - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/contains.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/contains.jsp index 1ba3cca8..6643c1a0 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/contains.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/contains.jsp @@ -1,19 +1,19 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - -Insert title here + + Insert title here header['User-Agent'] = "${ header['User-Agent'] }";

-您使用 +您使用 IE 浏览器 Firefox 浏览器 Maxth 浏览器 @@ -32,4 +32,4 @@ header['User-Agent'] = "${ header['User-Agent'] }";

。 - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/containsIgnoreCase.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/containsIgnoreCase.jsp index 0cdd7260..64a0912e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/containsIgnoreCase.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/containsIgnoreCase.jsp @@ -1,19 +1,19 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - -Insert title here + + Insert title here header['User-Agent'] = "${ header['User-Agent'] }";

-您使用 +您使用 IE 浏览器 Firefox 浏览器 Maxth 浏览器 @@ -30,4 +30,4 @@ header['User-Agent'] = "${ header['User-Agent'] }";

。 - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/dateParam.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/dateParam.jsp index 12fd3595..b7509651 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/dateParam.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/dateParam.jsp @@ -1,79 +1,80 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - -Insert title here - + .title td { + text-align: center; + background: #DDDDDD; + } + + textarea { + width: 400px; + height: 50px; + } + + password="admin" + url="jdbc:mysql://localhost:3306/jstl?charachterEncoding=UTF-8" + var="dataSource" scope="page"/> - create table if not exists tb_person - ( id integer auto_increment, - name varchar(255), - birthday timestamp null, - primary key (id) - ) + create table if not exists tb_person + ( id integer auto_increment, + name varchar(255), + birthday timestamp null, + primary key (id) + ) - insert into tb_person ( name, birthday ) values ( ?, ? ) - - + insert into tb_person ( name, birthday ) values ( ?, ? ) + + - select * from tb_person where birthday > ( ? - 1 ) - + select * from tb_person where birthday > ( ? - 1 ) + - - - - - + + + + + - - - - - - - + + + + + + +
${ columnName }
${ columnName }
${ row[columnName] }
${ row[columnName] }
- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/endsWith.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/endsWith.jsp index 8ce959a1..e790baf6 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/endsWith.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/endsWith.jsp @@ -1,67 +1,70 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> -<%@page import="java.io.File"%> +<%@page import="java.io.File" %> - -Insert title here - + + Insert title here + <% - request.setAttribute("files", new File("c:\\").listFiles()); + request.setAttribute("files", new File("c:\\").listFiles()); %> - - - - - - - - - - - - + + + + + + + + + + + +
File NameType
${ file.name } - - - 文件夹 - - JPG 图片 - EXE 应用程序 - GIF 图片 - TXT 文本文件 - WORD 文件 - Excel 文件 - LOG 日志文件 - SQL 数据库脚本文件 - - - -
File NameType
${ file.name } + + + 文件夹 + + JPG 图片 + EXE 应用程序 + GIF 图片 + TXT 文本文件 + WORD 文件 + Excel 文件 + LOG 日志文件 + SQL 数据库脚本文件 + + + +
- + - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/escapeXml.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/escapeXml.jsp index 3478adef..575e6121 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/escapeXml.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/escapeXml.jsp @@ -1,12 +1,12 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - -Insert title here + + Insert title here @@ -18,4 +18,4 @@ ${ fn:escapeXml(source) }
- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/fmt.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/fmt.jsp index 3a102ae0..137d57fa 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/fmt.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/fmt.jsp @@ -1,14 +1,14 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> - -Insert title here + + Insert title here - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/fn.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/fn.jsp index 69d7bafb..09972959 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/fn.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/fn.jsp @@ -1,14 +1,14 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - -Insert title here + + Insert title here - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forEach.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forEach.jsp index 065c535c..59b80a92 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forEach.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forEach.jsp @@ -1,36 +1,39 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - -Insert title here - + + Insert title here + -
${ num }
+
${ num }

@@ -39,16 +42,16 @@ td { - - - - - - - - - - + + + + + + + + + +
Header NameHeader Value
${ headerName }${ header[headerName] }
Header NameHeader Value
${ headerName }${ header[headerName] }

@@ -57,17 +60,17 @@ td { - - - - - - - - - - + + + + + + + + + +
Header NameHeader Value
${ item.key }${ item.value }
Header NameHeader Value
${ item.key }${ item.value }
- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forEachWithList.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forEachWithList.jsp index f132fc12..eca5a1f4 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forEachWithList.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forEachWithList.jsp @@ -1,155 +1,158 @@ -<%@ page language="java" import="java.util.*, com.helloweenvsfei.jstl.bean.*" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> +<%@ page language="java" import="com.helloweenvsfei.jstl.bean.*, java.util.ArrayList" contentType="text/html; charset=UTF-8" %> +<%@ page import="java.util.List" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <% - List personList = new ArrayList(); - - int i = 1; - - Person person = new Person(); - person.setId(i++); - person.setName("张三"); - person.setAge(20); - person.setSex("男"); - person.setAddress("北京市海淀区上地软件园"); - person.setBirthday("2008-08-08"); - person.setMobile("13820080808"); - person.setTelephone("69653234"); - person.setCity("北京"); - - personList.add(person); - - Person person2 = new Person(); - person2.setId(i++); - person2.setName("李四"); - person2.setAge(20); - person2.setSex("男"); - person2.setAddress("北京市东皇城根锡拉胡同"); - person2.setBirthday("2008-01-01"); - person2.setMobile("13820080808"); - person2.setTelephone("20054879"); - person2.setCity("北京"); - - personList.add(person2); - - Person person3 = new Person(); - person3.setId(i++); - person3.setName("王五"); - person3.setAge(20); - person3.setSex("男"); - person3.setAddress("北京市东皇城根锡拉胡同"); - person3.setBirthday("2008-01-01"); - person3.setMobile("13820080808"); - person3.setTelephone("20054879"); - person3.setCity("北京"); - - personList.add(person3); - - Person person4 = new Person(); - person4.setId(i++); - person4.setName("王二麻子"); - person4.setAge(20); - person4.setSex("男"); - person4.setAddress("北京市东皇城根锡拉胡同"); - person4.setBirthday("2008-01-01"); - person4.setMobile("13820080808"); - person4.setTelephone("20054879"); - person4.setCity("北京"); - - personList.add(person4); - - Person person5 = new Person(); - person5.setId(i++); - person5.setName("王二麻子"); - person5.setAge(20); - person5.setSex("男"); - person5.setAddress("北京市东皇城根锡拉胡同"); - person5.setBirthday("2008-01-01"); - person5.setMobile("13820080808"); - person5.setTelephone("20054879"); - person5.setCity("北京"); - - personList.add(person5); - - Person person6 = new Person(); - person6.setId(i++); - person6.setName("王二麻子"); - person6.setAge(20); - person6.setSex("男"); - person6.setAddress("北京市东皇城根锡拉胡同"); - person6.setBirthday("2008-01-01"); - person6.setMobile("13820080808"); - person6.setTelephone("20054879"); - person6.setCity("北京"); - - personList.add(person6); - - Person person7 = new Person(); - person7.setId(i++); - person7.setName("王二麻子"); - person7.setAge(20); - person7.setSex("男"); - person7.setAddress("北京市东皇城根锡拉胡同"); - person7.setBirthday("2008-01-01"); - person7.setMobile("13820080808"); - person7.setTelephone("20054879"); - person7.setCity("北京"); - - personList.add(person7); - - request.setAttribute("personList", personList); + List personList = new ArrayList(); + + int i = 1; + + Person person = new Person(); + person.setId(i++); + person.setName("张三"); + person.setAge(20); + person.setSex("男"); + person.setAddress("北京市海淀区上地软件园"); + person.setBirthday("2008-08-08"); + person.setMobile("13820080808"); + person.setTelephone("69653234"); + person.setCity("北京"); + + personList.add(person); + + Person person2 = new Person(); + person2.setId(i++); + person2.setName("李四"); + person2.setAge(20); + person2.setSex("男"); + person2.setAddress("北京市东皇城根锡拉胡同"); + person2.setBirthday("2008-01-01"); + person2.setMobile("13820080808"); + person2.setTelephone("20054879"); + person2.setCity("北京"); + + personList.add(person2); + + Person person3 = new Person(); + person3.setId(i++); + person3.setName("王五"); + person3.setAge(20); + person3.setSex("男"); + person3.setAddress("北京市东皇城根锡拉胡同"); + person3.setBirthday("2008-01-01"); + person3.setMobile("13820080808"); + person3.setTelephone("20054879"); + person3.setCity("北京"); + + personList.add(person3); + + Person person4 = new Person(); + person4.setId(i++); + person4.setName("王二麻子"); + person4.setAge(20); + person4.setSex("男"); + person4.setAddress("北京市东皇城根锡拉胡同"); + person4.setBirthday("2008-01-01"); + person4.setMobile("13820080808"); + person4.setTelephone("20054879"); + person4.setCity("北京"); + + personList.add(person4); + + Person person5 = new Person(); + person5.setId(i++); + person5.setName("王二麻子"); + person5.setAge(20); + person5.setSex("男"); + person5.setAddress("北京市东皇城根锡拉胡同"); + person5.setBirthday("2008-01-01"); + person5.setMobile("13820080808"); + person5.setTelephone("20054879"); + person5.setCity("北京"); + + personList.add(person5); + + Person person6 = new Person(); + person6.setId(i++); + person6.setName("王二麻子"); + person6.setAge(20); + person6.setSex("男"); + person6.setAddress("北京市东皇城根锡拉胡同"); + person6.setBirthday("2008-01-01"); + person6.setMobile("13820080808"); + person6.setTelephone("20054879"); + person6.setCity("北京"); + + personList.add(person6); + + Person person7 = new Person(); + person7.setId(i++); + person7.setName("王二麻子"); + person7.setAge(20); + person7.setSex("男"); + person7.setAddress("北京市东皇城根锡拉胡同"); + person7.setBirthday("2008-01-01"); + person7.setMobile("13820080808"); + person7.setTelephone("20054879"); + person7.setCity("北京"); + + personList.add(person7); + + request.setAttribute("personList", personList); %> - -Insert title here - + + Insert title here + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + +
编号姓名年龄性别城市地址生日手机电话
${ person.id }${ person.name }${ person.age }${ person.sex }${ person.city }${ person.address }${ person.birthday }${ person.mobile }${ person.telephone }
编号姓名年龄性别城市地址生日手机电话
${ person.id }${ person.name }${ person.age }${ person.sex }${ person.city }${ person.address }${ person.birthday }${ person.mobile }${ person.telephone }
@@ -158,35 +161,35 @@ td {
- - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + +
编号姓名年龄性别城市地址生日手机电话
${ varStatus.current.id }${ varStatus.current.name }${ varStatus.current.age }${ varStatus.current.sex }${ varStatus.current.city }${ varStatus.current.address }${ varStatus.current.birthday }${ varStatus.current.mobile }${ varStatus.current.telephone }
编号姓名年龄性别城市地址生日手机电话
${ varStatus.current.id }${ varStatus.current.name }${ varStatus.current.age }${ varStatus.current.sex }${ varStatus.current.city }${ varStatus.current.address }${ varStatus.current.birthday }${ varStatus.current.mobile }${ varStatus.current.telephone }
- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forTokens.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forTokens.jsp index 9de4ecd8..85962133 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forTokens.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forTokens.jsp @@ -1,38 +1,39 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - -Insert title here - + + Insert title here + - - - - - - - - - - + + + + + + + + + +
varStatus.indexname
${ varStatus.index }${ item }
varStatus.indexname
${ varStatus.index }${ item }
- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/formatDate.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/formatDate.jsp index 993ce5c4..b4e36b33 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/formatDate.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/formatDate.jsp @@ -1,75 +1,77 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> -<%@page import="java.util.Locale"%> -<%@page import="java.lang.reflect.Field"%> -<%@page import="java.util.ArrayList"%> -<%@page import="java.util.List"%> -<%@page import="java.util.Date"%> +<%@page import="java.lang.reflect.Field" %> +<%@page import="java.util.ArrayList" %> +<%@page import="java.util.List" %> +<%@page import="java.util.Locale" %> - -Insert title here - + + Insert title here + <% - Field[] field = Locale.class.getFields(); + Field[] field = Locale.class.getFields(); - List localeList = new ArrayList(); + List localeList = new ArrayList(); - for(int i=0; i - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + +
LocaleDate and TimeNumbercurrency
${ locale }
LocaleDate and TimeNumbercurrency
${ locale }
- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/formatNumber.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/formatNumber.jsp index ab1cab2e..c2fe6ffe 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/formatNumber.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/formatNumber.jsp @@ -1,54 +1,56 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> -<%@page import="java.util.Locale"%> -<%@page import="java.lang.reflect.Field"%> -<%@page import="java.util.ArrayList"%> -<%@page import="java.util.List"%> -<%@page import="java.util.Date"%> +<%@page import="java.lang.reflect.Field" %> +<%@page import="java.util.ArrayList" %> +<%@page import="java.util.List" %> +<%@page import="java.util.Locale" %> - -Insert title here - + + Insert title here + <% - Field[] field = Locale.class.getFields(); + Field[] field = Locale.class.getFields(); - List localeList = new ArrayList(); + List localeList = new ArrayList(); - for(int i=0; i @@ -58,30 +60,30 @@ td {   - ${ locale }  + ${ locale }  - - - - - - - - - - - - - - - + + + + + + + + + + + + + + +
数字原值数字格式货币格式百分数格式
${ number }
数字原值数字格式货币格式百分数格式
${ number }
- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/if.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/if.jsp index c43ef5ef..03aaab59 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/if.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/if.jsp @@ -1,51 +1,56 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - - - + + +
- - 添加操作 - - - - - - - - - -
帐号
真实姓名
-
- - 修改操作 - - - - - - - - - -
帐号
真实姓名
-
- -
- - -
+ + 添加操作 + + + + + + + + + +
帐号
真实姓名
+
+ + 修改操作 + + + + + + + + + +
帐号
真实姓名
+
+ + + + + +
+ + - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/import.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/import.jsp index 70936ec2..13b9de8a 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/import.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/import.jsp @@ -1,15 +1,15 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - -Insert title here - + + Insert title here + @@ -18,8 +18,8 @@ body { Baidu 的源代码为: - + - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/index.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/index.jsp index 3d9e4430..3f83c65a 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/index.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/index.jsp @@ -1,10 +1,10 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - -Insert title here + + Insert title here @@ -13,4 +13,4 @@ index.jsp Test - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/indexOf.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/indexOf.jsp index d670c482..1192f92d 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/indexOf.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/indexOf.jsp @@ -1,16 +1,16 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - -Insert title here + + Insert title here fn:indexOf('filename.txt', '.') = ${ fn:indexOf('filename.txt', '.') } - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/join.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/join.jsp index a9444c3d..997b97ea 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/join.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/join.jsp @@ -1,20 +1,20 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - -Insert title here + + Insert title here <% - request.setAttribute("array", new String[]{"John", "Tom", "Tommi", "Kurt", }); + request.setAttribute("array", new String[] {"John", "Tom", "Tommi", "Kurt",}); %> ${ fn:join(array, '; ') } - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/length.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/length.jsp index c0472d25..ed26b2f2 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/length.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/length.jsp @@ -1,12 +1,12 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - -Insert title here + + Insert title here @@ -15,4 +15,4 @@ Cookie[] 的长度:${ fn:length(pageContext.request.cookies) }
Map header 的长度: ${ fn:length(header) }
- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/param.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/param.jsp index 13a18a56..a9ef20e2 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/param.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/param.jsp @@ -1,17 +1,17 @@ -<%@ page language="java" contentType="text/html; charset=GBK"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> +<%@ page language="java" contentType="text/html; charset=GBK" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - -Insert title here + + Insert title here - - + + - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parse.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parse.jsp index 04dd7b53..eb1c1fe7 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parse.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parse.jsp @@ -1,50 +1,51 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/xml" prefix="x"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/xml" prefix="x" %> - -Insert title here - + + Insert title here + - +
- +
新浪 RSS
-版本:
-标题:
-来源:
-版权:
-出版时间:
+版本:
+标题:
+来源:
+版权:
+出版时间:
链接地址:
- - Helloween - 20 - + + Helloween + 20 + - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parseDate.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parseDate.jsp index 07282468..9c512a4a 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parseDate.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parseDate.jsp @@ -1,16 +1,16 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> - -Insert title here + + Insert title here - - - + + + - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parseNumber.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parseNumber.jsp index 20bd9c71..ad75c1f2 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parseNumber.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parseNumber.jsp @@ -1,11 +1,11 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> - -Insert title here + + Insert title here @@ -13,4 +13,4 @@
- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/query.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/query.jsp index f6bd23e9..5e2e8f1d 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/query.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/query.jsp @@ -1,57 +1,57 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - -Insert title here - + + Insert title here + + password="admin" + url="jdbc:mysql://localhost:3306/jstl?charachterEncoding=UTF-8" + var="dataSource"/> + sql="select * from tb_corporation"> - - - - - - - - - - - - - + + + + + + + + + + + + +
IDNameDescription
${ row['id'] }${ row['name'] }${ row['description'] }
IDNameDescription
${ row['id'] }${ row['name'] }${ row['description'] }
- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/queryPagination.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/queryPagination.jsp index 63ed4d80..6a905a2d 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/queryPagination.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/queryPagination.jsp @@ -1,76 +1,79 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> -<%@page import="com.helloweenvsfei.util.Pagination"%> +<%@page import="com.helloweenvsfei.util.Pagination" %> - -Insert title here - + td { + border: 1px solid #000000; + padding: 2px; + } + + .title td { + text-align: center; + background: #DDDDDD; + } + <% - request.setAttribute("pagination", new Pagination(request, response)); + request.setAttribute("pagination", new Pagination(request, response)); %> + password="admin" + url="jdbc:mysql://localhost:3306/mysql?charachterEncoding=UTF-8" + var="dataSource"/> - SELECT count(*) count FROM help_topic + SELECT count(*) count FROM help_topic - + - SELECT * FROM help_topic + startRow="${ pagination.firstResult }" + maxRows="${ pagination.pageSize }"> + SELECT * FROM help_topic - - - - - + + + + + - - - - - - - + + + + + + +
Help_IDNameDescription
Help_IDNameDescription
${ row['help_topic_id'] }${ row['name'] }${ row['description'] }
${ row['help_topic_id'] }${ row['name'] }${ row['description'] }

${ pagination } - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/queryReflect.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/queryReflect.jsp index c904c5c1..f81a2989 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/queryReflect.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/queryReflect.jsp @@ -1,74 +1,75 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - -Insert title here - + .title td { + text-align: center; + background: #DDDDDD; + } + + textarea { + width: 400px; + height: 50px; + } +
-
- +
+
+ password="admin" + url="jdbc:mysql://localhost:3306/mysql?charachterEncoding=UTF-8" + var="dataSource"/> - - ${ param.sql } - - - - - - - - - - - - - - - - - -
${ columnName }
${ row[columnName] }
+ + ${ param.sql } + + + + + + + + + + + + + + + + + +
${ columnName }
${ row[columnName] }
-
Exception: ${ e.message }
+
Exception: ${ e.message }
- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/redirect.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/redirect.jsp index 4b9f6030..5541dfca 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/redirect.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/redirect.jsp @@ -1,14 +1,14 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - -Insert title here + + Insert title here - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/remove.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/remove.jsp index 91c17b30..9d79d166 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/remove.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/remove.jsp @@ -1,21 +1,21 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@page import="java.util.HashMap"%> +<%@page import="java.util.HashMap" %> - -Insert title here + + Insert title here <% - request.setAttribute("somemap", new HashMap()); + request.setAttribute("somemap", new HashMap()); %> - + ${ somemap == null ? 'somemap 已经被删除' : 'somemap 没有被删除' } - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/requestEncoding.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/requestEncoding.jsp index 0c19c8ad..deb904c8 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/requestEncoding.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/requestEncoding.jsp @@ -1,24 +1,26 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> - -Insert title here - + + Insert title here +
- 关键字: -

- + 关键字: +

+
- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/set.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/set.jsp index be63603f..e6f72ddc 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/set.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/set.jsp @@ -1,10 +1,10 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - -Insert title here + + Insert title here @@ -21,14 +21,14 @@

<% - request.setAttribute("person", new com.helloweenvsfei.jstl.bean.Person()); - request.setAttribute("map", new java.util.HashMap()); + request.setAttribute("person", new com.helloweenvsfei.jstl.bean.Person()); + request.setAttribute("map", new java.util.HashMap()); %> ${ person.name } - + ${ map.name } - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setBundle.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setBundle.jsp index 390ba5f3..ec69c43f 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setBundle.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setBundle.jsp @@ -1,23 +1,23 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> - -Insert title here + + Insert title here - + - Helloween + Helloween
- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setDataSource.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setDataSource.jsp index ed4aeb1e..f6fea51b 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setDataSource.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setDataSource.jsp @@ -1,22 +1,22 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - -Insert title here + + Insert title here + password="123" + url="jdbc:mysql://localhost:3306/jstl?charachterEncoding=UTF-8" + var="dataSource" scope="page"/> - + 数据源:${ dataSource.class.name } @@ -24,4 +24,4 @@ ${ serverDataSource } - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setLocale.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setLocale.jsp index ff66b766..bd4f6138 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setLocale.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setLocale.jsp @@ -1,62 +1,68 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> -<%@page import="java.util.Locale"%> +<%@page import="java.util.Locale" %> - -Insert title here - + + Insert title here + <% - request.setAttribute("localeList", Locale.getAvailableLocales()); + request.setAttribute("localeList", Locale.getAvailableLocales()); %> - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + +
LocaleLanguageDate and TimeNumbercurrency
${ locale.displayName }${ locale.displayLanguage }
LocaleLanguageDate and TimeNumbercurrency
${ locale.displayName }${ locale.displayLanguage }
- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setTimeZone.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setTimeZone.jsp index b0650ce2..8e6bb383 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setTimeZone.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setTimeZone.jsp @@ -1,40 +1,43 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> - -Insert title here - + + Insert title here + - + 现在时刻:北京时间 -
+
-
- GMT${ i>=12 ? '+' : '' }${ i-12 } : - - - -
+
+ GMT${ i>=12 ? '+' : '' }${ i-12 } : + + + +
- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/sina.xml b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/sina.xml index 2bc06c38..1578574d 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/sina.xml +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/sina.xml @@ -6,168 +6,168 @@ With 230 million registered users worldwide, 450 million daily page views and ov In various surveys and polls, SINA has been recognized as the most valuable brand and the most popular website in China. For 2003 and 2005, SINA was ranked the "Most Preferred Website" in China according to the Chinese Academy of Social Sciences and considered "The Most Respected Chinese Company" for three consecutive years in 2003, 2004 and 2005 by the Economic Observer and the Management Case Study Center of Beijing University. At the same time, South China Weekend in both 2003 and 2004 honored SINA with the prestigious award of the "Chinese Language Medium of the Year." --> - - - <![CDATA[新闻中心-新闻要闻]]> - - - - <![CDATA[新闻中心]]> - - http://news.sina.com.cn - http://www.sinaimg.cn/dy/sina_news626.gif - - - - - http://news.sina.com.cn/iframe/o/allnews/input/index.htm - zh-cn - WWW.SINA.COM.CN - 5 - - - - Fri, 30 Nov 2007 05:25:01 GMT - - - - - - <![CDATA[视频-联盟杯拜仁紫百合惊险战平 克洛斯笑纳空门]]> - - http://video.sina.com.cn/sports/g/bn/2007-11-30/13236492.shtml - WWW.SINA.COM.CN - http://video.sina.com.cn/sports/g/bn/2007-11-30/13236492.shtml - - - - Fri, 30 Nov 2007 05:23:53 GMT - - - - - - - - <![CDATA[18律师声援遭诱捕浏阳商人]]> - - http://finance.sina.com.cn/column/complain/20071130/13234236791.shtml - WWW.SINA.COM.CN - http://finance.sina.com.cn/column/complain/20071130/13234236791.shtml - - - - Fri, 30 Nov 2007 05:23:08 GMT - - - + + <![CDATA[新闻中心-新闻要闻]]> + + + + <![CDATA[新闻中心]]> + + http://news.sina.com.cn + http://www.sinaimg.cn/dy/sina_news626.gif + + + + + http://news.sina.com.cn/iframe/o/allnews/input/index.htm + zh-cn + WWW.SINA.COM.CN + 5 + + + + Fri, 30 Nov 2007 05:25:01 GMT + + + + + + <![CDATA[视频-联盟杯拜仁紫百合惊险战平 克洛斯笑纳空门]]> + + http://video.sina.com.cn/sports/g/bn/2007-11-30/13236492.shtml + WWW.SINA.COM.CN + http://video.sina.com.cn/sports/g/bn/2007-11-30/13236492.shtml + + + + Fri, 30 Nov 2007 05:23:53 GMT + + + + + + + + <![CDATA[18律师声援遭诱捕浏阳商人]]> + + http://finance.sina.com.cn/column/complain/20071130/13234236791.shtml + WWW.SINA.COM.CN + http://finance.sina.com.cn/column/complain/20071130/13234236791.shtml + + + + Fri, 30 Nov 2007 05:23:08 GMT + + + - - - - - <![CDATA[视频-马竞必杀定位球降服弱旅 幸运之神眷顾纽伦堡]]> - - http://video.sina.com.cn/sports/g/bn/2007-11-30/13206491.shtml - WWW.SINA.COM.CN - http://video.sina.com.cn/sports/g/bn/2007-11-30/13206491.shtml - - - - Fri, 30 Nov 2007 05:20:23 GMT - - - - - - - - <![CDATA[[泰国橡胶]USS3橡胶现货价格下跌,因基本面疲弱]]> - - http://finance.sina.com.cn/money/future/20071130/13204236790.shtml - WWW.SINA.COM.CN - http://finance.sina.com.cn/money/future/20071130/13204236790.shtml - - - - Fri, 30 Nov 2007 05:20:17 GMT - - - + + + + <![CDATA[视频-马竞必杀定位球降服弱旅 幸运之神眷顾纽伦堡]]> + + http://video.sina.com.cn/sports/g/bn/2007-11-30/13206491.shtml + WWW.SINA.COM.CN + http://video.sina.com.cn/sports/g/bn/2007-11-30/13206491.shtml + + + + Fri, 30 Nov 2007 05:20:23 GMT + + + + + + + + <![CDATA[[泰国橡胶]USS3橡胶现货价格下跌,因基本面疲弱]]> + + http://finance.sina.com.cn/money/future/20071130/13204236790.shtml + WWW.SINA.COM.CN + http://finance.sina.com.cn/money/future/20071130/13204236790.shtml + + + + Fri, 30 Nov 2007 05:20:17 GMT + + + - - - - - <![CDATA[[马来棕榈油]2007年11月30日午马来西亚棕榈油现货行情]]> - - http://finance.sina.com.cn/money/future/20071130/13204236789.shtml - WWW.SINA.COM.CN - http://finance.sina.com.cn/money/future/20071130/13204236789.shtml - - - - Fri, 30 Nov 2007 05:20:13 GMT - - - + + + + <![CDATA[[马来棕榈油]2007年11月30日午马来西亚棕榈油现货行情]]> + + http://finance.sina.com.cn/money/future/20071130/13204236789.shtml + WWW.SINA.COM.CN + http://finance.sina.com.cn/money/future/20071130/13204236789.shtml + + + + Fri, 30 Nov 2007 05:20:13 GMT + + + - - - - - <![CDATA[马来西亚BMD毛棕榈油期货午盘下滑,市场等待出口预估数据]]> - - http://finance.sina.com.cn/money/future/20071130/13204236788.shtml - WWW.SINA.COM.CN - http://finance.sina.com.cn/money/future/20071130/13204236788.shtml - - - - Fri, 30 Nov 2007 05:20:04 GMT - - - + + + + <![CDATA[马来西亚BMD毛棕榈油期货午盘下滑,市场等待出口预估数据]]> + + http://finance.sina.com.cn/money/future/20071130/13204236788.shtml + WWW.SINA.COM.CN + http://finance.sina.com.cn/money/future/20071130/13204236788.shtml + + + + Fri, 30 Nov 2007 05:20:04 GMT + + + - - - - - <![CDATA[[机构看盘]集成利期货:郑糖探低回升中幅收跌,短线压力依旧较大]]> - - http://finance.sina.com.cn/money/future/20071130/13194236787.shtml - WWW.SINA.COM.CN - http://finance.sina.com.cn/money/future/20071130/13194236787.shtml - - - - Fri, 30 Nov 2007 05:19:56 GMT - - - - - - - - <![CDATA[[现货行情]11月30日广西糖网食糖批发市场收市行情]]> - - http://finance.sina.com.cn/money/future/20071130/13194236786.shtml - WWW.SINA.COM.CN - http://finance.sina.com.cn/money/future/20071130/13194236786.shtml - - - - Fri, 30 Nov 2007 05:19:52 GMT - - - + + + + <![CDATA[[机构看盘]集成利期货:郑糖探低回升中幅收跌,短线压力依旧较大]]> + + http://finance.sina.com.cn/money/future/20071130/13194236787.shtml + WWW.SINA.COM.CN + http://finance.sina.com.cn/money/future/20071130/13194236787.shtml + + + + Fri, 30 Nov 2007 05:19:56 GMT + + + + + + + + <![CDATA[[现货行情]11月30日广西糖网食糖批发市场收市行情]]> + + http://finance.sina.com.cn/money/future/20071130/13194236786.shtml + WWW.SINA.COM.CN + http://finance.sina.com.cn/money/future/20071130/13194236786.shtml + + + + Fri, 30 Nov 2007 05:19:52 GMT + + + - - - - - <![CDATA[[现货行情]11月30日昆明商品中心批发市场收市行情]]> - - http://finance.sina.com.cn/money/future/20071130/13194236785.shtml - WWW.SINA.COM.CN - http://finance.sina.com.cn/money/future/20071130/13194236785.shtml - - - - Fri, 30 Nov 2007 05:19:48 GMT - - - + + + + <![CDATA[[现货行情]11月30日昆明商品中心批发市场收市行情]]> + + http://finance.sina.com.cn/money/future/20071130/13194236785.shtml + WWW.SINA.COM.CN + http://finance.sina.com.cn/money/future/20071130/13194236785.shtml + + + + Fri, 30 Nov 2007 05:19:48 GMT + + + - - - - - <![CDATA[长沙百亿企业添新]]> - - http://finance.sina.com.cn/china/dfjj/20071130/13194236784.shtml - WWW.SINA.COM.CN - http://finance.sina.com.cn/china/dfjj/20071130/13194236784.shtml - - - - Fri, 30 Nov 2007 05:19:41 GMT - - - + + + + <![CDATA[长沙百亿企业添新]]> + + http://finance.sina.com.cn/china/dfjj/20071130/13194236784.shtml + WWW.SINA.COM.CN + http://finance.sina.com.cn/china/dfjj/20071130/13194236784.shtml + + + + Fri, 30 Nov 2007 05:19:41 GMT + + + - - - - - <![CDATA[新代码启用 垃圾短信难隐形]]> - - http://tech.sina.com.cn/t/2007-11-30/13191884840.shtml - WWW.SINA.COM.CN - http://tech.sina.com.cn/t/2007-11-30/13191884840.shtml - - - - Fri, 30 Nov 2007 05:19:36 GMT - - - + + + + <![CDATA[新代码启用 垃圾短信难隐形]]> + + http://tech.sina.com.cn/t/2007-11-30/13191884840.shtml + WWW.SINA.COM.CN + http://tech.sina.com.cn/t/2007-11-30/13191884840.shtml + + + + Fri, 30 Nov 2007 05:19:36 GMT + + + - - - - - <![CDATA[至酷腕表型MP3 松下MP130V仅400元甩卖]]> - - http://tech.sina.com.cn/digi/mp3/2007-11-30/13191884839.shtml - WWW.SINA.COM.CN - http://tech.sina.com.cn/digi/mp3/2007-11-30/13191884839.shtml - - - - Fri, 30 Nov 2007 05:19:12 GMT - - - + + + + <![CDATA[至酷腕表型MP3 松下MP130V仅400元甩卖]]> + + http://tech.sina.com.cn/digi/mp3/2007-11-30/13191884839.shtml + WWW.SINA.COM.CN + http://tech.sina.com.cn/digi/mp3/2007-11-30/13191884839.shtml + + + + Fri, 30 Nov 2007 05:19:12 GMT + + + - - - - - <![CDATA[稳定成品油市场供应加强价格监管]]> - - http://finance.sina.com.cn/g/20071130/13184236783.shtml - WWW.SINA.COM.CN - http://finance.sina.com.cn/g/20071130/13184236783.shtml - - - - Fri, 30 Nov 2007 05:18:42 GMT - - - - - - - \ No newline at end of file + + + + + <![CDATA[稳定成品油市场供应加强价格监管]]> + + http://finance.sina.com.cn/g/20071130/13184236783.shtml + WWW.SINA.COM.CN + http://finance.sina.com.cn/g/20071130/13184236783.shtml + + + + Fri, 30 Nov 2007 05:18:42 GMT + + + + + + + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/split.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/split.jsp index f728f7a8..050f4d55 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/split.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/split.jsp @@ -1,21 +1,21 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - -Insert title here + + Insert title here ${ header['accept'] } - + header['accept']:

- ${ name }
+ ${ name }
- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/sql.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/sql.jsp index 9025ef1a..6eedfffe 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/sql.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/sql.jsp @@ -1,17 +1,16 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - -Insert title here + + Insert title here - - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/substring.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/substring.jsp index f83ae4e6..1c0f0de2 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/substring.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/substring.jsp @@ -1,12 +1,12 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - -Insert title here + + Insert title here @@ -18,4 +18,4 @@ ${ fn:substringBefore(email, '@') } ${ fn:substringAfter(email, '@') } - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/timeZone.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/timeZone.jsp index 659de4fe..27a07ea4 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/timeZone.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/timeZone.jsp @@ -1,75 +1,81 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> - - +<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> + + +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> - -Insert title here - + + Insert title here + <% - Map hashMap = new HashMap(); - - for(String ID : TimeZone.getAvailableIDs()){ - hashMap.put(ID, TimeZone.getTimeZone(ID)); - } - - request.setAttribute("timeZoneIds", TimeZone.getAvailableIDs()); - request.setAttribute("timeZone", hashMap); + Map hashMap = new HashMap(); + + for (String ID : TimeZone.getAvailableIDs()) { + hashMap.put(ID, TimeZone.getTimeZone(ID)); + } + + request.setAttribute("timeZoneIds", TimeZone.getAvailableIDs()); + request.setAttribute("timeZone", hashMap); %> - + 现在时刻:<%= TimeZone.getDefault().getDisplayName() %> -
+
- - - - - - + + + + + + - - - - - - - - + + + + + + + +
时区ID时区现在时间时差
时区ID时区现在时间时差
${ ID }${ timeZone[ID].displayName } - - - - ${ timeZone[ID].rawOffset / 60 / 60 / 1000 }
${ ID }${ timeZone[ID].displayName } + + + + ${ timeZone[ID].rawOffset / 60 / 60 / 1000 }
- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/transaction.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/transaction.jsp index 7667b2d3..27fc7a71 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/transaction.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/transaction.jsp @@ -1,87 +1,91 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - -Insert title here - + + Insert title here + + password="admin" + url="jdbc:mysql://localhost:3306/jstl?charachterEncoding=UTF-8" + var="dataSource"/> - - - - - insert into tb_corporation ( name, description ) values ('事务测试', '事务测试') - - - 已插入一条。
-
- - - - insert into tb_corporation ( id, name, description ) values (1, '事务测试', '事务测试') - - -
+ + + + + insert into tb_corporation ( name, description ) values ('事务测试', '事务测试') + + + 已插入一条。
+
+ + + + insert into tb_corporation ( id, name, description ) values (1, '事务测试', '事务测试') + + +
-
操作异常,原因:${ e.message }。事务已经回滚。
+
操作异常,原因:${ e.message }。事务已经回滚。
- select * from tb_corporation + select * from tb_corporation - - - - - - - - - - - - - + + + + + + + + + + + + +
${ columnName }
${ row[columnName] }
${ columnName }
${ row[columnName] }
- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/update.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/update.jsp index 242afb79..7b909066 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/update.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/update.jsp @@ -1,65 +1,65 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - -Insert title here - + .title td { + text-align: center; + background: #DDDDDD; + } + + password="admin" + url="jdbc:mysql://localhost:3306/jstl?charachterEncoding=UTF-8" + var="dataSource"/> - drop table if exists tb_corporation + drop table if exists tb_corporation DROP TABLE, 影响到的数据条数:${ result }
-create table tb_corporation ( - id integer auto_increment, - name varchar(255), - description text, - primary key(id) -) + create table tb_corporation ( + id integer auto_increment, + name varchar(255), + description text, + primary key(id) + ) CREATE TABLE, 影响到的数据条数:${ result }
- insert into tb_corporation ( name, description ) values ('MicroSoft', '微软') + insert into tb_corporation ( name, description ) values ('MicroSoft', '微软') INSERT, 影响到的数据条数:${ result }
- insert into tb_corporation ( name, description ) values ('IBM', '国际商用机器') + insert into tb_corporation ( name, description ) values ('IBM', '国际商用机器') INSERT, 影响到的数据条数:${ result }
- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/url.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/url.jsp index 3ef0f5e8..25bd315a 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/url.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/url.jsp @@ -1,10 +1,10 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - -Insert title here + + Insert title here @@ -12,4 +12,4 @@
- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_choose.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_choose.jsp index b412a755..c2359bd4 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_choose.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_choose.jsp @@ -1,43 +1,43 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/xml" prefix="x"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/xml" prefix="x" %> - -Insert title here - + .source { + white-space: pre; + width: 100%; + height: 200px; + overflow: scroll; + border: 1px solid #999999; + padding: 2px; + margin-bottom: 20px; + } + - + -
+
- - 使用了 JNDI 属性。 - - - 没有使用 JNDI 属性。 - + + 使用了 JNDI 属性。 + + + 没有使用 JNDI 属性。 + - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_forEach.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_forEach.jsp index e01aecb6..a9163f3f 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_forEach.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_forEach.jsp @@ -1,43 +1,44 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/xml" prefix="x"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/xml" prefix="x" %> - -Insert title here - + + Insert title here + - +
- +
- ${ status.count }. - " target="_blank">  -
-

- + ${ status.count }. + " target="_blank">  +
+

+
- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_if.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_if.jsp index 0c383fff..89af31eb 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_if.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_if.jsp @@ -1,42 +1,42 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/xml" prefix="x"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/xml" prefix="x" %> - -Insert title here - + .source { + white-space: pre; + width: 100%; + height: 200px; + overflow: scroll; + border: 1px solid #999999; + padding: 2px; + margin-bottom: 20px; + } + - + -
+
- 属性 Driver Class Name 存在: .
+ 属性 Driver Class Name 存在: .
- 属性 Driver Class Name 不存在。 + 属性 Driver Class Name 不存在。 - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_set.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_set.jsp index b7ffdda5..067bfd24 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_set.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_set.jsp @@ -1,32 +1,32 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/xml" prefix="x"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/xml" prefix="x" %> - -Insert title here - + .source { + white-space: pre; + width: 100%; + height: 200px; + overflow: scroll; + border: 1px solid #999999; + padding: 2px; + margin-bottom: 20px; + } + - + -
+
@@ -35,4 +35,4 @@ body,td,div { Driver Class Name: - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_transform.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_transform.jsp index 3419077e..fb96c6e1 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_transform.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_transform.jsp @@ -1,103 +1,107 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/xml" prefix="x"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/xml" prefix="x" %> - -Insert title here - + + Insert title here + - - - - 1 - 张三 - zhangsan@host.com - Software Engine - - - 2 - 李四 - lisi@somehost.com - Sales - - - 3 - 王五 - wangwu@someweb.com - Manager - - + + + + 1 + 张三 + zhangsan@host.com + Software Engine + + + 2 + 李四 + lisi@somehost.com + Sales + + + 3 + 王五 + wangwu@someweb.com + Manager + + - - - - - - - - - - - - - - - - - - -
编号姓名电子邮件描述
-
-
- + + + + + + + + + + + + + + + + + + +
编号姓名电子邮件描述
+
+
+
- +
- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/xml.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/xml.jsp index ecc8590a..ff830aab 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/xml.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/xml.jsp @@ -1,26 +1,25 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> -<%@ taglib uri="http://java.sun.com/jsp/jstl/xml" prefix="x"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/xml" prefix="x" %> - -Insert title here + + Insert title here - - Helloween - 20 - + + Helloween + 20 + Description:
-Name:
-age:
- +Name:
+age:
- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_choose.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_choose.jsp index 0d2ac381..50effb3b 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_choose.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_choose.jsp @@ -1,5 +1,5 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> + pageEncoding="UTF-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %> @@ -39,4 +39,4 @@ - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_forEach.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_forEach.jsp index ea0bba13..2907945e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_forEach.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_forEach.jsp @@ -1,5 +1,5 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> + pageEncoding="UTF-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %> @@ -28,9 +28,9 @@
    -
  • Book Name:
  • +
  • Book Name:
- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_if.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_if.jsp index 204cfdce..b29fb6a9 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_if.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_if.jsp @@ -30,11 +30,11 @@ Document has at least one element. - -
- -Book prices are very high - + +
+ + Book prices are very high + - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_param.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_param.jsp index c5000bbc..6903184c 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_param.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_param.jsp @@ -1,5 +1,5 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> + pageEncoding="UTF-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %> @@ -30,4 +30,4 @@ - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_transform.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_transform.jsp index 158b1fcd..adfecce5 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_transform.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_transform.jsp @@ -1,5 +1,5 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> + pageEncoding="UTF-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %> @@ -28,4 +28,4 @@ - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/xml/books.xml b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/xml/books.xml index 4969ebae..34ee43f7 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/xml/books.xml +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/xml/books.xml @@ -1,12 +1,12 @@ - - Padam History - ZARA - 100 - - - Great Mistry - NUHA - 2000 - - \ No newline at end of file + + Padam History + ZARA + 100 + + + Great Mistry + NUHA + 2000 + + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/xml/style.xsl b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/xml/style.xsl index f7ef2ad9..cdac4018 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/xml/style.xsl +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/xml/style.xsl @@ -1,32 +1,32 @@ - + - - - - - - - + + + + + + + - - - - - - - - - -
- - - - - - - -
-
\ No newline at end of file + + + + + + + + + +
+ + + + + + + +
+
diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java b/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java index 26e801e6..f4a1a2e9 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java @@ -1,13 +1,13 @@ package io.github.dunwu.javaee.server; -import java.util.ArrayList; - import org.apache.commons.lang3.StringUtils; import org.assertj.core.util.Lists; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.webapp.WebAppClassLoader; import org.eclipse.jetty.webapp.WebAppContext; +import java.util.ArrayList; + /** * JettyFactory 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij * 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 @@ -16,93 +16,95 @@ */ @SuppressWarnings("unused") public class JettyFactory { - private static final int PORT = 9798; - private static final String CONTEXT = "/"; - private static final String RESOURCE_BASE_PATH = "src/main/webapp"; - private static final String WEB_XML_PATH = "/WEB-INF/web.xml"; - private static final String[] TLD_JAR_NAMES = - new String[]{"sitemesh", "spring-webmvc", "shiro-web", "tiles"}; - private static final String WINDOWS_WEBDEFAULT_PATH = "jetty/webdefault.xml"; - - public static final int IDE_ECLIPSE = 0; - public static final int IDE_INTELLIJ = 1; - - - public static Server initServer() { - Profiles.setProfileAsSystemProperty(Profiles.DEVELOPMENT); - WebAppContext webAppContext = new WebAppContext(); - Server server = new Server(PORT); - server.setHandler(webAppContext); - return server; - } - - public static void initWebAppContext(Server server, int type) throws Exception { - System.out.println("[INFO] Application loading"); - WebAppContext webAppContext = (WebAppContext) server.getHandler(); - webAppContext.setContextPath(CONTEXT); - webAppContext.setResourceBase(getAbsolutePath() + RESOURCE_BASE_PATH); - webAppContext.setDescriptor(getAbsolutePath() + RESOURCE_BASE_PATH + WEB_XML_PATH); - - if (IDE_INTELLIJ == type) { - webAppContext.setDefaultsDescriptor(WINDOWS_WEBDEFAULT_PATH); - supportJspAndSetTldJarNames(server, TLD_JAR_NAMES); - } else { - webAppContext.setParentLoaderPriority(true); - } - - System.out.println("[INFO] Application loaded"); - } - - public static void reloadWebAppContext(Server server) throws Exception { - WebAppContext webAppContext = (WebAppContext) server.getHandler(); - System.out.println("[INFO] Application reloading"); - webAppContext.stop(); - WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext); - classLoader.addClassPath(getAbsolutePath() + "target/classes"); - classLoader.addClassPath(getAbsolutePath() + "target/test-classes"); - webAppContext.setClassLoader(classLoader); - webAppContext.start(); - System.out.println("[INFO] Application reloaded"); - } - - - public static void startServer(Server server) throws Exception { - System.out.println("[HINT] Don't forget to set -XX:MaxPermSize=128m"); - server.start(); - System.out.println("Server running at http://localhost:" + PORT + CONTEXT); - System.out.println("[HINT] Hit Enter to reload the application quickly"); - } - - public static String getAbsolutePath() { - String path = null; - String folderPath = JettyFactory.class.getProtectionDomain().getCodeSource().getLocation() - .getPath().substring(1); - if (folderPath.indexOf("target") > 0) { - path = folderPath.substring(0, folderPath.indexOf("target")); - } - return path; - } - - public static void supportJspAndSetTldJarNames(Server server, String... jarNames) { - WebAppContext context = (WebAppContext) server.getHandler(); - // This webapp will use jsps and jstl. We need to enable the AnnotationConfiguration in - // order to correctly set up the jsp container - org.eclipse.jetty.webapp.Configuration.ClassList classlist = - org.eclipse.jetty.webapp.Configuration.ClassList.setServerDefault(server); - classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", - "org.eclipse.jetty.annotations.AnnotationConfiguration"); - // Set the ContainerIncludeJarPattern so that jetty examines these container-path jars for - // tlds, web-fragments etc. - // If you omit the jar that contains the jstl .tlds, the jsp engine will scan for them - // instead. - ArrayList jarNameExprssions = Lists.newArrayList(".*/[^/]*servlet-api-[^/]*\\.jar$", - ".*/javax.servlet.jsp.jstl-.*\\.jar$", ".*/[^/]*taglibs.*\\.jar$"); - for (String jarName : jarNames) { - jarNameExprssions.add(".*/" + jarName + "-[^/]*\\.jar$"); - } + public static final int IDE_ECLIPSE = 0; + public static final int IDE_INTELLIJ = 1; + private static final int PORT = 9798; + private static final String CONTEXT = "/"; + private static final String RESOURCE_BASE_PATH = "src/main/webapp"; + private static final String WEB_XML_PATH = "/WEB-INF/web.xml"; + private static final String[] TLD_JAR_NAMES = new String[] { "sitemesh", "spring-webmvc", "shiro-web", "tiles" }; + private static final String WINDOWS_WEBDEFAULT_PATH = "jetty/webdefault.xml"; + + public static Server initServer() { + Profiles.setProfileAsSystemProperty(Profiles.DEVELOPMENT); + WebAppContext webAppContext = new WebAppContext(); + Server server = new Server(PORT); + server.setHandler(webAppContext); + return server; + } + + public static void initWebAppContext(Server server, int type) throws Exception { + System.out.println("[INFO] Application loading"); + WebAppContext webAppContext = (WebAppContext) server.getHandler(); + webAppContext.setContextPath(CONTEXT); + webAppContext.setResourceBase(getAbsolutePath() + RESOURCE_BASE_PATH); + webAppContext.setDescriptor(getAbsolutePath() + RESOURCE_BASE_PATH + WEB_XML_PATH); + + if (IDE_INTELLIJ == type) { + webAppContext.setDefaultsDescriptor(WINDOWS_WEBDEFAULT_PATH); + supportJspAndSetTldJarNames(server, TLD_JAR_NAMES); + } + else { + webAppContext.setParentLoaderPriority(true); + } + + System.out.println("[INFO] Application loaded"); + } + + public static void reloadWebAppContext(Server server) throws Exception { + WebAppContext webAppContext = (WebAppContext) server.getHandler(); + System.out.println("[INFO] Application reloading"); + webAppContext.stop(); + WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext); + classLoader.addClassPath(getAbsolutePath() + "target/classes"); + classLoader.addClassPath(getAbsolutePath() + "target/test-classes"); + webAppContext.setClassLoader(classLoader); + webAppContext.start(); + System.out.println("[INFO] Application reloaded"); + } + + public static void startServer(Server server) throws Exception { + System.out.println("[HINT] Don't forget to set -XX:MaxPermSize=128m"); + server.start(); + System.out.println("Server running at http://localhost:" + PORT + CONTEXT); + System.out.println("[HINT] Hit Enter to reload the application quickly"); + } + + public static String getAbsolutePath() { + String path = null; + String folderPath = JettyFactory.class.getProtectionDomain().getCodeSource().getLocation().getPath() + .substring(1); + if (folderPath.indexOf("target") > 0) { + path = folderPath.substring(0, folderPath.indexOf("target")); + } + return path; + } + + public static void supportJspAndSetTldJarNames(Server server, String... jarNames) { + WebAppContext context = (WebAppContext) server.getHandler(); + // This webapp will use jsps and jstl. We need to enable the + // AnnotationConfiguration in + // order to correctly set up the jsp container + org.eclipse.jetty.webapp.Configuration.ClassList classlist = org.eclipse.jetty.webapp.Configuration.ClassList + .setServerDefault(server); + classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", + "org.eclipse.jetty.annotations.AnnotationConfiguration"); + // Set the ContainerIncludeJarPattern so that jetty examines these container-path + // jars for + // tlds, web-fragments etc. + // If you omit the jar that contains the jstl .tlds, the jsp engine will scan for + // them + // instead. + ArrayList jarNameExprssions = Lists.newArrayList(".*/[^/]*servlet-api-[^/]*\\.jar$", + ".*/javax.servlet.jsp.jstl-.*\\.jar$", ".*/[^/]*taglibs.*\\.jar$"); + + for (String jarName : jarNames) { + jarNameExprssions.add(".*/" + jarName + "-[^/]*\\.jar$"); + } + + context.setAttribute("org.eclipse.jetty.io.github.dunwu.javaee.server.webapp.ContainerIncludeJarPattern", + StringUtils.join(jarNameExprssions, '|')); + } - context.setAttribute("org.eclipse.jetty.io.github.dunwu.javaee.server.webapp.ContainerIncludeJarPattern", - StringUtils.join(jarNameExprssions, '|')); - } } diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/Profiles.java b/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/Profiles.java index 3a8ee7c4..dcfd3e39 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/Profiles.java +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/Profiles.java @@ -13,11 +13,15 @@ public class Profiles { public static final String ACTIVE_PROFILE = "spring.profiles.active"; + public static final String DEFAULT_PROFILE = "spring.profiles.default"; public static final String PRODUCTION = "production"; + public static final String DEVELOPMENT = "development"; + public static final String UNIT_TEST = "test"; + public static final String FUNCTIONAL_TEST = "functional"; /** @@ -26,4 +30,5 @@ public class Profiles { public static void setProfileAsSystemProperty(String profile) { System.setProperty(ACTIVE_PROFILE, profile); } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java b/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java index dd0ac658..c09f0fc1 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java @@ -8,26 +8,29 @@ * @author Zhang Peng */ public class QuickStartServer { - // private static int STARTUP_TYPE = JettyFactory.IDE_ECLIPSE; - private static int STARTUP_TYPE = JettyFactory.IDE_INTELLIJ; - public static void main(String[] args) throws Exception { - Server server = JettyFactory.initServer(); - JettyFactory.initWebAppContext(server, STARTUP_TYPE); + // private static int STARTUP_TYPE = JettyFactory.IDE_ECLIPSE; + private static int STARTUP_TYPE = JettyFactory.IDE_INTELLIJ; - try { - JettyFactory.startServer(server); + public static void main(String[] args) throws Exception { + Server server = JettyFactory.initServer(); + JettyFactory.initWebAppContext(server, STARTUP_TYPE); + + try { + JettyFactory.startServer(server); + + // 等待用户输入回车重载应用 + while (true) { + char c = (char) System.in.read(); + if (c == '\n') { + JettyFactory.reloadWebAppContext(server); + } + } + } + catch (Exception e) { + e.printStackTrace(); + System.exit(-1); + } + } - // 等待用户输入回车重载应用 - while (true) { - char c = (char) System.in.read(); - if (c == '\n') { - JettyFactory.reloadWebAppContext(server); - } - } - } catch (Exception e) { - e.printStackTrace(); - System.exit(-1); - } - } } diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/pom.xml b/codes/javaee-tutorial/javaee-tutorial-listener/pom.xml index 5982640a..1c87eb2d 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/pom.xml +++ b/codes/javaee-tutorial/javaee-tutorial-listener/pom.xml @@ -1,4 +1,4 @@ - 4.0.0 diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/LoginSessionListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/LoginSessionListener.java index 21bbcac4..e5250ad4 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/LoginSessionListener.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/LoginSessionListener.java @@ -1,13 +1,12 @@ package io.github.dunwu.javaee.listener; -import javax.servlet.http.HttpSession; -import javax.servlet.http.HttpSessionAttributeListener; -import javax.servlet.http.HttpSessionBindingEvent; - import io.github.dunwu.javaee.listener.bean.PersonInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionAttributeListener; +import javax.servlet.http.HttpSessionBindingEvent; import java.util.HashMap; import java.util.Map; @@ -19,73 +18,73 @@ */ public class LoginSessionListener implements HttpSessionAttributeListener { - private final Logger logger = LoggerFactory.getLogger(getClass()); - Map map = new HashMap(); - - @Override - public void attributeAdded(HttpSessionBindingEvent event) { - String name = event.getName(); - - // 登录 - if (name.equals("personInfo")) { - - PersonInfo personInfo = (PersonInfo) event.getValue(); - - if (map.get(personInfo.getAccount()) != null) { - - // map 中有记录,表明该帐号在其他机器上登录过,将以前的登录失效 - HttpSession session = map.get(personInfo.getAccount()); - PersonInfo oldPersonInfo = (PersonInfo) session.getAttribute("personInfo"); - - logger.debug("帐号" + oldPersonInfo.getAccount() + "在" + oldPersonInfo.getIp() - + "已经登录,该登录将被迫下线。"); - - session.removeAttribute("personInfo"); - session.setAttribute("msg", "您的帐号已经在其他机器上登录,您被迫下线。"); - } - - // 将session以用户名为索引,放入map中 - map.put(personInfo.getAccount(), event.getSession()); - logger.debug("帐号" + personInfo.getAccount() + "在" + personInfo.getIp() + "登录。"); - } - } - - @Override - public void attributeRemoved(HttpSessionBindingEvent event) { - String name = event.getName(); - - // 注销 - if (name.equals("personInfo")) { - // 将该session从map中移除 - PersonInfo personInfo = (PersonInfo) event.getValue(); - map.remove(personInfo.getAccount()); - logger.debug("帐号" + personInfo.getAccount() + "注销。"); - } - } - - @Override - public void attributeReplaced(HttpSessionBindingEvent event) { - String name = event.getName(); - - // 没有注销的情况下,用另一个帐号登录 - if (name.equals("personInfo")) { - - // 移除旧的的登录信息 - PersonInfo oldPersonInfo = (PersonInfo) event.getValue(); - map.remove(oldPersonInfo.getAccount()); - - // 新的登录信息 - PersonInfo personInfo = (PersonInfo) event.getSession().getAttribute("personInfo"); - - // 也要检查新登录的帐号是否在别的机器上登录过 - if (map.get(personInfo.getAccount()) != null) { - // map 中有记录,表明该帐号在其他机器上登录过,将以前的登录失效 - HttpSession session = map.get(personInfo.getAccount()); - session.removeAttribute("personInfo"); - session.setAttribute("msg", "您的帐号已经在其他机器上登录,您被迫下线。"); - } - map.put("personInfo", event.getSession()); - } - } + private final Logger logger = LoggerFactory.getLogger(getClass()); + + Map map = new HashMap(); + + @Override + public void attributeAdded(HttpSessionBindingEvent event) { + String name = event.getName(); + + // 登录 + if (name.equals("personInfo")) { + + PersonInfo personInfo = (PersonInfo) event.getValue(); + + if (map.get(personInfo.getAccount()) != null) { + + // map 中有记录,表明该帐号在其他机器上登录过,将以前的登录失效 + HttpSession session = map.get(personInfo.getAccount()); + PersonInfo oldPersonInfo = (PersonInfo) session.getAttribute("personInfo"); + + logger.debug("帐号" + oldPersonInfo.getAccount() + "在" + oldPersonInfo.getIp() + "已经登录,该登录将被迫下线。"); + + session.removeAttribute("personInfo"); + session.setAttribute("msg", "您的帐号已经在其他机器上登录,您被迫下线。"); + } + + // 将session以用户名为索引,放入map中 + map.put(personInfo.getAccount(), event.getSession()); + logger.debug("帐号" + personInfo.getAccount() + "在" + personInfo.getIp() + "登录。"); + } + } + + @Override + public void attributeRemoved(HttpSessionBindingEvent event) { + String name = event.getName(); + + // 注销 + if (name.equals("personInfo")) { + // 将该session从map中移除 + PersonInfo personInfo = (PersonInfo) event.getValue(); + map.remove(personInfo.getAccount()); + logger.debug("帐号" + personInfo.getAccount() + "注销。"); + } + } + + @Override + public void attributeReplaced(HttpSessionBindingEvent event) { + String name = event.getName(); + + // 没有注销的情况下,用另一个帐号登录 + if (name.equals("personInfo")) { + + // 移除旧的的登录信息 + PersonInfo oldPersonInfo = (PersonInfo) event.getValue(); + map.remove(oldPersonInfo.getAccount()); + + // 新的登录信息 + PersonInfo personInfo = (PersonInfo) event.getSession().getAttribute("personInfo"); + + // 也要检查新登录的帐号是否在别的机器上登录过 + if (map.get(personInfo.getAccount()) != null) { + // map 中有记录,表明该帐号在其他机器上登录过,将以前的登录失效 + HttpSession session = map.get(personInfo.getAccount()); + session.removeAttribute("personInfo"); + session.setAttribute("msg", "您的帐号已经在其他机器上登录,您被迫下线。"); + } + map.put("personInfo", event.getSession()); + } + } } diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionActivationListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionActivationListener.java index 24c1a9ea..4c8360d0 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionActivationListener.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionActivationListener.java @@ -15,31 +15,33 @@ */ public class MyHttpSessionActivationListener implements HttpSessionActivationListener, Serializable { + private static final long serialVersionUID = 1L; - private static final long serialVersionUID = 1L; - private final Logger logger = LoggerFactory.getLogger(getClass()); - private String name; + private final Logger logger = LoggerFactory.getLogger(getClass()); - @Override - public void sessionWillPassivate(HttpSessionEvent se) { + private String name; - logger.debug(name + "和session一起被序列化到硬盘了,session的id是:" + se.getSession().getId()); - } + public MyHttpSessionActivationListener(String name) { + this.name = name; + } - @Override - public void sessionDidActivate(HttpSessionEvent se) { - logger.debug(name + "和session一起从硬盘反序列化回到内存了,session的id是:" + se.getSession().getId()); - } + @Override + public void sessionWillPassivate(HttpSessionEvent se) { - public String getName() { - return name; - } + logger.debug(name + "和session一起被序列化到硬盘了,session的id是:" + se.getSession().getId()); + } - public void setName(String name) { - this.name = name; - } + @Override + public void sessionDidActivate(HttpSessionEvent se) { + logger.debug(name + "和session一起从硬盘反序列化回到内存了,session的id是:" + se.getSession().getId()); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } - public MyHttpSessionActivationListener(String name) { - this.name = name; - } } diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionAttributeListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionAttributeListener.java index 19a3e1a5..d233a001 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionAttributeListener.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionAttributeListener.java @@ -15,30 +15,31 @@ */ public class MyHttpSessionAttributeListener implements HttpSessionAttributeListener { - private final Logger logger = LoggerFactory.getLogger(getClass()); - - // 添加属性 - @Override - public void attributeAdded(HttpSessionBindingEvent se) { - HttpSession session = se.getSession(); - String name = se.getName(); - logger.info("新建session属性:" + name + ", 值为:" + se.getValue()); - } - - // 删除属性 - @Override - public void attributeRemoved(HttpSessionBindingEvent se) { - HttpSession session = se.getSession(); - String name = se.getName(); - logger.info("删除session属性:" + name + ", 值为:" + se.getValue()); - } - - // 修改属性 - @Override - public void attributeReplaced(HttpSessionBindingEvent se) { - HttpSession session = se.getSession(); - String name = se.getName(); - Object oldValue = se.getValue(); - logger.info("修改session属性:" + name + ", 原值:" + oldValue + ", 新值:" + session.getAttribute(name)); - } + private final Logger logger = LoggerFactory.getLogger(getClass()); + + // 添加属性 + @Override + public void attributeAdded(HttpSessionBindingEvent se) { + HttpSession session = se.getSession(); + String name = se.getName(); + logger.info("新建session属性:" + name + ", 值为:" + se.getValue()); + } + + // 删除属性 + @Override + public void attributeRemoved(HttpSessionBindingEvent se) { + HttpSession session = se.getSession(); + String name = se.getName(); + logger.info("删除session属性:" + name + ", 值为:" + se.getValue()); + } + + // 修改属性 + @Override + public void attributeReplaced(HttpSessionBindingEvent se) { + HttpSession session = se.getSession(); + String name = se.getName(); + Object oldValue = se.getValue(); + logger.info("修改session属性:" + name + ", 原值:" + oldValue + ", 新值:" + session.getAttribute(name)); + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionBindingListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionBindingListener.java index e6a190ec..c0678da1 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionBindingListener.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionBindingListener.java @@ -1,28 +1,29 @@ package io.github.dunwu.javaee.listener; -import javax.servlet.http.HttpSessionBindingEvent; -import javax.servlet.http.HttpSessionBindingListener; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionBindingListener; + /** - * HttpSessionBindingListener 接口的 JavaBean 对象可以感知自己被绑定或解绑定到 Session 中的事件。 + * HttpSessionBindingListener 接口的 JavaBean 对象可以感知自己被绑定或解绑定到 Session 中的事件。 + * * @author Zhang Peng * @since 2017-04-04 */ public class MyHttpSessionBindingListener implements HttpSessionBindingListener { - private final Logger logger = LoggerFactory.getLogger(getClass()); + private final Logger logger = LoggerFactory.getLogger(getClass()); - @Override - public void valueBound(HttpSessionBindingEvent event) { - logger.debug("HttpSessionBinding valueBound"); - } + @Override + public void valueBound(HttpSessionBindingEvent event) { + logger.debug("HttpSessionBinding valueBound"); + } - @Override - public void valueUnbound(HttpSessionBindingEvent event) { - logger.debug("HttpSessionBinding valueUnbound"); - } + @Override + public void valueUnbound(HttpSessionBindingEvent event) { + logger.debug("HttpSessionBinding valueUnbound"); + } } diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionListener.java index f1b21bc9..be295497 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionListener.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionListener.java @@ -1,47 +1,49 @@ package io.github.dunwu.javaee.listener; -import java.util.Date; +import io.github.dunwu.javaee.listener.util.ApplicationConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionListener; - -import io.github.dunwu.javaee.listener.util.ApplicationConstants; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import java.util.Date; /** * HttpSessionListener 接口用于监听 HttpSession 对象的创建和销毁。 + * * @author Zhang Peng * @since 2017-04-04 */ public class MyHttpSessionListener implements HttpSessionListener { - private final Logger logger = LoggerFactory.getLogger(getClass()); - - @Override - public void sessionCreated(HttpSessionEvent se) { - HttpSession session = se.getSession(); - - // 将 session 放入 map - ApplicationConstants.SESSION_MAP.put(session.getId(), session); - // 总访问人数++ - ApplicationConstants.TOTAL_HISTORY_COUNT++; - - // 如果当前在线人数超过历史记录,则更新最大在线人数,并记录时间 - if (ApplicationConstants.SESSION_MAP.size() > ApplicationConstants.MAX_ONLINE_COUNT) { - ApplicationConstants.MAX_ONLINE_COUNT = ApplicationConstants.SESSION_MAP.size(); - ApplicationConstants.MAX_ONLINE_COUNT_DATE = new Date(); - } - - logger.debug("创建了一个session: {}", session); - } - - @Override - public void sessionDestroyed(HttpSessionEvent se) { - HttpSession session = se.getSession(); - // 将session从map中移除 - ApplicationConstants.SESSION_MAP.remove(session.getId()); - - logger.debug("销毁了一个session: {}", session); - } + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + @Override + public void sessionCreated(HttpSessionEvent se) { + HttpSession session = se.getSession(); + + // 将 session 放入 map + ApplicationConstants.SESSION_MAP.put(session.getId(), session); + // 总访问人数++ + ApplicationConstants.TOTAL_HISTORY_COUNT++; + + // 如果当前在线人数超过历史记录,则更新最大在线人数,并记录时间 + if (ApplicationConstants.SESSION_MAP.size() > ApplicationConstants.MAX_ONLINE_COUNT) { + ApplicationConstants.MAX_ONLINE_COUNT = ApplicationConstants.SESSION_MAP.size(); + ApplicationConstants.MAX_ONLINE_COUNT_DATE = new Date(); + } + + logger.debug("创建了一个session: {}", session); + } + + @Override + public void sessionDestroyed(HttpSessionEvent se) { + HttpSession session = se.getSession(); + // 将session从map中移除 + ApplicationConstants.SESSION_MAP.remove(session.getId()); + + logger.debug("销毁了一个session: {}", session); + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextAttributeListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextAttributeListener.java index cbfe7716..2d4c451c 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextAttributeListener.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextAttributeListener.java @@ -14,20 +14,21 @@ */ public class MyServletContextAttributeListener implements ServletContextAttributeListener { - private final Logger logger = LoggerFactory.getLogger(getClass()); - - @Override - public void attributeAdded(ServletContextAttributeEvent scab) { - logger.debug("ServletContext域对象中添加了属性:{},属性值是:{}", scab.getName(), scab.getValue()); - } - - @Override - public void attributeRemoved(ServletContextAttributeEvent scab) { - logger.debug("ServletContext域对象中删除了属性:{},属性值是:{}", scab.getName(), scab.getValue()); - } - - @Override - public void attributeReplaced(ServletContextAttributeEvent scab) { - logger.debug("ServletContext域对象中替换了属性:{},原值是:{}, 现值是:{}", scab.getName(), scab.getSource(), scab.getValue()); - } + private final Logger logger = LoggerFactory.getLogger(getClass()); + + @Override + public void attributeAdded(ServletContextAttributeEvent scab) { + logger.debug("ServletContext域对象中添加了属性:{},属性值是:{}", scab.getName(), scab.getValue()); + } + + @Override + public void attributeRemoved(ServletContextAttributeEvent scab) { + logger.debug("ServletContext域对象中删除了属性:{},属性值是:{}", scab.getName(), scab.getValue()); + } + + @Override + public void attributeReplaced(ServletContextAttributeEvent scab) { + logger.debug("ServletContext域对象中替换了属性:{},原值是:{}, 现值是:{}", scab.getName(), scab.getSource(), scab.getValue()); + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextListener.java index 2f0b96f9..c25a406c 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextListener.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextListener.java @@ -17,22 +17,23 @@ */ public class MyServletContextListener implements ServletContextListener { - private final Logger logger = LoggerFactory.getLogger(getClass()); + private final Logger logger = LoggerFactory.getLogger(getClass()); - @Override - public void contextInitialized(ServletContextEvent event) { - // 启动时,记录服务器启动时间 - ApplicationConstants.START_DATE = new Date(); - ServletContext servletContext = event.getServletContext(); - logger.info("即将启动 {}", servletContext.getContextPath()); - } + @Override + public void contextInitialized(ServletContextEvent event) { + // 启动时,记录服务器启动时间 + ApplicationConstants.START_DATE = new Date(); + ServletContext servletContext = event.getServletContext(); + logger.info("即将启动 {}", servletContext.getContextPath()); + } + + @Override + public void contextDestroyed(ServletContextEvent event) { + // 关闭时,将结果清除。也可以将结果保存到硬盘上。 + ApplicationConstants.START_DATE = null; + ApplicationConstants.MAX_ONLINE_COUNT_DATE = null; + ServletContext servletContext = event.getServletContext(); + logger.info("即将关闭 {}", servletContext.getContextPath()); + } - @Override - public void contextDestroyed(ServletContextEvent event) { - // 关闭时,将结果清除。也可以将结果保存到硬盘上。 - ApplicationConstants.START_DATE = null; - ApplicationConstants.MAX_ONLINE_COUNT_DATE = null; - ServletContext servletContext = event.getServletContext(); - logger.info("即将关闭 {}", servletContext.getContextPath()); - } } diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestAttributeListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestAttributeListener.java index 3693736b..3caedbd1 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestAttributeListener.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestAttributeListener.java @@ -14,20 +14,21 @@ */ public class MyServletRequestAttributeListener implements ServletRequestAttributeListener { - private final Logger logger = LoggerFactory.getLogger(getClass()); - - @Override - public void attributeAdded(ServletRequestAttributeEvent srae) { - logger.debug("ServletRequest域对象中添加了属性:{},属性值是:{}", srae.getName(), srae.getValue()); - } - - @Override - public void attributeRemoved(ServletRequestAttributeEvent srae) { - logger.debug("ServletRequest域对象中删除了属性:{},属性值是:{}", srae.getName(), srae.getValue()); - } - - @Override - public void attributeReplaced(ServletRequestAttributeEvent srae) { - logger.debug("ServletRequest域对象中替换了属性:{},原值是:{}, 现值是:{}", srae.getName(), srae.getSource(), srae.getValue()); - } + private final Logger logger = LoggerFactory.getLogger(getClass()); + + @Override + public void attributeAdded(ServletRequestAttributeEvent srae) { + logger.debug("ServletRequest域对象中添加了属性:{},属性值是:{}", srae.getName(), srae.getValue()); + } + + @Override + public void attributeRemoved(ServletRequestAttributeEvent srae) { + logger.debug("ServletRequest域对象中删除了属性:{},属性值是:{}", srae.getName(), srae.getValue()); + } + + @Override + public void attributeReplaced(ServletRequestAttributeEvent srae) { + logger.debug("ServletRequest域对象中替换了属性:{},原值是:{}, 现值是:{}", srae.getName(), srae.getSource(), srae.getValue()); + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestListener.java index 578e83ed..3a28b414 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestListener.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestListener.java @@ -1,59 +1,61 @@ package io.github.dunwu.javaee.listener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - /** * ServletRequestListener 接口用于监听 ServletRequest 对象的创建和销毁。 + * * @author Zhang Peng * @since 2017-04-04 */ public class MyServletRequestListener implements ServletRequestListener { - private final Logger logger = LoggerFactory.getLogger(getClass()); + private final Logger logger = LoggerFactory.getLogger(getClass()); - @Override - public void requestInitialized(ServletRequestEvent event) { + @Override + public void requestInitialized(ServletRequestEvent event) { - HttpServletRequest request = (HttpServletRequest) event.getServletRequest(); + HttpServletRequest request = (HttpServletRequest) event.getServletRequest(); - HttpSession session = request.getSession(true); + HttpSession session = request.getSession(true); - // 记录IP地址 - session.setAttribute("ip", request.getRemoteAddr()); + // 记录IP地址 + session.setAttribute("ip", request.getRemoteAddr()); - // 记录访问次数,只记录访问 .html, .do, .jsp, .action 的累计次数 - String uri = request.getRequestURI(); - uri = request.getQueryString() == null ? uri : (uri + "?" + request.getQueryString()); - request.setAttribute("dateCreated", System.currentTimeMillis()); - String[] suffix = {".html", ".do", ".jsp", ".action"}; - for (int i = 0; i < suffix.length; i++) { - if (uri.endsWith(suffix[i])) { - break; - } - if (i == suffix.length - 1) return; - } + // 记录访问次数,只记录访问 .html, .do, .jsp, .action 的累计次数 + String uri = request.getRequestURI(); + uri = request.getQueryString() == null ? uri : (uri + "?" + request.getQueryString()); + request.setAttribute("dateCreated", System.currentTimeMillis()); + String[] suffix = { ".html", ".do", ".jsp", ".action" }; + for (int i = 0; i < suffix.length; i++) { + if (uri.endsWith(suffix[i])) { + break; + } + if (i == suffix.length - 1) + return; + } - Integer activeTimes = (Integer) session.getAttribute("activeTimes"); + Integer activeTimes = (Integer) session.getAttribute("activeTimes"); - if (activeTimes == null) { - activeTimes = 0; - } + if (activeTimes == null) { + activeTimes = 0; + } - session.setAttribute("activeTimes", activeTimes + 1); - logger.debug("IP: {} 请求 {}", request.getRemoteAddr(), uri); - } + session.setAttribute("activeTimes", activeTimes + 1); + logger.debug("IP: {} 请求 {}", request.getRemoteAddr(), uri); + } - @Override - public void requestDestroyed(ServletRequestEvent event) { - HttpServletRequest request = (HttpServletRequest) event.getServletRequest(); - long time = System.currentTimeMillis() - (Long) request.getAttribute("dateCreated"); - logger.debug("{} 请求处理结束, 用时 {} 毫秒", request.getRemoteAddr(), time); - } -} + @Override + public void requestDestroyed(ServletRequestEvent event) { + HttpServletRequest request = (HttpServletRequest) event.getServletRequest(); + long time = System.currentTimeMillis() - (Long) request.getAttribute("dateCreated"); + logger.debug("{} 请求处理结束, 用时 {} 毫秒", request.getRemoteAddr(), time); + } +} diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/PersonInfoListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/PersonInfoListener.java index da0c4cf6..22fc6f5b 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/PersonInfoListener.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/PersonInfoListener.java @@ -1,27 +1,21 @@ package io.github.dunwu.javaee.listener; -import java.io.Serializable; -import java.util.Date; - -import javax.servlet.http.HttpSession; -import javax.servlet.http.HttpSessionActivationListener; -import javax.servlet.http.HttpSessionBindingEvent; -import javax.servlet.http.HttpSessionBindingListener; -import javax.servlet.http.HttpSessionEvent; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.servlet.http.*; +import java.io.Serializable; +import java.util.Date; + /** * @author Zhang Peng * @since 2017-04-04 */ -public class PersonInfoListener implements HttpSessionActivationListener, - HttpSessionBindingListener, Serializable { +public class PersonInfoListener implements HttpSessionActivationListener, HttpSessionBindingListener, Serializable { private static final long serialVersionUID = -4780592776386225973L; - private final Logger logger = LoggerFactory.getLogger(getClass()); + private final Logger logger = LoggerFactory.getLogger(getClass()); private String name; @@ -59,8 +53,7 @@ public void sessionWillPassivate(HttpSessionEvent se) { public void valueBound(HttpSessionBindingEvent event) { HttpSession session = event.getSession(); String name = event.getName(); - logger.info(this + "被绑定到session \"" + session.getId() + "\"的" + name - + "属性上"); + logger.info(this + "被绑定到session \"" + session.getId() + "\"的" + name + "属性上"); // 记录放到session中的时间 this.setDateCreated(new Date()); @@ -70,8 +63,7 @@ public void valueBound(HttpSessionBindingEvent event) { public void valueUnbound(HttpSessionBindingEvent event) { HttpSession session = event.getSession(); String name = event.getName(); - logger.info(this + "被从session \"" + session.getId() + "\"的" + name - + "属性上移除"); + logger.info(this + "被从session \"" + session.getId() + "\"的" + name + "属性上移除"); } @Override diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/bean/PersonInfo.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/bean/PersonInfo.java index 73ffb230..24098f6c 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/bean/PersonInfo.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/bean/PersonInfo.java @@ -5,46 +5,47 @@ public class PersonInfo implements Serializable { - private static final long serialVersionUID = 4063725584941336123L; + private static final long serialVersionUID = 4063725584941336123L; - // 帐号 - private String account; + // 帐号 + private String account; - // 登录IP地址 - private String ip; + // 登录IP地址 + private String ip; - // 登录时间 - private Date loginDate; + // 登录时间 + private Date loginDate; - public String getAccount() { - return account; - } + public String getAccount() { + return account; + } - public void setAccount(String account) { - this.account = account; - } + public void setAccount(String account) { + this.account = account; + } - public String getIp() { - return ip; - } + public String getIp() { + return ip; + } - public void setIp(String ip) { - this.ip = ip; - } + public void setIp(String ip) { + this.ip = ip; + } - public Date getLoginDate() { - return loginDate; - } + public Date getLoginDate() { + return loginDate; + } - public void setLoginDate(Date loginDate) { - this.loginDate = loginDate; - } + public void setLoginDate(Date loginDate) { + this.loginDate = loginDate; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof PersonInfo)) { + return false; + } + return account.equalsIgnoreCase(((PersonInfo) obj).getAccount()); + } - @Override - public boolean equals(Object obj) { - if (obj == null || !(obj instanceof PersonInfo)) { - return false; - } - return account.equalsIgnoreCase(((PersonInfo) obj).getAccount()); - } } diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/util/ApplicationConstants.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/util/ApplicationConstants.java index bbdbceaa..327daabb 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/util/ApplicationConstants.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/util/ApplicationConstants.java @@ -1,28 +1,28 @@ package io.github.dunwu.javaee.listener.util; +import javax.servlet.http.HttpSession; import java.util.Date; import java.util.HashMap; import java.util.Map; -import javax.servlet.http.HttpSession; - public class ApplicationConstants { - // 所有的 Session - public static Map SESSION_MAP = new HashMap(); + // 所有的 Session + public static Map SESSION_MAP = new HashMap(); + + // 当前登录的用户总数 + public static int CURRENT_LOGIN_COUNT = 0; - // 当前登录的用户总数 - public static int CURRENT_LOGIN_COUNT = 0; + // 历史访客总数 + public static int TOTAL_HISTORY_COUNT = 0; - // 历史访客总数 - public static int TOTAL_HISTORY_COUNT = 0; + // 服务器启动时间 + public static Date START_DATE = new Date(); - // 服务器启动时间 - public static Date START_DATE = new Date(); + // 最高在线时间 + public static Date MAX_ONLINE_COUNT_DATE = new Date(); - // 最高在线时间 - public static Date MAX_ONLINE_COUNT_DATE = new Date(); + // 最高在线人数 + public static int MAX_ONLINE_COUNT = 0; - // 最高在线人数 - public static int MAX_ONLINE_COUNT = 0; } diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/resources/logback.xml index c137b1c8..6d2b289d 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/resources/logback.xml +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/resources/logback.xml @@ -2,7 +2,7 @@ - + @@ -33,13 +33,13 @@ - - + + - - + + diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/META-INF/context.xml b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/META-INF/context.xml index c57698e0..2c49bcda 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/META-INF/context.xml +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/META-INF/context.xml @@ -1,5 +1,5 @@ - - - - \ No newline at end of file + + + + diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/WEB-INF/resources/jsp/index.jsp b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/WEB-INF/resources/jsp/index.jsp index 74319429..bde68afd 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/WEB-INF/resources/jsp/index.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/WEB-INF/resources/jsp/index.jsp @@ -1,16 +1,16 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> -My JSP 'index.jsp' starting page - - - - + My JSP 'index.jsp' starting page + + + + -

This is my JSP page.

+

This is my JSP page.

diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/WEB-INF/web.xml b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/WEB-INF/web.xml index 36c01cd6..f68bdb1d 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/WEB-INF/web.xml +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/WEB-INF/web.xml @@ -1,5 +1,5 @@ - diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/aa.html b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/aa.html index 4153961f..991b72a1 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/aa.html +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/aa.html @@ -3,10 +3,10 @@

sadfsdf

    -
  1. sadf
  2. -
  3. asdfsadf
  4. -
  5. sdf
  6. -
  7. sdfsadfasdf
  8. +
  9. sadf
  10. +
  11. asdfsadf
  12. +
  13. sdf
  14. +
  15. sdfsadfasdf
diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/listener.jsp b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/listener.jsp index fb7dbd3c..136f197f 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/listener.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/listener.jsp @@ -1,49 +1,51 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> - - + import="io.github.dunwu.javaee.listener.util.ApplicationConstants"/> + + <% - String action = request.getParameter("action"); - String name = request.getParameter("name"); - String value = request.getParameter("value"); + String action = request.getParameter("action"); + String name = request.getParameter("name"); + String value = request.getParameter("value"); - session.getId(); + session.getId(); - if ("addRequestAttribute".equals(action)) { - request.setAttribute(name, value); - } else if ("removeRequestAttribute".equals(action)) { - request.removeAttribute(name); - } else if ("addSessionAttribute".equals(action)) { - session.setAttribute(name, value); - } else if ("removeSessionAttribute".equals(action)) { - session.removeAttribute(name); - } else if ("logout".equals(action)) { - session.invalidate(); - out.println("返回"); - return; - } + if ("addRequestAttribute".equals(action)) { + request.setAttribute(name, value); + } else if ("removeRequestAttribute".equals(action)) { + request.removeAttribute(name); + } else if ("addSessionAttribute".equals(action)) { + session.setAttribute(name, value); + } else if ("removeSessionAttribute".equals(action)) { + session.removeAttribute(name); + } else if ("logout".equals(action)) { + session.invalidate(); + out.println("返回"); + return; + } %> - + - - - - + + + +
-
-
-
删除 Session
-
+
+
+
删除 Session
+
-
-
-
+
+
+
服务器启动时间:<%=DateFormat.getDateTimeInstance().format(ApplicationConstants.START_DATE)%>, 累计共接待过 <%= ApplicationConstants.TOTAL_HISTORY_COUNT %> 访客。
@@ -52,33 +54,38 @@ 目前在线总数:<%= ApplicationConstants.SESSION_MAP.size() %>,登录用户:<%=ApplicationConstants.CURRENT_LOGIN_COUNT%>。
- - - - - - - - - - <% - for (String id : ApplicationConstants.SESSION_MAP.keySet()) { - HttpSession sess = ApplicationConstants.SESSION_MAP.get(id); - %> - - - - - - - - - - <% - } - %> + + + + + + + + + + <% + for (String id : ApplicationConstants.SESSION_MAP.keySet()) { + HttpSession sess = ApplicationConstants.SESSION_MAP.get(id); + %> + + + + + + + + + + <% + } + %>
jsessionidaccountcreationTimelastAccessedTimenewactiveTimesip
<%=id%><%=sess.getAttribute("account")%><%=DateFormat.getDateTimeInstance().format( - new Date(sess.getCreationTime()))%><%=DateFormat.getDateTimeInstance().format( - new Date(sess.getLastAccessedTime()))%><%=sess.isNew()%><%=sess.getAttribute("activeTimes")%><%=sess.getAttribute("ip")%>
jsessionidaccountcreationTimelastAccessedTimenewactiveTimesip
<%=id%> + <%=sess.getAttribute("account")%> + <%=DateFormat.getDateTimeInstance().format(new Date(sess.getCreationTime()))%> + <%=DateFormat.getDateTimeInstance().format(new Date(sess.getLastAccessedTime()))%> + <%=sess.isNew()%> + <%=sess.getAttribute("activeTimes")%> + <%=sess.getAttribute("ip")%> +
diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/online.jsp b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/online.jsp index 2a03d70b..fb730d0e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/online.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/online.jsp @@ -1,11 +1,13 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> - - - + import="io.github.dunwu.javaee.listener.bean.PersonInfo"/> + + + 服务器启动时间:<%=DateFormat.getDateTimeInstance().format(ApplicationConstants.START_DATE)%>, @@ -15,33 +17,39 @@ 目前在线总数:<%= ApplicationConstants.SESSION_MAP.size() %>,登录用户:<%=ApplicationConstants.CURRENT_LOGIN_COUNT%>。
- - - - - - - - - - <% - for (String id : ApplicationConstants.SESSION_MAP.keySet()) { - HttpSession sess = ApplicationConstants.SESSION_MAP.get(id); - PersonInfo personInfo = (PersonInfo)sess.getAttribute("personInfo"); - %> - > - - - - - - - - - <% - } - %> + + + + + + + + + + <% + for (String id : ApplicationConstants.SESSION_MAP.keySet()) { + HttpSession sess = ApplicationConstants.SESSION_MAP.get(id); + PersonInfo personInfo = (PersonInfo) sess.getAttribute("personInfo"); + %> + > + + + + + + + + + <% + } + %>
jsessionidaccountcreationTimelastAccessedTimenewactiveTimesip
<%=id%><%=personInfo==null ? " " : personInfo.getAccount()%><%=DateFormat.getDateTimeInstance().format(sess.getCreationTime())%><%=DateFormat.getDateTimeInstance().format( - new Date(sess.getLastAccessedTime()))%><%=sess.isNew()%><%=sess.getAttribute("activeTimes")%><%=sess.getAttribute("ip") %>
jsessionidaccountcreationTimelastAccessedTimenewactiveTimesip
<%=id%> + <%=personInfo == null ? " " : personInfo.getAccount()%> + <%=DateFormat.getDateTimeInstance().format(sess.getCreationTime())%> + <%=DateFormat.getDateTimeInstance().format(new Date(sess.getLastAccessedTime()))%> + <%=sess.isNew()%> + <%=sess.getAttribute("activeTimes")%> + <%=sess.getAttribute("ip") %> +
diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/testLoginSessionListener.jsp b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/testLoginSessionListener.jsp index 1e60c1a9..c3f0679d 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/testLoginSessionListener.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/testLoginSessionListener.jsp @@ -1,71 +1,70 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> - + <% - String action = request.getParameter("action"); - String account = request.getParameter("account"); + String action = request.getParameter("action"); + String account = request.getParameter("account"); - if("login".equals(action) && account.trim().length() > 0){ + if ("login".equals(action) && account.trim().length() > 0) { - // 登录,将personInfo放入session - PersonInfo personInfo = new PersonInfo(); - personInfo.setAccount(account.trim().toLowerCase()); - personInfo.setIp(request.getRemoteAddr()); - personInfo.setLoginDate(new java.util.Date()); + // 登录,将personInfo放入session + PersonInfo personInfo = new PersonInfo(); + personInfo.setAccount(account.trim().toLowerCase()); + personInfo.setIp(request.getRemoteAddr()); + personInfo.setLoginDate(new java.util.Date()); - session.setAttribute("personInfo", personInfo); + session.setAttribute("personInfo", personInfo); - response.sendRedirect(response.encodeRedirectURL(request.getRequestURI())); - return; - } - else if("logout".equals(action)){ + response.sendRedirect(response.encodeRedirectURL(request.getRequestURI())); + return; + } else if ("logout".equals(action)) { - // 注销,将personInfo从session中移除 - session.removeAttribute("personInfo"); + // 注销,将personInfo从session中移除 + session.removeAttribute("personInfo"); - response.sendRedirect(response.encodeRedirectURL(request.getRequestURI())); - return; - } + response.sendRedirect(response.encodeRedirectURL(request.getRequestURI())); + return; + } %> - - - Insert title here - - - + + + Insert title here + + + - + - - - 欢迎您,${ personInfo.account }。
- 您的登录IP为${ personInfo.ip },
- 登录时间为。 - 退出 + + + 欢迎您,${ personInfo.account }。
+ 您的登录IP为${ personInfo.ip },
+ 登录时间为。 + 退出 - - -
+ + +
- - - ${ msg } - -
- 帐号: - - -
-
+ + + ${ msg } + +
+ 帐号: + + +
+
-
+
- + diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java index f9eb3106..b04f2c2b 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java @@ -9,113 +9,116 @@ import java.util.List; /** - * JettyFactory 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 + * JettyFactory 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij + * 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 * * @author Zhang Peng */ @SuppressWarnings("all") public class JettyFactory { - private static final int PORT = 9527; - private static final String CONTEXT = "/"; - private static final String RESOURCE_BASE_PATH = "src/main/webapp"; - private static final String WEB_XML_PATH = "/WEB-INF/web.xml"; - private static final String[] TLD_JAR_NAMES = new String[] {"sitemesh", "spring-webmvc", "tiles"}; - private static final String WINDOWS_WEBDEFAULT_PATH = "jetty/webdefault.xml"; - - public static final int IDE_ECLIPSE = 0; - public static final int IDE_INTELLIJ = 1; - - public static final String ACTIVE_PROFILE = "spring.profiles.active"; - public static final String DEFAULT_PROFILE = "spring.profiles.default"; - public static final String DEVELOPMENT = "development"; - - /** - * 在Spring启动前,设置profile的环境变量。 - */ - public static void setProfileAsSystemProperty(String profile) { - System.setProperty(ACTIVE_PROFILE, profile); - } - - - public static Server initServer() { - setProfileAsSystemProperty(DEVELOPMENT); - WebAppContext webAppContext = new WebAppContext(); - Server server = new Server(PORT); - server.setHandler(webAppContext); - return server; - } - - public static void initWebAppContext(Server server, int type) throws Exception { - System.out.println("[INFO] Application loading"); - WebAppContext webAppContext = (WebAppContext) server.getHandler(); - webAppContext.setContextPath(CONTEXT); - webAppContext.setResourceBase(getAbsolutePath() + RESOURCE_BASE_PATH); - webAppContext.setDescriptor(getAbsolutePath() + RESOURCE_BASE_PATH + WEB_XML_PATH); - - if (IDE_INTELLIJ == type) { - webAppContext.setDefaultsDescriptor(WINDOWS_WEBDEFAULT_PATH); - supportJspAndSetTldJarNames(server, TLD_JAR_NAMES); - } else { - webAppContext.setParentLoaderPriority(true); - } - - System.out.println("[INFO] Application loaded"); - } - - public static void reloadWebAppContext(Server server) throws Exception { - WebAppContext webAppContext = (WebAppContext) server.getHandler(); - System.out.println("[INFO] Application reloading"); - webAppContext.stop(); - WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext); - classLoader.addClassPath(getAbsolutePath() + "target/classes"); - classLoader.addClassPath(getAbsolutePath() + "target/test-classes"); - webAppContext.setClassLoader(classLoader); - webAppContext.start(); - System.out.println("[INFO] Application reloaded"); - } - - - public static void startServer(Server server) throws Exception { - System.out.println("[HINT] Don't forget to set -XX:MaxPermSize=128m"); - server.start(); - System.out.println("Server running at http://localhost:" + PORT + CONTEXT); - System.out.println("[HINT] Hit Enter to reload the application quickly"); - } - - public static String getAbsolutePath() { - String path = null; - String folderPath = - JettyFactory.class.getProtectionDomain().getCodeSource().getLocation().getPath().substring(1); - if (folderPath.indexOf("target") > 0) { - path = folderPath.substring(0, folderPath.indexOf("target")); - } - return path; - } - - public static void supportJspAndSetTldJarNames(Server server, String... jarNames) { - WebAppContext context = (WebAppContext) server.getHandler(); - // This webapp will use jsps and jstl. We need to enable the AnnotationConfiguration in - // order to correctly set up the jsp container - org.eclipse.jetty.webapp.Configuration.ClassList classlist = - org.eclipse.jetty.webapp.Configuration.ClassList.setServerDefault(server); - classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", - "org.eclipse.jetty.annotations.AnnotationConfiguration"); - // Set the ContainerIncludeJarPattern so that jetty examines these container-path jars for - // tlds, web-fragments etc. - // If you omit the jar that contains the jstl .tlds, the jsp engine will scan for them - // instead. - - List list = new ArrayList<>(); - list.add(".*/[^/]*servlet-api-[^/]*\\.jar$"); - list.add(".*/javax.servlet.jsp.jstl-.*\\.jar$"); - list.add(".*/[^/]*taglibs.*\\.jar$"); - - for (String jarName : jarNames) { - String str = ".*/" + jarName + "-[^/]*\\.jar$"; - list.add(str); - } - - context.setAttribute("org.eclipse.jetty.io.github.dunwu.javaee.server.webapp.ContainerIncludeJarPattern", - StringUtils.join(list, '|')); - } + + public static final int IDE_ECLIPSE = 0; + public static final int IDE_INTELLIJ = 1; + public static final String ACTIVE_PROFILE = "spring.profiles.active"; + public static final String DEFAULT_PROFILE = "spring.profiles.default"; + public static final String DEVELOPMENT = "development"; + private static final int PORT = 9527; + private static final String CONTEXT = "/"; + private static final String RESOURCE_BASE_PATH = "src/main/webapp"; + private static final String WEB_XML_PATH = "/WEB-INF/web.xml"; + private static final String[] TLD_JAR_NAMES = new String[] { "sitemesh", "spring-webmvc", "tiles" }; + private static final String WINDOWS_WEBDEFAULT_PATH = "jetty/webdefault.xml"; + + /** + * 在Spring启动前,设置profile的环境变量。 + */ + public static void setProfileAsSystemProperty(String profile) { + System.setProperty(ACTIVE_PROFILE, profile); + } + + public static Server initServer() { + setProfileAsSystemProperty(DEVELOPMENT); + WebAppContext webAppContext = new WebAppContext(); + Server server = new Server(PORT); + server.setHandler(webAppContext); + return server; + } + + public static void initWebAppContext(Server server, int type) throws Exception { + System.out.println("[INFO] Application loading"); + WebAppContext webAppContext = (WebAppContext) server.getHandler(); + webAppContext.setContextPath(CONTEXT); + webAppContext.setResourceBase(getAbsolutePath() + RESOURCE_BASE_PATH); + webAppContext.setDescriptor(getAbsolutePath() + RESOURCE_BASE_PATH + WEB_XML_PATH); + + if (IDE_INTELLIJ == type) { + webAppContext.setDefaultsDescriptor(WINDOWS_WEBDEFAULT_PATH); + supportJspAndSetTldJarNames(server, TLD_JAR_NAMES); + } + else { + webAppContext.setParentLoaderPriority(true); + } + + System.out.println("[INFO] Application loaded"); + } + + public static void reloadWebAppContext(Server server) throws Exception { + WebAppContext webAppContext = (WebAppContext) server.getHandler(); + System.out.println("[INFO] Application reloading"); + webAppContext.stop(); + WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext); + classLoader.addClassPath(getAbsolutePath() + "target/classes"); + classLoader.addClassPath(getAbsolutePath() + "target/test-classes"); + webAppContext.setClassLoader(classLoader); + webAppContext.start(); + System.out.println("[INFO] Application reloaded"); + } + + public static void startServer(Server server) throws Exception { + System.out.println("[HINT] Don't forget to set -XX:MaxPermSize=128m"); + server.start(); + System.out.println("Server running at http://localhost:" + PORT + CONTEXT); + System.out.println("[HINT] Hit Enter to reload the application quickly"); + } + + public static String getAbsolutePath() { + String path = null; + String folderPath = JettyFactory.class.getProtectionDomain().getCodeSource().getLocation().getPath() + .substring(1); + if (folderPath.indexOf("target") > 0) { + path = folderPath.substring(0, folderPath.indexOf("target")); + } + return path; + } + + public static void supportJspAndSetTldJarNames(Server server, String... jarNames) { + WebAppContext context = (WebAppContext) server.getHandler(); + // This webapp will use jsps and jstl. We need to enable the + // AnnotationConfiguration in + // order to correctly set up the jsp container + org.eclipse.jetty.webapp.Configuration.ClassList classlist = org.eclipse.jetty.webapp.Configuration.ClassList + .setServerDefault(server); + classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", + "org.eclipse.jetty.annotations.AnnotationConfiguration"); + // Set the ContainerIncludeJarPattern so that jetty examines these container-path + // jars for + // tlds, web-fragments etc. + // If you omit the jar that contains the jstl .tlds, the jsp engine will scan for + // them + // instead. + + List list = new ArrayList<>(); + list.add(".*/[^/]*servlet-api-[^/]*\\.jar$"); + list.add(".*/javax.servlet.jsp.jstl-.*\\.jar$"); + list.add(".*/[^/]*taglibs.*\\.jar$"); + + for (String jarName : jarNames) { + String str = ".*/" + jarName + "-[^/]*\\.jar$"; + list.add(str); + } + + context.setAttribute("org.eclipse.jetty.io.github.dunwu.javaee.server.webapp.ContainerIncludeJarPattern", + StringUtils.join(list, '|')); + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/ListenerDemosBootstrap.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/ListenerDemosBootstrap.java index b0c8d3da..2e7973e6 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/ListenerDemosBootstrap.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/ListenerDemosBootstrap.java @@ -8,26 +8,29 @@ * @author Zhang Peng */ public class ListenerDemosBootstrap { - // private static int STARTUP_TYPE = JettyFactory.IDE_ECLIPSE; - private static int STARTUP_TYPE = JettyFactory.IDE_INTELLIJ; - public static void main(String[] args) throws Exception { - Server server = JettyFactory.initServer(); - JettyFactory.initWebAppContext(server, STARTUP_TYPE); + // private static int STARTUP_TYPE = JettyFactory.IDE_ECLIPSE; + private static int STARTUP_TYPE = JettyFactory.IDE_INTELLIJ; - try { - JettyFactory.startServer(server); + public static void main(String[] args) throws Exception { + Server server = JettyFactory.initServer(); + JettyFactory.initWebAppContext(server, STARTUP_TYPE); + + try { + JettyFactory.startServer(server); + + // 等待用户输入回车重载应用 + while (true) { + char c = (char) System.in.read(); + if (c == '\n') { + JettyFactory.reloadWebAppContext(server); + } + } + } + catch (Exception e) { + e.printStackTrace(); + System.exit(-1); + } + } - // 等待用户输入回车重载应用 - while (true) { - char c = (char) System.in.read(); - if (c == '\n') { - JettyFactory.reloadWebAppContext(server); - } - } - } catch (Exception e) { - e.printStackTrace(); - System.exit(-1); - } - } } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/pom.xml b/codes/javaee-tutorial/javaee-tutorial-oss/pom.xml index 7d57e331..1eb2e364 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/pom.xml +++ b/codes/javaee-tutorial/javaee-tutorial-oss/pom.xml @@ -1,6 +1,6 @@ - 4.0.0 diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/DsaCoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/DsaCoder.java index b7d6627e..97e12fbf 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/DsaCoder.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/DsaCoder.java @@ -2,83 +2,81 @@ import org.apache.commons.codec.binary.Base64; -import java.security.KeyFactory; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.Signature; +import java.security.*; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; /** * @Title DsaCoder * @Description 数字签名算法(Digital Signature Algorithm, DSA)工具类。 DSA是一种数字签名算法。 - * DSA仅支持SHA系列算法,而JDK仅支持SHA1withDSA。 + * DSA仅支持SHA系列算法,而JDK仅支持SHA1withDSA。 * @Author victor zhang * @Date 2016年7月21日 */ public class DsaCoder { - public static final String KEY_ALGORITHM = "DSA"; - public static final String SIGN_ALGORITHM = "SHA1withDSA"; - - /** - * DSA密钥长度默认1024位。 密钥长度必须是64的整数倍,范围在512~1024之间 - */ - private static final int KEY_SIZE = 1024; - - private KeyPair keyPair; - - public DsaCoder() throws Exception { - keyPair = initKey(); - } - - public byte[] signature(byte[] data, byte[] privateKey) throws Exception { - PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey); - KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); - PrivateKey key = keyFactory.generatePrivate(keySpec); - - Signature signature = Signature.getInstance(SIGN_ALGORITHM); - signature.initSign(key); - signature.update(data); - return signature.sign(); - } - - public boolean verify(byte[] data, byte[] publicKey, byte[] sign) throws Exception { - X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey); - KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); - PublicKey key = keyFactory.generatePublic(keySpec); - - Signature signature = Signature.getInstance(SIGN_ALGORITHM); - signature.initVerify(key); - signature.update(data); - return signature.verify(sign); - } - - private KeyPair initKey() throws Exception { - // 初始化密钥对生成器 - KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM); - // 实例化密钥对生成器 - keyPairGen.initialize(KEY_SIZE); - // 实例化密钥对 - return keyPairGen.genKeyPair(); - } - - public byte[] getPublicKey() { - return keyPair.getPublic().getEncoded(); - } - - public byte[] getPrivateKey() { - return keyPair.getPrivate().getEncoded(); - } - - public static void main(String[] args) throws Exception { - String msg = "Hello World"; - DsaCoder dsa = new DsaCoder(); - byte[] sign = dsa.signature(msg.getBytes(), dsa.getPrivateKey()); - boolean flag = dsa.verify(msg.getBytes(), dsa.getPublicKey(), sign); - String result = flag ? "数字签名匹配" : "数字签名不匹配"; - System.out.println("数字签名:" + Base64.encodeBase64URLSafeString(sign)); - System.out.println("验证结果:" + result); - } + + public static final String KEY_ALGORITHM = "DSA"; + + public static final String SIGN_ALGORITHM = "SHA1withDSA"; + + /** + * DSA密钥长度默认1024位。 密钥长度必须是64的整数倍,范围在512~1024之间 + */ + private static final int KEY_SIZE = 1024; + + private KeyPair keyPair; + + public DsaCoder() throws Exception { + keyPair = initKey(); + } + + public static void main(String[] args) throws Exception { + String msg = "Hello World"; + DsaCoder dsa = new DsaCoder(); + byte[] sign = dsa.signature(msg.getBytes(), dsa.getPrivateKey()); + boolean flag = dsa.verify(msg.getBytes(), dsa.getPublicKey(), sign); + String result = flag ? "数字签名匹配" : "数字签名不匹配"; + System.out.println("数字签名:" + Base64.encodeBase64URLSafeString(sign)); + System.out.println("验证结果:" + result); + } + + public byte[] signature(byte[] data, byte[] privateKey) throws Exception { + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); + PrivateKey key = keyFactory.generatePrivate(keySpec); + + Signature signature = Signature.getInstance(SIGN_ALGORITHM); + signature.initSign(key); + signature.update(data); + return signature.sign(); + } + + public boolean verify(byte[] data, byte[] publicKey, byte[] sign) throws Exception { + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); + PublicKey key = keyFactory.generatePublic(keySpec); + + Signature signature = Signature.getInstance(SIGN_ALGORITHM); + signature.initVerify(key); + signature.update(data); + return signature.verify(sign); + } + + private KeyPair initKey() throws Exception { + // 初始化密钥对生成器 + KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM); + // 实例化密钥对生成器 + keyPairGen.initialize(KEY_SIZE); + // 实例化密钥对 + return keyPairGen.genKeyPair(); + } + + public byte[] getPublicKey() { + return keyPair.getPublic().getEncoded(); + } + + public byte[] getPrivateKey() { + return keyPair.getPrivate().getEncoded(); + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/HmacCoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/HmacCoder.java index a706d355..ad83e3ef 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/HmacCoder.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/HmacCoder.java @@ -7,30 +7,35 @@ /** * @Title HmacCoder - * @Description 消息认证码算法(Message Autherntication Code, MAC)是基于密码的消息摘要算法。 它兼容了MD/SHA的特性,并以此为基础加入了密钥。 + * @Description 消息认证码算法(Message Autherntication Code, MAC)是基于密码的消息摘要算法。 + * 它兼容了MD/SHA的特性,并以此为基础加入了密钥。 * @Author victor zhang * @Date 2016年7月21日 */ public class HmacCoder { - /** - * JDK支持HmacMD5, HmacSHA1, HmacSHA256, HmacSHA384, HmacSHA512 - */ - public enum HmacTypeEn { - HmacMD5, HmacSHA1, HmacSHA256, HmacSHA384, HmacSHA512; - } - - public static byte[] encode(byte[] plaintext, byte[] secretKey, HmacTypeEn type) throws Exception { - SecretKeySpec keySpec = new SecretKeySpec(secretKey, type.name()); - Mac mac = Mac.getInstance(keySpec.getAlgorithm()); - mac.init(keySpec); - return mac.doFinal(plaintext); - } - - public static void main(String[] args) throws Exception { - String msg = "Hello World!"; - byte[] secretKey = "Secret_Key".getBytes("UTF8"); - byte[] digest = HmacCoder.encode(msg.getBytes(), secretKey, HmacTypeEn.HmacSHA256); - System.out.println("原文: " + msg); - System.out.println("摘要: " + Base64.encodeBase64URLSafeString(digest)); - } + + public static byte[] encode(byte[] plaintext, byte[] secretKey, HmacTypeEn type) throws Exception { + SecretKeySpec keySpec = new SecretKeySpec(secretKey, type.name()); + Mac mac = Mac.getInstance(keySpec.getAlgorithm()); + mac.init(keySpec); + return mac.doFinal(plaintext); + } + + public static void main(String[] args) throws Exception { + String msg = "Hello World!"; + byte[] secretKey = "Secret_Key".getBytes("UTF8"); + byte[] digest = HmacCoder.encode(msg.getBytes(), secretKey, HmacTypeEn.HmacSHA256); + System.out.println("原文: " + msg); + System.out.println("摘要: " + Base64.encodeBase64URLSafeString(digest)); + } + + /** + * JDK支持HmacMD5, HmacSHA1, HmacSHA256, HmacSHA384, HmacSHA512 + */ + public enum HmacTypeEn { + + HmacMD5, HmacSHA1, HmacSHA256, HmacSHA384, HmacSHA512; + + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/MdCoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/MdCoder.java index 5cf5efce..4d7dc478 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/MdCoder.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/MdCoder.java @@ -11,34 +11,38 @@ * @Date 2016年7月21日 */ public class MdCoder { - /** - * JDK支持MD2和MD5两种MD算法 - */ - public enum MdTypeEn { - MD2, MD5 - } - - public static byte[] encode(byte[] input, MdTypeEn type) throws Exception { - // 根据类型,初始化消息摘要对象 - MessageDigest md5Digest = MessageDigest.getInstance(type.name()); - - // 更新要计算的内容 - md5Digest.update(input); - - // 完成哈希计算,返回摘要 - return md5Digest.digest(); - } - - public static byte[] encodeWithBase64(byte[] input, MdTypeEn type) throws Exception { - return Base64.encodeBase64URLSafe(encode(input, type)); - } - - public static void main(String[] args) throws Exception { - String msg = "Hello World!"; - byte[] encodeWithBase64 = MdCoder.encodeWithBase64(msg.getBytes(), MdTypeEn.MD5); - - String result = String.format("%s摘要:%s", MdTypeEn.MD5.name(), new String(encodeWithBase64)); - System.out.println("原文: " + msg); - System.out.println(result); - } + + public static byte[] encode(byte[] input, MdTypeEn type) throws Exception { + // 根据类型,初始化消息摘要对象 + MessageDigest md5Digest = MessageDigest.getInstance(type.name()); + + // 更新要计算的内容 + md5Digest.update(input); + + // 完成哈希计算,返回摘要 + return md5Digest.digest(); + } + + public static byte[] encodeWithBase64(byte[] input, MdTypeEn type) throws Exception { + return Base64.encodeBase64URLSafe(encode(input, type)); + } + + public static void main(String[] args) throws Exception { + String msg = "Hello World!"; + byte[] encodeWithBase64 = MdCoder.encodeWithBase64(msg.getBytes(), MdTypeEn.MD5); + + String result = String.format("%s摘要:%s", MdTypeEn.MD5.name(), new String(encodeWithBase64)); + System.out.println("原文: " + msg); + System.out.println(result); + } + + /** + * JDK支持MD2和MD5两种MD算法 + */ + public enum MdTypeEn { + + MD2, MD5 + + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/ShaCoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/ShaCoder.java index 69b3028c..cc76c08e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/ShaCoder.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/ShaCoder.java @@ -11,44 +11,48 @@ * @Date 2016年7月21日 */ public class ShaCoder { - /** - * JDK支持SHA1、SHA256、SHA384和SHA512几种SHA算法 - */ - public enum ShaTypeEn { - SHA1("SHA1"), SHA256("SHA-256"), SHA384("SHA-384"), SHA512("SHA-512"); - - private String name; - - ShaTypeEn(String name) { - this.name = name; - } - - public String getName() { - return this.name; - } - } - - public static byte[] encode(byte[] input, ShaTypeEn type) throws Exception { - // 根据类型,初始化消息摘要对象 - MessageDigest md5Digest = MessageDigest.getInstance(type.getName()); - - // 更新要计算的内容 - md5Digest.update(input); - - // 完成哈希计算,返回摘要 - return md5Digest.digest(); - } - - public static byte[] encodeWithBase64(byte[] input, ShaTypeEn type) throws Exception { - return Base64.encodeBase64URLSafe(encode(input, type)); - } - - public static void main(String[] args) throws Exception { - String msg = "Hello World!"; - byte[] encodeWithBase64 = ShaCoder.encodeWithBase64(msg.getBytes(), ShaTypeEn.SHA384); - - String result = String.format("%s摘要:%s", ShaTypeEn.SHA384.getName(), new String(encodeWithBase64)); - System.out.println("原文: " + msg); - System.out.println(result); - } + + public static byte[] encode(byte[] input, ShaTypeEn type) throws Exception { + // 根据类型,初始化消息摘要对象 + MessageDigest md5Digest = MessageDigest.getInstance(type.getName()); + + // 更新要计算的内容 + md5Digest.update(input); + + // 完成哈希计算,返回摘要 + return md5Digest.digest(); + } + + public static byte[] encodeWithBase64(byte[] input, ShaTypeEn type) throws Exception { + return Base64.encodeBase64URLSafe(encode(input, type)); + } + + public static void main(String[] args) throws Exception { + String msg = "Hello World!"; + byte[] encodeWithBase64 = ShaCoder.encodeWithBase64(msg.getBytes(), ShaTypeEn.SHA384); + + String result = String.format("%s摘要:%s", ShaTypeEn.SHA384.getName(), new String(encodeWithBase64)); + System.out.println("原文: " + msg); + System.out.println(result); + } + + /** + * JDK支持SHA1、SHA256、SHA384和SHA512几种SHA算法 + */ + public enum ShaTypeEn { + + SHA1("SHA1"), SHA256("SHA-256"), SHA384("SHA-384"), SHA512("SHA-512"); + + private String name; + + ShaTypeEn(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/AESCoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/AESCoder.java index 20207c6a..df47985d 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/AESCoder.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/AESCoder.java @@ -2,17 +2,9 @@ import org.bouncycastle.util.encoders.Base64; -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.KeyGenerator; -import javax.crypto.NoSuchPaddingException; +import javax.crypto.*; import javax.crypto.spec.IvParameterSpec; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.Key; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; +import java.security.*; /** * @Title AESCoder @@ -21,108 +13,119 @@ * @Date 2016年7月14日 */ public class AESCoder { - public static final String KEY_ALGORITHM_AES = "AES"; - public static final String CIPHER_AES_DEFAULT = "AES"; - public static final String CIPHER_AES_ECB_PKCS5PADDING = "AES/ECB/PKCS5Padding"; // 算法/模式/补码方式 - public static final String CIPHER_AES_CBC_PKCS5PADDING = "AES/CBC/PKCS5Padding"; - public static final String CIPHER_AES_CBC_NOPADDING = "AES/CBC/NoPadding"; - - private static final String SEED = "%%%today is nice***"; // 用于生成随机数的种子 - private Key key; - private Cipher cipher; - private String transformation; - - public AESCoder() throws NoSuchAlgorithmException, NoSuchPaddingException { - this.key = initKey(); - this.cipher = Cipher.getInstance(CIPHER_AES_DEFAULT); - this.transformation = CIPHER_AES_DEFAULT; - } - - public AESCoder(String transformation) throws NoSuchAlgorithmException, NoSuchPaddingException { - this.key = initKey(); - this.cipher = Cipher.getInstance(transformation); - this.transformation = transformation; - } - - /** - * @Title decrypt - * @Description 解密 - * @Author victor zhang - * @Date 2016年7月20日 - * @param input 密文 - * @return byte[] 明文 - * @throws InvalidKeyException - * @throws IllegalBlockSizeException - * @throws BadPaddingException - * @throws InvalidAlgorithmParameterException - */ - public byte[] decrypt(byte[] input) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, - InvalidAlgorithmParameterException { - if (transformation.equals(CIPHER_AES_CBC_PKCS5PADDING) || transformation.equals(CIPHER_AES_CBC_NOPADDING)) { - cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(getIV())); - } else { - cipher.init(Cipher.DECRYPT_MODE, key); - } - return cipher.doFinal(input); - } - - /** - * @Title encrypt - * @Description 加密 - * @Author victor zhang - * @Date 2016年7月20日 - * @param input 明文 - * @return byte[] 密文 - * @throws InvalidKeyException - * @throws IllegalBlockSizeException - * @throws BadPaddingException - * @throws InvalidAlgorithmParameterException - */ - public byte[] encrypt(byte[] input) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, - InvalidAlgorithmParameterException { - if (transformation.equals(CIPHER_AES_CBC_PKCS5PADDING) || transformation.equals(CIPHER_AES_CBC_NOPADDING)) { - cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(getIV())); - } else { - cipher.init(Cipher.ENCRYPT_MODE, key); - } - return cipher.doFinal(input); - } - - /** - * @Title initKey - * @Description 根据随机数种子生成一个密钥 - * @Author victor zhang - * @Date 2016年7月14日 - * @return Key - * @throws NoSuchAlgorithmException - */ - private Key initKey() throws NoSuchAlgorithmException { - // 根据种子生成一个安全的随机数 - SecureRandom secureRandom = null; - secureRandom = new SecureRandom(SEED.getBytes()); - - KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHM_AES); - keyGen.init(secureRandom); - return keyGen.generateKey(); - } - - private byte[] getIV() { - String iv = "0123456789ABCDEF"; // IV length: must be 16 bytes long - return iv.getBytes(); - } - - public static void main(String[] args) throws Exception { - AESCoder aes = new AESCoder(CIPHER_AES_CBC_PKCS5PADDING); - - String msg = "Hello World!"; - System.out.println("[AES加密、解密]"); - System.out.println("message: " + msg); - byte[] encoded = aes.encrypt(msg.getBytes("UTF8")); - String encodedBase64 = Base64.toBase64String(encoded); - System.out.println("encoded: " + encodedBase64); - - byte[] decodedBase64 = Base64.decode(encodedBase64); - byte[] decoded = aes.decrypt(decodedBase64); - System.out.println("decoded: " + new String(decoded)); - } + + public static final String KEY_ALGORITHM_AES = "AES"; + + public static final String CIPHER_AES_DEFAULT = "AES"; + + public static final String CIPHER_AES_ECB_PKCS5PADDING = "AES/ECB/PKCS5Padding"; // 算法/模式/补码方式 + + public static final String CIPHER_AES_CBC_PKCS5PADDING = "AES/CBC/PKCS5Padding"; + + public static final String CIPHER_AES_CBC_NOPADDING = "AES/CBC/NoPadding"; + + private static final String SEED = "%%%today is nice***"; // 用于生成随机数的种子 + + private Key key; + + private Cipher cipher; + + private String transformation; + + public AESCoder() throws NoSuchAlgorithmException, NoSuchPaddingException { + this.key = initKey(); + this.cipher = Cipher.getInstance(CIPHER_AES_DEFAULT); + this.transformation = CIPHER_AES_DEFAULT; + } + + public AESCoder(String transformation) throws NoSuchAlgorithmException, NoSuchPaddingException { + this.key = initKey(); + this.cipher = Cipher.getInstance(transformation); + this.transformation = transformation; + } + + public static void main(String[] args) throws Exception { + AESCoder aes = new AESCoder(CIPHER_AES_CBC_PKCS5PADDING); + + String msg = "Hello World!"; + System.out.println("[AES加密、解密]"); + System.out.println("message: " + msg); + byte[] encoded = aes.encrypt(msg.getBytes("UTF8")); + String encodedBase64 = Base64.toBase64String(encoded); + System.out.println("encoded: " + encodedBase64); + + byte[] decodedBase64 = Base64.decode(encodedBase64); + byte[] decoded = aes.decrypt(decodedBase64); + System.out.println("decoded: " + new String(decoded)); + } + + /** + * @Title decrypt + * @Description 解密 + * @Author victor zhang + * @Date 2016年7月20日 + * @param input 密文 + * @return byte[] 明文 + * @throws InvalidKeyException + * @throws IllegalBlockSizeException + * @throws BadPaddingException + * @throws InvalidAlgorithmParameterException + */ + public byte[] decrypt(byte[] input) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, + InvalidAlgorithmParameterException { + if (transformation.equals(CIPHER_AES_CBC_PKCS5PADDING) || transformation.equals(CIPHER_AES_CBC_NOPADDING)) { + cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(getIV())); + } + else { + cipher.init(Cipher.DECRYPT_MODE, key); + } + return cipher.doFinal(input); + } + + /** + * @Title encrypt + * @Description 加密 + * @Author victor zhang + * @Date 2016年7月20日 + * @param input 明文 + * @return byte[] 密文 + * @throws InvalidKeyException + * @throws IllegalBlockSizeException + * @throws BadPaddingException + * @throws InvalidAlgorithmParameterException + */ + public byte[] encrypt(byte[] input) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, + InvalidAlgorithmParameterException { + if (transformation.equals(CIPHER_AES_CBC_PKCS5PADDING) || transformation.equals(CIPHER_AES_CBC_NOPADDING)) { + cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(getIV())); + } + else { + cipher.init(Cipher.ENCRYPT_MODE, key); + } + return cipher.doFinal(input); + } + + /** + * @Title initKey + * @Description 根据随机数种子生成一个密钥 + * @Author victor zhang + * @Date 2016年7月14日 + * @return Key + * @throws NoSuchAlgorithmException + */ + private Key initKey() throws NoSuchAlgorithmException { + // 根据种子生成一个安全的随机数 + SecureRandom secureRandom = null; + secureRandom = new SecureRandom(SEED.getBytes()); + + KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHM_AES); + keyGen.init(secureRandom); + return keyGen.generateKey(); + } + + private byte[] getIV() { + String iv = "0123456789ABCDEF"; // IV length: must be 16 bytes long + return iv.getBytes(); + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/Base64Demo.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/Base64Demo.java index efd3a121..71042d6f 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/Base64Demo.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/Base64Demo.java @@ -11,14 +11,15 @@ * @Date 2016年7月21日 */ public class Base64Demo { - public static void main(String[] args) throws UnsupportedEncodingException { - String url = - "https://www.baidu.com/s?wd=Base64&rsv_spt=1&rsv_iqid=0xa9188d560005131f&issp=1&f=3&rsv_bp=0&rsv_idx=2&ie=utf-8&tn=baiduhome_pg&rsv_enter=1&rsv_sug3=1&rsv_sug1=1&rsv_sug7=001&rsv_sug2=1&rsp=0&rsv_sug9=es_2_1&rsv_sug4=2153&rsv_sug=9"; - // byte[] encoded = Base64.encodeBase64(url.getBytes("UTF8")); // 标准的Base64编码 - byte[] encoded = Base64.encodeBase64URLSafe(url.getBytes("UTF8")); // URL安全的Base64编码 - byte[] decoded = Base64.decodeBase64(encoded); - System.out.println("url:" + url); - System.out.println("encoded:" + new String(encoded)); - System.out.println("decoded:" + new String(decoded)); - } + + public static void main(String[] args) throws UnsupportedEncodingException { + String url = "https://www.baidu.com/s?wd=Base64&rsv_spt=1&rsv_iqid=0xa9188d560005131f&issp=1&f=3&rsv_bp=0&rsv_idx=2&ie=utf-8&tn=baiduhome_pg&rsv_enter=1&rsv_sug3=1&rsv_sug1=1&rsv_sug7=001&rsv_sug2=1&rsp=0&rsv_sug9=es_2_1&rsv_sug4=2153&rsv_sug=9"; + // byte[] encoded = Base64.encodeBase64(url.getBytes("UTF8")); // 标准的Base64编码 + byte[] encoded = Base64.encodeBase64URLSafe(url.getBytes("UTF8")); // URL安全的Base64编码 + byte[] decoded = Base64.decodeBase64(encoded); + System.out.println("url:" + url); + System.out.println("encoded:" + new String(encoded)); + System.out.println("decoded:" + new String(decoded)); + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/DESCoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/DESCoder.java index 5c905447..5e363d07 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/DESCoder.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/DESCoder.java @@ -2,18 +2,9 @@ import org.bouncycastle.util.encoders.Base64; -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.KeyGenerator; -import javax.crypto.NoSuchPaddingException; +import javax.crypto.*; import javax.crypto.spec.IvParameterSpec; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.Key; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.SecureRandom; +import java.security.*; /** * @Title DESCoder @@ -22,109 +13,120 @@ * @Date 2016年7月14日 */ public class DESCoder { - public static final String KEY_ALGORITHM_DES = "DES"; - public static final String CIPHER_DES_DEFAULT = "DES"; - public static final String CIPHER_DES_ECB_PKCS5PADDING = "DES/ECB/PKCS5Padding"; // 算法/模式/补码方式 - public static final String CIPHER_DES_CBC_PKCS5PADDING = "DES/CBC/PKCS5Padding"; - public static final String CIPHER_DES_CBC_NOPADDING = "DES/CBC/NoPadding"; - private static final String SEED = "%%%today is nice***"; // 用于生成随机数的种子 - - private Key key; - private Cipher cipher; - private String transformation; - - public DESCoder() throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException { - this.key = initKey(); - this.cipher = Cipher.getInstance(CIPHER_DES_DEFAULT); - this.transformation = CIPHER_DES_DEFAULT; - } - - public DESCoder(String transformation) - throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException { - this.key = initKey(); - this.cipher = Cipher.getInstance(transformation); - this.transformation = transformation; - } - - /** - * @Title decrypt - * @Description 解密 - * @Author victor zhang - * @Date 2016年7月20日 - * @param input 密文 - * @return byte[] 明文 - * @throws InvalidKeyException - * @throws IllegalBlockSizeException - * @throws BadPaddingException - * @throws InvalidAlgorithmParameterException - */ - public byte[] decrypt(byte[] input) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, - InvalidAlgorithmParameterException { - if (transformation.equals(CIPHER_DES_CBC_PKCS5PADDING) || transformation.equals(CIPHER_DES_CBC_NOPADDING)) { - cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(getIV())); - } else { - cipher.init(Cipher.DECRYPT_MODE, key); - } - return cipher.doFinal(input); - } - - /** - * @Title encrypt - * @Description 加密 - * @Author victor zhang - * @Date 2016年7月20日 - * @param input 明文 - * @return byte[] 密文 - * @throws InvalidKeyException - * @throws IllegalBlockSizeException - * @throws BadPaddingException - * @throws InvalidAlgorithmParameterException - */ - public byte[] encrypt(byte[] input) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, - InvalidAlgorithmParameterException { - if (transformation.equals(CIPHER_DES_CBC_PKCS5PADDING) || transformation.equals(CIPHER_DES_CBC_NOPADDING)) { - cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(getIV())); - } else { - cipher.init(Cipher.ENCRYPT_MODE, key); - } - return cipher.doFinal(input); - } - - /** - * @Title initKey - * @Description 根据随机数种子生成一个密钥 - * @Author victor zhang - * @Date 2016年7月14日 - * @Return Key - * @throws NoSuchAlgorithmException - * @throws NoSuchProviderException - */ - private Key initKey() throws NoSuchAlgorithmException, NoSuchProviderException { - // 根据种子生成一个安全的随机数 - SecureRandom secureRandom = null; - secureRandom = new SecureRandom(SEED.getBytes()); - - KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHM_DES); - keyGen.init(secureRandom); - return keyGen.generateKey(); - } - - private byte[] getIV() { - String iv = "01234567"; // IV length: must be 8 bytes long - return iv.getBytes(); - } - - public static void main(String[] args) throws Exception { - DESCoder aes = new DESCoder(CIPHER_DES_CBC_PKCS5PADDING); - - String msg = "Hello World!"; - System.out.println("原文: " + msg); - byte[] encoded = aes.encrypt(msg.getBytes("UTF8")); - String encodedBase64 = Base64.toBase64String(encoded); - System.out.println("密文: " + encodedBase64); - - byte[] decodedBase64 = Base64.decode(encodedBase64); - byte[] decoded = aes.decrypt(decodedBase64); - System.out.println("明文: " + new String(decoded)); - } + + public static final String KEY_ALGORITHM_DES = "DES"; + + public static final String CIPHER_DES_DEFAULT = "DES"; + + public static final String CIPHER_DES_ECB_PKCS5PADDING = "DES/ECB/PKCS5Padding"; // 算法/模式/补码方式 + + public static final String CIPHER_DES_CBC_PKCS5PADDING = "DES/CBC/PKCS5Padding"; + + public static final String CIPHER_DES_CBC_NOPADDING = "DES/CBC/NoPadding"; + + private static final String SEED = "%%%today is nice***"; // 用于生成随机数的种子 + + private Key key; + + private Cipher cipher; + + private String transformation; + + public DESCoder() throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException { + this.key = initKey(); + this.cipher = Cipher.getInstance(CIPHER_DES_DEFAULT); + this.transformation = CIPHER_DES_DEFAULT; + } + + public DESCoder(String transformation) + throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException { + this.key = initKey(); + this.cipher = Cipher.getInstance(transformation); + this.transformation = transformation; + } + + public static void main(String[] args) throws Exception { + DESCoder aes = new DESCoder(CIPHER_DES_CBC_PKCS5PADDING); + + String msg = "Hello World!"; + System.out.println("原文: " + msg); + byte[] encoded = aes.encrypt(msg.getBytes("UTF8")); + String encodedBase64 = Base64.toBase64String(encoded); + System.out.println("密文: " + encodedBase64); + + byte[] decodedBase64 = Base64.decode(encodedBase64); + byte[] decoded = aes.decrypt(decodedBase64); + System.out.println("明文: " + new String(decoded)); + } + + /** + * @Title decrypt + * @Description 解密 + * @Author victor zhang + * @Date 2016年7月20日 + * @param input 密文 + * @return byte[] 明文 + * @throws InvalidKeyException + * @throws IllegalBlockSizeException + * @throws BadPaddingException + * @throws InvalidAlgorithmParameterException + */ + public byte[] decrypt(byte[] input) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, + InvalidAlgorithmParameterException { + if (transformation.equals(CIPHER_DES_CBC_PKCS5PADDING) || transformation.equals(CIPHER_DES_CBC_NOPADDING)) { + cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(getIV())); + } + else { + cipher.init(Cipher.DECRYPT_MODE, key); + } + return cipher.doFinal(input); + } + + /** + * @Title encrypt + * @Description 加密 + * @Author victor zhang + * @Date 2016年7月20日 + * @param input 明文 + * @return byte[] 密文 + * @throws InvalidKeyException + * @throws IllegalBlockSizeException + * @throws BadPaddingException + * @throws InvalidAlgorithmParameterException + */ + public byte[] encrypt(byte[] input) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, + InvalidAlgorithmParameterException { + if (transformation.equals(CIPHER_DES_CBC_PKCS5PADDING) || transformation.equals(CIPHER_DES_CBC_NOPADDING)) { + cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(getIV())); + } + else { + cipher.init(Cipher.ENCRYPT_MODE, key); + } + return cipher.doFinal(input); + } + + /** + * @Title initKey + * @Description 根据随机数种子生成一个密钥 + * @Author victor zhang + * @Date 2016年7月14日 + * @Return Key + * @throws NoSuchAlgorithmException + * @throws NoSuchProviderException + */ + private Key initKey() throws NoSuchAlgorithmException, NoSuchProviderException { + // 根据种子生成一个安全的随机数 + SecureRandom secureRandom = null; + secureRandom = new SecureRandom(SEED.getBytes()); + + KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHM_DES); + keyGen.init(secureRandom); + return keyGen.generateKey(); + } + + private byte[] getIV() { + String iv = "01234567"; // IV length: must be 8 bytes long + return iv.getBytes(); + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/DESedeCoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/DESedeCoder.java index 7c21f497..3e60747a 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/DESedeCoder.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/DESedeCoder.java @@ -17,56 +17,58 @@ * @Date 2016年7月20日 */ public class DESedeCoder { - /** - * 加密算法 - */ - public static final String KEY_ALGORITHM = "DESede"; - /** - * 算法名称/加密模式/填充方式 - */ - public static final String CIPHER_ALGORITHM = "DESede/ECB/PKCS5Padding"; + /** + * 加密算法 + */ + public static final String KEY_ALGORITHM = "DESede"; - /** - * 密钥 - */ - private Key key; + /** + * 算法名称/加密模式/填充方式 + */ + public static final String CIPHER_ALGORITHM = "DESede/ECB/PKCS5Padding"; - public DESedeCoder() throws NoSuchAlgorithmException, NoSuchProviderException { - this.key = initKey(); - } + /** + * 密钥 + */ + private Key key; - public byte[] encrypt(byte[] plaintext) throws Exception { - Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); - cipher.init(Cipher.ENCRYPT_MODE, key); - return cipher.doFinal(plaintext); - } + public DESedeCoder() throws NoSuchAlgorithmException, NoSuchProviderException { + this.key = initKey(); + } - public byte[] decrypt(byte[] ciphertext) throws Exception { - Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); - cipher.init(Cipher.DECRYPT_MODE, key); - return cipher.doFinal(ciphertext); - } + public static void main(String[] args) throws Exception { + DESedeCoder desedeCoder = new DESedeCoder(); + String message = "Hello World!"; + byte[] ciphertext = desedeCoder.encrypt(message.getBytes()); + System.out.println(Base64.encodeBase64String(ciphertext)); - private Key initKey() throws NoSuchAlgorithmException, NoSuchProviderException { - // 标准的密钥生成 - // KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHM); - // keyGen.init(112); + byte[] plaintext = desedeCoder.decrypt(ciphertext); + System.out.println(new String(plaintext)); + } - // 标准的密钥生成不支持128位。如果要使用,需引入Bouncy Castle的加密算法,方法如下 - Security.addProvider(new BouncyCastleProvider()); - KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHM, "BC"); - keyGen.init(128); - return keyGen.generateKey(); - } + public byte[] encrypt(byte[] plaintext) throws Exception { + Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); + cipher.init(Cipher.ENCRYPT_MODE, key); + return cipher.doFinal(plaintext); + } - public static void main(String[] args) throws Exception { - DESedeCoder desedeCoder = new DESedeCoder(); - String message = "Hello World!"; - byte[] ciphertext = desedeCoder.encrypt(message.getBytes()); - System.out.println(Base64.encodeBase64String(ciphertext)); + public byte[] decrypt(byte[] ciphertext) throws Exception { + Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); + cipher.init(Cipher.DECRYPT_MODE, key); + return cipher.doFinal(ciphertext); + } + + private Key initKey() throws NoSuchAlgorithmException, NoSuchProviderException { + // 标准的密钥生成 + // KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHM); + // keyGen.init(112); + + // 标准的密钥生成不支持128位。如果要使用,需引入Bouncy Castle的加密算法,方法如下 + Security.addProvider(new BouncyCastleProvider()); + KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHM, "BC"); + keyGen.init(128); + return keyGen.generateKey(); + } - byte[] plaintext = desedeCoder.decrypt(ciphertext); - System.out.println(new String(plaintext)); - } } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/PBECoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/PBECoder.java index 2d654ef8..8dd6857e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/PBECoder.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/PBECoder.java @@ -19,74 +19,75 @@ * @Date 2016年7月20日 */ public class PBECoder { - public static final String KEY_ALGORITHM = "PBEWITHMD5andDES"; - - public static final int ITERATION_COUNT = 100; - - private Key key; - private byte[] salt; - - public PBECoder(String password) throws Exception { - this.salt = initSalt(); - this.key = initKey(password); - } - - public byte[] encrypt(byte[] plaintext) throws Exception { - PBEParameterSpec paramSpec = new PBEParameterSpec(salt, ITERATION_COUNT); - Cipher cipher = Cipher.getInstance(KEY_ALGORITHM); - cipher.init(Cipher.ENCRYPT_MODE, key, paramSpec); - return cipher.doFinal(plaintext); - } - - public byte[] decrypt(byte[] ciphertext) throws Exception { - PBEParameterSpec paramSpec = new PBEParameterSpec(salt, ITERATION_COUNT); - Cipher cipher = Cipher.getInstance(KEY_ALGORITHM); - cipher.init(Cipher.DECRYPT_MODE, key, paramSpec); - return cipher.doFinal(ciphertext); - } - - private byte[] initSalt() throws Exception { - SecureRandom secureRandom = new SecureRandom(); - return secureRandom.generateSeed(8); // 盐长度必须为8字节 - } - - private Key initKey(String password) throws Exception { - PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray()); - SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(KEY_ALGORITHM); - return keyFactory.generateSecret(keySpec); - } - - public static void main(String[] args) throws Exception { - PBECoder encode = new PBECoder("123456"); - String message = "Hello World!"; - byte[] ciphertext = encode.encrypt(message.getBytes()); - byte[] plaintext = encode.decrypt(ciphertext); - - System.out.println("原文:" + message); - System.out.println("密文:" + Base64.encodeBase64String(ciphertext)); - System.out.println("明文:" + new String(plaintext)); - } - - public static void test1() throws Exception { - - // 产生盐 - SecureRandom secureRandom = new SecureRandom(); - byte[] salt = secureRandom.generateSeed(8); // 盐长度必须为8字节 - - // 产生Key - String password = "123456"; - PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray()); - SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(KEY_ALGORITHM); - SecretKey secretKey = keyFactory.generateSecret(keySpec); - - - PBEParameterSpec paramSpec = new PBEParameterSpec(salt, ITERATION_COUNT); - Cipher cipher = Cipher.getInstance(KEY_ALGORITHM); - cipher.init(Cipher.ENCRYPT_MODE, secretKey, paramSpec); - - byte[] plaintext = "Hello World".getBytes(); - byte[] ciphertext = cipher.doFinal(plaintext); - new String (ciphertext); - } + + public static final String KEY_ALGORITHM = "PBEWITHMD5andDES"; + + public static final int ITERATION_COUNT = 100; + + private Key key; + + private byte[] salt; + + public PBECoder(String password) throws Exception { + this.salt = initSalt(); + this.key = initKey(password); + } + + public static void main(String[] args) throws Exception { + PBECoder encode = new PBECoder("123456"); + String message = "Hello World!"; + byte[] ciphertext = encode.encrypt(message.getBytes()); + byte[] plaintext = encode.decrypt(ciphertext); + + System.out.println("原文:" + message); + System.out.println("密文:" + Base64.encodeBase64String(ciphertext)); + System.out.println("明文:" + new String(plaintext)); + } + + public static void test1() throws Exception { + + // 产生盐 + SecureRandom secureRandom = new SecureRandom(); + byte[] salt = secureRandom.generateSeed(8); // 盐长度必须为8字节 + + // 产生Key + String password = "123456"; + PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray()); + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(KEY_ALGORITHM); + SecretKey secretKey = keyFactory.generateSecret(keySpec); + + PBEParameterSpec paramSpec = new PBEParameterSpec(salt, ITERATION_COUNT); + Cipher cipher = Cipher.getInstance(KEY_ALGORITHM); + cipher.init(Cipher.ENCRYPT_MODE, secretKey, paramSpec); + + byte[] plaintext = "Hello World".getBytes(); + byte[] ciphertext = cipher.doFinal(plaintext); + new String(ciphertext); + } + + public byte[] encrypt(byte[] plaintext) throws Exception { + PBEParameterSpec paramSpec = new PBEParameterSpec(salt, ITERATION_COUNT); + Cipher cipher = Cipher.getInstance(KEY_ALGORITHM); + cipher.init(Cipher.ENCRYPT_MODE, key, paramSpec); + return cipher.doFinal(plaintext); + } + + public byte[] decrypt(byte[] ciphertext) throws Exception { + PBEParameterSpec paramSpec = new PBEParameterSpec(salt, ITERATION_COUNT); + Cipher cipher = Cipher.getInstance(KEY_ALGORITHM); + cipher.init(Cipher.DECRYPT_MODE, key, paramSpec); + return cipher.doFinal(ciphertext); + } + + private byte[] initSalt() throws Exception { + SecureRandom secureRandom = new SecureRandom(); + return secureRandom.generateSeed(8); // 盐长度必须为8字节 + } + + private Key initKey(String password) throws Exception { + PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray()); + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(KEY_ALGORITHM); + return keyFactory.generateSecret(keySpec); + } } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/RSACoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/RSACoder.java index 6e682619..de1dafbe 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/RSACoder.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/RSACoder.java @@ -3,12 +3,7 @@ import org.apache.commons.codec.binary.Base64; import javax.crypto.Cipher; -import java.security.KeyFactory; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.Signature; +import java.security.*; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; @@ -19,121 +14,127 @@ * @Date 2016年7月20日 */ public class RSACoder { - public final static String KEY_ALGORITHM = "RSA"; - public final static String SIGN_ALGORITHM = "MD5WithRSA"; - public enum RsaSignTypeEn { - MD2WithRSA, MD5WithRSA, SHA1WithRSA - } - private KeyPair keyPair; - - public RSACoder() throws Exception { - this.keyPair = initKeyPair(); - } - - public byte[] encryptByPublicKey(byte[] plaintext, byte[] key) throws Exception { - X509EncodedKeySpec keySpec = new X509EncodedKeySpec(key); - KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); - PublicKey publicKey = keyFactory.generatePublic(keySpec); - Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); - cipher.init(Cipher.ENCRYPT_MODE, publicKey); - return cipher.doFinal(plaintext); - } - - public byte[] encryptByPrivateKey(byte[] plaintext, byte[] key) throws Exception { - PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(key); - KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); - PrivateKey privateKey = keyFactory.generatePrivate(keySpec); - Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); - cipher.init(Cipher.ENCRYPT_MODE, privateKey); - return cipher.doFinal(plaintext); - } - - public byte[] decryptByPublicKey(byte[] ciphertext, byte[] key) throws Exception { - X509EncodedKeySpec keySpec = new X509EncodedKeySpec(key); - KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); - PublicKey publicKey = keyFactory.generatePublic(keySpec); - Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); - cipher.init(Cipher.DECRYPT_MODE, publicKey); - return cipher.doFinal(ciphertext); - } - - public byte[] decryptByPrivateKey(byte[] ciphertext, byte[] key) throws Exception { - PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(key); - KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); - PrivateKey privateKey = keyFactory.generatePrivate(keySpec); - Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); - cipher.init(Cipher.DECRYPT_MODE, privateKey); - return cipher.doFinal(ciphertext); - } - - public byte[] signature(byte[] data, byte[] privateKey, RsaSignTypeEn type) throws Exception { - PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey); - KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); - PrivateKey key = keyFactory.generatePrivate(keySpec); - - Signature signature = Signature.getInstance(type.name()); - signature.initSign(key); - signature.update(data); - return signature.sign(); - } - - public boolean verify(byte[] data, byte[] publicKey, byte[] sign, RsaSignTypeEn type) throws Exception { - X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey); - KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); - PublicKey key = keyFactory.generatePublic(keySpec); - - Signature signature = Signature.getInstance(type.name()); - signature.initVerify(key); - signature.update(data); - return signature.verify(sign); - } - - public byte[] getPublicKey() { - return keyPair.getPublic().getEncoded(); - } - - public byte[] getPrivateKey() { - return keyPair.getPrivate().getEncoded(); - } - - private KeyPair initKeyPair() throws Exception { - // KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象 - KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM); - // 初始化密钥对生成器,密钥大小为1024位 - keyPairGen.initialize(1024); - // 生成一个密钥对 - return keyPairGen.genKeyPair(); - } - - public static void main(String[] args) throws Exception { - String msg = "Hello World!"; - RSACoder coder = new RSACoder(); - // 私钥加密,公钥解密 - byte[] ciphertext = coder.encryptByPrivateKey(msg.getBytes("UTF8"), coder.keyPair.getPrivate().getEncoded()); - byte[] plaintext = coder.decryptByPublicKey(ciphertext, coder.keyPair.getPublic().getEncoded()); - - // 公钥加密,私钥解密 - byte[] ciphertext2 = coder.encryptByPublicKey(msg.getBytes(), coder.keyPair.getPublic().getEncoded()); - byte[] plaintext2 = coder.decryptByPrivateKey(ciphertext2, coder.keyPair.getPrivate().getEncoded()); - - byte[] sign = coder.signature(msg.getBytes(), coder.getPrivateKey(), RsaSignTypeEn.SHA1WithRSA); - boolean flag = coder.verify(msg.getBytes(), coder.getPublicKey(), sign, RsaSignTypeEn.SHA1WithRSA); - String result = flag ? "数字签名匹配" : "数字签名不匹配"; - - System.out.println("原文:" + msg); - System.out.println("公钥:" + Base64.encodeBase64URLSafeString(coder.keyPair.getPublic().getEncoded())); - System.out.println("私钥:" + Base64.encodeBase64URLSafeString(coder.keyPair.getPrivate().getEncoded())); - - System.out.println("============== 私钥加密,公钥解密 =============="); - System.out.println("密文:" + Base64.encodeBase64URLSafeString(ciphertext)); - System.out.println("明文:" + new String(plaintext)); - - System.out.println("============== 公钥加密,私钥解密 =============="); - System.out.println("密文:" + Base64.encodeBase64URLSafeString(ciphertext2)); - System.out.println("明文:" + new String(plaintext2)); - - System.out.println("============== 数字签名 =============="); - System.out.println("数字签名:" + Base64.encodeBase64URLSafeString(sign)); - System.out.println("验证结果:" + result); - } + + public final static String KEY_ALGORITHM = "RSA"; + + public final static String SIGN_ALGORITHM = "MD5WithRSA"; + private KeyPair keyPair; + + public RSACoder() throws Exception { + this.keyPair = initKeyPair(); + } + + public static void main(String[] args) throws Exception { + String msg = "Hello World!"; + RSACoder coder = new RSACoder(); + // 私钥加密,公钥解密 + byte[] ciphertext = coder.encryptByPrivateKey(msg.getBytes("UTF8"), coder.keyPair.getPrivate().getEncoded()); + byte[] plaintext = coder.decryptByPublicKey(ciphertext, coder.keyPair.getPublic().getEncoded()); + + // 公钥加密,私钥解密 + byte[] ciphertext2 = coder.encryptByPublicKey(msg.getBytes(), coder.keyPair.getPublic().getEncoded()); + byte[] plaintext2 = coder.decryptByPrivateKey(ciphertext2, coder.keyPair.getPrivate().getEncoded()); + + byte[] sign = coder.signature(msg.getBytes(), coder.getPrivateKey(), RsaSignTypeEn.SHA1WithRSA); + boolean flag = coder.verify(msg.getBytes(), coder.getPublicKey(), sign, RsaSignTypeEn.SHA1WithRSA); + String result = flag ? "数字签名匹配" : "数字签名不匹配"; + + System.out.println("原文:" + msg); + System.out.println("公钥:" + Base64.encodeBase64URLSafeString(coder.keyPair.getPublic().getEncoded())); + System.out.println("私钥:" + Base64.encodeBase64URLSafeString(coder.keyPair.getPrivate().getEncoded())); + + System.out.println("============== 私钥加密,公钥解密 =============="); + System.out.println("密文:" + Base64.encodeBase64URLSafeString(ciphertext)); + System.out.println("明文:" + new String(plaintext)); + + System.out.println("============== 公钥加密,私钥解密 =============="); + System.out.println("密文:" + Base64.encodeBase64URLSafeString(ciphertext2)); + System.out.println("明文:" + new String(plaintext2)); + + System.out.println("============== 数字签名 =============="); + System.out.println("数字签名:" + Base64.encodeBase64URLSafeString(sign)); + System.out.println("验证结果:" + result); + } + + public byte[] encryptByPublicKey(byte[] plaintext, byte[] key) throws Exception { + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(key); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); + PublicKey publicKey = keyFactory.generatePublic(keySpec); + Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + return cipher.doFinal(plaintext); + } + + public byte[] encryptByPrivateKey(byte[] plaintext, byte[] key) throws Exception { + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(key); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); + PrivateKey privateKey = keyFactory.generatePrivate(keySpec); + Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); + cipher.init(Cipher.ENCRYPT_MODE, privateKey); + return cipher.doFinal(plaintext); + } + + public byte[] decryptByPublicKey(byte[] ciphertext, byte[] key) throws Exception { + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(key); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); + PublicKey publicKey = keyFactory.generatePublic(keySpec); + Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); + cipher.init(Cipher.DECRYPT_MODE, publicKey); + return cipher.doFinal(ciphertext); + } + + public byte[] decryptByPrivateKey(byte[] ciphertext, byte[] key) throws Exception { + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(key); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); + PrivateKey privateKey = keyFactory.generatePrivate(keySpec); + Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + return cipher.doFinal(ciphertext); + } + + public byte[] signature(byte[] data, byte[] privateKey, RsaSignTypeEn type) throws Exception { + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); + PrivateKey key = keyFactory.generatePrivate(keySpec); + + Signature signature = Signature.getInstance(type.name()); + signature.initSign(key); + signature.update(data); + return signature.sign(); + } + + public boolean verify(byte[] data, byte[] publicKey, byte[] sign, RsaSignTypeEn type) throws Exception { + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); + PublicKey key = keyFactory.generatePublic(keySpec); + + Signature signature = Signature.getInstance(type.name()); + signature.initVerify(key); + signature.update(data); + return signature.verify(sign); + } + + public byte[] getPublicKey() { + return keyPair.getPublic().getEncoded(); + } + + public byte[] getPrivateKey() { + return keyPair.getPrivate().getEncoded(); + } + + private KeyPair initKeyPair() throws Exception { + // KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象 + KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM); + // 初始化密钥对生成器,密钥大小为1024位 + keyPairGen.initialize(1024); + // 生成一个密钥对 + return keyPairGen.genKeyPair(); + } + + public enum RsaSignTypeEn { + + MD2WithRSA, MD5WithRSA, SHA1WithRSA + + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/DownloadPolicy.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/DownloadPolicy.java index 6a8eb1ca..5ded00f9 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/DownloadPolicy.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/DownloadPolicy.java @@ -8,105 +8,100 @@ * Created by victor zhang on 2016/7/22. */ public class DownloadPolicy implements Serializable { - /** - * - */ - private static final long serialVersionUID = 1159078308838844309L; - - /** - * 文件ID - */ - private Long fileId; - - /** - * 下载文件所属空间 - */ - private String namespace; - - /** - * 令牌有效的截止时间。用Unix时间表示。单位秒 - */ - private Long deadline; - - /** - * 允许下载文件类型 - */ - private String fType = UploadConstant.SUPPORT_FILE_TYPE; - - /** - * 操作类型 - */ - private final String operate = UploadConstant.TOKEN_DOWNLOAD; - - /** - * @param policy - * @return boolean - * @title isValid - * @description 判断数据是否有效 - * @author victor zhang - * @date 2016年7月22日 - */ - public static boolean isValid(DownloadPolicy policy) { - // 检查必要项是否为空 - if (StringUtils.isBlank(policy.namespace) || null == policy.deadline) { - return false; - } - - // 令牌截止时间不能是已过期时间 - long life = policy.deadline - System.currentTimeMillis() / 1000; - if (life <= 0) { - return false; - } - - // 检查文件类型 - if (StringUtils.isBlank(policy.fType)) { - return false; - } - String[] requestTypes = policy.fType.split("\\|"); - for (String item : requestTypes) { - if (!UploadConstant.SUPPORT_FILE_TYPE_SET.contains(item)) { - return false; - } - } - - return true; - } - - - public Long getFileId() { - return fileId; - } - - public void setFileId(Long fileId) { - this.fileId = fileId; - } - - public String getNamespace() { - return namespace; - } - - public void setNamespace(String namespace) { - this.namespace = namespace; - } - - public Long getDeadline() { - return deadline; - } - - public void setDeadline(Long e) { - this.deadline = e; - } - - public String getfType() { - return fType; - } - - public void setfType(String fType) { - this.fType = fType; - } - - public String getOperate() { - return operate; - } + + /** + * + */ + private static final long serialVersionUID = 1159078308838844309L; + /** + * 操作类型 + */ + private final String operate = UploadConstant.TOKEN_DOWNLOAD; + /** + * 文件ID + */ + private Long fileId; + /** + * 下载文件所属空间 + */ + private String namespace; + /** + * 令牌有效的截止时间。用Unix时间表示。单位秒 + */ + private Long deadline; + /** + * 允许下载文件类型 + */ + private String fType = UploadConstant.SUPPORT_FILE_TYPE; + + /** + * @param policy + * @return boolean + * @title isValid + * @description 判断数据是否有效 + * @author victor zhang + * @date 2016年7月22日 + */ + public static boolean isValid(DownloadPolicy policy) { + // 检查必要项是否为空 + if (StringUtils.isBlank(policy.namespace) || null == policy.deadline) { + return false; + } + + // 令牌截止时间不能是已过期时间 + long life = policy.deadline - System.currentTimeMillis() / 1000; + if (life <= 0) { + return false; + } + + // 检查文件类型 + if (StringUtils.isBlank(policy.fType)) { + return false; + } + String[] requestTypes = policy.fType.split("\\|"); + for (String item : requestTypes) { + if (!UploadConstant.SUPPORT_FILE_TYPE_SET.contains(item)) { + return false; + } + } + + return true; + } + + public Long getFileId() { + return fileId; + } + + public void setFileId(Long fileId) { + this.fileId = fileId; + } + + public String getNamespace() { + return namespace; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + public Long getDeadline() { + return deadline; + } + + public void setDeadline(Long e) { + this.deadline = e; + } + + public String getfType() { + return fType; + } + + public void setfType(String fType) { + this.fType = fType; + } + + public String getOperate() { + return operate; + } } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/ModifyPolicy.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/ModifyPolicy.java index 7783fc56..dd9cebf0 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/ModifyPolicy.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/ModifyPolicy.java @@ -8,88 +8,85 @@ * Created by victor zhang on 2016/7/22. */ public class ModifyPolicy implements Serializable { - private static final long serialVersionUID = -547821705196510884L; - - /** - * 下载文件所属空间 - */ - private String namespace; - - /** - * 令牌有效的截止时间。用Unix时间表示。单位秒 - */ - private Long deadline; - - /** - * 允许下载文件类型 - */ - private String fType = UploadConstant.SUPPORT_FILE_TYPE; - - /** - * 操作类型 - */ - private final String operate = UploadConstant.TOKEN_MODIFY; - - /** - * @param policy - * @return boolean - * @title isValid - * @description 判断数据是否有效 - * @author victor zhang - * @date 2016年7月22日 - */ - public static boolean isValid(ModifyPolicy policy) { - // 检查必要项是否为空 - if (StringUtils.isBlank(policy.namespace) || null == policy.deadline) { - return false; - } - - // 令牌截止时间不能是已过期时间 - long life = policy.deadline - System.currentTimeMillis() / 1000; - if (life <= 0) { - return false; - } - - // 检查文件类型 - if (StringUtils.isBlank(policy.fType)) { - return false; - } - String[] requestTypes = policy.fType.split("\\|"); - for (String item : requestTypes) { - if (!UploadConstant.SUPPORT_FILE_TYPE_SET.contains(item)) { - return false; - } - } - - return true; - } - - public String getNamespace() { - return namespace; - } - - public void setNamespace(String namespace) { - this.namespace = namespace; - } - - public Long getDeadline() { - return deadline; - } - - public void setDeadline(Long deadline) { - this.deadline = deadline; - } - - public String getfType() { - return fType; - } - - public void setfType(String fType) { - this.fType = fType; - } - - public String getOperate() { - return operate; - } + + private static final long serialVersionUID = -547821705196510884L; + /** + * 操作类型 + */ + private final String operate = UploadConstant.TOKEN_MODIFY; + /** + * 下载文件所属空间 + */ + private String namespace; + /** + * 令牌有效的截止时间。用Unix时间表示。单位秒 + */ + private Long deadline; + /** + * 允许下载文件类型 + */ + private String fType = UploadConstant.SUPPORT_FILE_TYPE; + + /** + * @param policy + * @return boolean + * @title isValid + * @description 判断数据是否有效 + * @author victor zhang + * @date 2016年7月22日 + */ + public static boolean isValid(ModifyPolicy policy) { + // 检查必要项是否为空 + if (StringUtils.isBlank(policy.namespace) || null == policy.deadline) { + return false; + } + + // 令牌截止时间不能是已过期时间 + long life = policy.deadline - System.currentTimeMillis() / 1000; + if (life <= 0) { + return false; + } + + // 检查文件类型 + if (StringUtils.isBlank(policy.fType)) { + return false; + } + String[] requestTypes = policy.fType.split("\\|"); + for (String item : requestTypes) { + if (!UploadConstant.SUPPORT_FILE_TYPE_SET.contains(item)) { + return false; + } + } + + return true; + } + + public String getNamespace() { + return namespace; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + public Long getDeadline() { + return deadline; + } + + public void setDeadline(Long deadline) { + this.deadline = deadline; + } + + public String getfType() { + return fType; + } + + public void setfType(String fType) { + this.fType = fType; + } + + public String getOperate() { + return operate; + } } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/TokenUtil.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/TokenUtil.java index 1e408358..7fe2fe61 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/TokenUtil.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/TokenUtil.java @@ -1,157 +1,170 @@ package io.github.dunwu.javaee.oss.encode.sample; -import io.github.dunwu.javaee.oss.encode.digest.HmacCoder; -import org.apache.commons.codec.binary.Base64; - import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; +import io.github.dunwu.javaee.oss.encode.digest.HmacCoder; +import org.apache.commons.codec.binary.Base64; /** * 令牌工具类 token = accessKey + digest + policy digest = HmacSHA256(policy, secretKey) */ public final class TokenUtil { - private static final String SEPARATOR = "|||"; - private static final String ACCESS_KEY = "ACCESS_KEY"; - private static final String SECRET_KEY = "SECRET_KEY"; - - public enum TokenTypeEN { - UPLOAD, - DOWNLOAD, - MODIFY - } - - public static String getToken(byte[] policy, byte[] accessKey, byte[] secretKey, String tokenType) - throws Exception { - JSONObject policyJson = JSONObject.parseObject(new String(policy)); - - // 检查令牌是否符合系统规格 - if (TokenTypeEN.UPLOAD.name().equalsIgnoreCase(tokenType)) { - UploadPolicy uploadPolicy = JSONObject.toJavaObject(policyJson, UploadPolicy.class); - if (!UploadPolicy.isValid(uploadPolicy)) { - throw new Exception("The policy is not conform to the specifications of the system."); - } - } else if (TokenTypeEN.DOWNLOAD.name().equalsIgnoreCase(tokenType)) { - DownloadPolicy downloadPolicy = JSONObject.toJavaObject(policyJson, DownloadPolicy.class); - if (!DownloadPolicy.isValid(downloadPolicy)) { - throw new Exception("The policy is not conform to the specifications of the system."); - } - } else if (TokenTypeEN.MODIFY.name().equalsIgnoreCase(tokenType)) { - ModifyPolicy modifyPolicy = JSONObject.toJavaObject(policyJson, ModifyPolicy.class); - if (!ModifyPolicy.isValid(modifyPolicy)) { - throw new Exception("The policy is not conform to the specifications of the system."); - } - } else { - throw new Exception("Required token is not supported."); - } - - // 根据secretKey和policy生成消息摘要(使用基于口令编码的HmacSHA256算法) - byte[] digest = HmacCoder.encode(policy, secretKey, HmacCoder.HmacTypeEn.HmacSHA512); - - // Token = AccessKey::Digest::Policy。数据拼接之前都要做URL安全的Base64编码 - String token = Base64.encodeBase64URLSafeString(accessKey) + SEPARATOR - + Base64.encodeBase64URLSafeString(digest) + SEPARATOR + Base64.encodeBase64URLSafeString(policy); - - return token; - } - - public static Object getPolicy(String policyBase64, String digestBase64, byte[] secretKey, String tokenType) - throws Exception { - String policy = new String(Base64.decodeBase64(policyBase64)); - - // 根据secretKey和policy验证摘要是否被篡改 - byte[] checkDigest = HmacCoder.encode(policy.getBytes(), secretKey, HmacCoder.HmacTypeEn.HmacSHA512); - String checkDigestBase64 = Base64.encodeBase64URLSafeString(checkDigest); - if (!checkDigestBase64.equals(digestBase64)) { - throw new SecurityException("The policy is not match to the digest."); - } - - JSONObject json = JSONObject.parseObject(policy); - if (TokenTypeEN.UPLOAD.name().equalsIgnoreCase(tokenType)) { - return JSONObject.toJavaObject(json, UploadPolicy.class); - } else if (TokenTypeEN.DOWNLOAD.name().equalsIgnoreCase(tokenType)) { - return JSONObject.toJavaObject(json, DownloadPolicy.class); - } else if (TokenTypeEN.MODIFY.name().equalsIgnoreCase(tokenType)) { - return JSONObject.toJavaObject(json, ModifyPolicy.class); - } - - return null; - } - - private static String initUploadPolicy() { - long deadline = System.currentTimeMillis() / 1000 + 3600 * 7; - UploadPolicy policy = new UploadPolicy(); - policy.setNamespace("namespace"); - policy.setDeadline(deadline); - policy.setfType("pdf"); - return JSON.toJSONString(policy); - } - - private static String initDownladPolicy() { - long deadline = System.currentTimeMillis() / 1000 + 3600 * 7; - DownloadPolicy policy = new DownloadPolicy(); - policy.setFileId(5748527L); - policy.setNamespace("namespace"); - policy.setDeadline(deadline); - policy.setfType("pdf"); - return JSON.toJSONString(policy); - } - - private static String initModifyPolicy() { - long deadline = System.currentTimeMillis() / 1000 + 3600 * 7; - ModifyPolicy policy = new ModifyPolicy(); - policy.setNamespace("namespace"); - policy.setDeadline(deadline); - policy.setfType("png"); - return JSON.toJSONString(policy); - } - - public static String testGetToken(String tokenType) throws Exception { - String policy = null; - if (TokenTypeEN.UPLOAD.name().equals(tokenType)) { - policy = initUploadPolicy(); - } else if (TokenTypeEN.DOWNLOAD.name().equals(tokenType)) { - policy = initDownladPolicy(); - } else if (TokenTypeEN.MODIFY.name().equals(tokenType)) { - policy = initModifyPolicy(); - } - String policyBase64 = Base64.encodeBase64URLSafeString(policy.getBytes()); - String accessKeyBase64 = Base64.encodeBase64URLSafeString(ACCESS_KEY.getBytes()); - - System.out.println(String.format("============== %s ==============", tokenType)); - System.out.println("policy:" + policy); - System.out.println("policyBase64:" + policyBase64); - System.out.println("accessKeyBase64:" + accessKeyBase64); - - String token = getToken(policy.getBytes(), ACCESS_KEY.getBytes(), SECRET_KEY.getBytes(), tokenType); - System.out.println("Token:" + token); - return token; - } - - public static Object testGetPolicy(String token, String tokenType) throws Exception { - String[] params = token.split(SEPARATOR); - // byte[] accessKey = Base64.decodeBase64(params[0]); - String digestBase64 = params[1]; - String policyBase64 = params[2]; - - if (TokenTypeEN.UPLOAD.name().equals(tokenType)) { - UploadPolicy policy = (UploadPolicy) getPolicy(policyBase64, digestBase64, SECRET_KEY.getBytes(), - tokenType); - return policy; - } else if (TokenTypeEN.DOWNLOAD.name().equals(tokenType)) { - DownloadPolicy policy = (DownloadPolicy) getPolicy(policyBase64, digestBase64, SECRET_KEY.getBytes(), - tokenType); - return policy; - } else if (TokenTypeEN.MODIFY.name().equals(tokenType)) { - ModifyPolicy policy = (ModifyPolicy) getPolicy(policyBase64, digestBase64, SECRET_KEY.getBytes(), - tokenType); - return policy; - } else { - return null; - } - - } - - public static void main(String[] args) throws Exception { - testGetToken(TokenTypeEN.DOWNLOAD.name()); - } + + private static final String SEPARATOR = "|||"; + + private static final String ACCESS_KEY = "ACCESS_KEY"; + + private static final String SECRET_KEY = "SECRET_KEY"; + + public static String getToken(byte[] policy, byte[] accessKey, byte[] secretKey, String tokenType) + throws Exception { + JSONObject policyJson = JSONObject.parseObject(new String(policy)); + + // 检查令牌是否符合系统规格 + if (TokenTypeEN.UPLOAD.name().equalsIgnoreCase(tokenType)) { + UploadPolicy uploadPolicy = JSONObject.toJavaObject(policyJson, UploadPolicy.class); + if (!UploadPolicy.isValid(uploadPolicy)) { + throw new Exception("The policy is not conform to the specifications of the system."); + } + } + else if (TokenTypeEN.DOWNLOAD.name().equalsIgnoreCase(tokenType)) { + DownloadPolicy downloadPolicy = JSONObject.toJavaObject(policyJson, DownloadPolicy.class); + if (!DownloadPolicy.isValid(downloadPolicy)) { + throw new Exception("The policy is not conform to the specifications of the system."); + } + } + else if (TokenTypeEN.MODIFY.name().equalsIgnoreCase(tokenType)) { + ModifyPolicy modifyPolicy = JSONObject.toJavaObject(policyJson, ModifyPolicy.class); + if (!ModifyPolicy.isValid(modifyPolicy)) { + throw new Exception("The policy is not conform to the specifications of the system."); + } + } + else { + throw new Exception("Required token is not supported."); + } + + // 根据secretKey和policy生成消息摘要(使用基于口令编码的HmacSHA256算法) + byte[] digest = HmacCoder.encode(policy, secretKey, HmacCoder.HmacTypeEn.HmacSHA512); + + // Token = AccessKey::Digest::Policy。数据拼接之前都要做URL安全的Base64编码 + String token = Base64.encodeBase64URLSafeString(accessKey) + SEPARATOR + + Base64.encodeBase64URLSafeString(digest) + SEPARATOR + Base64.encodeBase64URLSafeString(policy); + + return token; + } + + public static Object getPolicy(String policyBase64, String digestBase64, byte[] secretKey, String tokenType) + throws Exception { + String policy = new String(Base64.decodeBase64(policyBase64)); + + // 根据secretKey和policy验证摘要是否被篡改 + byte[] checkDigest = HmacCoder.encode(policy.getBytes(), secretKey, HmacCoder.HmacTypeEn.HmacSHA512); + String checkDigestBase64 = Base64.encodeBase64URLSafeString(checkDigest); + if (!checkDigestBase64.equals(digestBase64)) { + throw new SecurityException("The policy is not match to the digest."); + } + + JSONObject json = JSONObject.parseObject(policy); + if (TokenTypeEN.UPLOAD.name().equalsIgnoreCase(tokenType)) { + return JSONObject.toJavaObject(json, UploadPolicy.class); + } + else if (TokenTypeEN.DOWNLOAD.name().equalsIgnoreCase(tokenType)) { + return JSONObject.toJavaObject(json, DownloadPolicy.class); + } + else if (TokenTypeEN.MODIFY.name().equalsIgnoreCase(tokenType)) { + return JSONObject.toJavaObject(json, ModifyPolicy.class); + } + + return null; + } + + private static String initUploadPolicy() { + long deadline = System.currentTimeMillis() / 1000 + 3600 * 7; + UploadPolicy policy = new UploadPolicy(); + policy.setNamespace("namespace"); + policy.setDeadline(deadline); + policy.setfType("pdf"); + return JSON.toJSONString(policy); + } + + private static String initDownladPolicy() { + long deadline = System.currentTimeMillis() / 1000 + 3600 * 7; + DownloadPolicy policy = new DownloadPolicy(); + policy.setFileId(5748527L); + policy.setNamespace("namespace"); + policy.setDeadline(deadline); + policy.setfType("pdf"); + return JSON.toJSONString(policy); + } + + private static String initModifyPolicy() { + long deadline = System.currentTimeMillis() / 1000 + 3600 * 7; + ModifyPolicy policy = new ModifyPolicy(); + policy.setNamespace("namespace"); + policy.setDeadline(deadline); + policy.setfType("png"); + return JSON.toJSONString(policy); + } + + public static String testGetToken(String tokenType) throws Exception { + String policy = null; + if (TokenTypeEN.UPLOAD.name().equals(tokenType)) { + policy = initUploadPolicy(); + } + else if (TokenTypeEN.DOWNLOAD.name().equals(tokenType)) { + policy = initDownladPolicy(); + } + else if (TokenTypeEN.MODIFY.name().equals(tokenType)) { + policy = initModifyPolicy(); + } + String policyBase64 = Base64.encodeBase64URLSafeString(policy.getBytes()); + String accessKeyBase64 = Base64.encodeBase64URLSafeString(ACCESS_KEY.getBytes()); + + System.out.println(String.format("============== %s ==============", tokenType)); + System.out.println("policy:" + policy); + System.out.println("policyBase64:" + policyBase64); + System.out.println("accessKeyBase64:" + accessKeyBase64); + + String token = getToken(policy.getBytes(), ACCESS_KEY.getBytes(), SECRET_KEY.getBytes(), tokenType); + System.out.println("Token:" + token); + return token; + } + + public static Object testGetPolicy(String token, String tokenType) throws Exception { + String[] params = token.split(SEPARATOR); + // byte[] accessKey = Base64.decodeBase64(params[0]); + String digestBase64 = params[1]; + String policyBase64 = params[2]; + + if (TokenTypeEN.UPLOAD.name().equals(tokenType)) { + UploadPolicy policy = (UploadPolicy) getPolicy(policyBase64, digestBase64, SECRET_KEY.getBytes(), + tokenType); + return policy; + } + else if (TokenTypeEN.DOWNLOAD.name().equals(tokenType)) { + DownloadPolicy policy = (DownloadPolicy) getPolicy(policyBase64, digestBase64, SECRET_KEY.getBytes(), + tokenType); + return policy; + } + else if (TokenTypeEN.MODIFY.name().equals(tokenType)) { + ModifyPolicy policy = (ModifyPolicy) getPolicy(policyBase64, digestBase64, SECRET_KEY.getBytes(), + tokenType); + return policy; + } + else { + return null; + } + + } + + public static void main(String[] args) throws Exception { + testGetToken(TokenTypeEN.DOWNLOAD.name()); + } + + public enum TokenTypeEN { + + UPLOAD, DOWNLOAD, MODIFY + + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/UploadConstant.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/UploadConstant.java index c317737e..6cf495aa 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/UploadConstant.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/UploadConstant.java @@ -10,20 +10,27 @@ */ public class UploadConstant { - public static final long FSIZE_MIN = 1024L; // 1KB - public static final long FSIZE_MAX = 5 * 1024 * 1024L; // 5MB - public static final long FSIZE_MIN_DEFAULT = 1024L; // 1KB - public static final long FSIZE_MAX_DEFAULT = 2 * 1024 * 1024L; // 2MB - - public static final String TOKEN_UPLOAD = "UPLOAD"; - public static final String TOKEN_DOWNLOAD = "DOWNLOAD"; - public static final String TOKEN_MODIFY = "MODIFY"; - - public static final String SUPPORT_FILE_TYPE = "pdf|doc|docx|png|jpg|jpeg|gif"; - public static final Set SUPPORT_FILE_TYPE_SET; - static { - SUPPORT_FILE_TYPE_SET = new HashSet(); - String[] supportedTypes = UploadConstant.SUPPORT_FILE_TYPE.split("\\|"); - CollectionUtils.addAll(SUPPORT_FILE_TYPE_SET, supportedTypes); - } + public static final long FSIZE_MIN = 1024L; // 1KB + + public static final long FSIZE_MAX = 5 * 1024 * 1024L; // 5MB + + public static final long FSIZE_MIN_DEFAULT = 1024L; // 1KB + + public static final long FSIZE_MAX_DEFAULT = 2 * 1024 * 1024L; // 2MB + + public static final String TOKEN_UPLOAD = "UPLOAD"; + + public static final String TOKEN_DOWNLOAD = "DOWNLOAD"; + + public static final String TOKEN_MODIFY = "MODIFY"; + + public static final String SUPPORT_FILE_TYPE = "pdf|doc|docx|png|jpg|jpeg|gif"; + + public static final Set SUPPORT_FILE_TYPE_SET; + static { + SUPPORT_FILE_TYPE_SET = new HashSet(); + String[] supportedTypes = UploadConstant.SUPPORT_FILE_TYPE.split("\\|"); + CollectionUtils.addAll(SUPPORT_FILE_TYPE_SET, supportedTypes); + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/UploadPolicy.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/UploadPolicy.java index c1ba2d7e..12a3f739 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/UploadPolicy.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/UploadPolicy.java @@ -8,146 +8,139 @@ * Created by victor zhang on 2016/7/22. */ public class UploadPolicy implements Serializable { - private static final long serialVersionUID = 8289239747395166646L; - - /** - * 上传文件所属空间 - */ - private String namespace; - - /** - * 令牌有效的截止时间。用Unix时间表示。单位秒 - */ - private Long deadline; - - /** - * 文件上传后保留时间。单位天。默认值-1,表示永久保留 - */ - private Integer deleteAfterDays = -1; - - /** - * 上传文件大小上限。单位Byte。 - */ - private Long fsizeMin = UploadConstant.FSIZE_MIN_DEFAULT; - - /** - * 上传文件大小上限。单位Byte。 - */ - private Long fsizeMax = UploadConstant.FSIZE_MAX_DEFAULT; - - /** - * 允许上传文件类型 - */ - private String fType = UploadConstant.SUPPORT_FILE_TYPE; - - /** - * 是否认证令牌。0表示认证,1表示不认证。默认为0 - */ - private Integer verifyToken = 0; - - /** - * 操作类型 - */ - private final String operate = UploadConstant.TOKEN_UPLOAD; - - /** - * @param policy - * @return boolean - * @title isValid - * @description 判断数据是否有效 - * @author victor zhang - * @date 2016年7月22日 - */ - public static boolean isValid(UploadPolicy policy) { - // 检查必要项是否为空 - if (StringUtils.isBlank(policy.namespace) || null == policy.deadline) { - return false; - } - - // 令牌截止时间不能是已过期时间 - long life = policy.deadline - System.currentTimeMillis() / 1000; - if (life <= 0) { - return false; - } - - // 判断文件大小的上限、下限是否符合系统规格 - if (policy.fsizeMin > policy.fsizeMax || policy.fsizeMin < UploadConstant.FSIZE_MIN - || policy.fsizeMax > UploadConstant.FSIZE_MAX) { - return false; - } - - // 检查文件类型 - if (StringUtils.isBlank(policy.fType)) { - return false; - } - String[] requestTypes = policy.fType.split("\\|"); - for (String item : requestTypes) { - if (!UploadConstant.SUPPORT_FILE_TYPE_SET.contains(item)) { - return false; - } - } - - return true; - } - - public String getNamespace() { - return namespace; - } - - public void setNamespace(String namespace) { - this.namespace = namespace; - } - - public Long getDeadline() { - return deadline; - } - - public void setDeadline(Long deadline) { - this.deadline = deadline; - } - - public Integer getDeleteAfterDays() { - return deleteAfterDays; - } - - public void setDeleteAfterDays(Integer deleteAfterDays) { - this.deleteAfterDays = deleteAfterDays; - } - - public Long getFsizeMin() { - return fsizeMin; - } - - public void setFsizeMin(Long fsizeMin) { - this.fsizeMin = fsizeMin; - } - - public Long getFsizeMax() { - return fsizeMax; - } - - public void setFsizeMax(Long fsizeMax) { - this.fsizeMax = fsizeMax; - } - - public String getfType() { - return fType; - } - - public void setfType(String fType) { - this.fType = fType; - } - - public Integer getVerifyToken() { - return verifyToken; - } - - public void setVerifyToken(Integer verifyToken) { - this.verifyToken = verifyToken; - } - - public String getOperate() { - return operate; - } + + private static final long serialVersionUID = 8289239747395166646L; + /** + * 操作类型 + */ + private final String operate = UploadConstant.TOKEN_UPLOAD; + /** + * 上传文件所属空间 + */ + private String namespace; + /** + * 令牌有效的截止时间。用Unix时间表示。单位秒 + */ + private Long deadline; + /** + * 文件上传后保留时间。单位天。默认值-1,表示永久保留 + */ + private Integer deleteAfterDays = -1; + /** + * 上传文件大小上限。单位Byte。 + */ + private Long fsizeMin = UploadConstant.FSIZE_MIN_DEFAULT; + /** + * 上传文件大小上限。单位Byte。 + */ + private Long fsizeMax = UploadConstant.FSIZE_MAX_DEFAULT; + /** + * 允许上传文件类型 + */ + private String fType = UploadConstant.SUPPORT_FILE_TYPE; + /** + * 是否认证令牌。0表示认证,1表示不认证。默认为0 + */ + private Integer verifyToken = 0; + + /** + * @param policy + * @return boolean + * @title isValid + * @description 判断数据是否有效 + * @author victor zhang + * @date 2016年7月22日 + */ + public static boolean isValid(UploadPolicy policy) { + // 检查必要项是否为空 + if (StringUtils.isBlank(policy.namespace) || null == policy.deadline) { + return false; + } + + // 令牌截止时间不能是已过期时间 + long life = policy.deadline - System.currentTimeMillis() / 1000; + if (life <= 0) { + return false; + } + + // 判断文件大小的上限、下限是否符合系统规格 + if (policy.fsizeMin > policy.fsizeMax || policy.fsizeMin < UploadConstant.FSIZE_MIN + || policy.fsizeMax > UploadConstant.FSIZE_MAX) { + return false; + } + + // 检查文件类型 + if (StringUtils.isBlank(policy.fType)) { + return false; + } + String[] requestTypes = policy.fType.split("\\|"); + for (String item : requestTypes) { + if (!UploadConstant.SUPPORT_FILE_TYPE_SET.contains(item)) { + return false; + } + } + + return true; + } + + public String getNamespace() { + return namespace; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + public Long getDeadline() { + return deadline; + } + + public void setDeadline(Long deadline) { + this.deadline = deadline; + } + + public Integer getDeleteAfterDays() { + return deleteAfterDays; + } + + public void setDeleteAfterDays(Integer deleteAfterDays) { + this.deleteAfterDays = deleteAfterDays; + } + + public Long getFsizeMin() { + return fsizeMin; + } + + public void setFsizeMin(Long fsizeMin) { + this.fsizeMin = fsizeMin; + } + + public Long getFsizeMax() { + return fsizeMax; + } + + public void setFsizeMax(Long fsizeMax) { + this.fsizeMax = fsizeMax; + } + + public String getfType() { + return fType; + } + + public void setfType(String fType) { + this.fType = fType; + } + + public Integer getVerifyToken() { + return verifyToken; + } + + public void setVerifyToken(Integer verifyToken) { + this.verifyToken = verifyToken; + } + + public String getOperate() { + return operate; + } } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/html/CnblogParser.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/html/CnblogParser.java index 27d1ff44..2d584d25 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/html/CnblogParser.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/html/CnblogParser.java @@ -18,35 +18,36 @@ * @date 2016/11/8. */ public class CnblogParser { - private static final String BLOG_URL = "http://www.cnblogs.com/jingmoxukong/"; - public static void main(String[] args) throws Exception { - int total = 0; - for (int page = 0; page <= 16; page++) { - total += printAllTitleInPage(BLOG_URL, page); - } - System.out.println("总文章数:" + total); - } + private static final String BLOG_URL = "http://www.cnblogs.com/jingmoxukong/"; + + public static void main(String[] args) throws Exception { + int total = 0; + for (int page = 0; page <= 16; page++) { + total += printAllTitleInPage(BLOG_URL, page); + } + System.out.println("总文章数:" + total); + } + + /** + * 获取指定页HTML 文档指定的body + * @throws IOException + */ + private static int printAllTitleInPage(String blogUrl, int page) throws IOException { + int count = 0; + Document doc = Jsoup.connect(blogUrl + "default.html?page=" + page).get(); + Elements postTitles = doc.body().getElementsByClass("postTitle"); + for (Element postTitle : postTitles) { + Elements links = postTitle.getElementsByTag("a"); + for (Element link : links) { + if (link.hasText()) { + System.out.println(link.text()); + System.out.println(link.attr("href")); + count++; + } + } + } + return count; + } - /** - * 获取指定页HTML 文档指定的body - * - * @throws IOException - */ - private static int printAllTitleInPage(String blogUrl, int page) throws IOException { - int count = 0; - Document doc = Jsoup.connect(blogUrl + "default.html?page=" + page).get(); - Elements postTitles = doc.body().getElementsByClass("postTitle"); - for (Element postTitle : postTitles) { - Elements links = postTitle.getElementsByTag("a"); - for (Element link : links) { - if (link.hasText()) { - System.out.println(link.text()); - System.out.println(link.attr("href")); - count++; - } - } - } - return count; - } } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/html/XiamiParser.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/html/XiamiParser.java index b6b1109d..63a9292d 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/html/XiamiParser.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/html/XiamiParser.java @@ -21,84 +21,88 @@ * @date 2016/11/8. */ public class XiamiParser { - private static final String BLOG_URL = "http://www.xiami.com/space/lib-song/u/5524914/page"; - - public static void main(String[] args) throws Exception { - XiamiParser parser = new XiamiParser(); - Set allSongInfos = new HashSet<>(); - for (int page = 0; page <= 65; page++) { - Set curPageSongs = parser.getSongInfoSet(BLOG_URL, page); - CollectionUtils.addAll(allSongInfos, curPageSongs.iterator()); - } - System.out.println("总歌曲数目:" + allSongInfos.size()); - parser.printAllSongInfo(allSongInfos); - } - - /** - * 获取指定页HTML 文档指定的body - * - * @throws IOException - */ - private Set getSongInfoSet(String blogUrl, int page) throws IOException { - Set songList = new HashSet(); - Document doc = Jsoup.connect(blogUrl + "/" + page).get(); - Elements postTitles = doc.body().getElementsByClass("track_list"); - for (Element postTitle : postTitles) { - Elements songs = postTitle.getElementsByTag("tr"); - for (Element song : songs) { - Elements name = song.getElementsByClass("song_name"); - for (Element link : name) { - SongInfo songinfo = new SongInfo(); - songinfo.setName(link.child(0).text()); - Elements artistName = link.getElementsByClass("artist_name"); - songinfo.setArtist(artistName.get(0).text()); - songList.add(songinfo); - } - } - } - return songList; - } - - private void printAllSongInfo(Set songs) { - for (SongInfo song : songs) { - System.out.println(song.getName() + "\t" + song.getArtist()); - } - } - - public class SongInfo { - private String name; - private String artist; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getArtist() { - return artist; - } - - public void setArtist(String artist) { - this.artist = artist; - } - - @Override - public boolean equals(Object obj) { - if (obj.getClass() != SongInfo.class) - return false; - SongInfo external = (SongInfo) obj; - if (external.getName().equals(this.getName()) && external.getArtist().equals(this.getArtist())) - return true; - else - return false; - } - - @Override - public int hashCode() { - return 1; - } - } + + private static final String BLOG_URL = "http://www.xiami.com/space/lib-song/u/5524914/page"; + + public static void main(String[] args) throws Exception { + XiamiParser parser = new XiamiParser(); + Set allSongInfos = new HashSet<>(); + for (int page = 0; page <= 65; page++) { + Set curPageSongs = parser.getSongInfoSet(BLOG_URL, page); + CollectionUtils.addAll(allSongInfos, curPageSongs.iterator()); + } + System.out.println("总歌曲数目:" + allSongInfos.size()); + parser.printAllSongInfo(allSongInfos); + } + + /** + * 获取指定页HTML 文档指定的body + * @throws IOException + */ + private Set getSongInfoSet(String blogUrl, int page) throws IOException { + Set songList = new HashSet(); + Document doc = Jsoup.connect(blogUrl + "/" + page).get(); + Elements postTitles = doc.body().getElementsByClass("track_list"); + for (Element postTitle : postTitles) { + Elements songs = postTitle.getElementsByTag("tr"); + for (Element song : songs) { + Elements name = song.getElementsByClass("song_name"); + for (Element link : name) { + SongInfo songinfo = new SongInfo(); + songinfo.setName(link.child(0).text()); + Elements artistName = link.getElementsByClass("artist_name"); + songinfo.setArtist(artistName.get(0).text()); + songList.add(songinfo); + } + } + } + return songList; + } + + private void printAllSongInfo(Set songs) { + for (SongInfo song : songs) { + System.out.println(song.getName() + "\t" + song.getArtist()); + } + } + + public class SongInfo { + + private String name; + + private String artist; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getArtist() { + return artist; + } + + public void setArtist(String artist) { + this.artist = artist; + } + + @Override + public boolean equals(Object obj) { + if (obj.getClass() != SongInfo.class) + return false; + SongInfo external = (SongInfo) obj; + if (external.getName().equals(this.getName()) && external.getArtist().equals(this.getArtist())) + return true; + else + return false; + } + + @Override + public int hashCode() { + return 1; + } + + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/ImageUtil.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/ImageUtil.java index d9557a4d..f579acc5 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/ImageUtil.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/ImageUtil.java @@ -4,28 +4,17 @@ */ package io.github.dunwu.javaee.oss.image; -import java.awt.image.BufferedImage; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import javax.imageio.ImageIO; - import io.github.dunwu.javaee.oss.image.dto.ImageParamDTO; +import net.coobird.thumbnailator.Thumbnails; +import net.coobird.thumbnailator.geometry.Positions; +import net.sf.jmimemagic.*; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import net.coobird.thumbnailator.Thumbnails; -import net.coobird.thumbnailator.geometry.Positions; -import net.sf.jmimemagic.Magic; -import net.sf.jmimemagic.MagicException; -import net.sf.jmimemagic.MagicMatch; -import net.sf.jmimemagic.MagicMatchNotFoundException; -import net.sf.jmimemagic.MagicParseException; +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.*; /** * 图片工具类 @@ -34,122 +23,127 @@ * @date 2017/1/16. */ public class ImageUtil { - private static final Logger logger = LoggerFactory.getLogger(ImageUtil.class); - - public static void toFile(String oldFile, String newFile, ImageParamDTO params) throws IOException { - if (StringUtils.isBlank(oldFile) || StringUtils.isBlank(newFile)) { - logger.error("原文件名或目标文件名为空"); - return; - } - Thumbnails.Builder builder = Thumbnails.of(oldFile); - fillBuilderWithParams(builder, params); - if (null == builder) { - return; - } - builder.toFile(newFile); - } - - public static BufferedImage toBufferedImage(String oldFile, ImageParamDTO params) throws IOException { - if (StringUtils.isBlank(oldFile)) { - logger.error("原文件名或目标文件名为空"); - return null; - } - Thumbnails.Builder builder = Thumbnails.of(oldFile); - fillBuilderWithParams(builder, params); - if (null == builder) { - return null; - } - return builder.asBufferedImage(); - } - - public static OutputStream toOutputStream(InputStream input, OutputStream output, ImageParamDTO params) - throws IOException { - Thumbnails.Builder builder = Thumbnails.of(input); - if (null == builder) { - return null; - } - - try { - fillBuilderWithParams(builder, params); - builder.toOutputStream(output); - } catch (IOException e) { - logger.error("图片处理失败\n" + e.getMessage()); - throw e; - } - - return output; - } - - private static void fillBuilderWithParams(Thumbnails.Builder builder, ImageParamDTO params) throws IOException { - if (null == params) { - throw new IOException("图片格式化参数为空"); - } - - // 按照一定规则改变原图尺寸 - if (null != params.getWidth() && null != params.getHeight()) { - builder.size(params.getWidth(), params.getHeight()); - } else if (null != params.getXscale() && null != params.getYscale()) { - builder.scale(params.getXscale(), params.getYscale()); - } else if (null != params.getScale()) { - builder.scale(params.getScale(), params.getScale()); - } else { - builder.scale(1.0); // 如果没有设置尺寸参数,默认大小为原图大小 - } - - // 设置图片旋转角度 - if (null != params.getRotate()) { - builder.rotate(params.getRotate()); - } - - // 设置图片压缩质量 - if (null != params.getQuality()) { - builder.outputQuality(params.getQuality()); - } - - // 设置图片格式 - if (StringUtils.isNotBlank(params.getFormat())) { - builder.outputFormat(params.getFormat()); - } - - // 设置水印 - ImageParamDTO.WaterMark waterMark = params.getWaterMark(); - if (null != waterMark) { - Positions pos = ImageParamDTO.getPostionsByCode(waterMark.getPosition()); - if (null == pos) { - throw new IOException("请检查水印图片的位置类型,有效范围在[1,9]"); - } - BufferedImage bufferedImage = ImageIO.read(new FileInputStream(waterMark.getImage())); - builder.watermark(pos, bufferedImage, waterMark.getOpacity()); - } - } - - /** - * 获取文件的 ContentType - * - * @param content - * @return - * @throws MagicParseException - * @throws MagicException - * @throws MagicMatchNotFoundException - */ - public static String getContentType(byte[] content) - throws MagicParseException, MagicException, MagicMatchNotFoundException { - MagicMatch match = Magic.getMagicMatch(content); - return match.getMimeType(); - } - - public static final InputStream bytes2InputStream(byte[] buf) { - return new ByteArrayInputStream(buf); - } - - public static final byte[] inputStream2bytes(InputStream inStream) throws IOException { - ByteArrayOutputStream swapStream = new ByteArrayOutputStream(); - byte[] buff = new byte[100]; - int rc = 0; - while ((rc = inStream.read(buff, 0, 100)) > 0) { - swapStream.write(buff, 0, rc); - } - byte[] in2b = swapStream.toByteArray(); - return in2b; - } + + private static final Logger logger = LoggerFactory.getLogger(ImageUtil.class); + + public static void toFile(String oldFile, String newFile, ImageParamDTO params) throws IOException { + if (StringUtils.isBlank(oldFile) || StringUtils.isBlank(newFile)) { + logger.error("原文件名或目标文件名为空"); + return; + } + Thumbnails.Builder builder = Thumbnails.of(oldFile); + fillBuilderWithParams(builder, params); + if (null == builder) { + return; + } + builder.toFile(newFile); + } + + public static BufferedImage toBufferedImage(String oldFile, ImageParamDTO params) throws IOException { + if (StringUtils.isBlank(oldFile)) { + logger.error("原文件名或目标文件名为空"); + return null; + } + Thumbnails.Builder builder = Thumbnails.of(oldFile); + fillBuilderWithParams(builder, params); + if (null == builder) { + return null; + } + return builder.asBufferedImage(); + } + + public static OutputStream toOutputStream(InputStream input, OutputStream output, ImageParamDTO params) + throws IOException { + Thumbnails.Builder builder = Thumbnails.of(input); + if (null == builder) { + return null; + } + + try { + fillBuilderWithParams(builder, params); + builder.toOutputStream(output); + } + catch (IOException e) { + logger.error("图片处理失败\n" + e.getMessage()); + throw e; + } + + return output; + } + + private static void fillBuilderWithParams(Thumbnails.Builder builder, ImageParamDTO params) throws IOException { + if (null == params) { + throw new IOException("图片格式化参数为空"); + } + + // 按照一定规则改变原图尺寸 + if (null != params.getWidth() && null != params.getHeight()) { + builder.size(params.getWidth(), params.getHeight()); + } + else if (null != params.getXscale() && null != params.getYscale()) { + builder.scale(params.getXscale(), params.getYscale()); + } + else if (null != params.getScale()) { + builder.scale(params.getScale(), params.getScale()); + } + else { + builder.scale(1.0); // 如果没有设置尺寸参数,默认大小为原图大小 + } + + // 设置图片旋转角度 + if (null != params.getRotate()) { + builder.rotate(params.getRotate()); + } + + // 设置图片压缩质量 + if (null != params.getQuality()) { + builder.outputQuality(params.getQuality()); + } + + // 设置图片格式 + if (StringUtils.isNotBlank(params.getFormat())) { + builder.outputFormat(params.getFormat()); + } + + // 设置水印 + ImageParamDTO.WaterMark waterMark = params.getWaterMark(); + if (null != waterMark) { + Positions pos = ImageParamDTO.getPostionsByCode(waterMark.getPosition()); + if (null == pos) { + throw new IOException("请检查水印图片的位置类型,有效范围在[1,9]"); + } + BufferedImage bufferedImage = ImageIO.read(new FileInputStream(waterMark.getImage())); + builder.watermark(pos, bufferedImage, waterMark.getOpacity()); + } + } + + /** + * 获取文件的 ContentType + * @param content + * @return + * @throws MagicParseException + * @throws MagicException + * @throws MagicMatchNotFoundException + */ + public static String getContentType(byte[] content) + throws MagicParseException, MagicException, MagicMatchNotFoundException { + MagicMatch match = Magic.getMagicMatch(content); + return match.getMimeType(); + } + + public static final InputStream bytes2InputStream(byte[] buf) { + return new ByteArrayInputStream(buf); + } + + public static final byte[] inputStream2bytes(InputStream inStream) throws IOException { + ByteArrayOutputStream swapStream = new ByteArrayOutputStream(); + byte[] buff = new byte[100]; + int rc = 0; + while ((rc = inStream.read(buff, 0, 100)) > 0) { + swapStream.write(buff, 0, rc); + } + byte[] in2b = swapStream.toByteArray(); + return in2b; + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/QRCodeUtil.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/QRCodeUtil.java index 8ca45307..4507a60f 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/QRCodeUtil.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/QRCodeUtil.java @@ -1,27 +1,19 @@ package io.github.dunwu.javaee.oss.image; -import java.awt.image.BufferedImage; -import java.io.FileInputStream; -import java.io.IOException; -import java.nio.file.FileSystems; -import java.nio.file.Path; - -import javax.imageio.ImageIO; - -import com.google.zxing.Binarizer; -import com.google.zxing.BinaryBitmap; -import com.google.zxing.LuminanceSource; -import com.google.zxing.MultiFormatReader; -import com.google.zxing.MultiFormatWriter; -import com.google.zxing.Result; -import com.google.zxing.WriterException; +import com.google.zxing.*; import com.google.zxing.client.j2se.BufferedImageLuminanceSource; import com.google.zxing.client.j2se.MatrixToImageWriter; import com.google.zxing.common.BitMatrix; import com.google.zxing.common.HybridBinarizer; - import io.github.dunwu.javaee.oss.image.dto.BarcodeParamDTO; +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.file.FileSystems; +import java.nio.file.Path; + /** * 二维码工具类 * @@ -29,39 +21,40 @@ * @date 2017/1/16. */ public class QRCodeUtil { - /** - * 创建一个qrcode图片 - * - * @param content 加密信息,建议使用json格式 - * @param paramDTO qrcode 参数 - * @throws WriterException - * @throws IOException - */ - public static void encode(String content, BarcodeParamDTO paramDTO) throws WriterException, IOException { - // 生成矩阵 - BitMatrix bitMatrix = new MultiFormatWriter().encode(content, paramDTO.getBarcodeFormat(), paramDTO.getWidth(), - paramDTO.getHeight(), paramDTO.getEncodeHints()); - Path path = FileSystems.getDefault().getPath(paramDTO.getFilepath()); - MatrixToImageWriter.writeToPath(bitMatrix, paramDTO.getImageFormat(), path);// 输出图像 - } - /** - * 解析 qrcode 图片 - * - * @param paramDTO qrcode 参数 - * @return - */ - public static String decode(BarcodeParamDTO paramDTO) { - try { - BufferedImage bufferedImage = ImageIO.read(new FileInputStream(paramDTO.getFilepath())); - LuminanceSource source = new BufferedImageLuminanceSource(bufferedImage); - Binarizer binarizer = new HybridBinarizer(source); - BinaryBitmap bitmap = new BinaryBitmap(binarizer); - Result result = new MultiFormatReader().decode(bitmap, paramDTO.getDecodeHints()); - return result.getText(); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } + /** + * 创建一个qrcode图片 + * @param content 加密信息,建议使用json格式 + * @param paramDTO qrcode 参数 + * @throws WriterException + * @throws IOException + */ + public static void encode(String content, BarcodeParamDTO paramDTO) throws WriterException, IOException { + // 生成矩阵 + BitMatrix bitMatrix = new MultiFormatWriter().encode(content, paramDTO.getBarcodeFormat(), paramDTO.getWidth(), + paramDTO.getHeight(), paramDTO.getEncodeHints()); + Path path = FileSystems.getDefault().getPath(paramDTO.getFilepath()); + MatrixToImageWriter.writeToPath(bitMatrix, paramDTO.getImageFormat(), path);// 输出图像 + } + + /** + * 解析 qrcode 图片 + * @param paramDTO qrcode 参数 + * @return + */ + public static String decode(BarcodeParamDTO paramDTO) { + try { + BufferedImage bufferedImage = ImageIO.read(new FileInputStream(paramDTO.getFilepath())); + LuminanceSource source = new BufferedImageLuminanceSource(bufferedImage); + Binarizer binarizer = new HybridBinarizer(source); + BinaryBitmap bitmap = new BinaryBitmap(binarizer); + Result result = new MultiFormatReader().decode(bitmap, paramDTO.getDecodeHints()); + return result.getText(); + } + catch (Exception e) { + e.printStackTrace(); + return null; + } + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/dto/BarcodeParamDTO.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/dto/BarcodeParamDTO.java index ee532eba..7a1c2a8a 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/dto/BarcodeParamDTO.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/dto/BarcodeParamDTO.java @@ -15,67 +15,75 @@ * @date 2017/1/17. */ public class BarcodeParamDTO { - private Integer width; // 图像宽度 - private Integer height; // 图像高度 - private String filepath; // 图片路径 - private String imageFormat; // 图片文件格式 - private BarcodeFormat barcodeFormat; // 二维码形式 - private Map encodeHints; // 二维码的编码参数 - private Map decodeHints; // 二维码的解码参数 - - public Integer getWidth() { - return width; - } - - public void setWidth(Integer width) { - this.width = width; - } - - public Integer getHeight() { - return height; - } - - public void setHeight(Integer height) { - this.height = height; - } - - public String getFilepath() { - return filepath; - } - - public void setFilepath(String filepath) { - this.filepath = filepath; - } - - public String getImageFormat() { - return imageFormat; - } - - public void setImageFormat(String imageFormat) { - this.imageFormat = imageFormat; - } - - public BarcodeFormat getBarcodeFormat() { - return barcodeFormat; - } - - public void setBarcodeFormat(BarcodeFormat barcodeFormat) { - this.barcodeFormat = barcodeFormat; - } - - public Map getEncodeHints() { - return encodeHints; - } - - public void setEncodeHints(Map encodeHints) { - this.encodeHints = encodeHints; - } - - public Map getDecodeHints() { - return decodeHints; - } - - public void setDecodeHints(Map decodeHints) { - this.decodeHints = decodeHints; - } + + private Integer width; // 图像宽度 + + private Integer height; // 图像高度 + + private String filepath; // 图片路径 + + private String imageFormat; // 图片文件格式 + + private BarcodeFormat barcodeFormat; // 二维码形式 + + private Map encodeHints; // 二维码的编码参数 + + private Map decodeHints; // 二维码的解码参数 + + public Integer getWidth() { + return width; + } + + public void setWidth(Integer width) { + this.width = width; + } + + public Integer getHeight() { + return height; + } + + public void setHeight(Integer height) { + this.height = height; + } + + public String getFilepath() { + return filepath; + } + + public void setFilepath(String filepath) { + this.filepath = filepath; + } + + public String getImageFormat() { + return imageFormat; + } + + public void setImageFormat(String imageFormat) { + this.imageFormat = imageFormat; + } + + public BarcodeFormat getBarcodeFormat() { + return barcodeFormat; + } + + public void setBarcodeFormat(BarcodeFormat barcodeFormat) { + this.barcodeFormat = barcodeFormat; + } + + public Map getEncodeHints() { + return encodeHints; + } + + public void setEncodeHints(Map encodeHints) { + this.encodeHints = encodeHints; + } + + public Map getDecodeHints() { + return decodeHints; + } + + public void setDecodeHints(Map decodeHints) { + this.decodeHints = decodeHints; + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/dto/ImageParamDTO.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/dto/ImageParamDTO.java index aa0a53ce..8f9c7019 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/dto/ImageParamDTO.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/dto/ImageParamDTO.java @@ -13,157 +13,171 @@ * @date 2017/1/16. */ public class ImageParamDTO implements Serializable { - public static String[] IMAGE_TYPES = {"png", "jpg", "jpeg", "bmp", "gif"}; - - private Integer width; // 宽度 - private Integer height; // 高度 - private Double xscale; // 宽度比例 - private Double yscale; // 高度比例 - private Double scale; // 总比例,相当于将xscale和yscale都设为同比例 - private Double rotate; // 旋转角度,范围为[0.0, 360.0] - private Double quality; // 压缩质量,范围为[0.0, 1.0] - private String format; // 图片格式,支持jpg,jpeg,png,bmp,gif - private WaterMark waterMark; // 水印信息 - - public Integer getWidth() { - return width; - } - - public void setWidth(Integer width) { - this.width = width; - } - - public Integer getHeight() { - return height; - } - - public void setHeight(Integer height) { - this.height = height; - } - - public Double getXscale() { - return xscale; - } - - public void setXscale(Double xscale) { - this.xscale = xscale; - } - - public Double getYscale() { - return yscale; - } - - public void setYscale(Double yscale) { - this.yscale = yscale; - } - - public Double getScale() { - return scale; - } - - public void setScale(Double scale) { - this.scale = scale; - } - - public Double getRotate() { - return rotate; - } - - public void setRotate(Double rotate) { - this.rotate = rotate; - } - - public Double getQuality() { - return quality; - } - - public void setQuality(Double quality) { - this.quality = quality; - } - - public String getFormat() { - return format; - } - - public void setFormat(String format) { - this.format = format; - } - - public WaterMark getWaterMark() { - return waterMark; - } - - public void setWaterMark(WaterMark waterMark) { - this.waterMark = waterMark; - } - - /** - * 将位置类型码转换为 thumbnailator 可以识别的位置类型 - * - * @param code - * @return - */ - public static Positions getPostionsByCode(Integer code) { - switch (code) { - case 1: - return Positions.TOP_LEFT; - case 2: - return Positions.TOP_CENTER; - case 3: - return Positions.TOP_RIGHT; - case 4: - return Positions.CENTER_LEFT; - case 5: - return Positions.CENTER; - case 6: - return Positions.CENTER_RIGHT; - case 7: - return Positions.BOTTOM_LEFT; - case 8: - return Positions.BOTTOM_CENTER; - case 9: - return Positions.BOTTOM_RIGHT; - default: - return null; - } - } - - public static class WaterMark { - private Integer position; - private String image; - private Float opacity; - - public WaterMark() { - } - - public WaterMark(Integer position, String image, Float opacity) { - this.position = position; - this.image = image; - this.opacity = opacity; - } - - public Integer getPosition() { - return position; - } - - public void setPosition(Integer position) { - this.position = position; - } - - public String getImage() { - return image; - } - - public void setImage(String image) { - this.image = image; - } - - public Float getOpacity() { - return opacity; - } - - public void setOpacity(Float opacity) { - this.opacity = opacity; - } - } + + public static String[] IMAGE_TYPES = { "png", "jpg", "jpeg", "bmp", "gif" }; + + private Integer width; // 宽度 + + private Integer height; // 高度 + + private Double xscale; // 宽度比例 + + private Double yscale; // 高度比例 + + private Double scale; // 总比例,相当于将xscale和yscale都设为同比例 + + private Double rotate; // 旋转角度,范围为[0.0, 360.0] + + private Double quality; // 压缩质量,范围为[0.0, 1.0] + + private String format; // 图片格式,支持jpg,jpeg,png,bmp,gif + + private WaterMark waterMark; // 水印信息 + + /** + * 将位置类型码转换为 thumbnailator 可以识别的位置类型 + * @param code + * @return + */ + public static Positions getPostionsByCode(Integer code) { + switch (code) { + case 1: + return Positions.TOP_LEFT; + case 2: + return Positions.TOP_CENTER; + case 3: + return Positions.TOP_RIGHT; + case 4: + return Positions.CENTER_LEFT; + case 5: + return Positions.CENTER; + case 6: + return Positions.CENTER_RIGHT; + case 7: + return Positions.BOTTOM_LEFT; + case 8: + return Positions.BOTTOM_CENTER; + case 9: + return Positions.BOTTOM_RIGHT; + default: + return null; + } + } + + public Integer getWidth() { + return width; + } + + public void setWidth(Integer width) { + this.width = width; + } + + public Integer getHeight() { + return height; + } + + public void setHeight(Integer height) { + this.height = height; + } + + public Double getXscale() { + return xscale; + } + + public void setXscale(Double xscale) { + this.xscale = xscale; + } + + public Double getYscale() { + return yscale; + } + + public void setYscale(Double yscale) { + this.yscale = yscale; + } + + public Double getScale() { + return scale; + } + + public void setScale(Double scale) { + this.scale = scale; + } + + public Double getRotate() { + return rotate; + } + + public void setRotate(Double rotate) { + this.rotate = rotate; + } + + public Double getQuality() { + return quality; + } + + public void setQuality(Double quality) { + this.quality = quality; + } + + public String getFormat() { + return format; + } + + public void setFormat(String format) { + this.format = format; + } + + public WaterMark getWaterMark() { + return waterMark; + } + + public void setWaterMark(WaterMark waterMark) { + this.waterMark = waterMark; + } + + + public static class WaterMark { + + private Integer position; + + private String image; + + private Float opacity; + + public WaterMark() { + } + + public WaterMark(Integer position, String image, Float opacity) { + this.position = position; + this.image = image; + this.opacity = opacity; + } + + public Integer getPosition() { + return position; + } + + public void setPosition(Integer position) { + this.position = position; + } + + public String getImage() { + return image; + } + + public void setImage(String image) { + this.image = image; + } + + public Float getOpacity() { + return opacity; + } + + public void setOpacity(Float opacity) { + this.opacity = opacity; + } + + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSHelloWorld.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSHelloWorld.java index 0a12bf56..52b1cf8a 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSHelloWorld.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSHelloWorld.java @@ -6,140 +6,140 @@ import org.apache.activemq.ActiveMQConnectionFactory; -import javax.jms.Connection; -import javax.jms.DeliveryMode; -import javax.jms.Destination; -import javax.jms.ExceptionListener; -import javax.jms.JMSException; -import javax.jms.Message; -import javax.jms.MessageConsumer; -import javax.jms.MessageProducer; -import javax.jms.Session; -import javax.jms.TextMessage; +import javax.jms.*; /** * @author Victor Zhang * @date 2016/11/29. */ public class JMSHelloWorld { - public static void main(String[] args) throws Exception { - thread(new HelloWorldProducer(), false); - thread(new HelloWorldProducer(), false); - thread(new HelloWorldConsumer(), false); - Thread.sleep(1000); - thread(new HelloWorldConsumer(), false); - thread(new HelloWorldProducer(), false); - thread(new HelloWorldConsumer(), false); - thread(new HelloWorldProducer(), false); - Thread.sleep(1000); - thread(new HelloWorldConsumer(), false); - thread(new HelloWorldProducer(), false); - thread(new HelloWorldConsumer(), false); - thread(new HelloWorldConsumer(), false); - thread(new HelloWorldProducer(), false); - thread(new HelloWorldProducer(), false); - Thread.sleep(1000); - thread(new HelloWorldProducer(), false); - thread(new HelloWorldConsumer(), false); - thread(new HelloWorldConsumer(), false); - thread(new HelloWorldProducer(), false); - thread(new HelloWorldConsumer(), false); - thread(new HelloWorldProducer(), false); - thread(new HelloWorldConsumer(), false); - thread(new HelloWorldProducer(), false); - thread(new HelloWorldConsumer(), false); - thread(new HelloWorldConsumer(), false); - thread(new HelloWorldProducer(), false); - } - - public static void thread(Runnable runnable, boolean daemon) { - Thread brokerThread = new Thread(runnable); - brokerThread.setDaemon(daemon); - brokerThread.start(); - } - - public static class HelloWorldProducer implements Runnable { - public void run() { - try { - // Create a ConnectionFactory - ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("vm://localhost"); - - // Create a Connection - Connection connection = connectionFactory.createConnection(); - connection.start(); - - // Create a Session - Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); - - // Create the destination (Topic or Queue) - Destination destination = session.createQueue("TEST.FOO"); - - // Create a MessageProducer from the Session to the Topic or Queue - MessageProducer producer = session.createProducer(destination); - producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT); - - // Create a messages - String text = "Hello world! From: " + Thread.currentThread().getName() + " : " + this.hashCode(); - TextMessage message = session.createTextMessage(text); - - // Tell the producer to send the message - System.out.println("Sent message: " + message.hashCode() + " : " + Thread.currentThread().getName()); - producer.send(message); - - // Clean up - session.close(); - connection.close(); - } catch (Exception e) { - System.out.println("Caught: " + e); - e.printStackTrace(); - } - } - } - - public static class HelloWorldConsumer implements Runnable, ExceptionListener { - public void run() { - try { - - // Create a ConnectionFactory - ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("vm://localhost"); - - // Create a Connection - Connection connection = connectionFactory.createConnection(); - connection.start(); - - connection.setExceptionListener(this); - - // Create a Session - Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); - - // Create the destination (Topic or Queue) - Destination destination = session.createQueue("TEST.FOO"); - - // Create a MessageConsumer from the Session to the Topic or Queue - MessageConsumer consumer = session.createConsumer(destination); - - // Wait for a message - Message message = consumer.receive(1000); - - if (message instanceof TextMessage) { - TextMessage textMessage = (TextMessage) message; - String text = textMessage.getText(); - System.out.println("Received: " + text); - } else { - System.out.println("Received: " + message); - } - - consumer.close(); - session.close(); - connection.close(); - } catch (Exception e) { - System.out.println("Caught: " + e); - e.printStackTrace(); - } - } - - public synchronized void onException(JMSException ex) { - System.out.println("JMS Exception occured. Shutting down client."); - } - } + + public static void main(String[] args) throws Exception { + thread(new HelloWorldProducer(), false); + thread(new HelloWorldProducer(), false); + thread(new HelloWorldConsumer(), false); + Thread.sleep(1000); + thread(new HelloWorldConsumer(), false); + thread(new HelloWorldProducer(), false); + thread(new HelloWorldConsumer(), false); + thread(new HelloWorldProducer(), false); + Thread.sleep(1000); + thread(new HelloWorldConsumer(), false); + thread(new HelloWorldProducer(), false); + thread(new HelloWorldConsumer(), false); + thread(new HelloWorldConsumer(), false); + thread(new HelloWorldProducer(), false); + thread(new HelloWorldProducer(), false); + Thread.sleep(1000); + thread(new HelloWorldProducer(), false); + thread(new HelloWorldConsumer(), false); + thread(new HelloWorldConsumer(), false); + thread(new HelloWorldProducer(), false); + thread(new HelloWorldConsumer(), false); + thread(new HelloWorldProducer(), false); + thread(new HelloWorldConsumer(), false); + thread(new HelloWorldProducer(), false); + thread(new HelloWorldConsumer(), false); + thread(new HelloWorldConsumer(), false); + thread(new HelloWorldProducer(), false); + } + + public static void thread(Runnable runnable, boolean daemon) { + Thread brokerThread = new Thread(runnable); + brokerThread.setDaemon(daemon); + brokerThread.start(); + } + + public static class HelloWorldProducer implements Runnable { + + public void run() { + try { + // Create a ConnectionFactory + ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("vm://localhost"); + + // Create a Connection + Connection connection = connectionFactory.createConnection(); + connection.start(); + + // Create a Session + Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + + // Create the destination (Topic or Queue) + Destination destination = session.createQueue("TEST.FOO"); + + // Create a MessageProducer from the Session to the Topic or Queue + MessageProducer producer = session.createProducer(destination); + producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT); + + // Create a messages + String text = "Hello world! From: " + Thread.currentThread().getName() + " : " + this.hashCode(); + TextMessage message = session.createTextMessage(text); + + // Tell the producer to send the message + System.out.println("Sent message: " + message.hashCode() + " : " + Thread.currentThread().getName()); + producer.send(message); + + // Clean up + session.close(); + connection.close(); + } + catch (Exception e) { + System.out.println("Caught: " + e); + e.printStackTrace(); + } + } + + } + + public static class HelloWorldConsumer implements Runnable, ExceptionListener { + + public void run() { + try { + + // Create a ConnectionFactory + ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("vm://localhost"); + + // Create a Connection + Connection connection = connectionFactory.createConnection(); + connection.start(); + + connection.setExceptionListener(this); + + // Create a Session + Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + + // Create the destination (Topic or Queue) + Destination destination = session.createQueue("TEST.FOO"); + + // Create a MessageConsumer from the Session to the Topic or Queue + MessageConsumer consumer = session.createConsumer(destination); + + // Wait for a message + Message message = consumer.receive(1000); + + if (message instanceof TextMessage) { + TextMessage textMessage = (TextMessage) message; + String text = textMessage.getText(); + System.out.println("Received: " + text); + } + else { + System.out.println("Received: " + message); + } + + consumer.close(); + session.close(); + connection.close(); + } + catch (Exception e) { + System.out.println("Caught: " + e); + e.printStackTrace(); + } + } + + public synchronized void onException(JMSException ex) { + System.out.println("JMS Exception occured. Shutting down client."); + } + + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSReceiver.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSReceiver.java index ecc0a98c..cc92f4d5 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSReceiver.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSReceiver.java @@ -7,12 +7,7 @@ import org.apache.activemq.ActiveMQConnection; import org.apache.activemq.ActiveMQConnectionFactory; -import javax.jms.Connection; -import javax.jms.ConnectionFactory; -import javax.jms.Destination; -import javax.jms.MessageConsumer; -import javax.jms.Session; -import javax.jms.TextMessage; +import javax.jms.*; /** * 消息的消费者 @@ -21,48 +16,52 @@ * @date 2016/11/28. */ public class JMSReceiver { - public static void main(String[] args) { - // ConnectionFactory :连接工厂,JMS 用它创建连接 - ConnectionFactory connectionFactory; - // Connection :JMS 客户端到JMS Provider 的连接 - Connection connection = null; - // Session: 一个发送或接收消息的线程 - Session session; - // Destination :消息的目的地;消息发送给谁. - Destination destination; - // 消费者,消息接收者 - MessageConsumer consumer; - connectionFactory = new ActiveMQConnectionFactory( - ActiveMQConnection.DEFAULT_USER, - ActiveMQConnection.DEFAULT_PASSWORD, - ActiveMQConnection.DEFAULT_BROKER_URL); - try { - // 构造从工厂得到连接对象 - connection = connectionFactory.createConnection(); - // 启动 - connection.start(); - // 获取操作连接 - session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE); - // 获取session注意参数值xingbo.xu-queue是一个服务器的queue,须在在ActiveMq的console配置 - destination = session.createQueue("FirstQueue"); - consumer = session.createConsumer(destination); - while (true) { - //设置接收者接收消息的时间,为了便于测试,这里谁定为100s - TextMessage message = (TextMessage) consumer.receive(100000); - if (null != message) { - System.out.println("收到消息" + message.getText()); - } else { - break; - } - } - } catch (Exception e) { - e.printStackTrace(); - } finally { - try { - if (null != connection) - connection.close(); - } catch (Throwable ignore) { - } - } - } + + public static void main(String[] args) { + // ConnectionFactory :连接工厂,JMS 用它创建连接 + ConnectionFactory connectionFactory; + // Connection :JMS 客户端到JMS Provider 的连接 + Connection connection = null; + // Session: 一个发送或接收消息的线程 + Session session; + // Destination :消息的目的地;消息发送给谁. + Destination destination; + // 消费者,消息接收者 + MessageConsumer consumer; + connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_USER, + ActiveMQConnection.DEFAULT_PASSWORD, ActiveMQConnection.DEFAULT_BROKER_URL); + try { + // 构造从工厂得到连接对象 + connection = connectionFactory.createConnection(); + // 启动 + connection.start(); + // 获取操作连接 + session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE); + // 获取session注意参数值xingbo.xu-queue是一个服务器的queue,须在在ActiveMq的console配置 + destination = session.createQueue("FirstQueue"); + consumer = session.createConsumer(destination); + while (true) { + // 设置接收者接收消息的时间,为了便于测试,这里谁定为100s + TextMessage message = (TextMessage) consumer.receive(100000); + if (null != message) { + System.out.println("收到消息" + message.getText()); + } + else { + break; + } + } + } + catch (Exception e) { + e.printStackTrace(); + } + finally { + try { + if (null != connection) + connection.close(); + } + catch (Throwable ignore) { + } + } + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSSender.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSSender.java index 24296ac0..1a92ae2d 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSSender.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSSender.java @@ -1,16 +1,10 @@ package io.github.dunwu.javaee.oss.jms; -import javax.jms.Connection; -import javax.jms.ConnectionFactory; -import javax.jms.DeliveryMode; -import javax.jms.Destination; -import javax.jms.MessageProducer; -import javax.jms.Session; -import javax.jms.TextMessage; - import org.apache.activemq.ActiveMQConnection; import org.apache.activemq.ActiveMQConnectionFactory; +import javax.jms.*; + /** * 消息的生产者 * @@ -18,65 +12,69 @@ * @date 2016/11/28. */ public class JMSSender { - private static final int SEND_NUMBER = 4; - public static void main(String[] args) { - // ConnectionFactory :连接工厂,JMS 用它创建连接 - ConnectionFactory connectionFactory; - // Connection :JMS 客户端到JMS Provider 的连接 - Connection connection = null; - // Session: 一个发送或接收消息的线程 - Session session; - // Destination :消息的目的地 - Destination destination; - // MessageProducer:消息发送者 - MessageProducer producer; - // TextMessage message; - // 构造ConnectionFactory实例对象,此处采用ActiveMq的实现jar - connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_USER, - ActiveMQConnection.DEFAULT_PASSWORD, ActiveMQConnection.DEFAULT_BROKER_URL); - try { - // 构造从工厂得到连接对象 - connection = connectionFactory.createConnection(); - // 启动 - connection.start(); - // 获取操作连接 - session = connection.createSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE); - // 获取session注意参数值xingbo.xu-queue是一个服务器的queue,须在在ActiveMq的console配置 - destination = session.createQueue("FirstQueue"); - // 得到消息生成者【发送者】 - producer = session.createProducer(destination); - // 设置不持久化,此处学习,实际根据项目决定 - producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT); - // 构造消息,此处写死,项目就是参数,或者方法获取 - sendMessage(session, producer); - session.commit(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - try { - if (null != connection) - connection.close(); - } catch (Throwable ignore) { - } - } - } + private static final int SEND_NUMBER = 4; + + public static void main(String[] args) { + // ConnectionFactory :连接工厂,JMS 用它创建连接 + ConnectionFactory connectionFactory; + // Connection :JMS 客户端到JMS Provider 的连接 + Connection connection = null; + // Session: 一个发送或接收消息的线程 + Session session; + // Destination :消息的目的地 + Destination destination; + // MessageProducer:消息发送者 + MessageProducer producer; + // TextMessage message; + // 构造ConnectionFactory实例对象,此处采用ActiveMq的实现jar + connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_USER, + ActiveMQConnection.DEFAULT_PASSWORD, ActiveMQConnection.DEFAULT_BROKER_URL); + try { + // 构造从工厂得到连接对象 + connection = connectionFactory.createConnection(); + // 启动 + connection.start(); + // 获取操作连接 + session = connection.createSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE); + // 获取session注意参数值xingbo.xu-queue是一个服务器的queue,须在在ActiveMq的console配置 + destination = session.createQueue("FirstQueue"); + // 得到消息生成者【发送者】 + producer = session.createProducer(destination); + // 设置不持久化,此处学习,实际根据项目决定 + producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT); + // 构造消息,此处写死,项目就是参数,或者方法获取 + sendMessage(session, producer); + session.commit(); + } + catch (Exception e) { + e.printStackTrace(); + } + finally { + try { + if (null != connection) + connection.close(); + } + catch (Throwable ignore) { + } + } + } + + /** + * 发送消息 + * @param session + * @param messageProducer 消息生产者 + * @throws Exception + */ + public static void sendMessage(Session session, MessageProducer messageProducer) throws Exception { + for (int i = 0; i < SEND_NUMBER; i++) { + // 创建一条文本消息 + TextMessage message = session.createTextMessage("ActiveMQ 发送消息" + i); + System.out.println("发送消息:Activemq 发送消息" + i); + // 通过消息生产者发出消息 + messageProducer.send(message); + } - /** - * 发送消息 - * - * @param session - * @param messageProducer 消息生产者 - * @throws Exception - */ - public static void sendMessage(Session session, MessageProducer messageProducer) throws Exception { - for (int i = 0; i < SEND_NUMBER; i++) { - // 创建一条文本消息 - TextMessage message = session.createTextMessage("ActiveMQ 发送消息" + i); - System.out.println("发送消息:Activemq 发送消息" + i); - // 通过消息生产者发出消息 - messageProducer.send(message); - } + } - } } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/logging/JclDemo.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/logging/JclDemo.java index a34e66c9..99d2489b 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/logging/JclDemo.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/logging/JclDemo.java @@ -14,15 +14,17 @@ * @date 2016/11/18. */ public class JclDemo { - private static final Log log = LogFactory.getLog(JclDemo.class); - public static void main(String[] args) { - String msg = "print logging, current level: "; - log.trace(msg + "trace"); - log.debug(msg + "debug"); - log.info(msg + "info"); - log.warn(msg + "warn"); - log.error(msg + "error"); - log.fatal(msg + "fatal"); - } + private static final Log log = LogFactory.getLog(JclDemo.class); + + public static void main(String[] args) { + String msg = "print logging, current level: "; + log.trace(msg + "trace"); + log.debug(msg + "debug"); + log.info(msg + "info"); + log.warn(msg + "warn"); + log.error(msg + "error"); + log.fatal(msg + "fatal"); + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/logging/Slf4jDemo.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/logging/Slf4jDemo.java index 3c87626e..fe43686e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/logging/Slf4jDemo.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/logging/Slf4jDemo.java @@ -14,14 +14,16 @@ * @date 2016/11/18. */ public class Slf4jDemo { - private static final Logger log = LoggerFactory.getLogger(Slf4jDemo.class); - public static void main(String[] args) { - String msg = "print log, current level: {}"; - log.trace(msg, "trace"); - log.debug(msg, "debug"); - log.info(msg, "info"); - log.warn(msg, "warn"); - log.error(msg, "error"); - } + private static final Logger log = LoggerFactory.getLogger(Slf4jDemo.class); + + public static void main(String[] args) { + String msg = "print log, current level: {}"; + log.trace(msg, "trace"); + log.debug(msg, "debug"); + log.info(msg, "info"); + log.warn(msg, "warn"); + log.error(msg, "error"); + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/ForwardMail.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/ForwardMail.java index 08e5141f..61dc3970 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/ForwardMail.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/ForwardMail.java @@ -1,108 +1,111 @@ package io.github.dunwu.javaee.oss.mail; /** - * The Apache License 2.0 - * Copyright (c) 2016 victor zhang - */ + * The Apache License 2.0 + * Copyright (c) 2016 victor zhang + */ -import java.util.Date; -import java.util.Properties; - -import javax.mail.Folder; -import javax.mail.Message; -import javax.mail.Multipart; -import javax.mail.Session; -import javax.mail.Store; -import javax.mail.Transport; +import javax.mail.*; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; +import java.util.Date; +import java.util.Properties; /** * @author victor zhang * @date 2017/4/5. */ public class ForwardMail { - private static final String MAIL_SERVER_SMTP = "smtp.163.com"; - private static final String MAIL_SERVER_POP3 = "pop3.163.com"; - private static final String USER = "xxxxxx"; - private static final String PASSWORD = "******"; - private static final String MAIL_FROM = "xxxxxx@163.com"; - private static final String MAIL_TO = "xxxxxx@163.com"; - private static final String MAIL_CC = "xxxxxx@163.com"; - private static final String MAIL_BCC = "xxxxxx@163.com"; - - public static void main(String[] args) throws Exception { - Properties prop = new Properties(); - prop.put("mail.store.protocol", "pop3"); - prop.put("mail.pop3.host", MAIL_SERVER_POP3); - prop.put("mail.pop3.starttls.enable", "true"); - prop.put("mail.smtp.auth", "true"); - prop.put("mail.smtp.host", MAIL_SERVER_SMTP); - - // 1、创建session - Session session = Session.getDefaultInstance(prop); - - // 2、读取邮件夹 - Store store = session.getStore("pop3"); - store.connect(MAIL_SERVER_POP3, USER, PASSWORD); - Folder folder = store.getFolder("inbox"); - folder.open(Folder.READ_ONLY); - - // 获取邮件夹中第1封邮件信息 - Message[] messages = folder.getMessages(); - if (messages.length <= 0) { - return; - } - Message message = messages[0]; - - // 打印邮件关键信息 - String from = InternetAddress.toString(message.getFrom()); - if (from != null) { - System.out.println("From: " + from); - } - - String replyTo = InternetAddress.toString(message.getReplyTo()); - if (replyTo != null) { - System.out.println("Reply-to: " + replyTo); - } - - String to = InternetAddress.toString(message.getRecipients(Message.RecipientType.TO)); - if (to != null) { - System.out.println("To: " + to); - } - - String subject = message.getSubject(); - if (subject != null) { - System.out.println("Subject: " + subject); - } - - Date sent = message.getSentDate(); - if (sent != null) { - System.out.println("Sent: " + sent); - } - - // 设置转发邮件信息头 - Message forward = new MimeMessage(session); - forward.setFrom(new InternetAddress(MAIL_FROM)); - forward.setRecipient(Message.RecipientType.TO, new InternetAddress(MAIL_TO)); - forward.setSubject("Fwd: " + message.getSubject()); - - // 设置转发邮件内容 - MimeBodyPart bodyPart = new MimeBodyPart(); - bodyPart.setContent(message, "message/rfc822"); - - Multipart multipart = new MimeMultipart(); - multipart.addBodyPart(bodyPart); - forward.setContent(multipart); - forward.saveChanges(); - - Transport ts = session.getTransport("smtp"); - ts.connect(USER, PASSWORD); - ts.sendMessage(forward, forward.getAllRecipients()); - - folder.close(false); - store.close(); - ts.close(); - System.out.println("message forwarded successfully...."); - } + + private static final String MAIL_SERVER_SMTP = "smtp.163.com"; + + private static final String MAIL_SERVER_POP3 = "pop3.163.com"; + + private static final String USER = "xxxxxx"; + + private static final String PASSWORD = "******"; + + private static final String MAIL_FROM = "xxxxxx@163.com"; + + private static final String MAIL_TO = "xxxxxx@163.com"; + + private static final String MAIL_CC = "xxxxxx@163.com"; + + private static final String MAIL_BCC = "xxxxxx@163.com"; + + public static void main(String[] args) throws Exception { + Properties prop = new Properties(); + prop.put("mail.store.protocol", "pop3"); + prop.put("mail.pop3.host", MAIL_SERVER_POP3); + prop.put("mail.pop3.starttls.enable", "true"); + prop.put("mail.smtp.auth", "true"); + prop.put("mail.smtp.host", MAIL_SERVER_SMTP); + + // 1、创建session + Session session = Session.getDefaultInstance(prop); + + // 2、读取邮件夹 + Store store = session.getStore("pop3"); + store.connect(MAIL_SERVER_POP3, USER, PASSWORD); + Folder folder = store.getFolder("inbox"); + folder.open(Folder.READ_ONLY); + + // 获取邮件夹中第1封邮件信息 + Message[] messages = folder.getMessages(); + if (messages.length <= 0) { + return; + } + Message message = messages[0]; + + // 打印邮件关键信息 + String from = InternetAddress.toString(message.getFrom()); + if (from != null) { + System.out.println("From: " + from); + } + + String replyTo = InternetAddress.toString(message.getReplyTo()); + if (replyTo != null) { + System.out.println("Reply-to: " + replyTo); + } + + String to = InternetAddress.toString(message.getRecipients(Message.RecipientType.TO)); + if (to != null) { + System.out.println("To: " + to); + } + + String subject = message.getSubject(); + if (subject != null) { + System.out.println("Subject: " + subject); + } + + Date sent = message.getSentDate(); + if (sent != null) { + System.out.println("Sent: " + sent); + } + + // 设置转发邮件信息头 + Message forward = new MimeMessage(session); + forward.setFrom(new InternetAddress(MAIL_FROM)); + forward.setRecipient(Message.RecipientType.TO, new InternetAddress(MAIL_TO)); + forward.setSubject("Fwd: " + message.getSubject()); + + // 设置转发邮件内容 + MimeBodyPart bodyPart = new MimeBodyPart(); + bodyPart.setContent(message, "message/rfc822"); + + Multipart multipart = new MimeMultipart(); + multipart.addBodyPart(bodyPart); + forward.setContent(multipart); + forward.saveChanges(); + + Transport ts = session.getTransport("smtp"); + ts.connect(USER, PASSWORD); + ts.sendMessage(forward, forward.getAllRecipients()); + + folder.close(false); + store.close(); + ts.close(); + System.out.println("message forwarded successfully...."); + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailConfigDTO.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailConfigDTO.java index 4f5444a6..048aabeb 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailConfigDTO.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailConfigDTO.java @@ -9,58 +9,65 @@ * @date 2016/12/23. */ public class MailConfigDTO { - private String smtpHost; - private String pop3Host; - private String mailDomain; - private String mailAccount; - private String mailPassword; - private String mailFromHost; - - public String getSmtpHost() { - return smtpHost; - } - - public void setSmtpHost(String smtpHost) { - this.smtpHost = smtpHost; - } - - public String getPop3Host() { - return pop3Host; - } - - public void setPop3Host(String pop3Host) { - this.pop3Host = pop3Host; - } - - public String getMailDomain() { - return mailDomain; - } - - public void setMailDomain(String mailDomain) { - this.mailDomain = mailDomain; - } - - public String getMailAccount() { - return mailAccount; - } - - public void setMailAccount(String mailAccount) { - this.mailAccount = mailAccount; - } - - public String getMailPassword() { - return mailPassword; - } - - public void setMailPassword(String mailPassword) { - this.mailPassword = mailPassword; - } - - public String getMailFromHost() { - return mailFromHost; - } - - public void setMailFromHost(String mailFromHost) { - this.mailFromHost = mailFromHost; - } + + private String smtpHost; + + private String pop3Host; + + private String mailDomain; + + private String mailAccount; + + private String mailPassword; + + private String mailFromHost; + + public String getSmtpHost() { + return smtpHost; + } + + public void setSmtpHost(String smtpHost) { + this.smtpHost = smtpHost; + } + + public String getPop3Host() { + return pop3Host; + } + + public void setPop3Host(String pop3Host) { + this.pop3Host = pop3Host; + } + + public String getMailDomain() { + return mailDomain; + } + + public void setMailDomain(String mailDomain) { + this.mailDomain = mailDomain; + } + + public String getMailAccount() { + return mailAccount; + } + + public void setMailAccount(String mailAccount) { + this.mailAccount = mailAccount; + } + + public String getMailPassword() { + return mailPassword; + } + + public void setMailPassword(String mailPassword) { + this.mailPassword = mailPassword; + } + + public String getMailFromHost() { + return mailFromHost; + } + + public void setMailFromHost(String mailFromHost) { + this.mailFromHost = mailFromHost; + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailDTO.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailDTO.java index cb5d4de0..8363d8e6 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailDTO.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailDTO.java @@ -11,85 +11,95 @@ * @date 2016/12/22. */ public class MailDTO { - private String from; - private String to; // 邮件的收件人 - private String cc; // 邮件的抄送人 - private String bcc; // 邮件的密送人 - private String subject; // 邮件主题 - private String type; // text或html两种类型 - private String text; // 邮件文本内容 - private String charset; // 邮件编码类型(如UTF-8、GBK等) - private MimeMultipart content; - - public String getFrom() { - return from; - } - - public void setFrom(String from) { - this.from = from; - } - - public String getTo() { - return to; - } - - public void setTo(String to) { - this.to = to; - } - - public String getCc() { - return cc; - } - - public void setCc(String cc) { - this.cc = cc; - } - - public String getBcc() { - return bcc; - } - - public void setBcc(String bcc) { - this.bcc = bcc; - } - - public String getSubject() { - return subject; - } - - public void setSubject(String subject) { - this.subject = subject; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public String getText() { - return text; - } - - public void setText(String text) { - this.text = text; - } - - public String getCharset() { - return charset; - } - - public void setCharset(String charset) { - this.charset = charset; - } - - public MimeMultipart getContent() { - return content; - } - - public void setContent(MimeMultipart content) { - this.content = content; - } + + private String from; + + private String to; // 邮件的收件人 + + private String cc; // 邮件的抄送人 + + private String bcc; // 邮件的密送人 + + private String subject; // 邮件主题 + + private String type; // text或html两种类型 + + private String text; // 邮件文本内容 + + private String charset; // 邮件编码类型(如UTF-8、GBK等) + + private MimeMultipart content; + + public String getFrom() { + return from; + } + + public void setFrom(String from) { + this.from = from; + } + + public String getTo() { + return to; + } + + public void setTo(String to) { + this.to = to; + } + + public String getCc() { + return cc; + } + + public void setCc(String cc) { + this.cc = cc; + } + + public String getBcc() { + return bcc; + } + + public void setBcc(String bcc) { + this.bcc = bcc; + } + + public String getSubject() { + return subject; + } + + public void setSubject(String subject) { + this.subject = subject; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public String getCharset() { + return charset; + } + + public void setCharset(String charset) { + this.charset = charset; + } + + public MimeMultipart getContent() { + return content; + } + + public void setContent(MimeMultipart content) { + this.content = content; + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailUtil.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailUtil.java index 6f467d6a..2ea681e0 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailUtil.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailUtil.java @@ -3,202 +3,207 @@ */ package io.github.dunwu.javaee.oss.mail; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; +import org.apache.commons.lang3.StringUtils; -import javax.mail.*; +import javax.mail.MessagingException; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeUtility; - -import org.apache.commons.lang3.StringUtils; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; /** * @author Victor Zhang * @date 2016/12/22. */ public class MailUtil { - private MailConfigDTO configDTO; - private static final String TYPE_TEXT = "text"; - private static final String TYPE_HTML = "html"; - - /** - * 以默认配置初始化邮件收发工具 - */ - public MailUtil() { - this.configDTO = initEmailConfig(); - } - - /** - * 以自定义配置初始化邮件收发工具 - */ - public MailUtil(MailConfigDTO configDTO) { - this.configDTO = configDTO; - } - - private MailConfigDTO initEmailConfig() { - MailConfigDTO configDTO = new MailConfigDTO(); - Properties p = new Properties(); - try { - p.load(MailUtil.class.getResourceAsStream("/mail/mail.properties")); - } catch (IOException e) { - e.printStackTrace(); - } - - configDTO.setSmtpHost(p.getProperty("smtp.host")); - configDTO.setPop3Host(p.getProperty("pop3.host")); - configDTO.setMailDomain(p.getProperty("mail.host")); - configDTO.setMailAccount(p.getProperty("mail.account")); - configDTO.setMailPassword(p.getProperty("mail.password")); - configDTO.setMailFromHost(configDTO.getMailAccount() + configDTO.getMailDomain()); - return configDTO; - } - - /** - * 发送邮件 - * - * @param info - * @throws MessagingException - */ - public void sendEmail(MailDTO info) throws MessagingException { - Properties props = new Properties(); - props.setProperty("mail.debug", "true"); - props.setProperty("mail.transport.protocol", "smtp"); - props.setProperty("mail.host", configDTO.getSmtpHost()); - props.setProperty("mail.smtp.auth", "true"); - - // 1、创建session - Session session = Session.getInstance(props); - - // 2、通过session得到transport对象 - Transport ts = session.getTransport(); - - // 3、连上邮件服务器 - ts.connect(configDTO.getSmtpHost(), configDTO.getMailAccount(), configDTO.getMailPassword()); - - // 4、创建邮件 - MimeMessage message = new MimeMessage(session); - if (!fillEmail(message, info)) { - return; - } - - // 5、发送邮件 - ts.sendMessage(message, message.getAllRecipients()); - ts.close(); - } - - private boolean fillEmail(MimeMessage message, MailDTO info) throws MessagingException { - return fillEmailHeader(message, info) && fillEmailBody(message, info); - } - - /** - * 填充邮件头 - */ - private boolean fillEmailHeader(MimeMessage message, MailDTO info) throws MessagingException { - // 邮件的发件人 - if (StringUtils.isNotBlank(configDTO.getMailFromHost())) { - message.setFrom(new InternetAddress(configDTO.getMailFromHost())); - } else { - System.out.println("发件人不能为空"); - return false; - } - - // 邮件的收件人 - if (StringUtils.isNotBlank(info.getTo())) { - message.setRecipient(Message.RecipientType.TO, new InternetAddress(info.getTo())); - } else { - System.out.println("收件人不能为空"); - return false; - } - - // 邮件的抄送人 - if (StringUtils.isNotBlank(info.getCc())) { - message.setRecipient(Message.RecipientType.CC, new InternetAddress(info.getCc())); - } - - // 邮件的密送人 - if (StringUtils.isNotBlank(info.getBcc())) { - message.setRecipient(Message.RecipientType.BCC, new InternetAddress(info.getBcc())); - } - - // 邮件的标题 - if (StringUtils.isNotBlank(info.getCharset())) { - message.setSubject(info.getSubject(), info.getCharset()); - } else { - message.setSubject(info.getSubject()); - } - - - return true; - } - - /** - * 填充邮件内容 - */ - private boolean fillEmailBody(MimeMessage message, MailDTO info) throws MessagingException { - if (StringUtils.isBlank(info.getType()) - || StringUtils.isBlank(info.getText())) { - return false; - } - - if (StringUtils.equals(info.getType(), TYPE_TEXT)) { - if (StringUtils.isNotBlank(info.getCharset())) { - message.setText(info.getText(), info.getCharset()); - } else { - message.setText(info.getText()); - } - - } else if (StringUtils.equals(info.getType(), TYPE_HTML)) { - String type = "text/html"; - if (StringUtils.isNotBlank(info.getCharset())) { - type += ";charset=" + info.getCharset(); - } - message.setContent(info.getText(), type); - } - - return true; - } - - public List receiveEmail() throws MessagingException, IOException { - // 创建一个有具体连接信息的Properties对象 - Properties prop = new Properties(); - prop.setProperty("mail.debug", "false"); - prop.setProperty("mail.store.protocol", "pop3"); - prop.setProperty("mail.pop3.host", configDTO.getPop3Host()); - - // 1、创建session - Session session = Session.getInstance(prop); - - // 2、通过session得到Store对象 - Store store = session.getStore(); - - // 3、连上邮件服务器 - store.connect(configDTO.getPop3Host(), configDTO.getMailAccount(), configDTO.getMailPassword()); - - // 4、获得邮箱内的邮件夹 - Folder folder = store.getFolder("inbox"); - folder.open(Folder.READ_ONLY); - - // 获得邮件夹Folder内的所有邮件Message对象 - Message[] messages = folder.getMessages(); - - List results = new ArrayList(); - for (int i = 0; i < messages.length; i++) { - MailDTO dto = new MailDTO(); - dto.setFrom(MimeUtility.decodeText(messages[i].getFrom()[0].toString())); - dto.setSubject(messages[i].getSubject()); - dto.setText(messages[i].getContent().toString()); - results.add(dto); - System.out.println("第 " + (i + 1) + "封邮件的主题:" + dto.getSubject()); - System.out.println("第 " + (i + 1) + "封邮件的发件人地址:" + dto.getFrom()); - // System.out.println("第 " + (i + 1) + "封邮件的内容:\n" + messages[i].getContent().toString()); - } - - // 5、关闭 - folder.close(false); - store.close(); - return results; - } + + private static final String TYPE_TEXT = "text"; + private static final String TYPE_HTML = "html"; + private MailConfigDTO configDTO; + + /** + * 以默认配置初始化邮件收发工具 + */ + public MailUtil() { + this.configDTO = initEmailConfig(); + } + + /** + * 以自定义配置初始化邮件收发工具 + */ + public MailUtil(MailConfigDTO configDTO) { + this.configDTO = configDTO; + } + + private MailConfigDTO initEmailConfig() { + MailConfigDTO configDTO = new MailConfigDTO(); + Properties p = new Properties(); + try { + p.load(MailUtil.class.getResourceAsStream("/mail/mail.properties")); + } + catch (IOException e) { + e.printStackTrace(); + } + + configDTO.setSmtpHost(p.getProperty("smtp.host")); + configDTO.setPop3Host(p.getProperty("pop3.host")); + configDTO.setMailDomain(p.getProperty("mail.host")); + configDTO.setMailAccount(p.getProperty("mail.account")); + configDTO.setMailPassword(p.getProperty("mail.password")); + configDTO.setMailFromHost(configDTO.getMailAccount() + configDTO.getMailDomain()); + return configDTO; + } + + /** + * 发送邮件 + * @param info + * @throws MessagingException + */ + public void sendEmail(MailDTO info) throws MessagingException { + Properties props = new Properties(); + props.setProperty("mail.debug", "true"); + props.setProperty("mail.transport.protocol", "smtp"); + props.setProperty("mail.host", configDTO.getSmtpHost()); + props.setProperty("mail.smtp.auth", "true"); + + // 1、创建session + Session session = Session.getInstance(props); + + // 2、通过session得到transport对象 + Transport ts = session.getTransport(); + + // 3、连上邮件服务器 + ts.connect(configDTO.getSmtpHost(), configDTO.getMailAccount(), configDTO.getMailPassword()); + + // 4、创建邮件 + MimeMessage message = new MimeMessage(session); + if (!fillEmail(message, info)) { + return; + } + + // 5、发送邮件 + ts.sendMessage(message, message.getAllRecipients()); + ts.close(); + } + + private boolean fillEmail(MimeMessage message, MailDTO info) throws MessagingException { + return fillEmailHeader(message, info) && fillEmailBody(message, info); + } + + /** + * 填充邮件头 + */ + private boolean fillEmailHeader(MimeMessage message, MailDTO info) throws MessagingException { + // 邮件的发件人 + if (StringUtils.isNotBlank(configDTO.getMailFromHost())) { + message.setFrom(new InternetAddress(configDTO.getMailFromHost())); + } + else { + System.out.println("发件人不能为空"); + return false; + } + + // 邮件的收件人 + if (StringUtils.isNotBlank(info.getTo())) { + message.setRecipient(Message.RecipientType.TO, new InternetAddress(info.getTo())); + } + else { + System.out.println("收件人不能为空"); + return false; + } + + // 邮件的抄送人 + if (StringUtils.isNotBlank(info.getCc())) { + message.setRecipient(Message.RecipientType.CC, new InternetAddress(info.getCc())); + } + + // 邮件的密送人 + if (StringUtils.isNotBlank(info.getBcc())) { + message.setRecipient(Message.RecipientType.BCC, new InternetAddress(info.getBcc())); + } + + // 邮件的标题 + if (StringUtils.isNotBlank(info.getCharset())) { + message.setSubject(info.getSubject(), info.getCharset()); + } + else { + message.setSubject(info.getSubject()); + } + + return true; + } + + /** + * 填充邮件内容 + */ + private boolean fillEmailBody(MimeMessage message, MailDTO info) throws MessagingException { + if (StringUtils.isBlank(info.getType()) || StringUtils.isBlank(info.getText())) { + return false; + } + + if (StringUtils.equals(info.getType(), TYPE_TEXT)) { + if (StringUtils.isNotBlank(info.getCharset())) { + message.setText(info.getText(), info.getCharset()); + } + else { + message.setText(info.getText()); + } + + } + else if (StringUtils.equals(info.getType(), TYPE_HTML)) { + String type = "text/html"; + if (StringUtils.isNotBlank(info.getCharset())) { + type += ";charset=" + info.getCharset(); + } + message.setContent(info.getText(), type); + } + + return true; + } + + public List receiveEmail() throws MessagingException, IOException { + // 创建一个有具体连接信息的Properties对象 + Properties prop = new Properties(); + prop.setProperty("mail.debug", "false"); + prop.setProperty("mail.store.protocol", "pop3"); + prop.setProperty("mail.pop3.host", configDTO.getPop3Host()); + + // 1、创建session + Session session = Session.getInstance(prop); + + // 2、通过session得到Store对象 + Store store = session.getStore(); + + // 3、连上邮件服务器 + store.connect(configDTO.getPop3Host(), configDTO.getMailAccount(), configDTO.getMailPassword()); + + // 4、获得邮箱内的邮件夹 + Folder folder = store.getFolder("inbox"); + folder.open(Folder.READ_ONLY); + + // 获得邮件夹Folder内的所有邮件Message对象 + Message[] messages = folder.getMessages(); + + List results = new ArrayList(); + for (int i = 0; i < messages.length; i++) { + MailDTO dto = new MailDTO(); + dto.setFrom(MimeUtility.decodeText(messages[i].getFrom()[0].toString())); + dto.setSubject(messages[i].getSubject()); + dto.setText(messages[i].getContent().toString()); + results.add(dto); + System.out.println("第 " + (i + 1) + "封邮件的主题:" + dto.getSubject()); + System.out.println("第 " + (i + 1) + "封邮件的发件人地址:" + dto.getFrom()); + // System.out.println("第 " + (i + 1) + "封邮件的内容:\n" + + // messages[i].getContent().toString()); + } + + // 5、关闭 + folder.close(false); + store.close(); + return results; + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendAttachmentMail.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendAttachmentMail.java index bdf90c26..d675d84d 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendAttachmentMail.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendAttachmentMail.java @@ -1,7 +1,7 @@ package io.github.dunwu.javaee.oss.mail; /** - * The Apache License 2.0 - * Copyright (c) 2016 victor zhang - */ + * The Apache License 2.0 + * Copyright (c) 2016 victor zhang + */ import javax.mail.Message; import javax.mail.Session; @@ -17,63 +17,69 @@ * @date 2017/4/5. */ public class SendAttachmentMail { - private static final String MAIL_SERVER_HOST = "smtp.163.com"; - private static final String USER = "xxxxxx"; - private static final String PASSWORD = "******"; - private static final String MAIL_FROM = "xxxxxx@163.com"; - private static final String MAIL_TO = "xxxxxx@163.com"; - private static final String MAIL_CC = "xxxxxx@163.com"; - private static final String MAIL_BCC = "xxxxxx@163.com"; - - public static void main(String[] args) throws Exception { - Properties prop = new Properties(); - prop.setProperty("mail.debug", "true"); - prop.setProperty("mail.host", MAIL_SERVER_HOST); - prop.setProperty("mail.transport.protocol", "smtp"); - prop.setProperty("mail.smtp.auth", "true"); - - // 1、创建session - Session session = Session.getInstance(prop); - - // 2、通过session得到transport对象 - Transport ts = session.getTransport(); - - // 3、连上邮件服务器 - ts.connect(MAIL_SERVER_HOST, USER, PASSWORD); - - // 4、创建邮件 - MimeMessage message = new MimeMessage(session); - - // 邮件消息头 - message.setFrom(new InternetAddress(MAIL_FROM)); // 邮件的发件人 - message.setRecipient(Message.RecipientType.TO, new InternetAddress(MAIL_TO)); // 邮件的收件人 - message.setRecipient(Message.RecipientType.CC, new InternetAddress(MAIL_CC)); // 邮件的抄送人 - message.setRecipient(Message.RecipientType.BCC, new InternetAddress(MAIL_BCC)); // 邮件的密送人 - message.setSubject("测试带附件邮件"); // 邮件的标题 - - MimeBodyPart text = new MimeBodyPart(); - text.setContent("邮件中有两个附件。", "text/html;charset=UTF-8"); - - // 描述数据关系 - MimeMultipart mm = new MimeMultipart(); - mm.setSubType("related"); - mm.addBodyPart(text); - String[] files = { - "D:\\00_Temp\\temp\\1.jpg", "D:\\00_Temp\\temp\\2.png" - }; - - // 添加邮件附件 - for (String filename : files) { - MimeBodyPart attachPart = new MimeBodyPart(); - attachPart.attachFile(filename); - mm.addBodyPart(attachPart); - } - - message.setContent(mm); - message.saveChanges(); - - // 5、发送邮件 - ts.sendMessage(message, message.getAllRecipients()); - ts.close(); - } + + private static final String MAIL_SERVER_HOST = "smtp.163.com"; + + private static final String USER = "xxxxxx"; + + private static final String PASSWORD = "******"; + + private static final String MAIL_FROM = "xxxxxx@163.com"; + + private static final String MAIL_TO = "xxxxxx@163.com"; + + private static final String MAIL_CC = "xxxxxx@163.com"; + + private static final String MAIL_BCC = "xxxxxx@163.com"; + + public static void main(String[] args) throws Exception { + Properties prop = new Properties(); + prop.setProperty("mail.debug", "true"); + prop.setProperty("mail.host", MAIL_SERVER_HOST); + prop.setProperty("mail.transport.protocol", "smtp"); + prop.setProperty("mail.smtp.auth", "true"); + + // 1、创建session + Session session = Session.getInstance(prop); + + // 2、通过session得到transport对象 + Transport ts = session.getTransport(); + + // 3、连上邮件服务器 + ts.connect(MAIL_SERVER_HOST, USER, PASSWORD); + + // 4、创建邮件 + MimeMessage message = new MimeMessage(session); + + // 邮件消息头 + message.setFrom(new InternetAddress(MAIL_FROM)); // 邮件的发件人 + message.setRecipient(Message.RecipientType.TO, new InternetAddress(MAIL_TO)); // 邮件的收件人 + message.setRecipient(Message.RecipientType.CC, new InternetAddress(MAIL_CC)); // 邮件的抄送人 + message.setRecipient(Message.RecipientType.BCC, new InternetAddress(MAIL_BCC)); // 邮件的密送人 + message.setSubject("测试带附件邮件"); // 邮件的标题 + + MimeBodyPart text = new MimeBodyPart(); + text.setContent("邮件中有两个附件。", "text/html;charset=UTF-8"); + + // 描述数据关系 + MimeMultipart mm = new MimeMultipart(); + mm.setSubType("related"); + mm.addBodyPart(text); + String[] files = { "D:\\00_Temp\\temp\\1.jpg", "D:\\00_Temp\\temp\\2.png" }; + + // 添加邮件附件 + for (String filename : files) { + MimeBodyPart attachPart = new MimeBodyPart(); + attachPart.attachFile(filename); + mm.addBodyPart(attachPart); + } + + message.setContent(mm); + message.saveChanges(); + + // 5、发送邮件 + ts.sendMessage(message, message.getAllRecipients()); + ts.close(); + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendTemplateMail.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendTemplateMail.java index 8ea8dc48..df6f51c7 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendTemplateMail.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendTemplateMail.java @@ -4,76 +4,81 @@ */ package io.github.dunwu.javaee.oss.mail; +import io.github.dunwu.javaee.oss.template.VelocityUtil; +import org.apache.velocity.VelocityContext; + +import javax.mail.MessagingException; import java.util.ArrayList; import java.util.Date; import java.util.List; -import javax.mail.MessagingException; - -import io.github.dunwu.javaee.oss.template.VelocityUtil; -import org.apache.velocity.VelocityContext; - /** * @author Victor Zhang * @date 2016/12/23. * @see org.zp.javaee.tools.mail.MailUtil 注意:如果想要成功发送邮件,需要修改JavaParty项目 - * src\javaee\tools\src\main\resources\mail\mail.properties 中的 参数,请根据实际邮箱来配置。 + * src\javaee\tools\src\main\resources\mail\mail.properties 中的 参数,请根据实际邮箱来配置。 */ public class SendTemplateMail { - private static final String DEFAULT_TO = "xxxxxx@163.com"; - - public static class Hyperlink { - private String link; - private String name; - - Hyperlink(String name, String link) { - this.name = name; - this.link = link; - } - - public String getLink() { - return link; - } - - public void setLink(String link) { - this.link = link; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - } - - public static void main(String[] args) throws MessagingException { - VelocityContext context = new VelocityContext(); - context.put("name", "Victor Zhang"); - context.put("hint", "欢迎使用Velocity邮件模板:"); - - // 直接传入一个对象 - context.put("date", new Date()); - - // 传入一个Vector - Hyperlink item1 = new Hyperlink("百度首页", "https://www.baidu.com"); - Hyperlink item2 = new Hyperlink("网易首页", "http://www.163.com/"); - List list = new ArrayList<>(); - list.add(item1); - list.add(item2); - context.put("links", list); - context.put("logo", - "http://images.cnblogs.com/cnblogs_com/jingmoxukong/709053/o_%e6%94%bb%e5%9f%8e%e7%8b%ae2.png"); - - MailDTO info = new MailDTO(); - info.setTo(DEFAULT_TO); // 收件人邮箱 - info.setSubject("测试html邮件"); // 邮件主题 - info.setType("html"); - info.setCharset("utf-8"); - - info.setText(VelocityUtil.getMergeOutput(context, "template/mail.vm")); - MailUtil mailUtil = new MailUtil(); - mailUtil.sendEmail(info); - } + + private static final String DEFAULT_TO = "xxxxxx@163.com"; + + public static void main(String[] args) throws MessagingException { + VelocityContext context = new VelocityContext(); + context.put("name", "Victor Zhang"); + context.put("hint", "欢迎使用Velocity邮件模板:"); + + // 直接传入一个对象 + context.put("date", new Date()); + + // 传入一个Vector + Hyperlink item1 = new Hyperlink("百度首页", "https://www.baidu.com"); + Hyperlink item2 = new Hyperlink("网易首页", "http://www.163.com/"); + List list = new ArrayList<>(); + list.add(item1); + list.add(item2); + context.put("links", list); + context.put("logo", + "http://images.cnblogs.com/cnblogs_com/jingmoxukong/709053/o_%e6%94%bb%e5%9f%8e%e7%8b%ae2.png"); + + MailDTO info = new MailDTO(); + info.setTo(DEFAULT_TO); // 收件人邮箱 + info.setSubject("测试html邮件"); // 邮件主题 + info.setType("html"); + info.setCharset("utf-8"); + + info.setText(VelocityUtil.getMergeOutput(context, "template/mail.vm")); + MailUtil mailUtil = new MailUtil(); + mailUtil.sendEmail(info); + } + + + public static class Hyperlink { + + private String link; + + private String name; + + Hyperlink(String name, String link) { + this.name = name; + this.link = link; + } + + public String getLink() { + return link; + } + + public void setLink(String link) { + this.link = link; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendTextMail.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendTextMail.java index 59c55125..548e4141 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendTextMail.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendTextMail.java @@ -1,7 +1,7 @@ package io.github.dunwu.javaee.oss.mail; /** - * The Apache License 2.0 - * Copyright (c) 2016 victor zhang - */ + * The Apache License 2.0 + * Copyright (c) 2016 victor zhang + */ import javax.mail.Message; import javax.mail.Session; @@ -15,46 +15,54 @@ * @date 2017/4/5. */ public class SendTextMail { - private static final String MAIL_SERVER_HOST = "smtp.163.com"; - private static final String USER = "xxxxxx"; - private static final String PASSWORD = "******"; - private static final String MAIL_FROM = "xxxxxx@163.com"; - private static final String MAIL_TO = "xxxxxx@163.com"; - private static final String MAIL_CC = "xxxxxx@163.com"; - private static final String MAIL_BCC = "xxxxxx@163.com"; - - public static void main(String[] args) throws Exception { - Properties prop = new Properties(); - prop.setProperty("mail.debug", "true"); - prop.setProperty("mail.host", MAIL_SERVER_HOST); - prop.setProperty("mail.transport.protocol", "smtp"); - prop.setProperty("mail.smtp.auth", "true"); - - // 1、创建session - Session session = Session.getInstance(prop); - Transport ts = null; - - // 2、通过session得到transport对象 - ts = session.getTransport(); - - // 3、连上邮件服务器 - ts.connect(MAIL_SERVER_HOST, USER, PASSWORD); - - // 4、创建邮件 - MimeMessage message = new MimeMessage(session); - - // 邮件消息头 - message.setFrom(new InternetAddress(MAIL_FROM)); // 邮件的发件人 - message.setRecipient(Message.RecipientType.TO, new InternetAddress(MAIL_TO)); // 邮件的收件人 - message.setRecipient(Message.RecipientType.CC, new InternetAddress(MAIL_CC)); // 邮件的抄送人 - message.setRecipient(Message.RecipientType.BCC, new InternetAddress(MAIL_BCC)); // 邮件的密送人 - message.setSubject("测试文本邮件"); // 邮件的标题 - - // 邮件消息体 - message.setText("天下无双。"); - - // 5、发送邮件 - ts.sendMessage(message, message.getAllRecipients()); - ts.close(); - } + + private static final String MAIL_SERVER_HOST = "smtp.163.com"; + + private static final String USER = "xxxxxx"; + + private static final String PASSWORD = "******"; + + private static final String MAIL_FROM = "xxxxxx@163.com"; + + private static final String MAIL_TO = "xxxxxx@163.com"; + + private static final String MAIL_CC = "xxxxxx@163.com"; + + private static final String MAIL_BCC = "xxxxxx@163.com"; + + public static void main(String[] args) throws Exception { + Properties prop = new Properties(); + prop.setProperty("mail.debug", "true"); + prop.setProperty("mail.host", MAIL_SERVER_HOST); + prop.setProperty("mail.transport.protocol", "smtp"); + prop.setProperty("mail.smtp.auth", "true"); + + // 1、创建session + Session session = Session.getInstance(prop); + Transport ts = null; + + // 2、通过session得到transport对象 + ts = session.getTransport(); + + // 3、连上邮件服务器 + ts.connect(MAIL_SERVER_HOST, USER, PASSWORD); + + // 4、创建邮件 + MimeMessage message = new MimeMessage(session); + + // 邮件消息头 + message.setFrom(new InternetAddress(MAIL_FROM)); // 邮件的发件人 + message.setRecipient(Message.RecipientType.TO, new InternetAddress(MAIL_TO)); // 邮件的收件人 + message.setRecipient(Message.RecipientType.CC, new InternetAddress(MAIL_CC)); // 邮件的抄送人 + message.setRecipient(Message.RecipientType.BCC, new InternetAddress(MAIL_BCC)); // 邮件的密送人 + message.setSubject("测试文本邮件"); // 邮件的标题 + + // 邮件消息体 + message.setText("天下无双。"); + + // 5、发送邮件 + ts.sendMessage(message, message.getAllRecipients()); + ts.close(); + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/StoreMail.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/StoreMail.java index e39cbcfb..ff8250ec 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/StoreMail.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/StoreMail.java @@ -1,7 +1,7 @@ package io.github.dunwu.javaee.oss.mail; /** - * The Apache License 2.0 - * Copyright (c) 2016 victor zhang - */ + * The Apache License 2.0 + * Copyright (c) 2016 victor zhang + */ import javax.mail.Folder; import javax.mail.Message; @@ -15,47 +15,53 @@ */ public class StoreMail { - private static final String MAIL_SERVER_HOST = "pop3.163.com"; - private static final String USER = "xxxxxx"; - private static final String PASSWORD = "******"; - private static final String MAIL_FROM = "xxxxxx@163.com"; - private static final String MAIL_TO = "xxxxxx@163.com"; - private static final String MAIL_CC = "xxxxxx@163.com"; - private static final String MAIL_BCC = "xxxxxx@163.com"; + private static final String MAIL_SERVER_HOST = "pop3.163.com"; + private static final String USER = "xxxxxx"; -public static void main(String[] args) throws Exception { + private static final String PASSWORD = "******"; - // 创建一个有具体连接信息的Properties对象 - Properties prop = new Properties(); - prop.setProperty("mail.debug", "true"); - prop.setProperty("mail.store.protocol", "pop3"); - prop.setProperty("mail.pop3.host", MAIL_SERVER_HOST); + private static final String MAIL_FROM = "xxxxxx@163.com"; - // 1、创建session - Session session = Session.getInstance(prop); + private static final String MAIL_TO = "xxxxxx@163.com"; - // 2、通过session得到Store对象 - Store store = session.getStore(); + private static final String MAIL_CC = "xxxxxx@163.com"; - // 3、连上邮件服务器 - store.connect(MAIL_SERVER_HOST, USER, PASSWORD); + private static final String MAIL_BCC = "xxxxxx@163.com"; - // 4、获得邮箱内的邮件夹 - Folder folder = store.getFolder("inbox"); - folder.open(Folder.READ_ONLY); + public static void main(String[] args) throws Exception { - // 获得邮件夹Folder内的所有邮件Message对象 - Message[] messages = folder.getMessages(); - for (int i = 0; i < messages.length; i++) { - String subject = messages[i].getSubject(); - String from = (messages[i].getFrom()[0]).toString(); - System.out.println("第 " + (i + 1) + "封邮件的主题:" + subject); - System.out.println("第 " + (i + 1) + "封邮件的发件人地址:" + from); - } + // 创建一个有具体连接信息的Properties对象 + Properties prop = new Properties(); + prop.setProperty("mail.debug", "true"); + prop.setProperty("mail.store.protocol", "pop3"); + prop.setProperty("mail.pop3.host", MAIL_SERVER_HOST); + + // 1、创建session + Session session = Session.getInstance(prop); + + // 2、通过session得到Store对象 + Store store = session.getStore(); + + // 3、连上邮件服务器 + store.connect(MAIL_SERVER_HOST, USER, PASSWORD); + + // 4、获得邮箱内的邮件夹 + Folder folder = store.getFolder("inbox"); + folder.open(Folder.READ_ONLY); + + // 获得邮件夹Folder内的所有邮件Message对象 + Message[] messages = folder.getMessages(); + for (int i = 0; i < messages.length; i++) { + String subject = messages[i].getSubject(); + String from = (messages[i].getFrom()[0]).toString(); + System.out.println("第 " + (i + 1) + "封邮件的主题:" + subject); + System.out.println("第 " + (i + 1) + "封邮件的发件人地址:" + from); + } + + // 5、关闭 + folder.close(false); + store.close(); + } - // 5、关闭 - folder.close(false); - store.close(); -} } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/sendHtmlMail.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/sendHtmlMail.java index 74eae4ec..f9a486b9 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/sendHtmlMail.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/sendHtmlMail.java @@ -1,7 +1,7 @@ package io.github.dunwu.javaee.oss.mail; /** - * The Apache License 2.0 - * Copyright (c) 2016 victor zhang - */ + * The Apache License 2.0 + * Copyright (c) 2016 victor zhang + */ import javax.activation.DataHandler; import javax.activation.FileDataSource; @@ -19,59 +19,67 @@ * @date 2017/4/5. */ public class sendHtmlMail { - private static final String MAIL_SERVER_HOST = "smtp.163.com"; - private static final String USER = "xxxxxx"; - private static final String PASSWORD = "******"; - private static final String MAIL_FROM = "xxxxxx@163.com"; - private static final String MAIL_TO = "xxxxxx@163.com"; - private static final String MAIL_CC = "xxxxxx@163.com"; - private static final String MAIL_BCC = "xxxxxx@163.com"; - - public static void main(String[] args) throws Exception { - Properties prop = new Properties(); - prop.setProperty("mail.debug", "true"); - prop.setProperty("mail.host", MAIL_SERVER_HOST); - prop.setProperty("mail.transport.protocol", "smtp"); - prop.setProperty("mail.smtp.auth", "true"); - - // 1、创建session - Session session = Session.getInstance(prop); - Transport ts = null; - - // 2、通过session得到transport对象 - ts = session.getTransport(); - - // 3、连上邮件服务器 - ts.connect(MAIL_SERVER_HOST, USER, PASSWORD); - - // 4、创建邮件 - MimeMessage message = new MimeMessage(session); - - // 邮件消息头 - message.setFrom(new InternetAddress(MAIL_FROM)); // 邮件的发件人 - message.setRecipient(Message.RecipientType.TO, new InternetAddress(MAIL_TO)); // 邮件的收件人 - message.setRecipient(Message.RecipientType.CC, new InternetAddress(MAIL_CC)); // 邮件的抄送人 - message.setRecipient(Message.RecipientType.BCC, new InternetAddress(MAIL_BCC)); // 邮件的密送人 - message.setSubject("测试HTML邮件"); // 邮件的标题 - - String htmlContent = "

Hello

" + "

显示图片1.jpg

"; - MimeBodyPart text = new MimeBodyPart(); - text.setContent(htmlContent, "text/html;charset=UTF-8"); - MimeBodyPart image = new MimeBodyPart(); - DataHandler dh = new DataHandler(new FileDataSource("D:\\05_Datas\\图库\\吉他少年背影.png")); - image.setDataHandler(dh); - image.setContentID("abc.jpg"); - - // 描述数据关系 - MimeMultipart mm = new MimeMultipart(); - mm.addBodyPart(text); - mm.addBodyPart(image); - mm.setSubType("related"); - message.setContent(mm); - message.saveChanges(); - - // 5、发送邮件 - ts.sendMessage(message, message.getAllRecipients()); - ts.close(); - } + + private static final String MAIL_SERVER_HOST = "smtp.163.com"; + + private static final String USER = "xxxxxx"; + + private static final String PASSWORD = "******"; + + private static final String MAIL_FROM = "xxxxxx@163.com"; + + private static final String MAIL_TO = "xxxxxx@163.com"; + + private static final String MAIL_CC = "xxxxxx@163.com"; + + private static final String MAIL_BCC = "xxxxxx@163.com"; + + public static void main(String[] args) throws Exception { + Properties prop = new Properties(); + prop.setProperty("mail.debug", "true"); + prop.setProperty("mail.host", MAIL_SERVER_HOST); + prop.setProperty("mail.transport.protocol", "smtp"); + prop.setProperty("mail.smtp.auth", "true"); + + // 1、创建session + Session session = Session.getInstance(prop); + Transport ts = null; + + // 2、通过session得到transport对象 + ts = session.getTransport(); + + // 3、连上邮件服务器 + ts.connect(MAIL_SERVER_HOST, USER, PASSWORD); + + // 4、创建邮件 + MimeMessage message = new MimeMessage(session); + + // 邮件消息头 + message.setFrom(new InternetAddress(MAIL_FROM)); // 邮件的发件人 + message.setRecipient(Message.RecipientType.TO, new InternetAddress(MAIL_TO)); // 邮件的收件人 + message.setRecipient(Message.RecipientType.CC, new InternetAddress(MAIL_CC)); // 邮件的抄送人 + message.setRecipient(Message.RecipientType.BCC, new InternetAddress(MAIL_BCC)); // 邮件的密送人 + message.setSubject("测试HTML邮件"); // 邮件的标题 + + String htmlContent = "

Hello

" + "

显示图片1.jpg

"; + MimeBodyPart text = new MimeBodyPart(); + text.setContent(htmlContent, "text/html;charset=UTF-8"); + MimeBodyPart image = new MimeBodyPart(); + DataHandler dh = new DataHandler(new FileDataSource("D:\\05_Datas\\图库\\吉他少年背影.png")); + image.setDataHandler(dh); + image.setContentID("abc.jpg"); + + // 描述数据关系 + MimeMultipart mm = new MimeMultipart(); + mm.addBodyPart(text); + mm.addBodyPart(image); + mm.setSubType("related"); + message.setContent(mm); + message.saveChanges(); + + // 5、发送邮件 + ts.sendMessage(message, message.getAllRecipients()); + ts.close(); + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/LoadVelocityDemo.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/LoadVelocityDemo.java index cc2fa11a..2710755f 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/LoadVelocityDemo.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/LoadVelocityDemo.java @@ -4,6 +4,10 @@ */ package io.github.dunwu.javaee.oss.template; +import org.apache.velocity.Template; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.VelocityEngine; + import java.io.IOException; import java.io.StringWriter; import java.util.ArrayList; @@ -11,84 +15,82 @@ import java.util.List; import java.util.Properties; -import org.apache.velocity.Template; -import org.apache.velocity.VelocityContext; -import org.apache.velocity.app.VelocityEngine; - /** * @author Victor Zhang * @date 2016/12/22. */ public class LoadVelocityDemo { - public static void main(String[] args) throws IOException { - loadByClasspath(); - loadByFilepath(); - loadByConfig(); - } - - /** - * 加载classpath目录下的vm文件 - */ - public static void loadByClasspath() { - System.out.println("========== loadByClasspath =========="); - - Properties p = new Properties(); - p.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); - VelocityEngine ve = new VelocityEngine(); - ve.init(p); - Template t = ve.getTemplate("template/hello.vm"); - - System.out.println(fillTemplate(t)); - } - - /** - * 根据绝对路径加载,vm文件置于硬盘某分区中 - */ - public static void loadByFilepath() { - System.out.println("========== loadByFilepath =========="); - - Properties p = new Properties(); - p.put(VelocityEngine.FILE_RESOURCE_LOADER_PATH, - "D:\\01_Workspace\\Project\\zp\\javaparty\\src\\toolbox\\template\\src\\main\\resources"); - VelocityEngine ve = new VelocityEngine(); - ve.init(p); - Template t = ve.getTemplate("hello.vm"); - - System.out.println(fillTemplate(t)); - } - - /** - * 根据资源路径加载 - */ - public static void loadByConfig() throws IOException { - System.out.println("========== loadByConfig =========="); - - Properties p = new Properties(); - p.load(LoadVelocityDemo.class.getResourceAsStream("/template/velocity.properties")); - VelocityEngine ve = new VelocityEngine(); - ve.init(p); - Template t = ve.getTemplate("template/hello.vm"); - - System.out.println(fillTemplate(t)); - } - - /** - * 使用文本文件,使用文本文件,如:velocity.properties - */ - private static String fillTemplate(Template t) { - // 初始化VelocityContext - VelocityContext ctx = new VelocityContext(); - ctx.put("name", "victor"); - ctx.put("date", (new Date()).toString()); - List temp = new ArrayList(); - temp.add("1"); - temp.add("2"); - ctx.put("list", temp); - - // 初始化Writer - StringWriter sw = new StringWriter(); - - t.merge(ctx, sw); - return sw.toString(); - } + + public static void main(String[] args) throws IOException { + loadByClasspath(); + loadByFilepath(); + loadByConfig(); + } + + /** + * 加载classpath目录下的vm文件 + */ + public static void loadByClasspath() { + System.out.println("========== loadByClasspath =========="); + + Properties p = new Properties(); + p.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); + VelocityEngine ve = new VelocityEngine(); + ve.init(p); + Template t = ve.getTemplate("template/hello.vm"); + + System.out.println(fillTemplate(t)); + } + + /** + * 根据绝对路径加载,vm文件置于硬盘某分区中 + */ + public static void loadByFilepath() { + System.out.println("========== loadByFilepath =========="); + + Properties p = new Properties(); + p.put(VelocityEngine.FILE_RESOURCE_LOADER_PATH, + "D:\\01_Workspace\\Project\\zp\\javaparty\\src\\toolbox\\template\\src\\main\\resources"); + VelocityEngine ve = new VelocityEngine(); + ve.init(p); + Template t = ve.getTemplate("hello.vm"); + + System.out.println(fillTemplate(t)); + } + + /** + * 根据资源路径加载 + */ + public static void loadByConfig() throws IOException { + System.out.println("========== loadByConfig =========="); + + Properties p = new Properties(); + p.load(LoadVelocityDemo.class.getResourceAsStream("/template/velocity.properties")); + VelocityEngine ve = new VelocityEngine(); + ve.init(p); + Template t = ve.getTemplate("template/hello.vm"); + + System.out.println(fillTemplate(t)); + } + + /** + * 使用文本文件,使用文本文件,如:velocity.properties + */ + private static String fillTemplate(Template t) { + // 初始化VelocityContext + VelocityContext ctx = new VelocityContext(); + ctx.put("name", "victor"); + ctx.put("date", (new Date()).toString()); + List temp = new ArrayList(); + temp.add("1"); + temp.add("2"); + ctx.put("list", temp); + + // 初始化Writer + StringWriter sw = new StringWriter(); + + t.merge(ctx, sw); + return sw.toString(); + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityHelloWorld.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityHelloWorld.java index fc6a95e9..09c17cb7 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityHelloWorld.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityHelloWorld.java @@ -1,7 +1,7 @@ package io.github.dunwu.javaee.oss.template; /** - * The Apache License 2.0 - * Copyright (c) 2016 Victor Zhang - */ + * The Apache License 2.0 + * Copyright (c) 2016 Victor Zhang + */ import org.apache.velocity.Template; import org.apache.velocity.VelocityContext; @@ -16,26 +16,29 @@ * @date 2016/12/22. */ public class VelocityHelloWorld { - public static void main(String args[]) { - /* 1.初始化 Velocity */ - VelocityEngine velocityEngine = new VelocityEngine(); - velocityEngine.setProperty(VelocityEngine.RESOURCE_LOADER, "file"); - velocityEngine.setProperty(VelocityEngine.FILE_RESOURCE_LOADER_PATH, "D:/01_Workspace/Project/zp/javaparty/src/toolbox/template/src/main/resources"); - velocityEngine.init(); - - /* 2.创建一个上下文对象 */ - VelocityContext context = new VelocityContext(); - - /* 3.添加你的数据对象到上下文 */ - context.put("name", "Victor Zhang"); - context.put("project", "Velocity"); - - /* 4.选择一个模板 */ - Template template = velocityEngine.getTemplate("template/hello.vm"); - - /* 5.将你的数据与模板合并,产生输出内容 */ - StringWriter sw = new StringWriter(); - template.merge(context, sw); - System.out.println("final output:\n" + sw); - } + + public static void main(String args[]) { + /* 1.初始化 Velocity */ + VelocityEngine velocityEngine = new VelocityEngine(); + velocityEngine.setProperty(VelocityEngine.RESOURCE_LOADER, "file"); + velocityEngine.setProperty(VelocityEngine.FILE_RESOURCE_LOADER_PATH, + "D:/01_Workspace/Project/zp/javaparty/src/toolbox/template/src/main/resources"); + velocityEngine.init(); + + /* 2.创建一个上下文对象 */ + VelocityContext context = new VelocityContext(); + + /* 3.添加你的数据对象到上下文 */ + context.put("name", "Victor Zhang"); + context.put("project", "Velocity"); + + /* 4.选择一个模板 */ + Template template = velocityEngine.getTemplate("template/hello.vm"); + + /* 5.将你的数据与模板合并,产生输出内容 */ + StringWriter sw = new StringWriter(); + template.merge(context, sw); + System.out.println("final output:\n" + sw); + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityHelloWorld2.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityHelloWorld2.java index 7c3c0709..96f0f5b9 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityHelloWorld2.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityHelloWorld2.java @@ -1,7 +1,7 @@ package io.github.dunwu.javaee.oss.template; /** - * The Apache License 2.0 - * Copyright (c) 2016 Victor Zhang - */ + * The Apache License 2.0 + * Copyright (c) 2016 Victor Zhang + */ import org.apache.velocity.Template; import org.apache.velocity.VelocityContext; @@ -18,30 +18,33 @@ * @date 2016/12/22. */ public class VelocityHelloWorld2 { - public static void main(String args[]) { - /* 1.初始化 Velocity */ - Properties p = new Properties(); - try { - p.load(VelocityUtil.class.getResourceAsStream("/template/velocity.properties")); - } catch (IOException e) { - e.printStackTrace(); - } - VelocityEngine velocityEngine = new VelocityEngine(); - velocityEngine.init(p); - - /* 2.创建一个上下文对象 */ - VelocityContext context = new VelocityContext(); - - /* 3.添加你的数据对象到上下文 */ - context.put("name", "Victor Zhang"); - context.put("project", "Velocity"); - - /* 4.选择一个模板 */ - Template template = velocityEngine.getTemplate("template/hello.vm"); - - /* 5.将你的数据与模板合并,产生输出内容 */ - StringWriter sw = new StringWriter(); - template.merge(context, sw); - System.out.println("final output:\n" + sw); - } + + public static void main(String args[]) { + /* 1.初始化 Velocity */ + Properties p = new Properties(); + try { + p.load(VelocityUtil.class.getResourceAsStream("/template/velocity.properties")); + } + catch (IOException e) { + e.printStackTrace(); + } + VelocityEngine velocityEngine = new VelocityEngine(); + velocityEngine.init(p); + + /* 2.创建一个上下文对象 */ + VelocityContext context = new VelocityContext(); + + /* 3.添加你的数据对象到上下文 */ + context.put("name", "Victor Zhang"); + context.put("project", "Velocity"); + + /* 4.选择一个模板 */ + Template template = velocityEngine.getTemplate("template/hello.vm"); + + /* 5.将你的数据与模板合并,产生输出内容 */ + StringWriter sw = new StringWriter(); + template.merge(context, sw); + System.out.println("final output:\n" + sw); + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityUtil.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityUtil.java index 833c8b3b..55c2a6a8 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityUtil.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityUtil.java @@ -17,30 +17,34 @@ * @date 2016/12/23. */ public class VelocityUtil { - private static VelocityEngine velocityEngine; - - static { - Properties props = new Properties(); - try { - props.load(VelocityUtil.class.getResourceAsStream("/template/velocity.properties")); - } catch (IOException e) { - e.printStackTrace(); - } - velocityEngine = new VelocityEngine(); - velocityEngine.init(props); - } - - public static String getMergeOutput(VelocityContext context, String templateName) { - Template template = velocityEngine.getTemplate(templateName); - - StringWriter sw = new StringWriter(); - template.merge(context, sw); - String output = sw.toString(); - try { - sw.close(); - } catch (IOException e) { - e.printStackTrace(); - } - return output; - } + + private static VelocityEngine velocityEngine; + + static { + Properties props = new Properties(); + try { + props.load(VelocityUtil.class.getResourceAsStream("/template/velocity.properties")); + } + catch (IOException e) { + e.printStackTrace(); + } + velocityEngine = new VelocityEngine(); + velocityEngine.init(props); + } + + public static String getMergeOutput(VelocityContext context, String templateName) { + Template template = velocityEngine.getTemplate(templateName); + + StringWriter sw = new StringWriter(); + template.merge(context, sw); + String output = sw.toString(); + try { + sw.close(); + } + catch (IOException e) { + e.printStackTrace(); + } + return output; + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/html/example.html b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/html/example.html index f38346b2..dae73f9d 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/html/example.html +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/html/example.html @@ -1,8 +1,8 @@ - 测试html页面 + 测试html页面

牛刀小试

- \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/mail.vm b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/mail.vm index b5c6c594..1529cc18 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/mail.vm +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/mail.vm @@ -23,7 +23,7 @@ ## 页首 - #parse("template/header.vm") + #parse("template/header.vm") @@ -53,9 +53,9 @@

将超链接、超链接名存入对象,将多个这样的对象放入列表:

- #foreach( $item in $links ) - $item.name
- #end + #foreach( $item in $links ) + $item.name
+ #end

传图片url,显示logo

logo @@ -85,7 +85,7 @@ ## 页脚 - #parse("template/footer.vm") + #parse("template/footer.vm") @@ -132,4 +132,4 @@ color: #064977 } - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/velocity.properties b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/velocity.properties index c299635c..799b340b 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/velocity.properties +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/velocity.properties @@ -1,4 +1,4 @@ -resource.loader=file -file.resource.loader.path=D:/01_Workspace/Project/zp/java/javaee-tutorial/codes/oss/src/main/resources -input.encoding=utf-8 -output.encoding=utf-8 +resource.loader = file +file.resource.loader.path = D:/01_Workspace/Project/zp/java/javaee-tutorial/codes/oss/src/main/resources +input.encoding = utf-8 +output.encoding = utf-8 diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/html/JsoupTest.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/html/JsoupTest.java index d5b374cc..fcadab9c 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/html/JsoupTest.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/html/JsoupTest.java @@ -4,9 +4,6 @@ */ package io.github.dunwu.javaee.oss.html; -import java.io.File; -import java.io.IOException; - import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; @@ -14,90 +11,97 @@ import org.junit.Before; import org.junit.Test; +import java.io.File; +import java.io.IOException; + /** * @author Victor Zhang * @date 2016/11/24. */ public class JsoupTest { - final String filePath = System.getProperty("user.dir") + "\\src\\test\\resources\\html\\jsoup-cookbook.html"; - private Document docFromStr; - private Document docFromUrl; - private Document docFromFile; - - /** - * Jsoup 有三种方式加载文档(Document): HTML字符串、URL地址、html文件 - * - * @throws IOException - */ - @Before - public void before() throws IOException { - // 从一个html字符串加载Document对象 - final String html = "First parse" - + "

Parsed HTML into a doc.

"; - docFromStr = Jsoup.parse(html); - - // 从一个URL加载Document对象 - docFromUrl = Jsoup.connect("https://www.baidu.com/").get(); - - // 从一个文件加载Document对象 - File input = new File(filePath); - docFromFile = Jsoup.parse(input, "UTF-8"); - - String htmlFragment = "

Lorem ipsum.

"; - Document doc = Jsoup.parse(htmlFragment); - } - - /** - * 获取html的title、head、body - */ - @Test - public void testGetHeadAndBody() { - System.out.println("title内容:\n" + docFromStr.title()); - System.out.println("head内容:\n" + docFromStr.head()); - System.out.println("body内容:\n" + docFromStr.body()); - } - - /** - * 使用DOM方法来遍历一个文档 - */ - @Test - public void test01() { - // 遍历一个Document对象中所有的链接 - Element content = docFromFile.body(); - Elements links = content.getElementsByTag("a"); - for (Element link : links) { - System.out.println("linkHref: " + link.attr("href")); - System.out.println("linkText: " + link.text()); - } - } - - /** - * 使用选择器语法来查找元素 - */ - @Test - public void testSelect() { - // 带有href属性的a元素 - Elements hrefs = docFromUrl.select("a[href]"); - System.out.println("[hrefs]\n" + hrefs.toString()); - // 扩展名为.png的图片 - Elements pngs = docFromUrl.select("img[src$=.png]"); - System.out.println("[pngs]\n" + pngs.toString()); - // class等于masthead的div标签 - Element head_wrappers = docFromUrl.select("div.head_wrapper").first(); - System.out.println("[head_wrapper:]\n" + head_wrappers.toString()); - // 在h3元素之后的a元素 - Elements resultLinks = docFromUrl.select("div.head_wrapper > a"); - System.out.println("[resultLinks]\n" + resultLinks.toString()); - } - - @Test - public void test() { - // 从元素集合抽取属性、文本和html内容 - Element link = docFromUrl.select("a").first();// 查找第一个a元素 - System.out.println("outerHtml: " + link.outerHtml()); - System.out.println("html: " + link.html()); // 取得链接内的html内容 - System.out.println("href: " + link.attr("href")); // 取得字符串中的文本 - System.out.println("text: " + link.text()); // 取得链接地址中的文本 - - } + + final String filePath = System.getProperty("user.dir") + "\\src\\test\\resources\\html\\jsoup-cookbook.html"; + + private Document docFromStr; + + private Document docFromUrl; + + private Document docFromFile; + + /** + * Jsoup 有三种方式加载文档(Document): HTML字符串、URL地址、html文件 + * @throws IOException + */ + @Before + public void before() throws IOException { + // 从一个html字符串加载Document对象 + final String html = "First parse" + + "

Parsed HTML into a doc.

"; + docFromStr = Jsoup.parse(html); + + // 从一个URL加载Document对象 + docFromUrl = Jsoup.connect("https://www.baidu.com/").get(); + + // 从一个文件加载Document对象 + File input = new File(filePath); + docFromFile = Jsoup.parse(input, "UTF-8"); + + String htmlFragment = "

Lorem ipsum.

"; + Document doc = Jsoup.parse(htmlFragment); + } + + /** + * 获取html的title、head、body + */ + @Test + public void testGetHeadAndBody() { + System.out.println("title内容:\n" + docFromStr.title()); + System.out.println("head内容:\n" + docFromStr.head()); + System.out.println("body内容:\n" + docFromStr.body()); + } + + /** + * 使用DOM方法来遍历一个文档 + */ + @Test + public void test01() { + // 遍历一个Document对象中所有的链接 + Element content = docFromFile.body(); + Elements links = content.getElementsByTag("a"); + for (Element link : links) { + System.out.println("linkHref: " + link.attr("href")); + System.out.println("linkText: " + link.text()); + } + } + + /** + * 使用选择器语法来查找元素 + */ + @Test + public void testSelect() { + // 带有href属性的a元素 + Elements hrefs = docFromUrl.select("a[href]"); + System.out.println("[hrefs]\n" + hrefs.toString()); + // 扩展名为.png的图片 + Elements pngs = docFromUrl.select("img[src$=.png]"); + System.out.println("[pngs]\n" + pngs.toString()); + // class等于masthead的div标签 + Element head_wrappers = docFromUrl.select("div.head_wrapper").first(); + System.out.println("[head_wrapper:]\n" + head_wrappers.toString()); + // 在h3元素之后的a元素 + Elements resultLinks = docFromUrl.select("div.head_wrapper > a"); + System.out.println("[resultLinks]\n" + resultLinks.toString()); + } + + @Test + public void test() { + // 从元素集合抽取属性、文本和html内容 + Element link = docFromUrl.select("a").first();// 查找第一个a元素 + System.out.println("outerHtml: " + link.outerHtml()); + System.out.println("html: " + link.html()); // 取得链接内的html内容 + System.out.println("href: " + link.attr("href")); // 取得字符串中的文本 + System.out.println("text: " + link.text()); // 取得链接地址中的文本 + + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/ImageUtilTest.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/ImageUtilTest.java index 4901badc..0cabb62d 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/ImageUtilTest.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/ImageUtilTest.java @@ -4,70 +4,67 @@ */ package io.github.dunwu.javaee.oss.image; -import java.awt.image.BufferedImage; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; - import io.github.dunwu.javaee.oss.image.dto.ImageParamDTO; -import org.junit.Assert; -import org.junit.Test; - import net.sf.jmimemagic.MagicException; import net.sf.jmimemagic.MagicMatchNotFoundException; import net.sf.jmimemagic.MagicParseException; +import org.junit.Assert; +import org.junit.Test; + +import java.awt.image.BufferedImage; +import java.io.*; /** * @author Victor Zhang * @date 2017/1/16. */ public class ImageUtilTest { - @Test - public void testToFile() throws IOException { - final String oldFile = System.getProperty("user.dir") + "/src/test/resources/images/lion2.jpg"; - final String newFile = System.getProperty("user.dir") + "/src/test/resources/images/lion2_watermark"; - final String warterFile = System.getProperty("user.dir") + "/src/test/resources/images/wartermark.png"; - ImageParamDTO params = new ImageParamDTO(); - params.setFormat("png"); - params.setWaterMark(new ImageParamDTO.WaterMark(9, warterFile, 0.6f)); - ImageUtil.toFile(oldFile, newFile, params); - } + private static byte[] toBytes(File file) throws IOException { + InputStream input = new FileInputStream(file); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int length = 0; + while ((length = input.read(buffer)) != -1) { + output.write(buffer, 0, length); + } + byte[] data = output.toByteArray(); + output.close(); + input.close(); + return data; + } + + @Test + public void testToFile() throws IOException { + final String oldFile = System.getProperty("user.dir") + "/src/test/resources/images/lion2.jpg"; + final String newFile = System.getProperty("user.dir") + "/src/test/resources/images/lion2_watermark"; + final String warterFile = System.getProperty("user.dir") + "/src/test/resources/images/wartermark.png"; + + ImageParamDTO params = new ImageParamDTO(); + params.setFormat("png"); + params.setWaterMark(new ImageParamDTO.WaterMark(9, warterFile, 0.6f)); + ImageUtil.toFile(oldFile, newFile, params); + } - @Test - public void testToBufferedImage() throws IOException { - final String oldFile = System.getProperty("user.dir") + "/src/test/resources/images/lion2.jpg"; + @Test + public void testToBufferedImage() throws IOException { + final String oldFile = System.getProperty("user.dir") + "/src/test/resources/images/lion2.jpg"; - ImageParamDTO params = new ImageParamDTO(); - params.setWidth(64); - params.setHeight(64); + ImageParamDTO params = new ImageParamDTO(); + params.setWidth(64); + params.setHeight(64); - BufferedImage bufferedImage = ImageUtil.toBufferedImage(oldFile, params); - Assert.assertNotNull(bufferedImage); - } + BufferedImage bufferedImage = ImageUtil.toBufferedImage(oldFile, params); + Assert.assertNotNull(bufferedImage); + } - @Test - public void testGetContentType() - throws MagicParseException, MagicException, MagicMatchNotFoundException, IOException { - final String oldFile = System.getProperty("user.dir") + "/src/test/resources/images/lion2.jpg"; - byte[] bytes = toBytes(new File(oldFile)); - String type = ImageUtil.getContentType(bytes); - Assert.assertEquals("image/jpeg", type); - } + @Test + public void testGetContentType() + throws MagicParseException, MagicException, MagicMatchNotFoundException, IOException { + final String oldFile = System.getProperty("user.dir") + "/src/test/resources/images/lion2.jpg"; + byte[] bytes = toBytes(new File(oldFile)); + String type = ImageUtil.getContentType(bytes); + Assert.assertEquals("image/jpeg", type); + } - private static byte[] toBytes(File file) throws IOException { - InputStream input = new FileInputStream(file); - ByteArrayOutputStream output = new ByteArrayOutputStream(); - byte[] buffer = new byte[1024]; - int length = 0; - while ((length = input.read(buffer)) != -1) { - output.write(buffer, 0, length); - } - byte[] data = output.toByteArray(); - output.close(); - input.close(); - return data; - } } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/QRCodeUtilTest.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/QRCodeUtilTest.java index 6542ec2c..5b59861e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/QRCodeUtilTest.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/QRCodeUtilTest.java @@ -4,11 +4,12 @@ */ package io.github.dunwu.javaee.oss.image; -import java.io.File; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.zxing.BarcodeFormat; +import com.google.zxing.DecodeHintType; +import com.google.zxing.EncodeHintType; +import com.google.zxing.WriterException; import io.github.dunwu.javaee.oss.image.dto.BarcodeParamDTO; import org.junit.Assert; import org.junit.Before; @@ -16,12 +17,10 @@ import org.junit.Test; import org.junit.runners.MethodSorters; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.zxing.BarcodeFormat; -import com.google.zxing.DecodeHintType; -import com.google.zxing.EncodeHintType; -import com.google.zxing.WriterException; +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; /** * 测试qrcode工具类 @@ -31,67 +30,71 @@ */ @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class QRCodeUtilTest { - private static final String qrcodeFile = "d:\\qrcode.png"; - private String jsonContent = null; - private BarcodeParamDTO paramDTO = null; - - @Before - public void before() throws JsonProcessingException { - jsonContent = initTestJson(); - paramDTO = initBarcodeParam(); - } - - private String initTestJson() throws JsonProcessingException { - Map userData = new HashMap(); - Map fullname = new HashMap(); - fullname.put("first", "Peng"); - fullname.put("last", "Zhang"); - userData.put("name", fullname); - userData.put("gender", "MALE"); - userData.put("email", "aaa@163.com"); - - ObjectMapper mapper = new ObjectMapper(); // can reuse, share globally - return mapper.writeValueAsString(userData); - } - - private BarcodeParamDTO initBarcodeParam() { - BarcodeParamDTO paramDTO = new BarcodeParamDTO(); - paramDTO.setWidth(200); - paramDTO.setHeight(200); - paramDTO.setFilepath(qrcodeFile); - paramDTO.setImageFormat("png"); - paramDTO.setBarcodeFormat(BarcodeFormat.QR_CODE); - - // 编码参数 - Map encodeHints = new HashMap(); - encodeHints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); - paramDTO.setEncodeHints(encodeHints); - - // 解码参数 - HashMap decodeHints = new HashMap(); - decodeHints.put(DecodeHintType.CHARACTER_SET, "UTF-8"); - paramDTO.setDecodeHints(decodeHints); - - return paramDTO; - } - - /** - * 测试创建qrcode图片 - */ - @Test - public void test01() throws IOException, WriterException { - QRCodeUtil.encode(jsonContent, paramDTO); - File f = new File(qrcodeFile); - Assert.assertTrue(f.exists()); - } - - /** - * 测试解析qrcode图片 - */ - @Test - public void test02() { - String expect = "{\"gender\":\"MALE\",\"name\":{\"last\":\"Zhang\",\"first\":\"Peng\"},\"email\":\"aaa@163.com\"}"; - String content = QRCodeUtil.decode(paramDTO); - Assert.assertEquals(expect, content); - } + + private static final String qrcodeFile = "d:\\qrcode.png"; + + private String jsonContent = null; + + private BarcodeParamDTO paramDTO = null; + + @Before + public void before() throws JsonProcessingException { + jsonContent = initTestJson(); + paramDTO = initBarcodeParam(); + } + + private String initTestJson() throws JsonProcessingException { + Map userData = new HashMap(); + Map fullname = new HashMap(); + fullname.put("first", "Peng"); + fullname.put("last", "Zhang"); + userData.put("name", fullname); + userData.put("gender", "MALE"); + userData.put("email", "aaa@163.com"); + + ObjectMapper mapper = new ObjectMapper(); // can reuse, share globally + return mapper.writeValueAsString(userData); + } + + private BarcodeParamDTO initBarcodeParam() { + BarcodeParamDTO paramDTO = new BarcodeParamDTO(); + paramDTO.setWidth(200); + paramDTO.setHeight(200); + paramDTO.setFilepath(qrcodeFile); + paramDTO.setImageFormat("png"); + paramDTO.setBarcodeFormat(BarcodeFormat.QR_CODE); + + // 编码参数 + Map encodeHints = new HashMap(); + encodeHints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); + paramDTO.setEncodeHints(encodeHints); + + // 解码参数 + HashMap decodeHints = new HashMap(); + decodeHints.put(DecodeHintType.CHARACTER_SET, "UTF-8"); + paramDTO.setDecodeHints(decodeHints); + + return paramDTO; + } + + /** + * 测试创建qrcode图片 + */ + @Test + public void test01() throws IOException, WriterException { + QRCodeUtil.encode(jsonContent, paramDTO); + File f = new File(qrcodeFile); + Assert.assertTrue(f.exists()); + } + + /** + * 测试解析qrcode图片 + */ + @Test + public void test02() { + String expect = "{\"gender\":\"MALE\",\"name\":{\"last\":\"Zhang\",\"first\":\"Peng\"},\"email\":\"aaa@163.com\"}"; + String content = QRCodeUtil.decode(paramDTO); + Assert.assertEquals(expect, content); + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/ThumbnailatorTest.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/ThumbnailatorTest.java index 946006f2..fb286007 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/ThumbnailatorTest.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/ThumbnailatorTest.java @@ -13,11 +13,7 @@ import javax.imageio.ImageIO; import javax.imageio.stream.FileImageOutputStream; import java.awt.image.BufferedImage; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; +import java.io.*; import java.net.URL; import java.util.ArrayList; import java.util.HashSet; @@ -29,153 +25,120 @@ * @date 2017/1/17. */ public class ThumbnailatorTest { - /** - * 测试输入单个对象 - * 执行后会在D:\下生成几张不同尺寸的图片 - */ - @Test - public void testInput01() throws IOException { - final String oldFile = System.getProperty("user.dir") + "/src/test/resources/images/lion.jpg"; - - // 输入形式:文件名 - Thumbnails.of(oldFile) - .size(16, 16) - .toFile("d:\\test_input_16_16.png"); - Assert.assertTrue(new File("d:\\test_input_16_16.png").exists()); - - // 输入形式:File 对象 - Thumbnails.of(new File(oldFile)) - .size(32, 32) - .toFile(new File("d:\\test_input_32_32.png")); - Assert.assertTrue(new File("d:\\test_input_32_32.png").exists()); - - // 输入形式:URL 对象 - URL url = new URL("https://raw.githubusercontent.com/dunwu/JavaParty/master/toolbox/image/src/test/resources/images/lion.jpg"); - Thumbnails.of(url) - .size(64, 64) - .toFile(new File("d:\\test_input_64_64.png")); - Assert.assertTrue(new File("d:\\test_input_64_64.png").exists()); - - // 输入形式:BufferedImage 对象 - BufferedImage originalImage = ImageIO.read(new File(oldFile)); - Thumbnails.of(originalImage) - .size(80, 80) - .toFile(new File("d:\\test_input_80_80.png")); - Assert.assertTrue(new File("d:\\test_input_80_80.png").exists()); - - // 输入形式:InputStream 对象 - InputStream fis = new FileInputStream(oldFile); - Thumbnails.of(fis) - .size(96, 96) - .toFile(new File("d:\\test_input_96_96.png")); - Assert.assertTrue(new File("d:\\test_input_96_96.png").exists()); - } - - /** - * 测试输入多个对象 - * 执行后会在D:\下生成几个含有图片的文件夹 - * 注:如果输入是多个对象,则输出也必须选用输出多个对象的方式 - * - * @throws IOException - */ - @Test - public void testInput02() throws IOException { - final String oldFile = System.getProperty("user.dir") + "/src/test/resources/images/lion.jpg"; - final String oldFile2 = System.getProperty("user.dir") + "/src/test/resources/images/lion2.jpg"; - - createFolderIfNotExist("D:\\fromFilenames"); - Set filenames = new HashSet(); - filenames.add(oldFile); - filenames.add(oldFile2); - Thumbnails.fromFilenames(filenames) - .size(50, 50) - .toFiles(new File("D:\\fromFilenames"), Rename.PREFIX_DOT_THUMBNAIL); - - createFolderIfNotExist("D:\\fromFiles"); - List files = new ArrayList(); - files.add(new File(oldFile)); - files.add(new File(oldFile2)); - Thumbnails.fromFiles(files) - .size(50, 50) - .toFiles(new File("D:\\fromFiles"), Rename.PREFIX_HYPHEN_THUMBNAIL); - } - - @Test - public void testOutput() throws IOException { - final String oldFile = System.getProperty("user.dir") + "/src/test/resources/images/lion2.jpg"; - File file = new File(oldFile); - BufferedImage bufferedImage = ImageIO.read(file); - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - // 输入形式:文件名 - Thumbnails.of(bufferedImage) - .scale(1.0) - .outputFormat("png") - .toOutputStream(outputStream); - byte[] bytes = outputStream.toByteArray(); - FileImageOutputStream imageOutput = new FileImageOutputStream(new File("d:\\lion_output.png")); - imageOutput.write(bytes, 0, bytes.length); - imageOutput.close(); - } - - @Test - public void testResize() throws IOException { - final String oldFile = System.getProperty("user.dir") + "/src/test/resources/images/lion2.jpg"; - Thumbnails.of(oldFile) - .size(16, 16) - .toFile("d:\\lion_16_16.png"); - - Thumbnails.of(oldFile) - .scale(2.0) - .toFile("d:\\lion_scale_2.0.png"); - - Thumbnails.of(oldFile) - .scale(1.0, 0.5) - .toFile("d:\\lion_scale_1.0_0.5.png"); - } - - @Test - public void testRotate() throws IOException { - final String oldFile = System.getProperty("user.dir") + "/src/test/resources/images/lion2.jpg"; - Thumbnails.of(oldFile) - .scale(0.8) - .rotate(90) - .toFile("d:\\lion2_rotate_90.png"); - - Thumbnails.of(oldFile) - .scale(0.8) - .rotate(180) - .toFile("d:\\lion2_rotate_180.png"); - } - - @Test - public void testWatermark() throws IOException { - final String oldFile = System.getProperty("user.dir") + "/src/test/resources/images/lion2.jpg"; - final String wartermarkFile = System.getProperty("user.dir") + "/src/test/resources/images/wartermark.png"; - BufferedImage watermarkImage = ImageIO.read(new File(wartermarkFile)); - Thumbnails.of(oldFile) - .scale(0.8) - .watermark(Positions.BOTTOM_LEFT, watermarkImage, 0.5f) - .toFile("d:\\lion2_watermark.png"); - } - - @Test - public void testBatchChange() throws IOException { - final String oldFile = System.getProperty("user.dir") + "/src/test/resources/images/lion.jpg"; - final String oldFile2 = System.getProperty("user.dir") + "/src/test/resources/images/lion2.jpg"; - final String wartermarkFile = System.getProperty("user.dir") + "/src/test/resources/images/wartermark.png"; - BufferedImage watermarkImage = ImageIO.read(new File(wartermarkFile)); - createFolderIfNotExist("D:\\watermark"); - - Thumbnails.of(oldFile, oldFile2) - .scale(0.8) - .watermark(Positions.BOTTOM_LEFT, watermarkImage, 0.5f) - .toFiles(new File("D:\\watermark"), Rename.PREFIX_DOT_THUMBNAIL); - } - - private void createFolderIfNotExist(String folderPath) throws IOException { - File f = new File(folderPath); - if (!(f.exists() && f.isDirectory())) { - f.mkdirs(); - } - } + + /** + * 测试输入单个对象 执行后会在D:\下生成几张不同尺寸的图片 + */ + @Test + public void testInput01() throws IOException { + final String oldFile = System.getProperty("user.dir") + "/src/test/resources/images/lion.jpg"; + + // 输入形式:文件名 + Thumbnails.of(oldFile).size(16, 16).toFile("d:\\test_input_16_16.png"); + Assert.assertTrue(new File("d:\\test_input_16_16.png").exists()); + + // 输入形式:File 对象 + Thumbnails.of(new File(oldFile)).size(32, 32).toFile(new File("d:\\test_input_32_32.png")); + Assert.assertTrue(new File("d:\\test_input_32_32.png").exists()); + + // 输入形式:URL 对象 + URL url = new URL( + "https://raw.githubusercontent.com/dunwu/JavaParty/master/toolbox/image/src/test/resources/images/lion.jpg"); + Thumbnails.of(url).size(64, 64).toFile(new File("d:\\test_input_64_64.png")); + Assert.assertTrue(new File("d:\\test_input_64_64.png").exists()); + + // 输入形式:BufferedImage 对象 + BufferedImage originalImage = ImageIO.read(new File(oldFile)); + Thumbnails.of(originalImage).size(80, 80).toFile(new File("d:\\test_input_80_80.png")); + Assert.assertTrue(new File("d:\\test_input_80_80.png").exists()); + + // 输入形式:InputStream 对象 + InputStream fis = new FileInputStream(oldFile); + Thumbnails.of(fis).size(96, 96).toFile(new File("d:\\test_input_96_96.png")); + Assert.assertTrue(new File("d:\\test_input_96_96.png").exists()); + } + + /** + * 测试输入多个对象 执行后会在D:\下生成几个含有图片的文件夹 注:如果输入是多个对象,则输出也必须选用输出多个对象的方式 + * @throws IOException + */ + @Test + public void testInput02() throws IOException { + final String oldFile = System.getProperty("user.dir") + "/src/test/resources/images/lion.jpg"; + final String oldFile2 = System.getProperty("user.dir") + "/src/test/resources/images/lion2.jpg"; + + createFolderIfNotExist("D:\\fromFilenames"); + Set filenames = new HashSet(); + filenames.add(oldFile); + filenames.add(oldFile2); + Thumbnails.fromFilenames(filenames).size(50, 50).toFiles(new File("D:\\fromFilenames"), + Rename.PREFIX_DOT_THUMBNAIL); + + createFolderIfNotExist("D:\\fromFiles"); + List files = new ArrayList(); + files.add(new File(oldFile)); + files.add(new File(oldFile2)); + Thumbnails.fromFiles(files).size(50, 50).toFiles(new File("D:\\fromFiles"), Rename.PREFIX_HYPHEN_THUMBNAIL); + } + + @Test + public void testOutput() throws IOException { + final String oldFile = System.getProperty("user.dir") + "/src/test/resources/images/lion2.jpg"; + File file = new File(oldFile); + BufferedImage bufferedImage = ImageIO.read(file); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + // 输入形式:文件名 + Thumbnails.of(bufferedImage).scale(1.0).outputFormat("png").toOutputStream(outputStream); + byte[] bytes = outputStream.toByteArray(); + FileImageOutputStream imageOutput = new FileImageOutputStream(new File("d:\\lion_output.png")); + imageOutput.write(bytes, 0, bytes.length); + imageOutput.close(); + } + + @Test + public void testResize() throws IOException { + final String oldFile = System.getProperty("user.dir") + "/src/test/resources/images/lion2.jpg"; + Thumbnails.of(oldFile).size(16, 16).toFile("d:\\lion_16_16.png"); + + Thumbnails.of(oldFile).scale(2.0).toFile("d:\\lion_scale_2.0.png"); + + Thumbnails.of(oldFile).scale(1.0, 0.5).toFile("d:\\lion_scale_1.0_0.5.png"); + } + + @Test + public void testRotate() throws IOException { + final String oldFile = System.getProperty("user.dir") + "/src/test/resources/images/lion2.jpg"; + Thumbnails.of(oldFile).scale(0.8).rotate(90).toFile("d:\\lion2_rotate_90.png"); + + Thumbnails.of(oldFile).scale(0.8).rotate(180).toFile("d:\\lion2_rotate_180.png"); + } + + @Test + public void testWatermark() throws IOException { + final String oldFile = System.getProperty("user.dir") + "/src/test/resources/images/lion2.jpg"; + final String wartermarkFile = System.getProperty("user.dir") + "/src/test/resources/images/wartermark.png"; + BufferedImage watermarkImage = ImageIO.read(new File(wartermarkFile)); + Thumbnails.of(oldFile).scale(0.8).watermark(Positions.BOTTOM_LEFT, watermarkImage, 0.5f) + .toFile("d:\\lion2_watermark.png"); + } + + @Test + public void testBatchChange() throws IOException { + final String oldFile = System.getProperty("user.dir") + "/src/test/resources/images/lion.jpg"; + final String oldFile2 = System.getProperty("user.dir") + "/src/test/resources/images/lion2.jpg"; + final String wartermarkFile = System.getProperty("user.dir") + "/src/test/resources/images/wartermark.png"; + BufferedImage watermarkImage = ImageIO.read(new File(wartermarkFile)); + createFolderIfNotExist("D:\\watermark"); + + Thumbnails.of(oldFile, oldFile2).scale(0.8).watermark(Positions.BOTTOM_LEFT, watermarkImage, 0.5f) + .toFiles(new File("D:\\watermark"), Rename.PREFIX_DOT_THUMBNAIL); + } + + private void createFolderIfNotExist(String folderPath) throws IOException { + File f = new File(folderPath); + if (!(f.exists() && f.isDirectory())) { + f.mkdirs(); + } + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/template/VelocityUtilTest.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/template/VelocityUtilTest.java index ce9767f0..34801459 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/template/VelocityUtilTest.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/template/VelocityUtilTest.java @@ -12,11 +12,13 @@ * @date 2016/12/23. */ public class VelocityUtilTest { - @Test - public void test() { - VelocityContext context = new VelocityContext(); - context.put("name", "Victor Zhang"); - context.put("project", "Velocity"); - System.out.println(VelocityUtil.getMergeOutput(context, "template/hello.vm")); - } + + @Test + public void test() { + VelocityContext context = new VelocityContext(); + context.put("name", "Victor Zhang"); + context.put("project", "Velocity"); + System.out.println(VelocityUtil.getMergeOutput(context, "template/hello.vm")); + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/test/JUnitExecTest.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/test/JUnitExecTest.java index 5b248b0c..e1344548 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/test/JUnitExecTest.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/test/JUnitExecTest.java @@ -1,14 +1,9 @@ package io.github.dunwu.javaee.oss.test; /** - * The Apache License 2.0 - * Copyright (c) 2016 Victor Zhang - */ + * The Apache License 2.0 + * Copyright (c) 2016 Victor Zhang + */ -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Test; +import org.junit.*; import static org.junit.Assert.fail; @@ -19,77 +14,79 @@ * @date 2016/11/18. */ public class JUnitExecTest { - private static JUnitExecTest instance = new JUnitExecTest(); - - @BeforeClass - public static void beforeClass1() { - System.out.println("@beforeClass1"); - } - - @BeforeClass - public static void beforeClass2() { - System.out.println("@beforeClass2"); - } - - @Before - public void before1() throws Exception { - System.out.println("@before1"); - } - - @Before - public void before2() throws Exception { - System.out.println("@before2"); - } - - @Test - public void testAdd() { - System.out.println(1); - } - - @Test - public void testSubstract() { - System.out.println(2); - } - - @Ignore("Multiply() Not yet implemented") - @Test - public void testMultiply() { - System.out.println(3); - fail("Not yet implemented"); - } - - @Test - public void testDivide() { - System.out.println(4); - } - - @Test(timeout = 1000) - public void testSquareRoot() { - System.out.println(5); - } - - @Test - public void divideByZero() { - System.out.println(6); - } - - @After - public void after1() { - System.out.println("@after1"); - } - - @After - public void after2() { - System.out.println("@after2"); - } - - @AfterClass - public static void afterClass1() { - System.out.println("@afterClass1"); - } - - @AfterClass - public static void afterClass2() { - System.out.println("@afterClass2"); - } + + private static JUnitExecTest instance = new JUnitExecTest(); + + @BeforeClass + public static void beforeClass1() { + System.out.println("@beforeClass1"); + } + + @BeforeClass + public static void beforeClass2() { + System.out.println("@beforeClass2"); + } + + @AfterClass + public static void afterClass1() { + System.out.println("@afterClass1"); + } + + @AfterClass + public static void afterClass2() { + System.out.println("@afterClass2"); + } + + @Before + public void before1() throws Exception { + System.out.println("@before1"); + } + + @Before + public void before2() throws Exception { + System.out.println("@before2"); + } + + @Test + public void testAdd() { + System.out.println(1); + } + + @Test + public void testSubstract() { + System.out.println(2); + } + + @Ignore("Multiply() Not yet implemented") + @Test + public void testMultiply() { + System.out.println(3); + fail("Not yet implemented"); + } + + @Test + public void testDivide() { + System.out.println(4); + } + + @Test(timeout = 1000) + public void testSquareRoot() { + System.out.println(5); + } + + @Test + public void divideByZero() { + System.out.println(6); + } + + @After + public void after1() { + System.out.println("@after1"); + } + + @After + public void after2() { + System.out.println("@after2"); + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/html/jsoup-cookbook.html b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/html/jsoup-cookbook.html index cc16bf8a..848697cf 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/html/jsoup-cookbook.html +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/html/jsoup-cookbook.html @@ -1,90 +1,108 @@ - - Cookbook: jsoup Java HTML parser - - - - - + - - -
- - -
-
-

jsoup cookbook

- + + + +
+ - - - - - \ No newline at end of file + + +
+ + + + + + diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/pom.xml b/codes/javaee-tutorial/javaee-tutorial-servlet/pom.xml index 5e56f950..be9c17ed 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/pom.xml +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/pom.xml @@ -1,6 +1,6 @@ - + 4.0.0 io.github.dunwu diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/AnnotationServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/AnnotationServlet.java index 4c925c22..c3ab27be 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/AnnotationServlet.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/AnnotationServlet.java @@ -1,14 +1,13 @@ package io.github.dunwu.javaee.servlet; -import java.io.IOException; -import java.io.PrintWriter; - import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; public class AnnotationServlet extends HttpServlet { @@ -24,7 +23,7 @@ public AnnotationServlet() { this.log("AnnotationServlet()"); } - public void log(String str){ + public void log(String str) { System.out.println(str); } @@ -39,14 +38,12 @@ public void destroy() { * The doGet method of the servlet.
* * This method is called when a form has its tag value method equals to get. - * * @param request the request send by the client to the server * @param response the response send by the server to the client * @throws ServletException if an error occurred * @throws IOException if an error occurred */ - public void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.log("doGet()"); @@ -67,7 +64,6 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) /** * Initialization of the servlet.
- * * @throws ServletException if an error occure */ public void init() throws ServletException { @@ -75,13 +71,11 @@ public void init() throws ServletException { } @PostConstruct - public void postConstruct(){ + public void postConstruct() { this.log("postConstruct()"); } - - - public @PreDestroy void preDestroy(){ + public @PreDestroy void preDestroy() { this.log("preDestroy()"); } diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ContextParamServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ContextParamServlet.java index fbacad87..1593dbfe 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ContextParamServlet.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ContextParamServlet.java @@ -1,20 +1,18 @@ package io.github.dunwu.javaee.servlet; -import java.io.IOException; -import java.io.PrintWriter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.io.PrintWriter; /** - * 读取web.xml中的 - * 配置在中,只能让对应的servlet使用; + * 读取web.xml中的 配置在中,只能让对应的servlet使用; * 配置在全局中,可以让所有的servlet使用。 */ public class ContextParamServlet extends HttpServlet { @@ -23,8 +21,7 @@ public class ContextParamServlet extends HttpServlet { private final Logger logger = LoggerFactory.getLogger(this.getClass()); - public void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { logger.info("ContextParamServlet 读取 web.xml 中的"); @@ -65,4 +62,5 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) out.flush(); out.close(); } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/FirstServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/FirstServlet.java index d3a4826d..7e2b814e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/FirstServlet.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/FirstServlet.java @@ -1,28 +1,26 @@ package io.github.dunwu.javaee.servlet; -import java.io.IOException; -import java.io.PrintWriter; - import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; /** * 第一个Servlet */ public class FirstServlet extends HttpServlet { + private static final long serialVersionUID = 2386052823761867369L; /** - * 以 GET 方式访问页面时执行该函数。 - * 执行 doGet 前会先执行 getLastModified,如果浏览器发现 getLastModified 返回的数值 - * 与上次访问时返回的数值相同,则认为该文档没有更新,浏览器采用缓存而不执行 doGet。 - * 如果 getLastModified 返回 -1,则认为是时刻更新的,总是执行该函数。 + * 以 GET 方式访问页面时执行该函数。 执行 doGet 前会先执行 getLastModified,如果浏览器发现 getLastModified 返回的数值 + * 与上次访问时返回的数值相同,则认为该文档没有更新,浏览器采用缓存而不执行 doGet。 如果 getLastModified 返回 + * -1,则认为是时刻更新的,总是执行该函数。 */ @Override - public void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 调用 HttpServlet 自带的日志函数输出信息到控制台 this.log("执行 doGet 方法... "); @@ -35,8 +33,7 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) * 以 POST 方式访问页面时执行该函数。 */ @Override - public void doPost(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { + public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 调用 HttpServlet 自带的日志函数输出信息到控制台 this.log("执行 doPost 方法... "); @@ -46,9 +43,8 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) } /** - * 返回该 Servlet 生成的文档的更新时间。对 Get 方式访问有效。 - * 返回的时间为相对于 1970年1月1日08:00:00 的毫秒数。 - * 如果为 -1 则认为是实时更新。默认为 -1。 + * 返回该 Servlet 生成的文档的更新时间。对 Get 方式访问有效。 返回的时间为相对于 1970年1月1日08:00:00 的毫秒数。 如果为 -1 + * 则认为是实时更新。默认为 -1。 */ @Override public long getLastModified(HttpServletRequest request) { @@ -60,7 +56,7 @@ public long getLastModified(HttpServletRequest request) { } private void execute(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException{ + throws ServletException, IOException { response.setCharacterEncoding("UTF-8"); request.setCharacterEncoding("UTF-8"); @@ -80,8 +76,10 @@ private void execute(HttpServletRequest request, HttpServletResponse response) out.println(" "); out.println(" 以 " + method + " 方式访问该页面。取到的 param 参数为:" + param + "
"); - out.println("
"); - out.println("
"); + out.println("
"); + out.println("
"); // 由客户端浏览器读取该文档的更新时间 out.println(" "); diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ForwardServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ForwardServlet.java index b6300feb..5938e942 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ForwardServlet.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ForwardServlet.java @@ -1,30 +1,28 @@ package io.github.dunwu.javaee.servlet; -import java.io.IOException; -import java.util.Date; - import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Date; public class ForwardServlet extends HttpServlet { private static final long serialVersionUID = -291840563095094360L; - public void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String destination = request.getParameter("destination"); // 跳转到 /WEB-INF/web.xml。通过地址栏输入网址是不能访问到该文件的,但是 forward 可以 - if("file".equals(destination)){ + if ("file".equals(destination)) { RequestDispatcher dispatcher = request.getRequestDispatcher("/WEB-INF/web.xml"); dispatcher.forward(request, response); } // 跳转到 /forward.jsp - else if("jsp".equals(destination)){ + else if ("jsp".equals(destination)) { // 通过 setAttribute 方法传递一个 Date 对象给 JSP 页面 Date date = new Date(); request.setAttribute("date", date); @@ -32,19 +30,19 @@ else if("jsp".equals(destination)){ dispatcher.forward(request, response); } // 跳转到另一个 Servlet - else if("servlet".equals(destination)){ + else if ("servlet".equals(destination)) { RequestDispatcher dispatcher = request.getRequestDispatcher("/servlet/LifeCycleServlet"); dispatcher.forward(request, response); } - else{ + else { response.setCharacterEncoding("UTF-8"); response.setContentType("text/html; charset=UTF-8"); response.getWriter().println("缺少参数。用法:" + request.getRequestURL() + "?destination=jsp 或者 file 或者 servlet "); } } - public void doPut(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { + + public void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/HelloServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/HelloServlet.java index 41084cc6..6f81d1c7 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/HelloServlet.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/HelloServlet.java @@ -16,94 +16,91 @@ * @date 2016/12/23. */ public class HelloServlet extends HttpServlet { - private static final long serialVersionUID = 1L; - - /** - * Constructor of the object. - */ - public HelloServlet() { - super(); - } - - /** - * Destruction of the servlet.
- */ - public void destroy() { - super.destroy(); // Just puts "destroy" string in log - // Put your code here - } - - /** - * The doGet method of the servlet.
- * - * This method is called when a form has its tag value method equals to get. - * - * @param request the request send by the client to the server - * @param response the response send by the server to the client - * @throws ServletException if an error occurred - * @throws IOException if an error occurred - */ - public void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - - // 设置 request,response 编码方式 - response.setCharacterEncoding("UTF-8"); - request.setCharacterEncoding("UTF-8"); - - // 设置文档类型 - response.setContentType("text/html"); - PrintWriter out = response.getWriter(); - - // 输出到客户端浏览器 - out.println(""); - out.println(""); - out.println(""); - out.println("A Servlet"); - out.println(" "); - - String requestURI = request.getRequestURI(); - out.println("
"); - out.println("请输入您的名字:"); - out.println(""); - out.println("
"); - out.println(""); - - // 取浏览器提交的 name 参数 - String name = request.getParameter("name"); - - // 如果 name 不为空且长度大于 0 - if(name != null && name.trim().length() > 0){ - out.println("您好, " + name + ". 欢迎来到 Java Web 世界. "); - } - - out.println(" "); - out.println(""); - out.flush(); - out.close(); - } - - /** - * The doPost method of the servlet.
- * - * This method is called when a form has its tag value method equals to post. - * - * @param request the request send by the client to the server - * @param response the response send by the server to the client - * @throws ServletException if an error occurred - * @throws IOException if an error occurred - */ - public void doPost(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - - this.doGet(request, response); - } - - /** - * Initialization of the servlet.
- * - * @throws ServletException if an error occure - */ - public void init() throws ServletException { - // Put your code here - } + + private static final long serialVersionUID = 1L; + + /** + * Constructor of the object. + */ + public HelloServlet() { + super(); + } + + /** + * Destruction of the servlet.
+ */ + public void destroy() { + super.destroy(); // Just puts "destroy" string in log + // Put your code here + } + + /** + * The doGet method of the servlet.
+ * + * This method is called when a form has its tag value method equals to get. + * @param request the request send by the client to the server + * @param response the response send by the server to the client + * @throws ServletException if an error occurred + * @throws IOException if an error occurred + */ + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + + // 设置 request,response 编码方式 + response.setCharacterEncoding("UTF-8"); + request.setCharacterEncoding("UTF-8"); + + // 设置文档类型 + response.setContentType("text/html"); + PrintWriter out = response.getWriter(); + + // 输出到客户端浏览器 + out.println(""); + out.println(""); + out.println(""); + out.println("A Servlet"); + out.println(" "); + + String requestURI = request.getRequestURI(); + out.println("
"); + out.println("请输入您的名字:"); + out.println(""); + out.println("
"); + out.println(""); + + // 取浏览器提交的 name 参数 + String name = request.getParameter("name"); + + // 如果 name 不为空且长度大于 0 + if (name != null && name.trim().length() > 0) { + out.println("您好, " + name + ". 欢迎来到 Java Web 世界. "); + } + + out.println(" "); + out.println(""); + out.flush(); + out.close(); + } + + /** + * The doPost method of the servlet.
+ * + * This method is called when a form has its tag value method equals to post. + * @param request the request send by the client to the server + * @param response the response send by the server to the client + * @throws ServletException if an error occurred + * @throws IOException if an error occurred + */ + public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + + this.doGet(request, response); + } + + /** + * Initialization of the servlet.
+ * @throws ServletException if an error occure + */ + public void init() throws ServletException { + // Put your code here + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ImageServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ImageServlet.java index 4d127d1d..1bc0193a 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ImageServlet.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ImageServlet.java @@ -1,15 +1,10 @@ package io.github.dunwu.javaee.servlet; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.io.*; public class ImageServlet extends HttpServlet { @@ -19,14 +14,12 @@ public ImageServlet() { System.out.println("正在加载 " + this.getClass().getName() + " ... "); } - public void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String referer = request.getHeader("referer"); // 如果直接输入的网址,或者是从别的网站打开的,显示错误信息。 - if(referer == null - || !referer.toLowerCase().startsWith("http://" + request.getServerName().toLowerCase())){ + if (referer == null || !referer.toLowerCase().startsWith("http://" + request.getServerName().toLowerCase())) { // 打开图片 error.gif request.getRequestDispatcher("/error.gif").forward(request, response); return; @@ -40,7 +33,7 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) this.log("请求文件 " + file.getAbsolutePath()); // 如果文件不存在,显示错误信息 - if(!file.exists()){ + if (!file.exists()) { response.getWriter().println("File " + requestURI + " doesn't exist. "); return; } @@ -49,13 +42,13 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) response.setHeader("Content-Disposition", "inline;filename=" + file.getName()); response.setHeader("Connection", "close"); - if(fileName.toLowerCase().endsWith(".jpg")) + if (fileName.toLowerCase().endsWith(".jpg")) // .jpg 图片格式 response.setHeader("Content-Type", "image/jpeg"); - else if(fileName.toLowerCase().endsWith(".gif")) + else if (fileName.toLowerCase().endsWith(".gif")) // .gif 图片格式 response.setHeader("Content-Type", "image/gif"); - else if(fileName.toLowerCase().endsWith(".doc")) + else if (fileName.toLowerCase().endsWith(".doc")) // word 格式 response.setHeader("Content-Type", "application/msword"); else @@ -67,24 +60,26 @@ else if(fileName.toLowerCase().endsWith(".doc")) // 通过 ous 发送给客户端 OutputStream ous = response.getOutputStream(); - try{ + try { // 缓存 byte[] buffer = new byte[1024]; int len = 0; // 读取文件内容并将它发送给客户端浏览器 - while((len=ins.read(buffer)) > -1){ + while ((len = ins.read(buffer)) > -1) { ous.write(buffer, 0, len); } - }finally{ - if(ous != null) ous.close(); - if(ins != null) ins.close(); + } + finally { + if (ous != null) + ous.close(); + if (ins != null) + ins.close(); } } - public void doPost(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { + public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/InitParamServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/InitParamServlet.java index 67cef195..23c4b21f 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/InitParamServlet.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/InitParamServlet.java @@ -1,32 +1,30 @@ package io.github.dunwu.javaee.servlet; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.Enumeration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Enumeration; /** - * 初始化参数示例 - * 读取web.xml中的 - * 配置在中,只能让对应的servlet使用; + * 初始化参数示例 读取web.xml中的 配置在中,只能让对应的servlet使用; * 配置在全局中,可以让所有的servlet使用。 */ public class InitParamServlet extends HttpServlet { + private static final long serialVersionUID = 7298032096933866458L; + private final Logger logger = LoggerFactory.getLogger(this.getClass()); - public void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - logger.info("InitParamServlet 初始化用户名、密码参数"); - request.setCharacterEncoding("UTF-8"); + logger.info("InitParamServlet 初始化用户名、密码参数"); + request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); response.setContentType("text/html"); @@ -42,14 +40,14 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) out.println(""); out.println(""); - if(true){ + if (true) { out.println("






用户名、密码为:
"); Enumeration params = this.getInitParameterNames(); - while(params.hasMoreElements()){ - String username = (String)params.nextElement(); + while (params.hasMoreElements()) { + String username = (String) params.nextElement(); String password = this.getInitParameter(username); out.println("[" + username + ", " + password + "], "); - logger.info("用户名={}、密码={}", username, password); + logger.info("用户名={}、密码={}", username, password); } } @@ -59,28 +57,26 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) out.close(); } - public void doPost(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { + public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 提交的 username 参数 String username = request.getParameter("username"); // 提交的 password 参数 String password = request.getParameter("password"); // 取所有的初始化参数名称 Enumeration params = this.getInitParameterNames(); - while(params.hasMoreElements()){ - String usernameParam = (String)params.nextElement(); + while (params.hasMoreElements()) { + String usernameParam = (String) params.nextElement(); // 取参数值 String passnameParam = this.getInitParameter(usernameParam); // 如果 username 匹配且 password 匹配. username 大小写不敏感,password大小写敏感 - if(usernameParam.equalsIgnoreCase(username) - && passnameParam.equals(password)){ + if (usernameParam.equalsIgnoreCase(username) && passnameParam.equals(password)) { // 显示文件。/WEB-INF 下的文件不能通过浏览器访问到,因此是安全的 request.getRequestDispatcher("/WEB-INF/notice.html").forward(request, response); return; } } // username,password 不匹配,显示登录页面 - logger.warn("用户名={}、密码={}不匹配。", username, password); + logger.warn("用户名={}、密码={}不匹配。", username, password); this.doGet(request, response); } diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/InjectionServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/InjectionServlet.java index 059f1726..172424a5 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/InjectionServlet.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/InjectionServlet.java @@ -1,29 +1,28 @@ package io.github.dunwu.javaee.servlet; -import java.io.IOException; -import java.io.PrintWriter; - import javax.annotation.Resource; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; public class InjectionServlet extends HttpServlet { private static final long serialVersionUID = -8526907492073769090L; // 注入的 字符串 - private @Resource(name="hello") String hello; + private @Resource(name = "hello") String hello; + // 注入的 整数 - private @Resource(name="i") int i; + private @Resource(name = "i") int i; // 注入更常见的写法 - @Resource(name="persons") + @Resource(name = "persons") private String persons; - public void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setCharacterEncoding("UTF-8"); request.setCharacterEncoding("UTF-8"); @@ -39,7 +38,7 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) out.println("注入的整数
  - " + i + "
"); out.println("注入的字符串数组
"); - for(String person : persons.split(",")){ + for (String person : persons.split(",")) { out.println("  - " + person + "
"); } diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/LifeCycleServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/LifeCycleServlet.java index 40b476ae..d36041e5 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/LifeCycleServlet.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/LifeCycleServlet.java @@ -1,13 +1,12 @@ package io.github.dunwu.javaee.servlet; -import java.io.IOException; -import java.io.PrintWriter; - import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; public class LifeCycleServlet extends HttpServlet { @@ -22,7 +21,6 @@ public void init() throws ServletException { startPoint = Double.parseDouble(conf.getInitParameter("startPoint")); } - @Override protected void service(HttpServletRequest arg0, HttpServletResponse arg1) throws ServletException, IOException { this.log("执行 service() 方法 ... "); @@ -30,8 +28,7 @@ protected void service(HttpServletRequest arg0, HttpServletResponse arg1) throws } @Override - public void doPost(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { + public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.log("执行 doPost() 方法 ... "); response.setCharacterEncoding("UTF-8"); response.setContentType("text/html"); @@ -43,7 +40,7 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) out.println("

个税计算器
"); - try{ + try { // 从参数中获取的工资数目 double income = new Double(request.getParameter("income")); // 应纳税部分 @@ -51,16 +48,36 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) // 缴税 double tax = 0; - if (charge<=0) {tax=0;} - if (charge>0&&charge<=500) {tax=charge*0.05;} - if (charge>500&&charge<=2000) {tax=charge*0.1-25;} - if (charge>2000&&charge<=5000) {tax=charge*0.15-125;} - if (charge>5000&&charge<=20000) {tax=charge*0.2-375;} - if (charge>20000&&charge<=40000) {tax=charge*0.25-1375;} - if (charge>40000&&charge<=60000) {tax=charge*0.30-3375;} - if (charge>60000&&charge<=80000) {tax=charge*0.35-6375;} - if (charge>80000&&charge<=100000) {tax=charge*0.4-10375;} - if (charge>100000) {tax=charge*0.45-15375;} + if (charge <= 0) { + tax = 0; + } + if (charge > 0 && charge <= 500) { + tax = charge * 0.05; + } + if (charge > 500 && charge <= 2000) { + tax = charge * 0.1 - 25; + } + if (charge > 2000 && charge <= 5000) { + tax = charge * 0.15 - 125; + } + if (charge > 5000 && charge <= 20000) { + tax = charge * 0.2 - 375; + } + if (charge > 20000 && charge <= 40000) { + tax = charge * 0.25 - 1375; + } + if (charge > 40000 && charge <= 60000) { + tax = charge * 0.30 - 3375; + } + if (charge > 60000 && charge <= 80000) { + tax = charge * 0.35 - 6375; + } + if (charge > 80000 && charge <= 100000) { + tax = charge * 0.4 - 10375; + } + if (charge > 100000) { + tax = charge * 0.45 - 15375; + } out.println("
"); out.println("
您的工资为
" + income + " 元
"); @@ -72,7 +89,8 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) out.println(""); - }catch(Exception e){ + } + catch (Exception e) { out.println("请输入数值类型数据。"); } out.println(""); @@ -82,8 +100,7 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) } @Override - public void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.log("执行 doGet() 方法 ... "); response.setCharacterEncoding("UTF-8"); response.setContentType("text/html"); @@ -96,11 +113,13 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) out.println("
"); out.println("
"); - out.println("
您的工资为
单位:元
"); + out.println( + "
您的工资为
单位:元
"); out.println("

"); out.println("
"); - out.println("
"); + out.println( + "
"); out.println("
"); out.println("
"); diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/PostServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/PostServlet.java index 2b0ea507..855b9eb8 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/PostServlet.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/PostServlet.java @@ -1,28 +1,25 @@ package io.github.dunwu.javaee.servlet; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - public class PostServlet extends HttpServlet { private static final long serialVersionUID = 2112378505872783022L; - public void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setCharacterEncoding("UTF-8"); response.getWriter().println("请使用 POST 方式提交数据。"); } - public void doPost(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { + public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setCharacterEncoding("UTF-8"); request.setCharacterEncoding("UTF-8"); @@ -39,7 +36,8 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) // 取 年龄. 需要把 字符串 转换为 int. // 如果格式不对会抛出 NumberFormattingException age = Integer.parseInt(request.getParameter("age")); - } catch (Exception e) { + } + catch (Exception e) { } Date birthday = null; @@ -48,7 +46,8 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) // 如果格式不对会抛出 ParseException DateFormat format = new SimpleDateFormat("yyyy-MM-dd"); birthday = format.parse(request.getParameter("birthday")); - } catch (Exception e) { + } + catch (Exception e) { } // 从 多选框 checkbox 中取多个值 @@ -64,15 +63,13 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) response.setContentType("text/html"); PrintWriter out = response.getWriter(); - out - .println(""); + out.println(""); out.println(""); out.println("感谢您提交信息"); out.println(""); out.println(""); @@ -98,8 +94,7 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) out.println("
"); out.println("
您的密码:
"); - out.println("
" + password - + "
"); + out.println("
" + password + "
"); out.println("
"); out.println("
"); @@ -133,8 +128,7 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) out.println("
"); out.println("
自我描述:
"); - out.println("
" + description - + "
"); + out.println("
" + description + "
"); out.println("
"); out.println("
"); @@ -145,8 +139,8 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) out.println("
"); out.println("
"); out.println("
"); - out - .println("

"); + out.println( + "

"); out.println("
"); out.println("
"); @@ -156,4 +150,5 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) out.flush(); out.close(); } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/RedirectServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/RedirectServlet.java index df34d10b..331577f0 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/RedirectServlet.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/RedirectServlet.java @@ -1,16 +1,15 @@ package io.github.dunwu.javaee.servlet; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - public class RedirectServlet extends HttpServlet { private static final long serialVersionUID = 4442189888545647793L; @@ -24,20 +23,19 @@ public void init() throws ServletException { map.put("/download/01.mp3", 0); } - public void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String filename = request.getParameter("filename"); - if(filename != null){ + if (filename != null) { // 取下载次数 int hit = map.get(filename); // 下载次数 + 1 后保存 - map.put(filename, ++ hit); + map.put(filename, ++hit); // 重定向到文件 response.sendRedirect(request.getContextPath() + filename); } - else{ + else { response.setCharacterEncoding("UTF-8"); response.setContentType("text/html"); PrintWriter out = response.getWriter(); @@ -55,11 +53,12 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) out.println(" 下载"); out.println(" "); - for(Entry entry : map.entrySet()){ + for (Entry entry : map.entrySet()) { out.println(""); out.println(" " + entry.getKey() + ""); out.println(" " + entry.getValue() + ""); - out.println(" 下载"); + out.println(" 下载"); out.println(""); } diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/RequestServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/RequestServlet.java index 8ee47db8..e128ef98 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/RequestServlet.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/RequestServlet.java @@ -1,168 +1,168 @@ package io.github.dunwu.javaee.servlet; -import java.io.IOException; -import java.io.PrintWriter; -import java.security.Principal; -import java.util.Locale; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.io.PrintWriter; +import java.security.Principal; +import java.util.Locale; /** * 获取HttpServletRequest信息示例 */ public class RequestServlet extends HttpServlet { - private static final long serialVersionUID = -7936817351382556277L; - - private final Logger logger = LoggerFactory.getLogger(this.getClass()); - - /** - * @param accept - * @return 客户端浏览器接受的文件类型 - */ - private String getAccept(String accept) { - StringBuffer buffer = new StringBuffer(); - if (accept.contains("image/gif")) - buffer.append("GIF 文件, "); - if (accept.contains("image/x-xbitmap")) - buffer.append("BMP 文件, "); - if (accept.contains("image/jpeg")) - buffer.append("JPG 文件, "); - if (accept.contains("application/vnd.ms-excel")) - buffer.append("Excel 文件, "); - if (accept.contains("application/vnd.ms-powerpoint")) - buffer.append("PPT 文件, "); - if (accept.contains("application/msword")) - buffer.append("Word 文件, "); - return buffer.toString().replaceAll(", $", ""); - } - - /** - * @param locale - * @return 语言环境名称 - */ - private String getLocale(Locale locale) { - if (Locale.SIMPLIFIED_CHINESE.equals(locale)) - return "简体中文"; - if (Locale.TRADITIONAL_CHINESE.equals(locale)) - return "繁体中文"; - if (Locale.ENGLISH.equals(locale)) - return "英文"; - if (Locale.JAPANESE.equals(locale)) - return "日文"; - return "未知语言环境"; - } - - /** - * @param userAgent - * @return 客户端浏览器信息 - */ - private String getNavigator(String userAgent) { - if (userAgent.indexOf("TencentTraveler") > 0) - return "腾讯浏览器"; - if (userAgent.indexOf("Maxthon") > 0) - return "Maxthon浏览器"; - if (userAgent.indexOf("MyIE2") > 0) - return "MyIE2浏览器"; - if (userAgent.indexOf("Firefox") > 0) - return "Firefox浏览器"; - if (userAgent.indexOf("MSIE") > 0) - return "IE 浏览器"; - return "未知浏览器"; - } - - /** - * @param userAgent - * @return 客户端操作系统 - */ - private String getOS(String userAgent) { - if (userAgent.indexOf("Windows NT 5.1") > 0) - return "Windows XP"; - if (userAgent.indexOf("Windows 98") > 0) - return "Windows 98"; - if (userAgent.indexOf("Windows NT 5.0") > 0) - return "Windows 2000"; - if (userAgent.indexOf("Linux") > 0) - return "Linux"; - if (userAgent.indexOf("Unix") > 0) - return "Unix"; - return "未知"; - } - - @Override - public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - logger.info("访问 doGet"); - - request.setCharacterEncoding("UTF-8"); - response.setCharacterEncoding("UTF-8"); - - response.setContentType("text/html"); - - String authType = request.getAuthType(); - String localAddr = request.getLocalAddr(); - Locale locale = request.getLocale(); - String localName = request.getLocalName(); - String contextPath = request.getContextPath(); - int localPort = request.getLocalPort(); - String method = request.getMethod(); - String pathInfo = request.getPathInfo(); - String pathTranslated = request.getPathTranslated(); - String protocol = request.getProtocol(); - String queryString = request.getQueryString(); - String remoteAddr = request.getRemoteAddr(); - int port = request.getRemotePort(); - String remoteUser = request.getRemoteUser(); - String requestedSessionId = request.getRequestedSessionId(); - String requestURI = request.getRequestURI(); - StringBuffer requestURL = request.getRequestURL(); - String scheme = request.getScheme(); - String serverName = request.getServerName(); - int serverPort = request.getServerPort(); - String servletPath = request.getServletPath(); - Principal userPrincipal = request.getUserPrincipal(); - - String accept = request.getHeader("accept"); - String referer = request.getHeader("referer"); - String userAgent = request.getHeader("user-agent"); - - String serverInfo = this.getServletContext().getServerInfo(); - - PrintWriter out = response.getWriter(); - out.println(""); - out.println(""); - - // 这里之间的信息在浏览器中显示为标题 - out.println(" Request Servlet"); - out.println(" "); - out.println(" "); - - out.println("您的IP为 " + remoteAddr + ";您使用 " + getOS(userAgent) + " 操作系统," - + getNavigator(userAgent) + " 。您使用 " + getLocale(locale) + "。
"); - out.println("服务器IP为 " + localAddr + localAddr + ";服务器使用 " + serverPort + " 端口,您的浏览器使用了 " - + port + " 端口访问本网页。
"); - out.println("服务器软件为:" + serverInfo + "。服务器名称为 " + localName + "。
"); - out.println("您的浏览器接受 " + getAccept(accept) + "。
"); - out.println("您从 " + referer + " 访问到该页面。
"); - out.println("使用的协议为 " + protocol + "。URL协议头 " + scheme + ",服务器名称 " + serverName - + ",您访问的URI为 " + requestURI + "。
"); - out.println("该 Servlet 路径为 " + servletPath + ",该 Servlet 类名为 " + this.getClass().getName() - + "。
"); - out.println("本应用程序在硬盘的根目录为 " + this.getServletContext().getRealPath("") + ",网络相对路径为 " - + contextPath + "。
"); - - out.println("
"); - - out.println("

点击刷新本页面 "); - - out.println(" "); - out.println(""); - out.flush(); - out.close(); - } + + private static final long serialVersionUID = -7936817351382556277L; + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** + * @param accept + * @return 客户端浏览器接受的文件类型 + */ + private String getAccept(String accept) { + StringBuffer buffer = new StringBuffer(); + if (accept.contains("image/gif")) + buffer.append("GIF 文件, "); + if (accept.contains("image/x-xbitmap")) + buffer.append("BMP 文件, "); + if (accept.contains("image/jpeg")) + buffer.append("JPG 文件, "); + if (accept.contains("application/vnd.ms-excel")) + buffer.append("Excel 文件, "); + if (accept.contains("application/vnd.ms-powerpoint")) + buffer.append("PPT 文件, "); + if (accept.contains("application/msword")) + buffer.append("Word 文件, "); + return buffer.toString().replaceAll(", $", ""); + } + + /** + * @param locale + * @return 语言环境名称 + */ + private String getLocale(Locale locale) { + if (Locale.SIMPLIFIED_CHINESE.equals(locale)) + return "简体中文"; + if (Locale.TRADITIONAL_CHINESE.equals(locale)) + return "繁体中文"; + if (Locale.ENGLISH.equals(locale)) + return "英文"; + if (Locale.JAPANESE.equals(locale)) + return "日文"; + return "未知语言环境"; + } + + /** + * @param userAgent + * @return 客户端浏览器信息 + */ + private String getNavigator(String userAgent) { + if (userAgent.indexOf("TencentTraveler") > 0) + return "腾讯浏览器"; + if (userAgent.indexOf("Maxthon") > 0) + return "Maxthon浏览器"; + if (userAgent.indexOf("MyIE2") > 0) + return "MyIE2浏览器"; + if (userAgent.indexOf("Firefox") > 0) + return "Firefox浏览器"; + if (userAgent.indexOf("MSIE") > 0) + return "IE 浏览器"; + return "未知浏览器"; + } + + /** + * @param userAgent + * @return 客户端操作系统 + */ + private String getOS(String userAgent) { + if (userAgent.indexOf("Windows NT 5.1") > 0) + return "Windows XP"; + if (userAgent.indexOf("Windows 98") > 0) + return "Windows 98"; + if (userAgent.indexOf("Windows NT 5.0") > 0) + return "Windows 2000"; + if (userAgent.indexOf("Linux") > 0) + return "Linux"; + if (userAgent.indexOf("Unix") > 0) + return "Unix"; + return "未知"; + } + + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + logger.info("访问 doGet"); + + request.setCharacterEncoding("UTF-8"); + response.setCharacterEncoding("UTF-8"); + + response.setContentType("text/html"); + + String authType = request.getAuthType(); + String localAddr = request.getLocalAddr(); + Locale locale = request.getLocale(); + String localName = request.getLocalName(); + String contextPath = request.getContextPath(); + int localPort = request.getLocalPort(); + String method = request.getMethod(); + String pathInfo = request.getPathInfo(); + String pathTranslated = request.getPathTranslated(); + String protocol = request.getProtocol(); + String queryString = request.getQueryString(); + String remoteAddr = request.getRemoteAddr(); + int port = request.getRemotePort(); + String remoteUser = request.getRemoteUser(); + String requestedSessionId = request.getRequestedSessionId(); + String requestURI = request.getRequestURI(); + StringBuffer requestURL = request.getRequestURL(); + String scheme = request.getScheme(); + String serverName = request.getServerName(); + int serverPort = request.getServerPort(); + String servletPath = request.getServletPath(); + Principal userPrincipal = request.getUserPrincipal(); + + String accept = request.getHeader("accept"); + String referer = request.getHeader("referer"); + String userAgent = request.getHeader("user-agent"); + + String serverInfo = this.getServletContext().getServerInfo(); + + PrintWriter out = response.getWriter(); + out.println(""); + out.println(""); + + // 这里之间的信息在浏览器中显示为标题 + out.println(" Request Servlet"); + out.println(" "); + out.println(" "); + + out.println("您的IP为 " + remoteAddr + ";您使用 " + getOS(userAgent) + " 操作系统," + + getNavigator(userAgent) + " 。您使用 " + getLocale(locale) + "。
"); + out.println("服务器IP为 " + localAddr + localAddr + ";服务器使用 " + serverPort + " 端口,您的浏览器使用了 " + + port + " 端口访问本网页。
"); + out.println("服务器软件为:" + serverInfo + "。服务器名称为 " + localName + "。
"); + out.println("您的浏览器接受 " + getAccept(accept) + "。
"); + out.println("您从 " + referer + " 访问到该页面。
"); + out.println("使用的协议为 " + protocol + "。URL协议头 " + scheme + ",服务器名称 " + serverName + + ",您访问的URI为 " + requestURI + "。
"); + out.println("该 Servlet 路径为 " + servletPath + ",该 Servlet 类名为 " + this.getClass().getName() + + "。
"); + out.println("本应用程序在硬盘的根目录为 " + this.getServletContext().getRealPath("") + ",网络相对路径为 " + + contextPath + "。
"); + + out.println("
"); + + out.println("

点击刷新本页面 "); + + out.println(" "); + out.println(""); + out.flush(); + out.close(); + } } diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ThreadSafetyServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ThreadSafetyServlet.java index b498dbaa..81ed6d65 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ThreadSafetyServlet.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ThreadSafetyServlet.java @@ -1,11 +1,10 @@ package io.github.dunwu.javaee.servlet; -import java.io.IOException; - import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.io.IOException; public class ThreadSafetyServlet extends HttpServlet { @@ -13,24 +12,24 @@ public class ThreadSafetyServlet extends HttpServlet { private String name; - public void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { name = request.getParameter("name"); try { Thread.sleep(5000); - } catch (InterruptedException e) { + } + catch (InterruptedException e) { } response.getWriter().println("您好, " + name + ". 您使用了 GET 方式提交数据"); } - public void doPost(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { + public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { name = request.getParameter("name"); response.getWriter().println("您好, " + name + ". 您使用了 POST 方式提交数据"); } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/UploadServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/UploadServlet.java index 2e7421fe..0f69db01 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/UploadServlet.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/UploadServlet.java @@ -1,36 +1,28 @@ package io.github.dunwu.javaee.servlet; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.PrintWriter; -import java.net.URLEncoder; -import java.util.List; +import org.apache.commons.fileupload.DiskFileUpload; +import org.apache.commons.fileupload.FileItem; +import org.apache.commons.fileupload.FileUploadException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; - -import org.apache.commons.fileupload.DiskFileUpload; -import org.apache.commons.fileupload.FileItem; -import org.apache.commons.fileupload.FileUploadException; +import java.io.*; +import java.net.URLEncoder; +import java.util.List; public class UploadServlet extends HttpServlet { private static final long serialVersionUID = 7523024737218332088L; - public void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setCharacterEncoding("UTF-8"); response.getWriter().println("请以 POST 方式上传文件"); } @SuppressWarnings("unchecked") - public void doPost(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { + public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { File file1 = null, file2 = null; String description1 = null, description2 = null; @@ -58,23 +50,23 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) List list = diskFileUpload.parseRequest(request); out.println("遍历所有的 FileItem ...
"); // 遍历 list 中所有的 FileItem - for(FileItem fileItem : list){ - if(fileItem.isFormField()){ + for (FileItem fileItem : list) { + if (fileItem.isFormField()) { // 如果是 文本域 - if("description1".equals(fileItem.getFieldName())){ + if ("description1".equals(fileItem.getFieldName())) { // 如果该 FileItem 名称为 description1 out.println("遍历到 description1 ...
"); description1 = new String(fileItem.getString().getBytes(), "UTF-8"); } - if("description2".equals(fileItem.getFieldName())){ + if ("description2".equals(fileItem.getFieldName())) { // 如果该 FileItem 名称为 description2 out.println("遍历到 description2 ...
"); description2 = new String(fileItem.getString().getBytes(), "UTF-8"); } } - else{ + else { // 否则,为文件域 - if("file1".equals(fileItem.getFieldName())){ + if ("file1".equals(fileItem.getFieldName())) { // 客户端文件路径构建的 File 对象 File remoteFile = new File(new String(fileItem.getName().getBytes(), "UTF-8")); out.println("遍历到 file1 ...
"); @@ -88,18 +80,19 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) InputStream ins = fileItem.getInputStream(); OutputStream ous = new FileOutputStream(file1); - try{ + try { byte[] buffer = new byte[1024]; int len = 0; - while((len=ins.read(buffer)) > -1) + while ((len = ins.read(buffer)) > -1) ous.write(buffer, 0, len); out.println("已保存文件" + file1.getAbsolutePath() + "
"); - }finally{ + } + finally { ous.close(); ins.close(); } } - if("file2".equals(fileItem.getFieldName())){ + if ("file2".equals(fileItem.getFieldName())) { // 客户端文件路径构建的 File 对象 File remoteFile = new File(new String(fileItem.getName().getBytes(), "UTF-8")); out.println("遍历到 file2 ...
"); @@ -113,13 +106,14 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) InputStream ins = fileItem.getInputStream(); OutputStream ous = new FileOutputStream(file2); - try{ + try { byte[] buffer = new byte[1024]; int len = 0; - while((len=ins.read(buffer)) > -1) + while ((len = ins.read(buffer)) > -1) ous.write(buffer, 0, len); out.println("已保存文件" + file2.getAbsolutePath() + "
"); - }finally{ + } + finally { ous.close(); ins.close(); } @@ -127,7 +121,8 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) } } out.println("Request 解析完毕"); - } catch (FileUploadException e) { + } + catch (FileUploadException e) { // TODO Auto-generated catch block e.printStackTrace(); } @@ -135,25 +130,26 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) out.println("
"); out.println("
"); - if(file1 != null){ - out.println("
"); - out.println("
file1:
"); - out.println("
"); - out.println(" " + file1.getName() + "" ); - out.println("
"); - out.println("
"); + if (file1 != null) { + out.println("
"); + out.println("
file1:
"); + out.println("
"); + out.println(" " + file1.getName() + ""); + out.println("
"); + out.println("
"); } - if(file2 != null){ - out.println("
"); - out.println("
file2:
"); - out.println("
"); - out.println(" " + file2.getName() + "" ); - out.println("
"); - out.println("
"); + if (file2 != null) { + out.println("
"); + out.println("
file2:
"); + out.println("
"); + out.println(" " + file2.getName() + ""); + out.println("
"); + out.println("
"); } - out.println("
"); out.println("
description1:
"); out.println("
"); diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/ProgressUploadServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/ProgressUploadServlet.java index a71abaab..2ba7f00a 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/ProgressUploadServlet.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/ProgressUploadServlet.java @@ -1,28 +1,22 @@ package io.github.dunwu.javaee.servlet.upload; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Iterator; -import java.util.List; +import org.apache.commons.fileupload.FileItem; +import org.apache.commons.fileupload.disk.DiskFileItemFactory; +import org.apache.commons.fileupload.servlet.ServletFileUpload; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; - -import org.apache.commons.fileupload.FileItem; -import org.apache.commons.fileupload.disk.DiskFileItemFactory; -import org.apache.commons.fileupload.servlet.ServletFileUpload; +import java.io.*; +import java.util.Iterator; +import java.util.List; public class ProgressUploadServlet extends HttpServlet { private static final long serialVersionUID = -4935921396709035718L; - public void doPost(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { + public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 上传状态 UploadStatus status = new UploadStatus(); @@ -34,8 +28,7 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) request.getSession(true).setAttribute("uploadStatus", status); // Apache 上传工具 - ServletFileUpload upload = new ServletFileUpload( - new DiskFileItemFactory()); + ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory()); // 设置 listener upload.setProgressListener(listener); @@ -46,9 +39,9 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) for (Iterator it = itemList.iterator(); it.hasNext();) { FileItem item = (FileItem) it.next(); if (item.isFormField()) { - System.out.println("FormField: " + item.getFieldName() - + " = " + item.getString()); - } else { + System.out.println("FormField: " + item.getFieldName() + " = " + item.getString()); + } + else { System.out.println("File: " + item.getName()); // 统一 Linux 与 windows 的路径分隔符 @@ -74,21 +67,20 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) response.getWriter().println("已保存文件:" + saved); } } - } catch (Exception e) { + } + catch (Exception e) { e.printStackTrace(); response.getWriter().println("上传发生错误:" + e.getMessage()); } } - public void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setHeader("Cache-Control", "no-store"); response.setHeader("Pragrma", "no-cache"); response.setDateHeader("Expires", 0); - UploadStatus status = (UploadStatus) request.getSession(true) - .getAttribute("uploadStatus"); + UploadStatus status = (UploadStatus) request.getSession(true).getAttribute("uploadStatus"); if (status == null) { response.getWriter().println("没有上传信息"); @@ -111,8 +103,7 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) double timeLeft = totalTime - time; // 已完成的百分比 - int percent = (int) (100 * (double) status.getBytesRead() / (double) status - .getContentLength()); + int percent = (int) (100 * (double) status.getBytesRead() / (double) status.getContentLength()); // 已完成数 单位:M double length = ((double) status.getBytesRead()) / 1024 / 1024; @@ -121,9 +112,8 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) double totalLength = ((double) status.getContentLength()) / 1024 / 1024; // 格式:百分比||已完成数(M)||文件总长度(M)||传输速率(K)||已用时间(s)||估计总时间(s)||估计剩余时间(s)||正在上传第几个文件 - String value = percent + "||" + length + "||" + totalLength + "||" - + velocity + "||" + time + "||" + totalTime + "||" + timeLeft - + "||" + status.getItems(); + String value = percent + "||" + length + "||" + totalLength + "||" + velocity + "||" + time + "||" + totalTime + + "||" + timeLeft + "||" + status.getItems(); response.getWriter().println(value); } diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/UploadListener.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/UploadListener.java index 94708317..3812ae18 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/UploadListener.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/UploadListener.java @@ -15,4 +15,5 @@ public void update(long bytesRead, long contentLength, int items) { status.setContentLength(contentLength); status.setItems(items); } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/resources/logback.xml index d0ebb62e..b27f1272 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/resources/logback.xml +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/resources/logback.xml @@ -3,7 +3,7 @@ - + @@ -34,12 +34,12 @@ - + - - + + diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/WEB-INF/notice.html b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/WEB-INF/notice.html index c6af966b..62696a93 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/WEB-INF/notice.html +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/WEB-INF/notice.html @@ -1,8 +1,8 @@ - -Eclipse.org Software User Agreement + + Eclipse.org Software User Agreement @@ -11,69 +11,106 @@

Eclipse Foundation Software User Agreement

Usage Of Content

-

THE ECLIPSE FOUNDATION MAKES AVAILABLE SOFTWARE, DOCUMENTATION, INFORMATION AND/OR OTHER MATERIALS FOR OPEN SOURCE PROJECTS - (COLLECTIVELY "CONTENT"). USE OF THE CONTENT IS GOVERNED BY THE TERMS AND CONDITIONS OF THIS AGREEMENT AND/OR THE TERMS AND - CONDITIONS OF LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW. BY USING THE CONTENT, YOU AGREE THAT YOUR USE - OF THE CONTENT IS GOVERNED BY THIS AGREEMENT AND/OR THE TERMS AND CONDITIONS OF ANY APPLICABLE LICENSE AGREEMENTS OR - NOTICES INDICATED OR REFERENCED BELOW. IF YOU DO NOT AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT AND THE TERMS AND - CONDITIONS OF ANY APPLICABLE LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW, THEN YOU MAY NOT USE THE CONTENT.

- -

Applicable Licenses

- -

Unless otherwise indicated, all Content made available by the Eclipse Foundation is provided to you under the terms and conditions of the Eclipse Public License Version 1.0 - ("EPL"). A copy of the EPL is provided with this Content and is also available at http://www.eclipse.org/legal/epl-v10.html. - For purposes of the EPL, "Program" will mean the Content.

- -

Content includes, but is not limited to, source code, object code, documentation and other files maintained in the Eclipse.org CVS repository ("Repository") in CVS - modules ("Modules") and made available as downloadable archives ("Downloads").

- +

THE ECLIPSE FOUNDATION MAKES AVAILABLE SOFTWARE, DOCUMENTATION, INFORMATION AND/OR OTHER MATERIALS FOR OPEN SOURCE + PROJECTS + (COLLECTIVELY "CONTENT"). USE OF THE CONTENT IS GOVERNED BY THE TERMS AND CONDITIONS OF THIS AGREEMENT + AND/OR THE TERMS AND + CONDITIONS OF LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW. BY USING THE CONTENT, YOU AGREE THAT YOUR + USE + OF THE CONTENT IS GOVERNED BY THIS AGREEMENT AND/OR THE TERMS AND CONDITIONS OF ANY APPLICABLE LICENSE AGREEMENTS OR + NOTICES INDICATED OR REFERENCED BELOW. IF YOU DO NOT AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT AND THE TERMS + AND + CONDITIONS OF ANY APPLICABLE LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW, THEN YOU MAY NOT USE THE + CONTENT.

+ +

Applicable Licenses

+ +

Unless otherwise indicated, all Content made available by the Eclipse Foundation is provided to you under the terms + and conditions of the Eclipse Public License Version 1.0 + ("EPL"). A copy of the EPL is provided with this Content and is also available at http://www.eclipse.org/legal/epl-v10.html. + For purposes of the EPL, "Program" will mean the Content.

+ +

Content includes, but is not limited to, source code, object code, documentation and other files maintained in the + Eclipse.org CVS repository ("Repository") in CVS + modules ("Modules") and made available as downloadable archives ("Downloads").

+
    -
  • Content may be structured and packaged into modules to facilitate delivering, extending, and upgrading the Content. Typical modules may include plug-ins ("Plug-ins"), plug-in fragments ("Fragments"), and features ("Features").
  • -
  • Each Plug-in or Fragment may be packaged as a sub-directory or JAR (Java™ ARchive) in a directory named "plugins".
  • -
  • A Feature is a bundle of one or more Plug-ins and/or Fragments and associated material. Each Feature may be packaged as a sub-directory in a directory named "features". Within a Feature, files named "feature.xml" may contain a list of the names and version numbers of the Plug-ins - and/or Fragments associated with that Feature.
  • -
  • Features may also include other Features ("Included Features"). Within a Feature, files named "feature.xml" may contain a list of the names and version numbers of Included Features.
  • -
- -

The terms and conditions governing Plug-ins and Fragments should be contained in files named "about.html" ("Abouts"). The terms and conditions governing Features and -Included Features should be contained in files named "license.html" ("Feature Licenses"). Abouts and Feature Licenses may be located in any directory of a Download or Module -including, but not limited to the following locations:

+
  • Content may be structured and packaged into modules to facilitate delivering, extending, and upgrading the + Content. Typical modules may include plug-ins ("Plug-ins"), plug-in fragments ("Fragments"), and + features ("Features"). +
  • +
  • Each Plug-in or Fragment may be packaged as a sub-directory or JAR (Java™ ARchive) in a directory named + "plugins". +
  • +
  • A Feature is a bundle of one or more Plug-ins and/or Fragments and associated material. Each Feature may be + packaged as a sub-directory in a directory named "features". Within a Feature, files named "feature.xml" + may contain a list of the names and version numbers of the Plug-ins + and/or Fragments associated with that Feature. +
  • +
  • Features may also include other Features ("Included Features"). Within a Feature, files named "feature.xml" + may contain a list of the names and version numbers of Included Features. +
  • + + +

    The terms and conditions governing Plug-ins and Fragments should be contained in files named "about.html" ("Abouts"). + The terms and conditions governing Features and + Included Features should be contained in files named "license.html" ("Feature Licenses"). Abouts + and Feature Licenses may be located in any directory of a Download or Module + including, but not limited to the following locations:

      -
    • The top-level (root) directory
    • -
    • Plug-in and Fragment directories
    • -
    • Inside Plug-ins and Fragments packaged as JARs
    • -
    • Sub-directories of the directory named "src" of certain Plug-ins
    • -
    • Feature directories
    • +
    • The top-level (root) directory
    • +
    • Plug-in and Fragment directories
    • +
    • Inside Plug-ins and Fragments packaged as JARs
    • +
    • Sub-directories of the directory named "src" of certain Plug-ins
    • +
    • Feature directories
    - -

    Note: if a Feature made available by the Eclipse Foundation is installed using the Eclipse Update Manager, you must agree to a license ("Feature Update License") during the -installation process. If the Feature contains Included Features, the Feature Update License should either provide you with the terms and conditions governing the Included Features or -inform you where you can locate them. Feature Update Licenses may be found in the "license" property of files named "feature.properties" found within a Feature. -Such Abouts, Feature Licenses, and Feature Update Licenses contain the terms and conditions (or references to such terms and conditions) that govern your use of the associated Content in -that directory.

    -

    THE ABOUTS, FEATURE LICENSES, AND FEATURE UPDATE LICENSES MAY REFER TO THE EPL OR OTHER LICENSE AGREEMENTS, NOTICES OR TERMS AND CONDITIONS. SOME OF THESE -OTHER LICENSE AGREEMENTS MAY INCLUDE (BUT ARE NOT LIMITED TO):

    +

    Note: if a Feature made available by the Eclipse Foundation is installed using the Eclipse Update Manager, you must + agree to a license ("Feature Update License") during the + installation process. If the Feature contains Included Features, the Feature Update License should either provide you + with the terms and conditions governing the Included Features or + inform you where you can locate them. Feature Update Licenses may be found in the "license" property of + files named "feature.properties" found within a Feature. + Such Abouts, Feature Licenses, and Feature Update Licenses contain the terms and conditions (or references to such + terms and conditions) that govern your use of the associated Content in + that directory.

    + +

    THE ABOUTS, FEATURE LICENSES, AND FEATURE UPDATE LICENSES MAY REFER TO THE EPL OR OTHER LICENSE AGREEMENTS, NOTICES + OR TERMS AND CONDITIONS. SOME OF THESE + OTHER LICENSE AGREEMENTS MAY INCLUDE (BUT ARE NOT LIMITED TO):

    -

    IT IS YOUR OBLIGATION TO READ AND ACCEPT ALL SUCH TERMS AND CONDITIONS PRIOR TO USE OF THE CONTENT. If no About, Feature License, or Feature Update License is provided, please -contact the Eclipse Foundation to determine what terms and conditions govern that particular Content.

    +

    IT IS YOUR OBLIGATION TO READ AND ACCEPT ALL SUCH TERMS AND CONDITIONS PRIOR TO USE OF THE CONTENT. If no About, + Feature License, or Feature Update License is provided, please + contact the Eclipse Foundation to determine what terms and conditions govern that particular Content.

    Cryptography

    -

    Content may contain encryption software. The country in which you are currently may have restrictions on the import, possession, and use, and/or re-export to - another country, of encryption software. BEFORE using any encryption software, please check the country's laws, regulations and policies concerning the import, - possession, or use, and re-export of encryption software, to see if this is permitted.

    - -Java and all Java-based trademarks are trademarks of Sun Microsystems, Inc. in the United States, other countries, or both. +

    Content may contain encryption software. The country in which you are currently may have restrictions on the import, + possession, and use, and/or re-export to + another country, of encryption software. BEFORE using any encryption software, please check the country's laws, + regulations and policies concerning the import, + possession, or use, and re-export of encryption software, to see if this is permitted.

    + +Java and all Java-based trademarks are trademarks of Sun Microsystems, Inc. in the United States, other + countries, or both. diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/WEB-INF/web.xml b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/WEB-INF/web.xml index 4ca585b8..eb2509c4 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/WEB-INF/web.xml +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/WEB-INF/web.xml @@ -1,7 +1,7 @@ - diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/index.jsp b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/index.jsp index 21f6a540..de98af6d 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/index.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/index.jsp @@ -1,33 +1,33 @@ <%@ page contentType="text/html;charset=UTF-8" language="java" %> - javaee-web + javaee-web

    首页

    欢迎访问

    测试链接

    diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/views/jsp/postPersonalInformation.html b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/views/jsp/postPersonalInformation.html index b643fcda..c771ec69 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/views/jsp/postPersonalInformation.html +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/views/jsp/postPersonalInformation.html @@ -1,120 +1,152 @@ -提交用户信息 - - + 提交用户信息 + +
    -
    -
    -
    - 填写用户信息 -
    -
    -
    请填写您的姓名:
    -
    - -
    -
    -
    -
    请填写您的密码:
    -
    - -
    -
    -
    -
    请再次输入密码:
    -
    - -
    -
    -
    -
    请选择性别:
    -
    - - - - -
    -
    -
    -
    请输入年龄:
    -
    - -
    -
    -
    -
    请输入生日:
    -
    - -
    格式:"yyyy-mm-dd" -
    -
    -
    -
    请选择您的爱好
    -
    - - - - - - -
    -
    -
    -
    请选择省市:
    -
    - -
    -
    -
    -
    自我描述:
    -
    - -
    -
    -
    -
    -
    -

    -
    -
    -
    -
    +
    +
    +
    + 填写用户信息 +
    +
    +
    请填写您的姓名:
    +
    + +
    +
    +
    +
    请填写您的密码:
    +
    + +
    +
    +
    +
    请再次输入密码:
    +
    + +
    +
    +
    +
    请选择性别:
    +
    + + + + +
    +
    +
    +
    请输入年龄:
    +
    + +
    +
    +
    +
    请输入生日:
    +
    + +
    格式:"yyyy-mm-dd" +
    +
    +
    +
    请选择您的爱好
    +
    + + + + + + +
    +
    +
    +
    请选择省市:
    +
    + +
    +
    +
    +
    自我描述:
    +
    + +
    +
    +
    +
    +
    +

    +
    +
    +
    +
    diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java index 721835ae..209a0eb7 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java @@ -16,93 +16,95 @@ */ @SuppressWarnings("unused") public class JettyFactory { - private static final int PORT = 8080; - private static final String CONTEXT = "/"; - private static final String RESOURCE_BASE_PATH = "src/main/webapp"; - private static final String WEB_XML_PATH = "/WEB-INF/web.xml"; - private static final String[] TLD_JAR_NAMES = - new String[]{"sitemesh", "spring-webmvc", "shiro-web", "tiles"}; - private static final String WINDOWS_WEBDEFAULT_PATH = "jetty/webdefault.xml"; - public static final int IDE_ECLIPSE = 0; - public static final int IDE_INTELLIJ = 1; + public static final int IDE_ECLIPSE = 0; + public static final int IDE_INTELLIJ = 1; + private static final int PORT = 8080; + private static final String CONTEXT = "/"; + private static final String RESOURCE_BASE_PATH = "src/main/webapp"; + private static final String WEB_XML_PATH = "/WEB-INF/web.xml"; + private static final String[] TLD_JAR_NAMES = new String[] { "sitemesh", "spring-webmvc", "shiro-web", "tiles" }; + private static final String WINDOWS_WEBDEFAULT_PATH = "jetty/webdefault.xml"; + + public static Server initServer() { + Profiles.setProfileAsSystemProperty(Profiles.DEVELOPMENT); + WebAppContext webAppContext = new WebAppContext(); + Server server = new Server(PORT); + server.setHandler(webAppContext); + return server; + } + + public static void initWebAppContext(Server server, int type) throws Exception { + System.out.println("[INFO] Application loading"); + WebAppContext webAppContext = (WebAppContext) server.getHandler(); + webAppContext.setContextPath(CONTEXT); + webAppContext.setResourceBase(getAbsolutePath() + RESOURCE_BASE_PATH); + webAppContext.setDescriptor(getAbsolutePath() + RESOURCE_BASE_PATH + WEB_XML_PATH); + + if (IDE_INTELLIJ == type) { + webAppContext.setDefaultsDescriptor(WINDOWS_WEBDEFAULT_PATH); + supportJspAndSetTldJarNames(server, TLD_JAR_NAMES); + } + else { + webAppContext.setParentLoaderPriority(true); + } + + System.out.println("[INFO] Application loaded"); + } + + public static void reloadWebAppContext(Server server) throws Exception { + WebAppContext webAppContext = (WebAppContext) server.getHandler(); + System.out.println("[INFO] Application reloading"); + webAppContext.stop(); + WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext); + classLoader.addClassPath(getAbsolutePath() + "target/classes"); + classLoader.addClassPath(getAbsolutePath() + "target/test-classes"); + webAppContext.setClassLoader(classLoader); + webAppContext.start(); + System.out.println("[INFO] Application reloaded"); + } + + public static void startServer(Server server) throws Exception { + System.out.println("[HINT] Don't forget to set -XX:MaxPermSize=128m"); + server.start(); + System.out.println("Server running at http://localhost:" + PORT + CONTEXT); + System.out.println("[HINT] Hit Enter to reload the application quickly"); + } + + public static String getAbsolutePath() { + String path = null; + String folderPath = JettyFactory.class.getProtectionDomain().getCodeSource().getLocation().getPath() + .substring(1); + if (folderPath.indexOf("target") > 0) { + path = folderPath.substring(0, folderPath.indexOf("target")); + } + return path; + } + + public static void supportJspAndSetTldJarNames(Server server, String... jarNames) { + WebAppContext context = (WebAppContext) server.getHandler(); + // This webapp will use jsps and jstl. We need to enable the + // AnnotationConfiguration in + // order to correctly set up the jsp container + org.eclipse.jetty.webapp.Configuration.ClassList classlist = org.eclipse.jetty.webapp.Configuration.ClassList + .setServerDefault(server); + classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", + "org.eclipse.jetty.annotations.AnnotationConfiguration"); + // Set the ContainerIncludeJarPattern so that jetty examines these container-path + // jars for + // tlds, web-fragments etc. + // If you omit the jar that contains the jstl .tlds, the jsp engine will scan for + // them + // instead. + ArrayList jarNameExprssions = Lists.newArrayList(".*/[^/]*servlet-api-[^/]*\\.jar$", + ".*/javax.servlet.jsp.jstl-.*\\.jar$", ".*/[^/]*taglibs.*\\.jar$"); + + for (String jarName : jarNames) { + jarNameExprssions.add(".*/" + jarName + "-[^/]*\\.jar$"); + } + + context.setAttribute("org.eclipse.jetty.io.github.dunwu.javaee.server.webapp.ContainerIncludeJarPattern", + StringUtils.join(jarNameExprssions, '|')); + } - - public static Server initServer() { - Profiles.setProfileAsSystemProperty(Profiles.DEVELOPMENT); - WebAppContext webAppContext = new WebAppContext(); - Server server = new Server(PORT); - server.setHandler(webAppContext); - return server; - } - - public static void initWebAppContext(Server server, int type) throws Exception { - System.out.println("[INFO] Application loading"); - WebAppContext webAppContext = (WebAppContext) server.getHandler(); - webAppContext.setContextPath(CONTEXT); - webAppContext.setResourceBase(getAbsolutePath() + RESOURCE_BASE_PATH); - webAppContext.setDescriptor(getAbsolutePath() + RESOURCE_BASE_PATH + WEB_XML_PATH); - - if (IDE_INTELLIJ == type) { - webAppContext.setDefaultsDescriptor(WINDOWS_WEBDEFAULT_PATH); - supportJspAndSetTldJarNames(server, TLD_JAR_NAMES); - } else { - webAppContext.setParentLoaderPriority(true); - } - - System.out.println("[INFO] Application loaded"); - } - - public static void reloadWebAppContext(Server server) throws Exception { - WebAppContext webAppContext = (WebAppContext) server.getHandler(); - System.out.println("[INFO] Application reloading"); - webAppContext.stop(); - WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext); - classLoader.addClassPath(getAbsolutePath() + "target/classes"); - classLoader.addClassPath(getAbsolutePath() + "target/test-classes"); - webAppContext.setClassLoader(classLoader); - webAppContext.start(); - System.out.println("[INFO] Application reloaded"); - } - - - public static void startServer(Server server) throws Exception { - System.out.println("[HINT] Don't forget to set -XX:MaxPermSize=128m"); - server.start(); - System.out.println("Server running at http://localhost:" + PORT + CONTEXT); - System.out.println("[HINT] Hit Enter to reload the application quickly"); - } - - public static String getAbsolutePath() { - String path = null; - String folderPath = JettyFactory.class.getProtectionDomain().getCodeSource().getLocation() - .getPath().substring(1); - if (folderPath.indexOf("target") > 0) { - path = folderPath.substring(0, folderPath.indexOf("target")); - } - return path; - } - - public static void supportJspAndSetTldJarNames(Server server, String... jarNames) { - WebAppContext context = (WebAppContext) server.getHandler(); - // This webapp will use jsps and jstl. We need to enable the AnnotationConfiguration in - // order to correctly set up the jsp container - org.eclipse.jetty.webapp.Configuration.ClassList classlist = - org.eclipse.jetty.webapp.Configuration.ClassList.setServerDefault(server); - classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", - "org.eclipse.jetty.annotations.AnnotationConfiguration"); - // Set the ContainerIncludeJarPattern so that jetty examines these container-path jars for - // tlds, web-fragments etc. - // If you omit the jar that contains the jstl .tlds, the jsp engine will scan for them - // instead. - ArrayList jarNameExprssions = Lists.newArrayList(".*/[^/]*servlet-api-[^/]*\\.jar$", - ".*/javax.servlet.jsp.jstl-.*\\.jar$", ".*/[^/]*taglibs.*\\.jar$"); - - for (String jarName : jarNames) { - jarNameExprssions.add(".*/" + jarName + "-[^/]*\\.jar$"); - } - - context.setAttribute("org.eclipse.jetty.io.github.dunwu.javaee.server.webapp.ContainerIncludeJarPattern", - StringUtils.join(jarNameExprssions, '|')); - } } diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/Profiles.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/Profiles.java index 3a8ee7c4..dcfd3e39 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/Profiles.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/Profiles.java @@ -13,11 +13,15 @@ public class Profiles { public static final String ACTIVE_PROFILE = "spring.profiles.active"; + public static final String DEFAULT_PROFILE = "spring.profiles.default"; public static final String PRODUCTION = "production"; + public static final String DEVELOPMENT = "development"; + public static final String UNIT_TEST = "test"; + public static final String FUNCTIONAL_TEST = "functional"; /** @@ -26,4 +30,5 @@ public class Profiles { public static void setProfileAsSystemProperty(String profile) { System.setProperty(ACTIVE_PROFILE, profile); } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java index dd0ac658..c09f0fc1 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java @@ -8,26 +8,29 @@ * @author Zhang Peng */ public class QuickStartServer { - // private static int STARTUP_TYPE = JettyFactory.IDE_ECLIPSE; - private static int STARTUP_TYPE = JettyFactory.IDE_INTELLIJ; - public static void main(String[] args) throws Exception { - Server server = JettyFactory.initServer(); - JettyFactory.initWebAppContext(server, STARTUP_TYPE); + // private static int STARTUP_TYPE = JettyFactory.IDE_ECLIPSE; + private static int STARTUP_TYPE = JettyFactory.IDE_INTELLIJ; - try { - JettyFactory.startServer(server); + public static void main(String[] args) throws Exception { + Server server = JettyFactory.initServer(); + JettyFactory.initWebAppContext(server, STARTUP_TYPE); + + try { + JettyFactory.startServer(server); + + // 等待用户输入回车重载应用 + while (true) { + char c = (char) System.in.read(); + if (c == '\n') { + JettyFactory.reloadWebAppContext(server); + } + } + } + catch (Exception e) { + e.printStackTrace(); + System.exit(-1); + } + } - // 等待用户输入回车重载应用 - while (true) { - char c = (char) System.in.read(); - if (c == '\n') { - JettyFactory.reloadWebAppContext(server); - } - } - } catch (Exception e) { - e.printStackTrace(); - System.exit(-1); - } - } } diff --git a/codes/javaee-tutorial/javaee-tutorial-session/pom.xml b/codes/javaee-tutorial/javaee-tutorial-session/pom.xml index cdfb1a91..a1d2b06e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/pom.xml +++ b/codes/javaee-tutorial/javaee-tutorial-session/pom.xml @@ -1,4 +1,4 @@ - 4.0.0 diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/AddCookies.java b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/AddCookies.java index bd34fb53..47d3b08e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/AddCookies.java +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/AddCookies.java @@ -3,16 +3,15 @@ */ package io.github.dunwu.javaee.cookie; -import java.io.IOException; -import java.io.PrintWriter; -import java.net.URLEncoder; - import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.net.URLEncoder; /** * @author Zhang Peng @@ -20,51 +19,50 @@ */ @WebServlet("/servlet/AddCookies") public class AddCookies extends HttpServlet { - private static final long serialVersionUID = 1L; - /** - * @see HttpServlet#HttpServlet() - */ - public AddCookies() { - super(); - } + private static final long serialVersionUID = 1L; + + /** + * @see HttpServlet#HttpServlet() + */ + public AddCookies() { + super(); + } - /** - * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) - */ - public void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - // 为名字和姓氏创建 Cookie - Cookie name = new Cookie("name", URLEncoder.encode(request.getParameter("name"), "UTF-8")); // 中文转码 - Cookie url = new Cookie("url", request.getParameter("url")); + /** + * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) + */ + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + // 为名字和姓氏创建 Cookie + Cookie name = new Cookie("name", URLEncoder.encode(request.getParameter("name"), "UTF-8")); // 中文转码 + Cookie url = new Cookie("url", request.getParameter("url")); - // 为两个 Cookie 设置过期日期为 24 小时后 - name.setMaxAge(60 * 60 * 24); - url.setMaxAge(60 * 60 * 24); + // 为两个 Cookie 设置过期日期为 24 小时后 + name.setMaxAge(60 * 60 * 24); + url.setMaxAge(60 * 60 * 24); - // 在响应头中添加两个 Cookie - response.addCookie(name); - response.addCookie(url); + // 在响应头中添加两个 Cookie + response.addCookie(name); + response.addCookie(url); - // 设置响应内容类型 - response.setContentType("text/html;charset=UTF-8"); + // 设置响应内容类型 + response.setContentType("text/html;charset=UTF-8"); - PrintWriter out = response.getWriter(); - String title = "设置 Cookie 实例"; - String docType = "\n"; - out.println(docType + "\n" + "" + title + "\n" - + "\n" + "

    " + title - + "

    \n" + "
      \n" + "
    • 站点名::" + request.getParameter("name") - + "\n
    • " + "
    • 站点 URL::" + request.getParameter("url") - + "\n
    • " + "
    \n" + ""); - } + PrintWriter out = response.getWriter(); + String title = "设置 Cookie 实例"; + String docType = "\n"; + out.println(docType + "\n" + "" + title + "\n" + + "\n" + "

    " + title + "

    \n" + "
      \n" + + "
    • 站点名::" + request.getParameter("name") + "\n
    • " + "
    • 站点 URL::" + + request.getParameter("url") + "\n
    • " + "
    \n" + ""); + } - /** - * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) - */ - protected void doPost(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - doGet(request, response); - } + /** + * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) + */ + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + doGet(request, response); + } } diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/DeleteCookies.java b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/DeleteCookies.java index 5f7dfb64..c7331c08 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/DeleteCookies.java +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/DeleteCookies.java @@ -3,15 +3,14 @@ */ package io.github.dunwu.javaee.cookie; -import java.io.IOException; -import java.io.PrintWriter; - import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; /** * @author Zhang Peng @@ -19,58 +18,59 @@ */ @WebServlet("/servlet/DeleteCookies") public class DeleteCookies extends HttpServlet { - private static final long serialVersionUID = 1L; - /** - * @see HttpServlet#HttpServlet() - */ - public DeleteCookies() { - super(); - } + private static final long serialVersionUID = 1L; + + /** + * @see HttpServlet#HttpServlet() + */ + public DeleteCookies() { + super(); + } - /** - * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) - */ - public void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - Cookie cookie = null; - Cookie[] cookies = null; - // 获取与该域相关的 Cookie 的数组 - cookies = request.getCookies(); + /** + * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) + */ + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + Cookie cookie = null; + Cookie[] cookies = null; + // 获取与该域相关的 Cookie 的数组 + cookies = request.getCookies(); - // 设置响应内容类型 - response.setContentType("text/html;charset=UTF-8"); + // 设置响应内容类型 + response.setContentType("text/html;charset=UTF-8"); - PrintWriter out = response.getWriter(); - String title = "删除 Cookie 实例"; - String docType = "\n"; - out.println(docType + "\n" + "" + title + "\n" - + "\n"); - if (cookies != null) { - out.println("

    Cookie 名称和值

    "); - for (int i = 0; i < cookies.length; i++) { - cookie = cookies[i]; - if ((cookie.getName()).compareTo("url") == 0) { - cookie.setMaxAge(0); - response.addCookie(cookie); - out.print("已删除的 cookie:" + cookie.getName() + "
    "); - } - out.print("名称:" + cookie.getName() + ","); - out.print("值:" + cookie.getValue() + "
    "); - } - } else { - out.println("

    No Cookie founds

    "); - } - out.println(""); - out.println(""); - } + PrintWriter out = response.getWriter(); + String title = "删除 Cookie 实例"; + String docType = "\n"; + out.println( + docType + "\n" + "" + title + "\n" + "\n"); + if (cookies != null) { + out.println("

    Cookie 名称和值

    "); + for (int i = 0; i < cookies.length; i++) { + cookie = cookies[i]; + if ((cookie.getName()).compareTo("url") == 0) { + cookie.setMaxAge(0); + response.addCookie(cookie); + out.print("已删除的 cookie:" + cookie.getName() + "
    "); + } + out.print("名称:" + cookie.getName() + ","); + out.print("值:" + cookie.getValue() + "
    "); + } + } + else { + out.println("

    No Cookie founds

    "); + } + out.println(""); + out.println(""); + } - /** - * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) - */ - protected void doPost(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - doGet(request, response); - } + /** + * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) + */ + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + doGet(request, response); + } } diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/ReadCookies.java b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/ReadCookies.java index f2d63937..5ad663a7 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/ReadCookies.java +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/ReadCookies.java @@ -3,16 +3,15 @@ */ package io.github.dunwu.javaee.cookie; -import java.io.IOException; -import java.io.PrintWriter; -import java.net.URLDecoder; - import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.net.URLDecoder; /** * @author Zhang Peng @@ -20,58 +19,59 @@ */ @WebServlet("/servlet/ReadCookies") public class ReadCookies extends HttpServlet { - private static final long serialVersionUID = 1L; - /** - * @see HttpServlet#HttpServlet() - */ - public ReadCookies() { - super(); - } + private static final long serialVersionUID = 1L; + + /** + * @see HttpServlet#HttpServlet() + */ + public ReadCookies() { + super(); + } - /** - * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) - */ - public void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - Cookie cookie = null; - Cookie[] cookies = null; - // 获取与该域相关的 Cookie 的数组 - cookies = request.getCookies(); + /** + * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) + */ + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + Cookie cookie = null; + Cookie[] cookies = null; + // 获取与该域相关的 Cookie 的数组 + cookies = request.getCookies(); - // 设置响应内容类型 - response.setContentType("text/html;charset=UTF-8"); + // 设置响应内容类型 + response.setContentType("text/html;charset=UTF-8"); - PrintWriter out = response.getWriter(); - String title = "Delete Cookie Example"; - String docType = "\n"; - out.println(docType + "\n" + "" + title + "\n" - + "\n"); - if (cookies != null) { - out.println("

    Cookie 名称和值

    "); - for (int i = 0; i < cookies.length; i++) { - cookie = cookies[i]; - if ((cookie.getName()).compareTo("name") == 0) { - cookie.setMaxAge(0); - response.addCookie(cookie); - out.print("已删除的 cookie:" + cookie.getName() + "
    "); - } - out.print("名称:" + cookie.getName() + ","); - out.print("值:" + URLDecoder.decode(cookie.getValue(), "utf-8") + "
    "); - } - } else { - out.println("

    No Cookie founds

    "); - } - out.println(""); - out.println(""); - } + PrintWriter out = response.getWriter(); + String title = "Delete Cookie Example"; + String docType = "\n"; + out.println( + docType + "\n" + "" + title + "\n" + "\n"); + if (cookies != null) { + out.println("

    Cookie 名称和值

    "); + for (int i = 0; i < cookies.length; i++) { + cookie = cookies[i]; + if ((cookie.getName()).compareTo("name") == 0) { + cookie.setMaxAge(0); + response.addCookie(cookie); + out.print("已删除的 cookie:" + cookie.getName() + "
    "); + } + out.print("名称:" + cookie.getName() + ","); + out.print("值:" + URLDecoder.decode(cookie.getValue(), "utf-8") + "
    "); + } + } + else { + out.println("

    No Cookie founds

    "); + } + out.println(""); + out.println(""); + } - /** - * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) - */ - protected void doPost(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - doGet(request, response); - } + /** + * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) + */ + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + doGet(request, response); + } } diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Person.java b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Person.java index a8308691..8bfab7aa 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Person.java +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Person.java @@ -12,54 +12,54 @@ */ public class Person implements Serializable { - private static final long serialVersionUID = -827111150707830908L; + private static final long serialVersionUID = -827111150707830908L; - private String name; + private String name; - private String password; + private String password; - private int age; + private int age; - private Date birthday; + private Date birthday; - public Person(String name, String password, int age, Date birthday) { -// super(); - this.name = name; - this.password = password; - this.age = age; - this.birthday = birthday; - } + public Person(String name, String password, int age, Date birthday) { + // super(); + this.name = name; + this.password = password; + this.age = age; + this.birthday = birthday; + } - public int getAge() { - return age; - } + public int getAge() { + return age; + } - public void setAge(int age) { - this.age = age; - } + public void setAge(int age) { + this.age = age; + } - public Date getBirthday() { - return birthday; - } + public Date getBirthday() { + return birthday; + } - public void setBirthday(Date birthday) { - this.birthday = birthday; - } + public void setBirthday(Date birthday) { + this.birthday = birthday; + } - public String getName() { - return name; - } + public String getName() { + return name; + } - public void setName(String name) { - this.name = name; - } + public void setName(String name) { + this.name = name; + } - public String getPassword() { - return password; - } + public String getPassword() { + return password; + } - public void setPassword(String password) { - this.password = password; - } -} + public void setPassword(String password) { + this.password = password; + } +} diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Topic.java b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Topic.java index 9f08813a..52371698 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Topic.java +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Topic.java @@ -9,34 +9,34 @@ */ public class Topic { - private int id; + private int id; - private String title; + private String title; - private String content; + private String content; - public String getContent() { - return content; - } + public String getContent() { + return content; + } - public void setContent(String content) { - this.content = content; - } + public void setContent(String content) { + this.content = content; + } - public int getId() { - return id; - } + public int getId() { + return id; + } - public void setId(int id) { - this.id = id; - } + public void setId(int id) { + this.id = id; + } - public String getTitle() { - return title; - } + public String getTitle() { + return title; + } - public void setTitle(String title) { - this.title = title; - } + public void setTitle(String title) { + this.title = title; + } } diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/session/SessionTrackServlet.java b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/session/SessionTrackServlet.java index 2940d7b6..231d11e5 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/session/SessionTrackServlet.java +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/session/SessionTrackServlet.java @@ -3,17 +3,16 @@ */ package io.github.dunwu.javaee.session; -import java.io.IOException; -import java.io.PrintWriter; -import java.text.SimpleDateFormat; -import java.util.Date; - import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; +import java.io.IOException; +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.Date; /** * @author Zhang Peng @@ -21,51 +20,51 @@ */ @WebServlet("/servlet/SessionTrackServlet") public class SessionTrackServlet extends HttpServlet { - private static final long serialVersionUID = 1L; - public void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - // 如果不存在 session 会话,则创建一个 session 对象 - HttpSession session = request.getSession(true); - // 获取 session 创建时间 - Date createTime = new Date(session.getCreationTime()); - // 获取该网页的最后一次访问时间 - Date lastAccessTime = new Date(session.getLastAccessedTime()); + private static final long serialVersionUID = 1L; + + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + // 如果不存在 session 会话,则创建一个 session 对象 + HttpSession session = request.getSession(true); + // 获取 session 创建时间 + Date createTime = new Date(session.getCreationTime()); + // 获取该网页的最后一次访问时间 + Date lastAccessTime = new Date(session.getLastAccessedTime()); + + // 设置日期输出的格式 + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - // 设置日期输出的格式 - SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String title = "Servlet Session 实例"; + Integer visitCount = new Integer(0); + String visitCountKey = new String("visitCount"); + String userIDKey = new String("userID"); + String userID = new String("admin"); - String title = "Servlet Session 实例"; - Integer visitCount = new Integer(0); - String visitCountKey = new String("visitCount"); - String userIDKey = new String("userID"); - String userID = new String("admin"); + // 检查网页上是否有新的访问者 + if (session.isNew()) { + session.setAttribute(userIDKey, userID); + } + else { + visitCount = (Integer) session.getAttribute(visitCountKey); + visitCount = visitCount + 1; + userID = (String) session.getAttribute(userIDKey); + } + session.setAttribute(visitCountKey, visitCount); - // 检查网页上是否有新的访问者 - if (session.isNew()) { - session.setAttribute(userIDKey, userID); - } else { - visitCount = (Integer) session.getAttribute(visitCountKey); - visitCount = visitCount + 1; - userID = (String) session.getAttribute(userIDKey); - } - session.setAttribute(visitCountKey, visitCount); + // 设置响应内容类型 + response.setContentType("text/html;charset=UTF-8"); + PrintWriter out = response.getWriter(); - // 设置响应内容类型 - response.setContentType("text/html;charset=UTF-8"); - PrintWriter out = response.getWriter(); + String docType = "\n"; + out.println(docType + "\n" + "" + title + "\n" + + "\n" + "

    " + title + "

    \n" + + "

    Session 信息

    \n" + "\n" + + "\n" + " \n" + "\n" + " \n" + + " \n" + "\n" + " \n" + " \n" + "\n" + " \n" + " \n" + "\n" + " \n" + " \n" + "\n" + " \n" + " \n" + + "
    Session 信息
    id" + session.getId() + "
    创建时间" + + df.format(createTime) + "
    最后访问时间" + + df.format(lastAccessTime) + "
    用户 ID" + userID + + "
    访问统计:" + visitCount + "
    \n" + ""); + } - String docType = "\n"; - out.println(docType + "\n" + "" + title + "\n" - + "\n" + "

    " + title - + "

    \n" + "

    Session 信息

    \n" - + "\n" + "\n" - + " \n" + "\n" + " \n" - + " \n" + "\n" - + " \n" + " \n" - + "\n" + " \n" + " \n" + "\n" + " \n" + " \n" + "\n" + " \n" + " \n" + "
    Session 信息
    id" + session.getId() + "
    创建时间" + df.format(createTime) + "
    最后访问时间" + df.format(lastAccessTime) - + "
    用户 ID" + userID - + "
    访问统计:" + visitCount - + "
    \n" + ""); - } } diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-session/src/main/resources/logback.xml index 4a10fe94..17812db8 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/resources/logback.xml +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/resources/logback.xml @@ -2,7 +2,7 @@ - + @@ -33,12 +33,12 @@ - - + + - + diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/WEB-INF/web.xml b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/WEB-INF/web.xml index 25bb24d6..100af212 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/WEB-INF/web.xml +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/WEB-INF/web.xml @@ -1,49 +1,49 @@ - - - AddCookies - io.github.dunwu.javaee.servlet.cookie.AddCookies - - - AddCookies - /servlet/AddCookies - + + AddCookies + io.github.dunwu.javaee.servlet.cookie.AddCookies + + + AddCookies + /servlet/AddCookies + - - ReadCookies - io.github.dunwu.javaee.servlet.cookie.ReadCookies - - - ReadCookies - /servlet/ReadCookies - + + ReadCookies + io.github.dunwu.javaee.servlet.cookie.ReadCookies + + + ReadCookies + /servlet/ReadCookies + - - DeleteCookies - io.github.dunwu.javaee.servlet.cookie.DeleteCookies - - - DeleteCookies - /servlet/DeleteCookies - + + DeleteCookies + io.github.dunwu.javaee.servlet.cookie.DeleteCookies + + + DeleteCookies + /servlet/DeleteCookies + - - SessionTrackServlet - io.github.dunwu.javaee.session.SessionTrackServlet - - - SessionTrackServlet - /servlet/SessionTrackServlet - + + SessionTrackServlet + io.github.dunwu.javaee.session.SessionTrackServlet + + + SessionTrackServlet + /servlet/SessionTrackServlet + - - 20 - + + 20 + - + diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/encodeURL.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/encodeURL.jsp index 9efa8d28..2f7de5f7 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/encodeURL.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/encodeURL.jsp @@ -2,15 +2,14 @@ -Cookie Encoding + Cookie Encoding - ">Homepage - - <%= response.encodeURL("index.jsp") %>?c=1&wd=Java - - - +">Homepage + +<%= response.encodeURL("index.jsp") %>?c=1&wd=Java + + diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/index.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/index.jsp index 19ce2916..2839e947 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/index.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/index.jsp @@ -1,26 +1,26 @@ -<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> +<%@ page language="java" pageEncoding="UTF-8" %> <% -String path = request.getContextPath(); -String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; + String path = request.getContextPath(); + String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; %> - - - - My JSP 'index.jsp' starting page - - - - - - - - - - This is my JSP page.
    - + + + + My JSP 'index.jsp' starting page + + + + + + + + + +This is my JSP page.
    + diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/css/style.css b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/css/style.css index 8b9fae5a..8e2eb97a 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/css/style.css +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/css/style.css @@ -1,51 +1,51 @@ body, div, td, input { - font-size: 12px; - margin: 0px; + font-size: 12px; + margin: 0px; } select { - height: 20px; - width: 300px; + height: 20px; + width: 300px; } .title { - font-size: 16px; - padding: 10px; - margin: 10px; - width: 80%; + font-size: 16px; + padding: 10px; + margin: 10px; + width: 80%; } .text { - height: 20px; - width: 300px; - border: 1px solid #AAAAAA; + height: 20px; + width: 300px; + border: 1px solid #AAAAAA; } .line { - margin: 2px; + margin: 2px; } .leftDiv { - width: 110px; - float: left; - height: 22px; - line-height: 22px; - font-weight: bold; + width: 110px; + float: left; + height: 22px; + line-height: 22px; + font-weight: bold; } .rightDiv { - height: 22px; - line-height: 22px; + height: 22px; + line-height: 22px; } .button { - color: #fff; - font-weight: bold; - font-size: 11px; - text-align: center; - padding: .17em 0 .2em .17em; - border-style: solid; - border-width: 1px; - border-color: #9cf #159 #159 #9cf; - background: #69c url(../images/bg-btn-blue.gif) repeat-x; -} \ No newline at end of file + color: #FFFFFF; + font-weight: bold; + font-size: 11px; + text-align: center; + padding: .17em 0 .2em .17em; + border-style: solid; + border-width: 1px; + border-color: #99CCFF #115599 #115599 #99CCFF; + background: #6699CC url(../images/bg-btn-blue.gif) repeat-x; +} diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/encoding.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/encoding.jsp index 9d6c2c0c..792bed1e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/encoding.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/encoding.jsp @@ -3,9 +3,7 @@ <% // 使用中文的 Cookie. name 与 value 都使用 UTF-8 编码. - Cookie cookie = new Cookie( - URLEncoder.encode("姓名", "UTF-8"), - URLEncoder.encode("张三", "UTF-8")); + Cookie cookie = new Cookie(URLEncoder.encode("姓名", "UTF-8"), URLEncoder.encode("张三", "UTF-8")); // 发送到客户端 response.addCookie(cookie); diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/history.js b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/history.js index 441a0ff5..ca664266 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/history.js +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/history.js @@ -1,24 +1,25 @@ function getCookie(name) { - var str = document.cookie; - if (!str || str.indexOf(name + "=") < 0) return; - var cookies = str.split("; "); + var str = document.cookie + if (!str || str.indexOf(name + '=') < 0) return + var cookies = str.split('; ') for (var i = 0; i < cookies.length; i++) { - var cookie = cookies[i]; - if (cookie.indexOf(name + "=") == 0) { - var value = cookie.substring(name.length + 1); + var cookie = cookies[i] + if (cookie.indexOf(name + '=') == 0) { + var value = cookie.substring(name.length + 1) } } } + function setCookie(name, value) { - var expires = (arguments.length > 2) ? arguments[2] : null; - var path = (arguments.length > 3) ? arguments[3] : null; - var domain = (arguments.length > 4) ? arguments[4] : null; - var secure = (arguments.length > 5) ? arguments[5] : false; - document.cookie = name + "=" + encodeURI(value) - + ((expires == null) ? "" : ("; expires=" + expires.toGMTString())) - + ((path == null) ? "" : ("; path=" + path)) - + ((domain == null) ? "" : ("; domain=" + domain)) - + ((secure == true) ? "; secure" : ""); + var expires = (arguments.length > 2) ? arguments[2] : null + var path = (arguments.length > 3) ? arguments[3] : null + var domain = (arguments.length > 4) ? arguments[4] : null + var secure = (arguments.length > 5) ? arguments[5] : false + document.cookie = name + '=' + encodeURI(value) + + ((expires == null) ? '' : ('; expires=' + expires.toGMTString())) + + ((path == null) ? '' : ('; path=' + path)) + + ((domain == null) ? '' : ('; domain=' + domain)) + + ((secure == true) ? '; secure' : '') } -var ss = getCookie('history'); +var ss = getCookie('history') diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/javascript.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/javascript.jsp index f74530bd..4474cf8f 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/javascript.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/javascript.jsp @@ -16,16 +16,17 @@ } } } + function setCookie(name, value) { var expires = (arguments.length > 2) ? arguments[2] : null; var path = (arguments.length > 3) ? arguments[3] : null; var domain = (arguments.length > 4) ? arguments[4] : null; var secure = (arguments.length > 5) ? arguments[5] : false; document.cookie = name + "=" + encodeURI(value) - + ((expires == null) ? "" : ("; expires=" + expires.toGMTString())) - + ((path == null) ? "" : ("; path=" + path)) - + ((domain == null) ? "" : ("; domain=" + domain)) - + ((secure == true) ? "; secure" : ""); + + ((expires == null) ? "" : ("; expires=" + expires.toGMTString())) + + ((path == null) ? "" : ("; path=" + path)) + + ((domain == null) ? "" : ("; domain=" + domain)) + + ((secure == true) ? "; secure" : ""); } diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/setCookie.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/setCookie.jsp index 54c559f3..9879ce3a 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/setCookie.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/setCookie.jsp @@ -20,15 +20,18 @@ if (!isNull(name)) { - Cookie cookie = new Cookie( - URLEncoder.encode(name, "UTF-8"), - URLEncoder.encode(value, "UTF-8")); + Cookie cookie = new Cookie(URLEncoder.encode(name, "UTF-8"), URLEncoder.encode(value, "UTF-8")); - if (!isNull(maxAge)) cookie.setMaxAge(Integer.parseInt(maxAge)); - if (!isNull(domain)) cookie.setDomain(domain); - if (!isNull(path)) cookie.setPath(path); - if (!isNull(comment)) cookie.setComment(comment); - if (!isNull(secure)) cookie.setSecure("true".equalsIgnoreCase(secure)); + if (!isNull(maxAge)) + cookie.setMaxAge(Integer.parseInt(maxAge)); + if (!isNull(domain)) + cookie.setDomain(domain); + if (!isNull(path)) + cookie.setPath(path); + if (!isNull(comment)) + cookie.setComment(comment); + if (!isNull(secure)) + cookie.setSecure("true".equalsIgnoreCase(secure)); response.addCookie(cookie); } diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/session/session.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/session/session.jsp index 19e2ec3d..2e0e3a87 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/session/session.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/session/session.jsp @@ -9,11 +9,9 @@ <% response.setCharacterEncoding("UTF-8"); - Person[] persons = { - new Person("Liu Jinghua", "password1", 34, dateFormat.parse("1982-01-01")), - new Person("Hello Kitty", "hellokitty", 23, dateFormat.parse("1984-02-25")), - new Person("Garfield", "garfield_pass", 23, dateFormat.parse("1994-09-12")), - }; + Person[] persons = {new Person("Liu Jinghua", "password1", 34, dateFormat.parse("1982-01-01")), + new Person("Hello Kitty", "hellokitty", 23, dateFormat.parse("1984-02-25")), + new Person("Garfield", "garfield_pass", 23, dateFormat.parse("1994-09-12")),}; String message = ""; @@ -21,8 +19,10 @@ for (int i = 0; i < persons.length; i++) { // 如果 用户名正确 且 密码正确 - if (persons[i].getName().equalsIgnoreCase(request.getParameter("username")) - && persons[i].getPassword().equals(request.getParameter("password"))) { + if (persons[i].getName().equalsIgnoreCase(request.getParameter("username")) && persons[i].getPassword() + .equals( + request.getParameter( + "password"))) { // 登录成功, 设置将用户的信息以及登录时间保存到 Session session.setAttribute("person", persons[i]); diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java b/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java index bd66a4e1..dfc4513e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java @@ -1,13 +1,13 @@ package io.github.dunwu.javaee.server; -import java.util.ArrayList; - import org.apache.commons.lang3.StringUtils; import org.assertj.core.util.Lists; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.webapp.WebAppClassLoader; import org.eclipse.jetty.webapp.WebAppContext; +import java.util.ArrayList; + /** * JettyFactory 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij * 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 @@ -16,93 +16,95 @@ */ @SuppressWarnings("unused") public class JettyFactory { - private static final int PORT = 9899; - private static final String CONTEXT = "/"; - private static final String RESOURCE_BASE_PATH = "src/main/webapp"; - private static final String WEB_XML_PATH = "/WEB-INF/web.xml"; - private static final String[] TLD_JAR_NAMES = - new String[]{"sitemesh", "spring-webmvc", "shiro-web", "tiles"}; - private static final String WINDOWS_WEBDEFAULT_PATH = "jetty/webdefault.xml"; - - public static final int IDE_ECLIPSE = 0; - public static final int IDE_INTELLIJ = 1; - - - public static Server initServer() { - Profiles.setProfileAsSystemProperty(Profiles.DEVELOPMENT); - WebAppContext webAppContext = new WebAppContext(); - Server server = new Server(PORT); - server.setHandler(webAppContext); - return server; - } - - public static void initWebAppContext(Server server, int type) throws Exception { - System.out.println("[INFO] Application loading"); - WebAppContext webAppContext = (WebAppContext) server.getHandler(); - webAppContext.setContextPath(CONTEXT); - webAppContext.setResourceBase(getAbsolutePath() + RESOURCE_BASE_PATH); - webAppContext.setDescriptor(getAbsolutePath() + RESOURCE_BASE_PATH + WEB_XML_PATH); - - if (IDE_INTELLIJ == type) { - webAppContext.setDefaultsDescriptor(WINDOWS_WEBDEFAULT_PATH); - supportJspAndSetTldJarNames(server, TLD_JAR_NAMES); - } else { - webAppContext.setParentLoaderPriority(true); - } - - System.out.println("[INFO] Application loaded"); - } - - public static void reloadWebAppContext(Server server) throws Exception { - WebAppContext webAppContext = (WebAppContext) server.getHandler(); - System.out.println("[INFO] Application reloading"); - webAppContext.stop(); - WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext); - classLoader.addClassPath(getAbsolutePath() + "target/classes"); - classLoader.addClassPath(getAbsolutePath() + "target/test-classes"); - webAppContext.setClassLoader(classLoader); - webAppContext.start(); - System.out.println("[INFO] Application reloaded"); - } - - - public static void startServer(Server server) throws Exception { - System.out.println("[HINT] Don't forget to set -XX:MaxPermSize=128m"); - server.start(); - System.out.println("Server running at http://localhost:" + PORT + CONTEXT); - System.out.println("[HINT] Hit Enter to reload the application quickly"); - } - - public static String getAbsolutePath() { - String path = null; - String folderPath = JettyFactory.class.getProtectionDomain().getCodeSource().getLocation() - .getPath().substring(1); - if (folderPath.indexOf("target") > 0) { - path = folderPath.substring(0, folderPath.indexOf("target")); - } - return path; - } - - public static void supportJspAndSetTldJarNames(Server server, String... jarNames) { - WebAppContext context = (WebAppContext) server.getHandler(); - // This webapp will use jsps and jstl. We need to enable the AnnotationConfiguration in - // order to correctly set up the jsp container - org.eclipse.jetty.webapp.Configuration.ClassList classlist = - org.eclipse.jetty.webapp.Configuration.ClassList.setServerDefault(server); - classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", - "org.eclipse.jetty.annotations.AnnotationConfiguration"); - // Set the ContainerIncludeJarPattern so that jetty examines these container-path jars for - // tlds, web-fragments etc. - // If you omit the jar that contains the jstl .tlds, the jsp engine will scan for them - // instead. - ArrayList jarNameExprssions = Lists.newArrayList(".*/[^/]*servlet-api-[^/]*\\.jar$", - ".*/javax.servlet.jsp.jstl-.*\\.jar$", ".*/[^/]*taglibs.*\\.jar$"); - for (String jarName : jarNames) { - jarNameExprssions.add(".*/" + jarName + "-[^/]*\\.jar$"); - } + public static final int IDE_ECLIPSE = 0; + public static final int IDE_INTELLIJ = 1; + private static final int PORT = 9899; + private static final String CONTEXT = "/"; + private static final String RESOURCE_BASE_PATH = "src/main/webapp"; + private static final String WEB_XML_PATH = "/WEB-INF/web.xml"; + private static final String[] TLD_JAR_NAMES = new String[] { "sitemesh", "spring-webmvc", "shiro-web", "tiles" }; + private static final String WINDOWS_WEBDEFAULT_PATH = "jetty/webdefault.xml"; + + public static Server initServer() { + Profiles.setProfileAsSystemProperty(Profiles.DEVELOPMENT); + WebAppContext webAppContext = new WebAppContext(); + Server server = new Server(PORT); + server.setHandler(webAppContext); + return server; + } + + public static void initWebAppContext(Server server, int type) throws Exception { + System.out.println("[INFO] Application loading"); + WebAppContext webAppContext = (WebAppContext) server.getHandler(); + webAppContext.setContextPath(CONTEXT); + webAppContext.setResourceBase(getAbsolutePath() + RESOURCE_BASE_PATH); + webAppContext.setDescriptor(getAbsolutePath() + RESOURCE_BASE_PATH + WEB_XML_PATH); + + if (IDE_INTELLIJ == type) { + webAppContext.setDefaultsDescriptor(WINDOWS_WEBDEFAULT_PATH); + supportJspAndSetTldJarNames(server, TLD_JAR_NAMES); + } + else { + webAppContext.setParentLoaderPriority(true); + } + + System.out.println("[INFO] Application loaded"); + } + + public static void reloadWebAppContext(Server server) throws Exception { + WebAppContext webAppContext = (WebAppContext) server.getHandler(); + System.out.println("[INFO] Application reloading"); + webAppContext.stop(); + WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext); + classLoader.addClassPath(getAbsolutePath() + "target/classes"); + classLoader.addClassPath(getAbsolutePath() + "target/test-classes"); + webAppContext.setClassLoader(classLoader); + webAppContext.start(); + System.out.println("[INFO] Application reloaded"); + } + + public static void startServer(Server server) throws Exception { + System.out.println("[HINT] Don't forget to set -XX:MaxPermSize=128m"); + server.start(); + System.out.println("Server running at http://localhost:" + PORT + CONTEXT); + System.out.println("[HINT] Hit Enter to reload the application quickly"); + } + + public static String getAbsolutePath() { + String path = null; + String folderPath = JettyFactory.class.getProtectionDomain().getCodeSource().getLocation().getPath() + .substring(1); + if (folderPath.indexOf("target") > 0) { + path = folderPath.substring(0, folderPath.indexOf("target")); + } + return path; + } + + public static void supportJspAndSetTldJarNames(Server server, String... jarNames) { + WebAppContext context = (WebAppContext) server.getHandler(); + // This webapp will use jsps and jstl. We need to enable the + // AnnotationConfiguration in + // order to correctly set up the jsp container + org.eclipse.jetty.webapp.Configuration.ClassList classlist = org.eclipse.jetty.webapp.Configuration.ClassList + .setServerDefault(server); + classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", + "org.eclipse.jetty.annotations.AnnotationConfiguration"); + // Set the ContainerIncludeJarPattern so that jetty examines these container-path + // jars for + // tlds, web-fragments etc. + // If you omit the jar that contains the jstl .tlds, the jsp engine will scan for + // them + // instead. + ArrayList jarNameExprssions = Lists.newArrayList(".*/[^/]*servlet-api-[^/]*\\.jar$", + ".*/javax.servlet.jsp.jstl-.*\\.jar$", ".*/[^/]*taglibs.*\\.jar$"); + + for (String jarName : jarNames) { + jarNameExprssions.add(".*/" + jarName + "-[^/]*\\.jar$"); + } + + context.setAttribute("org.eclipse.jetty.io.github.dunwu.javaee.server.webapp.ContainerIncludeJarPattern", + StringUtils.join(jarNameExprssions, '|')); + } - context.setAttribute("org.eclipse.jetty.io.github.dunwu.javaee.server.webapp.ContainerIncludeJarPattern", - StringUtils.join(jarNameExprssions, '|')); - } } diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/Profiles.java b/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/Profiles.java index 3a8ee7c4..dcfd3e39 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/Profiles.java +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/Profiles.java @@ -13,11 +13,15 @@ public class Profiles { public static final String ACTIVE_PROFILE = "spring.profiles.active"; + public static final String DEFAULT_PROFILE = "spring.profiles.default"; public static final String PRODUCTION = "production"; + public static final String DEVELOPMENT = "development"; + public static final String UNIT_TEST = "test"; + public static final String FUNCTIONAL_TEST = "functional"; /** @@ -26,4 +30,5 @@ public class Profiles { public static void setProfileAsSystemProperty(String profile) { System.setProperty(ACTIVE_PROFILE, profile); } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java b/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java index dd0ac658..c09f0fc1 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java @@ -8,26 +8,29 @@ * @author Zhang Peng */ public class QuickStartServer { - // private static int STARTUP_TYPE = JettyFactory.IDE_ECLIPSE; - private static int STARTUP_TYPE = JettyFactory.IDE_INTELLIJ; - public static void main(String[] args) throws Exception { - Server server = JettyFactory.initServer(); - JettyFactory.initWebAppContext(server, STARTUP_TYPE); + // private static int STARTUP_TYPE = JettyFactory.IDE_ECLIPSE; + private static int STARTUP_TYPE = JettyFactory.IDE_INTELLIJ; - try { - JettyFactory.startServer(server); + public static void main(String[] args) throws Exception { + Server server = JettyFactory.initServer(); + JettyFactory.initWebAppContext(server, STARTUP_TYPE); + + try { + JettyFactory.startServer(server); + + // 等待用户输入回车重载应用 + while (true) { + char c = (char) System.in.read(); + if (c == '\n') { + JettyFactory.reloadWebAppContext(server); + } + } + } + catch (Exception e) { + e.printStackTrace(); + System.exit(-1); + } + } - // 等待用户输入回车重载应用 - while (true) { - char c = (char) System.in.read(); - if (c == '\n') { - JettyFactory.reloadWebAppContext(server); - } - } - } catch (Exception e) { - e.printStackTrace(); - System.exit(-1); - } - } } diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/pom.xml b/codes/javaee-tutorial/javaee-tutorial-taglib/pom.xml index 1efc392c..98a2f4f5 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/pom.xml +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/pom.xml @@ -1,7 +1,7 @@ - - 4.0.0 + + 4.0.0 io.github.dunwu diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/Copyright.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/Copyright.java index dbae07e3..d2eebcca 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/Copyright.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/Copyright.java @@ -3,60 +3,61 @@ */ package io.github.dunwu.javaee.taglib; -import java.io.IOException; -import java.util.ResourceBundle; - import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.PageContext; import javax.servlet.jsp.tagext.Tag; +import java.io.IOException; +import java.util.ResourceBundle; /** * @author Zhang Peng * @date 2017/4/3. */ public class Copyright implements Tag { - private Tag parent; - private PageContext pageContext; + private Tag parent; - @Override - public int doEndTag() throws JspException { - JspWriter out = pageContext.getOut(); + private PageContext pageContext; - try { - out.println("
    "); - out.println(ResourceBundle.getBundle("copyright").getString("copyright")); - out.println("
    "); - } catch (IOException e) { - throw new JspException(e); - } + @Override + public int doEndTag() throws JspException { + JspWriter out = pageContext.getOut(); - return EVAL_PAGE; - } + try { + out.println("
    "); + out.println(ResourceBundle.getBundle("copyright").getString("copyright")); + out.println("
    "); + } + catch (IOException e) { + throw new JspException(e); + } - @Override - public int doStartTag() throws JspException { - return SKIP_BODY; - } + return EVAL_PAGE; + } - @Override - public Tag getParent() { - return this.parent; - } + @Override + public int doStartTag() throws JspException { + return SKIP_BODY; + } - @Override - public void release() {} + @Override + public Tag getParent() { + return this.parent; + } - @Override - public void setPageContext(PageContext pageContext) { - this.pageContext = pageContext; - } + @Override + public void setParent(Tag parent) { + this.parent = parent; + } - @Override - public void setParent(Tag parent) { - this.parent = parent; - } + @Override + public void release() { + } -} + @Override + public void setPageContext(PageContext pageContext) { + this.pageContext = pageContext; + } +} diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag.java index 52d8232d..0f90b552 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag.java @@ -8,16 +8,17 @@ * @date 2017/4/3. */ -import java.io.IOException; - import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.tagext.SimpleTagSupport; +import java.io.IOException; public class HelloTag extends SimpleTagSupport { - @Override - public void doTag() throws JspException, IOException { - JspWriter out = getJspContext().getOut(); - out.println("Hello Custom Tag!"); - } + + @Override + public void doTag() throws JspException, IOException { + JspWriter out = getJspContext().getOut(); + out.println("Hello Custom Tag!"); + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag2.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag2.java index 9f3f6af9..18afc538 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag2.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag2.java @@ -3,21 +3,22 @@ */ package io.github.dunwu.javaee.taglib; -import java.io.IOException; -import java.io.StringWriter; - import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.SimpleTagSupport; +import java.io.IOException; +import java.io.StringWriter; /** * @author Zhang Peng * @date 2017/4/3. */ public class HelloTag2 extends SimpleTagSupport { - StringWriter sw = new StringWriter(); - public void doTag() throws JspException, IOException { - getJspBody().invoke(sw); - getJspContext().getOut().println(sw.toString()); - } + StringWriter sw = new StringWriter(); + + public void doTag() throws JspException, IOException { + getJspBody().invoke(sw); + getJspContext().getOut().println(sw.toString()); + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag3.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag3.java index f8caaf72..7b27b835 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag3.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag3.java @@ -3,12 +3,11 @@ */ package io.github.dunwu.javaee.taglib; -import java.io.IOException; -import java.io.StringWriter; - import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.tagext.SimpleTagSupport; +import java.io.IOException; +import java.io.StringWriter; /** * @author Zhang Peng @@ -16,24 +15,24 @@ */ public class HelloTag3 extends SimpleTagSupport { - private String message; - - public void setMessage(String msg) { - this.message = msg; - } - - StringWriter sw = new StringWriter(); - - public void doTag() throws JspException, IOException { - if (message != null) { - /* 从属性中使用消息 */ - JspWriter out = getJspContext().getOut(); - out.println(message); - } else { - /* 从内容体中使用消息 */ - getJspBody().invoke(sw); - getJspContext().getOut().println(sw.toString()); - } - } + StringWriter sw = new StringWriter(); + private String message; + + public void setMessage(String msg) { + this.message = msg; + } + + public void doTag() throws JspException, IOException { + if (message != null) { + /* 从属性中使用消息 */ + JspWriter out = getJspContext().getOut(); + out.println(message); + } + else { + /* 从内容体中使用消息 */ + getJspBody().invoke(sw); + getJspContext().getOut().println(sw.toString()); + } + } } diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/bean/Person.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/bean/Person.java index 5ac24fcb..07868678 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/bean/Person.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/bean/Person.java @@ -2,104 +2,104 @@ public class Person { - private int id; + private int id; - private String name; + private String name; - private String sex; + private String sex; - private int age; + private int age; - private String telephone; + private String telephone; - private String birthday; + private String birthday; - private String mobile; + private String mobile; - private String address; + private String address; - private String city; + private String city; - private boolean deleted; + private boolean deleted; - public int getId() { - return id; - } + public int getId() { + return id; + } - public void setId(int id) { - this.id = id; - } + public void setId(int id) { + this.id = id; + } - public String getName() { - return name; - } + public String getName() { + return name; + } - public void setName(String name) { - this.name = name; - } + public void setName(String name) { + this.name = name; + } - public String getSex() { - return sex; - } + public String getSex() { + return sex; + } - public void setSex(String sex) { - this.sex = sex; - } + public void setSex(String sex) { + this.sex = sex; + } - public int getAge() { - return age; - } + public int getAge() { + return age; + } - public void setAge(int age) { - this.age = age; - } + public void setAge(int age) { + this.age = age; + } - public boolean isDeleted() { - return deleted; - } + public boolean isDeleted() { + return deleted; + } - public void setDeleted(boolean deleted) { - this.deleted = deleted; - } + public void setDeleted(boolean deleted) { + this.deleted = deleted; + } - public String getTelephone() { - return telephone; - } + public String getTelephone() { + return telephone; + } - public void setTelephone(String telephone) { - this.telephone = telephone; - } + public void setTelephone(String telephone) { + this.telephone = telephone; + } - public String getBirthday() { - return birthday; - } + public String getBirthday() { + return birthday; + } - public void setBirthday(String birthday) { - this.birthday = birthday; - } + public void setBirthday(String birthday) { + this.birthday = birthday; + } - public String getMobile() { - return mobile; - } + public String getMobile() { + return mobile; + } - public void setMobile(String mobile) { - this.mobile = mobile; - } + public void setMobile(String mobile) { + this.mobile = mobile; + } - public String getAddress() { - return address; - } + public String getAddress() { + return address; + } - public void setAddress(String address) { - this.address = address; - } + public void setAddress(String address) { + this.address = address; + } - public String getCity() { - return city; - } + public String getCity() { + return city; + } - public void setCity(String city) { - this.city = city; - } + public void setCity(String city) { + this.city = city; + } } diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/function/Function.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/function/Function.java index 34c64a55..472d8e87 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/function/Function.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/function/Function.java @@ -4,56 +4,58 @@ public class Function { - /** - * 返回字节长度 - * - * @param obj - * @return - */ - @SuppressWarnings("unchecked") - public static int length(Object obj) { + /** + * 返回字节长度 + * @param obj + * @return + */ + @SuppressWarnings("unchecked") + public static int length(Object obj) { - if (obj == null) return 0; + if (obj == null) + return 0; - if (obj instanceof StringBuffer) { - return length(((StringBuffer) obj).toString()); - } + if (obj instanceof StringBuffer) { + return length(((StringBuffer) obj).toString()); + } - if (obj instanceof String) { - return ((String) obj).getBytes().length; - } + if (obj instanceof String) { + return ((String) obj).getBytes().length; + } - if (obj instanceof Collection) { - return ((Collection) obj).size(); - } + if (obj instanceof Collection) { + return ((Collection) obj).size(); + } - return 0; - } + return 0; + } - public static String substring(String str, int byteLength) { + public static String substring(String str, int byteLength) { - if (str == null) return ""; + if (str == null) + return ""; - StringBuffer buffer = new StringBuffer(); + StringBuffer buffer = new StringBuffer(); - for (int i = 0; i < str.length(); i++) { - char ch = str.charAt(i); - if (length(buffer.toString() + ch) > byteLength) { - break; - } else { - buffer.append(ch); - } - } + for (int i = 0; i < str.length(); i++) { + char ch = str.charAt(i); + if (length(buffer.toString() + ch) > byteLength) { + break; + } + else { + buffer.append(ch); + } + } - return buffer.toString(); - } + return buffer.toString(); + } - public static void main(String[] args) { + public static void main(String[] args) { - System.out.println(length("中文测试")); + System.out.println(length("中文测试")); - System.out.println(substring("中文测试", 5)); + System.out.println(substring("中文测试", 5)); - } + } } diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/AddTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/AddTag.java index 60576f7d..28064eec 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/AddTag.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/AddTag.java @@ -5,28 +5,29 @@ public class AddTag extends TagSupport { - private static final long serialVersionUID = -579746915908972833L; - - private int num1; - - private int num2; - - public void setNum1(int num1) { - this.num1 = num1; - } - - public void setNum2(int num2) { - this.num2 = num2; - } - - @Override - public int doEndTag() throws JspException { - try { - this.pageContext.getOut().println(num1 + " + " + num2 + " = " + (num1 + num2)); - } catch (Exception e) { - e.printStackTrace(); - } - return EVAL_PAGE; - } + private static final long serialVersionUID = -579746915908972833L; + + private int num1; + + private int num2; + + public void setNum1(int num1) { + this.num1 = num1; + } + + public void setNum2(int num2) { + this.num2 = num2; + } + + @Override + public int doEndTag() throws JspException { + try { + this.pageContext.getOut().println(num1 + " + " + num2 + " = " + (num1 + num2)); + } + catch (Exception e) { + e.printStackTrace(); + } + return EVAL_PAGE; + } } diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/Copyright.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/Copyright.java index 4b238b6e..400c71f8 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/Copyright.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/Copyright.java @@ -1,68 +1,57 @@ package io.github.dunwu.javaee.taglib.tags; -import java.io.IOException; -import java.util.ResourceBundle; - import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.PageContext; import javax.servlet.jsp.tagext.Tag; +import java.io.IOException; +import java.util.ResourceBundle; + +public class Copyright implements Tag { -public class Copyright implements Tag -{ - private Tag parent; + private Tag parent; - private PageContext pageContext; + private PageContext pageContext; - @Override - public int doEndTag() - throws JspException - { - JspWriter out = pageContext.getOut(); + @Override + public int doEndTag() throws JspException { + JspWriter out = pageContext.getOut(); - try - { - out.println("
    "); - out.println(ResourceBundle.getBundle("copyright").getString("copyright")); - out.println("
    "); - } - catch (IOException e) - { - throw new JspException(e); - } + try { + out.println("
    "); + out.println(ResourceBundle.getBundle("copyright").getString("copyright")); + out.println("
    "); + } + catch (IOException e) { + throw new JspException(e); + } - return EVAL_PAGE; - } + return EVAL_PAGE; + } - @Override - public int doStartTag() - throws JspException - { - return SKIP_BODY; - } + @Override + public int doStartTag() throws JspException { + return SKIP_BODY; + } - @Override - public Tag getParent() - { - return this.parent; - } + @Override + public Tag getParent() { + return this.parent; + } - @Override - public void release() - { - } + @Override + public void setParent(Tag parent) { + this.parent = parent; + } - @Override - public void setPageContext(PageContext pageContext) - { - this.pageContext = pageContext; - } + @Override + public void release() { + } - @Override - public void setParent(Tag parent) - { - this.parent = parent; - } + @Override + public void setPageContext(PageContext pageContext) { + this.pageContext = pageContext; + } } diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/Copyright2.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/Copyright2.java index 3f4bbd25..332e9366 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/Copyright2.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/Copyright2.java @@ -1,34 +1,34 @@ package io.github.dunwu.javaee.taglib.tags; -import java.io.IOException; -import java.util.ResourceBundle; - import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.tagext.TagSupport; +import java.io.IOException; +import java.util.ResourceBundle; public class Copyright2 extends TagSupport { - private static final long serialVersionUID = -2936770589554413334L; + private static final long serialVersionUID = -2936770589554413334L; - @Override - public int doEndTag() throws JspException { - JspWriter out = pageContext.getOut(); + @Override + public int doEndTag() throws JspException { + JspWriter out = pageContext.getOut(); - try { - out.println("
    "); - out.println(ResourceBundle.getBundle("copyright").getString("copyright")); - out.println("
    "); - } catch (IOException e) { - throw new JspException(e); - } + try { + out.println("
    "); + out.println(ResourceBundle.getBundle("copyright").getString("copyright")); + out.println("
    "); + } + catch (IOException e) { + throw new JspException(e); + } - return EVAL_PAGE; - } + return EVAL_PAGE; + } - @Override - public int doStartTag() throws JspException { - return super.doStartTag(); - } + @Override + public int doStartTag() throws JspException { + return super.doStartTag(); + } } diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/DynamicAttributeTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/DynamicAttributeTag.java index 62402fb2..51252e56 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/DynamicAttributeTag.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/DynamicAttributeTag.java @@ -1,16 +1,14 @@ package io.github.dunwu.javaee.taglib.tags; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; - import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.tagext.DynamicAttributes; import javax.servlet.jsp.tagext.TagSupport; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; -public class DynamicAttributeTag extends TagSupport implements - DynamicAttributes { +public class DynamicAttributeTag extends TagSupport implements DynamicAttributes { private static final long serialVersionUID = -1477571708507488373L; @@ -45,16 +43,17 @@ public int doEndTag() throws JspException { try { out.write(buffer.toString()); - } catch (Exception e) { + } + catch (Exception e) { } return super.doEndTag(); } @Override - public void setDynamicAttribute(String uri, String key, Object value) - throws JspException { + public void setDynamicAttribute(String uri, String key, Object value) throws JspException { map.put(key, Double.parseDouble((String) value)); } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/HelloTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/HelloTag.java index 68fe5624..14faaaa6 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/HelloTag.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/HelloTag.java @@ -14,7 +14,8 @@ public int doEndTag() throws JspException { try { this.pageContext.getOut().println("Hello, " + name); - } catch (Exception e) { + } + catch (Exception e) { throw new JspException(e); } diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/IteratorTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/IteratorTag.java index 4126287a..d724c10d 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/IteratorTag.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/IteratorTag.java @@ -1,9 +1,8 @@ package io.github.dunwu.javaee.taglib.tags; -import java.util.Collection; - import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.TagSupport; +import java.util.Collection; public class IteratorTag extends TagSupport { @@ -18,7 +17,8 @@ public int doEndTag() throws JspException { for (Object obj : connection) { this.pageContext.getOut().println(obj + ",
    "); } - } catch (Exception e) { + } + catch (Exception e) { throw new JspException(e); } diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/LoopTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/LoopTag.java index 961ac4dd..84488afb 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/LoopTag.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/LoopTag.java @@ -22,15 +22,16 @@ public int doAfterBody() throws JspException { /** 只要 times > 0 就继续循环,同时 times 自减 */ try { - this.getPreviousOut() - .println(this.getBodyContent().getString()); + this.getPreviousOut().println(this.getBodyContent().getString()); - } catch (Exception e) { + } + catch (Exception e) { } return EVAL_BODY_AGAIN; - } else { + } + else { /** 结束运行,同时复原 times */ diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/ToLowerCaseTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/ToLowerCaseTag.java index 60e372b3..5a6e0d31 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/ToLowerCaseTag.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/ToLowerCaseTag.java @@ -14,7 +14,8 @@ public int doEndTag() throws JspException { try { this.pageContext.getOut().print(content.toLowerCase()); - } catch (Exception e) { + } + catch (Exception e) { } return EVAL_PAGE; diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/table/Column.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/table/Column.java index 0992333c..7bee7e13 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/table/Column.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/table/Column.java @@ -1,65 +1,64 @@ package io.github.dunwu.javaee.taglib.tags.table; -import java.util.HashMap; -import java.util.Map; - import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.TagSupport; +import java.util.HashMap; +import java.util.Map; public class Column extends TagSupport { - private static final long serialVersionUID = 5119493903438602864L; - private String property; + private static final long serialVersionUID = 5119493903438602864L; - private String label; + private String property; - private String type; + private String label; - public int doStartTag() throws JspException { - if (!(this.getParent() instanceof Table)) { - throw new JspException("Column must be inside Table. "); - } + private String type; + public int doStartTag() throws JspException { + if (!(this.getParent() instanceof Table)) { + throw new JspException("Column must be inside Table. "); + } - Map column = new HashMap(); + Map column = new HashMap(); - column.put("label", label); - column.put("property", property); - column.put("type", type); + column.put("label", label); + column.put("property", property); + column.put("type", type); - Table table = (Table) this.getParent(); + Table table = (Table) this.getParent(); - table.getColumns().add(column); + table.getColumns().add(column); - return SKIP_BODY; - } + return SKIP_BODY; + } - public int doEndTag() throws JspException { - return EVAL_PAGE; - } + public int doEndTag() throws JspException { + return EVAL_PAGE; + } - public String getProperty() { - return property; - } + public String getProperty() { + return property; + } - public void setProperty(String property) { - this.property = property; - } + public void setProperty(String property) { + this.property = property; + } - public String getLabel() { - return label; - } + public String getLabel() { + return label; + } - public void setLabel(String label) { - this.label = label; - } + public void setLabel(String label) { + this.label = label; + } - public String getType() { - return type; - } + public String getType() { + return type; + } - public void setType(String type) { - this.type = type; - } + public void setType(String type) { + this.type = type; + } } diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/table/Table.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/table/Table.java index a0942848..046158c2 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/table/Table.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/table/Table.java @@ -1,200 +1,201 @@ package io.github.dunwu.javaee.taglib.tags.table; -import java.io.IOException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - import javax.servlet.http.HttpServletRequest; import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.tagext.BodyContent; import javax.servlet.jsp.tagext.BodyTagSupport; +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; public class Table extends BodyTagSupport { - private static final long serialVersionUID = 3358444196409845360L; - - /** 存储列信息 */ - private List> columns = new ArrayList>(); - /** 存储数据,可能为 集合类型的或者数组类型的 */ - private Object items; + private static final long serialVersionUID = 3358444196409845360L; - /** 取排序数据的 URL */ - private String url; + /** 存储列信息 */ + private List> columns = new ArrayList>(); - @Override - public int doStartTag() throws JspException { - columns.clear(); + /** 存储数据,可能为 集合类型的或者数组类型的 */ + private Object items; - return super.doStartTag(); - } + /** 取排序数据的 URL */ + private String url; - @Override - @SuppressWarnings("unchecked") - public int doAfterBody() throws JspException { - try { - BodyContent bc = getBodyContent(); - JspWriter out = bc.getEnclosingWriter(); + @Override + public int doStartTag() throws JspException { + columns.clear(); - HttpServletRequest request = (HttpServletRequest) pageContext.getRequest(); + return super.doStartTag(); + } - /** 按哪一列排序 */ - String orderName = request.getParameter("orderName"); - /** 按升序还是降序排序 */ - String orderType = request.getParameter("orderType"); + @Override + @SuppressWarnings("unchecked") + public int doAfterBody() throws JspException { + try { + BodyContent bc = getBodyContent(); + JspWriter out = bc.getEnclosingWriter(); - orderType = "desc".equals(orderType) ? "desc" : "asc"; + HttpServletRequest request = (HttpServletRequest) pageContext.getRequest(); - out.println(""); - out.println(" "); - out.println(" "); + /** 按哪一列排序 */ + String orderName = request.getParameter("orderName"); + /** 按升序还是降序排序 */ + String orderType = request.getParameter("orderType"); - for (int i = 0; i < columns.size(); i++) { - /** 获取列信息 */ - Map column = columns.get(i); + orderType = "desc".equals(orderType) ? "desc" : "asc"; - /** 列头 */ - String label = column.get("label"); - /** 该列对应的 Java Bean 的属性 */ - String property = column.get("property"); + out.println("
    "); + out.println(" "); + out.println(" "); - label = label == null ? property : label; + for (int i = 0; i < columns.size(); i++) { + /** 获取列信息 */ + Map column = columns.get(i); - out.println(""); - out.println(""); - } + out.println(""); + } - out.println(" "); + out.println(""); - if (items != null) { + out.println(""); + out.println(""); + } - /** 遍历所有的数据 */ - for (Object obj : (Iterable) items) { + out.println(" "); - out.println(" "); + if (items != null) { - for (int i = 0; i < columns.size(); i++) { + /** 遍历所有的数据 */ + for (Object obj : (Iterable) items) { - Map column = columns.get(i); + out.println(" "); - String property = column.get("property"); + for (int i = 0; i < columns.size(); i++) { - String getterStyle = toGetterStyle(property); + Map column = columns.get(i); - try { - String getter = "get" + getterStyle; - String is = "is" + getterStyle; + String property = column.get("property"); - Method method = null; + String getterStyle = toGetterStyle(property); - try { - /** 获取 getXxx() 形式的方法 */ - method = obj.getClass().getMethod(getter); - } catch (Exception e) {} + try { + String getter = "get" + getterStyle; + String is = "is" + getterStyle; - if (method == null) { - /** 如果没有,获取 isXxx() 形式的方法 */ - method = obj.getClass().getMethod(is); - } + Method method = null; - method.setAccessible(true); + try { + /** 获取 getXxx() 形式的方法 */ + method = obj.getClass().getMethod(getter); + } + catch (Exception e) { + } - /** 获取属性值 */ - Object value = method.invoke(obj); - out.println(""); + if (method == null) { + /** 如果没有,获取 isXxx() 形式的方法 */ + method = obj.getClass().getMethod(is); + } - } catch (Exception e) { - throw new JspException(e); - } - } - out.println(" "); - } - } + method.setAccessible(true); - out.println("
    "); - out.println(""); + /** 列头 */ + String label = column.get("label"); + /** 该列对应的 Java Bean 的属性 */ + String property = column.get("property"); - out.println(""); + label = label == null ? property : label; - out.println(label); + out.println(""); + out.println(""); - if (property.equals(orderName)) { + out.println(""); - out.println(""); - } + out.println(label); - out.println(""); + if (property.equals(orderName)) { - out.println("
    " + value - + "
    "); + /** 获取属性值 */ + Object value = method.invoke(obj); + out.println("" + value + ""); - out.println(""); + } + catch (Exception e) { + throw new JspException(e); + } + } + out.println(" "); + } + } - } catch (IOException ioe) { - throw new JspException("Error: " + ioe.getMessage()); - } + out.println(""); - return SKIP_BODY; - } + out.println(""); - public Object getItems() { - return items; - } + } + catch (IOException ioe) { + throw new JspException("Error: " + ioe.getMessage()); + } - public void setItems(Object items) { - this.items = items; - } + return SKIP_BODY; + } - /** - * 首字母大写 - * - * @param column - * @return - */ - public String toGetterStyle(String column) { - if (column.length() == 1) return column.toUpperCase(); + public Object getItems() { + return items; + } - char ch = column.charAt(0); + public void setItems(Object items) { + this.items = items; + } - return Character.toUpperCase(ch) + column.substring(1); + /** + * 首字母大写 + * @param column + * @return + */ + public String toGetterStyle(String column) { + if (column.length() == 1) + return column.toUpperCase(); - } + char ch = column.charAt(0); - public List> getColumns() { - return columns; - } + return Character.toUpperCase(ch) + column.substring(1); - public void setColumns(List> columns) { - this.columns = columns; - } + } - public String getUrl() { - return url; - } + public List> getColumns() { + return columns; + } - public void setUrl(String url) { - this.url = url; - } + public void setColumns(List> columns) { + this.columns = columns; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } } diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/MultiAttributeTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/MultiAttributeTag.java index 4488a295..ae065252 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/MultiAttributeTag.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/MultiAttributeTag.java @@ -1,48 +1,45 @@ package io.github.dunwu.javaee.taglib.tags2; -import java.io.IOException; -import java.io.StringWriter; - import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.JspFragment; import javax.servlet.jsp.tagext.SimpleTagSupport; +import java.io.IOException; +import java.io.StringWriter; public class MultiAttributeTag extends SimpleTagSupport { - private JspFragment body1; + private JspFragment body1; - private JspFragment body2; + private JspFragment body2; - public void setBody1(JspFragment body1) { - this.body1 = body1; - } + public void setBody1(JspFragment body1) { + this.body1 = body1; + } - public void setBody2(JspFragment body2) { - this.body2 = body2; - } + public void setBody2(JspFragment body2) { + this.body2 = body2; + } - @Override - public void doTag() throws JspException, IOException { + @Override + public void doTag() throws JspException, IOException { - StringWriter writer1 = new StringWriter(); - StringWriter writer2 = new StringWriter(); + StringWriter writer1 = new StringWriter(); + StringWriter writer2 = new StringWriter(); - for (int i = 0; i < 5; i++) { - // body1 调用 5 次 - body1.invoke(writer1); - } + for (int i = 0; i < 5; i++) { + // body1 调用 5 次 + body1.invoke(writer1); + } - for (int i = 0; i < 3; i++) { - // body2 调用 3 次 - body2.invoke(writer2); - } + for (int i = 0; i < 3; i++) { + // body2 调用 3 次 + body2.invoke(writer2); + } - this.getJspContext().getOut() - .print("3 次调用 body2 后的结果:" + writer2.getBuffer().toString() + "

    "); + this.getJspContext().getOut().print("3 次调用 body2 后的结果:" + writer2.getBuffer().toString() + "

    "); - this.getJspContext().getOut() - .print("5 次调用 body1 后的结果:" + writer1.getBuffer().toString() + "

    "); + this.getJspContext().getOut().print("5 次调用 body1 后的结果:" + writer1.getBuffer().toString() + "

    "); - } + } } diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/MultiTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/MultiTag.java index fa92c801..c9f0e62f 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/MultiTag.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/MultiTag.java @@ -1,35 +1,34 @@ package io.github.dunwu.javaee.taglib.tags2; -import java.io.IOException; - import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.SimpleTagSupport; +import java.io.IOException; public class MultiTag extends SimpleTagSupport { - private int num1; + private int num1; - private int num2; + private int num2; - @Override - public void doTag() throws JspException, IOException { - this.getJspContext().getOut().write("" + num1 + " * " + num2 + " = " + (num1 * num2)); - } + @Override + public void doTag() throws JspException, IOException { + this.getJspContext().getOut().write("" + num1 + " * " + num2 + " = " + (num1 * num2)); + } - public int getNum1() { - return num1; - } + public int getNum1() { + return num1; + } - public void setNum1(int num1) { - this.num1 = num1; - } + public void setNum1(int num1) { + this.num1 = num1; + } - public int getNum2() { - return num2; - } + public int getNum2() { + return num2; + } - public void setNum2(int num2) { - this.num2 = num2; - } + public void setNum2(int num2) { + this.num2 = num2; + } } diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/RepeatTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/RepeatTag.java index 9d0ab423..ecd9dd61 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/RepeatTag.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/RepeatTag.java @@ -4,14 +4,14 @@ public class RepeatTag extends SimpleTagSupport { - private int times; + private int times; - public int getTimes() { - return times; - } + public int getTimes() { + return times; + } - public void setTimes(int times) { - this.times = times; - } + public void setTimes(int times) { + this.times = times; + } } diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/ToUpperCaseTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/ToUpperCaseTag.java index 14a08b1d..669957b0 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/ToUpperCaseTag.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/ToUpperCaseTag.java @@ -1,29 +1,28 @@ package io.github.dunwu.javaee.taglib.tags2; -import java.io.IOException; -import java.io.StringWriter; - import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.JspFragment; import javax.servlet.jsp.tagext.SimpleTagSupport; +import java.io.IOException; +import java.io.StringWriter; public class ToUpperCaseTag extends SimpleTagSupport { - @Override - public void doTag() throws JspException, IOException { + @Override + public void doTag() throws JspException, IOException { - // 将 标签体内容读入该 writer - StringWriter writer = new StringWriter(); + // 将 标签体内容读入该 writer + StringWriter writer = new StringWriter(); - // 标签体 为 JspFragment 的形式 - JspFragment jspFragment = this.getJspBody(); + // 标签体 为 JspFragment 的形式 + JspFragment jspFragment = this.getJspBody(); - // 通过 invoke 输出到指定的 writer 中。 - // 如果参数为 null,将输出到默认的 writer 中,即 getJspContext().getOut() 输出到HTML中 - jspFragment.invoke(writer); + // 通过 invoke 输出到指定的 writer 中。 + // 如果参数为 null,将输出到默认的 writer 中,即 getJspContext().getOut() 输出到HTML中 + jspFragment.invoke(writer); - String content = writer.getBuffer().toString(); - this.getJspContext().getOut().print(content.toUpperCase()); - } + String content = writer.getBuffer().toString(); + this.getJspContext().getOut().print(content.toUpperCase()); + } } diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/Messages.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/Messages.java index 32f5baff..05fba10b 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/Messages.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/Messages.java @@ -5,28 +5,33 @@ import java.util.ResourceBundle; public class Messages { - private static final String BUNDLE_NAME = "com.helloweenvsfei.test.messages"; //$NON-NLS-1$ - private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle(BUNDLE_NAME); + private static final String BUNDLE_NAME = "com.helloweenvsfei.test.messages"; //$NON-NLS-1$ - private Messages() {} + private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle(BUNDLE_NAME); - public static String getString(String key) { - try { - return RESOURCE_BUNDLE.getString(key); - } catch (MissingResourceException e) { - return '!' + key + '!'; - } - } + private Messages() { + } - public static String getString(String key, Object... params) { - try { - String value = RESOURCE_BUNDLE.getString(key); + public static String getString(String key) { + try { + return RESOURCE_BUNDLE.getString(key); + } + catch (MissingResourceException e) { + return '!' + key + '!'; + } + } - return MessageFormat.format(value, params); + public static String getString(String key, Object... params) { + try { + String value = RESOURCE_BUNDLE.getString(key); + + return MessageFormat.format(value, params); + + } + catch (MissingResourceException e) { + return '!' + key + '!'; + } + } - } catch (MissingResourceException e) { - return '!' + key + '!'; - } - } } diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/TestMessage.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/TestMessage.java index 1fa39ff8..e97e5ed9 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/TestMessage.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/TestMessage.java @@ -1,7 +1,9 @@ package io.github.dunwu.javaee.taglib.test; public class TestMessage { - public static void main(String[] args) { - System.out.println(Messages.getString("TestMessage.0", "A", "B")); //$NON-NLS-1$ - } + + public static void main(String[] args) { + System.out.println(Messages.getString("TestMessage.0", "A", "B")); //$NON-NLS-1$ + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/messages.properties b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/messages.properties index c99d9bfc..c31367f8 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/messages.properties +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/messages.properties @@ -1 +1 @@ -TestMessage.0=test {0}, {1} +TestMessage.0 = test {0}, {1} diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/resources/copyright.properties b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/resources/copyright.properties index 2a835e8b..4cbf90ff 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/resources/copyright.properties +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/resources/copyright.properties @@ -1 +1 @@ -copyright=©2016-2017, Zhang Peng +copyright = ©2016-2017, Zhang Peng diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/resources/logback.xml index abe2ed7a..12497c03 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/resources/logback.xml +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/resources/logback.xml @@ -2,7 +2,7 @@ - + @@ -33,13 +33,13 @@ - - + + - - + + diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/function.tld b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/function.tld index 78530770..3ae6e5b2 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/function.tld +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/function.tld @@ -1,44 +1,44 @@ - - custom functions library - custom functions - 1.1 - function - http://www.victorzhang.com/function + custom functions library + custom functions + 1.1 + function + http://www.victorzhang.com/function - - - - length - + + + + length + io.github.dunwu.javaee.taglib.function.Function - - int length(java.lang.Object) - - - ${fn:length(string)} - - + + int length(java.lang.Object) + + + ${fn:length(string)} + + - - - - substring - + + + + substring + io.github.dunwu.javaee.taglib.function.Function - - java.lang.String substring(java.lang.String, int) - - - ${fn:length(string, 3)} - - + + java.lang.String substring(java.lang.String, int) + + + ${fn:length(string, 3)} + + diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello.tld b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello.tld index 2dca9237..68451e93 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello.tld +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello.tld @@ -1,10 +1,10 @@ - 1.0 - 2.0 - Example TLD - - Hello - io.github.dunwu.javaee.taglib.HelloTag - empty - + 1.0 + 2.0 + Example TLD + + Hello + io.github.dunwu.javaee.taglib.HelloTag + empty + diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello2.tld b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello2.tld index 44728ef9..a9e70e71 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello2.tld +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello2.tld @@ -1,10 +1,10 @@ - 1.0 - 2.0 - Example TLD with Body - - Hello - io.github.dunwu.javaee.taglib.HelloTag2 - scriptless - + 1.0 + 2.0 + Example TLD with Body + + Hello + io.github.dunwu.javaee.taglib.HelloTag2 + scriptless + diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello3.tld b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello3.tld index 2a7c20bb..0be7ed4d 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello3.tld +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello3.tld @@ -7,7 +7,7 @@ io.github.dunwu.javaee.taglib.HelloTag3 scriptless - message + message diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/taglib.tld b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/taglib.tld index d1a10666..c5a95ac8 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/taglib.tld +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/taglib.tld @@ -2,148 +2,148 @@ - + - 1.0 - 1.1 - taglib - http://www.victorzhang.com/tags - A simple tab library for the examples + 1.0 + 1.1 + taglib + http://www.victorzhang.com/tags + A simple tab library for the examples - - copyright - io.github.dunwu.javaee.taglib.Copyright - JSP - Copyright tag. - + + copyright + io.github.dunwu.javaee.taglib.Copyright + JSP + Copyright tag. + - - hello - io.github.dunwu.javaee.taglib.tags.HelloTag - empty - Hello tag with parameters. - - name - true - true - - + + hello + io.github.dunwu.javaee.taglib.tags.HelloTag + empty + Hello tag with parameters. + + name + true + true + + - - add - io.github.dunwu.javaee.taglib.tags.AddTag - empty - Add tag with parameters. - - num1 - true - true - - - num2 - true - true - - + + add + io.github.dunwu.javaee.taglib.tags.AddTag + empty + Add tag with parameters. + + num1 + true + true + + + num2 + true + true + + - - toLowerCase - io.github.dunwu.javaee.taglib.tags.ToLowerCaseTag - JSP - Tag with body. - + + toLowerCase + io.github.dunwu.javaee.taglib.tags.ToLowerCaseTag + JSP + Tag with body. + - - loop - io.github.dunwu.javaee.taglib.tags.LoopTag - JSP - Tag with body. - + + loop + io.github.dunwu.javaee.taglib.tags.LoopTag + JSP + Tag with body. + - - dynamicAttribute - io.github.dunwu.javaee.taglib.tags.DynamicAttributeTag - empty - true - Tag with dynamic attribute. - + + dynamicAttribute + io.github.dunwu.javaee.taglib.tags.DynamicAttributeTag + empty + true + Tag with dynamic attribute. + - - table - io.github.dunwu.javaee.taglib.tags.table.Table - JSP - Table tag. - - items - true - true - - - url - false - true - - + + table + io.github.dunwu.javaee.taglib.tags.table.Table + JSP + Table tag. + + items + true + true + + + url + false + true + + - - column - io.github.dunwu.javaee.taglib.tags.table.Column - empty - Column tag. - - property - true - true - - - label - false - true - - + + column + io.github.dunwu.javaee.taglib.tags.table.Column + empty + Column tag. + + property + true + true + + + label + false + true + + - - multi - io.github.dunwu.javaee.taglib.tags2.MultiTag - empty - multi tag with parameters. - - num1 - true - true - - - num2 - true - true - - + + multi + io.github.dunwu.javaee.taglib.tags2.MultiTag + empty + multi tag with parameters. + + num1 + true + true + + + num2 + true + true + + - - toUpperCase - io.github.dunwu.javaee.taglib.tags2.ToUpperCaseTag - tagdependent - body tag - + + toUpperCase + io.github.dunwu.javaee.taglib.tags2.ToUpperCaseTag + tagdependent + body tag + - - multiAttribute - io.github.dunwu.javaee.taglib.tags2.MultiAttributeTag - tagdependent - multi attribute tag with parameters. - - body1 - false - true - - - body2 - false - true - - + + multiAttribute + io.github.dunwu.javaee.taglib.tags2.MultiAttributeTag + tagdependent + multi attribute tag with parameters. + + body1 + false + true + + + body2 + false + true + + diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/web.xml b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/web.xml index aa1943d6..ae92ddac 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/web.xml +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/web.xml @@ -1,25 +1,25 @@ - - - HelloServlet - /examples/configuration.jsp - - message - welcome to jsp - - 1 - - - HelloServlet - /config - /config.jsp - + + HelloServlet + /examples/configuration.jsp + + message + welcome to jsp + + 1 + + + HelloServlet + /config + /config.jsp + - - - /WEB-INF/views/jsp/index.jsp - + + + /WEB-INF/views/jsp/index.jsp + diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/index.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/index.jsp index 3a2872cf..92b12378 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/index.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/index.jsp @@ -23,6 +23,7 @@ obj.pareneTableW = theObjTable.offsetWidth; obj.setCapture(); } + function MouseMoveToResize(obj) { if (!obj.mouseDownX) return false; var newWidth = obj.pareneTdW * 1 + event.clientX * 1 - obj.mouseDownX; @@ -31,6 +32,7 @@ theObjTable.style.width = obj.pareneTableW * 1 + event.clientX * 1 - obj.mouseDownX; } } + function MouseUpToResize(obj) { obj.releaseCapture(); obj.mouseDownX = 0; @@ -38,6 +40,7 @@ document.cookie = objId + '_width=' + obj.parentElement.style.width; document.cookie = theObjTable.id + '_width=' + theObjTable.style.width; } + function readCookie(name) { var start1 = document.cookie.indexOf(name + "="); if (start1 == -1) @@ -79,4 +82,4 @@ - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/loop.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/loop.jsp index 20c7afea..b46bf1a0 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/loop.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/loop.jsp @@ -1,9 +1,9 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://www.victorzhang.com/tags" prefix="taglib"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://www.victorzhang.com/tags" prefix="taglib" %> - -Insert title here + + Insert title here @@ -12,4 +12,4 @@
    - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/multi.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/multi.jsp index a9d2d65b..10f213ab 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/multi.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/multi.jsp @@ -1,13 +1,13 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://www.victorzhang.com/tags" prefix="taglib"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://www.victorzhang.com/tags" prefix="taglib" %> - -Insert title here + + Insert title here - + - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/multiAttribute.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/multiAttribute.jsp index 1450c5bd..d618bbc7 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/multiAttribute.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/multiAttribute.jsp @@ -1,21 +1,21 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://www.victorzhang.com/tags" prefix="taglib"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://www.victorzhang.com/tags" prefix="taglib" %> - -Insert title here - + + Insert title here + - 标签体一, - 标签体二, + 标签体一, + 标签体二, - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/style/style.css b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/style/style.css index d6233e67..8774e7dc 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/style/style.css +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/style/style.css @@ -1,10 +1,10 @@ body, td, div, input, textarea, span { - font-size: 12px; - font-family: Arial; + font-size: 12px; + font-family: Arial; } .list_table { - border-collapse: collapse; + border-collapse: collapse; } .list_table .tr_title { @@ -12,55 +12,55 @@ body, td, div, input, textarea, span { } .list_table .tr_title td { - border: 1px solid #DDDDDD; - background: #EEEEEE; - background: url("../images/ibm-tab-background.gif") no-repeat 0px 0px; - border-top: 1px solid #7E9AB0; - color: #FFFFFF; - text-align: center; - padding-top: 4px; - padding-bottom: 4px; - white-space: nowrap; - font-weight: bold; + border: 1px solid #DDDDDD; + background: #EEEEEE; + background: url("../images/ibm-tab-background.gif") no-repeat 0px 0px; + border-top: 1px solid #7E9AB0; + color: #FFFFFF; + text-align: center; + padding-top: 4px; + padding-bottom: 4px; + white-space: nowrap; + font-weight: bold; } .list_table .tr_title td span { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .list_table .tr_title img { - padding-top: 1px; - padding-bottom: 1px; + padding-top: 1px; + padding-bottom: 1px; } .list_table .tr_data { - padding: 2px; - text-align: center; + padding: 2px; + text-align: center; } .list_table .tr_data td { - border: 1px solid #DDDDDD; - padding: 2px; + border: 1px solid #DDDDDD; + padding: 2px; } .list_table .tr_data td span { - width: 100%; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .button { - color: #fff; - font-weight: bold; - font-size: 11px; - font-family: verdana; - text-align: center; - padding: .17em 0 .2em .17em; - border-style: solid; - border-width: 1px; - border-color: #9cf #159 #159 #9cf; - background: #69c url(../images/bg-btn-blue.gif) repeat-x; + color: #FFFFFF; + font-weight: bold; + font-size: 11px; + font-family: verdana; + text-align: center; + padding: .17em 0 .2em .17em; + border-style: solid; + border-width: 1px; + border-color: #99CCFF #115599 #115599 #99CCFF; + background: #6699CC url(../images/bg-btn-blue.gif) repeat-x; } diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/table.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/table.jsp index 25f735e4..6e5a2362 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/table.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/table.jsp @@ -1,185 +1,185 @@ -<%@ page language="java" contentType="text/html; charset=gb2312"%> -<%@ taglib uri="http://www.victorzhang.com/tags" prefix="taglib"%> -<%@page import="java.util.ArrayList"%> -<%@page import="io.github.dunwu.javaee.taglib.bean.Person"%> -<%@page import="java.util.List"%> +<%@ page language="java" contentType="text/html; charset=gb2312" %> +<%@ taglib uri="http://www.victorzhang.com/tags" prefix="taglib" %> +<%@page import="io.github.dunwu.javaee.taglib.bean.Person" %> +<%@page import="java.util.ArrayList" %> +<%@page import="java.util.List" %> <% - List personList = new ArrayList(); - - int i = 1; - - Person person = new Person(); - person.setId(i++); - person.setName(""); - person.setAge(20); - person.setSex(""); - person.setAddress("кϵ԰"); - person.setBirthday("2008-08-08"); - person.setMobile("13820080808"); - person.setTelephone("69653234"); - person.setCity(""); - - personList.add(person); - - Person person2 = new Person(); - person2.setId(i++); - person2.setName(""); - person2.setAge(20); - person2.setSex(""); - person2.setAddress("жʳǸͬ"); - person2.setBirthday("2008-01-01"); - person2.setMobile("13820080808"); - person2.setTelephone("20054879"); - person2.setCity(""); - - personList.add(person2); - - Person person3 = new Person(); - person3.setId(i++); - person3.setName(""); - person3.setAge(20); - person3.setSex(""); - person3.setAddress("жʳǸͬ"); - person3.setBirthday("2008-01-01"); - person3.setMobile("13820080808"); - person3.setTelephone("20054879"); - person3.setCity(""); - - personList.add(person3); - - Person person4 = new Person(); - person4.setId(i++); - person4.setName(""); - person4.setAge(20); - person4.setSex("Ů"); - person4.setAddress("жʳǸͬ"); - person4.setBirthday("2008-01-01"); - person4.setMobile("13820080808"); - person4.setTelephone("20054879"); - person4.setCity(""); - - personList.add(person4); - - Person person5 = new Person(); - person5.setId(i++); - person5.setName(""); - person5.setAge(20); - person5.setSex(""); - person5.setAddress("жʳǸͬ"); - person5.setBirthday("2008-01-01"); - person5.setMobile("13820080808"); - person5.setTelephone("20054879"); - person5.setCity(""); - - personList.add(person5); - - Person person6 = new Person(); - person6.setId(i++); - person6.setName(""); - person6.setAge(20); - person6.setSex("Ů"); - person6.setAddress("жʳǸͬ"); - person6.setBirthday("2008-01-01"); - person6.setMobile("13820080808"); - person6.setTelephone("20054879"); - person6.setCity(""); - - personList.add(person6); - - Person person7 = new Person(); - person7.setId(i++); - person7.setName(""); - person7.setAge(20); - person7.setSex(""); - person7.setAddress("кּ36A"); - person7.setBirthday("2008-01-01"); - person7.setMobile("13820080808"); - person7.setTelephone("20054879"); - person7.setCity(""); - - personList.add(person7); - - request.setAttribute("personList", personList); + List personList = new ArrayList(); + + int i = 1; + + Person person = new Person(); + person.setId(i++); + person.setName(""); + person.setAge(20); + person.setSex(""); + person.setAddress("кϵ԰"); + person.setBirthday("2008-08-08"); + person.setMobile("13820080808"); + person.setTelephone("69653234"); + person.setCity(""); + + personList.add(person); + + Person person2 = new Person(); + person2.setId(i++); + person2.setName(""); + person2.setAge(20); + person2.setSex(""); + person2.setAddress("жʳǸͬ"); + person2.setBirthday("2008-01-01"); + person2.setMobile("13820080808"); + person2.setTelephone("20054879"); + person2.setCity(""); + + personList.add(person2); + + Person person3 = new Person(); + person3.setId(i++); + person3.setName(""); + person3.setAge(20); + person3.setSex(""); + person3.setAddress("жʳǸͬ"); + person3.setBirthday("2008-01-01"); + person3.setMobile("13820080808"); + person3.setTelephone("20054879"); + person3.setCity(""); + + personList.add(person3); + + Person person4 = new Person(); + person4.setId(i++); + person4.setName(""); + person4.setAge(20); + person4.setSex("Ů"); + person4.setAddress("жʳǸͬ"); + person4.setBirthday("2008-01-01"); + person4.setMobile("13820080808"); + person4.setTelephone("20054879"); + person4.setCity(""); + + personList.add(person4); + + Person person5 = new Person(); + person5.setId(i++); + person5.setName(""); + person5.setAge(20); + person5.setSex(""); + person5.setAddress("жʳǸͬ"); + person5.setBirthday("2008-01-01"); + person5.setMobile("13820080808"); + person5.setTelephone("20054879"); + person5.setCity(""); + + personList.add(person5); + + Person person6 = new Person(); + person6.setId(i++); + person6.setName(""); + person6.setAge(20); + person6.setSex("Ů"); + person6.setAddress("жʳǸͬ"); + person6.setBirthday("2008-01-01"); + person6.setMobile("13820080808"); + person6.setTelephone("20054879"); + person6.setCity(""); + + personList.add(person6); + + Person person7 = new Person(); + person7.setId(i++); + person7.setName(""); + person7.setAge(20); + person7.setSex(""); + person7.setAddress("кּ36A"); + person7.setBirthday("2008-01-01"); + person7.setMobile("13820080808"); + person7.setTelephone("20054879"); + person7.setCity(""); + + personList.add(person7); + + request.setAttribute("personList", personList); %> - -Insert title here - - - - + + Insert title here + + + + - - - - - - - - - + + + + + + + + + diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/taglib/copyright.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/taglib/copyright.jsp index 6f0b5dde..add4b34c 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/taglib/copyright.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/taglib/copyright.jsp @@ -1,13 +1,13 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@ taglib uri="http://www.victorzhang.com/tags" prefix="taglib"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://www.victorzhang.com/tags" prefix="taglib" %> - -Insert title here + + Insert title here Hello, taglib - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/taglib/helloTag2.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/taglib/helloTag2.jsp index e6fbe55c..526855ba 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/taglib/helloTag2.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/taglib/helloTag2.jsp @@ -1,4 +1,4 @@ -<%@ taglib prefix="ex" uri="/WEB-INF/tld/hello2.tld"%> +<%@ taglib prefix="ex" uri="/WEB-INF/tld/hello2.tld" %> A sample custom tag @@ -8,4 +8,4 @@ This is message body - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/test.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/test.jsp index 42d01f2d..260e7b39 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/test.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/test.jsp @@ -1,41 +1,39 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8"%> -<%@page import="java.util.List"%> -<%@page import="java.util.ArrayList"%> -<%@page import="io.github.dunwu.javaee.taglib.bean.Person"%> +<%@ page language="java" contentType="text/html; charset=UTF-8" %> +<%@page import="io.github.dunwu.javaee.taglib.bean.Person" %> +<%@page import="java.util.ArrayList" %> +<%@page import="java.util.List" %> <% - if (true) - { - List personList = new ArrayList(); + if (true) { + List personList = new ArrayList(); - Person person = new Person(); - person.setId(1); - person.setName("张三"); - person.setAge(20); - person.setSex("男"); - person.setAddress("北京市海淀区上地软件园"); - person.setBirthday("2008-08-08"); - person.setMobile("13820080808"); - person.setTelephone("69653234"); - person.setCity("北京"); + Person person = new Person(); + person.setId(1); + person.setName("张三"); + person.setAge(20); + person.setSex("男"); + person.setAddress("北京市海淀区上地软件园"); + person.setBirthday("2008-08-08"); + person.setMobile("13820080808"); + person.setTelephone("69653234"); + person.setCity("北京"); - personList.add(person); - Person person2 = new Person(); - person2.setId(2); - person2.setName("李四"); - person2.setAge(20); - person2.setSex("女"); - person2.setAddress("北京市东皇城根锡拉胡同"); - person2.setBirthday("2008-01-01"); - person2.setMobile("13820080808"); - person2.setTelephone("20054879"); - person2.setCity("北京"); + personList.add(person); + Person person2 = new Person(); + person2.setId(2); + person2.setName("李四"); + person2.setAge(20); + person2.setSex("女"); + person2.setAddress("北京市东皇城根锡拉胡同"); + person2.setBirthday("2008-01-01"); + person2.setMobile("13820080808"); + person2.setTelephone("20054879"); + person2.setCity("北京"); - personList.add(person2); + personList.add(person2); - request.setAttribute("personList", personList); + request.setAttribute("personList", personList); - request.getRequestDispatcher("/index.jsp").forward(request, - response); + request.getRequestDispatcher("/index.jsp").forward(request, response); - } + } %> diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java index 26e801e6..f4a1a2e9 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java @@ -1,13 +1,13 @@ package io.github.dunwu.javaee.server; -import java.util.ArrayList; - import org.apache.commons.lang3.StringUtils; import org.assertj.core.util.Lists; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.webapp.WebAppClassLoader; import org.eclipse.jetty.webapp.WebAppContext; +import java.util.ArrayList; + /** * JettyFactory 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij * 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 @@ -16,93 +16,95 @@ */ @SuppressWarnings("unused") public class JettyFactory { - private static final int PORT = 9798; - private static final String CONTEXT = "/"; - private static final String RESOURCE_BASE_PATH = "src/main/webapp"; - private static final String WEB_XML_PATH = "/WEB-INF/web.xml"; - private static final String[] TLD_JAR_NAMES = - new String[]{"sitemesh", "spring-webmvc", "shiro-web", "tiles"}; - private static final String WINDOWS_WEBDEFAULT_PATH = "jetty/webdefault.xml"; - - public static final int IDE_ECLIPSE = 0; - public static final int IDE_INTELLIJ = 1; - - - public static Server initServer() { - Profiles.setProfileAsSystemProperty(Profiles.DEVELOPMENT); - WebAppContext webAppContext = new WebAppContext(); - Server server = new Server(PORT); - server.setHandler(webAppContext); - return server; - } - - public static void initWebAppContext(Server server, int type) throws Exception { - System.out.println("[INFO] Application loading"); - WebAppContext webAppContext = (WebAppContext) server.getHandler(); - webAppContext.setContextPath(CONTEXT); - webAppContext.setResourceBase(getAbsolutePath() + RESOURCE_BASE_PATH); - webAppContext.setDescriptor(getAbsolutePath() + RESOURCE_BASE_PATH + WEB_XML_PATH); - - if (IDE_INTELLIJ == type) { - webAppContext.setDefaultsDescriptor(WINDOWS_WEBDEFAULT_PATH); - supportJspAndSetTldJarNames(server, TLD_JAR_NAMES); - } else { - webAppContext.setParentLoaderPriority(true); - } - - System.out.println("[INFO] Application loaded"); - } - - public static void reloadWebAppContext(Server server) throws Exception { - WebAppContext webAppContext = (WebAppContext) server.getHandler(); - System.out.println("[INFO] Application reloading"); - webAppContext.stop(); - WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext); - classLoader.addClassPath(getAbsolutePath() + "target/classes"); - classLoader.addClassPath(getAbsolutePath() + "target/test-classes"); - webAppContext.setClassLoader(classLoader); - webAppContext.start(); - System.out.println("[INFO] Application reloaded"); - } - - - public static void startServer(Server server) throws Exception { - System.out.println("[HINT] Don't forget to set -XX:MaxPermSize=128m"); - server.start(); - System.out.println("Server running at http://localhost:" + PORT + CONTEXT); - System.out.println("[HINT] Hit Enter to reload the application quickly"); - } - - public static String getAbsolutePath() { - String path = null; - String folderPath = JettyFactory.class.getProtectionDomain().getCodeSource().getLocation() - .getPath().substring(1); - if (folderPath.indexOf("target") > 0) { - path = folderPath.substring(0, folderPath.indexOf("target")); - } - return path; - } - - public static void supportJspAndSetTldJarNames(Server server, String... jarNames) { - WebAppContext context = (WebAppContext) server.getHandler(); - // This webapp will use jsps and jstl. We need to enable the AnnotationConfiguration in - // order to correctly set up the jsp container - org.eclipse.jetty.webapp.Configuration.ClassList classlist = - org.eclipse.jetty.webapp.Configuration.ClassList.setServerDefault(server); - classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", - "org.eclipse.jetty.annotations.AnnotationConfiguration"); - // Set the ContainerIncludeJarPattern so that jetty examines these container-path jars for - // tlds, web-fragments etc. - // If you omit the jar that contains the jstl .tlds, the jsp engine will scan for them - // instead. - ArrayList jarNameExprssions = Lists.newArrayList(".*/[^/]*servlet-api-[^/]*\\.jar$", - ".*/javax.servlet.jsp.jstl-.*\\.jar$", ".*/[^/]*taglibs.*\\.jar$"); - for (String jarName : jarNames) { - jarNameExprssions.add(".*/" + jarName + "-[^/]*\\.jar$"); - } + public static final int IDE_ECLIPSE = 0; + public static final int IDE_INTELLIJ = 1; + private static final int PORT = 9798; + private static final String CONTEXT = "/"; + private static final String RESOURCE_BASE_PATH = "src/main/webapp"; + private static final String WEB_XML_PATH = "/WEB-INF/web.xml"; + private static final String[] TLD_JAR_NAMES = new String[] { "sitemesh", "spring-webmvc", "shiro-web", "tiles" }; + private static final String WINDOWS_WEBDEFAULT_PATH = "jetty/webdefault.xml"; + + public static Server initServer() { + Profiles.setProfileAsSystemProperty(Profiles.DEVELOPMENT); + WebAppContext webAppContext = new WebAppContext(); + Server server = new Server(PORT); + server.setHandler(webAppContext); + return server; + } + + public static void initWebAppContext(Server server, int type) throws Exception { + System.out.println("[INFO] Application loading"); + WebAppContext webAppContext = (WebAppContext) server.getHandler(); + webAppContext.setContextPath(CONTEXT); + webAppContext.setResourceBase(getAbsolutePath() + RESOURCE_BASE_PATH); + webAppContext.setDescriptor(getAbsolutePath() + RESOURCE_BASE_PATH + WEB_XML_PATH); + + if (IDE_INTELLIJ == type) { + webAppContext.setDefaultsDescriptor(WINDOWS_WEBDEFAULT_PATH); + supportJspAndSetTldJarNames(server, TLD_JAR_NAMES); + } + else { + webAppContext.setParentLoaderPriority(true); + } + + System.out.println("[INFO] Application loaded"); + } + + public static void reloadWebAppContext(Server server) throws Exception { + WebAppContext webAppContext = (WebAppContext) server.getHandler(); + System.out.println("[INFO] Application reloading"); + webAppContext.stop(); + WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext); + classLoader.addClassPath(getAbsolutePath() + "target/classes"); + classLoader.addClassPath(getAbsolutePath() + "target/test-classes"); + webAppContext.setClassLoader(classLoader); + webAppContext.start(); + System.out.println("[INFO] Application reloaded"); + } + + public static void startServer(Server server) throws Exception { + System.out.println("[HINT] Don't forget to set -XX:MaxPermSize=128m"); + server.start(); + System.out.println("Server running at http://localhost:" + PORT + CONTEXT); + System.out.println("[HINT] Hit Enter to reload the application quickly"); + } + + public static String getAbsolutePath() { + String path = null; + String folderPath = JettyFactory.class.getProtectionDomain().getCodeSource().getLocation().getPath() + .substring(1); + if (folderPath.indexOf("target") > 0) { + path = folderPath.substring(0, folderPath.indexOf("target")); + } + return path; + } + + public static void supportJspAndSetTldJarNames(Server server, String... jarNames) { + WebAppContext context = (WebAppContext) server.getHandler(); + // This webapp will use jsps and jstl. We need to enable the + // AnnotationConfiguration in + // order to correctly set up the jsp container + org.eclipse.jetty.webapp.Configuration.ClassList classlist = org.eclipse.jetty.webapp.Configuration.ClassList + .setServerDefault(server); + classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", + "org.eclipse.jetty.annotations.AnnotationConfiguration"); + // Set the ContainerIncludeJarPattern so that jetty examines these container-path + // jars for + // tlds, web-fragments etc. + // If you omit the jar that contains the jstl .tlds, the jsp engine will scan for + // them + // instead. + ArrayList jarNameExprssions = Lists.newArrayList(".*/[^/]*servlet-api-[^/]*\\.jar$", + ".*/javax.servlet.jsp.jstl-.*\\.jar$", ".*/[^/]*taglibs.*\\.jar$"); + + for (String jarName : jarNames) { + jarNameExprssions.add(".*/" + jarName + "-[^/]*\\.jar$"); + } + + context.setAttribute("org.eclipse.jetty.io.github.dunwu.javaee.server.webapp.ContainerIncludeJarPattern", + StringUtils.join(jarNameExprssions, '|')); + } - context.setAttribute("org.eclipse.jetty.io.github.dunwu.javaee.server.webapp.ContainerIncludeJarPattern", - StringUtils.join(jarNameExprssions, '|')); - } } diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/Profiles.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/Profiles.java index 3a8ee7c4..dcfd3e39 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/Profiles.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/Profiles.java @@ -13,11 +13,15 @@ public class Profiles { public static final String ACTIVE_PROFILE = "spring.profiles.active"; + public static final String DEFAULT_PROFILE = "spring.profiles.default"; public static final String PRODUCTION = "production"; + public static final String DEVELOPMENT = "development"; + public static final String UNIT_TEST = "test"; + public static final String FUNCTIONAL_TEST = "functional"; /** @@ -26,4 +30,5 @@ public class Profiles { public static void setProfileAsSystemProperty(String profile) { System.setProperty(ACTIVE_PROFILE, profile); } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java index dd0ac658..c09f0fc1 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java @@ -8,26 +8,29 @@ * @author Zhang Peng */ public class QuickStartServer { - // private static int STARTUP_TYPE = JettyFactory.IDE_ECLIPSE; - private static int STARTUP_TYPE = JettyFactory.IDE_INTELLIJ; - public static void main(String[] args) throws Exception { - Server server = JettyFactory.initServer(); - JettyFactory.initWebAppContext(server, STARTUP_TYPE); + // private static int STARTUP_TYPE = JettyFactory.IDE_ECLIPSE; + private static int STARTUP_TYPE = JettyFactory.IDE_INTELLIJ; - try { - JettyFactory.startServer(server); + public static void main(String[] args) throws Exception { + Server server = JettyFactory.initServer(); + JettyFactory.initWebAppContext(server, STARTUP_TYPE); + + try { + JettyFactory.startServer(server); + + // 等待用户输入回车重载应用 + while (true) { + char c = (char) System.in.read(); + if (c == '\n') { + JettyFactory.reloadWebAppContext(server); + } + } + } + catch (Exception e) { + e.printStackTrace(); + System.exit(-1); + } + } - // 等待用户输入回车重载应用 - while (true) { - char c = (char) System.in.read(); - if (c == '\n') { - JettyFactory.reloadWebAppContext(server); - } - } - } catch (Exception e) { - e.printStackTrace(); - System.exit(-1); - } - } } diff --git a/codes/javaee-tutorial/javaee-tutorial-websocket/pom.xml b/codes/javaee-tutorial/javaee-tutorial-websocket/pom.xml index 13703216..0b35efa0 100644 --- a/codes/javaee-tutorial/javaee-tutorial-websocket/pom.xml +++ b/codes/javaee-tutorial/javaee-tutorial-websocket/pom.xml @@ -1,6 +1,6 @@ - + 4.0.0 io.github.dunwu diff --git a/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/servlet/SocketServlet.java b/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/servlet/SocketServlet.java index 93423d5e..5026ffd9 100644 --- a/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/servlet/SocketServlet.java +++ b/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/servlet/SocketServlet.java @@ -1,60 +1,60 @@ package io.github.dunwu.javaee.servlet; -import java.io.IOException; +import io.github.dunwu.javaee.websocket.WebSocketServer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import io.github.dunwu.javaee.websocket.WebSocketServer; +import java.io.IOException; /** * SocketServlet信息示例 */ public class SocketServlet extends HttpServlet { - private static final long serialVersionUID = -7936817351382556277L; - - private final Logger logger = LoggerFactory.getLogger(this.getClass()); - - /** - * Constructor of the object. - */ - public SocketServlet() { - super(); - } - - public void destroy() { - super.destroy(); // Just puts "destroy" string in log - // Put your code here - } - - public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - - // 设置 request,response 编码方式 - response.setCharacterEncoding("UTF-8"); - request.setCharacterEncoding("UTF-8"); - - // 取浏览器提交的 name 参数 - String id = request.getParameter("id"); - String text = request.getParameter("text"); - - if (id != null && id.length() > 0) { - WebSocketServer.send(id, text); - } else { - WebSocketServer.sendAll(text); - } - } - - public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - this.doGet(request, response); - } - - public void init() throws ServletException { - // Put your code here - } + + private static final long serialVersionUID = -7936817351382556277L; + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** + * Constructor of the object. + */ + public SocketServlet() { + super(); + } + + public void destroy() { + super.destroy(); // Just puts "destroy" string in log + // Put your code here + } + + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + + // 设置 request,response 编码方式 + response.setCharacterEncoding("UTF-8"); + request.setCharacterEncoding("UTF-8"); + + // 取浏览器提交的 name 参数 + String id = request.getParameter("id"); + String text = request.getParameter("text"); + + if (id != null && id.length() > 0) { + WebSocketServer.send(id, text); + } + else { + WebSocketServer.sendAll(text); + } + } + + public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + this.doGet(request, response); + } + + public void init() throws ServletException { + // Put your code here + } } diff --git a/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/websocket/WebSocketServer.java b/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/websocket/WebSocketServer.java index a1b40405..b096d805 100644 --- a/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/websocket/WebSocketServer.java +++ b/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/websocket/WebSocketServer.java @@ -1,5 +1,11 @@ package io.github.dunwu.javaee.websocket; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.websocket.*; +import javax.websocket.server.PathParam; +import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.HashSet; import java.util.Iterator; @@ -7,118 +13,109 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import javax.websocket.CloseReason; -import javax.websocket.EncodeException; -import javax.websocket.EndpointConfig; -import javax.websocket.OnClose; -import javax.websocket.OnError; -import javax.websocket.OnMessage; -import javax.websocket.OnOpen; -import javax.websocket.Session; -import javax.websocket.server.PathParam; -import javax.websocket.server.ServerEndpoint; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - /** * Websocket 消息处理中心 + * * @see https://github.com/jetty-project/embedded-jetty-websocket-examples * @author Zhang Peng */ @ServerEndpoint(value = "/auth/user/{id}", configurator = WebSocketServerConfigurator.class) public class WebSocketServer { - private static final Logger logger = LoggerFactory.getLogger(WebSocketServer.class); - - static Map> userSessionMap = new ConcurrentHashMap<>(); - // private static Map userSessionMap = new ConcurrentHashMap>(); - - @OnMessage - public void onMessage(String message, Session session) throws IOException, InterruptedException { - logger.debug("收到一条客户端消息。session:{}, msg:{}", session.getId(), message); - } - - @OnOpen - public void onOpen(Session session, EndpointConfig config, @PathParam("id") String id) { - logger.info("Session {} connected.", session.getId()); - - // 如果是新 Session,记录进 Map - boolean isNewUser = true; - Iterator i = userSessionMap.entrySet().iterator(); - while (i.hasNext()) { - Map.Entry entry = (Map.Entry) i.next(); - String key = (String) entry.getKey(); - if (key.equals(id)) { - userSessionMap.get(key).add(session); - isNewUser = false; - break; - } - } - if (isNewUser) { - Set sessions = new HashSet<>(); - sessions.add(session); - userSessionMap.put(id, sessions); - } - logger.info("当前在线用户数: {}", userSessionMap.values().size()); - } - - @OnClose - public void onClose(Session session, CloseReason closeReason) { - logger.info("Session {} disconnected. Because of {}", session.getId(), closeReason); - for (Set item : userSessionMap.values()) { - if (item.contains(session)) { - // 删除连接 session - item.remove(session); - // 如果 userId 对应的 session 数为 0 ,删除该 userId 对应的记录 - if (0 == item.size()) { - userSessionMap.values().remove(item); - } - break; - } - } - logger.info("当前在线用户数: {}", userSessionMap.values().size()); - } - - @OnError - public void onError(Throwable error) { - logger.error("Websocket error: {}", error.getMessage()); - } - - /** - * 发送广播消息 - * @param message - */ - public static void sendAll(String message) { - logger.info("SendAll: {}", message); - - for (Set groups : userSessionMap.values()) { - for (Session session : groups) { - try { - session.getBasicRemote().sendObject(message); - } catch (IOException e) { - logger.error(e.toString()); - } catch (EncodeException e) { - logger.error(e.toString()); - } - } - } - } - - public static void send(String userId, String message) { - for (String id : userSessionMap.keySet()) { - if (id.equals(userId)) { - for (Session session : userSessionMap.get(userId)) { - try { - session.getBasicRemote().sendObject(message); - logger.info("SendAll: {}", message); - } catch (Exception e) { - logger.error(e.toString()); - } - } - } - } - - } + private static final Logger logger = LoggerFactory.getLogger(WebSocketServer.class); + + static Map> userSessionMap = new ConcurrentHashMap<>(); + + // private static Map userSessionMap = new ConcurrentHashMap>(); + + /** + * 发送广播消息 + * @param message + */ + public static void sendAll(String message) { + logger.info("SendAll: {}", message); + + for (Set groups : userSessionMap.values()) { + for (Session session : groups) { + try { + session.getBasicRemote().sendObject(message); + } + catch (IOException e) { + logger.error(e.toString()); + } + catch (EncodeException e) { + logger.error(e.toString()); + } + } + } + } + + public static void send(String userId, String message) { + for (String id : userSessionMap.keySet()) { + if (id.equals(userId)) { + for (Session session : userSessionMap.get(userId)) { + try { + session.getBasicRemote().sendObject(message); + logger.info("SendAll: {}", message); + } + catch (Exception e) { + logger.error(e.toString()); + } + } + } + } + + } + + @OnMessage + public void onMessage(String message, Session session) throws IOException, InterruptedException { + logger.debug("收到一条客户端消息。session:{}, msg:{}", session.getId(), message); + } + + @OnOpen + public void onOpen(Session session, EndpointConfig config, @PathParam("id") String id) { + logger.info("Session {} connected.", session.getId()); + + // 如果是新 Session,记录进 Map + boolean isNewUser = true; + Iterator i = userSessionMap.entrySet().iterator(); + while (i.hasNext()) { + Map.Entry entry = (Map.Entry) i.next(); + String key = (String) entry.getKey(); + if (key.equals(id)) { + userSessionMap.get(key).add(session); + isNewUser = false; + break; + } + } + if (isNewUser) { + Set sessions = new HashSet<>(); + sessions.add(session); + userSessionMap.put(id, sessions); + } + logger.info("当前在线用户数: {}", userSessionMap.values().size()); + } + + @OnClose + public void onClose(Session session, CloseReason closeReason) { + logger.info("Session {} disconnected. Because of {}", session.getId(), closeReason); + for (Set item : userSessionMap.values()) { + if (item.contains(session)) { + // 删除连接 session + item.remove(session); + // 如果 userId 对应的 session 数为 0 ,删除该 userId 对应的记录 + if (0 == item.size()) { + userSessionMap.values().remove(item); + } + break; + } + } + logger.info("当前在线用户数: {}", userSessionMap.values().size()); + } + + @OnError + public void onError(Throwable error) { + logger.error("Websocket error: {}", error.getMessage()); + } } diff --git a/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/websocket/WebSocketServerConfigurator.java b/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/websocket/WebSocketServerConfigurator.java index 36f15097..4b515645 100644 --- a/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/websocket/WebSocketServerConfigurator.java +++ b/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/websocket/WebSocketServerConfigurator.java @@ -9,9 +9,11 @@ * @author Zhang Peng */ public class WebSocketServerConfigurator extends ServerEndpointConfig.Configurator { - @Override - public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) { - HttpSession httpSession = (HttpSession) request.getHttpSession(); - sec.getUserProperties().put(HttpSession.class.getName(), httpSession); - } + + @Override + public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) { + HttpSession httpSession = (HttpSession) request.getHttpSession(); + sec.getUserProperties().put(HttpSession.class.getName(), httpSession); + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/webapp/WEB-INF/web.xml b/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/webapp/WEB-INF/web.xml index 32dca619..17cc6c77 100644 --- a/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/webapp/WEB-INF/web.xml +++ b/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/webapp/WEB-INF/web.xml @@ -1,7 +1,7 @@ - diff --git a/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/webapp/websocketA.html b/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/webapp/websocketA.html index 68b310d4..9408973e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/webapp/websocketA.html +++ b/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/webapp/websocketA.html @@ -8,42 +8,40 @@ /* websocket 示例 */ function WebSocketTest() { - var content = document.getElementById("content"); + var content = document.getElementById('content') - if ("WebSocket" in window) { - var node = document.createElement("p"); - node.innerText = "您的浏览器支持 WebSocket!"; - content.appendChild(node); + if ('WebSocket' in window) { + var node = document.createElement('p') + node.innerText = '您的浏览器支持 WebSocket!' + content.appendChild(node) // 打开一个 web socket - var ws = new WebSocket("ws://localhost:8089/auth/user/aaa"); + var ws = new WebSocket('ws://localhost:8089/auth/user/aaa') - ws.onopen = function () { + ws.onopen = function() { // Web Socket 已连接上,使用 send() 方法发送数据 - ws.send("发送数据"); - var node = document.createElement("p"); - node.innerText = "数据发送中..."; - content.appendChild(node); - }; - - ws.onmessage = function (evt) { - var received_msg = evt.data; - var node = document.createElement("p"); - node.innerText = received_msg; - content.appendChild(node); - }; + ws.send('发送数据') + var node = document.createElement('p') + node.innerText = '数据发送中...' + content.appendChild(node) + } + + ws.onmessage = function(evt) { + var received_msg = evt.data + var node = document.createElement('p') + node.innerText = received_msg + content.appendChild(node) + } // ws.onclose = function () { // // 关闭 websocket // alert("连接已关闭..."); // }; - } - - else { + } else { // 浏览器不支持 WebSocket - var node = document.createElement("p"); - node.innerText = "您的浏览器不支持 WebSocket!"; - content.appendChild(node); + var node = document.createElement('p') + node.innerText = '您的浏览器不支持 WebSocket!' + content.appendChild(node) } } diff --git a/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/webapp/websocketB.html b/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/webapp/websocketB.html index b0c88eaa..c53f0362 100644 --- a/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/webapp/websocketB.html +++ b/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/webapp/websocketB.html @@ -8,42 +8,40 @@ /* websocket 示例 */ function WebSocketTest() { - var content = document.getElementById("content"); + var content = document.getElementById('content') - if ("WebSocket" in window) { - var node = document.createElement("p"); - node.innerText = "您的浏览器支持 WebSocket!"; - content.appendChild(node); + if ('WebSocket' in window) { + var node = document.createElement('p') + node.innerText = '您的浏览器支持 WebSocket!' + content.appendChild(node) // 打开一个 web socket - var ws = new WebSocket("ws://localhost:8089/auth/user/bbb"); + var ws = new WebSocket('ws://localhost:8089/auth/user/bbb') - ws.onopen = function () { + ws.onopen = function() { // Web Socket 已连接上,使用 send() 方法发送数据 - ws.send("发送数据"); - var node = document.createElement("p"); - node.innerText = "数据发送中..."; - content.appendChild(node); - }; - - ws.onmessage = function (evt) { - var received_msg = evt.data; - var node = document.createElement("p"); - node.innerText = received_msg; - content.appendChild(node); - }; + ws.send('发送数据') + var node = document.createElement('p') + node.innerText = '数据发送中...' + content.appendChild(node) + } + + ws.onmessage = function(evt) { + var received_msg = evt.data + var node = document.createElement('p') + node.innerText = received_msg + content.appendChild(node) + } // ws.onclose = function () { // // 关闭 websocket // alert("连接已关闭..."); // }; - } - - else { + } else { // 浏览器不支持 WebSocket - var node = document.createElement("p"); - node.innerText = "您的浏览器不支持 WebSocket!"; - content.appendChild(node); + var node = document.createElement('p') + node.innerText = '您的浏览器不支持 WebSocket!' + content.appendChild(node) } } diff --git a/codes/javaee-tutorial/javaee-tutorial-websocket/src/test/java/io/github/dunwu/javaee/server/DevServer.java b/codes/javaee-tutorial/javaee-tutorial-websocket/src/test/java/io/github/dunwu/javaee/server/DevServer.java index bc049622..2cb683f0 100644 --- a/codes/javaee-tutorial/javaee-tutorial-websocket/src/test/java/io/github/dunwu/javaee/server/DevServer.java +++ b/codes/javaee-tutorial/javaee-tutorial-websocket/src/test/java/io/github/dunwu/javaee/server/DevServer.java @@ -1,7 +1,7 @@ package io.github.dunwu.javaee.server; -import java.util.ArrayList; - +import com.google.common.collect.Lists; +import io.github.dunwu.javaee.websocket.WebSocketServer; import org.apache.commons.lang3.StringUtils; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.webapp.WebAppClassLoader; @@ -9,117 +9,120 @@ import org.eclipse.jetty.websocket.jsr356.server.ServerContainer; import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer; -import com.google.common.collect.Lists; - -import io.github.dunwu.javaee.websocket.WebSocketServer; +import java.util.ArrayList; /** - * DevServer 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 + * DevServer 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij + * 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 * * @author Zhang Peng */ @SuppressWarnings("unused") public class DevServer { - private static final int PORT = 8089; - private static final String CONTEXT = "/"; - private static final String RESOURCE_BASE_PATH = "src/main/webapp"; - private static final String WEB_XML_PATH = "/WEB-INF/web.xml"; - private static final String[] TLD_JAR_NAMES = new String[] { "sitemesh", "spring-webmvc", "shiro-web", "tiles" }; - private static final String WINDOWS_WEBDEFAULT_PATH = "jetty/webdefault.xml"; - - public static final int IDE_ECLIPSE = 0; - public static final int IDE_INTELLIJ = 1; - - public static final String PRODUCTION = "production"; - public static final String DEVELOPMENT = "development"; - public static final String UNIT_TEST = "test"; - public static final String FUNCTIONAL_TEST = "functional"; - - private static void reloadWebAppContext(Server server) throws Exception { - WebAppContext webAppContext = (WebAppContext) server.getHandler(); - System.out.println("[INFO] Application reloading"); - webAppContext.stop(); - WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext); - classLoader.addClassPath(getAbsolutePath() + "target/classes"); - classLoader.addClassPath(getAbsolutePath() + "target/test-classes"); - webAppContext.setClassLoader(classLoader); - webAppContext.start(); - System.out.println("[INFO] Application reloaded"); - } - - private static String getAbsolutePath() { - String path = null; - String folderPath = DevServer.class.getProtectionDomain().getCodeSource().getLocation().getPath().substring(1); - if (folderPath.indexOf("target") > 0) { - path = folderPath.substring(0, folderPath.indexOf("target")); - } - return path; - } - - private static void supportJspAndSetTldJarNames(Server server, String... jarNames) { - WebAppContext context = (WebAppContext) server.getHandler(); - // This webapp will use jsps and jstl. We need to enable the AnnotationConfiguration in - // order to correctly set up the jsp container - org.eclipse.jetty.webapp.Configuration.ClassList classlist = org.eclipse.jetty.webapp.Configuration.ClassList - .setServerDefault(server); - classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", - "org.eclipse.jetty.annotations.AnnotationConfiguration"); - // Set the ContainerIncludeJarPattern so that jetty examines these container-path jars for - // tlds, web-fragments etc. - // If you omit the jar that contains the jstl .tlds, the jsp engine will scan for them - // instead. - ArrayList jarNameExprssions = Lists.newArrayList(".*/[^/]*servlet-api-[^/]*\\.jar$", - ".*/javax.servlet.jsp.jstl-.*\\.jar$", ".*/[^/]*taglibs.*\\.jar$"); - - for (String jarName : jarNames) { - jarNameExprssions.add(".*/" + jarName + "-[^/]*\\.jar$"); - } - - context.setAttribute("org.eclipse.jetty.io.github.dunwu.javaee.server.webapp.ContainerIncludeJarPattern", - StringUtils.join(jarNameExprssions, '|')); - } - - public static void main(String[] args) throws Exception { - - // 初始化 WebAppContext - System.out.println("[INFO] Application loading"); - WebAppContext webAppContext = new WebAppContext(); - webAppContext.setContextPath(CONTEXT); - webAppContext.setResourceBase(getAbsolutePath() + RESOURCE_BASE_PATH); - webAppContext.setDescriptor(getAbsolutePath() + RESOURCE_BASE_PATH + WEB_XML_PATH); - webAppContext.setDefaultsDescriptor(WINDOWS_WEBDEFAULT_PATH); - - // 初始化 server - Server server = new Server(PORT); - server.setHandler(webAppContext); - supportJspAndSetTldJarNames(server, TLD_JAR_NAMES); - - System.out.println("[INFO] Application loaded"); - - try { - // Initialize javax.websocket layer - ServerContainer wscontainer = WebSocketServerContainerInitializer.configureContext(webAppContext); - - // Add WebSocket endpoint to javax.websocket layer - wscontainer.addEndpoint(WebSocketServer.class); - - System.out.println("[HINT] Don't forget to set -XX:MaxPermSize=128m"); - System.out.println("Server running at http://localhost:" + PORT + CONTEXT); - System.out.println("[HINT] Hit Enter to reload the application quickly"); - - server.start(); - // server.join(); - - // 等待用户输入回车重载应用 - while (true) { - char c = (char) System.in.read(); - if (c == '\n') { - DevServer.reloadWebAppContext(server); - } - } - } catch (Exception e) { - e.printStackTrace(); - System.exit(-1); - } - } + + public static final int IDE_ECLIPSE = 0; + public static final int IDE_INTELLIJ = 1; + public static final String PRODUCTION = "production"; + public static final String DEVELOPMENT = "development"; + public static final String UNIT_TEST = "test"; + public static final String FUNCTIONAL_TEST = "functional"; + private static final int PORT = 8089; + private static final String CONTEXT = "/"; + private static final String RESOURCE_BASE_PATH = "src/main/webapp"; + private static final String WEB_XML_PATH = "/WEB-INF/web.xml"; + private static final String[] TLD_JAR_NAMES = new String[] { "sitemesh", "spring-webmvc", "shiro-web", "tiles" }; + private static final String WINDOWS_WEBDEFAULT_PATH = "jetty/webdefault.xml"; + + private static void reloadWebAppContext(Server server) throws Exception { + WebAppContext webAppContext = (WebAppContext) server.getHandler(); + System.out.println("[INFO] Application reloading"); + webAppContext.stop(); + WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext); + classLoader.addClassPath(getAbsolutePath() + "target/classes"); + classLoader.addClassPath(getAbsolutePath() + "target/test-classes"); + webAppContext.setClassLoader(classLoader); + webAppContext.start(); + System.out.println("[INFO] Application reloaded"); + } + + private static String getAbsolutePath() { + String path = null; + String folderPath = DevServer.class.getProtectionDomain().getCodeSource().getLocation().getPath().substring(1); + if (folderPath.indexOf("target") > 0) { + path = folderPath.substring(0, folderPath.indexOf("target")); + } + return path; + } + + private static void supportJspAndSetTldJarNames(Server server, String... jarNames) { + WebAppContext context = (WebAppContext) server.getHandler(); + // This webapp will use jsps and jstl. We need to enable the + // AnnotationConfiguration in + // order to correctly set up the jsp container + org.eclipse.jetty.webapp.Configuration.ClassList classlist = org.eclipse.jetty.webapp.Configuration.ClassList + .setServerDefault(server); + classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", + "org.eclipse.jetty.annotations.AnnotationConfiguration"); + // Set the ContainerIncludeJarPattern so that jetty examines these container-path + // jars for + // tlds, web-fragments etc. + // If you omit the jar that contains the jstl .tlds, the jsp engine will scan for + // them + // instead. + ArrayList jarNameExprssions = Lists.newArrayList(".*/[^/]*servlet-api-[^/]*\\.jar$", + ".*/javax.servlet.jsp.jstl-.*\\.jar$", ".*/[^/]*taglibs.*\\.jar$"); + + for (String jarName : jarNames) { + jarNameExprssions.add(".*/" + jarName + "-[^/]*\\.jar$"); + } + + context.setAttribute("org.eclipse.jetty.io.github.dunwu.javaee.server.webapp.ContainerIncludeJarPattern", + StringUtils.join(jarNameExprssions, '|')); + } + + public static void main(String[] args) throws Exception { + + // 初始化 WebAppContext + System.out.println("[INFO] Application loading"); + WebAppContext webAppContext = new WebAppContext(); + webAppContext.setContextPath(CONTEXT); + webAppContext.setResourceBase(getAbsolutePath() + RESOURCE_BASE_PATH); + webAppContext.setDescriptor(getAbsolutePath() + RESOURCE_BASE_PATH + WEB_XML_PATH); + webAppContext.setDefaultsDescriptor(WINDOWS_WEBDEFAULT_PATH); + + // 初始化 server + Server server = new Server(PORT); + server.setHandler(webAppContext); + supportJspAndSetTldJarNames(server, TLD_JAR_NAMES); + + System.out.println("[INFO] Application loaded"); + + try { + // Initialize javax.websocket layer + ServerContainer wscontainer = WebSocketServerContainerInitializer.configureContext(webAppContext); + + // Add WebSocket endpoint to javax.websocket layer + wscontainer.addEndpoint(WebSocketServer.class); + + System.out.println("[HINT] Don't forget to set -XX:MaxPermSize=128m"); + System.out.println("Server running at http://localhost:" + PORT + CONTEXT); + System.out.println("[HINT] Hit Enter to reload the application quickly"); + + server.start(); + // server.join(); + + // 等待用户输入回车重载应用 + while (true) { + char c = (char) System.in.read(); + if (c == '\n') { + DevServer.reloadWebAppContext(server); + } + } + } + catch (Exception e) { + e.printStackTrace(); + System.exit(-1); + } + } + } diff --git a/codes/javaee-tutorial/pom.xml b/codes/javaee-tutorial/pom.xml index ac104294..85fbb1e8 100644 --- a/codes/javaee-tutorial/pom.xml +++ b/codes/javaee-tutorial/pom.xml @@ -1,5 +1,5 @@ - 4.0.0 diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/pom.xml b/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/pom.xml index a088444f..cc678c3f 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/pom.xml +++ b/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 2.1.4.RELEASE + 2.1.9.RELEASE io.github.dunwu.javaweb diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaConsumer.java b/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaConsumer.java index d086807c..bac02124 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaConsumer.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaConsumer.java @@ -7,16 +7,18 @@ /** * Kafka 消费者 + * * @author Zhang Peng * @date 2018-11-28 */ @Component public class KafkaConsumer { - private final Logger log = LoggerFactory.getLogger(KafkaConsumer.class); + private final Logger log = LoggerFactory.getLogger(KafkaConsumer.class); + + @KafkaListener(topics = "test") + public void processMessage(String data) { + log.info("收到kafka消息:{}", data); + } - @KafkaListener(topics = "test") - public void processMessage(String data) { - log.info("收到kafka消息:{}", data); - } } diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaProducer.java b/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaProducer.java index 58f54665..1d833aa4 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaProducer.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaProducer.java @@ -16,21 +16,22 @@ @Component public class KafkaProducer { - private final Logger log = LoggerFactory.getLogger(KafkaProducer.class); + private final Logger log = LoggerFactory.getLogger(KafkaProducer.class); - @Autowired - private KafkaTemplate template; + @Autowired + private KafkaTemplate template; + + @Transactional(rollbackFor = RuntimeException.class) + public void sendTransactionMsg(String topic, String data) { + log.info("向kafka发送数据:[{}]", data); + template.executeInTransaction(t -> { + t.send(topic, "prepare"); + if ("error".equals(data)) { + throw new RuntimeException("failed"); + } + t.send(topic, "finish"); + return true; + }); + } - @Transactional(rollbackFor = RuntimeException.class) - public void sendTransactionMsg(String topic, String data) { - log.info("向kafka发送数据:[{}]", data); - template.executeInTransaction(t -> { - t.send(topic, "prepare"); - if ("error".equals(data)) { - throw new RuntimeException("failed"); - } - t.send(topic, "finish"); - return true; - }); - } } diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaProducerController.java b/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaProducerController.java index 261be609..86c8f9c2 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaProducerController.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaProducerController.java @@ -10,17 +10,19 @@ * 此 Controller 作为生产者,接受REST接口传入的消息,并写入到指定 Kafka Topic *

    * 访问方式:http://localhost:8080/kafka/send?topic=xxx&data=xxx + * * @author Zhang Peng */ @RestController @RequestMapping("kafka") public class KafkaProducerController { - @Autowired - private KafkaProducer kafkaProducer; + @Autowired + private KafkaProducer kafkaProducer; + + @RequestMapping("sendTx") + public void send(String topic, String data) { + kafkaProducer.sendTransactionMsg(topic, data); + } - @RequestMapping("sendTx") - public void send(String topic, String data) { - kafkaProducer.sendTransactionMsg(topic, data); - } } diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/MsgKafkaApplication.java b/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/MsgKafkaApplication.java index f94b013b..002ad216 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/MsgKafkaApplication.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/MsgKafkaApplication.java @@ -6,7 +6,8 @@ @SpringBootApplication public class MsgKafkaApplication { - public static void main(String[] args) { - SpringApplication.run(MsgKafkaApplication.class, args); - } + public static void main(String[] args) { + SpringApplication.run(MsgKafkaApplication.class, args); + } + } diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/config/KafkaConsumerConfig.java b/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/config/KafkaConsumerConfig.java index 65579303..8bde8d31 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/config/KafkaConsumerConfig.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/config/KafkaConsumerConfig.java @@ -19,42 +19,42 @@ @EnableKafka public class KafkaConsumerConfig { - @Value("${spring.kafka.bootstrap-servers}") - private String bootstrapServers; - - public Map consumerConfig(String consumerGroupId){ - Map props = new HashMap<>(); - props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); - props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false); - props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "100"); - props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, "15000"); - props.put(ConsumerConfig.GROUP_ID_CONFIG, consumerGroupId); - props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest"); - props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); - props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); - return props; - } - public ConsumerFactory consumerFactory(String consumerGroupId) { - return new DefaultKafkaConsumerFactory<>(consumerConfig(consumerGroupId)); - } - - - - @Bean(name="kafkaListenerContainerFactory") - public KafkaListenerContainerFactory> kafkaListenerContainerFactory() { - ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); - factory.setConsumerFactory(consumerFactory("groupA")); - factory.setConcurrency(3); - factory.getContainerProperties().setPollTimeout(3000); - return factory; - } - - @Bean(name="kafkaListenerContainerFactory1") - public KafkaListenerContainerFactory> kafkaListenerContainerFactory1() { - ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); - factory.setConsumerFactory(consumerFactory("groupB")); - factory.setConcurrency(3); - factory.getContainerProperties().setPollTimeout(3000); - return factory; - } + @Value("${spring.kafka.bootstrap-servers}") + private String bootstrapServers; + + public Map consumerConfig(String consumerGroupId) { + Map props = new HashMap<>(); + props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); + props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false); + props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "100"); + props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, "15000"); + props.put(ConsumerConfig.GROUP_ID_CONFIG, consumerGroupId); + props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest"); + props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); + props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); + return props; + } + + public ConsumerFactory consumerFactory(String consumerGroupId) { + return new DefaultKafkaConsumerFactory<>(consumerConfig(consumerGroupId)); + } + + @Bean(name = "kafkaListenerContainerFactory") + public KafkaListenerContainerFactory> kafkaListenerContainerFactory() { + ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); + factory.setConsumerFactory(consumerFactory("groupA")); + factory.setConcurrency(3); + factory.getContainerProperties().setPollTimeout(3000); + return factory; + } + + @Bean(name = "kafkaListenerContainerFactory1") + public KafkaListenerContainerFactory> kafkaListenerContainerFactory1() { + ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); + factory.setConsumerFactory(consumerFactory("groupB")); + factory.setConcurrency(3); + factory.getContainerProperties().setPollTimeout(3000); + return factory; + } + } diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/config/KafkaProducerConfig.java b/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/config/KafkaProducerConfig.java index 0d288f74..c18a0800 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/config/KafkaProducerConfig.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/config/KafkaProducerConfig.java @@ -18,43 +18,44 @@ @EnableKafka public class KafkaProducerConfig { - @Value("${spring.kafka.bootstrap-servers}") - private String bootstrapServers; - - @Value("${spring.kafka.producer.retries}") - private Integer retries; - - @Value("${spring.kafka.producer.batch-size}") - private Integer batchSize; - - @Bean - public Map producerConfigs() { - Map props = new HashMap<>(7); - props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); - props.put(ProducerConfig.RETRIES_CONFIG, retries); - props.put(ProducerConfig.BATCH_SIZE_CONFIG, batchSize); - props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); - props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class); - return props; - } - - @Bean - public ProducerFactory producerFactory() { - DefaultKafkaProducerFactory producerFactory = new DefaultKafkaProducerFactory<>( - producerConfigs()); - producerFactory.transactionCapable(); - producerFactory.setTransactionIdPrefix("hous-"); - return producerFactory; - } - - @Bean - public KafkaTransactionManager transactionManager() { - KafkaTransactionManager manager = new KafkaTransactionManager(producerFactory()); - return manager; - } - - @Bean - public KafkaTemplate kafkaTemplate() { - return new KafkaTemplate<>(producerFactory()); - } + @Value("${spring.kafka.bootstrap-servers}") + private String bootstrapServers; + + @Value("${spring.kafka.producer.retries}") + private Integer retries; + + @Value("${spring.kafka.producer.batch-size}") + private Integer batchSize; + + @Bean + public Map producerConfigs() { + Map props = new HashMap<>(7); + props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); + props.put(ProducerConfig.RETRIES_CONFIG, retries); + props.put(ProducerConfig.BATCH_SIZE_CONFIG, batchSize); + props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + return props; + } + + @Bean + public ProducerFactory producerFactory() { + DefaultKafkaProducerFactory producerFactory = new DefaultKafkaProducerFactory<>( + producerConfigs()); + producerFactory.transactionCapable(); + producerFactory.setTransactionIdPrefix("hous-"); + return producerFactory; + } + + @Bean + public KafkaTransactionManager transactionManager() { + KafkaTransactionManager manager = new KafkaTransactionManager(producerFactory()); + return manager; + } + + @Bean + public KafkaTemplate kafkaTemplate() { + return new KafkaTemplate<>(producerFactory()); + } + } diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/test/java/io/github/dunwu/javaweb/KafkaProducerTest.java b/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/test/java/io/github/dunwu/javaweb/KafkaProducerTest.java index b9ecb914..628b6430 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/test/java/io/github/dunwu/javaweb/KafkaProducerTest.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/test/java/io/github/dunwu/javaweb/KafkaProducerTest.java @@ -13,12 +13,14 @@ @RunWith(SpringRunner.class) @SpringBootTest(classes = MsgKafkaApplication.class) public class KafkaProducerTest { - @Autowired - private KafkaProducer kafkaProducer; - @Test - public void test() { - kafkaProducer.sendTransactionMsg("test", "上联:天王盖地虎"); - kafkaProducer.sendTransactionMsg("test", "下联:宝塔镇河妖"); - } + @Autowired + private KafkaProducer kafkaProducer; + + @Test + public void test() { + kafkaProducer.sendTransactionMsg("test", "上联:天王盖地虎"); + kafkaProducer.sendTransactionMsg("test", "下联:宝塔镇河妖"); + } + } diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/pom.xml b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/pom.xml index d3847e69..aa178eb4 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/pom.xml +++ b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/pom.xml @@ -1,6 +1,6 @@ - + 4.0.0 io.github.dunwu.javaweb diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerAOC.java b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerAOC.java index 9843aa14..3eda24c6 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerAOC.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerAOC.java @@ -1,42 +1,44 @@ package io.github.dunwu.javaweb.kafka; -import java.util.Arrays; -import java.util.Properties; import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecords; import org.apache.kafka.clients.consumer.KafkaConsumer; +import java.util.Arrays; +import java.util.Properties; + /** * Kafka 消费者消费消息示例 消费者配置参考:https://kafka.apache.org/documentation/#consumerconfigs */ public class ConsumerAOC { - private static final String HOST = "localhost:9092"; - - public static void main(String[] args) { - // 1. 指定消费者的配置 - final Properties props = new Properties(); - props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, HOST); - props.put(ConsumerConfig.GROUP_ID_CONFIG, "test"); - props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true"); - props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000"); - props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringDeserializer"); - props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringDeserializer"); - - // 2. 使用配置初始化 Kafka 消费者 - KafkaConsumer consumer = new KafkaConsumer<>(props); - - // 3. 消费者订阅 Topic - consumer.subscribe(Arrays.asList("t1")); - while (true) { - // 4. 消费消息 - ConsumerRecords records = consumer.poll(100); - for (ConsumerRecord record : records) { - System.out.printf("offset = %d, key = %s, value = %s%n", - record.offset(), record.key(), record.value()); - } - } - } + + private static final String HOST = "localhost:9092"; + + public static void main(String[] args) { + // 1. 指定消费者的配置 + final Properties props = new Properties(); + props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, HOST); + props.put(ConsumerConfig.GROUP_ID_CONFIG, "test"); + props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true"); + props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000"); + props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.StringDeserializer"); + props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.StringDeserializer"); + + // 2. 使用配置初始化 Kafka 消费者 + KafkaConsumer consumer = new KafkaConsumer<>(props); + + // 3. 消费者订阅 Topic + consumer.subscribe(Arrays.asList("t1")); + while (true) { + // 4. 消费消息 + ConsumerRecords records = consumer.poll(100); + for (ConsumerRecord record : records) { + System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value()); + } + } + } + } diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManual.java b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManual.java index 4310dad5..fe1361ad 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManual.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManual.java @@ -1,45 +1,48 @@ package io.github.dunwu.javaweb.kafka; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Properties; import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecords; import org.apache.kafka.clients.consumer.KafkaConsumer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Properties; + /** * @author Zhang Peng * @date 2018/7/12 */ public class ConsumerManual { - private static final String HOST = "localhost:9092"; - public static void main(String[] args) { - Properties props = new Properties(); - props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, HOST); - props.put(ConsumerConfig.GROUP_ID_CONFIG, "test"); - props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false"); - props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringDeserializer"); - props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringDeserializer"); + private static final String HOST = "localhost:9092"; + + public static void main(String[] args) { + Properties props = new Properties(); + props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, HOST); + props.put(ConsumerConfig.GROUP_ID_CONFIG, "test"); + props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false"); + props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.StringDeserializer"); + props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.StringDeserializer"); + + KafkaConsumer consumer = new KafkaConsumer<>(props); + consumer.subscribe(Arrays.asList("t1", "t2")); + final int minBatchSize = 200; + List> buffer = new ArrayList<>(); + while (true) { + ConsumerRecords records = consumer.poll(100); + for (ConsumerRecord record : records) { + buffer.add(record); + } + if (buffer.size() >= minBatchSize) { + // 逻辑处理,例如保存到数据库 + consumer.commitSync(); + buffer.clear(); + } + } + } - KafkaConsumer consumer = new KafkaConsumer<>(props); - consumer.subscribe(Arrays.asList("t1", "t2")); - final int minBatchSize = 200; - List> buffer = new ArrayList<>(); - while (true) { - ConsumerRecords records = consumer.poll(100); - for (ConsumerRecord record : records) { - buffer.add(record); - } - if (buffer.size() >= minBatchSize) { - //逻辑处理,例如保存到数据库 - consumer.commitSync(); - buffer.clear(); - } - } - } } diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManualPartition.java b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManualPartition.java index c97890d9..d0014c41 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManualPartition.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManualPartition.java @@ -1,56 +1,50 @@ package io.github.dunwu.javaweb.kafka; +import org.apache.kafka.clients.consumer.*; +import org.apache.kafka.common.TopicPartition; + import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Properties; -import org.apache.kafka.clients.consumer.ConsumerConfig; -import org.apache.kafka.clients.consumer.ConsumerRecord; -import org.apache.kafka.clients.consumer.ConsumerRecords; -import org.apache.kafka.clients.consumer.KafkaConsumer; -import org.apache.kafka.clients.consumer.OffsetAndMetadata; -import org.apache.kafka.common.TopicPartition; /** * @author Zhang Peng * @date 2018/7/12 */ public class ConsumerManualPartition { - private static final String HOST = "localhost:9092"; - - public static void main(String[] args) { - Properties props = new Properties(); - props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, HOST); - props.put(ConsumerConfig.GROUP_ID_CONFIG, "test2"); - props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false"); - props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringDeserializer"); - props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringDeserializer"); - - KafkaConsumer consumer = new KafkaConsumer<>(props); - consumer.subscribe(Arrays.asList("t1")); - - try { - while (true) { - ConsumerRecords records = consumer.poll(Long.MAX_VALUE); - for (TopicPartition partition : records.partitions()) { - List> partitionRecords = - records.records(partition); - for (ConsumerRecord record : partitionRecords) { - System.out.println(partition.partition() + ": " - + record.offset() + ": " - + record.value()); - } - long lastOffset = partitionRecords.get( - partitionRecords.size() - 1).offset(); - consumer.commitSync( - Collections.singletonMap(partition, - new OffsetAndMetadata(lastOffset + 1))); - } - } - } finally { - consumer.close(); - } - } + + private static final String HOST = "localhost:9092"; + + public static void main(String[] args) { + Properties props = new Properties(); + props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, HOST); + props.put(ConsumerConfig.GROUP_ID_CONFIG, "test2"); + props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false"); + props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.StringDeserializer"); + props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.StringDeserializer"); + + KafkaConsumer consumer = new KafkaConsumer<>(props); + consumer.subscribe(Arrays.asList("t1")); + + try { + while (true) { + ConsumerRecords records = consumer.poll(Long.MAX_VALUE); + for (TopicPartition partition : records.partitions()) { + List> partitionRecords = records.records(partition); + for (ConsumerRecord record : partitionRecords) { + System.out.println(partition.partition() + ": " + record.offset() + ": " + record.value()); + } + long lastOffset = partitionRecords.get(partitionRecords.size() - 1).offset(); + consumer.commitSync(Collections.singletonMap(partition, new OffsetAndMetadata(lastOffset + 1))); + } + } + } + finally { + consumer.close(); + } + } + } diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerDemo.java b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerDemo.java index 01772342..d5639cef 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerDemo.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerDemo.java @@ -11,37 +11,41 @@ * Kafka 生产者生产消息示例 生产者配置参考:https://kafka.apache.org/documentation/#producerconfigs */ public class ProducerDemo { - private static final String HOST = "localhost:9092"; - - public static void main(String[] args) { - // 1. 指定生产者的配置 - Properties properties = new Properties(); - properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, HOST); - properties.put(ProducerConfig.ACKS_CONFIG, "all"); - properties.put(ProducerConfig.RETRIES_CONFIG, 0); - properties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384); - properties.put(ProducerConfig.LINGER_MS_CONFIG, 1); - properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432); - properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringSerializer"); - properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringSerializer"); - - // 2. 使用配置初始化 Kafka 生产者 - Producer producer = new KafkaProducer<>(properties); - - try { - // 3. 使用 send 方法发送异步消息 - for (int i = 0; i < 100; i++) { - String msg = "Message " + i; - producer.send(new ProducerRecord<>("HelloWorld", msg)); - System.out.println("Sent:" + msg); - } - } catch (Exception e) { - e.printStackTrace(); - } finally { - // 4. 关闭生产者 - producer.close(); - } - } + + private static final String HOST = "localhost:9092"; + + public static void main(String[] args) { + // 1. 指定生产者的配置 + Properties properties = new Properties(); + properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, HOST); + properties.put(ProducerConfig.ACKS_CONFIG, "all"); + properties.put(ProducerConfig.RETRIES_CONFIG, 0); + properties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384); + properties.put(ProducerConfig.LINGER_MS_CONFIG, 1); + properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432); + properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.StringSerializer"); + properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.StringSerializer"); + + // 2. 使用配置初始化 Kafka 生产者 + Producer producer = new KafkaProducer<>(properties); + + try { + // 3. 使用 send 方法发送异步消息 + for (int i = 0; i < 100; i++) { + String msg = "Message " + i; + producer.send(new ProducerRecord<>("HelloWorld", msg)); + System.out.println("Sent:" + msg); + } + } + catch (Exception e) { + e.printStackTrace(); + } + finally { + // 4. 关闭生产者 + producer.close(); + } + } + } diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerInTransaction.java b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerInTransaction.java index 54c31ba0..2f69e842 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerInTransaction.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerInTransaction.java @@ -1,146 +1,147 @@ package io.github.dunwu.javaweb.kafka; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; -import org.apache.kafka.clients.consumer.Consumer; -import org.apache.kafka.clients.consumer.ConsumerConfig; -import org.apache.kafka.clients.consumer.ConsumerRecord; -import org.apache.kafka.clients.consumer.ConsumerRecords; -import org.apache.kafka.clients.consumer.KafkaConsumer; -import org.apache.kafka.clients.consumer.OffsetAndMetadata; +import org.apache.kafka.clients.consumer.*; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.Producer; import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.common.TopicPartition; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + /** * @author Zhang Peng */ public class ProducerInTransaction { - private static final String HOST = "192.168.28.32:9092"; - - public static Producer buildProducer() { - // 1. 指定生产者的配置 - Properties properties = new Properties(); - properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, HOST); - properties.put(ProducerConfig.ACKS_CONFIG, "all"); - properties.put(ProducerConfig.RETRIES_CONFIG, 1); - properties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384); - properties.put(ProducerConfig.LINGER_MS_CONFIG, 1); - properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432); - properties.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG, "first-transactional"); - properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringSerializer"); - properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringSerializer"); - - // 2. 使用配置初始化 Kafka 生产者 - Producer producer = new KafkaProducer<>(properties); - return producer; - } - - public static Consumer buildConsumer() { - // 1. 指定消费者的配置 - final Properties props = new Properties(); - props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, HOST); - props.put(ConsumerConfig.GROUP_ID_CONFIG, "test"); - props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true"); - props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000"); - props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringDeserializer"); - props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringDeserializer"); - - // 2. 使用配置初始化 Kafka 消费者 - Consumer consumer = new KafkaConsumer<>(props); - return consumer; - } - - /** - * 在一个事务只有生产消息操作 - */ - public static void onlyProduceInTransaction() { - Producer producer = buildProducer(); - - // 1.初始化事务 - producer.initTransactions(); - - // 2.开启事务 - producer.beginTransaction(); - - try { - // 3.kafka写操作集合 - // 3.1 do业务逻辑 - - // 3.2 发送消息 - producer.send(new ProducerRecord("test", "transaction-data-1")); - producer.send(new ProducerRecord("test", "transaction-data-2")); - - // 3.3 do其他业务逻辑,还可以发送其他topic的消息。 - - // 4.事务提交 - producer.commitTransaction(); - } catch (Exception e) { - // 5.放弃事务 - producer.abortTransaction(); - } - - } - - /** - * 在一个事务内,即有生产消息又有消费消息 - */ - public static void consumeTransferProduce() { - // 1.构建上产者 - Producer producer = buildProducer(); - // 2.初始化事务(生成productId),对于一个生产者,只能执行一次初始化事务操作 - producer.initTransactions(); - - // 3.构建消费者和订阅主题 - Consumer consumer = buildConsumer(); - consumer.subscribe(Arrays.asList("test")); - while (true) { - // 4.开启事务 - producer.beginTransaction(); - - // 5.1 接受消息 - ConsumerRecords records = consumer.poll(500); - - try { - // 5.2 do业务逻辑; - System.out.println("customer Message---"); - Map commits = new HashMap<>(); - for (ConsumerRecord record : records) { - // 5.2.1 读取消息,并处理消息。print the offset,key and value for the consumer records. - System.out.printf("offset = %d, key = %s, value = %s\n", - record.offset(), record.key(), record.value()); - - // 5.2.2 记录提交的偏移量 - commits.put(new TopicPartition(record.topic(), record.partition()), - new OffsetAndMetadata(record.offset())); - - // 6.生产新的消息。比如外卖订单状态的消息,如果订单成功,则需要发送跟商家结转消息或者派送员的提成消息 - producer.send(new ProducerRecord("test", "data2")); - } - - // 7.提交偏移量 - producer.sendOffsetsToTransaction(commits, "group0323"); - - // 8.事务提交 - producer.commitTransaction(); - - } catch (Exception e) { - // 7.放弃事务 - producer.abortTransaction(); - } - } - } - - public static void main(String[] args) { - onlyProduceInTransaction(); - consumeTransferProduce(); - } + + private static final String HOST = "192.168.28.32:9092"; + + public static Producer buildProducer() { + // 1. 指定生产者的配置 + Properties properties = new Properties(); + properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, HOST); + properties.put(ProducerConfig.ACKS_CONFIG, "all"); + properties.put(ProducerConfig.RETRIES_CONFIG, 1); + properties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384); + properties.put(ProducerConfig.LINGER_MS_CONFIG, 1); + properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432); + properties.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG, "first-transactional"); + properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.StringSerializer"); + properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.StringSerializer"); + + // 2. 使用配置初始化 Kafka 生产者 + Producer producer = new KafkaProducer<>(properties); + return producer; + } + + public static Consumer buildConsumer() { + // 1. 指定消费者的配置 + final Properties props = new Properties(); + props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, HOST); + props.put(ConsumerConfig.GROUP_ID_CONFIG, "test"); + props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true"); + props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000"); + props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.StringDeserializer"); + props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.StringDeserializer"); + + // 2. 使用配置初始化 Kafka 消费者 + Consumer consumer = new KafkaConsumer<>(props); + return consumer; + } + + /** + * 在一个事务只有生产消息操作 + */ + public static void onlyProduceInTransaction() { + Producer producer = buildProducer(); + + // 1.初始化事务 + producer.initTransactions(); + + // 2.开启事务 + producer.beginTransaction(); + + try { + // 3.kafka写操作集合 + // 3.1 do业务逻辑 + + // 3.2 发送消息 + producer.send(new ProducerRecord("test", "transaction-data-1")); + producer.send(new ProducerRecord("test", "transaction-data-2")); + + // 3.3 do其他业务逻辑,还可以发送其他topic的消息。 + + // 4.事务提交 + producer.commitTransaction(); + } + catch (Exception e) { + // 5.放弃事务 + producer.abortTransaction(); + } + + } + + /** + * 在一个事务内,即有生产消息又有消费消息 + */ + public static void consumeTransferProduce() { + // 1.构建上产者 + Producer producer = buildProducer(); + // 2.初始化事务(生成productId),对于一个生产者,只能执行一次初始化事务操作 + producer.initTransactions(); + + // 3.构建消费者和订阅主题 + Consumer consumer = buildConsumer(); + consumer.subscribe(Arrays.asList("test")); + while (true) { + // 4.开启事务 + producer.beginTransaction(); + + // 5.1 接受消息 + ConsumerRecords records = consumer.poll(500); + + try { + // 5.2 do业务逻辑; + System.out.println("customer Message---"); + Map commits = new HashMap<>(); + for (ConsumerRecord record : records) { + // 5.2.1 读取消息,并处理消息。print the offset,key and value for the consumer + // records. + System.out.printf("offset = %d, key = %s, value = %s\n", record.offset(), record.key(), + record.value()); + + // 5.2.2 记录提交的偏移量 + commits.put(new TopicPartition(record.topic(), record.partition()), + new OffsetAndMetadata(record.offset())); + + // 6.生产新的消息。比如外卖订单状态的消息,如果订单成功,则需要发送跟商家结转消息或者派送员的提成消息 + producer.send(new ProducerRecord("test", "data2")); + } + + // 7.提交偏移量 + producer.sendOffsetsToTransaction(commits, "group0323"); + + // 8.事务提交 + producer.commitTransaction(); + + } + catch (Exception e) { + // 7.放弃事务 + producer.abortTransaction(); + } + } + } + + public static void main(String[] args) { + onlyProduceInTransaction(); + consumeTransferProduce(); + } + } diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/StreamDemo.java b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/StreamDemo.java index 0dc14e5b..202b12ca 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/StreamDemo.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/StreamDemo.java @@ -1,7 +1,5 @@ package io.github.dunwu.javaweb.kafka; -import java.util.Arrays; -import java.util.Properties; import org.apache.kafka.common.serialization.Serdes; import org.apache.kafka.common.utils.Bytes; import org.apache.kafka.streams.KafkaStreams; @@ -13,31 +11,36 @@ import org.apache.kafka.streams.kstream.Produced; import org.apache.kafka.streams.state.KeyValueStore; +import java.util.Arrays; +import java.util.Properties; + /** * Kafka 流示例 消费者配置参考:https://kafka.apache.org/documentation/#streamsconfigs */ public class StreamDemo { - private static final String HOST = "localhost:9092"; - - public static void main(String[] args) { - // 1. 指定流的配置 - Properties config = new Properties(); - config.put(StreamsConfig.APPLICATION_ID_CONFIG, "wordcount-application"); - config.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, HOST); - config.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass()); - config.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass()); - - // 设置流构造器 - StreamsBuilder builder = new StreamsBuilder(); - KStream textLines = builder.stream("TextLinesTopic"); - KTable wordCounts = textLines - .flatMapValues(textLine -> Arrays.asList(textLine.toLowerCase().split("\\W+"))) - .groupBy((key, word) -> word) - .count(Materialized.>as("counts-store")); - wordCounts.toStream().to("WordsWithCountsTopic", Produced.with(Serdes.String(), Serdes.Long())); - - // 根据流构造器和流配置初始化 Kafka 流 - KafkaStreams streams = new KafkaStreams(builder.build(), config); - streams.start(); - } + + private static final String HOST = "localhost:9092"; + + public static void main(String[] args) { + // 1. 指定流的配置 + Properties config = new Properties(); + config.put(StreamsConfig.APPLICATION_ID_CONFIG, "wordcount-application"); + config.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, HOST); + config.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass()); + config.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass()); + + // 设置流构造器 + StreamsBuilder builder = new StreamsBuilder(); + KStream textLines = builder.stream("TextLinesTopic"); + KTable wordCounts = textLines + .flatMapValues(textLine -> Arrays.asList(textLine.toLowerCase().split("\\W+"))) + .groupBy((key, word) -> word) + .count(Materialized.>as("counts-store")); + wordCounts.toStream().to("WordsWithCountsTopic", Produced.with(Serdes.String(), Serdes.Long())); + + // 根据流构造器和流配置初始化 Kafka 流 + KafkaStreams streams = new KafkaStreams(builder.build(), config); + streams.start(); + } + } diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/resources/logback.xml b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/resources/logback.xml index 45fa970a..2fc0985e 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/resources/logback.xml +++ b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/resources/logback.xml @@ -4,7 +4,7 @@ - + diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/pom.xml b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/pom.xml index 01ac0736..9952733a 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/pom.xml +++ b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/pom.xml @@ -1,6 +1,6 @@ - + 4.0.0 io.github.dunwu.javaweb diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/AsyncProducer.java b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/AsyncProducer.java index d2324131..1a71cc32 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/AsyncProducer.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/AsyncProducer.java @@ -7,40 +7,39 @@ import org.apache.rocketmq.remoting.common.RemotingHelper; /** - * 发送可靠的异步消息示例 - * 异步传输通常用于响应时间敏感的业务场景。 + * 发送可靠的异步消息示例 异步传输通常用于响应时间敏感的业务场景。 + * * @author Zhang Peng */ public class AsyncProducer { - public static void main(String[] args) throws Exception { - //Instantiate with a producer group name. - DefaultMQProducer producer = new DefaultMQProducer("ExampleProducerGroup"); - producer.setNamesrvAddr(RocketConfig.HOST); - //Launch the instance. - producer.start(); - producer.setRetryTimesWhenSendAsyncFailed(0); - for (int i = 0; i < 100; i++) { - final int index = i; - //Create a message instance, specifying topic, tag and message body. - Message msg = new Message("TopicTest", - "TagA", - "OrderID188", - "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET)); - producer.send(msg, new SendCallback() { - @Override - public void onSuccess(SendResult sendResult) { - System.out.printf("%-10d OK %s %n", index, - sendResult.getMsgId()); - } - @Override - public void onException(Throwable e) { - System.out.printf("%-10d Exception %s %n", index, e); - e.printStackTrace(); - } - }); - } - //Shut down once the producer instance is not longer in use. - producer.shutdown(); - } + public static void main(String[] args) throws Exception { + // Instantiate with a producer group name. + DefaultMQProducer producer = new DefaultMQProducer("ExampleProducerGroup"); + producer.setNamesrvAddr(RocketConfig.HOST); + // Launch the instance. + producer.start(); + producer.setRetryTimesWhenSendAsyncFailed(0); + for (int i = 0; i < 100; i++) { + final int index = i; + // Create a message instance, specifying topic, tag and message body. + Message msg = new Message("TopicTest", "TagA", "OrderID188", + "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET)); + producer.send(msg, new SendCallback() { + @Override + public void onSuccess(SendResult sendResult) { + System.out.printf("%-10d OK %s %n", index, sendResult.getMsgId()); + } + + @Override + public void onException(Throwable e) { + System.out.printf("%-10d Exception %s %n", index, e); + e.printStackTrace(); + } + }); + } + // Shut down once the producer instance is not longer in use. + producer.shutdown(); + } + } diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BatchProducer.java b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BatchProducer.java index cc16d015..7f022db5 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BatchProducer.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BatchProducer.java @@ -1,86 +1,97 @@ package io.github.dunwu.javaweb.rocketmq; +import org.apache.rocketmq.client.producer.DefaultMQProducer; +import org.apache.rocketmq.common.message.Message; + import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; -import org.apache.rocketmq.client.producer.DefaultMQProducer; -import org.apache.rocketmq.common.message.Message; /** * 批量发送消息 + * * @author Zhang Peng */ public class BatchProducer { - public static class ListSplitter implements Iterator> { - private final int SIZE_LIMIT = 1000 * 1000; - private final List messages; - private int currIndex; - - public ListSplitter(List messages) { - this.messages = messages; - } - - @Override - public boolean hasNext() { - return currIndex < messages.size(); - } - - @Override - public List next() { - int nextIndex = currIndex; - int totalSize = 0; - for (; nextIndex < messages.size(); nextIndex++) { - Message message = messages.get(nextIndex); - int tmpSize = message.getTopic().length() + message.getBody().length; - Map properties = message.getProperties(); - for (Map.Entry entry : properties.entrySet()) { - tmpSize += entry.getKey().length() + entry.getValue().length(); - } - tmpSize = tmpSize + 20; //for log overhead - if (tmpSize > SIZE_LIMIT) { - //it is unexpected that single message exceeds the SIZE_LIMIT - //here just let it go, otherwise it will block the splitting process - if (nextIndex - currIndex == 0) { - //if the next sublist has no element, add this one and then break, otherwise just break - nextIndex++; - } - break; - } - if (tmpSize + totalSize > SIZE_LIMIT) { - break; - } else { - totalSize += tmpSize; - } - - } - List subList = messages.subList(currIndex, nextIndex); - currIndex = nextIndex; - return subList; - } - } - - public static void main(String[] args) throws Exception { - //Instantiate with a producer group name. - DefaultMQProducer producer = new DefaultMQProducer("ExampleProducerGroup"); - producer.setNamesrvAddr(RocketConfig.HOST); - producer.start(); - producer.setRetryTimesWhenSendAsyncFailed(0); - - String topic = "BatchTest"; - List messages = new ArrayList<>(); - messages.add(new Message(topic, "TagA", "OrderID001", "Hello world 0".getBytes())); - messages.add(new Message(topic, "TagA", "OrderID002", "Hello world 1".getBytes())); - messages.add(new Message(topic, "TagA", "OrderID003", "Hello world 2".getBytes())); - //then you could split the large list into small ones: - ListSplitter splitter = new ListSplitter(messages); - - while (splitter.hasNext()) { - List listItem = splitter.next(); - producer.send(listItem); - } - - //Shut down once the producer instance is not longer in use. - producer.shutdown(); - } + + public static void main(String[] args) throws Exception { + // Instantiate with a producer group name. + DefaultMQProducer producer = new DefaultMQProducer("ExampleProducerGroup"); + producer.setNamesrvAddr(RocketConfig.HOST); + producer.start(); + producer.setRetryTimesWhenSendAsyncFailed(0); + + String topic = "BatchTest"; + List messages = new ArrayList<>(); + messages.add(new Message(topic, "TagA", "OrderID001", "Hello world 0".getBytes())); + messages.add(new Message(topic, "TagA", "OrderID002", "Hello world 1".getBytes())); + messages.add(new Message(topic, "TagA", "OrderID003", "Hello world 2".getBytes())); + // then you could split the large list into small ones: + ListSplitter splitter = new ListSplitter(messages); + + while (splitter.hasNext()) { + List listItem = splitter.next(); + producer.send(listItem); + } + + // Shut down once the producer instance is not longer in use. + producer.shutdown(); + } + + + public static class ListSplitter implements Iterator> { + + private final int SIZE_LIMIT = 1000 * 1000; + + private final List messages; + + private int currIndex; + + public ListSplitter(List messages) { + this.messages = messages; + } + + @Override + public boolean hasNext() { + return currIndex < messages.size(); + } + + @Override + public List next() { + int nextIndex = currIndex; + int totalSize = 0; + for (; nextIndex < messages.size(); nextIndex++) { + Message message = messages.get(nextIndex); + int tmpSize = message.getTopic().length() + message.getBody().length; + Map properties = message.getProperties(); + for (Map.Entry entry : properties.entrySet()) { + tmpSize += entry.getKey().length() + entry.getValue().length(); + } + tmpSize = tmpSize + 20; // for log overhead + if (tmpSize > SIZE_LIMIT) { + // it is unexpected that single message exceeds the SIZE_LIMIT + // here just let it go, otherwise it will block the splitting process + if (nextIndex - currIndex == 0) { + // if the next sublist has no element, add this one and then + // break, otherwise just break + nextIndex++; + } + break; + } + if (tmpSize + totalSize > SIZE_LIMIT) { + break; + } + else { + totalSize += tmpSize; + } + + } + List subList = messages.subList(currIndex, nextIndex); + currIndex = nextIndex; + return subList; + } + + } + } diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastConsumer.java b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastConsumer.java index 2370a3f8..54f4929a 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastConsumer.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastConsumer.java @@ -1,6 +1,5 @@ package io.github.dunwu.javaweb.rocketmq; -import java.util.List; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; @@ -9,33 +8,37 @@ import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; +import java.util.List; + /** * 接收广播消息示例 + * * @author Zhang Peng */ public class BroadcastConsumer { - public static void main(String[] args) throws Exception { - DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("example_group_name"); - consumer.setNamesrvAddr(RocketConfig.HOST); - consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); + public static void main(String[] args) throws Exception { + DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("example_group_name"); + consumer.setNamesrvAddr(RocketConfig.HOST); + + consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); + + // set to broadcast mode + consumer.setMessageModel(MessageModel.BROADCASTING); - //set to broadcast mode - consumer.setMessageModel(MessageModel.BROADCASTING); + consumer.subscribe("TopicTest", "TagA || TagC || TagD"); - consumer.subscribe("TopicTest", "TagA || TagC || TagD"); + consumer.registerMessageListener(new MessageListenerConcurrently() { - consumer.registerMessageListener(new MessageListenerConcurrently() { + @Override + public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { + System.out.printf(Thread.currentThread().getName() + " Receive New Messages: " + msgs + "%n"); + return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; + } + }); - @Override - public ConsumeConcurrentlyStatus consumeMessage(List msgs, - ConsumeConcurrentlyContext context) { - System.out.printf(Thread.currentThread().getName() + " Receive New Messages: " + msgs + "%n"); - return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; - } - }); + consumer.start(); + System.out.printf("Broadcast Consumer Started.%n"); + } - consumer.start(); - System.out.printf("Broadcast Consumer Started.%n"); - } } diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastProducer.java b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastProducer.java index 7f2b70ac..e209e8c7 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastProducer.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastProducer.java @@ -7,22 +7,23 @@ /** * 发送广播消息示例 + * * @author Zhang Peng */ public class BroadcastProducer { - public static void main(String[] args) throws Exception { - DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName"); - producer.setNamesrvAddr(RocketConfig.HOST); - producer.start(); - for (int i = 0; i < 100; i++) { - Message msg = new Message("TopicTest", - "TagA", - "OrderID188", - "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET)); - SendResult sendResult = producer.send(msg); - System.out.printf("%s%n", sendResult); - } - producer.shutdown(); - } + public static void main(String[] args) throws Exception { + DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName"); + producer.setNamesrvAddr(RocketConfig.HOST); + producer.start(); + + for (int i = 0; i < 100; i++) { + Message msg = new Message("TopicTest", "TagA", "OrderID188", + "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET)); + SendResult sendResult = producer.send(msg); + System.out.printf("%s%n", sendResult); + } + producer.shutdown(); + } + } diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OnewayProducer.java b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OnewayProducer.java index a5293466..99934b58 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OnewayProducer.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OnewayProducer.java @@ -5,29 +5,29 @@ import org.apache.rocketmq.remoting.common.RemotingHelper; /** - * 单向传输示例 - * 单向传输用于需要中等可靠性的情况,例如日志收集。 + * 单向传输示例 单向传输用于需要中等可靠性的情况,例如日志收集。 + * * @author Zhang Peng */ public class OnewayProducer { - public static void main(String[] args) throws Exception { - //Instantiate with a producer group name. - DefaultMQProducer producer = new DefaultMQProducer("ExampleProducerGroup"); - producer.setNamesrvAddr(RocketConfig.HOST); - //Launch the instance. - producer.start(); - for (int i = 0; i < 100; i++) { - //Create a message instance, specifying topic, tag and message body. - Message msg = new Message("TopicTest" /* Topic */, - "TagA" /* Tag */, - ("Hello RocketMQ " + - i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */ - ); - //Call send message to deliver message to one of brokers. - producer.sendOneway(msg); - } - //Shut down once the producer instance is not longer in use. - producer.shutdown(); - } + public static void main(String[] args) throws Exception { + // Instantiate with a producer group name. + DefaultMQProducer producer = new DefaultMQProducer("ExampleProducerGroup"); + producer.setNamesrvAddr(RocketConfig.HOST); + // Launch the instance. + producer.start(); + for (int i = 0; i < 100; i++) { + // Create a message instance, specifying topic, tag and message body. + Message msg = new Message("TopicTest" /* Topic */, "TagA" /* Tag */, ("Hello RocketMQ " + i) + .getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */ + ); + // Call send message to deliver message to one of brokers. + producer.sendOneway(msg); + + } + // Shut down once the producer instance is not longer in use. + producer.shutdown(); + } + } diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedConsumer.java b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedConsumer.java index 24938e73..b646330a 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedConsumer.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedConsumer.java @@ -1,7 +1,5 @@ package io.github.dunwu.javaweb.rocketmq; -import java.util.List; -import java.util.concurrent.atomic.AtomicLong; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus; @@ -9,46 +7,54 @@ import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.MessageExt; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + /** * 接收全局和分区排序的消息。 + * * @author Zhang Peng */ public class OrderedConsumer { - public static void main(String[] args) throws Exception { - DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("example_group_name"); - consumer.setNamesrvAddr(RocketConfig.HOST); - - consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); - - consumer.subscribe("TopicTest", "TagA || TagC || TagD"); - - consumer.registerMessageListener(new MessageListenerOrderly() { - - AtomicLong consumeTimes = new AtomicLong(0); - - @Override - public ConsumeOrderlyStatus consumeMessage(List msgs, - ConsumeOrderlyContext context) { - context.setAutoCommit(false); - System.out.printf(Thread.currentThread().getName() + " Receive New Messages: " + msgs + "%n"); - this.consumeTimes.incrementAndGet(); - if ((this.consumeTimes.get() % 2) == 0) { - return ConsumeOrderlyStatus.SUCCESS; - } else if ((this.consumeTimes.get() % 3) == 0) { - return ConsumeOrderlyStatus.ROLLBACK; - } else if ((this.consumeTimes.get() % 4) == 0) { - return ConsumeOrderlyStatus.COMMIT; - } else if ((this.consumeTimes.get() % 5) == 0) { - context.setSuspendCurrentQueueTimeMillis(3000); - return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT; - } - return ConsumeOrderlyStatus.SUCCESS; - - } - }); - - consumer.start(); - - System.out.printf("Consumer Started.%n"); - } + + public static void main(String[] args) throws Exception { + DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("example_group_name"); + consumer.setNamesrvAddr(RocketConfig.HOST); + + consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); + + consumer.subscribe("TopicTest", "TagA || TagC || TagD"); + + consumer.registerMessageListener(new MessageListenerOrderly() { + + AtomicLong consumeTimes = new AtomicLong(0); + + @Override + public ConsumeOrderlyStatus consumeMessage(List msgs, ConsumeOrderlyContext context) { + context.setAutoCommit(false); + System.out.printf(Thread.currentThread().getName() + " Receive New Messages: " + msgs + "%n"); + this.consumeTimes.incrementAndGet(); + if ((this.consumeTimes.get() % 2) == 0) { + return ConsumeOrderlyStatus.SUCCESS; + } + else if ((this.consumeTimes.get() % 3) == 0) { + return ConsumeOrderlyStatus.ROLLBACK; + } + else if ((this.consumeTimes.get() % 4) == 0) { + return ConsumeOrderlyStatus.COMMIT; + } + else if ((this.consumeTimes.get() % 5) == 0) { + context.setSuspendCurrentQueueTimeMillis(3000); + return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT; + } + return ConsumeOrderlyStatus.SUCCESS; + + } + }); + + consumer.start(); + + System.out.printf("Consumer Started.%n"); + } + } diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedProducer.java b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedProducer.java index 8e2f36b2..bcb3d12c 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedProducer.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedProducer.java @@ -1,6 +1,5 @@ package io.github.dunwu.javaweb.rocketmq; -import java.util.List; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.MQProducer; import org.apache.rocketmq.client.producer.MessageQueueSelector; @@ -9,35 +8,40 @@ import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.common.RemotingHelper; +import java.util.List; + /** * 发送全局和分区排序的消息。 + * * @author Zhang Peng */ public class OrderedProducer { - public static void main(String[] args) throws Exception { - //Instantiate with a producer group name. - MQProducer producer = new DefaultMQProducer("example_group_name"); - ((DefaultMQProducer) producer).setNamesrvAddr(RocketConfig.HOST); - //Launch the instance. - producer.start(); - String[] tags = new String[]{"TagA", "TagB", "TagC", "TagD", "TagE"}; - for (int i = 0; i < 100; i++) { - int orderId = i % 10; - //Create a message instance, specifying topic, tag and message body. - Message msg = new Message("TopicTestjjj", tags[i % tags.length], "KEY" + i, - ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET)); - SendResult sendResult = producer.send(msg, new MessageQueueSelector() { - @Override - public MessageQueue select(List mqs, Message msg, Object arg) { - Integer id = (Integer) arg; - int index = id % mqs.size(); - return mqs.get(index); - } - }, orderId); - System.out.printf("%s%n", sendResult); - } - //server shutdown - producer.shutdown(); - } + public static void main(String[] args) throws Exception { + // Instantiate with a producer group name. + MQProducer producer = new DefaultMQProducer("example_group_name"); + ((DefaultMQProducer) producer).setNamesrvAddr(RocketConfig.HOST); + // Launch the instance. + producer.start(); + String[] tags = new String[] { "TagA", "TagB", "TagC", "TagD", "TagE" }; + for (int i = 0; i < 100; i++) { + int orderId = i % 10; + // Create a message instance, specifying topic, tag and message body. + Message msg = new Message("TopicTestjjj", tags[i % tags.length], "KEY" + i, + ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET)); + SendResult sendResult = producer.send(msg, new MessageQueueSelector() { + @Override + public MessageQueue select(List mqs, Message msg, Object arg) { + Integer id = (Integer) arg; + int index = id % mqs.size(); + return mqs.get(index); + } + }, orderId); + + System.out.printf("%s%n", sendResult); + } + // server shutdown + producer.shutdown(); + } + } diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/RocketConfig.java b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/RocketConfig.java index ab58601b..3c9fa344 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/RocketConfig.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/RocketConfig.java @@ -4,5 +4,7 @@ * @author Zhang Peng */ public class RocketConfig { - public static final String HOST = "192.168.28.32:9876"; + + public static final String HOST = "192.168.28.32:9876"; + } diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageConsumer.java b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageConsumer.java index 00f3bc45..c0462f93 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageConsumer.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageConsumer.java @@ -1,36 +1,40 @@ package io.github.dunwu.javaweb.rocketmq; -import java.util.List; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.common.message.MessageExt; +import java.util.List; + /** * 接收定时消息 + * * @author Zhang Peng */ public class ScheduledMessageConsumer { - public static void main(String[] args) throws Exception { - // Instantiate message consumer - DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("ExampleConsumer"); - // Subscribe topics - consumer.subscribe("TestTopic", "*"); - // Register message listener - consumer.registerMessageListener(new MessageListenerConcurrently() { - @Override - public ConsumeConcurrentlyStatus consumeMessage(List messages, - ConsumeConcurrentlyContext context) { - for (MessageExt message : messages) { - // Print approximate delay time period - System.out.println("Receive message[msgId=" + message.getMsgId() + "] " - + (System.currentTimeMillis() - message.getStoreTimestamp()) + "ms later"); - } - return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; - } - }); - // Launch consumer - consumer.start(); - } + + public static void main(String[] args) throws Exception { + // Instantiate message consumer + DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("ExampleConsumer"); + // Subscribe topics + consumer.subscribe("TestTopic", "*"); + // Register message listener + consumer.registerMessageListener(new MessageListenerConcurrently() { + @Override + public ConsumeConcurrentlyStatus consumeMessage(List messages, + ConsumeConcurrentlyContext context) { + for (MessageExt message : messages) { + // Print approximate delay time period + System.out.println("Receive message[msgId=" + message.getMsgId() + "] " + + (System.currentTimeMillis() - message.getStoreTimestamp()) + "ms later"); + } + return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; + } + }); + // Launch consumer + consumer.start(); + } + } diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageProducer.java b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageProducer.java index a8426342..83b53096 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageProducer.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageProducer.java @@ -5,24 +5,27 @@ /** * 发送定时消息 + * * @author Zhang Peng */ public class ScheduledMessageProducer { - public static void main(String[] args) throws Exception { - // Instantiate a producer to send scheduled messages - DefaultMQProducer producer = new DefaultMQProducer("ExampleProducerGroup"); - // Launch producer - producer.start(); - int totalMessagesToSend = 100; - for (int i = 0; i < totalMessagesToSend; i++) { - Message message = new Message("TestTopic", ("Hello scheduled message " + i).getBytes()); - // This message will be delivered to consumer 10 seconds later. - message.setDelayTimeLevel(3); - // Send the message - producer.send(message); - } - // Shutdown producer after use. - producer.shutdown(); - } + public static void main(String[] args) throws Exception { + // Instantiate a producer to send scheduled messages + DefaultMQProducer producer = new DefaultMQProducer("ExampleProducerGroup"); + // Launch producer + producer.start(); + int totalMessagesToSend = 100; + for (int i = 0; i < totalMessagesToSend; i++) { + Message message = new Message("TestTopic", ("Hello scheduled message " + i).getBytes()); + // This message will be delivered to consumer 10 seconds later. + message.setDelayTimeLevel(3); + // Send the message + producer.send(message); + } + + // Shutdown producer after use. + producer.shutdown(); + } + } diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/SyncProducer.java b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/SyncProducer.java index daf18f20..4614ed25 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/SyncProducer.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/SyncProducer.java @@ -6,30 +6,29 @@ import org.apache.rocketmq.remoting.common.RemotingHelper; /** - * 发送可靠的同步消息示例 - * 可靠的同步传输用于广泛的场景,如重要的通知消息,短信通知,短信营销系统等。 + * 发送可靠的同步消息示例 可靠的同步传输用于广泛的场景,如重要的通知消息,短信通知,短信营销系统等。 + * * @author Zhang Peng */ public class SyncProducer { - public static void main(String[] args) throws Exception { - //Instantiate with a producer group name. - DefaultMQProducer producer = new - DefaultMQProducer("please_rename_unique_group_name"); - producer.setNamesrvAddr(RocketConfig.HOST); - //Launch the instance. - producer.start(); - for (int i = 0; i < 100; i++) { - //Create a message instance, specifying topic, tag and message body. - Message msg = new Message("TopicTest" /* Topic */, - "TagA" /* Tag */, - ("Hello RocketMQ " + - i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */ - ); - //Call send message to deliver message to one of brokers. - SendResult sendResult = producer.send(msg); - System.out.printf("%s%n", sendResult); - } - //Shut down once the producer instance is not longer in use. - producer.shutdown(); - } + + public static void main(String[] args) throws Exception { + // Instantiate with a producer group name. + DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name"); + producer.setNamesrvAddr(RocketConfig.HOST); + // Launch the instance. + producer.start(); + for (int i = 0; i < 100; i++) { + // Create a message instance, specifying topic, tag and message body. + Message msg = new Message("TopicTest" /* Topic */, "TagA" /* Tag */, ("Hello RocketMQ " + i) + .getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */ + ); + // Call send message to deliver message to one of brokers. + SendResult sendResult = producer.send(msg); + System.out.printf("%s%n", sendResult); + } + // Shut down once the producer instance is not longer in use. + producer.shutdown(); + } + } diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/resources/logback.xml b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/resources/logback.xml index 28bbd739..7cac2011 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/resources/logback.xml +++ b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/resources/logback.xml @@ -4,7 +4,7 @@ - + diff --git a/codes/javaweb/javaweb-mq/pom.xml b/codes/javaweb/javaweb-mq/pom.xml index ed5e542f..2159378f 100644 --- a/codes/javaweb/javaweb-mq/pom.xml +++ b/codes/javaweb/javaweb-mq/pom.xml @@ -1,5 +1,5 @@ - 4.0.0 diff --git a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/pom.xml b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/pom.xml index 0b32e2d5..ffc18644 100644 --- a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/pom.xml +++ b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/pom.xml @@ -1,6 +1,6 @@ - + 4.0.0 io.github.dunwu.javaweb diff --git a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKConnection.java b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKConnection.java index 26a2bae0..b13802b1 100644 --- a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKConnection.java +++ b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKConnection.java @@ -1,48 +1,50 @@ package io.github.dunwu.javaweb.zookeeper; -import java.io.IOException; -import java.util.concurrent.CountDownLatch; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.Watcher.Event.KeeperState; import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.ZooKeeper.States; +import java.io.IOException; +import java.util.concurrent.CountDownLatch; + /** * ZooKeeper 连接示例 */ public class ZKConnection { - // declare zookeeper instance to access ZooKeeper ensemble - private ZooKeeper zoo; - private static final String HOST = "localhost"; - final CountDownLatch connectedSignal = new CountDownLatch(1); - - // Method to connect zookeeper ensemble. - public ZooKeeper connect(String host) throws IOException, InterruptedException { - - zoo = new ZooKeeper(host, 5000, new Watcher() { - public void process(WatchedEvent we) { - if (we.getState() == KeeperState.SyncConnected) { - connectedSignal.countDown(); - } - } - }); - - connectedSignal.await(); - return zoo; - } - - // Method to disconnect from zookeeper server - public void close() throws InterruptedException { - zoo.close(); - } - - public static void main(String[] args) throws IOException, InterruptedException { - ZKConnection zkConnection = new ZKConnection(); - ZooKeeper zk = zkConnection.connect(HOST); - States state = zk.getState(); - System.out.println("ZooKeeper isAlive:" + state.isAlive()); - zk.close(); - } + private static final String HOST = "localhost"; + final CountDownLatch connectedSignal = new CountDownLatch(1); + // declare zookeeper instance to access ZooKeeper ensemble + private ZooKeeper zoo; + + public static void main(String[] args) throws IOException, InterruptedException { + ZKConnection zkConnection = new ZKConnection(); + ZooKeeper zk = zkConnection.connect(HOST); + States state = zk.getState(); + System.out.println("ZooKeeper isAlive:" + state.isAlive()); + zk.close(); + } + + // Method to connect zookeeper ensemble. + public ZooKeeper connect(String host) throws IOException, InterruptedException { + + zoo = new ZooKeeper(host, 5000, new Watcher() { + public void process(WatchedEvent we) { + if (we.getState() == KeeperState.SyncConnected) { + connectedSignal.countDown(); + } + } + }); + + connectedSignal.await(); + return zoo; + } + + // Method to disconnect from zookeeper server + public void close() throws InterruptedException { + zoo.close(); + } + } diff --git a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKCreate.java b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKCreate.java index 55a74d77..064c15b7 100644 --- a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKCreate.java +++ b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKCreate.java @@ -9,38 +9,39 @@ * ZooKeeper 添加 Znode 示例 */ public class ZKCreate { - // create static instance for zookeeper class. - private static ZooKeeper zk; - - // create static instance for ZooKeeperConnection class. - private static ZKConnection conn; - - private static final String HOST = "localhost"; - - // Method to create znode in zookeeper ensemble - public static void create(String path, byte[] data) throws - KeeperException, InterruptedException { - zk.create(path, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); - } - - public static void main(String[] args) throws InterruptedException { - - // znode path - String path = "/MyFirstZnode"; // Assign path to znode - - // data in byte array - byte[] data = "My first zookeeper app".getBytes(); // Declare data - - try { - conn = new ZKConnection(); - zk = conn.connect(HOST); - create(path, data); // Create the data to the specified path - } catch (Exception e) { - System.out.println(e.getMessage()); //Catch error message - } finally { - if (conn != null) { - conn.close(); - } - } - } + + private static final String HOST = "localhost"; + // create static instance for zookeeper class. + private static ZooKeeper zk; + // create static instance for ZooKeeperConnection class. + private static ZKConnection conn; + + // Method to create znode in zookeeper ensemble + public static void create(String path, byte[] data) throws KeeperException, InterruptedException { + zk.create(path, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + } + + public static void main(String[] args) throws InterruptedException { + + // znode path + String path = "/MyFirstZnode"; // Assign path to znode + + // data in byte array + byte[] data = "My first zookeeper app".getBytes(); // Declare data + + try { + conn = new ZKConnection(); + zk = conn.connect(HOST); + create(path, data); // Create the data to the specified path + } + catch (Exception e) { + System.out.println(e.getMessage()); // Catch error message + } + finally { + if (conn != null) { + conn.close(); + } + } + } + } diff --git a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKDelete.java b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKDelete.java index cce6e62e..f3f0daac 100644 --- a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKDelete.java +++ b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKDelete.java @@ -4,28 +4,32 @@ import org.apache.zookeeper.ZooKeeper; public class ZKDelete { - private static ZooKeeper zk; - private static ZKConnection conn; - private static final String HOST = "localhost"; - // Method to check existence of znode and its status, if znode is available. - public static void delete(String path) throws KeeperException, InterruptedException { - zk.delete(path, zk.exists(path, true).getVersion()); - } + private static final String HOST = "localhost"; + private static ZooKeeper zk; + private static ZKConnection conn; - public static void main(String[] args) throws InterruptedException, KeeperException { - String path = "/MyFirstZnode"; //Assign path to the znode + // Method to check existence of znode and its status, if znode is available. + public static void delete(String path) throws KeeperException, InterruptedException { + zk.delete(path, zk.exists(path, true).getVersion()); + } + + public static void main(String[] args) throws InterruptedException, KeeperException { + String path = "/MyFirstZnode"; // Assign path to the znode + + try { + conn = new ZKConnection(); + zk = conn.connect(HOST); + delete(path); // delete the node with the specified path + } + catch (Exception e) { + System.out.println(e.getMessage()); // catches error messages + } + finally { + if (conn != null) { + conn.close(); + } + } + } - try { - conn = new ZKConnection(); - zk = conn.connect(HOST); - delete(path); //delete the node with the specified path - } catch (Exception e) { - System.out.println(e.getMessage()); // catches error messages - } finally { - if (conn != null) { - conn.close(); - } - } - } } diff --git a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKExists.java b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKExists.java index d0afba0b..3d52c9a7 100644 --- a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKExists.java +++ b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKExists.java @@ -5,33 +5,35 @@ import org.apache.zookeeper.data.Stat; public class ZKExists { - private static ZooKeeper zk; - private static ZKConnection conn; - private static final String HOST = "localhost"; - - // Method to check existence of znode and its status, if znode is available. - public static Stat znode_exists(String path) throws - KeeperException, InterruptedException { - return zk.exists(path, true); - } - - public static void main(String[] args) throws InterruptedException, KeeperException { - String path = "/MyFirstZnode"; // Assign znode to the specified path - - try { - conn = new ZKConnection(); - zk = conn.connect(HOST); - Stat stat = znode_exists(path); // Stat checks the path of the znode - - if (stat != null) { - System.out.println("Node exists and the node version is " + - stat.getVersion()); - } else { - System.out.println("Node does not exists"); - } - - } catch (Exception e) { - System.out.println(e.getMessage()); // Catches error messages - } - } + + private static final String HOST = "localhost"; + private static ZooKeeper zk; + private static ZKConnection conn; + + // Method to check existence of znode and its status, if znode is available. + public static Stat znode_exists(String path) throws KeeperException, InterruptedException { + return zk.exists(path, true); + } + + public static void main(String[] args) throws InterruptedException, KeeperException { + String path = "/MyFirstZnode"; // Assign znode to the specified path + + try { + conn = new ZKConnection(); + zk = conn.connect(HOST); + Stat stat = znode_exists(path); // Stat checks the path of the znode + + if (stat != null) { + System.out.println("Node exists and the node version is " + stat.getVersion()); + } + else { + System.out.println("Node does not exists"); + } + + } + catch (Exception e) { + System.out.println(e.getMessage()); // Catches error messages + } + } + } diff --git a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKGetChildren.java b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKGetChildren.java index 34f57393..44d26dd6 100644 --- a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKGetChildren.java +++ b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKGetChildren.java @@ -1,44 +1,50 @@ package io.github.dunwu.javaweb.zookeeper; -import java.util.List; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.data.Stat; +import java.util.List; + public class ZKGetChildren { - private static ZooKeeper zk; - private static ZKConnection conn; - private static final String HOST = "localhost"; - - // Method to check existence of znode and its status, if znode is available. - public static Stat znode_exists(String path) throws - KeeperException, InterruptedException { - return zk.exists(path, true); - } - - public static void main(String[] args) throws InterruptedException, KeeperException { - String path = "/MyFirstZnode"; // Assign path to the znode - - try { - conn = new ZKConnection(); - zk = conn.connect(HOST); - Stat stat = znode_exists(path); // Stat checks the path - - if (stat != null) { - // getChildren method - get all the children of znode.It has two args, path and watch - List children = zk.getChildren(path, false); - for (int i = 0; i < children.size(); i++) { - System.out.println(children.get(i)); //Print children's - } - } else { - System.out.println("Node does not exists"); - } - } catch (Exception e) { - System.out.println(e.getMessage()); - } finally { - if (conn != null) { - conn.close(); - } - } - } + + private static final String HOST = "localhost"; + private static ZooKeeper zk; + private static ZKConnection conn; + + // Method to check existence of znode and its status, if znode is available. + public static Stat znode_exists(String path) throws KeeperException, InterruptedException { + return zk.exists(path, true); + } + + public static void main(String[] args) throws InterruptedException, KeeperException { + String path = "/MyFirstZnode"; // Assign path to the znode + + try { + conn = new ZKConnection(); + zk = conn.connect(HOST); + Stat stat = znode_exists(path); // Stat checks the path + + if (stat != null) { + // getChildren method - get all the children of znode.It has two args, + // path and watch + List children = zk.getChildren(path, false); + for (int i = 0; i < children.size(); i++) { + System.out.println(children.get(i)); // Print children's + } + } + else { + System.out.println("Node does not exists"); + } + } + catch (Exception e) { + System.out.println(e.getMessage()); + } + finally { + if (conn != null) { + conn.close(); + } + } + } + } diff --git a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKGetData.java b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKGetData.java index cee2780a..8f8abe64 100644 --- a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKGetData.java +++ b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKGetData.java @@ -1,74 +1,78 @@ package io.github.dunwu.javaweb.zookeeper; -import java.util.concurrent.CountDownLatch; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.data.Stat; +import java.util.concurrent.CountDownLatch; + public class ZKGetData { - private static ZooKeeper zk; - private static ZKConnection conn; - private static final String HOST = "localhost"; + private static final String HOST = "localhost"; + private static ZooKeeper zk; + private static ZKConnection conn; + + public static Stat existsZnode(String path) throws KeeperException, InterruptedException { + return zk.exists(path, true); + } - public static Stat existsZnode(String path) throws - KeeperException, InterruptedException { - return zk.exists(path, true); - } + public static void main(String[] args) throws InterruptedException { + String path = "/MyFirstZnode"; + final CountDownLatch connectedSignal = new CountDownLatch(1); - public static void main(String[] args) throws InterruptedException { - String path = "/MyFirstZnode"; - final CountDownLatch connectedSignal = new CountDownLatch(1); + try { + conn = new ZKConnection(); + zk = conn.connect(HOST); + Stat stat = existsZnode(path); - try { - conn = new ZKConnection(); - zk = conn.connect(HOST); - Stat stat = existsZnode(path); + if (stat != null) { + byte[] b = zk.getData(path, new Watcher() { + public void process(WatchedEvent we) { - if (stat != null) { - byte[] b = zk.getData(path, new Watcher() { - public void process(WatchedEvent we) { + if (we.getType() == Event.EventType.None) { + switch (we.getState()) { + case Expired: + connectedSignal.countDown(); + break; + } - if (we.getType() == Event.EventType.None) { - switch (we.getState()) { - case Expired: - connectedSignal.countDown(); - break; - } + } + else { + String path = "/MyFirstZnode"; - } else { - String path = "/MyFirstZnode"; + try { + byte[] bn = zk.getData(path, false, null); + String data = new String(bn, "UTF-8"); + System.out.println(data); + connectedSignal.countDown(); - try { - byte[] bn = zk.getData(path, - false, null); - String data = new String(bn, - "UTF-8"); - System.out.println(data); - connectedSignal.countDown(); + } + catch (Exception ex) { + System.out.println(ex.getMessage()); + } + } + } + }, null); - } catch (Exception ex) { - System.out.println(ex.getMessage()); - } - } - } - }, null); + String data = new String(b, "UTF-8"); + System.out.println(data); + connectedSignal.await(); - String data = new String(b, "UTF-8"); - System.out.println(data); - connectedSignal.await(); + } + else { + System.out.println("Node does not exists"); + } + } + catch (Exception e) { + System.out.println(e.getMessage()); + } + finally { + if (conn != null) { + conn.close(); + } + } + } - } else { - System.out.println("Node does not exists"); - } - } catch (Exception e) { - System.out.println(e.getMessage()); - } finally { - if (conn != null) { - conn.close(); - } - } - } } diff --git a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKSetData.java b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKSetData.java index d9c81aff..b85519ae 100644 --- a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKSetData.java +++ b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKSetData.java @@ -4,31 +4,33 @@ import org.apache.zookeeper.ZooKeeper; public class ZKSetData { - private static ZooKeeper zk; - private static ZKConnection conn; - private static final String HOST = "localhost"; + private static final String HOST = "localhost"; + private static ZooKeeper zk; + private static ZKConnection conn; - // Method to update the data in a znode. Similar to getData but without watcher. - public static void update(String path, byte[] data) throws - KeeperException, InterruptedException { - zk.setData(path, data, zk.exists(path, true).getVersion()); - } + // Method to update the data in a znode. Similar to getData but without watcher. + public static void update(String path, byte[] data) throws KeeperException, InterruptedException { + zk.setData(path, data, zk.exists(path, true).getVersion()); + } - public static void main(String[] args) throws InterruptedException { - String path = "/MyFirstZnode"; - byte[] data = "Success".getBytes(); //Assign data which is to be updated. + public static void main(String[] args) throws InterruptedException { + String path = "/MyFirstZnode"; + byte[] data = "Success".getBytes(); // Assign data which is to be updated. + + try { + conn = new ZKConnection(); + zk = conn.connect(HOST); + update(path, data); // Update znode data to the specified path + } + catch (Exception e) { + System.out.println(e.getMessage()); + } + finally { + if (conn != null) { + conn.close(); + } + } + } - try { - conn = new ZKConnection(); - zk = conn.connect(HOST); - update(path, data); // Update znode data to the specified path - } catch (Exception e) { - System.out.println(e.getMessage()); - } finally { - if (conn != null) { - conn.close(); - } - } - } } diff --git a/codes/javaweb/javaweb-rpc/pom.xml b/codes/javaweb/javaweb-rpc/pom.xml index d98845bf..e7680ca1 100644 --- a/codes/javaweb/javaweb-rpc/pom.xml +++ b/codes/javaweb/javaweb-rpc/pom.xml @@ -1,5 +1,5 @@ - 4.0.0 diff --git a/codes/javaweb/pom.xml b/codes/javaweb/pom.xml index 434ef8ba..fb02a9c0 100644 --- a/codes/javaweb/pom.xml +++ b/codes/javaweb/pom.xml @@ -1,5 +1,5 @@ - 4.0.0 diff --git a/docs/assets/cache.eddx b/docs/assets/cache.eddx index b1ee7cc30ba2ae2ba6196460ebef83aa87fd3f5d..7d5bc6b753fb215bb61818d9fd9b725d818de62e 100644 GIT binary patch delta 51455 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 - - - JavaWeb - - - - - - - + - + - + - + - - -

    - - - - - + } + + + +
    正在加载...
    + + + + + - - - - - - + + + + + + diff --git a/docs/technology/cache/cache-theory.md b/docs/technology/cache/cache-theory.md index 70fa7f2a..95b34949 100644 --- a/docs/technology/cache/cache-theory.md +++ b/docs/technology/cache/cache-theory.md @@ -359,54 +359,71 @@ Memcached 服务器之间彼此不通信,它的分布式能力是依赖客户 ## 6. 多级缓存 -很多人一想到缓存马上脑子里面就会出现下面的图: +### 6.1. 使用进程内缓存 -![img](https://user-gold-cdn.xitu.io/2018/8/21/1655c597d9001318?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) +如果应用服务不是分布式系统,那么进程内缓存当然是缓存的首选方案。 -Redis 用来存储热点数据,Redis 中没有的数据则直接去数据库访问。 +对于进程内缓存,其本来受限于内存的大小的限制,以及进程缓存更新后其他缓存无法得知,所以一般来说进程缓存适用于: -在之前介绍本地缓存的时候,很多人都问我,我已经有 Redis 了,我干嘛还需要了解 Guava,Caffeine 这些进程缓存呢。我基本统一回复下面两个答案: +1. 数据量不是很大且更新频率较低的数据。 +2. 如果更新频繁的数据,也想使用进程内缓存,那么可以将其过期时间设置为较短的时间,或者设置较短的自动刷新时间。 -1. Redis 如果挂了或者使用老版本的 Redis,其会进行全量同步,此时 Redis 是不可用的,这个时候我们只能访问数据库,很容易造成雪崩。 -2. 访问 Redis 会有一定的网络 I/O 以及序列化反序列化,虽然性能很高但是其终究没有本地方法快,可以将最热的数据存放在本地,以便进一步加快访问速度。这个思路并不是我们做互联网架构独有的,在计算机系统中使用 L1,L2,L3 多级缓存,用来减少对内存的直接访问,从而加快访问速度。 +这种方案存在以下问题: -![img](https://user-gold-cdn.xitu.io/2018/7/30/164e946ff882653a?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) +- 如果应用服务是分布式系统,应用节点之间无法共享缓存,存在数据不一致问题。 +- 由于进程内缓存受限于内存大小的限制,所以缓存不能无限扩展。 -所以如果仅仅是使用 Redis,能满足我们大部分需求,但是当需要追求更高的性能以及更高的可用性的时候,那就不得不了解多级缓存。 +### 使用分布式缓存 -### 6.1. 使用进程缓存 +如果应用服务是分布式系统,那么最简单的缓存方案就是直接使用分布式缓存。 + +其应用场景如图所示: + +
    + +Redis 用来存储热点数据,如果缓存不命中,则去查询数据库,并更新缓存。 + +这种方案存在以下问题: + +1. 缓存服务如果挂了,这时应用只能访问数据库,容易造成缓存雪崩。 +2. 访问分布式缓存服务会有一定的 I/O 以及序列化反序列化的开销,虽然性能很高,但是其终究没有在内存中查询快。 -对于进程内缓存,其本来受限于内存的大小的限制,以及进程缓存更新后其他缓存无法得知,所以一般来说进程缓存适用于: -1. 数据量不是很大,数据更新频率较低,之前我们有个查询商家名字的服务,在发送短信的时候需要调用,由于商家名字变更频率较低,并且就算是变更了没有及时变更缓存,短信里面带有老的商家名字客户也能接受。利用 Caffeine 作为本地缓存,size 设置为 1 万,过期时间设置为 1 个小时,基本能在高峰期解决问题。 -2. 如果数据量更新频繁,也想使用进程缓存的话,那么可以将其过期时间设置为较短,或者设置其较短的自动刷新的时间。这些对于 Caffeine 或者 Guava Cache 来说都是现成的 API。 ### 6.2. 使用多级缓存 -> 俗话说得好,世界上没有什么是一个缓存解决不了的事,如果有,那就两个。 +单纯使用进程内缓存和分布式缓存都存在各自的不足。如果需要更高的性能以及更好的可用性,我们可以将缓存设计为多级结构。将最热的数据使用进程内缓存存储在内存中,进一步提升访问速度。 + +这个设计思路在计算机系统中也存在,比如 CPU 使用 L1、L2、L3 多级缓存,用来减少对内存的直接访问,从而加快访问速度。 + +一般来说,多级缓存架构使用二级缓存已可以满足大部分业务需求,过多的分级会增加系统的复杂度以及维护的成本。因此,多级缓存不是分级越多越好,需要根据实际情况进行权衡。 + +一个典型的二级缓存架构,可以使用进程内缓存(如: Caffeine/Google Guava/Ehcache/HashMap)作为一级缓存;使用分布式缓存(如:Redis/Memcached)作为二级缓存。 + +#### 多级缓存查询 -一般来说我们选择一个进程缓存和一个分布式缓存来搭配做多级缓存,一般来说引入两个也足够了,如果使用三个,四个的话,技术维护成本会很高,反而有可能会得不偿失,如下图所示: +
    -![img](https://user-gold-cdn.xitu.io/2018/8/21/1655d35701c24b7f?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) +多级缓存查询流程如下: -利用 Caffeine 做一级缓存,Redis 作为二级缓存。 +1. 首先,查询 L1 缓存,如果缓存命中,直接返回结果;如果没有命中,执行下一步。 +2. 接下来,查询 L2 缓存,如果缓存命中,直接返回结果并回填 L1 缓存;如果没有命中,执行下一步。 +3. 最后,查询数据库,返回结果并依次回填 L2 缓存、L1 缓存。 -1. 首先去 Caffeine 中查询数据,如果有直接返回。如果没有则进行第 2 步。 -2. 再去 Redis 中查询,如果查询到了返回数据并在 Caffeine 中填充此数据。如果没有查到则进行第 3 步。 -3. 最后去 Mysql 中查询,如果查询到了返回数据并在 Redis,Caffeine 中依次填充此数据。 +#### 多级缓存更新 -对于 Caffeine 的缓存,如果有数据更新,只能删除更新数据的那台机器上的缓存,其他机器只能通过超时来过期缓存,超时设定可以有两种策略: +对于 L1 缓存,如果有数据更新,只能删除并更新所在机器上的缓存,其他机器只能通过超时机制来刷新缓存。超时设定可以有两种策略: - 设置成写入后多少时间后过期 - 设置成写入后多少时间刷新 -对于 Redis 的缓存更新,其他机器立马可见,但是也必须要设置超时时间,其时间比 Caffeine 的过期长。 +对于 L1 缓存,如果有数据更新,其他机器立马可见。但是,也必须要设置超时时间,其时间应该比 L1 缓存的有效时间长。 -为了解决进程内缓存的问题,设计进一步优化: +为了解决进程内缓存不一致的问题,设计可以进一步优化: -![img](https://user-gold-cdn.xitu.io/2018/8/22/1655d50555645a36?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) +
    -通过 Redis 的 pub/sub,可以通知其他进程缓存对此缓存进行删除。如果 Redis 挂了或者订阅机制不靠谱,依靠超时设定,依然可以做兜底处理。 +通过消息队列的发布、订阅机制,可以通知其他应用节点对进程内缓存进行更新。使用这种方案,即使消息队列服务挂了或不可靠,由于先执行了数据库更新,但进程内缓存过期,刷新缓存时,也能保证数据的最终一致性。 ## 7. 缓存问题 diff --git a/docs/technology/cache/caffeine.md b/docs/technology/cache/caffeine.md index ea3989ff..d80d03b4 100644 --- a/docs/technology/cache/caffeine.md +++ b/docs/technology/cache/caffeine.md @@ -4,5 +4,5 @@ ## 参考资料 -[caffeine github](https://github.com/ben-manes/caffeine) - +- [caffeine github](https://github.com/ben-manes/caffeine) +- [深入解密来自未来的缓存-Caffeine](https://juejin.im/post/5b8df63c6fb9a019e04ebaf4) \ No newline at end of file From 27c86bf096010fbb20e7e436a24289e9ecc42520 Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Wed, 16 Oct 2019 20:54:45 +0800 Subject: [PATCH 39/75] update docs --- README.md | 4 + docs/README.md | 4 + docs/coverpage.md | 2 +- docs/index.html | 4 +- docs/technology/sharding/ShardingSphere.md | 55 ++++++++ docs/technology/sharding/sharding-theory.md | 145 ++++++++++++++++++++ 6 files changed, 211 insertions(+), 3 deletions(-) create mode 100644 docs/technology/sharding/ShardingSphere.md create mode 100644 docs/technology/sharding/sharding-theory.md diff --git a/README.md b/README.md index 482f7880..78ee8a46 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,10 @@ - [Mybatis 应用指南](docs/technology/orm/mybatis.md) +### 分库分表 + +- [分库分表原理](docs/technology/sharding/sharding-theory.md) + ## ☕ JavaEE > [JavaEE](docs/javaee) 技术——Java Web 的基石 diff --git a/docs/README.md b/docs/README.md index 717a7c67..296033a2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -79,6 +79,10 @@ - [Mybatis 应用指南](technology/orm/mybatis.md) +### 分库分表 + +- [分库分表原理](technology/sharding/sharding-theory.md) + ## ☕ JavaEE > [JavaEE](javaee) 技术——Java Web 的基石 diff --git a/docs/coverpage.md b/docs/coverpage.md index 40b59be0..9d621514 100644 --- a/docs/coverpage.md +++ b/docs/coverpage.md @@ -1,4 +1,4 @@ -
    +
    # JavaWeb diff --git a/docs/index.html b/docs/index.html index cf837045..7d7dbf0d 100644 --- a/docs/index.html +++ b/docs/index.html @@ -10,7 +10,7 @@ name="viewport" /> @@ -260,7 +260,7 @@ window.$docsify = { name: 'Java Web', repo: 'https://github.com/dunwu/javaweb', - logo: 'http://dunwu.test.upcdn.net/images/others/zp_100_100.png', + logo: 'http://dunwu.test.upcdn.net/common/logo/zp_100_100.png', auto2top: true, coverpage: 'coverpage.md', maxLevel: 4, diff --git a/docs/technology/sharding/ShardingSphere.md b/docs/technology/sharding/ShardingSphere.md new file mode 100644 index 00000000..fe6a8610 --- /dev/null +++ b/docs/technology/sharding/ShardingSphere.md @@ -0,0 +1,55 @@ +# ShardingSphere + +> ShardingSphere 是一套开源的分布式数据库中间件解决方案组成的生态圈,它由 Sharding-JDBC、Sharding-Proxy 和 Sharding-Sidecar(计划中)这 3 款相互独立的产品组成。 他们均提供标准化的数据分片、分布式事务和数据库治理功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。 + +## 架构 + +![ShardingSphere Scope](https://shardingsphere.apache.org/document/current/img/shardingsphere-scope_cn.png) + +### Sharding-JDBC + +定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务。 它使用客户端直连数据库,以 jar 包形式提供服务,无需额外部署和依赖,可理解为增强版的 JDBC 驱动,完全兼容 JDBC 和各种 ORM 框架。 + +- 适用于任何基于 Java 的 ORM 框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template 或直接使用 JDBC。 +- 基于任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP 等。 +- 支持任意实现 JDBC 规范的数据库。目前支持 MySQL,Oracle,SQLServer 和 PostgreSQL。 + +![Sharding-JDBC Architecture](https://shardingsphere.apache.org/document/current/img/sharding-jdbc-brief.png) + +### Sharding-Proxy + +定位为透明化的数据库代理端,提供封装了数据库二进制协议的服务端版本,用于完成对异构语言的支持。 目前先提供 MySQL/PostgreSQL 版本,它可以使用任何兼容 MySQL/PostgreSQL 协议的访问客户端(如:MySQL Command Client, MySQL Workbench, Navicat 等)操作数据,对 DBA 更加友好。 + +- 向应用程序完全透明,可直接当做 MySQL/PostgreSQL 使用。 +- 适用于任何兼容 MySQL/PostgreSQL 协议的的客户端。 + +![Sharding-Proxy Architecture](https://shardingsphere.apache.org/document/current/img/sharding-proxy-brief_v2.png) + +### Sharding-Sidecar(TODO) + +定位为 Kubernetes 的云原生数据库代理,以 Sidecar 的形式代理所有对数据库的访问。 通过无中心、零侵入的方案提供与数据库交互的的啮合层,即 Database Mesh,又可称数据网格。 + +Database Mesh 的关注重点在于如何将分布式的数据访问应用与数据库有机串联起来,它更加关注的是交互,是将杂乱无章的应用与数据库之间的交互有效的梳理。使用 Database Mesh,访问数据库的应用和数据库终将形成一个巨大的网格体系,应用和数据库只需在网格体系中对号入座即可,它们都是被啮合层所治理的对象。 + +![Sharding-Sidecar Architecture](https://shardingsphere.apache.org/document/current/img/sharding-sidecar-brief_v2.png) + +| _Sharding-JDBC_ | _Sharding-Proxy_ | _Sharding-Sidecar_ | | +| :-------------- | :--------------- | :----------------- | ------ | +| 数据库 | 任意 | MySQL | MySQL | +| 连接消耗数 | 高 | 低 | 高 | +| 异构语言 | 仅 Java | 任意 | 任意 | +| 性能 | 损耗低 | 损耗略高 | 损耗低 | +| 无中心化 | 是 | 否 | 是 | +| 静态入口 | 无 | 有 | 无 | + +### 混合架构 + +Sharding-JDBC 采用无中心化架构,适用于 Java 开发的高性能的轻量级 OLTP 应用;Sharding-Proxy 提供静态入口以及异构语言的支持,适用于 OLAP 应用以及对分片数据库进行管理和运维的场景。 + +ShardingSphere 是多接入端共同组成的生态圈。 通过混合使用 Sharding-JDBC 和 Sharding-Proxy,并采用同一注册中心统一配置分片策略,能够灵活的搭建适用于各种场景的应用系统,架构师可以更加自由的调整适合于当前业务的最佳系统架构。 + +![ShardingSphere Hybrid Architecture](https://shardingsphere.apache.org/document/current/img/shardingsphere-hybrid.png) + +## 参考资料 + +- [shardingsphere](https://github.com/apache/incubator-shardingsphere) diff --git a/docs/technology/sharding/sharding-theory.md b/docs/technology/sharding/sharding-theory.md new file mode 100644 index 00000000..cfc673b6 --- /dev/null +++ b/docs/technology/sharding/sharding-theory.md @@ -0,0 +1,145 @@ +# 分库分表原理 + +分片(Sharding)的基本思想就要把一个数据库切分成多个部分,存储在不同的数据库(server)上,从而缓解单一数据库的性能问题。 + +## 数据分片 + +数据分片指按照某个维度将存放在单一数据库中的数据分散地存放至多个数据库或表中以达到提升性能瓶颈以及可用性的效果。 数据分片的有效手段是对关系型数据库进行分库和分表。分库和分表均可以有效的避免由数据量超过可承受阈值而产生的查询瓶颈。 除此之外,分库还能够用于有效的分散对数据库单点的访问量;分表虽然无法缓解数据库压力,但却能够提供尽量将分布式事务转化为本地事务的可能,一旦涉及到跨库的更新操作,分布式事务往往会使问题变得复杂。 使用多主多从的分片方式,可以有效的避免数据单点,从而提升数据架构的可用性。 + +通过分库和分表进行数据的拆分来使得各个表的数据量保持在阈值以下,以及对流量进行疏导应对高访问量,是应对高并发和海量数据系统的有效手段。 数据分片的拆分方式又分为垂直分片和水平分片。 + +### 垂直分片 + +**按照业务拆分的方式称为垂直分片**,又称为纵向拆分,它的核心理念是专库专用。 + +在拆分之前,一个数据库由多个数据表构成,每个表对应着不同的业务。而拆分之后,则是**按照业务将表进行归类,分布到不同的数据库中**,从而将压力分散至不同的数据库。下图展示了根据业务需要,将用户表和订单表垂直分片到不同的数据库的方案。 + +![垂直分片](https://shardingsphere.apache.org/document/current/img/sharding/vertical_sharding.png) + +垂直分片往往需要对架构和设计进行调整。通常来讲,是来不及应对互联网业务需求快速变化的;而且,它也并无法真正的解决单点瓶颈。**垂直拆分可以缓解数据量和访问量带来的问题,但无法根治。如果垂直拆分之后,表中的数据量依然超过单节点所能承载的阈值,则需要水平分片来进一步处理**。 + +### 水平分片 + +水平分片又称为横向拆分。 相对于垂直分片,**水平分片不再将数据根据业务逻辑分类,而是通过某个字段(或某几个字段),根据某种规则将数据分散至多个库或表中,每个分片仅包含数据的一部分**。 例如:根据主键分片,偶数主键的记录放入 0 库(或表),奇数主键的记录放入 1 库(或表),如下图所示。 + +![水平分片](https://shardingsphere.apache.org/document/current/img/sharding/horizontal_sharding.png) + +**水平分片从理论上突破了单机数据量处理的瓶颈,并且扩展相对自由,是分库分表的标准解决方案**。 + +## 分库分表的问题 + +### 事务问题 + +解决事务问题目前有两种可行的方案:分布式事务和通过应用程序与数据库共同控制实现事务下面对两套方案进行一个简单的对比。 + +- 方案一:使用分布式事务 + - 优点:交由数据库管理,简单有效 + - 缺点:性能代价高,特别是 sharding 越来越多时 +- 方案二:由应用程序和数据库共同控制 + - 原理:将一个跨多个数据库的分布式事务分拆成多个仅处于单个数据库上面的小事务,并通过应用程序来总控各个小事务。 + - 优点:性能上有优势 + - 缺点:需要应用程序在事务控制上做灵活设计。如果使用 了[spring](http://lib.csdn.net/base/javaee)的事务管理,改动起来会面临一定的困难。 + +跨库事务也是分布式的数据库集群要面对的棘手事情。 合理采用分表,可以在降低单表数据量的情况下,尽量使用本地事务,善于使用同库不同表可有效避免分布式事务带来的麻烦。在不能避免跨库事务的场景,有些业务仍然需要保持事务的一致性。 而基于 XA 的分布式事务由于在并发度高的场景中性能无法满足需要,并未被互联网巨头大规模使用,他们大多采用最终一致性的柔性事务代替强一致事务。 + +### 跨节点 Join 的问题 + +只要是进行切分,跨节点 Join 的问题是不可避免的。但是良好的设计和切分却可以减少此类情况的发生。解决这一问题的普遍做法是分两次查询实现。在第一次查询的结果集中找出关联数据的 id,根据这些 id 发起第二次请求得到关联数据。 + +### 跨节点的 count,order by,group by 以及聚合函数问题 + +这些是一类问题,因为它们都需要基于全部数据集合进行计算。多数的代理都不会自动处理合并工作。解决方案:与解决跨节点 join 问题的类似,分别在各个节点上得到结果后在应用程序端进行合并。和 join 不同的是每个结点的查询可以并行执行,因此很多时候它的速度要比单一大表快很多。但如果结果集很大,对应用程序内存的消耗是一个问题。 + +### 数据迁移,容量规划,扩容等问题 + +来自淘宝综合业务平台团队,它利用对 2 的倍数取余具有向前兼容的特性(如对 4 取余得 1 的数对 2 取余也是 1)来分配数据,避免了**行级别的数据迁移**,但是依然需要进行**表级别的迁移**,同时对扩容规模和分表数量都有限制。总得来说,这些方案都不是十分的理想,多多少少都存在一些缺点,这也从一个侧面反映出了 Sharding 扩容的难度。 + +### ID 问题 + +一旦数据库被切分到多个物理结点上,我们将不能再依赖数据库自身的主键生成机制。一方面,某个分区数据库自生成的 ID 无法保证在全局上是唯一的;另一方面,应用程序在插入数据之前需要先获得 ID,以便进行 SQL 路由. 一些常见的主键生成策略 + +#### UUID + +使用 UUID 作主键是最简单的方案,但是缺点也是非常明显的。由于 UUID 非常的长,除占用大量存储空间外,最主要的问题是在索引上,在建立索引和基于索引进行查询时都存在性能问题。 + +#### 结合数据库维护一个 Sequence 表 + +此方案的思路也很简单,在数据库中建立一个 Sequence 表,表的结构类似于: + +```SQL +CREATE TABLE `SEQUENCE` ( + `table_name` varchar(18) NOT NULL, + `nextid` bigint(20) NOT NULL, + PRIMARY KEY (`table_name`) +) ENGINE=InnoDB +``` + +每当需要为某个表的新纪录生成 ID 时就从 Sequence 表中取出对应表的 nextid,并将 nextid 的值加 1 后更新到数据库中以备下次使用。此方案也较简单,但缺点同样明显:由于所有插入任何都需要访问该表,该表很容易成为系统性能瓶颈,同时它也存在单点问题,一旦该表数据库失效,整个应用程序将无法工作。有人提出使用 Master-Slave 进行主从同步,但这也只能解决单点问题,并不能解决读写比为 1:1 的访问压力问题。 + +#### [Twitter 的分布式自增 ID 算法 Snowflake](http://blog.sina.com.cn/s/blog_6b7c2e660102vbi2.html) + +在分布式系统中,需要生成全局 UID 的场合还是比较多的,twitter 的 snowflake 解决了这种需求,实现也还是很简单的,除去配置信息,核心代码就是毫秒级时间 41 位 机器 ID 10 位 毫秒内序列 12 位。 + +- 10---0000000000 0000000000 0000000000 0000000000 0 --- 00000 ---00000 ---000000000000 在上面的字符串中,第一位为未使用(实际上也可作为 long 的符号位),接下来的 41 位为毫秒级时间,然后 5 位 datacenter 标识位,5 位机器 ID(并不算标识符,实际是为线程标识),然后 12 位该毫秒内的当前毫秒内的计数,加起来刚好 64 位,为一个 Long 型。 + +这样的好处是,整体上按照时间自增排序,并且整个分布式系统内不会产生 ID 碰撞(由 datacenter 和机器 ID 作区分),并且效率较高,经测试,snowflake 每秒能够产生 26 万 ID 左右,完全满足需要。 + +### 跨分片的排序分页 + +一般来讲,分页时需要按照指定字段进行排序。当排序字段就是分片字段的时候,我们通过分片规则可以比较容易定位到指定的分片,而当排序字段非分片字段的时候,情况就会变得比较复杂了。为了最终结果的准确性,我们需要在不同的分片节点中将数据进行排序并返回,并将不同分片返回的结果集进行汇总和再次排序,最后再返回给用户。如下图所示: + +![img](https://upload-images.jianshu.io/upload_images/3710706-925381b9a478c8df.png?imageMogr2/auto-orient/strip|imageView2/2/w/640/format/webp) + +上面图中所描述的只是最简单的一种情况(取第一页数据),看起来对性能的影响并不大。但是,如果想取出第 10 页数据,情况又将变得复杂很多,如下图所示: + +![img](https://upload-images.jianshu.io/upload_images/3710706-9a7cfbdb95bb9b70.png?imageMogr2/auto-orient/strip|imageView2/2/w/640/format/webp) + +有些读者可能并不太理解,为什么不能像获取第一页数据那样简单处理(排序取出前 10 条再合并、排序)。其实并不难理解,因为各分片节点中的数据可能是随机的,为了排序的准确性,必须把所有分片节点的前 N 页数据都排序好后做合并,最后再进行整体的排序。很显然,这样的操作是比较消耗资源的,用户越往后翻页,系统性能将会越差。 + +那如何解决分库情况下的分页问题呢?有以下几种办法: + +如果是在前台应用提供分页,则限定用户只能看前面 n 页,这个限制在业务上也是合理的,一般看后面的分页意义不大(如果一定要看,可以要求用户缩小范围重新查询)。 + +如果是后台批处理任务要求分批获取数据,则可以加大 page size,比如每次获取 5000 条记录,有效减少分页数(当然离线访问一般走备库,避免冲击主库)。 + +分库设计时,一般还有配套大数据平台汇总所有分库的记录,有些分页查询可以考虑走大数据平台。 + +### 分库策略 + +- 根据数值范围,比如用户 Id 为 1-9999 的记录分到第一个库,10000-20000 的分到第二个库,以此类推。 +- 根据数值取模,比如用户 Id mod n,余数为 0 的记录放到第一个库,余数为 1 的放到第二个库,以此类推。 + +优劣比较:评价指标按照范围分库按照 Mod 分库库数量前期数目比较小,可以随用户/业务按需增长前期即根据 mode 因子确定库数量,数目一般比较大访问性能前期库数量小,全库查询消耗资源少,单库查询性能略差前期库数量大,全库查询消耗资源多,单库查询性能略好调整库数量比较容易,一般只需为新用户增加库,老库拆分也只影响单个库困难,改变 mod 因子导致数据在所有库之间迁移数据热点新旧用户购物频率有差异,有数据热点问题新旧用户均匀到分布到各个库,无热点实践中,为了处理简单,选择 mod 分库的比较多。同时二次分库时,为了数据迁移方便,一般是按倍数增加,比如初始 4 个库,二次分裂为 8 个,再 16 个。这样对于某个库的数据,一半数据移到新库,剩余不动,对比每次只增加一个库,所有数据都要大规模变动。补充下,mod 分库一般每个库记录数比较均匀,但也有些数据库,存在超级 Id,这些 Id 的记录远远超过其他 Id,比如在广告场景下,某个大广告主的广告数可能占总体很大比例。如果按照广告主 Id 取模分库,某些库的记录数会特别多,对于这些超级 Id,需要提供单独库来存储记录。 + +### 分库数量 + +分库数量首先和单库能处理的记录数有关,一般来说,Mysql 单库超过 5000 万条记录,Oracle 单库超过 1 亿条记录,DB 压力就很大(当然处理能力和字段数量/访问模式/记录长度有进一步关系)。 + +在满足上述前提下,如果分库数量少,达不到分散存储和减轻 DB 性能压力的目的;如果分库的数量多,好处是每个库记录少,单库访问性能好,但对于跨多个库的访问,应用程序需要访问多个库,如果是并发模式,要消耗宝贵的线程资源;如果是串行模式,执行时间会急剧增加。 + +最后分库数量还直接影响硬件的投入,一般每个分库跑在单独物理机上,多一个库意味多一台设备。所以具体分多少个库,要综合评估,一般初次分库建议分 4-8 个库。 + +## 读写分离 + +面对日益增加的系统访问量,数据库的吞吐量面临着巨大瓶颈。 对于同一时刻有大量并发读操作和较少写操作类型的应用系统来说,将数据库拆分为主库和从库,主库负责处理事务性的增删改操作,从库负责处理查询操作,能够有效的避免由数据更新导致的行锁,使得整个系统的查询性能得到极大的改善。 + +通过一主多从的配置方式,可以将查询请求均匀的分散到多个数据副本,能够进一步的提升系统的处理能力。 使用多主多从的方式,不但能够提升系统的吞吐量,还能够提升系统的可用性,可以达到在任何一个数据库宕机,甚至磁盘物理损坏的情况下仍然不影响系统的正常运行。 + +与将数据根据分片键打散至各个数据节点的水平分片不同,读写分离则是根据 SQL 语义的分析,将读操作和写操作分别路由至主库与从库。 + +![读写分离](https://shardingsphere.apache.org/document/current/img/read-write-split/read-write-split.png) + +读写分离的数据节点中的数据内容是一致的,而水平分片的每个数据节点的数据内容却并不相同。将水平分片和读写分离联合使用,能够更加有效的提升系统性能。 + +### 挑战 + +读写分离虽然可以提升系统的吞吐量和可用性,但同时也带来了数据不一致的问题。 这包括多个主库之间的数据一致性,以及主库与从库之间的数据一致性的问题。 并且,读写分离也带来了与数据分片同样的问题,它同样会使得应用开发和运维人员对数据库的操作和运维变得更加复杂。 下图展现了将分库分表与读写分离一同使用时,应用程序与数据库集群之间的复杂拓扑关系。 + +![数据分片 + 读写分离](https://shardingsphere.apache.org/document/current/img/read-write-split/sharding-read-write-split.png) + +## 参考资料 + +- https://github.com/apache/incubator-shardingsphere +- [“分库分表" ?选型和流程要慎重,否则会失控](https://juejin.im/post/5bf778ef5188251b8a26ed8b) +- [分库分表需要考虑的问题及方案](https://www.jianshu.com/p/32b3e91aa22c) + From b5bb1151d2d2a40ee3fbf78eb6a2699d5d79a038 Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Wed, 23 Oct 2019 20:24:45 +0800 Subject: [PATCH 40/75] reformat codes --- .../javaee-tutorial-filter/pom.xml | 204 +- .../dunwu/javaee/filter/CacheFilter.java | 14 +- .../filter/CharacterEncodingFilter.java | 13 +- .../javaee/filter/ExceptionHandlerFilter.java | 16 +- .../dunwu/javaee/filter/FilterImpl.java | 6 +- .../dunwu/javaee/filter/GZipFilter.java | 11 +- .../javaee/filter/ImageRedirectFilter.java | 13 +- .../github/dunwu/javaee/filter/LogFilter.java | 6 +- .../github/dunwu/javaee/filter/MyFilter.java | 8 +- .../javaee/filter/OutputReplaceFilter.java | 11 +- .../dunwu/javaee/filter/PrivilegeFilter.java | 11 +- .../dunwu/javaee/filter/UploadFilter.java | 5 +- .../dunwu/javaee/filter/WaterMarkFilter.java | 8 +- .../dunwu/javaee/filter/XSLTFilter.java | 17 +- .../dunwu/javaee/filter/test/Download.java | 8 +- .../dunwu/javaee/filter/test/GZipTest.java | 14 +- .../filter/wrapper/CacheResponseWrapper.java | 6 +- .../filter/wrapper/GZipOutputStream.java | 8 +- .../filter/wrapper/GZipResponseWrapper.java | 20 +- .../wrapper/HttpCharacterResponseWrapper.java | 6 +- .../filter/wrapper/UploadRequestWrapper.java | 24 +- .../filter/wrapper/WaterMarkOutputStream.java | 6 +- .../wrapper/WaterMarkResponseWrapper.java | 15 +- .../src/main/resources/logback.xml | 78 +- .../main/webapp/WEB-INF/privilege.properties | 14 +- .../main/webapp/WEB-INF/sensitive.properties | 10 +- .../src/main/webapp/WEB-INF/web.xml | 352 +- .../src/main/webapp/views/js/dojo.js | 13382 ++++++++-------- .../webapp/views/jsp/accountException.jsp | 58 +- .../webapp/views/jsp/businessException.jsp | 24 +- .../src/main/webapp/views/jsp/dispatcher.jsp | 6 +- .../src/main/webapp/views/jsp/exception.jsp | 24 +- .../src/main/webapp/views/jsp/image.jsp | 6 +- .../src/main/webapp/views/jsp/index.jsp | 72 +- .../main/webapp/views/jsp/testCacheFilter.jsp | 52 +- .../views/jsp/testCharacterEncodingFilter.jsp | 26 +- .../views/jsp/testExceptionHandlerFilter.jsp | 20 +- .../main/webapp/views/jsp/testGZipFilter.jsp | 88 +- .../views/jsp/testOutputReplaceFilter.jsp | 4 +- .../webapp/views/jsp/testUploadFilter.jsp | 48 +- .../webapp/views/jsp/testWaterMarkFilter.jsp | 4 +- .../src/main/webapp/views/xml/book.xml | 8 +- .../src/main/webapp/views/xml/book.xsl | 74 +- .../src/main/webapp/views/xml/demo.xml | 148 +- .../src/main/webapp/views/xml/messageLog.xsl | Bin 11114 -> 9852 bytes .../src/main/webapp/views/xml/thinkInJava.xml | 8 +- .../src/main/webapp/views/xml/xml.xsl | 30 +- .../javaee/server/FilterDemosBootstrap.java | 3 +- .../dunwu/javaee/server/JettyFactory.java | 83 +- .../src/test/resources/jetty/webdefault.xml | 910 +- .../javaee-tutorial-jsp/pom.xml | 173 +- .../com/sun/products/applet/demo/Graph.java | 89 +- .../github/dunwu/javaee/jsp/util/IpUtil.java | 3 +- .../dunwu/javaee/jsp/util/ip/IPEntry.java | 36 +- .../dunwu/javaee/jsp/util/ip/IPSeeker.java | 730 +- .../github/dunwu/javaee/jsp/util/ip/Test.java | 13 +- .../dunwu/javaee/jsp/util/ip/Utils.java | 70 +- .../src/main/resources/logback.xml | 80 +- .../webapp/WEB-INF/resources/jsp/index.jsp | 10 +- .../src/main/webapp/WEB-INF/web.xml | 38 +- .../main/webapp/examples/02.grammar/break.jsp | 18 +- .../main/webapp/examples/02.grammar/for.jsp | 84 +- .../main/webapp/examples/02.grammar/if.jsp | 6 +- .../main/webapp/examples/02.grammar/if2.jsp | 4 +- .../webapp/examples/02.grammar/scriptlet.jsp | 14 +- .../webapp/examples/02.grammar/switch.jsp | 50 +- .../main/webapp/examples/02.grammar/while.jsp | 18 +- .../webapp/examples/03.directive/foot.jsp | 12 +- .../webapp/examples/03.directive/head.jsp | 24 +- .../webapp/examples/03.directive/include.jsp | 2 +- .../webapp/examples/03.directive/page.jsp | 6 +- .../main/webapp/examples/04.action/date.jsp | 4 +- .../webapp/examples/04.action/jspforward.jsp | 16 +- .../webapp/examples/04.action/jspinclude.jsp | 6 +- .../webapp/examples/04.action/jspplugin.jsp | 38 +- .../webapp/examples/04.action/jspuseBean.jsp | 72 +- .../webapp/examples/04.action/jspuseBean2.jsp | 50 +- .../webapp/examples/04.action/useBean.html | 62 +- .../examples/05.implicit_object/error.jsp | 2 +- .../examples/05.implicit_object/exception.jsp | 8 +- .../webapp/examples/cookie/readCookie.jsp | 38 +- .../main/webapp/examples/cookie/session.jsp | 104 +- .../webapp/examples/cookie/writeCookie.jsp | 38 +- .../main/webapp/examples/locale/language.jsp | 20 +- .../main/webapp/examples/locale/language2.jsp | 16 +- .../webapp/examples/locale/localeCurrency.jsp | 14 +- .../webapp/examples/locale/localeDate.jsp | 12 +- .../webapp/examples/locale/localePercent.jsp | 14 +- .../main/webapp/examples/practice/date.jsp | 14 +- .../examples/practice/form/checkbox.html | 12 +- .../examples/practice/form/checkbox.jsp | 24 +- .../examples/practice/form/checkbox2.html | 12 +- .../examples/practice/form/checkbox2.jsp | 32 +- .../examples/practice/form/formGet.html | 12 +- .../webapp/examples/practice/form/formGet.jsp | 18 +- .../examples/practice/form/formPost.html | 12 +- .../examples/practice/form/formPost.jsp | 26 +- .../webapp/examples/practice/hitCount.jsp | 26 +- .../examples/practice/mail/sendMail.jsp | 72 +- .../webapp/examples/practice/redirect.jsp | 12 +- .../main/webapp/examples/practice/refresh.jsp | 33 +- .../webapp/examples/uncheck/01.helloWorld.jsp | 4 +- .../examples/uncheck/01.helloWorld_zh.jsp | 6 +- .../main/webapp/examples/uncheck/02.life.jsp | 39 +- .../webapp/examples/uncheck/03.yourIp.jsp | 8 +- .../webapp/examples/uncheck/configuration.jsp | 10 +- .../src/main/webapp/views/css/style.css | 56 +- .../src/main/webapp/views/jsp/action/date.jsp | 2 +- .../main/webapp/views/jsp/action/forward.jsp | 4 +- .../webapp/views/jsp/action/getProperty.jsp | 2 +- .../main/webapp/views/jsp/action/include.jsp | 4 +- .../main/webapp/views/jsp/action/useBean.html | 58 +- .../main/webapp/views/jsp/action/useBean.jsp | 70 +- .../src/main/webapp/views/jsp/application.jsp | 4 +- .../src/main/webapp/views/jsp/break.jsp | 18 +- .../src/main/webapp/views/jsp/comment.jsp | 6 +- .../src/main/webapp/views/jsp/counter.jsp | 50 +- .../views/jsp/directive/include/foot.jsp | 12 +- .../views/jsp/directive/include/head.jsp | 28 +- .../views/jsp/directive/include/include.jsp | 16 +- .../views/jsp/directive/page/contentType.jsp | 2 +- .../views/jsp/directive/page/errorPage.jsp | 6 +- .../views/jsp/directive/page/isErrorPage.jsp | 4 +- .../src/main/webapp/views/jsp/el.jsp | 16 +- .../src/main/webapp/views/jsp/error.jsp | 4 +- .../src/main/webapp/views/jsp/exception.jsp | 8 +- .../src/main/webapp/views/jsp/grammar/for.jsp | 82 +- .../src/main/webapp/views/jsp/grammar/if.jsp | 6 +- .../main/webapp/views/jsp/grammar/while.jsp | 16 +- .../src/main/webapp/views/jsp/greeting.jsp | 52 +- .../src/main/webapp/views/jsp/index.jsp | 24 +- .../src/main/webapp/views/jsp/life.jsp | 4 +- .../src/main/webapp/views/jsp/method.jsp | 126 +- .../src/main/webapp/views/jsp/plugin.jsp | 38 +- .../src/main/webapp/views/jsp/return.jsp | 8 +- .../src/main/webapp/views/jsp/scriptlet.jsp | 16 +- .../src/main/webapp/views/jsp/taglib.jsp | 4 +- .../dunwu/javaee/server/JettyFactory.java | 66 +- .../dunwu/javaee/server/QuickStartServer.java | 3 +- .../src/test/resources/jetty/webdefault.xml | 910 +- .../src/test/resources/logback.xml | 80 +- .../javaee-tutorial-jstl/pom.xml | 215 +- .../io/github/dunwu/javaee/util/Example.java | 2 +- .../dunwu/javaee/util/Example_es_ES.java | 2 +- .../github/dunwu/javaee/util/Pagination.java | 33 +- .../src/main/resources/logback.xml | 80 +- .../src/main/resources/messages.properties | 4 +- .../main/resources/messages_zh_CN.properties | 4 +- .../src/main/webapp/WEB-INF/web.xml | 38 +- .../src/main/webapp/examples/core/c_catch.jsp | 12 +- .../main/webapp/examples/core/c_choose.jsp | 24 +- .../main/webapp/examples/core/c_forEach.jsp | 6 +- .../main/webapp/examples/core/c_forTokens.jsp | 6 +- .../src/main/webapp/examples/core/c_if.jsp | 6 +- .../main/webapp/examples/core/c_import.jsp | 6 +- .../src/main/webapp/examples/core/c_out.jsp | 8 +- .../src/main/webapp/examples/core/c_param.jsp | 8 +- .../main/webapp/examples/core/c_redirect.jsp | 6 +- .../main/webapp/examples/core/c_remove.jsp | 6 +- .../src/main/webapp/examples/core/c_set.jsp | 4 +- .../src/main/webapp/examples/core/c_url.jsp | 8 +- .../main/webapp/examples/fmt/fmt_bundle.jsp | 10 +- .../webapp/examples/fmt/fmt_formatDate.jsp | 26 +- .../webapp/examples/fmt/fmt_formatNumber.jsp | 26 +- .../main/webapp/examples/fmt/fmt_message.jsp | 16 +- .../webapp/examples/fmt/fmt_parseDate.jsp | 8 +- .../webapp/examples/fmt/fmt_parseNumber.jsp | 8 +- .../examples/fmt/fmt_requestEncoding.jsp | 4 +- .../webapp/examples/fmt/fmt_setLocale.jsp | 16 +- .../webapp/examples/fmt/fmt_setTimeZone.jsp | 10 +- .../main/webapp/examples/fmt/fmt_timeZone.jsp | 58 +- .../webapp/examples/function/fn_contains.jsp | 10 +- .../function/fn_containsIgnoreCase.jsp | 10 +- .../webapp/examples/function/fn_endsWith.jsp | 10 +- .../webapp/examples/function/fn_escapeXml.jsp | 4 +- .../webapp/examples/function/fn_indexOf.jsp | 4 +- .../main/webapp/examples/function/fn_join.jsp | 4 +- .../webapp/examples/function/fn_length.jsp | 4 +- .../webapp/examples/function/fn_replace.jsp | 4 +- .../webapp/examples/function/fn_split.jsp | 4 +- .../examples/function/fn_startsWith.jsp | 8 +- .../webapp/examples/function/fn_substring.jsp | 4 +- .../examples/function/fn_substringAfter.jsp | 4 +- .../examples/function/fn_substringBefore.jsp | 4 +- .../examples/function/fn_toLowerCase.jsp | 4 +- .../examples/function/fn_toUpperCase.jsp | 4 +- .../main/webapp/examples/function/fn_trim.jsp | 4 +- .../webapp/examples/sql/sql_dateParam.jsp | 50 +- .../main/webapp/examples/sql/sql_param.jsp | 44 +- .../main/webapp/examples/sql/sql_query.jsp | 40 +- .../webapp/examples/sql/sql_setDataSource.jsp | 10 +- .../webapp/examples/sql/sql_transaction.jsp | 64 +- .../main/webapp/examples/sql/sql_update.jsp | 42 +- .../main/webapp/examples/uncheck/bundle.jsp | 12 +- .../main/webapp/examples/uncheck/catch.jsp | 18 +- .../main/webapp/examples/uncheck/choose.jsp | 16 +- .../main/webapp/examples/uncheck/contains.jsp | 4 +- .../examples/uncheck/containsIgnoreCase.jsp | 4 +- .../webapp/examples/uncheck/dateParam.jsp | 92 +- .../main/webapp/examples/uncheck/endsWith.jsp | 88 +- .../webapp/examples/uncheck/escapeXml.jsp | 4 +- .../src/main/webapp/examples/uncheck/fmt.jsp | 4 +- .../src/main/webapp/examples/uncheck/fn.jsp | 4 +- .../main/webapp/examples/uncheck/forEach.jsp | 88 +- .../examples/uncheck/forEachWithList.jsp | 341 +- .../webapp/examples/uncheck/forTokens.jsp | 50 +- .../webapp/examples/uncheck/formatDate.jsp | 92 +- .../webapp/examples/uncheck/formatNumber.jsp | 106 +- .../src/main/webapp/examples/uncheck/if.jsp | 76 +- .../main/webapp/examples/uncheck/import.jsp | 14 +- .../main/webapp/examples/uncheck/index.jsp | 4 +- .../main/webapp/examples/uncheck/indexOf.jsp | 4 +- .../src/main/webapp/examples/uncheck/join.jsp | 6 +- .../main/webapp/examples/uncheck/length.jsp | 4 +- .../src/main/webapp/examples/uncheck/out.jsp | 6 +- .../main/webapp/examples/uncheck/param.jsp | 8 +- .../main/webapp/examples/uncheck/parse.jsp | 42 +- .../webapp/examples/uncheck/parseDate.jsp | 4 +- .../webapp/examples/uncheck/parseNumber.jsp | 4 +- .../main/webapp/examples/uncheck/query.jsp | 70 +- .../examples/uncheck/queryPagination.jsp | 82 +- .../webapp/examples/uncheck/queryReflect.jsp | 86 +- .../main/webapp/examples/uncheck/redirect.jsp | 4 +- .../main/webapp/examples/uncheck/remove.jsp | 6 +- .../examples/uncheck/requestEncoding.jsp | 20 +- .../src/main/webapp/examples/uncheck/set.jsp | 8 +- .../webapp/examples/uncheck/setBundle.jsp | 6 +- .../webapp/examples/uncheck/setDataSource.jsp | 10 +- .../webapp/examples/uncheck/setLocale.jsp | 84 +- .../webapp/examples/uncheck/setTimeZone.jsp | 42 +- .../src/main/webapp/examples/uncheck/sina.xml | 468 +- .../main/webapp/examples/uncheck/split.jsp | 6 +- .../src/main/webapp/examples/uncheck/sql.jsp | 4 +- .../webapp/examples/uncheck/substring.jsp | 4 +- .../main/webapp/examples/uncheck/timeZone.jsp | 94 +- .../webapp/examples/uncheck/transaction.jsp | 112 +- .../main/webapp/examples/uncheck/update.jsp | 56 +- .../src/main/webapp/examples/uncheck/url.jsp | 4 +- .../main/webapp/examples/uncheck/x_choose.jsp | 44 +- .../webapp/examples/uncheck/x_forEach.jsp | 42 +- .../src/main/webapp/examples/uncheck/x_if.jsp | 36 +- .../main/webapp/examples/uncheck/x_out.jsp | 14 +- .../main/webapp/examples/uncheck/x_set.jsp | 32 +- .../webapp/examples/uncheck/x_transform.jsp | 156 +- .../src/main/webapp/examples/uncheck/xml.jsp | 12 +- .../src/main/webapp/examples/xml/x_choose.jsp | 46 +- .../main/webapp/examples/xml/x_forEach.jsp | 34 +- .../src/main/webapp/examples/xml/x_if.jsp | 38 +- .../src/main/webapp/examples/xml/x_out.jsp | 30 +- .../src/main/webapp/examples/xml/x_param.jsp | 30 +- .../src/main/webapp/examples/xml/x_parse.jsp | 6 +- .../src/main/webapp/examples/xml/x_set.jsp | 28 +- .../main/webapp/examples/xml/x_transform.jsp | 28 +- .../src/main/webapp/xml/books.xml | 20 +- .../src/main/webapp/xml/style.xsl | 54 +- .../dunwu/javaee/server/JettyFactory.java | 66 +- .../dunwu/javaee/server/QuickStartServer.java | 3 +- .../src/test/resources/jetty/webdefault.xml | 910 +- .../javaee-tutorial-listener/pom.xml | 184 +- .../javaee/listener/LoginSessionListener.java | 9 +- .../MyHttpSessionActivationListener.java | 7 +- .../MyHttpSessionAttributeListener.java | 5 +- .../MyHttpSessionBindingListener.java | 5 +- .../listener/MyHttpSessionListener.java | 7 +- .../MyServletContextAttributeListener.java | 5 +- .../listener/MyServletContextListener.java | 7 +- .../MyServletRequestAttributeListener.java | 5 +- .../listener/MyServletRequestListener.java | 10 +- .../javaee/listener/PersonInfoListener.java | 6 +- .../javaee/listener/bean/PersonInfo.java | 16 +- .../listener/util/ApplicationConstants.java | 2 +- .../src/main/resources/logback.xml | 80 +- .../src/main/webapp/META-INF/context.xml | 6 +- .../webapp/WEB-INF/resources/jsp/index.jsp | 10 +- .../src/main/webapp/WEB-INF/web.xml | 92 +- .../src/main/webapp/aa.html | 8 +- .../src/main/webapp/active.jsp | 22 +- .../src/main/webapp/index.jsp | 8 +- .../src/main/webapp/listener.jsp | 128 +- .../src/main/webapp/online.jsp | 74 +- .../main/webapp/testLoginSessionListener.jsp | 86 +- .../dunwu/javaee/server/JettyFactory.java | 83 +- .../javaee/server/ListenerDemosBootstrap.java | 3 +- .../src/test/resources/jetty/webdefault.xml | 910 +- .../javaee-tutorial-oss/pom.xml | 274 +- .../javaee/oss/encode/digest/DsaCoder.java | 39 +- .../javaee/oss/encode/digest/HmacCoder.java | 27 +- .../javaee/oss/encode/digest/MdCoder.java | 38 +- .../javaee/oss/encode/digest/ShaCoder.java | 38 +- .../javaee/oss/encode/encrypt/AESCoder.java | 100 +- .../javaee/oss/encode/encrypt/Base64Demo.java | 14 +- .../javaee/oss/encode/encrypt/DESCoder.java | 105 +- .../oss/encode/encrypt/DESedeCoder.java | 41 +- .../javaee/oss/encode/encrypt/PBECoder.java | 66 +- .../javaee/oss/encode/encrypt/RSACoder.java | 60 +- .../oss/encode/sample/DownloadPolicy.java | 18 +- .../oss/encode/sample/ModifyPolicy.java | 17 +- .../javaee/oss/encode/sample/TokenUtil.java | 164 +- .../oss/encode/sample/UploadConstant.java | 7 +- .../oss/encode/sample/UploadPolicy.java | 23 +- .../dunwu/javaee/oss/html/CnblogParser.java | 9 +- .../dunwu/javaee/oss/html/XiamiParser.java | 37 +- .../dunwu/javaee/oss/image/ImageUtil.java | 86 +- .../dunwu/javaee/oss/image/QRCodeUtil.java | 14 +- .../javaee/oss/image/dto/BarcodeParamDTO.java | 6 +- .../javaee/oss/image/dto/ImageParamDTO.java | 52 +- .../dunwu/javaee/oss/jms/JMSHelloWorld.java | 17 +- .../dunwu/javaee/oss/jms/JMSReceiver.java | 25 +- .../dunwu/javaee/oss/jms/JMSSender.java | 21 +- .../dunwu/javaee/oss/logging/JclDemo.java | 5 +- .../dunwu/javaee/oss/logging/Slf4jDemo.java | 5 +- .../dunwu/javaee/oss/mail/ForwardMail.java | 13 +- .../dunwu/javaee/oss/mail/MailConfigDTO.java | 5 +- .../github/dunwu/javaee/oss/mail/MailDTO.java | 5 +- .../dunwu/javaee/oss/mail/MailUtil.java | 49 +- .../javaee/oss/mail/SendAttachmentMail.java | 13 +- .../javaee/oss/mail/SendTemplateMail.java | 17 +- .../dunwu/javaee/oss/mail/SendTextMail.java | 11 +- .../dunwu/javaee/oss/mail/StoreMail.java | 11 +- .../dunwu/javaee/oss/mail/sendHtmlMail.java | 11 +- .../javaee/oss/template/LoadVelocityDemo.java | 14 +- .../oss/template/VelocityHelloWorld.java | 12 +- .../oss/template/VelocityHelloWorld2.java | 17 +- .../javaee/oss/template/VelocityUtil.java | 18 +- .../src/main/resources/html/example.html | 2 +- .../src/main/resources/log4j.xml | 72 +- .../src/main/resources/logback.xml | 366 +- .../src/main/resources/mail/mail.properties | 10 +- .../src/main/resources/template/footer.vm | 25 +- .../src/main/resources/template/header.vm | 9 +- .../src/main/resources/template/mail.vm | 215 +- .../resources/template/velocity.properties | 8 +- .../dunwu/javaee/oss/html/JsoupTest.java | 14 +- .../dunwu/javaee/oss/image/ImageUtilTest.java | 39 +- .../javaee/oss/image/QRCodeUtilTest.java | 17 +- .../javaee/oss/image/ThumbnailatorTest.java | 45 +- .../javaee/oss/template/VelocityUtilTest.java | 5 +- .../dunwu/javaee/oss/test/JUnitExecTest.java | 9 +- .../test/resources/html/jsoup-cookbook.html | 177 +- .../src/test/resources/logback.xml | 106 +- .../javaee-tutorial-servlet/pom.xml | 157 +- .../javaee/servlet/AnnotationServlet.java | 15 +- .../javaee/servlet/ContextParamServlet.java | 12 +- .../dunwu/javaee/servlet/FirstServlet.java | 63 +- .../dunwu/javaee/servlet/ForwardServlet.java | 16 +- .../dunwu/javaee/servlet/HelloServlet.java | 46 +- .../dunwu/javaee/servlet/ImageServlet.java | 40 +- .../javaee/servlet/InitParamServlet.java | 60 +- .../javaee/servlet/InjectionServlet.java | 10 +- .../javaee/servlet/LifeCycleServlet.java | 12 +- .../dunwu/javaee/servlet/PostServlet.java | 19 +- .../dunwu/javaee/servlet/RedirectServlet.java | 14 +- .../dunwu/javaee/servlet/RequestServlet.java | 189 +- .../javaee/servlet/ThreadSafetyServlet.java | 5 +- .../dunwu/javaee/servlet/UploadServlet.java | 34 +- .../servlet/upload/ProgressUploadServlet.java | 22 +- .../src/main/resources/logback.xml | 78 +- .../src/main/webapp/WEB-INF/notice.html | 146 +- .../src/main/webapp/WEB-INF/web.xml | 398 +- .../src/main/webapp/index.jsp | 42 +- .../views/jsp/postPersonalInformation.html | 272 +- .../dunwu/javaee/server/JettyFactory.java | 66 +- .../dunwu/javaee/server/QuickStartServer.java | 3 +- .../src/test/resources/jetty/webdefault.xml | 910 +- .../src/test/resources/logback.xml | 80 +- .../javaee-tutorial-session/pom.xml | 166 +- .../dunwu/javaee/cookie/AddCookies.java | 30 +- .../dunwu/javaee/cookie/DeleteCookies.java | 27 +- .../dunwu/javaee/cookie/ReadCookies.java | 29 +- .../dunwu/javaee/cookie/bean/Person.java | 2 +- .../dunwu/javaee/cookie/bean/Topic.java | 2 +- .../javaee/session/SessionTrackServlet.java | 29 +- .../src/main/resources/logback.xml | 78 +- .../src/main/webapp/WEB-INF/web.xml | 78 +- .../src/main/webapp/encodeURL.jsp | 2 +- .../src/main/webapp/index.jsp | 24 +- .../src/main/webapp/views/css/style.css | 56 +- .../webapp/views/jsp/cookie/addCookies.jsp | 14 +- .../main/webapp/views/jsp/cookie/base64.jsp | 30 +- .../webapp/views/jsp/cookie/base64_decode.jsp | 40 +- .../main/webapp/views/jsp/cookie/cookie.jsp | 122 +- .../views/jsp/cookie/cookieAttribute.jsp | 25 +- .../webapp/views/jsp/cookie/cookieDomain.jsp | 24 +- .../main/webapp/views/jsp/cookie/encoding.jsp | 32 +- .../main/webapp/views/jsp/cookie/history.js | 40 +- .../main/webapp/views/jsp/cookie/history.jsp | 92 +- .../webapp/views/jsp/cookie/javascript.jsp | 156 +- .../main/webapp/views/jsp/cookie/login.jsp | 100 +- .../webapp/views/jsp/cookie/loginCookie.jsp | 260 +- .../main/webapp/views/jsp/cookie/maxAge.jsp | 22 +- .../webapp/views/jsp/cookie/setCookie.jsp | 167 +- .../main/webapp/views/jsp/session/session.jsp | 131 +- .../main/webapp/views/jsp/session/vote.jsp | 82 +- .../main/webapp/views/jsp/session/welcome.jsp | 100 +- .../dunwu/javaee/server/JettyFactory.java | 66 +- .../dunwu/javaee/server/QuickStartServer.java | 3 +- .../src/test/resources/jetty/webdefault.xml | 910 +- .../javaee-tutorial-taglib/pom.xml | 215 +- .../github/dunwu/javaee/taglib/Copyright.java | 9 +- .../github/dunwu/javaee/taglib/HelloTag.java | 4 +- .../github/dunwu/javaee/taglib/HelloTag2.java | 6 +- .../github/dunwu/javaee/taglib/HelloTag3.java | 10 +- .../javaee/taglib/function/Function.java | 11 +- .../dunwu/javaee/taglib/tags/AddTag.java | 3 +- .../dunwu/javaee/taglib/tags/Copyright.java | 7 +- .../dunwu/javaee/taglib/tags/Copyright2.java | 7 +- .../taglib/tags/DynamicAttributeTag.java | 9 +- .../dunwu/javaee/taglib/tags/HelloTag.java | 3 +- .../dunwu/javaee/taglib/tags/IteratorTag.java | 5 +- .../dunwu/javaee/taglib/tags/LoopTag.java | 8 +- .../javaee/taglib/tags/ToLowerCaseTag.java | 3 +- .../javaee/taglib/tags/table/Column.java | 4 +- .../dunwu/javaee/taglib/tags/table/Table.java | 54 +- .../taglib/tags2/MultiAttributeTag.java | 5 +- .../dunwu/javaee/taglib/tags2/MultiTag.java | 2 +- .../javaee/taglib/tags2/ToUpperCaseTag.java | 4 +- .../dunwu/javaee/taglib/test/Messages.java | 7 +- .../javaee/taglib/test/messages.properties | 2 +- .../src/main/resources/copyright.properties | 2 +- .../src/main/resources/logback.xml | 80 +- .../src/main/webapp/WEB-INF/tld/function.tld | 72 +- .../src/main/webapp/WEB-INF/tld/hello.tld | 16 +- .../src/main/webapp/WEB-INF/tld/hello2.tld | 16 +- .../src/main/webapp/WEB-INF/tld/hello3.tld | 22 +- .../src/main/webapp/WEB-INF/tld/taglib.tld | 260 +- .../src/main/webapp/WEB-INF/web.xml | 38 +- .../src/main/webapp/dynamic.jsp | 16 +- .../src/main/webapp/function.jsp | 60 +- .../src/main/webapp/hello.jsp | 6 +- .../src/main/webapp/index.jsp | 52 +- .../src/main/webapp/loop.jsp | 4 +- .../src/main/webapp/multi.jsp | 4 +- .../src/main/webapp/multiAttribute.jsp | 18 +- .../src/main/webapp/style/style.css | 72 +- .../src/main/webapp/table.jsp | 246 +- .../src/main/webapp/taglib/copyright.jsp | 4 +- .../src/main/webapp/taglib/helloTag.jsp | 4 +- .../src/main/webapp/taglib/helloTag2.jsp | 4 +- .../src/main/webapp/taglib/helloTag3.jsp | 4 +- .../src/main/webapp/test.jsp | 55 +- .../src/main/webapp/toLowerCase.jsp | 6 +- .../src/main/webapp/toUpperCase.jsp | 8 +- .../dunwu/javaee/server/JettyFactory.java | 66 +- .../dunwu/javaee/server/QuickStartServer.java | 3 +- .../src/test/resources/jetty/webdefault.xml | 910 +- .../javaee-tutorial-websocket/pom.xml | 227 +- .../dunwu/javaee/servlet/SocketServlet.java | 18 +- .../javaee/websocket/WebSocketServer.java | 23 +- .../src/main/resources/logback.xml | 64 +- .../src/main/webapp/WEB-INF/web.xml | 32 +- .../src/main/webapp/websocketA.html | 50 +- .../src/main/webapp/websocketB.html | 50 +- .../github/dunwu/javaee/server/DevServer.java | 114 +- .../src/test/resources/jetty/webdefault.xml | 910 +- .../src/test/resources/logback.xml | 64 +- codes/javaee-tutorial/pom.xml | 222 +- .../javaweb-mq-kafka-spring-boot/pom.xml | 96 +- .../github/dunwu/javaweb/KafkaConsumer.java | 2 +- .../github/dunwu/javaweb/KafkaProducer.java | 2 +- .../javaweb/config/KafkaConsumerConfig.java | 35 +- .../javaweb/config/KafkaProducerConfig.java | 29 +- .../src/main/resources/application.properties | 24 +- .../src/main/resources/logback.xml | 20 +- .../dunwu/javaweb/KafkaProducerTest.java | 2 +- .../javaweb-mq/javaweb-mq-kafka/pom.xml | 63 +- .../dunwu/javaweb/kafka/ConsumerAOC.java | 9 +- .../dunwu/javaweb/kafka/ConsumerManual.java | 15 +- .../kafka/ConsumerManualPartition.java | 14 +- .../dunwu/javaweb/kafka/ProducerDemo.java | 13 +- .../javaweb/kafka/ProducerInTransaction.java | 97 +- .../dunwu/javaweb/kafka/StreamDemo.java | 11 +- .../src/main/resources/logback.xml | 86 +- .../javaweb-mq/javaweb-mq-rocketmq/pom.xml | 53 +- .../dunwu/javaweb/rocketmq/AsyncProducer.java | 2 +- .../dunwu/javaweb/rocketmq/BatchProducer.java | 10 +- .../javaweb/rocketmq/BroadcastConsumer.java | 3 +- .../javaweb/rocketmq/BroadcastProducer.java | 2 +- .../javaweb/rocketmq/OnewayProducer.java | 3 +- .../javaweb/rocketmq/OrderedConsumer.java | 15 +- .../javaweb/rocketmq/OrderedProducer.java | 7 +- .../rocketmq/ScheduledMessageConsumer.java | 7 +- .../dunwu/javaweb/rocketmq/SyncProducer.java | 2 +- .../src/main/resources/logback.xml | 88 +- codes/javaweb/javaweb-mq/pom.xml | 22 +- .../javaweb-rpc/javaweb-rpc-zookeeper/pom.xml | 42 +- .../dunwu/javaweb/zookeeper/ZKConnection.java | 7 +- .../dunwu/javaweb/zookeeper/ZKCreate.java | 18 +- .../dunwu/javaweb/zookeeper/ZKDelete.java | 18 +- .../dunwu/javaweb/zookeeper/ZKExists.java | 19 +- .../javaweb/zookeeper/ZKGetChildren.java | 24 +- .../dunwu/javaweb/zookeeper/ZKGetData.java | 37 +- .../dunwu/javaweb/zookeeper/ZKSetData.java | 18 +- codes/javaweb/javaweb-rpc/pom.xml | 18 +- codes/javaweb/pom.xml | 20 +- 494 files changed, 20114 insertions(+), 20127 deletions(-) diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/pom.xml b/codes/javaee-tutorial/javaee-tutorial-filter/pom.xml index 309ad0f7..13461775 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/pom.xml +++ b/codes/javaee-tutorial/javaee-tutorial-filter/pom.xml @@ -1,113 +1,113 @@ - 4.0.0 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 - io.github.dunwu - javaee-tutorial-filter - 1.0.0 - war - javaee-tutorial-filter - JavaEE 学习笔记之 Filter + io.github.dunwu + javaee-tutorial-filter + 1.0.0 + war + javaee-tutorial-filter + JavaEE 学习笔记之 Filter - - UTF-8 - 1.7 - ${java.version} - ${java.version} - + + UTF-8 + 1.7 + ${java.version} + ${java.version} + - - - - ch.qos.logback - logback-classic - - - ch.qos.logback - logback-core - - - org.slf4j - jcl-over-slf4j - - + + + + ch.qos.logback + logback-classic + + + ch.qos.logback + logback-core + + + org.slf4j + jcl-over-slf4j + + - - - net.coobird - thumbnailator - - + + + net.coobird + thumbnailator + + - - - commons-fileupload - commons-fileupload - - - commons-io - commons-io - - - org.apache.commons - commons-lang3 - - + + + commons-fileupload + commons-fileupload + + + commons-io + commons-io + + + org.apache.commons + commons-lang3 + + - - - javax.servlet - javax.servlet-api - provided - - + + + javax.servlet + javax.servlet-api + provided + + - - - org.eclipse.jetty - jetty-webapp - test - - - org.eclipse.jetty - jetty-server - test - - - org.eclipse.jetty - jetty-annotations - test - - - org.eclipse.jetty - apache-jsp - test - - + + + org.eclipse.jetty + jetty-webapp + test + + + org.eclipse.jetty + jetty-server + test + + + org.eclipse.jetty + jetty-annotations + test + + + org.eclipse.jetty + apache-jsp + test + + - - - junit - junit - test - - - org.assertj - assertj-core - test - - - + + + junit + junit + test + + + org.assertj + assertj-core + test + + + - - - - io.github.dunwu - dunwu-dependencies - 0.4.2-SNAPSHOT - pom - import - - - + + + + io.github.dunwu + dunwu-dependencies + 0.4.2-SNAPSHOT + pom + import + + + diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/CacheFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/CacheFilter.java index 4f24cd8c..22d245cd 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/CacheFilter.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/CacheFilter.java @@ -1,16 +1,13 @@ package io.github.dunwu.javaee.filter; import io.github.dunwu.javaee.filter.wrapper.CacheResponseWrapper; - -import javax.servlet.*; +import java.net.URLEncoder; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.*; -import java.net.URLEncoder; /** * @author
    Zhang Peng - * @date 2017/3/27. + * @since 2017/3/27. */ public class CacheFilter extends MyFilter { @@ -32,7 +29,7 @@ public void init(FilterConfig filterConfig) throws ServletException { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { + throws IOException, ServletException { logger.info("{} 开始做过滤处理", this.getClass().getName()); @@ -47,8 +44,9 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha // 请求的 URI String uri = httpServletRequest.getRequestURI(); - if (uri == null) + if (uri == null) { uri = ""; + } uri = uri.replace(httpServletRequest.getContextPath() + "/", ""); uri = uri.trim().length() == 0 ? "index.jsp" : uri; uri = httpServletRequest.getQueryString() == null ? uri : (uri + "?" + httpServletRequest.getQueryString()); @@ -59,7 +57,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha // 如果缓存文件不存在 或者已经超出缓存时间 则请求 Servlet if (!cacheFile.exists() || cacheFile.length() == 0 - || cacheFile.lastModified() < System.currentTimeMillis() - cacheTime) { + || cacheFile.lastModified() < System.currentTimeMillis() - cacheTime) { CacheResponseWrapper cacheResponse = new CacheResponseWrapper(httpServletResponse); diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/CharacterEncodingFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/CharacterEncodingFilter.java index ee90a3fa..545189fc 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/CharacterEncodingFilter.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/CharacterEncodingFilter.java @@ -1,15 +1,13 @@ package io.github.dunwu.javaee.filter; import io.github.dunwu.javaee.filter.wrapper.UploadRequestWrapper; -import org.apache.commons.lang3.StringUtils; - -import javax.servlet.*; -import javax.servlet.http.HttpServletRequest; import java.io.IOException; +import javax.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.StringUtils; /** * @author Zhang Peng - * @date 2017/3/27. + * @since 2017/3/27. */ public class CharacterEncodingFilter extends MyFilter { @@ -27,7 +25,7 @@ public void init(FilterConfig config) throws ServletException { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { + throws IOException, ServletException { logger.info("{} 开始做过滤处理", this.getClass().getName()); if (enabled || StringUtils.isNotBlank(characterEncoding)) { @@ -46,12 +44,11 @@ public void destroy() { } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { + throws IOException, ServletException { UploadRequestWrapper uploadRequest = new UploadRequestWrapper((HttpServletRequest) request); chain.doFilter(uploadRequest, response); - } public void init(FilterConfig filterConfig) throws ServletException { diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/ExceptionHandlerFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/ExceptionHandlerFilter.java index f5691d6d..aba7ca1a 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/ExceptionHandlerFilter.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/ExceptionHandlerFilter.java @@ -2,27 +2,25 @@ import io.github.dunwu.javaee.filter.exception.AccountException; import io.github.dunwu.javaee.filter.exception.BusinessException; - +import java.io.IOException; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; -import java.io.IOException; /** * @author Zhang Peng - * @date 2017/3/27. + * @since 2017/3/27. */ public class ExceptionHandlerFilter extends MyFilter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { + throws IOException, ServletException { try { chain.doFilter(request, response); - } - catch (Exception e) { + } catch (Exception e) { logger.info("{} 捕捉到异常", this.getClass().getName()); Throwable rootCause = e; @@ -39,11 +37,9 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha if (rootCause instanceof AccountException) { request.getRequestDispatcher("/views/jsp/accountException.jsp").forward(request, response); - } - else if (rootCause instanceof BusinessException) { + } else if (rootCause instanceof BusinessException) { request.getRequestDispatcher("/views/jsp/businessException.jsp").forward(request, response); - } - else { + } else { request.getRequestDispatcher("/views/jsp/exception.jsp").forward(request, response); } } diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/FilterImpl.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/FilterImpl.java index ade6b18b..be6e3ea6 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/FilterImpl.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/FilterImpl.java @@ -1,11 +1,10 @@ package io.github.dunwu.javaee.filter; -import javax.servlet.*; import java.io.IOException; /** * @author Zhang Peng - * @date 2017/3/27. + * @since 2017/3/27. */ public class FilterImpl implements Filter { @@ -19,14 +18,13 @@ public void init(FilterConfig filterConfig) throws ServletException { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { + throws IOException, ServletException { System.out.println("befor doFilter(). "); chain.doFilter(request, response); System.out.println("after doFitler(). "); - } @Override diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/GZipFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/GZipFilter.java index 0bd18889..96a39a07 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/GZipFilter.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/GZipFilter.java @@ -1,24 +1,23 @@ package io.github.dunwu.javaee.filter; import io.github.dunwu.javaee.filter.wrapper.GZipResponseWrapper; - +import java.io.IOException; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.IOException; /** * @author Zhang Peng - * @date 2017/3/27. + * @since 2017/3/27. */ public class GZipFilter extends MyFilter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { + throws IOException, ServletException { logger.info("{} 开始做过滤处理", this.getClass().getName()); @@ -36,9 +35,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha // 输出压缩数据 gzipResponse.finishResponse(); - - } - else { + } else { // 否则, 不压缩 chain.doFilter(httpServletRequest, httpServletResponse); } diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/ImageRedirectFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/ImageRedirectFilter.java index 6128c973..9fca074a 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/ImageRedirectFilter.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/ImageRedirectFilter.java @@ -1,22 +1,22 @@ package io.github.dunwu.javaee.filter; +import java.io.IOException; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.IOException; /** * @author Zhang Peng - * @date 2017/3/27. + * @since 2017/3/27. */ public class ImageRedirectFilter extends MyFilter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { + throws IOException, ServletException { logger.info("{} 开始做过滤处理", this.getClass().getName()); @@ -34,14 +34,11 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha if (referer == null || !referer.contains(httpServletRequest.getServerName())) { // 如果 链接地址来自其他网站,则返回错误图片 httpServletRequest.getRequestDispatcher("/views/images/error.gif").forward(httpServletRequest, - httpServletResponse); - - } - else { + httpServletResponse); + } else { // 图片正常显示 chain.doFilter(httpServletRequest, httpServletResponse); } - } } diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/LogFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/LogFilter.java index 75ef2236..c57eb31e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/LogFilter.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/LogFilter.java @@ -1,22 +1,22 @@ package io.github.dunwu.javaee.filter; +import java.io.IOException; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.IOException; /** * @author Zhang Peng - * @date 2017/3/27. + * @since 2017/3/27. */ public class LogFilter extends MyFilter { @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) - throws IOException, ServletException { + throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/MyFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/MyFilter.java index 0fd74875..33426b0f 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/MyFilter.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/MyFilter.java @@ -3,15 +3,13 @@ */ package io.github.dunwu.javaee.filter; +import java.io.IOException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.servlet.*; -import java.io.IOException; - /** * @author Zhang Peng - * @date 2017/3/27. + * @since 2017/3/27. */ public abstract class MyFilter implements Filter { @@ -28,7 +26,7 @@ public void init(FilterConfig filterConfig) throws ServletException { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { + throws IOException, ServletException { } @Override diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/OutputReplaceFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/OutputReplaceFilter.java index c6f696e8..66a881c9 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/OutputReplaceFilter.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/OutputReplaceFilter.java @@ -1,17 +1,15 @@ package io.github.dunwu.javaee.filter; import io.github.dunwu.javaee.filter.wrapper.HttpCharacterResponseWrapper; - -import javax.servlet.*; -import javax.servlet.http.HttpServletResponse; import java.io.FileInputStream; import java.io.IOException; import java.io.PrintWriter; import java.util.Properties; +import javax.servlet.http.HttpServletResponse; /** * @author Zhang Peng - * @date 2017/3/27. + * @since 2017/3/27. */ public class OutputReplaceFilter extends MyFilter { @@ -24,15 +22,14 @@ public void init(FilterConfig filterConfig) throws ServletException { String realPath = filterConfig.getServletContext().getRealPath(file); try { properties.load(new FileInputStream(realPath)); - } - catch (IOException e) { + } catch (IOException e) { e.printStackTrace(); } } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { + throws IOException, ServletException { logger.info("{} 开始做过滤处理", this.getClass().getName()); // 自定义的 response diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/PrivilegeFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/PrivilegeFilter.java index b3bc5755..293daf5a 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/PrivilegeFilter.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/PrivilegeFilter.java @@ -1,16 +1,14 @@ package io.github.dunwu.javaee.filter; import io.github.dunwu.javaee.filter.exception.AccountException; - -import javax.servlet.*; -import javax.servlet.http.HttpServletRequest; import java.io.FileInputStream; import java.io.IOException; import java.util.Properties; +import javax.servlet.http.HttpServletRequest; /** * @author Zhang Peng - * @date 2017/3/27. + * @since 2017/3/27. */ public class PrivilegeFilter extends MyFilter { @@ -24,15 +22,14 @@ public void init(FilterConfig config) throws ServletException { String realPath = config.getServletContext().getRealPath(file); try { pp.load(new FileInputStream(realPath)); - } - catch (Exception e) { + } catch (Exception e) { config.getServletContext().log("读取权限控制文件失败。", e); } } @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) - throws IOException, ServletException { + throws IOException, ServletException { logger.info("{} 开始做过滤处理", this.getClass().getName()); diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/UploadFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/UploadFilter.java index 231008a6..3ae65193 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/UploadFilter.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/UploadFilter.java @@ -1,13 +1,12 @@ package io.github.dunwu.javaee.filter; import io.github.dunwu.javaee.filter.wrapper.UploadRequestWrapper; - +import java.io.IOException; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; -import java.io.IOException; /** * @author Zhang Peng @@ -17,7 +16,7 @@ public class UploadFilter extends MyFilter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { + throws IOException, ServletException { UploadRequestWrapper uploadRequest = new UploadRequestWrapper((HttpServletRequest) request); chain.doFilter(uploadRequest, response); } diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/WaterMarkFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/WaterMarkFilter.java index 66f2efef..a5c6166a 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/WaterMarkFilter.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/WaterMarkFilter.java @@ -1,15 +1,13 @@ package io.github.dunwu.javaee.filter; import io.github.dunwu.javaee.filter.wrapper.WaterMarkResponseWrapper; - -import javax.servlet.*; +import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.IOException; /** * @author Zhang Peng - * @date 2017/3/27. + * @since 2017/3/27. */ public class WaterMarkFilter extends MyFilter { @@ -25,7 +23,7 @@ public void init(FilterConfig filterConfig) throws ServletException { @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) - throws IOException, ServletException { + throws IOException, ServletException { logger.info("{} 开始做过滤处理", this.getClass().getName()); HttpServletRequest request = (HttpServletRequest) req; diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/XSLTFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/XSLTFilter.java index 1fe94744..96bd240f 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/XSLTFilter.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/XSLTFilter.java @@ -1,6 +1,8 @@ package io.github.dunwu.javaee.filter; -import javax.servlet.*; +import java.io.CharArrayWriter; +import java.io.IOException; +import java.io.PrintWriter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.transform.Source; @@ -8,13 +10,10 @@ import javax.xml.transform.TransformerFactory; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; -import java.io.CharArrayWriter; -import java.io.IOException; -import java.io.PrintWriter; /** * @author Zhang Peng - * @date 2017/3/28. + * @since 2017/3/28. */ public class XSLTFilter extends MyFilter { @@ -28,7 +27,7 @@ public void init(FilterConfig filterConfig) throws ServletException { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { + throws IOException, ServletException { logger.info("{} 开始做过滤处理", this.getClass().getName()); @@ -40,7 +39,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha // 请求的 xml 文件 Source xmlSource = new StreamSource(servletContext - .getRealPath(httpServletRequest.getRequestURI().replace(httpServletRequest.getContextPath() + "", ""))); + .getRealPath(httpServletRequest.getRequestURI().replace(httpServletRequest.getContextPath() + "", ""))); try { // 转换器工厂 @@ -61,9 +60,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha httpServletResponse.setContentLength(charArrayWriter.toString().length()); PrintWriter out = httpServletResponse.getWriter(); out.write(charArrayWriter.toString()); - - } - catch (Exception e) { + } catch (Exception e) { e.printStackTrace(); } } diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/test/Download.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/test/Download.java index e45591ee..26c115bf 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/test/Download.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/test/Download.java @@ -7,6 +7,10 @@ public class Download { + public static void main(String[] args) throws Exception { + System.out.println(getContent("http://localhost:8080/filter/book/thinkInJava.xml")); + } + public static String getContent(String url) throws Exception { URL r = new URL(url); @@ -29,8 +33,4 @@ public static String getContent(String url) throws Exception { return buffer.toString(); } - public static void main(String[] args) throws Exception { - System.out.println(getContent("http://localhost:8080/filter/book/thinkInJava.xml")); - } - } diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/test/GZipTest.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/test/GZipTest.java index cdcd6d62..9b4899aa 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/test/GZipTest.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/test/GZipTest.java @@ -6,6 +6,12 @@ public class GZipTest { + public static void main(String[] args) throws Exception { + test("http://localhost:8080/filter/dojo/dojo.js"); + test("http://localhost:8080/filter/image.jsp"); + test("http://localhost:8080/filter/winter.jpg"); + } + public static void test(String url) throws Exception { /** 支持 GZIP 的连接 */ @@ -21,14 +27,8 @@ public static void test(String url) throws Exception { System.out.println("网址: " + url); System.out.println("压缩后: " + lengthGzip + " byte, \t压缩前: " + lengthCommon + " byte, \t比率: " - + NumberFormat.getPercentInstance().format(rate)); + + NumberFormat.getPercentInstance().format(rate)); System.out.println(); } - public static void main(String[] args) throws Exception { - test("http://localhost:8080/filter/dojo/dojo.js"); - test("http://localhost:8080/filter/image.jsp"); - test("http://localhost:8080/filter/winter.jpg"); - } - } diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/CacheResponseWrapper.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/CacheResponseWrapper.java index dc666ad3..79d8bf0c 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/CacheResponseWrapper.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/CacheResponseWrapper.java @@ -1,14 +1,14 @@ package io.github.dunwu.javaee.filter.wrapper; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpServletResponseWrapper; import java.io.CharArrayWriter; import java.io.IOException; import java.io.PrintWriter; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; /** * @author Zhang Peng - * @date 2017/3/27. + * @since 2017/3/27. */ public class CacheResponseWrapper extends HttpServletResponseWrapper { diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/GZipOutputStream.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/GZipOutputStream.java index b9f74bf5..48e1adc1 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/GZipOutputStream.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/GZipOutputStream.java @@ -1,15 +1,15 @@ package io.github.dunwu.javaee.filter.wrapper; -import javax.servlet.ServletOutputStream; -import javax.servlet.WriteListener; -import javax.servlet.http.HttpServletResponse; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.zip.GZIPOutputStream; +import javax.servlet.ServletOutputStream; +import javax.servlet.WriteListener; +import javax.servlet.http.HttpServletResponse; /** * @author Zhang Peng - * @date 2017/3/27. + * @since 2017/3/27. */ public class GZipOutputStream extends ServletOutputStream { diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/GZipResponseWrapper.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/GZipResponseWrapper.java index 0d24b2ac..f230b860 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/GZipResponseWrapper.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/GZipResponseWrapper.java @@ -1,15 +1,15 @@ package io.github.dunwu.javaee.filter.wrapper; -import javax.servlet.ServletOutputStream; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpServletResponseWrapper; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; /** * @author Zhang Peng - * @date 2017/3/27. + * @since 2017/3/27. */ public class GZipResponseWrapper extends HttpServletResponseWrapper { @@ -28,14 +28,16 @@ public GZipResponseWrapper(HttpServletResponse response) throws IOException { } public ServletOutputStream getOutputStream() throws IOException { - if (gzipOutputStream == null) + if (gzipOutputStream == null) { gzipOutputStream = new GZipOutputStream(response); + } return gzipOutputStream; } public PrintWriter getWriter() throws IOException { - if (writer == null) + if (writer == null) { writer = new PrintWriter(new OutputStreamWriter(new GZipOutputStream(response), "UTF-8")); + } return writer; } @@ -48,10 +50,12 @@ public void flushBuffer() throws IOException { } public void finishResponse() throws IOException { - if (gzipOutputStream != null) + if (gzipOutputStream != null) { gzipOutputStream.close(); - if (writer != null) + } + if (writer != null) { writer.close(); + } } } diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/HttpCharacterResponseWrapper.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/HttpCharacterResponseWrapper.java index 3e1a0a7f..d9798ee2 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/HttpCharacterResponseWrapper.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/HttpCharacterResponseWrapper.java @@ -1,14 +1,14 @@ package io.github.dunwu.javaee.filter.wrapper; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpServletResponseWrapper; import java.io.CharArrayWriter; import java.io.IOException; import java.io.PrintWriter; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; /** * @author Zhang Peng - * @date 2017/3/27. + * @since 2017/3/27. */ public class HttpCharacterResponseWrapper extends HttpServletResponseWrapper { diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/UploadRequestWrapper.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/UploadRequestWrapper.java index 12285e1d..612bf7c2 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/UploadRequestWrapper.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/UploadRequestWrapper.java @@ -1,10 +1,5 @@ package io.github.dunwu.javaee.filter.wrapper; -import org.apache.commons.fileupload.DiskFileUpload; -import org.apache.commons.fileupload.FileItem; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletRequestWrapper; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStream; @@ -12,10 +7,14 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import org.apache.commons.fileupload.DiskFileUpload; +import org.apache.commons.fileupload.FileItem; /** * @author Zhang Peng - * @date 2017/3/27. + * @since 2017/3/27. */ public class UploadRequestWrapper extends HttpServletRequestWrapper { @@ -34,7 +33,7 @@ public UploadRequestWrapper(HttpServletRequest request) { // 判断是否为上传文件 multipart = request.getHeader(MULTIPART_HEADER) != null - && request.getHeader(MULTIPART_HEADER).startsWith("multipart/form-data"); + && request.getHeader(MULTIPART_HEADER).startsWith("multipart/form-data"); if (multipart) { @@ -46,7 +45,7 @@ public UploadRequestWrapper(HttpServletRequest request) { // 解析,获得所有的文本域与文件域 List fileItems = upload.parseRequest(request); - for (Iterator it = fileItems.iterator(); it.hasNext();) { + for (Iterator it = fileItems.iterator(); it.hasNext(); ) { // 遍历 FileItem item = it.next(); @@ -54,9 +53,7 @@ public UploadRequestWrapper(HttpServletRequest request) { // 如果是文本域,直接放到map里 params.put(item.getFieldName(), item.getString("utf8")); - - } - else { + } else { // 否则,为文件,先获取文件名称 String filename = item.getName().replace("\\", "/"); @@ -74,9 +71,7 @@ public UploadRequestWrapper(HttpServletRequest request) { params.put(item.getFieldName(), file); } } - - } - catch (Exception e) { + } catch (Exception e) { e.printStackTrace(); } } @@ -85,7 +80,6 @@ public UploadRequestWrapper(HttpServletRequest request) { public static void main(String[] args) { System.out.println(System.getProperties().toString().replace(", ", "\r\n")); - } @Override diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/WaterMarkOutputStream.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/WaterMarkOutputStream.java index 5d61453c..b94322c4 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/WaterMarkOutputStream.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/WaterMarkOutputStream.java @@ -1,13 +1,13 @@ package io.github.dunwu.javaee.filter.wrapper; -import javax.servlet.ServletOutputStream; -import javax.servlet.WriteListener; import java.io.ByteArrayOutputStream; import java.io.IOException; +import javax.servlet.ServletOutputStream; +import javax.servlet.WriteListener; /** * @author Zhang Peng - * @date 2017/3/27. + * @since 2017/3/27. */ public class WaterMarkOutputStream extends ServletOutputStream { diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/WaterMarkResponseWrapper.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/WaterMarkResponseWrapper.java index 3fe0d38e..5f57cca4 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/WaterMarkResponseWrapper.java +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/WaterMarkResponseWrapper.java @@ -1,19 +1,18 @@ package io.github.dunwu.javaee.filter.wrapper; -import net.coobird.thumbnailator.Thumbnails; -import net.coobird.thumbnailator.geometry.Positions; - +import java.awt.image.BufferedImage; +import java.io.FileInputStream; +import java.io.IOException; import javax.imageio.ImageIO; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; -import java.awt.image.BufferedImage; -import java.io.FileInputStream; -import java.io.IOException; +import net.coobird.thumbnailator.Thumbnails; +import net.coobird.thumbnailator.geometry.Positions; /** * @author Zhang Peng - * @date 2017/3/27. + * @since 2017/3/27. */ public class WaterMarkResponseWrapper extends HttpServletResponseWrapper { @@ -29,7 +28,7 @@ public class WaterMarkResponseWrapper extends HttpServletResponseWrapper { private WaterMarkOutputStream waterMarkOutputStream; public WaterMarkResponseWrapper(HttpServletResponse response, String originFile, String waterMarkFile) - throws IOException { + throws IOException { super(response); this.response = response; this.originFile = originFile; diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/resources/logback.xml index c64f0d73..5f9e7061 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/resources/logback.xml +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/resources/logback.xml @@ -2,44 +2,44 @@ - - - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n - - - - - - - - logs/${FILE_NAME}-all.%d{yyyy-MM-dd}.log - 30 - - - - - 30MB - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n - - - - - - - - - - - - - - - + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n + + + + + + + + logs/${FILE_NAME}-all.%d{yyyy-MM-dd}.log + 30 + + + + + 30MB + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n + + + + + + + + + + + + + + + diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/WEB-INF/privilege.properties b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/WEB-INF/privilege.properties index 164833d5..ab78f73c 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/WEB-INF/privilege.properties +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/WEB-INF/privilege.properties @@ -1,8 +1,8 @@ # Privilege Settings -admin.do?action\=* = administrator -log.do?action\=* = administrator -list.do?action\=add = member -list.do?action\=delete = member -list.do?action\=save = member -list.do?action\=view = guest -list.do?action\=list = guest +admin.do?action\=*=administrator +log.do?action\=*=administrator +list.do?action\=add=member +list.do?action\=delete=member +list.do?action\=save=member +list.do?action\=view=guest +list.do?action\=list=guest diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/WEB-INF/sensitive.properties b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/WEB-INF/sensitive.properties index 0b0b749f..cdc23204 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/WEB-INF/sensitive.properties +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/WEB-INF/sensitive.properties @@ -1,7 +1,7 @@ # \u81ea\u52a8\u66f4\u6b63 -Chna = China -www.baidu.com.cn = www.baidu.com +Chna=China +www.baidu.com.cn=www.baidu.com # \u81ea\u52a8\u66ff\u6362 -\u8272\u60c5 = ** -\u60c5\u8272 = ** -\u8d4c\u535a = ** +\u8272\u60c5=** +\u60c5\u8272=** +\u8d4c\u535a=** diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/WEB-INF/web.xml b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/WEB-INF/web.xml index d350afae..4a7c507e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/WEB-INF/web.xml +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/WEB-INF/web.xml @@ -1,182 +1,182 @@ - javaee-filter - - - - index.jsp - /views/jsp/index.jsp - - - index.jsp - / - - - - dispatcherServlet - /views/jsp/dispatcher.jsp - - - dispatcherServlet - *.do - - - - - - CacheFilter - - io.github.dunwu.javaee.filter.CacheFilter - - - cache - true - - - cacheTime - 1000000 - - - - CacheFilter - *.jsp - *.html - *.do - REQUEST - - - - characterEncodingFilter - - io.github.dunwu.javaee.filter.CharacterEncodingFilter - - - characterEncoding - UTF-8 - - - enable - true - - - - characterEncodingFilter - /* - - - - ExceptionHandlerFilter - - io.github.dunwu.javaee.filter.ExceptionHandlerFilter - - - - ExceptionHandlerFilter - /* - - - - GZipFilter - io.github.dunwu.javaee.filter.GZipFilter - - - GZipFilter - /* - - - - ImageRedirectFilter - - io.github.dunwu.javaee.filter.ImageRedirectFilter - - - - ImageRedirectFilter - /views/images/* - - - - LogFilter - io.github.dunwu.javaee.filter.LogFilter - - - LogFilter - /* - - - - OutputReplaceFilter - - io.github.dunwu.javaee.filter.OutputReplaceFilter - - - file - /WEB-INF/sensitive.properties - - - - OutputReplaceFilter - *.jsp - - - - UploadFilter - io.github.dunwu.javaee.filter.UploadFilter - - - UploadFilter - /* - - - - PrivilegeFilter - - io.github.dunwu.javaee.filter.PrivilegeFilter - - - file - /WEB-INF/privilege.properties - - - - PrivilegeFilter - *.do - - - - WaterMarkFilter - - io.github.dunwu.javaee.filter.WaterMarkFilter - - - waterMarkFile - /WEB-INF/logo.png - - - - WaterMarkFilter - *.jpg - *.png - *.bmp - - - - XSLTFilter - io.github.dunwu.javaee.filter.XSLTFilter - - - XSLTFilter - /views/xml/* - - - - - /views/jsp/index.html - /views/jsp/index.htm - /views/jsp/index.jsp - + javaee-filter + + + + index.jsp + /views/jsp/index.jsp + + + index.jsp + / + + + + dispatcherServlet + /views/jsp/dispatcher.jsp + + + dispatcherServlet + *.do + + + + + + CacheFilter + + io.github.dunwu.javaee.filter.CacheFilter + + + cache + true + + + cacheTime + 1000000 + + + + CacheFilter + *.jsp + *.html + *.do + REQUEST + + + + characterEncodingFilter + + io.github.dunwu.javaee.filter.CharacterEncodingFilter + + + characterEncoding + UTF-8 + + + enable + true + + + + characterEncodingFilter + /* + + + + ExceptionHandlerFilter + + io.github.dunwu.javaee.filter.ExceptionHandlerFilter + + + + ExceptionHandlerFilter + /* + + + + GZipFilter + io.github.dunwu.javaee.filter.GZipFilter + + + GZipFilter + /* + + + + ImageRedirectFilter + + io.github.dunwu.javaee.filter.ImageRedirectFilter + + + + ImageRedirectFilter + /views/images/* + + + + LogFilter + io.github.dunwu.javaee.filter.LogFilter + + + LogFilter + /* + + + + OutputReplaceFilter + + io.github.dunwu.javaee.filter.OutputReplaceFilter + + + file + /WEB-INF/sensitive.properties + + + + OutputReplaceFilter + *.jsp + + + + UploadFilter + io.github.dunwu.javaee.filter.UploadFilter + + + UploadFilter + /* + + + + PrivilegeFilter + + io.github.dunwu.javaee.filter.PrivilegeFilter + + + file + /WEB-INF/privilege.properties + + + + PrivilegeFilter + *.do + + + + WaterMarkFilter + + io.github.dunwu.javaee.filter.WaterMarkFilter + + + waterMarkFile + /WEB-INF/logo.png + + + + WaterMarkFilter + *.jpg + *.png + *.bmp + + + + XSLTFilter + io.github.dunwu.javaee.filter.XSLTFilter + + + XSLTFilter + /views/xml/* + + + + + /views/jsp/index.html + /views/jsp/index.htm + /views/jsp/index.jsp + diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/js/dojo.js b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/js/dojo.js index 16acf30b..d5a0fc94 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/js/dojo.js +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/js/dojo.js @@ -18,6715 +18,6795 @@ */ if (typeof dojo == 'undefined') { - var dj_global = this - var dj_currentContext = this + var dj_global = this; + var dj_currentContext = this; - function dj_undef(_1, _2) { - return (typeof (_2 || dj_currentContext)[_1] == 'undefined') - } + function dj_undef(_1, _2) { + return (typeof (_2 || dj_currentContext)[_1] == 'undefined') + } - if (dj_undef('djConfig', this)) { - var djConfig = {} - } - if (dj_undef('dojo', this)) { - var dojo = {} - } - dojo.global = function() { - return dj_currentContext - } - dojo.locale = djConfig.locale - dojo.version = { - major: 0, - minor: 4, - patch: 3, - flag: '-20070622', - revision: Number('$Rev: 8617 $'.match(/[0-9]+/)[0]), - toString: function() { - with (dojo.version) { - return major + '.' + minor + '.' + patch + flag + ' (' + revision + ')' - } - } - } - dojo.evalProp = function(_3, _4, _5) { - if ((!_4) || (!_3)) { - return undefined - } - if (!dj_undef(_3, _4)) { - return _4[_3] - } - return (_5 ? (_4[_3] = {}) : undefined) - } - dojo.parseObjPath = function(_6, _7, _8) { - var _9 = (_7 || dojo.global()) - var _a = _6.split('.') - var _b = _a.pop() - for (var i = 0, l = _a.length; i < l && _9; i++) { - _9 = dojo.evalProp(_a[i], _9, _8) - } - return { obj: _9, prop: _b } - } - dojo.evalObjPath = function(_e, _f) { - if (typeof _e != 'string') { - return dojo.global() - } - if (_e.indexOf('.') == -1) { - return dojo.evalProp(_e, dojo.global(), _f) - } - var ref = dojo.parseObjPath(_e, dojo.global(), _f) - if (ref) { - return dojo.evalProp(ref.prop, ref.obj, _f) - } - return null - } - dojo.errorToString = function(_11) { - if (!dj_undef('message', _11)) { - return _11.message - } else { - if (!dj_undef('description', _11)) { - return _11.description - } else { - return _11 - } - } - } - dojo.raise = function(_12, _13) { - if (_13) { - _12 = _12 + ': ' + dojo.errorToString(_13) - } else { - _12 = dojo.errorToString(_12) - } - try { - if (djConfig.isDebug) { - dojo.hostenv.println('FATAL exception raised: ' + _12) - } - } catch (e) { - } - throw _13 || Error(_12) - } - dojo.debug = function() { - } - dojo.debugShallow = function(obj) { - } - dojo.profile = { - start: function() { - }, end: function() { - }, stop: function() { - }, dump: function() { - } - } + if (dj_undef('djConfig', this)) { + var djConfig = {} + } + if (dj_undef('dojo', this)) { + var dojo = {} + } + dojo.global = function () { + return dj_currentContext + }; + dojo.locale = djConfig.locale; + dojo.version = { + major: 0, + minor: 4, + patch: 3, + flag: '-20070622', + revision: Number('$Rev: 8617 $'.match(/[0-9]+/)[0]), + toString: function () { + with (dojo.version) { + return major + '.' + minor + '.' + patch + flag + ' (' + revision + ')' + } + } + }; + dojo.evalProp = function (_3, _4, _5) { + if ((!_4) || (!_3)) { + return undefined + } + if (!dj_undef(_3, _4)) { + return _4[_3] + } + return (_5 ? (_4[_3] = {}) : undefined) + }; + dojo.parseObjPath = function (_6, _7, _8) { + var _9 = (_7 || dojo.global()); + var _a = _6.split('.'); + var _b = _a.pop(); + for (var i = 0, l = _a.length; i < l && _9; i++) { + _9 = dojo.evalProp(_a[i], _9, _8) + } + return {obj: _9, prop: _b} + }; + dojo.evalObjPath = function (_e, _f) { + if (typeof _e != 'string') { + return dojo.global() + } + if (_e.indexOf('.') == -1) { + return dojo.evalProp(_e, dojo.global(), _f) + } + var ref = dojo.parseObjPath(_e, dojo.global(), _f); + if (ref) { + return dojo.evalProp(ref.prop, ref.obj, _f) + } + return null + }; + dojo.errorToString = function (_11) { + if (!dj_undef('message', _11)) { + return _11.message + } else { + if (!dj_undef('description', _11)) { + return _11.description + } else { + return _11 + } + } + }; + dojo.raise = function (_12, _13) { + if (_13) { + _12 = _12 + ': ' + dojo.errorToString(_13) + } else { + _12 = dojo.errorToString(_12) + } + try { + if (djConfig.isDebug) { + dojo.hostenv.println('FATAL exception raised: ' + _12) + } + } catch (e) { + } + throw _13 || Error(_12) + }; + dojo.debug = function () { + }; + dojo.debugShallow = function (obj) { + }; + dojo.profile = { + start: function () { + }, end: function () { + }, stop: function () { + }, dump: function () { + } + }; - function dj_eval(_15) { - return dj_global.eval ? dj_global.eval(_15) : eval(_15) - } + function dj_eval(_15) { + return dj_global.eval ? dj_global.eval(_15) : eval(_15) + } - dojo.unimplemented = function(_16, _17) { - var _18 = '\'' + _16 + '\' not implemented' - if (_17 != null) { - _18 += ' ' + _17 - } - dojo.raise(_18) - } - dojo.deprecated = function(_19, _1a, _1b) { - var _1c = 'DEPRECATED: ' + _19 - if (_1a) { - _1c += ' ' + _1a - } - if (_1b) { - _1c += ' -- will be removed in version: ' + _1b - } - dojo.debug(_1c) - } - dojo.render = (function() { - function vscaffold(_1d, _1e) { - var tmp = { capable: false, support: { builtin: false, plugin: false }, prefixes: _1d } - for (var i = 0; i < _1e.length; i++) { - tmp[_1e[i]] = false - } - return tmp - } + dojo.unimplemented = function (_16, _17) { + var _18 = '\'' + _16 + '\' not implemented'; + if (_17 != null) { + _18 += ' ' + _17 + } + dojo.raise(_18) + }; + dojo.deprecated = function (_19, _1a, _1b) { + var _1c = 'DEPRECATED: ' + _19; + if (_1a) { + _1c += ' ' + _1a + } + if (_1b) { + _1c += ' -- will be removed in version: ' + _1b + } + dojo.debug(_1c) + }; + dojo.render = (function () { + function vscaffold(_1d, _1e) { + var tmp = {capable: false, support: {builtin: false, plugin: false}, prefixes: _1d}; + for (var i = 0; i < _1e.length; i++) { + tmp[_1e[i]] = false + } + return tmp + } - return { - name: '', - ver: dojo.version, - os: { win: false, linux: false, osx: false }, - html: vscaffold(['html'], ['ie', 'opera', 'khtml', 'safari', 'moz']), - svg: vscaffold(['svg'], ['corel', 'adobe', 'batik']), - vml: vscaffold(['vml'], ['ie']), - swf: vscaffold(['Swf', 'Flash', 'Mm'], ['mm']), - swt: vscaffold(['Swt'], ['ibm']) - } - })() - dojo.hostenv = (function() { - var _21 = { - isDebug: false, - allowQueryConfig: false, - baseScriptUri: '', - baseRelativePath: '', - libraryScriptUri: '', - iePreventClobber: false, - ieClobberMinimal: true, - preventBackButtonFix: true, - delayMozLoadingFix: false, - searchIds: [], - parseWidgets: true - } - if (typeof djConfig == 'undefined') { - djConfig = _21 - } else { - for (var _22 in _21) { - if (typeof djConfig[_22] == 'undefined') { - djConfig[_22] = _21[_22] - } - } - } - return { - name_: '(unset)', version_: '(unset)', getName: function() { - return this.name_ - }, getVersion: function() { - return this.version_ - }, getText: function(uri) { - dojo.unimplemented('getText', 'uri=' + uri) - } - } - })() - dojo.hostenv.getBaseScriptUri = function() { - if (djConfig.baseScriptUri.length) { - return djConfig.baseScriptUri - } - var uri = new String(djConfig.libraryScriptUri || djConfig.baseRelativePath) - if (!uri) { - dojo.raise('Nothing returned by getLibraryScriptUri(): ' + uri) - } - var _25 = uri.lastIndexOf('/') - djConfig.baseScriptUri = djConfig.baseRelativePath - return djConfig.baseScriptUri - }; - (function() { - var _26 = { - pkgFileName: '__package__', - loading_modules_: {}, - loaded_modules_: {}, - addedToLoadingCount: [], - removedFromLoadingCount: [], - inFlightCount: 0, - modulePrefixes_: { dojo: { name: 'dojo', value: 'src' } }, - setModulePrefix: function(_27, _28) { - this.modulePrefixes_[_27] = { name: _27, value: _28 } - }, - moduleHasPrefix: function(_29) { - var mp = this.modulePrefixes_ - return Boolean(mp[_29] && mp[_29].value) - }, - getModulePrefix: function(_2b) { - if (this.moduleHasPrefix(_2b)) { - return this.modulePrefixes_[_2b].value - } - return _2b - }, - getTextStack: [], - loadUriStack: [], - loadedUris: [], - post_load_: false, - modulesLoadedListeners: [], - unloadListeners: [], - loadNotifying: false - } - for (var _2c in _26) { - dojo.hostenv[_2c] = _26[_2c] - } - })() - dojo.hostenv.loadPath = function(_2d, _2e, cb) { - var uri - if (_2d.charAt(0) == '/' || _2d.match(/^\w+:/)) { - uri = _2d - } else { - uri = this.getBaseScriptUri() + _2d - } - if (djConfig.cacheBust && dojo.render.html.capable) { - uri += '?' + String(djConfig.cacheBust).replace(/\W+/g, '') - } - try { - return !_2e ? this.loadUri(uri, cb) : this.loadUriAndCheck(uri, _2e, cb) - } catch (e) { - dojo.debug(e) - return false - } - } - dojo.hostenv.loadUri = function(uri, cb) { - if (this.loadedUris[uri]) { - return true - } - var _33 = this.getText(uri, null, true) - if (!_33) { - return false - } - this.loadedUris[uri] = true - if (cb) { - _33 = '(' + _33 + ')' - } - var _34 = dj_eval(_33) - if (cb) { - cb(_34) - } - return true - } - dojo.hostenv.loadUriAndCheck = function(uri, _36, cb) { - var ok = true - try { - ok = this.loadUri(uri, cb) - } catch (e) { - dojo.debug('failed loading ', uri, ' with error: ', e) - } - return Boolean(ok && this.findModule(_36, false)) - } - dojo.loaded = function() { - } - dojo.unloaded = function() { - } - dojo.hostenv.loaded = function() { - this.loadNotifying = true - this.post_load_ = true - var mll = this.modulesLoadedListeners - for (var x = 0; x < mll.length; x++) { - mll[x]() - } - this.modulesLoadedListeners = [] - this.loadNotifying = false - dojo.loaded() - } - dojo.hostenv.unloaded = function() { - var mll = this.unloadListeners - while (mll.length) { - (mll.pop())() - } - dojo.unloaded() - } - dojo.addOnLoad = function(obj, _3d) { - var dh = dojo.hostenv - if (arguments.length == 1) { - dh.modulesLoadedListeners.push(obj) - } else { - if (arguments.length > 1) { - dh.modulesLoadedListeners.push(function() { - obj[_3d]() - }) - } - } - if (dh.post_load_ && dh.inFlightCount == 0 && !dh.loadNotifying) { - dh.callLoaded() - } - } - dojo.addOnUnload = function(obj, _40) { - var dh = dojo.hostenv - if (arguments.length == 1) { - dh.unloadListeners.push(obj) - } else { - if (arguments.length > 1) { - dh.unloadListeners.push(function() { - obj[_40]() - }) - } - } - } - dojo.hostenv.modulesLoaded = function() { - if (this.post_load_) { - return - } - if (this.loadUriStack.length == 0 && this.getTextStack.length == 0) { - if (this.inFlightCount > 0) { - dojo.debug('files still in flight!') - return - } - dojo.hostenv.callLoaded() - } - } - dojo.hostenv.callLoaded = function() { - if (typeof setTimeout == 'object' || (djConfig['useXDomain'] && dojo.render.html.opera)) { - setTimeout('dojo.hostenv.loaded();', 0) - } else { - dojo.hostenv.loaded() - } - } - dojo.hostenv.getModuleSymbols = function(_42) { - var _43 = _42.split('.') - for (var i = _43.length; i > 0; i--) { - var _45 = _43.slice(0, i).join('.') - if ((i == 1) && !this.moduleHasPrefix(_45)) { - _43[0] = '../' + _43[0] - } else { - var _46 = this.getModulePrefix(_45) - if (_46 != _45) { - _43.splice(0, i, _46) - break - } - } - } - return _43 - } - dojo.hostenv._global_omit_module_check = false - dojo.hostenv.loadModule = function(_47, _48, _49) { - if (!_47) { - return - } - _49 = this._global_omit_module_check || _49 - var _4a = this.findModule(_47, false) - if (_4a) { - return _4a - } - if (dj_undef(_47, this.loading_modules_)) { - this.addedToLoadingCount.push(_47) - } - this.loading_modules_[_47] = 1 - var _4b = _47.replace(/\./g, '/') + '.js' - var _4c = _47.split('.') - var _4d = this.getModuleSymbols(_47) - var _4e = ((_4d[0].charAt(0) != '/') && !_4d[0].match(/^\w+:/)) - var _4f = _4d[_4d.length - 1] - var ok - if (_4f == '*') { - _47 = _4c.slice(0, -1).join('.') - while (_4d.length) { - _4d.pop() - _4d.push(this.pkgFileName) - _4b = _4d.join('/') + '.js' - if (_4e && _4b.charAt(0) == '/') { - _4b = _4b.slice(1) - } - ok = this.loadPath(_4b, !_49 ? _47 : null) - if (ok) { - break - } - _4d.pop() - } - } else { - _4b = _4d.join('/') + '.js' - _47 = _4c.join('.') - var _51 = !_49 ? _47 : null - ok = this.loadPath(_4b, _51) - if (!ok && !_48) { - _4d.pop() - while (_4d.length) { - _4b = _4d.join('/') + '.js' - ok = this.loadPath(_4b, _51) - if (ok) { - break - } - _4d.pop() - _4b = _4d.join('/') + '/' + this.pkgFileName + '.js' - if (_4e && _4b.charAt(0) == '/') { - _4b = _4b.slice(1) - } - ok = this.loadPath(_4b, _51) - if (ok) { - break - } - } - } - if (!ok && !_49) { - dojo.raise('Could not load \'' + _47 + '\'; last tried \'' + _4b + '\'') - } - } - if (!_49 && !this['isXDomain']) { - _4a = this.findModule(_47, false) - if (!_4a) { - dojo.raise('symbol \'' + _47 + '\' is not defined after loading \'' + _4b + '\'') - } - } - return _4a - } - dojo.hostenv.startPackage = function(_52) { - var _53 = String(_52) - var _54 = _53 - var _55 = _52.split(/\./) - if (_55[_55.length - 1] == '*') { - _55.pop() - _54 = _55.join('.') - } - var _56 = dojo.evalObjPath(_54, true) - this.loaded_modules_[_53] = _56 - this.loaded_modules_[_54] = _56 - return _56 - } - dojo.hostenv.findModule = function(_57, _58) { - var lmn = String(_57) - if (this.loaded_modules_[lmn]) { - return this.loaded_modules_[lmn] - } - if (_58) { - dojo.raise('no loaded module named \'' + _57 + '\'') - } - return null - } - dojo.kwCompoundRequire = function(_5a) { - var _5b = _5a['common'] || [] - var _5c = _5a[dojo.hostenv.name_] ? _5b.concat(_5a[dojo.hostenv.name_] || []) : _5b.concat(_5a['default'] || []) - for (var x = 0; x < _5c.length; x++) { - var _5e = _5c[x] - if (_5e.constructor == Array) { - dojo.hostenv.loadModule.apply(dojo.hostenv, _5e) - } else { - dojo.hostenv.loadModule(_5e) - } - } - } - dojo.require = function(_5f) { - dojo.hostenv.loadModule.apply(dojo.hostenv, arguments) - } - dojo.requireIf = function(_60, _61) { - var _62 = arguments[0] - if ((_62 === true) || (_62 == 'common') || (_62 && dojo.render[_62].capable)) { - var _63 = [] - for (var i = 1; i < arguments.length; i++) { - _63.push(arguments[i]) - } - dojo.require.apply(dojo, _63) - } - } - dojo.requireAfterIf = dojo.requireIf - dojo.provide = function(_65) { - return dojo.hostenv.startPackage.apply(dojo.hostenv, arguments) - } - dojo.registerModulePath = function(_66, _67) { - return dojo.hostenv.setModulePrefix(_66, _67) - } - if (djConfig['modulePaths']) { - for (var param in djConfig['modulePaths']) { - dojo.registerModulePath(param, djConfig['modulePaths'][param]) - } - } - dojo.setModulePrefix = function(_68, _69) { - dojo.deprecated('dojo.setModulePrefix("' + _68 + '", "' + _69 + '")', 'replaced by dojo.registerModulePath', '0.5') - return dojo.registerModulePath(_68, _69) - } - dojo.exists = function(obj, _6b) { - var p = _6b.split('.') - for (var i = 0; i < p.length; i++) { - if (!obj[p[i]]) { - return false - } - obj = obj[p[i]] - } - return true - } - dojo.hostenv.normalizeLocale = function(_6e) { - var _6f = _6e ? _6e.toLowerCase() : dojo.locale - if (_6f == 'root') { - _6f = 'ROOT' - } - return _6f - } - dojo.hostenv.searchLocalePath = function(_70, _71, _72) { - _70 = dojo.hostenv.normalizeLocale(_70) - var _73 = _70.split('-') - var _74 = [] - for (var i = _73.length; i > 0; i--) { - _74.push(_73.slice(0, i).join('-')) - } - _74.push(false) - if (_71) { - _74.reverse() - } - for (var j = _74.length - 1; j >= 0; j--) { - var loc = _74[j] || 'ROOT' - var _78 = _72(loc) - if (_78) { - break - } - } - } - dojo.hostenv.localesGenerated - dojo.hostenv.registerNlsPrefix = function() { - dojo.registerModulePath('nls', 'nls') - } - dojo.hostenv.preloadLocalizations = function() { - if (dojo.hostenv.localesGenerated) { - dojo.hostenv.registerNlsPrefix() + return { + name: '', + ver: dojo.version, + os: {win: false, linux: false, osx: false}, + html: vscaffold(['html'], ['ie', 'opera', 'khtml', 'safari', 'moz']), + svg: vscaffold(['svg'], ['corel', 'adobe', 'batik']), + vml: vscaffold(['vml'], ['ie']), + swf: vscaffold(['Swf', 'Flash', 'Mm'], ['mm']), + swt: vscaffold(['Swt'], ['ibm']) + } + })(); + dojo.hostenv = (function () { + var _21 = { + isDebug: false, + allowQueryConfig: false, + baseScriptUri: '', + baseRelativePath: '', + libraryScriptUri: '', + iePreventClobber: false, + ieClobberMinimal: true, + preventBackButtonFix: true, + delayMozLoadingFix: false, + searchIds: [], + parseWidgets: true + }; + if (typeof djConfig == 'undefined') { + djConfig = _21 + } else { + for (var _22 in _21) { + if (typeof djConfig[_22] == 'undefined') { + djConfig[_22] = _21[_22] + } + } + } + return { + name_: '(unset)', version_: '(unset)', getName: function () { + return this.name_ + }, getVersion: function () { + return this.version_ + }, getText: function (uri) { + dojo.unimplemented('getText', 'uri=' + uri) + } + } + })(); + dojo.hostenv.getBaseScriptUri = function () { + if (djConfig.baseScriptUri.length) { + return djConfig.baseScriptUri + } + var uri = String(djConfig.libraryScriptUri || djConfig.baseRelativePath); + if (!uri) { + dojo.raise('Nothing returned by getLibraryScriptUri(): ' + uri) + } + var _25 = uri.lastIndexOf('/'); + djConfig.baseScriptUri = djConfig.baseRelativePath; + return djConfig.baseScriptUri + }; + (function () { + var _26 = { + pkgFileName: '__package__', + loading_modules_: {}, + loaded_modules_: {}, + addedToLoadingCount: [], + removedFromLoadingCount: [], + inFlightCount: 0, + modulePrefixes_: {dojo: {name: 'dojo', value: 'src'}}, + setModulePrefix: function (_27, _28) { + this.modulePrefixes_[_27] = {name: _27, value: _28} + }, + moduleHasPrefix: function (_29) { + var mp = this.modulePrefixes_; + return Boolean(mp[_29] && mp[_29].value) + }, + getModulePrefix: function (_2b) { + if (this.moduleHasPrefix(_2b)) { + return this.modulePrefixes_[_2b].value + } + return _2b + }, + getTextStack: [], + loadUriStack: [], + loadedUris: [], + post_load_: false, + modulesLoadedListeners: [], + unloadListeners: [], + loadNotifying: false + }; + for (var _2c in _26) { + dojo.hostenv[_2c] = _26[_2c] + } + })(); + dojo.hostenv.loadPath = function (_2d, _2e, cb) { + var uri; + if (_2d.charAt(0) == '/' || _2d.match(/^\w+:/)) { + uri = _2d + } else { + uri = this.getBaseScriptUri() + _2d + } + if (djConfig.cacheBust && dojo.render.html.capable) { + uri += '?' + String(djConfig.cacheBust).replace(/\W+/g, '') + } + try { + return !_2e ? this.loadUri(uri, cb) : this.loadUriAndCheck(uri, _2e, cb) + } catch (e) { + dojo.debug(e); + return false + } + }; + dojo.hostenv.loadUri = function (uri, cb) { + if (this.loadedUris[uri]) { + return true + } + var _33 = this.getText(uri, null, true); + if (!_33) { + return false + } + this.loadedUris[uri] = true; + if (cb) { + _33 = '(' + _33 + ')' + } + var _34 = dj_eval(_33); + if (cb) { + cb(_34) + } + return true + }; + dojo.hostenv.loadUriAndCheck = function (uri, _36, cb) { + var ok = true; + try { + ok = this.loadUri(uri, cb) + } catch (e) { + dojo.debug('failed loading ', uri, ' with error: ', e) + } + return Boolean(ok && this.findModule(_36, false)) + }; + dojo.loaded = function () { + }; + dojo.unloaded = function () { + }; + dojo.hostenv.loaded = function () { + this.loadNotifying = true; + this.post_load_ = true; + var mll = this.modulesLoadedListeners; + for (var x = 0; x < mll.length; x++) { + mll[x]() + } + this.modulesLoadedListeners = []; + this.loadNotifying = false; + dojo.loaded() + }; + dojo.hostenv.unloaded = function () { + var mll = this.unloadListeners; + while (mll.length) { + (mll.pop())() + } + dojo.unloaded() + }; + dojo.addOnLoad = function (obj, _3d) { + var dh = dojo.hostenv; + if (arguments.length == 1) { + dh.modulesLoadedListeners.push(obj) + } else { + if (arguments.length > 1) { + dh.modulesLoadedListeners.push(function () { + obj[_3d]() + }) + } + } + if (dh.post_load_ && dh.inFlightCount == 0 && !dh.loadNotifying) { + dh.callLoaded() + } + }; + dojo.addOnUnload = function (obj, _40) { + var dh = dojo.hostenv; + if (arguments.length == 1) { + dh.unloadListeners.push(obj) + } else { + if (arguments.length > 1) { + dh.unloadListeners.push(function () { + obj[_40]() + }) + } + } + }; + dojo.hostenv.modulesLoaded = function () { + if (this.post_load_) { + return + } + if (this.loadUriStack.length == 0 && this.getTextStack.length == 0) { + if (this.inFlightCount > 0) { + dojo.debug('files still in flight!'); + return + } + dojo.hostenv.callLoaded() + } + }; + dojo.hostenv.callLoaded = function () { + if (typeof setTimeout == 'object' || (djConfig['useXDomain'] && dojo.render.html.opera)) { + setTimeout('dojo.hostenv.loaded();', 0) + } else { + dojo.hostenv.loaded() + } + }; + dojo.hostenv.getModuleSymbols = function (_42) { + var _43 = _42.split('.'); + for (var i = _43.length; i > 0; i--) { + var _45 = _43.slice(0, i).join('.'); + if ((i == 1) && !this.moduleHasPrefix(_45)) { + _43[0] = '../' + _43[0] + } else { + var _46 = this.getModulePrefix(_45); + if (_46 != _45) { + _43.splice(0, i, _46); + break + } + } + } + return _43 + }; + dojo.hostenv._global_omit_module_check = false; + dojo.hostenv.loadModule = function (_47, _48, _49) { + if (!_47) { + return + } + _49 = this._global_omit_module_check || _49; + var _4a = this.findModule(_47, false); + if (_4a) { + return _4a + } + if (dj_undef(_47, this.loading_modules_)) { + this.addedToLoadingCount.push(_47) + } + this.loading_modules_[_47] = 1; + var _4b = _47.replace(/\./g, '/') + '.js'; + var _4c = _47.split('.'); + var _4d = this.getModuleSymbols(_47); + var _4e = ((_4d[0].charAt(0) != '/') && !_4d[0].match(/^\w+:/)); + var _4f = _4d[_4d.length - 1]; + var ok; + if (_4f == '*') { + _47 = _4c.slice(0, -1).join('.'); + while (_4d.length) { + _4d.pop(); + _4d.push(this.pkgFileName); + _4b = _4d.join('/') + '.js'; + if (_4e && _4b.charAt(0) == '/') { + _4b = _4b.slice(1) + } + ok = this.loadPath(_4b, !_49 ? _47 : null); + if (ok) { + break + } + _4d.pop() + } + } else { + _4b = _4d.join('/') + '.js'; + _47 = _4c.join('.'); + var _51 = !_49 ? _47 : null; + ok = this.loadPath(_4b, _51); + if (!ok && !_48) { + _4d.pop(); + while (_4d.length) { + _4b = _4d.join('/') + '.js'; + ok = this.loadPath(_4b, _51); + if (ok) { + break + } + _4d.pop(); + _4b = _4d.join('/') + '/' + this.pkgFileName + '.js'; + if (_4e && _4b.charAt(0) == '/') { + _4b = _4b.slice(1) + } + ok = this.loadPath(_4b, _51); + if (ok) { + break + } + } + } + if (!ok && !_49) { + dojo.raise('Could not load \'' + _47 + '\'; last tried \'' + _4b + '\'') + } + } + if (!_49 && !this['isXDomain']) { + _4a = this.findModule(_47, false); + if (!_4a) { + dojo.raise('symbol \'' + _47 + '\' is not defined after loading \'' + _4b + '\'') + } + } + return _4a + }; + dojo.hostenv.startPackage = function (_52) { + var _53 = String(_52); + var _54 = _53; + var _55 = _52.split(/\./); + if (_55[_55.length - 1] == '*') { + _55.pop(); + _54 = _55.join('.') + } + var _56 = dojo.evalObjPath(_54, true); + this.loaded_modules_[_53] = _56; + this.loaded_modules_[_54] = _56; + return _56 + }; + dojo.hostenv.findModule = function (_57, _58) { + var lmn = String(_57); + if (this.loaded_modules_[lmn]) { + return this.loaded_modules_[lmn] + } + if (_58) { + dojo.raise('no loaded module named \'' + _57 + '\'') + } + return null + }; + dojo.kwCompoundRequire = function (_5a) { + var _5b = _5a['common'] || []; + var _5c = _5a[dojo.hostenv.name_] ? _5b.concat(_5a[dojo.hostenv.name_] || []) : _5b.concat(_5a['default'] || []); + for (var x = 0; x < _5c.length; x++) { + var _5e = _5c[x]; + if (_5e.constructor == Array) { + dojo.hostenv.loadModule.apply(dojo.hostenv, _5e) + } else { + dojo.hostenv.loadModule(_5e) + } + } + }; + dojo.require = function (_5f) { + dojo.hostenv.loadModule.apply(dojo.hostenv, arguments) + }; + dojo.requireIf = function (_60, _61) { + var _62 = arguments[0]; + if ((_62 === true) || (_62 == 'common') || (_62 && dojo.render[_62].capable)) { + var _63 = []; + for (var i = 1; i < arguments.length; i++) { + _63.push(arguments[i]) + } + dojo.require.apply(dojo, _63) + } + }; + dojo.requireAfterIf = dojo.requireIf; + dojo.provide = function (_65) { + return dojo.hostenv.startPackage.apply(dojo.hostenv, arguments) + }; + dojo.registerModulePath = function (_66, _67) { + return dojo.hostenv.setModulePrefix(_66, _67) + }; + if (djConfig['modulePaths']) { + for (var param in djConfig['modulePaths']) { + dojo.registerModulePath(param, djConfig['modulePaths'][param]) + } + } + dojo.setModulePrefix = function (_68, _69) { + dojo.deprecated('dojo.setModulePrefix("' + _68 + '", "' + _69 + '")', 'replaced by dojo.registerModulePath', '0.5'); + return dojo.registerModulePath(_68, _69) + }; + dojo.exists = function (obj, _6b) { + var p = _6b.split('.'); + for (var i = 0; i < p.length; i++) { + if (!obj[p[i]]) { + return false + } + obj = obj[p[i]] + } + return true + }; + dojo.hostenv.normalizeLocale = function (_6e) { + var _6f = _6e ? _6e.toLowerCase() : dojo.locale; + if (_6f == 'root') { + _6f = 'ROOT' + } + return _6f + }; + dojo.hostenv.searchLocalePath = function (_70, _71, _72) { + _70 = dojo.hostenv.normalizeLocale(_70); + var _73 = _70.split('-'); + var _74 = []; + for (var i = _73.length; i > 0; i--) { + _74.push(_73.slice(0, i).join('-')) + } + _74.push(false); + if (_71) { + _74.reverse() + } + for (var j = _74.length - 1; j >= 0; j--) { + var loc = _74[j] || 'ROOT'; + var _78 = _72(loc); + if (_78) { + break + } + } + }; + dojo.hostenv.localesGenerated; + dojo.hostenv.registerNlsPrefix = function () { + dojo.registerModulePath('nls', 'nls') + }; + dojo.hostenv.preloadLocalizations = function () { + if (dojo.hostenv.localesGenerated) { + dojo.hostenv.registerNlsPrefix(); - function preload(_79) { - _79 = dojo.hostenv.normalizeLocale(_79) - dojo.hostenv.searchLocalePath(_79, true, function(loc) { - for (var i = 0; i < dojo.hostenv.localesGenerated.length; i++) { - if (dojo.hostenv.localesGenerated[i] == loc) { - dojo['require']('nls.dojo_' + loc) - return true - } - } - return false - }) - } + function preload(_79) { + _79 = dojo.hostenv.normalizeLocale(_79); + dojo.hostenv.searchLocalePath(_79, true, function (loc) { + for (var i = 0; i < dojo.hostenv.localesGenerated.length; i++) { + if (dojo.hostenv.localesGenerated[i] == loc) { + dojo['require']('nls.dojo_' + loc); + return true + } + } + return false + }) + } - preload() - var _7c = djConfig.extraLocale || [] - for (var i = 0; i < _7c.length; i++) { - preload(_7c[i]) - } - } - dojo.hostenv.preloadLocalizations = function() { - } - } - dojo.requireLocalization = function(_7e, _7f, _80, _81) { - dojo.hostenv.preloadLocalizations() - var _82 = dojo.hostenv.normalizeLocale(_80) - var _83 = [_7e, 'nls', _7f].join('.') - var _84 = '' - if (_81) { - var _85 = _81.split(',') - for (var i = 0; i < _85.length; i++) { - if (_82.indexOf(_85[i]) == 0) { - if (_85[i].length > _84.length) { - _84 = _85[i] - } - } - } - if (!_84) { - _84 = 'ROOT' - } - } - var _87 = _81 ? _84 : _82 - var _88 = dojo.hostenv.findModule(_83) - var _89 = null - if (_88) { - if (djConfig.localizationComplete && _88._built) { - return - } - var _8a = _87.replace('-', '_') - var _8b = _83 + '.' + _8a - _89 = dojo.hostenv.findModule(_8b) - } - if (!_89) { - _88 = dojo.hostenv.startPackage(_83) - var _8c = dojo.hostenv.getModuleSymbols(_7e) - var _8d = _8c.concat('nls').join('/') - var _8e - dojo.hostenv.searchLocalePath(_87, _81, function(loc) { - var _90 = loc.replace('-', '_') - var _91 = _83 + '.' + _90 - var _92 = false - if (!dojo.hostenv.findModule(_91)) { - dojo.hostenv.startPackage(_91) - var _93 = [_8d] - if (loc != 'ROOT') { - _93.push(loc) - } - _93.push(_7f) - var _94 = _93.join('/') + '.js' - _92 = dojo.hostenv.loadPath(_94, null, function(_95) { - var _96 = function() { - } - _96.prototype = _8e - _88[_90] = new _96() - for (var j in _95) { - _88[_90][j] = _95[j] - } - }) - } else { - _92 = true - } - if (_92 && _88[_90]) { - _8e = _88[_90] - } else { - _88[_90] = _8e - } - if (_81) { - return true - } - }) - } - if (_81 && _82 != _84) { - _88[_82.replace('-', '_')] = _88[_84.replace('-', '_')] - } - }; - (function() { - var _98 = djConfig.extraLocale - if (_98) { - if (!_98 instanceof Array) { - _98 = [_98] - } - var req = dojo.requireLocalization - dojo.requireLocalization = function(m, b, _9c, _9d) { - req(m, b, _9c, _9d) - if (_9c) { - return - } - for (var i = 0; i < _98.length; i++) { - req(m, b, _98[i], _9d) - } - } - } - })() + preload(); + var _7c = djConfig.extraLocale || []; + for (var i = 0; i < _7c.length; i++) { + preload(_7c[i]) + } + } + dojo.hostenv.preloadLocalizations = function () { + } + }; + dojo.requireLocalization = function (_7e, _7f, _80, _81) { + dojo.hostenv.preloadLocalizations(); + var _82 = dojo.hostenv.normalizeLocale(_80); + var _83 = [_7e, 'nls', _7f].join('.'); + var _84 = ''; + if (_81) { + var _85 = _81.split(','); + for (var i = 0; i < _85.length; i++) { + if (_82.indexOf(_85[i]) == 0) { + if (_85[i].length > _84.length) { + _84 = _85[i] + } + } + } + if (!_84) { + _84 = 'ROOT' + } + } + var _87 = _81 ? _84 : _82; + var _88 = dojo.hostenv.findModule(_83); + var _89 = null; + if (_88) { + if (djConfig.localizationComplete && _88._built) { + return + } + var _8a = _87.replace('-', '_'); + var _8b = _83 + '.' + _8a; + _89 = dojo.hostenv.findModule(_8b) + } + if (!_89) { + _88 = dojo.hostenv.startPackage(_83); + var _8c = dojo.hostenv.getModuleSymbols(_7e); + var _8d = _8c.concat('nls').join('/'); + var _8e; + dojo.hostenv.searchLocalePath(_87, _81, function (loc) { + var _90 = loc.replace('-', '_'); + var _91 = _83 + '.' + _90; + var _92 = false; + if (!dojo.hostenv.findModule(_91)) { + dojo.hostenv.startPackage(_91); + var _93 = [_8d]; + if (loc != 'ROOT') { + _93.push(loc) + } + _93.push(_7f); + var _94 = _93.join('/') + '.js'; + _92 = dojo.hostenv.loadPath(_94, null, function (_95) { + var _96 = function () { + }; + _96.prototype = _8e; + _88[_90] = new _96(); + for (var j in _95) { + _88[_90][j] = _95[j] + } + }) + } else { + _92 = true + } + if (_92 && _88[_90]) { + _8e = _88[_90] + } else { + _88[_90] = _8e + } + if (_81) { + return true + } + }) + } + if (_81 && _82 != _84) { + _88[_82.replace('-', '_')] = _88[_84.replace('-', '_')] + } + }; + (function () { + var _98 = djConfig.extraLocale; + if (_98) { + if (!_98 instanceof Array) { + _98 = [_98] + } + var req = dojo.requireLocalization; + dojo.requireLocalization = function (m, b, _9c, _9d) { + req(m, b, _9c, _9d); + if (_9c) { + return + } + for (var i = 0; i < _98.length; i++) { + req(m, b, _98[i], _9d) + } + } + } + })() } if (typeof window != 'undefined') { - (function() { - if (djConfig.allowQueryConfig) { - var _9f = document.location.toString() - var _a0 = _9f.split('?', 2) - if (_a0.length > 1) { - var _a1 = _a0[1] - var _a2 = _a1.split('&') - for (var x in _a2) { - var sp = _a2[x].split('=') - if ((sp[0].length > 9) && (sp[0].substr(0, 9) == 'djConfig.')) { - var opt = sp[0].substr(9) - try { - djConfig[opt] = eval(sp[1]) - } catch (e) { - djConfig[opt] = sp[1] - } - } - } - } - } - if (((djConfig['baseScriptUri'] == '') || (djConfig['baseRelativePath'] == '')) && (document && document.getElementsByTagName)) { - var _a6 = document.getElementsByTagName('script') - var _a7 = /(__package__|dojo|bootstrap1)\.js([\?\.]|$)/i - for (var i = 0; i < _a6.length; i++) { - var src = _a6[i].getAttribute('src') - if (!src) { - continue - } - var m = src.match(_a7) - if (m) { - var _ab = src.substring(0, m.index) - if (src.indexOf('bootstrap1') > -1) { - _ab += '../' - } - if (!this['djConfig']) { - djConfig = {} - } - if (djConfig['baseScriptUri'] == '') { - djConfig['baseScriptUri'] = _ab - } - if (djConfig['baseRelativePath'] == '') { - djConfig['baseRelativePath'] = _ab - } - break - } - } - } - var dr = dojo.render - var drh = dojo.render.html - var drs = dojo.render.svg - var dua = (drh.UA = navigator.userAgent) - var dav = (drh.AV = navigator.appVersion) - var t = true - var f = false - drh.capable = t - drh.support.builtin = t - dr.ver = parseFloat(drh.AV) - dr.os.mac = dav.indexOf('Macintosh') >= 0 - dr.os.win = dav.indexOf('Windows') >= 0 - dr.os.linux = dav.indexOf('X11') >= 0 - drh.opera = dua.indexOf('Opera') >= 0 - drh.khtml = (dav.indexOf('Konqueror') >= 0) || (dav.indexOf('Safari') >= 0) - drh.safari = dav.indexOf('Safari') >= 0 - var _b3 = dua.indexOf('Gecko') - drh.mozilla = drh.moz = (_b3 >= 0) && (!drh.khtml) - if (drh.mozilla) { - drh.geckoVersion = dua.substring(_b3 + 6, _b3 + 14) - } - drh.ie = (document.all) && (!drh.opera) - drh.ie50 = drh.ie && dav.indexOf('MSIE 5.0') >= 0 - drh.ie55 = drh.ie && dav.indexOf('MSIE 5.5') >= 0 - drh.ie60 = drh.ie && dav.indexOf('MSIE 6.0') >= 0 - drh.ie70 = drh.ie && dav.indexOf('MSIE 7.0') >= 0 - var cm = document['compatMode'] - drh.quirks = (cm == 'BackCompat') || (cm == 'QuirksMode') || drh.ie55 || drh.ie50 - dojo.locale = dojo.locale || (drh.ie ? navigator.userLanguage : navigator.language).toLowerCase() - dr.vml.capable = drh.ie - drs.capable = f - drs.support.plugin = f - drs.support.builtin = f - var _b5 = window['document'] - var tdi = _b5['implementation'] - if (drh.ie && (window.location.protocol == 'file:')) { - djConfig.ieForceActiveXXhr = true - } - if ((tdi) && (tdi['hasFeature']) && (tdi.hasFeature('org.w3c.dom.svg', '1.0'))) { - drs.capable = t - drs.support.builtin = t - drs.support.plugin = f - } - if (drh.safari) { - var tmp = dua.split('AppleWebKit/')[1] - var ver = parseFloat(tmp.split(' ')[0]) - if (ver >= 420) { - drs.capable = t - drs.support.builtin = t - drs.support.plugin = f - } - } else { - } - })() - dojo.hostenv.startPackage('dojo.hostenv') - dojo.render.name = dojo.hostenv.name_ = 'browser' - dojo.hostenv.searchIds = [] - dojo.hostenv._XMLHTTP_PROGIDS = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'] - dojo.hostenv.getXmlhttpObject = function() { - var _b9 = null - var _ba = null - if (!dojo.render.html.ie || !djConfig.ieForceActiveXXhr) { - try { - _b9 = new XMLHttpRequest() - } catch (e) { - } - } - if (!_b9) { - for (var i = 0; i < 3; ++i) { - var _bc = dojo.hostenv._XMLHTTP_PROGIDS[i] - try { - _b9 = new ActiveXObject(_bc) - } catch (e) { - _ba = e - } - if (_b9) { - dojo.hostenv._XMLHTTP_PROGIDS = [_bc] - break - } - } - } - if (!_b9) { - return dojo.raise('XMLHTTP not available', _ba) - } - return _b9 - } - dojo.hostenv._blockAsync = false - dojo.hostenv.getText = function(uri, _be, _bf) { - if (!_be) { - this._blockAsync = true - } - var _c0 = this.getXmlhttpObject() + (function () { + if (djConfig.allowQueryConfig) { + var _9f = document.location.toString(); + var _a0 = _9f.split('?', 2); + if (_a0.length > 1) { + var _a1 = _a0[1]; + var _a2 = _a1.split('&'); + for (var x in _a2) { + var sp = _a2[x].split('='); + if ((sp[0].length > 9) && (sp[0].substr(0, 9) == 'djConfig.')) { + var opt = sp[0].substr(9); + try { + djConfig[opt] = eval(sp[1]) + } catch (e) { + djConfig[opt] = sp[1] + } + } + } + } + } + if (((djConfig['baseScriptUri'] == '') || (djConfig['baseRelativePath'] == '')) && (document + && document.getElementsByTagName)) { + var _a6 = document.getElementsByTagName('script'); + var _a7 = /(__package__|dojo|bootstrap1)\.js([\?\.]|$)/i; + for (var i = 0; i < _a6.length; i++) { + var src = _a6[i].getAttribute('src'); + if (!src) { + continue + } + var m = src.match(_a7); + if (m) { + var _ab = src.substring(0, m.index); + if (src.indexOf('bootstrap1') > -1) { + _ab += '../' + } + if (!this['djConfig']) { + djConfig = {} + } + if (djConfig['baseScriptUri'] == '') { + djConfig['baseScriptUri'] = _ab + } + if (djConfig['baseRelativePath'] == '') { + djConfig['baseRelativePath'] = _ab + } + break + } + } + } + var dr = dojo.render; + var drh = dojo.render.html; + var drs = dojo.render.svg; + var dua = (drh.UA = navigator.userAgent); + var dav = (drh.AV = navigator.appVersion); + var t = true; + var f = false; + drh.capable = t; + drh.support.builtin = t; + dr.ver = parseFloat(drh.AV); + dr.os.mac = dav.indexOf('Macintosh') >= 0; + dr.os.win = dav.indexOf('Windows') >= 0; + dr.os.linux = dav.indexOf('X11') >= 0; + drh.opera = dua.indexOf('Opera') >= 0; + drh.khtml = (dav.indexOf('Konqueror') >= 0) || (dav.indexOf('Safari') >= 0); + drh.safari = dav.indexOf('Safari') >= 0; + var _b3 = dua.indexOf('Gecko'); + drh.mozilla = drh.moz = (_b3 >= 0) && (!drh.khtml); + if (drh.mozilla) { + drh.geckoVersion = dua.substring(_b3 + 6, _b3 + 14) + } + drh.ie = (document.all) && (!drh.opera); + drh.ie50 = drh.ie && dav.indexOf('MSIE 5.0') >= 0; + drh.ie55 = drh.ie && dav.indexOf('MSIE 5.5') >= 0; + drh.ie60 = drh.ie && dav.indexOf('MSIE 6.0') >= 0; + drh.ie70 = drh.ie && dav.indexOf('MSIE 7.0') >= 0; + var cm = document['compatMode']; + drh.quirks = (cm == 'BackCompat') || (cm == 'QuirksMode') || drh.ie55 || drh.ie50; + dojo.locale = dojo.locale || (drh.ie ? navigator.userLanguage : navigator.language).toLowerCase(); + dr.vml.capable = drh.ie; + drs.capable = f; + drs.support.plugin = f; + drs.support.builtin = f; + var _b5 = window['document']; + var tdi = _b5['implementation']; + if (drh.ie && (window.location.protocol == 'file:')) { + djConfig.ieForceActiveXXhr = true + } + if ((tdi) && (tdi['hasFeature']) && (tdi.hasFeature('org.w3c.dom.svg', '1.0'))) { + drs.capable = t; + drs.support.builtin = t; + drs.support.plugin = f + } + if (drh.safari) { + var tmp = dua.split('AppleWebKit/')[1]; + var ver = parseFloat(tmp.split(' ')[0]); + if (ver >= 420) { + drs.capable = t; + drs.support.builtin = t; + drs.support.plugin = f + } + } else { + } + })(); + dojo.hostenv.startPackage('dojo.hostenv'); + dojo.render.name = dojo.hostenv.name_ = 'browser'; + dojo.hostenv.searchIds = []; + dojo.hostenv._XMLHTTP_PROGIDS = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0']; + dojo.hostenv.getXmlhttpObject = function () { + var _b9 = null; + var _ba = null; + if (!dojo.render.html.ie || !djConfig.ieForceActiveXXhr) { + try { + _b9 = new XMLHttpRequest() + } catch (e) { + } + } + if (!_b9) { + for (var i = 0; i < 3; ++i) { + var _bc = dojo.hostenv._XMLHTTP_PROGIDS[i]; + try { + _b9 = new ActiveXObject(_bc) + } catch (e) { + _ba = e + } + if (_b9) { + dojo.hostenv._XMLHTTP_PROGIDS = [_bc]; + break + } + } + } + if (!_b9) { + return dojo.raise('XMLHTTP not available', _ba) + } + return _b9 + }; + dojo.hostenv._blockAsync = false; + dojo.hostenv.getText = function (uri, _be, _bf) { + if (!_be) { + this._blockAsync = true + } + var _c0 = this.getXmlhttpObject(); - function isDocumentOk(_c1) { - var _c2 = _c1['status'] - return Boolean((!_c2) || ((200 <= _c2) && (300 > _c2)) || (_c2 == 304)) - } + function isDocumentOk(_c1) { + var _c2 = _c1['status']; + return Boolean((!_c2) || ((200 <= _c2) && (300 > _c2)) || (_c2 == 304)) + } - if (_be) { - var _c3 = this, _c4 = null, gbl = dojo.global() - var xhr = dojo.evalObjPath('dojo.io.XMLHTTPTransport') - _c0.onreadystatechange = function() { - if (_c4) { - gbl.clearTimeout(_c4) - _c4 = null - } - if (_c3._blockAsync || (xhr && xhr._blockAsync)) { - _c4 = gbl.setTimeout(function() { - _c0.onreadystatechange.apply(this) - }, 10) - } else { - if (4 == _c0.readyState) { - if (isDocumentOk(_c0)) { - _be(_c0.responseText) - } - } - } - } - } - _c0.open('GET', uri, _be ? true : false) - try { - _c0.send(null) - if (_be) { - return null - } - if (!isDocumentOk(_c0)) { - var err = Error('Unable to load ' + uri + ' status:' + _c0.status) - err.status = _c0.status - err.responseText = _c0.responseText - throw err - } - } catch (e) { - this._blockAsync = false - if ((_bf) && (!_be)) { - return null - } else { - throw e - } - } - this._blockAsync = false - return _c0.responseText - } - dojo.hostenv.defaultDebugContainerId = 'dojoDebug' - dojo.hostenv._println_buffer = [] - dojo.hostenv._println_safe = false - dojo.hostenv.println = function(_c8) { - if (!dojo.hostenv._println_safe) { - dojo.hostenv._println_buffer.push(_c8) - } else { - try { - var _c9 = document.getElementById(djConfig.debugContainerId ? djConfig.debugContainerId : dojo.hostenv.defaultDebugContainerId) - if (!_c9) { - _c9 = dojo.body() - } - var div = document.createElement('div') - div.appendChild(document.createTextNode(_c8)) - _c9.appendChild(div) - } catch (e) { - try { - document.write('
    ' + _c8 + '
    ') - } catch (e2) { - window.status = _c8 - } - } - } - } - dojo.addOnLoad(function() { - dojo.hostenv._println_safe = true - while (dojo.hostenv._println_buffer.length > 0) { - dojo.hostenv.println(dojo.hostenv._println_buffer.shift()) - } - }) + if (_be) { + var _c3 = this, _c4 = null, gbl = dojo.global(); + var xhr = dojo.evalObjPath('dojo.io.XMLHTTPTransport'); + _c0.onreadystatechange = function () { + if (_c4) { + gbl.clearTimeout(_c4); + _c4 = null + } + if (_c3._blockAsync || (xhr && xhr._blockAsync)) { + _c4 = gbl.setTimeout(function () { + _c0.onreadystatechange.apply(this) + }, 10) + } else { + if (4 == _c0.readyState) { + if (isDocumentOk(_c0)) { + _be(_c0.responseText) + } + } + } + } + } + _c0.open('GET', uri, _be ? true : false); + try { + _c0.send(null); + if (_be) { + return null + } + if (!isDocumentOk(_c0)) { + var err = Error('Unable to load ' + uri + ' status:' + _c0.status); + err.status = _c0.status; + err.responseText = _c0.responseText; + throw err + } + } catch (e) { + this._blockAsync = false; + if ((_bf) && (!_be)) { + return null + } else { + throw e + } + } + this._blockAsync = false; + return _c0.responseText + }; + dojo.hostenv.defaultDebugContainerId = 'dojoDebug'; + dojo.hostenv._println_buffer = []; + dojo.hostenv._println_safe = false; + dojo.hostenv.println = function (_c8) { + if (!dojo.hostenv._println_safe) { + dojo.hostenv._println_buffer.push(_c8) + } else { + try { + var _c9 = document.getElementById(djConfig.debugContainerId ? djConfig.debugContainerId : dojo.hostenv.defaultDebugContainerId); + if (!_c9) { + _c9 = dojo.body() + } + var div = document.createElement('div'); + div.appendChild(document.createTextNode(_c8)); + _c9.appendChild(div) + } catch (e) { + try { + document.write('
    ' + _c8 + '
    ') + } catch (e2) { + window.status = _c8 + } + } + } + }; + dojo.addOnLoad(function () { + dojo.hostenv._println_safe = true; + while (dojo.hostenv._println_buffer.length > 0) { + dojo.hostenv.println(dojo.hostenv._println_buffer.shift()) + } + }); - function dj_addNodeEvtHdlr(_cb, _cc, fp) { - var _ce = _cb['on' + _cc] || function() { - } - _cb['on' + _cc] = function() { - fp.apply(_cb, arguments) - _ce.apply(_cb, arguments) - } - return true - } + function dj_addNodeEvtHdlr(_cb, _cc, fp) { + var _ce = _cb['on' + _cc] || function () { + }; + _cb['on' + _cc] = function () { + fp.apply(_cb, arguments); + _ce.apply(_cb, arguments) + }; + return true + } - dojo.hostenv._djInitFired = false + dojo.hostenv._djInitFired = false; - function dj_load_init(e) { - dojo.hostenv._djInitFired = true - var _d0 = (e && e.type) ? e.type.toLowerCase() : 'load' - if (arguments.callee.initialized || (_d0 != 'domcontentloaded' && _d0 != 'load')) { - return - } - arguments.callee.initialized = true - if (typeof (_timer) != 'undefined') { - clearInterval(_timer) - delete _timer - } - var _d1 = function() { - if (dojo.render.html.ie) { - dojo.hostenv.makeWidgets() - } - } - if (dojo.hostenv.inFlightCount == 0) { - _d1() - dojo.hostenv.modulesLoaded() - } else { - dojo.hostenv.modulesLoadedListeners.unshift(_d1) - } - } + function dj_load_init(e) { + dojo.hostenv._djInitFired = true; + var _d0 = (e && e.type) ? e.type.toLowerCase() : 'load'; + if (arguments.callee.initialized || (_d0 != 'domcontentloaded' && _d0 != 'load')) { + return + } + arguments.callee.initialized = true; + if (typeof (_timer) != 'undefined') { + clearInterval(_timer); + delete _timer + } + var _d1 = function () { + if (dojo.render.html.ie) { + dojo.hostenv.makeWidgets() + } + }; + if (dojo.hostenv.inFlightCount == 0) { + _d1(); + dojo.hostenv.modulesLoaded() + } else { + dojo.hostenv.modulesLoadedListeners.unshift(_d1) + } + } - if (document.addEventListener) { - if (dojo.render.html.opera || (dojo.render.html.moz && (djConfig['enableMozDomContentLoaded'] === true))) { - document.addEventListener('DOMContentLoaded', dj_load_init, null) - } - window.addEventListener('load', dj_load_init, null) - } - if (dojo.render.html.ie && dojo.render.os.win) { - document.attachEvent('onreadystatechange', function(e) { - if (document.readyState == 'complete') { - dj_load_init() - } - }) - } - if (/(WebKit|khtml)/i.test(navigator.userAgent)) { - var _timer = setInterval(function() { - if (/loaded|complete/.test(document.readyState)) { - dj_load_init() - } - }, 10) - } - if (dojo.render.html.ie) { - dj_addNodeEvtHdlr(window, 'beforeunload', function() { - dojo.hostenv._unloading = true - window.setTimeout(function() { - dojo.hostenv._unloading = false - }, 0) - }) - } - dj_addNodeEvtHdlr(window, 'unload', function() { - dojo.hostenv.unloaded() - if ((!dojo.render.html.ie) || (dojo.render.html.ie && dojo.hostenv._unloading)) { - dojo.hostenv.unloaded() - } - }) - dojo.hostenv.makeWidgets = function() { - var _d3 = [] - if (djConfig.searchIds && djConfig.searchIds.length > 0) { - _d3 = _d3.concat(djConfig.searchIds) - } - if (dojo.hostenv.searchIds && dojo.hostenv.searchIds.length > 0) { - _d3 = _d3.concat(dojo.hostenv.searchIds) - } - if ((djConfig.parseWidgets) || (_d3.length > 0)) { - if (dojo.evalObjPath('dojo.widget.Parse')) { - var _d4 = new dojo.xml.Parse() - if (_d3.length > 0) { - for (var x = 0; x < _d3.length; x++) { - var _d6 = document.getElementById(_d3[x]) - if (!_d6) { - continue - } - var _d7 = _d4.parseElement(_d6, null, true) - dojo.widget.getParser().createComponents(_d7) - } - } else { - if (djConfig.parseWidgets) { - var _d7 = _d4.parseElement(dojo.body(), null, true) - dojo.widget.getParser().createComponents(_d7) - } - } - } - } - } - dojo.addOnLoad(function() { - if (!dojo.render.html.ie) { - dojo.hostenv.makeWidgets() - } - }) - try { - if (dojo.render.html.ie) { - document.namespaces.add('v', 'urn:schemas-microsoft-com:vml') - document.createStyleSheet().addRule('v\\:*', 'behavior:url(#default#VML)') - } - } catch (e) { - } - dojo.hostenv.writeIncludes = function() { - } - if (!dj_undef('document', this)) { - dj_currentDocument = this.document - } - dojo.doc = function() { - return dj_currentDocument - } - dojo.body = function() { - return dojo.doc().body || dojo.doc().getElementsByTagName('body')[0] - } - dojo.byId = function(id, doc) { - if ((id) && ((typeof id == 'string') || (id instanceof String))) { - if (!doc) { - doc = dj_currentDocument - } - var ele = doc.getElementById(id) - if (ele && (ele.id != id) && doc.all) { - ele = null - eles = doc.all[id] - if (eles) { - if (eles.length) { - for (var i = 0; i < eles.length; i++) { - if (eles[i].id == id) { - ele = eles[i] - break - } - } - } else { - ele = eles - } - } - } - return ele - } - return id - } - dojo.setContext = function(_dc, _dd) { - dj_currentContext = _dc - dj_currentDocument = _dd - } - dojo._fireCallback = function(_de, _df, _e0) { - if ((_df) && ((typeof _de == 'string') || (_de instanceof String))) { - _de = _df[_de] - } - return (_df ? _de.apply(_df, _e0 || []) : _de()) - } - dojo.withGlobal = function(_e1, _e2, _e3, _e4) { - var _e5 - var _e6 = dj_currentContext - var _e7 = dj_currentDocument - try { - dojo.setContext(_e1, _e1.document) - _e5 = dojo._fireCallback(_e2, _e3, _e4) - } finally { - dojo.setContext(_e6, _e7) - } - return _e5 - } - dojo.withDoc = function(_e8, _e9, _ea, _eb) { - var _ec - var _ed = dj_currentDocument - try { - dj_currentDocument = _e8 - _ec = dojo._fireCallback(_e9, _ea, _eb) - } finally { - dj_currentDocument = _ed - } - return _ec - } -} -dojo.requireIf((djConfig['isDebug'] || djConfig['debugAtAllCosts']), 'dojo.debug') -dojo.requireIf(djConfig['debugAtAllCosts'] && !window.widget && !djConfig['useXDomain'], 'dojo.browser_debug') -dojo.requireIf(djConfig['debugAtAllCosts'] && !window.widget && djConfig['useXDomain'], 'dojo.browser_debug_xd') -dojo.provide('dojo.string.common') -dojo.string.trim = function(str, wh) { - if (!str.replace) { - return str - } - if (!str.length) { - return str - } - var re = (wh > 0) ? (/^\s+/) : (wh < 0) ? (/\s+$/) : (/^\s+|\s+$/g) - return str.replace(re, '') -} -dojo.string.trimStart = function(str) { - return dojo.string.trim(str, 1) -} -dojo.string.trimEnd = function(str) { - return dojo.string.trim(str, -1) -} -dojo.string.repeat = function(str, _f4, _f5) { - var out = '' - for (var i = 0; i < _f4; i++) { - out += str - if (_f5 && i < _f4 - 1) { - out += _f5 - } - } - return out -} -dojo.string.pad = function(str, len, c, dir) { - var out = String(str) - if (!c) { - c = '0' - } - if (!dir) { - dir = 1 - } - while (out.length < len) { - if (dir > 0) { - out = c + out - } else { - out += c - } - } - return out -} -dojo.string.padLeft = function(str, len, c) { - return dojo.string.pad(str, len, c, 1) -} -dojo.string.padRight = function(str, len, c) { - return dojo.string.pad(str, len, c, -1) -} -dojo.provide('dojo.string') -dojo.provide('dojo.lang.common') -dojo.lang.inherits = function(_103, _104) { - if (!dojo.lang.isFunction(_104)) { - dojo.raise('dojo.inherits: superclass argument [' + _104 + '] must be a function (subclass: [' + _103 + '\']') - } - _103.prototype = new _104() - _103.prototype.constructor = _103 - _103.superclass = _104.prototype - _103['super'] = _104.prototype -} -dojo.lang._mixin = function(obj, _106) { - var tobj = {} - for (var x in _106) { - if ((typeof tobj[x] == 'undefined') || (tobj[x] != _106[x])) { - obj[x] = _106[x] - } - } - if (dojo.render.html.ie && (typeof (_106['toString']) == 'function') && (_106['toString'] != obj['toString']) && (_106['toString'] != tobj['toString'])) { - obj.toString = _106.toString - } - return obj -} -dojo.lang.mixin = function(obj, _10a) { - for (var i = 1, l = arguments.length; i < l; i++) { - dojo.lang._mixin(obj, arguments[i]) - } - return obj -} -dojo.lang.extend = function(_10d, _10e) { - for (var i = 1, l = arguments.length; i < l; i++) { - dojo.lang._mixin(_10d.prototype, arguments[i]) - } - return _10d -} -dojo.inherits = dojo.lang.inherits -dojo.mixin = dojo.lang.mixin -dojo.extend = dojo.lang.extend -dojo.lang.find = function(_111, _112, _113, _114) { - if (!dojo.lang.isArrayLike(_111) && dojo.lang.isArrayLike(_112)) { - dojo.deprecated('dojo.lang.find(value, array)', 'use dojo.lang.find(array, value) instead', '0.5') - var temp = _111 - _111 = _112 - _112 = temp - } - var _116 = dojo.lang.isString(_111) - if (_116) { - _111 = _111.split('') - } - if (_114) { - var step = -1 - var i = _111.length - 1 - var end = -1 - } else { - var step = 1 - var i = 0 - var end = _111.length - } - if (_113) { - while (i != end) { - if (_111[i] === _112) { - return i - } - i += step - } - } else { - while (i != end) { - if (_111[i] == _112) { - return i - } - i += step - } - } - return -1 -} -dojo.lang.indexOf = dojo.lang.find -dojo.lang.findLast = function(_11a, _11b, _11c) { - return dojo.lang.find(_11a, _11b, _11c, true) -} -dojo.lang.lastIndexOf = dojo.lang.findLast -dojo.lang.inArray = function(_11d, _11e) { - return dojo.lang.find(_11d, _11e) > -1 -} -dojo.lang.isObject = function(it) { - if (typeof it == 'undefined') { - return false - } - return (typeof it == 'object' || it === null || dojo.lang.isArray(it) || dojo.lang.isFunction(it)) -} -dojo.lang.isArray = function(it) { - return (it && it instanceof Array || typeof it == 'array') -} -dojo.lang.isArrayLike = function(it) { - if ((!it) || (dojo.lang.isUndefined(it))) { - return false - } - if (dojo.lang.isString(it)) { - return false - } - if (dojo.lang.isFunction(it)) { - return false - } - if (dojo.lang.isArray(it)) { - return true - } - if ((it.tagName) && (it.tagName.toLowerCase() == 'form')) { - return false - } - if (dojo.lang.isNumber(it.length) && isFinite(it.length)) { - return true - } - return false -} -dojo.lang.isFunction = function(it) { - return (it instanceof Function || typeof it == 'function') -}; -(function() { - if ((dojo.render.html.capable) && (dojo.render.html['safari'])) { - dojo.lang.isFunction = function(it) { - if ((typeof (it) == 'function') && (it == '[object NodeList]')) { - return false - } - return (it instanceof Function || typeof it == 'function') - } - } -})() -dojo.lang.isString = function(it) { - return (typeof it == 'string' || it instanceof String) -} -dojo.lang.isAlien = function(it) { - if (!it) { - return false - } - return !dojo.lang.isFunction(it) && /\{\s*\[native code\]\s*\}/.test(String(it)) -} -dojo.lang.isBoolean = function(it) { - return (it instanceof Boolean || typeof it == 'boolean') -} -dojo.lang.isNumber = function(it) { - return (it instanceof Number || typeof it == 'number') -} -dojo.lang.isUndefined = function(it) { - return ((typeof (it) == 'undefined') && (it == undefined)) -} -dojo.provide('dojo.lang.extras') -dojo.lang.setTimeout = function(func, _12a) { - var _12b = window, _12c = 2 - if (!dojo.lang.isFunction(func)) { - _12b = func - func = _12a - _12a = arguments[2] - _12c++ - } - if (dojo.lang.isString(func)) { - func = _12b[func] - } - var args = [] - for (var i = _12c; i < arguments.length; i++) { - args.push(arguments[i]) - } - return dojo.global().setTimeout(function() { - func.apply(_12b, args) - }, _12a) -} -dojo.lang.clearTimeout = function(_12f) { - dojo.global().clearTimeout(_12f) -} -dojo.lang.getNameInObj = function(ns, item) { - if (!ns) { - ns = dj_global - } - for (var x in ns) { - if (ns[x] === item) { - return new String(x) - } - } - return null -} -dojo.lang.shallowCopy = function(obj, deep) { - var i, ret - if (obj === null) { - return null - } - if (dojo.lang.isObject(obj)) { - ret = new obj.constructor() - for (i in obj) { - if (dojo.lang.isUndefined(ret[i])) { - ret[i] = deep ? dojo.lang.shallowCopy(obj[i], deep) : obj[i] - } - } - } else { - if (dojo.lang.isArray(obj)) { - ret = [] - for (i = 0; i < obj.length; i++) { - ret[i] = deep ? dojo.lang.shallowCopy(obj[i], deep) : obj[i] - } - } else { - ret = obj - } - } - return ret -} -dojo.lang.firstValued = function() { - for (var i = 0; i < arguments.length; i++) { - if (typeof arguments[i] != 'undefined') { - return arguments[i] - } - } - return undefined -} -dojo.lang.getObjPathValue = function(_138, _139, _13a) { - with (dojo.parseObjPath(_138, _139, _13a)) { - return dojo.evalProp(prop, obj, _13a) - } -} -dojo.lang.setObjPathValue = function(_13b, _13c, _13d, _13e) { - dojo.deprecated('dojo.lang.setObjPathValue', 'use dojo.parseObjPath and the \'=\' operator', '0.6') - if (arguments.length < 4) { - _13e = true - } - with (dojo.parseObjPath(_13b, _13d, _13e)) { - if (obj && (_13e || (prop in obj))) { - obj[prop] = _13c - } - } -} -dojo.provide('dojo.io.common') -dojo.io.transports = [] -dojo.io.hdlrFuncNames = ['load', 'error', 'timeout'] -dojo.io.Request = function(url, _140, _141, _142) { - if ((arguments.length == 1) && (arguments[0].constructor == Object)) { - this.fromKwArgs(arguments[0]) - } else { - this.url = url - if (_140) { - this.mimetype = _140 - } - if (_141) { - this.transport = _141 - } - if (arguments.length >= 4) { - this.changeUrl = _142 - } - } -} + if (document.addEventListener) { + if (dojo.render.html.opera || (dojo.render.html.moz && (djConfig['enableMozDomContentLoaded'] === true))) { + document.addEventListener('DOMContentLoaded', dj_load_init, null) + } + window.addEventListener('load', dj_load_init, null) + } + if (dojo.render.html.ie && dojo.render.os.win) { + document.attachEvent('onreadystatechange', function (e) { + if (document.readyState == 'complete') { + dj_load_init() + } + }) + } + if (/(WebKit|khtml)/i.test(navigator.userAgent)) { + var _timer = setInterval(function () { + if (/loaded|complete/.test(document.readyState)) { + dj_load_init() + } + }, 10) + } + if (dojo.render.html.ie) { + dj_addNodeEvtHdlr(window, 'beforeunload', function () { + dojo.hostenv._unloading = true; + window.setTimeout(function () { + dojo.hostenv._unloading = false + }, 0) + }) + } + dj_addNodeEvtHdlr(window, 'unload', function () { + dojo.hostenv.unloaded(); + if ((!dojo.render.html.ie) || (dojo.render.html.ie && dojo.hostenv._unloading)) { + dojo.hostenv.unloaded() + } + }); + dojo.hostenv.makeWidgets = function () { + var _d3 = []; + if (djConfig.searchIds && djConfig.searchIds.length > 0) { + _d3 = _d3.concat(djConfig.searchIds) + } + if (dojo.hostenv.searchIds && dojo.hostenv.searchIds.length > 0) { + _d3 = _d3.concat(dojo.hostenv.searchIds) + } + if ((djConfig.parseWidgets) || (_d3.length > 0)) { + if (dojo.evalObjPath('dojo.widget.Parse')) { + var _d4 = new dojo.xml.Parse(); + if (_d3.length > 0) { + for (var x = 0; x < _d3.length; x++) { + var _d6 = document.getElementById(_d3[x]); + if (!_d6) { + continue + } + var _d7 = _d4.parseElement(_d6, null, true); + dojo.widget.getParser().createComponents(_d7) + } + } else { + if (djConfig.parseWidgets) { + var _d7 = _d4.parseElement(dojo.body(), null, true); + dojo.widget.getParser().createComponents(_d7) + } + } + } + } + }; + dojo.addOnLoad(function () { + if (!dojo.render.html.ie) { + dojo.hostenv.makeWidgets() + } + }); + try { + if (dojo.render.html.ie) { + document.namespaces.add('v', 'urn:schemas-microsoft-com:vml'); + document.createStyleSheet().addRule('v\\:*', 'behavior:url(#default#VML)') + } + } catch (e) { + } + dojo.hostenv.writeIncludes = function () { + }; + if (!dj_undef('document', this)) { + dj_currentDocument = this.document + } + dojo.doc = function () { + return dj_currentDocument + }; + dojo.body = function () { + return dojo.doc().body || dojo.doc().getElementsByTagName('body')[0] + }; + dojo.byId = function (id, doc) { + if ((id) && ((typeof id == 'string') || (id instanceof String))) { + if (!doc) { + doc = dj_currentDocument + } + var ele = doc.getElementById(id); + if (ele && (ele.id != id) && doc.all) { + ele = null; + eles = doc.all[id]; + if (eles) { + if (eles.length) { + for (var i = 0; i < eles.length; i++) { + if (eles[i].id == id) { + ele = eles[i]; + break + } + } + } else { + ele = eles + } + } + } + return ele + } + return id + }; + dojo.setContext = function (_dc, _dd) { + dj_currentContext = _dc; + dj_currentDocument = _dd + }; + dojo._fireCallback = function (_de, _df, _e0) { + if ((_df) && ((typeof _de == 'string') || (_de instanceof String))) { + _de = _df[_de] + } + return (_df ? _de.apply(_df, _e0 || []) : _de()) + }; + dojo.withGlobal = function (_e1, _e2, _e3, _e4) { + var _e5; + var _e6 = dj_currentContext; + var _e7 = dj_currentDocument; + try { + dojo.setContext(_e1, _e1.document); + _e5 = dojo._fireCallback(_e2, _e3, _e4) + } finally { + dojo.setContext(_e6, _e7) + } + return _e5 + }; + dojo.withDoc = function (_e8, _e9, _ea, _eb) { + var _ec; + var _ed = dj_currentDocument; + try { + dj_currentDocument = _e8; + _ec = dojo._fireCallback(_e9, _ea, _eb) + } finally { + dj_currentDocument = _ed + } + return _ec + } +} +dojo.requireIf((djConfig['isDebug'] || djConfig['debugAtAllCosts']), 'dojo.debug'); +dojo.requireIf(djConfig['debugAtAllCosts'] && !window.widget && !djConfig['useXDomain'], 'dojo.browser_debug'); +dojo.requireIf(djConfig['debugAtAllCosts'] && !window.widget && djConfig['useXDomain'], 'dojo.browser_debug_xd'); +dojo.provide('dojo.string.common'); +dojo.string.trim = function (str, wh) { + if (!str.replace) { + return str + } + if (!str.length) { + return str + } + var re = (wh > 0) ? (/^\s+/) : (wh < 0) ? (/\s+$/) : (/^\s+|\s+$/g); + return str.replace(re, '') +}; +dojo.string.trimStart = function (str) { + return dojo.string.trim(str, 1) +}; +dojo.string.trimEnd = function (str) { + return dojo.string.trim(str, -1) +}; +dojo.string.repeat = function (str, _f4, _f5) { + var out = ''; + for (var i = 0; i < _f4; i++) { + out += str; + if (_f5 && i < _f4 - 1) { + out += _f5 + } + } + return out +}; +dojo.string.pad = function (str, len, c, dir) { + var out = String(str); + if (!c) { + c = '0' + } + if (!dir) { + dir = 1 + } + while (out.length < len) { + if (dir > 0) { + out = c + out + } else { + out += c + } + } + return out +}; +dojo.string.padLeft = function (str, len, c) { + return dojo.string.pad(str, len, c, 1) +}; +dojo.string.padRight = function (str, len, c) { + return dojo.string.pad(str, len, c, -1) +}; +dojo.provide('dojo.string'); +dojo.provide('dojo.lang.common'); +dojo.lang.inherits = function (_103, _104) { + if (!dojo.lang.isFunction(_104)) { + dojo.raise('dojo.inherits: superclass argument [' + _104 + '] must be a function (subclass: [' + _103 + '\']') + } + _103.prototype = new _104(); + _103.prototype.constructor = _103; + _103.superclass = _104.prototype; + _103['super'] = _104.prototype +}; +dojo.lang._mixin = function (obj, _106) { + var tobj = {}; + for (var x in _106) { + if ((typeof tobj[x] == 'undefined') || (tobj[x] != _106[x])) { + obj[x] = _106[x] + } + } + if (dojo.render.html.ie + && (typeof (_106['toString']) == 'function') + && (_106['toString'] != obj['toString']) + && (_106['toString'] != tobj['toString'])) { + obj.toString = _106.toString + } + return obj +}; +dojo.lang.mixin = function (obj, _10a) { + for (var i = 1, l = arguments.length; i < l; i++) { + dojo.lang._mixin(obj, arguments[i]) + } + return obj +}; +dojo.lang.extend = function (_10d, _10e) { + for (var i = 1, l = arguments.length; i < l; i++) { + dojo.lang._mixin(_10d.prototype, arguments[i]) + } + return _10d +}; +dojo.inherits = dojo.lang.inherits; +dojo.mixin = dojo.lang.mixin; +dojo.extend = dojo.lang.extend; +dojo.lang.find = function (_111, _112, _113, _114) { + if (!dojo.lang.isArrayLike(_111) && dojo.lang.isArrayLike(_112)) { + dojo.deprecated('dojo.lang.find(value, array)', 'use dojo.lang.find(array, value) instead', '0.5'); + var temp = _111; + _111 = _112; + _112 = temp + } + var _116 = dojo.lang.isString(_111); + if (_116) { + _111 = _111.split('') + } + if (_114) { + var step = -1; + var i = _111.length - 1; + var end = -1 + } else { + var step = 1; + var i = 0; + var end = _111.length + } + if (_113) { + while (i != end) { + if (_111[i] === _112) { + return i + } + i += step + } + } else { + while (i != end) { + if (_111[i] == _112) { + return i + } + i += step + } + } + return -1 +}; +dojo.lang.indexOf = dojo.lang.find; +dojo.lang.findLast = function (_11a, _11b, _11c) { + return dojo.lang.find(_11a, _11b, _11c, true) +}; +dojo.lang.lastIndexOf = dojo.lang.findLast; +dojo.lang.inArray = function (_11d, _11e) { + return dojo.lang.find(_11d, _11e) > -1 +}; +dojo.lang.isObject = function (it) { + if (typeof it == 'undefined') { + return false + } + return (typeof it == 'object' || it === null || dojo.lang.isArray(it) || dojo.lang.isFunction(it)) +}; +dojo.lang.isArray = function (it) { + return (it && it instanceof Array || typeof it == 'array') +}; +dojo.lang.isArrayLike = function (it) { + if ((!it) || (dojo.lang.isUndefined(it))) { + return false + } + if (dojo.lang.isString(it)) { + return false + } + if (dojo.lang.isFunction(it)) { + return false + } + if (dojo.lang.isArray(it)) { + return true + } + if ((it.tagName) && (it.tagName.toLowerCase() == 'form')) { + return false + } + if (dojo.lang.isNumber(it.length) && isFinite(it.length)) { + return true + } + return false +}; +dojo.lang.isFunction = function (it) { + return (it instanceof Function || typeof it == 'function') +}; +(function () { + if ((dojo.render.html.capable) && (dojo.render.html['safari'])) { + dojo.lang.isFunction = function (it) { + if ((typeof (it) == 'function') && (it == '[object NodeList]')) { + return false + } + return (it instanceof Function || typeof it == 'function') + } + } +})(); +dojo.lang.isString = function (it) { + return (typeof it == 'string' || it instanceof String) +}; +dojo.lang.isAlien = function (it) { + if (!it) { + return false + } + return !dojo.lang.isFunction(it) && /\{\s*\[native code\]\s*\}/.test(String(it)) +}; +dojo.lang.isBoolean = function (it) { + return (it instanceof Boolean || typeof it == 'boolean') +}; +dojo.lang.isNumber = function (it) { + return (it instanceof Number || typeof it == 'number') +}; +dojo.lang.isUndefined = function (it) { + return ((typeof (it) == 'undefined') && (it == undefined)) +}; +dojo.provide('dojo.lang.extras'); +dojo.lang.setTimeout = function (func, _12a) { + var _12b = window, _12c = 2; + if (!dojo.lang.isFunction(func)) { + _12b = func; + func = _12a; + _12a = arguments[2]; + _12c++ + } + if (dojo.lang.isString(func)) { + func = _12b[func] + } + var args = []; + for (var i = _12c; i < arguments.length; i++) { + args.push(arguments[i]) + } + return dojo.global().setTimeout(function () { + func.apply(_12b, args) + }, _12a) +}; +dojo.lang.clearTimeout = function (_12f) { + dojo.global().clearTimeout(_12f) +}; +dojo.lang.getNameInObj = function (ns, item) { + if (!ns) { + ns = dj_global + } + for (var x in ns) { + if (ns[x] === item) { + return String(x) + } + } + return null +}; +dojo.lang.shallowCopy = function (obj, deep) { + var i, ret; + if (obj === null) { + return null + } + if (dojo.lang.isObject(obj)) { + ret = new obj.constructor(); + for (i in obj) { + if (dojo.lang.isUndefined(ret[i])) { + ret[i] = deep ? dojo.lang.shallowCopy(obj[i], deep) : obj[i] + } + } + } else { + if (dojo.lang.isArray(obj)) { + ret = []; + for (i = 0; i < obj.length; i++) { + ret[i] = deep ? dojo.lang.shallowCopy(obj[i], deep) : obj[i] + } + } else { + ret = obj + } + } + return ret +}; +dojo.lang.firstValued = function () { + for (var i = 0; i < arguments.length; i++) { + if (typeof arguments[i] != 'undefined') { + return arguments[i] + } + } + return undefined +}; +dojo.lang.getObjPathValue = function (_138, _139, _13a) { + with (dojo.parseObjPath(_138, _139, _13a)) { + return dojo.evalProp(prop, obj, _13a) + } +}; +dojo.lang.setObjPathValue = function (_13b, _13c, _13d, _13e) { + dojo.deprecated('dojo.lang.setObjPathValue', 'use dojo.parseObjPath and the \'=\' operator', '0.6'); + if (arguments.length < 4) { + _13e = true + } + with (dojo.parseObjPath(_13b, _13d, _13e)) { + if (obj && (_13e || (prop in obj))) { + obj[prop] = _13c + } + } +}; +dojo.provide('dojo.io.common'); +dojo.io.transports = []; +dojo.io.hdlrFuncNames = ['load', 'error', 'timeout']; +dojo.io.Request = function (url, _140, _141, _142) { + if ((arguments.length == 1) && (arguments[0].constructor == Object)) { + this.fromKwArgs(arguments[0]) + } else { + this.url = url; + if (_140) { + this.mimetype = _140 + } + if (_141) { + this.transport = _141 + } + if (arguments.length >= 4) { + this.changeUrl = _142 + } + } +}; dojo.lang.extend(dojo.io.Request, { - url: '', - mimetype: 'text/plain', - method: 'GET', - content: undefined, - transport: undefined, - changeUrl: undefined, - formNode: undefined, - sync: false, - bindSuccess: false, - useCache: false, - preventCache: false, - jsonFilter: function(_143) { - if ((this.mimetype == 'text/json-comment-filtered') || (this.mimetype == 'application/json-comment-filtered')) { - var _144 = _143.indexOf('/*') - var _145 = _143.lastIndexOf('*/') - if ((_144 == -1) || (_145 == -1)) { - dojo.debug('your JSON wasn\'t comment filtered!') - return '' - } - return _143.substring(_144 + 2, _145) - } - dojo.debug('please consider using a mimetype of text/json-comment-filtered to avoid potential security issues with JSON endpoints') - return _143 - }, - load: function(type, data, _148, _149) { - }, - error: function(type, _14b, _14c, _14d) { - }, - timeout: function(type, _14f, _150, _151) { - }, - handle: function(type, data, _154, _155) { - }, - timeoutSeconds: 0, - abort: function() { - }, - fromKwArgs: function(_156) { - if (_156['url']) { - _156.url = _156.url.toString() - } - if (_156['formNode']) { - _156.formNode = dojo.byId(_156.formNode) - } - if (!_156['method'] && _156['formNode'] && _156['formNode'].method) { - _156.method = _156['formNode'].method - } - if (!_156['handle'] && _156['handler']) { - _156.handle = _156.handler - } - if (!_156['load'] && _156['loaded']) { - _156.load = _156.loaded - } - if (!_156['changeUrl'] && _156['changeURL']) { - _156.changeUrl = _156.changeURL - } - _156.encoding = dojo.lang.firstValued(_156['encoding'], djConfig['bindEncoding'], '') - _156.sendTransport = dojo.lang.firstValued(_156['sendTransport'], djConfig['ioSendTransport'], false) - var _157 = dojo.lang.isFunction - for (var x = 0; x < dojo.io.hdlrFuncNames.length; x++) { - var fn = dojo.io.hdlrFuncNames[x] - if (_156[fn] && _157(_156[fn])) { - continue - } - if (_156['handle'] && _157(_156['handle'])) { - _156[fn] = _156.handle - } - } - dojo.lang.mixin(this, _156) - } -}) -dojo.io.Error = function(msg, type, num) { - this.message = msg - this.type = type || 'unknown' - this.number = num || 0 -} -dojo.io.transports.addTransport = function(name) { - this.push(name) - this[name] = dojo.io[name] -} -dojo.io.bind = function(_15e) { - if (!(_15e instanceof dojo.io.Request)) { - try { - _15e = new dojo.io.Request(_15e) - } catch (e) { - dojo.debug(e) - } - } - var _15f = '' - if (_15e['transport']) { - _15f = _15e['transport'] - if (!this[_15f]) { - dojo.io.sendBindError(_15e, 'No dojo.io.bind() transport with name \'' + _15e['transport'] + '\'.') - return _15e - } - if (!this[_15f].canHandle(_15e)) { - dojo.io.sendBindError(_15e, 'dojo.io.bind() transport with name \'' + _15e['transport'] + '\' cannot handle this type of request.') - return _15e - } - } else { - for (var x = 0; x < dojo.io.transports.length; x++) { - var tmp = dojo.io.transports[x] - if ((this[tmp]) && (this[tmp].canHandle(_15e))) { - _15f = tmp - break - } - } - if (_15f == '') { - dojo.io.sendBindError(_15e, 'None of the loaded transports for dojo.io.bind()' + ' can handle the request.') - return _15e - } - } - this[_15f].bind(_15e) - _15e.bindSuccess = true - return _15e -} -dojo.io.sendBindError = function(_162, _163) { - if ((typeof _162.error == 'function' || typeof _162.handle == 'function') && (typeof setTimeout == 'function' || typeof setTimeout == 'object')) { - var _164 = new dojo.io.Error(_163) - setTimeout(function() { - _162[(typeof _162.error == 'function') ? 'error' : 'handle']('error', _164, null, _162) - }, 50) - } else { - dojo.raise(_163) - } -} -dojo.io.queueBind = function(_165) { - if (!(_165 instanceof dojo.io.Request)) { - try { - _165 = new dojo.io.Request(_165) - } catch (e) { - dojo.debug(e) - } - } - var _166 = _165.load - _165.load = function() { - dojo.io._queueBindInFlight = false - var ret = _166.apply(this, arguments) - dojo.io._dispatchNextQueueBind() - return ret - } - var _168 = _165.error - _165.error = function() { - dojo.io._queueBindInFlight = false - var ret = _168.apply(this, arguments) - dojo.io._dispatchNextQueueBind() - return ret - } - dojo.io._bindQueue.push(_165) - dojo.io._dispatchNextQueueBind() - return _165 -} -dojo.io._dispatchNextQueueBind = function() { - if (!dojo.io._queueBindInFlight) { - dojo.io._queueBindInFlight = true - if (dojo.io._bindQueue.length > 0) { - dojo.io.bind(dojo.io._bindQueue.shift()) - } else { - dojo.io._queueBindInFlight = false - } - } -} -dojo.io._bindQueue = [] -dojo.io._queueBindInFlight = false -dojo.io.argsFromMap = function(map, _16b, last) { - var enc = /utf/i.test(_16b || '') ? encodeURIComponent : dojo.string.encodeAscii - var _16e = [] - var _16f = new Object() - for (var name in map) { - var _171 = function(elt) { - var val = enc(name) + '=' + enc(elt) - _16e[(last == name) ? 'push' : 'unshift'](val) - } - if (!_16f[name]) { - var _174 = map[name] - if (dojo.lang.isArray(_174)) { - dojo.lang.forEach(_174, _171) - } else { - _171(_174) - } - } - } - return _16e.join('&') -} -dojo.io.setIFrameSrc = function(_175, src, _177) { - try { - var r = dojo.render.html - if (!_177) { - if (r.safari) { - _175.location = src - } else { - frames[_175.name].location = src - } - } else { - var idoc - if (r.ie) { - idoc = _175.contentWindow.document - } else { - if (r.safari) { - idoc = _175.document - } else { - idoc = _175.contentWindow - } - } - if (!idoc) { - _175.location = src - return - } else { - idoc.location.replace(src) - } - } - } catch (e) { - dojo.debug(e) - dojo.debug('setIFrameSrc: ' + e) - } -} -dojo.provide('dojo.lang.array') + url: '', + mimetype: 'text/plain', + method: 'GET', + content: undefined, + transport: undefined, + changeUrl: undefined, + formNode: undefined, + sync: false, + bindSuccess: false, + useCache: false, + preventCache: false, + jsonFilter: function (_143) { + if ((this.mimetype == 'text/json-comment-filtered') || (this.mimetype == 'application/json-comment-filtered')) { + var _144 = _143.indexOf('/*'); + var _145 = _143.lastIndexOf('*/'); + if ((_144 == -1) || (_145 == -1)) { + dojo.debug('your JSON wasn\'t comment filtered!'); + return '' + } + return _143.substring(_144 + 2, _145) + } + dojo.debug('please consider using a mimetype of text/json-comment-filtered to avoid potential security issues with JSON endpoints'); + return _143 + }, + load: function (type, data, _148, _149) { + }, + error: function (type, _14b, _14c, _14d) { + }, + timeout: function (type, _14f, _150, _151) { + }, + handle: function (type, data, _154, _155) { + }, + timeoutSeconds: 0, + abort: function () { + }, + fromKwArgs: function (_156) { + if (_156['url']) { + _156.url = _156.url.toString() + } + if (_156['formNode']) { + _156.formNode = dojo.byId(_156.formNode) + } + if (!_156['method'] && _156['formNode'] && _156['formNode'].method) { + _156.method = _156['formNode'].method + } + if (!_156['handle'] && _156['handler']) { + _156.handle = _156.handler + } + if (!_156['load'] && _156['loaded']) { + _156.load = _156.loaded + } + if (!_156['changeUrl'] && _156['changeURL']) { + _156.changeUrl = _156.changeURL + } + _156.encoding = dojo.lang.firstValued(_156['encoding'], djConfig['bindEncoding'], ''); + _156.sendTransport = dojo.lang.firstValued(_156['sendTransport'], djConfig['ioSendTransport'], false); + var _157 = dojo.lang.isFunction; + for (var x = 0; x < dojo.io.hdlrFuncNames.length; x++) { + var fn = dojo.io.hdlrFuncNames[x]; + if (_156[fn] && _157(_156[fn])) { + continue + } + if (_156['handle'] && _157(_156['handle'])) { + _156[fn] = _156.handle + } + } + dojo.lang.mixin(this, _156) + } +}); +dojo.io.Error = function (msg, type, num) { + this.message = msg; + this.type = type || 'unknown'; + this.number = num || 0 +}; +dojo.io.transports.addTransport = function (name) { + this.push(name); + this[name] = dojo.io[name] +}; +dojo.io.bind = function (_15e) { + if (!(_15e instanceof dojo.io.Request)) { + try { + _15e = new dojo.io.Request(_15e) + } catch (e) { + dojo.debug(e) + } + } + var _15f = ''; + if (_15e['transport']) { + _15f = _15e['transport']; + if (!this[_15f]) { + dojo.io.sendBindError(_15e, 'No dojo.io.bind() transport with name \'' + _15e['transport'] + '\'.'); + return _15e + } + if (!this[_15f].canHandle(_15e)) { + dojo.io.sendBindError(_15e, 'dojo.io.bind() transport with name \'' + + _15e['transport'] + + '\' cannot handle this type of request.'); + return _15e + } + } else { + for (var x = 0; x < dojo.io.transports.length; x++) { + var tmp = dojo.io.transports[x]; + if ((this[tmp]) && (this[tmp].canHandle(_15e))) { + _15f = tmp; + break + } + } + if (_15f == '') { + dojo.io.sendBindError(_15e, 'None of the loaded transports for dojo.io.bind()' + ' can handle the request.'); + return _15e + } + } + this[_15f].bind(_15e); + _15e.bindSuccess = true; + return _15e +}; +dojo.io.sendBindError = function (_162, _163) { + if ((typeof _162.error == 'function' || typeof _162.handle == 'function') && (typeof setTimeout + == 'function' + || typeof setTimeout + == 'object')) { + var _164 = new dojo.io.Error(_163); + setTimeout(function () { + _162[(typeof _162.error == 'function') ? 'error' : 'handle']('error', _164, null, _162) + }, 50) + } else { + dojo.raise(_163) + } +}; +dojo.io.queueBind = function (_165) { + if (!(_165 instanceof dojo.io.Request)) { + try { + _165 = new dojo.io.Request(_165) + } catch (e) { + dojo.debug(e) + } + } + var _166 = _165.load; + _165.load = function () { + dojo.io._queueBindInFlight = false; + var ret = _166.apply(this, arguments); + dojo.io._dispatchNextQueueBind(); + return ret + }; + var _168 = _165.error; + _165.error = function () { + dojo.io._queueBindInFlight = false; + var ret = _168.apply(this, arguments); + dojo.io._dispatchNextQueueBind(); + return ret + }; + dojo.io._bindQueue.push(_165); + dojo.io._dispatchNextQueueBind(); + return _165 +}; +dojo.io._dispatchNextQueueBind = function () { + if (!dojo.io._queueBindInFlight) { + dojo.io._queueBindInFlight = true; + if (dojo.io._bindQueue.length > 0) { + dojo.io.bind(dojo.io._bindQueue.shift()) + } else { + dojo.io._queueBindInFlight = false + } + } +}; +dojo.io._bindQueue = []; +dojo.io._queueBindInFlight = false; +dojo.io.argsFromMap = function (map, _16b, last) { + var enc = /utf/i.test(_16b || '') ? encodeURIComponent : dojo.string.encodeAscii; + var _16e = []; + var _16f = {}; + for (var name in map) { + var _171 = function (elt) { + var val = enc(name) + '=' + enc(elt); + _16e[(last == name) ? 'push' : 'unshift'](val) + }; + if (!_16f[name]) { + var _174 = map[name]; + if (dojo.lang.isArray(_174)) { + dojo.lang.forEach(_174, _171) + } else { + _171(_174) + } + } + } + return _16e.join('&') +}; +dojo.io.setIFrameSrc = function (_175, src, _177) { + try { + var r = dojo.render.html; + if (!_177) { + if (r.safari) { + _175.location = src + } else { + frames[_175.name].location = src + } + } else { + var idoc; + if (r.ie) { + idoc = _175.contentWindow.document + } else { + if (r.safari) { + idoc = _175.document + } else { + idoc = _175.contentWindow + } + } + if (!idoc) { + _175.location = src; + + } else { + idoc.location.replace(src) + } + } + } catch (e) { + dojo.debug(e); + dojo.debug('setIFrameSrc: ' + e) + } +}; +dojo.provide('dojo.lang.array'); dojo.lang.mixin(dojo.lang, { - has: function(obj, name) { - try { - return typeof obj[name] != 'undefined' - } catch (e) { - return false - } - }, isEmpty: function(obj) { - if (dojo.lang.isObject(obj)) { - var tmp = {} - var _17e = 0 - for (var x in obj) { - if (obj[x] && (!tmp[x])) { - _17e++ - break - } - } - return _17e == 0 - } else { - if (dojo.lang.isArrayLike(obj) || dojo.lang.isString(obj)) { - return obj.length == 0 - } - } - }, map: function(arr, obj, _182) { - var _183 = dojo.lang.isString(arr) - if (_183) { - arr = arr.split('') - } - if (dojo.lang.isFunction(obj) && (!_182)) { - _182 = obj - obj = dj_global - } else { - if (dojo.lang.isFunction(obj) && _182) { - var _184 = obj - obj = _182 - _182 = _184 - } - } - if (Array.map) { - var _185 = Array.map(arr, _182, obj) - } else { - var _185 = [] - for (var i = 0; i < arr.length; ++i) { - _185.push(_182.call(obj, arr[i])) - } - } - if (_183) { - return _185.join('') - } else { - return _185 - } - }, reduce: function(arr, _188, obj, _18a) { - var _18b = _188 - if (arguments.length == 2) { - _18a = _188 - _18b = arr[0] - arr = arr.slice(1) - } else { - if (arguments.length == 3) { - if (dojo.lang.isFunction(obj)) { - _18a = obj - obj = null - } - } else { - if (dojo.lang.isFunction(obj)) { - var tmp = _18a - _18a = obj - obj = tmp - } - } - } - var ob = obj || dj_global - dojo.lang.map(arr, function(val) { - _18b = _18a.call(ob, _18b, val) - }) - return _18b - }, forEach: function(_18f, _190, _191) { - if (dojo.lang.isString(_18f)) { - _18f = _18f.split('') - } - if (Array.forEach) { - Array.forEach(_18f, _190, _191) - } else { - if (!_191) { - _191 = dj_global - } - for (var i = 0, l = _18f.length; i < l; i++) { - _190.call(_191, _18f[i], i, _18f) - } - } - }, _everyOrSome: function(_194, arr, _196, _197) { - if (dojo.lang.isString(arr)) { - arr = arr.split('') - } - if (Array.every) { - return Array[_194 ? 'every' : 'some'](arr, _196, _197) - } else { - if (!_197) { - _197 = dj_global - } - for (var i = 0, l = arr.length; i < l; i++) { - var _19a = _196.call(_197, arr[i], i, arr) - if (_194 && !_19a) { - return false - } else { - if ((!_194) && (_19a)) { - return true - } - } - } - return Boolean(_194) - } - }, every: function(arr, _19c, _19d) { - return this._everyOrSome(true, arr, _19c, _19d) - }, some: function(arr, _19f, _1a0) { - return this._everyOrSome(false, arr, _19f, _1a0) - }, filter: function(arr, _1a2, _1a3) { - var _1a4 = dojo.lang.isString(arr) - if (_1a4) { - arr = arr.split('') - } - var _1a5 - if (Array.filter) { - _1a5 = Array.filter(arr, _1a2, _1a3) - } else { - if (!_1a3) { - if (arguments.length >= 3) { - dojo.raise('thisObject doesn\'t exist!') - } - _1a3 = dj_global - } - _1a5 = [] - for (var i = 0; i < arr.length; i++) { - if (_1a2.call(_1a3, arr[i], i, arr)) { - _1a5.push(arr[i]) - } - } - } - if (_1a4) { - return _1a5.join('') - } else { - return _1a5 - } - }, unnest: function() { - var out = [] - for (var i = 0; i < arguments.length; i++) { - if (dojo.lang.isArrayLike(arguments[i])) { - var add = dojo.lang.unnest.apply(this, arguments[i]) - out = out.concat(add) - } else { - out.push(arguments[i]) - } - } - return out - }, toArray: function(_1aa, _1ab) { - var _1ac = [] - for (var i = _1ab || 0; i < _1aa.length; i++) { - _1ac.push(_1aa[i]) - } - return _1ac - } -}) -dojo.provide('dojo.lang.func') -dojo.lang.hitch = function(_1ae, _1af) { - var args = [] - for (var x = 2; x < arguments.length; x++) { - args.push(arguments[x]) - } - var fcn = (dojo.lang.isString(_1af) ? _1ae[_1af] : _1af) || function() { - } - return function() { - var ta = args.concat([]) - for (var x = 0; x < arguments.length; x++) { - ta.push(arguments[x]) - } - return fcn.apply(_1ae, ta) - } -} -dojo.lang.anonCtr = 0 -dojo.lang.anon = {} -dojo.lang.nameAnonFunc = function(_1b5, _1b6, _1b7) { - var nso = (_1b6 || dojo.lang.anon) - if ((_1b7) || ((dj_global['djConfig']) && (djConfig['slowAnonFuncLookups'] == true))) { - for (var x in nso) { - try { - if (nso[x] === _1b5) { - return x - } - } catch (e) { - } - } - } - var ret = '__' + dojo.lang.anonCtr++ - while (typeof nso[ret] != 'undefined') { - ret = '__' + dojo.lang.anonCtr++ - } - nso[ret] = _1b5 - return ret -} -dojo.lang.forward = function(_1bb) { - return function() { - return this[_1bb].apply(this, arguments) - } -} -dojo.lang.curry = function(_1bc, func) { - var _1be = [] - _1bc = _1bc || dj_global - if (dojo.lang.isString(func)) { - func = _1bc[func] - } - for (var x = 2; x < arguments.length; x++) { - _1be.push(arguments[x]) - } - var _1c0 = (func['__preJoinArity'] || func.length) - _1be.length + has: function (obj, name) { + try { + return typeof obj[name] != 'undefined' + } catch (e) { + return false + } + }, isEmpty: function (obj) { + if (dojo.lang.isObject(obj)) { + var tmp = {}; + var _17e = 0; + for (var x in obj) { + if (obj[x] && (!tmp[x])) { + _17e++; + break + } + } + return _17e == 0 + } else { + if (dojo.lang.isArrayLike(obj) || dojo.lang.isString(obj)) { + return obj.length == 0 + } + } + }, map: function (arr, obj, _182) { + var _183 = dojo.lang.isString(arr); + if (_183) { + arr = arr.split('') + } + if (dojo.lang.isFunction(obj) && (!_182)) { + _182 = obj; + obj = dj_global + } else { + if (dojo.lang.isFunction(obj) && _182) { + var _184 = obj; + obj = _182; + _182 = _184 + } + } + if (Array.map) { + var _185 = Array.map(arr, _182, obj) + } else { + var _185 = []; + for (var i = 0; i < arr.length; ++i) { + _185.push(_182.call(obj, arr[i])) + } + } + if (_183) { + return _185.join('') + } else { + return _185 + } + }, reduce: function (arr, _188, obj, _18a) { + var _18b = _188; + if (arguments.length == 2) { + _18a = _188; + _18b = arr[0]; + arr = arr.slice(1) + } else { + if (arguments.length == 3) { + if (dojo.lang.isFunction(obj)) { + _18a = obj; + obj = null + } + } else { + if (dojo.lang.isFunction(obj)) { + var tmp = _18a; + _18a = obj; + obj = tmp + } + } + } + var ob = obj || dj_global; + dojo.lang.map(arr, function (val) { + _18b = _18a.call(ob, _18b, val) + }); + return _18b + }, forEach: function (_18f, _190, _191) { + if (dojo.lang.isString(_18f)) { + _18f = _18f.split('') + } + if (Array.forEach) { + Array.forEach(_18f, _190, _191) + } else { + if (!_191) { + _191 = dj_global + } + for (var i = 0, l = _18f.length; i < l; i++) { + _190.call(_191, _18f[i], i, _18f) + } + } + }, _everyOrSome: function (_194, arr, _196, _197) { + if (dojo.lang.isString(arr)) { + arr = arr.split('') + } + if (Array.every) { + return Array[_194 ? 'every' : 'some'](arr, _196, _197) + } else { + if (!_197) { + _197 = dj_global + } + for (var i = 0, l = arr.length; i < l; i++) { + var _19a = _196.call(_197, arr[i], i, arr); + if (_194 && !_19a) { + return false + } else { + if ((!_194) && (_19a)) { + return true + } + } + } + return Boolean(_194) + } + }, every: function (arr, _19c, _19d) { + return this._everyOrSome(true, arr, _19c, _19d) + }, some: function (arr, _19f, _1a0) { + return this._everyOrSome(false, arr, _19f, _1a0) + }, filter: function (arr, _1a2, _1a3) { + var _1a4 = dojo.lang.isString(arr); + if (_1a4) { + arr = arr.split('') + } + var _1a5; + if (Array.filter) { + _1a5 = Array.filter(arr, _1a2, _1a3) + } else { + if (!_1a3) { + if (arguments.length >= 3) { + dojo.raise('thisObject doesn\'t exist!') + } + _1a3 = dj_global + } + _1a5 = []; + for (var i = 0; i < arr.length; i++) { + if (_1a2.call(_1a3, arr[i], i, arr)) { + _1a5.push(arr[i]) + } + } + } + if (_1a4) { + return _1a5.join('') + } else { + return _1a5 + } + }, unnest: function () { + var out = []; + for (var i = 0; i < arguments.length; i++) { + if (dojo.lang.isArrayLike(arguments[i])) { + var add = dojo.lang.unnest.apply(this, arguments[i]); + out = out.concat(add) + } else { + out.push(arguments[i]) + } + } + return out + }, toArray: function (_1aa, _1ab) { + var _1ac = []; + for (var i = _1ab || 0; i < _1aa.length; i++) { + _1ac.push(_1aa[i]) + } + return _1ac + } +}); +dojo.provide('dojo.lang.func'); +dojo.lang.hitch = function (_1ae, _1af) { + var args = []; + for (var x = 2; x < arguments.length; x++) { + args.push(arguments[x]) + } + var fcn = (dojo.lang.isString(_1af) ? _1ae[_1af] : _1af) || function () { + }; + return function () { + var ta = args.concat([]); + for (var x = 0; x < arguments.length; x++) { + ta.push(arguments[x]) + } + return fcn.apply(_1ae, ta) + } +}; +dojo.lang.anonCtr = 0; +dojo.lang.anon = {}; +dojo.lang.nameAnonFunc = function (_1b5, _1b6, _1b7) { + var nso = (_1b6 || dojo.lang.anon); + if ((_1b7) || ((dj_global['djConfig']) && (djConfig['slowAnonFuncLookups'] == true))) { + for (var x in nso) { + try { + if (nso[x] === _1b5) { + return x + } + } catch (e) { + } + } + } + var ret = '__' + dojo.lang.anonCtr++; + while (typeof nso[ret] != 'undefined') { + ret = '__' + dojo.lang.anonCtr++ + } + nso[ret] = _1b5; + return ret +}; +dojo.lang.forward = function (_1bb) { + return function () { + return this[_1bb].apply(this, arguments) + } +}; +dojo.lang.curry = function (_1bc, func) { + var _1be = []; + _1bc = _1bc || dj_global; + if (dojo.lang.isString(func)) { + func = _1bc[func] + } + for (var x = 2; x < arguments.length; x++) { + _1be.push(arguments[x]) + } + var _1c0 = (func['__preJoinArity'] || func.length) - _1be.length; - function gather(_1c1, _1c2, _1c3) { - var _1c4 = _1c3 - var _1c5 = _1c2.slice(0) - for (var x = 0; x < _1c1.length; x++) { - _1c5.push(_1c1[x]) - } - _1c3 = _1c3 - _1c1.length - if (_1c3 <= 0) { - var res = func.apply(_1bc, _1c5) - _1c3 = _1c4 - return res - } else { - return function() { - return gather(arguments, _1c5, _1c3) - } - } - } + function gather(_1c1, _1c2, _1c3) { + var _1c4 = _1c3; + var _1c5 = _1c2.slice(0); + for (var x = 0; x < _1c1.length; x++) { + _1c5.push(_1c1[x]) + } + _1c3 = _1c3 - _1c1.length; + if (_1c3 <= 0) { + var res = func.apply(_1bc, _1c5); + _1c3 = _1c4; + return res + } else { + return function () { + return gather(arguments, _1c5, _1c3) + } + } + } - return gather([], _1be, _1c0) -} -dojo.lang.curryArguments = function(_1c8, func, args, _1cb) { - var _1cc = [] - var x = _1cb || 0 - for (x = _1cb; x < args.length; x++) { - _1cc.push(args[x]) - } - return dojo.lang.curry.apply(dojo.lang, [_1c8, func].concat(_1cc)) -} -dojo.lang.tryThese = function() { - for (var x = 0; x < arguments.length; x++) { - try { - if (typeof arguments[x] == 'function') { - var ret = (arguments[x]()) - if (ret) { - return ret - } - } - } catch (e) { - dojo.debug(e) - } - } -} -dojo.lang.delayThese = function(farr, cb, _1d2, _1d3) { - if (!farr.length) { - if (typeof _1d3 == 'function') { - _1d3() - } - return - } - if ((typeof _1d2 == 'undefined') && (typeof cb == 'number')) { - _1d2 = cb - cb = function() { - } - } else { - if (!cb) { - cb = function() { - } - if (!_1d2) { - _1d2 = 0 - } - } - } - setTimeout(function() { - (farr.shift())() - cb() - dojo.lang.delayThese(farr, cb, _1d2, _1d3) - }, _1d2) -} -dojo.provide('dojo.string.extras') -dojo.string.substituteParams = function(_1d4, hash) { - var map = (typeof hash == 'object') ? hash : dojo.lang.toArray(arguments, 1) - return _1d4.replace(/\%\{(\w+)\}/g, function(_1d7, key) { - if (typeof (map[key]) != 'undefined' && map[key] != null) { - return map[key] - } - dojo.raise('Substitution not found: ' + key) - }) -} -dojo.string.capitalize = function(str) { - if (!dojo.lang.isString(str)) { - return '' - } - if (arguments.length == 0) { - str = this - } - var _1da = str.split(' ') - for (var i = 0; i < _1da.length; i++) { - _1da[i] = _1da[i].charAt(0).toUpperCase() + _1da[i].substring(1) - } - return _1da.join(' ') -} -dojo.string.isBlank = function(str) { - if (!dojo.lang.isString(str)) { - return true - } - return (dojo.string.trim(str).length == 0) -} -dojo.string.encodeAscii = function(str) { - if (!dojo.lang.isString(str)) { - return str - } - var ret = '' - var _1df = escape(str) - var _1e0, re = /%u([0-9A-F]{4})/i - while ((_1e0 = _1df.match(re))) { - var num = Number('0x' + _1e0[1]) - var _1e3 = escape('&#' + num + ';') - ret += _1df.substring(0, _1e0.index) + _1e3 - _1df = _1df.substring(_1e0.index + _1e0[0].length) - } - ret += _1df.replace(/\+/g, '%2B') - return ret -} -dojo.string.escape = function(type, str) { - var args = dojo.lang.toArray(arguments, 1) - switch (type.toLowerCase()) { - case 'xml': - case 'html': - case 'xhtml': - return dojo.string.escapeXml.apply(this, args) - case 'sql': - return dojo.string.escapeSql.apply(this, args) - case 'regexp': - case 'regex': - return dojo.string.escapeRegExp.apply(this, args) - case 'javascript': - case 'jscript': - case 'js': - return dojo.string.escapeJavaScript.apply(this, args) - case 'ascii': - return dojo.string.encodeAscii.apply(this, args) - default: - return str - } -} -dojo.string.escapeXml = function(str, _1e8) { - str = str.replace(/&/gm, '&').replace(//gm, '>').replace(/"/gm, '"') - if (!_1e8) { - str = str.replace(/'/gm, ''') - } - return str -} -dojo.string.escapeSql = function(str) { - return str.replace(/'/gm, '\'\'') -} -dojo.string.escapeRegExp = function(str) { - return str.replace(/\\/gm, '\\\\').replace(/([\f\b\n\t\r[\^$|?*+(){}])/gm, '\\$1') -} -dojo.string.escapeJavaScript = function(str) { - return str.replace(/(["'\f\b\n\t\r])/gm, '\\$1') -} -dojo.string.escapeString = function(str) { - return ('"' + str.replace(/(["\\])/g, '\\$1') + '"').replace(/[\f]/g, '\\f').replace(/[\b]/g, '\\b').replace(/[\n]/g, '\\n').replace(/[\t]/g, '\\t').replace(/[\r]/g, '\\r') -} -dojo.string.summary = function(str, len) { - if (!len || str.length <= len) { - return str - } - return str.substring(0, len).replace(/\.+$/, '') + '...' -} -dojo.string.endsWith = function(str, end, _1f1) { - if (_1f1) { - str = str.toLowerCase() - end = end.toLowerCase() - } - if ((str.length - end.length) < 0) { - return false - } - return str.lastIndexOf(end) == str.length - end.length -} -dojo.string.endsWithAny = function(str) { - for (var i = 1; i < arguments.length; i++) { - if (dojo.string.endsWith(str, arguments[i])) { - return true - } - } - return false -} -dojo.string.startsWith = function(str, _1f5, _1f6) { - if (_1f6) { - str = str.toLowerCase() - _1f5 = _1f5.toLowerCase() - } - return str.indexOf(_1f5) == 0 -} -dojo.string.startsWithAny = function(str) { - for (var i = 1; i < arguments.length; i++) { - if (dojo.string.startsWith(str, arguments[i])) { - return true - } - } - return false -} -dojo.string.has = function(str) { - for (var i = 1; i < arguments.length; i++) { - if (str.indexOf(arguments[i]) > -1) { - return true - } - } - return false -} -dojo.string.normalizeNewlines = function(text, _1fc) { - if (_1fc == '\n') { - text = text.replace(/\r\n/g, '\n') - text = text.replace(/\r/g, '\n') - } else { - if (_1fc == '\r') { - text = text.replace(/\r\n/g, '\r') - text = text.replace(/\n/g, '\r') - } else { - text = text.replace(/([^\r])\n/g, '$1\r\n').replace(/\r([^\n])/g, '\r\n$1') - } - } - return text -} -dojo.string.splitEscaped = function(str, _1fe) { - var _1ff = [] - for (var i = 0, _201 = 0; i < str.length; i++) { - if (str.charAt(i) == '\\') { - i++ - continue - } - if (str.charAt(i) == _1fe) { - _1ff.push(str.substring(_201, i)) - _201 = i + 1 - } - } - _1ff.push(str.substr(_201)) - return _1ff -} -dojo.provide('dojo.dom') -dojo.dom.ELEMENT_NODE = 1 -dojo.dom.ATTRIBUTE_NODE = 2 -dojo.dom.TEXT_NODE = 3 -dojo.dom.CDATA_SECTION_NODE = 4 -dojo.dom.ENTITY_REFERENCE_NODE = 5 -dojo.dom.ENTITY_NODE = 6 -dojo.dom.PROCESSING_INSTRUCTION_NODE = 7 -dojo.dom.COMMENT_NODE = 8 -dojo.dom.DOCUMENT_NODE = 9 -dojo.dom.DOCUMENT_TYPE_NODE = 10 -dojo.dom.DOCUMENT_FRAGMENT_NODE = 11 -dojo.dom.NOTATION_NODE = 12 -dojo.dom.dojoml = 'http://www.dojotoolkit.org/2004/dojoml' + return gather([], _1be, _1c0) +}; +dojo.lang.curryArguments = function (_1c8, func, args, _1cb) { + var _1cc = []; + var x = _1cb || 0; + for (x = _1cb; x < args.length; x++) { + _1cc.push(args[x]) + } + return dojo.lang.curry.apply(dojo.lang, [_1c8, func].concat(_1cc)) +}; +dojo.lang.tryThese = function () { + for (var x = 0; x < arguments.length; x++) { + try { + if (typeof arguments[x] == 'function') { + var ret = (arguments[x]()); + if (ret) { + return ret + } + } + } catch (e) { + dojo.debug(e) + } + } +}; +dojo.lang.delayThese = function (farr, cb, _1d2, _1d3) { + if (!farr.length) { + if (typeof _1d3 == 'function') { + _1d3() + } + return + } + if ((typeof _1d2 == 'undefined') && (typeof cb == 'number')) { + _1d2 = cb; + cb = function () { + } + } else { + if (!cb) { + cb = function () { + }; + if (!_1d2) { + _1d2 = 0 + } + } + } + setTimeout(function () { + (farr.shift())(); + cb(); + dojo.lang.delayThese(farr, cb, _1d2, _1d3) + }, _1d2) +}; +dojo.provide('dojo.string.extras'); +dojo.string.substituteParams = function (_1d4, hash) { + var map = (typeof hash == 'object') ? hash : dojo.lang.toArray(arguments, 1); + return _1d4.replace(/\%\{(\w+)\}/g, function (_1d7, key) { + if (typeof (map[key]) != 'undefined' && map[key] != null) { + return map[key] + } + dojo.raise('Substitution not found: ' + key) + }) +}; +dojo.string.capitalize = function (str) { + if (!dojo.lang.isString(str)) { + return '' + } + if (arguments.length == 0) { + str = this + } + var _1da = str.split(' '); + for (var i = 0; i < _1da.length; i++) { + _1da[i] = _1da[i].charAt(0).toUpperCase() + _1da[i].substring(1) + } + return _1da.join(' ') +}; +dojo.string.isBlank = function (str) { + if (!dojo.lang.isString(str)) { + return true + } + return (dojo.string.trim(str).length == 0) +}; +dojo.string.encodeAscii = function (str) { + if (!dojo.lang.isString(str)) { + return str + } + var ret = ''; + var _1df = escape(str); + var _1e0, re = /%u([0-9A-F]{4})/i; + while ((_1e0 = _1df.match(re))) { + var num = Number('0x' + _1e0[1]); + var _1e3 = escape('&#' + num + ';'); + ret += _1df.substring(0, _1e0.index) + _1e3; + _1df = _1df.substring(_1e0.index + _1e0[0].length) + } + ret += _1df.replace(/\+/g, '%2B'); + return ret +}; +dojo.string.escape = function (type, str) { + var args = dojo.lang.toArray(arguments, 1); + switch (type.toLowerCase()) { + case 'xml': + case 'html': + case 'xhtml': + return dojo.string.escapeXml.apply(this, args); + case 'sql': + return dojo.string.escapeSql.apply(this, args); + case 'regexp': + case 'regex': + return dojo.string.escapeRegExp.apply(this, args); + case 'javascript': + case 'jscript': + case 'js': + return dojo.string.escapeJavaScript.apply(this, args); + case 'ascii': + return dojo.string.encodeAscii.apply(this, args); + default: + return str + } +}; +dojo.string.escapeXml = function (str, _1e8) { + str = str.replace(/&/gm, '&').replace(//gm, '>').replace(/"/gm, '"'); + if (!_1e8) { + str = str.replace(/'/gm, ''') + } + return str +}; +dojo.string.escapeSql = function (str) { + return str.replace(/'/gm, '\'\'') +}; +dojo.string.escapeRegExp = function (str) { + return str.replace(/\\/gm, '\\\\').replace(/([\f\b\n\t\r[\^$|?*+(){}])/gm, '\\$1') +}; +dojo.string.escapeJavaScript = function (str) { + return str.replace(/(["'\f\b\n\t\r])/gm, '\\$1') +}; +dojo.string.escapeString = function (str) { + return ('"' + + str.replace(/(["\\])/g, '\\$1') + + '"').replace(/[\f]/g, '\\f').replace(/[\b]/g, '\\b').replace(/[\n]/g, '\\n').replace(/[\t]/g, '\\t').replace(/[\r]/g, '\\r') +}; +dojo.string.summary = function (str, len) { + if (!len || str.length <= len) { + return str + } + return str.substring(0, len).replace(/\.+$/, '') + '...' +}; +dojo.string.endsWith = function (str, end, _1f1) { + if (_1f1) { + str = str.toLowerCase(); + end = end.toLowerCase() + } + if ((str.length - end.length) < 0) { + return false + } + return str.lastIndexOf(end) == str.length - end.length +}; +dojo.string.endsWithAny = function (str) { + for (var i = 1; i < arguments.length; i++) { + if (dojo.string.endsWith(str, arguments[i])) { + return true + } + } + return false +}; +dojo.string.startsWith = function (str, _1f5, _1f6) { + if (_1f6) { + str = str.toLowerCase(); + _1f5 = _1f5.toLowerCase() + } + return str.indexOf(_1f5) == 0 +}; +dojo.string.startsWithAny = function (str) { + for (var i = 1; i < arguments.length; i++) { + if (dojo.string.startsWith(str, arguments[i])) { + return true + } + } + return false +}; +dojo.string.has = function (str) { + for (var i = 1; i < arguments.length; i++) { + if (str.indexOf(arguments[i]) > -1) { + return true + } + } + return false +}; +dojo.string.normalizeNewlines = function (text, _1fc) { + if (_1fc == '\n') { + text = text.replace(/\r\n/g, '\n'); + text = text.replace(/\r/g, '\n') + } else { + if (_1fc == '\r') { + text = text.replace(/\r\n/g, '\r'); + text = text.replace(/\n/g, '\r') + } else { + text = text.replace(/([^\r])\n/g, '$1\r\n').replace(/\r([^\n])/g, '\r\n$1') + } + } + return text +}; +dojo.string.splitEscaped = function (str, _1fe) { + var _1ff = []; + for (var i = 0, _201 = 0; i < str.length; i++) { + if (str.charAt(i) == '\\') { + i++; + continue + } + if (str.charAt(i) == _1fe) { + _1ff.push(str.substring(_201, i)); + _201 = i + 1 + } + } + _1ff.push(str.substr(_201)); + return _1ff +}; +dojo.provide('dojo.dom'); +dojo.dom.ELEMENT_NODE = 1; +dojo.dom.ATTRIBUTE_NODE = 2; +dojo.dom.TEXT_NODE = 3; +dojo.dom.CDATA_SECTION_NODE = 4; +dojo.dom.ENTITY_REFERENCE_NODE = 5; +dojo.dom.ENTITY_NODE = 6; +dojo.dom.PROCESSING_INSTRUCTION_NODE = 7; +dojo.dom.COMMENT_NODE = 8; +dojo.dom.DOCUMENT_NODE = 9; +dojo.dom.DOCUMENT_TYPE_NODE = 10; +dojo.dom.DOCUMENT_FRAGMENT_NODE = 11; +dojo.dom.NOTATION_NODE = 12; +dojo.dom.dojoml = 'http://www.dojotoolkit.org/2004/dojoml'; dojo.dom.xmlns = { - svg: 'http://www.w3.org/2000/svg', - smil: 'http://www.w3.org/2001/SMIL20/', - mml: 'http://www.w3.org/1998/Math/MathML', - cml: 'http://www.xml-cml.org', - xlink: 'http://www.w3.org/1999/xlink', - xhtml: 'http://www.w3.org/1999/xhtml', - xul: 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul', - xbl: 'http://www.mozilla.org/xbl', - fo: 'http://www.w3.org/1999/XSL/Format', - xsl: 'http://www.w3.org/1999/XSL/Transform', - xslt: 'http://www.w3.org/1999/XSL/Transform', - xi: 'http://www.w3.org/2001/XInclude', - xforms: 'http://www.w3.org/2002/01/xforms', - saxon: 'http://icl.com/saxon', - xalan: 'http://xml.apache.org/xslt', - xsd: 'http://www.w3.org/2001/XMLSchema', - dt: 'http://www.w3.org/2001/XMLSchema-datatypes', - xsi: 'http://www.w3.org/2001/XMLSchema-instance', - rdf: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', - rdfs: 'http://www.w3.org/2000/01/rdf-schema#', - dc: 'http://purl.org/dc/elements/1.1/', - dcq: 'http://purl.org/dc/qualifiers/1.0', - 'soap-env': 'http://schemas.xmlsoap.org/soap/envelope/', - wsdl: 'http://schemas.xmlsoap.org/wsdl/', - AdobeExtensions: 'http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/' -} -dojo.dom.isNode = function(wh) { - if (typeof Element == 'function') { - try { - return wh instanceof Element - } catch (e) { - } - } else { - return wh && !isNaN(wh.nodeType) - } -} -dojo.dom.getUniqueId = function() { - var _203 = dojo.doc() - do { - var id = 'dj_unique_' + (++arguments.callee._idIncrement) - } while (_203.getElementById(id)) - return id -} -dojo.dom.getUniqueId._idIncrement = 0 -dojo.dom.firstElement = dojo.dom.getFirstChildElement = function(_205, _206) { - var node = _205.firstChild - while (node && node.nodeType != dojo.dom.ELEMENT_NODE) { - node = node.nextSibling - } - if (_206 && node && node.tagName && node.tagName.toLowerCase() != _206.toLowerCase()) { - node = dojo.dom.nextElement(node, _206) - } - return node -} -dojo.dom.lastElement = dojo.dom.getLastChildElement = function(_208, _209) { - var node = _208.lastChild - while (node && node.nodeType != dojo.dom.ELEMENT_NODE) { - node = node.previousSibling - } - if (_209 && node && node.tagName && node.tagName.toLowerCase() != _209.toLowerCase()) { - node = dojo.dom.prevElement(node, _209) - } - return node -} -dojo.dom.nextElement = dojo.dom.getNextSiblingElement = function(node, _20c) { - if (!node) { - return null - } - do { - node = node.nextSibling - } while (node && node.nodeType != dojo.dom.ELEMENT_NODE) - if (node && _20c && _20c.toLowerCase() != node.tagName.toLowerCase()) { - return dojo.dom.nextElement(node, _20c) - } - return node -} -dojo.dom.prevElement = dojo.dom.getPreviousSiblingElement = function(node, _20e) { - if (!node) { - return null - } - if (_20e) { - _20e = _20e.toLowerCase() - } - do { - node = node.previousSibling - } while (node && node.nodeType != dojo.dom.ELEMENT_NODE) - if (node && _20e && _20e.toLowerCase() != node.tagName.toLowerCase()) { - return dojo.dom.prevElement(node, _20e) - } - return node -} -dojo.dom.moveChildren = function(_20f, _210, trim) { - var _212 = 0 - if (trim) { - while (_20f.hasChildNodes() && _20f.firstChild.nodeType == dojo.dom.TEXT_NODE) { - _20f.removeChild(_20f.firstChild) - } - while (_20f.hasChildNodes() && _20f.lastChild.nodeType == dojo.dom.TEXT_NODE) { - _20f.removeChild(_20f.lastChild) - } - } - while (_20f.hasChildNodes()) { - _210.appendChild(_20f.firstChild) - _212++ - } - return _212 -} -dojo.dom.copyChildren = function(_213, _214, trim) { - var _216 = _213.cloneNode(true) - return this.moveChildren(_216, _214, trim) -} -dojo.dom.replaceChildren = function(node, _218) { - var _219 = [] - if (dojo.render.html.ie) { - for (var i = 0; i < node.childNodes.length; i++) { - _219.push(node.childNodes[i]) - } - } - dojo.dom.removeChildren(node) - node.appendChild(_218) - for (var i = 0; i < _219.length; i++) { - dojo.dom.destroyNode(_219[i]) - } -} -dojo.dom.removeChildren = function(node) { - var _21c = node.childNodes.length - while (node.hasChildNodes()) { - dojo.dom.removeNode(node.firstChild) - } - return _21c -} -dojo.dom.replaceNode = function(node, _21e) { - return node.parentNode.replaceChild(_21e, node) -} -dojo.dom.destroyNode = function(node) { - if (node.parentNode) { - node = dojo.dom.removeNode(node) - } - if (node.nodeType != 3) { - if (dojo.evalObjPath('dojo.event.browser.clean', false)) { - dojo.event.browser.clean(node) - } - if (dojo.render.html.ie) { - node.outerHTML = '' - } - } -} -dojo.dom.removeNode = function(node) { - if (node && node.parentNode) { - return node.parentNode.removeChild(node) - } -} -dojo.dom.getAncestors = function(node, _222, _223) { - var _224 = [] - var _225 = (_222 && (_222 instanceof Function || typeof _222 == 'function')) - while (node) { - if (!_225 || _222(node)) { - _224.push(node) - } - if (_223 && _224.length > 0) { - return _224[0] - } - node = node.parentNode - } - if (_223) { - return null - } - return _224 -} -dojo.dom.getAncestorsByTag = function(node, tag, _228) { - tag = tag.toLowerCase() - return dojo.dom.getAncestors(node, function(el) { - return ((el.tagName) && (el.tagName.toLowerCase() == tag)) - }, _228) -} -dojo.dom.getFirstAncestorByTag = function(node, tag) { - return dojo.dom.getAncestorsByTag(node, tag, true) -} -dojo.dom.isDescendantOf = function(node, _22d, _22e) { - if (_22e && node) { - node = node.parentNode - } - while (node) { - if (node == _22d) { - return true - } - node = node.parentNode - } - return false -} -dojo.dom.innerXML = function(node) { - if (node.innerXML) { - return node.innerXML - } else { - if (node.xml) { - return node.xml - } else { - if (typeof XMLSerializer != 'undefined') { - return (new XMLSerializer()).serializeToString(node) - } - } - } -} -dojo.dom.createDocument = function() { - var doc = null - var _231 = dojo.doc() - if (!dj_undef('ActiveXObject')) { - var _232 = ['MSXML2', 'Microsoft', 'MSXML', 'MSXML3'] - for (var i = 0; i < _232.length; i++) { - try { - doc = new ActiveXObject(_232[i] + '.XMLDOM') - } catch (e) { - } - if (doc) { - break - } - } - } else { - if ((_231.implementation) && (_231.implementation.createDocument)) { - doc = _231.implementation.createDocument('', '', null) - } - } - return doc -} -dojo.dom.createDocumentFromText = function(str, _235) { - if (!_235) { - _235 = 'text/xml' - } - if (!dj_undef('DOMParser')) { - var _236 = new DOMParser() - return _236.parseFromString(str, _235) - } else { - if (!dj_undef('ActiveXObject')) { - var _237 = dojo.dom.createDocument() - if (_237) { - _237.async = false - _237.loadXML(str) - return _237 - } else { - dojo.debug('toXml didn\'t work?') - } - } else { - var _238 = dojo.doc() - if (_238.createElement) { - var tmp = _238.createElement('xml') - tmp.innerHTML = str - if (_238.implementation && _238.implementation.createDocument) { - var _23a = _238.implementation.createDocument('foo', '', null) - for (var i = 0; i < tmp.childNodes.length; i++) { - _23a.importNode(tmp.childNodes.item(i), true) - } - return _23a - } - return ((tmp.document) && (tmp.document.firstChild ? tmp.document.firstChild : tmp)) - } - } - } - return null -} -dojo.dom.prependChild = function(node, _23d) { - if (_23d.firstChild) { - _23d.insertBefore(node, _23d.firstChild) - } else { - _23d.appendChild(node) - } - return true -} -dojo.dom.insertBefore = function(node, ref, _240) { - if ((_240 != true) && (node === ref || node.nextSibling === ref)) { - return false - } - var _241 = ref.parentNode - _241.insertBefore(node, ref) - return true -} -dojo.dom.insertAfter = function(node, ref, _244) { - var pn = ref.parentNode - if (ref == pn.lastChild) { - if ((_244 != true) && (node === ref)) { - return false - } - pn.appendChild(node) - } else { - return this.insertBefore(node, ref.nextSibling, _244) - } - return true -} -dojo.dom.insertAtPosition = function(node, ref, _248) { - if ((!node) || (!ref) || (!_248)) { - return false - } - switch (_248.toLowerCase()) { - case 'before': - return dojo.dom.insertBefore(node, ref) - case 'after': - return dojo.dom.insertAfter(node, ref) - case 'first': - if (ref.firstChild) { - return dojo.dom.insertBefore(node, ref.firstChild) - } else { - ref.appendChild(node) - return true - } - break - default: - ref.appendChild(node) - return true - } -} -dojo.dom.insertAtIndex = function(node, _24a, _24b) { - var _24c = _24a.childNodes - if (!_24c.length || _24c.length == _24b) { - _24a.appendChild(node) - return true - } - if (_24b == 0) { - return dojo.dom.prependChild(node, _24a) - } - return dojo.dom.insertAfter(node, _24c[_24b - 1]) -} -dojo.dom.textContent = function(node, text) { - if (arguments.length > 1) { - var _24f = dojo.doc() - dojo.dom.replaceChildren(node, _24f.createTextNode(text)) - return text - } else { - if (node.textContent != undefined) { - return node.textContent - } - var _250 = '' - if (node == null) { - return _250 - } - for (var i = 0; i < node.childNodes.length; i++) { - switch (node.childNodes[i].nodeType) { - case 1: - case 5: - _250 += dojo.dom.textContent(node.childNodes[i]) - break - case 3: - case 2: - case 4: - _250 += node.childNodes[i].nodeValue - break - default: - break - } - } - return _250 - } -} -dojo.dom.hasParent = function(node) { - return Boolean(node && node.parentNode && dojo.dom.isNode(node.parentNode)) -} -dojo.dom.isTag = function(node) { - if (node && node.tagName) { - for (var i = 1; i < arguments.length; i++) { - if (node.tagName == String(arguments[i])) { - return String(arguments[i]) - } - } - } - return '' -} -dojo.dom.setAttributeNS = function(elem, _256, _257, _258) { - if (elem == null || ((elem == undefined) && (typeof elem == 'undefined'))) { - dojo.raise('No element given to dojo.dom.setAttributeNS') - } - if (!((elem.setAttributeNS == undefined) && (typeof elem.setAttributeNS == 'undefined'))) { - elem.setAttributeNS(_256, _257, _258) - } else { - var _259 = elem.ownerDocument - var _25a = _259.createNode(2, _257, _256) - _25a.nodeValue = _258 - elem.setAttributeNode(_25a) - } -} -dojo.provide('dojo.undo.browser') + svg: 'http://www.w3.org/2000/svg', + smil: 'http://www.w3.org/2001/SMIL20/', + mml: 'http://www.w3.org/1998/Math/MathML', + cml: 'http://www.xml-cml.org', + xlink: 'http://www.w3.org/1999/xlink', + xhtml: 'http://www.w3.org/1999/xhtml', + xul: 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul', + xbl: 'http://www.mozilla.org/xbl', + fo: 'http://www.w3.org/1999/XSL/Format', + xsl: 'http://www.w3.org/1999/XSL/Transform', + xslt: 'http://www.w3.org/1999/XSL/Transform', + xi: 'http://www.w3.org/2001/XInclude', + xforms: 'http://www.w3.org/2002/01/xforms', + saxon: 'http://icl.com/saxon', + xalan: 'http://xml.apache.org/xslt', + xsd: 'http://www.w3.org/2001/XMLSchema', + dt: 'http://www.w3.org/2001/XMLSchema-datatypes', + xsi: 'http://www.w3.org/2001/XMLSchema-instance', + rdf: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', + rdfs: 'http://www.w3.org/2000/01/rdf-schema#', + dc: 'http://purl.org/dc/elements/1.1/', + dcq: 'http://purl.org/dc/qualifiers/1.0', + 'soap-env': 'http://schemas.xmlsoap.org/soap/envelope/', + wsdl: 'http://schemas.xmlsoap.org/wsdl/', + AdobeExtensions: 'http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/' +}; +dojo.dom.isNode = function (wh) { + if (typeof Element == 'function') { + try { + return wh instanceof Element + } catch (e) { + } + } else { + return wh && !isNaN(wh.nodeType) + } +}; +dojo.dom.getUniqueId = function () { + var _203 = dojo.doc(); + do { + var id = 'dj_unique_' + (++arguments.callee._idIncrement) + } while (_203.getElementById(id)); + return id +}; +dojo.dom.getUniqueId._idIncrement = 0; +dojo.dom.firstElement = dojo.dom.getFirstChildElement = function (_205, _206) { + var node = _205.firstChild; + while (node && node.nodeType != dojo.dom.ELEMENT_NODE) { + node = node.nextSibling + } + if (_206 && node && node.tagName && node.tagName.toLowerCase() != _206.toLowerCase()) { + node = dojo.dom.nextElement(node, _206) + } + return node +}; +dojo.dom.lastElement = dojo.dom.getLastChildElement = function (_208, _209) { + var node = _208.lastChild; + while (node && node.nodeType != dojo.dom.ELEMENT_NODE) { + node = node.previousSibling + } + if (_209 && node && node.tagName && node.tagName.toLowerCase() != _209.toLowerCase()) { + node = dojo.dom.prevElement(node, _209) + } + return node +}; +dojo.dom.nextElement = dojo.dom.getNextSiblingElement = function (node, _20c) { + if (!node) { + return null + } + do { + node = node.nextSibling + } while (node && node.nodeType != dojo.dom.ELEMENT_NODE); + if (node && _20c && _20c.toLowerCase() != node.tagName.toLowerCase()) { + return dojo.dom.nextElement(node, _20c) + } + return node +}; +dojo.dom.prevElement = dojo.dom.getPreviousSiblingElement = function (node, _20e) { + if (!node) { + return null + } + if (_20e) { + _20e = _20e.toLowerCase() + } + do { + node = node.previousSibling + } while (node && node.nodeType != dojo.dom.ELEMENT_NODE); + if (node && _20e && _20e.toLowerCase() != node.tagName.toLowerCase()) { + return dojo.dom.prevElement(node, _20e) + } + return node +}; +dojo.dom.moveChildren = function (_20f, _210, trim) { + var _212 = 0; + if (trim) { + while (_20f.hasChildNodes() && _20f.firstChild.nodeType == dojo.dom.TEXT_NODE) { + _20f.removeChild(_20f.firstChild) + } + while (_20f.hasChildNodes() && _20f.lastChild.nodeType == dojo.dom.TEXT_NODE) { + _20f.removeChild(_20f.lastChild) + } + } + while (_20f.hasChildNodes()) { + _210.appendChild(_20f.firstChild); + _212++ + } + return _212 +}; +dojo.dom.copyChildren = function (_213, _214, trim) { + var _216 = _213.cloneNode(true); + return this.moveChildren(_216, _214, trim) +}; +dojo.dom.replaceChildren = function (node, _218) { + var _219 = []; + if (dojo.render.html.ie) { + for (var i = 0; i < node.childNodes.length; i++) { + _219.push(node.childNodes[i]) + } + } + dojo.dom.removeChildren(node); + node.appendChild(_218); + for (var i = 0; i < _219.length; i++) { + dojo.dom.destroyNode(_219[i]) + } +}; +dojo.dom.removeChildren = function (node) { + var _21c = node.childNodes.length; + while (node.hasChildNodes()) { + dojo.dom.removeNode(node.firstChild) + } + return _21c +}; +dojo.dom.replaceNode = function (node, _21e) { + return node.parentNode.replaceChild(_21e, node) +}; +dojo.dom.destroyNode = function (node) { + if (node.parentNode) { + node = dojo.dom.removeNode(node) + } + if (node.nodeType != 3) { + if (dojo.evalObjPath('dojo.event.browser.clean', false)) { + dojo.event.browser.clean(node) + } + if (dojo.render.html.ie) { + node.outerHTML = '' + } + } +}; +dojo.dom.removeNode = function (node) { + if (node && node.parentNode) { + return node.parentNode.removeChild(node) + } +}; +dojo.dom.getAncestors = function (node, _222, _223) { + var _224 = []; + var _225 = (_222 && (_222 instanceof Function || typeof _222 == 'function')); + while (node) { + if (!_225 || _222(node)) { + _224.push(node) + } + if (_223 && _224.length > 0) { + return _224[0] + } + node = node.parentNode + } + if (_223) { + return null + } + return _224 +}; +dojo.dom.getAncestorsByTag = function (node, tag, _228) { + tag = tag.toLowerCase(); + return dojo.dom.getAncestors(node, function (el) { + return ((el.tagName) && (el.tagName.toLowerCase() == tag)) + }, _228) +}; +dojo.dom.getFirstAncestorByTag = function (node, tag) { + return dojo.dom.getAncestorsByTag(node, tag, true) +}; +dojo.dom.isDescendantOf = function (node, _22d, _22e) { + if (_22e && node) { + node = node.parentNode + } + while (node) { + if (node == _22d) { + return true + } + node = node.parentNode + } + return false +}; +dojo.dom.innerXML = function (node) { + if (node.innerXML) { + return node.innerXML + } else { + if (node.xml) { + return node.xml + } else { + if (typeof XMLSerializer != 'undefined') { + return (new XMLSerializer()).serializeToString(node) + } + } + } +}; +dojo.dom.createDocument = function () { + var doc = null; + var _231 = dojo.doc(); + if (!dj_undef('ActiveXObject')) { + var _232 = ['MSXML2', 'Microsoft', 'MSXML', 'MSXML3']; + for (var i = 0; i < _232.length; i++) { + try { + doc = new ActiveXObject(_232[i] + '.XMLDOM') + } catch (e) { + } + if (doc) { + break + } + } + } else { + if ((_231.implementation) && (_231.implementation.createDocument)) { + doc = _231.implementation.createDocument('', '', null) + } + } + return doc +}; +dojo.dom.createDocumentFromText = function (str, _235) { + if (!_235) { + _235 = 'text/xml' + } + if (!dj_undef('DOMParser')) { + var _236 = new DOMParser(); + return _236.parseFromString(str, _235) + } else { + if (!dj_undef('ActiveXObject')) { + var _237 = dojo.dom.createDocument(); + if (_237) { + _237.async = false; + _237.loadXML(str); + return _237 + } else { + dojo.debug('toXml didn\'t work?') + } + } else { + var _238 = dojo.doc(); + if (_238.createElement) { + var tmp = _238.createElement('xml'); + tmp.innerHTML = str; + if (_238.implementation && _238.implementation.createDocument) { + var _23a = _238.implementation.createDocument('foo', '', null); + for (var i = 0; i < tmp.childNodes.length; i++) { + _23a.importNode(tmp.childNodes.item(i), true) + } + return _23a + } + return ((tmp.document) && (tmp.document.firstChild ? tmp.document.firstChild : tmp)) + } + } + } + return null +}; +dojo.dom.prependChild = function (node, _23d) { + if (_23d.firstChild) { + _23d.insertBefore(node, _23d.firstChild) + } else { + _23d.appendChild(node) + } + return true +}; +dojo.dom.insertBefore = function (node, ref, _240) { + if ((_240 != true) && (node === ref || node.nextSibling === ref)) { + return false + } + var _241 = ref.parentNode; + _241.insertBefore(node, ref); + return true +}; +dojo.dom.insertAfter = function (node, ref, _244) { + var pn = ref.parentNode; + if (ref == pn.lastChild) { + if ((_244 != true) && (node === ref)) { + return false + } + pn.appendChild(node) + } else { + return this.insertBefore(node, ref.nextSibling, _244) + } + return true +}; +dojo.dom.insertAtPosition = function (node, ref, _248) { + if ((!node) || (!ref) || (!_248)) { + return false + } + switch (_248.toLowerCase()) { + case 'before': + return dojo.dom.insertBefore(node, ref); + case 'after': + return dojo.dom.insertAfter(node, ref); + case 'first': + if (ref.firstChild) { + return dojo.dom.insertBefore(node, ref.firstChild) + } else { + ref.appendChild(node); + return true + } + break; + default: + ref.appendChild(node); + return true + } +}; +dojo.dom.insertAtIndex = function (node, _24a, _24b) { + var _24c = _24a.childNodes; + if (!_24c.length || _24c.length == _24b) { + _24a.appendChild(node); + return true + } + if (_24b == 0) { + return dojo.dom.prependChild(node, _24a) + } + return dojo.dom.insertAfter(node, _24c[_24b - 1]) +}; +dojo.dom.textContent = function (node, text) { + if (arguments.length > 1) { + var _24f = dojo.doc(); + dojo.dom.replaceChildren(node, _24f.createTextNode(text)); + return text + } else { + if (node.textContent != undefined) { + return node.textContent + } + var _250 = ''; + if (node == null) { + return _250 + } + for (var i = 0; i < node.childNodes.length; i++) { + switch (node.childNodes[i].nodeType) { + case 1: + case 5: + _250 += dojo.dom.textContent(node.childNodes[i]); + break; + case 3: + case 2: + case 4: + _250 += node.childNodes[i].nodeValue; + break; + default: + break + } + } + return _250 + } +}; +dojo.dom.hasParent = function (node) { + return Boolean(node && node.parentNode && dojo.dom.isNode(node.parentNode)) +}; +dojo.dom.isTag = function (node) { + if (node && node.tagName) { + for (var i = 1; i < arguments.length; i++) { + if (node.tagName == String(arguments[i])) { + return String(arguments[i]) + } + } + } + return '' +}; +dojo.dom.setAttributeNS = function (elem, _256, _257, _258) { + if (elem == null || ((elem == undefined) && (typeof elem == 'undefined'))) { + dojo.raise('No element given to dojo.dom.setAttributeNS') + } + if (!((elem.setAttributeNS == undefined) && (typeof elem.setAttributeNS == 'undefined'))) { + elem.setAttributeNS(_256, _257, _258) + } else { + var _259 = elem.ownerDocument; + var _25a = _259.createNode(2, _257, _256); + _25a.nodeValue = _258; + elem.setAttributeNode(_25a) + } +}; +dojo.provide('dojo.undo.browser'); try { - if ((!djConfig['preventBackButtonFix']) && (!dojo.hostenv.post_load_)) { - document.write('') - } + if ((!djConfig['preventBackButtonFix']) && (!dojo.hostenv.post_load_)) { + document.write('') + } } catch (e) { } if (dojo.render.html.opera) { - dojo.debug('Opera is not supported with dojo.undo.browser, so back/forward detection will not work.') + dojo.debug('Opera is not supported with dojo.undo.browser, so back/forward detection will not work.') } dojo.undo.browser = { - initialHref: (!dj_undef('window')) ? window.location.href : '', - initialHash: (!dj_undef('window')) ? window.location.hash : '', - moveForward: false, - historyStack: [], - forwardStack: [], - historyIframe: null, - bookmarkAnchor: null, - locationTimer: null, - setInitialState: function(args) { - this.initialState = this._createState(this.initialHref, args, this.initialHash) - }, - addToHistory: function(args) { - this.forwardStack = [] - var hash = null - var url = null - if (!this.historyIframe) { - if (djConfig['useXDomain'] && !djConfig['dojoIframeHistoryUrl']) { - dojo.debug('dojo.undo.browser: When using cross-domain Dojo builds,' + ' please save iframe_history.html to your domain and set djConfig.dojoIframeHistoryUrl' + ' to the path on your domain to iframe_history.html') - } - this.historyIframe = window.frames['djhistory'] - } - if (!this.bookmarkAnchor) { - this.bookmarkAnchor = document.createElement('a') - dojo.body().appendChild(this.bookmarkAnchor) - this.bookmarkAnchor.style.display = 'none' - } - if (args['changeUrl']) { - hash = '#' + ((args['changeUrl'] !== true) ? args['changeUrl'] : (new Date()).getTime()) - if (this.historyStack.length == 0 && this.initialState.urlHash == hash) { - this.initialState = this._createState(url, args, hash) - return - } else { - if (this.historyStack.length > 0 && this.historyStack[this.historyStack.length - 1].urlHash == hash) { - this.historyStack[this.historyStack.length - 1] = this._createState(url, args, hash) - return - } - } - this.changingUrl = true - setTimeout('window.location.href = \'' + hash + '\'; dojo.undo.browser.changingUrl = false;', 1) - this.bookmarkAnchor.href = hash - if (dojo.render.html.ie) { - url = this._loadIframeHistory() - var _25f = args['back'] || args['backButton'] || args['handle'] - var tcb = function(_261) { - if (window.location.hash != '') { - setTimeout('window.location.href = \'' + hash + '\';', 1) - } - _25f.apply(this, [_261]) - } - if (args['back']) { - args.back = tcb - } else { - if (args['backButton']) { - args.backButton = tcb - } else { - if (args['handle']) { - args.handle = tcb - } - } - } - var _262 = args['forward'] || args['forwardButton'] || args['handle'] - var tfw = function(_264) { - if (window.location.hash != '') { - window.location.href = hash - } - if (_262) { - _262.apply(this, [_264]) - } - } - if (args['forward']) { - args.forward = tfw - } else { - if (args['forwardButton']) { - args.forwardButton = tfw - } else { - if (args['handle']) { - args.handle = tfw - } - } - } - } else { - if (dojo.render.html.moz) { - if (!this.locationTimer) { - this.locationTimer = setInterval('dojo.undo.browser.checkLocation();', 200) - } - } - } - } else { - url = this._loadIframeHistory() - } - this.historyStack.push(this._createState(url, args, hash)) - }, - checkLocation: function() { - if (!this.changingUrl) { - var hsl = this.historyStack.length - if ((window.location.hash == this.initialHash || window.location.href == this.initialHref) && (hsl == 1)) { - this.handleBackButton() - return - } - if (this.forwardStack.length > 0) { - if (this.forwardStack[this.forwardStack.length - 1].urlHash == window.location.hash) { - this.handleForwardButton() - return - } - } - if ((hsl >= 2) && (this.historyStack[hsl - 2])) { - if (this.historyStack[hsl - 2].urlHash == window.location.hash) { - this.handleBackButton() - return - } - } - } - }, - iframeLoaded: function(evt, _267) { - if (!dojo.render.html.opera) { - var _268 = this._getUrlQuery(_267.href) - if (_268 == null) { - if (this.historyStack.length == 1) { - this.handleBackButton() - } - return - } - if (this.moveForward) { - this.moveForward = false - return - } - if (this.historyStack.length >= 2 && _268 == this._getUrlQuery(this.historyStack[this.historyStack.length - 2].url)) { - this.handleBackButton() - } else { - if (this.forwardStack.length > 0 && _268 == this._getUrlQuery(this.forwardStack[this.forwardStack.length - 1].url)) { - this.handleForwardButton() - } - } - } - }, - handleBackButton: function() { - var _269 = this.historyStack.pop() - if (!_269) { - return - } - var last = this.historyStack[this.historyStack.length - 1] - if (!last && this.historyStack.length == 0) { - last = this.initialState - } - if (last) { - if (last.kwArgs['back']) { - last.kwArgs['back']() - } else { - if (last.kwArgs['backButton']) { - last.kwArgs['backButton']() - } else { - if (last.kwArgs['handle']) { - last.kwArgs.handle('back') - } - } - } - } - this.forwardStack.push(_269) - }, - handleForwardButton: function() { - var last = this.forwardStack.pop() - if (!last) { - return - } - if (last.kwArgs['forward']) { - last.kwArgs.forward() - } else { - if (last.kwArgs['forwardButton']) { - last.kwArgs.forwardButton() - } else { - if (last.kwArgs['handle']) { - last.kwArgs.handle('forward') - } - } - } - this.historyStack.push(last) - }, - _createState: function(url, args, hash) { - return { 'url': url, 'kwArgs': args, 'urlHash': hash } - }, - _getUrlQuery: function(url) { - var _270 = url.split('?') - if (_270.length < 2) { - return null - } else { - return _270[1] - } - }, - _loadIframeHistory: function() { - var url = (djConfig['dojoIframeHistoryUrl'] || dojo.hostenv.getBaseScriptUri() + 'iframe_history.html') + '?' + (new Date()).getTime() - this.moveForward = true - dojo.io.setIFrameSrc(this.historyIframe, url, false) - return url - } -} -dojo.provide('dojo.io.BrowserIO') + initialHref: (!dj_undef('window')) ? window.location.href : '', + initialHash: (!dj_undef('window')) ? window.location.hash : '', + moveForward: false, + historyStack: [], + forwardStack: [], + historyIframe: null, + bookmarkAnchor: null, + locationTimer: null, + setInitialState: function (args) { + this.initialState = this._createState(this.initialHref, args, this.initialHash) + }, + addToHistory: function (args) { + this.forwardStack = []; + var hash = null; + var url = null; + if (!this.historyIframe) { + if (djConfig['useXDomain'] && !djConfig['dojoIframeHistoryUrl']) { + dojo.debug('dojo.undo.browser: When using cross-domain Dojo builds,' + + ' please save iframe_history.html to your domain and set djConfig.dojoIframeHistoryUrl' + + ' to the path on your domain to iframe_history.html') + } + this.historyIframe = window.frames['djhistory'] + } + if (!this.bookmarkAnchor) { + this.bookmarkAnchor = document.createElement('a'); + dojo.body().appendChild(this.bookmarkAnchor); + this.bookmarkAnchor.style.display = 'none' + } + if (args['changeUrl']) { + hash = '#' + ((args['changeUrl'] !== true) ? args['changeUrl'] : (new Date()).getTime()); + if (this.historyStack.length == 0 && this.initialState.urlHash == hash) { + this.initialState = this._createState(url, args, hash); + return + } else { + if (this.historyStack.length > 0 && this.historyStack[this.historyStack.length - 1].urlHash == hash) { + this.historyStack[this.historyStack.length - 1] = this._createState(url, args, hash); + return + } + } + this.changingUrl = true; + setTimeout('window.location.href = \'' + hash + '\'; dojo.undo.browser.changingUrl = false;', 1); + this.bookmarkAnchor.href = hash; + if (dojo.render.html.ie) { + url = this._loadIframeHistory(); + var _25f = args['back'] || args['backButton'] || args['handle']; + var tcb = function (_261) { + if (window.location.hash != '') { + setTimeout('window.location.href = \'' + hash + '\';', 1) + } + _25f.apply(this, [_261]) + }; + if (args['back']) { + args.back = tcb + } else { + if (args['backButton']) { + args.backButton = tcb + } else { + if (args['handle']) { + args.handle = tcb + } + } + } + var _262 = args['forward'] || args['forwardButton'] || args['handle']; + var tfw = function (_264) { + if (window.location.hash != '') { + window.location.href = hash + } + if (_262) { + _262.apply(this, [_264]) + } + }; + if (args['forward']) { + args.forward = tfw + } else { + if (args['forwardButton']) { + args.forwardButton = tfw + } else { + if (args['handle']) { + args.handle = tfw + } + } + } + } else { + if (dojo.render.html.moz) { + if (!this.locationTimer) { + this.locationTimer = setInterval('dojo.undo.browser.checkLocation();', 200) + } + } + } + } else { + url = this._loadIframeHistory() + } + this.historyStack.push(this._createState(url, args, hash)) + }, + checkLocation: function () { + if (!this.changingUrl) { + var hsl = this.historyStack.length; + if ((window.location.hash == this.initialHash || window.location.href == this.initialHref) && (hsl == 1)) { + this.handleBackButton(); + return + } + if (this.forwardStack.length > 0) { + if (this.forwardStack[this.forwardStack.length - 1].urlHash == window.location.hash) { + this.handleForwardButton(); + return + } + } + if ((hsl >= 2) && (this.historyStack[hsl - 2])) { + if (this.historyStack[hsl - 2].urlHash == window.location.hash) { + this.handleBackButton(); + + } + } + } + }, + iframeLoaded: function (evt, _267) { + if (!dojo.render.html.opera) { + var _268 = this._getUrlQuery(_267.href); + if (_268 == null) { + if (this.historyStack.length == 1) { + this.handleBackButton() + } + return + } + if (this.moveForward) { + this.moveForward = false; + return + } + if (this.historyStack.length >= 2 && _268 == this._getUrlQuery(this.historyStack[this.historyStack.length + - 2].url)) { + this.handleBackButton() + } else { + if (this.forwardStack.length > 0 && _268 == this._getUrlQuery(this.forwardStack[this.forwardStack.length + - 1].url)) { + this.handleForwardButton() + } + } + } + }, + handleBackButton: function () { + var _269 = this.historyStack.pop(); + if (!_269) { + return + } + var last = this.historyStack[this.historyStack.length - 1]; + if (!last && this.historyStack.length == 0) { + last = this.initialState + } + if (last) { + if (last.kwArgs['back']) { + last.kwArgs['back']() + } else { + if (last.kwArgs['backButton']) { + last.kwArgs['backButton']() + } else { + if (last.kwArgs['handle']) { + last.kwArgs.handle('back') + } + } + } + } + this.forwardStack.push(_269) + }, + handleForwardButton: function () { + var last = this.forwardStack.pop(); + if (!last) { + return + } + if (last.kwArgs['forward']) { + last.kwArgs.forward() + } else { + if (last.kwArgs['forwardButton']) { + last.kwArgs.forwardButton() + } else { + if (last.kwArgs['handle']) { + last.kwArgs.handle('forward') + } + } + } + this.historyStack.push(last) + }, + _createState: function (url, args, hash) { + return {'url': url, 'kwArgs': args, 'urlHash': hash} + }, + _getUrlQuery: function (url) { + var _270 = url.split('?'); + if (_270.length < 2) { + return null + } else { + return _270[1] + } + }, + _loadIframeHistory: function () { + var url = (djConfig['dojoIframeHistoryUrl'] || dojo.hostenv.getBaseScriptUri() + 'iframe_history.html') + + '?' + + (new Date()).getTime(); + this.moveForward = true; + dojo.io.setIFrameSrc(this.historyIframe, url, false); + return url + } +}; +dojo.provide('dojo.io.BrowserIO'); if (!dj_undef('window')) { - dojo.io.checkChildrenForFile = function(node) { - var _273 = false - var _274 = node.getElementsByTagName('input') - dojo.lang.forEach(_274, function(_275) { - if (_273) { - return - } - if (_275.getAttribute('type') == 'file') { - _273 = true - } - }) - return _273 - } - dojo.io.formHasFile = function(_276) { - return dojo.io.checkChildrenForFile(_276) - } - dojo.io.updateNode = function(node, _278) { - node = dojo.byId(node) - var args = _278 - if (dojo.lang.isString(_278)) { - args = { url: _278 } - } - args.mimetype = 'text/html' - args.load = function(t, d, e) { - while (node.firstChild) { - dojo.dom.destroyNode(node.firstChild) - } - node.innerHTML = d - } - dojo.io.bind(args) - } - dojo.io.formFilter = function(node) { - var type = (node.type || '').toLowerCase() - return !node.disabled && node.name && !dojo.lang.inArray(['file', 'submit', 'image', 'reset', 'button'], type) - } - dojo.io.encodeForm = function(_27f, _280, _281) { - if ((!_27f) || (!_27f.tagName) || (!_27f.tagName.toLowerCase() == 'form')) { - dojo.raise('Attempted to encode a non-form element.') - } - if (!_281) { - _281 = dojo.io.formFilter - } - var enc = /utf/i.test(_280 || '') ? encodeURIComponent : dojo.string.encodeAscii - var _283 = [] - for (var i = 0; i < _27f.elements.length; i++) { - var elm = _27f.elements[i] - if (!elm || elm.tagName.toLowerCase() == 'fieldset' || !_281(elm)) { - continue - } - var name = enc(elm.name) - var type = elm.type.toLowerCase() - if (type == 'select-multiple') { - for (var j = 0; j < elm.options.length; j++) { - if (elm.options[j].selected) { - _283.push(name + '=' + enc(elm.options[j].value)) - } - } - } else { - if (dojo.lang.inArray(['radio', 'checkbox'], type)) { - if (elm.checked) { - _283.push(name + '=' + enc(elm.value)) - } - } else { - _283.push(name + '=' + enc(elm.value)) - } - } - } - var _289 = _27f.getElementsByTagName('input') - for (var i = 0; i < _289.length; i++) { - var _28a = _289[i] - if (_28a.type.toLowerCase() == 'image' && _28a.form == _27f && _281(_28a)) { - var name = enc(_28a.name) - _283.push(name + '=' + enc(_28a.value)) - _283.push(name + '.x=0') - _283.push(name + '.y=0') - } - } - return _283.join('&') + '&' - } - dojo.io.FormBind = function(args) { - this.bindArgs = {} - if (args && args.formNode) { - this.init(args) - } else { - if (args) { - this.init({ formNode: args }) - } - } - } - dojo.lang.extend(dojo.io.FormBind, { - form: null, bindArgs: null, clickedButton: null, init: function(args) { - var form = dojo.byId(args.formNode) - if (!form || !form.tagName || form.tagName.toLowerCase() != 'form') { - throw new Error('FormBind: Couldn\'t apply, invalid form') - } else { - if (this.form == form) { - return - } else { - if (this.form) { - throw new Error('FormBind: Already applied to a form') - } - } - } - dojo.lang.mixin(this.bindArgs, args) - this.form = form - this.connect(form, 'onsubmit', 'submit') - for (var i = 0; i < form.elements.length; i++) { - var node = form.elements[i] - if (node && node.type && dojo.lang.inArray(['submit', 'button'], node.type.toLowerCase())) { - this.connect(node, 'onclick', 'click') - } - } - var _290 = form.getElementsByTagName('input') - for (var i = 0; i < _290.length; i++) { - var _291 = _290[i] - if (_291.type.toLowerCase() == 'image' && _291.form == form) { - this.connect(_291, 'onclick', 'click') - } - } - }, onSubmit: function(form) { - return true - }, submit: function(e) { - e.preventDefault() - if (this.onSubmit(this.form)) { - dojo.io.bind(dojo.lang.mixin(this.bindArgs, { formFilter: dojo.lang.hitch(this, 'formFilter') })) - } - }, click: function(e) { - var node = e.currentTarget - if (node.disabled) { - return - } - this.clickedButton = node - }, formFilter: function(node) { - var type = (node.type || '').toLowerCase() - var _298 = false - if (node.disabled || !node.name) { - _298 = false - } else { - if (dojo.lang.inArray(['submit', 'button', 'image'], type)) { - if (!this.clickedButton) { - this.clickedButton = node - } - _298 = node == this.clickedButton - } else { - _298 = !dojo.lang.inArray(['file', 'submit', 'reset', 'button'], type) - } - } - return _298 - }, connect: function(_299, _29a, _29b) { - if (dojo.evalObjPath('dojo.event.connect')) { - dojo.event.connect(_299, _29a, this, _29b) - } else { - var fcn = dojo.lang.hitch(this, _29b) - _299[_29a] = function(e) { - if (!e) { - e = window.event - } - if (!e.currentTarget) { - e.currentTarget = e.srcElement - } - if (!e.preventDefault) { - e.preventDefault = function() { - window.event.returnValue = false - } - } - fcn(e) - } - } - } - }) - dojo.io.XMLHTTPTransport = new function() { - var _29e = this - var _29f = {} - this.useCache = false - this.preventCache = false + dojo.io.checkChildrenForFile = function (node) { + var _273 = false; + var _274 = node.getElementsByTagName('input'); + dojo.lang.forEach(_274, function (_275) { + if (_273) { + return + } + if (_275.getAttribute('type') == 'file') { + _273 = true + } + }); + return _273 + }; + dojo.io.formHasFile = function (_276) { + return dojo.io.checkChildrenForFile(_276) + }; + dojo.io.updateNode = function (node, _278) { + node = dojo.byId(node); + var args = _278; + if (dojo.lang.isString(_278)) { + args = {url: _278} + } + args.mimetype = 'text/html'; + args.load = function (t, d, e) { + while (node.firstChild) { + dojo.dom.destroyNode(node.firstChild) + } + node.innerHTML = d + }; + dojo.io.bind(args) + }; + dojo.io.formFilter = function (node) { + var type = (node.type || '').toLowerCase(); + return !node.disabled && node.name && !dojo.lang.inArray(['file', 'submit', 'image', 'reset', 'button'], type) + }; + dojo.io.encodeForm = function (_27f, _280, _281) { + if ((!_27f) || (!_27f.tagName) || (!_27f.tagName.toLowerCase() == 'form')) { + dojo.raise('Attempted to encode a non-form element.') + } + if (!_281) { + _281 = dojo.io.formFilter + } + var enc = /utf/i.test(_280 || '') ? encodeURIComponent : dojo.string.encodeAscii; + var _283 = []; + for (var i = 0; i < _27f.elements.length; i++) { + var elm = _27f.elements[i]; + if (!elm || elm.tagName.toLowerCase() == 'fieldset' || !_281(elm)) { + continue + } + var name = enc(elm.name); + var type = elm.type.toLowerCase(); + if (type == 'select-multiple') { + for (var j = 0; j < elm.options.length; j++) { + if (elm.options[j].selected) { + _283.push(name + '=' + enc(elm.options[j].value)) + } + } + } else { + if (dojo.lang.inArray(['radio', 'checkbox'], type)) { + if (elm.checked) { + _283.push(name + '=' + enc(elm.value)) + } + } else { + _283.push(name + '=' + enc(elm.value)) + } + } + } + var _289 = _27f.getElementsByTagName('input'); + for (var i = 0; i < _289.length; i++) { + var _28a = _289[i]; + if (_28a.type.toLowerCase() == 'image' && _28a.form == _27f && _281(_28a)) { + var name = enc(_28a.name); + _283.push(name + '=' + enc(_28a.value)); + _283.push(name + '.x=0'); + _283.push(name + '.y=0') + } + } + return _283.join('&') + '&' + }; + dojo.io.FormBind = function (args) { + this.bindArgs = {}; + if (args && args.formNode) { + this.init(args) + } else { + if (args) { + this.init({formNode: args}) + } + } + }; + dojo.lang.extend(dojo.io.FormBind, { + form: null, bindArgs: null, clickedButton: null, init: function (args) { + var form = dojo.byId(args.formNode); + if (!form || !form.tagName || form.tagName.toLowerCase() != 'form') { + throw new Error('FormBind: Couldn\'t apply, invalid form') + } else { + if (this.form == form) { + return + } else { + if (this.form) { + throw new Error('FormBind: Already applied to a form') + } + } + } + dojo.lang.mixin(this.bindArgs, args); + this.form = form; + this.connect(form, 'onsubmit', 'submit'); + for (var i = 0; i < form.elements.length; i++) { + var node = form.elements[i]; + if (node && node.type && dojo.lang.inArray(['submit', 'button'], node.type.toLowerCase())) { + this.connect(node, 'onclick', 'click') + } + } + var _290 = form.getElementsByTagName('input'); + for (var i = 0; i < _290.length; i++) { + var _291 = _290[i]; + if (_291.type.toLowerCase() == 'image' && _291.form == form) { + this.connect(_291, 'onclick', 'click') + } + } + }, onSubmit: function (form) { + return true + }, submit: function (e) { + e.preventDefault(); + if (this.onSubmit(this.form)) { + dojo.io.bind(dojo.lang.mixin(this.bindArgs, {formFilter: dojo.lang.hitch(this, 'formFilter')})) + } + }, click: function (e) { + var node = e.currentTarget; + if (node.disabled) { + return + } + this.clickedButton = node + }, formFilter: function (node) { + var type = (node.type || '').toLowerCase(); + var _298 = false; + if (node.disabled || !node.name) { + _298 = false + } else { + if (dojo.lang.inArray(['submit', 'button', 'image'], type)) { + if (!this.clickedButton) { + this.clickedButton = node + } + _298 = node == this.clickedButton + } else { + _298 = !dojo.lang.inArray(['file', 'submit', 'reset', 'button'], type) + } + } + return _298 + }, connect: function (_299, _29a, _29b) { + if (dojo.evalObjPath('dojo.event.connect')) { + dojo.event.connect(_299, _29a, this, _29b) + } else { + var fcn = dojo.lang.hitch(this, _29b); + _299[_29a] = function (e) { + if (!e) { + e = window.event + } + if (!e.currentTarget) { + e.currentTarget = e.srcElement + } + if (!e.preventDefault) { + e.preventDefault = function () { + window.event.returnValue = false + } + } + fcn(e) + } + } + } + }); + dojo.io.XMLHTTPTransport = new function () { + var _29e = this; + var _29f = {}; + this.useCache = false; + this.preventCache = false; - function getCacheKey(url, _2a1, _2a2) { - return url + '|' + _2a1 + '|' + _2a2.toLowerCase() - } + function getCacheKey(url, _2a1, _2a2) { + return url + '|' + _2a1 + '|' + _2a2.toLowerCase() + } - function addToCache(url, _2a4, _2a5, http) { - _29f[getCacheKey(url, _2a4, _2a5)] = http - } + function addToCache(url, _2a4, _2a5, http) { + _29f[getCacheKey(url, _2a4, _2a5)] = http + } - function getFromCache(url, _2a8, _2a9) { - return _29f[getCacheKey(url, _2a8, _2a9)] - } + function getFromCache(url, _2a8, _2a9) { + return _29f[getCacheKey(url, _2a8, _2a9)] + } - this.clearCache = function() { - _29f = {} - } + this.clearCache = function () { + _29f = {} + }; - function doLoad(_2aa, http, url, _2ad, _2ae) { - if (((http.status >= 200) && (http.status < 300)) || (http.status == 304) || (http.status == 1223) || (location.protocol == 'file:' && (http.status == 0 || http.status == undefined)) || (location.protocol == 'chrome:' && (http.status == 0 || http.status == undefined))) { - var ret - if (_2aa.method.toLowerCase() == 'head') { - var _2b0 = http.getAllResponseHeaders() - ret = {} - ret.toString = function() { - return _2b0 - } - var _2b1 = _2b0.split(/[\r\n]+/g) - for (var i = 0; i < _2b1.length; i++) { - var pair = _2b1[i].match(/^([^:]+)\s*:\s*(.+)$/i) - if (pair) { - ret[pair[1]] = pair[2] - } - } - } else { - if (_2aa.mimetype == 'text/javascript') { - try { - ret = dj_eval(http.responseText) - } catch (e) { - dojo.debug(e) - dojo.debug(http.responseText) - ret = null - } - } else { - if (_2aa.mimetype.substr(0, 9) == 'text/json' || _2aa.mimetype.substr(0, 16) == 'application/json') { - try { - ret = dj_eval('(' + _2aa.jsonFilter(http.responseText) + ')') - } catch (e) { - dojo.debug(e) - dojo.debug(http.responseText) - ret = false - } - } else { - if ((_2aa.mimetype == 'application/xml') || (_2aa.mimetype == 'text/xml')) { - ret = http.responseXML - if (!ret || typeof ret == 'string' || !http.getResponseHeader('Content-Type')) { - ret = dojo.dom.createDocumentFromText(http.responseText) - } - } else { - ret = http.responseText - } - } - } - } - if (_2ae) { - addToCache(url, _2ad, _2aa.method, http) - } - _2aa[(typeof _2aa.load == 'function') ? 'load' : 'handle']('load', ret, http, _2aa) - } else { - var _2b4 = new dojo.io.Error('XMLHttpTransport Error: ' + http.status + ' ' + http.statusText) - _2aa[(typeof _2aa.error == 'function') ? 'error' : 'handle']('error', _2b4, http, _2aa) - } - } + function doLoad(_2aa, http, url, _2ad, _2ae) { + if (((http.status >= 200) && (http.status < 300)) + || (http.status == 304) + || (http.status == 1223) + || (location.protocol == 'file:' && (http.status == 0 || http.status == undefined)) + || (location.protocol == 'chrome:' && (http.status == 0 || http.status == undefined))) { + var ret; + if (_2aa.method.toLowerCase() == 'head') { + var _2b0 = http.getAllResponseHeaders(); + ret = {}; + ret.toString = function () { + return _2b0 + }; + var _2b1 = _2b0.split(/[\r\n]+/g); + for (var i = 0; i < _2b1.length; i++) { + var pair = _2b1[i].match(/^([^:]+)\s*:\s*(.+)$/i); + if (pair) { + ret[pair[1]] = pair[2] + } + } + } else { + if (_2aa.mimetype == 'text/javascript') { + try { + ret = dj_eval(http.responseText) + } catch (e) { + dojo.debug(e); + dojo.debug(http.responseText); + ret = null + } + } else { + if (_2aa.mimetype.substr(0, 9) == 'text/json' || _2aa.mimetype.substr(0, 16) == 'application/json') { + try { + ret = dj_eval('(' + _2aa.jsonFilter(http.responseText) + ')') + } catch (e) { + dojo.debug(e); + dojo.debug(http.responseText); + ret = false + } + } else { + if ((_2aa.mimetype == 'application/xml') || (_2aa.mimetype == 'text/xml')) { + ret = http.responseXML; + if (!ret || typeof ret == 'string' || !http.getResponseHeader('Content-Type')) { + ret = dojo.dom.createDocumentFromText(http.responseText) + } + } else { + ret = http.responseText + } + } + } + } + if (_2ae) { + addToCache(url, _2ad, _2aa.method, http) + } + _2aa[(typeof _2aa.load == 'function') ? 'load' : 'handle']('load', ret, http, _2aa) + } else { + var _2b4 = new dojo.io.Error('XMLHttpTransport Error: ' + http.status + ' ' + http.statusText); + _2aa[(typeof _2aa.error == 'function') ? 'error' : 'handle']('error', _2b4, http, _2aa) + } + } - function setHeaders(http, _2b6) { - if (_2b6['headers']) { - for (var _2b7 in _2b6['headers']) { - if (_2b7.toLowerCase() == 'content-type' && !_2b6['contentType']) { - _2b6['contentType'] = _2b6['headers'][_2b7] - } else { - http.setRequestHeader(_2b7, _2b6['headers'][_2b7]) - } - } - } - } + function setHeaders(http, _2b6) { + if (_2b6['headers']) { + for (var _2b7 in _2b6['headers']) { + if (_2b7.toLowerCase() == 'content-type' && !_2b6['contentType']) { + _2b6['contentType'] = _2b6['headers'][_2b7] + } else { + http.setRequestHeader(_2b7, _2b6['headers'][_2b7]) + } + } + } + } - this.inFlight = [] - this.inFlightTimer = null - this.startWatchingInFlight = function() { - if (!this.inFlightTimer) { - this.inFlightTimer = setTimeout('dojo.io.XMLHTTPTransport.watchInFlight();', 10) - } - } - this.watchInFlight = function() { - var now = null - if (!dojo.hostenv._blockAsync && !_29e._blockAsync) { - for (var x = this.inFlight.length - 1; x >= 0; x--) { - try { - var tif = this.inFlight[x] - if (!tif || tif.http._aborted || !tif.http.readyState) { - this.inFlight.splice(x, 1) - continue - } - if (4 == tif.http.readyState) { - this.inFlight.splice(x, 1) - doLoad(tif.req, tif.http, tif.url, tif.query, tif.useCache) - } else { - if (tif.startTime) { - if (!now) { - now = (new Date()).getTime() - } - if (tif.startTime + (tif.req.timeoutSeconds * 1000) < now) { - if (typeof tif.http.abort == 'function') { - tif.http.abort() - } - this.inFlight.splice(x, 1) - tif.req[(typeof tif.req.timeout == 'function') ? 'timeout' : 'handle']('timeout', null, tif.http, tif.req) - } - } - } - } catch (e) { - try { - var _2bb = new dojo.io.Error('XMLHttpTransport.watchInFlight Error: ' + e) - tif.req[(typeof tif.req.error == 'function') ? 'error' : 'handle']('error', _2bb, tif.http, tif.req) - } catch (e2) { - dojo.debug('XMLHttpTransport error callback failed: ' + e2) - } - } - } - } - clearTimeout(this.inFlightTimer) - if (this.inFlight.length == 0) { - this.inFlightTimer = null - return - } - this.inFlightTimer = setTimeout('dojo.io.XMLHTTPTransport.watchInFlight();', 10) - } - var _2bc = dojo.hostenv.getXmlhttpObject() ? true : false - this.canHandle = function(_2bd) { - var mlc = _2bd['mimetype'].toLowerCase() || '' - return _2bc && ((dojo.lang.inArray(['text/plain', 'text/html', 'application/xml', 'text/xml', 'text/javascript'], mlc)) || (mlc.substr(0, 9) == 'text/json' || mlc.substr(0, 16) == 'application/json')) && !(_2bd['formNode'] && dojo.io.formHasFile(_2bd['formNode'])) - } - this.multipartBoundary = '45309FFF-BD65-4d50-99C9-36986896A96F' - this.bind = function(_2bf) { - if (!_2bf['url']) { - if (!_2bf['formNode'] && (_2bf['backButton'] || _2bf['back'] || _2bf['changeUrl'] || _2bf['watchForURL']) && (!djConfig.preventBackButtonFix)) { - dojo.deprecated('Using dojo.io.XMLHTTPTransport.bind() to add to browser history without doing an IO request', 'Use dojo.undo.browser.addToHistory() instead.', '0.4') - dojo.undo.browser.addToHistory(_2bf) - return true - } - } - var url = _2bf.url - var _2c1 = '' - if (_2bf['formNode']) { - var ta = _2bf.formNode.getAttribute('action') - if ((ta) && (!_2bf['url'])) { - url = ta - } - var tp = _2bf.formNode.getAttribute('method') - if ((tp) && (!_2bf['method'])) { - _2bf.method = tp - } - _2c1 += dojo.io.encodeForm(_2bf.formNode, _2bf.encoding, _2bf['formFilter']) - } - if (url.indexOf('#') > -1) { - dojo.debug('Warning: dojo.io.bind: stripping hash values from url:', url) - url = url.split('#')[0] - } - if (_2bf['file']) { - _2bf.method = 'post' - } - if (!_2bf['method']) { - _2bf.method = 'get' - } - if (_2bf.method.toLowerCase() == 'get') { - _2bf.multipart = false - } else { - if (_2bf['file']) { - _2bf.multipart = true - } else { - if (!_2bf['multipart']) { - _2bf.multipart = false - } - } - } - if (_2bf['backButton'] || _2bf['back'] || _2bf['changeUrl']) { - dojo.undo.browser.addToHistory(_2bf) - } - var _2c4 = _2bf['content'] || {} - if (_2bf.sendTransport) { - _2c4['dojo.transport'] = 'xmlhttp' - } - do { - if (_2bf.postContent) { - _2c1 = _2bf.postContent - break - } - if (_2c4) { - _2c1 += dojo.io.argsFromMap(_2c4, _2bf.encoding) - } - if (_2bf.method.toLowerCase() == 'get' || !_2bf.multipart) { - break - } - var t = [] - if (_2c1.length) { - var q = _2c1.split('&') - for (var i = 0; i < q.length; ++i) { - if (q[i].length) { - var p = q[i].split('=') - t.push('--' + this.multipartBoundary, 'Content-Disposition: form-data; name="' + p[0] + '"', '', p[1]) - } - } - } - if (_2bf.file) { - if (dojo.lang.isArray(_2bf.file)) { - for (var i = 0; i < _2bf.file.length; ++i) { - var o = _2bf.file[i] - t.push('--' + this.multipartBoundary, 'Content-Disposition: form-data; name="' + o.name + '"; filename="' + ('fileName' in o ? o.fileName : o.name) + '"', 'Content-Type: ' + ('contentType' in o ? o.contentType : 'application/octet-stream'), '', o.content) - } - } else { - var o = _2bf.file - t.push('--' + this.multipartBoundary, 'Content-Disposition: form-data; name="' + o.name + '"; filename="' + ('fileName' in o ? o.fileName : o.name) + '"', 'Content-Type: ' + ('contentType' in o ? o.contentType : 'application/octet-stream'), '', o.content) - } - } - if (t.length) { - t.push('--' + this.multipartBoundary + '--', '') - _2c1 = t.join('\r\n') - } - } while (false) - var _2ca = _2bf['sync'] ? false : true - var _2cb = _2bf['preventCache'] || (this.preventCache == true && _2bf['preventCache'] != false) - var _2cc = _2bf['useCache'] == true || (this.useCache == true && _2bf['useCache'] != false) - if (!_2cb && _2cc) { - var _2cd = getFromCache(url, _2c1, _2bf.method) - if (_2cd) { - doLoad(_2bf, _2cd, url, _2c1, false) - return - } - } - var http = dojo.hostenv.getXmlhttpObject(_2bf) - var _2cf = false - if (_2ca) { - var _2d0 = this.inFlight.push({ - 'req': _2bf, - 'http': http, - 'url': url, - 'query': _2c1, - 'useCache': _2cc, - 'startTime': _2bf.timeoutSeconds ? (new Date()).getTime() : 0 - }) - this.startWatchingInFlight() - } else { - _29e._blockAsync = true - } - if (_2bf.method.toLowerCase() == 'post') { - if (!_2bf.user) { - http.open('POST', url, _2ca) - } else { - http.open('POST', url, _2ca, _2bf.user, _2bf.password) - } - setHeaders(http, _2bf) - http.setRequestHeader('Content-Type', _2bf.multipart ? ('multipart/form-data; boundary=' + this.multipartBoundary) : (_2bf.contentType || 'application/x-www-form-urlencoded')) - try { - http.send(_2c1) - } catch (e) { - if (typeof http.abort == 'function') { - http.abort() - } - doLoad(_2bf, { status: 404 }, url, _2c1, _2cc) - } - } else { - var _2d1 = url - if (_2c1 != '') { - _2d1 += (_2d1.indexOf('?') > -1 ? '&' : '?') + _2c1 - } - if (_2cb) { - _2d1 += (dojo.string.endsWithAny(_2d1, '?', '&') ? '' : (_2d1.indexOf('?') > -1 ? '&' : '?')) + 'dojo.preventCache=' + new Date().valueOf() - } - if (!_2bf.user) { - http.open(_2bf.method.toUpperCase(), _2d1, _2ca) - } else { - http.open(_2bf.method.toUpperCase(), _2d1, _2ca, _2bf.user, _2bf.password) - } - setHeaders(http, _2bf) - try { - http.send(null) - } catch (e) { - if (typeof http.abort == 'function') { - http.abort() - } - doLoad(_2bf, { status: 404 }, url, _2c1, _2cc) - } - } - if (!_2ca) { - doLoad(_2bf, http, url, _2c1, _2cc) - _29e._blockAsync = false - } - _2bf.abort = function() { - try { - http._aborted = true - } catch (e) { - } - return http.abort() - } - return - } - dojo.io.transports.addTransport('XMLHTTPTransport') - } -} -dojo.provide('dojo.io.cookie') -dojo.io.cookie.setCookie = function(name, _2d3, days, path, _2d6, _2d7) { - var _2d8 = -1 - if ((typeof days == 'number') && (days >= 0)) { - var d = new Date() - d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000)) - _2d8 = d.toGMTString() - } - _2d3 = escape(_2d3) - document.cookie = name + '=' + _2d3 + ';' + (_2d8 != -1 ? ' expires=' + _2d8 + ';' : '') + (path ? 'path=' + path : '') + (_2d6 ? '; domain=' + _2d6 : '') + (_2d7 ? '; secure' : '') -} -dojo.io.cookie.set = dojo.io.cookie.setCookie -dojo.io.cookie.getCookie = function(name) { - var idx = document.cookie.lastIndexOf(name + '=') - if (idx == -1) { - return null - } - var _2dc = document.cookie.substring(idx + name.length + 1) - var end = _2dc.indexOf(';') - if (end == -1) { - end = _2dc.length - } - _2dc = _2dc.substring(0, end) - _2dc = unescape(_2dc) - return _2dc -} -dojo.io.cookie.get = dojo.io.cookie.getCookie -dojo.io.cookie.deleteCookie = function(name) { - dojo.io.cookie.setCookie(name, '-', 0) -} -dojo.io.cookie.setObjectCookie = function(name, obj, days, path, _2e3, _2e4, _2e5) { - if (arguments.length == 5) { - _2e5 = _2e3 - _2e3 = null - _2e4 = null - } - var _2e6 = [], _2e7, _2e8 = '' - if (!_2e5) { - _2e7 = dojo.io.cookie.getObjectCookie(name) - } - if (days >= 0) { - if (!_2e7) { - _2e7 = {} - } - for (var prop in obj) { - if (obj[prop] == null) { - delete _2e7[prop] - } else { - if ((typeof obj[prop] == 'string') || (typeof obj[prop] == 'number')) { - _2e7[prop] = obj[prop] - } - } - } - prop = null - for (var prop in _2e7) { - _2e6.push(escape(prop) + '=' + escape(_2e7[prop])) - } - _2e8 = _2e6.join('&') - } - dojo.io.cookie.setCookie(name, _2e8, days, path, _2e3, _2e4) -} -dojo.io.cookie.getObjectCookie = function(name) { - var _2eb = null, _2ec = dojo.io.cookie.getCookie(name) - if (_2ec) { - _2eb = {} - var _2ed = _2ec.split('&') - for (var i = 0; i < _2ed.length; i++) { - var pair = _2ed[i].split('=') - var _2f0 = pair[1] - if (isNaN(_2f0)) { - _2f0 = unescape(pair[1]) - } - _2eb[unescape(pair[0])] = _2f0 - } - } - return _2eb -} -dojo.io.cookie.isSupported = function() { - if (typeof navigator.cookieEnabled != 'boolean') { - dojo.io.cookie.setCookie('__TestingYourBrowserForCookieSupport__', 'CookiesAllowed', 90, null) - var _2f1 = dojo.io.cookie.getCookie('__TestingYourBrowserForCookieSupport__') - navigator.cookieEnabled = (_2f1 == 'CookiesAllowed') - if (navigator.cookieEnabled) { - this.deleteCookie('__TestingYourBrowserForCookieSupport__') - } - } - return navigator.cookieEnabled -} + this.inFlight = []; + this.inFlightTimer = null; + this.startWatchingInFlight = function () { + if (!this.inFlightTimer) { + this.inFlightTimer = setTimeout('dojo.io.XMLHTTPTransport.watchInFlight();', 10) + } + }; + this.watchInFlight = function () { + var now = null; + if (!dojo.hostenv._blockAsync && !_29e._blockAsync) { + for (var x = this.inFlight.length - 1; x >= 0; x--) { + try { + var tif = this.inFlight[x]; + if (!tif || tif.http._aborted || !tif.http.readyState) { + this.inFlight.splice(x, 1); + continue + } + if (4 == tif.http.readyState) { + this.inFlight.splice(x, 1); + doLoad(tif.req, tif.http, tif.url, tif.query, tif.useCache) + } else { + if (tif.startTime) { + if (!now) { + now = (new Date()).getTime() + } + if (tif.startTime + (tif.req.timeoutSeconds * 1000) < now) { + if (typeof tif.http.abort == 'function') { + tif.http.abort() + } + this.inFlight.splice(x, 1); + tif.req[(typeof tif.req.timeout + == 'function') ? 'timeout' : 'handle']('timeout', null, tif.http, tif.req) + } + } + } + } catch (e) { + try { + var _2bb = new dojo.io.Error('XMLHttpTransport.watchInFlight Error: ' + e); + tif.req[(typeof tif.req.error == 'function') ? 'error' : 'handle']('error', _2bb, tif.http, tif.req) + } catch (e2) { + dojo.debug('XMLHttpTransport error callback failed: ' + e2) + } + } + } + } + clearTimeout(this.inFlightTimer); + if (this.inFlight.length == 0) { + this.inFlightTimer = null; + return + } + this.inFlightTimer = setTimeout('dojo.io.XMLHTTPTransport.watchInFlight();', 10) + }; + var _2bc = dojo.hostenv.getXmlhttpObject() ? true : false; + this.canHandle = function (_2bd) { + var mlc = _2bd['mimetype'].toLowerCase() || ''; + return _2bc + && ((dojo.lang.inArray(['text/plain', 'text/html', 'application/xml', 'text/xml', 'text/javascript'], mlc)) + || (mlc.substr(0, 9) == 'text/json' || mlc.substr(0, 16) == 'application/json')) + && !(_2bd['formNode'] && dojo.io.formHasFile(_2bd['formNode'])) + }; + this.multipartBoundary = '45309FFF-BD65-4d50-99C9-36986896A96F'; + this.bind = function (_2bf) { + if (!_2bf['url']) { + if (!_2bf['formNode'] + && (_2bf['backButton'] || _2bf['back'] || _2bf['changeUrl'] || _2bf['watchForURL']) + && (!djConfig.preventBackButtonFix)) { + dojo.deprecated('Using dojo.io.XMLHTTPTransport.bind() to add to browser history without doing an IO request', 'Use dojo.undo.browser.addToHistory() instead.', '0.4'); + dojo.undo.browser.addToHistory(_2bf); + return true + } + } + var url = _2bf.url; + var _2c1 = ''; + if (_2bf['formNode']) { + var ta = _2bf.formNode.getAttribute('action'); + if ((ta) && (!_2bf['url'])) { + url = ta + } + var tp = _2bf.formNode.getAttribute('method'); + if ((tp) && (!_2bf['method'])) { + _2bf.method = tp + } + _2c1 += dojo.io.encodeForm(_2bf.formNode, _2bf.encoding, _2bf['formFilter']) + } + if (url.indexOf('#') > -1) { + dojo.debug('Warning: dojo.io.bind: stripping hash values from url:', url); + url = url.split('#')[0] + } + if (_2bf['file']) { + _2bf.method = 'post' + } + if (!_2bf['method']) { + _2bf.method = 'get' + } + if (_2bf.method.toLowerCase() == 'get') { + _2bf.multipart = false + } else { + if (_2bf['file']) { + _2bf.multipart = true + } else { + if (!_2bf['multipart']) { + _2bf.multipart = false + } + } + } + if (_2bf['backButton'] || _2bf['back'] || _2bf['changeUrl']) { + dojo.undo.browser.addToHistory(_2bf) + } + var _2c4 = _2bf['content'] || {}; + if (_2bf.sendTransport) { + _2c4['dojo.transport'] = 'xmlhttp' + } + do { + if (_2bf.postContent) { + _2c1 = _2bf.postContent; + break + } + if (_2c4) { + _2c1 += dojo.io.argsFromMap(_2c4, _2bf.encoding) + } + if (_2bf.method.toLowerCase() == 'get' || !_2bf.multipart) { + break + } + var t = []; + if (_2c1.length) { + var q = _2c1.split('&'); + for (var i = 0; i < q.length; ++i) { + if (q[i].length) { + var p = q[i].split('='); + t.push('--' + this.multipartBoundary, 'Content-Disposition: form-data; name="' + p[0] + '"', '', p[1]) + } + } + } + if (_2bf.file) { + if (dojo.lang.isArray(_2bf.file)) { + for (var i = 0; i < _2bf.file.length; ++i) { + var o = _2bf.file[i]; + t.push('--' + this.multipartBoundary, 'Content-Disposition: form-data; name="' + + o.name + + '"; filename="' + + ('fileName' in o ? o.fileName : o.name) + + '"', 'Content-Type: ' + ('contentType' + in o ? o.contentType : 'application/octet-stream'), '', o.content) + } + } else { + var o = _2bf.file; + t.push('--' + this.multipartBoundary, 'Content-Disposition: form-data; name="' + + o.name + + '"; filename="' + + ('fileName' in o ? o.fileName : o.name) + + '"', 'Content-Type: ' + ('contentType' + in o ? o.contentType : 'application/octet-stream'), '', o.content) + } + } + if (t.length) { + t.push('--' + this.multipartBoundary + '--', ''); + _2c1 = t.join('\r\n') + } + } while (false); + var _2ca = _2bf['sync'] ? false : true; + var _2cb = _2bf['preventCache'] || (this.preventCache == true && _2bf['preventCache'] != false); + var _2cc = _2bf['useCache'] == true || (this.useCache == true && _2bf['useCache'] != false); + if (!_2cb && _2cc) { + var _2cd = getFromCache(url, _2c1, _2bf.method); + if (_2cd) { + doLoad(_2bf, _2cd, url, _2c1, false); + return + } + } + var http = dojo.hostenv.getXmlhttpObject(_2bf); + var _2cf = false; + if (_2ca) { + var _2d0 = this.inFlight.push({ + 'req': _2bf, + 'http': http, + 'url': url, + 'query': _2c1, + 'useCache': _2cc, + 'startTime': _2bf.timeoutSeconds ? (new Date()).getTime() : 0 + }); + this.startWatchingInFlight() + } else { + _29e._blockAsync = true + } + if (_2bf.method.toLowerCase() == 'post') { + if (!_2bf.user) { + http.open('POST', url, _2ca) + } else { + http.open('POST', url, _2ca, _2bf.user, _2bf.password) + } + setHeaders(http, _2bf); + http.setRequestHeader('Content-Type', _2bf.multipart ? ('multipart/form-data; boundary=' + + this.multipartBoundary) : (_2bf.contentType || 'application/x-www-form-urlencoded')); + try { + http.send(_2c1) + } catch (e) { + if (typeof http.abort == 'function') { + http.abort() + } + doLoad(_2bf, {status: 404}, url, _2c1, _2cc) + } + } else { + var _2d1 = url; + if (_2c1 != '') { + _2d1 += (_2d1.indexOf('?') > -1 ? '&' : '?') + _2c1 + } + if (_2cb) { + _2d1 += (dojo.string.endsWithAny(_2d1, '?', '&') ? '' : (_2d1.indexOf('?') > -1 ? '&' : '?')) + + 'dojo.preventCache=' + + new Date().valueOf() + } + if (!_2bf.user) { + http.open(_2bf.method.toUpperCase(), _2d1, _2ca) + } else { + http.open(_2bf.method.toUpperCase(), _2d1, _2ca, _2bf.user, _2bf.password) + } + setHeaders(http, _2bf); + try { + http.send(null) + } catch (e) { + if (typeof http.abort == 'function') { + http.abort() + } + doLoad(_2bf, {status: 404}, url, _2c1, _2cc) + } + } + if (!_2ca) { + doLoad(_2bf, http, url, _2c1, _2cc); + _29e._blockAsync = false + } + _2bf.abort = function () { + try { + http._aborted = true + } catch (e) { + } + return http.abort() + }; + + }; + dojo.io.transports.addTransport('XMLHTTPTransport') + }; +} +dojo.provide('dojo.io.cookie'); +dojo.io.cookie.setCookie = function (name, _2d3, days, path, _2d6, _2d7) { + var _2d8 = -1; + if ((typeof days == 'number') && (days >= 0)) { + var d = new Date(); + d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000)); + _2d8 = d.toGMTString() + } + _2d3 = escape(_2d3); + document.cookie = name + '=' + _2d3 + ';' + (_2d8 != -1 ? ' expires=' + _2d8 + ';' : '') + (path ? 'path=' + + path : '') + (_2d6 ? '; domain=' + _2d6 : '') + (_2d7 ? '; secure' : '') +}; +dojo.io.cookie.set = dojo.io.cookie.setCookie; +dojo.io.cookie.getCookie = function (name) { + var idx = document.cookie.lastIndexOf(name + '='); + if (idx == -1) { + return null + } + var _2dc = document.cookie.substring(idx + name.length + 1); + var end = _2dc.indexOf(';'); + if (end == -1) { + end = _2dc.length + } + _2dc = _2dc.substring(0, end); + _2dc = unescape(_2dc); + return _2dc +}; +dojo.io.cookie.get = dojo.io.cookie.getCookie; +dojo.io.cookie.deleteCookie = function (name) { + dojo.io.cookie.setCookie(name, '-', 0) +}; +dojo.io.cookie.setObjectCookie = function (name, obj, days, path, _2e3, _2e4, _2e5) { + if (arguments.length == 5) { + _2e5 = _2e3; + _2e3 = null; + _2e4 = null + } + var _2e6 = [], _2e7, _2e8 = ''; + if (!_2e5) { + _2e7 = dojo.io.cookie.getObjectCookie(name) + } + if (days >= 0) { + if (!_2e7) { + _2e7 = {} + } + for (var prop in obj) { + if (obj[prop] == null) { + delete _2e7[prop] + } else { + if ((typeof obj[prop] == 'string') || (typeof obj[prop] == 'number')) { + _2e7[prop] = obj[prop] + } + } + } + prop = null; + for (var prop in _2e7) { + _2e6.push(escape(prop) + '=' + escape(_2e7[prop])) + } + _2e8 = _2e6.join('&') + } + dojo.io.cookie.setCookie(name, _2e8, days, path, _2e3, _2e4) +}; +dojo.io.cookie.getObjectCookie = function (name) { + var _2eb = null, _2ec = dojo.io.cookie.getCookie(name); + if (_2ec) { + _2eb = {}; + var _2ed = _2ec.split('&'); + for (var i = 0; i < _2ed.length; i++) { + var pair = _2ed[i].split('='); + var _2f0 = pair[1]; + if (isNaN(_2f0)) { + _2f0 = unescape(pair[1]) + } + _2eb[unescape(pair[0])] = _2f0 + } + } + return _2eb +}; +dojo.io.cookie.isSupported = function () { + if (typeof navigator.cookieEnabled != 'boolean') { + dojo.io.cookie.setCookie('__TestingYourBrowserForCookieSupport__', 'CookiesAllowed', 90, null); + var _2f1 = dojo.io.cookie.getCookie('__TestingYourBrowserForCookieSupport__'); + navigator.cookieEnabled = (_2f1 == 'CookiesAllowed'); + if (navigator.cookieEnabled) { + this.deleteCookie('__TestingYourBrowserForCookieSupport__') + } + } + return navigator.cookieEnabled +}; if (!dojo.io.cookies) { - dojo.io.cookies = dojo.io.cookie + dojo.io.cookies = dojo.io.cookie } dojo.kwCompoundRequire({ - common: ['dojo.io.common'], - rhino: ['dojo.io.RhinoIO'], - browser: ['dojo.io.BrowserIO', 'dojo.io.cookie'], - dashboard: ['dojo.io.BrowserIO', 'dojo.io.cookie'] -}) -dojo.provide('dojo.io.*') -dojo.provide('dojo.event.common') -dojo.event = new function() { - this._canTimeout = dojo.lang.isFunction(dj_global['setTimeout']) || dojo.lang.isAlien(dj_global['setTimeout']) + common: ['dojo.io.common'], + rhino: ['dojo.io.RhinoIO'], + browser: ['dojo.io.BrowserIO', 'dojo.io.cookie'], + dashboard: ['dojo.io.BrowserIO', 'dojo.io.cookie'] +}); +dojo.provide('dojo.io.*'); +dojo.provide('dojo.event.common'); +dojo.event = new function () { + this._canTimeout = dojo.lang.isFunction(dj_global['setTimeout']) || dojo.lang.isAlien(dj_global['setTimeout']); - function interpolateArgs(args, _2f3) { - var dl = dojo.lang - var ao = { - srcObj: dj_global, - srcFunc: null, - adviceObj: dj_global, - adviceFunc: null, - aroundObj: null, - aroundFunc: null, - adviceType: (args.length > 2) ? args[0] : 'after', - precedence: 'last', - once: false, - delay: null, - rate: 0, - adviceMsg: false, - maxCalls: -1 - } - switch (args.length) { - case 0: - return - case 1: - return - case 2: - ao.srcFunc = args[0] - ao.adviceFunc = args[1] - break - case 3: - if ((dl.isObject(args[0])) && (dl.isString(args[1])) && (dl.isString(args[2]))) { - ao.adviceType = 'after' - ao.srcObj = args[0] - ao.srcFunc = args[1] - ao.adviceFunc = args[2] - } else { - if ((dl.isString(args[1])) && (dl.isString(args[2]))) { - ao.srcFunc = args[1] - ao.adviceFunc = args[2] - } else { - if ((dl.isObject(args[0])) && (dl.isString(args[1])) && (dl.isFunction(args[2]))) { - ao.adviceType = 'after' - ao.srcObj = args[0] - ao.srcFunc = args[1] - var _2f6 = dl.nameAnonFunc(args[2], ao.adviceObj, _2f3) - ao.adviceFunc = _2f6 - } else { - if ((dl.isFunction(args[0])) && (dl.isObject(args[1])) && (dl.isString(args[2]))) { - ao.adviceType = 'after' - ao.srcObj = dj_global - var _2f6 = dl.nameAnonFunc(args[0], ao.srcObj, _2f3) - ao.srcFunc = _2f6 - ao.adviceObj = args[1] - ao.adviceFunc = args[2] - } - } - } - } - break - case 4: - if ((dl.isObject(args[0])) && (dl.isObject(args[2]))) { - ao.adviceType = 'after' - ao.srcObj = args[0] - ao.srcFunc = args[1] - ao.adviceObj = args[2] - ao.adviceFunc = args[3] - } else { - if ((dl.isString(args[0])) && (dl.isString(args[1])) && (dl.isObject(args[2]))) { - ao.adviceType = args[0] - ao.srcObj = dj_global - ao.srcFunc = args[1] - ao.adviceObj = args[2] - ao.adviceFunc = args[3] - } else { - if ((dl.isString(args[0])) && (dl.isFunction(args[1])) && (dl.isObject(args[2]))) { - ao.adviceType = args[0] - ao.srcObj = dj_global - var _2f6 = dl.nameAnonFunc(args[1], dj_global, _2f3) - ao.srcFunc = _2f6 - ao.adviceObj = args[2] - ao.adviceFunc = args[3] - } else { - if ((dl.isString(args[0])) && (dl.isObject(args[1])) && (dl.isString(args[2])) && (dl.isFunction(args[3]))) { - ao.srcObj = args[1] - ao.srcFunc = args[2] - var _2f6 = dl.nameAnonFunc(args[3], dj_global, _2f3) - ao.adviceObj = dj_global - ao.adviceFunc = _2f6 - } else { - if (dl.isObject(args[1])) { - ao.srcObj = args[1] - ao.srcFunc = args[2] - ao.adviceObj = dj_global - ao.adviceFunc = args[3] - } else { - if (dl.isObject(args[2])) { - ao.srcObj = dj_global - ao.srcFunc = args[1] - ao.adviceObj = args[2] - ao.adviceFunc = args[3] - } else { - ao.srcObj = ao.adviceObj = ao.aroundObj = dj_global - ao.srcFunc = args[1] - ao.adviceFunc = args[2] - ao.aroundFunc = args[3] - } - } - } - } - } - } - break - case 6: - ao.srcObj = args[1] - ao.srcFunc = args[2] - ao.adviceObj = args[3] - ao.adviceFunc = args[4] - ao.aroundFunc = args[5] - ao.aroundObj = dj_global - break - default: - ao.srcObj = args[1] - ao.srcFunc = args[2] - ao.adviceObj = args[3] - ao.adviceFunc = args[4] - ao.aroundObj = args[5] - ao.aroundFunc = args[6] - ao.once = args[7] - ao.delay = args[8] - ao.rate = args[9] - ao.adviceMsg = args[10] - ao.maxCalls = (!isNaN(parseInt(args[11]))) ? args[11] : -1 - break - } - if (dl.isFunction(ao.aroundFunc)) { - var _2f6 = dl.nameAnonFunc(ao.aroundFunc, ao.aroundObj, _2f3) - ao.aroundFunc = _2f6 - } - if (dl.isFunction(ao.srcFunc)) { - ao.srcFunc = dl.getNameInObj(ao.srcObj, ao.srcFunc) - } - if (dl.isFunction(ao.adviceFunc)) { - ao.adviceFunc = dl.getNameInObj(ao.adviceObj, ao.adviceFunc) - } - if ((ao.aroundObj) && (dl.isFunction(ao.aroundFunc))) { - ao.aroundFunc = dl.getNameInObj(ao.aroundObj, ao.aroundFunc) - } - if (!ao.srcObj) { - dojo.raise('bad srcObj for srcFunc: ' + ao.srcFunc) - } - if (!ao.adviceObj) { - dojo.raise('bad adviceObj for adviceFunc: ' + ao.adviceFunc) - } - if (!ao.adviceFunc) { - dojo.debug('bad adviceFunc for srcFunc: ' + ao.srcFunc) - dojo.debugShallow(ao) - } - return ao - } + function interpolateArgs(args, _2f3) { + var dl = dojo.lang; + var ao = { + srcObj: dj_global, + srcFunc: null, + adviceObj: dj_global, + adviceFunc: null, + aroundObj: null, + aroundFunc: null, + adviceType: (args.length > 2) ? args[0] : 'after', + precedence: 'last', + once: false, + delay: null, + rate: 0, + adviceMsg: false, + maxCalls: -1 + }; + switch (args.length) { + case 0: + return; + case 1: + return; + case 2: + ao.srcFunc = args[0]; + ao.adviceFunc = args[1]; + break; + case 3: + if ((dl.isObject(args[0])) && (dl.isString(args[1])) && (dl.isString(args[2]))) { + ao.adviceType = 'after'; + ao.srcObj = args[0]; + ao.srcFunc = args[1]; + ao.adviceFunc = args[2] + } else { + if ((dl.isString(args[1])) && (dl.isString(args[2]))) { + ao.srcFunc = args[1]; + ao.adviceFunc = args[2] + } else { + if ((dl.isObject(args[0])) && (dl.isString(args[1])) && (dl.isFunction(args[2]))) { + ao.adviceType = 'after'; + ao.srcObj = args[0]; + ao.srcFunc = args[1]; + var _2f6 = dl.nameAnonFunc(args[2], ao.adviceObj, _2f3); + ao.adviceFunc = _2f6 + } else { + if ((dl.isFunction(args[0])) && (dl.isObject(args[1])) && (dl.isString(args[2]))) { + ao.adviceType = 'after'; + ao.srcObj = dj_global; + var _2f6 = dl.nameAnonFunc(args[0], ao.srcObj, _2f3); + ao.srcFunc = _2f6; + ao.adviceObj = args[1]; + ao.adviceFunc = args[2] + } + } + } + } + break; + case 4: + if ((dl.isObject(args[0])) && (dl.isObject(args[2]))) { + ao.adviceType = 'after'; + ao.srcObj = args[0]; + ao.srcFunc = args[1]; + ao.adviceObj = args[2]; + ao.adviceFunc = args[3] + } else { + if ((dl.isString(args[0])) && (dl.isString(args[1])) && (dl.isObject(args[2]))) { + ao.adviceType = args[0]; + ao.srcObj = dj_global; + ao.srcFunc = args[1]; + ao.adviceObj = args[2]; + ao.adviceFunc = args[3] + } else { + if ((dl.isString(args[0])) && (dl.isFunction(args[1])) && (dl.isObject(args[2]))) { + ao.adviceType = args[0]; + ao.srcObj = dj_global; + var _2f6 = dl.nameAnonFunc(args[1], dj_global, _2f3); + ao.srcFunc = _2f6; + ao.adviceObj = args[2]; + ao.adviceFunc = args[3] + } else { + if ((dl.isString(args[0])) + && (dl.isObject(args[1])) + && (dl.isString(args[2])) + && (dl.isFunction(args[3]))) { + ao.srcObj = args[1]; + ao.srcFunc = args[2]; + var _2f6 = dl.nameAnonFunc(args[3], dj_global, _2f3); + ao.adviceObj = dj_global; + ao.adviceFunc = _2f6 + } else { + if (dl.isObject(args[1])) { + ao.srcObj = args[1]; + ao.srcFunc = args[2]; + ao.adviceObj = dj_global; + ao.adviceFunc = args[3] + } else { + if (dl.isObject(args[2])) { + ao.srcObj = dj_global; + ao.srcFunc = args[1]; + ao.adviceObj = args[2]; + ao.adviceFunc = args[3] + } else { + ao.srcObj = ao.adviceObj = ao.aroundObj = dj_global; + ao.srcFunc = args[1]; + ao.adviceFunc = args[2]; + ao.aroundFunc = args[3] + } + } + } + } + } + } + break; + case 6: + ao.srcObj = args[1]; + ao.srcFunc = args[2]; + ao.adviceObj = args[3]; + ao.adviceFunc = args[4]; + ao.aroundFunc = args[5]; + ao.aroundObj = dj_global; + break; + default: + ao.srcObj = args[1]; + ao.srcFunc = args[2]; + ao.adviceObj = args[3]; + ao.adviceFunc = args[4]; + ao.aroundObj = args[5]; + ao.aroundFunc = args[6]; + ao.once = args[7]; + ao.delay = args[8]; + ao.rate = args[9]; + ao.adviceMsg = args[10]; + ao.maxCalls = (!isNaN(parseInt(args[11]))) ? args[11] : -1; + break + } + if (dl.isFunction(ao.aroundFunc)) { + var _2f6 = dl.nameAnonFunc(ao.aroundFunc, ao.aroundObj, _2f3); + ao.aroundFunc = _2f6 + } + if (dl.isFunction(ao.srcFunc)) { + ao.srcFunc = dl.getNameInObj(ao.srcObj, ao.srcFunc) + } + if (dl.isFunction(ao.adviceFunc)) { + ao.adviceFunc = dl.getNameInObj(ao.adviceObj, ao.adviceFunc) + } + if ((ao.aroundObj) && (dl.isFunction(ao.aroundFunc))) { + ao.aroundFunc = dl.getNameInObj(ao.aroundObj, ao.aroundFunc) + } + if (!ao.srcObj) { + dojo.raise('bad srcObj for srcFunc: ' + ao.srcFunc) + } + if (!ao.adviceObj) { + dojo.raise('bad adviceObj for adviceFunc: ' + ao.adviceFunc) + } + if (!ao.adviceFunc) { + dojo.debug('bad adviceFunc for srcFunc: ' + ao.srcFunc); + dojo.debugShallow(ao) + } + return ao + } - this.connect = function() { - if (arguments.length == 1) { - var ao = arguments[0] - } else { - var ao = interpolateArgs(arguments, true) - } - if (dojo.lang.isString(ao.srcFunc) && (ao.srcFunc.toLowerCase() == 'onkey')) { - if (dojo.render.html.ie) { - ao.srcFunc = 'onkeydown' - this.connect(ao) - } - ao.srcFunc = 'onkeypress' - } - if (dojo.lang.isArray(ao.srcObj) && ao.srcObj != '') { - var _2f8 = {} - for (var x in ao) { - _2f8[x] = ao[x] - } - var mjps = [] - dojo.lang.forEach(ao.srcObj, function(src) { - if ((dojo.render.html.capable) && (dojo.lang.isString(src))) { - src = dojo.byId(src) - } - _2f8.srcObj = src - mjps.push(dojo.event.connect.call(dojo.event, _2f8)) - }) - return mjps - } - var mjp = dojo.event.MethodJoinPoint.getForMethod(ao.srcObj, ao.srcFunc) - if (ao.adviceFunc) { - var mjp2 = dojo.event.MethodJoinPoint.getForMethod(ao.adviceObj, ao.adviceFunc) - } - mjp.kwAddAdvice(ao) - return mjp - } - this.log = function(a1, a2) { - var _300 - if ((arguments.length == 1) && (typeof a1 == 'object')) { - _300 = a1 - } else { - _300 = { srcObj: a1, srcFunc: a2 } - } - _300.adviceFunc = function() { - var _301 = [] - for (var x = 0; x < arguments.length; x++) { - _301.push(arguments[x]) - } - dojo.debug('(' + _300.srcObj + ').' + _300.srcFunc, ':', _301.join(', ')) - } - this.kwConnect(_300) - } - this.connectBefore = function() { - var args = ['before'] - for (var i = 0; i < arguments.length; i++) { - args.push(arguments[i]) - } - return this.connect.apply(this, args) - } - this.connectAround = function() { - var args = ['around'] - for (var i = 0; i < arguments.length; i++) { - args.push(arguments[i]) - } - return this.connect.apply(this, args) - } - this.connectOnce = function() { - var ao = interpolateArgs(arguments, true) - ao.once = true - return this.connect(ao) - } - this.connectRunOnce = function() { - var ao = interpolateArgs(arguments, true) - ao.maxCalls = 1 - return this.connect(ao) - } - this._kwConnectImpl = function(_309, _30a) { - var fn = (_30a) ? 'disconnect' : 'connect' - if (typeof _309['srcFunc'] == 'function') { - _309.srcObj = _309['srcObj'] || dj_global - var _30c = dojo.lang.nameAnonFunc(_309.srcFunc, _309.srcObj, true) - _309.srcFunc = _30c - } - if (typeof _309['adviceFunc'] == 'function') { - _309.adviceObj = _309['adviceObj'] || dj_global - var _30c = dojo.lang.nameAnonFunc(_309.adviceFunc, _309.adviceObj, true) - _309.adviceFunc = _30c - } - _309.srcObj = _309['srcObj'] || dj_global - _309.adviceObj = _309['adviceObj'] || _309['targetObj'] || dj_global - _309.adviceFunc = _309['adviceFunc'] || _309['targetFunc'] - return dojo.event[fn](_309) - } - this.kwConnect = function(_30d) { - return this._kwConnectImpl(_30d, false) - } - this.disconnect = function() { - if (arguments.length == 1) { - var ao = arguments[0] - } else { - var ao = interpolateArgs(arguments, true) - } - if (!ao.adviceFunc) { - return - } - if (dojo.lang.isString(ao.srcFunc) && (ao.srcFunc.toLowerCase() == 'onkey')) { - if (dojo.render.html.ie) { - ao.srcFunc = 'onkeydown' - this.disconnect(ao) - } - ao.srcFunc = 'onkeypress' - } - if (!ao.srcObj[ao.srcFunc]) { - return null - } - var mjp = dojo.event.MethodJoinPoint.getForMethod(ao.srcObj, ao.srcFunc, true) - mjp.removeAdvice(ao.adviceObj, ao.adviceFunc, ao.adviceType, ao.once) - return mjp - } - this.kwDisconnect = function(_310) { - return this._kwConnectImpl(_310, true) - } -} -dojo.event.MethodInvocation = function(_311, obj, args) { - this.jp_ = _311 - this.object = obj - this.args = [] - for (var x = 0; x < args.length; x++) { - this.args[x] = args[x] - } - this.around_index = -1 -} -dojo.event.MethodInvocation.prototype.proceed = function() { - this.around_index++ - if (this.around_index >= this.jp_.around.length) { - return this.jp_.object[this.jp_.methodname].apply(this.jp_.object, this.args) - } else { - var ti = this.jp_.around[this.around_index] - var mobj = ti[0] || dj_global - var meth = ti[1] - return mobj[meth].call(mobj, this) - } -} -dojo.event.MethodJoinPoint = function(obj, _319) { - this.object = obj || dj_global - this.methodname = _319 - this.methodfunc = this.object[_319] - this.squelch = false -} -dojo.event.MethodJoinPoint.getForMethod = function(obj, _31b) { - if (!obj) { - obj = dj_global - } - var ofn = obj[_31b] - if (!ofn) { - ofn = obj[_31b] = function() { - } - if (!obj[_31b]) { - dojo.raise('Cannot set do-nothing method on that object ' + _31b) - } - } else { - if ((typeof ofn != 'function') && (!dojo.lang.isFunction(ofn)) && (!dojo.lang.isAlien(ofn))) { - return null - } - } - var _31d = _31b + '$joinpoint' - var _31e = _31b + '$joinpoint$method' - var _31f = obj[_31d] - if (!_31f) { - var _320 = false - if (dojo.event['browser']) { - if ((obj['attachEvent']) || (obj['nodeType']) || (obj['addEventListener'])) { - _320 = true - dojo.event.browser.addClobberNodeAttrs(obj, [_31d, _31e, _31b]) - } - } - var _321 = ofn.length - obj[_31e] = ofn - _31f = obj[_31d] = new dojo.event.MethodJoinPoint(obj, _31e) - if (!_320) { - obj[_31b] = function() { - return _31f.run.apply(_31f, arguments) - } - } else { - obj[_31b] = function() { - var args = [] - if (!arguments.length) { - var evt = null - try { - if (obj.ownerDocument) { - evt = obj.ownerDocument.parentWindow.event - } else { - if (obj.documentElement) { - evt = obj.documentElement.ownerDocument.parentWindow.event - } else { - if (obj.event) { - evt = obj.event - } else { - evt = window.event - } - } - } - } catch (e) { - evt = window.event - } - if (evt) { - args.push(dojo.event.browser.fixEvent(evt, this)) - } - } else { - for (var x = 0; x < arguments.length; x++) { - if ((x == 0) && (dojo.event.browser.isEvent(arguments[x]))) { - args.push(dojo.event.browser.fixEvent(arguments[x], this)) - } else { - args.push(arguments[x]) - } - } - } - return _31f.run.apply(_31f, args) - } - } - obj[_31b].__preJoinArity = _321 - } - return _31f -} + this.connect = function () { + if (arguments.length == 1) { + var ao = arguments[0] + } else { + var ao = interpolateArgs(arguments, true) + } + if (dojo.lang.isString(ao.srcFunc) && (ao.srcFunc.toLowerCase() == 'onkey')) { + if (dojo.render.html.ie) { + ao.srcFunc = 'onkeydown'; + this.connect(ao) + } + ao.srcFunc = 'onkeypress' + } + if (dojo.lang.isArray(ao.srcObj) && ao.srcObj != '') { + var _2f8 = {}; + for (var x in ao) { + _2f8[x] = ao[x] + } + var mjps = []; + dojo.lang.forEach(ao.srcObj, function (src) { + if ((dojo.render.html.capable) && (dojo.lang.isString(src))) { + src = dojo.byId(src) + } + _2f8.srcObj = src; + mjps.push(dojo.event.connect.call(dojo.event, _2f8)) + }); + return mjps + } + var mjp = dojo.event.MethodJoinPoint.getForMethod(ao.srcObj, ao.srcFunc); + if (ao.adviceFunc) { + var mjp2 = dojo.event.MethodJoinPoint.getForMethod(ao.adviceObj, ao.adviceFunc) + } + mjp.kwAddAdvice(ao); + return mjp + }; + this.log = function (a1, a2) { + var _300; + if ((arguments.length == 1) && (typeof a1 == 'object')) { + _300 = a1 + } else { + _300 = {srcObj: a1, srcFunc: a2} + } + _300.adviceFunc = function () { + var _301 = []; + for (var x = 0; x < arguments.length; x++) { + _301.push(arguments[x]) + } + dojo.debug('(' + _300.srcObj + ').' + _300.srcFunc, ':', _301.join(', ')) + }; + this.kwConnect(_300) + }; + this.connectBefore = function () { + var args = ['before']; + for (var i = 0; i < arguments.length; i++) { + args.push(arguments[i]) + } + return this.connect.apply(this, args) + }; + this.connectAround = function () { + var args = ['around']; + for (var i = 0; i < arguments.length; i++) { + args.push(arguments[i]) + } + return this.connect.apply(this, args) + }; + this.connectOnce = function () { + var ao = interpolateArgs(arguments, true); + ao.once = true; + return this.connect(ao) + }; + this.connectRunOnce = function () { + var ao = interpolateArgs(arguments, true); + ao.maxCalls = 1; + return this.connect(ao) + }; + this._kwConnectImpl = function (_309, _30a) { + var fn = (_30a) ? 'disconnect' : 'connect'; + if (typeof _309['srcFunc'] == 'function') { + _309.srcObj = _309['srcObj'] || dj_global; + var _30c = dojo.lang.nameAnonFunc(_309.srcFunc, _309.srcObj, true); + _309.srcFunc = _30c + } + if (typeof _309['adviceFunc'] == 'function') { + _309.adviceObj = _309['adviceObj'] || dj_global; + var _30c = dojo.lang.nameAnonFunc(_309.adviceFunc, _309.adviceObj, true); + _309.adviceFunc = _30c + } + _309.srcObj = _309['srcObj'] || dj_global; + _309.adviceObj = _309['adviceObj'] || _309['targetObj'] || dj_global; + _309.adviceFunc = _309['adviceFunc'] || _309['targetFunc']; + return dojo.event[fn](_309) + }; + this.kwConnect = function (_30d) { + return this._kwConnectImpl(_30d, false) + }; + this.disconnect = function () { + if (arguments.length == 1) { + var ao = arguments[0] + } else { + var ao = interpolateArgs(arguments, true) + } + if (!ao.adviceFunc) { + return + } + if (dojo.lang.isString(ao.srcFunc) && (ao.srcFunc.toLowerCase() == 'onkey')) { + if (dojo.render.html.ie) { + ao.srcFunc = 'onkeydown'; + this.disconnect(ao) + } + ao.srcFunc = 'onkeypress' + } + if (!ao.srcObj[ao.srcFunc]) { + return null + } + var mjp = dojo.event.MethodJoinPoint.getForMethod(ao.srcObj, ao.srcFunc, true); + mjp.removeAdvice(ao.adviceObj, ao.adviceFunc, ao.adviceType, ao.once); + return mjp + }; + this.kwDisconnect = function (_310) { + return this._kwConnectImpl(_310, true) + } +}; +;dojo.event.MethodInvocation = function (_311, obj, args) { + this.jp_ = _311; + this.object = obj; + this.args = []; + for (var x = 0; x < args.length; x++) { + this.args[x] = args[x] + } + this.around_index = -1 +}; +dojo.event.MethodInvocation.prototype.proceed = function () { + this.around_index++; + if (this.around_index >= this.jp_.around.length) { + return this.jp_.object[this.jp_.methodname].apply(this.jp_.object, this.args) + } else { + var ti = this.jp_.around[this.around_index]; + var mobj = ti[0] || dj_global; + var meth = ti[1]; + return mobj[meth].call(mobj, this) + } +}; +dojo.event.MethodJoinPoint = function (obj, _319) { + this.object = obj || dj_global; + this.methodname = _319; + this.methodfunc = this.object[_319]; + this.squelch = false +}; +dojo.event.MethodJoinPoint.getForMethod = function (obj, _31b) { + if (!obj) { + obj = dj_global + } + var ofn = obj[_31b]; + if (!ofn) { + ofn = obj[_31b] = function () { + }; + if (!obj[_31b]) { + dojo.raise('Cannot set do-nothing method on that object ' + _31b) + } + } else { + if ((typeof ofn != 'function') && (!dojo.lang.isFunction(ofn)) && (!dojo.lang.isAlien(ofn))) { + return null + } + } + var _31d = _31b + '$joinpoint'; + var _31e = _31b + '$joinpoint$method'; + var _31f = obj[_31d]; + if (!_31f) { + var _320 = false; + if (dojo.event['browser']) { + if ((obj['attachEvent']) || (obj['nodeType']) || (obj['addEventListener'])) { + _320 = true; + dojo.event.browser.addClobberNodeAttrs(obj, [_31d, _31e, _31b]) + } + } + var _321 = ofn.length; + obj[_31e] = ofn; + _31f = obj[_31d] = new dojo.event.MethodJoinPoint(obj, _31e); + if (!_320) { + obj[_31b] = function () { + return _31f.run.apply(_31f, arguments) + } + } else { + obj[_31b] = function () { + var args = []; + if (!arguments.length) { + var evt = null; + try { + if (obj.ownerDocument) { + evt = obj.ownerDocument.parentWindow.event + } else { + if (obj.documentElement) { + evt = obj.documentElement.ownerDocument.parentWindow.event + } else { + if (obj.event) { + evt = obj.event + } else { + evt = window.event + } + } + } + } catch (e) { + evt = window.event + } + if (evt) { + args.push(dojo.event.browser.fixEvent(evt, this)) + } + } else { + for (var x = 0; x < arguments.length; x++) { + if ((x == 0) && (dojo.event.browser.isEvent(arguments[x]))) { + args.push(dojo.event.browser.fixEvent(arguments[x], this)) + } else { + args.push(arguments[x]) + } + } + } + return _31f.run.apply(_31f, args) + } + } + obj[_31b].__preJoinArity = _321 + } + return _31f +}; dojo.lang.extend(dojo.event.MethodJoinPoint, { - squelch: false, unintercept: function() { - this.object[this.methodname] = this.methodfunc - this.before = [] - this.after = [] - this.around = [] - }, disconnect: dojo.lang.forward('unintercept'), run: function() { - var obj = this.object || dj_global - var args = arguments - var _327 = [] - for (var x = 0; x < args.length; x++) { - _327[x] = args[x] - } - var _329 = function(marr) { - if (!marr) { - dojo.debug('Null argument to unrollAdvice()') - return - } - var _32b = marr[0] || dj_global - var _32c = marr[1] - if (!_32b[_32c]) { - dojo.raise('function "' + _32c + '" does not exist on "' + _32b + '"') - } - var _32d = marr[2] || dj_global - var _32e = marr[3] - var msg = marr[6] - var _330 = marr[7] - if (_330 > -1) { - if (_330 == 0) { - return - } - marr[7]-- - } - var _331 - var to = { - args: [], jp_: this, object: obj, proceed: function() { - return _32b[_32c].apply(_32b, to.args) - } - } - to.args = _327 - var _333 = parseInt(marr[4]) - var _334 = ((!isNaN(_333)) && (marr[4] !== null) && (typeof marr[4] != 'undefined')) - if (marr[5]) { - var rate = parseInt(marr[5]) - var cur = new Date() - var _337 = false - if ((marr['last']) && ((cur - marr.last) <= rate)) { - if (dojo.event._canTimeout) { - if (marr['delayTimer']) { - clearTimeout(marr.delayTimer) - } - var tod = parseInt(rate * 2) - var mcpy = dojo.lang.shallowCopy(marr) - marr.delayTimer = setTimeout(function() { - mcpy[5] = 0 - _329(mcpy) - }, tod) - } - return - } else { - marr.last = cur - } - } - if (_32e) { - _32d[_32e].call(_32d, to) - } else { - if ((_334) && ((dojo.render.html) || (dojo.render.svg))) { - dj_global['setTimeout'](function() { - if (msg) { - _32b[_32c].call(_32b, to) - } else { - _32b[_32c].apply(_32b, args) - } - }, _333) - } else { - if (msg) { - _32b[_32c].call(_32b, to) - } else { - _32b[_32c].apply(_32b, args) - } - } - } - } - var _33a = function() { - if (this.squelch) { - try { - return _329.apply(this, arguments) - } catch (e) { - dojo.debug(e) - } - } else { - return _329.apply(this, arguments) - } - } - if ((this['before']) && (this.before.length > 0)) { - dojo.lang.forEach(this.before.concat(new Array()), _33a) - } - var _33b - try { - if ((this['around']) && (this.around.length > 0)) { - var mi = new dojo.event.MethodInvocation(this, obj, args) - _33b = mi.proceed() - } else { - if (this.methodfunc) { - _33b = this.object[this.methodname].apply(this.object, args) - } - } - } catch (e) { - if (!this.squelch) { - dojo.debug(e, 'when calling', this.methodname, 'on', this.object, 'with arguments', args) - dojo.raise(e) - } - } - if ((this['after']) && (this.after.length > 0)) { - dojo.lang.forEach(this.after.concat(new Array()), _33a) - } - return (this.methodfunc) ? _33b : null - }, getArr: function(kind) { - var type = 'after' - if ((typeof kind == 'string') && (kind.indexOf('before') != -1)) { - type = 'before' - } else { - if (kind == 'around') { - type = 'around' - } - } - if (!this[type]) { - this[type] = [] - } - return this[type] - }, kwAddAdvice: function(args) { - this.addAdvice(args['adviceObj'], args['adviceFunc'], args['aroundObj'], args['aroundFunc'], args['adviceType'], args['precedence'], args['once'], args['delay'], args['rate'], args['adviceMsg'], args['maxCalls']) - }, addAdvice: function(_340, _341, _342, _343, _344, _345, once, _347, rate, _349, _34a) { - var arr = this.getArr(_344) - if (!arr) { - dojo.raise('bad this: ' + this) - } - var ao = [_340, _341, _342, _343, _347, rate, _349, _34a] - if (once) { - if (this.hasAdvice(_340, _341, _344, arr) >= 0) { - return - } - } - if (_345 == 'first') { - arr.unshift(ao) - } else { - arr.push(ao) - } - }, hasAdvice: function(_34d, _34e, _34f, arr) { - if (!arr) { - arr = this.getArr(_34f) - } - var ind = -1 - for (var x = 0; x < arr.length; x++) { - var aao = (typeof _34e == 'object') ? (new String(_34e)).toString() : _34e - var a1o = (typeof arr[x][1] == 'object') ? (new String(arr[x][1])).toString() : arr[x][1] - if ((arr[x][0] == _34d) && (a1o == aao)) { - ind = x - } - } - return ind - }, removeAdvice: function(_355, _356, _357, once) { - var arr = this.getArr(_357) - var ind = this.hasAdvice(_355, _356, _357, arr) - if (ind == -1) { - return false - } - while (ind != -1) { - arr.splice(ind, 1) - if (once) { - break - } - ind = this.hasAdvice(_355, _356, _357, arr) - } - return true - } -}) -dojo.provide('dojo.event.topic') -dojo.event.topic = new function() { - this.topics = {} - this.getTopic = function(_35b) { - if (!this.topics[_35b]) { - this.topics[_35b] = new this.TopicImpl(_35b) - } - return this.topics[_35b] - } - this.registerPublisher = function(_35c, obj, _35e) { - var _35c = this.getTopic(_35c) - _35c.registerPublisher(obj, _35e) - } - this.subscribe = function(_35f, obj, _361) { - var _35f = this.getTopic(_35f) - _35f.subscribe(obj, _361) - } - this.unsubscribe = function(_362, obj, _364) { - var _362 = this.getTopic(_362) - _362.unsubscribe(obj, _364) - } - this.destroy = function(_365) { - this.getTopic(_365).destroy() - delete this.topics[_365] - } - this.publishApply = function(_366, args) { - var _366 = this.getTopic(_366) - _366.sendMessage.apply(_366, args) - } - this.publish = function(_368, _369) { - var _368 = this.getTopic(_368) - var args = [] - for (var x = 1; x < arguments.length; x++) { - args.push(arguments[x]) - } - _368.sendMessage.apply(_368, args) - } -} -dojo.event.topic.TopicImpl = function(_36c) { - this.topicName = _36c - this.subscribe = function(_36d, _36e) { - var tf = _36e || _36d - var to = (!_36e) ? dj_global : _36d - return dojo.event.kwConnect({ srcObj: this, srcFunc: 'sendMessage', adviceObj: to, adviceFunc: tf }) - } - this.unsubscribe = function(_371, _372) { - var tf = (!_372) ? _371 : _372 - var to = (!_372) ? null : _371 - return dojo.event.kwDisconnect({ srcObj: this, srcFunc: 'sendMessage', adviceObj: to, adviceFunc: tf }) - } - this._getJoinPoint = function() { - return dojo.event.MethodJoinPoint.getForMethod(this, 'sendMessage') - } - this.setSquelch = function(_375) { - this._getJoinPoint().squelch = _375 - } - this.destroy = function() { - this._getJoinPoint().disconnect() - } - this.registerPublisher = function(_376, _377) { - dojo.event.connect(_376, _377, this, 'sendMessage') - } - this.sendMessage = function(_378) { - } -} -dojo.provide('dojo.event.browser') -dojo._ie_clobber = new function() { - this.clobberNodes = [] + squelch: false, unintercept: function () { + this.object[this.methodname] = this.methodfunc; + this.before = []; + this.after = []; + this.around = [] + }, disconnect: dojo.lang.forward('unintercept'), run: function () { + var obj = this.object || dj_global; + var args = arguments; + var _327 = []; + for (var x = 0; x < args.length; x++) { + _327[x] = args[x] + } + var _329 = function (marr) { + if (!marr) { + dojo.debug('Null argument to unrollAdvice()'); + return + } + var _32b = marr[0] || dj_global; + var _32c = marr[1]; + if (!_32b[_32c]) { + dojo.raise('function "' + _32c + '" does not exist on "' + _32b + '"') + } + var _32d = marr[2] || dj_global; + var _32e = marr[3]; + var msg = marr[6]; + var _330 = marr[7]; + if (_330 > -1) { + if (_330 == 0) { + return + } + marr[7]-- + } + var _331; + var to = { + args: [], jp_: this, object: obj, proceed: function () { + return _32b[_32c].apply(_32b, to.args) + } + }; + to.args = _327; + var _333 = parseInt(marr[4]); + var _334 = ((!isNaN(_333)) && (marr[4] !== null) && (typeof marr[4] != 'undefined')); + if (marr[5]) { + var rate = parseInt(marr[5]); + var cur = new Date(); + var _337 = false; + if ((marr['last']) && ((cur - marr.last) <= rate)) { + if (dojo.event._canTimeout) { + if (marr['delayTimer']) { + clearTimeout(marr.delayTimer) + } + var tod = parseInt(rate * 2); + var mcpy = dojo.lang.shallowCopy(marr); + marr.delayTimer = setTimeout(function () { + mcpy[5] = 0; + _329(mcpy) + }, tod) + } + return + } else { + marr.last = cur + } + } + if (_32e) { + _32d[_32e].call(_32d, to) + } else { + if ((_334) && ((dojo.render.html) || (dojo.render.svg))) { + dj_global['setTimeout'](function () { + if (msg) { + _32b[_32c].call(_32b, to) + } else { + _32b[_32c].apply(_32b, args) + } + }, _333) + } else { + if (msg) { + _32b[_32c].call(_32b, to) + } else { + _32b[_32c].apply(_32b, args) + } + } + } + }; + var _33a = function () { + if (this.squelch) { + try { + return _329.apply(this, arguments) + } catch (e) { + dojo.debug(e) + } + } else { + return _329.apply(this, arguments) + } + }; + if ((this['before']) && (this.before.length > 0)) { + dojo.lang.forEach(this.before.concat([]), _33a) + } + var _33b; + try { + if ((this['around']) && (this.around.length > 0)) { + var mi = new dojo.event.MethodInvocation(this, obj, args); + _33b = mi.proceed() + } else { + if (this.methodfunc) { + _33b = this.object[this.methodname].apply(this.object, args) + } + } + } catch (e) { + if (!this.squelch) { + dojo.debug(e, 'when calling', this.methodname, 'on', this.object, 'with arguments', args); + dojo.raise(e) + } + } + if ((this['after']) && (this.after.length > 0)) { + dojo.lang.forEach(this.after.concat([]), _33a) + } + return (this.methodfunc) ? _33b : null + }, getArr: function (kind) { + var type = 'after'; + if ((typeof kind == 'string') && (kind.indexOf('before') != -1)) { + type = 'before' + } else { + if (kind == 'around') { + type = 'around' + } + } + if (!this[type]) { + this[type] = [] + } + return this[type] + }, kwAddAdvice: function (args) { + this.addAdvice(args['adviceObj'], args['adviceFunc'], args['aroundObj'], args['aroundFunc'], args['adviceType'], args['precedence'], args['once'], args['delay'], args['rate'], args['adviceMsg'], args['maxCalls']) + }, addAdvice: function (_340, _341, _342, _343, _344, _345, once, _347, rate, _349, _34a) { + var arr = this.getArr(_344); + if (!arr) { + dojo.raise('bad this: ' + this) + } + var ao = [_340, _341, _342, _343, _347, rate, _349, _34a]; + if (once) { + if (this.hasAdvice(_340, _341, _344, arr) >= 0) { + return + } + } + if (_345 == 'first') { + arr.unshift(ao) + } else { + arr.push(ao) + } + }, hasAdvice: function (_34d, _34e, _34f, arr) { + if (!arr) { + arr = this.getArr(_34f) + } + var ind = -1; + for (var x = 0; x < arr.length; x++) { + var aao = (typeof _34e == 'object') ? (String(_34e)).toString() : _34e; + var a1o = (typeof arr[x][1] == 'object') ? (String(arr[x][1])).toString() : arr[x][1]; + if ((arr[x][0] == _34d) && (a1o == aao)) { + ind = x + } + } + return ind + }, removeAdvice: function (_355, _356, _357, once) { + var arr = this.getArr(_357); + var ind = this.hasAdvice(_355, _356, _357, arr); + if (ind == -1) { + return false + } + while (ind != -1) { + arr.splice(ind, 1); + if (once) { + break + } + ind = this.hasAdvice(_355, _356, _357, arr) + } + return true + } +}); +dojo.provide('dojo.event.topic'); +dojo.event.topic = new function () { + this.topics = {}; + this.getTopic = function (_35b) { + if (!this.topics[_35b]) { + this.topics[_35b] = new this.TopicImpl(_35b) + } + return this.topics[_35b] + }; + this.registerPublisher = function (_35c, obj, _35e) { + var _35c = this.getTopic(_35c); + _35c.registerPublisher(obj, _35e) + }; + this.subscribe = function (_35f, obj, _361) { + var _35f = this.getTopic(_35f); + _35f.subscribe(obj, _361) + }; + this.unsubscribe = function (_362, obj, _364) { + var _362 = this.getTopic(_362); + _362.unsubscribe(obj, _364) + }; + this.destroy = function (_365) { + this.getTopic(_365).destroy(); + delete this.topics[_365] + }; + this.publishApply = function (_366, args) { + var _366 = this.getTopic(_366); + _366.sendMessage.apply(_366, args) + }; + this.publish = function (_368, _369) { + var _368 = this.getTopic(_368); + var args = []; + for (var x = 1; x < arguments.length; x++) { + args.push(arguments[x]) + } + _368.sendMessage.apply(_368, args) + } +}; +;dojo.event.topic.TopicImpl = function (_36c) { + this.topicName = _36c; + this.subscribe = function (_36d, _36e) { + var tf = _36e || _36d; + var to = (!_36e) ? dj_global : _36d; + return dojo.event.kwConnect({srcObj: this, srcFunc: 'sendMessage', adviceObj: to, adviceFunc: tf}) + }; + this.unsubscribe = function (_371, _372) { + var tf = (!_372) ? _371 : _372; + var to = (!_372) ? null : _371; + return dojo.event.kwDisconnect({srcObj: this, srcFunc: 'sendMessage', adviceObj: to, adviceFunc: tf}) + }; + this._getJoinPoint = function () { + return dojo.event.MethodJoinPoint.getForMethod(this, 'sendMessage') + }; + this.setSquelch = function (_375) { + this._getJoinPoint().squelch = _375 + }; + this.destroy = function () { + this._getJoinPoint().disconnect() + }; + this.registerPublisher = function (_376, _377) { + dojo.event.connect(_376, _377, this, 'sendMessage') + }; + this.sendMessage = function (_378) { + } +}; +dojo.provide('dojo.event.browser'); +dojo._ie_clobber = new function () { + this.clobberNodes = []; - function nukeProp(node, prop) { - try { - node[prop] = null - } catch (e) { - } - try { - delete node[prop] - } catch (e) { - } - try { - node.removeAttribute(prop) - } catch (e) { - } - } + function nukeProp(node, prop) { + try { + node[prop] = null + } catch (e) { + } + try { + delete node[prop] + } catch (e) { + } + try { + node.removeAttribute(prop) + } catch (e) { + } + } - this.clobber = function(_37b) { - var na - var tna - if (_37b) { - tna = _37b.all || _37b.getElementsByTagName('*') - na = [_37b] - for (var x = 0; x < tna.length; x++) { - if (tna[x]['__doClobber__']) { - na.push(tna[x]) - } - } - } else { - try { - window.onload = null - } catch (e) { - } - na = (this.clobberNodes.length) ? this.clobberNodes : document.all - } - tna = null - var _37f = {} - for (var i = na.length - 1; i >= 0; i = i - 1) { - var el = na[i] - try { - if (el && el['__clobberAttrs__']) { - for (var j = 0; j < el.__clobberAttrs__.length; j++) { - nukeProp(el, el.__clobberAttrs__[j]) - } - nukeProp(el, '__clobberAttrs__') - nukeProp(el, '__doClobber__') - } - } catch (e) { - } - } - na = null - } -} + this.clobber = function (_37b) { + var na; + var tna; + if (_37b) { + tna = _37b.all || _37b.getElementsByTagName('*'); + na = [_37b]; + for (var x = 0; x < tna.length; x++) { + if (tna[x]['__doClobber__']) { + na.push(tna[x]) + } + } + } else { + try { + window.onload = null + } catch (e) { + } + na = (this.clobberNodes.length) ? this.clobberNodes : document.all + } + tna = null; + var _37f = {}; + for (var i = na.length - 1; i >= 0; i = i - 1) { + var el = na[i]; + try { + if (el && el['__clobberAttrs__']) { + for (var j = 0; j < el.__clobberAttrs__.length; j++) { + nukeProp(el, el.__clobberAttrs__[j]) + } + nukeProp(el, '__clobberAttrs__'); + nukeProp(el, '__doClobber__') + } + } catch (e) { + } + } + na = null + } +}; +; if (dojo.render.html.ie) { - dojo.addOnUnload(function() { - dojo._ie_clobber.clobber() - try { - if ((dojo['widget']) && (dojo.widget['manager'])) { - dojo.widget.manager.destroyAll() - } - } catch (e) { - } - if (dojo.widget) { - for (var name in dojo.widget._templateCache) { - if (dojo.widget._templateCache[name].node) { - dojo.dom.destroyNode(dojo.widget._templateCache[name].node) - dojo.widget._templateCache[name].node = null - delete dojo.widget._templateCache[name].node - } - } - } - try { - window.onload = null - } catch (e) { - } - try { - window.onunload = null - } catch (e) { - } - dojo._ie_clobber.clobberNodes = [] - }) -} -dojo.event.browser = new function() { - var _384 = 0 - this.normalizedEventName = function(_385) { - switch (_385) { - case 'CheckboxStateChange': - case 'DOMAttrModified': - case 'DOMMenuItemActive': - case 'DOMMenuItemInactive': - case 'DOMMouseScroll': - case 'DOMNodeInserted': - case 'DOMNodeRemoved': - case 'RadioStateChange': - return _385 - break - default: - var lcn = _385.toLowerCase() - return (lcn.indexOf('on') == 0) ? lcn.substr(2) : lcn - break - } - } - this.clean = function(node) { - if (dojo.render.html.ie) { - dojo._ie_clobber.clobber(node) - } - } - this.addClobberNode = function(node) { - if (!dojo.render.html.ie) { - return - } - if (!node['__doClobber__']) { - node.__doClobber__ = true - dojo._ie_clobber.clobberNodes.push(node) - node.__clobberAttrs__ = [] - } - } - this.addClobberNodeAttrs = function(node, _38a) { - if (!dojo.render.html.ie) { - return - } - this.addClobberNode(node) - for (var x = 0; x < _38a.length; x++) { - node.__clobberAttrs__.push(_38a[x]) - } - } - this.removeListener = function(node, _38d, fp, _38f) { - if (!_38f) { - var _38f = false - } - _38d = dojo.event.browser.normalizedEventName(_38d) - if (_38d == 'key') { - if (dojo.render.html.ie) { - this.removeListener(node, 'onkeydown', fp, _38f) - } - _38d = 'keypress' - } - if (node.removeEventListener) { - node.removeEventListener(_38d, fp, _38f) - } - } - this.addListener = function(node, _391, fp, _393, _394) { - if (!node) { - return - } - if (!_393) { - var _393 = false - } - _391 = dojo.event.browser.normalizedEventName(_391) - if (_391 == 'key') { - if (dojo.render.html.ie) { - this.addListener(node, 'onkeydown', fp, _393, _394) - } - _391 = 'keypress' - } - if (!_394) { - var _395 = function(evt) { - if (!evt) { - evt = window.event - } - var ret = fp(dojo.event.browser.fixEvent(evt, this)) - if (_393) { - dojo.event.browser.stopEvent(evt) - } - return ret - } - } else { - _395 = fp - } - if (node.addEventListener) { - node.addEventListener(_391, _395, _393) - return _395 - } else { - _391 = 'on' + _391 - if (typeof node[_391] == 'function') { - var _398 = node[_391] - node[_391] = function(e) { - _398(e) - return _395(e) - } - } else { - node[_391] = _395 - } - if (dojo.render.html.ie) { - this.addClobberNodeAttrs(node, [_391]) - } - return _395 - } - } - this.isEvent = function(obj) { - return (typeof obj != 'undefined') && (obj) && (typeof Event != 'undefined') && (obj.eventPhase) - } - this.currentEvent = null - this.callListener = function(_39b, _39c) { - if (typeof _39b != 'function') { - dojo.raise('listener not a function: ' + _39b) - } - dojo.event.browser.currentEvent.currentTarget = _39c - return _39b.call(_39c, dojo.event.browser.currentEvent) - } - this._stopPropagation = function() { - dojo.event.browser.currentEvent.cancelBubble = true - } - this._preventDefault = function() { - dojo.event.browser.currentEvent.returnValue = false - } - this.keys = { - KEY_BACKSPACE: 8, - KEY_TAB: 9, - KEY_CLEAR: 12, - KEY_ENTER: 13, - KEY_SHIFT: 16, - KEY_CTRL: 17, - KEY_ALT: 18, - KEY_PAUSE: 19, - KEY_CAPS_LOCK: 20, - KEY_ESCAPE: 27, - KEY_SPACE: 32, - KEY_PAGE_UP: 33, - KEY_PAGE_DOWN: 34, - KEY_END: 35, - KEY_HOME: 36, - KEY_LEFT_ARROW: 37, - KEY_UP_ARROW: 38, - KEY_RIGHT_ARROW: 39, - KEY_DOWN_ARROW: 40, - KEY_INSERT: 45, - KEY_DELETE: 46, - KEY_HELP: 47, - KEY_LEFT_WINDOW: 91, - KEY_RIGHT_WINDOW: 92, - KEY_SELECT: 93, - KEY_NUMPAD_0: 96, - KEY_NUMPAD_1: 97, - KEY_NUMPAD_2: 98, - KEY_NUMPAD_3: 99, - KEY_NUMPAD_4: 100, - KEY_NUMPAD_5: 101, - KEY_NUMPAD_6: 102, - KEY_NUMPAD_7: 103, - KEY_NUMPAD_8: 104, - KEY_NUMPAD_9: 105, - KEY_NUMPAD_MULTIPLY: 106, - KEY_NUMPAD_PLUS: 107, - KEY_NUMPAD_ENTER: 108, - KEY_NUMPAD_MINUS: 109, - KEY_NUMPAD_PERIOD: 110, - KEY_NUMPAD_DIVIDE: 111, - KEY_F1: 112, - KEY_F2: 113, - KEY_F3: 114, - KEY_F4: 115, - KEY_F5: 116, - KEY_F6: 117, - KEY_F7: 118, - KEY_F8: 119, - KEY_F9: 120, - KEY_F10: 121, - KEY_F11: 122, - KEY_F12: 123, - KEY_F13: 124, - KEY_F14: 125, - KEY_F15: 126, - KEY_NUM_LOCK: 144, - KEY_SCROLL_LOCK: 145 - } - this.revKeys = [] - for (var key in this.keys) { - this.revKeys[this.keys[key]] = key - } - this.fixEvent = function(evt, _39f) { - if (!evt) { - if (window['event']) { - evt = window.event - } - } - if ((evt['type']) && (evt['type'].indexOf('key') == 0)) { - evt.keys = this.revKeys - for (var key in this.keys) { - evt[key] = this.keys[key] - } - if (evt['type'] == 'keydown' && dojo.render.html.ie) { - switch (evt.keyCode) { - case evt.KEY_SHIFT: - case evt.KEY_CTRL: - case evt.KEY_ALT: - case evt.KEY_CAPS_LOCK: - case evt.KEY_LEFT_WINDOW: - case evt.KEY_RIGHT_WINDOW: - case evt.KEY_SELECT: - case evt.KEY_NUM_LOCK: - case evt.KEY_SCROLL_LOCK: - case evt.KEY_NUMPAD_0: - case evt.KEY_NUMPAD_1: - case evt.KEY_NUMPAD_2: - case evt.KEY_NUMPAD_3: - case evt.KEY_NUMPAD_4: - case evt.KEY_NUMPAD_5: - case evt.KEY_NUMPAD_6: - case evt.KEY_NUMPAD_7: - case evt.KEY_NUMPAD_8: - case evt.KEY_NUMPAD_9: - case evt.KEY_NUMPAD_PERIOD: - break - case evt.KEY_NUMPAD_MULTIPLY: - case evt.KEY_NUMPAD_PLUS: - case evt.KEY_NUMPAD_ENTER: - case evt.KEY_NUMPAD_MINUS: - case evt.KEY_NUMPAD_DIVIDE: - break - case evt.KEY_PAUSE: - case evt.KEY_TAB: - case evt.KEY_BACKSPACE: - case evt.KEY_ENTER: - case evt.KEY_ESCAPE: - case evt.KEY_PAGE_UP: - case evt.KEY_PAGE_DOWN: - case evt.KEY_END: - case evt.KEY_HOME: - case evt.KEY_LEFT_ARROW: - case evt.KEY_UP_ARROW: - case evt.KEY_RIGHT_ARROW: - case evt.KEY_DOWN_ARROW: - case evt.KEY_INSERT: - case evt.KEY_DELETE: - case evt.KEY_F1: - case evt.KEY_F2: - case evt.KEY_F3: - case evt.KEY_F4: - case evt.KEY_F5: - case evt.KEY_F6: - case evt.KEY_F7: - case evt.KEY_F8: - case evt.KEY_F9: - case evt.KEY_F10: - case evt.KEY_F11: - case evt.KEY_F12: - case evt.KEY_F12: - case evt.KEY_F13: - case evt.KEY_F14: - case evt.KEY_F15: - case evt.KEY_CLEAR: - case evt.KEY_HELP: - evt.key = evt.keyCode - break - default: - if (evt.ctrlKey || evt.altKey) { - var _3a1 = evt.keyCode - if (_3a1 >= 65 && _3a1 <= 90 && evt.shiftKey == false) { - _3a1 += 32 - } - if (_3a1 >= 1 && _3a1 <= 26 && evt.ctrlKey) { - _3a1 += 96 - } - evt.key = String.fromCharCode(_3a1) - } - } - } else { - if (evt['type'] == 'keypress') { - if (dojo.render.html.opera) { - if (evt.which == 0) { - evt.key = evt.keyCode - } else { - if (evt.which > 0) { - switch (evt.which) { - case evt.KEY_SHIFT: - case evt.KEY_CTRL: - case evt.KEY_ALT: - case evt.KEY_CAPS_LOCK: - case evt.KEY_NUM_LOCK: - case evt.KEY_SCROLL_LOCK: - break - case evt.KEY_PAUSE: - case evt.KEY_TAB: - case evt.KEY_BACKSPACE: - case evt.KEY_ENTER: - case evt.KEY_ESCAPE: - evt.key = evt.which - break - default: - var _3a1 = evt.which - if ((evt.ctrlKey || evt.altKey || evt.metaKey) && (evt.which >= 65 && evt.which <= 90 && evt.shiftKey == false)) { - _3a1 += 32 - } - evt.key = String.fromCharCode(_3a1) - } - } - } - } else { - if (dojo.render.html.ie) { - if (!evt.ctrlKey && !evt.altKey && evt.keyCode >= evt.KEY_SPACE) { - evt.key = String.fromCharCode(evt.keyCode) - } - } else { - if (dojo.render.html.safari) { - switch (evt.keyCode) { - case 25: - evt.key = evt.KEY_TAB - evt.shift = true - break - case 63232: - evt.key = evt.KEY_UP_ARROW - break - case 63233: - evt.key = evt.KEY_DOWN_ARROW - break - case 63234: - evt.key = evt.KEY_LEFT_ARROW - break - case 63235: - evt.key = evt.KEY_RIGHT_ARROW - break - case 63236: - evt.key = evt.KEY_F1 - break - case 63237: - evt.key = evt.KEY_F2 - break - case 63238: - evt.key = evt.KEY_F3 - break - case 63239: - evt.key = evt.KEY_F4 - break - case 63240: - evt.key = evt.KEY_F5 - break - case 63241: - evt.key = evt.KEY_F6 - break - case 63242: - evt.key = evt.KEY_F7 - break - case 63243: - evt.key = evt.KEY_F8 - break - case 63244: - evt.key = evt.KEY_F9 - break - case 63245: - evt.key = evt.KEY_F10 - break - case 63246: - evt.key = evt.KEY_F11 - break - case 63247: - evt.key = evt.KEY_F12 - break - case 63250: - evt.key = evt.KEY_PAUSE - break - case 63272: - evt.key = evt.KEY_DELETE - break - case 63273: - evt.key = evt.KEY_HOME - break - case 63275: - evt.key = evt.KEY_END - break - case 63276: - evt.key = evt.KEY_PAGE_UP - break - case 63277: - evt.key = evt.KEY_PAGE_DOWN - break - case 63302: - evt.key = evt.KEY_INSERT - break - case 63248: - case 63249: - case 63289: - break - default: - evt.key = evt.charCode >= evt.KEY_SPACE ? String.fromCharCode(evt.charCode) : evt.keyCode - } - } else { - evt.key = evt.charCode > 0 ? String.fromCharCode(evt.charCode) : evt.keyCode - } - } - } - } - } - } - if (dojo.render.html.ie) { - if (!evt.target) { - evt.target = evt.srcElement - } - if (!evt.currentTarget) { - evt.currentTarget = (_39f ? _39f : evt.srcElement) - } - if (!evt.layerX) { - evt.layerX = evt.offsetX - } - if (!evt.layerY) { - evt.layerY = evt.offsetY - } - var doc = (evt.srcElement && evt.srcElement.ownerDocument) ? evt.srcElement.ownerDocument : document - var _3a3 = ((dojo.render.html.ie55) || (doc['compatMode'] == 'BackCompat')) ? doc.body : doc.documentElement - if (!evt.pageX) { - evt.pageX = evt.clientX + (_3a3.scrollLeft || 0) - } - if (!evt.pageY) { - evt.pageY = evt.clientY + (_3a3.scrollTop || 0) - } - if (evt.type == 'mouseover') { - evt.relatedTarget = evt.fromElement - } - if (evt.type == 'mouseout') { - evt.relatedTarget = evt.toElement - } - this.currentEvent = evt - evt.callListener = this.callListener - evt.stopPropagation = this._stopPropagation - evt.preventDefault = this._preventDefault - } - return evt - } - this.stopEvent = function(evt) { - if (window.event) { - evt.cancelBubble = true - evt.returnValue = false - } else { - evt.preventDefault() - evt.stopPropagation() - } - } -} -dojo.kwCompoundRequire({ - common: ['dojo.event.common', 'dojo.event.topic'], - browser: ['dojo.event.browser'], - dashboard: ['dojo.event.browser'] -}) -dojo.provide('dojo.event.*') -dojo.provide('dojo.gfx.color') -dojo.gfx.color.Color = function(r, g, b, a) { - if (dojo.lang.isArray(r)) { - this.r = r[0] - this.g = r[1] - this.b = r[2] - this.a = r[3] || 1 - } else { - if (dojo.lang.isString(r)) { - var rgb = dojo.gfx.color.extractRGB(r) - this.r = rgb[0] - this.g = rgb[1] - this.b = rgb[2] - this.a = g || 1 - } else { - if (r instanceof dojo.gfx.color.Color) { - this.r = r.r - this.b = r.b - this.g = r.g - this.a = r.a - } else { - this.r = r - this.g = g - this.b = b - this.a = a - } - } - } -} -dojo.gfx.color.Color.fromArray = function(arr) { - return new dojo.gfx.color.Color(arr[0], arr[1], arr[2], arr[3]) -} + dojo.addOnUnload(function () { + dojo._ie_clobber.clobber(); + try { + if ((dojo['widget']) && (dojo.widget['manager'])) { + dojo.widget.manager.destroyAll() + } + } catch (e) { + } + if (dojo.widget) { + for (var name in dojo.widget._templateCache) { + if (dojo.widget._templateCache[name].node) { + dojo.dom.destroyNode(dojo.widget._templateCache[name].node); + dojo.widget._templateCache[name].node = null; + delete dojo.widget._templateCache[name].node + } + } + } + try { + window.onload = null + } catch (e) { + } + try { + window.onunload = null + } catch (e) { + } + dojo._ie_clobber.clobberNodes = [] + }) +} +dojo.event.browser = new function () { + var _384 = 0; + this.normalizedEventName = function (_385) { + switch (_385) { + case 'CheckboxStateChange': + case 'DOMAttrModified': + case 'DOMMenuItemActive': + case 'DOMMenuItemInactive': + case 'DOMMouseScroll': + case 'DOMNodeInserted': + case 'DOMNodeRemoved': + case 'RadioStateChange': + return _385; + break; + default: + var lcn = _385.toLowerCase(); + return (lcn.indexOf('on') == 0) ? lcn.substr(2) : lcn; + break + } + }; + this.clean = function (node) { + if (dojo.render.html.ie) { + dojo._ie_clobber.clobber(node) + } + }; + this.addClobberNode = function (node) { + if (!dojo.render.html.ie) { + return + } + if (!node['__doClobber__']) { + node.__doClobber__ = true; + dojo._ie_clobber.clobberNodes.push(node); + node.__clobberAttrs__ = [] + } + }; + this.addClobberNodeAttrs = function (node, _38a) { + if (!dojo.render.html.ie) { + return + } + this.addClobberNode(node); + for (var x = 0; x < _38a.length; x++) { + node.__clobberAttrs__.push(_38a[x]) + } + }; + this.removeListener = function (node, _38d, fp, _38f) { + if (!_38f) { + var _38f = false + } + _38d = dojo.event.browser.normalizedEventName(_38d); + if (_38d == 'key') { + if (dojo.render.html.ie) { + this.removeListener(node, 'onkeydown', fp, _38f) + } + _38d = 'keypress' + } + if (node.removeEventListener) { + node.removeEventListener(_38d, fp, _38f) + } + }; + this.addListener = function (node, _391, fp, _393, _394) { + if (!node) { + return + } + if (!_393) { + var _393 = false + } + _391 = dojo.event.browser.normalizedEventName(_391); + if (_391 == 'key') { + if (dojo.render.html.ie) { + this.addListener(node, 'onkeydown', fp, _393, _394) + } + _391 = 'keypress' + } + if (!_394) { + var _395 = function (evt) { + if (!evt) { + evt = window.event + } + var ret = fp(dojo.event.browser.fixEvent(evt, this)); + if (_393) { + dojo.event.browser.stopEvent(evt) + } + return ret + } + } else { + _395 = fp + } + if (node.addEventListener) { + node.addEventListener(_391, _395, _393); + return _395 + } else { + _391 = 'on' + _391; + if (typeof node[_391] == 'function') { + var _398 = node[_391]; + node[_391] = function (e) { + _398(e); + return _395(e) + } + } else { + node[_391] = _395 + } + if (dojo.render.html.ie) { + this.addClobberNodeAttrs(node, [_391]) + } + return _395 + } + }; + this.isEvent = function (obj) { + return (typeof obj != 'undefined') && (obj) && (typeof Event != 'undefined') && (obj.eventPhase) + }; + this.currentEvent = null; + this.callListener = function (_39b, _39c) { + if (typeof _39b != 'function') { + dojo.raise('listener not a function: ' + _39b) + } + dojo.event.browser.currentEvent.currentTarget = _39c; + return _39b.call(_39c, dojo.event.browser.currentEvent) + }; + this._stopPropagation = function () { + dojo.event.browser.currentEvent.cancelBubble = true + }; + this._preventDefault = function () { + dojo.event.browser.currentEvent.returnValue = false + }; + this.keys = { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_CLEAR: 12, + KEY_ENTER: 13, + KEY_SHIFT: 16, + KEY_CTRL: 17, + KEY_ALT: 18, + KEY_PAUSE: 19, + KEY_CAPS_LOCK: 20, + KEY_ESCAPE: 27, + KEY_SPACE: 32, + KEY_PAGE_UP: 33, + KEY_PAGE_DOWN: 34, + KEY_END: 35, + KEY_HOME: 36, + KEY_LEFT_ARROW: 37, + KEY_UP_ARROW: 38, + KEY_RIGHT_ARROW: 39, + KEY_DOWN_ARROW: 40, + KEY_INSERT: 45, + KEY_DELETE: 46, + KEY_HELP: 47, + KEY_LEFT_WINDOW: 91, + KEY_RIGHT_WINDOW: 92, + KEY_SELECT: 93, + KEY_NUMPAD_0: 96, + KEY_NUMPAD_1: 97, + KEY_NUMPAD_2: 98, + KEY_NUMPAD_3: 99, + KEY_NUMPAD_4: 100, + KEY_NUMPAD_5: 101, + KEY_NUMPAD_6: 102, + KEY_NUMPAD_7: 103, + KEY_NUMPAD_8: 104, + KEY_NUMPAD_9: 105, + KEY_NUMPAD_MULTIPLY: 106, + KEY_NUMPAD_PLUS: 107, + KEY_NUMPAD_ENTER: 108, + KEY_NUMPAD_MINUS: 109, + KEY_NUMPAD_PERIOD: 110, + KEY_NUMPAD_DIVIDE: 111, + KEY_F1: 112, + KEY_F2: 113, + KEY_F3: 114, + KEY_F4: 115, + KEY_F5: 116, + KEY_F6: 117, + KEY_F7: 118, + KEY_F8: 119, + KEY_F9: 120, + KEY_F10: 121, + KEY_F11: 122, + KEY_F12: 123, + KEY_F13: 124, + KEY_F14: 125, + KEY_F15: 126, + KEY_NUM_LOCK: 144, + KEY_SCROLL_LOCK: 145 + }; + this.revKeys = []; + for (var key in this.keys) { + this.revKeys[this.keys[key]] = key + } + this.fixEvent = function (evt, _39f) { + if (!evt) { + if (window['event']) { + evt = window.event + } + } + if ((evt['type']) && (evt['type'].indexOf('key') == 0)) { + evt.keys = this.revKeys; + for (var key in this.keys) { + evt[key] = this.keys[key] + } + if (evt['type'] == 'keydown' && dojo.render.html.ie) { + switch (evt.keyCode) { + case evt.KEY_SHIFT: + case evt.KEY_CTRL: + case evt.KEY_ALT: + case evt.KEY_CAPS_LOCK: + case evt.KEY_LEFT_WINDOW: + case evt.KEY_RIGHT_WINDOW: + case evt.KEY_SELECT: + case evt.KEY_NUM_LOCK: + case evt.KEY_SCROLL_LOCK: + case evt.KEY_NUMPAD_0: + case evt.KEY_NUMPAD_1: + case evt.KEY_NUMPAD_2: + case evt.KEY_NUMPAD_3: + case evt.KEY_NUMPAD_4: + case evt.KEY_NUMPAD_5: + case evt.KEY_NUMPAD_6: + case evt.KEY_NUMPAD_7: + case evt.KEY_NUMPAD_8: + case evt.KEY_NUMPAD_9: + case evt.KEY_NUMPAD_PERIOD: + break; + case evt.KEY_NUMPAD_MULTIPLY: + case evt.KEY_NUMPAD_PLUS: + case evt.KEY_NUMPAD_ENTER: + case evt.KEY_NUMPAD_MINUS: + case evt.KEY_NUMPAD_DIVIDE: + break; + case evt.KEY_PAUSE: + case evt.KEY_TAB: + case evt.KEY_BACKSPACE: + case evt.KEY_ENTER: + case evt.KEY_ESCAPE: + case evt.KEY_PAGE_UP: + case evt.KEY_PAGE_DOWN: + case evt.KEY_END: + case evt.KEY_HOME: + case evt.KEY_LEFT_ARROW: + case evt.KEY_UP_ARROW: + case evt.KEY_RIGHT_ARROW: + case evt.KEY_DOWN_ARROW: + case evt.KEY_INSERT: + case evt.KEY_DELETE: + case evt.KEY_F1: + case evt.KEY_F2: + case evt.KEY_F3: + case evt.KEY_F4: + case evt.KEY_F5: + case evt.KEY_F6: + case evt.KEY_F7: + case evt.KEY_F8: + case evt.KEY_F9: + case evt.KEY_F10: + case evt.KEY_F11: + case evt.KEY_F12: + case evt.KEY_F12: + case evt.KEY_F13: + case evt.KEY_F14: + case evt.KEY_F15: + case evt.KEY_CLEAR: + case evt.KEY_HELP: + evt.key = evt.keyCode; + break; + default: + if (evt.ctrlKey || evt.altKey) { + var _3a1 = evt.keyCode; + if (_3a1 >= 65 && _3a1 <= 90 && evt.shiftKey == false) { + _3a1 += 32 + } + if (_3a1 >= 1 && _3a1 <= 26 && evt.ctrlKey) { + _3a1 += 96 + } + evt.key = String.fromCharCode(_3a1) + } + } + } else { + if (evt['type'] == 'keypress') { + if (dojo.render.html.opera) { + if (evt.which == 0) { + evt.key = evt.keyCode + } else { + if (evt.which > 0) { + switch (evt.which) { + case evt.KEY_SHIFT: + case evt.KEY_CTRL: + case evt.KEY_ALT: + case evt.KEY_CAPS_LOCK: + case evt.KEY_NUM_LOCK: + case evt.KEY_SCROLL_LOCK: + break; + case evt.KEY_PAUSE: + case evt.KEY_TAB: + case evt.KEY_BACKSPACE: + case evt.KEY_ENTER: + case evt.KEY_ESCAPE: + evt.key = evt.which; + break; + default: + var _3a1 = evt.which; + if ((evt.ctrlKey || evt.altKey || evt.metaKey) && (evt.which + >= 65 + && evt.which + <= 90 + && evt.shiftKey + == false)) { + _3a1 += 32 + } + evt.key = String.fromCharCode(_3a1) + } + } + } + } else { + if (dojo.render.html.ie) { + if (!evt.ctrlKey && !evt.altKey && evt.keyCode >= evt.KEY_SPACE) { + evt.key = String.fromCharCode(evt.keyCode) + } + } else { + if (dojo.render.html.safari) { + switch (evt.keyCode) { + case 25: + evt.key = evt.KEY_TAB; + evt.shift = true; + break; + case 63232: + evt.key = evt.KEY_UP_ARROW; + break; + case 63233: + evt.key = evt.KEY_DOWN_ARROW; + break; + case 63234: + evt.key = evt.KEY_LEFT_ARROW; + break; + case 63235: + evt.key = evt.KEY_RIGHT_ARROW; + break; + case 63236: + evt.key = evt.KEY_F1; + break; + case 63237: + evt.key = evt.KEY_F2; + break; + case 63238: + evt.key = evt.KEY_F3; + break; + case 63239: + evt.key = evt.KEY_F4; + break; + case 63240: + evt.key = evt.KEY_F5; + break; + case 63241: + evt.key = evt.KEY_F6; + break; + case 63242: + evt.key = evt.KEY_F7; + break; + case 63243: + evt.key = evt.KEY_F8; + break; + case 63244: + evt.key = evt.KEY_F9; + break; + case 63245: + evt.key = evt.KEY_F10; + break; + case 63246: + evt.key = evt.KEY_F11; + break; + case 63247: + evt.key = evt.KEY_F12; + break; + case 63250: + evt.key = evt.KEY_PAUSE; + break; + case 63272: + evt.key = evt.KEY_DELETE; + break; + case 63273: + evt.key = evt.KEY_HOME; + break; + case 63275: + evt.key = evt.KEY_END; + break; + case 63276: + evt.key = evt.KEY_PAGE_UP; + break; + case 63277: + evt.key = evt.KEY_PAGE_DOWN; + break; + case 63302: + evt.key = evt.KEY_INSERT; + break; + case 63248: + case 63249: + case 63289: + break; + default: + evt.key = evt.charCode >= evt.KEY_SPACE ? String.fromCharCode(evt.charCode) : evt.keyCode + } + } else { + evt.key = evt.charCode > 0 ? String.fromCharCode(evt.charCode) : evt.keyCode + } + } + } + } + } + } + if (dojo.render.html.ie) { + if (!evt.target) { + evt.target = evt.srcElement + } + if (!evt.currentTarget) { + evt.currentTarget = (_39f ? _39f : evt.srcElement) + } + if (!evt.layerX) { + evt.layerX = evt.offsetX + } + if (!evt.layerY) { + evt.layerY = evt.offsetY + } + var doc = (evt.srcElement && evt.srcElement.ownerDocument) ? evt.srcElement.ownerDocument : document; + var _3a3 = ((dojo.render.html.ie55) || (doc['compatMode'] == 'BackCompat')) ? doc.body : doc.documentElement; + if (!evt.pageX) { + evt.pageX = evt.clientX + (_3a3.scrollLeft || 0) + } + if (!evt.pageY) { + evt.pageY = evt.clientY + (_3a3.scrollTop || 0) + } + if (evt.type == 'mouseover') { + evt.relatedTarget = evt.fromElement + } + if (evt.type == 'mouseout') { + evt.relatedTarget = evt.toElement + } + this.currentEvent = evt; + evt.callListener = this.callListener; + evt.stopPropagation = this._stopPropagation; + evt.preventDefault = this._preventDefault + } + return evt + }; + this.stopEvent = function (evt) { + if (window.event) { + evt.cancelBubble = true; + evt.returnValue = false + } else { + evt.preventDefault(); + evt.stopPropagation() + } + } +}; +;dojo.kwCompoundRequire({ + common: ['dojo.event.common', 'dojo.event.topic'], browser: ['dojo.event.browser'], dashboard: ['dojo.event.browser'] +}); +dojo.provide('dojo.event.*'); +dojo.provide('dojo.gfx.color'); +dojo.gfx.color.Color = function (r, g, b, a) { + if (dojo.lang.isArray(r)) { + this.r = r[0]; + this.g = r[1]; + this.b = r[2]; + this.a = r[3] || 1 + } else { + if (dojo.lang.isString(r)) { + var rgb = dojo.gfx.color.extractRGB(r); + this.r = rgb[0]; + this.g = rgb[1]; + this.b = rgb[2]; + this.a = g || 1 + } else { + if (r instanceof dojo.gfx.color.Color) { + this.r = r.r; + this.b = r.b; + this.g = r.g; + this.a = r.a + } else { + this.r = r; + this.g = g; + this.b = b; + this.a = a + } + } + } +}; +dojo.gfx.color.Color.fromArray = function (arr) { + return new dojo.gfx.color.Color(arr[0], arr[1], arr[2], arr[3]) +}; dojo.extend(dojo.gfx.color.Color, { - toRgb: function(_3ab) { - if (_3ab) { - return this.toRgba() - } else { - return [this.r, this.g, this.b] - } - }, toRgba: function() { - return [this.r, this.g, this.b, this.a] - }, toHex: function() { - return dojo.gfx.color.rgb2hex(this.toRgb()) - }, toCss: function() { - return 'rgb(' + this.toRgb().join() + ')' - }, toString: function() { - return this.toHex() - }, blend: function(_3ac, _3ad) { - var rgb = null - if (dojo.lang.isArray(_3ac)) { - rgb = _3ac - } else { - if (_3ac instanceof dojo.gfx.color.Color) { - rgb = _3ac.toRgb() - } else { - rgb = new dojo.gfx.color.Color(_3ac).toRgb() - } - } - return dojo.gfx.color.blend(this.toRgb(), rgb, _3ad) - } -}) + toRgb: function (_3ab) { + if (_3ab) { + return this.toRgba() + } else { + return [this.r, this.g, this.b] + } + }, toRgba: function () { + return [this.r, this.g, this.b, this.a] + }, toHex: function () { + return dojo.gfx.color.rgb2hex(this.toRgb()) + }, toCss: function () { + return 'rgb(' + this.toRgb().join() + ')' + }, toString: function () { + return this.toHex() + }, blend: function (_3ac, _3ad) { + var rgb = null; + if (dojo.lang.isArray(_3ac)) { + rgb = _3ac + } else { + if (_3ac instanceof dojo.gfx.color.Color) { + rgb = _3ac.toRgb() + } else { + rgb = new dojo.gfx.color.Color(_3ac).toRgb() + } + } + return dojo.gfx.color.blend(this.toRgb(), rgb, _3ad) + } +}); dojo.gfx.color.named = { - white: [255, 255, 255], - black: [0, 0, 0], - red: [255, 0, 0], - green: [0, 255, 0], - lime: [0, 255, 0], - blue: [0, 0, 255], - navy: [0, 0, 128], - gray: [128, 128, 128], - silver: [192, 192, 192] -} -dojo.gfx.color.blend = function(a, b, _3b1) { - if (typeof a == 'string') { - return dojo.gfx.color.blendHex(a, b, _3b1) - } - if (!_3b1) { - _3b1 = 0 - } - _3b1 = Math.min(Math.max(-1, _3b1), 1) - _3b1 = ((_3b1 + 1) / 2) - var c = [] - for (var x = 0; x < 3; x++) { - c[x] = parseInt(b[x] + ((a[x] - b[x]) * _3b1)) - } - return c -} -dojo.gfx.color.blendHex = function(a, b, _3b6) { - return dojo.gfx.color.rgb2hex(dojo.gfx.color.blend(dojo.gfx.color.hex2rgb(a), dojo.gfx.color.hex2rgb(b), _3b6)) -} -dojo.gfx.color.extractRGB = function(_3b7) { - var hex = '0123456789abcdef' - _3b7 = _3b7.toLowerCase() - if (_3b7.indexOf('rgb') == 0) { - var _3b9 = _3b7.match(/rgba*\((\d+), *(\d+), *(\d+)/i) - var ret = _3b9.splice(1, 3) - return ret - } else { - var _3bb = dojo.gfx.color.hex2rgb(_3b7) - if (_3bb) { - return _3bb - } else { - return dojo.gfx.color.named[_3b7] || [255, 255, 255] - } - } -} -dojo.gfx.color.hex2rgb = function(hex) { - var _3bd = '0123456789ABCDEF' - var rgb = new Array(3) - if (hex.indexOf('#') == 0) { - hex = hex.substring(1) - } - hex = hex.toUpperCase() - if (hex.replace(new RegExp('[' + _3bd + ']', 'g'), '') != '') { - return null - } - if (hex.length == 3) { - rgb[0] = hex.charAt(0) + hex.charAt(0) - rgb[1] = hex.charAt(1) + hex.charAt(1) - rgb[2] = hex.charAt(2) + hex.charAt(2) - } else { - rgb[0] = hex.substring(0, 2) - rgb[1] = hex.substring(2, 4) - rgb[2] = hex.substring(4) - } - for (var i = 0; i < rgb.length; i++) { - rgb[i] = _3bd.indexOf(rgb[i].charAt(0)) * 16 + _3bd.indexOf(rgb[i].charAt(1)) - } - return rgb -} -dojo.gfx.color.rgb2hex = function(r, g, b) { - if (dojo.lang.isArray(r)) { - g = r[1] || 0 - b = r[2] || 0 - r = r[0] || 0 - } - var ret = dojo.lang.map([r, g, b], function(x) { - x = new Number(x) - var s = x.toString(16) - while (s.length < 2) { - s = '0' + s - } - return s - }) - ret.unshift('#') - return ret.join('') -} -dojo.provide('dojo.lfx.Animation') -dojo.lfx.Line = function(_3c6, end) { - this.start = _3c6 - this.end = end - if (dojo.lang.isArray(_3c6)) { - var diff = [] - dojo.lang.forEach(this.start, function(s, i) { - diff[i] = this.end[i] - s - }, this) - this.getValue = function(n) { - var res = [] - dojo.lang.forEach(this.start, function(s, i) { - res[i] = (diff[i] * n) + s - }, this) - return res - } - } else { - var diff = end - _3c6 - this.getValue = function(n) { - return (diff * n) + this.start - } - } -} + white: [255, 255, 255], + black: [0, 0, 0], + red: [255, 0, 0], + green: [0, 255, 0], + lime: [0, 255, 0], + blue: [0, 0, 255], + navy: [0, 0, 128], + gray: [128, 128, 128], + silver: [192, 192, 192] +}; +dojo.gfx.color.blend = function (a, b, _3b1) { + if (typeof a == 'string') { + return dojo.gfx.color.blendHex(a, b, _3b1) + } + if (!_3b1) { + _3b1 = 0 + } + _3b1 = Math.min(Math.max(-1, _3b1), 1); + _3b1 = ((_3b1 + 1) / 2); + var c = []; + for (var x = 0; x < 3; x++) { + c[x] = parseInt(b[x] + ((a[x] - b[x]) * _3b1)) + } + return c +}; +dojo.gfx.color.blendHex = function (a, b, _3b6) { + return dojo.gfx.color.rgb2hex(dojo.gfx.color.blend(dojo.gfx.color.hex2rgb(a), dojo.gfx.color.hex2rgb(b), _3b6)) +}; +dojo.gfx.color.extractRGB = function (_3b7) { + var hex = '0123456789abcdef'; + _3b7 = _3b7.toLowerCase(); + if (_3b7.indexOf('rgb') == 0) { + var _3b9 = _3b7.match(/rgba*\((\d+), *(\d+), *(\d+)/i); + var ret = _3b9.splice(1, 3); + return ret + } else { + var _3bb = dojo.gfx.color.hex2rgb(_3b7); + if (_3bb) { + return _3bb + } else { + return dojo.gfx.color.named[_3b7] || [255, 255, 255] + } + } +}; +dojo.gfx.color.hex2rgb = function (hex) { + var _3bd = '0123456789ABCDEF'; + var rgb = new Array(3); + if (hex.indexOf('#') == 0) { + hex = hex.substring(1) + } + hex = hex.toUpperCase(); + if (hex.replace(new RegExp('[' + _3bd + ']', 'g'), '') != '') { + return null + } + if (hex.length == 3) { + rgb[0] = hex.charAt(0) + hex.charAt(0); + rgb[1] = hex.charAt(1) + hex.charAt(1); + rgb[2] = hex.charAt(2) + hex.charAt(2) + } else { + rgb[0] = hex.substring(0, 2); + rgb[1] = hex.substring(2, 4); + rgb[2] = hex.substring(4) + } + for (var i = 0; i < rgb.length; i++) { + rgb[i] = _3bd.indexOf(rgb[i].charAt(0)) * 16 + _3bd.indexOf(rgb[i].charAt(1)) + } + return rgb +}; +dojo.gfx.color.rgb2hex = function (r, g, b) { + if (dojo.lang.isArray(r)) { + g = r[1] || 0; + b = r[2] || 0; + r = r[0] || 0 + } + var ret = dojo.lang.map([r, g, b], function (x) { + x = Number(x); + var s = x.toString(16); + while (s.length < 2) { + s = '0' + s + } + return s + }); + ret.unshift('#'); + return ret.join('') +}; +dojo.provide('dojo.lfx.Animation'); +dojo.lfx.Line = function (_3c6, end) { + this.start = _3c6; + this.end = end; + if (dojo.lang.isArray(_3c6)) { + var diff = []; + dojo.lang.forEach(this.start, function (s, i) { + diff[i] = this.end[i] - s + }, this); + this.getValue = function (n) { + var res = []; + dojo.lang.forEach(this.start, function (s, i) { + res[i] = (diff[i] * n) + s + }, this); + return res + } + } else { + var diff = end - _3c6; + this.getValue = function (n) { + return (diff * n) + this.start + } + } +}; if ((dojo.render.html.khtml) && (!dojo.render.html.safari)) { - dojo.lfx.easeDefault = function(n) { - return (parseFloat('0.5') + ((Math.sin((n + parseFloat('1.5')) * Math.PI)) / 2)) - } + dojo.lfx.easeDefault = function (n) { + return (parseFloat('0.5') + ((Math.sin((n + parseFloat('1.5')) * Math.PI)) / 2)) + } } else { - dojo.lfx.easeDefault = function(n) { - return (0.5 + ((Math.sin((n + 1.5) * Math.PI)) / 2)) - } -} -dojo.lfx.easeIn = function(n) { - return Math.pow(n, 3) -} -dojo.lfx.easeOut = function(n) { - return (1 - Math.pow(1 - n, 3)) -} -dojo.lfx.easeInOut = function(n) { - return ((3 * Math.pow(n, 2)) - (2 * Math.pow(n, 3))) -} -dojo.lfx.IAnimation = function() { + dojo.lfx.easeDefault = function (n) { + return (0.5 + ((Math.sin((n + 1.5) * Math.PI)) / 2)) + } } +dojo.lfx.easeIn = function (n) { + return Math.pow(n, 3) +}; +dojo.lfx.easeOut = function (n) { + return (1 - Math.pow(1 - n, 3)) +}; +dojo.lfx.easeInOut = function (n) { + return ((3 * Math.pow(n, 2)) - (2 * Math.pow(n, 3))) +}; +dojo.lfx.IAnimation = function () { +}; dojo.lang.extend(dojo.lfx.IAnimation, { - curve: null, - duration: 1000, - easing: null, - repeatCount: 0, - rate: 10, - handler: null, - beforeBegin: null, - onBegin: null, - onAnimate: null, - onEnd: null, - onPlay: null, - onPause: null, - onStop: null, - play: null, - pause: null, - stop: null, - connect: function(evt, _3d6, _3d7) { - if (!_3d7) { - _3d7 = _3d6 - _3d6 = this - } - _3d7 = dojo.lang.hitch(_3d6, _3d7) - var _3d8 = this[evt] || function() { - } - this[evt] = function() { - var ret = _3d8.apply(this, arguments) - _3d7.apply(this, arguments) - return ret - } - return this - }, - fire: function(evt, args) { - if (this[evt]) { - this[evt].apply(this, (args || [])) - } - return this - }, - repeat: function(_3dc) { - this.repeatCount = _3dc - return this - }, - _active: false, - _paused: false -}) -dojo.lfx.Animation = function(_3dd, _3de, _3df, _3e0, _3e1, rate) { - dojo.lfx.IAnimation.call(this) - if (dojo.lang.isNumber(_3dd) || (!_3dd && _3de.getValue)) { - rate = _3e1 - _3e1 = _3e0 - _3e0 = _3df - _3df = _3de - _3de = _3dd - _3dd = null - } else { - if (_3dd.getValue || dojo.lang.isArray(_3dd)) { - rate = _3e0 - _3e1 = _3df - _3e0 = _3de - _3df = _3dd - _3de = null - _3dd = null - } - } - if (dojo.lang.isArray(_3df)) { - this.curve = new dojo.lfx.Line(_3df[0], _3df[1]) - } else { - this.curve = _3df - } - if (_3de != null && _3de > 0) { - this.duration = _3de - } - if (_3e1) { - this.repeatCount = _3e1 - } - if (rate) { - this.rate = rate - } - if (_3dd) { - dojo.lang.forEach(['handler', 'beforeBegin', 'onBegin', 'onEnd', 'onPlay', 'onStop', 'onAnimate'], function(item) { - if (_3dd[item]) { - this.connect(item, _3dd[item]) - } - }, this) - } - if (_3e0 && dojo.lang.isFunction(_3e0)) { - this.easing = _3e0 - } -} -dojo.inherits(dojo.lfx.Animation, dojo.lfx.IAnimation) + curve: null, + duration: 1000, + easing: null, + repeatCount: 0, + rate: 10, + handler: null, + beforeBegin: null, + onBegin: null, + onAnimate: null, + onEnd: null, + onPlay: null, + onPause: null, + onStop: null, + play: null, + pause: null, + stop: null, + connect: function (evt, _3d6, _3d7) { + if (!_3d7) { + _3d7 = _3d6; + _3d6 = this + } + _3d7 = dojo.lang.hitch(_3d6, _3d7); + var _3d8 = this[evt] || function () { + }; + this[evt] = function () { + var ret = _3d8.apply(this, arguments); + _3d7.apply(this, arguments); + return ret + }; + return this + }, + fire: function (evt, args) { + if (this[evt]) { + this[evt].apply(this, (args || [])) + } + return this + }, + repeat: function (_3dc) { + this.repeatCount = _3dc; + return this + }, + _active: false, + _paused: false +}); +dojo.lfx.Animation = function (_3dd, _3de, _3df, _3e0, _3e1, rate) { + dojo.lfx.IAnimation.call(this); + if (dojo.lang.isNumber(_3dd) || (!_3dd && _3de.getValue)) { + rate = _3e1; + _3e1 = _3e0; + _3e0 = _3df; + _3df = _3de; + _3de = _3dd; + _3dd = null + } else { + if (_3dd.getValue || dojo.lang.isArray(_3dd)) { + rate = _3e0; + _3e1 = _3df; + _3e0 = _3de; + _3df = _3dd; + _3de = null; + _3dd = null + } + } + if (dojo.lang.isArray(_3df)) { + this.curve = new dojo.lfx.Line(_3df[0], _3df[1]) + } else { + this.curve = _3df + } + if (_3de != null && _3de > 0) { + this.duration = _3de + } + if (_3e1) { + this.repeatCount = _3e1 + } + if (rate) { + this.rate = rate + } + if (_3dd) { + dojo.lang.forEach(['handler', 'beforeBegin', 'onBegin', 'onEnd', 'onPlay', 'onStop', 'onAnimate'], function (item) { + if (_3dd[item]) { + this.connect(item, _3dd[item]) + } + }, this) + } + if (_3e0 && dojo.lang.isFunction(_3e0)) { + this.easing = _3e0 + } +}; +dojo.inherits(dojo.lfx.Animation, dojo.lfx.IAnimation); dojo.lang.extend(dojo.lfx.Animation, { - _startTime: null, _endTime: null, _timer: null, _percent: 0, _startRepeatCount: 0, play: function(_3e4, _3e5) { - if (_3e5) { - clearTimeout(this._timer) - this._active = false - this._paused = false - this._percent = 0 - } else { - if (this._active && !this._paused) { - return this - } - } - this.fire('handler', ['beforeBegin']) - this.fire('beforeBegin') - if (_3e4 > 0) { - setTimeout(dojo.lang.hitch(this, function() { - this.play(null, _3e5) - }), _3e4) - return this - } - this._startTime = new Date().valueOf() - if (this._paused) { - this._startTime -= (this.duration * this._percent / 100) - } - this._endTime = this._startTime + this.duration - this._active = true - this._paused = false - var step = this._percent / 100 - var _3e7 = this.curve.getValue(step) - if (this._percent == 0) { - if (!this._startRepeatCount) { - this._startRepeatCount = this.repeatCount - } - this.fire('handler', ['begin', _3e7]) - this.fire('onBegin', [_3e7]) - } - this.fire('handler', ['play', _3e7]) - this.fire('onPlay', [_3e7]) - this._cycle() - return this - }, pause: function() { - clearTimeout(this._timer) - if (!this._active) { - return this - } - this._paused = true - var _3e8 = this.curve.getValue(this._percent / 100) - this.fire('handler', ['pause', _3e8]) - this.fire('onPause', [_3e8]) - return this - }, gotoPercent: function(pct, _3ea) { - clearTimeout(this._timer) - this._active = true - this._paused = true - this._percent = pct - if (_3ea) { - this.play() - } - return this - }, stop: function(_3eb) { - clearTimeout(this._timer) - var step = this._percent / 100 - if (_3eb) { - step = 1 - } - var _3ed = this.curve.getValue(step) - this.fire('handler', ['stop', _3ed]) - this.fire('onStop', [_3ed]) - this._active = false - this._paused = false - return this - }, status: function() { - if (this._active) { - return this._paused ? 'paused' : 'playing' - } else { - return 'stopped' - } - return this - }, _cycle: function() { - clearTimeout(this._timer) - if (this._active) { - var curr = new Date().valueOf() - var step = (curr - this._startTime) / (this._endTime - this._startTime) - if (step >= 1) { - step = 1 - this._percent = 100 - } else { - this._percent = step * 100 - } - if ((this.easing) && (dojo.lang.isFunction(this.easing))) { - step = this.easing(step) - } - var _3f0 = this.curve.getValue(step) - this.fire('handler', ['animate', _3f0]) - this.fire('onAnimate', [_3f0]) - if (step < 1) { - this._timer = setTimeout(dojo.lang.hitch(this, '_cycle'), this.rate) - } else { - this._active = false - this.fire('handler', ['end']) - this.fire('onEnd') - if (this.repeatCount > 0) { - this.repeatCount-- - this.play(null, true) - } else { - if (this.repeatCount == -1) { - this.play(null, true) - } else { - if (this._startRepeatCount) { - this.repeatCount = this._startRepeatCount - this._startRepeatCount = 0 - } - } - } - } - } - return this - } -}) -dojo.lfx.Combine = function(_3f1) { - dojo.lfx.IAnimation.call(this) - this._anims = [] - this._animsEnded = 0 - var _3f2 = arguments - if (_3f2.length == 1 && (dojo.lang.isArray(_3f2[0]) || dojo.lang.isArrayLike(_3f2[0]))) { - _3f2 = _3f2[0] - } - dojo.lang.forEach(_3f2, function(anim) { - this._anims.push(anim) - anim.connect('onEnd', dojo.lang.hitch(this, '_onAnimsEnded')) - }, this) -} -dojo.inherits(dojo.lfx.Combine, dojo.lfx.IAnimation) + _startTime: null, _endTime: null, _timer: null, _percent: 0, _startRepeatCount: 0, play: function (_3e4, _3e5) { + if (_3e5) { + clearTimeout(this._timer); + this._active = false; + this._paused = false; + this._percent = 0 + } else { + if (this._active && !this._paused) { + return this + } + } + this.fire('handler', ['beforeBegin']); + this.fire('beforeBegin'); + if (_3e4 > 0) { + setTimeout(dojo.lang.hitch(this, function () { + this.play(null, _3e5) + }), _3e4); + return this + } + this._startTime = new Date().valueOf(); + if (this._paused) { + this._startTime -= (this.duration * this._percent / 100) + } + this._endTime = this._startTime + this.duration; + this._active = true; + this._paused = false; + var step = this._percent / 100; + var _3e7 = this.curve.getValue(step); + if (this._percent == 0) { + if (!this._startRepeatCount) { + this._startRepeatCount = this.repeatCount + } + this.fire('handler', ['begin', _3e7]); + this.fire('onBegin', [_3e7]) + } + this.fire('handler', ['play', _3e7]); + this.fire('onPlay', [_3e7]); + this._cycle(); + return this + }, pause: function () { + clearTimeout(this._timer); + if (!this._active) { + return this + } + this._paused = true; + var _3e8 = this.curve.getValue(this._percent / 100); + this.fire('handler', ['pause', _3e8]); + this.fire('onPause', [_3e8]); + return this + }, gotoPercent: function (pct, _3ea) { + clearTimeout(this._timer); + this._active = true; + this._paused = true; + this._percent = pct; + if (_3ea) { + this.play() + } + return this + }, stop: function (_3eb) { + clearTimeout(this._timer); + var step = this._percent / 100; + if (_3eb) { + step = 1 + } + var _3ed = this.curve.getValue(step); + this.fire('handler', ['stop', _3ed]); + this.fire('onStop', [_3ed]); + this._active = false; + this._paused = false; + return this + }, status: function () { + if (this._active) { + return this._paused ? 'paused' : 'playing' + } else { + return 'stopped' + } + return this + }, _cycle: function () { + clearTimeout(this._timer); + if (this._active) { + var curr = new Date().valueOf(); + var step = (curr - this._startTime) / (this._endTime - this._startTime); + if (step >= 1) { + step = 1; + this._percent = 100 + } else { + this._percent = step * 100 + } + if ((this.easing) && (dojo.lang.isFunction(this.easing))) { + step = this.easing(step) + } + var _3f0 = this.curve.getValue(step); + this.fire('handler', ['animate', _3f0]); + this.fire('onAnimate', [_3f0]); + if (step < 1) { + this._timer = setTimeout(dojo.lang.hitch(this, '_cycle'), this.rate) + } else { + this._active = false; + this.fire('handler', ['end']); + this.fire('onEnd'); + if (this.repeatCount > 0) { + this.repeatCount--; + this.play(null, true) + } else { + if (this.repeatCount == -1) { + this.play(null, true) + } else { + if (this._startRepeatCount) { + this.repeatCount = this._startRepeatCount; + this._startRepeatCount = 0 + } + } + } + } + } + return this + } +}); +dojo.lfx.Combine = function (_3f1) { + dojo.lfx.IAnimation.call(this); + this._anims = []; + this._animsEnded = 0; + var _3f2 = arguments; + if (_3f2.length == 1 && (dojo.lang.isArray(_3f2[0]) || dojo.lang.isArrayLike(_3f2[0]))) { + _3f2 = _3f2[0] + } + dojo.lang.forEach(_3f2, function (anim) { + this._anims.push(anim); + anim.connect('onEnd', dojo.lang.hitch(this, '_onAnimsEnded')) + }, this) +}; +dojo.inherits(dojo.lfx.Combine, dojo.lfx.IAnimation); dojo.lang.extend(dojo.lfx.Combine, { - _animsEnded: 0, play: function(_3f4, _3f5) { - if (!this._anims.length) { - return this - } - this.fire('beforeBegin') - if (_3f4 > 0) { - setTimeout(dojo.lang.hitch(this, function() { - this.play(null, _3f5) - }), _3f4) - return this - } - if (_3f5 || this._anims[0].percent == 0) { - this.fire('onBegin') - } - this.fire('onPlay') - this._animsCall('play', null, _3f5) - return this - }, pause: function() { - this.fire('onPause') - this._animsCall('pause') - return this - }, stop: function(_3f6) { - this.fire('onStop') - this._animsCall('stop', _3f6) - return this - }, _onAnimsEnded: function() { - this._animsEnded++ - if (this._animsEnded >= this._anims.length) { - this.fire('onEnd') - } - return this - }, _animsCall: function(_3f7) { - var args = [] - if (arguments.length > 1) { - for (var i = 1; i < arguments.length; i++) { - args.push(arguments[i]) - } - } - var _3fa = this - dojo.lang.forEach(this._anims, function(anim) { - anim[_3f7](args) - }, _3fa) - return this - } -}) -dojo.lfx.Chain = function(_3fc) { - dojo.lfx.IAnimation.call(this) - this._anims = [] - this._currAnim = -1 - var _3fd = arguments - if (_3fd.length == 1 && (dojo.lang.isArray(_3fd[0]) || dojo.lang.isArrayLike(_3fd[0]))) { - _3fd = _3fd[0] - } - var _3fe = this - dojo.lang.forEach(_3fd, function(anim, i, _401) { - this._anims.push(anim) - if (i < _401.length - 1) { - anim.connect('onEnd', dojo.lang.hitch(this, '_playNext')) - } else { - anim.connect('onEnd', dojo.lang.hitch(this, function() { - this.fire('onEnd') - })) - } - }, this) -} -dojo.inherits(dojo.lfx.Chain, dojo.lfx.IAnimation) + _animsEnded: 0, play: function (_3f4, _3f5) { + if (!this._anims.length) { + return this + } + this.fire('beforeBegin'); + if (_3f4 > 0) { + setTimeout(dojo.lang.hitch(this, function () { + this.play(null, _3f5) + }), _3f4); + return this + } + if (_3f5 || this._anims[0].percent == 0) { + this.fire('onBegin') + } + this.fire('onPlay'); + this._animsCall('play', null, _3f5); + return this + }, pause: function () { + this.fire('onPause'); + this._animsCall('pause'); + return this + }, stop: function (_3f6) { + this.fire('onStop'); + this._animsCall('stop', _3f6); + return this + }, _onAnimsEnded: function () { + this._animsEnded++; + if (this._animsEnded >= this._anims.length) { + this.fire('onEnd') + } + return this + }, _animsCall: function (_3f7) { + var args = []; + if (arguments.length > 1) { + for (var i = 1; i < arguments.length; i++) { + args.push(arguments[i]) + } + } + var _3fa = this; + dojo.lang.forEach(this._anims, function (anim) { + anim[_3f7](args) + }, _3fa); + return this + } +}); +dojo.lfx.Chain = function (_3fc) { + dojo.lfx.IAnimation.call(this); + this._anims = []; + this._currAnim = -1; + var _3fd = arguments; + if (_3fd.length == 1 && (dojo.lang.isArray(_3fd[0]) || dojo.lang.isArrayLike(_3fd[0]))) { + _3fd = _3fd[0] + } + var _3fe = this; + dojo.lang.forEach(_3fd, function (anim, i, _401) { + this._anims.push(anim); + if (i < _401.length - 1) { + anim.connect('onEnd', dojo.lang.hitch(this, '_playNext')) + } else { + anim.connect('onEnd', dojo.lang.hitch(this, function () { + this.fire('onEnd') + })) + } + }, this) +}; +dojo.inherits(dojo.lfx.Chain, dojo.lfx.IAnimation); dojo.lang.extend(dojo.lfx.Chain, { - _currAnim: -1, play: function(_402, _403) { - if (!this._anims.length) { - return this - } - if (_403 || !this._anims[this._currAnim]) { - this._currAnim = 0 - } - var _404 = this._anims[this._currAnim] - this.fire('beforeBegin') - if (_402 > 0) { - setTimeout(dojo.lang.hitch(this, function() { - this.play(null, _403) - }), _402) - return this - } - if (_404) { - if (this._currAnim == 0) { - this.fire('handler', ['begin', this._currAnim]) - this.fire('onBegin', [this._currAnim]) - } - this.fire('onPlay', [this._currAnim]) - _404.play(null, _403) - } - return this - }, pause: function() { - if (this._anims[this._currAnim]) { - this._anims[this._currAnim].pause() - this.fire('onPause', [this._currAnim]) - } - return this - }, playPause: function() { - if (this._anims.length == 0) { - return this - } - if (this._currAnim == -1) { - this._currAnim = 0 - } - var _405 = this._anims[this._currAnim] - if (_405) { - if (!_405._active || _405._paused) { - this.play() - } else { - this.pause() - } - } - return this - }, stop: function() { - var _406 = this._anims[this._currAnim] - if (_406) { - _406.stop() - this.fire('onStop', [this._currAnim]) - } - return _406 - }, _playNext: function() { - if (this._currAnim == -1 || this._anims.length == 0) { - return this - } - this._currAnim++ - if (this._anims[this._currAnim]) { - this._anims[this._currAnim].play(null, true) - } - return this - } -}) -dojo.lfx.combine = function(_407) { - var _408 = arguments - if (dojo.lang.isArray(arguments[0])) { - _408 = arguments[0] - } - if (_408.length == 1) { - return _408[0] - } - return new dojo.lfx.Combine(_408) -} -dojo.lfx.chain = function(_409) { - var _40a = arguments - if (dojo.lang.isArray(arguments[0])) { - _40a = arguments[0] - } - if (_40a.length == 1) { - return _40a[0] - } - return new dojo.lfx.Chain(_40a) -} -dojo.provide('dojo.html.common') -dojo.lang.mixin(dojo.html, dojo.dom) -dojo.html.body = function() { - dojo.deprecated('dojo.html.body() moved to dojo.body()', '0.5') - return dojo.body() -} -dojo.html.getEventTarget = function(evt) { - if (!evt) { - evt = dojo.global().event || {} - } - var t = (evt.srcElement ? evt.srcElement : (evt.target ? evt.target : null)) - while ((t) && (t.nodeType != 1)) { - t = t.parentNode - } - return t -} -dojo.html.getViewport = function() { - var _40d = dojo.global() - var _40e = dojo.doc() - var w = 0 - var h = 0 - if (dojo.render.html.mozilla) { - w = _40e.documentElement.clientWidth - h = _40d.innerHeight - } else { - if (!dojo.render.html.opera && _40d.innerWidth) { - w = _40d.innerWidth - h = _40d.innerHeight - } else { - if (!dojo.render.html.opera && dojo.exists(_40e, 'documentElement.clientWidth')) { - var w2 = _40e.documentElement.clientWidth - if (!w || w2 && w2 < w) { - w = w2 - } - h = _40e.documentElement.clientHeight - } else { - if (dojo.body().clientWidth) { - w = dojo.body().clientWidth - h = dojo.body().clientHeight - } - } - } - } - return { width: w, height: h } -} -dojo.html.getScroll = function() { - var _412 = dojo.global() - var _413 = dojo.doc() - var top = _412.pageYOffset || _413.documentElement.scrollTop || dojo.body().scrollTop || 0 - var left = _412.pageXOffset || _413.documentElement.scrollLeft || dojo.body().scrollLeft || 0 - return { top: top, left: left, offset: { x: left, y: top } } -} -dojo.html.getParentByType = function(node, type) { - var _418 = dojo.doc() - var _419 = dojo.byId(node) - type = type.toLowerCase() - while ((_419) && (_419.nodeName.toLowerCase() != type)) { - if (_419 == (_418['body'] || _418['documentElement'])) { - return null - } - _419 = _419.parentNode - } - return _419 -} -dojo.html.getAttribute = function(node, attr) { - node = dojo.byId(node) - if ((!node) || (!node.getAttribute)) { - return null - } - var ta = typeof attr == 'string' ? attr : new String(attr) - var v = node.getAttribute(ta.toUpperCase()) - if ((v) && (typeof v == 'string') && (v != '')) { - return v - } - if (v && v.value) { - return v.value - } - if ((node.getAttributeNode) && (node.getAttributeNode(ta))) { - return (node.getAttributeNode(ta)).value - } else { - if (node.getAttribute(ta)) { - return node.getAttribute(ta) - } else { - if (node.getAttribute(ta.toLowerCase())) { - return node.getAttribute(ta.toLowerCase()) - } - } - } - return null -} -dojo.html.hasAttribute = function(node, attr) { - return dojo.html.getAttribute(dojo.byId(node), attr) ? true : false -} -dojo.html.getCursorPosition = function(e) { - e = e || dojo.global().event - var _421 = { x: 0, y: 0 } - if (e.pageX || e.pageY) { - _421.x = e.pageX - _421.y = e.pageY - } else { - var de = dojo.doc().documentElement - var db = dojo.body() - _421.x = e.clientX + ((de || db)['scrollLeft']) - ((de || db)['clientLeft']) - _421.y = e.clientY + ((de || db)['scrollTop']) - ((de || db)['clientTop']) - } - return _421 -} -dojo.html.isTag = function(node) { - node = dojo.byId(node) - if (node && node.tagName) { - for (var i = 1; i < arguments.length; i++) { - if (node.tagName.toLowerCase() == String(arguments[i]).toLowerCase()) { - return String(arguments[i]).toLowerCase() - } - } - } - return '' -} + _currAnim: -1, play: function (_402, _403) { + if (!this._anims.length) { + return this + } + if (_403 || !this._anims[this._currAnim]) { + this._currAnim = 0 + } + var _404 = this._anims[this._currAnim]; + this.fire('beforeBegin'); + if (_402 > 0) { + setTimeout(dojo.lang.hitch(this, function () { + this.play(null, _403) + }), _402); + return this + } + if (_404) { + if (this._currAnim == 0) { + this.fire('handler', ['begin', this._currAnim]); + this.fire('onBegin', [this._currAnim]) + } + this.fire('onPlay', [this._currAnim]); + _404.play(null, _403) + } + return this + }, pause: function () { + if (this._anims[this._currAnim]) { + this._anims[this._currAnim].pause(); + this.fire('onPause', [this._currAnim]) + } + return this + }, playPause: function () { + if (this._anims.length == 0) { + return this + } + if (this._currAnim == -1) { + this._currAnim = 0 + } + var _405 = this._anims[this._currAnim]; + if (_405) { + if (!_405._active || _405._paused) { + this.play() + } else { + this.pause() + } + } + return this + }, stop: function () { + var _406 = this._anims[this._currAnim]; + if (_406) { + _406.stop(); + this.fire('onStop', [this._currAnim]) + } + return _406 + }, _playNext: function () { + if (this._currAnim == -1 || this._anims.length == 0) { + return this + } + this._currAnim++; + if (this._anims[this._currAnim]) { + this._anims[this._currAnim].play(null, true) + } + return this + } +}); +dojo.lfx.combine = function (_407) { + var _408 = arguments; + if (dojo.lang.isArray(arguments[0])) { + _408 = arguments[0] + } + if (_408.length == 1) { + return _408[0] + } + return new dojo.lfx.Combine(_408) +}; +dojo.lfx.chain = function (_409) { + var _40a = arguments; + if (dojo.lang.isArray(arguments[0])) { + _40a = arguments[0] + } + if (_40a.length == 1) { + return _40a[0] + } + return new dojo.lfx.Chain(_40a) +}; +dojo.provide('dojo.html.common'); +dojo.lang.mixin(dojo.html, dojo.dom); +dojo.html.body = function () { + dojo.deprecated('dojo.html.body() moved to dojo.body()', '0.5'); + return dojo.body() +}; +dojo.html.getEventTarget = function (evt) { + if (!evt) { + evt = dojo.global().event || {} + } + var t = (evt.srcElement ? evt.srcElement : (evt.target ? evt.target : null)); + while ((t) && (t.nodeType != 1)) { + t = t.parentNode + } + return t +}; +dojo.html.getViewport = function () { + var _40d = dojo.global(); + var _40e = dojo.doc(); + var w = 0; + var h = 0; + if (dojo.render.html.mozilla) { + w = _40e.documentElement.clientWidth; + h = _40d.innerHeight + } else { + if (!dojo.render.html.opera && _40d.innerWidth) { + w = _40d.innerWidth; + h = _40d.innerHeight + } else { + if (!dojo.render.html.opera && dojo.exists(_40e, 'documentElement.clientWidth')) { + var w2 = _40e.documentElement.clientWidth; + if (!w || w2 && w2 < w) { + w = w2 + } + h = _40e.documentElement.clientHeight + } else { + if (dojo.body().clientWidth) { + w = dojo.body().clientWidth; + h = dojo.body().clientHeight + } + } + } + } + return {width: w, height: h} +}; +dojo.html.getScroll = function () { + var _412 = dojo.global(); + var _413 = dojo.doc(); + var top = _412.pageYOffset || _413.documentElement.scrollTop || dojo.body().scrollTop || 0; + var left = _412.pageXOffset || _413.documentElement.scrollLeft || dojo.body().scrollLeft || 0; + return {top: top, left: left, offset: {x: left, y: top}} +}; +dojo.html.getParentByType = function (node, type) { + var _418 = dojo.doc(); + var _419 = dojo.byId(node); + type = type.toLowerCase(); + while ((_419) && (_419.nodeName.toLowerCase() != type)) { + if (_419 == (_418['body'] || _418['documentElement'])) { + return null + } + _419 = _419.parentNode + } + return _419 +}; +dojo.html.getAttribute = function (node, attr) { + node = dojo.byId(node); + if ((!node) || (!node.getAttribute)) { + return null + } + var ta = typeof attr == 'string' ? attr : String(attr); + var v = node.getAttribute(ta.toUpperCase()); + if ((v) && (typeof v == 'string') && (v != '')) { + return v + } + if (v && v.value) { + return v.value + } + if ((node.getAttributeNode) && (node.getAttributeNode(ta))) { + return (node.getAttributeNode(ta)).value + } else { + if (node.getAttribute(ta)) { + return node.getAttribute(ta) + } else { + if (node.getAttribute(ta.toLowerCase())) { + return node.getAttribute(ta.toLowerCase()) + } + } + } + return null +}; +dojo.html.hasAttribute = function (node, attr) { + return dojo.html.getAttribute(dojo.byId(node), attr) ? true : false +}; +dojo.html.getCursorPosition = function (e) { + e = e || dojo.global().event; + var _421 = {x: 0, y: 0}; + if (e.pageX || e.pageY) { + _421.x = e.pageX; + _421.y = e.pageY + } else { + var de = dojo.doc().documentElement; + var db = dojo.body(); + _421.x = e.clientX + ((de || db)['scrollLeft']) - ((de || db)['clientLeft']); + _421.y = e.clientY + ((de || db)['scrollTop']) - ((de || db)['clientTop']) + } + return _421 +}; +dojo.html.isTag = function (node) { + node = dojo.byId(node); + if (node && node.tagName) { + for (var i = 1; i < arguments.length; i++) { + if (node.tagName.toLowerCase() == String(arguments[i]).toLowerCase()) { + return String(arguments[i]).toLowerCase() + } + } + } + return '' +}; if (dojo.render.html.ie && !dojo.render.html.ie70) { - if (window.location.href.substr(0, 6).toLowerCase() != 'https:') { - (function() { - var _426 = dojo.doc().createElement('script') - _426.src = 'javascript:\'dojo.html.createExternalElement=function(doc, tag){ return doc.createElement(tag); }\'' - dojo.doc().getElementsByTagName('head')[0].appendChild(_426) - })() - } + if (window.location.href.substr(0, 6).toLowerCase() != 'https:') { + (function () { + var _426 = dojo.doc().createElement('script'); + _426.src = 'javascript:\'dojo.html.createExternalElement=function(doc, tag){ return doc.createElement(tag); }\''; + dojo.doc().getElementsByTagName('head')[0].appendChild(_426) + })() + } } else { - dojo.html.createExternalElement = function(doc, tag) { - return doc.createElement(tag) - } -} -dojo.html._callDeprecated = function(_429, _42a, args, _42c, _42d) { - dojo.deprecated('dojo.html.' + _429, 'replaced by dojo.html.' + _42a + '(' + (_42c ? 'node, {' + _42c + ': ' + _42c + '}' : '') + ')' + (_42d ? '.' + _42d : ''), '0.5') - var _42e = [] - if (_42c) { - var _42f = {} - _42f[_42c] = args[1] - _42e.push(args[0]) - _42e.push(_42f) - } else { - _42e = args - } - var ret = dojo.html[_42a].apply(dojo.html, args) - if (_42d) { - return ret[_42d] - } else { - return ret - } -} -dojo.html.getViewportWidth = function() { - return dojo.html._callDeprecated('getViewportWidth', 'getViewport', arguments, null, 'width') -} -dojo.html.getViewportHeight = function() { - return dojo.html._callDeprecated('getViewportHeight', 'getViewport', arguments, null, 'height') -} -dojo.html.getViewportSize = function() { - return dojo.html._callDeprecated('getViewportSize', 'getViewport', arguments) -} -dojo.html.getScrollTop = function() { - return dojo.html._callDeprecated('getScrollTop', 'getScroll', arguments, null, 'top') -} -dojo.html.getScrollLeft = function() { - return dojo.html._callDeprecated('getScrollLeft', 'getScroll', arguments, null, 'left') -} -dojo.html.getScrollOffset = function() { - return dojo.html._callDeprecated('getScrollOffset', 'getScroll', arguments, null, 'offset') -} -dojo.provide('dojo.uri.Uri') -dojo.uri = new function() { - this.dojoUri = function(uri) { - return new dojo.uri.Uri(dojo.hostenv.getBaseScriptUri(), uri) - } - this.moduleUri = function(_432, uri) { - var loc = dojo.hostenv.getModuleSymbols(_432).join('/') - if (!loc) { - return null - } - if (loc.lastIndexOf('/') != loc.length - 1) { - loc += '/' - } - var _435 = loc.indexOf(':') - var _436 = loc.indexOf('/') - if (loc.charAt(0) != '/' && (_435 == -1 || _435 > _436)) { - loc = dojo.hostenv.getBaseScriptUri() + loc - } - return new dojo.uri.Uri(loc, uri) - } - this.Uri = function() { - var uri = arguments[0] - for (var i = 1; i < arguments.length; i++) { - if (!arguments[i]) { - continue - } - var _439 = new dojo.uri.Uri(arguments[i].toString()) - var _43a = new dojo.uri.Uri(uri.toString()) - if ((_439.path == '') && (_439.scheme == null) && (_439.authority == null) && (_439.query == null)) { - if (_439.fragment != null) { - _43a.fragment = _439.fragment - } - _439 = _43a - } else { - if (_439.scheme == null) { - _439.scheme = _43a.scheme - if (_439.authority == null) { - _439.authority = _43a.authority - if (_439.path.charAt(0) != '/') { - var path = _43a.path.substring(0, _43a.path.lastIndexOf('/') + 1) + _439.path - var segs = path.split('/') - for (var j = 0; j < segs.length; j++) { - if (segs[j] == '.') { - if (j == segs.length - 1) { - segs[j] = '' - } else { - segs.splice(j, 1) - j-- - } - } else { - if (j > 0 && !(j == 1 && segs[0] == '') && segs[j] == '..' && segs[j - 1] != '..') { - if (j == segs.length - 1) { - segs.splice(j, 1) - segs[j - 1] = '' - } else { - segs.splice(j - 1, 2) - j -= 2 - } - } - } - } - _439.path = segs.join('/') - } - } - } - } - uri = '' - if (_439.scheme != null) { - uri += _439.scheme + ':' - } - if (_439.authority != null) { - uri += '//' + _439.authority - } - uri += _439.path - if (_439.query != null) { - uri += '?' + _439.query - } - if (_439.fragment != null) { - uri += '#' + _439.fragment - } - } - this.uri = uri.toString() - var _43e = '^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?$' - var r = this.uri.match(new RegExp(_43e)) - this.scheme = r[2] || (r[1] ? '' : null) - this.authority = r[4] || (r[3] ? '' : null) - this.path = r[5] - this.query = r[7] || (r[6] ? '' : null) - this.fragment = r[9] || (r[8] ? '' : null) - if (this.authority != null) { - _43e = '^((([^:]+:)?([^@]+))@)?([^:]*)(:([0-9]+))?$' - r = this.authority.match(new RegExp(_43e)) - this.user = r[3] || null - this.password = r[4] || null - this.host = r[5] - this.port = r[7] || null - } - this.toString = function() { - return this.uri - } - } -} -dojo.provide('dojo.html.style') -dojo.html.getClass = function(node) { - node = dojo.byId(node) - if (!node) { - return '' - } - var cs = '' - if (node.className) { - cs = node.className - } else { - if (dojo.html.hasAttribute(node, 'class')) { - cs = dojo.html.getAttribute(node, 'class') - } - } - return cs.replace(/^\s+|\s+$/g, '') -} -dojo.html.getClasses = function(node) { - var c = dojo.html.getClass(node) - return (c == '') ? [] : c.split(/\s+/g) -} -dojo.html.hasClass = function(node, _445) { - return (new RegExp('(^|\\s+)' + _445 + '(\\s+|$)')).test(dojo.html.getClass(node)) -} -dojo.html.prependClass = function(node, _447) { - _447 += ' ' + dojo.html.getClass(node) - return dojo.html.setClass(node, _447) -} -dojo.html.addClass = function(node, _449) { - if (dojo.html.hasClass(node, _449)) { - return false - } - _449 = (dojo.html.getClass(node) + ' ' + _449).replace(/^\s+|\s+$/g, '') - return dojo.html.setClass(node, _449) -} -dojo.html.setClass = function(node, _44b) { - node = dojo.byId(node) - var cs = new String(_44b) - try { - if (typeof node.className == 'string') { - node.className = cs - } else { - if (node.setAttribute) { - node.setAttribute('class', _44b) - node.className = cs - } else { - return false - } - } - } catch (e) { - dojo.debug('dojo.html.setClass() failed', e) - } - return true -} -dojo.html.removeClass = function(node, _44e, _44f) { - try { - if (!_44f) { - var _450 = dojo.html.getClass(node).replace(new RegExp('(^|\\s+)' + _44e + '(\\s+|$)'), '$1$2') - } else { - var _450 = dojo.html.getClass(node).replace(_44e, '') - } - dojo.html.setClass(node, _450) - } catch (e) { - dojo.debug('dojo.html.removeClass() failed', e) - } - return true -} -dojo.html.replaceClass = function(node, _452, _453) { - dojo.html.removeClass(node, _453) - dojo.html.addClass(node, _452) -} -dojo.html.classMatchType = { ContainsAll: 0, ContainsAny: 1, IsOnly: 2 } -dojo.html.getElementsByClass = function(_454, _455, _456, _457, _458) { - _458 = false - var _459 = dojo.doc() - _455 = dojo.byId(_455) || _459 - var _45a = _454.split(/\s+/g) - var _45b = [] - if (_457 != 1 && _457 != 2) { - _457 = 0 - } - var _45c = new RegExp('(\\s|^)((' + _45a.join(')|(') + '))(\\s|$)') - var _45d = _45a.join(' ').length - var _45e = [] - if (!_458 && _459.evaluate) { - var _45f = './/' + (_456 || '*') + '[contains(' - if (_457 != dojo.html.classMatchType.ContainsAny) { - _45f += 'concat(\' \',@class,\' \'), \' ' + _45a.join(' \') and contains(concat(\' \',@class,\' \'), \' ') + ' \')' - if (_457 == 2) { - _45f += ' and string-length(@class)=' + _45d + ']' - } else { - _45f += ']' - } - } else { - _45f += 'concat(\' \',@class,\' \'), \' ' + _45a.join(' \') or contains(concat(\' \',@class,\' \'), \' ') + ' \')]' - } - var _460 = _459.evaluate(_45f, _455, null, XPathResult.ANY_TYPE, null) - var _461 = _460.iterateNext() - while (_461) { - try { - _45e.push(_461) - _461 = _460.iterateNext() - } catch (e) { - break - } - } - return _45e - } else { - if (!_456) { - _456 = '*' - } - _45e = _455.getElementsByTagName(_456) - var node, i = 0 - outer: - while (node = _45e[i++]) { - var _464 = dojo.html.getClasses(node) - if (_464.length == 0) { - continue outer - } - var _465 = 0 - for (var j = 0; j < _464.length; j++) { - if (_45c.test(_464[j])) { - if (_457 == dojo.html.classMatchType.ContainsAny) { - _45b.push(node) - continue outer - } else { - _465++ - } - } else { - if (_457 == dojo.html.classMatchType.IsOnly) { - continue outer - } - } - } - if (_465 == _45a.length) { - if ((_457 == dojo.html.classMatchType.IsOnly) && (_465 == _464.length)) { - _45b.push(node) - } else { - if (_457 == dojo.html.classMatchType.ContainsAll) { - _45b.push(node) - } - } - } - } - return _45b - } -} -dojo.html.getElementsByClassName = dojo.html.getElementsByClass -dojo.html.toCamelCase = function(_467) { - var arr = _467.split('-'), cc = arr[0] - for (var i = 1; i < arr.length; i++) { - cc += arr[i].charAt(0).toUpperCase() + arr[i].substring(1) - } - return cc -} -dojo.html.toSelectorCase = function(_46b) { - return _46b.replace(/([A-Z])/g, '-$1').toLowerCase() -} + dojo.html.createExternalElement = function (doc, tag) { + return doc.createElement(tag) + } +} +dojo.html._callDeprecated = function (_429, _42a, args, _42c, _42d) { + dojo.deprecated('dojo.html.' + _429, 'replaced by dojo.html.' + _42a + '(' + (_42c ? 'node, {' + + _42c + + ': ' + + _42c + + '}' : '') + ')' + (_42d ? '.' + _42d : ''), '0.5'); + var _42e = []; + if (_42c) { + var _42f = {}; + _42f[_42c] = args[1]; + _42e.push(args[0]); + _42e.push(_42f) + } else { + _42e = args + } + var ret = dojo.html[_42a].apply(dojo.html, args); + if (_42d) { + return ret[_42d] + } else { + return ret + } +}; +dojo.html.getViewportWidth = function () { + return dojo.html._callDeprecated('getViewportWidth', 'getViewport', arguments, null, 'width') +}; +dojo.html.getViewportHeight = function () { + return dojo.html._callDeprecated('getViewportHeight', 'getViewport', arguments, null, 'height') +}; +dojo.html.getViewportSize = function () { + return dojo.html._callDeprecated('getViewportSize', 'getViewport', arguments) +}; +dojo.html.getScrollTop = function () { + return dojo.html._callDeprecated('getScrollTop', 'getScroll', arguments, null, 'top') +}; +dojo.html.getScrollLeft = function () { + return dojo.html._callDeprecated('getScrollLeft', 'getScroll', arguments, null, 'left') +}; +dojo.html.getScrollOffset = function () { + return dojo.html._callDeprecated('getScrollOffset', 'getScroll', arguments, null, 'offset') +}; +dojo.provide('dojo.uri.Uri'); +dojo.uri = new function () { + this.dojoUri = function (uri) { + return new dojo.uri.Uri(dojo.hostenv.getBaseScriptUri(), uri) + }; + this.moduleUri = function (_432, uri) { + var loc = dojo.hostenv.getModuleSymbols(_432).join('/'); + if (!loc) { + return null + } + if (loc.lastIndexOf('/') != loc.length - 1) { + loc += '/' + } + var _435 = loc.indexOf(':'); + var _436 = loc.indexOf('/'); + if (loc.charAt(0) != '/' && (_435 == -1 || _435 > _436)) { + loc = dojo.hostenv.getBaseScriptUri() + loc + } + return new dojo.uri.Uri(loc, uri) + }; + this.Uri = function () { + var uri = arguments[0]; + for (var i = 1; i < arguments.length; i++) { + if (!arguments[i]) { + continue + } + var _439 = new dojo.uri.Uri(arguments[i].toString()); + var _43a = new dojo.uri.Uri(uri.toString()); + if ((_439.path == '') && (_439.scheme == null) && (_439.authority == null) && (_439.query == null)) { + if (_439.fragment != null) { + _43a.fragment = _439.fragment + } + _439 = _43a + } else { + if (_439.scheme == null) { + _439.scheme = _43a.scheme; + if (_439.authority == null) { + _439.authority = _43a.authority; + if (_439.path.charAt(0) != '/') { + var path = _43a.path.substring(0, _43a.path.lastIndexOf('/') + 1) + _439.path; + var segs = path.split('/'); + for (var j = 0; j < segs.length; j++) { + if (segs[j] == '.') { + if (j == segs.length - 1) { + segs[j] = '' + } else { + segs.splice(j, 1); + j-- + } + } else { + if (j > 0 && !(j == 1 && segs[0] == '') && segs[j] == '..' && segs[j - 1] != '..') { + if (j == segs.length - 1) { + segs.splice(j, 1); + segs[j - 1] = '' + } else { + segs.splice(j - 1, 2); + j -= 2 + } + } + } + } + _439.path = segs.join('/') + } + } + } + } + uri = ''; + if (_439.scheme != null) { + uri += _439.scheme + ':' + } + if (_439.authority != null) { + uri += '//' + _439.authority + } + uri += _439.path; + if (_439.query != null) { + uri += '?' + _439.query + } + if (_439.fragment != null) { + uri += '#' + _439.fragment + } + } + this.uri = uri.toString(); + var _43e = '^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?$'; + var r = this.uri.match(new RegExp(_43e)); + this.scheme = r[2] || (r[1] ? '' : null); + this.authority = r[4] || (r[3] ? '' : null); + this.path = r[5]; + this.query = r[7] || (r[6] ? '' : null); + this.fragment = r[9] || (r[8] ? '' : null); + if (this.authority != null) { + _43e = '^((([^:]+:)?([^@]+))@)?([^:]*)(:([0-9]+))?$'; + r = this.authority.match(new RegExp(_43e)); + this.user = r[3] || null; + this.password = r[4] || null; + this.host = r[5]; + this.port = r[7] || null + } + this.toString = function () { + return this.uri + } + } +}; +;dojo.provide('dojo.html.style'); +dojo.html.getClass = function (node) { + node = dojo.byId(node); + if (!node) { + return '' + } + var cs = ''; + if (node.className) { + cs = node.className + } else { + if (dojo.html.hasAttribute(node, 'class')) { + cs = dojo.html.getAttribute(node, 'class') + } + } + return cs.replace(/^\s+|\s+$/g, '') +}; +dojo.html.getClasses = function (node) { + var c = dojo.html.getClass(node); + return (c == '') ? [] : c.split(/\s+/g) +}; +dojo.html.hasClass = function (node, _445) { + return (new RegExp('(^|\\s+)' + _445 + '(\\s+|$)')).test(dojo.html.getClass(node)) +}; +dojo.html.prependClass = function (node, _447) { + _447 += ' ' + dojo.html.getClass(node); + return dojo.html.setClass(node, _447) +}; +dojo.html.addClass = function (node, _449) { + if (dojo.html.hasClass(node, _449)) { + return false + } + _449 = (dojo.html.getClass(node) + ' ' + _449).replace(/^\s+|\s+$/g, ''); + return dojo.html.setClass(node, _449) +}; +dojo.html.setClass = function (node, _44b) { + node = dojo.byId(node); + var cs = String(_44b); + try { + if (typeof node.className == 'string') { + node.className = cs + } else { + if (node.setAttribute) { + node.setAttribute('class', _44b); + node.className = cs + } else { + return false + } + } + } catch (e) { + dojo.debug('dojo.html.setClass() failed', e) + } + return true +}; +dojo.html.removeClass = function (node, _44e, _44f) { + try { + if (!_44f) { + var _450 = dojo.html.getClass(node).replace(new RegExp('(^|\\s+)' + _44e + '(\\s+|$)'), '$1$2') + } else { + var _450 = dojo.html.getClass(node).replace(_44e, '') + } + dojo.html.setClass(node, _450) + } catch (e) { + dojo.debug('dojo.html.removeClass() failed', e) + } + return true +}; +dojo.html.replaceClass = function (node, _452, _453) { + dojo.html.removeClass(node, _453); + dojo.html.addClass(node, _452) +}; +dojo.html.classMatchType = {ContainsAll: 0, ContainsAny: 1, IsOnly: 2}; +dojo.html.getElementsByClass = function (_454, _455, _456, _457, _458) { + _458 = false; + var _459 = dojo.doc(); + _455 = dojo.byId(_455) || _459; + var _45a = _454.split(/\s+/g); + var _45b = []; + if (_457 != 1 && _457 != 2) { + _457 = 0 + } + var _45c = new RegExp('(\\s|^)((' + _45a.join(')|(') + '))(\\s|$)'); + var _45d = _45a.join(' ').length; + var _45e = []; + if (!_458 && _459.evaluate) { + var _45f = './/' + (_456 || '*') + '[contains('; + if (_457 != dojo.html.classMatchType.ContainsAny) { + _45f += 'concat(\' \',@class,\' \'), \' ' + + _45a.join(' \') and contains(concat(\' \',@class,\' \'), \' ') + + ' \')'; + if (_457 == 2) { + _45f += ' and string-length(@class)=' + _45d + ']' + } else { + _45f += ']' + } + } else { + _45f += 'concat(\' \',@class,\' \'), \' ' + + _45a.join(' \') or contains(concat(\' \',@class,\' \'), \' ') + + ' \')]' + } + var _460 = _459.evaluate(_45f, _455, null, XPathResult.ANY_TYPE, null); + var _461 = _460.iterateNext(); + while (_461) { + try { + _45e.push(_461); + _461 = _460.iterateNext() + } catch (e) { + break + } + } + return _45e + } else { + if (!_456) { + _456 = '*' + } + _45e = _455.getElementsByTagName(_456); + var node, i = 0; + outer: + while (node = _45e[i++]) { + var _464 = dojo.html.getClasses(node); + if (_464.length == 0) { + continue; + } + var _465 = 0; + for (var j = 0; j < _464.length; j++) { + if (_45c.test(_464[j])) { + if (_457 == dojo.html.classMatchType.ContainsAny) { + _45b.push(node); + continue outer + } else { + _465++ + } + } else { + if (_457 == dojo.html.classMatchType.IsOnly) { + continue outer + } + } + } + if (_465 == _45a.length) { + if ((_457 == dojo.html.classMatchType.IsOnly) && (_465 == _464.length)) { + _45b.push(node) + } else { + if (_457 == dojo.html.classMatchType.ContainsAll) { + _45b.push(node) + } + } + } + } + return _45b + } +}; +dojo.html.getElementsByClassName = dojo.html.getElementsByClass; +dojo.html.toCamelCase = function (_467) { + var arr = _467.split('-'), cc = arr[0]; + for (var i = 1; i < arr.length; i++) { + cc += arr[i].charAt(0).toUpperCase() + arr[i].substring(1) + } + return cc +}; +dojo.html.toSelectorCase = function (_46b) { + return _46b.replace(/([A-Z])/g, '-$1').toLowerCase() +}; if (dojo.render.html.ie) { - dojo.html.getComputedStyle = function(node, _46d, _46e) { - node = dojo.byId(node) - if (!node || !node.currentStyle) { - return _46e - } - return node.currentStyle[dojo.html.toCamelCase(_46d)] - } - dojo.html.getComputedStyles = function(node) { - return node.currentStyle - } + dojo.html.getComputedStyle = function (node, _46d, _46e) { + node = dojo.byId(node); + if (!node || !node.currentStyle) { + return _46e + } + return node.currentStyle[dojo.html.toCamelCase(_46d)] + }; + dojo.html.getComputedStyles = function (node) { + return node.currentStyle + } } else { - dojo.html.getComputedStyle = function(node, _471, _472) { - node = dojo.byId(node) - if (!node || !node.style) { - return _472 - } - var s = document.defaultView.getComputedStyle(node, null) - return (s && s[dojo.html.toCamelCase(_471)]) || '' - } - dojo.html.getComputedStyles = function(node) { - return document.defaultView.getComputedStyle(node, null) - } -} -dojo.html.getStyleProperty = function(node, _476) { - node = dojo.byId(node) - return (node && node.style ? node.style[dojo.html.toCamelCase(_476)] : undefined) -} -dojo.html.getStyle = function(node, _478) { - var _479 = dojo.html.getStyleProperty(node, _478) - return (_479 ? _479 : dojo.html.getComputedStyle(node, _478)) -} -dojo.html.setStyle = function(node, _47b, _47c) { - node = dojo.byId(node) - if (node && node.style) { - var _47d = dojo.html.toCamelCase(_47b) - node.style[_47d] = _47c - } -} -dojo.html.setStyleText = function(_47e, text) { - try { - _47e.style.cssText = text - } catch (e) { - _47e.setAttribute('style', text) - } -} -dojo.html.copyStyle = function(_480, _481) { - if (!_481.style.cssText) { - _480.setAttribute('style', _481.getAttribute('style')) - } else { - _480.style.cssText = _481.style.cssText - } - dojo.html.addClass(_480, dojo.html.getClass(_481)) -} -dojo.html.getUnitValue = function(node, _483, _484) { - var s = dojo.html.getComputedStyle(node, _483) - if ((!s) || ((s == 'auto') && (_484))) { - return { value: 0, units: 'px' } - } - var _486 = s.match(/(\-?[\d.]+)([a-z%]*)/i) - if (!_486) { - return dojo.html.getUnitValue.bad - } - return { value: Number(_486[1]), units: _486[2].toLowerCase() } -} -dojo.html.getUnitValue.bad = { value: NaN, units: '' } + dojo.html.getComputedStyle = function (node, _471, _472) { + node = dojo.byId(node); + if (!node || !node.style) { + return _472 + } + var s = document.defaultView.getComputedStyle(node, null); + return (s && s[dojo.html.toCamelCase(_471)]) || '' + }; + dojo.html.getComputedStyles = function (node) { + return document.defaultView.getComputedStyle(node, null) + } +} +dojo.html.getStyleProperty = function (node, _476) { + node = dojo.byId(node); + return (node && node.style ? node.style[dojo.html.toCamelCase(_476)] : undefined) +}; +dojo.html.getStyle = function (node, _478) { + var _479 = dojo.html.getStyleProperty(node, _478); + return (_479 ? _479 : dojo.html.getComputedStyle(node, _478)) +}; +dojo.html.setStyle = function (node, _47b, _47c) { + node = dojo.byId(node); + if (node && node.style) { + var _47d = dojo.html.toCamelCase(_47b); + node.style[_47d] = _47c + } +}; +dojo.html.setStyleText = function (_47e, text) { + try { + _47e.style.cssText = text + } catch (e) { + _47e.setAttribute('style', text) + } +}; +dojo.html.copyStyle = function (_480, _481) { + if (!_481.style.cssText) { + _480.setAttribute('style', _481.getAttribute('style')) + } else { + _480.style.cssText = _481.style.cssText + } + dojo.html.addClass(_480, dojo.html.getClass(_481)) +}; +dojo.html.getUnitValue = function (node, _483, _484) { + var s = dojo.html.getComputedStyle(node, _483); + if ((!s) || ((s == 'auto') && (_484))) { + return {value: 0, units: 'px'} + } + var _486 = s.match(/(\-?[\d.]+)([a-z%]*)/i); + if (!_486) { + return dojo.html.getUnitValue.bad + } + return {value: Number(_486[1]), units: _486[2].toLowerCase()} +}; +dojo.html.getUnitValue.bad = {value: NaN, units: ''}; if (dojo.render.html.ie) { - dojo.html.toPixelValue = function(_487, _488) { - if (!_488) { - return 0 - } - if (_488.slice(-2) == 'px') { - return parseFloat(_488) - } - var _489 = 0 - with (_487) { - var _48a = style.left - var _48b = runtimeStyle.left - runtimeStyle.left = currentStyle.left - try { - style.left = _488 || 0 - _489 = style.pixelLeft - style.left = _48a - runtimeStyle.left = _48b - } catch (e) { - } - } - return _489 - } + dojo.html.toPixelValue = function (_487, _488) { + if (!_488) { + return 0 + } + if (_488.slice(-2) == 'px') { + return parseFloat(_488) + } + var _489 = 0; + with (_487) { + var _48a = style.left; + var _48b = runtimeStyle.left; + runtimeStyle.left = currentStyle.left; + try { + style.left = _488 || 0; + _489 = style.pixelLeft; + style.left = _48a; + runtimeStyle.left = _48b + } catch (e) { + } + } + return _489 + } } else { - dojo.html.toPixelValue = function(_48c, _48d) { - return (_48d && (_48d.slice(-2) == 'px') ? parseFloat(_48d) : 0) - } -} -dojo.html.getPixelValue = function(node, _48f, _490) { - return dojo.html.toPixelValue(node, dojo.html.getComputedStyle(node, _48f)) -} -dojo.html.setPositivePixelValue = function(node, _492, _493) { - if (isNaN(_493)) { - return false - } - node.style[_492] = Math.max(0, _493) + 'px' - return true -} -dojo.html.styleSheet = null -dojo.html.insertCssRule = function(_494, _495, _496) { - if (!dojo.html.styleSheet) { - if (document.createStyleSheet) { - dojo.html.styleSheet = document.createStyleSheet() - } else { - if (document.styleSheets[0]) { - dojo.html.styleSheet = document.styleSheets[0] - } else { - return null - } - } - } - if (arguments.length < 3) { - if (dojo.html.styleSheet.cssRules) { - _496 = dojo.html.styleSheet.cssRules.length - } else { - if (dojo.html.styleSheet.rules) { - _496 = dojo.html.styleSheet.rules.length - } else { - return null - } - } - } - if (dojo.html.styleSheet.insertRule) { - var rule = _494 + ' { ' + _495 + ' }' - return dojo.html.styleSheet.insertRule(rule, _496) - } else { - if (dojo.html.styleSheet.addRule) { - return dojo.html.styleSheet.addRule(_494, _495, _496) - } else { - return null - } - } -} -dojo.html.removeCssRule = function(_498) { - if (!dojo.html.styleSheet) { - dojo.debug('no stylesheet defined for removing rules') - return false - } - if (dojo.render.html.ie) { - if (!_498) { - _498 = dojo.html.styleSheet.rules.length - dojo.html.styleSheet.removeRule(_498) - } - } else { - if (document.styleSheets[0]) { - if (!_498) { - _498 = dojo.html.styleSheet.cssRules.length - } - dojo.html.styleSheet.deleteRule(_498) - } - } - return true -} -dojo.html._insertedCssFiles = [] -dojo.html.insertCssFile = function(URI, doc, _49b, _49c) { - if (!URI) { - return - } - if (!doc) { - doc = document - } - var _49d = dojo.hostenv.getText(URI, false, _49c) - if (_49d === null) { - return - } - _49d = dojo.html.fixPathsInCssText(_49d, URI) - if (_49b) { - var idx = -1, node, ent = dojo.html._insertedCssFiles - for (var i = 0; i < ent.length; i++) { - if ((ent[i].doc == doc) && (ent[i].cssText == _49d)) { - idx = i - node = ent[i].nodeRef - break - } - } - if (node) { - var _4a2 = doc.getElementsByTagName('style') - for (var i = 0; i < _4a2.length; i++) { - if (_4a2[i] == node) { - return - } - } - dojo.html._insertedCssFiles.shift(idx, 1) - } - } - var _4a3 = dojo.html.insertCssText(_49d, doc) - dojo.html._insertedCssFiles.push({ 'doc': doc, 'cssText': _49d, 'nodeRef': _4a3 }) - if (_4a3 && djConfig.isDebug) { - _4a3.setAttribute('dbgHref', URI) - } - return _4a3 -} -dojo.html.insertCssText = function(_4a4, doc, URI) { - if (!_4a4) { - return - } - if (!doc) { - doc = document - } - if (URI) { - _4a4 = dojo.html.fixPathsInCssText(_4a4, URI) - } - var _4a7 = doc.createElement('style') - _4a7.setAttribute('type', 'text/css') - var head = doc.getElementsByTagName('head')[0] - if (!head) { - dojo.debug('No head tag in document, aborting styles') - return - } else { - head.appendChild(_4a7) - } - if (_4a7.styleSheet) { - var _4a9 = function() { - try { - _4a7.styleSheet.cssText = _4a4 - } catch (e) { - dojo.debug(e) - } - } - if (_4a7.styleSheet.disabled) { - setTimeout(_4a9, 10) - } else { - _4a9() - } - } else { - var _4aa = doc.createTextNode(_4a4) - _4a7.appendChild(_4aa) - } - return _4a7 -} -dojo.html.fixPathsInCssText = function(_4ab, URI) { - if (!_4ab || !URI) { - return - } - var _4ad, str = '', url = '', _4b0 = '[\\t\\s\\w\\(\\)\\/\\.\\\\\'"-:#=&?~]+' - var _4b1 = new RegExp('url\\(\\s*(' + _4b0 + ')\\s*\\)') - var _4b2 = /(file|https?|ftps?):\/\// - regexTrim = new RegExp('^[\\s]*([\'"]?)(' + _4b0 + ')\\1[\\s]*?$') - if (dojo.render.html.ie55 || dojo.render.html.ie60) { - var _4b3 = new RegExp('AlphaImageLoader\\((.*)src=[\'"](' + _4b0 + ')[\'"]') - while (_4ad = _4b3.exec(_4ab)) { - url = _4ad[2].replace(regexTrim, '$2') - if (!_4b2.exec(url)) { - url = (new dojo.uri.Uri(URI, url).toString()) - } - str += _4ab.substring(0, _4ad.index) + 'AlphaImageLoader(' + _4ad[1] + 'src=\'' + url + '\'' - _4ab = _4ab.substr(_4ad.index + _4ad[0].length) - } - _4ab = str + _4ab - str = '' - } - while (_4ad = _4b1.exec(_4ab)) { - url = _4ad[1].replace(regexTrim, '$2') - if (!_4b2.exec(url)) { - url = (new dojo.uri.Uri(URI, url).toString()) - } - str += _4ab.substring(0, _4ad.index) + 'url(' + url + ')' - _4ab = _4ab.substr(_4ad.index + _4ad[0].length) - } - return str + _4ab -} -dojo.html.setActiveStyleSheet = function(_4b4) { - var i = 0, a, els = dojo.doc().getElementsByTagName('link') - while (a = els[i++]) { - if (a.getAttribute('rel').indexOf('style') != -1 && a.getAttribute('title')) { - a.disabled = true - if (a.getAttribute('title') == _4b4) { - a.disabled = false - } - } - } -} -dojo.html.getActiveStyleSheet = function() { - var i = 0, a, els = dojo.doc().getElementsByTagName('link') - while (a = els[i++]) { - if (a.getAttribute('rel').indexOf('style') != -1 && a.getAttribute('title') && !a.disabled) { - return a.getAttribute('title') - } - } - return null -} -dojo.html.getPreferredStyleSheet = function() { - var i = 0, a, els = dojo.doc().getElementsByTagName('link') - while (a = els[i++]) { - if (a.getAttribute('rel').indexOf('style') != -1 && a.getAttribute('rel').indexOf('alt') == -1 && a.getAttribute('title')) { - return a.getAttribute('title') - } - } - return null -} -dojo.html.applyBrowserClass = function(node) { - var drh = dojo.render.html - var _4c0 = { - dj_ie: drh.ie, - dj_ie55: drh.ie55, - dj_ie6: drh.ie60, - dj_ie7: drh.ie70, - dj_iequirks: drh.ie && drh.quirks, - dj_opera: drh.opera, - dj_opera8: drh.opera && (Math.floor(dojo.render.version) == 8), - dj_opera9: drh.opera && (Math.floor(dojo.render.version) == 9), - dj_khtml: drh.khtml, - dj_safari: drh.safari, - dj_gecko: drh.mozilla - } - for (var p in _4c0) { - if (_4c0[p]) { - dojo.html.addClass(node, p) - } - } -} -dojo.provide('dojo.html.display') -dojo.html._toggle = function(node, _4c3, _4c4) { - node = dojo.byId(node) - _4c4(node, !_4c3(node)) - return _4c3(node) -} -dojo.html.show = function(node) { - node = dojo.byId(node) - if (dojo.html.getStyleProperty(node, 'display') == 'none') { - dojo.html.setStyle(node, 'display', (node.dojoDisplayCache || '')) - node.dojoDisplayCache = undefined - } -} -dojo.html.hide = function(node) { - node = dojo.byId(node) - if (typeof node['dojoDisplayCache'] == 'undefined') { - var d = dojo.html.getStyleProperty(node, 'display') - if (d != 'none') { - node.dojoDisplayCache = d - } - } - dojo.html.setStyle(node, 'display', 'none') -} -dojo.html.setShowing = function(node, _4c9) { - dojo.html[(_4c9 ? 'show' : 'hide')](node) -} -dojo.html.isShowing = function(node) { - return (dojo.html.getStyleProperty(node, 'display') != 'none') -} -dojo.html.toggleShowing = function(node) { - return dojo.html._toggle(node, dojo.html.isShowing, dojo.html.setShowing) -} -dojo.html.displayMap = { tr: '', td: '', th: '', img: 'inline', span: 'inline', input: 'inline', button: 'inline' } -dojo.html.suggestDisplayByTagName = function(node) { - node = dojo.byId(node) - if (node && node.tagName) { - var tag = node.tagName.toLowerCase() - return (tag in dojo.html.displayMap ? dojo.html.displayMap[tag] : 'block') - } -} -dojo.html.setDisplay = function(node, _4cf) { - dojo.html.setStyle(node, 'display', ((_4cf instanceof String || typeof _4cf == 'string') ? _4cf : (_4cf ? dojo.html.suggestDisplayByTagName(node) : 'none'))) -} -dojo.html.isDisplayed = function(node) { - return (dojo.html.getComputedStyle(node, 'display') != 'none') -} -dojo.html.toggleDisplay = function(node) { - return dojo.html._toggle(node, dojo.html.isDisplayed, dojo.html.setDisplay) -} -dojo.html.setVisibility = function(node, _4d3) { - dojo.html.setStyle(node, 'visibility', ((_4d3 instanceof String || typeof _4d3 == 'string') ? _4d3 : (_4d3 ? 'visible' : 'hidden'))) -} -dojo.html.isVisible = function(node) { - return (dojo.html.getComputedStyle(node, 'visibility') != 'hidden') -} -dojo.html.toggleVisibility = function(node) { - return dojo.html._toggle(node, dojo.html.isVisible, dojo.html.setVisibility) -} -dojo.html.setOpacity = function(node, _4d7, _4d8) { - node = dojo.byId(node) - var h = dojo.render.html - if (!_4d8) { - if (_4d7 >= 1) { - if (h.ie) { - dojo.html.clearOpacity(node) - return - } else { - _4d7 = 0.999999 - } - } else { - if (_4d7 < 0) { - _4d7 = 0 - } - } - } - if (h.ie) { - if (node.nodeName.toLowerCase() == 'tr') { - var tds = node.getElementsByTagName('td') - for (var x = 0; x < tds.length; x++) { - tds[x].style.filter = 'Alpha(Opacity=' + _4d7 * 100 + ')' - } - } - node.style.filter = 'Alpha(Opacity=' + _4d7 * 100 + ')' - } else { - if (h.moz) { - node.style.opacity = _4d7 - node.style.MozOpacity = _4d7 - } else { - if (h.safari) { - node.style.opacity = _4d7 - node.style.KhtmlOpacity = _4d7 - } else { - node.style.opacity = _4d7 - } - } - } -} -dojo.html.clearOpacity = function(node) { - node = dojo.byId(node) - var ns = node.style - var h = dojo.render.html - if (h.ie) { - try { - if (node.filters && node.filters.alpha) { - ns.filter = '' - } - } catch (e) { - } - } else { - if (h.moz) { - ns.opacity = 1 - ns.MozOpacity = 1 - } else { - if (h.safari) { - ns.opacity = 1 - ns.KhtmlOpacity = 1 - } else { - ns.opacity = 1 - } - } - } -} -dojo.html.getOpacity = function(node) { - node = dojo.byId(node) - var h = dojo.render.html - if (h.ie) { - var opac = (node.filters && node.filters.alpha && typeof node.filters.alpha.opacity == 'number' ? node.filters.alpha.opacity : 100) / 100 - } else { - var opac = node.style.opacity || node.style.MozOpacity || node.style.KhtmlOpacity || 1 - } - return opac >= 0.999999 ? 1 : Number(opac) -} -dojo.provide('dojo.html.color') -dojo.html.getBackgroundColor = function(node) { - node = dojo.byId(node) - var _4e3 - do { - _4e3 = dojo.html.getStyle(node, 'background-color') - if (_4e3.toLowerCase() == 'rgba(0, 0, 0, 0)') { - _4e3 = 'transparent' - } - if (node == document.getElementsByTagName('body')[0]) { - node = null - break - } - node = node.parentNode - } while (node && dojo.lang.inArray(['transparent', ''], _4e3)) - if (_4e3 == 'transparent') { - _4e3 = [255, 255, 255, 0] - } else { - _4e3 = dojo.gfx.color.extractRGB(_4e3) - } - return _4e3 -} -dojo.provide('dojo.html.layout') -dojo.html.sumAncestorProperties = function(node, prop) { - node = dojo.byId(node) - if (!node) { - return 0 - } - var _4e6 = 0 - while (node) { - if (dojo.html.getComputedStyle(node, 'position') == 'fixed') { - return 0 - } - var val = node[prop] - if (val) { - _4e6 += val - 0 - if (node == dojo.body()) { - break - } - } - node = node.parentNode - } - return _4e6 -} -dojo.html.setStyleAttributes = function(node, _4e9) { - node = dojo.byId(node) - var _4ea = _4e9.replace(/(;)?\s*$/, '').split(';') - for (var i = 0; i < _4ea.length; i++) { - var _4ec = _4ea[i].split(':') - var name = _4ec[0].replace(/\s*$/, '').replace(/^\s*/, '').toLowerCase() - var _4ee = _4ec[1].replace(/\s*$/, '').replace(/^\s*/, '') - switch (name) { - case 'opacity': - dojo.html.setOpacity(node, _4ee) - break - case 'content-height': - dojo.html.setContentBox(node, { height: _4ee }) - break - case 'content-width': - dojo.html.setContentBox(node, { width: _4ee }) - break - case 'outer-height': - dojo.html.setMarginBox(node, { height: _4ee }) - break - case 'outer-width': - dojo.html.setMarginBox(node, { width: _4ee }) - break - default: - node.style[dojo.html.toCamelCase(name)] = _4ee - } - } + dojo.html.toPixelValue = function (_48c, _48d) { + return (_48d && (_48d.slice(-2) == 'px') ? parseFloat(_48d) : 0) + } } +dojo.html.getPixelValue = function (node, _48f, _490) { + return dojo.html.toPixelValue(node, dojo.html.getComputedStyle(node, _48f)) +}; +dojo.html.setPositivePixelValue = function (node, _492, _493) { + if (isNaN(_493)) { + return false + } + node.style[_492] = Math.max(0, _493) + 'px'; + return true +}; +dojo.html.styleSheet = null; +dojo.html.insertCssRule = function (_494, _495, _496) { + if (!dojo.html.styleSheet) { + if (document.createStyleSheet) { + dojo.html.styleSheet = document.createStyleSheet() + } else { + if (document.styleSheets[0]) { + dojo.html.styleSheet = document.styleSheets[0] + } else { + return null + } + } + } + if (arguments.length < 3) { + if (dojo.html.styleSheet.cssRules) { + _496 = dojo.html.styleSheet.cssRules.length + } else { + if (dojo.html.styleSheet.rules) { + _496 = dojo.html.styleSheet.rules.length + } else { + return null + } + } + } + if (dojo.html.styleSheet.insertRule) { + var rule = _494 + ' { ' + _495 + ' }'; + return dojo.html.styleSheet.insertRule(rule, _496) + } else { + if (dojo.html.styleSheet.addRule) { + return dojo.html.styleSheet.addRule(_494, _495, _496) + } else { + return null + } + } +}; +dojo.html.removeCssRule = function (_498) { + if (!dojo.html.styleSheet) { + dojo.debug('no stylesheet defined for removing rules'); + return false + } + if (dojo.render.html.ie) { + if (!_498) { + _498 = dojo.html.styleSheet.rules.length; + dojo.html.styleSheet.removeRule(_498) + } + } else { + if (document.styleSheets[0]) { + if (!_498) { + _498 = dojo.html.styleSheet.cssRules.length + } + dojo.html.styleSheet.deleteRule(_498) + } + } + return true +}; +dojo.html._insertedCssFiles = []; +dojo.html.insertCssFile = function (URI, doc, _49b, _49c) { + if (!URI) { + return + } + if (!doc) { + doc = document + } + var _49d = dojo.hostenv.getText(URI, false, _49c); + if (_49d === null) { + return + } + _49d = dojo.html.fixPathsInCssText(_49d, URI); + if (_49b) { + var idx = -1, node, ent = dojo.html._insertedCssFiles; + for (var i = 0; i < ent.length; i++) { + if ((ent[i].doc == doc) && (ent[i].cssText == _49d)) { + idx = i; + node = ent[i].nodeRef; + break + } + } + if (node) { + var _4a2 = doc.getElementsByTagName('style'); + for (var i = 0; i < _4a2.length; i++) { + if (_4a2[i] == node) { + return + } + } + dojo.html._insertedCssFiles.shift(idx, 1) + } + } + var _4a3 = dojo.html.insertCssText(_49d, doc); + dojo.html._insertedCssFiles.push({'doc': doc, 'cssText': _49d, 'nodeRef': _4a3}); + if (_4a3 && djConfig.isDebug) { + _4a3.setAttribute('dbgHref', URI) + } + return _4a3 +}; +dojo.html.insertCssText = function (_4a4, doc, URI) { + if (!_4a4) { + return + } + if (!doc) { + doc = document + } + if (URI) { + _4a4 = dojo.html.fixPathsInCssText(_4a4, URI) + } + var _4a7 = doc.createElement('style'); + _4a7.setAttribute('type', 'text/css'); + var head = doc.getElementsByTagName('head')[0]; + if (!head) { + dojo.debug('No head tag in document, aborting styles'); + return + } else { + head.appendChild(_4a7) + } + if (_4a7.styleSheet) { + var _4a9 = function () { + try { + _4a7.styleSheet.cssText = _4a4 + } catch (e) { + dojo.debug(e) + } + }; + if (_4a7.styleSheet.disabled) { + setTimeout(_4a9, 10) + } else { + _4a9() + } + } else { + var _4aa = doc.createTextNode(_4a4); + _4a7.appendChild(_4aa) + } + return _4a7 +}; +dojo.html.fixPathsInCssText = function (_4ab, URI) { + if (!_4ab || !URI) { + return + } + var _4ad, str = '', url = '', _4b0 = '[\\t\\s\\w\\(\\)\\/\\.\\\\\'"-:#=&?~]+'; + var _4b1 = new RegExp('url\\(\\s*(' + _4b0 + ')\\s*\\)'); + var _4b2 = /(file|https?|ftps?):\/\//; + regexTrim = new RegExp('^[\\s]*([\'"]?)(' + _4b0 + ')\\1[\\s]*?$'); + if (dojo.render.html.ie55 || dojo.render.html.ie60) { + var _4b3 = new RegExp('AlphaImageLoader\\((.*)src=[\'"](' + _4b0 + ')[\'"]'); + while (_4ad = _4b3.exec(_4ab)) { + url = _4ad[2].replace(regexTrim, '$2'); + if (!_4b2.exec(url)) { + url = (new dojo.uri.Uri(URI, url).toString()) + } + str += _4ab.substring(0, _4ad.index) + 'AlphaImageLoader(' + _4ad[1] + 'src=\'' + url + '\''; + _4ab = _4ab.substr(_4ad.index + _4ad[0].length) + } + _4ab = str + _4ab; + str = '' + } + while (_4ad = _4b1.exec(_4ab)) { + url = _4ad[1].replace(regexTrim, '$2'); + if (!_4b2.exec(url)) { + url = (new dojo.uri.Uri(URI, url).toString()) + } + str += _4ab.substring(0, _4ad.index) + 'url(' + url + ')'; + _4ab = _4ab.substr(_4ad.index + _4ad[0].length) + } + return str + _4ab +}; +dojo.html.setActiveStyleSheet = function (_4b4) { + var i = 0, a, els = dojo.doc().getElementsByTagName('link'); + while (a = els[i++]) { + if (a.getAttribute('rel').indexOf('style') != -1 && a.getAttribute('title')) { + a.disabled = true; + if (a.getAttribute('title') == _4b4) { + a.disabled = false + } + } + } +}; +dojo.html.getActiveStyleSheet = function () { + var i = 0, a, els = dojo.doc().getElementsByTagName('link'); + while (a = els[i++]) { + if (a.getAttribute('rel').indexOf('style') != -1 && a.getAttribute('title') && !a.disabled) { + return a.getAttribute('title') + } + } + return null +}; +dojo.html.getPreferredStyleSheet = function () { + var i = 0, a, els = dojo.doc().getElementsByTagName('link'); + while (a = els[i++]) { + if (a.getAttribute('rel').indexOf('style') + != -1 + && a.getAttribute('rel').indexOf('alt') + == -1 + && a.getAttribute('title')) { + return a.getAttribute('title') + } + } + return null +}; +dojo.html.applyBrowserClass = function (node) { + var drh = dojo.render.html; + var _4c0 = { + dj_ie: drh.ie, + dj_ie55: drh.ie55, + dj_ie6: drh.ie60, + dj_ie7: drh.ie70, + dj_iequirks: drh.ie && drh.quirks, + dj_opera: drh.opera, + dj_opera8: drh.opera && (Math.floor(dojo.render.version) == 8), + dj_opera9: drh.opera && (Math.floor(dojo.render.version) == 9), + dj_khtml: drh.khtml, + dj_safari: drh.safari, + dj_gecko: drh.mozilla + }; + for (var p in _4c0) { + if (_4c0[p]) { + dojo.html.addClass(node, p) + } + } +}; +dojo.provide('dojo.html.display'); +dojo.html._toggle = function (node, _4c3, _4c4) { + node = dojo.byId(node); + _4c4(node, !_4c3(node)); + return _4c3(node) +}; +dojo.html.show = function (node) { + node = dojo.byId(node); + if (dojo.html.getStyleProperty(node, 'display') == 'none') { + dojo.html.setStyle(node, 'display', (node.dojoDisplayCache || '')); + node.dojoDisplayCache = undefined + } +}; +dojo.html.hide = function (node) { + node = dojo.byId(node); + if (typeof node['dojoDisplayCache'] == 'undefined') { + var d = dojo.html.getStyleProperty(node, 'display'); + if (d != 'none') { + node.dojoDisplayCache = d + } + } + dojo.html.setStyle(node, 'display', 'none') +}; +dojo.html.setShowing = function (node, _4c9) { + dojo.html[(_4c9 ? 'show' : 'hide')](node) +}; +dojo.html.isShowing = function (node) { + return (dojo.html.getStyleProperty(node, 'display') != 'none') +}; +dojo.html.toggleShowing = function (node) { + return dojo.html._toggle(node, dojo.html.isShowing, dojo.html.setShowing) +}; +dojo.html.displayMap = {tr: '', td: '', th: '', img: 'inline', span: 'inline', input: 'inline', button: 'inline'}; +dojo.html.suggestDisplayByTagName = function (node) { + node = dojo.byId(node); + if (node && node.tagName) { + var tag = node.tagName.toLowerCase(); + return (tag in dojo.html.displayMap ? dojo.html.displayMap[tag] : 'block') + } +}; +dojo.html.setDisplay = function (node, _4cf) { + dojo.html.setStyle(node, 'display', ((_4cf + instanceof String + || typeof _4cf + == 'string') ? _4cf : (_4cf ? dojo.html.suggestDisplayByTagName(node) : 'none'))) +}; +dojo.html.isDisplayed = function (node) { + return (dojo.html.getComputedStyle(node, 'display') != 'none') +}; +dojo.html.toggleDisplay = function (node) { + return dojo.html._toggle(node, dojo.html.isDisplayed, dojo.html.setDisplay) +}; +dojo.html.setVisibility = function (node, _4d3) { + dojo.html.setStyle(node, 'visibility', ((_4d3 + instanceof String + || typeof _4d3 + == 'string') ? _4d3 : (_4d3 ? 'visible' : 'hidden'))) +}; +dojo.html.isVisible = function (node) { + return (dojo.html.getComputedStyle(node, 'visibility') != 'hidden') +}; +dojo.html.toggleVisibility = function (node) { + return dojo.html._toggle(node, dojo.html.isVisible, dojo.html.setVisibility) +}; +dojo.html.setOpacity = function (node, _4d7, _4d8) { + node = dojo.byId(node); + var h = dojo.render.html; + if (!_4d8) { + if (_4d7 >= 1) { + if (h.ie) { + dojo.html.clearOpacity(node); + return + } else { + _4d7 = 0.999999 + } + } else { + if (_4d7 < 0) { + _4d7 = 0 + } + } + } + if (h.ie) { + if (node.nodeName.toLowerCase() == 'tr') { + var tds = node.getElementsByTagName('td'); + for (var x = 0; x < tds.length; x++) { + tds[x].style.filter = 'Alpha(Opacity=' + _4d7 * 100 + ')' + } + } + node.style.filter = 'Alpha(Opacity=' + _4d7 * 100 + ')' + } else { + if (h.moz) { + node.style.opacity = _4d7; + node.style.MozOpacity = _4d7 + } else { + if (h.safari) { + node.style.opacity = _4d7; + node.style.KhtmlOpacity = _4d7 + } else { + node.style.opacity = _4d7 + } + } + } +}; +dojo.html.clearOpacity = function (node) { + node = dojo.byId(node); + var ns = node.style; + var h = dojo.render.html; + if (h.ie) { + try { + if (node.filters && node.filters.alpha) { + ns.filter = '' + } + } catch (e) { + } + } else { + if (h.moz) { + ns.opacity = 1; + ns.MozOpacity = 1 + } else { + if (h.safari) { + ns.opacity = 1; + ns.KhtmlOpacity = 1 + } else { + ns.opacity = 1 + } + } + } +}; +dojo.html.getOpacity = function (node) { + node = dojo.byId(node); + var h = dojo.render.html; + if (h.ie) { + var opac = (node.filters + && node.filters.alpha + && typeof node.filters.alpha.opacity + == 'number' ? node.filters.alpha.opacity : 100) / 100 + } else { + var opac = node.style.opacity || node.style.MozOpacity || node.style.KhtmlOpacity || 1 + } + return opac >= 0.999999 ? 1 : Number(opac) +}; +dojo.provide('dojo.html.color'); +dojo.html.getBackgroundColor = function (node) { + node = dojo.byId(node); + var _4e3; + do { + _4e3 = dojo.html.getStyle(node, 'background-color'); + if (_4e3.toLowerCase() == 'rgba(0, 0, 0, 0)') { + _4e3 = 'transparent' + } + if (node == document.getElementsByTagName('body')[0]) { + node = null; + break + } + node = node.parentNode + } while (node && dojo.lang.inArray(['transparent', ''], _4e3)); + if (_4e3 == 'transparent') { + _4e3 = [255, 255, 255, 0] + } else { + _4e3 = dojo.gfx.color.extractRGB(_4e3) + } + return _4e3 +}; +dojo.provide('dojo.html.layout'); +dojo.html.sumAncestorProperties = function (node, prop) { + node = dojo.byId(node); + if (!node) { + return 0 + } + var _4e6 = 0; + while (node) { + if (dojo.html.getComputedStyle(node, 'position') == 'fixed') { + return 0 + } + var val = node[prop]; + if (val) { + _4e6 += val - 0; + if (node == dojo.body()) { + break + } + } + node = node.parentNode + } + return _4e6 +}; +dojo.html.setStyleAttributes = function (node, _4e9) { + node = dojo.byId(node); + var _4ea = _4e9.replace(/(;)?\s*$/, '').split(';'); + for (var i = 0; i < _4ea.length; i++) { + var _4ec = _4ea[i].split(':'); + var name = _4ec[0].replace(/\s*$/, '').replace(/^\s*/, '').toLowerCase(); + var _4ee = _4ec[1].replace(/\s*$/, '').replace(/^\s*/, ''); + switch (name) { + case 'opacity': + dojo.html.setOpacity(node, _4ee); + break; + case 'content-height': + dojo.html.setContentBox(node, {height: _4ee}); + break; + case 'content-width': + dojo.html.setContentBox(node, {width: _4ee}); + break; + case 'outer-height': + dojo.html.setMarginBox(node, {height: _4ee}); + break; + case 'outer-width': + dojo.html.setMarginBox(node, {width: _4ee}); + break; + default: + node.style[dojo.html.toCamelCase(name)] = _4ee + } + } +}; dojo.html.boxSizing = { - MARGIN_BOX: 'margin-box', - BORDER_BOX: 'border-box', - PADDING_BOX: 'padding-box', - CONTENT_BOX: 'content-box' -} -dojo.html.getAbsolutePosition = dojo.html.abs = function(node, _4f0, _4f1) { - node = dojo.byId(node, node.ownerDocument) - var ret = { x: 0, y: 0 } - var bs = dojo.html.boxSizing - if (!_4f1) { - _4f1 = bs.CONTENT_BOX - } - var _4f4 = 2 - var _4f5 - switch (_4f1) { - case bs.MARGIN_BOX: - _4f5 = 3 - break - case bs.BORDER_BOX: - _4f5 = 2 - break - case bs.PADDING_BOX: - default: - _4f5 = 1 - break - case bs.CONTENT_BOX: - _4f5 = 0 - break - } - var h = dojo.render.html - var db = document['body'] || document['documentElement'] - if (h.ie) { - with (node.getBoundingClientRect()) { - ret.x = left - 2 - ret.y = top - 2 - } - } else { - if (document.getBoxObjectFor) { - _4f4 = 1 - try { - var bo = document.getBoxObjectFor(node) - ret.x = bo.x - dojo.html.sumAncestorProperties(node, 'scrollLeft') - ret.y = bo.y - dojo.html.sumAncestorProperties(node, 'scrollTop') - } catch (e) { - } - } else { - if (node['offsetParent']) { - var _4f9 - if ((h.safari) && (node.style.getPropertyValue('position') == 'absolute') && (node.parentNode == db)) { - _4f9 = db - } else { - _4f9 = db.parentNode - } - if (node.parentNode != db) { - var nd = node - if (dojo.render.html.opera) { - nd = db - } - ret.x -= dojo.html.sumAncestorProperties(nd, 'scrollLeft') - ret.y -= dojo.html.sumAncestorProperties(nd, 'scrollTop') - } - var _4fb = node - do { - var n = _4fb['offsetLeft'] - if (!h.opera || n > 0) { - ret.x += isNaN(n) ? 0 : n - } - var m = _4fb['offsetTop'] - ret.y += isNaN(m) ? 0 : m - _4fb = _4fb.offsetParent - } while ((_4fb != _4f9) && (_4fb != null)) - } else { - if (node['x'] && node['y']) { - ret.x += isNaN(node.x) ? 0 : node.x - ret.y += isNaN(node.y) ? 0 : node.y - } - } - } - } - if (_4f0) { - var _4fe = dojo.html.getScroll() - ret.y += _4fe.top - ret.x += _4fe.left - } - var _4ff = [dojo.html.getPaddingExtent, dojo.html.getBorderExtent, dojo.html.getMarginExtent] - if (_4f4 > _4f5) { - for (var i = _4f5; i < _4f4; ++i) { - ret.y += _4ff[i](node, 'top') - ret.x += _4ff[i](node, 'left') - } - } else { - if (_4f4 < _4f5) { - for (var i = _4f5; i > _4f4; --i) { - ret.y -= _4ff[i - 1](node, 'top') - ret.x -= _4ff[i - 1](node, 'left') - } - } - } - ret.top = ret.y - ret.left = ret.x - return ret -} -dojo.html.isPositionAbsolute = function(node) { - return (dojo.html.getComputedStyle(node, 'position') == 'absolute') -} -dojo.html._sumPixelValues = function(node, _503, _504) { - var _505 = 0 - for (var x = 0; x < _503.length; x++) { - _505 += dojo.html.getPixelValue(node, _503[x], _504) - } - return _505 -} -dojo.html.getMargin = function(node) { - return { - width: dojo.html._sumPixelValues(node, ['margin-left', 'margin-right'], (dojo.html.getComputedStyle(node, 'position') == 'absolute')), - height: dojo.html._sumPixelValues(node, ['margin-top', 'margin-bottom'], (dojo.html.getComputedStyle(node, 'position') == 'absolute')) - } -} -dojo.html.getBorder = function(node) { - return { - width: dojo.html.getBorderExtent(node, 'left') + dojo.html.getBorderExtent(node, 'right'), - height: dojo.html.getBorderExtent(node, 'top') + dojo.html.getBorderExtent(node, 'bottom') - } -} -dojo.html.getBorderExtent = function(node, side) { - return (dojo.html.getStyle(node, 'border-' + side + '-style') == 'none' ? 0 : dojo.html.getPixelValue(node, 'border-' + side + '-width')) -} -dojo.html.getMarginExtent = function(node, side) { - return dojo.html._sumPixelValues(node, ['margin-' + side], dojo.html.isPositionAbsolute(node)) -} -dojo.html.getPaddingExtent = function(node, side) { - return dojo.html._sumPixelValues(node, ['padding-' + side], true) -} -dojo.html.getPadding = function(node) { - return { - width: dojo.html._sumPixelValues(node, ['padding-left', 'padding-right'], true), - height: dojo.html._sumPixelValues(node, ['padding-top', 'padding-bottom'], true) - } -} -dojo.html.getPadBorder = function(node) { - var pad = dojo.html.getPadding(node) - var _512 = dojo.html.getBorder(node) - return { width: pad.width + _512.width, height: pad.height + _512.height } -} -dojo.html.getBoxSizing = function(node) { - var h = dojo.render.html - var bs = dojo.html.boxSizing - if (((h.ie) || (h.opera)) && node.nodeName.toLowerCase() != 'img') { - var cm = document['compatMode'] - if ((cm == 'BackCompat') || (cm == 'QuirksMode')) { - return bs.BORDER_BOX - } else { - return bs.CONTENT_BOX - } - } else { - if (arguments.length == 0) { - node = document.documentElement - } - var _517 - if (!h.ie) { - _517 = dojo.html.getStyle(node, '-moz-box-sizing') - if (!_517) { - _517 = dojo.html.getStyle(node, 'box-sizing') - } - } - return (_517 ? _517 : bs.CONTENT_BOX) - } -} -dojo.html.isBorderBox = function(node) { - return (dojo.html.getBoxSizing(node) == dojo.html.boxSizing.BORDER_BOX) -} -dojo.html.getBorderBox = function(node) { - node = dojo.byId(node) - return { width: node.offsetWidth, height: node.offsetHeight } -} -dojo.html.getPaddingBox = function(node) { - var box = dojo.html.getBorderBox(node) - var _51c = dojo.html.getBorder(node) - return { width: box.width - _51c.width, height: box.height - _51c.height } -} -dojo.html.getContentBox = function(node) { - node = dojo.byId(node) - var _51e = dojo.html.getPadBorder(node) - return { width: node.offsetWidth - _51e.width, height: node.offsetHeight - _51e.height } -} -dojo.html.setContentBox = function(node, args) { - node = dojo.byId(node) - var _521 = 0 - var _522 = 0 - var isbb = dojo.html.isBorderBox(node) - var _524 = (isbb ? dojo.html.getPadBorder(node) : { width: 0, height: 0 }) - var ret = {} - if (typeof args.width != 'undefined') { - _521 = args.width + _524.width - ret.width = dojo.html.setPositivePixelValue(node, 'width', _521) - } - if (typeof args.height != 'undefined') { - _522 = args.height + _524.height - ret.height = dojo.html.setPositivePixelValue(node, 'height', _522) - } - return ret -} -dojo.html.getMarginBox = function(node) { - var _527 = dojo.html.getBorderBox(node) - var _528 = dojo.html.getMargin(node) - return { width: _527.width + _528.width, height: _527.height + _528.height } -} -dojo.html.setMarginBox = function(node, args) { - node = dojo.byId(node) - var _52b = 0 - var _52c = 0 - var isbb = dojo.html.isBorderBox(node) - var _52e = (!isbb ? dojo.html.getPadBorder(node) : { width: 0, height: 0 }) - var _52f = dojo.html.getMargin(node) - var ret = {} - if (typeof args.width != 'undefined') { - _52b = args.width - _52e.width - _52b -= _52f.width - ret.width = dojo.html.setPositivePixelValue(node, 'width', _52b) - } - if (typeof args.height != 'undefined') { - _52c = args.height - _52e.height - _52c -= _52f.height - ret.height = dojo.html.setPositivePixelValue(node, 'height', _52c) - } - return ret -} -dojo.html.getElementBox = function(node, type) { - var bs = dojo.html.boxSizing - switch (type) { - case bs.MARGIN_BOX: - return dojo.html.getMarginBox(node) - case bs.BORDER_BOX: - return dojo.html.getBorderBox(node) - case bs.PADDING_BOX: - return dojo.html.getPaddingBox(node) - case bs.CONTENT_BOX: - default: - return dojo.html.getContentBox(node) - } -} -dojo.html.toCoordinateObject = dojo.html.toCoordinateArray = function(_534, _535, _536) { - if (_534 instanceof Array || typeof _534 == 'array') { - dojo.deprecated('dojo.html.toCoordinateArray', 'use dojo.html.toCoordinateObject({left: , top: , width: , height: }) instead', '0.5') - while (_534.length < 4) { - _534.push(0) - } - while (_534.length > 4) { - _534.pop() - } - var ret = { left: _534[0], top: _534[1], width: _534[2], height: _534[3] } - } else { - if (!_534.nodeType && !(_534 instanceof String || typeof _534 == 'string') && ('width' in _534 || 'height' in _534 || 'left' in _534 || 'x' in _534 || 'top' in _534 || 'y' in _534)) { - var ret = { - left: _534.left || _534.x || 0, - top: _534.top || _534.y || 0, - width: _534.width || 0, - height: _534.height || 0 - } - } else { - var node = dojo.byId(_534) - var pos = dojo.html.abs(node, _535, _536) - var _53a = dojo.html.getMarginBox(node) - var ret = { left: pos.left, top: pos.top, width: _53a.width, height: _53a.height } - } - } - ret.x = ret.left - ret.y = ret.top - return ret -} -dojo.html.setMarginBoxWidth = dojo.html.setOuterWidth = function(node, _53c) { - return dojo.html._callDeprecated('setMarginBoxWidth', 'setMarginBox', arguments, 'width') -} -dojo.html.setMarginBoxHeight = dojo.html.setOuterHeight = function() { - return dojo.html._callDeprecated('setMarginBoxHeight', 'setMarginBox', arguments, 'height') -} -dojo.html.getMarginBoxWidth = dojo.html.getOuterWidth = function() { - return dojo.html._callDeprecated('getMarginBoxWidth', 'getMarginBox', arguments, null, 'width') -} -dojo.html.getMarginBoxHeight = dojo.html.getOuterHeight = function() { - return dojo.html._callDeprecated('getMarginBoxHeight', 'getMarginBox', arguments, null, 'height') -} -dojo.html.getTotalOffset = function(node, type, _53f) { - return dojo.html._callDeprecated('getTotalOffset', 'getAbsolutePosition', arguments, null, type) -} -dojo.html.getAbsoluteX = function(node, _541) { - return dojo.html._callDeprecated('getAbsoluteX', 'getAbsolutePosition', arguments, null, 'x') -} -dojo.html.getAbsoluteY = function(node, _543) { - return dojo.html._callDeprecated('getAbsoluteY', 'getAbsolutePosition', arguments, null, 'y') -} -dojo.html.totalOffsetLeft = function(node, _545) { - return dojo.html._callDeprecated('totalOffsetLeft', 'getAbsolutePosition', arguments, null, 'left') -} -dojo.html.totalOffsetTop = function(node, _547) { - return dojo.html._callDeprecated('totalOffsetTop', 'getAbsolutePosition', arguments, null, 'top') -} -dojo.html.getMarginWidth = function(node) { - return dojo.html._callDeprecated('getMarginWidth', 'getMargin', arguments, null, 'width') -} -dojo.html.getMarginHeight = function(node) { - return dojo.html._callDeprecated('getMarginHeight', 'getMargin', arguments, null, 'height') -} -dojo.html.getBorderWidth = function(node) { - return dojo.html._callDeprecated('getBorderWidth', 'getBorder', arguments, null, 'width') -} -dojo.html.getBorderHeight = function(node) { - return dojo.html._callDeprecated('getBorderHeight', 'getBorder', arguments, null, 'height') -} -dojo.html.getPaddingWidth = function(node) { - return dojo.html._callDeprecated('getPaddingWidth', 'getPadding', arguments, null, 'width') -} -dojo.html.getPaddingHeight = function(node) { - return dojo.html._callDeprecated('getPaddingHeight', 'getPadding', arguments, null, 'height') -} -dojo.html.getPadBorderWidth = function(node) { - return dojo.html._callDeprecated('getPadBorderWidth', 'getPadBorder', arguments, null, 'width') -} -dojo.html.getPadBorderHeight = function(node) { - return dojo.html._callDeprecated('getPadBorderHeight', 'getPadBorder', arguments, null, 'height') -} -dojo.html.getBorderBoxWidth = dojo.html.getInnerWidth = function() { - return dojo.html._callDeprecated('getBorderBoxWidth', 'getBorderBox', arguments, null, 'width') -} -dojo.html.getBorderBoxHeight = dojo.html.getInnerHeight = function() { - return dojo.html._callDeprecated('getBorderBoxHeight', 'getBorderBox', arguments, null, 'height') -} -dojo.html.getContentBoxWidth = dojo.html.getContentWidth = function() { - return dojo.html._callDeprecated('getContentBoxWidth', 'getContentBox', arguments, null, 'width') -} -dojo.html.getContentBoxHeight = dojo.html.getContentHeight = function() { - return dojo.html._callDeprecated('getContentBoxHeight', 'getContentBox', arguments, null, 'height') -} -dojo.html.setContentBoxWidth = dojo.html.setContentWidth = function(node, _551) { - return dojo.html._callDeprecated('setContentBoxWidth', 'setContentBox', arguments, 'width') -} -dojo.html.setContentBoxHeight = dojo.html.setContentHeight = function(node, _553) { - return dojo.html._callDeprecated('setContentBoxHeight', 'setContentBox', arguments, 'height') -} -dojo.provide('dojo.lfx.html') -dojo.lfx.html._byId = function(_554) { - if (!_554) { - return [] - } - if (dojo.lang.isArrayLike(_554)) { - if (!_554.alreadyChecked) { - var n = [] - dojo.lang.forEach(_554, function(node) { - n.push(dojo.byId(node)) - }) - n.alreadyChecked = true - return n - } else { - return _554 - } - } else { - var n = [] - n.push(dojo.byId(_554)) - n.alreadyChecked = true - return n - } -} -dojo.lfx.html.propertyAnimation = function(_557, _558, _559, _55a, _55b) { - _557 = dojo.lfx.html._byId(_557) - var _55c = { 'propertyMap': _558, 'nodes': _557, 'duration': _559, 'easing': _55a || dojo.lfx.easeDefault } - var _55d = function(args) { - if (args.nodes.length == 1) { - var pm = args.propertyMap - if (!dojo.lang.isArray(args.propertyMap)) { - var parr = [] - for (var _561 in pm) { - pm[_561].property = _561 - parr.push(pm[_561]) - } - pm = args.propertyMap = parr - } - dojo.lang.forEach(pm, function(prop) { - if (dj_undef('start', prop)) { - if (prop.property != 'opacity') { - prop.start = parseInt(dojo.html.getComputedStyle(args.nodes[0], prop.property)) - } else { - prop.start = dojo.html.getOpacity(args.nodes[0]) - } - } - }) - } - } - var _563 = function(_564) { - var _565 = [] - dojo.lang.forEach(_564, function(c) { - _565.push(Math.round(c)) - }) - return _565 - } - var _567 = function(n, _569) { - n = dojo.byId(n) - if (!n || !n.style) { - return - } - for (var s in _569) { - try { - if (s == 'opacity') { - dojo.html.setOpacity(n, _569[s]) - } else { - n.style[s] = _569[s] - } - } catch (e) { - dojo.debug(e) - } - } - } - var _56b = function(_56c) { - this._properties = _56c - this.diffs = new Array(_56c.length) - dojo.lang.forEach(_56c, function(prop, i) { - if (dojo.lang.isFunction(prop.start)) { - prop.start = prop.start(prop, i) - } - if (dojo.lang.isFunction(prop.end)) { - prop.end = prop.end(prop, i) - } - if (dojo.lang.isArray(prop.start)) { - this.diffs[i] = null - } else { - if (prop.start instanceof dojo.gfx.color.Color) { - prop.startRgb = prop.start.toRgb() - prop.endRgb = prop.end.toRgb() - } else { - this.diffs[i] = prop.end - prop.start - } - } - }, this) - this.getValue = function(n) { - var ret = {} - dojo.lang.forEach(this._properties, function(prop, i) { - var _573 = null - if (dojo.lang.isArray(prop.start)) { - } else { - if (prop.start instanceof dojo.gfx.color.Color) { - _573 = (prop.units || 'rgb') + '(' - for (var j = 0; j < prop.startRgb.length; j++) { - _573 += Math.round(((prop.endRgb[j] - prop.startRgb[j]) * n) + prop.startRgb[j]) + (j < prop.startRgb.length - 1 ? ',' : '') - } - _573 += ')' - } else { - _573 = ((this.diffs[i]) * n) + prop.start + (prop.property != 'opacity' ? prop.units || 'px' : '') - } - } - ret[dojo.html.toCamelCase(prop.property)] = _573 - }, this) - return ret - } - } - var anim = new dojo.lfx.Animation({ - beforeBegin: function() { - _55d(_55c) - anim.curve = new _56b(_55c.propertyMap) - }, onAnimate: function(_576) { - dojo.lang.forEach(_55c.nodes, function(node) { - _567(node, _576) - }) - } - }, _55c.duration, null, _55c.easing) - if (_55b) { - for (var x in _55b) { - if (dojo.lang.isFunction(_55b[x])) { - anim.connect(x, anim, _55b[x]) - } - } - } - return anim -} -dojo.lfx.html._makeFadeable = function(_579) { - var _57a = function(node) { - if (dojo.render.html.ie) { - if ((node.style.zoom.length == 0) && (dojo.html.getStyle(node, 'zoom') == 'normal')) { - node.style.zoom = '1' - } - if ((node.style.width.length == 0) && (dojo.html.getStyle(node, 'width') == 'auto')) { - node.style.width = 'auto' - } - } - } - if (dojo.lang.isArrayLike(_579)) { - dojo.lang.forEach(_579, _57a) - } else { - _57a(_579) - } -} -dojo.lfx.html.fade = function(_57c, _57d, _57e, _57f, _580) { - _57c = dojo.lfx.html._byId(_57c) - var _581 = { property: 'opacity' } - if (!dj_undef('start', _57d)) { - _581.start = _57d.start - } else { - _581.start = function() { - return dojo.html.getOpacity(_57c[0]) - } - } - if (!dj_undef('end', _57d)) { - _581.end = _57d.end - } else { - dojo.raise('dojo.lfx.html.fade needs an end value') - } - var anim = dojo.lfx.propertyAnimation(_57c, [_581], _57e, _57f) - anim.connect('beforeBegin', function() { - dojo.lfx.html._makeFadeable(_57c) - }) - if (_580) { - anim.connect('onEnd', function() { - _580(_57c, anim) - }) - } - return anim -} -dojo.lfx.html.fadeIn = function(_583, _584, _585, _586) { - return dojo.lfx.html.fade(_583, { end: 1 }, _584, _585, _586) -} -dojo.lfx.html.fadeOut = function(_587, _588, _589, _58a) { - return dojo.lfx.html.fade(_587, { end: 0 }, _588, _589, _58a) -} -dojo.lfx.html.fadeShow = function(_58b, _58c, _58d, _58e) { - _58b = dojo.lfx.html._byId(_58b) - dojo.lang.forEach(_58b, function(node) { - dojo.html.setOpacity(node, 0) - }) - var anim = dojo.lfx.html.fadeIn(_58b, _58c, _58d, _58e) - anim.connect('beforeBegin', function() { - if (dojo.lang.isArrayLike(_58b)) { - dojo.lang.forEach(_58b, dojo.html.show) - } else { - dojo.html.show(_58b) - } - }) - return anim -} -dojo.lfx.html.fadeHide = function(_591, _592, _593, _594) { - var anim = dojo.lfx.html.fadeOut(_591, _592, _593, function() { - if (dojo.lang.isArrayLike(_591)) { - dojo.lang.forEach(_591, dojo.html.hide) - } else { - dojo.html.hide(_591) - } - if (_594) { - _594(_591, anim) - } - }) - return anim -} -dojo.lfx.html.wipeIn = function(_596, _597, _598, _599) { - _596 = dojo.lfx.html._byId(_596) - var _59a = [] - dojo.lang.forEach(_596, function(node) { - var _59c = {} - var _59d, _59e, _59f - with (node.style) { - _59d = top - _59e = left - _59f = position - top = '-9999px' - left = '-9999px' - position = 'absolute' - display = '' - } - var _5a0 = dojo.html.getBorderBox(node).height - with (node.style) { - top = _59d - left = _59e - position = _59f - display = 'none' - } - var anim = dojo.lfx.propertyAnimation(node, { - 'height': { - start: 1, end: function() { - return _5a0 - } - } - }, _597, _598) - anim.connect('beforeBegin', function() { - _59c.overflow = node.style.overflow - _59c.height = node.style.height - with (node.style) { - overflow = 'hidden' - height = '1px' - } - dojo.html.show(node) - }) - anim.connect('onEnd', function() { - with (node.style) { - overflow = _59c.overflow - height = _59c.height - } - if (_599) { - _599(node, anim) - } - }) - _59a.push(anim) - }) - return dojo.lfx.combine(_59a) -} -dojo.lfx.html.wipeOut = function(_5a2, _5a3, _5a4, _5a5) { - _5a2 = dojo.lfx.html._byId(_5a2) - var _5a6 = [] - dojo.lang.forEach(_5a2, function(node) { - var _5a8 = {} - var anim = dojo.lfx.propertyAnimation(node, { - 'height': { - start: function() { - return dojo.html.getContentBox(node).height - }, end: 1 - } - }, _5a3, _5a4, { - 'beforeBegin': function() { - _5a8.overflow = node.style.overflow - _5a8.height = node.style.height - with (node.style) { - overflow = 'hidden' - } - dojo.html.show(node) - }, 'onEnd': function() { - dojo.html.hide(node) - with (node.style) { - overflow = _5a8.overflow - height = _5a8.height - } - if (_5a5) { - _5a5(node, anim) - } - } - }) - _5a6.push(anim) - }) - return dojo.lfx.combine(_5a6) -} -dojo.lfx.html.slideTo = function(_5aa, _5ab, _5ac, _5ad, _5ae) { - _5aa = dojo.lfx.html._byId(_5aa) - var _5af = [] - var _5b0 = dojo.html.getComputedStyle - if (dojo.lang.isArray(_5ab)) { - dojo.deprecated('dojo.lfx.html.slideTo(node, array)', 'use dojo.lfx.html.slideTo(node, {top: value, left: value});', '0.5') - _5ab = { top: _5ab[0], left: _5ab[1] } - } - dojo.lang.forEach(_5aa, function(node) { - var top = null - var left = null - var init = (function() { - var _5b5 = node - return function() { - var pos = _5b0(_5b5, 'position') - top = (pos == 'absolute' ? node.offsetTop : parseInt(_5b0(node, 'top')) || 0) - left = (pos == 'absolute' ? node.offsetLeft : parseInt(_5b0(node, 'left')) || 0) - if (!dojo.lang.inArray(['absolute', 'relative'], pos)) { - var ret = dojo.html.abs(_5b5, true) - dojo.html.setStyleAttributes(_5b5, 'position:absolute;top:' + ret.y + 'px;left:' + ret.x + 'px;') - top = ret.y - left = ret.x - } - } - })() - init() - var anim = dojo.lfx.propertyAnimation(node, { - 'top': { start: top, end: (_5ab.top || 0) }, - 'left': { start: left, end: (_5ab.left || 0) } - }, _5ac, _5ad, { 'beforeBegin': init }) - if (_5ae) { - anim.connect('onEnd', function() { - _5ae(_5aa, anim) - }) - } - _5af.push(anim) - }) - return dojo.lfx.combine(_5af) -} -dojo.lfx.html.slideBy = function(_5b9, _5ba, _5bb, _5bc, _5bd) { - _5b9 = dojo.lfx.html._byId(_5b9) - var _5be = [] - var _5bf = dojo.html.getComputedStyle - if (dojo.lang.isArray(_5ba)) { - dojo.deprecated('dojo.lfx.html.slideBy(node, array)', 'use dojo.lfx.html.slideBy(node, {top: value, left: value});', '0.5') - _5ba = { top: _5ba[0], left: _5ba[1] } - } - dojo.lang.forEach(_5b9, function(node) { - var top = null - var left = null - var init = (function() { - var _5c4 = node - return function() { - var pos = _5bf(_5c4, 'position') - top = (pos == 'absolute' ? node.offsetTop : parseInt(_5bf(node, 'top')) || 0) - left = (pos == 'absolute' ? node.offsetLeft : parseInt(_5bf(node, 'left')) || 0) - if (!dojo.lang.inArray(['absolute', 'relative'], pos)) { - var ret = dojo.html.abs(_5c4, true) - dojo.html.setStyleAttributes(_5c4, 'position:absolute;top:' + ret.y + 'px;left:' + ret.x + 'px;') - top = ret.y - left = ret.x - } - } - })() - init() - var anim = dojo.lfx.propertyAnimation(node, { - 'top': { start: top, end: top + (_5ba.top || 0) }, - 'left': { start: left, end: left + (_5ba.left || 0) } - }, _5bb, _5bc).connect('beforeBegin', init) - if (_5bd) { - anim.connect('onEnd', function() { - _5bd(_5b9, anim) - }) - } - _5be.push(anim) - }) - return dojo.lfx.combine(_5be) -} -dojo.lfx.html.explode = function(_5c8, _5c9, _5ca, _5cb, _5cc) { - var h = dojo.html - _5c8 = dojo.byId(_5c8) - _5c9 = dojo.byId(_5c9) - var _5ce = h.toCoordinateObject(_5c8, true) - var _5cf = document.createElement('div') - h.copyStyle(_5cf, _5c9) - if (_5c9.explodeClassName) { - _5cf.className = _5c9.explodeClassName - } - with (_5cf.style) { - position = 'absolute' - display = 'none' - var _5d0 = h.getStyle(_5c8, 'background-color') - backgroundColor = _5d0 ? _5d0.toLowerCase() : 'transparent' - backgroundColor = (backgroundColor == 'transparent') ? 'rgb(221, 221, 221)' : backgroundColor - } - dojo.body().appendChild(_5cf) - with (_5c9.style) { - visibility = 'hidden' - display = 'block' - } - var _5d1 = h.toCoordinateObject(_5c9, true) - with (_5c9.style) { - display = 'none' - visibility = 'visible' - } - var _5d2 = { opacity: { start: 0.5, end: 1 } } - dojo.lang.forEach(['height', 'width', 'top', 'left'], function(type) { - _5d2[type] = { start: _5ce[type], end: _5d1[type] } - }) - var anim = new dojo.lfx.propertyAnimation(_5cf, _5d2, _5ca, _5cb, { - 'beforeBegin': function() { - h.setDisplay(_5cf, 'block') - }, 'onEnd': function() { - h.setDisplay(_5c9, 'block') - _5cf.parentNode.removeChild(_5cf) - } - }) - if (_5cc) { - anim.connect('onEnd', function() { - _5cc(_5c9, anim) - }) - } - return anim -} -dojo.lfx.html.implode = function(_5d5, end, _5d7, _5d8, _5d9) { - var h = dojo.html - _5d5 = dojo.byId(_5d5) - end = dojo.byId(end) - var _5db = dojo.html.toCoordinateObject(_5d5, true) - var _5dc = dojo.html.toCoordinateObject(end, true) - var _5dd = document.createElement('div') - dojo.html.copyStyle(_5dd, _5d5) - if (_5d5.explodeClassName) { - _5dd.className = _5d5.explodeClassName - } - dojo.html.setOpacity(_5dd, 0.3) - with (_5dd.style) { - position = 'absolute' - display = 'none' - backgroundColor = h.getStyle(_5d5, 'background-color').toLowerCase() - } - dojo.body().appendChild(_5dd) - var _5de = { opacity: { start: 1, end: 0.5 } } - dojo.lang.forEach(['height', 'width', 'top', 'left'], function(type) { - _5de[type] = { start: _5db[type], end: _5dc[type] } - }) - var anim = new dojo.lfx.propertyAnimation(_5dd, _5de, _5d7, _5d8, { - 'beforeBegin': function() { - dojo.html.hide(_5d5) - dojo.html.show(_5dd) - }, 'onEnd': function() { - _5dd.parentNode.removeChild(_5dd) - } - }) - if (_5d9) { - anim.connect('onEnd', function() { - _5d9(_5d5, anim) - }) - } - return anim -} -dojo.lfx.html.highlight = function(_5e1, _5e2, _5e3, _5e4, _5e5) { - _5e1 = dojo.lfx.html._byId(_5e1) - var _5e6 = [] - dojo.lang.forEach(_5e1, function(node) { - var _5e8 = dojo.html.getBackgroundColor(node) - var bg = dojo.html.getStyle(node, 'background-color').toLowerCase() - var _5ea = dojo.html.getStyle(node, 'background-image') - var _5eb = (bg == 'transparent' || bg == 'rgba(0, 0, 0, 0)') - while (_5e8.length > 3) { - _5e8.pop() - } - var rgb = new dojo.gfx.color.Color(_5e2) - var _5ed = new dojo.gfx.color.Color(_5e8) - var anim = dojo.lfx.propertyAnimation(node, { 'background-color': { start: rgb, end: _5ed } }, _5e3, _5e4, { - 'beforeBegin': function() { - if (_5ea) { - node.style.backgroundImage = 'none' - } - node.style.backgroundColor = 'rgb(' + rgb.toRgb().join(',') + ')' - }, 'onEnd': function() { - if (_5ea) { - node.style.backgroundImage = _5ea - } - if (_5eb) { - node.style.backgroundColor = 'transparent' - } - if (_5e5) { - _5e5(node, anim) - } - } - }) - _5e6.push(anim) - }) - return dojo.lfx.combine(_5e6) -} -dojo.lfx.html.unhighlight = function(_5ef, _5f0, _5f1, _5f2, _5f3) { - _5ef = dojo.lfx.html._byId(_5ef) - var _5f4 = [] - dojo.lang.forEach(_5ef, function(node) { - var _5f6 = new dojo.gfx.color.Color(dojo.html.getBackgroundColor(node)) - var rgb = new dojo.gfx.color.Color(_5f0) - var _5f8 = dojo.html.getStyle(node, 'background-image') - var anim = dojo.lfx.propertyAnimation(node, { 'background-color': { start: _5f6, end: rgb } }, _5f1, _5f2, { - 'beforeBegin': function() { - if (_5f8) { - node.style.backgroundImage = 'none' - } - node.style.backgroundColor = 'rgb(' + _5f6.toRgb().join(',') + ')' - }, 'onEnd': function() { - if (_5f3) { - _5f3(node, anim) - } - } - }) - _5f4.push(anim) - }) - return dojo.lfx.combine(_5f4) -} -dojo.lang.mixin(dojo.lfx, dojo.lfx.html) -dojo.kwCompoundRequire({ browser: ['dojo.lfx.html'], dashboard: ['dojo.lfx.html'] }) -dojo.provide('dojo.lfx.*') + MARGIN_BOX: 'margin-box', BORDER_BOX: 'border-box', PADDING_BOX: 'padding-box', CONTENT_BOX: 'content-box' +}; +dojo.html.getAbsolutePosition = dojo.html.abs = function (node, _4f0, _4f1) { + node = dojo.byId(node, node.ownerDocument); + var ret = {x: 0, y: 0}; + var bs = dojo.html.boxSizing; + if (!_4f1) { + _4f1 = bs.CONTENT_BOX + } + var _4f4 = 2; + var _4f5; + switch (_4f1) { + case bs.MARGIN_BOX: + _4f5 = 3; + break; + case bs.BORDER_BOX: + _4f5 = 2; + break; + case bs.PADDING_BOX: + default: + _4f5 = 1; + break; + case bs.CONTENT_BOX: + _4f5 = 0; + break + } + var h = dojo.render.html; + var db = document['body'] || document['documentElement']; + if (h.ie) { + with (node.getBoundingClientRect()) { + ret.x = left - 2; + ret.y = top - 2 + } + } else { + if (document.getBoxObjectFor) { + _4f4 = 1; + try { + var bo = document.getBoxObjectFor(node); + ret.x = bo.x - dojo.html.sumAncestorProperties(node, 'scrollLeft'); + ret.y = bo.y - dojo.html.sumAncestorProperties(node, 'scrollTop') + } catch (e) { + } + } else { + if (node['offsetParent']) { + var _4f9; + if ((h.safari) && (node.style.getPropertyValue('position') == 'absolute') && (node.parentNode == db)) { + _4f9 = db + } else { + _4f9 = db.parentNode + } + if (node.parentNode != db) { + var nd = node; + if (dojo.render.html.opera) { + nd = db + } + ret.x -= dojo.html.sumAncestorProperties(nd, 'scrollLeft'); + ret.y -= dojo.html.sumAncestorProperties(nd, 'scrollTop') + } + var _4fb = node; + do { + var n = _4fb['offsetLeft']; + if (!h.opera || n > 0) { + ret.x += isNaN(n) ? 0 : n + } + var m = _4fb['offsetTop']; + ret.y += isNaN(m) ? 0 : m; + _4fb = _4fb.offsetParent + } while ((_4fb != _4f9) && (_4fb != null)) + } else { + if (node['x'] && node['y']) { + ret.x += isNaN(node.x) ? 0 : node.x; + ret.y += isNaN(node.y) ? 0 : node.y + } + } + } + } + if (_4f0) { + var _4fe = dojo.html.getScroll(); + ret.y += _4fe.top; + ret.x += _4fe.left + } + var _4ff = [dojo.html.getPaddingExtent, dojo.html.getBorderExtent, dojo.html.getMarginExtent]; + if (_4f4 > _4f5) { + for (var i = _4f5; i < _4f4; ++i) { + ret.y += _4ff[i](node, 'top'); + ret.x += _4ff[i](node, 'left') + } + } else { + if (_4f4 < _4f5) { + for (var i = _4f5; i > _4f4; --i) { + ret.y -= _4ff[i - 1](node, 'top'); + ret.x -= _4ff[i - 1](node, 'left') + } + } + } + ret.top = ret.y; + ret.left = ret.x; + return ret +}; +dojo.html.isPositionAbsolute = function (node) { + return (dojo.html.getComputedStyle(node, 'position') == 'absolute') +}; +dojo.html._sumPixelValues = function (node, _503, _504) { + var _505 = 0; + for (var x = 0; x < _503.length; x++) { + _505 += dojo.html.getPixelValue(node, _503[x], _504) + } + return _505 +}; +dojo.html.getMargin = function (node) { + return { + width: dojo.html._sumPixelValues(node, ['margin-left', 'margin-right'], (dojo.html.getComputedStyle(node, 'position') + == 'absolute')), + height: dojo.html._sumPixelValues(node, ['margin-top', 'margin-bottom'], (dojo.html.getComputedStyle(node, 'position') + == 'absolute')) + } +}; +dojo.html.getBorder = function (node) { + return { + width: dojo.html.getBorderExtent(node, 'left') + dojo.html.getBorderExtent(node, 'right'), + height: dojo.html.getBorderExtent(node, 'top') + dojo.html.getBorderExtent(node, 'bottom') + } +}; +dojo.html.getBorderExtent = function (node, side) { + return (dojo.html.getStyle(node, 'border-' + side + '-style') == 'none' ? 0 : dojo.html.getPixelValue(node, 'border-' + + side + + '-width')) +}; +dojo.html.getMarginExtent = function (node, side) { + return dojo.html._sumPixelValues(node, ['margin-' + side], dojo.html.isPositionAbsolute(node)) +}; +dojo.html.getPaddingExtent = function (node, side) { + return dojo.html._sumPixelValues(node, ['padding-' + side], true) +}; +dojo.html.getPadding = function (node) { + return { + width: dojo.html._sumPixelValues(node, ['padding-left', 'padding-right'], true), + height: dojo.html._sumPixelValues(node, ['padding-top', 'padding-bottom'], true) + } +}; +dojo.html.getPadBorder = function (node) { + var pad = dojo.html.getPadding(node); + var _512 = dojo.html.getBorder(node); + return {width: pad.width + _512.width, height: pad.height + _512.height} +}; +dojo.html.getBoxSizing = function (node) { + var h = dojo.render.html; + var bs = dojo.html.boxSizing; + if (((h.ie) || (h.opera)) && node.nodeName.toLowerCase() != 'img') { + var cm = document['compatMode']; + if ((cm == 'BackCompat') || (cm == 'QuirksMode')) { + return bs.BORDER_BOX + } else { + return bs.CONTENT_BOX + } + } else { + if (arguments.length == 0) { + node = document.documentElement + } + var _517; + if (!h.ie) { + _517 = dojo.html.getStyle(node, '-moz-box-sizing'); + if (!_517) { + _517 = dojo.html.getStyle(node, 'box-sizing') + } + } + return (_517 ? _517 : bs.CONTENT_BOX) + } +}; +dojo.html.isBorderBox = function (node) { + return (dojo.html.getBoxSizing(node) == dojo.html.boxSizing.BORDER_BOX) +}; +dojo.html.getBorderBox = function (node) { + node = dojo.byId(node); + return {width: node.offsetWidth, height: node.offsetHeight} +}; +dojo.html.getPaddingBox = function (node) { + var box = dojo.html.getBorderBox(node); + var _51c = dojo.html.getBorder(node); + return {width: box.width - _51c.width, height: box.height - _51c.height} +}; +dojo.html.getContentBox = function (node) { + node = dojo.byId(node); + var _51e = dojo.html.getPadBorder(node); + return {width: node.offsetWidth - _51e.width, height: node.offsetHeight - _51e.height} +}; +dojo.html.setContentBox = function (node, args) { + node = dojo.byId(node); + var _521 = 0; + var _522 = 0; + var isbb = dojo.html.isBorderBox(node); + var _524 = (isbb ? dojo.html.getPadBorder(node) : {width: 0, height: 0}); + var ret = {}; + if (typeof args.width != 'undefined') { + _521 = args.width + _524.width; + ret.width = dojo.html.setPositivePixelValue(node, 'width', _521) + } + if (typeof args.height != 'undefined') { + _522 = args.height + _524.height; + ret.height = dojo.html.setPositivePixelValue(node, 'height', _522) + } + return ret +}; +dojo.html.getMarginBox = function (node) { + var _527 = dojo.html.getBorderBox(node); + var _528 = dojo.html.getMargin(node); + return {width: _527.width + _528.width, height: _527.height + _528.height} +}; +dojo.html.setMarginBox = function (node, args) { + node = dojo.byId(node); + var _52b = 0; + var _52c = 0; + var isbb = dojo.html.isBorderBox(node); + var _52e = (!isbb ? dojo.html.getPadBorder(node) : {width: 0, height: 0}); + var _52f = dojo.html.getMargin(node); + var ret = {}; + if (typeof args.width != 'undefined') { + _52b = args.width - _52e.width; + _52b -= _52f.width; + ret.width = dojo.html.setPositivePixelValue(node, 'width', _52b) + } + if (typeof args.height != 'undefined') { + _52c = args.height - _52e.height; + _52c -= _52f.height; + ret.height = dojo.html.setPositivePixelValue(node, 'height', _52c) + } + return ret +}; +dojo.html.getElementBox = function (node, type) { + var bs = dojo.html.boxSizing; + switch (type) { + case bs.MARGIN_BOX: + return dojo.html.getMarginBox(node); + case bs.BORDER_BOX: + return dojo.html.getBorderBox(node); + case bs.PADDING_BOX: + return dojo.html.getPaddingBox(node); + case bs.CONTENT_BOX: + default: + return dojo.html.getContentBox(node) + } +}; +dojo.html.toCoordinateObject = dojo.html.toCoordinateArray = function (_534, _535, _536) { + if (_534 instanceof Array || typeof _534 == 'array') { + dojo.deprecated('dojo.html.toCoordinateArray', 'use dojo.html.toCoordinateObject({left: , top: , width: , height: }) instead', '0.5'); + while (_534.length < 4) { + _534.push(0) + } + while (_534.length > 4) { + _534.pop() + } + var ret = {left: _534[0], top: _534[1], width: _534[2], height: _534[3]} + } else { + if (!_534.nodeType && !(_534 instanceof String || typeof _534 == 'string') && ('width' + in _534 + || 'height' + in _534 + || 'left' + in _534 + || 'x' + in _534 + || 'top' + in _534 + || 'y' + in _534)) { + var ret = { + left: _534.left || _534.x || 0, top: _534.top || _534.y || 0, width: _534.width || 0, height: _534.height || 0 + } + } else { + var node = dojo.byId(_534); + var pos = dojo.html.abs(node, _535, _536); + var _53a = dojo.html.getMarginBox(node); + var ret = {left: pos.left, top: pos.top, width: _53a.width, height: _53a.height} + } + } + ret.x = ret.left; + ret.y = ret.top; + return ret +}; +dojo.html.setMarginBoxWidth = dojo.html.setOuterWidth = function (node, _53c) { + return dojo.html._callDeprecated('setMarginBoxWidth', 'setMarginBox', arguments, 'width') +}; +dojo.html.setMarginBoxHeight = dojo.html.setOuterHeight = function () { + return dojo.html._callDeprecated('setMarginBoxHeight', 'setMarginBox', arguments, 'height') +}; +dojo.html.getMarginBoxWidth = dojo.html.getOuterWidth = function () { + return dojo.html._callDeprecated('getMarginBoxWidth', 'getMarginBox', arguments, null, 'width') +}; +dojo.html.getMarginBoxHeight = dojo.html.getOuterHeight = function () { + return dojo.html._callDeprecated('getMarginBoxHeight', 'getMarginBox', arguments, null, 'height') +}; +dojo.html.getTotalOffset = function (node, type, _53f) { + return dojo.html._callDeprecated('getTotalOffset', 'getAbsolutePosition', arguments, null, type) +}; +dojo.html.getAbsoluteX = function (node, _541) { + return dojo.html._callDeprecated('getAbsoluteX', 'getAbsolutePosition', arguments, null, 'x') +}; +dojo.html.getAbsoluteY = function (node, _543) { + return dojo.html._callDeprecated('getAbsoluteY', 'getAbsolutePosition', arguments, null, 'y') +}; +dojo.html.totalOffsetLeft = function (node, _545) { + return dojo.html._callDeprecated('totalOffsetLeft', 'getAbsolutePosition', arguments, null, 'left') +}; +dojo.html.totalOffsetTop = function (node, _547) { + return dojo.html._callDeprecated('totalOffsetTop', 'getAbsolutePosition', arguments, null, 'top') +}; +dojo.html.getMarginWidth = function (node) { + return dojo.html._callDeprecated('getMarginWidth', 'getMargin', arguments, null, 'width') +}; +dojo.html.getMarginHeight = function (node) { + return dojo.html._callDeprecated('getMarginHeight', 'getMargin', arguments, null, 'height') +}; +dojo.html.getBorderWidth = function (node) { + return dojo.html._callDeprecated('getBorderWidth', 'getBorder', arguments, null, 'width') +}; +dojo.html.getBorderHeight = function (node) { + return dojo.html._callDeprecated('getBorderHeight', 'getBorder', arguments, null, 'height') +}; +dojo.html.getPaddingWidth = function (node) { + return dojo.html._callDeprecated('getPaddingWidth', 'getPadding', arguments, null, 'width') +}; +dojo.html.getPaddingHeight = function (node) { + return dojo.html._callDeprecated('getPaddingHeight', 'getPadding', arguments, null, 'height') +}; +dojo.html.getPadBorderWidth = function (node) { + return dojo.html._callDeprecated('getPadBorderWidth', 'getPadBorder', arguments, null, 'width') +}; +dojo.html.getPadBorderHeight = function (node) { + return dojo.html._callDeprecated('getPadBorderHeight', 'getPadBorder', arguments, null, 'height') +}; +dojo.html.getBorderBoxWidth = dojo.html.getInnerWidth = function () { + return dojo.html._callDeprecated('getBorderBoxWidth', 'getBorderBox', arguments, null, 'width') +}; +dojo.html.getBorderBoxHeight = dojo.html.getInnerHeight = function () { + return dojo.html._callDeprecated('getBorderBoxHeight', 'getBorderBox', arguments, null, 'height') +}; +dojo.html.getContentBoxWidth = dojo.html.getContentWidth = function () { + return dojo.html._callDeprecated('getContentBoxWidth', 'getContentBox', arguments, null, 'width') +}; +dojo.html.getContentBoxHeight = dojo.html.getContentHeight = function () { + return dojo.html._callDeprecated('getContentBoxHeight', 'getContentBox', arguments, null, 'height') +}; +dojo.html.setContentBoxWidth = dojo.html.setContentWidth = function (node, _551) { + return dojo.html._callDeprecated('setContentBoxWidth', 'setContentBox', arguments, 'width') +}; +dojo.html.setContentBoxHeight = dojo.html.setContentHeight = function (node, _553) { + return dojo.html._callDeprecated('setContentBoxHeight', 'setContentBox', arguments, 'height') +}; +dojo.provide('dojo.lfx.html'); +dojo.lfx.html._byId = function (_554) { + if (!_554) { + return [] + } + if (dojo.lang.isArrayLike(_554)) { + if (!_554.alreadyChecked) { + var n = []; + dojo.lang.forEach(_554, function (node) { + n.push(dojo.byId(node)) + }); + n.alreadyChecked = true; + return n + } else { + return _554 + } + } else { + var n = []; + n.push(dojo.byId(_554)); + n.alreadyChecked = true; + return n + } +}; +dojo.lfx.html.propertyAnimation = function (_557, _558, _559, _55a, _55b) { + _557 = dojo.lfx.html._byId(_557); + var _55c = {'propertyMap': _558, 'nodes': _557, 'duration': _559, 'easing': _55a || dojo.lfx.easeDefault}; + var _55d = function (args) { + if (args.nodes.length == 1) { + var pm = args.propertyMap; + if (!dojo.lang.isArray(args.propertyMap)) { + var parr = []; + for (var _561 in pm) { + pm[_561].property = _561; + parr.push(pm[_561]) + } + pm = args.propertyMap = parr + } + dojo.lang.forEach(pm, function (prop) { + if (dj_undef('start', prop)) { + if (prop.property != 'opacity') { + prop.start = parseInt(dojo.html.getComputedStyle(args.nodes[0], prop.property)) + } else { + prop.start = dojo.html.getOpacity(args.nodes[0]) + } + } + }) + } + }; + var _563 = function (_564) { + var _565 = []; + dojo.lang.forEach(_564, function (c) { + _565.push(Math.round(c)) + }); + return _565 + }; + var _567 = function (n, _569) { + n = dojo.byId(n); + if (!n || !n.style) { + return + } + for (var s in _569) { + try { + if (s == 'opacity') { + dojo.html.setOpacity(n, _569[s]) + } else { + n.style[s] = _569[s] + } + } catch (e) { + dojo.debug(e) + } + } + }; + var _56b = function (_56c) { + this._properties = _56c; + this.diffs = new Array(_56c.length); + dojo.lang.forEach(_56c, function (prop, i) { + if (dojo.lang.isFunction(prop.start)) { + prop.start = prop.start(prop, i) + } + if (dojo.lang.isFunction(prop.end)) { + prop.end = prop.end(prop, i) + } + if (dojo.lang.isArray(prop.start)) { + this.diffs[i] = null + } else { + if (prop.start instanceof dojo.gfx.color.Color) { + prop.startRgb = prop.start.toRgb(); + prop.endRgb = prop.end.toRgb() + } else { + this.diffs[i] = prop.end - prop.start + } + } + }, this); + this.getValue = function (n) { + var ret = {}; + dojo.lang.forEach(this._properties, function (prop, i) { + var _573 = null; + if (dojo.lang.isArray(prop.start)) { + } else { + if (prop.start instanceof dojo.gfx.color.Color) { + _573 = (prop.units || 'rgb') + '('; + for (var j = 0; j < prop.startRgb.length; j++) { + _573 += Math.round(((prop.endRgb[j] - prop.startRgb[j]) * n) + prop.startRgb[j]) + (j + < prop.startRgb.length + - 1 ? ',' : '') + } + _573 += ')' + } else { + _573 = ((this.diffs[i]) * n) + prop.start + (prop.property != 'opacity' ? prop.units || 'px' : '') + } + } + ret[dojo.html.toCamelCase(prop.property)] = _573 + }, this); + return ret + } + }; + var anim = new dojo.lfx.Animation({ + beforeBegin: function () { + _55d(_55c); + anim.curve = new _56b(_55c.propertyMap) + }, onAnimate: function (_576) { + dojo.lang.forEach(_55c.nodes, function (node) { + _567(node, _576) + }) + } + }, _55c.duration, null, _55c.easing); + if (_55b) { + for (var x in _55b) { + if (dojo.lang.isFunction(_55b[x])) { + anim.connect(x, anim, _55b[x]) + } + } + } + return anim +}; +dojo.lfx.html._makeFadeable = function (_579) { + var _57a = function (node) { + if (dojo.render.html.ie) { + if ((node.style.zoom.length == 0) && (dojo.html.getStyle(node, 'zoom') == 'normal')) { + node.style.zoom = '1' + } + if ((node.style.width.length == 0) && (dojo.html.getStyle(node, 'width') == 'auto')) { + node.style.width = 'auto' + } + } + }; + if (dojo.lang.isArrayLike(_579)) { + dojo.lang.forEach(_579, _57a) + } else { + _57a(_579) + } +}; +dojo.lfx.html.fade = function (_57c, _57d, _57e, _57f, _580) { + _57c = dojo.lfx.html._byId(_57c); + var _581 = {property: 'opacity'}; + if (!dj_undef('start', _57d)) { + _581.start = _57d.start + } else { + _581.start = function () { + return dojo.html.getOpacity(_57c[0]) + } + } + if (!dj_undef('end', _57d)) { + _581.end = _57d.end + } else { + dojo.raise('dojo.lfx.html.fade needs an end value') + } + var anim = dojo.lfx.propertyAnimation(_57c, [_581], _57e, _57f); + anim.connect('beforeBegin', function () { + dojo.lfx.html._makeFadeable(_57c) + }); + if (_580) { + anim.connect('onEnd', function () { + _580(_57c, anim) + }) + } + return anim +}; +dojo.lfx.html.fadeIn = function (_583, _584, _585, _586) { + return dojo.lfx.html.fade(_583, {end: 1}, _584, _585, _586) +}; +dojo.lfx.html.fadeOut = function (_587, _588, _589, _58a) { + return dojo.lfx.html.fade(_587, {end: 0}, _588, _589, _58a) +}; +dojo.lfx.html.fadeShow = function (_58b, _58c, _58d, _58e) { + _58b = dojo.lfx.html._byId(_58b); + dojo.lang.forEach(_58b, function (node) { + dojo.html.setOpacity(node, 0) + }); + var anim = dojo.lfx.html.fadeIn(_58b, _58c, _58d, _58e); + anim.connect('beforeBegin', function () { + if (dojo.lang.isArrayLike(_58b)) { + dojo.lang.forEach(_58b, dojo.html.show) + } else { + dojo.html.show(_58b) + } + }); + return anim +}; +dojo.lfx.html.fadeHide = function (_591, _592, _593, _594) { + var anim = dojo.lfx.html.fadeOut(_591, _592, _593, function () { + if (dojo.lang.isArrayLike(_591)) { + dojo.lang.forEach(_591, dojo.html.hide) + } else { + dojo.html.hide(_591) + } + if (_594) { + _594(_591, anim) + } + }); + return anim +}; +dojo.lfx.html.wipeIn = function (_596, _597, _598, _599) { + _596 = dojo.lfx.html._byId(_596); + var _59a = []; + dojo.lang.forEach(_596, function (node) { + var _59c = {}; + var _59d, _59e, _59f; + with (node.style) { + _59d = top; + _59e = left; + _59f = position; + top = '-9999px'; + left = '-9999px'; + position = 'absolute'; + display = '' + } + var _5a0 = dojo.html.getBorderBox(node).height; + with (node.style) { + top = _59d; + left = _59e; + position = _59f; + display = 'none' + } + var anim = dojo.lfx.propertyAnimation(node, { + 'height': { + start: 1, end: function () { + return _5a0 + } + } + }, _597, _598); + anim.connect('beforeBegin', function () { + _59c.overflow = node.style.overflow; + _59c.height = node.style.height; + with (node.style) { + overflow = 'hidden'; + height = '1px' + } + dojo.html.show(node) + }); + anim.connect('onEnd', function () { + with (node.style) { + overflow = _59c.overflow; + height = _59c.height + } + if (_599) { + _599(node, anim) + } + }); + _59a.push(anim) + }); + return dojo.lfx.combine(_59a) +}; +dojo.lfx.html.wipeOut = function (_5a2, _5a3, _5a4, _5a5) { + _5a2 = dojo.lfx.html._byId(_5a2); + var _5a6 = []; + dojo.lang.forEach(_5a2, function (node) { + var _5a8 = {}; + var anim = dojo.lfx.propertyAnimation(node, { + 'height': { + start: function () { + return dojo.html.getContentBox(node).height + }, end: 1 + } + }, _5a3, _5a4, { + 'beforeBegin': function () { + _5a8.overflow = node.style.overflow; + _5a8.height = node.style.height; + with (node.style) { + overflow = 'hidden' + } + dojo.html.show(node) + }, 'onEnd': function () { + dojo.html.hide(node); + with (node.style) { + overflow = _5a8.overflow; + height = _5a8.height + } + if (_5a5) { + _5a5(node, anim) + } + } + }); + _5a6.push(anim) + }); + return dojo.lfx.combine(_5a6) +}; +dojo.lfx.html.slideTo = function (_5aa, _5ab, _5ac, _5ad, _5ae) { + _5aa = dojo.lfx.html._byId(_5aa); + var _5af = []; + var _5b0 = dojo.html.getComputedStyle; + if (dojo.lang.isArray(_5ab)) { + dojo.deprecated('dojo.lfx.html.slideTo(node, array)', 'use dojo.lfx.html.slideTo(node, {top: value, left: value});', '0.5'); + _5ab = {top: _5ab[0], left: _5ab[1]} + } + dojo.lang.forEach(_5aa, function (node) { + var top = null; + var left = null; + var init = (function () { + var _5b5 = node; + return function () { + var pos = _5b0(_5b5, 'position'); + top = (pos == 'absolute' ? node.offsetTop : parseInt(_5b0(node, 'top')) || 0); + left = (pos == 'absolute' ? node.offsetLeft : parseInt(_5b0(node, 'left')) || 0); + if (!dojo.lang.inArray(['absolute', 'relative'], pos)) { + var ret = dojo.html.abs(_5b5, true); + dojo.html.setStyleAttributes(_5b5, 'position:absolute;top:' + ret.y + 'px;left:' + ret.x + 'px;'); + top = ret.y; + left = ret.x + } + } + })(); + init(); + var anim = dojo.lfx.propertyAnimation(node, { + 'top': {start: top, end: (_5ab.top || 0)}, 'left': {start: left, end: (_5ab.left || 0)} + }, _5ac, _5ad, {'beforeBegin': init}); + if (_5ae) { + anim.connect('onEnd', function () { + _5ae(_5aa, anim) + }) + } + _5af.push(anim) + }); + return dojo.lfx.combine(_5af) +}; +dojo.lfx.html.slideBy = function (_5b9, _5ba, _5bb, _5bc, _5bd) { + _5b9 = dojo.lfx.html._byId(_5b9); + var _5be = []; + var _5bf = dojo.html.getComputedStyle; + if (dojo.lang.isArray(_5ba)) { + dojo.deprecated('dojo.lfx.html.slideBy(node, array)', 'use dojo.lfx.html.slideBy(node, {top: value, left: value});', '0.5'); + _5ba = {top: _5ba[0], left: _5ba[1]} + } + dojo.lang.forEach(_5b9, function (node) { + var top = null; + var left = null; + var init = (function () { + var _5c4 = node; + return function () { + var pos = _5bf(_5c4, 'position'); + top = (pos == 'absolute' ? node.offsetTop : parseInt(_5bf(node, 'top')) || 0); + left = (pos == 'absolute' ? node.offsetLeft : parseInt(_5bf(node, 'left')) || 0); + if (!dojo.lang.inArray(['absolute', 'relative'], pos)) { + var ret = dojo.html.abs(_5c4, true); + dojo.html.setStyleAttributes(_5c4, 'position:absolute;top:' + ret.y + 'px;left:' + ret.x + 'px;'); + top = ret.y; + left = ret.x + } + } + })(); + init(); + var anim = dojo.lfx.propertyAnimation(node, { + 'top': {start: top, end: top + (_5ba.top || 0)}, 'left': {start: left, end: left + (_5ba.left || 0)} + }, _5bb, _5bc).connect('beforeBegin', init); + if (_5bd) { + anim.connect('onEnd', function () { + _5bd(_5b9, anim) + }) + } + _5be.push(anim) + }); + return dojo.lfx.combine(_5be) +}; +dojo.lfx.html.explode = function (_5c8, _5c9, _5ca, _5cb, _5cc) { + var h = dojo.html; + _5c8 = dojo.byId(_5c8); + _5c9 = dojo.byId(_5c9); + var _5ce = h.toCoordinateObject(_5c8, true); + var _5cf = document.createElement('div'); + h.copyStyle(_5cf, _5c9); + if (_5c9.explodeClassName) { + _5cf.className = _5c9.explodeClassName + } + with (_5cf.style) { + position = 'absolute'; + display = 'none'; + var _5d0 = h.getStyle(_5c8, 'background-color'); + backgroundColor = _5d0 ? _5d0.toLowerCase() : 'transparent'; + backgroundColor = (backgroundColor == 'transparent') ? 'rgb(221, 221, 221)' : backgroundColor + } + dojo.body().appendChild(_5cf); + with (_5c9.style) { + visibility = 'hidden'; + display = 'block' + } + var _5d1 = h.toCoordinateObject(_5c9, true); + with (_5c9.style) { + display = 'none'; + visibility = 'visible' + } + var _5d2 = {opacity: {start: 0.5, end: 1}}; + dojo.lang.forEach(['height', 'width', 'top', 'left'], function (type) { + _5d2[type] = {start: _5ce[type], end: _5d1[type]} + }); + var anim = new dojo.lfx.propertyAnimation(_5cf, _5d2, _5ca, _5cb, { + 'beforeBegin': function () { + h.setDisplay(_5cf, 'block') + }, 'onEnd': function () { + h.setDisplay(_5c9, 'block'); + _5cf.parentNode.removeChild(_5cf) + } + }); + if (_5cc) { + anim.connect('onEnd', function () { + _5cc(_5c9, anim) + }) + } + return anim +}; +dojo.lfx.html.implode = function (_5d5, end, _5d7, _5d8, _5d9) { + var h = dojo.html; + _5d5 = dojo.byId(_5d5); + end = dojo.byId(end); + var _5db = dojo.html.toCoordinateObject(_5d5, true); + var _5dc = dojo.html.toCoordinateObject(end, true); + var _5dd = document.createElement('div'); + dojo.html.copyStyle(_5dd, _5d5); + if (_5d5.explodeClassName) { + _5dd.className = _5d5.explodeClassName + } + dojo.html.setOpacity(_5dd, 0.3); + with (_5dd.style) { + position = 'absolute'; + display = 'none'; + backgroundColor = h.getStyle(_5d5, 'background-color').toLowerCase() + } + dojo.body().appendChild(_5dd); + var _5de = {opacity: {start: 1, end: 0.5}}; + dojo.lang.forEach(['height', 'width', 'top', 'left'], function (type) { + _5de[type] = {start: _5db[type], end: _5dc[type]} + }); + var anim = new dojo.lfx.propertyAnimation(_5dd, _5de, _5d7, _5d8, { + 'beforeBegin': function () { + dojo.html.hide(_5d5); + dojo.html.show(_5dd) + }, 'onEnd': function () { + _5dd.parentNode.removeChild(_5dd) + } + }); + if (_5d9) { + anim.connect('onEnd', function () { + _5d9(_5d5, anim) + }) + } + return anim +}; +dojo.lfx.html.highlight = function (_5e1, _5e2, _5e3, _5e4, _5e5) { + _5e1 = dojo.lfx.html._byId(_5e1); + var _5e6 = []; + dojo.lang.forEach(_5e1, function (node) { + var _5e8 = dojo.html.getBackgroundColor(node); + var bg = dojo.html.getStyle(node, 'background-color').toLowerCase(); + var _5ea = dojo.html.getStyle(node, 'background-image'); + var _5eb = (bg == 'transparent' || bg == 'rgba(0, 0, 0, 0)'); + while (_5e8.length > 3) { + _5e8.pop() + } + var rgb = new dojo.gfx.color.Color(_5e2); + var _5ed = new dojo.gfx.color.Color(_5e8); + var anim = dojo.lfx.propertyAnimation(node, {'background-color': {start: rgb, end: _5ed}}, _5e3, _5e4, { + 'beforeBegin': function () { + if (_5ea) { + node.style.backgroundImage = 'none' + } + node.style.backgroundColor = 'rgb(' + rgb.toRgb().join(',') + ')' + }, 'onEnd': function () { + if (_5ea) { + node.style.backgroundImage = _5ea + } + if (_5eb) { + node.style.backgroundColor = 'transparent' + } + if (_5e5) { + _5e5(node, anim) + } + } + }); + _5e6.push(anim) + }); + return dojo.lfx.combine(_5e6) +}; +dojo.lfx.html.unhighlight = function (_5ef, _5f0, _5f1, _5f2, _5f3) { + _5ef = dojo.lfx.html._byId(_5ef); + var _5f4 = []; + dojo.lang.forEach(_5ef, function (node) { + var _5f6 = new dojo.gfx.color.Color(dojo.html.getBackgroundColor(node)); + var rgb = new dojo.gfx.color.Color(_5f0); + var _5f8 = dojo.html.getStyle(node, 'background-image'); + var anim = dojo.lfx.propertyAnimation(node, {'background-color': {start: _5f6, end: rgb}}, _5f1, _5f2, { + 'beforeBegin': function () { + if (_5f8) { + node.style.backgroundImage = 'none' + } + node.style.backgroundColor = 'rgb(' + _5f6.toRgb().join(',') + ')' + }, 'onEnd': function () { + if (_5f3) { + _5f3(node, anim) + } + } + }); + _5f4.push(anim) + }); + return dojo.lfx.combine(_5f4) +}; +dojo.lang.mixin(dojo.lfx, dojo.lfx.html); +dojo.kwCompoundRequire({browser: ['dojo.lfx.html'], dashboard: ['dojo.lfx.html']}); +dojo.provide('dojo.lfx.*'); diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/accountException.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/accountException.jsp index 5fea13e6..308c7052 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/accountException.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/accountException.jsp @@ -2,43 +2,43 @@ - - accountException - + .error { + padding: 5px; + border: 1px solid #FF0000; + background: url(../images/error.gif) 8px 5px no-repeat #FFEEFF; + padding-left: 30px; + } +
    - ${ message } + ${ message }
    - - - - - - - - - - - - - -
    Account
    Password
     
    + + + + + + + + + + + + + +
    Account
    Password
     
    - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/businessException.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/businessException.jsp index 7cb2e232..54abdded 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/businessException.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/businessException.jsp @@ -2,22 +2,22 @@ - - businessException - + + businessException +
    - ${ message } 返回 + ${ message } 返回
    diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/dispatcher.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/dispatcher.jsp index 9bc1bea5..3372bdc7 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/dispatcher.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/dispatcher.jsp @@ -2,12 +2,12 @@ - - ${ pageContext.request.requestURI } + + ${ pageContext.request.requestURI } 你正在访问 ${ pageContext.request.requestURI }?${ pageContext.request.queryString }. - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/exception.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/exception.jsp index 246fd973..7e581362 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/exception.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/exception.jsp @@ -2,22 +2,22 @@ - - exception - + + exception +
    - ${ message } 返回 + ${ message } 返回
    diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/image.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/image.jsp index 080a5146..b1608a46 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/image.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/image.jsp @@ -2,15 +2,15 @@ - - Insert title here + + Insert title here 刷新 直接访问 + onclick="window.open('../images/winter.jpg'); return false; ">直接访问
    request.getHeader("referer"): ${ header['referer'] } diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/index.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/index.jsp index 8b2052f2..ffc11dfa 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/index.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/index.jsp @@ -1,52 +1,52 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <% - String path = request.getContextPath(); - String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; + String path = request.getContextPath(); + String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; %> - javaee-filter 首页 + javaee-filter 首页

    javaee-filter 首页

    <%out.print("Server Ip:" + basePath);%>

    diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testCacheFilter.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testCacheFilter.jsp index 1adb0f45..77d55892 100644 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testCacheFilter.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testCacheFilter.jsp @@ -2,39 +2,39 @@ - - Insert title here - + a { + color: #0000FF; + } + "); return buffer.toString(); + } + private void calculate() { + pageCount = (recordCount + pageSize - 1) / pageSize; + + firstResult = (pageNum - 1) * pageSize; } public int getPageSize() { diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/logback.xml index f5e516ab..5985be1e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/logback.xml +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/logback.xml @@ -2,45 +2,45 @@ - - - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n - - - - - - - - logs/${FILE_NAME}-all.%d{yyyy-MM-dd}.log - 30 - - - - - 30MB - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n - - - - - - - - - - - - - - - - + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n + + + + + + + + logs/${FILE_NAME}-all.%d{yyyy-MM-dd}.log + 30 + + + + + 30MB + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n + + + + + + + + + + + + + + + + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/messages.properties b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/messages.properties index 3a9590dd..8530dcc4 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/messages.properties +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/messages.properties @@ -1,2 +1,2 @@ -prompt.hello = Hello, "{0}". -prompt.greeting = Nice to meet you. +prompt.hello=Hello, "{0}". +prompt.greeting=Nice to meet you. diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/messages_zh_CN.properties b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/messages_zh_CN.properties index 8ef7827c..1cec3305 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/messages_zh_CN.properties +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/messages_zh_CN.properties @@ -1,2 +1,2 @@ -prompt.hello = \u4f60\u597d, "{0}". -prompt.greeting = \u5f88\u9ad8\u5174\u89c1\u5230\u4f60. +prompt.hello=\u4f60\u597d, "{0}". +prompt.greeting=\u5f88\u9ad8\u5174\u89c1\u5230\u4f60. diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/WEB-INF/web.xml b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/WEB-INF/web.xml index ae92ddac..a403cc53 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/WEB-INF/web.xml +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/WEB-INF/web.xml @@ -1,25 +1,25 @@ - - HelloServlet - /examples/configuration.jsp - - message - welcome to jsp - - 1 - - - HelloServlet - /config - /config.jsp - + + HelloServlet + /examples/configuration.jsp + + message + welcome to jsp + + 1 + + + HelloServlet + /config + /config.jsp + - - - /WEB-INF/views/jsp/index.jsp - + + + /WEB-INF/views/jsp/index.jsp + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_catch.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_catch.jsp index 050092ba..87fa389f 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_catch.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_catch.jsp @@ -1,20 +1,20 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - c:catch 标签实例 + c:catch 标签实例 - <% int x = 5 / 0;%> + <% int x = 5 / 0;%> -

    异常为 : ${catchException}
    - 发生了异常: ${catchException.message}

    +

    异常为 : ${catchException}
    + 发生了异常: ${catchException.message}

    - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_choose.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_choose.jsp index 392ba3a1..898147dc 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_choose.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_choose.jsp @@ -1,23 +1,23 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - c:choose 标签实例 + c:choose 标签实例

    你的工资为 :

    - - 太惨了。 - - - 不错的薪水,还能生活。 - - - 什么都没有。 - + + 太惨了。 + + + 不错的薪水,还能生活。 + + + 什么都没有。 + - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_forEach.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_forEach.jsp index 2562e5c4..57b8421e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_forEach.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_forEach.jsp @@ -1,13 +1,13 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - c:forEach 标签实例 + c:forEach 标签实例 Item

    - + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_forTokens.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_forTokens.jsp index 3d2470b4..046f4c49 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_forTokens.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_forTokens.jsp @@ -1,13 +1,13 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - c:forTokens 标签实例 + c:forTokens 标签实例

    - + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_if.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_if.jsp index 91b904ac..4abf6c52 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_if.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_if.jsp @@ -1,14 +1,14 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - c:if 标签实例 + c:if 标签实例

    我的工资为:

    - + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_import.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_import.jsp index 9abd8c64..98562585 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_import.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_import.jsp @@ -1,12 +1,12 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - c:import 标签实例 + c:import 标签实例 - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_out.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_out.jsp index 4318e366..ab88b66f 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_out.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_out.jsp @@ -1,15 +1,15 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - c:out 标签实例 + c:out 标签实例 - <c:out>实例 + <c:out>实例

    <c:out> 实例

    @@ -17,4 +17,4 @@
    使用的表达式结果为null,则输出该默认值
    - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_param.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_param.jsp index 78fb0860..95438a73 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_param.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_param.jsp @@ -1,15 +1,15 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - c:forTokens 标签实例 + c:forTokens 标签实例

    <c:param> 实例

    - - + + <%-- diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_redirect.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_redirect.jsp index addaa6f8..fbfbf835 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_redirect.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_redirect.jsp @@ -1,11 +1,11 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - c:redirect 标签实例 + c:redirect 标签实例 - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_remove.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_remove.jsp index d1eb3d92..5d53732b 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_remove.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_remove.jsp @@ -1,9 +1,9 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - c:remove 标签实例 + c:remove 标签实例 @@ -11,4 +11,4 @@

    删除 salary 变量后的值:

    - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_set.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_set.jsp index 66f5f045..e78c3bca 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_set.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_set.jsp @@ -1,9 +1,9 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - c:set 标签实例 + c:set 标签实例 diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_url.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_url.jsp index 34fee31e..d0e892cf 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_url.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_url.jsp @@ -1,14 +1,14 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - c:url 标签实例 + c:url 标签实例

    <c:url>实例 Demo

    "> - 这个链接通过 <c:url> 标签生成。 + 这个链接通过 <c:url> 标签生成。 - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_bundle.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_bundle.jsp index 80920b0c..b6e3211d 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_bundle.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_bundle.jsp @@ -1,17 +1,17 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> - fmt:bundle 标签 + fmt:bundle 标签 -
    -
    -
    +
    +
    +
    diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_formatDate.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_formatDate.jsp index a78e01af..958f062c 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_formatDate.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_formatDate.jsp @@ -1,33 +1,33 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> - fmt:dateNumber 标签 + fmt:dateNumber 标签

    日期格式化:

    日期格式化 (1):

    + value="${now}"/>

    日期格式化 (2):

    + value="${now}"/>

    日期格式化 (3):

    + value="${now}"/>

    日期格式化 (4):

    + dateStyle="short" timeStyle="short" + value="${now}"/>

    日期格式化 (5):

    + dateStyle="medium" timeStyle="medium" + value="${now}"/>

    日期格式化 (6):

    + dateStyle="long" timeStyle="long" + value="${now}"/>

    日期格式化 (7):

    + value="${now}"/>

    - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_formatNumber.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_formatNumber.jsp index 55c3101b..529449bb 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_formatNumber.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_formatNumber.jsp @@ -1,33 +1,33 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> - fmt:formatNumber 标签 + fmt:formatNumber 标签

    数字格式化:

    格式化数字 (1):

    + type="currency"/>

    格式化数字 (2):

    + maxIntegerDigits="3" value="${balance}"/>

    格式化数字 (3):

    + maxFractionDigits="3" value="${balance}"/>

    格式化数字 (4):

    + groupingUsed="false" value="${balance}"/>

    格式化数字 (5):

    + maxIntegerDigits="3" value="${balance}"/>

    格式化数字 (6):

    + minFractionDigits="10" value="${balance}"/>

    格式化数字 (7):

    + maxIntegerDigits="3" value="${balance}"/>

    格式化数字 (8):

    + pattern="###.###E0" value="${balance}"/>

    美元 : - -

    + +

    - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_message.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_message.jsp index 3c7b5f5b..cac1b759 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_message.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_message.jsp @@ -1,25 +1,25 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> - fmt:message 标签 + fmt:message 标签 -
    -
    -
    +
    +
    +
    -
    -
    -
    +
    +
    +
    diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_parseDate.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_parseDate.jsp index 98192b0c..a6d5241c 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_parseDate.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_parseDate.jsp @@ -1,19 +1,19 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> - fmt:parseDate 标签 + fmt:parseDate 标签

    日期解析:

    + pattern="dd-MM-yyyy"/>

    解析后的日期为:

    - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_parseNumber.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_parseNumber.jsp index 96b84388..4918be0d 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_parseNumber.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_parseNumber.jsp @@ -1,10 +1,10 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> - fmt:parseNumber 标签 + fmt:parseNumber 标签

    数字解析:

    @@ -13,8 +13,8 @@

    数字解析 (1) :

    + type="number" value="${balance}"/>

    数字解析 (2) :

    - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_requestEncoding.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_requestEncoding.jsp index 201b7e2f..e81f2b3f 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_requestEncoding.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_requestEncoding.jsp @@ -1,10 +1,10 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> - fmt:message 标签 + fmt:message 标签 diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_setLocale.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_setLocale.jsp index 832995cc..6941dd40 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_setLocale.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_setLocale.jsp @@ -1,25 +1,25 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> - fmt:setLocale 标签 + fmt:setLocale 标签 -
    -
    -
    +
    +
    +
    -
    -
    -
    +
    +
    +
    diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_setTimeZone.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_setTimeZone.jsp index 6ee374dc..e05a7959 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_setTimeZone.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_setTimeZone.jsp @@ -1,18 +1,18 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> - fmt:setTimeZone 标签 + fmt:setTimeZone 标签

    当前时区时间:

    + type="both" timeStyle="long" dateStyle="long"/>

    修改为 GMT-8 时区:

    Date in Changed Zone:

    + type="both" timeStyle="long" dateStyle="long"/>

    - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_timeZone.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_timeZone.jsp index f03a9053..adb6f13b 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_timeZone.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_timeZone.jsp @@ -1,42 +1,42 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> - fmt:timeZone 标签 + fmt:timeZone 标签 - - - + + + - - - - - - + + + + + +
    -

    - - Formatting: - - - -

    -
    +

    + + Formatting: + + + +

    +
    - - - - - -
    + + + + + +
    - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_contains.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_contains.jsp index 33bb67f7..9ec50047 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_contains.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_contains.jsp @@ -1,10 +1,10 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - fn:contains 示例 + fn:contains 示例 @@ -13,12 +13,12 @@

    找到 china

    - + - +

    找到 CHINA

    - + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_containsIgnoreCase.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_containsIgnoreCase.jsp index d80a5b2e..512104b7 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_containsIgnoreCase.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_containsIgnoreCase.jsp @@ -1,10 +1,10 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - 使用 JSTL 函数 + 使用 JSTL 函数 @@ -13,12 +13,12 @@

    找到 china

    - + - +

    找到 CHINA

    - + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_endsWith.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_endsWith.jsp index 5b04d5cc..8ec96d0e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_endsWith.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_endsWith.jsp @@ -1,10 +1,10 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - 使用 JSTL 函数 + 使用 JSTL 函数 @@ -13,12 +13,12 @@

    字符串以 123 结尾

    - + - +

    字符串以 china 结尾

    - + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_escapeXml.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_escapeXml.jsp index 33651ba9..d9509b06 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_escapeXml.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_escapeXml.jsp @@ -1,10 +1,10 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - Using JSTL Functions + Using JSTL Functions diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_indexOf.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_indexOf.jsp index 87f0ebf3..d4896f7a 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_indexOf.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_indexOf.jsp @@ -1,10 +1,10 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - Using JSTL Functions + Using JSTL Functions diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_join.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_join.jsp index 8860a092..aba2d96e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_join.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_join.jsp @@ -1,10 +1,10 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - 使用 JSTL 函数 + 使用 JSTL 函数 diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_length.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_length.jsp index 28b6f0f8..0361946a 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_length.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_length.jsp @@ -1,10 +1,10 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - 使用 JSTL 函数 + 使用 JSTL 函数 diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_replace.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_replace.jsp index 91f7aa2a..e5931747 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_replace.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_replace.jsp @@ -1,10 +1,10 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - 使用 JSTL 函数 + 使用 JSTL 函数 diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_split.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_split.jsp index cba38eca..5ea82273 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_split.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_split.jsp @@ -1,10 +1,10 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - 使用 JSTL 函数 + 使用 JSTL 函数 diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_startsWith.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_startsWith.jsp index 61fa5d0f..9879a840 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_startsWith.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_startsWith.jsp @@ -1,20 +1,20 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - 使用 JSTL 函数 + 使用 JSTL 函数 -

    字符串以 Google 开头

    +

    字符串以 Google 开头


    -

    字符串以 China 开头

    +

    字符串以 China 开头

    diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substring.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substring.jsp index 20a2f441..55f74fc4 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substring.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substring.jsp @@ -1,10 +1,10 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - 使用 JSTL 函数 + 使用 JSTL 函数 diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substringAfter.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substringAfter.jsp index 33e92370..0ec7b3f4 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substringAfter.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substringAfter.jsp @@ -1,10 +1,10 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - 使用 JSTL 函数 + 使用 JSTL 函数 diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substringBefore.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substringBefore.jsp index aec080dd..c6967814 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substringBefore.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substringBefore.jsp @@ -1,10 +1,10 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - 使用 JSTL 函数 + 使用 JSTL 函数 diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_toLowerCase.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_toLowerCase.jsp index 4d3fdc21..804d856d 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_toLowerCase.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_toLowerCase.jsp @@ -1,10 +1,10 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - 使用 JSTL 函数 + 使用 JSTL 函数 diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_toUpperCase.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_toUpperCase.jsp index c2f5a9cb..3ea5988a 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_toUpperCase.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_toUpperCase.jsp @@ -1,10 +1,10 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - 使用 JSTL 函数 + 使用 JSTL 函数 diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_trim.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_trim.jsp index 38a372d5..1ac0e961 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_trim.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_trim.jsp @@ -1,10 +1,10 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - 使用 JSTL 函数 + 使用 JSTL 函数 diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_dateParam.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_dateParam.jsp index 21ee5290..a24b0770 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_dateParam.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_dateParam.jsp @@ -1,5 +1,5 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ page import="java.util.Date" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %> @@ -8,45 +8,45 @@ - sql:dataParam 示例 + sql:dataParam 示例 + url="jdbc:mysql://localhost/test" + user="root" password="root"/> <% - Date DoB = new Date("2001/12/16"); - int studentId = 100; + Date DoB = new Date("2001/12/16"); + int studentId = 100; %> - UPDATE Students SET dob = ? WHERE Id = ? - - + UPDATE Students SET dob = ? WHERE Id = ? + + - SELECT * from Students; + SELECT * from Students; - - - - - - - - - - - - - - + + + + + + + + + + + + + +
    Emp IDFirst NameLast NameDoB
    Emp IDFirst NameLast NameDoB
    - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_param.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_param.jsp index e3e21cca..4c53b6d6 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_param.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_param.jsp @@ -1,5 +1,5 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %> @@ -7,41 +7,41 @@ - sql:param 示例 + sql:param 示例 + url="jdbc:mysql://localhost/test" + user="root" password="root"/> - DELETE FROM Employees WHERE Id = ? - + DELETE FROM Employees WHERE Id = ? + - SELECT * from Employees; + SELECT * from Employees; - - - - - - - - - - - - - - + + + + + + + + + + + + + +
    Emp IDFirst NameLast NameAge
    Emp IDFirst NameLast NameAge
    - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_query.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_query.jsp index cd113c09..41d515fe 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_query.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_query.jsp @@ -1,5 +1,5 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %> @@ -7,34 +7,34 @@ - sql:query 示例 + sql:query 示例 + url="jdbc:mysql://localhost/test" + user="root" password="root"/> - SELECT * from test.Employees; + SELECT * from test.Employees; - - - - - - - - - - - - - - + + + + + + + + + + + + + +
    Emp IDFirst NameLast NameAge
    Emp IDFirst NameLast NameAge
    - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_setDataSource.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_setDataSource.jsp index 3a52911a..73986187 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_setDataSource.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_setDataSource.jsp @@ -1,18 +1,18 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %> - sql:setDataSource 示例 + sql:setDataSource 示例 + url="jdbc:mysql://localhost/world" + user="root" password="root"/> - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_transaction.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_transaction.jsp index 2b18036b..f792744a 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_transaction.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_transaction.jsp @@ -1,5 +1,5 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ page import="java.util.Date" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> @@ -9,52 +9,52 @@ - sql:transaction 示例 + sql:transaction 示例 + url="jdbc:mysql://localhost/test" + user="root" password="root"/> <% - Date DoB = new Date("2001/12/16"); - int studentId = 100; + Date DoB = new Date("2001/12/16"); + int studentId = 100; %> - - UPDATE Students SET last = 'Ali' WHERE Id = 102 - - - UPDATE Students SET last = 'Shah' WHERE Id = 103 - - - INSERT INTO Students - VALUES (104,'Nuha', 'Ali', '2010/05/26'); - + + UPDATE Students SET last = 'Ali' WHERE Id = 102 + + + UPDATE Students SET last = 'Shah' WHERE Id = 103 + + + INSERT INTO Students + VALUES (104,'Nuha', 'Ali', '2010/05/26'); + - SELECT * from Students; + SELECT * from Students; - - - - - - - - - - - - - - + + + + + + + + + + + + + +
    Emp IDFirst NameLast NameDoB
    Emp IDFirst NameLast NameDoB
    - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_update.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_update.jsp index 4cdd9952..651ae8d4 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_update.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_update.jsp @@ -1,5 +1,5 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %> @@ -7,38 +7,38 @@ - sql:update 示例 + sql:update 示例 + url="jdbc:mysql://localhost/test" + user="root" password="root"/> - INSERT INTO Employees VALUES (104, 2, 'Nuha', 'Ali'); + INSERT INTO Employees VALUES (104, 2, 'Nuha', 'Ali'); - SELECT * from Employees; + SELECT * from Employees; - - - - - - - - - - - - - - + + + + + + + + + + + + + +
    Emp IDFirst NameLast NameAge
    Emp IDFirst NameLast NameAge
    - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/bundle.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/bundle.jsp index 6076fd10..77cd39f6 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/bundle.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/bundle.jsp @@ -4,8 +4,8 @@ - - Insert title here + + Insert title here @@ -13,11 +13,11 @@ - - -
    + + +
    - +
    diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/catch.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/catch.jsp index 1b5aa361..4a502a96 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/catch.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/catch.jsp @@ -3,22 +3,22 @@ - - Insert title here - + + Insert title here + - + - 程序抛出了异常 ${ e.class.name },原因: ${ e.message } + 程序抛出了异常 ${ e.class.name },原因: ${ e.message } diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/choose.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/choose.jsp index 8e213de2..69d457a7 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/choose.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/choose.jsp @@ -3,18 +3,18 @@ - - Insert title here + + Insert title here - - when 标签的输出 - - - otherwise 标签的输出 - + + when 标签的输出 + + + otherwise 标签的输出 + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/contains.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/contains.jsp index 6643c1a0..98e1b491 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/contains.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/contains.jsp @@ -5,8 +5,8 @@ - - Insert title here + + Insert title here diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/containsIgnoreCase.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/containsIgnoreCase.jsp index 64a0912e..f63fc4c2 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/containsIgnoreCase.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/containsIgnoreCase.jsp @@ -5,8 +5,8 @@ - - Insert title here + + Insert title here diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/dateParam.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/dateParam.jsp index b7509651..71223df1 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/dateParam.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/dateParam.jsp @@ -6,73 +6,73 @@ - - Insert title here - + textarea { + width: 400px; + height: 50px; + } + + password="admin" + url="jdbc:mysql://localhost:3306/jstl?charachterEncoding=UTF-8" + var="dataSource" scope="page"/> - create table if not exists tb_person - ( id integer auto_increment, - name varchar(255), - birthday timestamp null, - primary key (id) - ) + create table if not exists tb_person + ( id integer auto_increment, + name varchar(255), + birthday timestamp null, + primary key (id) + ) - insert into tb_person ( name, birthday ) values ( ?, ? ) - - + insert into tb_person ( name, birthday ) values ( ?, ? ) + + - select * from tb_person where birthday > ( ? - 1 ) - + select * from tb_person where birthday > ( ? - 1 ) + - - - - - + + + + + - - - - - - - + + + + + + +
    ${ columnName }
    ${ columnName }
    ${ row[columnName] }
    ${ row[columnName] }
    diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/endsWith.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/endsWith.jsp index e790baf6..588a50b6 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/endsWith.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/endsWith.jsp @@ -6,63 +6,63 @@ <%@page import="java.io.File" %> - - Insert title here - + .title td { + text-align: center; + background: #EEEEEE; + } + <% - request.setAttribute("files", new File("c:\\").listFiles()); + request.setAttribute("files", new File("c:\\").listFiles()); %> - - - - + + + + - - - - + + - - + + +
    File NameType
    File NameType
    ${ file.name } + +
    ${ file.name } - - 文件夹 - - JPG 图片 - EXE 应用程序 - GIF 图片 - TXT 文本文件 - WORD 文件 - Excel 文件 - LOG 日志文件 - SQL 数据库脚本文件 - - + + 文件夹 + + JPG 图片 + EXE 应用程序 + GIF 图片 + TXT 文本文件 + WORD 文件 + Excel 文件 + LOG 日志文件 + SQL 数据库脚本文件 + + -
    diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/escapeXml.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/escapeXml.jsp index 575e6121..fb35be08 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/escapeXml.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/escapeXml.jsp @@ -5,8 +5,8 @@ - - Insert title here + + Insert title here diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/fmt.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/fmt.jsp index 137d57fa..c237093e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/fmt.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/fmt.jsp @@ -4,8 +4,8 @@ - - Insert title here + + Insert title here diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/fn.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/fn.jsp index 09972959..cfa2276c 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/fn.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/fn.jsp @@ -5,8 +5,8 @@ - - Insert title here + + Insert title here diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forEach.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forEach.jsp index 59b80a92..f8eeeb8f 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forEach.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forEach.jsp @@ -3,37 +3,37 @@ - - Insert title here - + .header { + background: #DDDDDD; + text-align: center; + } + -
    ${ num }
    +
    ${ num }

    @@ -42,16 +42,16 @@ - - - - - - - - - - + + + + + + + + + +
    Header NameHeader Value
    ${ headerName }${ header[headerName] }
    Header NameHeader Value
    ${ headerName }${ header[headerName] }

    @@ -60,16 +60,16 @@ - - - - - - - - - - + + + + + + + + + +
    Header NameHeader Value
    ${ item.key }${ item.value }
    Header NameHeader Value
    ${ item.key }${ item.value }
    diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forEachWithList.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forEachWithList.jsp index eca5a1f4..e4e54bce 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forEachWithList.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forEachWithList.jsp @@ -1,158 +1,159 @@ -<%@ page language="java" import="com.helloweenvsfei.jstl.bean.*, java.util.ArrayList" contentType="text/html; charset=UTF-8" %> +<%@ page language="java" import="com.helloweenvsfei.jstl.bean.*, java.util.ArrayList" + contentType="text/html; charset=UTF-8" %> <%@ page import="java.util.List" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <% - List personList = new ArrayList(); - - int i = 1; - - Person person = new Person(); - person.setId(i++); - person.setName("张三"); - person.setAge(20); - person.setSex("男"); - person.setAddress("北京市海淀区上地软件园"); - person.setBirthday("2008-08-08"); - person.setMobile("13820080808"); - person.setTelephone("69653234"); - person.setCity("北京"); - - personList.add(person); - - Person person2 = new Person(); - person2.setId(i++); - person2.setName("李四"); - person2.setAge(20); - person2.setSex("男"); - person2.setAddress("北京市东皇城根锡拉胡同"); - person2.setBirthday("2008-01-01"); - person2.setMobile("13820080808"); - person2.setTelephone("20054879"); - person2.setCity("北京"); - - personList.add(person2); - - Person person3 = new Person(); - person3.setId(i++); - person3.setName("王五"); - person3.setAge(20); - person3.setSex("男"); - person3.setAddress("北京市东皇城根锡拉胡同"); - person3.setBirthday("2008-01-01"); - person3.setMobile("13820080808"); - person3.setTelephone("20054879"); - person3.setCity("北京"); - - personList.add(person3); - - Person person4 = new Person(); - person4.setId(i++); - person4.setName("王二麻子"); - person4.setAge(20); - person4.setSex("男"); - person4.setAddress("北京市东皇城根锡拉胡同"); - person4.setBirthday("2008-01-01"); - person4.setMobile("13820080808"); - person4.setTelephone("20054879"); - person4.setCity("北京"); - - personList.add(person4); - - Person person5 = new Person(); - person5.setId(i++); - person5.setName("王二麻子"); - person5.setAge(20); - person5.setSex("男"); - person5.setAddress("北京市东皇城根锡拉胡同"); - person5.setBirthday("2008-01-01"); - person5.setMobile("13820080808"); - person5.setTelephone("20054879"); - person5.setCity("北京"); - - personList.add(person5); - - Person person6 = new Person(); - person6.setId(i++); - person6.setName("王二麻子"); - person6.setAge(20); - person6.setSex("男"); - person6.setAddress("北京市东皇城根锡拉胡同"); - person6.setBirthday("2008-01-01"); - person6.setMobile("13820080808"); - person6.setTelephone("20054879"); - person6.setCity("北京"); - - personList.add(person6); - - Person person7 = new Person(); - person7.setId(i++); - person7.setName("王二麻子"); - person7.setAge(20); - person7.setSex("男"); - person7.setAddress("北京市东皇城根锡拉胡同"); - person7.setBirthday("2008-01-01"); - person7.setMobile("13820080808"); - person7.setTelephone("20054879"); - person7.setCity("北京"); - - personList.add(person7); - - request.setAttribute("personList", personList); + List personList = new ArrayList(); + + int i = 1; + + Person person = new Person(); + person.setId(i++); + person.setName("张三"); + person.setAge(20); + person.setSex("男"); + person.setAddress("北京市海淀区上地软件园"); + person.setBirthday("2008-08-08"); + person.setMobile("13820080808"); + person.setTelephone("69653234"); + person.setCity("北京"); + + personList.add(person); + + Person person2 = new Person(); + person2.setId(i++); + person2.setName("李四"); + person2.setAge(20); + person2.setSex("男"); + person2.setAddress("北京市东皇城根锡拉胡同"); + person2.setBirthday("2008-01-01"); + person2.setMobile("13820080808"); + person2.setTelephone("20054879"); + person2.setCity("北京"); + + personList.add(person2); + + Person person3 = new Person(); + person3.setId(i++); + person3.setName("王五"); + person3.setAge(20); + person3.setSex("男"); + person3.setAddress("北京市东皇城根锡拉胡同"); + person3.setBirthday("2008-01-01"); + person3.setMobile("13820080808"); + person3.setTelephone("20054879"); + person3.setCity("北京"); + + personList.add(person3); + + Person person4 = new Person(); + person4.setId(i++); + person4.setName("王二麻子"); + person4.setAge(20); + person4.setSex("男"); + person4.setAddress("北京市东皇城根锡拉胡同"); + person4.setBirthday("2008-01-01"); + person4.setMobile("13820080808"); + person4.setTelephone("20054879"); + person4.setCity("北京"); + + personList.add(person4); + + Person person5 = new Person(); + person5.setId(i++); + person5.setName("王二麻子"); + person5.setAge(20); + person5.setSex("男"); + person5.setAddress("北京市东皇城根锡拉胡同"); + person5.setBirthday("2008-01-01"); + person5.setMobile("13820080808"); + person5.setTelephone("20054879"); + person5.setCity("北京"); + + personList.add(person5); + + Person person6 = new Person(); + person6.setId(i++); + person6.setName("王二麻子"); + person6.setAge(20); + person6.setSex("男"); + person6.setAddress("北京市东皇城根锡拉胡同"); + person6.setBirthday("2008-01-01"); + person6.setMobile("13820080808"); + person6.setTelephone("20054879"); + person6.setCity("北京"); + + personList.add(person6); + + Person person7 = new Person(); + person7.setId(i++); + person7.setName("王二麻子"); + person7.setAge(20); + person7.setSex("男"); + person7.setAddress("北京市东皇城根锡拉胡同"); + person7.setBirthday("2008-01-01"); + person7.setMobile("13820080808"); + person7.setTelephone("20054879"); + person7.setCity("北京"); + + personList.add(person7); + + request.setAttribute("personList", personList); %> - - Insert title here - + + Insert title here + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + +
    编号姓名年龄性别城市地址生日手机电话
    ${ person.id }${ person.name }${ person.age }${ person.sex }${ person.city }${ person.address }${ person.birthday }${ person.mobile }${ person.telephone }
    编号姓名年龄性别城市地址生日手机电话
    ${ person.id }${ person.name }${ person.age }${ person.sex }${ person.city }${ person.address }${ person.birthday }${ person.mobile }${ person.telephone }
    @@ -161,33 +162,33 @@
    - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + +
    编号姓名年龄性别城市地址生日手机电话
    ${ varStatus.current.id }${ varStatus.current.name }${ varStatus.current.age }${ varStatus.current.sex }${ varStatus.current.city }${ varStatus.current.address }${ varStatus.current.birthday }${ varStatus.current.mobile }${ varStatus.current.telephone }
    编号姓名年龄性别城市地址生日手机电话
    ${ varStatus.current.id }${ varStatus.current.name }${ varStatus.current.age }${ varStatus.current.sex }${ varStatus.current.city }${ varStatus.current.address }${ varStatus.current.birthday }${ varStatus.current.mobile }${ varStatus.current.telephone }
    diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forTokens.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forTokens.jsp index 85962133..f0c9e9cd 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forTokens.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forTokens.jsp @@ -3,36 +3,36 @@ - - Insert title here - + td { + border: 1px #000000 solid; + text-align: center; + width: 150px; + } + - - - - - - - - - - + + + + + + + + + +
    varStatus.indexname
    ${ varStatus.index }${ item }
    varStatus.indexname
    ${ varStatus.index }${ item }
    diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/formatDate.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/formatDate.jsp index b4e36b33..bf224c8d 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/formatDate.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/formatDate.jsp @@ -8,68 +8,68 @@ <%@page import="java.util.Locale" %> - - Insert title here - + .title td { + background: #EEEEEE; + width: 100px; + } + <% - Field[] field = Locale.class.getFields(); + Field[] field = Locale.class.getFields(); - List localeList = new ArrayList(); + List localeList = new ArrayList(); - for (int i = 0; i < field.length; i++) { - if (field[i].getType().equals(Locale.class)) { - localeList.add((Locale) field[i].get(null)); - } - } + for (int i = 0; i < field.length; i++) { + if (field[i].getType().equals(Locale.class)) { + localeList.add((Locale) field[i].get(null)); + } + } - request.setAttribute("localeList", localeList); + request.setAttribute("localeList", localeList); %> - - - - - - + + + + + + - + - - - - - - - - - + + + + + + + + +
    LocaleDate and TimeNumbercurrency
    LocaleDate and TimeNumbercurrency
    ${ locale }
    ${ locale }
    diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/formatNumber.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/formatNumber.jsp index c2fe6ffe..55306342 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/formatNumber.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/formatNumber.jsp @@ -8,49 +8,49 @@ <%@page import="java.util.Locale" %> - - Insert title here - + + Insert title here + <% - Field[] field = Locale.class.getFields(); + Field[] field = Locale.class.getFields(); - List localeList = new ArrayList(); + List localeList = new ArrayList(); - for (int i = 0; i < field.length; i++) { - if (field[i].getType().equals(Locale.class)) { - localeList.add((Locale) field[i].get(null)); - } - } + for (int i = 0; i < field.length; i++) { + if (field[i].getType().equals(Locale.class)) { + localeList.add((Locale) field[i].get(null)); + } + } - request.setAttribute("localeList", localeList); + request.setAttribute("localeList", localeList); - double[] numbers = {0, 10000, 55.0, -123.2568}; - request.setAttribute("numbers", numbers); + double[] numbers = {0, 10000, 55.0, -123.2568}; + request.setAttribute("numbers", numbers); %> @@ -60,27 +60,27 @@   - ${ locale }  + ${ locale }  - - - - - - - - - - - - - - - + + + + + + + + + + + + + + +
    数字原值数字格式货币格式百分数格式
    ${ number }
    数字原值数字格式货币格式百分数格式
    ${ number }
    diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/if.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/if.jsp index 03aaab59..7e8910e1 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/if.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/if.jsp @@ -3,50 +3,50 @@ - - - + + +
    - - 添加操作 - - - - - - - - - -
    帐号
    真实姓名
    -
    - - 修改操作 - - - - - - - - - -
    帐号
    真实姓名
    -
    + + 添加操作 + + + + + + + + + +
    帐号
    真实姓名
    +
    + + 修改操作 + + + + + + + + + +
    帐号
    真实姓名
    +
    - - - - -
    + + + + +
    diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/import.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/import.jsp index 13b9de8a..d1c07771 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/import.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/import.jsp @@ -3,13 +3,13 @@ - - Insert title here - + + Insert title here + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/index.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/index.jsp index 3f83c65a..938d29b0 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/index.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/index.jsp @@ -3,8 +3,8 @@ - - Insert title here + + Insert title here diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/indexOf.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/indexOf.jsp index 1192f92d..7d8293ab 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/indexOf.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/indexOf.jsp @@ -5,8 +5,8 @@ - - Insert title here + + Insert title here diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/join.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/join.jsp index 997b97ea..29971147 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/join.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/join.jsp @@ -5,13 +5,13 @@ - - Insert title here + + Insert title here <% - request.setAttribute("array", new String[] {"John", "Tom", "Tommi", "Kurt",}); + request.setAttribute("array", new String[] {"John", "Tom", "Tommi", "Kurt",}); %> ${ fn:join(array, '; ') } diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/length.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/length.jsp index ed26b2f2..1972e925 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/length.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/length.jsp @@ -5,8 +5,8 @@ - - Insert title here + + Insert title here diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/out.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/out.jsp index 632d93e3..a72e6d0c 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/out.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/out.jsp @@ -3,12 +3,12 @@ - - Insert title here + + Insert title here action 参数为: - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/param.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/param.jsp index a9ef20e2..e378bd06 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/param.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/param.jsp @@ -3,14 +3,14 @@ - - Insert title here + + Insert title here - - + + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parse.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parse.jsp index eb1c1fe7..301b736e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parse.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parse.jsp @@ -4,30 +4,30 @@ - - Insert title here - + .source { + white-space: pre; + width: 100%; + height: 200px; + overflow: scroll; + border: 1px solid #999999; + padding: 2px; + margin-bottom: 20px; + } +
    - +
    @@ -41,10 +41,10 @@ 链接地址:
    - - Helloween - 20 - + + Helloween + 20 + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parseDate.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parseDate.jsp index 9c512a4a..56aba204 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parseDate.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parseDate.jsp @@ -4,8 +4,8 @@ - - Insert title here + + Insert title here diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parseNumber.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parseNumber.jsp index ad75c1f2..8c6bc209 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parseNumber.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parseNumber.jsp @@ -4,8 +4,8 @@ - - Insert title here + + Insert title here diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/query.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/query.jsp index 5e2e8f1d..a1428b47 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/query.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/query.jsp @@ -6,50 +6,50 @@ - - Insert title here - + + Insert title here + + password="admin" + url="jdbc:mysql://localhost:3306/jstl?charachterEncoding=UTF-8" + var="dataSource"/> + sql="select * from tb_corporation"> - - - - - - - - - - - - - + + + + + + + + + + + + +
    IDNameDescription
    ${ row['id'] }${ row['name'] }${ row['description'] }
    IDNameDescription
    ${ row['id'] }${ row['name'] }${ row['description'] }
    diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/queryPagination.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/queryPagination.jsp index 6a905a2d..64cde5e5 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/queryPagination.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/queryPagination.jsp @@ -7,69 +7,69 @@ <%@page import="com.helloweenvsfei.util.Pagination" %> - - Insert title here - + .title td { + text-align: center; + background: #DDDDDD; + } + <% - request.setAttribute("pagination", new Pagination(request, response)); + request.setAttribute("pagination", new Pagination(request, response)); %> + password="admin" + url="jdbc:mysql://localhost:3306/mysql?charachterEncoding=UTF-8" + var="dataSource"/> - SELECT count(*) count FROM help_topic + SELECT count(*) count FROM help_topic - + - SELECT * FROM help_topic + startRow="${ pagination.firstResult }" + maxRows="${ pagination.pageSize }"> + SELECT * FROM help_topic - - - - - + + + + + - - - - - - - + + + + + + +
    Help_IDNameDescription
    Help_IDNameDescription
    ${ row['help_topic_id'] }${ row['name'] }${ row['description'] }
    ${ row['help_topic_id'] }${ row['name'] }${ row['description'] }

    diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/queryReflect.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/queryReflect.jsp index f81a2989..43e26dbe 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/queryReflect.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/queryReflect.jsp @@ -6,69 +6,69 @@ - - Insert title here - + textarea { + width: 400px; + height: 50px; + } +
    -
    - +
    +
    + password="admin" + url="jdbc:mysql://localhost:3306/mysql?charachterEncoding=UTF-8" + var="dataSource"/> - - ${ param.sql } - + + ${ param.sql } + - - - - - - +
    ${ columnName }
    + + + + + - - - - - - - + + + + + + + -
    ${ columnName }
    ${ row[columnName] }
    ${ row[columnName] }
    +
    -
    Exception: ${ e.message }
    +
    Exception: ${ e.message }
    diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/redirect.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/redirect.jsp index 5541dfca..8ac64b11 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/redirect.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/redirect.jsp @@ -3,8 +3,8 @@ - - Insert title here + + Insert title here diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/remove.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/remove.jsp index 9d79d166..74a5e512 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/remove.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/remove.jsp @@ -4,13 +4,13 @@ <%@page import="java.util.HashMap" %> - - Insert title here + + Insert title here <% - request.setAttribute("somemap", new HashMap()); + request.setAttribute("somemap", new HashMap()); %> diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/requestEncoding.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/requestEncoding.jsp index deb904c8..8c62bad5 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/requestEncoding.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/requestEncoding.jsp @@ -4,22 +4,22 @@ - - Insert title here - + + Insert title here +
    - 关键字: -

    - + 关键字: +

    +
    diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/set.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/set.jsp index e6f72ddc..cfe7d922 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/set.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/set.jsp @@ -3,8 +3,8 @@ - - Insert title here + + Insert title here @@ -21,8 +21,8 @@

    <% - request.setAttribute("person", new com.helloweenvsfei.jstl.bean.Person()); - request.setAttribute("map", new java.util.HashMap()); + request.setAttribute("person", new com.helloweenvsfei.jstl.bean.Person()); + request.setAttribute("map", new java.util.HashMap()); %> ${ person.name } diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setBundle.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setBundle.jsp index ec69c43f..a702dd29 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setBundle.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setBundle.jsp @@ -4,8 +4,8 @@ - - Insert title here + + Insert title here @@ -14,7 +14,7 @@ - Helloween + Helloween
    diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setDataSource.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setDataSource.jsp index f6fea51b..bf4fc563 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setDataSource.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setDataSource.jsp @@ -6,15 +6,15 @@ - - Insert title here + + Insert title here + password="123" + url="jdbc:mysql://localhost:3306/jstl?charachterEncoding=UTF-8" + var="dataSource" scope="page"/> diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setLocale.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setLocale.jsp index bd4f6138..b077fe91 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setLocale.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setLocale.jsp @@ -5,62 +5,62 @@ <%@page import="java.util.Locale" %> - - Insert title here - + .title td { + text-align: center; + } + <% - request.setAttribute("localeList", Locale.getAvailableLocales()); + request.setAttribute("localeList", Locale.getAvailableLocales()); %> - - - - - - - + + + + + + + - + - - - - - - - - - - + + + + + + + + + +
    LocaleLanguageDate and TimeNumbercurrency
    LocaleLanguageDate and TimeNumbercurrency
    ${ locale.displayName }${ locale.displayLanguage }
    ${ locale.displayName }${ locale.displayLanguage }
    diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setTimeZone.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setTimeZone.jsp index 8e6bb383..2e3f0dde 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setTimeZone.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setTimeZone.jsp @@ -4,22 +4,22 @@ - - Insert title here - + div { + float: left; + width: 200px; + line-height: 22px; + border: 1px solid #000000; + padding: 4px; + border-collapse: collapse; + } + @@ -31,12 +31,12 @@
    -
    - GMT${ i>=12 ? '+' : '' }${ i-12 } : - - - -
    +
    + GMT${ i>=12 ? '+' : '' }${ i-12 } : + + + +
    diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/sina.xml b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/sina.xml index 1578574d..772fca53 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/sina.xml +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/sina.xml @@ -6,166 +6,166 @@ With 230 million registered users worldwide, 450 million daily page views and ov In various surveys and polls, SINA has been recognized as the most valuable brand and the most popular website in China. For 2003 and 2005, SINA was ranked the "Most Preferred Website" in China according to the Chinese Academy of Social Sciences and considered "The Most Respected Chinese Company" for three consecutive years in 2003, 2004 and 2005 by the Economic Observer and the Management Case Study Center of Beijing University. At the same time, South China Weekend in both 2003 and 2004 honored SINA with the prestigious award of the "Chinese Language Medium of the Year." --> - - - <![CDATA[新闻中心-新闻要闻]]> - - - - <![CDATA[新闻中心]]> - - http://news.sina.com.cn - http://www.sinaimg.cn/dy/sina_news626.gif - - - - - http://news.sina.com.cn/iframe/o/allnews/input/index.htm - zh-cn - WWW.SINA.COM.CN - 5 - - - - Fri, 30 Nov 2007 05:25:01 GMT - - - - - - <![CDATA[视频-联盟杯拜仁紫百合惊险战平 克洛斯笑纳空门]]> - - http://video.sina.com.cn/sports/g/bn/2007-11-30/13236492.shtml - WWW.SINA.COM.CN - http://video.sina.com.cn/sports/g/bn/2007-11-30/13236492.shtml - - - - Fri, 30 Nov 2007 05:23:53 GMT - - - - - - - - <![CDATA[18律师声援遭诱捕浏阳商人]]> - - http://finance.sina.com.cn/column/complain/20071130/13234236791.shtml - WWW.SINA.COM.CN - http://finance.sina.com.cn/column/complain/20071130/13234236791.shtml - - - - Fri, 30 Nov 2007 05:23:08 GMT - - - + + <![CDATA[新闻中心-新闻要闻]]> + + + + <![CDATA[新闻中心]]> + + http://news.sina.com.cn + http://www.sinaimg.cn/dy/sina_news626.gif + + + + + http://news.sina.com.cn/iframe/o/allnews/input/index.htm + zh-cn + WWW.SINA.COM.CN + 5 + + + + Fri, 30 Nov 2007 05:25:01 GMT + + + + + + <![CDATA[视频-联盟杯拜仁紫百合惊险战平 克洛斯笑纳空门]]> + + http://video.sina.com.cn/sports/g/bn/2007-11-30/13236492.shtml + WWW.SINA.COM.CN + http://video.sina.com.cn/sports/g/bn/2007-11-30/13236492.shtml + + + + Fri, 30 Nov 2007 05:23:53 GMT + + + + + + + + <![CDATA[18律师声援遭诱捕浏阳商人]]> + + http://finance.sina.com.cn/column/complain/20071130/13234236791.shtml + WWW.SINA.COM.CN + http://finance.sina.com.cn/column/complain/20071130/13234236791.shtml + + + + Fri, 30 Nov 2007 05:23:08 GMT + + + - - - - - <![CDATA[视频-马竞必杀定位球降服弱旅 幸运之神眷顾纽伦堡]]> - - http://video.sina.com.cn/sports/g/bn/2007-11-30/13206491.shtml - WWW.SINA.COM.CN - http://video.sina.com.cn/sports/g/bn/2007-11-30/13206491.shtml - - - - Fri, 30 Nov 2007 05:20:23 GMT - - - - - - - - <![CDATA[[泰国橡胶]USS3橡胶现货价格下跌,因基本面疲弱]]> - - http://finance.sina.com.cn/money/future/20071130/13204236790.shtml - WWW.SINA.COM.CN - http://finance.sina.com.cn/money/future/20071130/13204236790.shtml - - - - Fri, 30 Nov 2007 05:20:17 GMT - - - + + + + <![CDATA[视频-马竞必杀定位球降服弱旅 幸运之神眷顾纽伦堡]]> + + http://video.sina.com.cn/sports/g/bn/2007-11-30/13206491.shtml + WWW.SINA.COM.CN + http://video.sina.com.cn/sports/g/bn/2007-11-30/13206491.shtml + + + + Fri, 30 Nov 2007 05:20:23 GMT + + + + + + + + <![CDATA[[泰国橡胶]USS3橡胶现货价格下跌,因基本面疲弱]]> + + http://finance.sina.com.cn/money/future/20071130/13204236790.shtml + WWW.SINA.COM.CN + http://finance.sina.com.cn/money/future/20071130/13204236790.shtml + + + + Fri, 30 Nov 2007 05:20:17 GMT + + + - - - - - <![CDATA[[马来棕榈油]2007年11月30日午马来西亚棕榈油现货行情]]> - - http://finance.sina.com.cn/money/future/20071130/13204236789.shtml - WWW.SINA.COM.CN - http://finance.sina.com.cn/money/future/20071130/13204236789.shtml - - - - Fri, 30 Nov 2007 05:20:13 GMT - - - + + + + <![CDATA[[马来棕榈油]2007年11月30日午马来西亚棕榈油现货行情]]> + + http://finance.sina.com.cn/money/future/20071130/13204236789.shtml + WWW.SINA.COM.CN + http://finance.sina.com.cn/money/future/20071130/13204236789.shtml + + + + Fri, 30 Nov 2007 05:20:13 GMT + + + - - - - - <![CDATA[马来西亚BMD毛棕榈油期货午盘下滑,市场等待出口预估数据]]> - - http://finance.sina.com.cn/money/future/20071130/13204236788.shtml - WWW.SINA.COM.CN - http://finance.sina.com.cn/money/future/20071130/13204236788.shtml - - - - Fri, 30 Nov 2007 05:20:04 GMT - - - + + + + <![CDATA[马来西亚BMD毛棕榈油期货午盘下滑,市场等待出口预估数据]]> + + http://finance.sina.com.cn/money/future/20071130/13204236788.shtml + WWW.SINA.COM.CN + http://finance.sina.com.cn/money/future/20071130/13204236788.shtml + + + + Fri, 30 Nov 2007 05:20:04 GMT + + + - - - - - <![CDATA[[机构看盘]集成利期货:郑糖探低回升中幅收跌,短线压力依旧较大]]> - - http://finance.sina.com.cn/money/future/20071130/13194236787.shtml - WWW.SINA.COM.CN - http://finance.sina.com.cn/money/future/20071130/13194236787.shtml - - - - Fri, 30 Nov 2007 05:19:56 GMT - - - - - - - - <![CDATA[[现货行情]11月30日广西糖网食糖批发市场收市行情]]> - - http://finance.sina.com.cn/money/future/20071130/13194236786.shtml - WWW.SINA.COM.CN - http://finance.sina.com.cn/money/future/20071130/13194236786.shtml - - - - Fri, 30 Nov 2007 05:19:52 GMT - - - + + + + <![CDATA[[机构看盘]集成利期货:郑糖探低回升中幅收跌,短线压力依旧较大]]> + + http://finance.sina.com.cn/money/future/20071130/13194236787.shtml + WWW.SINA.COM.CN + http://finance.sina.com.cn/money/future/20071130/13194236787.shtml + + + + Fri, 30 Nov 2007 05:19:56 GMT + + + + + + + + <![CDATA[[现货行情]11月30日广西糖网食糖批发市场收市行情]]> + + http://finance.sina.com.cn/money/future/20071130/13194236786.shtml + WWW.SINA.COM.CN + http://finance.sina.com.cn/money/future/20071130/13194236786.shtml + + + + Fri, 30 Nov 2007 05:19:52 GMT + + + - - - - - <![CDATA[[现货行情]11月30日昆明商品中心批发市场收市行情]]> - - http://finance.sina.com.cn/money/future/20071130/13194236785.shtml - WWW.SINA.COM.CN - http://finance.sina.com.cn/money/future/20071130/13194236785.shtml - - - - Fri, 30 Nov 2007 05:19:48 GMT - - - + + + + <![CDATA[[现货行情]11月30日昆明商品中心批发市场收市行情]]> + + http://finance.sina.com.cn/money/future/20071130/13194236785.shtml + WWW.SINA.COM.CN + http://finance.sina.com.cn/money/future/20071130/13194236785.shtml + + + + Fri, 30 Nov 2007 05:19:48 GMT + + + - - - - - <![CDATA[长沙百亿企业添新]]> - - http://finance.sina.com.cn/china/dfjj/20071130/13194236784.shtml - WWW.SINA.COM.CN - http://finance.sina.com.cn/china/dfjj/20071130/13194236784.shtml - - - - Fri, 30 Nov 2007 05:19:41 GMT - - - + + + + <![CDATA[长沙百亿企业添新]]> + + http://finance.sina.com.cn/china/dfjj/20071130/13194236784.shtml + WWW.SINA.COM.CN + http://finance.sina.com.cn/china/dfjj/20071130/13194236784.shtml + + + + Fri, 30 Nov 2007 05:19:41 GMT + + + - - - - - <![CDATA[新代码启用 垃圾短信难隐形]]> - - http://tech.sina.com.cn/t/2007-11-30/13191884840.shtml - WWW.SINA.COM.CN - http://tech.sina.com.cn/t/2007-11-30/13191884840.shtml - - - - Fri, 30 Nov 2007 05:19:36 GMT - - - + + + + <![CDATA[新代码启用 垃圾短信难隐形]]> + + http://tech.sina.com.cn/t/2007-11-30/13191884840.shtml + WWW.SINA.COM.CN + http://tech.sina.com.cn/t/2007-11-30/13191884840.shtml + + + + Fri, 30 Nov 2007 05:19:36 GMT + + + - - - - - <![CDATA[至酷腕表型MP3 松下MP130V仅400元甩卖]]> - - http://tech.sina.com.cn/digi/mp3/2007-11-30/13191884839.shtml - WWW.SINA.COM.CN - http://tech.sina.com.cn/digi/mp3/2007-11-30/13191884839.shtml - - - - Fri, 30 Nov 2007 05:19:12 GMT - - - + + + + <![CDATA[至酷腕表型MP3 松下MP130V仅400元甩卖]]> + + http://tech.sina.com.cn/digi/mp3/2007-11-30/13191884839.shtml + WWW.SINA.COM.CN + http://tech.sina.com.cn/digi/mp3/2007-11-30/13191884839.shtml + + + + Fri, 30 Nov 2007 05:19:12 GMT + + + - - - - - <![CDATA[稳定成品油市场供应加强价格监管]]> - - http://finance.sina.com.cn/g/20071130/13184236783.shtml - WWW.SINA.COM.CN - http://finance.sina.com.cn/g/20071130/13184236783.shtml - - - - Fri, 30 Nov 2007 05:18:42 GMT - - - - - - + + + + + <![CDATA[稳定成品油市场供应加强价格监管]]> + + http://finance.sina.com.cn/g/20071130/13184236783.shtml + WWW.SINA.COM.CN + http://finance.sina.com.cn/g/20071130/13184236783.shtml + + + + Fri, 30 Nov 2007 05:18:42 GMT + + + + + + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/split.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/split.jsp index 050f4d55..a418e32d 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/split.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/split.jsp @@ -5,8 +5,8 @@ - - Insert title here + + Insert title here ${ header['accept'] } @@ -14,7 +14,7 @@ ${ header['accept'] } header['accept']:

    - ${ name }
    + ${ name }
    diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/sql.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/sql.jsp index 6eedfffe..cad2d4b0 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/sql.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/sql.jsp @@ -6,8 +6,8 @@ - - Insert title here + + Insert title here diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/substring.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/substring.jsp index 1c0f0de2..d1e5af91 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/substring.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/substring.jsp @@ -5,8 +5,8 @@ - - Insert title here + + Insert title here diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/timeZone.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/timeZone.jsp index 27a07ea4..e3521846 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/timeZone.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/timeZone.jsp @@ -7,45 +7,45 @@ - - Insert title here - + .title td { + text-align: center; + } + <% - Map hashMap = new HashMap(); + Map hashMap = new HashMap(); - for (String ID : TimeZone.getAvailableIDs()) { - hashMap.put(ID, TimeZone.getTimeZone(ID)); - } + for (String ID : TimeZone.getAvailableIDs()) { + hashMap.put(ID, TimeZone.getTimeZone(ID)); + } - request.setAttribute("timeZoneIds", TimeZone.getAvailableIDs()); - request.setAttribute("timeZone", hashMap); + request.setAttribute("timeZoneIds", TimeZone.getAvailableIDs()); + request.setAttribute("timeZone", hashMap); %> @@ -56,25 +56,25 @@
    - - - - - - + + + + + + - - - - - - - - + + + + + + + +
    时区ID时区现在时间时差
    时区ID时区现在时间时差
    ${ ID }${ timeZone[ID].displayName } - - - - ${ timeZone[ID].rawOffset / 60 / 60 / 1000 }
    ${ ID }${ timeZone[ID].displayName } + + + + ${ timeZone[ID].rawOffset / 60 / 60 / 1000 }
    diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/transaction.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/transaction.jsp index 27fc7a71..388df0d8 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/transaction.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/transaction.jsp @@ -6,84 +6,84 @@ - - Insert title here - + + Insert title here + + password="admin" + url="jdbc:mysql://localhost:3306/jstl?charachterEncoding=UTF-8" + var="dataSource"/> - + - - - insert into tb_corporation ( name, description ) values ('事务测试', '事务测试') - + + + insert into tb_corporation ( name, description ) values ('事务测试', '事务测试') + - 已插入一条。
    -
    + 已插入一条。
    +
    - - insert into tb_corporation ( id, name, description ) values (1, '事务测试', '事务测试') - + + insert into tb_corporation ( id, name, description ) values (1, '事务测试', '事务测试') + -
    +
    -
    操作异常,原因:${ e.message }。事务已经回滚。
    +
    操作异常,原因:${ e.message }。事务已经回滚。
    - select * from tb_corporation + select * from tb_corporation - - - - - - - - - - - - - + + + + + + + + + + + + +
    ${ columnName }
    ${ row[columnName] }
    ${ columnName }
    ${ row[columnName] }
    diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/update.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/update.jsp index 7b909066..3e03c34e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/update.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/update.jsp @@ -6,57 +6,57 @@ - - Insert title here - + .title td { + text-align: center; + background: #DDDDDD; + } + + password="admin" + url="jdbc:mysql://localhost:3306/jstl?charachterEncoding=UTF-8" + var="dataSource"/> - drop table if exists tb_corporation + drop table if exists tb_corporation DROP TABLE, 影响到的数据条数:${ result }
    - create table tb_corporation ( - id integer auto_increment, - name varchar(255), - description text, - primary key(id) - ) + create table tb_corporation ( + id integer auto_increment, + name varchar(255), + description text, + primary key(id) + ) CREATE TABLE, 影响到的数据条数:${ result }
    - insert into tb_corporation ( name, description ) values ('MicroSoft', '微软') + insert into tb_corporation ( name, description ) values ('MicroSoft', '微软') INSERT, 影响到的数据条数:${ result }
    - insert into tb_corporation ( name, description ) values ('IBM', '国际商用机器') + insert into tb_corporation ( name, description ) values ('IBM', '国际商用机器') INSERT, 影响到的数据条数:${ result }
    diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/url.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/url.jsp index 25bd315a..099f71ff 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/url.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/url.jsp @@ -3,8 +3,8 @@ - - Insert title here + + Insert title here diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_choose.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_choose.jsp index c2359bd4..f2169bc8 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_choose.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_choose.jsp @@ -4,23 +4,23 @@ - - Insert title here - + .source { + white-space: pre; + width: 100%; + height: 200px; + overflow: scroll; + border: 1px solid #999999; + padding: 2px; + margin-bottom: 20px; + } + @@ -31,12 +31,12 @@ - - 使用了 JNDI 属性。 - - - 没有使用 JNDI 属性。 - + + 使用了 JNDI 属性。 + + + 没有使用 JNDI 属性。 + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_forEach.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_forEach.jsp index a9163f3f..fdb7ef5d 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_forEach.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_forEach.jsp @@ -4,39 +4,39 @@ - - Insert title here - + .source { + white-space: pre; + width: 100%; + height: 200px; + overflow: scroll; + border: 1px solid #999999; + padding: 2px; + margin-bottom: 20px; + } +
    - +
    - ${ status.count }. - " target="_blank">  -
    -

    + ${ status.count }. + " target="_blank">  +
    +

    diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_if.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_if.jsp index 89af31eb..96521079 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_if.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_if.jsp @@ -4,23 +4,23 @@ - - Insert title here - + .source { + white-space: pre; + width: 100%; + height: 200px; + overflow: scroll; + border: 1px solid #999999; + padding: 2px; + margin-bottom: 20px; + } + @@ -31,11 +31,11 @@ - 属性 Driver Class Name 存在: .
    + 属性 Driver Class Name 存在: .
    - 属性 Driver Class Name 不存在。 + 属性 Driver Class Name 不存在。 diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_out.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_out.jsp index a902d1de..6ba9e20a 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_out.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_out.jsp @@ -4,16 +4,16 @@ - - Insert title here + + Insert title here - - Helloween - 20 - + + Helloween + 20 + content: ${ content }
    @@ -22,4 +22,4 @@ content: ${ content }
    输出元素 name:
    - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_set.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_set.jsp index 067bfd24..5deff358 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_set.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_set.jsp @@ -4,23 +4,23 @@ - - Insert title here - + .source { + white-space: pre; + width: 100%; + height: 200px; + overflow: scroll; + border: 1px solid #999999; + padding: 2px; + margin-bottom: 20px; + } + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_transform.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_transform.jsp index fb96c6e1..ff902f9a 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_transform.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_transform.jsp @@ -4,101 +4,101 @@ - - Insert title here - + td { + border: 1px solid #999999; + padding: 2px; + text-align: center; + padding-left: 10px; + padding-right: 10px; + } + - - - - 1 - 张三 - zhangsan@host.com - Software Engine - - - 2 - 李四 - lisi@somehost.com - Sales - - - 3 - 王五 - wangwu@someweb.com - Manager - - + + + + 1 + 张三 + zhangsan@host.com + Software Engine + + + 2 + 李四 + lisi@somehost.com + Sales + + + 3 + 王五 + wangwu@someweb.com + Manager + + - - - - - - - - - - - - - - - - - - -
    编号姓名电子邮件描述
    -
    -
    + + + + + + + + + + + + + + + + + + +
    编号姓名电子邮件描述
    +
    +
    - +
    diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/xml.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/xml.jsp index ff830aab..db0f4a94 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/xml.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/xml.jsp @@ -4,16 +4,16 @@ - - Insert title here + + Insert title here - - Helloween - 20 - + + Helloween + 20 + Description:
    diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_choose.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_choose.jsp index 50effb3b..d4261640 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_choose.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_choose.jsp @@ -1,41 +1,41 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %> - x:choose 标签 + x:choose 标签

    Books Info:

    - - - Padam History - ZARA - 100 - - - Great Mistry - NUHA - 2000 - - + + + Padam History + ZARA + 100 + + + Great Mistry + NUHA + 2000 + + - - Book is written by ZARA - - - Book is written by NUHA - - - Unknown author. - + + Book is written by ZARA + + + Book is written by NUHA + + + Unknown author. + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_forEach.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_forEach.jsp index 2907945e..81dc6762 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_forEach.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_forEach.jsp @@ -1,35 +1,35 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %> - x:forEach 标签 + x:forEach 标签

    Books Info:

    - - - Padam History - ZARA - 100 - - - Great Mistry - NUHA - 2000 - - + + + Padam History + ZARA + 100 + + + Great Mistry + NUHA + 2000 + +
      - -
    • Book Name:
    • -
      + +
    • Book Name:
    • +
    diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_if.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_if.jsp index b29fb6a9..6222d2f8 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_if.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_if.jsp @@ -1,28 +1,28 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %> - x:if 标签 + x:if 标签

    Books Info:

    - - - Padam History - ZARA - 100 - - - Great Mistry - NUHA - 2000 - - + + + Padam History + ZARA + 100 + + + Great Mistry + NUHA + 2000 + + @@ -30,11 +30,11 @@ Document has at least one element. - -
    - - Book prices are very high - + +
    + + Book prices are very high + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_out.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_out.jsp index 24d7e7ba..11924dbd 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_out.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_out.jsp @@ -1,28 +1,28 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %> - x:out 标签 + x:out 标签

    Books Info:

    - - - Padam History - ZARA - 100 - - - Great Mistry - NUHA - 2000 - - + + + Padam History + ZARA + 100 + + + Great Mistry + NUHA + 2000 + + @@ -32,4 +32,4 @@ The price of the second book: - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_param.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_param.jsp index 6903184c..e7701603 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_param.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_param.jsp @@ -1,32 +1,32 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %> - x:param 标签 + x:param 标签

    Books Info:

    - - - Padam History - ZARA - 100 - - - Great Mistry - NUHA - 2000 - - + + + Padam History + ZARA + 100 + + + Great Mistry + NUHA + 2000 + + - + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_parse.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_parse.jsp index 0df6f613..22aca6a5 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_parse.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_parse.jsp @@ -1,11 +1,11 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %> - x:parse 标签 + x:parse 标签

    Books Info:

    @@ -19,4 +19,4 @@ - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_set.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_set.jsp index 65df38cf..2c0617df 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_set.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_set.jsp @@ -3,24 +3,24 @@ - x:set 标签 + x:set 标签

    Books Info:

    - - - Padam History - ZARA - 100 - - - Great Mistry - NUHA - 2000 - - + + + Padam History + ZARA + 100 + + + Great Mistry + NUHA + 2000 + + @@ -28,4 +28,4 @@ The price of the second book: - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_transform.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_transform.jsp index adfecce5..c8bc4075 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_transform.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_transform.jsp @@ -1,27 +1,27 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> + pageEncoding="UTF-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %> - x:transform 标签 + x:transform 标签

    Books Info:

    - - - Padam History - ZARA - 100 - - - Great Mistry - NUHA - 2000 - - + + + Padam History + ZARA + 100 + + + Great Mistry + NUHA + 2000 + + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/xml/books.xml b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/xml/books.xml index 34ee43f7..1ff9cc88 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/xml/books.xml +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/xml/books.xml @@ -1,12 +1,12 @@ - - Padam History - ZARA - 100 - - - Great Mistry - NUHA - 2000 - + + Padam History + ZARA + 100 + + + Great Mistry + NUHA + 2000 + diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/xml/style.xsl b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/xml/style.xsl index cdac4018..728b223b 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/xml/style.xsl +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/xml/style.xsl @@ -1,32 +1,32 @@ - + - - - - - - - + + + + + + + - - - - - - - - - -
    - - - - - - - -
    -
    + + + + + + + + + +
    + + + + + + + +
    +
    diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java b/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java index f4a1a2e9..5fb3a29d 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java @@ -1,16 +1,14 @@ package io.github.dunwu.javaee.server; +import java.util.ArrayList; import org.apache.commons.lang3.StringUtils; import org.assertj.core.util.Lists; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.webapp.WebAppClassLoader; import org.eclipse.jetty.webapp.WebAppContext; -import java.util.ArrayList; - /** - * JettyFactory 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij - * 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 + * JettyFactory 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 * * @author Zhang Peng */ @@ -18,12 +16,19 @@ public class JettyFactory { public static final int IDE_ECLIPSE = 0; + public static final int IDE_INTELLIJ = 1; + private static final int PORT = 9798; + private static final String CONTEXT = "/"; + private static final String RESOURCE_BASE_PATH = "src/main/webapp"; + private static final String WEB_XML_PATH = "/WEB-INF/web.xml"; - private static final String[] TLD_JAR_NAMES = new String[] { "sitemesh", "spring-webmvc", "shiro-web", "tiles" }; + + private static final String[] TLD_JAR_NAMES = new String[] {"sitemesh", "spring-webmvc", "shiro-web", "tiles"} + private static final String WINDOWS_WEBDEFAULT_PATH = "jetty/webdefault.xml"; public static Server initServer() { @@ -44,37 +49,17 @@ public static void initWebAppContext(Server server, int type) throws Exception { if (IDE_INTELLIJ == type) { webAppContext.setDefaultsDescriptor(WINDOWS_WEBDEFAULT_PATH); supportJspAndSetTldJarNames(server, TLD_JAR_NAMES); - } - else { + } else { webAppContext.setParentLoaderPriority(true); } System.out.println("[INFO] Application loaded"); } - public static void reloadWebAppContext(Server server) throws Exception { - WebAppContext webAppContext = (WebAppContext) server.getHandler(); - System.out.println("[INFO] Application reloading"); - webAppContext.stop(); - WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext); - classLoader.addClassPath(getAbsolutePath() + "target/classes"); - classLoader.addClassPath(getAbsolutePath() + "target/test-classes"); - webAppContext.setClassLoader(classLoader); - webAppContext.start(); - System.out.println("[INFO] Application reloaded"); - } - - public static void startServer(Server server) throws Exception { - System.out.println("[HINT] Don't forget to set -XX:MaxPermSize=128m"); - server.start(); - System.out.println("Server running at http://localhost:" + PORT + CONTEXT); - System.out.println("[HINT] Hit Enter to reload the application quickly"); - } - public static String getAbsolutePath() { String path = null; String folderPath = JettyFactory.class.getProtectionDomain().getCodeSource().getLocation().getPath() - .substring(1); + .substring(1); if (folderPath.indexOf("target") > 0) { path = folderPath.substring(0, folderPath.indexOf("target")); } @@ -87,9 +72,9 @@ public static void supportJspAndSetTldJarNames(Server server, String... jarNames // AnnotationConfiguration in // order to correctly set up the jsp container org.eclipse.jetty.webapp.Configuration.ClassList classlist = org.eclipse.jetty.webapp.Configuration.ClassList - .setServerDefault(server); + .setServerDefault(server); classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", - "org.eclipse.jetty.annotations.AnnotationConfiguration"); + "org.eclipse.jetty.annotations.AnnotationConfiguration"); // Set the ContainerIncludeJarPattern so that jetty examines these container-path // jars for // tlds, web-fragments etc. @@ -97,14 +82,33 @@ public static void supportJspAndSetTldJarNames(Server server, String... jarNames // them // instead. ArrayList jarNameExprssions = Lists.newArrayList(".*/[^/]*servlet-api-[^/]*\\.jar$", - ".*/javax.servlet.jsp.jstl-.*\\.jar$", ".*/[^/]*taglibs.*\\.jar$"); + ".*/javax.servlet.jsp.jstl-.*\\.jar$", ".*/[^/]*taglibs.*\\.jar$"); for (String jarName : jarNames) { jarNameExprssions.add(".*/" + jarName + "-[^/]*\\.jar$"); } context.setAttribute("org.eclipse.jetty.io.github.dunwu.javaee.server.webapp.ContainerIncludeJarPattern", - StringUtils.join(jarNameExprssions, '|')); + StringUtils.join(jarNameExprssions, '|')); + } + + public static void reloadWebAppContext(Server server) throws Exception { + WebAppContext webAppContext = (WebAppContext) server.getHandler(); + System.out.println("[INFO] Application reloading"); + webAppContext.stop(); + WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext); + classLoader.addClassPath(getAbsolutePath() + "target/classes"); + classLoader.addClassPath(getAbsolutePath() + "target/test-classes"); + webAppContext.setClassLoader(classLoader); + webAppContext.start(); + System.out.println("[INFO] Application reloaded"); + } + + public static void startServer(Server server) throws Exception { + System.out.println("[HINT] Don't forget to set -XX:MaxPermSize=128m"); + server.start(); + System.out.println("Server running at http://localhost:" + PORT + CONTEXT); + System.out.println("[HINT] Hit Enter to reload the application quickly"); } } diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java b/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java index c09f0fc1..049c8802 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java @@ -26,8 +26,7 @@ public static void main(String[] args) throws Exception { JettyFactory.reloadWebAppContext(server); } } - } - catch (Exception e) { + } catch (Exception e) { e.printStackTrace(); System.exit(-1); } diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/resources/jetty/webdefault.xml b/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/resources/jetty/webdefault.xml index 65ec9cae..b991d44c 100644 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/resources/jetty/webdefault.xml +++ b/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/resources/jetty/webdefault.xml @@ -1,76 +1,76 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://xmlns.jcp.org/xml/ns/javaee" + xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" + metadata-complete="false" + version="3.1"> - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - Default web.xml file. - This file is applied to a Web application before it's own WEB_INF/web.xml file - + + Default web.xml file. + This file is applied to a Web application before it's own WEB_INF/web.xml file + - - - - - - org.eclipse.jetty.servlet.listener.ELContextCleaner - + + + + + + org.eclipse.jetty.servlet.listener.ELContextCleaner + - - - - - - org.eclipse.jetty.servlet.listener.IntrospectorCleaner - + + + + + + org.eclipse.jetty.servlet.listener.IntrospectorCleaner + - - - - + + + + - - - - - - + + + + + - - - default - org.eclipse.jetty.servlet.DefaultServlet - - aliases - false - - - acceptRanges - true - - - dirAllowed - true - - - welcomeServlets - false - - - redirectWelcome - false - - - maxCacheSize - 256000000 - - - maxCachedFileSize - 200000000 - - - maxCachedFiles - 2048 - - - gzip - false - - - etags - false - - - useFileMappedBuffer - false - - - - 0 - + + + default + org.eclipse.jetty.servlet.DefaultServlet + + aliases + false + + + acceptRanges + true + + + dirAllowed + true + + + welcomeServlets + false + + + redirectWelcome + false + + + maxCacheSize + 256000000 + + + maxCachedFileSize + 200000000 + + + maxCachedFiles + 2048 + + + gzip + false + + + etags + false + + + useFileMappedBuffer + false + + + + 0 + - - default - / - + + default + / + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - jsp - org.eclipse.jetty.jsp.JettyJspServlet - - logVerbosityLevel - DEBUG - - - fork - false - - - xpoweredBy - false - - - compilerTargetVM - 1.7 - - - compilerSourceVM - 1.7 - - - 0 - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + jsp + org.eclipse.jetty.jsp.JettyJspServlet + + logVerbosityLevel + DEBUG + + + fork + false + + + xpoweredBy + false + + + compilerTargetVM + 1.7 + + + compilerSourceVM + 1.7 + + + 0 + - - jsp - *.jsp - *.jspf - *.jspx - *.xsp - *.JSP - *.JSPF - *.JSPX - *.XSP - + + jsp + *.jsp + *.jspf + *.jspx + *.xsp + *.JSP + *.JSPF + *.JSPX + *.XSP + - - - - - 30 - + + + + + 30 + - - - - - - - + + + + + + + - - - - - index.html - index.htm - index.jsp - + + + + + index.html + index.htm + index.jsp + - - - - - - ar - ISO-8859-6 - - - be - ISO-8859-5 - - - bg - ISO-8859-5 - - - ca - ISO-8859-1 - - - cs - ISO-8859-2 - - - da - ISO-8859-1 - - - de - ISO-8859-1 - - - el - ISO-8859-7 - - - en - ISO-8859-1 - - - es - ISO-8859-1 - - - et - ISO-8859-1 - - - fi - ISO-8859-1 - - - fr - ISO-8859-1 - - - hr - ISO-8859-2 - - - hu - ISO-8859-2 - - - is - ISO-8859-1 - - - it - ISO-8859-1 - - - iw - ISO-8859-8 - - - ja - Shift_JIS - - - ko - EUC-KR - - - lt - ISO-8859-2 - - - lv - ISO-8859-2 - - - mk - ISO-8859-5 - - - nl - ISO-8859-1 - - - no - ISO-8859-1 - - - pl - ISO-8859-2 - - - pt - ISO-8859-1 - - - ro - ISO-8859-2 - - - ru - ISO-8859-5 - - - sh - ISO-8859-5 - - - sk - ISO-8859-2 - - - sl - ISO-8859-2 - - - sq - ISO-8859-2 - - - sr - ISO-8859-5 - - - sv - ISO-8859-1 - - - tr - ISO-8859-9 - - - uk - ISO-8859-5 - - - zh - GB2312 - - - zh_TW - Big5 - - + + + + + + ar + ISO-8859-6 + + + be + ISO-8859-5 + + + bg + ISO-8859-5 + + + ca + ISO-8859-1 + + + cs + ISO-8859-2 + + + da + ISO-8859-1 + + + de + ISO-8859-1 + + + el + ISO-8859-7 + + + en + ISO-8859-1 + + + es + ISO-8859-1 + + + et + ISO-8859-1 + + + fi + ISO-8859-1 + + + fr + ISO-8859-1 + + + hr + ISO-8859-2 + + + hu + ISO-8859-2 + + + is + ISO-8859-1 + + + it + ISO-8859-1 + + + iw + ISO-8859-8 + + + ja + Shift_JIS + + + ko + EUC-KR + + + lt + ISO-8859-2 + + + lv + ISO-8859-2 + + + mk + ISO-8859-5 + + + nl + ISO-8859-1 + + + no + ISO-8859-1 + + + pl + ISO-8859-2 + + + pt + ISO-8859-1 + + + ro + ISO-8859-2 + + + ru + ISO-8859-5 + + + sh + ISO-8859-5 + + + sk + ISO-8859-2 + + + sl + ISO-8859-2 + + + sq + ISO-8859-2 + + + sr + ISO-8859-5 + + + sv + ISO-8859-1 + + + tr + ISO-8859-9 + + + uk + ISO-8859-5 + + + zh + GB2312 + + + zh_TW + Big5 + + - - - - - - Disable TRACE - / - TRACE - - - - - - Enable everything but TRACE - / - TRACE - - + + + + + + Disable TRACE + / + TRACE + + + + + + Enable everything but TRACE + / + TRACE + + diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/pom.xml b/codes/javaee-tutorial/javaee-tutorial-listener/pom.xml index 1c87eb2d..f67b65d1 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/pom.xml +++ b/codes/javaee-tutorial/javaee-tutorial-listener/pom.xml @@ -1,102 +1,102 @@ - 4.0.0 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 - io.github.dunwu - javaee-tutorial-listener - 1.0.0 - war - javaee-tutorial-listener - JavaEE 学习笔记之 listener + io.github.dunwu + javaee-tutorial-listener + 1.0.0 + war + javaee-tutorial-listener + JavaEE 学习笔记之 listener - - UTF-8 - 1.7 - ${java.version} - ${java.version} - + + UTF-8 + 1.7 + ${java.version} + ${java.version} + - - - - ch.qos.logback - logback-classic - - - org.slf4j - jcl-over-slf4j - - + + + + ch.qos.logback + logback-classic + + + org.slf4j + jcl-over-slf4j + + - - - commons-fileupload - commons-fileupload - - - commons-io - commons-io - - - org.apache.commons - commons-lang3 - - + + + commons-fileupload + commons-fileupload + + + commons-io + commons-io + + + org.apache.commons + commons-lang3 + + - - - javax.servlet - javax.servlet-api - provided - - - javax.servlet.jsp - javax.servlet.jsp-api - provided - - + + + javax.servlet + javax.servlet-api + provided + + + javax.servlet.jsp + javax.servlet.jsp-api + provided + + - - - org.eclipse.jetty - jetty-webapp - test - - - org.eclipse.jetty - jetty-annotations - test - - - org.eclipse.jetty - apache-jsp - test - - - org.eclipse.jetty - apache-jstl - test - - + + + org.eclipse.jetty + jetty-webapp + test + + + org.eclipse.jetty + jetty-annotations + test + + + org.eclipse.jetty + apache-jsp + test + + + org.eclipse.jetty + apache-jstl + test + + - - - junit - junit - test - - - + + + junit + junit + test + + + - - - - io.github.dunwu - dunwu-dependencies - 0.4.2-SNAPSHOT - pom - import - - - + + + + io.github.dunwu + dunwu-dependencies + 0.4.2-SNAPSHOT + pom + import + + + diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/LoginSessionListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/LoginSessionListener.java index e5250ad4..05906ade 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/LoginSessionListener.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/LoginSessionListener.java @@ -1,14 +1,13 @@ package io.github.dunwu.javaee.listener; import io.github.dunwu.javaee.listener.bean.PersonInfo; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - +import java.util.HashMap; +import java.util.Map; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionAttributeListener; import javax.servlet.http.HttpSessionBindingEvent; -import java.util.HashMap; -import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * 单点登录处理类 diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionActivationListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionActivationListener.java index 4c8360d0..647a568e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionActivationListener.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionActivationListener.java @@ -1,11 +1,10 @@ package io.github.dunwu.javaee.listener; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - +import java.io.Serializable; import javax.servlet.http.HttpSessionActivationListener; import javax.servlet.http.HttpSessionEvent; -import java.io.Serializable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * HttpSessionActivationListener 接口的 JavaBean 对象可以感知自己被序列化或反序列化的事件。 diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionAttributeListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionAttributeListener.java index d233a001..cc55a3e1 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionAttributeListener.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionAttributeListener.java @@ -1,11 +1,10 @@ package io.github.dunwu.javaee.listener; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionAttributeListener; import javax.servlet.http.HttpSessionBindingEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * HttpSessionAttributeListener 用于监听 HttpSession 中的属性变化 diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionBindingListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionBindingListener.java index c0678da1..783712c8 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionBindingListener.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionBindingListener.java @@ -1,10 +1,9 @@ package io.github.dunwu.javaee.listener; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import javax.servlet.http.HttpSessionBindingEvent; import javax.servlet.http.HttpSessionBindingListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * HttpSessionBindingListener 接口的 JavaBean 对象可以感知自己被绑定或解绑定到 Session 中的事件。 diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionListener.java index be295497..0fa16180 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionListener.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionListener.java @@ -1,13 +1,12 @@ package io.github.dunwu.javaee.listener; import io.github.dunwu.javaee.listener.util.ApplicationConstants; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - +import java.util.Date; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionListener; -import java.util.Date; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * HttpSessionListener 接口用于监听 HttpSession 对象的创建和销毁。 diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextAttributeListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextAttributeListener.java index 2d4c451c..e1d2723e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextAttributeListener.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextAttributeListener.java @@ -1,10 +1,9 @@ package io.github.dunwu.javaee.listener; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import javax.servlet.ServletContextAttributeEvent; import javax.servlet.ServletContextAttributeListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * ServletContextAttributeListener 用于监听 ServletContext 中的属性变化 diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextListener.java index c25a406c..2855c52e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextListener.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextListener.java @@ -1,13 +1,12 @@ package io.github.dunwu.javaee.listener; import io.github.dunwu.javaee.listener.util.ApplicationConstants; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - +import java.util.Date; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; -import java.util.Date; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * ServletContextListener 接口用于监听 ServletContext 对象的创建和销毁事件。 diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestAttributeListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestAttributeListener.java index 3caedbd1..1a6fad17 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestAttributeListener.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestAttributeListener.java @@ -1,10 +1,9 @@ package io.github.dunwu.javaee.listener; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import javax.servlet.ServletRequestAttributeEvent; import javax.servlet.ServletRequestAttributeListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * ServletContextAttributeListener 用于监听 ServletRequest 中的属性变化 diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestListener.java index 3a28b414..55803334 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestListener.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestListener.java @@ -1,12 +1,11 @@ package io.github.dunwu.javaee.listener; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * ServletRequestListener 接口用于监听 ServletRequest 对象的创建和销毁。 @@ -32,13 +31,14 @@ public void requestInitialized(ServletRequestEvent event) { String uri = request.getRequestURI(); uri = request.getQueryString() == null ? uri : (uri + "?" + request.getQueryString()); request.setAttribute("dateCreated", System.currentTimeMillis()); - String[] suffix = { ".html", ".do", ".jsp", ".action" }; + String[] suffix = {".html", ".do", ".jsp", ".action"} for (int i = 0; i < suffix.length; i++) { if (uri.endsWith(suffix[i])) { break; } - if (i == suffix.length - 1) + if (i == suffix.length - 1) { return; + } } Integer activeTimes = (Integer) session.getAttribute("activeTimes"); diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/PersonInfoListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/PersonInfoListener.java index 22fc6f5b..20a43920 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/PersonInfoListener.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/PersonInfoListener.java @@ -1,11 +1,9 @@ package io.github.dunwu.javaee.listener; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.servlet.http.*; import java.io.Serializable; import java.util.Date; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * @author Zhang Peng diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/bean/PersonInfo.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/bean/PersonInfo.java index 24098f6c..360c969e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/bean/PersonInfo.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/bean/PersonInfo.java @@ -16,14 +16,6 @@ public class PersonInfo implements Serializable { // 登录时间 private Date loginDate; - public String getAccount() { - return account; - } - - public void setAccount(String account) { - this.account = account; - } - public String getIp() { return ip; } @@ -48,4 +40,12 @@ public boolean equals(Object obj) { return account.equalsIgnoreCase(((PersonInfo) obj).getAccount()); } + public String getAccount() { + return account; + } + + public void setAccount(String account) { + this.account = account; + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/util/ApplicationConstants.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/util/ApplicationConstants.java index 327daabb..f91d75c3 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/util/ApplicationConstants.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/util/ApplicationConstants.java @@ -1,9 +1,9 @@ package io.github.dunwu.javaee.listener.util; -import javax.servlet.http.HttpSession; import java.util.Date; import java.util.HashMap; import java.util.Map; +import javax.servlet.http.HttpSession; public class ApplicationConstants { diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/resources/logback.xml index 6d2b289d..c05e3f34 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/resources/logback.xml +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/resources/logback.xml @@ -2,45 +2,45 @@ - - - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n - - - - - - - - logs/${FILE_NAME}-all.%d{yyyy-MM-dd}.log - 30 - - - - - 30MB - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n - - - - - - - - - - - - - - - - + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n + + + + + + + + logs/${FILE_NAME}-all.%d{yyyy-MM-dd}.log + 30 + + + + + 30MB + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n + + + + + + + + + + + + + + + + diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/META-INF/context.xml b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/META-INF/context.xml index 2c49bcda..9d357dc7 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/META-INF/context.xml +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/META-INF/context.xml @@ -1,5 +1,5 @@ - - - + + + diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/WEB-INF/resources/jsp/index.jsp b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/WEB-INF/resources/jsp/index.jsp index bde68afd..473a1315 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/WEB-INF/resources/jsp/index.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/WEB-INF/resources/jsp/index.jsp @@ -3,11 +3,11 @@ - My JSP 'index.jsp' starting page - - - - + My JSP 'index.jsp' starting page + + + + diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/WEB-INF/web.xml b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/WEB-INF/web.xml index f68bdb1d..b9947a30 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/WEB-INF/web.xml +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/WEB-INF/web.xml @@ -1,57 +1,57 @@ - - contextPath - value. sdf - + + contextPath + value. sdf + - - - io.github.dunwu.javaee.listener.MyHttpSessionListener - - - io.github.dunwu.javaee.listener.MyServletContextListener - - - io.github.dunwu.javaee.listener.MyServletRequestListener - + + + io.github.dunwu.javaee.listener.MyHttpSessionListener + + + io.github.dunwu.javaee.listener.MyServletContextListener + + + io.github.dunwu.javaee.listener.MyServletRequestListener + - - - io.github.dunwu.javaee.listener.MyHttpSessionAttributeListener - - - io.github.dunwu.javaee.listener.MyServletContextAttributeListener - - - io.github.dunwu.javaee.listener.MyServletRequestAttributeListener - + + + io.github.dunwu.javaee.listener.MyHttpSessionAttributeListener + + + io.github.dunwu.javaee.listener.MyServletContextAttributeListener + + + io.github.dunwu.javaee.listener.MyServletRequestAttributeListener + - - - io.github.dunwu.javaee.listener.MyHttpSessionBindingListener - - - io.github.dunwu.javaee.listener.MyHttpSessionActivationListener - + + + io.github.dunwu.javaee.listener.MyHttpSessionBindingListener + + + io.github.dunwu.javaee.listener.MyHttpSessionActivationListener + - - - io.github.dunwu.javaee.listener.LoginSessionListener - - + + + io.github.dunwu.javaee.listener.LoginSessionListener + + - - - io.github.dunwu.javaee.listener.PersonInfoListener - - - + + + io.github.dunwu.javaee.listener.PersonInfoListener + + + - - index.jsp - + + index.jsp + diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/aa.html b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/aa.html index 991b72a1..4153961f 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/aa.html +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/aa.html @@ -3,10 +3,10 @@

    sadfsdf

      -
    1. sadf
    2. -
    3. asdfsadf
    4. -
    5. sdf
    6. -
    7. sdfsadfasdf
    8. +
    9. sadf
    10. +
    11. asdfsadf
    12. +
    13. sdf
    14. +
    15. sdfsadfasdf
    diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/active.jsp b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/active.jsp index e7fe89e3..a2bc7909 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/active.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/active.jsp @@ -3,21 +3,21 @@ <%@ page import="io.github.dunwu.javaee.listener.bean.PersonInfo" %> - - Insert title here + + Insert title here <% - PersonInfo personInfo = (PersonInfo) session.getAttribute("personInfo"); - if (personInfo == null) { - personInfo = new PersonInfo(); - personInfo.setAccount("Zhang Peng"); - session.setAttribute("personInfo", personInfo); - out.println("PersonInfo 对象不存在。已经成功新建。sessionId: " + session.getId()); - } else { - out.println("PersonInfo 对象存在。无需新建。sessionId: " + session.getId()); - } + PersonInfo personInfo = (PersonInfo) session.getAttribute("personInfo"); + if (personInfo == null) { + personInfo = new PersonInfo(); + personInfo.setAccount("Zhang Peng"); + session.setAttribute("personInfo", personInfo); + out.println("PersonInfo 对象不存在。已经成功新建。sessionId: " + session.getId()); + } else { + out.println("PersonInfo 对象存在。无需新建。sessionId: " + session.getId()); + } %> diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/index.jsp b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/index.jsp index b45578ba..7f55cd70 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/index.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/index.jsp @@ -1,14 +1,14 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" %> <% - String path = request.getContextPath(); - String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; + String path = request.getContextPath(); + String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; %> - - javaee-listener 首页 + + javaee-listener 首页 diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/listener.jsp b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/listener.jsp index 136f197f..c8326ee4 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/listener.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/listener.jsp @@ -1,46 +1,46 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" %> + import="io.github.dunwu.javaee.listener.util.ApplicationConstants"/> <% - String action = request.getParameter("action"); - String name = request.getParameter("name"); - String value = request.getParameter("value"); + String action = request.getParameter("action"); + String name = request.getParameter("name"); + String value = request.getParameter("value"); - session.getId(); + session.getId(); - if ("addRequestAttribute".equals(action)) { - request.setAttribute(name, value); - } else if ("removeRequestAttribute".equals(action)) { - request.removeAttribute(name); - } else if ("addSessionAttribute".equals(action)) { - session.setAttribute(name, value); - } else if ("removeSessionAttribute".equals(action)) { - session.removeAttribute(name); - } else if ("logout".equals(action)) { - session.invalidate(); - out.println("返回"); - return; - } + if ("addRequestAttribute".equals(action)) { + request.setAttribute(name, value); + } else if ("removeRequestAttribute".equals(action)) { + request.removeAttribute(name); + } else if ("addSessionAttribute".equals(action)) { + session.setAttribute(name, value); + } else if ("removeSessionAttribute".equals(action)) { + session.removeAttribute(name); + } else if ("logout".equals(action)) { + session.invalidate(); + out.println("返回"); + return; + } %> - - - - + + + +
    -
    -
    -
    删除 Session
    -
    +
    +
    +
    删除 Session
    +

    @@ -54,38 +54,38 @@ 目前在线总数:<%= ApplicationConstants.SESSION_MAP.size() %>,登录用户:<%=ApplicationConstants.CURRENT_LOGIN_COUNT%>。
    - - - - - - - - - - <% - for (String id : ApplicationConstants.SESSION_MAP.keySet()) { - HttpSession sess = ApplicationConstants.SESSION_MAP.get(id); - %> - - - - - - - - - - <% - } - %> + + + + + + + + + + <% + for (String id : ApplicationConstants.SESSION_MAP.keySet()) { + HttpSession sess = ApplicationConstants.SESSION_MAP.get(id); + %> + + + + + + + + + + <% + } + %>
    jsessionidaccountcreationTimelastAccessedTimenewactiveTimesip
    <%=id%> - <%=sess.getAttribute("account")%> - <%=DateFormat.getDateTimeInstance().format(new Date(sess.getCreationTime()))%> - <%=DateFormat.getDateTimeInstance().format(new Date(sess.getLastAccessedTime()))%> - <%=sess.isNew()%> - <%=sess.getAttribute("activeTimes")%> - <%=sess.getAttribute("ip")%> -
    jsessionidaccountcreationTimelastAccessedTimenewactiveTimesip
    <%=id%> + <%=sess.getAttribute("account")%> + <%=DateFormat.getDateTimeInstance().format(new Date(sess.getCreationTime()))%> + <%=DateFormat.getDateTimeInstance().format(new Date(sess.getLastAccessedTime()))%> + <%=sess.isNew()%> + <%=sess.getAttribute("activeTimes")%> + <%=sess.getAttribute("ip")%> +
    diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/online.jsp b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/online.jsp index fb730d0e..e9219e1c 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/online.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/online.jsp @@ -1,13 +1,13 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" %> + import="io.github.dunwu.javaee.listener.bean.PersonInfo"/> 服务器启动时间:<%=DateFormat.getDateTimeInstance().format(ApplicationConstants.START_DATE)%>, @@ -17,39 +17,39 @@ 目前在线总数:<%= ApplicationConstants.SESSION_MAP.size() %>,登录用户:<%=ApplicationConstants.CURRENT_LOGIN_COUNT%>。
    - - - - - - - - - - <% - for (String id : ApplicationConstants.SESSION_MAP.keySet()) { - HttpSession sess = ApplicationConstants.SESSION_MAP.get(id); - PersonInfo personInfo = (PersonInfo) sess.getAttribute("personInfo"); - %> - > - - - - - - - - - <% - } - %> + + + + + + + + + + <% + for (String id : ApplicationConstants.SESSION_MAP.keySet()) { + HttpSession sess = ApplicationConstants.SESSION_MAP.get(id); + PersonInfo personInfo = (PersonInfo) sess.getAttribute("personInfo"); + %> + > + + + + + + + + + <% + } + %>
    jsessionidaccountcreationTimelastAccessedTimenewactiveTimesip
    <%=id%> - <%=personInfo == null ? " " : personInfo.getAccount()%> - <%=DateFormat.getDateTimeInstance().format(sess.getCreationTime())%> - <%=DateFormat.getDateTimeInstance().format(new Date(sess.getLastAccessedTime()))%> - <%=sess.isNew()%> - <%=sess.getAttribute("activeTimes")%> - <%=sess.getAttribute("ip") %> -
    jsessionidaccountcreationTimelastAccessedTimenewactiveTimesip
    <%=id%> + <%=personInfo == null ? " " : personInfo.getAccount()%> + <%=DateFormat.getDateTimeInstance().format(sess.getCreationTime())%> + <%=DateFormat.getDateTimeInstance().format(new Date(sess.getLastAccessedTime()))%> + <%=sess.isNew()%> + <%=sess.getAttribute("activeTimes")%> + <%=sess.getAttribute("ip") %> +
    diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/testLoginSessionListener.jsp b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/testLoginSessionListener.jsp index c3f0679d..7ef99320 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/testLoginSessionListener.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/testLoginSessionListener.jsp @@ -3,66 +3,66 @@ <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> <% - String action = request.getParameter("action"); - String account = request.getParameter("account"); + String action = request.getParameter("action"); + String account = request.getParameter("account"); - if ("login".equals(action) && account.trim().length() > 0) { + if ("login".equals(action) && account.trim().length() > 0) { - // 登录,将personInfo放入session - PersonInfo personInfo = new PersonInfo(); - personInfo.setAccount(account.trim().toLowerCase()); - personInfo.setIp(request.getRemoteAddr()); - personInfo.setLoginDate(new java.util.Date()); + // 登录,将personInfo放入session + PersonInfo personInfo = new PersonInfo(); + personInfo.setAccount(account.trim().toLowerCase()); + personInfo.setIp(request.getRemoteAddr()); + personInfo.setLoginDate(new java.util.Date()); - session.setAttribute("personInfo", personInfo); + session.setAttribute("personInfo", personInfo); - response.sendRedirect(response.encodeRedirectURL(request.getRequestURI())); - return; - } else if ("logout".equals(action)) { + response.sendRedirect(response.encodeRedirectURL(request.getRequestURI())); + return; + } else if ("logout".equals(action)) { - // 注销,将personInfo从session中移除 - session.removeAttribute("personInfo"); + // 注销,将personInfo从session中移除 + session.removeAttribute("personInfo"); - response.sendRedirect(response.encodeRedirectURL(request.getRequestURI())); - return; - } + response.sendRedirect(response.encodeRedirectURL(request.getRequestURI())); + return; + } %> - - Insert title here - + + Insert title here + - - - 欢迎您,${ personInfo.account }。
    - 您的登录IP为${ personInfo.ip },
    - 登录时间为。 - 退出 + + + 欢迎您,${ personInfo.account }。
    + 您的登录IP为${ personInfo.ip },
    + 登录时间为。 + 退出 - - -
    + + +
    - - - ${ msg } - -
    - 帐号: - - -
    -
    + + + ${ msg } + +
    + 帐号: + + +
    +
    diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java index b04f2c2b..26b94cdb 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java @@ -1,16 +1,14 @@ package io.github.dunwu.javaee.server; +import java.util.ArrayList; +import java.util.List; import org.apache.commons.lang3.StringUtils; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.webapp.WebAppClassLoader; import org.eclipse.jetty.webapp.WebAppContext; -import java.util.ArrayList; -import java.util.List; - /** - * JettyFactory 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij - * 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 + * JettyFactory 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 * * @author Zhang Peng */ @@ -18,23 +16,26 @@ public class JettyFactory { public static final int IDE_ECLIPSE = 0; + public static final int IDE_INTELLIJ = 1; + public static final String ACTIVE_PROFILE = "spring.profiles.active"; + public static final String DEFAULT_PROFILE = "spring.profiles.default"; + public static final String DEVELOPMENT = "development"; + private static final int PORT = 9527; + private static final String CONTEXT = "/"; + private static final String RESOURCE_BASE_PATH = "src/main/webapp"; + private static final String WEB_XML_PATH = "/WEB-INF/web.xml"; - private static final String[] TLD_JAR_NAMES = new String[] { "sitemesh", "spring-webmvc", "tiles" }; - private static final String WINDOWS_WEBDEFAULT_PATH = "jetty/webdefault.xml"; - /** - * 在Spring启动前,设置profile的环境变量。 - */ - public static void setProfileAsSystemProperty(String profile) { - System.setProperty(ACTIVE_PROFILE, profile); - } + private static final String[] TLD_JAR_NAMES = new String[] {"sitemesh", "spring-webmvc", "tiles"} + + private static final String WINDOWS_WEBDEFAULT_PATH = "jetty/webdefault.xml"; public static Server initServer() { setProfileAsSystemProperty(DEVELOPMENT); @@ -44,6 +45,13 @@ public static Server initServer() { return server; } + /** + * 在Spring启动前,设置profile的环境变量。 + */ + public static void setProfileAsSystemProperty(String profile) { + System.setProperty(ACTIVE_PROFILE, profile); + } + public static void initWebAppContext(Server server, int type) throws Exception { System.out.println("[INFO] Application loading"); WebAppContext webAppContext = (WebAppContext) server.getHandler(); @@ -54,37 +62,17 @@ public static void initWebAppContext(Server server, int type) throws Exception { if (IDE_INTELLIJ == type) { webAppContext.setDefaultsDescriptor(WINDOWS_WEBDEFAULT_PATH); supportJspAndSetTldJarNames(server, TLD_JAR_NAMES); - } - else { + } else { webAppContext.setParentLoaderPriority(true); } System.out.println("[INFO] Application loaded"); } - public static void reloadWebAppContext(Server server) throws Exception { - WebAppContext webAppContext = (WebAppContext) server.getHandler(); - System.out.println("[INFO] Application reloading"); - webAppContext.stop(); - WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext); - classLoader.addClassPath(getAbsolutePath() + "target/classes"); - classLoader.addClassPath(getAbsolutePath() + "target/test-classes"); - webAppContext.setClassLoader(classLoader); - webAppContext.start(); - System.out.println("[INFO] Application reloaded"); - } - - public static void startServer(Server server) throws Exception { - System.out.println("[HINT] Don't forget to set -XX:MaxPermSize=128m"); - server.start(); - System.out.println("Server running at http://localhost:" + PORT + CONTEXT); - System.out.println("[HINT] Hit Enter to reload the application quickly"); - } - public static String getAbsolutePath() { String path = null; String folderPath = JettyFactory.class.getProtectionDomain().getCodeSource().getLocation().getPath() - .substring(1); + .substring(1); if (folderPath.indexOf("target") > 0) { path = folderPath.substring(0, folderPath.indexOf("target")); } @@ -97,9 +85,9 @@ public static void supportJspAndSetTldJarNames(Server server, String... jarNames // AnnotationConfiguration in // order to correctly set up the jsp container org.eclipse.jetty.webapp.Configuration.ClassList classlist = org.eclipse.jetty.webapp.Configuration.ClassList - .setServerDefault(server); + .setServerDefault(server); classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", - "org.eclipse.jetty.annotations.AnnotationConfiguration"); + "org.eclipse.jetty.annotations.AnnotationConfiguration"); // Set the ContainerIncludeJarPattern so that jetty examines these container-path // jars for // tlds, web-fragments etc. @@ -118,7 +106,26 @@ public static void supportJspAndSetTldJarNames(Server server, String... jarNames } context.setAttribute("org.eclipse.jetty.io.github.dunwu.javaee.server.webapp.ContainerIncludeJarPattern", - StringUtils.join(list, '|')); + StringUtils.join(list, '|')); + } + + public static void reloadWebAppContext(Server server) throws Exception { + WebAppContext webAppContext = (WebAppContext) server.getHandler(); + System.out.println("[INFO] Application reloading"); + webAppContext.stop(); + WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext); + classLoader.addClassPath(getAbsolutePath() + "target/classes"); + classLoader.addClassPath(getAbsolutePath() + "target/test-classes"); + webAppContext.setClassLoader(classLoader); + webAppContext.start(); + System.out.println("[INFO] Application reloaded"); + } + + public static void startServer(Server server) throws Exception { + System.out.println("[HINT] Don't forget to set -XX:MaxPermSize=128m"); + server.start(); + System.out.println("Server running at http://localhost:" + PORT + CONTEXT); + System.out.println("[HINT] Hit Enter to reload the application quickly"); } } diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/ListenerDemosBootstrap.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/ListenerDemosBootstrap.java index 2e7973e6..3ec9a1a0 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/ListenerDemosBootstrap.java +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/ListenerDemosBootstrap.java @@ -26,8 +26,7 @@ public static void main(String[] args) throws Exception { JettyFactory.reloadWebAppContext(server); } } - } - catch (Exception e) { + } catch (Exception e) { e.printStackTrace(); System.exit(-1); } diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/test/resources/jetty/webdefault.xml b/codes/javaee-tutorial/javaee-tutorial-listener/src/test/resources/jetty/webdefault.xml index 65ec9cae..b991d44c 100644 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/test/resources/jetty/webdefault.xml +++ b/codes/javaee-tutorial/javaee-tutorial-listener/src/test/resources/jetty/webdefault.xml @@ -1,76 +1,76 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://xmlns.jcp.org/xml/ns/javaee" + xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" + metadata-complete="false" + version="3.1"> - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - Default web.xml file. - This file is applied to a Web application before it's own WEB_INF/web.xml file - + + Default web.xml file. + This file is applied to a Web application before it's own WEB_INF/web.xml file + - - - - - - org.eclipse.jetty.servlet.listener.ELContextCleaner - + + + + + + org.eclipse.jetty.servlet.listener.ELContextCleaner + - - - - - - org.eclipse.jetty.servlet.listener.IntrospectorCleaner - + + + + + + org.eclipse.jetty.servlet.listener.IntrospectorCleaner + - - - - + + + + - - - - - - + + + + + - - - default - org.eclipse.jetty.servlet.DefaultServlet - - aliases - false - - - acceptRanges - true - - - dirAllowed - true - - - welcomeServlets - false - - - redirectWelcome - false - - - maxCacheSize - 256000000 - - - maxCachedFileSize - 200000000 - - - maxCachedFiles - 2048 - - - gzip - false - - - etags - false - - - useFileMappedBuffer - false - - - - 0 - + + + default + org.eclipse.jetty.servlet.DefaultServlet + + aliases + false + + + acceptRanges + true + + + dirAllowed + true + + + welcomeServlets + false + + + redirectWelcome + false + + + maxCacheSize + 256000000 + + + maxCachedFileSize + 200000000 + + + maxCachedFiles + 2048 + + + gzip + false + + + etags + false + + + useFileMappedBuffer + false + + + + 0 + - - default - / - + + default + / + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - jsp - org.eclipse.jetty.jsp.JettyJspServlet - - logVerbosityLevel - DEBUG - - - fork - false - - - xpoweredBy - false - - - compilerTargetVM - 1.7 - - - compilerSourceVM - 1.7 - - - 0 - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + jsp + org.eclipse.jetty.jsp.JettyJspServlet + + logVerbosityLevel + DEBUG + + + fork + false + + + xpoweredBy + false + + + compilerTargetVM + 1.7 + + + compilerSourceVM + 1.7 + + + 0 + - - jsp - *.jsp - *.jspf - *.jspx - *.xsp - *.JSP - *.JSPF - *.JSPX - *.XSP - + + jsp + *.jsp + *.jspf + *.jspx + *.xsp + *.JSP + *.JSPF + *.JSPX + *.XSP + - - - - - 30 - + + + + + 30 + - - - - - - - + + + + + + + - - - - - index.html - index.htm - index.jsp - + + + + + index.html + index.htm + index.jsp + - - - - - - ar - ISO-8859-6 - - - be - ISO-8859-5 - - - bg - ISO-8859-5 - - - ca - ISO-8859-1 - - - cs - ISO-8859-2 - - - da - ISO-8859-1 - - - de - ISO-8859-1 - - - el - ISO-8859-7 - - - en - ISO-8859-1 - - - es - ISO-8859-1 - - - et - ISO-8859-1 - - - fi - ISO-8859-1 - - - fr - ISO-8859-1 - - - hr - ISO-8859-2 - - - hu - ISO-8859-2 - - - is - ISO-8859-1 - - - it - ISO-8859-1 - - - iw - ISO-8859-8 - - - ja - Shift_JIS - - - ko - EUC-KR - - - lt - ISO-8859-2 - - - lv - ISO-8859-2 - - - mk - ISO-8859-5 - - - nl - ISO-8859-1 - - - no - ISO-8859-1 - - - pl - ISO-8859-2 - - - pt - ISO-8859-1 - - - ro - ISO-8859-2 - - - ru - ISO-8859-5 - - - sh - ISO-8859-5 - - - sk - ISO-8859-2 - - - sl - ISO-8859-2 - - - sq - ISO-8859-2 - - - sr - ISO-8859-5 - - - sv - ISO-8859-1 - - - tr - ISO-8859-9 - - - uk - ISO-8859-5 - - - zh - GB2312 - - - zh_TW - Big5 - - + + + + + + ar + ISO-8859-6 + + + be + ISO-8859-5 + + + bg + ISO-8859-5 + + + ca + ISO-8859-1 + + + cs + ISO-8859-2 + + + da + ISO-8859-1 + + + de + ISO-8859-1 + + + el + ISO-8859-7 + + + en + ISO-8859-1 + + + es + ISO-8859-1 + + + et + ISO-8859-1 + + + fi + ISO-8859-1 + + + fr + ISO-8859-1 + + + hr + ISO-8859-2 + + + hu + ISO-8859-2 + + + is + ISO-8859-1 + + + it + ISO-8859-1 + + + iw + ISO-8859-8 + + + ja + Shift_JIS + + + ko + EUC-KR + + + lt + ISO-8859-2 + + + lv + ISO-8859-2 + + + mk + ISO-8859-5 + + + nl + ISO-8859-1 + + + no + ISO-8859-1 + + + pl + ISO-8859-2 + + + pt + ISO-8859-1 + + + ro + ISO-8859-2 + + + ru + ISO-8859-5 + + + sh + ISO-8859-5 + + + sk + ISO-8859-2 + + + sl + ISO-8859-2 + + + sq + ISO-8859-2 + + + sr + ISO-8859-5 + + + sv + ISO-8859-1 + + + tr + ISO-8859-9 + + + uk + ISO-8859-5 + + + zh + GB2312 + + + zh_TW + Big5 + + - - - - - - Disable TRACE - / - TRACE - - - - - - Enable everything but TRACE - / - TRACE - - + + + + + + Disable TRACE + / + TRACE + + + + + + Enable everything but TRACE + / + TRACE + + diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/pom.xml b/codes/javaee-tutorial/javaee-tutorial-oss/pom.xml index 1eb2e364..db27089f 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/pom.xml +++ b/codes/javaee-tutorial/javaee-tutorial-oss/pom.xml @@ -1,154 +1,154 @@ - 4.0.0 + 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"> + 4.0.0 - io.github.dunwu - javaee-tutorial-oss - 1.0.0 - jar - javaee-tutorial-oss - JavaEE 学习笔记之 OSS(Open Source Software) + io.github.dunwu + javaee-tutorial-oss + 1.0.0 + jar + javaee-tutorial-oss + JavaEE 学习笔记之 OSS(Open Source Software) - - UTF-8 - 1.7 - ${java.version} - ${java.version} - + + UTF-8 + 1.7 + ${java.version} + ${java.version} + - - - - ch.qos.logback - logback-classic - - - org.slf4j - jcl-over-slf4j - - + + + + ch.qos.logback + logback-classic + + + org.slf4j + jcl-over-slf4j + + - - - commons-codec - commons-codec - - - org.bouncycastle - bcprov-jdk15on - 1.54 - + + + commons-codec + commons-codec + + + org.bouncycastle + bcprov-jdk15on + 1.54 + - + - - - org.jsoup - jsoup - 1.9.2 - - + + + org.jsoup + jsoup + 1.9.2 + + - - - com.google.zxing - core - 3.3.0 - - - com.google.zxing - javase - 3.3.0 - - - net.coobird - thumbnailator - - - net.sf.jmimemagic - jmimemagic - 0.1.4 - - + + + com.google.zxing + core + 3.3.0 + + + com.google.zxing + javase + 3.3.0 + + + net.coobird + thumbnailator + + + net.sf.jmimemagic + jmimemagic + 0.1.4 + + - - - org.apache.activemq - activemq-all - 5.14.1 - - + + + org.apache.activemq + activemq-all + 5.14.1 + + - - - com.alibaba - fastjson - - - com.fasterxml.jackson.core - jackson-core - - - com.fasterxml.jackson.core - jackson-databind - - + + + com.alibaba + fastjson + + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.core + jackson-databind + + - - - javax.mail - javax.mail-api - - - javax.validation - validation-api - - + + + javax.mail + javax.mail-api + + + javax.validation + validation-api + + - - - org.apache.velocity - velocity - 1.7 - - + + + org.apache.velocity + velocity + 1.7 + + - - - junit - junit - test - - - org.assertj - assertj-core - test - - + + + junit + junit + test + + + org.assertj + assertj-core + test + + - - - org.apache.commons - commons-lang3 - - - org.apache.commons - commons-collections4 - - - + + + org.apache.commons + commons-lang3 + + + org.apache.commons + commons-collections4 + + + - - - - io.github.dunwu - dunwu-dependencies - 0.4.2-SNAPSHOT - pom - import - - - + + + + io.github.dunwu + dunwu-dependencies + 0.4.2-SNAPSHOT + pom + import + + + diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/DsaCoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/DsaCoder.java index 97e12fbf..e58fc382 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/DsaCoder.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/DsaCoder.java @@ -1,17 +1,14 @@ package io.github.dunwu.javaee.oss.encode.digest; -import org.apache.commons.codec.binary.Base64; - -import java.security.*; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; +import org.apache.commons.codec.binary.Base64; /** - * @Title DsaCoder - * @Description 数字签名算法(Digital Signature Algorithm, DSA)工具类。 DSA是一种数字签名算法。 - * DSA仅支持SHA系列算法,而JDK仅支持SHA1withDSA。 - * @Author victor zhang - * @Date 2016年7月21日 + * 数字签名算法(Digital Signature Algorithm, DSA)工具类。 DSA是一种数字签名算法。 DSA仅支持SHA系列算法,而JDK仅支持SHA1withDSA。 + * + * @author Zhang Peng + * @since 2016年7月21日 */ public class DsaCoder { @@ -30,6 +27,15 @@ public DsaCoder() throws Exception { keyPair = initKey(); } + private KeyPair initKey() throws Exception { + // 初始化密钥对生成器 + KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM); + // 实例化密钥对生成器 + keyPairGen.initialize(KEY_SIZE); + // 实例化密钥对 + return keyPairGen.genKeyPair(); + } + public static void main(String[] args) throws Exception { String msg = "Hello World"; DsaCoder dsa = new DsaCoder(); @@ -51,6 +57,10 @@ public byte[] signature(byte[] data, byte[] privateKey) throws Exception { return signature.sign(); } + public byte[] getPrivateKey() { + return keyPair.getPrivate().getEncoded(); + } + public boolean verify(byte[] data, byte[] publicKey, byte[] sign) throws Exception { X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey); KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); @@ -62,21 +72,8 @@ public boolean verify(byte[] data, byte[] publicKey, byte[] sign) throws Excepti return signature.verify(sign); } - private KeyPair initKey() throws Exception { - // 初始化密钥对生成器 - KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM); - // 实例化密钥对生成器 - keyPairGen.initialize(KEY_SIZE); - // 实例化密钥对 - return keyPairGen.genKeyPair(); - } - public byte[] getPublicKey() { return keyPair.getPublic().getEncoded(); } - public byte[] getPrivateKey() { - return keyPair.getPrivate().getEncoded(); - } - } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/HmacCoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/HmacCoder.java index ad83e3ef..c0878d00 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/HmacCoder.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/HmacCoder.java @@ -1,26 +1,17 @@ package io.github.dunwu.javaee.oss.encode.digest; -import org.apache.commons.codec.binary.Base64; - import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; +import org.apache.commons.codec.binary.Base64; /** - * @Title HmacCoder - * @Description 消息认证码算法(Message Autherntication Code, MAC)是基于密码的消息摘要算法。 - * 它兼容了MD/SHA的特性,并以此为基础加入了密钥。 - * @Author victor zhang - * @Date 2016年7月21日 + * 消息认证码算法(Message Autherntication Code, MAC)是基于密码的消息摘要算法。 它兼容了MD/SHA的特性,并以此为基础加入了密钥。 + * + * @author Zhang Peng + * @since 2016年7月21日 */ public class HmacCoder { - public static byte[] encode(byte[] plaintext, byte[] secretKey, HmacTypeEn type) throws Exception { - SecretKeySpec keySpec = new SecretKeySpec(secretKey, type.name()); - Mac mac = Mac.getInstance(keySpec.getAlgorithm()); - mac.init(keySpec); - return mac.doFinal(plaintext); - } - public static void main(String[] args) throws Exception { String msg = "Hello World!"; byte[] secretKey = "Secret_Key".getBytes("UTF8"); @@ -29,13 +20,19 @@ public static void main(String[] args) throws Exception { System.out.println("摘要: " + Base64.encodeBase64URLSafeString(digest)); } + public static byte[] encode(byte[] plaintext, byte[] secretKey, HmacTypeEn type) throws Exception { + SecretKeySpec keySpec = new SecretKeySpec(secretKey, type.name()); + Mac mac = Mac.getInstance(keySpec.getAlgorithm()); + mac.init(keySpec); + return mac.doFinal(plaintext); + } + /** * JDK支持HmacMD5, HmacSHA1, HmacSHA256, HmacSHA384, HmacSHA512 */ public enum HmacTypeEn { HmacMD5, HmacSHA1, HmacSHA256, HmacSHA384, HmacSHA512; - } } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/MdCoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/MdCoder.java index 4d7dc478..b466a25e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/MdCoder.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/MdCoder.java @@ -1,17 +1,29 @@ package io.github.dunwu.javaee.oss.encode.digest; -import org.apache.commons.codec.binary.Base64; - import java.security.MessageDigest; +import org.apache.commons.codec.binary.Base64; /** - * @Title MdCoder - * @Description 消息摘要算法(Message Digest, MD)是消息摘要算法。 - * @Author victor zhang - * @Date 2016年7月21日 + * 消息摘要算法(Message Digest, MD)是消息摘要算法。 + * + * @author Zhang Peng + * @since 2016年7月21日 */ public class MdCoder { + public static void main(String[] args) throws Exception { + String msg = "Hello World!"; + byte[] encodeWithBase64 = MdCoder.encodeWithBase64(msg.getBytes(), MdTypeEn.MD5); + + String result = String.format("%s摘要:%s", MdTypeEn.MD5.name(), new String(encodeWithBase64)); + System.out.println("原文: " + msg); + System.out.println(result); + } + + public static byte[] encodeWithBase64(byte[] input, MdTypeEn type) throws Exception { + return Base64.encodeBase64URLSafe(encode(input, type)); + } + public static byte[] encode(byte[] input, MdTypeEn type) throws Exception { // 根据类型,初始化消息摘要对象 MessageDigest md5Digest = MessageDigest.getInstance(type.name()); @@ -23,26 +35,12 @@ public static byte[] encode(byte[] input, MdTypeEn type) throws Exception { return md5Digest.digest(); } - public static byte[] encodeWithBase64(byte[] input, MdTypeEn type) throws Exception { - return Base64.encodeBase64URLSafe(encode(input, type)); - } - - public static void main(String[] args) throws Exception { - String msg = "Hello World!"; - byte[] encodeWithBase64 = MdCoder.encodeWithBase64(msg.getBytes(), MdTypeEn.MD5); - - String result = String.format("%s摘要:%s", MdTypeEn.MD5.name(), new String(encodeWithBase64)); - System.out.println("原文: " + msg); - System.out.println(result); - } - /** * JDK支持MD2和MD5两种MD算法 */ public enum MdTypeEn { MD2, MD5 - } } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/ShaCoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/ShaCoder.java index cc76c08e..f22efaf3 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/ShaCoder.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/ShaCoder.java @@ -1,17 +1,29 @@ package io.github.dunwu.javaee.oss.encode.digest; -import org.apache.commons.codec.binary.Base64; - import java.security.MessageDigest; +import org.apache.commons.codec.binary.Base64; /** - * @Title MsgDigestDemo - * @Description 安全散列算法(Secure Hash Algorithm, SHA)是消息摘要算法 - * @Author victor zhang - * @Date 2016年7月21日 + * 安全散列算法(Secure Hash Algorithm, SHA)是消息摘要算法 + * + * @author Zhang Peng + * @since 2016年7月21日 */ public class ShaCoder { + public static void main(String[] args) throws Exception { + String msg = "Hello World!"; + byte[] encodeWithBase64 = ShaCoder.encodeWithBase64(msg.getBytes(), ShaTypeEn.SHA384); + + String result = String.format("%s摘要:%s", ShaTypeEn.SHA384.getName(), new String(encodeWithBase64)); + System.out.println("原文: " + msg); + System.out.println(result); + } + + public static byte[] encodeWithBase64(byte[] input, ShaTypeEn type) throws Exception { + return Base64.encodeBase64URLSafe(encode(input, type)); + } + public static byte[] encode(byte[] input, ShaTypeEn type) throws Exception { // 根据类型,初始化消息摘要对象 MessageDigest md5Digest = MessageDigest.getInstance(type.getName()); @@ -23,19 +35,6 @@ public static byte[] encode(byte[] input, ShaTypeEn type) throws Exception { return md5Digest.digest(); } - public static byte[] encodeWithBase64(byte[] input, ShaTypeEn type) throws Exception { - return Base64.encodeBase64URLSafe(encode(input, type)); - } - - public static void main(String[] args) throws Exception { - String msg = "Hello World!"; - byte[] encodeWithBase64 = ShaCoder.encodeWithBase64(msg.getBytes(), ShaTypeEn.SHA384); - - String result = String.format("%s摘要:%s", ShaTypeEn.SHA384.getName(), new String(encodeWithBase64)); - System.out.println("原文: " + msg); - System.out.println(result); - } - /** * JDK支持SHA1、SHA256、SHA384和SHA512几种SHA算法 */ @@ -52,7 +51,6 @@ public enum ShaTypeEn { public String getName() { return this.name; } - } } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/AESCoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/AESCoder.java index df47985d..5323b053 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/AESCoder.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/AESCoder.java @@ -1,16 +1,18 @@ package io.github.dunwu.javaee.oss.encode.encrypt; -import org.bouncycastle.util.encoders.Base64; - -import javax.crypto.*; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; import javax.crypto.spec.IvParameterSpec; -import java.security.*; +import org.bouncycastle.util.encoders.Base64; /** - * @Title AESCoder - * @Description AES安全编码:对称加密算法。DES的替代方案。 - * @Author victor zhang - * @Date 2016年7月14日 + * AES安全编码:对称加密算法。DES的替代方案。 + * + * @author Zhang Peng + * @since 2016年7月14日 */ public class AESCoder { @@ -38,6 +40,24 @@ public AESCoder() throws NoSuchAlgorithmException, NoSuchPaddingException { this.transformation = CIPHER_AES_DEFAULT; } + /** + * 根据随机数种子生成一个密钥 + * + * @return Key + * @throws NoSuchAlgorithmException + * @author Zhang Peng + * @since 2016年7月14日 + */ + private Key initKey() throws NoSuchAlgorithmException { + // 根据种子生成一个安全的随机数 + SecureRandom secureRandom = null; + secureRandom = new SecureRandom(SEED.getBytes()); + + KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHM_AES); + keyGen.init(secureRandom); + return keyGen.generateKey(); + } + public AESCoder(String transformation) throws NoSuchAlgorithmException, NoSuchPaddingException { this.key = initKey(); this.cipher = Cipher.getInstance(transformation); @@ -60,69 +80,49 @@ public static void main(String[] args) throws Exception { } /** - * @Title decrypt - * @Description 解密 - * @Author victor zhang - * @Date 2016年7月20日 - * @param input 密文 - * @return byte[] 明文 + * 加密 + * + * @param input 明文 + * @return byte[] 密文 * @throws InvalidKeyException * @throws IllegalBlockSizeException * @throws BadPaddingException * @throws InvalidAlgorithmParameterException + * @author Zhang Peng + * @since 2016年7月20日 */ - public byte[] decrypt(byte[] input) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, - InvalidAlgorithmParameterException { + public byte[] encrypt(byte[] input) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, + InvalidAlgorithmParameterException { if (transformation.equals(CIPHER_AES_CBC_PKCS5PADDING) || transformation.equals(CIPHER_AES_CBC_NOPADDING)) { - cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(getIV())); - } - else { - cipher.init(Cipher.DECRYPT_MODE, key); + cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(getIV())); + } else { + cipher.init(Cipher.ENCRYPT_MODE, key); } return cipher.doFinal(input); } /** - * @Title encrypt - * @Description 加密 - * @Author victor zhang - * @Date 2016年7月20日 - * @param input 明文 - * @return byte[] 密文 + * 解密 + * + * @param input 密文 + * @return byte[] 明文 * @throws InvalidKeyException * @throws IllegalBlockSizeException * @throws BadPaddingException * @throws InvalidAlgorithmParameterException + * @author Zhang Peng + * @since 2016年7月20日 */ - public byte[] encrypt(byte[] input) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, - InvalidAlgorithmParameterException { + public byte[] decrypt(byte[] input) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, + InvalidAlgorithmParameterException { if (transformation.equals(CIPHER_AES_CBC_PKCS5PADDING) || transformation.equals(CIPHER_AES_CBC_NOPADDING)) { - cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(getIV())); - } - else { - cipher.init(Cipher.ENCRYPT_MODE, key); + cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(getIV())); + } else { + cipher.init(Cipher.DECRYPT_MODE, key); } return cipher.doFinal(input); } - /** - * @Title initKey - * @Description 根据随机数种子生成一个密钥 - * @Author victor zhang - * @Date 2016年7月14日 - * @return Key - * @throws NoSuchAlgorithmException - */ - private Key initKey() throws NoSuchAlgorithmException { - // 根据种子生成一个安全的随机数 - SecureRandom secureRandom = null; - secureRandom = new SecureRandom(SEED.getBytes()); - - KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHM_AES); - keyGen.init(secureRandom); - return keyGen.generateKey(); - } - private byte[] getIV() { String iv = "0123456789ABCDEF"; // IV length: must be 16 bytes long return iv.getBytes(); diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/Base64Demo.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/Base64Demo.java index 71042d6f..7ebb28a4 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/Base64Demo.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/Base64Demo.java @@ -1,19 +1,19 @@ package io.github.dunwu.javaee.oss.encode.encrypt; -import org.apache.commons.codec.binary.Base64; - import java.io.UnsupportedEncodingException; +import org.apache.commons.codec.binary.Base64; /** - * @Title Base64Demo - * @Description Base64编码、解码范例 - * @Author victor zhang - * @Date 2016年7月21日 + * Base64编码、解码范例 + * + * @author Zhang Peng + * @since 2016年7月21日 */ public class Base64Demo { public static void main(String[] args) throws UnsupportedEncodingException { - String url = "https://www.baidu.com/s?wd=Base64&rsv_spt=1&rsv_iqid=0xa9188d560005131f&issp=1&f=3&rsv_bp=0&rsv_idx=2&ie=utf-8&tn=baiduhome_pg&rsv_enter=1&rsv_sug3=1&rsv_sug1=1&rsv_sug7=001&rsv_sug2=1&rsp=0&rsv_sug9=es_2_1&rsv_sug4=2153&rsv_sug=9"; + String url = + "https://www.baidu.com/s?wd=Base64&rsv_spt=1&rsv_iqid=0xa9188d560005131f&issp=1&f=3&rsv_bp=0&rsv_idx=2&ie=utf-8&tn=baiduhome_pg&rsv_enter=1&rsv_sug3=1&rsv_sug1=1&rsv_sug7=001&rsv_sug2=1&rsp=0&rsv_sug9=es_2_1&rsv_sug4=2153&rsv_sug=9"; // byte[] encoded = Base64.encodeBase64(url.getBytes("UTF8")); // 标准的Base64编码 byte[] encoded = Base64.encodeBase64URLSafe(url.getBytes("UTF8")); // URL安全的Base64编码 byte[] decoded = Base64.decodeBase64(encoded); diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/DESCoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/DESCoder.java index 5e363d07..5f5761f6 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/DESCoder.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/DESCoder.java @@ -1,16 +1,19 @@ package io.github.dunwu.javaee.oss.encode.encrypt; -import org.bouncycastle.util.encoders.Base64; - -import javax.crypto.*; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; import javax.crypto.spec.IvParameterSpec; -import java.security.*; +import org.bouncycastle.util.encoders.Base64; /** - * @Title DESCoder - * @Description DES安全编码:是经典的对称加密算法。密钥仅56位,且迭代次数偏少。已被视为并不安全的加密算法。 - * @Author victor zhang - * @Date 2016年7月14日 + * DES安全编码:是经典的对称加密算法。密钥仅56位,且迭代次数偏少。已被视为并不安全的加密算法。 + * + * @author Zhang Peng + * @since 2016年7月14日 */ public class DESCoder { @@ -38,8 +41,27 @@ public DESCoder() throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuc this.transformation = CIPHER_DES_DEFAULT; } + /** + * 根据随机数种子生成一个密钥 + * + * @return Key + * @throws NoSuchAlgorithmException + * @throws NoSuchProviderException + * @author Zhang Peng + * @since 2016年7月14日 + */ + private Key initKey() throws NoSuchAlgorithmException, NoSuchProviderException { + // 根据种子生成一个安全的随机数 + SecureRandom secureRandom = null; + secureRandom = new SecureRandom(SEED.getBytes()); + + KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHM_DES); + keyGen.init(secureRandom); + return keyGen.generateKey(); + } + public DESCoder(String transformation) - throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException { + throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException { this.key = initKey(); this.cipher = Cipher.getInstance(transformation); this.transformation = transformation; @@ -60,70 +82,49 @@ public static void main(String[] args) throws Exception { } /** - * @Title decrypt - * @Description 解密 - * @Author victor zhang - * @Date 2016年7月20日 - * @param input 密文 - * @return byte[] 明文 + * 加密 + * + * @param input 明文 + * @return byte[] 密文 * @throws InvalidKeyException * @throws IllegalBlockSizeException * @throws BadPaddingException * @throws InvalidAlgorithmParameterException + * @author Zhang Peng + * @since 2016年7月20日 */ - public byte[] decrypt(byte[] input) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, - InvalidAlgorithmParameterException { + public byte[] encrypt(byte[] input) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, + InvalidAlgorithmParameterException { if (transformation.equals(CIPHER_DES_CBC_PKCS5PADDING) || transformation.equals(CIPHER_DES_CBC_NOPADDING)) { - cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(getIV())); - } - else { - cipher.init(Cipher.DECRYPT_MODE, key); + cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(getIV())); + } else { + cipher.init(Cipher.ENCRYPT_MODE, key); } return cipher.doFinal(input); } /** - * @Title encrypt - * @Description 加密 - * @Author victor zhang - * @Date 2016年7月20日 - * @param input 明文 - * @return byte[] 密文 + * 解密 + * + * @param input 密文 + * @return byte[] 明文 * @throws InvalidKeyException * @throws IllegalBlockSizeException * @throws BadPaddingException * @throws InvalidAlgorithmParameterException + * @author Zhang Peng + * @since 2016年7月20日 */ - public byte[] encrypt(byte[] input) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, - InvalidAlgorithmParameterException { + public byte[] decrypt(byte[] input) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, + InvalidAlgorithmParameterException { if (transformation.equals(CIPHER_DES_CBC_PKCS5PADDING) || transformation.equals(CIPHER_DES_CBC_NOPADDING)) { - cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(getIV())); - } - else { - cipher.init(Cipher.ENCRYPT_MODE, key); + cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(getIV())); + } else { + cipher.init(Cipher.DECRYPT_MODE, key); } return cipher.doFinal(input); } - /** - * @Title initKey - * @Description 根据随机数种子生成一个密钥 - * @Author victor zhang - * @Date 2016年7月14日 - * @Return Key - * @throws NoSuchAlgorithmException - * @throws NoSuchProviderException - */ - private Key initKey() throws NoSuchAlgorithmException, NoSuchProviderException { - // 根据种子生成一个安全的随机数 - SecureRandom secureRandom = null; - secureRandom = new SecureRandom(SEED.getBytes()); - - KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHM_DES); - keyGen.init(secureRandom); - return keyGen.generateKey(); - } - private byte[] getIV() { String iv = "01234567"; // IV length: must be 8 bytes long return iv.getBytes(); diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/DESedeCoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/DESedeCoder.java index 3e60747a..1af976f8 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/DESedeCoder.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/DESedeCoder.java @@ -1,20 +1,19 @@ package io.github.dunwu.javaee.oss.encode.encrypt; -import org.apache.commons.codec.binary.Base64; -import org.bouncycastle.jce.provider.BouncyCastleProvider; - -import javax.crypto.Cipher; -import javax.crypto.KeyGenerator; import java.security.Key; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.Security; +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import org.apache.commons.codec.binary.Base64; +import org.bouncycastle.jce.provider.BouncyCastleProvider; /** - * @Title DESedeCoder - * @Description DESede安全编码,DES的升级版,支持更长的密钥,基本算法不变。 - * @Author victor zhang - * @Date 2016年7月20日 + * DESede安全编码,DES的升级版,支持更长的密钥,基本算法不变。 + * + * @author Zhang Peng + * @since 2016年7月20日 */ public class DESedeCoder { @@ -37,6 +36,18 @@ public DESedeCoder() throws NoSuchAlgorithmException, NoSuchProviderException { this.key = initKey(); } + private Key initKey() throws NoSuchAlgorithmException, NoSuchProviderException { + // 标准的密钥生成 + // KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHM); + // keyGen.init(112); + + // 标准的密钥生成不支持128位。如果要使用,需引入Bouncy Castle的加密算法,方法如下 + Security.addProvider(new BouncyCastleProvider()); + KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHM, "BC"); + keyGen.init(128); + return keyGen.generateKey(); + } + public static void main(String[] args) throws Exception { DESedeCoder desedeCoder = new DESedeCoder(); String message = "Hello World!"; @@ -59,16 +70,4 @@ public byte[] decrypt(byte[] ciphertext) throws Exception { return cipher.doFinal(ciphertext); } - private Key initKey() throws NoSuchAlgorithmException, NoSuchProviderException { - // 标准的密钥生成 - // KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHM); - // keyGen.init(112); - - // 标准的密钥生成不支持128位。如果要使用,需引入Bouncy Castle的加密算法,方法如下 - Security.addProvider(new BouncyCastleProvider()); - KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHM, "BC"); - keyGen.init(128); - return keyGen.generateKey(); - } - } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/PBECoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/PBECoder.java index 8dd6857e..c4f22552 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/PBECoder.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/PBECoder.java @@ -1,22 +1,20 @@ package io.github.dunwu.javaee.oss.encode.encrypt; -import org.apache.commons.codec.binary.Base64; - +import java.security.Key; +import java.security.SecureRandom; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.PBEParameterSpec; -import java.security.Key; -import java.security.SecureRandom; +import org.apache.commons.codec.binary.Base64; /** - * @Title PBECode - * @Description 基于口令加密(Password Based Encryption, PBE),是一种对称加密算法。 - * 其特点是:口令由用户自己掌管,采用随机数(这里叫做盐)杂凑多重加密等方法保证数据的安全性。 + * 基于口令加密(Password Based Encryption, PBE),是一种对称加密算法。 其特点是:口令由用户自己掌管,采用随机数(这里叫做盐)杂凑多重加密等方法保证数据的安全性。 * PBE没有密钥概念,密钥在其他对称加密算法中是经过计算得出的,PBE则使用口令替代了密钥。 - * @Author victor zhang - * @Date 2016年7月20日 + * + * @author Zhang Peng + * @since 2016年7月20日 */ public class PBECoder { @@ -33,6 +31,17 @@ public PBECoder(String password) throws Exception { this.key = initKey(password); } + private byte[] initSalt() throws Exception { + SecureRandom secureRandom = new SecureRandom(); + return secureRandom.generateSeed(8); // 盐长度必须为8字节 + } + + private Key initKey(String password) throws Exception { + PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray()); + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(KEY_ALGORITHM); + return keyFactory.generateSecret(keySpec); + } + public static void main(String[] args) throws Exception { PBECoder encode = new PBECoder("123456"); String message = "Hello World!"; @@ -44,6 +53,20 @@ public static void main(String[] args) throws Exception { System.out.println("明文:" + new String(plaintext)); } + public byte[] encrypt(byte[] plaintext) throws Exception { + PBEParameterSpec paramSpec = new PBEParameterSpec(salt, ITERATION_COUNT); + Cipher cipher = Cipher.getInstance(KEY_ALGORITHM); + cipher.init(Cipher.ENCRYPT_MODE, key, paramSpec); + return cipher.doFinal(plaintext); + } + + public byte[] decrypt(byte[] ciphertext) throws Exception { + PBEParameterSpec paramSpec = new PBEParameterSpec(salt, ITERATION_COUNT); + Cipher cipher = Cipher.getInstance(KEY_ALGORITHM); + cipher.init(Cipher.DECRYPT_MODE, key, paramSpec); + return cipher.doFinal(ciphertext); + } + public static void test1() throws Exception { // 产生盐 @@ -65,29 +88,4 @@ public static void test1() throws Exception { new String(ciphertext); } - public byte[] encrypt(byte[] plaintext) throws Exception { - PBEParameterSpec paramSpec = new PBEParameterSpec(salt, ITERATION_COUNT); - Cipher cipher = Cipher.getInstance(KEY_ALGORITHM); - cipher.init(Cipher.ENCRYPT_MODE, key, paramSpec); - return cipher.doFinal(plaintext); - } - - public byte[] decrypt(byte[] ciphertext) throws Exception { - PBEParameterSpec paramSpec = new PBEParameterSpec(salt, ITERATION_COUNT); - Cipher cipher = Cipher.getInstance(KEY_ALGORITHM); - cipher.init(Cipher.DECRYPT_MODE, key, paramSpec); - return cipher.doFinal(ciphertext); - } - - private byte[] initSalt() throws Exception { - SecureRandom secureRandom = new SecureRandom(); - return secureRandom.generateSeed(8); // 盐长度必须为8字节 - } - - private Key initKey(String password) throws Exception { - PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray()); - SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(KEY_ALGORITHM); - return keyFactory.generateSecret(keySpec); - } - } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/RSACoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/RSACoder.java index de1dafbe..79881b66 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/RSACoder.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/RSACoder.java @@ -1,29 +1,37 @@ package io.github.dunwu.javaee.oss.encode.encrypt; -import org.apache.commons.codec.binary.Base64; - -import javax.crypto.Cipher; -import java.security.*; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; +import javax.crypto.Cipher; +import org.apache.commons.codec.binary.Base64; /** - * @Title RSACoder - * @Description RSA安全编码:非对称加密算法。它既可以用来加密、解密,也可以用来做数字签名 - * @Author victor zhang - * @Date 2016年7月20日 + * RSA安全编码:非对称加密算法。它既可以用来加密、解密,也可以用来做数字签名 + * + * @author Zhang Peng + * @since 2016年7月20日 */ public class RSACoder { public final static String KEY_ALGORITHM = "RSA"; public final static String SIGN_ALGORITHM = "MD5WithRSA"; + private KeyPair keyPair; public RSACoder() throws Exception { this.keyPair = initKeyPair(); } + private KeyPair initKeyPair() throws Exception { + // KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象 + KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM); + // 初始化密钥对生成器,密钥大小为1024位 + keyPairGen.initialize(1024); + // 生成一个密钥对 + return keyPairGen.genKeyPair(); + } + public static void main(String[] args) throws Exception { String msg = "Hello World!"; RSACoder coder = new RSACoder(); @@ -56,15 +64,6 @@ public static void main(String[] args) throws Exception { System.out.println("验证结果:" + result); } - public byte[] encryptByPublicKey(byte[] plaintext, byte[] key) throws Exception { - X509EncodedKeySpec keySpec = new X509EncodedKeySpec(key); - KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); - PublicKey publicKey = keyFactory.generatePublic(keySpec); - Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); - cipher.init(Cipher.ENCRYPT_MODE, publicKey); - return cipher.doFinal(plaintext); - } - public byte[] encryptByPrivateKey(byte[] plaintext, byte[] key) throws Exception { PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(key); KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); @@ -83,6 +82,15 @@ public byte[] decryptByPublicKey(byte[] ciphertext, byte[] key) throws Exception return cipher.doFinal(ciphertext); } + public byte[] encryptByPublicKey(byte[] plaintext, byte[] key) throws Exception { + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(key); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); + PublicKey publicKey = keyFactory.generatePublic(keySpec); + Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + return cipher.doFinal(plaintext); + } + public byte[] decryptByPrivateKey(byte[] ciphertext, byte[] key) throws Exception { PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(key); KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); @@ -103,6 +111,10 @@ public byte[] signature(byte[] data, byte[] privateKey, RsaSignTypeEn type) thro return signature.sign(); } + public byte[] getPrivateKey() { + return keyPair.getPrivate().getEncoded(); + } + public boolean verify(byte[] data, byte[] publicKey, byte[] sign, RsaSignTypeEn type) throws Exception { X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey); KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); @@ -118,23 +130,9 @@ public byte[] getPublicKey() { return keyPair.getPublic().getEncoded(); } - public byte[] getPrivateKey() { - return keyPair.getPrivate().getEncoded(); - } - - private KeyPair initKeyPair() throws Exception { - // KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象 - KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM); - // 初始化密钥对生成器,密钥大小为1024位 - keyPairGen.initialize(1024); - // 生成一个密钥对 - return keyPairGen.genKeyPair(); - } - public enum RsaSignTypeEn { MD2WithRSA, MD5WithRSA, SHA1WithRSA - } } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/DownloadPolicy.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/DownloadPolicy.java index 5ded00f9..d5720446 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/DownloadPolicy.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/DownloadPolicy.java @@ -1,11 +1,10 @@ package io.github.dunwu.javaee.oss.encode.sample; -import org.apache.commons.lang3.StringUtils; - import java.io.Serializable; +import org.apache.commons.lang3.StringUtils; /** - * Created by victor zhang on 2016/7/22. + * Created by Zhang Peng on 2016/7/22. */ public class DownloadPolicy implements Serializable { @@ -13,34 +12,39 @@ public class DownloadPolicy implements Serializable { * */ private static final long serialVersionUID = 1159078308838844309L; + /** * 操作类型 */ private final String operate = UploadConstant.TOKEN_DOWNLOAD; + /** * 文件ID */ private Long fileId; + /** * 下载文件所属空间 */ private String namespace; + /** * 令牌有效的截止时间。用Unix时间表示。单位秒 */ private Long deadline; + /** * 允许下载文件类型 */ private String fType = UploadConstant.SUPPORT_FILE_TYPE; /** + * 判断数据是否有效 + * * @param policy * @return boolean - * @title isValid - * @description 判断数据是否有效 - * @author victor zhang - * @date 2016年7月22日 + * @author Zhang Peng + * @since 2016年7月22日 */ public static boolean isValid(DownloadPolicy policy) { // 检查必要项是否为空 diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/ModifyPolicy.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/ModifyPolicy.java index dd9cebf0..0ed0a444 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/ModifyPolicy.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/ModifyPolicy.java @@ -1,39 +1,42 @@ package io.github.dunwu.javaee.oss.encode.sample; -import org.apache.commons.lang3.StringUtils; - import java.io.Serializable; +import org.apache.commons.lang3.StringUtils; /** - * Created by victor zhang on 2016/7/22. + * Created by Zhang Peng on 2016/7/22. */ public class ModifyPolicy implements Serializable { private static final long serialVersionUID = -547821705196510884L; + /** * 操作类型 */ private final String operate = UploadConstant.TOKEN_MODIFY; + /** * 下载文件所属空间 */ private String namespace; + /** * 令牌有效的截止时间。用Unix时间表示。单位秒 */ private Long deadline; + /** * 允许下载文件类型 */ private String fType = UploadConstant.SUPPORT_FILE_TYPE; /** + * 判断数据是否有效 + * * @param policy * @return boolean - * @title isValid - * @description 判断数据是否有效 - * @author victor zhang - * @date 2016年7月22日 + * @author Zhang Peng + * @since 2016年7月22日 */ public static boolean isValid(ModifyPolicy policy) { // 检查必要项是否为空 diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/TokenUtil.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/TokenUtil.java index 7fe2fe61..35b5f1ab 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/TokenUtil.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/TokenUtil.java @@ -16,45 +16,31 @@ public final class TokenUtil { private static final String SECRET_KEY = "SECRET_KEY"; - public static String getToken(byte[] policy, byte[] accessKey, byte[] secretKey, String tokenType) - throws Exception { - JSONObject policyJson = JSONObject.parseObject(new String(policy)); + public static Object testGetPolicy(String token, String tokenType) throws Exception { + String[] params = token.split(SEPARATOR); + // byte[] accessKey = Base64.decodeBase64(params[0]); + String digestBase64 = params[1]; + String policyBase64 = params[2]; - // 检查令牌是否符合系统规格 - if (TokenTypeEN.UPLOAD.name().equalsIgnoreCase(tokenType)) { - UploadPolicy uploadPolicy = JSONObject.toJavaObject(policyJson, UploadPolicy.class); - if (!UploadPolicy.isValid(uploadPolicy)) { - throw new Exception("The policy is not conform to the specifications of the system."); - } - } - else if (TokenTypeEN.DOWNLOAD.name().equalsIgnoreCase(tokenType)) { - DownloadPolicy downloadPolicy = JSONObject.toJavaObject(policyJson, DownloadPolicy.class); - if (!DownloadPolicy.isValid(downloadPolicy)) { - throw new Exception("The policy is not conform to the specifications of the system."); - } - } - else if (TokenTypeEN.MODIFY.name().equalsIgnoreCase(tokenType)) { - ModifyPolicy modifyPolicy = JSONObject.toJavaObject(policyJson, ModifyPolicy.class); - if (!ModifyPolicy.isValid(modifyPolicy)) { - throw new Exception("The policy is not conform to the specifications of the system."); - } - } - else { - throw new Exception("Required token is not supported."); + if (TokenTypeEN.UPLOAD.name().equals(tokenType)) { + UploadPolicy policy = (UploadPolicy) getPolicy(policyBase64, digestBase64, SECRET_KEY.getBytes(), + tokenType); + return policy; + } else if (TokenTypeEN.DOWNLOAD.name().equals(tokenType)) { + DownloadPolicy policy = (DownloadPolicy) getPolicy(policyBase64, digestBase64, SECRET_KEY.getBytes(), + tokenType); + return policy; + } else if (TokenTypeEN.MODIFY.name().equals(tokenType)) { + ModifyPolicy policy = (ModifyPolicy) getPolicy(policyBase64, digestBase64, SECRET_KEY.getBytes(), + tokenType); + return policy; + } else { + return null; } - - // 根据secretKey和policy生成消息摘要(使用基于口令编码的HmacSHA256算法) - byte[] digest = HmacCoder.encode(policy, secretKey, HmacCoder.HmacTypeEn.HmacSHA512); - - // Token = AccessKey::Digest::Policy。数据拼接之前都要做URL安全的Base64编码 - String token = Base64.encodeBase64URLSafeString(accessKey) + SEPARATOR - + Base64.encodeBase64URLSafeString(digest) + SEPARATOR + Base64.encodeBase64URLSafeString(policy); - - return token; } public static Object getPolicy(String policyBase64, String digestBase64, byte[] secretKey, String tokenType) - throws Exception { + throws Exception { String policy = new String(Base64.decodeBase64(policyBase64)); // 根据secretKey和policy验证摘要是否被篡改 @@ -67,17 +53,41 @@ public static Object getPolicy(String policyBase64, String digestBase64, byte[] JSONObject json = JSONObject.parseObject(policy); if (TokenTypeEN.UPLOAD.name().equalsIgnoreCase(tokenType)) { return JSONObject.toJavaObject(json, UploadPolicy.class); - } - else if (TokenTypeEN.DOWNLOAD.name().equalsIgnoreCase(tokenType)) { + } else if (TokenTypeEN.DOWNLOAD.name().equalsIgnoreCase(tokenType)) { return JSONObject.toJavaObject(json, DownloadPolicy.class); - } - else if (TokenTypeEN.MODIFY.name().equalsIgnoreCase(tokenType)) { + } else if (TokenTypeEN.MODIFY.name().equalsIgnoreCase(tokenType)) { return JSONObject.toJavaObject(json, ModifyPolicy.class); } return null; } + public static void main(String[] args) throws Exception { + testGetToken(TokenTypeEN.DOWNLOAD.name()); + } + + public static String testGetToken(String tokenType) throws Exception { + String policy = null; + if (TokenTypeEN.UPLOAD.name().equals(tokenType)) { + policy = initUploadPolicy(); + } else if (TokenTypeEN.DOWNLOAD.name().equals(tokenType)) { + policy = initDownladPolicy(); + } else if (TokenTypeEN.MODIFY.name().equals(tokenType)) { + policy = initModifyPolicy(); + } + String policyBase64 = Base64.encodeBase64URLSafeString(policy.getBytes()); + String accessKeyBase64 = Base64.encodeBase64URLSafeString(ACCESS_KEY.getBytes()); + + System.out.println(String.format("============== %s ==============", tokenType)); + System.out.println("policy:" + policy); + System.out.println("policyBase64:" + policyBase64); + System.out.println("accessKeyBase64:" + accessKeyBase64); + + String token = getToken(policy.getBytes(), ACCESS_KEY.getBytes(), SECRET_KEY.getBytes(), tokenType); + System.out.println("Token:" + token); + return token; + } + private static String initUploadPolicy() { long deadline = System.currentTimeMillis() / 1000 + 3600 * 7; UploadPolicy policy = new UploadPolicy(); @@ -106,65 +116,43 @@ private static String initModifyPolicy() { return JSON.toJSONString(policy); } - public static String testGetToken(String tokenType) throws Exception { - String policy = null; - if (TokenTypeEN.UPLOAD.name().equals(tokenType)) { - policy = initUploadPolicy(); - } - else if (TokenTypeEN.DOWNLOAD.name().equals(tokenType)) { - policy = initDownladPolicy(); - } - else if (TokenTypeEN.MODIFY.name().equals(tokenType)) { - policy = initModifyPolicy(); - } - String policyBase64 = Base64.encodeBase64URLSafeString(policy.getBytes()); - String accessKeyBase64 = Base64.encodeBase64URLSafeString(ACCESS_KEY.getBytes()); - - System.out.println(String.format("============== %s ==============", tokenType)); - System.out.println("policy:" + policy); - System.out.println("policyBase64:" + policyBase64); - System.out.println("accessKeyBase64:" + accessKeyBase64); - - String token = getToken(policy.getBytes(), ACCESS_KEY.getBytes(), SECRET_KEY.getBytes(), tokenType); - System.out.println("Token:" + token); - return token; - } - - public static Object testGetPolicy(String token, String tokenType) throws Exception { - String[] params = token.split(SEPARATOR); - // byte[] accessKey = Base64.decodeBase64(params[0]); - String digestBase64 = params[1]; - String policyBase64 = params[2]; + public static String getToken(byte[] policy, byte[] accessKey, byte[] secretKey, String tokenType) + throws Exception { + JSONObject policyJson = JSONObject.parseObject(new String(policy)); - if (TokenTypeEN.UPLOAD.name().equals(tokenType)) { - UploadPolicy policy = (UploadPolicy) getPolicy(policyBase64, digestBase64, SECRET_KEY.getBytes(), - tokenType); - return policy; - } - else if (TokenTypeEN.DOWNLOAD.name().equals(tokenType)) { - DownloadPolicy policy = (DownloadPolicy) getPolicy(policyBase64, digestBase64, SECRET_KEY.getBytes(), - tokenType); - return policy; - } - else if (TokenTypeEN.MODIFY.name().equals(tokenType)) { - ModifyPolicy policy = (ModifyPolicy) getPolicy(policyBase64, digestBase64, SECRET_KEY.getBytes(), - tokenType); - return policy; - } - else { - return null; + // 检查令牌是否符合系统规格 + if (TokenTypeEN.UPLOAD.name().equalsIgnoreCase(tokenType)) { + UploadPolicy uploadPolicy = JSONObject.toJavaObject(policyJson, UploadPolicy.class); + if (!UploadPolicy.isValid(uploadPolicy)) { + throw new Exception("The policy is not conform to the specifications of the system."); + } + } else if (TokenTypeEN.DOWNLOAD.name().equalsIgnoreCase(tokenType)) { + DownloadPolicy downloadPolicy = JSONObject.toJavaObject(policyJson, DownloadPolicy.class); + if (!DownloadPolicy.isValid(downloadPolicy)) { + throw new Exception("The policy is not conform to the specifications of the system."); + } + } else if (TokenTypeEN.MODIFY.name().equalsIgnoreCase(tokenType)) { + ModifyPolicy modifyPolicy = JSONObject.toJavaObject(policyJson, ModifyPolicy.class); + if (!ModifyPolicy.isValid(modifyPolicy)) { + throw new Exception("The policy is not conform to the specifications of the system."); + } + } else { + throw new Exception("Required token is not supported."); } - } + // 根据secretKey和policy生成消息摘要(使用基于口令编码的HmacSHA256算法) + byte[] digest = HmacCoder.encode(policy, secretKey, HmacCoder.HmacTypeEn.HmacSHA512); - public static void main(String[] args) throws Exception { - testGetToken(TokenTypeEN.DOWNLOAD.name()); + // Token = AccessKey::Digest::Policy。数据拼接之前都要做URL安全的Base64编码 + String token = Base64.encodeBase64URLSafeString(accessKey) + SEPARATOR + + Base64.encodeBase64URLSafeString(digest) + SEPARATOR + Base64.encodeBase64URLSafeString(policy); + + return token; } public enum TokenTypeEN { UPLOAD, DOWNLOAD, MODIFY - } } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/UploadConstant.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/UploadConstant.java index 6cf495aa..8c6affbf 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/UploadConstant.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/UploadConstant.java @@ -1,12 +1,11 @@ package io.github.dunwu.javaee.oss.encode.sample; -import org.apache.commons.collections.CollectionUtils; - import java.util.HashSet; import java.util.Set; +import org.apache.commons.collections.CollectionUtils; /** - * Created by victor zhang on 2016/7/26. + * Created by Zhang Peng on 2016/7/26. */ public class UploadConstant { @@ -27,10 +26,10 @@ public class UploadConstant { public static final String SUPPORT_FILE_TYPE = "pdf|doc|docx|png|jpg|jpeg|gif"; public static final Set SUPPORT_FILE_TYPE_SET; + static { SUPPORT_FILE_TYPE_SET = new HashSet(); String[] supportedTypes = UploadConstant.SUPPORT_FILE_TYPE.split("\\|"); CollectionUtils.addAll(SUPPORT_FILE_TYPE_SET, supportedTypes); } - } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/UploadPolicy.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/UploadPolicy.java index 12a3f739..2afad08a 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/UploadPolicy.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/UploadPolicy.java @@ -1,55 +1,62 @@ package io.github.dunwu.javaee.oss.encode.sample; -import org.apache.commons.lang3.StringUtils; - import java.io.Serializable; +import org.apache.commons.lang3.StringUtils; /** - * Created by victor zhang on 2016/7/22. + * Created by Zhang Peng on 2016/7/22. */ public class UploadPolicy implements Serializable { private static final long serialVersionUID = 8289239747395166646L; + /** * 操作类型 */ private final String operate = UploadConstant.TOKEN_UPLOAD; + /** * 上传文件所属空间 */ private String namespace; + /** * 令牌有效的截止时间。用Unix时间表示。单位秒 */ private Long deadline; + /** * 文件上传后保留时间。单位天。默认值-1,表示永久保留 */ private Integer deleteAfterDays = -1; + /** * 上传文件大小上限。单位Byte。 */ private Long fsizeMin = UploadConstant.FSIZE_MIN_DEFAULT; + /** * 上传文件大小上限。单位Byte。 */ private Long fsizeMax = UploadConstant.FSIZE_MAX_DEFAULT; + /** * 允许上传文件类型 */ private String fType = UploadConstant.SUPPORT_FILE_TYPE; + /** * 是否认证令牌。0表示认证,1表示不认证。默认为0 */ private Integer verifyToken = 0; /** + * 判断数据是否有效 + * * @param policy * @return boolean - * @title isValid - * @description 判断数据是否有效 - * @author victor zhang - * @date 2016年7月22日 + * @author Zhang Peng + * @since 2016年7月22日 */ public static boolean isValid(UploadPolicy policy) { // 检查必要项是否为空 @@ -65,7 +72,7 @@ public static boolean isValid(UploadPolicy policy) { // 判断文件大小的上限、下限是否符合系统规格 if (policy.fsizeMin > policy.fsizeMax || policy.fsizeMin < UploadConstant.FSIZE_MIN - || policy.fsizeMax > UploadConstant.FSIZE_MAX) { + || policy.fsizeMax > UploadConstant.FSIZE_MAX) { return false; } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/html/CnblogParser.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/html/CnblogParser.java index 2d584d25..c8027e39 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/html/CnblogParser.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/html/CnblogParser.java @@ -1,21 +1,19 @@ /** - * The Apache License 2.0 - * Copyright (c) 2016 Victor Zhang + * The Apache License 2.0 Copyright (c) 2016 Victor Zhang */ package io.github.dunwu.javaee.oss.html; +import java.io.IOException; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; -import java.io.IOException; - /** * 博客园博文抓取工具 * * @author Victor Zhang - * @date 2016/11/8. + * @since 2016/11/8. */ public class CnblogParser { @@ -31,6 +29,7 @@ public static void main(String[] args) throws Exception { /** * 获取指定页HTML 文档指定的body + * * @throws IOException */ private static int printAllTitleInPage(String blogUrl, int page) throws IOException { diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/html/XiamiParser.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/html/XiamiParser.java index 63a9292d..e5708593 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/html/XiamiParser.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/html/XiamiParser.java @@ -1,24 +1,22 @@ /** - * The Apache License 2.0 - * Copyright (c) 2016 Victor Zhang + * The Apache License 2.0 Copyright (c) 2016 Victor Zhang */ package io.github.dunwu.javaee.oss.html; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; import org.apache.commons.collections.CollectionUtils; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; -import java.io.IOException; -import java.util.HashSet; -import java.util.Set; - /** * 获取虾米网我的音乐中所有曲目 * * @author Victor Zhang - * @date 2016/11/8. + * @since 2016/11/8. */ public class XiamiParser { @@ -37,6 +35,7 @@ public static void main(String[] args) throws Exception { /** * 获取指定页HTML 文档指定的body + * * @throws IOException */ private Set getSongInfoSet(String blogUrl, int page) throws IOException { @@ -71,6 +70,19 @@ public class SongInfo { private String artist; + @Override + public boolean equals(Object obj) { + if (obj.getClass() != SongInfo.class) { + return false; + } + SongInfo external = (SongInfo) obj; + if (external.getName().equals(this.getName()) && external.getArtist().equals(this.getArtist())) { + return true; + } else { + return false; + } + } + public String getName() { return name; } @@ -87,17 +99,6 @@ public void setArtist(String artist) { this.artist = artist; } - @Override - public boolean equals(Object obj) { - if (obj.getClass() != SongInfo.class) - return false; - SongInfo external = (SongInfo) obj; - if (external.getName().equals(this.getName()) && external.getArtist().equals(this.getArtist())) - return true; - else - return false; - } - @Override public int hashCode() { return 1; diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/ImageUtil.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/ImageUtil.java index f579acc5..d0edc676 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/ImageUtil.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/ImageUtil.java @@ -1,10 +1,11 @@ /** - * The Apache License 2.0 - * Copyright (c) 2016 Victor Zhang + * The Apache License 2.0 Copyright (c) 2016 Victor Zhang */ package io.github.dunwu.javaee.oss.image; import io.github.dunwu.javaee.oss.image.dto.ImageParamDTO; +import java.awt.image.BufferedImage; +import javax.imageio.ImageIO; import net.coobird.thumbnailator.Thumbnails; import net.coobird.thumbnailator.geometry.Positions; import net.sf.jmimemagic.*; @@ -12,15 +13,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.imageio.ImageIO; -import java.awt.image.BufferedImage; -import java.io.*; - /** * 图片工具类 * * @author Victor Zhang - * @date 2017/1/16. + * @since 2017/1/16. */ public class ImageUtil { @@ -39,38 +36,6 @@ public static void toFile(String oldFile, String newFile, ImageParamDTO params) builder.toFile(newFile); } - public static BufferedImage toBufferedImage(String oldFile, ImageParamDTO params) throws IOException { - if (StringUtils.isBlank(oldFile)) { - logger.error("原文件名或目标文件名为空"); - return null; - } - Thumbnails.Builder builder = Thumbnails.of(oldFile); - fillBuilderWithParams(builder, params); - if (null == builder) { - return null; - } - return builder.asBufferedImage(); - } - - public static OutputStream toOutputStream(InputStream input, OutputStream output, ImageParamDTO params) - throws IOException { - Thumbnails.Builder builder = Thumbnails.of(input); - if (null == builder) { - return null; - } - - try { - fillBuilderWithParams(builder, params); - builder.toOutputStream(output); - } - catch (IOException e) { - logger.error("图片处理失败\n" + e.getMessage()); - throw e; - } - - return output; - } - private static void fillBuilderWithParams(Thumbnails.Builder builder, ImageParamDTO params) throws IOException { if (null == params) { throw new IOException("图片格式化参数为空"); @@ -79,14 +44,11 @@ private static void fillBuilderWithParams(Thumbnails.Builder builder, ImageParam // 按照一定规则改变原图尺寸 if (null != params.getWidth() && null != params.getHeight()) { builder.size(params.getWidth(), params.getHeight()); - } - else if (null != params.getXscale() && null != params.getYscale()) { + } else if (null != params.getXscale() && null != params.getYscale()) { builder.scale(params.getXscale(), params.getYscale()); - } - else if (null != params.getScale()) { + } else if (null != params.getScale()) { builder.scale(params.getScale(), params.getScale()); - } - else { + } else { builder.scale(1.0); // 如果没有设置尺寸参数,默认大小为原图大小 } @@ -117,8 +79,40 @@ else if (null != params.getScale()) { } } + public static BufferedImage toBufferedImage(String oldFile, ImageParamDTO params) throws IOException { + if (StringUtils.isBlank(oldFile)) { + logger.error("原文件名或目标文件名为空"); + return null; + } + Thumbnails.Builder builder = Thumbnails.of(oldFile); + fillBuilderWithParams(builder, params); + if (null == builder) { + return null; + } + return builder.asBufferedImage(); + } + + public static OutputStream toOutputStream(InputStream input, OutputStream output, ImageParamDTO params) + throws IOException { + Thumbnails.Builder builder = Thumbnails.of(input); + if (null == builder) { + return null; + } + + try { + fillBuilderWithParams(builder, params); + builder.toOutputStream(output); + } catch (IOException e) { + logger.error("图片处理失败\n" + e.getMessage()); + throw e; + } + + return output; + } + /** * 获取文件的 ContentType + * * @param content * @return * @throws MagicParseException @@ -126,7 +120,7 @@ else if (null != params.getScale()) { * @throws MagicMatchNotFoundException */ public static String getContentType(byte[] content) - throws MagicParseException, MagicException, MagicMatchNotFoundException { + throws MagicParseException, MagicException, MagicMatchNotFoundException { MagicMatch match = Magic.getMagicMatch(content); return match.getMimeType(); } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/QRCodeUtil.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/QRCodeUtil.java index 4507a60f..376407f4 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/QRCodeUtil.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/QRCodeUtil.java @@ -6,25 +6,25 @@ import com.google.zxing.common.BitMatrix; import com.google.zxing.common.HybridBinarizer; import io.github.dunwu.javaee.oss.image.dto.BarcodeParamDTO; - -import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.FileInputStream; import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.Path; +import javax.imageio.ImageIO; /** * 二维码工具类 * * @author Victor Zhang - * @date 2017/1/16. + * @since 2017/1/16. */ public class QRCodeUtil { /** * 创建一个qrcode图片 - * @param content 加密信息,建议使用json格式 + * + * @param content 加密信息,建议使用json格式 * @param paramDTO qrcode 参数 * @throws WriterException * @throws IOException @@ -32,13 +32,14 @@ public class QRCodeUtil { public static void encode(String content, BarcodeParamDTO paramDTO) throws WriterException, IOException { // 生成矩阵 BitMatrix bitMatrix = new MultiFormatWriter().encode(content, paramDTO.getBarcodeFormat(), paramDTO.getWidth(), - paramDTO.getHeight(), paramDTO.getEncodeHints()); + paramDTO.getHeight(), paramDTO.getEncodeHints()); Path path = FileSystems.getDefault().getPath(paramDTO.getFilepath()); MatrixToImageWriter.writeToPath(bitMatrix, paramDTO.getImageFormat(), path);// 输出图像 } /** * 解析 qrcode 图片 + * * @param paramDTO qrcode 参数 * @return */ @@ -50,8 +51,7 @@ public static String decode(BarcodeParamDTO paramDTO) { BinaryBitmap bitmap = new BinaryBitmap(binarizer); Result result = new MultiFormatReader().decode(bitmap, paramDTO.getDecodeHints()); return result.getText(); - } - catch (Exception e) { + } catch (Exception e) { e.printStackTrace(); return null; } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/dto/BarcodeParamDTO.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/dto/BarcodeParamDTO.java index 7a1c2a8a..49c97e41 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/dto/BarcodeParamDTO.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/dto/BarcodeParamDTO.java @@ -1,18 +1,16 @@ /** - * The Apache License 2.0 - * Copyright (c) 2016 Victor Zhang + * The Apache License 2.0 Copyright (c) 2016 Victor Zhang */ package io.github.dunwu.javaee.oss.image.dto; import com.google.zxing.BarcodeFormat; import com.google.zxing.DecodeHintType; import com.google.zxing.EncodeHintType; - import java.util.Map; /** * @author Victor Zhang - * @date 2017/1/17. + * @since 2017/1/17. */ public class BarcodeParamDTO { diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/dto/ImageParamDTO.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/dto/ImageParamDTO.java index 8f9c7019..19146965 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/dto/ImageParamDTO.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/dto/ImageParamDTO.java @@ -1,20 +1,18 @@ /** - * The Apache License 2.0 - * Copyright (c) 2016 Victor Zhang + * The Apache License 2.0 Copyright (c) 2016 Victor Zhang */ package io.github.dunwu.javaee.oss.image.dto; -import net.coobird.thumbnailator.geometry.Positions; - import java.io.Serializable; +import net.coobird.thumbnailator.geometry.Positions; /** * @author Victor Zhang - * @date 2017/1/16. + * @since 2017/1/16. */ public class ImageParamDTO implements Serializable { - public static String[] IMAGE_TYPES = { "png", "jpg", "jpeg", "bmp", "gif" }; + public static String[] IMAGE_TYPES = {"png", "jpg", "jpeg", "bmp", "gif"} private Integer width; // 宽度 @@ -36,31 +34,32 @@ public class ImageParamDTO implements Serializable { /** * 将位置类型码转换为 thumbnailator 可以识别的位置类型 + * * @param code * @return */ public static Positions getPostionsByCode(Integer code) { switch (code) { - case 1: - return Positions.TOP_LEFT; - case 2: - return Positions.TOP_CENTER; - case 3: - return Positions.TOP_RIGHT; - case 4: - return Positions.CENTER_LEFT; - case 5: - return Positions.CENTER; - case 6: - return Positions.CENTER_RIGHT; - case 7: - return Positions.BOTTOM_LEFT; - case 8: - return Positions.BOTTOM_CENTER; - case 9: - return Positions.BOTTOM_RIGHT; - default: - return null; + case 1: + return Positions.TOP_LEFT; + case 2: + return Positions.TOP_CENTER; + case 3: + return Positions.TOP_RIGHT; + case 4: + return Positions.CENTER_LEFT; + case 5: + return Positions.CENTER; + case 6: + return Positions.CENTER_RIGHT; + case 7: + return Positions.BOTTOM_LEFT; + case 8: + return Positions.BOTTOM_CENTER; + case 9: + return Positions.BOTTOM_RIGHT; + default: + return null; } } @@ -136,7 +135,6 @@ public void setWaterMark(WaterMark waterMark) { this.waterMark = waterMark; } - public static class WaterMark { private Integer position; diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSHelloWorld.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSHelloWorld.java index 52b1cf8a..7808119c 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSHelloWorld.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSHelloWorld.java @@ -1,16 +1,14 @@ /** - * The Apache License 2.0 - * Copyright (c) 2016 Victor Zhang + * The Apache License 2.0 Copyright (c) 2016 Victor Zhang */ package io.github.dunwu.javaee.oss.jms; -import org.apache.activemq.ActiveMQConnectionFactory; - import javax.jms.*; +import org.apache.activemq.ActiveMQConnectionFactory; /** * @author Victor Zhang - * @date 2016/11/29. + * @since 2016/11/29. */ public class JMSHelloWorld { @@ -82,8 +80,7 @@ public void run() { // Clean up session.close(); connection.close(); - } - catch (Exception e) { + } catch (Exception e) { System.out.println("Caught: " + e); e.printStackTrace(); } @@ -121,16 +118,14 @@ public void run() { TextMessage textMessage = (TextMessage) message; String text = textMessage.getText(); System.out.println("Received: " + text); - } - else { + } else { System.out.println("Received: " + message); } consumer.close(); session.close(); connection.close(); - } - catch (Exception e) { + } catch (Exception e) { System.out.println("Caught: " + e); e.printStackTrace(); } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSReceiver.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSReceiver.java index cc92f4d5..604ccbb8 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSReceiver.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSReceiver.java @@ -1,19 +1,17 @@ /** - * The Apache License 2.0 - * Copyright (c) 2016 Victor Zhang + * The Apache License 2.0 Copyright (c) 2016 Victor Zhang */ package io.github.dunwu.javaee.oss.jms; +import javax.jms.*; import org.apache.activemq.ActiveMQConnection; import org.apache.activemq.ActiveMQConnectionFactory; -import javax.jms.*; - /** * 消息的消费者 * * @author Victor Zhang - * @date 2016/11/28. + * @since 2016/11/28. */ public class JMSReceiver { @@ -29,7 +27,7 @@ public static void main(String[] args) { // 消费者,消息接收者 MessageConsumer consumer; connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_USER, - ActiveMQConnection.DEFAULT_PASSWORD, ActiveMQConnection.DEFAULT_BROKER_URL); + ActiveMQConnection.DEFAULT_PASSWORD, ActiveMQConnection.DEFAULT_BROKER_URL); try { // 构造从工厂得到连接对象 connection = connectionFactory.createConnection(); @@ -45,21 +43,18 @@ public static void main(String[] args) { TextMessage message = (TextMessage) consumer.receive(100000); if (null != message) { System.out.println("收到消息" + message.getText()); - } - else { + } else { break; } } - } - catch (Exception e) { + } catch (Exception e) { e.printStackTrace(); - } - finally { + } finally { try { - if (null != connection) + if (null != connection) { connection.close(); - } - catch (Throwable ignore) { + } + } catch (Throwable ignore) { } } } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSSender.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSSender.java index 1a92ae2d..0f11282d 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSSender.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSSender.java @@ -1,15 +1,14 @@ package io.github.dunwu.javaee.oss.jms; +import javax.jms.*; import org.apache.activemq.ActiveMQConnection; import org.apache.activemq.ActiveMQConnectionFactory; -import javax.jms.*; - /** * 消息的生产者 * * @author Victor Zhang - * @date 2016/11/28. + * @since 2016/11/28. */ public class JMSSender { @@ -29,7 +28,7 @@ public static void main(String[] args) { // TextMessage message; // 构造ConnectionFactory实例对象,此处采用ActiveMq的实现jar connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_USER, - ActiveMQConnection.DEFAULT_PASSWORD, ActiveMQConnection.DEFAULT_BROKER_URL); + ActiveMQConnection.DEFAULT_PASSWORD, ActiveMQConnection.DEFAULT_BROKER_URL); try { // 构造从工厂得到连接对象 connection = connectionFactory.createConnection(); @@ -46,22 +45,21 @@ public static void main(String[] args) { // 构造消息,此处写死,项目就是参数,或者方法获取 sendMessage(session, producer); session.commit(); - } - catch (Exception e) { + } catch (Exception e) { e.printStackTrace(); - } - finally { + } finally { try { - if (null != connection) + if (null != connection) { connection.close(); - } - catch (Throwable ignore) { + } + } catch (Throwable ignore) { } } } /** * 发送消息 + * * @param session * @param messageProducer 消息生产者 * @throws Exception @@ -74,7 +72,6 @@ public static void sendMessage(Session session, MessageProducer messageProducer) // 通过消息生产者发出消息 messageProducer.send(message); } - } } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/logging/JclDemo.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/logging/JclDemo.java index 99d2489b..fa75b912 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/logging/JclDemo.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/logging/JclDemo.java @@ -1,6 +1,5 @@ /** - * The Apache License 2.0 - * Copyright (c) 2016 Victor Zhang + * The Apache License 2.0 Copyright (c) 2016 Victor Zhang */ package io.github.dunwu.javaee.oss.logging; @@ -11,7 +10,7 @@ * 测试 common-logging + log4j 输出日志 * * @author Victor Zhang - * @date 2016/11/18. + * @since 2016/11/18. */ public class JclDemo { diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/logging/Slf4jDemo.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/logging/Slf4jDemo.java index fe43686e..6f9ce216 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/logging/Slf4jDemo.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/logging/Slf4jDemo.java @@ -1,6 +1,5 @@ /** - * The Apache License 2.0 - * Copyright (c) 2016 Victor Zhang + * The Apache License 2.0 Copyright (c) 2016 Victor Zhang */ package io.github.dunwu.javaee.oss.logging; @@ -11,7 +10,7 @@ * 测试 slf4j + logback 输出日志 * * @author Victor Zhang - * @date 2016/11/18. + * @since 2016/11/18. */ public class Slf4jDemo { diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/ForwardMail.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/ForwardMail.java index 61dc3970..b4a013be 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/ForwardMail.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/ForwardMail.java @@ -1,19 +1,18 @@ package io.github.dunwu.javaee.oss.mail; /** - * The Apache License 2.0 - * Copyright (c) 2016 victor zhang - */ + * The Apache License 2.0 Copyright (c) 2016 Zhang Peng + */ +import java.util.Date; +import java.util.Properties; import javax.mail.*; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; -import java.util.Date; -import java.util.Properties; /** - * @author victor zhang - * @date 2017/4/5. + * @author Zhang Peng + * @since 2017/4/5. */ public class ForwardMail { diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailConfigDTO.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailConfigDTO.java index 048aabeb..6f001ef6 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailConfigDTO.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailConfigDTO.java @@ -1,12 +1,11 @@ /** - * The Apache License 2.0 - * Copyright (c) 2016 Victor Zhang + * The Apache License 2.0 Copyright (c) 2016 Victor Zhang */ package io.github.dunwu.javaee.oss.mail; /** * @author Victor Zhang - * @date 2016/12/23. + * @since 2016/12/23. */ public class MailConfigDTO { diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailDTO.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailDTO.java index 8363d8e6..a032cb45 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailDTO.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailDTO.java @@ -1,6 +1,5 @@ /** - * The Apache License 2.0 - * Copyright (c) 2016 Victor Zhang + * The Apache License 2.0 Copyright (c) 2016 Victor Zhang */ package io.github.dunwu.javaee.oss.mail; @@ -8,7 +7,7 @@ /** * @author Victor Zhang - * @date 2016/12/22. + * @since 2016/12/22. */ public class MailDTO { diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailUtil.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailUtil.java index 2ea681e0..90882856 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailUtil.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailUtil.java @@ -3,25 +3,26 @@ */ package io.github.dunwu.javaee.oss.mail; -import org.apache.commons.lang3.StringUtils; - -import javax.mail.MessagingException; -import javax.mail.internet.InternetAddress; -import javax.mail.internet.MimeMessage; -import javax.mail.internet.MimeUtility; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Properties; +import javax.mail.MessagingException; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeUtility; +import org.apache.commons.lang3.StringUtils; /** * @author Victor Zhang - * @date 2016/12/22. + * @since 2016/12/22. */ public class MailUtil { private static final String TYPE_TEXT = "text"; + private static final String TYPE_HTML = "html"; + private MailConfigDTO configDTO; /** @@ -31,20 +32,12 @@ public MailUtil() { this.configDTO = initEmailConfig(); } - /** - * 以自定义配置初始化邮件收发工具 - */ - public MailUtil(MailConfigDTO configDTO) { - this.configDTO = configDTO; - } - private MailConfigDTO initEmailConfig() { MailConfigDTO configDTO = new MailConfigDTO(); Properties p = new Properties(); try { p.load(MailUtil.class.getResourceAsStream("/mail/mail.properties")); - } - catch (IOException e) { + } catch (IOException e) { e.printStackTrace(); } @@ -57,8 +50,16 @@ private MailConfigDTO initEmailConfig() { return configDTO; } + /** + * 以自定义配置初始化邮件收发工具 + */ + public MailUtil(MailConfigDTO configDTO) { + this.configDTO = configDTO; + } + /** * 发送邮件 + * * @param info * @throws MessagingException */ @@ -100,8 +101,7 @@ private boolean fillEmailHeader(MimeMessage message, MailDTO info) throws Messag // 邮件的发件人 if (StringUtils.isNotBlank(configDTO.getMailFromHost())) { message.setFrom(new InternetAddress(configDTO.getMailFromHost())); - } - else { + } else { System.out.println("发件人不能为空"); return false; } @@ -109,8 +109,7 @@ private boolean fillEmailHeader(MimeMessage message, MailDTO info) throws Messag // 邮件的收件人 if (StringUtils.isNotBlank(info.getTo())) { message.setRecipient(Message.RecipientType.TO, new InternetAddress(info.getTo())); - } - else { + } else { System.out.println("收件人不能为空"); return false; } @@ -128,8 +127,7 @@ private boolean fillEmailHeader(MimeMessage message, MailDTO info) throws Messag // 邮件的标题 if (StringUtils.isNotBlank(info.getCharset())) { message.setSubject(info.getSubject(), info.getCharset()); - } - else { + } else { message.setSubject(info.getSubject()); } @@ -147,13 +145,10 @@ private boolean fillEmailBody(MimeMessage message, MailDTO info) throws Messagin if (StringUtils.equals(info.getType(), TYPE_TEXT)) { if (StringUtils.isNotBlank(info.getCharset())) { message.setText(info.getText(), info.getCharset()); - } - else { + } else { message.setText(info.getText()); } - - } - else if (StringUtils.equals(info.getType(), TYPE_HTML)) { + } else if (StringUtils.equals(info.getType(), TYPE_HTML)) { String type = "text/html"; if (StringUtils.isNotBlank(info.getCharset())) { type += ";charset=" + info.getCharset(); diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendAttachmentMail.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendAttachmentMail.java index d675d84d..8581319b 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendAttachmentMail.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendAttachmentMail.java @@ -1,8 +1,8 @@ package io.github.dunwu.javaee.oss.mail; /** - * The Apache License 2.0 - * Copyright (c) 2016 victor zhang - */ + * The Apache License 2.0 Copyright (c) 2016 Zhang Peng + */ +import java.util.Properties; import javax.mail.Message; import javax.mail.Session; import javax.mail.Transport; @@ -10,11 +10,10 @@ import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; -import java.util.Properties; /** - * @author victor zhang - * @date 2017/4/5. + * @author Zhang Peng + * @since 2017/4/5. */ public class SendAttachmentMail { @@ -65,7 +64,7 @@ public static void main(String[] args) throws Exception { MimeMultipart mm = new MimeMultipart(); mm.setSubType("related"); mm.addBodyPart(text); - String[] files = { "D:\\00_Temp\\temp\\1.jpg", "D:\\00_Temp\\temp\\2.png" }; + String[] files = {"D:\\00_Temp\\temp\\1.jpg", "D:\\00_Temp\\temp\\2.png"} // 添加邮件附件 for (String filename : files) { diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendTemplateMail.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendTemplateMail.java index df6f51c7..7e89e9ac 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendTemplateMail.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendTemplateMail.java @@ -1,22 +1,20 @@ /** - * The Apache License 2.0 - * Copyright (c) 2016 Victor Zhang + * The Apache License 2.0 Copyright (c) 2016 Victor Zhang */ package io.github.dunwu.javaee.oss.mail; import io.github.dunwu.javaee.oss.template.VelocityUtil; -import org.apache.velocity.VelocityContext; - -import javax.mail.MessagingException; import java.util.ArrayList; import java.util.Date; import java.util.List; +import javax.mail.MessagingException; +import org.apache.velocity.VelocityContext; /** * @author Victor Zhang - * @date 2016/12/23. - * @see org.zp.javaee.tools.mail.MailUtil 注意:如果想要成功发送邮件,需要修改JavaParty项目 - * src\javaee\tools\src\main\resources\mail\mail.properties 中的 参数,请根据实际邮箱来配置。 + * @see org.zp.javaee.tools.mail.MailUtil 注意:如果想要成功发送邮件,需要修改JavaParty项目 src\javaee\tools\src\main\resources\mail\mail.properties + * 中的 参数,请根据实际邮箱来配置。 + * @since 2016/12/23. */ public class SendTemplateMail { @@ -38,7 +36,7 @@ public static void main(String[] args) throws MessagingException { list.add(item2); context.put("links", list); context.put("logo", - "http://images.cnblogs.com/cnblogs_com/jingmoxukong/709053/o_%e6%94%bb%e5%9f%8e%e7%8b%ae2.png"); + "http://images.cnblogs.com/cnblogs_com/jingmoxukong/709053/o_%e6%94%bb%e5%9f%8e%e7%8b%ae2.png"); MailDTO info = new MailDTO(); info.setTo(DEFAULT_TO); // 收件人邮箱 @@ -51,7 +49,6 @@ public static void main(String[] args) throws MessagingException { mailUtil.sendEmail(info); } - public static class Hyperlink { private String link; diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendTextMail.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendTextMail.java index 548e4141..fe4833a4 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendTextMail.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendTextMail.java @@ -1,18 +1,17 @@ package io.github.dunwu.javaee.oss.mail; /** - * The Apache License 2.0 - * Copyright (c) 2016 victor zhang - */ + * The Apache License 2.0 Copyright (c) 2016 Zhang Peng + */ +import java.util.Properties; import javax.mail.Message; import javax.mail.Session; import javax.mail.Transport; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; -import java.util.Properties; /** - * @author victor zhang - * @date 2017/4/5. + * @author Zhang Peng + * @since 2017/4/5. */ public class SendTextMail { diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/StoreMail.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/StoreMail.java index ff8250ec..32df1236 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/StoreMail.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/StoreMail.java @@ -1,17 +1,16 @@ package io.github.dunwu.javaee.oss.mail; /** - * The Apache License 2.0 - * Copyright (c) 2016 victor zhang - */ + * The Apache License 2.0 Copyright (c) 2016 Zhang Peng + */ +import java.util.Properties; import javax.mail.Folder; import javax.mail.Message; import javax.mail.Session; import javax.mail.Store; -import java.util.Properties; /** - * @author victor zhang - * @date 2017/4/5. + * @author Zhang Peng + * @since 2017/4/5. */ public class StoreMail { diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/sendHtmlMail.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/sendHtmlMail.java index f9a486b9..bdceb7a1 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/sendHtmlMail.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/sendHtmlMail.java @@ -1,8 +1,8 @@ package io.github.dunwu.javaee.oss.mail; /** - * The Apache License 2.0 - * Copyright (c) 2016 victor zhang - */ + * The Apache License 2.0 Copyright (c) 2016 Zhang Peng + */ +import java.util.Properties; import javax.activation.DataHandler; import javax.activation.FileDataSource; import javax.mail.Message; @@ -12,11 +12,10 @@ import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; -import java.util.Properties; /** - * @author victor zhang - * @date 2017/4/5. + * @author Zhang Peng + * @since 2017/4/5. */ public class sendHtmlMail { diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/LoadVelocityDemo.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/LoadVelocityDemo.java index 2710755f..4d21f306 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/LoadVelocityDemo.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/LoadVelocityDemo.java @@ -1,23 +1,21 @@ /** - * The Apache License 2.0 - * Copyright (c) 2016 Victor Zhang + * The Apache License 2.0 Copyright (c) 2016 Victor Zhang */ package io.github.dunwu.javaee.oss.template; -import org.apache.velocity.Template; -import org.apache.velocity.VelocityContext; -import org.apache.velocity.app.VelocityEngine; - import java.io.IOException; import java.io.StringWriter; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Properties; +import org.apache.velocity.Template; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.VelocityEngine; /** * @author Victor Zhang - * @date 2016/12/22. + * @since 2016/12/22. */ public class LoadVelocityDemo { @@ -50,7 +48,7 @@ public static void loadByFilepath() { Properties p = new Properties(); p.put(VelocityEngine.FILE_RESOURCE_LOADER_PATH, - "D:\\01_Workspace\\Project\\zp\\javaparty\\src\\toolbox\\template\\src\\main\\resources"); + "D:\\01_Workspace\\Project\\zp\\javaparty\\src\\toolbox\\template\\src\\main\\resources"); VelocityEngine ve = new VelocityEngine(); ve.init(p); Template t = ve.getTemplate("hello.vm"); diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityHelloWorld.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityHelloWorld.java index 09c17cb7..c5d9b3ee 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityHelloWorld.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityHelloWorld.java @@ -1,19 +1,17 @@ package io.github.dunwu.javaee.oss.template; /** - * The Apache License 2.0 - * Copyright (c) 2016 Victor Zhang - */ + * The Apache License 2.0 Copyright (c) 2016 Victor Zhang + */ +import java.io.StringWriter; import org.apache.velocity.Template; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.VelocityEngine; -import java.io.StringWriter; - /** * Velocity 的 HelloWorld 示例 * * @author Victor Zhang - * @date 2016/12/22. + * @since 2016/12/22. */ public class VelocityHelloWorld { @@ -22,7 +20,7 @@ public static void main(String args[]) { VelocityEngine velocityEngine = new VelocityEngine(); velocityEngine.setProperty(VelocityEngine.RESOURCE_LOADER, "file"); velocityEngine.setProperty(VelocityEngine.FILE_RESOURCE_LOADER_PATH, - "D:/01_Workspace/Project/zp/javaparty/src/toolbox/template/src/main/resources"); + "D:/01_Workspace/Project/zp/javaparty/src/toolbox/template/src/main/resources"); velocityEngine.init(); /* 2.创建一个上下文对象 */ diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityHelloWorld2.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityHelloWorld2.java index 96f0f5b9..19398dfb 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityHelloWorld2.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityHelloWorld2.java @@ -1,21 +1,19 @@ package io.github.dunwu.javaee.oss.template; /** - * The Apache License 2.0 - * Copyright (c) 2016 Victor Zhang - */ - -import org.apache.velocity.Template; -import org.apache.velocity.VelocityContext; -import org.apache.velocity.app.VelocityEngine; + * The Apache License 2.0 Copyright (c) 2016 Victor Zhang + */ import java.io.IOException; import java.io.StringWriter; import java.util.Properties; +import org.apache.velocity.Template; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.VelocityEngine; /** * Velocity 的 HelloWorld 示例 * * @author Victor Zhang - * @date 2016/12/22. + * @since 2016/12/22. */ public class VelocityHelloWorld2 { @@ -24,8 +22,7 @@ public static void main(String args[]) { Properties p = new Properties(); try { p.load(VelocityUtil.class.getResourceAsStream("/template/velocity.properties")); - } - catch (IOException e) { + } catch (IOException e) { e.printStackTrace(); } VelocityEngine velocityEngine = new VelocityEngine(); diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityUtil.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityUtil.java index 55c2a6a8..77945c52 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityUtil.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityUtil.java @@ -1,20 +1,18 @@ /** - * The Apache License 2.0 - * Copyright (c) 2016 Victor Zhang + * The Apache License 2.0 Copyright (c) 2016 Victor Zhang */ package io.github.dunwu.javaee.oss.template; -import org.apache.velocity.Template; -import org.apache.velocity.VelocityContext; -import org.apache.velocity.app.VelocityEngine; - import java.io.IOException; import java.io.StringWriter; import java.util.Properties; +import org.apache.velocity.Template; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.VelocityEngine; /** * @author Victor Zhang - * @date 2016/12/23. + * @since 2016/12/23. */ public class VelocityUtil { @@ -24,8 +22,7 @@ public class VelocityUtil { Properties props = new Properties(); try { props.load(VelocityUtil.class.getResourceAsStream("/template/velocity.properties")); - } - catch (IOException e) { + } catch (IOException e) { e.printStackTrace(); } velocityEngine = new VelocityEngine(); @@ -40,8 +37,7 @@ public static String getMergeOutput(VelocityContext context, String templateName String output = sw.toString(); try { sw.close(); - } - catch (IOException e) { + } catch (IOException e) { e.printStackTrace(); } return output; diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/html/example.html b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/html/example.html index dae73f9d..32c7cf6a 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/html/example.html +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/html/example.html @@ -1,6 +1,6 @@ - 测试html页面 + 测试html页面

    牛刀小试

    diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/log4j.xml b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/log4j.xml index d4c6b712..6e67642f 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/log4j.xml +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/log4j.xml @@ -3,44 +3,44 @@ - - - - + + + + - - - - - - - + + + + + + + - - - - - - - - - - - + + + + + + + + + + + - - - - - - + + + + + + - - - - - - \ No newline at end of file + + + + + + diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/logback.xml index 5fffa5ed..f4b2c110 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/logback.xml +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/logback.xml @@ -3,186 +3,186 @@ - - - - - - %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n - - - - - - - - ${user.dir}/logs/${DIR_NAME}/all.%d{yyyy-MM-dd}.log - 30 - - - - - 30MB - - - - %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n - - - - - - - ${user.dir}/logs/${DIR_NAME}/error.%d{yyyy-MM-dd}.log - 30 - - - - - 10MB - - - - ERROR - ACCEPT - DENY - - - - %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n - - - - - - - ${user.dir}/logs/${DIR_NAME}/warn.%d{yyyy-MM-dd}.log - 30 - - - - - 10MB - - - - WARN - ACCEPT - DENY - - - - %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n - - - - - - - ${user.dir}/logs/${DIR_NAME}/info.%d{yyyy-MM-dd}.log - 30 - - - - - 10MB - - - - INFO - ACCEPT - DENY - - - - %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n - - - - - - - ${user.dir}/logs/${DIR_NAME}/debug.%d{yyyy-MM-dd}.log - 30 - - - - - 10MB - - - - DEBUG - ACCEPT - DENY - - - - %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n - - - - - - - ${user.dir}/logs/${DIR_NAME}/trace.%d{yyyy-MM-dd}.log - 30 - - - - - 10MB - - - - TRACE - ACCEPT - DENY - - - - %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n - - - - - - - ${user.dir}/logs/${DIR_NAME}/springframework.%d{yyyy-MM-dd}.log - - 30 - - - - - 10MB - - - - %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n + + + + + + + + ${user.dir}/logs/${DIR_NAME}/all.%d{yyyy-MM-dd}.log + 30 + + + + + 30MB + + + + %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n + + + + + + + ${user.dir}/logs/${DIR_NAME}/error.%d{yyyy-MM-dd}.log + 30 + + + + + 10MB + + + + ERROR + ACCEPT + DENY + + + + %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n + + + + + + + ${user.dir}/logs/${DIR_NAME}/warn.%d{yyyy-MM-dd}.log + 30 + + + + + 10MB + + + + WARN + ACCEPT + DENY + + + + %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n + + + + + + + ${user.dir}/logs/${DIR_NAME}/info.%d{yyyy-MM-dd}.log + 30 + + + + + 10MB + + + + INFO + ACCEPT + DENY + + + + %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n + + + + + + + ${user.dir}/logs/${DIR_NAME}/debug.%d{yyyy-MM-dd}.log + 30 + + + + + 10MB + + + + DEBUG + ACCEPT + DENY + + + + %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n + + + + + + + ${user.dir}/logs/${DIR_NAME}/trace.%d{yyyy-MM-dd}.log + 30 + + + + + 10MB + + + + TRACE + ACCEPT + DENY + + + + %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n + + + + + + + ${user.dir}/logs/${DIR_NAME}/springframework.%d{yyyy-MM-dd}.log + + 30 + + + + + 10MB + + + + %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/mail/mail.properties b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/mail/mail.properties index e26a9bdf..2c6aebd2 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/mail/mail.properties +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/mail/mail.properties @@ -1,5 +1,5 @@ -smtp.host = smtp.163.com -pop3.host = pop3.163.com -mail.host = @163.com -mail.account = xxxxxx -mail.password = xxxxxx \ No newline at end of file +smtp.host=smtp.163.com +pop3.host=pop3.163.com +mail.host=@163.com +mail.account=xxxxxx +mail.password=xxxxxx diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/footer.vm b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/footer.vm index 5ee4d9da..967693d7 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/footer.vm +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/footer.vm @@ -1,15 +1,16 @@ #set ($company = "Apache") #set ($year = "2016") - - - - - - - -
    - Copyright $company $year. 保留所有权利。 -
    - - \ No newline at end of file + + + + + + + +
    + Copyright $company $year. 保留所有权利。 +
    + + diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/header.vm b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/header.vm index 4d382929..877d1e5d 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/header.vm +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/header.vm @@ -1,5 +1,6 @@ - - Velocity 邮件模板 - - \ No newline at end of file + + Velocity 邮件模板 + + diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/mail.vm b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/mail.vm index 1529cc18..9f439023 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/mail.vm +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/mail.vm @@ -1,135 +1,140 @@ - - - - - + + + + + - - +
    + - ## 页首 - #parse("template/header.vm") + ## 页首 + #parse("template/header.vm") - - + - ## 页脚 - #parse("template/footer.vm") - + ## 页脚 + #parse("template/footer.vm") +
    - + + - + +
    + - - ## 收件人名 - - + ## 收件人名 + + - + + - ## 提示 - - + - + + - ## 邮件正文 - - - + ## 邮件正文 + + + - ## 日期 - - - + ## 日期 + + + - ## 系统提示 - - - + ## 系统提示 + + + - -
    - +
    + 亲爱的 $name:
    -
    - + ## 提示 +
    +

    $hint

    -
    -

    将超链接、超链接名存入对象,将多个这样的对象放入列表:

    - #foreach( $item in $links ) - $item.name
    - #end -

    传图片url,显示logo

    - logo -
    +

    将超链接、超链接名存入对象,将多个这样的对象放入列表:

    + #foreach( $item in $links ) + $item.name
    + #end +

    传图片url,显示logo

    + logo +
    -
    - $date -
    -
    +
    + $date +
    +
    -
    - 提示:此邮件由系统自动发送,请勿直接回复 -
    -
    +
    + 提示:此邮件由系统自动发送,请勿直接回复 +
    +
    -
    +
    diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/velocity.properties b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/velocity.properties index 799b340b..c299635c 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/velocity.properties +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/velocity.properties @@ -1,4 +1,4 @@ -resource.loader = file -file.resource.loader.path = D:/01_Workspace/Project/zp/java/javaee-tutorial/codes/oss/src/main/resources -input.encoding = utf-8 -output.encoding = utf-8 +resource.loader=file +file.resource.loader.path=D:/01_Workspace/Project/zp/java/javaee-tutorial/codes/oss/src/main/resources +input.encoding=utf-8 +output.encoding=utf-8 diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/html/JsoupTest.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/html/JsoupTest.java index fcadab9c..23503c78 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/html/JsoupTest.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/html/JsoupTest.java @@ -1,9 +1,10 @@ /** - * The Apache License 2.0 - * Copyright (c) 2016 Victor Zhang + * The Apache License 2.0 Copyright (c) 2016 Victor Zhang */ package io.github.dunwu.javaee.oss.html; +import java.io.File; +import java.io.IOException; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; @@ -11,12 +12,9 @@ import org.junit.Before; import org.junit.Test; -import java.io.File; -import java.io.IOException; - /** * @author Victor Zhang - * @date 2016/11/24. + * @since 2016/11/24. */ public class JsoupTest { @@ -30,13 +28,14 @@ public class JsoupTest { /** * Jsoup 有三种方式加载文档(Document): HTML字符串、URL地址、html文件 + * * @throws IOException */ @Before public void before() throws IOException { // 从一个html字符串加载Document对象 final String html = "First parse" - + "

    Parsed HTML into a doc.

    "; + + "

    Parsed HTML into a doc.

    "; docFromStr = Jsoup.parse(html); // 从一个URL加载Document对象 @@ -101,7 +100,6 @@ public void test() { System.out.println("html: " + link.html()); // 取得链接内的html内容 System.out.println("href: " + link.attr("href")); // 取得字符串中的文本 System.out.println("text: " + link.text()); // 取得链接地址中的文本 - } } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/ImageUtilTest.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/ImageUtilTest.java index 0cabb62d..b2e99641 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/ImageUtilTest.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/ImageUtilTest.java @@ -1,39 +1,22 @@ /** - * The Apache License 2.0 - * Copyright (c) 2016 Victor Zhang + * The Apache License 2.0 Copyright (c) 2016 Victor Zhang */ package io.github.dunwu.javaee.oss.image; import io.github.dunwu.javaee.oss.image.dto.ImageParamDTO; +import java.awt.image.BufferedImage; import net.sf.jmimemagic.MagicException; import net.sf.jmimemagic.MagicMatchNotFoundException; import net.sf.jmimemagic.MagicParseException; import org.junit.Assert; import org.junit.Test; -import java.awt.image.BufferedImage; -import java.io.*; - /** * @author Victor Zhang - * @date 2017/1/16. + * @since 2017/1/16. */ public class ImageUtilTest { - private static byte[] toBytes(File file) throws IOException { - InputStream input = new FileInputStream(file); - ByteArrayOutputStream output = new ByteArrayOutputStream(); - byte[] buffer = new byte[1024]; - int length = 0; - while ((length = input.read(buffer)) != -1) { - output.write(buffer, 0, length); - } - byte[] data = output.toByteArray(); - output.close(); - input.close(); - return data; - } - @Test public void testToFile() throws IOException { final String oldFile = System.getProperty("user.dir") + "/src/test/resources/images/lion2.jpg"; @@ -60,11 +43,25 @@ public void testToBufferedImage() throws IOException { @Test public void testGetContentType() - throws MagicParseException, MagicException, MagicMatchNotFoundException, IOException { + throws MagicParseException, MagicException, MagicMatchNotFoundException, IOException { final String oldFile = System.getProperty("user.dir") + "/src/test/resources/images/lion2.jpg"; byte[] bytes = toBytes(new File(oldFile)); String type = ImageUtil.getContentType(bytes); Assert.assertEquals("image/jpeg", type); } + private static byte[] toBytes(File file) throws IOException { + InputStream input = new FileInputStream(file); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int length = 0; + while ((length = input.read(buffer)) != -1) { + output.write(buffer, 0, length); + } + byte[] data = output.toByteArray(); + output.close(); + input.close(); + return data; + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/QRCodeUtilTest.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/QRCodeUtilTest.java index 5b59861e..7de69c35 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/QRCodeUtilTest.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/QRCodeUtilTest.java @@ -1,6 +1,5 @@ /** - * The Apache License 2.0 - * Copyright (c) 2016 Victor Zhang + * The Apache License 2.0 Copyright (c) 2016 Victor Zhang */ package io.github.dunwu.javaee.oss.image; @@ -11,22 +10,21 @@ import com.google.zxing.EncodeHintType; import com.google.zxing.WriterException; import io.github.dunwu.javaee.oss.image.dto.BarcodeParamDTO; +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; import org.junit.Assert; import org.junit.Before; import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runners.MethodSorters; -import java.io.File; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - /** * 测试qrcode工具类 * * @author Victor Zhang - * @date 2017/1/16. + * @since 2017/1/16. */ @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class QRCodeUtilTest { @@ -92,7 +90,8 @@ public void test01() throws IOException, WriterException { */ @Test public void test02() { - String expect = "{\"gender\":\"MALE\",\"name\":{\"last\":\"Zhang\",\"first\":\"Peng\"},\"email\":\"aaa@163.com\"}"; + String expect = + "{\"gender\":\"MALE\",\"name\":{\"last\":\"Zhang\",\"first\":\"Peng\"},\"email\":\"aaa@163.com\"}"; String content = QRCodeUtil.decode(paramDTO); Assert.assertEquals(expect, content); } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/ThumbnailatorTest.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/ThumbnailatorTest.java index fb286007..74442d51 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/ThumbnailatorTest.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/ThumbnailatorTest.java @@ -1,28 +1,26 @@ /** - * The Apache License 2.0 - * Copyright (c) 2016 Victor Zhang + * The Apache License 2.0 Copyright (c) 2016 Victor Zhang */ package io.github.dunwu.javaee.oss.image; -import net.coobird.thumbnailator.Thumbnails; -import net.coobird.thumbnailator.geometry.Positions; -import net.coobird.thumbnailator.name.Rename; -import org.junit.Assert; -import org.junit.Test; - -import javax.imageio.ImageIO; -import javax.imageio.stream.FileImageOutputStream; import java.awt.image.BufferedImage; -import java.io.*; +import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; +import javax.imageio.ImageIO; +import javax.imageio.stream.FileImageOutputStream; +import net.coobird.thumbnailator.Thumbnails; +import net.coobird.thumbnailator.geometry.Positions; +import net.coobird.thumbnailator.name.Rename; +import org.junit.Assert; +import org.junit.Test; /** * @author Victor Zhang - * @date 2017/1/17. + * @since 2017/1/17. */ public class ThumbnailatorTest { @@ -43,7 +41,7 @@ public void testInput01() throws IOException { // 输入形式:URL 对象 URL url = new URL( - "https://raw.githubusercontent.com/dunwu/JavaParty/master/toolbox/image/src/test/resources/images/lion.jpg"); + "https://raw.githubusercontent.com/dunwu/JavaParty/master/toolbox/image/src/test/resources/images/lion.jpg"); Thumbnails.of(url).size(64, 64).toFile(new File("d:\\test_input_64_64.png")); Assert.assertTrue(new File("d:\\test_input_64_64.png").exists()); @@ -60,6 +58,7 @@ public void testInput01() throws IOException { /** * 测试输入多个对象 执行后会在D:\下生成几个含有图片的文件夹 注:如果输入是多个对象,则输出也必须选用输出多个对象的方式 + * * @throws IOException */ @Test @@ -72,7 +71,7 @@ public void testInput02() throws IOException { filenames.add(oldFile); filenames.add(oldFile2); Thumbnails.fromFilenames(filenames).size(50, 50).toFiles(new File("D:\\fromFilenames"), - Rename.PREFIX_DOT_THUMBNAIL); + Rename.PREFIX_DOT_THUMBNAIL); createFolderIfNotExist("D:\\fromFiles"); List files = new ArrayList(); @@ -81,6 +80,13 @@ public void testInput02() throws IOException { Thumbnails.fromFiles(files).size(50, 50).toFiles(new File("D:\\fromFiles"), Rename.PREFIX_HYPHEN_THUMBNAIL); } + private void createFolderIfNotExist(String folderPath) throws IOException { + File f = new File(folderPath); + if (!(f.exists() && f.isDirectory())) { + f.mkdirs(); + } + } + @Test public void testOutput() throws IOException { final String oldFile = System.getProperty("user.dir") + "/src/test/resources/images/lion2.jpg"; @@ -119,7 +125,7 @@ public void testWatermark() throws IOException { final String wartermarkFile = System.getProperty("user.dir") + "/src/test/resources/images/wartermark.png"; BufferedImage watermarkImage = ImageIO.read(new File(wartermarkFile)); Thumbnails.of(oldFile).scale(0.8).watermark(Positions.BOTTOM_LEFT, watermarkImage, 0.5f) - .toFile("d:\\lion2_watermark.png"); + .toFile("d:\\lion2_watermark.png"); } @Test @@ -131,14 +137,7 @@ public void testBatchChange() throws IOException { createFolderIfNotExist("D:\\watermark"); Thumbnails.of(oldFile, oldFile2).scale(0.8).watermark(Positions.BOTTOM_LEFT, watermarkImage, 0.5f) - .toFiles(new File("D:\\watermark"), Rename.PREFIX_DOT_THUMBNAIL); - } - - private void createFolderIfNotExist(String folderPath) throws IOException { - File f = new File(folderPath); - if (!(f.exists() && f.isDirectory())) { - f.mkdirs(); - } + .toFiles(new File("D:\\watermark"), Rename.PREFIX_DOT_THUMBNAIL); } } diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/template/VelocityUtilTest.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/template/VelocityUtilTest.java index 34801459..8bae0bc6 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/template/VelocityUtilTest.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/template/VelocityUtilTest.java @@ -1,6 +1,5 @@ /** - * The Apache License 2.0 - * Copyright (c) 2016 Victor Zhang + * The Apache License 2.0 Copyright (c) 2016 Victor Zhang */ package io.github.dunwu.javaee.oss.template; @@ -9,7 +8,7 @@ /** * @author Victor Zhang - * @date 2016/12/23. + * @since 2016/12/23. */ public class VelocityUtilTest { diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/test/JUnitExecTest.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/test/JUnitExecTest.java index e1344548..dac6690b 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/test/JUnitExecTest.java +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/test/JUnitExecTest.java @@ -1,9 +1,6 @@ package io.github.dunwu.javaee.oss.test; /** - * The Apache License 2.0 - * Copyright (c) 2016 Victor Zhang - */ - -import org.junit.*; + * The Apache License 2.0 Copyright (c) 2016 Victor Zhang + */ import static org.junit.Assert.fail; @@ -11,7 +8,7 @@ * JUnit4开始支持注解,本例展示一个单元测试执行过程中,各个注解的调用顺序 * * @author Victor Zhang - * @date 2016/11/18. + * @since 2016/11/18. */ public class JUnitExecTest { diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/html/jsoup-cookbook.html b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/html/jsoup-cookbook.html index 848697cf..e1beb7a3 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/html/jsoup-cookbook.html +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/html/jsoup-cookbook.html @@ -2,103 +2,102 @@ - - Cookbook: jsoup Java HTML parser - - - - - - + +
    - - - - - + + + + +
    diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/logback.xml index d8cd7f71..1598ec4f 100644 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/logback.xml +++ b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/logback.xml @@ -3,66 +3,66 @@ - + - - - - %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n - - - + + + + %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n + + + - - - - - ${user.dir}/logs/${DIR_NAME}/all.%d{yyyy-MM-dd}.log - 30 - + + + + + ${user.dir}/logs/${DIR_NAME}/all.%d{yyyy-MM-dd}.log + 30 + - - - 30MB - + + + 30MB + - - %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n - - - - - - ${user.dir}/logs/${DIR_NAME}/spring.%d{yyyy-MM-dd}.log - - 30 - + + %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n + + + + + + ${user.dir}/logs/${DIR_NAME}/spring.%d{yyyy-MM-dd}.log + + 30 + - - - 10MB - + + + 10MB + - - %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n - - - + + %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n + + + - - - - - + + + + + - - - - + + + + - - - - + + + + - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/pom.xml b/codes/javaee-tutorial/javaee-tutorial-servlet/pom.xml index be9c17ed..9a107e69 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/pom.xml +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/pom.xml @@ -1,88 +1,89 @@ - 4.0.0 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" + xmlns="http://maven.apache.org/POM/4.0.0"> + 4.0.0 - io.github.dunwu - javaee-tutorial-servlet - 1.0.0 - war - javaee-tutorial-servlet - JavaEE 学习笔记之 servlet + io.github.dunwu + javaee-tutorial-servlet + 1.0.0 + war + javaee-tutorial-servlet + JavaEE 学习笔记之 servlet - - UTF-8 - 1.7 - ${java.version} - ${java.version} - 0.4.0 - - - - - ch.qos.logback - logback-classic - + + UTF-8 + 1.7 + ${java.version} + ${java.version} + 0.4.0 + + + + + ch.qos.logback + logback-classic + - - - javax.servlet - javax.servlet-api - - - org.eclipse.jetty - jetty-webapp - test - - - org.eclipse.jetty - jetty-annotations - test - - - org.eclipse.jetty - apache-jsp - test - - - org.eclipse.jetty - apache-jstl - test - + + + javax.servlet + javax.servlet-api + + + org.eclipse.jetty + jetty-webapp + test + + + org.eclipse.jetty + jetty-annotations + test + + + org.eclipse.jetty + apache-jsp + test + + + org.eclipse.jetty + apache-jstl + test + - - - junit - junit - test - - - org.assertj - assertj-core - test - + + + junit + junit + test + + + org.assertj + assertj-core + test + - - - org.apache.commons - commons-lang3 - - - commons-fileupload - commons-fileupload - - + + + org.apache.commons + commons-lang3 + + + commons-fileupload + commons-fileupload + + - - - - io.github.dunwu - dunwu-dependencies - 0.4.2-SNAPSHOT - pom - import - - - + + + + io.github.dunwu + dunwu-dependencies + 0.4.2-SNAPSHOT + pom + import + + + diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/AnnotationServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/AnnotationServlet.java index c3ab27be..9156ddb6 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/AnnotationServlet.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/AnnotationServlet.java @@ -1,13 +1,13 @@ package io.github.dunwu.javaee.servlet; +import java.io.IOException; +import java.io.PrintWriter; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.PrintWriter; public class AnnotationServlet extends HttpServlet { @@ -36,12 +36,13 @@ public void destroy() { /** * The doGet method of the servlet.
    - * + *

    * This method is called when a form has its tag value method equals to get. - * @param request the request send by the client to the server + * + * @param request the request send by the client to the server * @param response the response send by the server to the client * @throws ServletException if an error occurred - * @throws IOException if an error occurred + * @throws IOException if an error occurred */ public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { @@ -64,6 +65,7 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) thro /** * Initialization of the servlet.
    + * * @throws ServletException if an error occure */ public void init() throws ServletException { @@ -75,7 +77,8 @@ public void postConstruct() { this.log("postConstruct()"); } - public @PreDestroy void preDestroy() { + public @PreDestroy + void preDestroy() { this.log("preDestroy()"); } diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ContextParamServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ContextParamServlet.java index 1593dbfe..0c4c2732 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ContextParamServlet.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ContextParamServlet.java @@ -1,19 +1,17 @@ package io.github.dunwu.javaee.servlet; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - +import java.io.IOException; +import java.io.PrintWriter; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.PrintWriter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - * 读取web.xml中的 配置在中,只能让对应的servlet使用; - * 配置在全局中,可以让所有的servlet使用。 + * 读取web.xml中的 配置在中,只能让对应的servlet使用; 配置在全局中,可以让所有的servlet使用。 */ public class ContextParamServlet extends HttpServlet { diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/FirstServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/FirstServlet.java index 7e2b814e..36b6dde7 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/FirstServlet.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/FirstServlet.java @@ -1,11 +1,11 @@ package io.github.dunwu.javaee.servlet; +import java.io.IOException; +import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.PrintWriter; /** * 第一个Servlet @@ -16,8 +16,7 @@ public class FirstServlet extends HttpServlet { /** * 以 GET 方式访问页面时执行该函数。 执行 doGet 前会先执行 getLastModified,如果浏览器发现 getLastModified 返回的数值 - * 与上次访问时返回的数值相同,则认为该文档没有更新,浏览器采用缓存而不执行 doGet。 如果 getLastModified 返回 - * -1,则认为是时刻更新的,总是执行该函数。 + * 与上次访问时返回的数值相同,则认为该文档没有更新,浏览器采用缓存而不执行 doGet。 如果 getLastModified 返回 -1,则认为是时刻更新的,总是执行该函数。 */ @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { @@ -29,34 +28,8 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) thro this.execute(request, response); } - /** - * 以 POST 方式访问页面时执行该函数。 - */ - @Override - public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - - // 调用 HttpServlet 自带的日志函数输出信息到控制台 - this.log("执行 doPost 方法... "); - - // 处理 doPost - this.execute(request, response); - } - - /** - * 返回该 Servlet 生成的文档的更新时间。对 Get 方式访问有效。 返回的时间为相对于 1970年1月1日08:00:00 的毫秒数。 如果为 -1 - * 则认为是实时更新。默认为 -1。 - */ - @Override - public long getLastModified(HttpServletRequest request) { - - // 调用 HttpServlet 自带的日志函数输出信息到控制台 - this.log("执行 getLastModified 方法... "); - - return 0; - } - private void execute(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { + throws ServletException, IOException { response.setCharacterEncoding("UTF-8"); request.setCharacterEncoding("UTF-8"); @@ -77,9 +50,9 @@ private void execute(HttpServletRequest request, HttpServletResponse response) out.println(" 以 " + method + " 方式访问该页面。取到的 param 参数为:" + param + "
    "); out.println("

    "); + + "' method='get'>"); out.println("
    "); + + "' method='post'>"); // 由客户端浏览器读取该文档的更新时间 out.println(" "); @@ -89,7 +62,31 @@ private void execute(HttpServletRequest request, HttpServletResponse response) out.println(""); out.flush(); out.close(); + } + /** + * 以 POST 方式访问页面时执行该函数。 + */ + @Override + public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + + // 调用 HttpServlet 自带的日志函数输出信息到控制台 + this.log("执行 doPost 方法... "); + + // 处理 doPost + this.execute(request, response); + } + + /** + * 返回该 Servlet 生成的文档的更新时间。对 Get 方式访问有效。 返回的时间为相对于 1970年1月1日08:00:00 的毫秒数。 如果为 -1 则认为是实时更新。默认为 -1。 + */ + @Override + public long getLastModified(HttpServletRequest request) { + + // 调用 HttpServlet 自带的日志函数输出信息到控制台 + this.log("执行 getLastModified 方法... "); + + return 0; } } diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ForwardServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ForwardServlet.java index 5938e942..bb47a2ac 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ForwardServlet.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ForwardServlet.java @@ -1,17 +1,21 @@ package io.github.dunwu.javaee.servlet; +import java.io.IOException; +import java.util.Date; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.Date; public class ForwardServlet extends HttpServlet { private static final long serialVersionUID = -291840563095094360L; + public void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + this.doGet(request, response); + } + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String destination = request.getParameter("destination"); @@ -33,17 +37,11 @@ else if ("jsp".equals(destination)) { else if ("servlet".equals(destination)) { RequestDispatcher dispatcher = request.getRequestDispatcher("/servlet/LifeCycleServlet"); dispatcher.forward(request, response); - } - else { + } else { response.setCharacterEncoding("UTF-8"); response.setContentType("text/html; charset=UTF-8"); response.getWriter().println("缺少参数。用法:" + request.getRequestURL() + "?destination=jsp 或者 file 或者 servlet "); } - - } - - public void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - this.doGet(request, response); } } diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/HelloServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/HelloServlet.java index 6f81d1c7..97d42916 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/HelloServlet.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/HelloServlet.java @@ -1,19 +1,18 @@ /** - * The Apache License 2.0 - * Copyright (c) 2016 Zhang Peng + * The Apache License 2.0 Copyright (c) 2016 Zhang Peng */ package io.github.dunwu.javaee.servlet; +import java.io.IOException; +import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.PrintWriter; /** * @author Zhang Peng - * @date 2016/12/23. + * @since 2016/12/23. */ public class HelloServlet extends HttpServlet { @@ -35,13 +34,29 @@ public void destroy() { } /** - * The doGet method of the servlet.
    + * The doPost method of the servlet.
    + *

    + * This method is called when a form has its tag value method equals to post. * + * @param request the request send by the client to the server + * @param response the response send by the server to the client + * @throws ServletException if an error occurred + * @throws IOException if an error occurred + */ + public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + + this.doGet(request, response); + } + + /** + * The doGet method of the servlet.
    + *

    * This method is called when a form has its tag value method equals to get. - * @param request the request send by the client to the server + * + * @param request the request send by the client to the server * @param response the response send by the server to the client * @throws ServletException if an error occurred - * @throws IOException if an error occurred + * @throws IOException if an error occurred */ public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { @@ -81,22 +96,9 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) thro out.close(); } - /** - * The doPost method of the servlet.
    - * - * This method is called when a form has its tag value method equals to post. - * @param request the request send by the client to the server - * @param response the response send by the server to the client - * @throws ServletException if an error occurred - * @throws IOException if an error occurred - */ - public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - - this.doGet(request, response); - } - /** * Initialization of the servlet.
    + * * @throws ServletException if an error occure */ public void init() throws ServletException { diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ImageServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ImageServlet.java index 1bc0193a..32c8e135 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ImageServlet.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ImageServlet.java @@ -4,7 +4,6 @@ import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.*; public class ImageServlet extends HttpServlet { @@ -14,6 +13,11 @@ public ImageServlet() { System.out.println("正在加载 " + this.getClass().getName() + " ... "); } + public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + + this.doGet(request, response); + } + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String referer = request.getHeader("referer"); @@ -43,17 +47,22 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) thro response.setHeader("Connection", "close"); if (fileName.toLowerCase().endsWith(".jpg")) - // .jpg 图片格式 + // .jpg 图片格式 + { response.setHeader("Content-Type", "image/jpeg"); - else if (fileName.toLowerCase().endsWith(".gif")) - // .gif 图片格式 + } else if (fileName.toLowerCase().endsWith(".gif")) + // .gif 图片格式 + { response.setHeader("Content-Type", "image/gif"); - else if (fileName.toLowerCase().endsWith(".doc")) - // word 格式 + } else if (fileName.toLowerCase().endsWith(".doc")) + // word 格式 + { response.setHeader("Content-Type", "application/msword"); - else - // 其他格式 + } else + // 其他格式 + { response.setHeader("Content-Type", "application/octet-stream"); + } // 通过 ins 读取文件 InputStream ins = new FileInputStream(file); @@ -69,19 +78,14 @@ else if (fileName.toLowerCase().endsWith(".doc")) while ((len = ins.read(buffer)) > -1) { ous.write(buffer, 0, len); } - } - finally { - if (ous != null) + } finally { + if (ous != null) { ous.close(); - if (ins != null) + } + if (ins != null) { ins.close(); + } } - - } - - public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - - this.doGet(request, response); } } diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/InitParamServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/InitParamServlet.java index 23c4b21f..e05c2a44 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/InitParamServlet.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/InitParamServlet.java @@ -1,19 +1,17 @@ package io.github.dunwu.javaee.servlet; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Enumeration; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.Enumeration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - * 初始化参数示例 读取web.xml中的 配置在中,只能让对应的servlet使用; - * 配置在全局中,可以让所有的servlet使用。 + * 初始化参数示例 读取web.xml中的 配置在中,只能让对应的servlet使用; 配置在全局中,可以让所有的servlet使用。 */ public class InitParamServlet extends HttpServlet { @@ -21,6 +19,29 @@ public class InitParamServlet extends HttpServlet { private final Logger logger = LoggerFactory.getLogger(this.getClass()); + public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + // 提交的 username 参数 + String username = request.getParameter("username"); + // 提交的 password 参数 + String password = request.getParameter("password"); + // 取所有的初始化参数名称 + Enumeration params = this.getInitParameterNames(); + while (params.hasMoreElements()) { + String usernameParam = (String) params.nextElement(); + // 取参数值 + String passnameParam = this.getInitParameter(usernameParam); + // 如果 username 匹配且 password 匹配. username 大小写不敏感,password大小写敏感 + if (usernameParam.equalsIgnoreCase(username) && passnameParam.equals(password)) { + // 显示文件。/WEB-INF 下的文件不能通过浏览器访问到,因此是安全的 + request.getRequestDispatcher("/WEB-INF/notice.html").forward(request, response); + return; + } + } + // username,password 不匹配,显示登录页面 + logger.warn("用户名={}、密码={}不匹配。", username, password); + this.doGet(request, response); + } + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { logger.info("InitParamServlet 初始化用户名、密码参数"); @@ -57,27 +78,4 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) thro out.close(); } - public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - // 提交的 username 参数 - String username = request.getParameter("username"); - // 提交的 password 参数 - String password = request.getParameter("password"); - // 取所有的初始化参数名称 - Enumeration params = this.getInitParameterNames(); - while (params.hasMoreElements()) { - String usernameParam = (String) params.nextElement(); - // 取参数值 - String passnameParam = this.getInitParameter(usernameParam); - // 如果 username 匹配且 password 匹配. username 大小写不敏感,password大小写敏感 - if (usernameParam.equalsIgnoreCase(username) && passnameParam.equals(password)) { - // 显示文件。/WEB-INF 下的文件不能通过浏览器访问到,因此是安全的 - request.getRequestDispatcher("/WEB-INF/notice.html").forward(request, response); - return; - } - } - // username,password 不匹配,显示登录页面 - logger.warn("用户名={}、密码={}不匹配。", username, password); - this.doGet(request, response); - } - } diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/InjectionServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/InjectionServlet.java index 172424a5..66eca83b 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/InjectionServlet.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/InjectionServlet.java @@ -1,22 +1,24 @@ package io.github.dunwu.javaee.servlet; +import java.io.IOException; +import java.io.PrintWriter; import javax.annotation.Resource; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.PrintWriter; public class InjectionServlet extends HttpServlet { private static final long serialVersionUID = -8526907492073769090L; // 注入的 字符串 - private @Resource(name = "hello") String hello; + private @Resource(name = "hello") + String hello; // 注入的 整数 - private @Resource(name = "i") int i; + private @Resource(name = "i") + int i; // 注入更常见的写法 @Resource(name = "persons") diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/LifeCycleServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/LifeCycleServlet.java index d36041e5..f1c82384 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/LifeCycleServlet.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/LifeCycleServlet.java @@ -1,12 +1,12 @@ package io.github.dunwu.javaee.servlet; +import java.io.IOException; +import java.io.PrintWriter; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.PrintWriter; public class LifeCycleServlet extends HttpServlet { @@ -88,9 +88,7 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) thr out.println("
    "); out.println(""); - - } - catch (Exception e) { + } catch (Exception e) { out.println("请输入数值类型数据。"); } out.println(""); @@ -114,12 +112,12 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) thro out.println("

    "); out.println( - "
    您的工资为
    单位:元
    "); + "
    您的工资为
    单位:元
    "); out.println("

    "); out.println("
    "); out.println( - "
    "); + "
    "); out.println("
    "); out.println(""); diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/PostServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/PostServlet.java index 855b9eb8..33bb6a0c 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/PostServlet.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/PostServlet.java @@ -1,14 +1,14 @@ package io.github.dunwu.javaee.servlet; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; public class PostServlet extends HttpServlet { @@ -36,8 +36,7 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) thr // 取 年龄. 需要把 字符串 转换为 int. // 如果格式不对会抛出 NumberFormattingException age = Integer.parseInt(request.getParameter("age")); - } - catch (Exception e) { + } catch (Exception e) { } Date birthday = null; @@ -46,8 +45,7 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) thr // 如果格式不对会抛出 ParseException DateFormat format = new SimpleDateFormat("yyyy-MM-dd"); birthday = format.parse(request.getParameter("birthday")); - } - catch (Exception e) { + } catch (Exception e) { } // 从 多选框 checkbox 中取多个值 @@ -118,10 +116,11 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) thr out.println("
    您的兴趣:
    "); out.println("
    "); - if (interesting != null) + if (interesting != null) { for (String str : interesting) { out.println(str + ", "); } + } out.println("
    "); out.println(" "); @@ -140,7 +139,7 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) thr out.println("
    "); out.println("
    "); out.println( - "

    "); + "

    "); out.println("
    "); out.println(" "); diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/RedirectServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/RedirectServlet.java index 331577f0..f751bb3c 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/RedirectServlet.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/RedirectServlet.java @@ -1,14 +1,14 @@ package io.github.dunwu.javaee.servlet; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; public class RedirectServlet extends HttpServlet { @@ -34,8 +34,7 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) thro map.put(filename, ++hit); // 重定向到文件 response.sendRedirect(request.getContextPath() + filename); - } - else { + } else { response.setCharacterEncoding("UTF-8"); response.setContentType("text/html"); PrintWriter out = response.getWriter(); @@ -58,7 +57,7 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) thro out.println(" " + entry.getKey() + ""); out.println(" " + entry.getValue() + ""); out.println(" 下载"); + + "' target='_blank' onclick='location=location; '>下载"); out.println(""); } @@ -69,7 +68,6 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) thro out.flush(); out.close(); } - } @Override diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/RequestServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/RequestServlet.java index e128ef98..177e97af 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/RequestServlet.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/RequestServlet.java @@ -1,16 +1,15 @@ package io.github.dunwu.javaee.servlet; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.security.Principal; import java.util.Locale; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * 获取HttpServletRequest信息示例 @@ -21,79 +20,6 @@ public class RequestServlet extends HttpServlet { private final Logger logger = LoggerFactory.getLogger(this.getClass()); - /** - * @param accept - * @return 客户端浏览器接受的文件类型 - */ - private String getAccept(String accept) { - StringBuffer buffer = new StringBuffer(); - if (accept.contains("image/gif")) - buffer.append("GIF 文件, "); - if (accept.contains("image/x-xbitmap")) - buffer.append("BMP 文件, "); - if (accept.contains("image/jpeg")) - buffer.append("JPG 文件, "); - if (accept.contains("application/vnd.ms-excel")) - buffer.append("Excel 文件, "); - if (accept.contains("application/vnd.ms-powerpoint")) - buffer.append("PPT 文件, "); - if (accept.contains("application/msword")) - buffer.append("Word 文件, "); - return buffer.toString().replaceAll(", $", ""); - } - - /** - * @param locale - * @return 语言环境名称 - */ - private String getLocale(Locale locale) { - if (Locale.SIMPLIFIED_CHINESE.equals(locale)) - return "简体中文"; - if (Locale.TRADITIONAL_CHINESE.equals(locale)) - return "繁体中文"; - if (Locale.ENGLISH.equals(locale)) - return "英文"; - if (Locale.JAPANESE.equals(locale)) - return "日文"; - return "未知语言环境"; - } - - /** - * @param userAgent - * @return 客户端浏览器信息 - */ - private String getNavigator(String userAgent) { - if (userAgent.indexOf("TencentTraveler") > 0) - return "腾讯浏览器"; - if (userAgent.indexOf("Maxthon") > 0) - return "Maxthon浏览器"; - if (userAgent.indexOf("MyIE2") > 0) - return "MyIE2浏览器"; - if (userAgent.indexOf("Firefox") > 0) - return "Firefox浏览器"; - if (userAgent.indexOf("MSIE") > 0) - return "IE 浏览器"; - return "未知浏览器"; - } - - /** - * @param userAgent - * @return 客户端操作系统 - */ - private String getOS(String userAgent) { - if (userAgent.indexOf("Windows NT 5.1") > 0) - return "Windows XP"; - if (userAgent.indexOf("Windows 98") > 0) - return "Windows 98"; - if (userAgent.indexOf("Windows NT 5.0") > 0) - return "Windows 2000"; - if (userAgent.indexOf("Linux") > 0) - return "Linux"; - if (userAgent.indexOf("Unix") > 0) - return "Unix"; - return "未知"; - } - @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { logger.info("访问 doGet"); @@ -142,18 +68,18 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) thro out.println(" "); out.println("您的IP为 " + remoteAddr + ";您使用 " + getOS(userAgent) + " 操作系统," - + getNavigator(userAgent) + " 。您使用 " + getLocale(locale) + "。
    "); + + getNavigator(userAgent) + " 。您使用 " + getLocale(locale) + "。
    "); out.println("服务器IP为 " + localAddr + localAddr + ";服务器使用 " + serverPort + " 端口,您的浏览器使用了 " - + port + " 端口访问本网页。
    "); + + port + " 端口访问本网页。
    "); out.println("服务器软件为:" + serverInfo + "。服务器名称为 " + localName + "。
    "); out.println("您的浏览器接受 " + getAccept(accept) + "。
    "); out.println("您从 " + referer + " 访问到该页面。
    "); out.println("使用的协议为 " + protocol + "。URL协议头 " + scheme + ",服务器名称 " + serverName - + ",您访问的URI为 " + requestURI + "。
    "); + + ",您访问的URI为 " + requestURI + "。
    "); out.println("该 Servlet 路径为 " + servletPath + ",该 Servlet 类名为 " + this.getClass().getName() - + "。
    "); + + "。
    "); out.println("本应用程序在硬盘的根目录为 " + this.getServletContext().getRealPath("") + ",网络相对路径为 " - + contextPath + "。
    "); + + contextPath + "。
    "); out.println("
    "); @@ -165,4 +91,97 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) thro out.close(); } + /** + * @param userAgent + * @return 客户端操作系统 + */ + private String getOS(String userAgent) { + if (userAgent.indexOf("Windows NT 5.1") > 0) { + return "Windows XP"; + } + if (userAgent.indexOf("Windows 98") > 0) { + return "Windows 98"; + } + if (userAgent.indexOf("Windows NT 5.0") > 0) { + return "Windows 2000"; + } + if (userAgent.indexOf("Linux") > 0) { + return "Linux"; + } + if (userAgent.indexOf("Unix") > 0) { + return "Unix"; + } + return "未知"; + } + + /** + * @param userAgent + * @return 客户端浏览器信息 + */ + private String getNavigator(String userAgent) { + if (userAgent.indexOf("TencentTraveler") > 0) { + return "腾讯浏览器"; + } + if (userAgent.indexOf("Maxthon") > 0) { + return "Maxthon浏览器"; + } + if (userAgent.indexOf("MyIE2") > 0) { + return "MyIE2浏览器"; + } + if (userAgent.indexOf("Firefox") > 0) { + return "Firefox浏览器"; + } + if (userAgent.indexOf("MSIE") > 0) { + return "IE 浏览器"; + } + return "未知浏览器"; + } + + /** + * @param locale + * @return 语言环境名称 + */ + private String getLocale(Locale locale) { + if (Locale.SIMPLIFIED_CHINESE.equals(locale)) { + return "简体中文"; + } + if (Locale.TRADITIONAL_CHINESE.equals(locale)) { + return "繁体中文"; + } + if (Locale.ENGLISH.equals(locale)) { + return "英文"; + } + if (Locale.JAPANESE.equals(locale)) { + return "日文"; + } + return "未知语言环境"; + } + + /** + * @param accept + * @return 客户端浏览器接受的文件类型 + */ + private String getAccept(String accept) { + StringBuffer buffer = new StringBuffer(); + if (accept.contains("image/gif")) { + buffer.append("GIF 文件, "); + } + if (accept.contains("image/x-xbitmap")) { + buffer.append("BMP 文件, "); + } + if (accept.contains("image/jpeg")) { + buffer.append("JPG 文件, "); + } + if (accept.contains("application/vnd.ms-excel")) { + buffer.append("Excel 文件, "); + } + if (accept.contains("application/vnd.ms-powerpoint")) { + buffer.append("PPT 文件, "); + } + if (accept.contains("application/msword")) { + buffer.append("Word 文件, "); + } + return buffer.toString().replaceAll(", $", ""); + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ThreadSafetyServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ThreadSafetyServlet.java index 81ed6d65..3dd596f0 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ThreadSafetyServlet.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ThreadSafetyServlet.java @@ -1,10 +1,10 @@ package io.github.dunwu.javaee.servlet; +import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.IOException; public class ThreadSafetyServlet extends HttpServlet { @@ -18,8 +18,7 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) thro try { Thread.sleep(5000); - } - catch (InterruptedException e) { + } catch (InterruptedException e) { } response.getWriter().println("您好, " + name + ". 您使用了 GET 方式提交数据"); diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/UploadServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/UploadServlet.java index 0f69db01..0fc1b733 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/UploadServlet.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/UploadServlet.java @@ -1,16 +1,14 @@ package io.github.dunwu.javaee.servlet; -import org.apache.commons.fileupload.DiskFileUpload; -import org.apache.commons.fileupload.FileItem; -import org.apache.commons.fileupload.FileUploadException; - +import java.net.URLEncoder; +import java.util.List; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.*; -import java.net.URLEncoder; -import java.util.List; +import org.apache.commons.fileupload.DiskFileUpload; +import org.apache.commons.fileupload.FileItem; +import org.apache.commons.fileupload.FileUploadException; public class UploadServlet extends HttpServlet { @@ -63,8 +61,7 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) thr out.println("遍历到 description2 ...
    "); description2 = new String(fileItem.getString().getBytes(), "UTF-8"); } - } - else { + } else { // 否则,为文件域 if ("file1".equals(fileItem.getFieldName())) { // 客户端文件路径构建的 File 对象 @@ -83,11 +80,11 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) thr try { byte[] buffer = new byte[1024]; int len = 0; - while ((len = ins.read(buffer)) > -1) + while ((len = ins.read(buffer)) > -1) { ous.write(buffer, 0, len); + } out.println("已保存文件" + file1.getAbsolutePath() + "
    "); - } - finally { + } finally { ous.close(); ins.close(); } @@ -109,11 +106,11 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) thr try { byte[] buffer = new byte[1024]; int len = 0; - while ((len = ins.read(buffer)) > -1) + while ((len = ins.read(buffer)) > -1) { ous.write(buffer, 0, len); + } out.println("已保存文件" + file2.getAbsolutePath() + "
    "); - } - finally { + } finally { ous.close(); ins.close(); } @@ -121,8 +118,7 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) thr } } out.println("Request 解析完毕"); - } - catch (FileUploadException e) { + } catch (FileUploadException e) { // TODO Auto-generated catch block e.printStackTrace(); } @@ -135,7 +131,7 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) thr out.println("
    file1:
    "); out.println("
    "); out.println(" " + file1.getName() + ""); + + "' target=_blank>" + file1.getName() + ""); out.println("
    "); out.println(" "); } @@ -145,7 +141,7 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) thr out.println("
    file2:
    "); out.println("
    "); out.println(" " + file2.getName() + ""); + + URLEncoder.encode(file2.getName(), "UTF-8") + "' target=_blank>" + file2.getName() + ""); out.println("
    "); out.println(" "); } diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/ProgressUploadServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/ProgressUploadServlet.java index 2ba7f00a..cb62876c 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/ProgressUploadServlet.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/ProgressUploadServlet.java @@ -1,16 +1,14 @@ package io.github.dunwu.javaee.servlet.upload; -import org.apache.commons.fileupload.FileItem; -import org.apache.commons.fileupload.disk.DiskFileItemFactory; -import org.apache.commons.fileupload.servlet.ServletFileUpload; - +import java.util.Iterator; +import java.util.List; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.*; -import java.util.Iterator; -import java.util.List; +import org.apache.commons.fileupload.FileItem; +import org.apache.commons.fileupload.disk.DiskFileItemFactory; +import org.apache.commons.fileupload.servlet.ServletFileUpload; public class ProgressUploadServlet extends HttpServlet { @@ -36,12 +34,11 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) thr try { List itemList = upload.parseRequest(request); - for (Iterator it = itemList.iterator(); it.hasNext();) { + for (Iterator it = itemList.iterator(); it.hasNext(); ) { FileItem item = (FileItem) it.next(); if (item.isFormField()) { System.out.println("FormField: " + item.getFieldName() + " = " + item.getString()); - } - else { + } else { System.out.println("File: " + item.getName()); // 统一 Linux 与 windows 的路径分隔符 @@ -67,8 +64,7 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) thr response.getWriter().println("已保存文件:" + saved); } } - } - catch (Exception e) { + } catch (Exception e) { e.printStackTrace(); response.getWriter().println("上传发生错误:" + e.getMessage()); } @@ -113,7 +109,7 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) thro // 格式:百分比||已完成数(M)||文件总长度(M)||传输速率(K)||已用时间(s)||估计总时间(s)||估计剩余时间(s)||正在上传第几个文件 String value = percent + "||" + length + "||" + totalLength + "||" + velocity + "||" + time + "||" + totalTime - + "||" + timeLeft + "||" + status.getItems(); + + "||" + timeLeft + "||" + status.getItems(); response.getWriter().println(value); } diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/resources/logback.xml index b27f1272..8b8545db 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/resources/logback.xml +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/resources/logback.xml @@ -3,44 +3,44 @@ - - - - - - %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n - - - - - - - - ${user.dir}/logs/${DIR_NAME}/all.%d{yyyy-MM-dd}.log - 30 - - - - - 30MB - - - - %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n - - - - - - - - - - - - - - - + + + + + + %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n + + + + + + + + ${user.dir}/logs/${DIR_NAME}/all.%d{yyyy-MM-dd}.log + 30 + + + + + 30MB + + + + %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n + + + + + + + + + + + + + + + diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/WEB-INF/notice.html b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/WEB-INF/notice.html index 62696a93..aebd0084 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/WEB-INF/notice.html +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/WEB-INF/notice.html @@ -1,8 +1,8 @@ - - Eclipse.org Software User Agreement + + Eclipse.org Software User Agreement @@ -12,105 +12,105 @@

    Eclipse Foundation Software User Agreement

    Usage Of Content

    THE ECLIPSE FOUNDATION MAKES AVAILABLE SOFTWARE, DOCUMENTATION, INFORMATION AND/OR OTHER MATERIALS FOR OPEN SOURCE - PROJECTS - (COLLECTIVELY "CONTENT"). USE OF THE CONTENT IS GOVERNED BY THE TERMS AND CONDITIONS OF THIS AGREEMENT - AND/OR THE TERMS AND - CONDITIONS OF LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW. BY USING THE CONTENT, YOU AGREE THAT YOUR - USE - OF THE CONTENT IS GOVERNED BY THIS AGREEMENT AND/OR THE TERMS AND CONDITIONS OF ANY APPLICABLE LICENSE AGREEMENTS OR - NOTICES INDICATED OR REFERENCED BELOW. IF YOU DO NOT AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT AND THE TERMS - AND - CONDITIONS OF ANY APPLICABLE LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW, THEN YOU MAY NOT USE THE - CONTENT.

    + PROJECTS + (COLLECTIVELY "CONTENT"). USE OF THE CONTENT IS GOVERNED BY THE TERMS AND CONDITIONS OF THIS AGREEMENT + AND/OR THE TERMS AND + CONDITIONS OF LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW. BY USING THE CONTENT, YOU AGREE THAT YOUR + USE + OF THE CONTENT IS GOVERNED BY THIS AGREEMENT AND/OR THE TERMS AND CONDITIONS OF ANY APPLICABLE LICENSE AGREEMENTS OR + NOTICES INDICATED OR REFERENCED BELOW. IF YOU DO NOT AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT AND THE TERMS + AND + CONDITIONS OF ANY APPLICABLE LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW, THEN YOU MAY NOT USE THE + CONTENT.

    Applicable Licenses

    Unless otherwise indicated, all Content made available by the Eclipse Foundation is provided to you under the terms - and conditions of the Eclipse Public License Version 1.0 - ("EPL"). A copy of the EPL is provided with this Content and is also available at http://www.eclipse.org/legal/epl-v10.html. - For purposes of the EPL, "Program" will mean the Content.

    + and conditions of the Eclipse Public License Version 1.0 + ("EPL"). A copy of the EPL is provided with this Content and is also available at http://www.eclipse.org/legal/epl-v10.html. + For purposes of the EPL, "Program" will mean the Content.

    Content includes, but is not limited to, source code, object code, documentation and other files maintained in the - Eclipse.org CVS repository ("Repository") in CVS - modules ("Modules") and made available as downloadable archives ("Downloads").

    + Eclipse.org CVS repository ("Repository") in CVS + modules ("Modules") and made available as downloadable archives ("Downloads").

      -
    • Content may be structured and packaged into modules to facilitate delivering, extending, and upgrading the - Content. Typical modules may include plug-ins ("Plug-ins"), plug-in fragments ("Fragments"), and - features ("Features"). -
    • -
    • Each Plug-in or Fragment may be packaged as a sub-directory or JAR (Java™ ARchive) in a directory named - "plugins". -
    • -
    • A Feature is a bundle of one or more Plug-ins and/or Fragments and associated material. Each Feature may be - packaged as a sub-directory in a directory named "features". Within a Feature, files named "feature.xml" - may contain a list of the names and version numbers of the Plug-ins - and/or Fragments associated with that Feature. -
    • -
    • Features may also include other Features ("Included Features"). Within a Feature, files named "feature.xml" - may contain a list of the names and version numbers of Included Features. -
    • +
    • Content may be structured and packaged into modules to facilitate delivering, extending, and upgrading the + Content. Typical modules may include plug-ins ("Plug-ins"), plug-in fragments ("Fragments"), and + features ("Features"). +
    • +
    • Each Plug-in or Fragment may be packaged as a sub-directory or JAR (Java™ ARchive) in a directory named + "plugins". +
    • +
    • A Feature is a bundle of one or more Plug-ins and/or Fragments and associated material. Each Feature may be + packaged as a sub-directory in a directory named "features". Within a Feature, files named "feature.xml" + may contain a list of the names and version numbers of the Plug-ins + and/or Fragments associated with that Feature. +
    • +
    • Features may also include other Features ("Included Features"). Within a Feature, files named "feature.xml" + may contain a list of the names and version numbers of Included Features. +

    The terms and conditions governing Plug-ins and Fragments should be contained in files named "about.html" ("Abouts"). - The terms and conditions governing Features and - Included Features should be contained in files named "license.html" ("Feature Licenses"). Abouts - and Feature Licenses may be located in any directory of a Download or Module - including, but not limited to the following locations:

    + The terms and conditions governing Features and + Included Features should be contained in files named "license.html" ("Feature Licenses"). Abouts + and Feature Licenses may be located in any directory of a Download or Module + including, but not limited to the following locations:

      -
    • The top-level (root) directory
    • -
    • Plug-in and Fragment directories
    • -
    • Inside Plug-ins and Fragments packaged as JARs
    • -
    • Sub-directories of the directory named "src" of certain Plug-ins
    • -
    • Feature directories
    • +
    • The top-level (root) directory
    • +
    • Plug-in and Fragment directories
    • +
    • Inside Plug-ins and Fragments packaged as JARs
    • +
    • Sub-directories of the directory named "src" of certain Plug-ins
    • +
    • Feature directories

    Note: if a Feature made available by the Eclipse Foundation is installed using the Eclipse Update Manager, you must - agree to a license ("Feature Update License") during the - installation process. If the Feature contains Included Features, the Feature Update License should either provide you - with the terms and conditions governing the Included Features or - inform you where you can locate them. Feature Update Licenses may be found in the "license" property of - files named "feature.properties" found within a Feature. - Such Abouts, Feature Licenses, and Feature Update Licenses contain the terms and conditions (or references to such - terms and conditions) that govern your use of the associated Content in - that directory.

    + agree to a license ("Feature Update License") during the + installation process. If the Feature contains Included Features, the Feature Update License should either provide you + with the terms and conditions governing the Included Features or + inform you where you can locate them. Feature Update Licenses may be found in the "license" property of + files named "feature.properties" found within a Feature. + Such Abouts, Feature Licenses, and Feature Update Licenses contain the terms and conditions (or references to such + terms and conditions) that govern your use of the associated Content in + that directory.

    THE ABOUTS, FEATURE LICENSES, AND FEATURE UPDATE LICENSES MAY REFER TO THE EPL OR OTHER LICENSE AGREEMENTS, NOTICES - OR TERMS AND CONDITIONS. SOME OF THESE - OTHER LICENSE AGREEMENTS MAY INCLUDE (BUT ARE NOT LIMITED TO):

    + OR TERMS AND CONDITIONS. SOME OF THESE + OTHER LICENSE AGREEMENTS MAY INCLUDE (BUT ARE NOT LIMITED TO):

    IT IS YOUR OBLIGATION TO READ AND ACCEPT ALL SUCH TERMS AND CONDITIONS PRIOR TO USE OF THE CONTENT. If no About, - Feature License, or Feature Update License is provided, please - contact the Eclipse Foundation to determine what terms and conditions govern that particular Content.

    + Feature License, or Feature Update License is provided, please + contact the Eclipse Foundation to determine what terms and conditions govern that particular Content.

    Cryptography

    Content may contain encryption software. The country in which you are currently may have restrictions on the import, - possession, and use, and/or re-export to - another country, of encryption software. BEFORE using any encryption software, please check the country's laws, - regulations and policies concerning the import, - possession, or use, and re-export of encryption software, to see if this is permitted.

    + possession, and use, and/or re-export to + another country, of encryption software. BEFORE using any encryption software, please check the country's laws, + regulations and policies concerning the import, + possession, or use, and re-export of encryption software, to see if this is permitted.

    Java and all Java-based trademarks are trademarks of Sun Microsystems, Inc. in the United States, other - countries, or both. + countries, or both. diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/WEB-INF/web.xml b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/WEB-INF/web.xml index eb2509c4..38144543 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/WEB-INF/web.xml +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/WEB-INF/web.xml @@ -1,216 +1,216 @@ - - FirstServlet - - io.github.dunwu.javaee.servlet.FirstServlet - - - message - welcome to FirstServlet - - - encoding - utf-8 - - 1 - - - FirstServlet - /servlet/FirstServlet - /servlet/FirstServlet.asp - /servlet/FirstServlet.jsp - /servlet/FirstServlet.php - /servlet/FirstServlet.aspx - + + FirstServlet + + io.github.dunwu.javaee.servlet.FirstServlet + + + message + welcome to FirstServlet + + + encoding + utf-8 + + 1 + + + FirstServlet + /servlet/FirstServlet + /servlet/FirstServlet.asp + /servlet/FirstServlet.jsp + /servlet/FirstServlet.php + /servlet/FirstServlet.aspx + - - RequestServlet - - io.github.dunwu.javaee.servlet.RequestServlet - - - - RequestServlet - /servlet/RequestServlet - + + RequestServlet + + io.github.dunwu.javaee.servlet.RequestServlet + + + + RequestServlet + /servlet/RequestServlet + - - InitParamServlet - - io.github.dunwu.javaee.servlet.InitParamServlet - - - root - root - - - admin - admin - - - - InitParamServlet - /servlet/InitParamServlet - + + InitParamServlet + + io.github.dunwu.javaee.servlet.InitParamServlet + + + root + root + + + admin + admin + + + + InitParamServlet + /servlet/InitParamServlet + - - upload folder - attachment - - - allowed file type - .gif,.jpg,.bmp - - - ContextParamServlet - - io.github.dunwu.javaee.servlet.ContextParamServlet - - - - ContextParamServlet - /servlet/ContextParamServlet - + + upload folder + attachment + + + allowed file type + .gif,.jpg,.bmp + + + ContextParamServlet + + io.github.dunwu.javaee.servlet.ContextParamServlet + + + + ContextParamServlet + /servlet/ContextParamServlet + - - InjectionServlet - - io.github.dunwu.javaee.servlet.InjectionServlet - - - - InjectionServlet - /servlet/InjectionServlet - + + InjectionServlet + + io.github.dunwu.javaee.servlet.InjectionServlet + + + + InjectionServlet + /servlet/InjectionServlet + - - PostServlet - - io.github.dunwu.javaee.servlet.PostServlet - - - - PostServlet - /servlet/PostServlet - + + PostServlet + + io.github.dunwu.javaee.servlet.PostServlet + + + + PostServlet + /servlet/PostServlet + - - ImageServlet - - io.github.dunwu.javaee.servlet.ImageServlet - - 1 - - - UploadServlet - - io.github.dunwu.javaee.servlet.UploadServlet - - - - LifeCycleServlet - - io.github.dunwu.javaee.servlet.LifeCycleServlet - - - startPoint - 2000 - - - - AnnotationServlet - - io.github.dunwu.javaee.servlet.AnnotationServlet - - - - ForwardServlet - - io.github.dunwu.javaee.servlet.ForwardServlet - - - - RedirectServlet - - io.github.dunwu.javaee.servlet.RedirectServlet - - - - ProgressUploadServlet - - io.github.dunwu.javaee.servlet.upload.ProgressUploadServlet - - - - ThreadSafetyServlet - - io.github.dunwu.javaee.servlet.ThreadSafetyServlet - - + + ImageServlet + + io.github.dunwu.javaee.servlet.ImageServlet + + 1 + + + UploadServlet + + io.github.dunwu.javaee.servlet.UploadServlet + + + + LifeCycleServlet + + io.github.dunwu.javaee.servlet.LifeCycleServlet + + + startPoint + 2000 + + + + AnnotationServlet + + io.github.dunwu.javaee.servlet.AnnotationServlet + + + + ForwardServlet + + io.github.dunwu.javaee.servlet.ForwardServlet + + + + RedirectServlet + + io.github.dunwu.javaee.servlet.RedirectServlet + + + + ProgressUploadServlet + + io.github.dunwu.javaee.servlet.upload.ProgressUploadServlet + + + + ThreadSafetyServlet + + io.github.dunwu.javaee.servlet.ThreadSafetyServlet + + - - ImageServlet - /upload/* - - - UploadServlet - /servlet/UploadServlet - - - LifeCycleServlet - /servlet/LifeCycleServlet - - - AnnotationServlet - /servlet/AnnotationServlet - - - ForwardServlet - /servlet/ForwardServlet - - - RedirectServlet - /servlet/RedirectServlet - - - ProgressUploadServlet - /servlet/ProgressUploadServlet - - - ThreadSafetyServlet - /servlet/ThreadSafetyServlet - + + ImageServlet + /upload/* + + + UploadServlet + /servlet/UploadServlet + + + LifeCycleServlet + /servlet/LifeCycleServlet + + + AnnotationServlet + /servlet/AnnotationServlet + + + ForwardServlet + /servlet/ForwardServlet + + + RedirectServlet + /servlet/RedirectServlet + + + ProgressUploadServlet + /servlet/ProgressUploadServlet + + + ThreadSafetyServlet + /servlet/ThreadSafetyServlet + - - index.jsp - + + index.jsp + - - hello - java.lang.String - - Hello, Welcome to the JavaEE Resource Injection. - - + + hello + java.lang.String + + Hello, Welcome to the JavaEE Resource Injection. + + - - i - java.lang.Integer - 30 - + + i + java.lang.Integer + 30 + - - persons - java.lang.String - - Helloween, Cobain, Roses, Axl, - - + + persons + java.lang.String + + Helloween, Cobain, Roses, Axl, + + diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/index.jsp b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/index.jsp index de98af6d..b4a2d228 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/index.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/index.jsp @@ -1,33 +1,33 @@ <%@ page contentType="text/html;charset=UTF-8" language="java" %> - javaee-web + javaee-web

    首页

    欢迎访问

    测试链接

    diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/views/jsp/postPersonalInformation.html b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/views/jsp/postPersonalInformation.html index c771ec69..771902df 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/views/jsp/postPersonalInformation.html +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/views/jsp/postPersonalInformation.html @@ -1,152 +1,152 @@ - 提交用户信息 - - + .button { + color: #FFFFFF; + font-weight: bold; + font-size: 11px; + text-align: center; + padding: .17em 0 .2em .17em; + border-style: solid; + border-width: 1px; + border-color: #99CCFF #115599 #115599 #99CCFF; + background: #6699CC url(images/bg-btn-blue.gif) repeat-x; + } +
    -
    -
    -
    - 填写用户信息 -
    -
    -
    请填写您的姓名:
    -
    - -
    -
    -
    -
    请填写您的密码:
    -
    - -
    -
    -
    -
    请再次输入密码:
    -
    - -
    -
    -
    -
    请选择性别:
    -
    - - - - -
    -
    -
    -
    请输入年龄:
    -
    - -
    -
    -
    -
    请输入生日:
    -
    - -
    格式:"yyyy-mm-dd" -
    -
    -
    -
    请选择您的爱好
    -
    - - - - - - -
    -
    -
    -
    请选择省市:
    -
    - -
    -
    -
    -
    自我描述:
    -
    - -
    -
    -
    -
    -
    -

    -
    -
    -
    -
    +
    +
    +
    + 填写用户信息 +
    +
    +
    请填写您的姓名:
    +
    + +
    +
    +
    +
    请填写您的密码:
    +
    + +
    +
    +
    +
    请再次输入密码:
    +
    + +
    +
    +
    +
    请选择性别:
    +
    + + + + +
    +
    +
    +
    请输入年龄:
    +
    + +
    +
    +
    +
    请输入生日:
    +
    + +
    格式:"yyyy-mm-dd" +
    +
    +
    +
    请选择您的爱好
    +
    + + + + + + +
    +
    +
    +
    请选择省市:
    +
    + +
    +
    +
    +
    自我描述:
    +
    + +
    +
    +
    +
    +
    +

    +
    +
    +
    +
    diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java index 209a0eb7..f622dc2a 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java @@ -1,16 +1,14 @@ package io.github.dunwu.javaee.server; +import java.util.ArrayList; import org.apache.commons.lang3.StringUtils; import org.assertj.core.util.Lists; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.webapp.WebAppClassLoader; import org.eclipse.jetty.webapp.WebAppContext; -import java.util.ArrayList; - /** - * JettyFactory 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij - * 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 + * JettyFactory 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 * * @author Zhang Peng */ @@ -18,12 +16,19 @@ public class JettyFactory { public static final int IDE_ECLIPSE = 0; + public static final int IDE_INTELLIJ = 1; + private static final int PORT = 8080; + private static final String CONTEXT = "/"; + private static final String RESOURCE_BASE_PATH = "src/main/webapp"; + private static final String WEB_XML_PATH = "/WEB-INF/web.xml"; - private static final String[] TLD_JAR_NAMES = new String[] { "sitemesh", "spring-webmvc", "shiro-web", "tiles" }; + + private static final String[] TLD_JAR_NAMES = new String[] {"sitemesh", "spring-webmvc", "shiro-web", "tiles"} + private static final String WINDOWS_WEBDEFAULT_PATH = "jetty/webdefault.xml"; public static Server initServer() { @@ -44,37 +49,17 @@ public static void initWebAppContext(Server server, int type) throws Exception { if (IDE_INTELLIJ == type) { webAppContext.setDefaultsDescriptor(WINDOWS_WEBDEFAULT_PATH); supportJspAndSetTldJarNames(server, TLD_JAR_NAMES); - } - else { + } else { webAppContext.setParentLoaderPriority(true); } System.out.println("[INFO] Application loaded"); } - public static void reloadWebAppContext(Server server) throws Exception { - WebAppContext webAppContext = (WebAppContext) server.getHandler(); - System.out.println("[INFO] Application reloading"); - webAppContext.stop(); - WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext); - classLoader.addClassPath(getAbsolutePath() + "target/classes"); - classLoader.addClassPath(getAbsolutePath() + "target/test-classes"); - webAppContext.setClassLoader(classLoader); - webAppContext.start(); - System.out.println("[INFO] Application reloaded"); - } - - public static void startServer(Server server) throws Exception { - System.out.println("[HINT] Don't forget to set -XX:MaxPermSize=128m"); - server.start(); - System.out.println("Server running at http://localhost:" + PORT + CONTEXT); - System.out.println("[HINT] Hit Enter to reload the application quickly"); - } - public static String getAbsolutePath() { String path = null; String folderPath = JettyFactory.class.getProtectionDomain().getCodeSource().getLocation().getPath() - .substring(1); + .substring(1); if (folderPath.indexOf("target") > 0) { path = folderPath.substring(0, folderPath.indexOf("target")); } @@ -87,9 +72,9 @@ public static void supportJspAndSetTldJarNames(Server server, String... jarNames // AnnotationConfiguration in // order to correctly set up the jsp container org.eclipse.jetty.webapp.Configuration.ClassList classlist = org.eclipse.jetty.webapp.Configuration.ClassList - .setServerDefault(server); + .setServerDefault(server); classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", - "org.eclipse.jetty.annotations.AnnotationConfiguration"); + "org.eclipse.jetty.annotations.AnnotationConfiguration"); // Set the ContainerIncludeJarPattern so that jetty examines these container-path // jars for // tlds, web-fragments etc. @@ -97,14 +82,33 @@ public static void supportJspAndSetTldJarNames(Server server, String... jarNames // them // instead. ArrayList jarNameExprssions = Lists.newArrayList(".*/[^/]*servlet-api-[^/]*\\.jar$", - ".*/javax.servlet.jsp.jstl-.*\\.jar$", ".*/[^/]*taglibs.*\\.jar$"); + ".*/javax.servlet.jsp.jstl-.*\\.jar$", ".*/[^/]*taglibs.*\\.jar$"); for (String jarName : jarNames) { jarNameExprssions.add(".*/" + jarName + "-[^/]*\\.jar$"); } context.setAttribute("org.eclipse.jetty.io.github.dunwu.javaee.server.webapp.ContainerIncludeJarPattern", - StringUtils.join(jarNameExprssions, '|')); + StringUtils.join(jarNameExprssions, '|')); + } + + public static void reloadWebAppContext(Server server) throws Exception { + WebAppContext webAppContext = (WebAppContext) server.getHandler(); + System.out.println("[INFO] Application reloading"); + webAppContext.stop(); + WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext); + classLoader.addClassPath(getAbsolutePath() + "target/classes"); + classLoader.addClassPath(getAbsolutePath() + "target/test-classes"); + webAppContext.setClassLoader(classLoader); + webAppContext.start(); + System.out.println("[INFO] Application reloaded"); + } + + public static void startServer(Server server) throws Exception { + System.out.println("[HINT] Don't forget to set -XX:MaxPermSize=128m"); + server.start(); + System.out.println("Server running at http://localhost:" + PORT + CONTEXT); + System.out.println("[HINT] Hit Enter to reload the application quickly"); } } diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java index c09f0fc1..049c8802 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java @@ -26,8 +26,7 @@ public static void main(String[] args) throws Exception { JettyFactory.reloadWebAppContext(server); } } - } - catch (Exception e) { + } catch (Exception e) { e.printStackTrace(); System.exit(-1); } diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/resources/jetty/webdefault.xml b/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/resources/jetty/webdefault.xml index 65ec9cae..b991d44c 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/resources/jetty/webdefault.xml +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/resources/jetty/webdefault.xml @@ -1,76 +1,76 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://xmlns.jcp.org/xml/ns/javaee" + xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" + metadata-complete="false" + version="3.1"> - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - Default web.xml file. - This file is applied to a Web application before it's own WEB_INF/web.xml file - + + Default web.xml file. + This file is applied to a Web application before it's own WEB_INF/web.xml file + - - - - - - org.eclipse.jetty.servlet.listener.ELContextCleaner - + + + + + + org.eclipse.jetty.servlet.listener.ELContextCleaner + - - - - - - org.eclipse.jetty.servlet.listener.IntrospectorCleaner - + + + + + + org.eclipse.jetty.servlet.listener.IntrospectorCleaner + - - - - + + + + - - - - - - + + + + + - - - default - org.eclipse.jetty.servlet.DefaultServlet - - aliases - false - - - acceptRanges - true - - - dirAllowed - true - - - welcomeServlets - false - - - redirectWelcome - false - - - maxCacheSize - 256000000 - - - maxCachedFileSize - 200000000 - - - maxCachedFiles - 2048 - - - gzip - false - - - etags - false - - - useFileMappedBuffer - false - - - - 0 - + + + default + org.eclipse.jetty.servlet.DefaultServlet + + aliases + false + + + acceptRanges + true + + + dirAllowed + true + + + welcomeServlets + false + + + redirectWelcome + false + + + maxCacheSize + 256000000 + + + maxCachedFileSize + 200000000 + + + maxCachedFiles + 2048 + + + gzip + false + + + etags + false + + + useFileMappedBuffer + false + + + + 0 + - - default - / - + + default + / + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - jsp - org.eclipse.jetty.jsp.JettyJspServlet - - logVerbosityLevel - DEBUG - - - fork - false - - - xpoweredBy - false - - - compilerTargetVM - 1.7 - - - compilerSourceVM - 1.7 - - - 0 - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + jsp + org.eclipse.jetty.jsp.JettyJspServlet + + logVerbosityLevel + DEBUG + + + fork + false + + + xpoweredBy + false + + + compilerTargetVM + 1.7 + + + compilerSourceVM + 1.7 + + + 0 + - - jsp - *.jsp - *.jspf - *.jspx - *.xsp - *.JSP - *.JSPF - *.JSPX - *.XSP - + + jsp + *.jsp + *.jspf + *.jspx + *.xsp + *.JSP + *.JSPF + *.JSPX + *.XSP + - - - - - 30 - + + + + + 30 + - - - - - - - + + + + + + + - - - - - index.html - index.htm - index.jsp - + + + + + index.html + index.htm + index.jsp + - - - - - - ar - ISO-8859-6 - - - be - ISO-8859-5 - - - bg - ISO-8859-5 - - - ca - ISO-8859-1 - - - cs - ISO-8859-2 - - - da - ISO-8859-1 - - - de - ISO-8859-1 - - - el - ISO-8859-7 - - - en - ISO-8859-1 - - - es - ISO-8859-1 - - - et - ISO-8859-1 - - - fi - ISO-8859-1 - - - fr - ISO-8859-1 - - - hr - ISO-8859-2 - - - hu - ISO-8859-2 - - - is - ISO-8859-1 - - - it - ISO-8859-1 - - - iw - ISO-8859-8 - - - ja - Shift_JIS - - - ko - EUC-KR - - - lt - ISO-8859-2 - - - lv - ISO-8859-2 - - - mk - ISO-8859-5 - - - nl - ISO-8859-1 - - - no - ISO-8859-1 - - - pl - ISO-8859-2 - - - pt - ISO-8859-1 - - - ro - ISO-8859-2 - - - ru - ISO-8859-5 - - - sh - ISO-8859-5 - - - sk - ISO-8859-2 - - - sl - ISO-8859-2 - - - sq - ISO-8859-2 - - - sr - ISO-8859-5 - - - sv - ISO-8859-1 - - - tr - ISO-8859-9 - - - uk - ISO-8859-5 - - - zh - GB2312 - - - zh_TW - Big5 - - + + + + + + ar + ISO-8859-6 + + + be + ISO-8859-5 + + + bg + ISO-8859-5 + + + ca + ISO-8859-1 + + + cs + ISO-8859-2 + + + da + ISO-8859-1 + + + de + ISO-8859-1 + + + el + ISO-8859-7 + + + en + ISO-8859-1 + + + es + ISO-8859-1 + + + et + ISO-8859-1 + + + fi + ISO-8859-1 + + + fr + ISO-8859-1 + + + hr + ISO-8859-2 + + + hu + ISO-8859-2 + + + is + ISO-8859-1 + + + it + ISO-8859-1 + + + iw + ISO-8859-8 + + + ja + Shift_JIS + + + ko + EUC-KR + + + lt + ISO-8859-2 + + + lv + ISO-8859-2 + + + mk + ISO-8859-5 + + + nl + ISO-8859-1 + + + no + ISO-8859-1 + + + pl + ISO-8859-2 + + + pt + ISO-8859-1 + + + ro + ISO-8859-2 + + + ru + ISO-8859-5 + + + sh + ISO-8859-5 + + + sk + ISO-8859-2 + + + sl + ISO-8859-2 + + + sq + ISO-8859-2 + + + sr + ISO-8859-5 + + + sv + ISO-8859-1 + + + tr + ISO-8859-9 + + + uk + ISO-8859-5 + + + zh + GB2312 + + + zh_TW + Big5 + + - - - - - - Disable TRACE - / - TRACE - - - - - - Enable everything but TRACE - / - TRACE - - + + + + + + Disable TRACE + / + TRACE + + + + + + Enable everything but TRACE + / + TRACE + + diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/resources/logback.xml index 69f4fbae..5b969d92 100644 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/resources/logback.xml +++ b/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/resources/logback.xml @@ -2,45 +2,45 @@ - - - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n - - - - - - - - logs/${FILE_NAME}-all.%d{yyyy-MM-dd}.log - 30 - - - - - 30MB - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n - - - - - - - - - - - - - - - - + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n + + + + + + + + logs/${FILE_NAME}-all.%d{yyyy-MM-dd}.log + 30 + + + + + 30MB + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n + + + + + + + + + + + + + + + + diff --git a/codes/javaee-tutorial/javaee-tutorial-session/pom.xml b/codes/javaee-tutorial/javaee-tutorial-session/pom.xml index a1d2b06e..ed43f5eb 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/pom.xml +++ b/codes/javaee-tutorial/javaee-tutorial-session/pom.xml @@ -1,93 +1,93 @@ - 4.0.0 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 - io.github.dunwu - javaee-tutorial-session - 1.0.0 - war - javaee-tutorial-session - JavaEE 学习笔记之 session + io.github.dunwu + javaee-tutorial-session + 1.0.0 + war + javaee-tutorial-session + JavaEE 学习笔记之 session - - UTF-8 - 1.7 - ${java.version} - ${java.version} - + + UTF-8 + 1.7 + ${java.version} + ${java.version} + - - - - ch.qos.logback - logback-classic - - - org.slf4j - jcl-over-slf4j - - + + + + ch.qos.logback + logback-classic + + + org.slf4j + jcl-over-slf4j + + - - - org.apache.commons - commons-lang3 - - + + + org.apache.commons + commons-lang3 + + - - - javax.servlet - javax.servlet-api - - + + + javax.servlet + javax.servlet-api + + - - - org.eclipse.jetty - jetty-webapp - test - - - org.eclipse.jetty - jetty-annotations - test - - - org.eclipse.jetty - apache-jsp - test - - - org.eclipse.jetty - apache-jstl - test - - + + + org.eclipse.jetty + jetty-webapp + test + + + org.eclipse.jetty + jetty-annotations + test + + + org.eclipse.jetty + apache-jsp + test + + + org.eclipse.jetty + apache-jstl + test + + - - - junit - junit - test - - - org.assertj - assertj-core - test - - - + + + junit + junit + test + + + org.assertj + assertj-core + test + + + - - - - io.github.dunwu - dunwu-dependencies - 0.4.2-SNAPSHOT - pom - import - - - + + + + io.github.dunwu + dunwu-dependencies + 0.4.2-SNAPSHOT + pom + import + + + diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/AddCookies.java b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/AddCookies.java index 47d3b08e..d30df96a 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/AddCookies.java +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/AddCookies.java @@ -3,19 +3,19 @@ */ package io.github.dunwu.javaee.cookie; +import java.io.IOException; +import java.io.PrintWriter; +import java.net.URLEncoder; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.PrintWriter; -import java.net.URLEncoder; /** * @author Zhang Peng - * @date 2017/3/26. + * @since 2017/3/26. */ @WebServlet("/servlet/AddCookies") public class AddCookies extends HttpServlet { @@ -29,6 +29,14 @@ public AddCookies() { super(); } + /** + * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) + */ + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + doGet(request, response); + } + /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ @@ -52,17 +60,9 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) thro String title = "设置 Cookie 实例"; String docType = "\n"; out.println(docType + "\n" + "" + title + "\n" - + "\n" + "

    " + title + "

    \n" + "
      \n" - + "
    • 站点名::" + request.getParameter("name") + "\n
    • " + "
    • 站点 URL::" - + request.getParameter("url") + "\n
    • " + "
    \n" + ""); - } - - /** - * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) - */ - protected void doPost(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - doGet(request, response); + + "\n" + "

    " + title + "

    \n" + "
      \n" + + "
    • 站点名::" + request.getParameter("name") + "\n
    • " + "
    • 站点 URL::" + + request.getParameter("url") + "\n
    • " + "
    \n" + ""); } } diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/DeleteCookies.java b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/DeleteCookies.java index c7331c08..b3cd3193 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/DeleteCookies.java +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/DeleteCookies.java @@ -3,18 +3,18 @@ */ package io.github.dunwu.javaee.cookie; +import java.io.IOException; +import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.PrintWriter; /** * @author Zhang Peng - * @date 2017/3/26. + * @since 2017/3/26. */ @WebServlet("/servlet/DeleteCookies") public class DeleteCookies extends HttpServlet { @@ -28,6 +28,14 @@ public DeleteCookies() { super(); } + /** + * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) + */ + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + doGet(request, response); + } + /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ @@ -44,7 +52,7 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) thro String title = "删除 Cookie 实例"; String docType = "\n"; out.println( - docType + "\n" + "" + title + "\n" + "\n"); + docType + "\n" + "" + title + "\n" + "\n"); if (cookies != null) { out.println("

    Cookie 名称和值

    "); for (int i = 0; i < cookies.length; i++) { @@ -57,20 +65,11 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) thro out.print("名称:" + cookie.getName() + ","); out.print("值:" + cookie.getValue() + "
    "); } - } - else { + } else { out.println("

    No Cookie founds

    "); } out.println(""); out.println(""); } - /** - * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) - */ - protected void doPost(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - doGet(request, response); - } - } diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/ReadCookies.java b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/ReadCookies.java index 5ad663a7..add3ecab 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/ReadCookies.java +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/ReadCookies.java @@ -3,19 +3,19 @@ */ package io.github.dunwu.javaee.cookie; +import java.io.IOException; +import java.io.PrintWriter; +import java.net.URLDecoder; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.PrintWriter; -import java.net.URLDecoder; /** * @author Zhang Peng - * @date 2017/3/26. + * @since 2017/3/26. */ @WebServlet("/servlet/ReadCookies") public class ReadCookies extends HttpServlet { @@ -29,6 +29,14 @@ public ReadCookies() { super(); } + /** + * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) + */ + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + doGet(request, response); + } + /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ @@ -45,7 +53,7 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) thro String title = "Delete Cookie Example"; String docType = "\n"; out.println( - docType + "\n" + "" + title + "\n" + "\n"); + docType + "\n" + "" + title + "\n" + "\n"); if (cookies != null) { out.println("

    Cookie 名称和值

    "); for (int i = 0; i < cookies.length; i++) { @@ -58,20 +66,11 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) thro out.print("名称:" + cookie.getName() + ","); out.print("值:" + URLDecoder.decode(cookie.getValue(), "utf-8") + "
    "); } - } - else { + } else { out.println("

    No Cookie founds

    "); } out.println(""); out.println(""); } - /** - * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) - */ - protected void doPost(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - doGet(request, response); - } - } diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Person.java b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Person.java index 8bfab7aa..6e4d9271 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Person.java +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Person.java @@ -8,7 +8,7 @@ /** * @author Zhang Peng - * @date 2017/3/26. + * @since 2017/3/26. */ public class Person implements Serializable { diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Topic.java b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Topic.java index 52371698..4e263d60 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Topic.java +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Topic.java @@ -5,7 +5,7 @@ /** * @author Zhang Peng - * @date 2017/3/26. + * @since 2017/3/26. */ public class Topic { diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/session/SessionTrackServlet.java b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/session/SessionTrackServlet.java index 231d11e5..a495546d 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/session/SessionTrackServlet.java +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/session/SessionTrackServlet.java @@ -3,20 +3,20 @@ */ package io.github.dunwu.javaee.session; +import java.io.IOException; +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.Date; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; -import java.io.IOException; -import java.io.PrintWriter; -import java.text.SimpleDateFormat; -import java.util.Date; /** * @author Zhang Peng - * @date 2017/3/26. + * @since 2017/3/26. */ @WebServlet("/servlet/SessionTrackServlet") public class SessionTrackServlet extends HttpServlet { @@ -43,8 +43,7 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) thro // 检查网页上是否有新的访问者 if (session.isNew()) { session.setAttribute(userIDKey, userID); - } - else { + } else { visitCount = (Integer) session.getAttribute(visitCountKey); visitCount = visitCount + 1; userID = (String) session.getAttribute(userIDKey); @@ -57,14 +56,14 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) thro String docType = "\n"; out.println(docType + "\n" + "" + title + "\n" - + "\n" + "

    " + title + "

    \n" - + "

    Session 信息

    \n" + "\n" - + "\n" + " \n" + "\n" + " \n" - + " \n" + "\n" + " \n" + " \n" + "\n" + " \n" + " \n" + "\n" + " \n" + " \n" + "\n" + " \n" + " \n" - + "
    Session 信息
    id" + session.getId() + "
    创建时间" - + df.format(createTime) + "
    最后访问时间" - + df.format(lastAccessTime) + "
    用户 ID" + userID - + "
    访问统计:" + visitCount + "
    \n" + ""); + + "\n" + "

    " + title + "

    \n" + + "

    Session 信息

    \n" + "\n" + + "\n" + " \n" + "\n" + " \n" + + " \n" + "\n" + " \n" + " \n" + "\n" + " \n" + " \n" + "\n" + " \n" + " \n" + "\n" + " \n" + " \n" + + "
    Session 信息
    id" + session.getId() + "
    创建时间" + + df.format(createTime) + "
    最后访问时间" + + df.format(lastAccessTime) + "
    用户 ID" + userID + + "
    访问统计:" + visitCount + "
    \n" + ""); } } diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-session/src/main/resources/logback.xml index 17812db8..0e8f7363 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/resources/logback.xml +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/resources/logback.xml @@ -2,44 +2,44 @@ - - - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n - - - - - - - - logs/${FILE_NAME}-all.%d{yyyy-MM-dd}.log - 30 - - - - - 30MB - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n - - - - - - - - - - - - - - - + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n + + + + + + + + logs/${FILE_NAME}-all.%d{yyyy-MM-dd}.log + 30 + + + + + 30MB + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n + + + + + + + + + + + + + + + diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/WEB-INF/web.xml b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/WEB-INF/web.xml index 100af212..83c873a5 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/WEB-INF/web.xml +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/WEB-INF/web.xml @@ -1,49 +1,49 @@ - - AddCookies - io.github.dunwu.javaee.servlet.cookie.AddCookies - - - AddCookies - /servlet/AddCookies - + + AddCookies + io.github.dunwu.javaee.servlet.cookie.AddCookies + + + AddCookies + /servlet/AddCookies + - - ReadCookies - io.github.dunwu.javaee.servlet.cookie.ReadCookies - - - ReadCookies - /servlet/ReadCookies - + + ReadCookies + io.github.dunwu.javaee.servlet.cookie.ReadCookies + + + ReadCookies + /servlet/ReadCookies + - - DeleteCookies - io.github.dunwu.javaee.servlet.cookie.DeleteCookies - - - DeleteCookies - /servlet/DeleteCookies - + + DeleteCookies + io.github.dunwu.javaee.servlet.cookie.DeleteCookies + + + DeleteCookies + /servlet/DeleteCookies + - - SessionTrackServlet - io.github.dunwu.javaee.session.SessionTrackServlet - - - SessionTrackServlet - /servlet/SessionTrackServlet - + + SessionTrackServlet + io.github.dunwu.javaee.session.SessionTrackServlet + + + SessionTrackServlet + /servlet/SessionTrackServlet + - - 20 - + + 20 + - + diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/encodeURL.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/encodeURL.jsp index 2f7de5f7..c2aeaad0 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/encodeURL.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/encodeURL.jsp @@ -2,7 +2,7 @@ - Cookie Encoding + Cookie Encoding ">Homepage diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/index.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/index.jsp index 2839e947..79af5bcc 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/index.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/index.jsp @@ -1,23 +1,23 @@ <%@ page language="java" pageEncoding="UTF-8" %> <% - String path = request.getContextPath(); - String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; + String path = request.getContextPath(); + String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; %> - + - My JSP 'index.jsp' starting page - - - - - - + My JSP 'index.jsp' starting page + + + + + + diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/css/style.css b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/css/style.css index 8e2eb97a..2ae2e7bd 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/css/style.css +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/css/style.css @@ -1,51 +1,51 @@ body, div, td, input { - font-size: 12px; - margin: 0px; + font-size: 12px; + margin: 0px; } select { - height: 20px; - width: 300px; + height: 20px; + width: 300px; } .title { - font-size: 16px; - padding: 10px; - margin: 10px; - width: 80%; + font-size: 16px; + padding: 10px; + margin: 10px; + width: 80%; } .text { - height: 20px; - width: 300px; - border: 1px solid #AAAAAA; + height: 20px; + width: 300px; + border: 1px solid #AAAAAA; } .line { - margin: 2px; + margin: 2px; } .leftDiv { - width: 110px; - float: left; - height: 22px; - line-height: 22px; - font-weight: bold; + width: 110px; + float: left; + height: 22px; + line-height: 22px; + font-weight: bold; } .rightDiv { - height: 22px; - line-height: 22px; + height: 22px; + line-height: 22px; } .button { - color: #FFFFFF; - font-weight: bold; - font-size: 11px; - text-align: center; - padding: .17em 0 .2em .17em; - border-style: solid; - border-width: 1px; - border-color: #99CCFF #115599 #115599 #99CCFF; - background: #6699CC url(../images/bg-btn-blue.gif) repeat-x; + color: #FFFFFF; + font-weight: bold; + font-size: 11px; + text-align: center; + padding: .17em 0 .2em .17em; + border-style: solid; + border-width: 1px; + border-color: #99CCFF #115599 #115599 #99CCFF; + background: #6699CC url(../images/bg-btn-blue.gif) repeat-x; } diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/addCookies.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/addCookies.jsp index 787c14e5..58b5ff9e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/addCookies.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/addCookies.jsp @@ -2,15 +2,15 @@ - - 添加Cookie + + 添加Cookie
    - 站点名 : -
    - 站点 URL:
    - + 站点名 : +
    + 站点 URL:
    +
    - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/base64.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/base64.jsp index 2501eca4..49b525ac 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/base64.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/base64.jsp @@ -1,31 +1,31 @@ <%@ page language="java" pageEncoding="UTF-8" %> - + <% - File file = new File(this.getServletContext().getRealPath("../../images/cookie.gif")); + File file = new File(this.getServletContext().getRealPath("../../images/cookie.gif")); - // 二进制数组 - byte[] binary = new byte[(int) file.length()]; + // 二进制数组 + byte[] binary = new byte[(int) file.length()]; - // 从图片文件读取二进制数据. - InputStream ins = this.getServletContext().getResourceAsStream(file.getName()); - ins.read(binary); - ins.close(); + // 从图片文件读取二进制数据. + InputStream ins = this.getServletContext().getResourceAsStream(file.getName()); + ins.read(binary); + ins.close(); - // BASE64 编码 - String content = BASE64Encoder.class.newInstance().encode(binary); + // BASE64 编码 + String content = BASE64Encoder.class.newInstance().encode(binary); - // 包含二进制数据的 Cookie - Cookie cookie = new Cookie("file", content); + // 包含二进制数据的 Cookie + Cookie cookie = new Cookie("file", content); - // 将 Cookie 发送到客户端 - response.addCookie(cookie); + // 将 Cookie 发送到客户端 + response.addCookie(cookie); %> - Cookie Encoding + Cookie Encoding 从 Cookie 中获取到的二进制图片:
    diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/base64_decode.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/base64_decode.jsp index 2f4f933e..e317b628 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/base64_decode.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/base64_decode.jsp @@ -2,31 +2,31 @@ <% - // 清除输出 - out.clear(); + // 清除输出 + out.clear(); - for (Cookie cookie : request.getCookies()) { + for (Cookie cookie : request.getCookies()) { - if (cookie.getName().equals("file")) { + if (cookie.getName().equals("file")) { - // 从 Cookie 中取二进制数据 - byte[] binary = BASE64Decoder.class.newInstance().decodeBuffer(cookie.getValue().replace(" ", "")); + // 从 Cookie 中取二进制数据 + byte[] binary = BASE64Decoder.class.newInstance().decodeBuffer(cookie.getValue().replace(" ", "")); - // 设置内容类型为 gif 图片 - response.setHeader("Content-Type", "image/gif"); - response.setHeader("Content-Disposition", "inline;filename=cookie.gif"); - response.setHeader("Connection", "close"); + // 设置内容类型为 gif 图片 + response.setHeader("Content-Type", "image/gif"); + response.setHeader("Content-Disposition", "inline;filename=cookie.gif"); + response.setHeader("Connection", "close"); - // 设置长度 - response.setContentLength(binary.length); + // 设置长度 + response.setContentLength(binary.length); - // 输出到客户端 - response.getOutputStream().write(binary); - response.getOutputStream().flush(); - response.getOutputStream().close(); + // 输出到客户端 + response.getOutputStream().write(binary); + response.getOutputStream().flush(); + response.getOutputStream().close(); - return; - } - } + return; + } + } -%> \ No newline at end of file +%> diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/cookie.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/cookie.jsp index 4af95e27..e6d40bbd 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/cookie.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/cookie.jsp @@ -1,80 +1,80 @@ <%@ page language="java" pageEncoding="UTF-8" errorPage="login.jsp" %> <% - request.setCharacterEncoding("UTF-8"); + request.setCharacterEncoding("UTF-8"); - String username = ""; - int visitTimes = 0; + String username = ""; + int visitTimes = 0; - // 所有的 cookie - Cookie[] cookies = request.getCookies(); + // 所有的 cookie + Cookie[] cookies = request.getCookies(); - // 遍历所有的 Cookie 寻找 用户帐号信息与登录次数信息 - for (int i = 0; cookies != null && i < cookies.length; i++) { - Cookie cookie = cookies[i]; - if ("username".equals(cookie.getName())) { - username = cookie.getValue(); - } else if ("visitTimes".equals(cookie.getName())) { - visitTimes = Integer.parseInt(cookie.getValue()); - cookie.setValue("" + ++visitTimes); - } - } + // 遍历所有的 Cookie 寻找 用户帐号信息与登录次数信息 + for (int i = 0; cookies != null && i < cookies.length; i++) { + Cookie cookie = cookies[i]; + if ("username".equals(cookie.getName())) { + username = cookie.getValue(); + } else if ("visitTimes".equals(cookie.getName())) { + visitTimes = Integer.parseInt(cookie.getValue()); + cookie.setValue("" + ++visitTimes); + } + } - // 如果没有找到 Cookie 中保存的用户名,则转到登录界面 - if (username == null || username.trim().equals("")) { - throw new Exception("您还没有登录。请先登录"); - } + // 如果没有找到 Cookie 中保存的用户名,则转到登录界面 + if (username == null || username.trim().equals("")) { + throw new Exception("您还没有登录。请先登录"); + } - // 修改 Cookie,更新用户的访问次数 - Cookie visitTimesCookie = new Cookie("visitTimes", Integer.toString(++visitTimes)); - response.addCookie(visitTimesCookie); + // 修改 Cookie,更新用户的访问次数 + Cookie visitTimesCookie = new Cookie("visitTimes", Integer.toString(++visitTimes)); + response.addCookie(visitTimesCookie); %> - Cookie - - - - - - + Cookie + + + + + +
    -
    - 登录信息 -
    - - - - - - - - - - - - - -
    - 您的帐号: - - <%= username %> -
    - 登录次数: - - <%= visitTimes %> -
    - - -
    -
    -
    +
    + 登录信息 +
    + + + + + + + + + + + + + +
    + 您的帐号: + + <%= username %> +
    + 登录次数: + + <%= visitTimes %> +
    + + +
    +
    +
    diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/cookieAttribute.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/cookieAttribute.jsp index 1772d74d..2fda1553 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/cookieAttribute.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/cookieAttribute.jsp @@ -1,29 +1,28 @@ <%@ page language="java" pageEncoding="UTF-8" errorPage="login.jsp" %> <% - Cookie[] cc = request.getCookies(); + Cookie[] cc = request.getCookies(); - out.println(cc); + out.println(cc); - Cookie cookie = new Cookie("password1", "babyface009988"); + Cookie cookie = new Cookie("password1", "babyface009988"); - cookie.setPath(request.getContextPath()); + cookie.setPath(request.getContextPath()); - - response.addCookie(cookie); + response.addCookie(cookie); %> - Cookie - - - - - - + Cookie + + + + + + diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/cookieDomain.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/cookieDomain.jsp index 45da571a..4bfa7b8b 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/cookieDomain.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/cookieDomain.jsp @@ -1,25 +1,25 @@ <%@ page language="java" pageEncoding="UTF-8" errorPage="login.jsp" %> <% - Cookie cookie = new Cookie("time", "20080808"); - cookie.setDomain(".h_google.com"); - cookie.setPath("/"); - cookie.setMaxAge(Integer.MAX_VALUE); + Cookie cookie = new Cookie("time", "20080808"); + cookie.setDomain(".h_google.com"); + cookie.setPath("/"); + cookie.setMaxAge(Integer.MAX_VALUE); - response.addCookie(cookie); + response.addCookie(cookie); %> - Cookie - - - - - - + Cookie + + + + + + diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/encoding.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/encoding.jsp index 792bed1e..02391beb 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/encoding.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/encoding.jsp @@ -2,32 +2,32 @@ <% - // 使用中文的 Cookie. name 与 value 都使用 UTF-8 编码. - Cookie cookie = new Cookie(URLEncoder.encode("姓名", "UTF-8"), URLEncoder.encode("张三", "UTF-8")); + // 使用中文的 Cookie. name 与 value 都使用 UTF-8 编码. + Cookie cookie = new Cookie(URLEncoder.encode("姓名", "UTF-8"), URLEncoder.encode("张三", "UTF-8")); - // 发送到客户端 - response.addCookie(cookie); + // 发送到客户端 + response.addCookie(cookie); %> - Cookie Encoding + Cookie Encoding <% - Cookie[] cookies = request.getCookies(); - if (null != cookies) { - for (int i = 0; i < cookies.length; i++) { + Cookie[] cookies = request.getCookies(); + if (null != cookies) { + for (int i = 0; i < cookies.length; i++) { - String cookieName = URLDecoder.decode(cookies[i].getName(), "UTF-8"); - String cookieValue = URLDecoder.decode(cookies[i].getValue(), "UTF-8"); + String cookieName = URLDecoder.decode(cookies[i].getName(), "UTF-8"); + String cookieValue = URLDecoder.decode(cookies[i].getValue(), "UTF-8"); - out.println(cookieName + "="); - out.println(cookieValue + ";
    "); - } - } else { - out.println("Cookie 已经写入客户端. 请刷新页面. "); - } + out.println(cookieName + "="); + out.println(cookieValue + ";
    "); + } + } else { + out.println("Cookie 已经写入客户端. 请刷新页面. "); + } %> diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/history.js b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/history.js index ca664266..52dee341 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/history.js +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/history.js @@ -1,25 +1,27 @@ function getCookie(name) { - var str = document.cookie - if (!str || str.indexOf(name + '=') < 0) return - var cookies = str.split('; ') - for (var i = 0; i < cookies.length; i++) { - var cookie = cookies[i] - if (cookie.indexOf(name + '=') == 0) { - var value = cookie.substring(name.length + 1) - } - } + var str = document.cookie; + if (!str || str.indexOf(name + '=') < 0) return; + var cookies = str.split('; '); + for (var i = 0; i < cookies.length; i++) { + var cookie = cookies[i]; + if (cookie.indexOf(name + '=') == 0) { + var value = cookie.substring(name.length + 1) + } + } } function setCookie(name, value) { - var expires = (arguments.length > 2) ? arguments[2] : null - var path = (arguments.length > 3) ? arguments[3] : null - var domain = (arguments.length > 4) ? arguments[4] : null - var secure = (arguments.length > 5) ? arguments[5] : false - document.cookie = name + '=' + encodeURI(value) - + ((expires == null) ? '' : ('; expires=' + expires.toGMTString())) - + ((path == null) ? '' : ('; path=' + path)) - + ((domain == null) ? '' : ('; domain=' + domain)) - + ((secure == true) ? '; secure' : '') + var expires = (arguments.length > 2) ? arguments[2] : null; + var path = (arguments.length > 3) ? arguments[3] : null; + var domain = (arguments.length > 4) ? arguments[4] : null; + var secure = (arguments.length > 5) ? arguments[5] : false; + document.cookie = name + + '=' + + encodeURI(value) + + ((expires == null) ? '' : ('; expires=' + expires.toGMTString())) + + ((path == null) ? '' : ('; path=' + path)) + + ((domain == null) ? '' : ('; domain=' + domain)) + + ((secure == true) ? '; secure' : '') } -var ss = getCookie('history') +var ss = getCookie('history'); diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/history.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/history.jsp index 37883463..55a2886a 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/history.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/history.jsp @@ -9,55 +9,55 @@ - . - + . +
    -
    - 当前有效的 Cookie - -
    -
    - 历史记录 -
    - - - - - - - - - - - - - - - - - -
    - 帐号: - - -
    - 密码: - - -
    - 有效期: - - 关闭浏览器即失效
    - 30天内有效
    - 永久有效
    -
    - - -
    -
    -
    +
    + 当前有效的 Cookie + +
    +
    + 历史记录 +
    + + + + + + + + + + + + + + + + + +
    + 帐号: + + +
    + 密码: + + +
    + 有效期: + + 关闭浏览器即失效
    + 30天内有效
    + 永久有效
    +
    + + +
    +
    +
    diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/javascript.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/javascript.jsp index 4474cf8f..1060d5d5 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/javascript.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/javascript.jsp @@ -2,9 +2,9 @@ - 欢迎您 - - +
    -
    - 当前有效的 Cookie -
    - -
    -
    - - 欢迎您 - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - 读取 Cookie: - - -
    -   - -
    - 设置 Cookie: - -
    - Name 属性: - - -
    - Value 属性: - - -
    - - -
    -
    + + +
    + + 欢迎您 + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + 读取 Cookie: + + +
    +   + +
    + 设置 Cookie: + +
    + Name 属性: + + +
    + Value 属性: + + +
    + + +
    +
    diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/login.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/login.jsp index 3ab9b5cf..09f2a0a3 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/login.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/login.jsp @@ -1,67 +1,67 @@ <%@ page language="java" pageEncoding="UTF-8" isErrorPage="true" %> <% - request.setCharacterEncoding("UTF-8"); - response.setCharacterEncoding("UTF-8"); + request.setCharacterEncoding("UTF-8"); + response.setCharacterEncoding("UTF-8"); - if ("POST".equals(request.getMethod())) { + if ("POST".equals(request.getMethod())) { - Cookie usernameCookie = new Cookie("username", request.getParameter("username")); - Cookie visittimesCookie = new Cookie("visitTimes", "0"); + Cookie usernameCookie = new Cookie("username", request.getParameter("username")); + Cookie visittimesCookie = new Cookie("visitTimes", "0"); - response.addCookie(usernameCookie); - response.addCookie(visittimesCookie); + response.addCookie(usernameCookie); + response.addCookie(visittimesCookie); - response.sendRedirect(request.getContextPath() + "/cookie.jsp"); + response.sendRedirect(request.getContextPath() + "/cookie.jsp"); - return; - } + return; + } %> - 请先登录 - + 请先登录 +
    -
    - 登录 -
    - - - - - - - - - - - - - - - - - -
    - - - <%= exception.getMessage() %> -
    - 帐号: - - -
    - 密码: - - -
    - - -
    -
    -
    +
    + 登录 +
    + + + + + + + + + + + + + + + + + +
    + + + <%= exception.getMessage() %> +
    + 帐号: + + +
    + 密码: + + +
    + + +
    +
    +
    diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/loginCookie.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/loginCookie.jsp index 17cb0ff5..00ce6801 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/loginCookie.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/loginCookie.jsp @@ -1,162 +1,164 @@ <%@ page language="java" pageEncoding="UTF-8" isErrorPage="false" %> <%! - // 密钥 - private static final String KEY = ":cookie@helloweenvsfei.com"; - - // MD5 加密算法 - public final static String calcMD5(String ss) { - - String s = ss == null ? "" : ss; - - char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - try { - byte[] strTemp = s.getBytes(); - MessageDigest mdTemp = MessageDigest.getInstance("MD5"); - mdTemp.update(strTemp); - byte[] md = mdTemp.digest(); - int j = md.length; - char str[] = new char[j * 2]; - int k = 0; - for (int i = 0; i < j; i++) { - byte byte0 = md[i]; - str[k++] = hexDigits[byte0 >>> 4 & 0xf]; - str[k++] = hexDigits[byte0 & 0xf]; - } - return new String(str); - } catch (Exception e) { - return null; - } - } + // 密钥 + private static final String KEY = ":cookie@helloweenvsfei.com"; + + // MD5 加密算法 + public final static String calcMD5(String ss) { + + String s = ss == null ? "" : ss; + + char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + try { + byte[] strTemp = s.getBytes(); + MessageDigest mdTemp = MessageDigest.getInstance("MD5"); + mdTemp.update(strTemp); + byte[] md = mdTemp.digest(); + int j = md.length; + char[] str = new char[j * 2]; + int k = 0; + for (int i = 0; i < j; i++) { + byte byte0 = md[i]; + str[k++] = hexDigits[byte0 >>> 4 & 0xf]; + str[k++] = hexDigits[byte0 & 0xf]; + } + return new String(str); + } catch (Exception e) { + return null; + } + } %> <% - request.setCharacterEncoding("UTF-8"); - response.setCharacterEncoding("UTF-8"); + request.setCharacterEncoding("UTF-8"); + response.setCharacterEncoding("UTF-8"); - String action = request.getParameter("action"); + String action = request.getParameter("action"); - if ("login".equals(action)) { + if ("login".equals(action)) { - String account = request.getParameter("account"); - String password = request.getParameter("password"); - int timeout = new Integer(request.getParameter("timeout")); + String account = request.getParameter("account"); + String password = request.getParameter("password"); + int timeout = new Integer(request.getParameter("timeout")); - // 把帐号连同密钥使用MD5后加密后保存 - String ssid = calcMD5(account + KEY); + // 把帐号连同密钥使用MD5后加密后保存 + String ssid = calcMD5(account + KEY); - // 把帐号保存到Cookie中 并控制有效期 - Cookie accountCookie = new Cookie("account", account); - accountCookie.setMaxAge(timeout); + // 把帐号保存到Cookie中 并控制有效期 + Cookie accountCookie = new Cookie("account", account); + accountCookie.setMaxAge(timeout); - // 把加密结果保存到Cookie中 并控制有效期 - Cookie ssidCookie = new Cookie("ssid", ssid); - ssidCookie.setMaxAge(timeout); + // 把加密结果保存到Cookie中 并控制有效期 + Cookie ssidCookie = new Cookie("ssid", ssid); + ssidCookie.setMaxAge(timeout); - response.addCookie(accountCookie); - response.addCookie(ssidCookie); + response.addCookie(accountCookie); + response.addCookie(ssidCookie); - // 重新请求本页面 - response.sendRedirect(request.getRequestURI() + "?" + System.currentTimeMillis()); - return; - } else if ("logout".equals(action)) { + // 重新请求本页面 + response.sendRedirect(request.getRequestURI() + "?" + System.currentTimeMillis()); + return; + } else if ("logout".equals(action)) { - // 删除Cookie中的帐号 - Cookie accountCookie = new Cookie("account", ""); - accountCookie.setMaxAge(0); + // 删除Cookie中的帐号 + Cookie accountCookie = new Cookie("account", ""); + accountCookie.setMaxAge(0); - // 删除Cookie中的加密结果 - Cookie ssidCookie = new Cookie("ssid", ""); - ssidCookie.setMaxAge(0); + // 删除Cookie中的加密结果 + Cookie ssidCookie = new Cookie("ssid", ""); + ssidCookie.setMaxAge(0); - response.addCookie(accountCookie); - response.addCookie(ssidCookie); + response.addCookie(accountCookie); + response.addCookie(ssidCookie); - // 重新请求本页面 - response.sendRedirect(request.getRequestURI() + "?" + System.currentTimeMillis()); - return; - } + // 重新请求本页面 + response.sendRedirect(request.getRequestURI() + "?" + System.currentTimeMillis()); + return; + } - boolean loggin = false; + boolean loggin = false; - String account = null; - String ssid = null; + String account = null; + String ssid = null; - // 获取Cookie中的account与ssid - if (request.getCookies() != null) { - for (Cookie cookie : request.getCookies()) { - if (cookie.getName().equals("account")) - account = cookie.getValue(); - if (cookie.getName().equals("ssid")) - ssid = cookie.getValue(); - } - } + // 获取Cookie中的account与ssid + if (request.getCookies() != null) { + for (Cookie cookie : request.getCookies()) { + if (cookie.getName().equals("account")) { + account = cookie.getValue(); + } + if (cookie.getName().equals("ssid")) { + ssid = cookie.getValue(); + } + } + } - if (account != null && ssid != null) { - // 如果加密规则正确, 则视为已经登录 - loggin = ssid.equals(calcMD5(account + KEY)); - } + if (account != null && ssid != null) { + // 如果加密规则正确, 则视为已经登录 + loggin = ssid.equals(calcMD5(account + KEY)); + } %> - <%= loggin ? "欢迎您回来" : "请先登录" %> - - + <%= loggin ? "欢迎您回来" : "请先登录" %> + +
    -
    - 当前有效的 Cookie - -
    -
    - <%= loggin ? "欢迎您回来" : "请先登录" %> - - <% if (loggin) { %> - 欢迎您, ${ cookie.account.value }.    - 注销 - <% } else { %> -
    - - - - - - - - - - - - - - - - - -
    - 帐号: - - -
    - 密码: - - -
    - 有效期: - - 关闭浏览器即失效
    - 30天内有效
    - 永久有效
    -
    - - -
    -
    - <% } %> -
    +
    + 当前有效的 Cookie + +
    +
    + <%= loggin ? "欢迎您回来" : "请先登录" %> + + <% if (loggin) { %> + 欢迎您, ${ cookie.account.value }.    + 注销 + <% } else { %> +
    + + + + + + + + + + + + + + + + + +
    + 帐号: + + +
    + 密码: + + +
    + 有效期: + + 关闭浏览器即失效
    + 30天内有效
    + 永久有效
    +
    + + +
    +
    + <% } %> +
    diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/maxAge.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/maxAge.jsp index 650475a5..afc2ff95 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/maxAge.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/maxAge.jsp @@ -1,23 +1,23 @@ <%@ page language="java" pageEncoding="UTF-8" %> <% - Cookie cookie = new Cookie("username", "helloweenvsfei"); - cookie.setMaxAge(0); + Cookie cookie = new Cookie("username", "helloweenvsfei"); + cookie.setMaxAge(0); - // 必须执行这一句 - response.addCookie(cookie); + // 必须执行这一句 + response.addCookie(cookie); %> - Cookie maxAge - - - - - - + Cookie maxAge + + + + + + diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/setCookie.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/setCookie.jsp index 9879ce3a..bf6c1234 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/setCookie.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/setCookie.jsp @@ -1,103 +1,108 @@ <%@ page language="java" pageEncoding="UTF-8" %> <%! - boolean isNull(String str) { - return str == null || str.trim().length() == 0; - } + boolean isNull(String str) { + return str == null || str.trim().length() == 0; + } %> <% - request.setCharacterEncoding("UTF-8"); + request.setCharacterEncoding("UTF-8"); - if ("POST".equals(request.getMethod())) { + if ("POST".equals(request.getMethod())) { - String name = request.getParameter("name"); - String value = request.getParameter("value"); - String maxAge = request.getParameter("maxAge"); - String domain = request.getParameter("domain"); - String path = request.getParameter("path"); - String comment = request.getParameter("comment"); - String secure = request.getParameter("secure"); + String name = request.getParameter("name"); + String value = request.getParameter("value"); + String maxAge = request.getParameter("maxAge"); + String domain = request.getParameter("domain"); + String path = request.getParameter("path"); + String comment = request.getParameter("comment"); + String secure = request.getParameter("secure"); - if (!isNull(name)) { + if (!isNull(name)) { - Cookie cookie = new Cookie(URLEncoder.encode(name, "UTF-8"), URLEncoder.encode(value, "UTF-8")); + Cookie cookie = new Cookie(URLEncoder.encode(name, "UTF-8"), URLEncoder.encode(value, "UTF-8")); - if (!isNull(maxAge)) - cookie.setMaxAge(Integer.parseInt(maxAge)); - if (!isNull(domain)) - cookie.setDomain(domain); - if (!isNull(path)) - cookie.setPath(path); - if (!isNull(comment)) - cookie.setComment(comment); - if (!isNull(secure)) - cookie.setSecure("true".equalsIgnoreCase(secure)); + if (!isNull(maxAge)) { + cookie.setMaxAge(Integer.parseInt(maxAge)); + } + if (!isNull(domain)) { + cookie.setDomain(domain); + } + if (!isNull(path)) { + cookie.setPath(path); + } + if (!isNull(comment)) { + cookie.setComment(comment); + } + if (!isNull(secure)) { + cookie.setSecure("true".equalsIgnoreCase(secure)); + } - response.addCookie(cookie); - } - } + response.addCookie(cookie); + } + } %> - Cookie - - - - - - + Cookie + + + + + +
    -
    - 当前有效的 Cookie - -
    -
    - 设置新 Cookie -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    name:
    value:
    maxAge:
    domain:
    path:
    comment:
    secure:
    - - -
    -
    -
    + + +
    + 设置新 Cookie +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    name:
    value:
    maxAge:
    domain:
    path:
    comment:
    secure:
    + + +
    +
    +
    diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/session/session.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/session/session.jsp index 2e0e3a87..1a547152 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/session/session.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/session/session.jsp @@ -4,89 +4,88 @@ <%@ page import="java.util.Date" %> <%! - DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); %> <% - response.setCharacterEncoding("UTF-8"); + response.setCharacterEncoding("UTF-8"); - Person[] persons = {new Person("Liu Jinghua", "password1", 34, dateFormat.parse("1982-01-01")), - new Person("Hello Kitty", "hellokitty", 23, dateFormat.parse("1984-02-25")), - new Person("Garfield", "garfield_pass", 23, dateFormat.parse("1994-09-12")),}; + Person[] persons = {new Person("Liu Jinghua", "password1", 34, dateFormat.parse("1982-01-01")), + new Person("Hello Kitty", "hellokitty", 23, dateFormat.parse("1984-02-25")), + new Person("Garfield", "garfield_pass", 23, dateFormat.parse("1994-09-12")),}; - String message = ""; + String message = ""; - if (request.getMethod().equals("POST")) { + if (request.getMethod().equals("POST")) { - for (int i = 0; i < persons.length; i++) { - // 如果 用户名正确 且 密码正确 - if (persons[i].getName().equalsIgnoreCase(request.getParameter("username")) && persons[i].getPassword() - .equals( - request.getParameter( - "password"))) { + for (int i = 0; i < persons.length; i++) { + // 如果 用户名正确 且 密码正确 + if (persons[i].getName().equalsIgnoreCase(request.getParameter("username")) && persons[i].getPassword() + .equals( + request.getParameter( + "password"))) { - // 登录成功, 设置将用户的信息以及登录时间保存到 Session - session.setAttribute("person", persons[i]); - session.setAttribute("loginTime", new Date()); + // 登录成功, 设置将用户的信息以及登录时间保存到 Session + session.setAttribute("person", persons[i]); + session.setAttribute("loginTime", new Date()); - response.sendRedirect(request.getContextPath() + "welcome.jsp"); - return; + response.sendRedirect(request.getContextPath() + "welcome.jsp"); + return; + } + } - } - } - - // 登录失败 - message = "用户名密码不匹配,登录失败。"; - } + // 登录失败 + message = "用户名密码不匹配,登录失败。"; + } %> - 请先登录 - + 请先登录 +
    -
    - 登录 -
    - - <% if (!message.equals("")) { %> - - - - - <% } %> - - - - - - - - - - - - -
    - - - <%= message %> -
    - 帐号: - - -
    - 密码: - - -
    - - -
    -
    -
    +
    + 登录 +
    + + <% if (!message.equals("")) { %> + + + + + <% } %> + + + + + + + + + + + + +
    + + + <%= message %> +
    + 帐号: + + +
    + 密码: + + +
    + + +
    +
    +
    Hello Kitty, hellokitty diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/session/vote.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/session/vote.jsp index 7af342ab..17d7c865 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/session/vote.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/session/vote.jsp @@ -2,50 +2,50 @@ - 欢迎您 - + 欢迎您 +
    -
    - 投票 - - - - - - - - - - - - - - - - - - - - - -
    - 您的姓名: - -
    - -
    - 您的年龄: - -
    - 您的生日: - -
    - - -
    -
    +
    + 投票 + + + + + + + + + + + + + + + + + + + + + +
    + 您的姓名: + +
    + +
    + 您的年龄: + +
    + 您的生日: + +
    + + +
    +
    diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/session/welcome.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/session/welcome.jsp index f27b994e..5cb7b51b 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/session/welcome.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/session/welcome.jsp @@ -1,62 +1,62 @@ <%@ page language="java" pageEncoding="UTF-8" %> <% - if (session.getAttribute("person") == null) { - response.sendRedirect("session.jsp"); - return; - } + if (session.getAttribute("person") == null) { + response.sendRedirect("session.jsp"); + return; + } %> - 欢迎您, ${ person.name } - + 欢迎您, ${ person.name } +
    -
    - 欢迎您${ person.name } - - - - - - - - - - - - - - - - - - - - - -
    - 您的姓名: - - ${ person.name } -
    - 登录时间: - - ${ loginTime } -
    - 您的年龄: - - ${ person.age } -
    - 您的生日: - - ${ person.birthday } -
    - - -
    -
    +
    + 欢迎您${ person.name } + + + + + + + + + + + + + + + + + + + + + +
    + 您的姓名: + + ${ person.name } +
    + 登录时间: + + ${ loginTime } +
    + 您的年龄: + + ${ person.age } +
    + 您的生日: + + ${ person.birthday } +
    + + +
    +
    diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java b/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java index dfc4513e..6ac1a20f 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java @@ -1,16 +1,14 @@ package io.github.dunwu.javaee.server; +import java.util.ArrayList; import org.apache.commons.lang3.StringUtils; import org.assertj.core.util.Lists; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.webapp.WebAppClassLoader; import org.eclipse.jetty.webapp.WebAppContext; -import java.util.ArrayList; - /** - * JettyFactory 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij - * 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 + * JettyFactory 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 * * @author Zhang Peng */ @@ -18,12 +16,19 @@ public class JettyFactory { public static final int IDE_ECLIPSE = 0; + public static final int IDE_INTELLIJ = 1; + private static final int PORT = 9899; + private static final String CONTEXT = "/"; + private static final String RESOURCE_BASE_PATH = "src/main/webapp"; + private static final String WEB_XML_PATH = "/WEB-INF/web.xml"; - private static final String[] TLD_JAR_NAMES = new String[] { "sitemesh", "spring-webmvc", "shiro-web", "tiles" }; + + private static final String[] TLD_JAR_NAMES = new String[] {"sitemesh", "spring-webmvc", "shiro-web", "tiles"} + private static final String WINDOWS_WEBDEFAULT_PATH = "jetty/webdefault.xml"; public static Server initServer() { @@ -44,37 +49,17 @@ public static void initWebAppContext(Server server, int type) throws Exception { if (IDE_INTELLIJ == type) { webAppContext.setDefaultsDescriptor(WINDOWS_WEBDEFAULT_PATH); supportJspAndSetTldJarNames(server, TLD_JAR_NAMES); - } - else { + } else { webAppContext.setParentLoaderPriority(true); } System.out.println("[INFO] Application loaded"); } - public static void reloadWebAppContext(Server server) throws Exception { - WebAppContext webAppContext = (WebAppContext) server.getHandler(); - System.out.println("[INFO] Application reloading"); - webAppContext.stop(); - WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext); - classLoader.addClassPath(getAbsolutePath() + "target/classes"); - classLoader.addClassPath(getAbsolutePath() + "target/test-classes"); - webAppContext.setClassLoader(classLoader); - webAppContext.start(); - System.out.println("[INFO] Application reloaded"); - } - - public static void startServer(Server server) throws Exception { - System.out.println("[HINT] Don't forget to set -XX:MaxPermSize=128m"); - server.start(); - System.out.println("Server running at http://localhost:" + PORT + CONTEXT); - System.out.println("[HINT] Hit Enter to reload the application quickly"); - } - public static String getAbsolutePath() { String path = null; String folderPath = JettyFactory.class.getProtectionDomain().getCodeSource().getLocation().getPath() - .substring(1); + .substring(1); if (folderPath.indexOf("target") > 0) { path = folderPath.substring(0, folderPath.indexOf("target")); } @@ -87,9 +72,9 @@ public static void supportJspAndSetTldJarNames(Server server, String... jarNames // AnnotationConfiguration in // order to correctly set up the jsp container org.eclipse.jetty.webapp.Configuration.ClassList classlist = org.eclipse.jetty.webapp.Configuration.ClassList - .setServerDefault(server); + .setServerDefault(server); classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", - "org.eclipse.jetty.annotations.AnnotationConfiguration"); + "org.eclipse.jetty.annotations.AnnotationConfiguration"); // Set the ContainerIncludeJarPattern so that jetty examines these container-path // jars for // tlds, web-fragments etc. @@ -97,14 +82,33 @@ public static void supportJspAndSetTldJarNames(Server server, String... jarNames // them // instead. ArrayList jarNameExprssions = Lists.newArrayList(".*/[^/]*servlet-api-[^/]*\\.jar$", - ".*/javax.servlet.jsp.jstl-.*\\.jar$", ".*/[^/]*taglibs.*\\.jar$"); + ".*/javax.servlet.jsp.jstl-.*\\.jar$", ".*/[^/]*taglibs.*\\.jar$"); for (String jarName : jarNames) { jarNameExprssions.add(".*/" + jarName + "-[^/]*\\.jar$"); } context.setAttribute("org.eclipse.jetty.io.github.dunwu.javaee.server.webapp.ContainerIncludeJarPattern", - StringUtils.join(jarNameExprssions, '|')); + StringUtils.join(jarNameExprssions, '|')); + } + + public static void reloadWebAppContext(Server server) throws Exception { + WebAppContext webAppContext = (WebAppContext) server.getHandler(); + System.out.println("[INFO] Application reloading"); + webAppContext.stop(); + WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext); + classLoader.addClassPath(getAbsolutePath() + "target/classes"); + classLoader.addClassPath(getAbsolutePath() + "target/test-classes"); + webAppContext.setClassLoader(classLoader); + webAppContext.start(); + System.out.println("[INFO] Application reloaded"); + } + + public static void startServer(Server server) throws Exception { + System.out.println("[HINT] Don't forget to set -XX:MaxPermSize=128m"); + server.start(); + System.out.println("Server running at http://localhost:" + PORT + CONTEXT); + System.out.println("[HINT] Hit Enter to reload the application quickly"); } } diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java b/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java index c09f0fc1..049c8802 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java @@ -26,8 +26,7 @@ public static void main(String[] args) throws Exception { JettyFactory.reloadWebAppContext(server); } } - } - catch (Exception e) { + } catch (Exception e) { e.printStackTrace(); System.exit(-1); } diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/test/resources/jetty/webdefault.xml b/codes/javaee-tutorial/javaee-tutorial-session/src/test/resources/jetty/webdefault.xml index 65ec9cae..b991d44c 100644 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/test/resources/jetty/webdefault.xml +++ b/codes/javaee-tutorial/javaee-tutorial-session/src/test/resources/jetty/webdefault.xml @@ -1,76 +1,76 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://xmlns.jcp.org/xml/ns/javaee" + xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" + metadata-complete="false" + version="3.1"> - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - Default web.xml file. - This file is applied to a Web application before it's own WEB_INF/web.xml file - + + Default web.xml file. + This file is applied to a Web application before it's own WEB_INF/web.xml file + - - - - - - org.eclipse.jetty.servlet.listener.ELContextCleaner - + + + + + + org.eclipse.jetty.servlet.listener.ELContextCleaner + - - - - - - org.eclipse.jetty.servlet.listener.IntrospectorCleaner - + + + + + + org.eclipse.jetty.servlet.listener.IntrospectorCleaner + - - - - + + + + - - - - - - + + + + + - - - default - org.eclipse.jetty.servlet.DefaultServlet - - aliases - false - - - acceptRanges - true - - - dirAllowed - true - - - welcomeServlets - false - - - redirectWelcome - false - - - maxCacheSize - 256000000 - - - maxCachedFileSize - 200000000 - - - maxCachedFiles - 2048 - - - gzip - false - - - etags - false - - - useFileMappedBuffer - false - - - - 0 - + + + default + org.eclipse.jetty.servlet.DefaultServlet + + aliases + false + + + acceptRanges + true + + + dirAllowed + true + + + welcomeServlets + false + + + redirectWelcome + false + + + maxCacheSize + 256000000 + + + maxCachedFileSize + 200000000 + + + maxCachedFiles + 2048 + + + gzip + false + + + etags + false + + + useFileMappedBuffer + false + + + + 0 + - - default - / - + + default + / + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - jsp - org.eclipse.jetty.jsp.JettyJspServlet - - logVerbosityLevel - DEBUG - - - fork - false - - - xpoweredBy - false - - - compilerTargetVM - 1.7 - - - compilerSourceVM - 1.7 - - - 0 - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + jsp + org.eclipse.jetty.jsp.JettyJspServlet + + logVerbosityLevel + DEBUG + + + fork + false + + + xpoweredBy + false + + + compilerTargetVM + 1.7 + + + compilerSourceVM + 1.7 + + + 0 + - - jsp - *.jsp - *.jspf - *.jspx - *.xsp - *.JSP - *.JSPF - *.JSPX - *.XSP - + + jsp + *.jsp + *.jspf + *.jspx + *.xsp + *.JSP + *.JSPF + *.JSPX + *.XSP + - - - - - 30 - + + + + + 30 + - - - - - - - + + + + + + + - - - - - index.html - index.htm - index.jsp - + + + + + index.html + index.htm + index.jsp + - - - - - - ar - ISO-8859-6 - - - be - ISO-8859-5 - - - bg - ISO-8859-5 - - - ca - ISO-8859-1 - - - cs - ISO-8859-2 - - - da - ISO-8859-1 - - - de - ISO-8859-1 - - - el - ISO-8859-7 - - - en - ISO-8859-1 - - - es - ISO-8859-1 - - - et - ISO-8859-1 - - - fi - ISO-8859-1 - - - fr - ISO-8859-1 - - - hr - ISO-8859-2 - - - hu - ISO-8859-2 - - - is - ISO-8859-1 - - - it - ISO-8859-1 - - - iw - ISO-8859-8 - - - ja - Shift_JIS - - - ko - EUC-KR - - - lt - ISO-8859-2 - - - lv - ISO-8859-2 - - - mk - ISO-8859-5 - - - nl - ISO-8859-1 - - - no - ISO-8859-1 - - - pl - ISO-8859-2 - - - pt - ISO-8859-1 - - - ro - ISO-8859-2 - - - ru - ISO-8859-5 - - - sh - ISO-8859-5 - - - sk - ISO-8859-2 - - - sl - ISO-8859-2 - - - sq - ISO-8859-2 - - - sr - ISO-8859-5 - - - sv - ISO-8859-1 - - - tr - ISO-8859-9 - - - uk - ISO-8859-5 - - - zh - GB2312 - - - zh_TW - Big5 - - + + + + + + ar + ISO-8859-6 + + + be + ISO-8859-5 + + + bg + ISO-8859-5 + + + ca + ISO-8859-1 + + + cs + ISO-8859-2 + + + da + ISO-8859-1 + + + de + ISO-8859-1 + + + el + ISO-8859-7 + + + en + ISO-8859-1 + + + es + ISO-8859-1 + + + et + ISO-8859-1 + + + fi + ISO-8859-1 + + + fr + ISO-8859-1 + + + hr + ISO-8859-2 + + + hu + ISO-8859-2 + + + is + ISO-8859-1 + + + it + ISO-8859-1 + + + iw + ISO-8859-8 + + + ja + Shift_JIS + + + ko + EUC-KR + + + lt + ISO-8859-2 + + + lv + ISO-8859-2 + + + mk + ISO-8859-5 + + + nl + ISO-8859-1 + + + no + ISO-8859-1 + + + pl + ISO-8859-2 + + + pt + ISO-8859-1 + + + ro + ISO-8859-2 + + + ru + ISO-8859-5 + + + sh + ISO-8859-5 + + + sk + ISO-8859-2 + + + sl + ISO-8859-2 + + + sq + ISO-8859-2 + + + sr + ISO-8859-5 + + + sv + ISO-8859-1 + + + tr + ISO-8859-9 + + + uk + ISO-8859-5 + + + zh + GB2312 + + + zh_TW + Big5 + + - - - - - - Disable TRACE - / - TRACE - - - - - - Enable everything but TRACE - / - TRACE - - + + + + + + Disable TRACE + / + TRACE + + + + + + Enable everything but TRACE + / + TRACE + + diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/pom.xml b/codes/javaee-tutorial/javaee-tutorial-taglib/pom.xml index 98a2f4f5..ecc38dd2 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/pom.xml +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/pom.xml @@ -1,122 +1,123 @@ - 4.0.0 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" + xmlns="http://maven.apache.org/POM/4.0.0"> + 4.0.0 - - io.github.dunwu - javaee-tutorial-taglib - 1.0.0 - war - + + io.github.dunwu + javaee-tutorial-taglib + 1.0.0 + war + - - javaee-tutorial-taglib - JavaEE 学习笔记之 taglib - + + javaee-tutorial-taglib + JavaEE 学习笔记之 taglib + - - - UTF-8 - 1.7 - ${java.version} - ${java.version} - + + + UTF-8 + 1.7 + ${java.version} + ${java.version} + - - - - ch.qos.logback - logback-classic - - - org.slf4j - jcl-over-slf4j - - + + + + ch.qos.logback + logback-classic + + + org.slf4j + jcl-over-slf4j + + - - - org.apache.commons - commons-lang3 - - + + + org.apache.commons + commons-lang3 + + - - - javax.servlet - javax.servlet-api - provided - - - javax.servlet.jsp - javax.servlet.jsp-api - provided - - + + + javax.servlet + javax.servlet-api + provided + + + javax.servlet.jsp + javax.servlet.jsp-api + provided + + - - - org.eclipse.jetty - jetty-webapp - test - - - org.eclipse.jetty - jetty-annotations - test - - - org.eclipse.jetty - apache-jsp - test - - - org.eclipse.jetty - apache-jstl - test - - + + + org.eclipse.jetty + jetty-webapp + test + + + org.eclipse.jetty + jetty-annotations + test + + + org.eclipse.jetty + apache-jsp + test + + + org.eclipse.jetty + apache-jstl + test + + - - - junit - junit - test - - - org.assertj - assertj-core - test - - + + + junit + junit + test + + + org.assertj + assertj-core + test + + - - - - xalan - xalan - 2.7.2 - - - xerces - xercesImpl - 2.11.0 - - - + + + + xalan + xalan + 2.7.2 + + + xerces + xercesImpl + 2.11.0 + + + - - - - io.github.dunwu - dunwu-dependencies - 0.4.2-SNAPSHOT - pom - import - - - + + + + io.github.dunwu + dunwu-dependencies + 0.4.2-SNAPSHOT + pom + import + + + diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/Copyright.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/Copyright.java index d2eebcca..a1208fb2 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/Copyright.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/Copyright.java @@ -3,16 +3,16 @@ */ package io.github.dunwu.javaee.taglib; +import java.io.IOException; +import java.util.ResourceBundle; import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.PageContext; import javax.servlet.jsp.tagext.Tag; -import java.io.IOException; -import java.util.ResourceBundle; /** * @author Zhang Peng - * @date 2017/4/3. + * @since 2017/4/3. */ public class Copyright implements Tag { @@ -28,8 +28,7 @@ public int doEndTag() throws JspException { out.println("
    "); out.println(ResourceBundle.getBundle("copyright").getString("copyright")); out.println("
    "); - } - catch (IOException e) { + } catch (IOException e) { throw new JspException(e); } diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag.java index 0f90b552..348c1d2e 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag.java @@ -5,13 +5,13 @@ /** * @author Zhang Peng - * @date 2017/4/3. + * @since 2017/4/3. */ +import java.io.IOException; import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.tagext.SimpleTagSupport; -import java.io.IOException; public class HelloTag extends SimpleTagSupport { diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag2.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag2.java index 18afc538..604014b7 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag2.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag2.java @@ -3,14 +3,14 @@ */ package io.github.dunwu.javaee.taglib; -import javax.servlet.jsp.JspException; -import javax.servlet.jsp.tagext.SimpleTagSupport; import java.io.IOException; import java.io.StringWriter; +import javax.servlet.jsp.JspException; +import javax.servlet.jsp.tagext.SimpleTagSupport; /** * @author Zhang Peng - * @date 2017/4/3. + * @since 2017/4/3. */ public class HelloTag2 extends SimpleTagSupport { diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag3.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag3.java index 7b27b835..5aeb9a3a 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag3.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag3.java @@ -3,19 +3,20 @@ */ package io.github.dunwu.javaee.taglib; +import java.io.IOException; +import java.io.StringWriter; import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.tagext.SimpleTagSupport; -import java.io.IOException; -import java.io.StringWriter; /** * @author Zhang Peng - * @date 2017/4/3. + * @since 2017/4/3. */ public class HelloTag3 extends SimpleTagSupport { StringWriter sw = new StringWriter(); + private String message; public void setMessage(String msg) { @@ -27,8 +28,7 @@ public void doTag() throws JspException, IOException { /* 从属性中使用消息 */ JspWriter out = getJspContext().getOut(); out.println(message); - } - else { + } else { /* 从内容体中使用消息 */ getJspBody().invoke(sw); getJspContext().getOut().println(sw.toString()); diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/function/Function.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/function/Function.java index 472d8e87..9858e9c6 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/function/Function.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/function/Function.java @@ -6,14 +6,16 @@ public class Function { /** * 返回字节长度 + * * @param obj * @return */ @SuppressWarnings("unchecked") public static int length(Object obj) { - if (obj == null) + if (obj == null) { return 0; + } if (obj instanceof StringBuffer) { return length(((StringBuffer) obj).toString()); @@ -32,8 +34,9 @@ public static int length(Object obj) { public static String substring(String str, int byteLength) { - if (str == null) + if (str == null) { return ""; + } StringBuffer buffer = new StringBuffer(); @@ -41,8 +44,7 @@ public static String substring(String str, int byteLength) { char ch = str.charAt(i); if (length(buffer.toString() + ch) > byteLength) { break; - } - else { + } else { buffer.append(ch); } } @@ -55,7 +57,6 @@ public static void main(String[] args) { System.out.println(length("中文测试")); System.out.println(substring("中文测试", 5)); - } } diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/AddTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/AddTag.java index 28064eec..a8f23eac 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/AddTag.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/AddTag.java @@ -23,8 +23,7 @@ public void setNum2(int num2) { public int doEndTag() throws JspException { try { this.pageContext.getOut().println(num1 + " + " + num2 + " = " + (num1 + num2)); - } - catch (Exception e) { + } catch (Exception e) { e.printStackTrace(); } return EVAL_PAGE; diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/Copyright.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/Copyright.java index 400c71f8..649a5aa4 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/Copyright.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/Copyright.java @@ -1,11 +1,11 @@ package io.github.dunwu.javaee.taglib.tags; +import java.io.IOException; +import java.util.ResourceBundle; import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.PageContext; import javax.servlet.jsp.tagext.Tag; -import java.io.IOException; -import java.util.ResourceBundle; public class Copyright implements Tag { @@ -21,8 +21,7 @@ public int doEndTag() throws JspException { out.println("
    "); out.println(ResourceBundle.getBundle("copyright").getString("copyright")); out.println("
    "); - } - catch (IOException e) { + } catch (IOException e) { throw new JspException(e); } diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/Copyright2.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/Copyright2.java index 332e9366..93e6c32f 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/Copyright2.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/Copyright2.java @@ -1,10 +1,10 @@ package io.github.dunwu.javaee.taglib.tags; +import java.io.IOException; +import java.util.ResourceBundle; import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.tagext.TagSupport; -import java.io.IOException; -import java.util.ResourceBundle; public class Copyright2 extends TagSupport { @@ -18,8 +18,7 @@ public int doEndTag() throws JspException { out.println("
    "); out.println(ResourceBundle.getBundle("copyright").getString("copyright")); out.println("
    "); - } - catch (IOException e) { + } catch (IOException e) { throw new JspException(e); } diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/DynamicAttributeTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/DynamicAttributeTag.java index 51252e56..69edd78d 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/DynamicAttributeTag.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/DynamicAttributeTag.java @@ -1,12 +1,12 @@ package io.github.dunwu.javaee.taglib.tags; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.tagext.DynamicAttributes; import javax.servlet.jsp.tagext.TagSupport; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; public class DynamicAttributeTag extends TagSupport implements DynamicAttributes { @@ -43,8 +43,7 @@ public int doEndTag() throws JspException { try { out.write(buffer.toString()); - } - catch (Exception e) { + } catch (Exception e) { } return super.doEndTag(); diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/HelloTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/HelloTag.java index 14faaaa6..68fe5624 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/HelloTag.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/HelloTag.java @@ -14,8 +14,7 @@ public int doEndTag() throws JspException { try { this.pageContext.getOut().println("Hello, " + name); - } - catch (Exception e) { + } catch (Exception e) { throw new JspException(e); } diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/IteratorTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/IteratorTag.java index d724c10d..22eaad19 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/IteratorTag.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/IteratorTag.java @@ -1,8 +1,8 @@ package io.github.dunwu.javaee.taglib.tags; +import java.util.Collection; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.TagSupport; -import java.util.Collection; public class IteratorTag extends TagSupport { @@ -17,8 +17,7 @@ public int doEndTag() throws JspException { for (Object obj : connection) { this.pageContext.getOut().println(obj + ",
    "); } - } - catch (Exception e) { + } catch (Exception e) { throw new JspException(e); } diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/LoopTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/LoopTag.java index 84488afb..d89122ff 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/LoopTag.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/LoopTag.java @@ -23,15 +23,11 @@ public int doAfterBody() throws JspException { /** 只要 times > 0 就继续循环,同时 times 自减 */ try { this.getPreviousOut().println(this.getBodyContent().getString()); - - } - catch (Exception e) { + } catch (Exception e) { } return EVAL_BODY_AGAIN; - - } - else { + } else { /** 结束运行,同时复原 times */ diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/ToLowerCaseTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/ToLowerCaseTag.java index 5a6e0d31..60e372b3 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/ToLowerCaseTag.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/ToLowerCaseTag.java @@ -14,8 +14,7 @@ public int doEndTag() throws JspException { try { this.pageContext.getOut().print(content.toLowerCase()); - } - catch (Exception e) { + } catch (Exception e) { } return EVAL_PAGE; diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/table/Column.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/table/Column.java index 7bee7e13..016c7746 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/table/Column.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/table/Column.java @@ -1,9 +1,9 @@ package io.github.dunwu.javaee.taglib.tags.table; -import javax.servlet.jsp.JspException; -import javax.servlet.jsp.tagext.TagSupport; import java.util.HashMap; import java.util.Map; +import javax.servlet.jsp.JspException; +import javax.servlet.jsp.tagext.TagSupport; public class Column extends TagSupport { diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/table/Table.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/table/Table.java index 046158c2..5b13dd31 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/table/Table.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/table/Table.java @@ -1,27 +1,33 @@ package io.github.dunwu.javaee.taglib.tags.table; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.jsp.JspException; -import javax.servlet.jsp.JspWriter; -import javax.servlet.jsp.tagext.BodyContent; -import javax.servlet.jsp.tagext.BodyTagSupport; import java.io.IOException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.jsp.JspException; +import javax.servlet.jsp.JspWriter; +import javax.servlet.jsp.tagext.BodyContent; +import javax.servlet.jsp.tagext.BodyTagSupport; public class Table extends BodyTagSupport { private static final long serialVersionUID = 3358444196409845360L; - /** 存储列信息 */ + /** + * 存储列信息 + */ private List> columns = new ArrayList>(); - /** 存储数据,可能为 集合类型的或者数组类型的 */ + /** + * 存储数据,可能为 集合类型的或者数组类型的 + */ private Object items; - /** 取排序数据的 URL */ + /** + * 取排序数据的 URL + */ private String url; @Override @@ -111,8 +117,7 @@ public int doAfterBody() throws JspException { try { /** 获取 getXxx() 形式的方法 */ method = obj.getClass().getMethod(getter); - } - catch (Exception e) { + } catch (Exception e) { } if (method == null) { @@ -125,9 +130,7 @@ public int doAfterBody() throws JspException { /** 获取属性值 */ Object value = method.invoke(obj); out.println("" + value + ""); - - } - catch (Exception e) { + } catch (Exception e) { throw new JspException(e); } } @@ -143,43 +146,42 @@ public int doAfterBody() throws JspException { out.println(" function sort(column){"); out.println(" if(orderName == column){"); out.println(" location='" + url - + "?orderName=' + column + '&orderType=' + (orderType=='asc' ? 'desc' : 'asc'); "); + + "?orderName=' + column + '&orderType=' + (orderType=='asc' ? 'desc' : 'asc'); "); out.println(" }"); out.println(" else{"); out.println(" location='" + url + "?orderName=' + column + '&orderType=' + orderType; "); out.println(" }"); out.println(" }"); out.println(""); - - } - catch (IOException ioe) { + } catch (IOException ioe) { throw new JspException("Error: " + ioe.getMessage()); } return SKIP_BODY; } - public Object getItems() { - return items; - } - - public void setItems(Object items) { - this.items = items; - } - /** * 首字母大写 + * * @param column * @return */ public String toGetterStyle(String column) { - if (column.length() == 1) + if (column.length() == 1) { return column.toUpperCase(); + } char ch = column.charAt(0); return Character.toUpperCase(ch) + column.substring(1); + } + public Object getItems() { + return items; + } + + public void setItems(Object items) { + this.items = items; } public List> getColumns() { diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/MultiAttributeTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/MultiAttributeTag.java index ae065252..610efed7 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/MultiAttributeTag.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/MultiAttributeTag.java @@ -1,10 +1,10 @@ package io.github.dunwu.javaee.taglib.tags2; +import java.io.IOException; +import java.io.StringWriter; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.JspFragment; import javax.servlet.jsp.tagext.SimpleTagSupport; -import java.io.IOException; -import java.io.StringWriter; public class MultiAttributeTag extends SimpleTagSupport { @@ -39,7 +39,6 @@ public void doTag() throws JspException, IOException { this.getJspContext().getOut().print("3 次调用 body2 后的结果:" + writer2.getBuffer().toString() + "

    "); this.getJspContext().getOut().print("5 次调用 body1 后的结果:" + writer1.getBuffer().toString() + "

    "); - } } diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/MultiTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/MultiTag.java index c9f0e62f..6ec8ca32 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/MultiTag.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/MultiTag.java @@ -1,8 +1,8 @@ package io.github.dunwu.javaee.taglib.tags2; +import java.io.IOException; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.SimpleTagSupport; -import java.io.IOException; public class MultiTag extends SimpleTagSupport { diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/ToUpperCaseTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/ToUpperCaseTag.java index 669957b0..e48cb349 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/ToUpperCaseTag.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/ToUpperCaseTag.java @@ -1,10 +1,10 @@ package io.github.dunwu.javaee.taglib.tags2; +import java.io.IOException; +import java.io.StringWriter; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.JspFragment; import javax.servlet.jsp.tagext.SimpleTagSupport; -import java.io.IOException; -import java.io.StringWriter; public class ToUpperCaseTag extends SimpleTagSupport { diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/Messages.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/Messages.java index 05fba10b..624e2da6 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/Messages.java +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/Messages.java @@ -16,8 +16,7 @@ private Messages() { public static String getString(String key) { try { return RESOURCE_BUNDLE.getString(key); - } - catch (MissingResourceException e) { + } catch (MissingResourceException e) { return '!' + key + '!'; } } @@ -27,9 +26,7 @@ public static String getString(String key, Object... params) { String value = RESOURCE_BUNDLE.getString(key); return MessageFormat.format(value, params); - - } - catch (MissingResourceException e) { + } catch (MissingResourceException e) { return '!' + key + '!'; } } diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/messages.properties b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/messages.properties index c31367f8..c99d9bfc 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/messages.properties +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/messages.properties @@ -1 +1 @@ -TestMessage.0 = test {0}, {1} +TestMessage.0=test {0}, {1} diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/resources/copyright.properties b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/resources/copyright.properties index 4cbf90ff..2a835e8b 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/resources/copyright.properties +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/resources/copyright.properties @@ -1 +1 @@ -copyright = ©2016-2017, Zhang Peng +copyright=©2016-2017, Zhang Peng diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/resources/logback.xml index 12497c03..112831e0 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/resources/logback.xml +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/resources/logback.xml @@ -2,45 +2,45 @@ - - - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n - - - - - - - - logs/${FILE_NAME}-all.%d{yyyy-MM-dd}.log - 30 - - - - - 30MB - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n - - - - - - - - - - - - - - - - + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n + + + + + + + + logs/${FILE_NAME}-all.%d{yyyy-MM-dd}.log + 30 + + + + + 30MB + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n + + + + + + + + + + + + + + + + diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/function.tld b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/function.tld index 3ae6e5b2..4bf0a5e4 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/function.tld +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/function.tld @@ -1,44 +1,44 @@ + xmlns="http://java.sun.com/xml/ns/j2ee" + xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd" + version="2.0"> - custom functions library - custom functions - 1.1 - function - http://www.victorzhang.com/function + custom functions library + custom functions + 1.1 + function + http://www.victorzhang.com/function - - - - length - - io.github.dunwu.javaee.taglib.function.Function - - - int length(java.lang.Object) - - - ${fn:length(string)} - - + + + + length + + io.github.dunwu.javaee.taglib.function.Function + + + int length(java.lang.Object) + + + ${fn:length(string)} + + - - - - substring - - io.github.dunwu.javaee.taglib.function.Function - - - java.lang.String substring(java.lang.String, int) - - - ${fn:length(string, 3)} - - + + + + substring + + io.github.dunwu.javaee.taglib.function.Function + + + java.lang.String substring(java.lang.String, int) + + + ${fn:length(string, 3)} + + diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello.tld b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello.tld index 68451e93..2dca9237 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello.tld +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello.tld @@ -1,10 +1,10 @@ - 1.0 - 2.0 - Example TLD - - Hello - io.github.dunwu.javaee.taglib.HelloTag - empty - + 1.0 + 2.0 + Example TLD + + Hello + io.github.dunwu.javaee.taglib.HelloTag + empty + diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello2.tld b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello2.tld index a9e70e71..44728ef9 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello2.tld +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello2.tld @@ -1,10 +1,10 @@ - 1.0 - 2.0 - Example TLD with Body - - Hello - io.github.dunwu.javaee.taglib.HelloTag2 - scriptless - + 1.0 + 2.0 + Example TLD with Body + + Hello + io.github.dunwu.javaee.taglib.HelloTag2 + scriptless + diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello3.tld b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello3.tld index 0be7ed4d..6d995a74 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello3.tld +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello3.tld @@ -1,13 +1,13 @@ - 1.0 - 2.0 - Example TLD with Body - - Hello - io.github.dunwu.javaee.taglib.HelloTag3 - scriptless - - message - - + 1.0 + 2.0 + Example TLD with Body + + Hello + io.github.dunwu.javaee.taglib.HelloTag3 + scriptless + + message + + diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/taglib.tld b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/taglib.tld index c5a95ac8..0874c62f 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/taglib.tld +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/taglib.tld @@ -3,147 +3,147 @@ + xmlns="http://java.sun.com/xml/ns/j2ee" + xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee web-jsptaglibrary_2_0.xsd" + version="2.0"> - 1.0 - 1.1 - taglib - http://www.victorzhang.com/tags - A simple tab library for the examples + 1.0 + 1.1 + taglib + http://www.victorzhang.com/tags + A simple tab library for the examples - - copyright - io.github.dunwu.javaee.taglib.Copyright - JSP - Copyright tag. - + + copyright + io.github.dunwu.javaee.taglib.Copyright + JSP + Copyright tag. + - - hello - io.github.dunwu.javaee.taglib.tags.HelloTag - empty - Hello tag with parameters. - - name - true - true - - + + hello + io.github.dunwu.javaee.taglib.tags.HelloTag + empty + Hello tag with parameters. + + name + true + true + + - - add - io.github.dunwu.javaee.taglib.tags.AddTag - empty - Add tag with parameters. - - num1 - true - true - - - num2 - true - true - - + + add + io.github.dunwu.javaee.taglib.tags.AddTag + empty + Add tag with parameters. + + num1 + true + true + + + num2 + true + true + + - - toLowerCase - io.github.dunwu.javaee.taglib.tags.ToLowerCaseTag - JSP - Tag with body. - + + toLowerCase + io.github.dunwu.javaee.taglib.tags.ToLowerCaseTag + JSP + Tag with body. + - - loop - io.github.dunwu.javaee.taglib.tags.LoopTag - JSP - Tag with body. - + + loop + io.github.dunwu.javaee.taglib.tags.LoopTag + JSP + Tag with body. + - - dynamicAttribute - io.github.dunwu.javaee.taglib.tags.DynamicAttributeTag - empty - true - Tag with dynamic attribute. - + + dynamicAttribute + io.github.dunwu.javaee.taglib.tags.DynamicAttributeTag + empty + true + Tag with dynamic attribute. + - - table - io.github.dunwu.javaee.taglib.tags.table.Table - JSP - Table tag. - - items - true - true - - - url - false - true - - + + table + io.github.dunwu.javaee.taglib.tags.table.Table + JSP + Table tag. + + items + true + true + + + url + false + true + + - - column - io.github.dunwu.javaee.taglib.tags.table.Column - empty - Column tag. - - property - true - true - - - label - false - true - - + + column + io.github.dunwu.javaee.taglib.tags.table.Column + empty + Column tag. + + property + true + true + + + label + false + true + + - - multi - io.github.dunwu.javaee.taglib.tags2.MultiTag - empty - multi tag with parameters. - - num1 - true - true - - - num2 - true - true - - + + multi + io.github.dunwu.javaee.taglib.tags2.MultiTag + empty + multi tag with parameters. + + num1 + true + true + + + num2 + true + true + + - - toUpperCase - io.github.dunwu.javaee.taglib.tags2.ToUpperCaseTag - tagdependent - body tag - + + toUpperCase + io.github.dunwu.javaee.taglib.tags2.ToUpperCaseTag + tagdependent + body tag + - - multiAttribute - io.github.dunwu.javaee.taglib.tags2.MultiAttributeTag - tagdependent - multi attribute tag with parameters. - - body1 - false - true - - - body2 - false - true - - + + multiAttribute + io.github.dunwu.javaee.taglib.tags2.MultiAttributeTag + tagdependent + multi attribute tag with parameters. + + body1 + false + true + + + body2 + false + true + + diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/web.xml b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/web.xml index ae92ddac..a403cc53 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/web.xml +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/web.xml @@ -1,25 +1,25 @@ - - HelloServlet - /examples/configuration.jsp - - message - welcome to jsp - - 1 - - - HelloServlet - /config - /config.jsp - + + HelloServlet + /examples/configuration.jsp + + message + welcome to jsp + + 1 + + + HelloServlet + /config + /config.jsp + - - - /WEB-INF/views/jsp/index.jsp - + + + /WEB-INF/views/jsp/index.jsp + diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/dynamic.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/dynamic.jsp index 62a3ae09..abb85b60 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/dynamic.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/dynamic.jsp @@ -2,17 +2,17 @@ <%@ taglib uri="http://www.victorzhang.com/tags" prefix="taglib" %> - - Insert title here - + + Insert title here + - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/function.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/function.jsp index 8e42df47..67b405b6 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/function.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/function.jsp @@ -2,45 +2,45 @@ <%@ taglib uri="http://www.victorzhang.com/function" prefix="fn" %> - - Insert title here - + .l { + font-weight: bold; + } + <% - request.setAttribute("string", "字符串测试"); + request.setAttribute("string", "字符串测试"); %> - - - - - - - - - - - - + + + + + + + + + + + +
    字符串变量${ string }
    字符串长度(按字节计)${ fn:length(string) }
    截取 7 个字节${ fn:substring(string, 7) }
    字符串变量${ string }
    字符串长度(按字节计)${ fn:length(string) }
    截取 7 个字节${ fn:substring(string, 7) }
    - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/hello.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/hello.jsp index fc34f1f2..2aa13cd9 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/hello.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/hello.jsp @@ -2,8 +2,8 @@ <%@ taglib uri="http://www.victorzhang.com/tags" prefix="taglib" %> - - Insert title here + + Insert title here @@ -12,4 +12,4 @@
    - \ No newline at end of file + diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/index.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/index.jsp index 92b12378..b3346ace 100644 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/index.jsp +++ b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/index.jsp @@ -2,21 +2,21 @@ <%@ taglib uri="http://www.victorzhang.com/tags" prefix="taglib" %> - - Insert title here - - + + Insert title here + + - + - - - - - - - - - + + + + + + + + + + - - - - - - - - - + + + + + + + + + +
    diff --git a/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/webapp/websocketB.html b/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/webapp/websocketB.html index c53f0362..b9b7b081 100644 --- a/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/webapp/websocketB.html +++ b/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/webapp/websocketB.html @@ -1,56 +1,56 @@ - - webscocket test + + webscocket test - +
    diff --git a/codes/javaee-tutorial/javaee-tutorial-websocket/src/test/java/io/github/dunwu/javaee/server/DevServer.java b/codes/javaee-tutorial/javaee-tutorial-websocket/src/test/java/io/github/dunwu/javaee/server/DevServer.java index 2cb683f0..72849425 100644 --- a/codes/javaee-tutorial/javaee-tutorial-websocket/src/test/java/io/github/dunwu/javaee/server/DevServer.java +++ b/codes/javaee-tutorial/javaee-tutorial-websocket/src/test/java/io/github/dunwu/javaee/server/DevServer.java @@ -2,6 +2,7 @@ import com.google.common.collect.Lists; import io.github.dunwu.javaee.websocket.WebSocketServer; +import java.util.ArrayList; import org.apache.commons.lang3.StringUtils; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.webapp.WebAppClassLoader; @@ -9,11 +10,8 @@ import org.eclipse.jetty.websocket.jsr356.server.ServerContainer; import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer; -import java.util.ArrayList; - /** - * DevServer 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij - * 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 + * DevServer 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 * * @author Zhang Peng */ @@ -21,64 +19,28 @@ public class DevServer { public static final int IDE_ECLIPSE = 0; + public static final int IDE_INTELLIJ = 1; + public static final String PRODUCTION = "production"; + public static final String DEVELOPMENT = "development"; + public static final String UNIT_TEST = "test"; + public static final String FUNCTIONAL_TEST = "functional"; + private static final int PORT = 8089; + private static final String CONTEXT = "/"; - private static final String RESOURCE_BASE_PATH = "src/main/webapp"; - private static final String WEB_XML_PATH = "/WEB-INF/web.xml"; - private static final String[] TLD_JAR_NAMES = new String[] { "sitemesh", "spring-webmvc", "shiro-web", "tiles" }; - private static final String WINDOWS_WEBDEFAULT_PATH = "jetty/webdefault.xml"; - private static void reloadWebAppContext(Server server) throws Exception { - WebAppContext webAppContext = (WebAppContext) server.getHandler(); - System.out.println("[INFO] Application reloading"); - webAppContext.stop(); - WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext); - classLoader.addClassPath(getAbsolutePath() + "target/classes"); - classLoader.addClassPath(getAbsolutePath() + "target/test-classes"); - webAppContext.setClassLoader(classLoader); - webAppContext.start(); - System.out.println("[INFO] Application reloaded"); - } + private static final String RESOURCE_BASE_PATH = "src/main/webapp"; - private static String getAbsolutePath() { - String path = null; - String folderPath = DevServer.class.getProtectionDomain().getCodeSource().getLocation().getPath().substring(1); - if (folderPath.indexOf("target") > 0) { - path = folderPath.substring(0, folderPath.indexOf("target")); - } - return path; - } + private static final String WEB_XML_PATH = "/WEB-INF/web.xml"; - private static void supportJspAndSetTldJarNames(Server server, String... jarNames) { - WebAppContext context = (WebAppContext) server.getHandler(); - // This webapp will use jsps and jstl. We need to enable the - // AnnotationConfiguration in - // order to correctly set up the jsp container - org.eclipse.jetty.webapp.Configuration.ClassList classlist = org.eclipse.jetty.webapp.Configuration.ClassList - .setServerDefault(server); - classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", - "org.eclipse.jetty.annotations.AnnotationConfiguration"); - // Set the ContainerIncludeJarPattern so that jetty examines these container-path - // jars for - // tlds, web-fragments etc. - // If you omit the jar that contains the jstl .tlds, the jsp engine will scan for - // them - // instead. - ArrayList jarNameExprssions = Lists.newArrayList(".*/[^/]*servlet-api-[^/]*\\.jar$", - ".*/javax.servlet.jsp.jstl-.*\\.jar$", ".*/[^/]*taglibs.*\\.jar$"); + private static final String[] TLD_JAR_NAMES = new String[] {"sitemesh", "spring-webmvc", "shiro-web", "tiles"} - for (String jarName : jarNames) { - jarNameExprssions.add(".*/" + jarName + "-[^/]*\\.jar$"); - } - - context.setAttribute("org.eclipse.jetty.io.github.dunwu.javaee.server.webapp.ContainerIncludeJarPattern", - StringUtils.join(jarNameExprssions, '|')); - } + private static final String WINDOWS_WEBDEFAULT_PATH = "jetty/webdefault.xml"; public static void main(String[] args) throws Exception { @@ -118,11 +80,57 @@ public static void main(String[] args) throws Exception { DevServer.reloadWebAppContext(server); } } - } - catch (Exception e) { + } catch (Exception e) { e.printStackTrace(); System.exit(-1); } } + private static String getAbsolutePath() { + String path = null; + String folderPath = DevServer.class.getProtectionDomain().getCodeSource().getLocation().getPath().substring(1); + if (folderPath.indexOf("target") > 0) { + path = folderPath.substring(0, folderPath.indexOf("target")); + } + return path; + } + + private static void supportJspAndSetTldJarNames(Server server, String... jarNames) { + WebAppContext context = (WebAppContext) server.getHandler(); + // This webapp will use jsps and jstl. We need to enable the + // AnnotationConfiguration in + // order to correctly set up the jsp container + org.eclipse.jetty.webapp.Configuration.ClassList classlist = org.eclipse.jetty.webapp.Configuration.ClassList + .setServerDefault(server); + classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", + "org.eclipse.jetty.annotations.AnnotationConfiguration"); + // Set the ContainerIncludeJarPattern so that jetty examines these container-path + // jars for + // tlds, web-fragments etc. + // If you omit the jar that contains the jstl .tlds, the jsp engine will scan for + // them + // instead. + ArrayList jarNameExprssions = Lists.newArrayList(".*/[^/]*servlet-api-[^/]*\\.jar$", + ".*/javax.servlet.jsp.jstl-.*\\.jar$", ".*/[^/]*taglibs.*\\.jar$"); + + for (String jarName : jarNames) { + jarNameExprssions.add(".*/" + jarName + "-[^/]*\\.jar$"); + } + + context.setAttribute("org.eclipse.jetty.io.github.dunwu.javaee.server.webapp.ContainerIncludeJarPattern", + StringUtils.join(jarNameExprssions, '|')); + } + + private static void reloadWebAppContext(Server server) throws Exception { + WebAppContext webAppContext = (WebAppContext) server.getHandler(); + System.out.println("[INFO] Application reloading"); + webAppContext.stop(); + WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext); + classLoader.addClassPath(getAbsolutePath() + "target/classes"); + classLoader.addClassPath(getAbsolutePath() + "target/test-classes"); + webAppContext.setClassLoader(classLoader); + webAppContext.start(); + System.out.println("[INFO] Application reloaded"); + } + } diff --git a/codes/javaee-tutorial/javaee-tutorial-websocket/src/test/resources/jetty/webdefault.xml b/codes/javaee-tutorial/javaee-tutorial-websocket/src/test/resources/jetty/webdefault.xml index 65ec9cae..b991d44c 100644 --- a/codes/javaee-tutorial/javaee-tutorial-websocket/src/test/resources/jetty/webdefault.xml +++ b/codes/javaee-tutorial/javaee-tutorial-websocket/src/test/resources/jetty/webdefault.xml @@ -1,76 +1,76 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://xmlns.jcp.org/xml/ns/javaee" + xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" + metadata-complete="false" + version="3.1"> - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - Default web.xml file. - This file is applied to a Web application before it's own WEB_INF/web.xml file - + + Default web.xml file. + This file is applied to a Web application before it's own WEB_INF/web.xml file + - - - - - - org.eclipse.jetty.servlet.listener.ELContextCleaner - + + + + + + org.eclipse.jetty.servlet.listener.ELContextCleaner + - - - - - - org.eclipse.jetty.servlet.listener.IntrospectorCleaner - + + + + + + org.eclipse.jetty.servlet.listener.IntrospectorCleaner + - - - - + + + + - - - - - - + + + + + - - - default - org.eclipse.jetty.servlet.DefaultServlet - - aliases - false - - - acceptRanges - true - - - dirAllowed - true - - - welcomeServlets - false - - - redirectWelcome - false - - - maxCacheSize - 256000000 - - - maxCachedFileSize - 200000000 - - - maxCachedFiles - 2048 - - - gzip - false - - - etags - false - - - useFileMappedBuffer - false - - - - 0 - + + + default + org.eclipse.jetty.servlet.DefaultServlet + + aliases + false + + + acceptRanges + true + + + dirAllowed + true + + + welcomeServlets + false + + + redirectWelcome + false + + + maxCacheSize + 256000000 + + + maxCachedFileSize + 200000000 + + + maxCachedFiles + 2048 + + + gzip + false + + + etags + false + + + useFileMappedBuffer + false + + + + 0 + - - default - / - + + default + / + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - jsp - org.eclipse.jetty.jsp.JettyJspServlet - - logVerbosityLevel - DEBUG - - - fork - false - - - xpoweredBy - false - - - compilerTargetVM - 1.7 - - - compilerSourceVM - 1.7 - - - 0 - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + jsp + org.eclipse.jetty.jsp.JettyJspServlet + + logVerbosityLevel + DEBUG + + + fork + false + + + xpoweredBy + false + + + compilerTargetVM + 1.7 + + + compilerSourceVM + 1.7 + + + 0 + - - jsp - *.jsp - *.jspf - *.jspx - *.xsp - *.JSP - *.JSPF - *.JSPX - *.XSP - + + jsp + *.jsp + *.jspf + *.jspx + *.xsp + *.JSP + *.JSPF + *.JSPX + *.XSP + - - - - - 30 - + + + + + 30 + - - - - - - - + + + + + + + - - - - - index.html - index.htm - index.jsp - + + + + + index.html + index.htm + index.jsp + - - - - - - ar - ISO-8859-6 - - - be - ISO-8859-5 - - - bg - ISO-8859-5 - - - ca - ISO-8859-1 - - - cs - ISO-8859-2 - - - da - ISO-8859-1 - - - de - ISO-8859-1 - - - el - ISO-8859-7 - - - en - ISO-8859-1 - - - es - ISO-8859-1 - - - et - ISO-8859-1 - - - fi - ISO-8859-1 - - - fr - ISO-8859-1 - - - hr - ISO-8859-2 - - - hu - ISO-8859-2 - - - is - ISO-8859-1 - - - it - ISO-8859-1 - - - iw - ISO-8859-8 - - - ja - Shift_JIS - - - ko - EUC-KR - - - lt - ISO-8859-2 - - - lv - ISO-8859-2 - - - mk - ISO-8859-5 - - - nl - ISO-8859-1 - - - no - ISO-8859-1 - - - pl - ISO-8859-2 - - - pt - ISO-8859-1 - - - ro - ISO-8859-2 - - - ru - ISO-8859-5 - - - sh - ISO-8859-5 - - - sk - ISO-8859-2 - - - sl - ISO-8859-2 - - - sq - ISO-8859-2 - - - sr - ISO-8859-5 - - - sv - ISO-8859-1 - - - tr - ISO-8859-9 - - - uk - ISO-8859-5 - - - zh - GB2312 - - - zh_TW - Big5 - - + + + + + + ar + ISO-8859-6 + + + be + ISO-8859-5 + + + bg + ISO-8859-5 + + + ca + ISO-8859-1 + + + cs + ISO-8859-2 + + + da + ISO-8859-1 + + + de + ISO-8859-1 + + + el + ISO-8859-7 + + + en + ISO-8859-1 + + + es + ISO-8859-1 + + + et + ISO-8859-1 + + + fi + ISO-8859-1 + + + fr + ISO-8859-1 + + + hr + ISO-8859-2 + + + hu + ISO-8859-2 + + + is + ISO-8859-1 + + + it + ISO-8859-1 + + + iw + ISO-8859-8 + + + ja + Shift_JIS + + + ko + EUC-KR + + + lt + ISO-8859-2 + + + lv + ISO-8859-2 + + + mk + ISO-8859-5 + + + nl + ISO-8859-1 + + + no + ISO-8859-1 + + + pl + ISO-8859-2 + + + pt + ISO-8859-1 + + + ro + ISO-8859-2 + + + ru + ISO-8859-5 + + + sh + ISO-8859-5 + + + sk + ISO-8859-2 + + + sl + ISO-8859-2 + + + sq + ISO-8859-2 + + + sr + ISO-8859-5 + + + sv + ISO-8859-1 + + + tr + ISO-8859-9 + + + uk + ISO-8859-5 + + + zh + GB2312 + + + zh_TW + Big5 + + - - - - - - Disable TRACE - / - TRACE - - - - - - Enable everything but TRACE - / - TRACE - - + + + + + + Disable TRACE + / + TRACE + + + + + + Enable everything but TRACE + / + TRACE + + diff --git a/codes/javaee-tutorial/javaee-tutorial-websocket/src/test/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-websocket/src/test/resources/logback.xml index d3067f1e..220d0752 100644 --- a/codes/javaee-tutorial/javaee-tutorial-websocket/src/test/resources/logback.xml +++ b/codes/javaee-tutorial/javaee-tutorial-websocket/src/test/resources/logback.xml @@ -3,41 +3,41 @@ - + - - - - %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n - - - - - - ${user.dir}/logs/${DIR_NAME}/test.%d{yyyy-MM-dd}.log - 30 - + + + + %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n + + + + + + ${user.dir}/logs/${DIR_NAME}/test.%d{yyyy-MM-dd}.log + 30 + - - - 30MB - + + + 30MB + - - %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n - - - + + %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n + + + - - - - - - - - - - + + + + + + + + + + diff --git a/codes/javaee-tutorial/pom.xml b/codes/javaee-tutorial/pom.xml index 85fbb1e8..f58b67dc 100644 --- a/codes/javaee-tutorial/pom.xml +++ b/codes/javaee-tutorial/pom.xml @@ -1,121 +1,121 @@ - 4.0.0 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 - io.github.dunwu - javaee-tutorial - 1.0.0 - pom + io.github.dunwu + javaee-tutorial + 1.0.0 + pom - - javaee-tutorial-servlet - javaee-tutorial-jsp - javaee-tutorial-session - javaee-tutorial-filter - javaee-tutorial-listener - javaee-tutorial-jstl - javaee-tutorial-taglib - javaee-tutorial-oss - javaee-tutorial-websocket - + + javaee-tutorial-servlet + javaee-tutorial-jsp + javaee-tutorial-session + javaee-tutorial-filter + javaee-tutorial-listener + javaee-tutorial-jstl + javaee-tutorial-taglib + javaee-tutorial-oss + javaee-tutorial-websocket + - - UTF-8 - 1.6 - ${java.version} - ${java.version} + + UTF-8 + 1.6 + ${java.version} + ${java.version} - - 9.4.12.v20180830 - 4.1.4.RELEASE - 1.4.0 - 4.12 + + 9.4.12.v20180830 + 4.1.4.RELEASE + 1.4.0 + 4.12 - 2.9.5 - + 2.9.5 + - - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.12 - - - org.apache.tomcat.maven - tomcat7-maven-plugin - 2.2 - - - org.eclipse.jetty - jetty-maven-plugin - ${jetty.version} - - - org.codehaus.mojo - aspectj-maven-plugin - - 1.2 - - - - org.aspectj - aspectjrt - 1.8.1 - - - org.aspectj - aspectjtools - 1.8.1 - - - - - - compile - test-compile - - - - - true - ${java.version} - ${java.version} - - - - - org.mybatis.generator - mybatis-generator-maven-plugin - 1.3.5 - - - org.mybatis.generator - mybatis-generator-core - 1.3.5 - - - - - - + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.12 + + + org.apache.tomcat.maven + tomcat7-maven-plugin + 2.2 + + + org.eclipse.jetty + jetty-maven-plugin + ${jetty.version} + + + org.codehaus.mojo + aspectj-maven-plugin + + 1.2 + + + + org.aspectj + aspectjrt + 1.8.1 + + + org.aspectj + aspectjtools + 1.8.1 + + + + + + compile + test-compile + + + + + true + ${java.version} + ${java.version} + + + + + org.mybatis.generator + mybatis-generator-maven-plugin + 1.3.5 + + + org.mybatis.generator + mybatis-generator-core + 1.3.5 + + + + + + - 2016-2019 - - - Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0.txt - repo - A business-friendly OSS license - - - - - Zhang Peng - forbreak@163.com - +8 - - + 2016-2019 + + + Apache License, Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + repo + A business-friendly OSS license + + + + + Zhang Peng + forbreak@163.com + +8 + + diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/pom.xml b/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/pom.xml index cc678c3f..ac5eebd6 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/pom.xml +++ b/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/pom.xml @@ -1,57 +1,57 @@ - 4.0.0 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 2.1.9.RELEASE - + + org.springframework.boot + spring-boot-starter-parent + 2.1.9.RELEASE + - io.github.dunwu.javaweb - javaweb-mq-kafka-spring-boot - 1.0.0 - jar + io.github.dunwu.javaweb + javaweb-mq-kafka-spring-boot + 1.0.0 + jar - - UTF-8 - UTF-8 - 1.8 - + + UTF-8 + UTF-8 + 1.8 + - - - org.springframework.boot - spring-boot-starter - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-test - test - + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + - - org.springframework.kafka - spring-kafka - - - org.springframework.kafka - spring-kafka-test - test - - + + org.springframework.kafka + spring-kafka + + + org.springframework.kafka + spring-kafka-test + test + + - - - - org.springframework.boot - spring-boot-maven-plugin - - - + + + + org.springframework.boot + spring-boot-maven-plugin + + + diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaConsumer.java b/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaConsumer.java index bac02124..16bceae6 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaConsumer.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaConsumer.java @@ -9,7 +9,7 @@ * Kafka 消费者 * * @author Zhang Peng - * @date 2018-11-28 + * @since 2018-11-28 */ @Component public class KafkaConsumer { diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaProducer.java b/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaProducer.java index 1d833aa4..14cb19b4 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaProducer.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaProducer.java @@ -11,7 +11,7 @@ * Kafka生产者 * * @author Zhang Peng - * @date 2018-11-29 + * @since 2018-11-29 */ @Component public class KafkaProducer { diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/config/KafkaConsumerConfig.java b/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/config/KafkaConsumerConfig.java index 8bde8d31..f7dbca23 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/config/KafkaConsumerConfig.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/config/KafkaConsumerConfig.java @@ -1,5 +1,7 @@ package io.github.dunwu.javaweb.config; +import java.util.HashMap; +import java.util.Map; import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.common.serialization.StringDeserializer; import org.springframework.beans.factory.annotation.Value; @@ -12,9 +14,6 @@ import org.springframework.kafka.core.DefaultKafkaConsumerFactory; import org.springframework.kafka.listener.ConcurrentMessageListenerContainer; -import java.util.HashMap; -import java.util.Map; - @Configuration @EnableKafka public class KafkaConsumerConfig { @@ -22,6 +21,20 @@ public class KafkaConsumerConfig { @Value("${spring.kafka.bootstrap-servers}") private String bootstrapServers; + @Bean(name = "kafkaListenerContainerFactory") + public KafkaListenerContainerFactory> kafkaListenerContainerFactory() { + ConcurrentKafkaListenerContainerFactory factory = + new ConcurrentKafkaListenerContainerFactory<>(); + factory.setConsumerFactory(consumerFactory("groupA")); + factory.setConcurrency(3); + factory.getContainerProperties().setPollTimeout(3000); + return factory; + } + + public ConsumerFactory consumerFactory(String consumerGroupId) { + return new DefaultKafkaConsumerFactory<>(consumerConfig(consumerGroupId)); + } + public Map consumerConfig(String consumerGroupId) { Map props = new HashMap<>(); props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); @@ -35,22 +48,10 @@ public Map consumerConfig(String consumerGroupId) { return props; } - public ConsumerFactory consumerFactory(String consumerGroupId) { - return new DefaultKafkaConsumerFactory<>(consumerConfig(consumerGroupId)); - } - - @Bean(name = "kafkaListenerContainerFactory") - public KafkaListenerContainerFactory> kafkaListenerContainerFactory() { - ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); - factory.setConsumerFactory(consumerFactory("groupA")); - factory.setConcurrency(3); - factory.getContainerProperties().setPollTimeout(3000); - return factory; - } - @Bean(name = "kafkaListenerContainerFactory1") public KafkaListenerContainerFactory> kafkaListenerContainerFactory1() { - ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); + ConcurrentKafkaListenerContainerFactory factory = + new ConcurrentKafkaListenerContainerFactory<>(); factory.setConsumerFactory(consumerFactory("groupB")); factory.setConcurrency(3); factory.getContainerProperties().setPollTimeout(3000); diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/config/KafkaProducerConfig.java b/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/config/KafkaProducerConfig.java index c18a0800..b8e1d2c4 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/config/KafkaProducerConfig.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/config/KafkaProducerConfig.java @@ -1,5 +1,7 @@ package io.github.dunwu.javaweb.config; +import java.util.HashMap; +import java.util.Map; import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.common.serialization.StringSerializer; import org.springframework.beans.factory.annotation.Value; @@ -11,9 +13,6 @@ import org.springframework.kafka.core.ProducerFactory; import org.springframework.kafka.transaction.KafkaTransactionManager; -import java.util.HashMap; -import java.util.Map; - @Configuration @EnableKafka public class KafkaProducerConfig { @@ -28,29 +27,29 @@ public class KafkaProducerConfig { private Integer batchSize; @Bean - public Map producerConfigs() { - Map props = new HashMap<>(7); - props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); - props.put(ProducerConfig.RETRIES_CONFIG, retries); - props.put(ProducerConfig.BATCH_SIZE_CONFIG, batchSize); - props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); - props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class); - return props; + public KafkaTransactionManager transactionManager() { + KafkaTransactionManager manager = new KafkaTransactionManager(producerFactory()); + return manager; } @Bean public ProducerFactory producerFactory() { DefaultKafkaProducerFactory producerFactory = new DefaultKafkaProducerFactory<>( - producerConfigs()); + producerConfigs()); producerFactory.transactionCapable(); producerFactory.setTransactionIdPrefix("hous-"); return producerFactory; } @Bean - public KafkaTransactionManager transactionManager() { - KafkaTransactionManager manager = new KafkaTransactionManager(producerFactory()); - return manager; + public Map producerConfigs() { + Map props = new HashMap<>(7); + props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); + props.put(ProducerConfig.RETRIES_CONFIG, retries); + props.put(ProducerConfig.BATCH_SIZE_CONFIG, batchSize); + props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + return props; } @Bean diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/resources/application.properties b/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/resources/application.properties index 3f664c6b..e25cabf2 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/resources/application.properties +++ b/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/resources/application.properties @@ -1,15 +1,13 @@ -spring.kafka.bootstrap-servers = tdh60dev01:9092,tdh60dev02:9092,tdh60dev03:9092 -spring.kafka.producer.retries = 3 -spring.kafka.producer.transaction-id-prefix = javaweb-kafka - +spring.kafka.bootstrap-servers=tdh60dev01:9092,tdh60dev02:9092,tdh60dev03:9092 +spring.kafka.producer.retries=3 +spring.kafka.producer.transaction-id-prefix=javaweb-kafka # producer -spring.kafka.producer.batch-size = 1000 -spring.kafka.producer.key-serializer = org.apache.kafka.common.serialization.StringSerializer -spring.kafka.producer.value-serializer = org.apache.kafka.common.serialization.StringSerializer - +spring.kafka.producer.batch-size=1000 +spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer +spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer # consumer -spring.kafka.consumer.group-id = javaweb -spring.kafka.consumer.enable-auto-commit = true -spring.kafka.consumer.auto-commit-interval = 1000 -spring.kafka.consumer.key-deserializer = org.apache.kafka.common.serialization.StringDeserializer -spring.kafka.consumer.value-deserializer = org.apache.kafka.common.serialization.StringDeserializer +spring.kafka.consumer.group-id=javaweb +spring.kafka.consumer.enable-auto-commit=true +spring.kafka.consumer.auto-commit-interval=1000 +spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer +spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/resources/logback.xml b/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/resources/logback.xml index 0e9e41b7..1946a29c 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/resources/logback.xml +++ b/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/resources/logback.xml @@ -1,15 +1,15 @@ - - - %d{HH:mm:ss.SSS} [%boldYellow(%thread)] [%highlight(%-5level)] %boldGreen(%c{36}.%M) - %boldBlue(%m%n) - - - + + + %d{HH:mm:ss.SSS} [%boldYellow(%thread)] [%highlight(%-5level)] %boldGreen(%c{36}.%M) - %boldBlue(%m%n) + + + - + - - - + + + diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/test/java/io/github/dunwu/javaweb/KafkaProducerTest.java b/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/test/java/io/github/dunwu/javaweb/KafkaProducerTest.java index 628b6430..ae4edf1a 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/test/java/io/github/dunwu/javaweb/KafkaProducerTest.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/test/java/io/github/dunwu/javaweb/KafkaProducerTest.java @@ -8,7 +8,7 @@ /** * @author Zhang Peng - * @date 2018-11-29 + * @since 2018-11-29 */ @RunWith(SpringRunner.class) @SpringBootTest(classes = MsgKafkaApplication.class) diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/pom.xml b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/pom.xml index aa178eb4..6c95de36 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/pom.xml +++ b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/pom.xml @@ -1,39 +1,36 @@ - 4.0.0 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" + xmlns="http://maven.apache.org/POM/4.0.0"> + 4.0.0 - io.github.dunwu.javaweb - javaweb-mq-kafka - 1.0.0 - jar + io.github.dunwu.javaweb + javaweb-mq-kafka + 1.0.0 + jar - - UTF-8 - 1.8 - ${java.version} - ${java.version} - + + UTF-8 + 1.8 + ${java.version} + ${java.version} + - - - org.apache.kafka - kafka-clients - 1.1.0 - - - org.apache.kafka - kafka-streams - 1.1.0 - - - ch.qos.logback - logback-classic - 1.1.2 - - - - - ${project.artifactId} - + + + org.apache.kafka + kafka-clients + 1.1.0 + + + org.apache.kafka + kafka-streams + 1.1.0 + + + ch.qos.logback + logback-classic + 1.1.2 + + diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerAOC.java b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerAOC.java index 3eda24c6..cc8268fa 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerAOC.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerAOC.java @@ -1,13 +1,12 @@ package io.github.dunwu.javaweb.kafka; +import java.util.Arrays; +import java.util.Properties; import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecords; import org.apache.kafka.clients.consumer.KafkaConsumer; -import java.util.Arrays; -import java.util.Properties; - /** * Kafka 消费者消费消息示例 消费者配置参考:https://kafka.apache.org/documentation/#consumerconfigs */ @@ -23,9 +22,9 @@ public static void main(String[] args) { props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true"); props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000"); props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringDeserializer"); + "org.apache.kafka.common.serialization.StringDeserializer"); props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringDeserializer"); + "org.apache.kafka.common.serialization.StringDeserializer"); // 2. 使用配置初始化 Kafka 消费者 KafkaConsumer consumer = new KafkaConsumer<>(props); diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManual.java b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManual.java index fe1361ad..02060366 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManual.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManual.java @@ -1,18 +1,17 @@ package io.github.dunwu.javaweb.kafka; -import org.apache.kafka.clients.consumer.ConsumerConfig; -import org.apache.kafka.clients.consumer.ConsumerRecord; -import org.apache.kafka.clients.consumer.ConsumerRecords; -import org.apache.kafka.clients.consumer.KafkaConsumer; - import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Properties; +import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.clients.consumer.ConsumerRecords; +import org.apache.kafka.clients.consumer.KafkaConsumer; /** * @author Zhang Peng - * @date 2018/7/12 + * @since 2018/7/12 */ public class ConsumerManual { @@ -24,9 +23,9 @@ public static void main(String[] args) { props.put(ConsumerConfig.GROUP_ID_CONFIG, "test"); props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false"); props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringDeserializer"); + "org.apache.kafka.common.serialization.StringDeserializer"); props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringDeserializer"); + "org.apache.kafka.common.serialization.StringDeserializer"); KafkaConsumer consumer = new KafkaConsumer<>(props); consumer.subscribe(Arrays.asList("t1", "t2")); diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManualPartition.java b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManualPartition.java index d0014c41..66a0980c 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManualPartition.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManualPartition.java @@ -1,16 +1,15 @@ package io.github.dunwu.javaweb.kafka; -import org.apache.kafka.clients.consumer.*; -import org.apache.kafka.common.TopicPartition; - import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Properties; +import org.apache.kafka.clients.consumer.*; +import org.apache.kafka.common.TopicPartition; /** * @author Zhang Peng - * @date 2018/7/12 + * @since 2018/7/12 */ public class ConsumerManualPartition { @@ -22,9 +21,9 @@ public static void main(String[] args) { props.put(ConsumerConfig.GROUP_ID_CONFIG, "test2"); props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false"); props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringDeserializer"); + "org.apache.kafka.common.serialization.StringDeserializer"); props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringDeserializer"); + "org.apache.kafka.common.serialization.StringDeserializer"); KafkaConsumer consumer = new KafkaConsumer<>(props); consumer.subscribe(Arrays.asList("t1")); @@ -41,8 +40,7 @@ public static void main(String[] args) { consumer.commitSync(Collections.singletonMap(partition, new OffsetAndMetadata(lastOffset + 1))); } } - } - finally { + } finally { consumer.close(); } } diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerDemo.java b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerDemo.java index d5639cef..b816055a 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerDemo.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerDemo.java @@ -1,12 +1,11 @@ package io.github.dunwu.javaweb.kafka; +import java.util.Properties; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.Producer; import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.clients.producer.ProducerRecord; -import java.util.Properties; - /** * Kafka 生产者生产消息示例 生产者配置参考:https://kafka.apache.org/documentation/#producerconfigs */ @@ -24,9 +23,9 @@ public static void main(String[] args) { properties.put(ProducerConfig.LINGER_MS_CONFIG, 1); properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432); properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringSerializer"); + "org.apache.kafka.common.serialization.StringSerializer"); properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringSerializer"); + "org.apache.kafka.common.serialization.StringSerializer"); // 2. 使用配置初始化 Kafka 生产者 Producer producer = new KafkaProducer<>(properties); @@ -38,11 +37,9 @@ public static void main(String[] args) { producer.send(new ProducerRecord<>("HelloWorld", msg)); System.out.println("Sent:" + msg); } - } - catch (Exception e) { + } catch (Exception e) { e.printStackTrace(); - } - finally { + } finally { // 4. 关闭生产者 producer.close(); } diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerInTransaction.java b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerInTransaction.java index 2f69e842..8e3af131 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerInTransaction.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerInTransaction.java @@ -1,5 +1,9 @@ package io.github.dunwu.javaweb.kafka; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; import org.apache.kafka.clients.consumer.*; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.Producer; @@ -7,11 +11,6 @@ import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.common.TopicPartition; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; - /** * @author Zhang Peng */ @@ -19,41 +18,9 @@ public class ProducerInTransaction { private static final String HOST = "192.168.28.32:9092"; - public static Producer buildProducer() { - // 1. 指定生产者的配置 - Properties properties = new Properties(); - properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, HOST); - properties.put(ProducerConfig.ACKS_CONFIG, "all"); - properties.put(ProducerConfig.RETRIES_CONFIG, 1); - properties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384); - properties.put(ProducerConfig.LINGER_MS_CONFIG, 1); - properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432); - properties.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG, "first-transactional"); - properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringSerializer"); - properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringSerializer"); - - // 2. 使用配置初始化 Kafka 生产者 - Producer producer = new KafkaProducer<>(properties); - return producer; - } - - public static Consumer buildConsumer() { - // 1. 指定消费者的配置 - final Properties props = new Properties(); - props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, HOST); - props.put(ConsumerConfig.GROUP_ID_CONFIG, "test"); - props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true"); - props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000"); - props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringDeserializer"); - props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringDeserializer"); - - // 2. 使用配置初始化 Kafka 消费者 - Consumer consumer = new KafkaConsumer<>(props); - return consumer; + public static void main(String[] args) { + onlyProduceInTransaction(); + consumeTransferProduce(); } /** @@ -80,12 +47,10 @@ public static void onlyProduceInTransaction() { // 4.事务提交 producer.commitTransaction(); - } - catch (Exception e) { + } catch (Exception e) { // 5.放弃事务 producer.abortTransaction(); } - } /** @@ -115,11 +80,11 @@ public static void consumeTransferProduce() { // 5.2.1 读取消息,并处理消息。print the offset,key and value for the consumer // records. System.out.printf("offset = %d, key = %s, value = %s\n", record.offset(), record.key(), - record.value()); + record.value()); // 5.2.2 记录提交的偏移量 commits.put(new TopicPartition(record.topic(), record.partition()), - new OffsetAndMetadata(record.offset())); + new OffsetAndMetadata(record.offset())); // 6.生产新的消息。比如外卖订单状态的消息,如果订单成功,则需要发送跟商家结转消息或者派送员的提成消息 producer.send(new ProducerRecord("test", "data2")); @@ -130,18 +95,48 @@ public static void consumeTransferProduce() { // 8.事务提交 producer.commitTransaction(); - - } - catch (Exception e) { + } catch (Exception e) { // 7.放弃事务 producer.abortTransaction(); } } } - public static void main(String[] args) { - onlyProduceInTransaction(); - consumeTransferProduce(); + public static Producer buildProducer() { + // 1. 指定生产者的配置 + Properties properties = new Properties(); + properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, HOST); + properties.put(ProducerConfig.ACKS_CONFIG, "all"); + properties.put(ProducerConfig.RETRIES_CONFIG, 1); + properties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384); + properties.put(ProducerConfig.LINGER_MS_CONFIG, 1); + properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432); + properties.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG, "first-transactional"); + properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.StringSerializer"); + properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.StringSerializer"); + + // 2. 使用配置初始化 Kafka 生产者 + Producer producer = new KafkaProducer<>(properties); + return producer; + } + + public static Consumer buildConsumer() { + // 1. 指定消费者的配置 + final Properties props = new Properties(); + props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, HOST); + props.put(ConsumerConfig.GROUP_ID_CONFIG, "test"); + props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true"); + props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000"); + props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.StringDeserializer"); + props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.StringDeserializer"); + + // 2. 使用配置初始化 Kafka 消费者 + Consumer consumer = new KafkaConsumer<>(props); + return consumer; } } diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/StreamDemo.java b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/StreamDemo.java index 202b12ca..9efe0918 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/StreamDemo.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/StreamDemo.java @@ -1,5 +1,7 @@ package io.github.dunwu.javaweb.kafka; +import java.util.Arrays; +import java.util.Properties; import org.apache.kafka.common.serialization.Serdes; import org.apache.kafka.common.utils.Bytes; import org.apache.kafka.streams.KafkaStreams; @@ -11,9 +13,6 @@ import org.apache.kafka.streams.kstream.Produced; import org.apache.kafka.streams.state.KeyValueStore; -import java.util.Arrays; -import java.util.Properties; - /** * Kafka 流示例 消费者配置参考:https://kafka.apache.org/documentation/#streamsconfigs */ @@ -33,9 +32,9 @@ public static void main(String[] args) { StreamsBuilder builder = new StreamsBuilder(); KStream textLines = builder.stream("TextLinesTopic"); KTable wordCounts = textLines - .flatMapValues(textLine -> Arrays.asList(textLine.toLowerCase().split("\\W+"))) - .groupBy((key, word) -> word) - .count(Materialized.>as("counts-store")); + .flatMapValues(textLine -> Arrays.asList(textLine.toLowerCase().split("\\W+"))) + .groupBy((key, word) -> word) + .count(Materialized.>as("counts-store")); wordCounts.toStream().to("WordsWithCountsTopic", Produced.with(Serdes.String(), Serdes.Long())); // 根据流构造器和流配置初始化 Kafka 流 diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/resources/logback.xml b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/resources/logback.xml index 2fc0985e..44ab53b1 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/resources/logback.xml +++ b/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/resources/logback.xml @@ -3,48 +3,48 @@ - - - - - - 10 - 100 - - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [javalib] [%thread] [%p] %c{36}#%M - %m%n - - - - - - ${LOG_PATH}/logs/${FILE_NAME}-error.%d{yyyy-MM-dd}.log - 30 - - - - - 10MB - - - - ERROR - ACCEPT - DENY - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [javalib] [%thread] [%p] %c{36}#%M - %m%n - - - - - - - - - + + + + + + 10 + 100 + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [javalib] [%thread] [%p] %c{36}#%M - %m%n + + + + + + ${LOG_PATH}/logs/${FILE_NAME}-error.%d{yyyy-MM-dd}.log + 30 + + + + + 10MB + + + + ERROR + ACCEPT + DENY + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [javalib] [%thread] [%p] %c{36}#%M - %m%n + + + + + + + + + diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/pom.xml b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/pom.xml index 9952733a..e7493e6f 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/pom.xml +++ b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/pom.xml @@ -1,35 +1,32 @@ - 4.0.0 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" + xmlns="http://maven.apache.org/POM/4.0.0"> + 4.0.0 - io.github.dunwu.javaweb - javaweb-mq-rocketmq - 1.0.0 - jar + io.github.dunwu.javaweb + javaweb-mq-rocketmq + 1.0.0 + jar - - UTF-8 - 1.8 - ${java.version} - ${java.version} - + + UTF-8 + 1.8 + ${java.version} + ${java.version} + - - - org.apache.rocketmq - rocketmq-client - 4.2.0 - - - ch.qos.logback - logback-classic - 1.1.2 - - - - - ${project.artifactId} - + + + org.apache.rocketmq + rocketmq-client + 4.2.0 + + + ch.qos.logback + logback-classic + 1.1.2 + + diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/AsyncProducer.java b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/AsyncProducer.java index 1a71cc32..2e42c4b1 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/AsyncProducer.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/AsyncProducer.java @@ -24,7 +24,7 @@ public static void main(String[] args) throws Exception { final int index = i; // Create a message instance, specifying topic, tag and message body. Message msg = new Message("TopicTest", "TagA", "OrderID188", - "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET)); + "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET)); producer.send(msg, new SendCallback() { @Override public void onSuccess(SendResult sendResult) { diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BatchProducer.java b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BatchProducer.java index 7f022db5..2243f3f5 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BatchProducer.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BatchProducer.java @@ -1,12 +1,11 @@ package io.github.dunwu.javaweb.rocketmq; -import org.apache.rocketmq.client.producer.DefaultMQProducer; -import org.apache.rocketmq.common.message.Message; - import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; +import org.apache.rocketmq.client.producer.DefaultMQProducer; +import org.apache.rocketmq.common.message.Message; /** * 批量发送消息 @@ -39,7 +38,6 @@ public static void main(String[] args) throws Exception { producer.shutdown(); } - public static class ListSplitter implements Iterator> { private final int SIZE_LIMIT = 1000 * 1000; @@ -81,11 +79,9 @@ public List next() { } if (tmpSize + totalSize > SIZE_LIMIT) { break; - } - else { + } else { totalSize += tmpSize; } - } List subList = messages.subList(currIndex, nextIndex); currIndex = nextIndex; diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastConsumer.java b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastConsumer.java index 54f4929a..c14a290c 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastConsumer.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastConsumer.java @@ -1,5 +1,6 @@ package io.github.dunwu.javaweb.rocketmq; +import java.util.List; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; @@ -8,8 +9,6 @@ import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; -import java.util.List; - /** * 接收广播消息示例 * diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastProducer.java b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastProducer.java index e209e8c7..f045af23 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastProducer.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastProducer.java @@ -19,7 +19,7 @@ public static void main(String[] args) throws Exception { for (int i = 0; i < 100; i++) { Message msg = new Message("TopicTest", "TagA", "OrderID188", - "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET)); + "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET)); SendResult sendResult = producer.send(msg); System.out.printf("%s%n", sendResult); } diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OnewayProducer.java b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OnewayProducer.java index 99934b58..bc0f9e63 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OnewayProducer.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OnewayProducer.java @@ -20,11 +20,10 @@ public static void main(String[] args) throws Exception { for (int i = 0; i < 100; i++) { // Create a message instance, specifying topic, tag and message body. Message msg = new Message("TopicTest" /* Topic */, "TagA" /* Tag */, ("Hello RocketMQ " + i) - .getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */ + .getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */ ); // Call send message to deliver message to one of brokers. producer.sendOneway(msg); - } // Shut down once the producer instance is not longer in use. producer.shutdown(); diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedConsumer.java b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedConsumer.java index b646330a..4cc068eb 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedConsumer.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedConsumer.java @@ -1,5 +1,7 @@ package io.github.dunwu.javaweb.rocketmq; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus; @@ -7,9 +9,6 @@ import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.MessageExt; -import java.util.List; -import java.util.concurrent.atomic.AtomicLong; - /** * 接收全局和分区排序的消息。 * @@ -36,19 +35,15 @@ public ConsumeOrderlyStatus consumeMessage(List msgs, ConsumeOrderly this.consumeTimes.incrementAndGet(); if ((this.consumeTimes.get() % 2) == 0) { return ConsumeOrderlyStatus.SUCCESS; - } - else if ((this.consumeTimes.get() % 3) == 0) { + } else if ((this.consumeTimes.get() % 3) == 0) { return ConsumeOrderlyStatus.ROLLBACK; - } - else if ((this.consumeTimes.get() % 4) == 0) { + } else if ((this.consumeTimes.get() % 4) == 0) { return ConsumeOrderlyStatus.COMMIT; - } - else if ((this.consumeTimes.get() % 5) == 0) { + } else if ((this.consumeTimes.get() % 5) == 0) { context.setSuspendCurrentQueueTimeMillis(3000); return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT; } return ConsumeOrderlyStatus.SUCCESS; - } }); diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedProducer.java b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedProducer.java index bcb3d12c..a781943e 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedProducer.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedProducer.java @@ -1,5 +1,6 @@ package io.github.dunwu.javaweb.rocketmq; +import java.util.List; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.MQProducer; import org.apache.rocketmq.client.producer.MessageQueueSelector; @@ -8,8 +9,6 @@ import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.common.RemotingHelper; -import java.util.List; - /** * 发送全局和分区排序的消息。 * @@ -23,12 +22,12 @@ public static void main(String[] args) throws Exception { ((DefaultMQProducer) producer).setNamesrvAddr(RocketConfig.HOST); // Launch the instance. producer.start(); - String[] tags = new String[] { "TagA", "TagB", "TagC", "TagD", "TagE" }; + String[] tags = new String[] {"TagA", "TagB", "TagC", "TagD", "TagE"} for (int i = 0; i < 100; i++) { int orderId = i % 10; // Create a message instance, specifying topic, tag and message body. Message msg = new Message("TopicTestjjj", tags[i % tags.length], "KEY" + i, - ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET)); + ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET)); SendResult sendResult = producer.send(msg, new MessageQueueSelector() { @Override public MessageQueue select(List mqs, Message msg, Object arg) { diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageConsumer.java b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageConsumer.java index c0462f93..7e58fd35 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageConsumer.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageConsumer.java @@ -1,13 +1,12 @@ package io.github.dunwu.javaweb.rocketmq; +import java.util.List; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.common.message.MessageExt; -import java.util.List; - /** * 接收定时消息 * @@ -24,11 +23,11 @@ public static void main(String[] args) throws Exception { consumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List messages, - ConsumeConcurrentlyContext context) { + ConsumeConcurrentlyContext context) { for (MessageExt message : messages) { // Print approximate delay time period System.out.println("Receive message[msgId=" + message.getMsgId() + "] " - + (System.currentTimeMillis() - message.getStoreTimestamp()) + "ms later"); + + (System.currentTimeMillis() - message.getStoreTimestamp()) + "ms later"); } return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/SyncProducer.java b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/SyncProducer.java index 4614ed25..8ba82ae6 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/SyncProducer.java +++ b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/SyncProducer.java @@ -21,7 +21,7 @@ public static void main(String[] args) throws Exception { for (int i = 0; i < 100; i++) { // Create a message instance, specifying topic, tag and message body. Message msg = new Message("TopicTest" /* Topic */, "TagA" /* Tag */, ("Hello RocketMQ " + i) - .getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */ + .getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */ ); // Call send message to deliver message to one of brokers. SendResult sendResult = producer.send(msg); diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/resources/logback.xml b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/resources/logback.xml index 7cac2011..ff488e4b 100644 --- a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/resources/logback.xml +++ b/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/resources/logback.xml @@ -3,49 +3,49 @@ - - - - - - 10 - 100 - - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%p] %c{36}#%M - %m%n - - - - - - ${LOG_PATH}/logs/${FILE_NAME}-error.%d{yyyy-MM-dd}.log - 30 - - - - - 10MB - - - - ERROR - ACCEPT - DENY - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%p] %c{36}#%M - %m%n - - - - - - - - - - + + + + + + 10 + 100 + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%p] %c{36}#%M - %m%n + + + + + + ${LOG_PATH}/logs/${FILE_NAME}-error.%d{yyyy-MM-dd}.log + 30 + + + + + 10MB + + + + ERROR + ACCEPT + DENY + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%p] %c{36}#%M - %m%n + + + + + + + + + + diff --git a/codes/javaweb/javaweb-mq/pom.xml b/codes/javaweb/javaweb-mq/pom.xml index 2159378f..30c2bdb4 100644 --- a/codes/javaweb/javaweb-mq/pom.xml +++ b/codes/javaweb/javaweb-mq/pom.xml @@ -1,16 +1,16 @@ - 4.0.0 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 - io.github.dunwu.javaweb - javaweb-mq - 1.0.0 - pom + io.github.dunwu.javaweb + javaweb-mq + 1.0.0 + pom - - javaweb-mq-kafka - javaweb-mq-kafka-spring-boot - javaweb-mq-rocketmq - + + javaweb-mq-kafka + javaweb-mq-kafka-spring-boot + javaweb-mq-rocketmq + diff --git a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/pom.xml b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/pom.xml index ffc18644..45b2a6c5 100644 --- a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/pom.xml +++ b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/pom.xml @@ -1,29 +1,27 @@ - 4.0.0 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" + xmlns="http://maven.apache.org/POM/4.0.0"> + 4.0.0 - io.github.dunwu.javaweb - javaweb-rpc-zookeeper - 1.0.0 - jar + io.github.dunwu.javaweb + javaweb-rpc-zookeeper + 1.0.0 + jar - - UTF-8 - 1.8 - ${java.version} - ${java.version} - + + UTF-8 + 1.8 + ${java.version} + ${java.version} + - - - org.apache.zookeeper - zookeeper - 3.4.12 - - + + + org.apache.zookeeper + zookeeper + 3.4.12 + + - - ${project.artifactId} - diff --git a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKConnection.java b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKConnection.java index b13802b1..68b8e1bc 100644 --- a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKConnection.java +++ b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKConnection.java @@ -1,21 +1,22 @@ package io.github.dunwu.javaweb.zookeeper; +import java.io.IOException; +import java.util.concurrent.CountDownLatch; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.Watcher.Event.KeeperState; import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.ZooKeeper.States; -import java.io.IOException; -import java.util.concurrent.CountDownLatch; - /** * ZooKeeper 连接示例 */ public class ZKConnection { private static final String HOST = "localhost"; + final CountDownLatch connectedSignal = new CountDownLatch(1); + // declare zookeeper instance to access ZooKeeper ensemble private ZooKeeper zoo; diff --git a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKCreate.java b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKCreate.java index 064c15b7..712b53ff 100644 --- a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKCreate.java +++ b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKCreate.java @@ -11,16 +11,13 @@ public class ZKCreate { private static final String HOST = "localhost"; + // create static instance for zookeeper class. private static ZooKeeper zk; + // create static instance for ZooKeeperConnection class. private static ZKConnection conn; - // Method to create znode in zookeeper ensemble - public static void create(String path, byte[] data) throws KeeperException, InterruptedException { - zk.create(path, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); - } - public static void main(String[] args) throws InterruptedException { // znode path @@ -33,15 +30,18 @@ public static void main(String[] args) throws InterruptedException { conn = new ZKConnection(); zk = conn.connect(HOST); create(path, data); // Create the data to the specified path - } - catch (Exception e) { + } catch (Exception e) { System.out.println(e.getMessage()); // Catch error message - } - finally { + } finally { if (conn != null) { conn.close(); } } } + // Method to create znode in zookeeper ensemble + public static void create(String path, byte[] data) throws KeeperException, InterruptedException { + zk.create(path, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + } + } diff --git a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKDelete.java b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKDelete.java index f3f0daac..20272ef3 100644 --- a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKDelete.java +++ b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKDelete.java @@ -6,13 +6,10 @@ public class ZKDelete { private static final String HOST = "localhost"; + private static ZooKeeper zk; - private static ZKConnection conn; - // Method to check existence of znode and its status, if znode is available. - public static void delete(String path) throws KeeperException, InterruptedException { - zk.delete(path, zk.exists(path, true).getVersion()); - } + private static ZKConnection conn; public static void main(String[] args) throws InterruptedException, KeeperException { String path = "/MyFirstZnode"; // Assign path to the znode @@ -21,15 +18,18 @@ public static void main(String[] args) throws InterruptedException, KeeperExcept conn = new ZKConnection(); zk = conn.connect(HOST); delete(path); // delete the node with the specified path - } - catch (Exception e) { + } catch (Exception e) { System.out.println(e.getMessage()); // catches error messages - } - finally { + } finally { if (conn != null) { conn.close(); } } } + // Method to check existence of znode and its status, if znode is available. + public static void delete(String path) throws KeeperException, InterruptedException { + zk.delete(path, zk.exists(path, true).getVersion()); + } + } diff --git a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKExists.java b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKExists.java index 3d52c9a7..d3d2f2c9 100644 --- a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKExists.java +++ b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKExists.java @@ -7,13 +7,10 @@ public class ZKExists { private static final String HOST = "localhost"; + private static ZooKeeper zk; - private static ZKConnection conn; - // Method to check existence of znode and its status, if znode is available. - public static Stat znode_exists(String path) throws KeeperException, InterruptedException { - return zk.exists(path, true); - } + private static ZKConnection conn; public static void main(String[] args) throws InterruptedException, KeeperException { String path = "/MyFirstZnode"; // Assign znode to the specified path @@ -25,15 +22,17 @@ public static void main(String[] args) throws InterruptedException, KeeperExcept if (stat != null) { System.out.println("Node exists and the node version is " + stat.getVersion()); - } - else { + } else { System.out.println("Node does not exists"); } - - } - catch (Exception e) { + } catch (Exception e) { System.out.println(e.getMessage()); // Catches error messages } } + // Method to check existence of znode and its status, if znode is available. + public static Stat znode_exists(String path) throws KeeperException, InterruptedException { + return zk.exists(path, true); + } + } diff --git a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKGetChildren.java b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKGetChildren.java index 44d26dd6..9f110865 100644 --- a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKGetChildren.java +++ b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKGetChildren.java @@ -1,21 +1,17 @@ package io.github.dunwu.javaweb.zookeeper; +import java.util.List; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.data.Stat; -import java.util.List; - public class ZKGetChildren { private static final String HOST = "localhost"; + private static ZooKeeper zk; - private static ZKConnection conn; - // Method to check existence of znode and its status, if znode is available. - public static Stat znode_exists(String path) throws KeeperException, InterruptedException { - return zk.exists(path, true); - } + private static ZKConnection conn; public static void main(String[] args) throws InterruptedException, KeeperException { String path = "/MyFirstZnode"; // Assign path to the znode @@ -32,19 +28,21 @@ public static void main(String[] args) throws InterruptedException, KeeperExcept for (int i = 0; i < children.size(); i++) { System.out.println(children.get(i)); // Print children's } - } - else { + } else { System.out.println("Node does not exists"); } - } - catch (Exception e) { + } catch (Exception e) { System.out.println(e.getMessage()); - } - finally { + } finally { if (conn != null) { conn.close(); } } } + // Method to check existence of znode and its status, if znode is available. + public static Stat znode_exists(String path) throws KeeperException, InterruptedException { + return zk.exists(path, true); + } + } diff --git a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKGetData.java b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKGetData.java index 8f8abe64..9b5ec589 100644 --- a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKGetData.java +++ b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKGetData.java @@ -1,22 +1,19 @@ package io.github.dunwu.javaweb.zookeeper; +import java.util.concurrent.CountDownLatch; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.data.Stat; -import java.util.concurrent.CountDownLatch; - public class ZKGetData { private static final String HOST = "localhost"; + private static ZooKeeper zk; - private static ZKConnection conn; - public static Stat existsZnode(String path) throws KeeperException, InterruptedException { - return zk.exists(path, true); - } + private static ZKConnection conn; public static void main(String[] args) throws InterruptedException { String path = "/MyFirstZnode"; @@ -33,13 +30,11 @@ public void process(WatchedEvent we) { if (we.getType() == Event.EventType.None) { switch (we.getState()) { - case Expired: - connectedSignal.countDown(); - break; + case Expired: + connectedSignal.countDown(); + break; } - - } - else { + } else { String path = "/MyFirstZnode"; try { @@ -47,9 +42,7 @@ public void process(WatchedEvent we) { String data = new String(bn, "UTF-8"); System.out.println(data); connectedSignal.countDown(); - - } - catch (Exception ex) { + } catch (Exception ex) { System.out.println(ex.getMessage()); } } @@ -59,20 +52,20 @@ public void process(WatchedEvent we) { String data = new String(b, "UTF-8"); System.out.println(data); connectedSignal.await(); - - } - else { + } else { System.out.println("Node does not exists"); } - } - catch (Exception e) { + } catch (Exception e) { System.out.println(e.getMessage()); - } - finally { + } finally { if (conn != null) { conn.close(); } } } + public static Stat existsZnode(String path) throws KeeperException, InterruptedException { + return zk.exists(path, true); + } + } diff --git a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKSetData.java b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKSetData.java index b85519ae..c2bb3740 100644 --- a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKSetData.java +++ b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKSetData.java @@ -6,13 +6,10 @@ public class ZKSetData { private static final String HOST = "localhost"; + private static ZooKeeper zk; - private static ZKConnection conn; - // Method to update the data in a znode. Similar to getData but without watcher. - public static void update(String path, byte[] data) throws KeeperException, InterruptedException { - zk.setData(path, data, zk.exists(path, true).getVersion()); - } + private static ZKConnection conn; public static void main(String[] args) throws InterruptedException { String path = "/MyFirstZnode"; @@ -22,15 +19,18 @@ public static void main(String[] args) throws InterruptedException { conn = new ZKConnection(); zk = conn.connect(HOST); update(path, data); // Update znode data to the specified path - } - catch (Exception e) { + } catch (Exception e) { System.out.println(e.getMessage()); - } - finally { + } finally { if (conn != null) { conn.close(); } } } + // Method to update the data in a znode. Similar to getData but without watcher. + public static void update(String path, byte[] data) throws KeeperException, InterruptedException { + zk.setData(path, data, zk.exists(path, true).getVersion()); + } + } diff --git a/codes/javaweb/javaweb-rpc/pom.xml b/codes/javaweb/javaweb-rpc/pom.xml index e7680ca1..9c83f47f 100644 --- a/codes/javaweb/javaweb-rpc/pom.xml +++ b/codes/javaweb/javaweb-rpc/pom.xml @@ -1,14 +1,14 @@ - 4.0.0 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 - io.github.dunwu.javaweb - javaweb-rpc - 1.0.0 - pom + io.github.dunwu.javaweb + javaweb-rpc + 1.0.0 + pom - - javaweb-rpc-zookeeper - + + javaweb-rpc-zookeeper + diff --git a/codes/javaweb/pom.xml b/codes/javaweb/pom.xml index fb02a9c0..fd39b2b8 100644 --- a/codes/javaweb/pom.xml +++ b/codes/javaweb/pom.xml @@ -1,15 +1,15 @@ - 4.0.0 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + javaweb - io.github.dunwu.javaweb - javaweb - 1.0.0 - pom + io.github.dunwu.javaweb + 4.0.0 + + javaweb-mq + javaweb-rpc + + pom - - javaweb-mq - javaweb-rpc - + 1.0.0 From 42bb5f2dc37595a453aaf3d294a1928dd9717602 Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Wed, 23 Oct 2019 20:24:57 +0800 Subject: [PATCH 41/75] update docs --- .editorconfig | 25 +- .gitattributes | 9 +- .gitignore | 33 +- README.md | 30 +- docs/README.md | 37 +- ...11\345\205\250\346\236\266\346\236\204.md" | 4 +- docs/index.html | 458 +++++++------- docs/javaee/javaee-cookie-sesion.md | 67 ++- docs/network/dns.md | 2 +- docs/network/network-guide.md | 2 +- docs/package.json | 14 +- docs/technology/cache/redis.md | 2 +- .../{orm/mybatis.md => data/Mybatis.md} | 2 +- .../{sharding => data}/ShardingSphere.md | 0 .../cache => theory}/cache-theory.md | 135 ++--- docs/theory/distributed-base-theory.md | 283 +++++++++ docs/theory/distributed-id-theory.md | 276 +++++++++ docs/theory/distributed-interview.md | 501 ++++++++++++++++ docs/theory/distributed-lock-theory.md | 450 ++++++++++++++ docs/theory/distributed-session-theory.md | 228 +++++++ docs/theory/distributed-storage-theory.md | 82 +++ docs/theory/distributed-transaction-theory.md | 185 ++++++ docs/theory/load-balance-theory.md | 563 ++++++++++++++++++ docs/{technology/mq => theory}/mq-theory.md | 94 +-- .../sharding => theory}/sharding-theory.md | 4 +- 25 files changed, 3024 insertions(+), 462 deletions(-) rename docs/technology/{orm/mybatis.md => data/Mybatis.md} (98%) rename docs/technology/{sharding => data}/ShardingSphere.md (100%) rename docs/{technology/cache => theory}/cache-theory.md (91%) create mode 100644 docs/theory/distributed-base-theory.md create mode 100644 docs/theory/distributed-id-theory.md create mode 100644 docs/theory/distributed-interview.md create mode 100644 docs/theory/distributed-lock-theory.md create mode 100644 docs/theory/distributed-session-theory.md create mode 100644 docs/theory/distributed-storage-theory.md create mode 100644 docs/theory/distributed-transaction-theory.md create mode 100644 docs/theory/load-balance-theory.md rename docs/{technology/mq => theory}/mq-theory.md (93%) rename docs/{technology/sharding => theory}/sharding-theory.md (99%) diff --git a/.editorconfig b/.editorconfig index 7f575a88..c7ca29b2 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,30 +1,27 @@ -# EditorConfig helps developers define and maintain consistent -# coding styles between different editors and IDEs -# http://editorconfig.org -# 所有文件换行以 Unix like 风格(LF),win 格式特定的除外(bat) -# 缩进 java 4 个空格,其他所有文件 2 个空格 +# EditorConfig 用于在 IDE 中检查代码的基本 Code Style +# @see: https://editorconfig.org/ + +# 配置说明: +# 所有文件换行使用 Unix 风格(LF),*.bat 文件使用 Windows 风格(CRLF) +# java / sh 文件缩进 4 个空格,其他所有文件缩进 2 个空格 root = true [*] -# Unix-style newlines with a newline ending every file end_of_line = lf - -# Change these settings to your own preference -indent_style = space indent_size = 2 - -# We recommend you to keep these unchanged +indent_style = tab +max_line_length = 120 charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true -[*.bat] +[*.{bat, cmd}] end_of_line = crlf -[*.java] -indent_style = space +[*.{java, groovy, kt, sh}] indent_size = 4 [*.md] +max_line_length = 0 trim_trailing_whitespace = false diff --git a/.gitattributes b/.gitattributes index fe47bf88..1e3aabc2 100644 --- a/.gitattributes +++ b/.gitattributes @@ -50,27 +50,26 @@ *.bin binary *.exe binary -# 图片 +# images *.png binary *.jpg binary *.ico binary *.gif binary -# 音视频 +# medias *.mp3 binary *.swf binary -# 字体 +# fonts *.eot binary *.svg binary *.ttf binary *.woff binary -# other doc +# others *.pdf binary *.doc binary *.docx binary *.xls binary *.xlsx binary *.xmind binary -*.eddx binary diff --git a/.gitignore b/.gitignore index 76e97fd1..b8875768 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,37 @@ -################ JAVA ################ -# temp folders +# --------------------------------------------------------------------- +# more gitignore templates see https://github.com/github/gitignore +# --------------------------------------------------------------------- + +# ------------------------------- java ------------------------------- +# compiled folders classes target logs +.mtj.tmp/ -# temp files +# compiled files *.class + +# bluej files +*.ctxt + +# package files # *.jar *.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs +hs_err_pid* + +# maven plugin temp files +.flattened-pom.xml -################ JAVASCRIPT ################ +# ------------------------------- javascript ------------------------------- # dependencies node_modules @@ -28,11 +49,11 @@ yarn-error.log* bundle*.js -################ IDEA ################ +# ------------------------------- intellij ------------------------------- .idea *.iml -################ Eclipse ################ +# ------------------------------- eclipse ------------------------------- .classpath .project diff --git a/README.md b/README.md index 78ee8a46..4537a250 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,9 @@ > - 🔁 项目同步维护:[Github](https://github.com/dunwu/javaweb/) | [Gitee](https://gitee.com/turnon/javaweb/) > - 📖 电子书阅读:[Github Pages](https://dunwu.github.io/javaweb/) | [Gitee Pages](http://turnon.gitee.io/javaweb/) -| 🔰 | 🏗 | ✨ | ⭐️ | ☕ | 🕸 | -| :---------------: | :---------------------: | :----------------------: | :-------------------------: | :------------------: | :---------------------: | -| [准备](#🔰️-准备) | [架构设计](#🏗-架构设计) | [系统原理](#✨-系统原理) | [分布式技术](#⭐️-主流技术) | [JavaEE](#☕-javaee) | [网络通信](#🕸-网络通信) | +| 🔰 | 🎨 | ✨ | ⭐️ | ☕ | 🌏 | +| :---------------: | :----------------------: | :---------------------: | :---------------------: | :-----------------: | :----------------------: | +| [准备](#🔰️-准备) | [架构设计](#🎨-架构设计) | [系统原理](#✨-系统原理) | [主流技术](#⭐-主流技术) | [JavaEE](#☕-JavaEE) | [网络通信](#🌏-网络通信) | ## 🔰️ 准备 @@ -28,7 +28,20 @@ - [网站的安全架构](docs/architecture/网站的安全架构.md) - [网站典型故障](docs/architecture/网站典型故障.md) -## ⭐️ 主流技术 +## ✨ 系统原理 + +- [分布式基本原理](docs/theory/distributed-base-theory.md) +- [负载均衡基本原理](docs/theory/load-balance-theory.md) +- [缓存基本原理](docs/theory/cache-theory.md) +- [消息队列基本原理](docs/theory/mq-theory.md) +- [分布式锁基本原理](docs/theory/distributed-lock-theory.md) +- [分布式会话基本原理](docs/theory/distributed-session-theory.md) +- [分布式存储基本原理](docs/theory/distributed-storage-theory.md) +- [分布式 ID 基本原理](docs/theory/distributed-id-theory.md) +- [分布式事务基本原理](docs/theory/distributed-transaction-theory.md) +- [分库分表基本原理](docs/theory/sharding-theory.md) + +## ⭐ 主流技术 > [主流技术](docs/technology) 汇集 JavaWeb 开发常用的各种主流技术。 @@ -45,7 +58,6 @@ ### 消息队列 -- [消息队列原理](docs/technology/mq/mq-theory.md) - Kafka - [Kafka 实战篇](docs/technology/mq/kafka/kafka-basics.md) - [Kafka 原理篇](docs/technology/mq/kafka/kafka-advanced.md) @@ -57,7 +69,6 @@ ### 缓存 - [缓存面经](docs/technology/cache/cache-interview.md) -- [全面理解缓存原理](docs/technology/cache/cache-theory.md) - [Redis](docs/technology/cache/redis.md) - [Ehcache](docs/technology/cache/ehcache.md) - [Caffeine](docs/technology/cache/caffeine.md) @@ -76,11 +87,8 @@ ### ORM -- [Mybatis 应用指南](docs/technology/orm/mybatis.md) - -### 分库分表 - -- [分库分表原理](docs/technology/sharding/sharding-theory.md) +- [Mybatis 应用指南](docs/technology/data/Mybatis.md) +- [ShardingSphere 应用指南](docs/technology/data/ShardingSphere.md) ## ☕ JavaEE diff --git a/docs/README.md b/docs/README.md index 296033a2..9b8c7d3c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -5,9 +5,9 @@ > - 🔁 项目同步维护:[Github](https://github.com/dunwu/javaweb/) | [Gitee](https://gitee.com/turnon/javaweb/) > - 📖 电子书阅读:[Github Pages](https://dunwu.github.io/javaweb/) | [Gitee Pages](http://turnon.gitee.io/javaweb/) -| 🔰 | 🏗 | ✨ | ⭐️ | ☕ | 🕸 | -| :---------------: | :---------------------: | :----------------------: | :-------------------------: | :------------------: | :---------------------: | -| [准备](#🔰️-准备) | [架构设计](#🏗-架构设计) | [系统原理](#✨-系统原理) | [分布式技术](#⭐️-主流技术) | [JavaEE](#☕-javaee) | [网络通信](#🕸-网络通信) | +| 🔰 | 🎨 | ✨ | ⭐️ | ☕ | 🌏 | +| :---------------: | :----------------------: | :---------------------: | :---------------------: | :-----------------: | :----------------------: | +| [准备](#🔰️-准备) | [架构设计](#🎨-架构设计) | [系统原理](#✨-系统原理) | [主流技术](#⭐-主流技术) | [JavaEE](#☕-JavaEE) | [网络通信](#🌏-网络通信) | ## 🔰️ 准备 @@ -28,7 +28,20 @@ - [网站的安全架构](architecture/网站的安全架构.md) - [网站典型故障](architecture/网站典型故障.md) -## ⭐️ 主流技术 +## ✨ 系统原理 + +- [分布式基本原理](theory/distributed-base-theory.md) +- [负载均衡基本原理](theory/load-balance-theory.md) +- [缓存基本原理](theory/cache-theory.md) +- [消息队列基本原理](theory/mq-theory.md) +- [分布式锁基本原理](theory/distributed-lock-theory.md) +- [分布式会话基本原理](theory/distributed-session-theory.md) +- [分布式存储基本原理](theory/distributed-storage-theory.md) +- [分布式 ID 基本原理](theory/distributed-id-theory.md) +- [分布式事务基本原理](theory/distributed-transaction-theory.md) +- [分库分表基本原理](theory/sharding-theory.md) + +## ⭐ 主流技术 > [主流技术](technology) 汇集 JavaWeb 开发常用的各种主流技术。 @@ -45,7 +58,6 @@ ### 消息队列 -- [消息队列原理](technology/mq/mq-theory.md) - Kafka - [Kafka 实战篇](technology/mq/kafka/kafka-basics.md) - [Kafka 原理篇](technology/mq/kafka/kafka-advanced.md) @@ -56,10 +68,8 @@ ### 缓存 -- [缓存面经](technology/cache/cache-interview.md) 🎯 -- [全面理解缓存原理](technology/cache/cache-theory.md) +- [缓存面经](technology/cache/cache-interview.md) - [Redis](technology/cache/redis.md) -- Memcache - [Ehcache](technology/cache/ehcache.md) - [Caffeine](technology/cache/caffeine.md) @@ -77,17 +87,14 @@ ### ORM -- [Mybatis 应用指南](technology/orm/mybatis.md) - -### 分库分表 - -- [分库分表原理](technology/sharding/sharding-theory.md) +- [Mybatis 应用指南](technology/data/Mybatis.md) +- [ShardingSphere 应用指南](technology/data/ShardingSphere.md) ## ☕ JavaEE > [JavaEE](javaee) 技术——Java Web 的基石 -- [JavaEE 面经](javaee/javaee-interview.md) 🎯 +- [JavaEE 面经](javaee/javaee-interview.md) - [JavaEE 之 Servlet 指南](javaee/javaee-servlet.md) - [JavaEE 之 Jsp 指南](javaee/javaee-jsp.md) - [JavaEE 之 Filter 和 Listener](javaee/javaee-filter-listener.md) @@ -96,7 +103,7 @@ ## 🌏 网络通信 - **面试** - - [网络通信面经](network/network-interview.md) 🎯 + - [网络通信面经](network/network-interview.md) - **网络分层** - [计算机网络概述](network/network-guide.md) - [计算机网络之物理层](network/network-physical.md) diff --git "a/docs/architecture/\347\275\221\347\253\231\347\232\204\345\256\211\345\205\250\346\236\266\346\236\204.md" "b/docs/architecture/\347\275\221\347\253\231\347\232\204\345\256\211\345\205\250\346\236\266\346\236\204.md" index 3a399f02..75c20085 100644 --- "a/docs/architecture/\347\275\221\347\253\231\347\232\204\345\256\211\345\205\250\346\236\266\346\236\204.md" +++ "b/docs/architecture/\347\275\221\347\253\231\347\232\204\345\256\211\345\205\250\346\236\266\346\236\204.md" @@ -224,7 +224,7 @@ MSSQL 服务器会执行这条 SQL 语句,包括它后面那个用于向系统 > :point_right: 参考阅读: > -> - [消息摘要](https://github.com/dunwu/JavaCore/blob/master/docs/advanced/encode/java-message-digest.md) +> - [消息摘要](https://github.com/dunwu/javacore/blob/master/docs/advanced/encode/java-message-digest.md) ### 2.2. 加密算法 @@ -246,7 +246,7 @@ MSSQL 服务器会执行这条 SQL 语句,包括它后面那个用于向系统 > :point_right: 参考阅读: > -> - [加密](https://github.com/dunwu/JavaCore/blob/master/docs/advanced/encode/java-encryption.md) +> - [加密](https://github.com/dunwu/javacore/blob/master/docs/advanced/encode/java-encryption.md) #### 2.3. 密钥安全管理 diff --git a/docs/index.html b/docs/index.html index 7d7dbf0d..c46a5566 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,258 +1,258 @@ - - JavaWeb - - - - - - - + - + - + - + + @media (max-width: 600px) { + pre { + padding-left: 0px !important; + padding-right: 0px !important; + } + } +
    正在加载...
    @@ -267,11 +267,7 @@ subMaxLevel: 4, formatUpdated: '{MM}/{DD} {HH}:{mm}', search: { - maxAge: 86400000, - paths: ['/'], - placeholder: '🔍 搜索', - noData: '没有结果!', - depth: 4 + maxAge: 86400000, paths: ['/'], placeholder: '🔍 搜索', noData: '没有结果!', depth: 4 } } @@ -281,9 +277,11 @@ + + diff --git a/docs/javaee/javaee-cookie-sesion.md b/docs/javaee/javaee-cookie-sesion.md index 1df06ec1..56a6fe8c 100644 --- a/docs/javaee/javaee-cookie-sesion.md +++ b/docs/javaee/javaee-cookie-sesion.md @@ -1,31 +1,36 @@ # JavaEE 之 Cookie 和 Session - + - [Cookie](#cookie) - - [Cookie 是什么?](#cookie-是什么) - - [Cookie 剖析](#cookie-剖析) - - [Cookie 类中的方法](#cookie-类中的方法) - - [Cookie 的有效期](#cookie-的有效期) - - [Cookie 的域名](#cookie-的域名) - - [Cookie 的路径](#cookie-的路径) - - [Cookie 的安全属性](#cookie-的安全属性) - - [实例](#实例) + - [Cookie 是什么?](#cookie-是什么) + - [Cookie 剖析](#cookie-剖析) + - [Cookie 类中的方法](#cookie-类中的方法) + - [Cookie 的有效期](#cookie-的有效期) + - [Cookie 的域名](#cookie-的域名) + - [Cookie 的路径](#cookie-的路径) + - [Cookie 的安全属性](#cookie-的安全属性) + - [实例](#实例) + - [添加 Cookie](#添加-cookie) + - [显示 Cookie](#显示-cookie) + - [删除 Cookie](#删除-cookie) - [Session](#session) - - [Session 是什么?](#session-是什么) - - [Session 类中的方法](#session-类中的方法) - - [Session 的有效期](#session-的有效期) - - [Session 对浏览器的要求](#session-对浏览器的要求) - - [URL 地址重写](#url-地址重写) - - [Session 中禁用 Cookie](#session-中禁用-cookie) - - [实例](#实例-1) + - [Session 是什么?](#session-是什么) + - [Session 类中的方法](#session-类中的方法) + - [Session 的有效期](#session-的有效期) + - [Session 对浏览器的要求](#session-对浏览器的要求) + - [URL 地址重写](#url-地址重写) + - [Session 中禁用 Cookie](#session-中禁用-cookie) + - [实例](#实例) + - [Session 跟踪](#session-跟踪) + - [删除 Session 会话数据](#删除-session-会话数据) - [Cookie vs Session](#cookie-vs-session) - - [存取方式](#存取方式) - - [隐私安全](#隐私安全) - - [有效期](#有效期) - - [服务器的开销](#服务器的开销) - - [浏览器的支持](#浏览器的支持) - - [跨域名](#跨域名) + - [存取方式](#存取方式) + - [隐私安全](#隐私安全) + - [有效期](#有效期) + - [服务器的开销](#服务器的开销) + - [浏览器的支持](#浏览器的支持) + - [跨域名](#跨域名) @@ -41,9 +46,9 @@ Cookie 实际上是存储在客户端上的文本信息,并保留了各种跟 **Cookie 工作步骤:** -1. 客户端请求服务器,如果服务器需要记录该用户的状态,就是用 response 向客户端浏览器颁发一个 Cookie。 -2. 客户端浏览器会把 Cookie 保存下来。 -3. 当浏览器再请求该网站时,浏览器把该请求的网址连同 Cookie 一同提交给服务器。服务器检查该 Cookie,以此来辨认用户状态。 +1. 客户端请求服务器,如果服务器需要记录该用户的状态,就是用 response 向客户端浏览器颁发一个 Cookie。 +2. 客户端浏览器会把 Cookie 保存下来。 +3. 当浏览器再请求该网站时,浏览器把该请求的网址连同 Cookie 一同提交给服务器。服务器检查该 Cookie,以此来辨认用户状态。 **_注:Cookie 功能需要浏览器的支持,如果浏览器不支持 Cookie 或者 Cookie 禁用了,Cookie 功能就会失效。_** @@ -136,11 +141,11 @@ Java 中使用`setSecure(booleanflag)`和`getSecure ()`方法来设置、获取 通过 Servlet 添加 Cookies 包括三个步骤: -1. 创建一个 Cookie 对象:您可以调用带有 cookie 名称和 cookie 值的 Cookie 构造函数,cookie 名称和 cookie 值都是字符串。 +1. 创建一个 Cookie 对象:您可以调用带有 cookie 名称和 cookie 值的 Cookie 构造函数,cookie 名称和 cookie 值都是字符串。 -2. 设置最大生存周期:您可以使用 `setMaxAge` 方法来指定 cookie 能够保持有效的时间(以秒为单位)。 +2. 设置最大生存周期:您可以使用 `setMaxAge` 方法来指定 cookie 能够保持有效的时间(以秒为单位)。 -3. 发送 Cookie 到 HTTP 响应头:您可以使用 `response.addCookie` 来添加 HTTP 响应头中的 Cookies。 +3. 发送 Cookie 到 HTTP 响应头:您可以使用 `response.addCookie` 来添加 HTTP 响应头中的 Cookies。 AddCookies.java @@ -310,11 +315,11 @@ public class ReadCookies extends HttpServlet { Java 中并没有提供直接删除 Cookie 的方法,如果想要删除一个 Cookie,直接将这个 Cookie 的有效期设为 0 就可以了。步骤如下: -1. 读取一个现有的 cookie,并把它存储在 Cookie 对象中。 +1. 读取一个现有的 cookie,并把它存储在 Cookie 对象中。 -2. 使用 `setMaxAge()` 方法设置 cookie 的年龄为零,来删除现有的 cookie。 +2. 使用 `setMaxAge()` 方法设置 cookie 的年龄为零,来删除现有的 cookie。 -3. 把这个 cookie 添加到响应头。 +3. 把这个 cookie 添加到响应头。 DeleteCookies.java diff --git a/docs/network/dns.md b/docs/network/dns.md index db01840e..eaef1496 100644 --- a/docs/network/dns.md +++ b/docs/network/dns.md @@ -81,7 +81,7 @@ DNS 中,常见的资源记录类型有: 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` 命令,在其中添加下面的内容来配置域名服务器地址: > diff --git a/docs/network/network-guide.md b/docs/network/network-guide.md index 0ff165a3..ae0c5a0f 100644 --- a/docs/network/network-guide.md +++ b/docs/network/network-guide.md @@ -11,7 +11,7 @@ date: 2019-02-20 22:26
    -## :bulb: 指南 +## 💡 指南 学习之前,先看一下入门三问: diff --git a/docs/package.json b/docs/package.json index cd779280..1c20bfc9 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,9 +1,9 @@ { - "name": "JavaWeb", - "version": "1.0.0", - "scripts": { - "start": "docsify serve ./ --port 4000" - }, - "dependencies": {}, - "devDependencies": {} + "name": "JavaWeb", + "version": "1.0.0", + "scripts": { + "start": "docsify serve ./ --port 4000" + }, + "dependencies": {}, + "devDependencies": {} } diff --git a/docs/technology/cache/redis.md b/docs/technology/cache/redis.md index 932f57a5..9cc8744e 100644 --- a/docs/technology/cache/redis.md +++ b/docs/technology/cache/redis.md @@ -346,7 +346,7 @@ Redis 可以为每个键设置过期时间,当键过期时,会自动删除 可以使用 `EXPIRE` 或 `EXPIREAT` 来为 key 设置过期时间。 -> 注意:当 `EXPIRE` 的时间如果设置的是负数,`EXPIREAT` 设置的时间戳是过期时间,将直接删除 key。 +> 🔔 注意:当 `EXPIRE` 的时间如果设置的是负数,`EXPIREAT` 设置的时间戳是过期时间,将直接删除 key。 示例: diff --git a/docs/technology/orm/mybatis.md b/docs/technology/data/Mybatis.md similarity index 98% rename from docs/technology/orm/mybatis.md rename to docs/technology/data/Mybatis.md index ae40d27b..bff0c065 100644 --- a/docs/technology/orm/mybatis.md +++ b/docs/technology/data/Mybatis.md @@ -102,7 +102,7 @@ MyBatis 和 Hibernate 都是 Java 世界中比较流行的 ORM 框架。我们 - **SqlSession 生命周期** -> 注意:当 Mybatis 与一些依赖注入框架(如 Spring 或者 Guice)同时使用时,`SqlSessions` 将被依赖注入框架所创建,所以你不需要使用 `SqlSessionFactoryBuilder` 或者 `SqlSessionFactory`。 +> 🔔 注意:当 Mybatis 与一些依赖注入框架(如 Spring 或者 Guice)同时使用时,`SqlSessions` 将被依赖注入框架所创建,所以你不需要使用 `SqlSessionFactoryBuilder` 或者 `SqlSessionFactory`。 **每个线程都应该有它自己的 `SqlSession` 实例。** diff --git a/docs/technology/sharding/ShardingSphere.md b/docs/technology/data/ShardingSphere.md similarity index 100% rename from docs/technology/sharding/ShardingSphere.md rename to docs/technology/data/ShardingSphere.md diff --git a/docs/technology/cache/cache-theory.md b/docs/theory/cache-theory.md similarity index 91% rename from docs/technology/cache/cache-theory.md rename to docs/theory/cache-theory.md index 95b34949..6cfaa59d 100644 --- a/docs/technology/cache/cache-theory.md +++ b/docs/theory/cache-theory.md @@ -1,53 +1,14 @@ -# 全面理解缓存原理 - - - -- [1. 缓存概述](#1-缓存概述) - - [1.1. 什么是缓存](#11-什么是缓存) - - [1.2. 为什么引入缓存](#12-为什么引入缓存) - - [1.3. 缓存的基本原理](#13-缓存的基本原理) - - [1.4. 缓存淘汰算法](#14-缓存淘汰算法) - - [1.5. 缓存的分类](#15-缓存的分类) - - [1.6. 缓存整体架构](#16-缓存整体架构) -- [2. CDN 缓存](#2-cdn-缓存) - - [2.1. CDN 原理](#21-cdn-原理) - - [2.2. CDN 特点](#22-cdn-特点) -- [3. 反向代理缓存](#3-反向代理缓存) - - [3.1. 缓存原理](#31-缓存原理) - - [3.2. 代理缓存比较](#32-代理缓存比较) -- [4. 进程内缓存](#4-进程内缓存) - - [4.1. ConcurrentHashMap](#41-concurrenthashmap) - - [4.2. LRUHashMap](#42-lruhashmap) - - [4.3. Guava Cache](#43-guava-cache) - - [4.4. Caffeine](#44-caffeine) - - [4.5. 选择进程内缓存](#45-选择进程内缓存) -- [5. 分布式缓存](#5-分布式缓存) - - [5.1. Memcache](#51-memcache) - - [5.2. Redis](#52-redis) - - [5.3. 选择分布式缓存](#53-选择分布式缓存) -- [6. 多级缓存](#6-多级缓存) - - [6.1. 使用进程缓存](#61-使用进程缓存) - - [6.2. 使用多级缓存](#62-使用多级缓存) -- [7. 缓存问题](#7-缓存问题) - - [7.1. 缓存雪崩](#71-缓存雪崩) - - [7.2. 缓存穿透](#72-缓存穿透) - - [7.3. 缓存击穿](#73-缓存击穿) - - [7.4. 缓存更新](#74-缓存更新) - - [7.5. 缓存预热](#75-缓存预热) - - [7.6. 缓存降级](#76-缓存降级) -- [8. 参考资料](#8-参考资料) - - - -## 1. 缓存概述 - -### 1.1. 什么是缓存 +# 缓存基本原理 + +## 缓存概述 + +### 什么是缓存 **缓存就是数据交换的缓冲区**。 缓存是用于存储数据的硬件或软件的组成部分,以使得后续更快访问相应的数据。缓存中的数据可能是提前计算好的结果、数据的副本等。典型的应用场景:有 cpu cache, 磁盘 cache 等。本文中提及到缓存主要是指互联网应用中所使用的缓存组件。 -### 1.2. 为什么引入缓存 +### 为什么引入缓存 传统的后端业务场景中,访问量以及对响应时间的要求均不高,通常只使用数据库即可满足要求。这种架构简单,便于快速部署,很多网站发展初期均考虑使用这种架构。但是随着访问量的上升,以及对响应时间的要求提升,一个数据库服务已无法再满足要求。这时候通常会考虑数据库拆分(sharding)、读写分离、甚至硬件升级(SSD)等以满足新的业务需求。但是这种方式仍然会面临很多问题,主要体现在: @@ -66,13 +27,13 @@ - 饥饿式(写时触发):先查询 DB 里的数据, 然后把相关的数据写入 Cache。 - 定期刷新:适合周期性的跑数据的任务,或者列表型的数据,而且不要求绝对实时性。 -### 1.3. 缓存的基本原理 +### 缓存的基本原理 -1. 将数据写入/读取速度更快的存储(设备); -2. 将数据缓存到离应用最近的位置; -3. 将数据缓存到离用户最近的位置。 +1. 将数据写入/读取速度更快的存储(设备); +2. 将数据缓存到离应用最近的位置; +3. 将数据缓存到离用户最近的位置。 -### 1.4. 缓存淘汰算法 +### 缓存淘汰算法 常见的缓存淘汰算法有以下几种: @@ -82,7 +43,7 @@ 这三种缓存淘汰算法,实现复杂度一个比一个高,同样的命中率也是一个比一个好。而我们一般来说选择的方案居中即可,即实现成本不是太高,而命中率也还行的 LRU。 -### 1.5. 缓存的分类 +### 缓存的分类 在分布式系统中,缓存的应用非常广泛,从部署角度有以下几个方面的缓存应用。 @@ -91,7 +52,7 @@ - **进程内缓存** - 缓存应用字典等常用数据。 - **分布式缓存** - 缓存数据库中的热点数据。 -### 1.6. 缓存整体架构 +### 缓存整体架构 通常,网站的缓存整体架构如下图所示: @@ -99,15 +60,15 @@ 请求过程: -1. 浏览器向客户端发起请求,如果 CDN 有缓存则直接返回; -2. 如果 CDN 无缓存,则访问反向代理服务器; -3. 如果反向代理服务器有缓存则直接返回; -4. 如果反向代理服务器无缓存或动态请求,则访问应用服务器; -5. 应用服务器访问进程内缓存;如果有缓存,则返回代理服务器,并缓存数据;(动态请求不缓存) -6. 如果进程内缓存无数据,则读取分布式缓存;并返回应用服务器;应用服务器将数据缓存到本地缓存(部分); -7. 如果分布式缓存无数据,则应用程序读取数据库数据,并放入分布式缓存; +1. 浏览器向客户端发起请求,如果 CDN 有缓存则直接返回; +2. 如果 CDN 无缓存,则访问反向代理服务器; +3. 如果反向代理服务器有缓存则直接返回; +4. 如果反向代理服务器无缓存或动态请求,则访问应用服务器; +5. 应用服务器访问进程内缓存;如果有缓存,则返回代理服务器,并缓存数据;(动态请求不缓存) +6. 如果进程内缓存无数据,则读取分布式缓存;并返回应用服务器;应用服务器将数据缓存到本地缓存(部分); +7. 如果分布式缓存无数据,则应用程序读取数据库数据,并放入分布式缓存; -## 2. CDN 缓存 +## CDN 缓存 > **CDN 将数据缓存到离用户物理距离最近的服务器,使得用户可以就近获取请求内容。CDN 一般缓存静态资源文件(页面,脚本,图片,视频,文件等)**。 > @@ -115,7 +76,7 @@
    -### 2.1. CDN 原理 +### CDN 原理 CDN 的基本原理是广泛采用各种缓存服务器,将这些缓存服务器分布到用户访问相对集中的地区或网络中,在用户访问网站时,利用全局负载技术将用户的访问指向距离最近的工作正常的缓存服务器上,由缓存服务器直接响应用户请求。 @@ -135,7 +96,7 @@ CDN 的基本原理是广泛采用各种缓存服务器,将这些缓存服务 与不部署 CDN 服务相比,减少了 1 个节点,4 个步骤的访问。极大的提高的系统的响应速度。 -### 2.2. CDN 特点 +### CDN 特点 - **优点** - **本地 Cache 加速** - 提升访问速度,尤其含有大量图片和静态页面站点; @@ -151,13 +112,13 @@ CDN 的基本原理是广泛采用各种缓存服务器,将这些缓存服务 - 设置缓存失效时间(1 个小时,过期后同步数据)。 - 针对资源设置版本号。 -## 3. 反向代理缓存 +## 反向代理缓存 > **反向代理(Reverse Proxy)方式是指以代理服务器来接受 internet 上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给 internet 上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。**
    -### 3.1. 缓存原理 +### 缓存原理 反向代理位于应用服务器同一网络,处理所有对 WEB 服务器的请求。 @@ -170,7 +131,7 @@ CDN 的基本原理是广泛采用各种缓存服务器,将这些缓存服务 **反向代理缓存一般针对的是静态资源,而将动态资源请求转发到应用服务器处理**。常用的缓存应用服务器有 Varnish,Ngnix,Squid。 -### 3.2. 代理缓存比较 +### 代理缓存比较 常用的代理缓存有 Varnish,Squid,Ngnix,简单比较如下: @@ -180,7 +141,7 @@ CDN 的基本原理是广泛采用各种缓存服务器,将这些缓存服务 - Squid 功能全而大,适合于各种静态的文件缓存,一般会在前端挂一个 HAProxy 或 Ngnix 做负载均衡跑多个实例; - Nginx 采用第三方模块 ncache 做的缓冲,性能基本达到 Varnish,一般作为反向代理使用,可以实现简单的缓存。 -## 4. 进程内缓存 +## 进程内缓存 > 进程内缓存是指应用内部的缓存,标准的分布式系统,一般有多级缓存构成。本地缓存是离应用最近的缓存,一般可以将数据缓存到硬盘或内存。 @@ -189,7 +150,7 @@ CDN 的基本原理是广泛采用各种缓存服务器,将这些缓存服务 常见的本地缓存实现方案:HashMap、Guava Cache、Caffeine、Ehcache。 -### 4.1. ConcurrentHashMap +### ConcurrentHashMap 最简单的进程内缓存可以通过 JDK 自带的 `HashMap` 或 `ConcurrentHashMap` 实现。 @@ -197,7 +158,7 @@ CDN 的基本原理是广泛采用各种缓存服务器,将这些缓存服务 缺点:无法进行缓存淘汰,内存会无限制的增长。 -### 4.2. LRUHashMap +### LRUHashMap 可以通过继承 `LinkedHashMap` 来实现一个简单的 `LRUHashMap`。重写 `removeEldestEntry` 方法,即可完成一个简单的最近最少使用算法。 @@ -206,7 +167,7 @@ CDN 的基本原理是广泛采用各种缓存服务器,将这些缓存服务 - 不支持过期时间 - 不支持自动刷新 -### 4.3. Guava Cache +### Guava Cache 解决了 `LRUHashMap` 中的几个缺点。 @@ -216,13 +177,13 @@ Guava Cache 对于过期的 Entry 并没有马上过期(也就是并没有后台 直接通过查询,判断其是否满足刷新条件,进行刷新。 -### 4.4. Caffeine +### Caffeine Caffeine 实现了 W-TinyLFU(LFU+LRU 算法的变种),其命中率和读写吞吐量大大优于 Guava Cache。 其实现原理较复杂,可以参考[你应该知道的缓存进化史](https://juejin.im/post/5b7593496fb9a009b62904fa#comment)。 -### 4.5. 选择进程内缓存 +### 选择进程内缓存 | 比较项 | ConcurrentHashMap | LRUMap | Ehcache | Guava Cache | Caffeine | | ------------ | ----------------- | ------------------------ | ----------------------------- | ----------------------------------- | ----------------------- | @@ -241,7 +202,7 @@ Caffeine 实现了 W-TinyLFU(LFU+LRU 算法的变种),其命中率和读写吞 总结一下:如果不需要淘汰算法则选择 `ConcurrentHashMap`,如果需要淘汰算法和一些丰富的 API,推荐选择 Caffeine。 -## 5. 分布式缓存 +## 分布式缓存 > **分布式缓存解决了进程内缓存最大的问题:如果应用是分布式系统,节点之间无法共享彼此的进程内缓存**。 > @@ -252,7 +213,7 @@ Caffeine 实现了 W-TinyLFU(LFU+LRU 算法的变种),其命中率和读写吞 不同分布式缓存的实现原理往往有比较大的差异。本文主要针对 Memcached 和 Redis 进行说明。 -### 5.1. Memcache +### Memcache > [Memcache](https://memcached.org/) 是一个高性能,分布式内存对象缓存系统,通过在内存里维护一个统一的巨大的 hash 表,它能够用来存储各种格式的数据,包括图像、视频、文件以及数据库检索的结果等。 > @@ -297,7 +258,7 @@ Memcached 服务器之间彼此不通信,它的分布式能力是依赖客户 - **一致性哈希算法** - 可以很好的解决 **稳定性问题**,可以将所有的 **存储节点** 排列在 **首尾相接** 的 `Hash` 环上,每个 `key` 在计算 `Hash` 后会 **顺时针** 找到 **临接** 的 **存储节点** 存放。而当有节点 **加入** 或 **退出** 时,仅影响该节点在 `Hash` 环上 **顺时针相邻** 的 **后续节点**。 - **虚拟 Hash 槽算法** - 使用 **分散度良好** 的 **哈希函数** 把所有数据 **映射** 到一个 **固定范围** 的 **整数集合** 中,整数定义为 **槽**(`slot`),这个范围一般 **远远大于** 节点数。**槽** 是集群内 **数据管理** 和 **迁移** 的 **基本单位**。采用 **大范围槽** 的主要目的是为了方便 **数据拆分** 和 **集群扩展**。每个节点会负责 **一定数量的槽**。 -### 5.2. Redis +### Redis > Redis 是一个开源(BSD 许可)的,基于内存的,多数据结构存储系统。可以用作数据库、缓存和消息中间件。 > @@ -337,7 +298,7 @@ Memcached 服务器之间彼此不通信,它的分布式能力是依赖客户 - Redis 不保证强一致性,因为这会使得集群性能大大降低。 - Redis 是通过异步复制来实现最终一致性。 -### 5.3. 选择分布式缓存 +### 选择分布式缓存 不同的分布式缓存功能特性和实现原理方面有很大的差异,因此他们所适应的场景也有所不同。 @@ -357,9 +318,9 @@ Memcached 服务器之间彼此不通信,它的分布式能力是依赖客户 总结:如果服务对延迟比较敏感,Map/Set 数据也比较多的话,比较适合 Redis。如果服务需要放入缓存量的数据很大,对延迟又不是特别敏感的话,那就可以选择 Tair。 -## 6. 多级缓存 +## 多级缓存 -### 6.1. 使用进程内缓存 +### 使用进程内缓存 如果应用服务不是分布式系统,那么进程内缓存当然是缓存的首选方案。 @@ -388,9 +349,7 @@ Redis 用来存储热点数据,如果缓存不命中,则去查询数据库 1. 缓存服务如果挂了,这时应用只能访问数据库,容易造成缓存雪崩。 2. 访问分布式缓存服务会有一定的 I/O 以及序列化反序列化的开销,虽然性能很高,但是其终究没有在内存中查询快。 - - -### 6.2. 使用多级缓存 +### 使用多级缓存 单纯使用进程内缓存和分布式缓存都存在各自的不足。如果需要更高的性能以及更好的可用性,我们可以将缓存设计为多级结构。将最热的数据使用进程内缓存存储在内存中,进一步提升访问速度。 @@ -425,9 +384,9 @@ Redis 用来存储热点数据,如果缓存不命中,则去查询数据库 通过消息队列的发布、订阅机制,可以通知其他应用节点对进程内缓存进行更新。使用这种方案,即使消息队列服务挂了或不可靠,由于先执行了数据库更新,但进程内缓存过期,刷新缓存时,也能保证数据的最终一致性。 -## 7. 缓存问题 +## 缓存问题 -### 7.1. 缓存雪崩 +### 缓存雪崩 > **缓存雪崩是指缓存不可用或者大量缓存由于超时时间相同在同一时间段失效,大量请求直接访问数据库,数据库压力过大导致系统雪崩**。 @@ -438,7 +397,7 @@ Redis 用来存储热点数据,如果缓存不命中,则去查询数据库 - 缓存的过期时间可以取个随机值。比如以前是设置 10 分钟的超时时间,那每个 Key 都可以随机 8-13 分钟过期,尽量让不同 Key 的过期时间不同。 - 对数据库进行过载保护或应用层限流。 -### 7.2. 缓存穿透 +### 缓存穿透 > **缓存穿透是指:查询的数据在数据库中不存在,那么缓存中自然也不存在。所以,应用在缓存中查不到,则会去查询数据库。当这样的请求多了后,数据库的压力就会增大。** @@ -456,7 +415,7 @@ Redis 用来存储热点数据,如果缓存不命中,则去查询数据库 **制定一些规则过滤一些不可能存在的数据**。小数据用 BitMap,大数据可以用布隆过滤器,比如你的订单 ID 明显是在一个范围 1-1000,如果不是 1-1000 之内的数据那其实可以直接给过滤掉。 -### 7.3. 缓存击穿 +### 缓存击穿 对于某些 key 设置了过期时间,但是其是热点数据,如果某个 key 失效,可能大量的请求打过来,缓存未命中,然后去数据库访问,此时数据库访问量会急剧增加。 @@ -465,7 +424,7 @@ Redis 用来存储热点数据,如果缓存不命中,则去查询数据库 - **分布式锁** - 锁住热点数据的 key,避免大量线程同时访问同一个 key。 - **异步加载** - 可以对部分数据采取到期自动刷新的策略,而不是到期自动淘汰。淘汰其实也是为了数据的时效性,所以采用自动刷新也可以。 -### 7.4. 缓存更新 +### 缓存更新 一般来说缓存的更新有两种情况: @@ -504,7 +463,7 @@ Redis 用来存储热点数据,如果缓存不命中,则去查询数据库 如果需要数据库和缓存保证强一致性,则可以通过 2PC 或 Paxos 协议来实现。但是 2PC 太慢,而 Paxos 太复杂,所以如果不是非常重要的数据,不建议使用强一致性方案。 -### 7.5. 缓存预热 +### 缓存预热 缓存预热是指系统启动后,将常用的数据直接缓存。这样就可以避免用户请求的时候,先查询数据库,然后再更新缓存的问题。 @@ -514,13 +473,13 @@ Redis 用来存储热点数据,如果缓存不命中,则去查询数据库 - 数据量不大,可以在项目启动的时候自动进行加载。 - 定时刷新缓存。 -### 7.6. 缓存降级 +### 缓存降级 当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。 降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。 -## 8. 参考资料 +## 参考资料 - 《大型网站技术架构》 - [你应该知道的缓存进化史](https://link.juejin.im/?target=https%3A%2F%2Fjuejin.im%2Fpost%2F5b7593496fb9a009b62904fa) diff --git a/docs/theory/distributed-base-theory.md b/docs/theory/distributed-base-theory.md new file mode 100644 index 00000000..ec1cfdff --- /dev/null +++ b/docs/theory/distributed-base-theory.md @@ -0,0 +1,283 @@ +--- +title: 分布式基本原理 +categories: ['design', 'distributed'] +tags: ['design', 'distributed'] +date: 2018-07-09 17:43 +--- + +# 分布式基本原理 + +## 简介 + +### 性能 + +常见的性能指标: + +- **`吞吐量(Throughput)` - 是指系统在单位时间内处理请求的数量**。 +- **`响应时间(Response Time, RT)` - 是指系统对请求作出响应的时间**。 +- **并发用户数** - 是指系统可以同时承载的正常工作的在线用户数。 +- **`每秒查询率(QPS)`** - 是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。在因特网上,作为域名系统服务器的机器的性能经常用每秒查询率来衡量。对应 fetches/sec,即每秒的响应请求数,也即是最大吞吐能力。 +- **每秒事务数(Transaction Per Second, TPS)** - 衡量系统处理事务或交易的能力,即服务器对客户请求的能力,每秒处理的事务数,一般在 LoadRunner 上使用,设置事务,然后统计单位时间内系统可以成功完成多少个定义的事务。 +- **点击量(Hits Per Second)** - 服务器每秒收到的 HTTP 请求数。 + +``` +QPS = 并发量 / 平均响应时间 +``` + +吞吐量和响应时间这两个指标往往是矛盾的,追求高吞吐的系统,往往很难做到低响应时间,解释如下: + +- 在无并发的系统中,吞吐量为响应时间的倒数,例如响应时间为 10 ms,那么吞吐量为 100 req/s,因此高吞吐也就意味着低响应时间。 +- 但是在并发的系统中,由于一个请求在调用 I/O 资源的时候,需要进行等待。服务器端一般使用的是异步等待方式,即等待的请求被阻塞之后不需要一直占用 CPU 资源。这种方式能大大提高 CPU 资源的利用率,例如上面的例子中,单个请求在无并发的系统中响应时间为 10 ms,如果在并发的系统中,那么吞吐量将大于 100 req/s。因此为了追求高吞吐量,通常会提高并发程度。但是并发程度的增加,会导致请求的平均响应时间也增加,因为请求不能马上被处理,需要和其它请求一起进行并发处理,响应时间自然就会增高。 + +### 可用性 + +可用性指系统在面对各种异常时可以提供正常服务的能力。可以用系统可用时间占总时间的比值来衡量,4 个 9 的可用性表示系统 99.99% 的时间是可用的。 + +重要术语: + +- **异常** + - **服务器宕机** - 内存错误、服务器停电等都会导致服务器宕机,此时节点无法正常工作,称为不可用。服务器宕机会导致节点失去所有内存信息,因此需要将内存信息保存到持久化介质上。 + - **网络异常** - 有一种特殊的网络异常称为——**网络分区** ,即集群的所有节点被划分为多个区域,每个区域内部可以通信,但是区域之间无法通信。 + - **磁盘故障** - 磁盘故障是一种发生概率很高的异常。使用冗余机制,将数据存储到多台服务器。 +- **超时** - 在分布式系统中,一个请求除了成功和失败两种状态,还存在着超时状态。可以将服务器的操作设计为具有 **幂等性** ,即执行多次的结果与执行一次的结果相同。如果使用这种方式,当出现超时的时候,可以不断地重新请求直到成功。 + +### 一致性 + +可以从两个角度理解一致性:从客户端的角度,读写操作是否满足某种特性;从服务器的角度,多个数据副本之间是否一致。 + +### 可扩展性 + +指系统通过扩展集群服务器规模来提高性能的能力。理想的分布式系统需要实现“线性可扩展”,即随着集群规模的增加,系统的整体性能也会线性增加。 + +## 数据分布 + +分布式存储系统的数据分布在多个节点中,常用的数据分布方式有哈希分布和顺序分布。 + +数据库的水平切分(Sharding)也是一种分布式存储方法,下面的数据分布方法同样适用于 Sharding。 + +### 哈希分布 + +哈希分布就是将数据计算哈希值之后,按照哈希值分配到不同的节点上。例如有 N 个节点,数据的主键为 key,则将该数据分配的节点序号为:hash(key)%N。 + +传统的哈希分布算法存在一个问题:当节点数量变化时,也就是 N 值变化,那么几乎所有的数据都需要重新分布,将导致大量的数据迁移。 + +**一致性哈希** + +Distributed Hash Table(DHT):对于哈希空间 [0, 2n-1],将该哈希空间看成一个哈希环,将每个节点都配置到哈希环上。每个数据对象通过哈希取模得到哈希值之后,存放到哈希环中顺时针方向第一个大于等于该哈希值的节点上。 + +一致性哈希的优点是在增加或者删除节点时只会影响到哈希环中相邻的节点,例如下图中新增节点 X,只需要将数据对象 C 重新存放到节点 X 上即可,对于节点 A、B、D 都没有影响。 + +### 顺序分布 + +哈希分布式破坏了数据的有序性,顺序分布则不会。 + +顺序分布的数据划分为多个连续的部分,按数据的 ID 或者时间分布到不同节点上。例如下图中,User 表的 ID 范围为 1 \~ 7000,使用顺序分布可以将其划分成多个子表,对应的主键范围为 1 \~ 1000,1001 \~ 2000,...,6001 \~ 7000。 + +顺序分布的优点是可以充分利用每个节点的空间,而哈希分布很难控制一个节点存储多少数据。 + +但是顺序分布需要使用一个映射表来存储数据到节点的映射,这个映射表通常使用单独的节点来存储。当数据量非常大时,映射表也随着变大,那么一个节点就可能无法存放下整个映射表。并且单个节点维护着整个映射表的开销很大,查找速度也会变慢。为了解决以上问题,引入了一个中间层,也就是 Meta 表,从而分担映射表的维护工作。 + +### 负载均衡 + +衡量负载的因素很多,如 CPU、内存、磁盘等资源使用情况、读写请求数等。 + +分布式系统存储应当能够自动负载均衡,当某个节点的负载较高,将它的部分数据迁移到其它节点。 + +每个集群都有一个总控节点,其它节点为工作节点,由总控节点根据全局负载信息进行整体调度,工作节点定时发送心跳包(Heartbeat)将节点负载相关的信息发送给总控节点。 + +一个新上线的工作节点,由于其负载较低,如果不加控制,总控节点会将大量数据同时迁移到该节点上,造成该节点一段时间内无法工作。因此负载均衡操作需要平滑进行,新加入的节点需要较长的一段时间来达到比较均衡的状态。 + +## 分布式系统理论 + +### CAP + +**分布式系统不可能同时满足一致性(C:Consistency)、可用性(A:Availability)和分区容忍性(P:Partition Tolerance),最多只能同时满足其中两项。** + +
    + +
    + +**(一)一致性** + +**一致性指的是多个数据副本是否能保持一致的特性。** + +- 在一致性的条件下,系统在执行数据更新操作之后能够从一致性状态转移到另一个一致性状态。 +- 对系统的一个数据更新成功之后,如果所有用户都能够读取到最新的值,该系统就被认为具有强一致性。 + +**(二)可用性** + +**可用性指分布式系统在面对各种异常时可以提供正常服务的能力,可以用系统可用时间占总时间的比值来衡量**,如:4 个 9 的可用性表示系统 `99.99%` 的时间是可用的。 + +在可用性条件下,系统提供的服务一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。 + +**(三)分区容忍性** + +**网络分区指分布式系统中的节点被划分为多个区域,每个区域内部可以通信,但是区域之间无法通信。** + +在分区容忍性条件下,分布式系统在遇到任何网络分区故障的时候,仍然需要能对外提供一致性和可用性的服务,除非是整个网络环境都发生了故障。 + +**(四)权衡** + +在分布式系统中,分区容忍性必不可少,因为需要总是假设网络是不可靠的。因此,CAP 理论实际在是要在可用性和一致性之间做权衡。 + +可用性和一致性往往是冲突的,很难都使它们同时满足。在多个节点之间进行数据同步时, + +- 为了保证一致性(CP),就需要让所有节点下线成为不可用的状态,等待同步完成; +- 为了保证可用性(AP),在同步过程中允许读取所有节点的数据,但是数据可能不一致。 + +### BASE + +**BASE 是基本可用(Basically Available)、软状态(Soft State)和最终一致性(Eventually Consistent)三个短语的缩写。** + +BASE 理论是对 CAP 中一致性和可用性权衡的结果,它的理论的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。 + +
    + +
    + +- **基本可用** - **指分布式系统在出现故障的时候,保证核心可用,允许损失部分可用性**。例如,电商在做促销时,为了保证购物系统的稳定性,部分消费者可能会被引导到一个降级的页面。 +- **软状态** - 指允许系统中的数据存在中间状态,并认为该中间状态不会影响系统整体可用性。即**允许系统不同节点的数据副本之间进行同步的过程存在延时**。 +- **最终一致性** - **最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能达到一致的状态**。 + +ACID 要求强一致性,通常运用在传统的数据库系统上。而 BASE 要求最终一致性,通过牺牲强一致性来达到可用性,通常运用在大型分布式系统中。 + +在实际的分布式场景中,不同业务单元和组件对一致性的要求是不同的,因此 ACID 和 BASE 往往会结合在一起使用。 + +## 共识性问题 + +### Paxos + +用于达成共识性问题,即对多个节点产生的值,该算法能保证只选出唯一一个值。 + +主要有三类节点: + +- 提议者(Proposer):提议一个值; +- 接受者(Acceptor):对每个提议进行投票; +- 告知者(Learner):被告知投票的结果,不参与投票过程。 + +算法需要满足 safety 和 liveness 两方面的约束要求(实际上这两个基础属性是大部分分布式算法都该考虑的): + +- safety:保证决议结果是对的,无歧义的,不会出现错误情况。 + - 决议(value)只有在被 proposers 提出的 proposal 才能被最终批准; + - 在一次执行实例中,只批准(chosen)一个最终决议,意味着多数接受(accept)的结果能成为决议; +- liveness:保证决议过程能在有限时间内完成。 + - 决议总会产生,并且 learners 能获得被批准(chosen)的决议。 + +基本过程包括 proposer 提出提案,先争取大多数 acceptor 的支持,超过一半支持时,则发送结案结果给所有人进行确认。一个潜在的问题是 proposer 在此过程中出现故障,可以通过超时机制来解决。极为凑巧的情况下,每次新的一轮提案的 proposer 都恰好故障,系统则永远无法达成一致(概率很小)。 + +Paxos 能保证在超过 $1/2$ 的正常节点存在时,系统能达成共识。 + +#### 单个提案者+多接收者 + +如果系统中限定只有某个特定节点是提案者,那么一致性肯定能达成(只有一个方案,要么达成,要么失败)。提案者只要收到了来自多数接收者的投票,即可认为通过,因为系统中不存在其他的提案。 + +但一旦提案者故障,则系统无法工作。 + +#### 多个提案者+单个接收者 + +限定某个节点作为接收者。这种情况下,共识也很容易达成,接收者收到多个提案,选第一个提案作为决议,拒绝掉后续的提案即可。 + +缺陷也是容易发生单点故障,包括接收者故障或首个提案者节点故障。 + +以上两种情形其实类似主从模式,虽然不那么可靠,但因为原理简单而被广泛采用。 + +当提案者和接收者都推广到多个的情形,会出现一些挑战。 + +#### 多个提案者+多个接收者 + +既然限定单提案者或单接收者都会出现故障,那么就得允许出现多个提案者和多个接收者。问题一下子变得复杂了。 + +一种情况是同一时间片段(如一个提案周期)内只有一个提案者,这时可以退化到单提案者的情形。需要设计一种机制来保障提案者的正确产生,例如按照时间、序列、或者大家猜拳(出一个数字来比较)之类。考虑到分布式系统要处理的工作量很大,这个过程要尽量高效,满足这一条件的机制非常难设计。 + +另一种情况是允许同一时间片段内可以出现多个提案者。那同一个节点可能收到多份提案,怎么对他们进行区分呢?这个时候采用只接受第一个提案而拒绝后续提案的方法也不适用。很自然的,提案需要带上不同的序号。节点需要根据提案序号来判断接受哪个。比如接受其中序号较大(往往意味着是接受新提出的,因为旧提案者故障概率更大)的提案。 + +如何为提案分配序号呢?一种可能方案是每个节点的提案数字区间彼此隔离开,互相不冲突。为了满足递增的需求可以配合用时间戳作为前缀字段。 + +此外,提案者即便收到了多数接收者的投票,也不敢说就一定通过。因为在此过程中系统可能还有其它的提案。 + +### Raft + +Raft 算法是 Paxos 算法的一种简化实现。 + +包括三种角色:leader、candidate 和 follower,其基本过程为: + +- **Leader 选举** - 每个 candidate 随机经过一定时间都会提出选举方案,最近阶段中得票最多者被选为 leader; +- **同步 log** - leader 会找到系统中 log 最新的记录,并强制所有的 follower 来刷新到这个记录; + +_注:此处 log 并非是指日志消息,而是各种事件的发生记录。_ + +#### 单个 Candidate 的竞选 + +有三种节点:Follower、Candidate 和 Leader。Leader 会周期性的发送心跳包给 Follower。每个 Follower 都设置了一个随机的竞选超时时间,一般为 150ms\~300ms,如果在这个时间内没有收到 Leader 的心跳包,就会变成 Candidate,进入竞选阶段。 + +- 下图表示一个分布式系统的最初阶段,此时只有 Follower,没有 Leader。Follower A 等待一个随机的竞选超时时间之后,没收到 Leader 发来的心跳包,因此进入竞选阶段。 + +
    + +
    + +- 此时 A 发送投票请求给其它所有节点。 + +
    + +
    + +- 其它节点会对请求进行回复,如果超过一半的节点回复了,那么该 Candidate 就会变成 Leader。 + +
    + +
    + +- 之后 Leader 会周期性地发送心跳包给 Follower,Follower 接收到心跳包,会重新开始计时。 + +
    + +
    + +#### 多个 Candidate 竞选 + +- 如果有多个 Follower 成为 Candidate,并且所获得票数相同,那么就需要重新开始投票,例如下图中 Candidate B 和 Candidate D 都获得两票,因此需要重新开始投票。 + +
    + +
    + +- 当重新开始投票时,由于每个节点设置的随机竞选超时时间不同,因此能下一次再次出现多个 Candidate 并获得同样票数的概率很低。 + +
    + +
    + +#### 同步日志 + +- 来自客户端的修改都会被传入 Leader。注意该修改还未被提交,只是写入日志中。 + +
    + +
    + +- Leader 会把修改复制到所有 Follower。 + +
    + +
    + +- Leader 会等待大多数的 Follower 也进行了修改,然后才将修改提交。 + +
    + +
    + +- 此时 Leader 会通知的所有 Follower 让它们也提交修改,此时所有节点的值达成一致。 + +
    + +
    + +## 参考资料 + +- 杨传辉. 大规模分布式存储系统: 原理解析与架构实战[M]. 机械工业出版社, 2013. +- [区块链技术指南](https://www.gitbook.com/book/yeasy/blockchain_guide/details) +- [NEAT ALGORITHMS - PAXOS](http://harry.me/blog/2014/12/27/neat-algorithms-paxos/) +- [Raft: Understandable Distributed Consensus](http://thesecretlivesofdata.com/raft) +- [Paxos By Example](https://angus.nyc/2012/paxos-by-example/) diff --git a/docs/theory/distributed-id-theory.md b/docs/theory/distributed-id-theory.md new file mode 100644 index 00000000..58bccf27 --- /dev/null +++ b/docs/theory/distributed-id-theory.md @@ -0,0 +1,276 @@ +# 分布式 ID 基本原理 + +> 传统数据库软件开发中,主键自动生成技术是基本需求。而各个数据库对于该需求也提供了相应的支持,比如 MySQL 的自增键,Oracle 的自增序列等。 +> +> 数据分片后,不同数据节点生成全局唯一主键是非常棘手的问题。同一个逻辑表内的不同实际表之间的自增键由于无法互相感知而产生重复主键。 虽然可通过约束自增主键初始值和步长的方式避免碰撞,但需引入额外的运维规则,使解决方案缺乏完整性和可扩展性。 +> +> 为此,需要使用分布式 ID 来解决此问题。本文总结业界常用的分布式 ID 解决方案。 + +## 简介 + +首先,分布式 ID 应该具备哪些特性呢? + +1. **全局唯一性** - 不能出现重复的 ID 号,既然是唯一标识,这是最基本的要求。 +2. **趋势递增** - 在 MySQL InnoDB 引擎中使用的是聚集索引,由于多数 RDBMS 使用 B-tree 的数据结构来存储索引数据,在主键的选择上面我们应该尽量使用有序的主键保证写入性能。 +3. **单调递增** - 保证下一个 ID 一定大于上一个 ID,例如事务版本号、IM 增量消息、排序等特殊需求。 +4. **信息安全** - 如果 ID 是连续的,恶意用户的扒取工作就非常容易做了,直接按照顺序下载指定 URL 即可;如果是订单号就更危险了,竞对可以直接知道我们一天的单量。所以在一些应用场景下,会需要 ID 无规则、不规则。 + +## UUID + +UUID 是通用唯一识别码(Universally Unique Identifier)的缩写,开放软件基金会(OSF)规范定义了包括网卡 MAC 地址、时间戳、名字空间(Namespace)、随机或伪随机数、时序等元素。利用这些元素来生成 UUID。 + +UUID 是由 128 位二进制组成,一般转换成十六进制,然后用 String 表示。在 java 中有个 UUID 类,在他的注释中我们看见这里有 4 种不同的 UUID 的生成策略: + +- random - 基于随机数生成 UUID,由于 Java 中的随机数是伪随机数,其重复的概率是可以被计算出来的。这个一般我们用下面的代码获取基于随机数的 UUID: + +```java +String id = UUID.randomUUID().toString(); +``` + +- time-based - 基于时间的 UUID,这个一般是通过当前时间,随机数,和本地 Mac 地址来计算出来,自带的 JDK 包并没有这个算法的我们在一些 UUIDUtil 中,比如我们的 log4j.core.util,会重新定义 UUID 的高位和低位。 + +```java + public static UUID getTimeBasedUuid() { + long time = System.currentTimeMillis() * 10000L + 122192928000000000L + (long)(COUNT.incrementAndGet() % 10000); + long timeLow = (time & 4294967295L) << 32; + long timeMid = (time & 281470681743360L) >> 16; + long timeHi = (time & 1152640029630136320L) >> 48; + long most = timeLow | timeMid | 4096L | timeHi; + return new UUID(most, LEAST); + } +``` + +- DCE security - DCE 安全的 UUID。 + +- name-based - 基于名字的 UUID,通过计算名字和名字空间的 MD5 来计算 UUID。 + +### UUID 的优点 + +- 通过本地生成,没有经过网络 I/O,性能较快 + +### UUID 的缺点 + +- **长度过长** - UUID 太长,16 字节 128 位,通常以 36 长度的字符串表示,很多场景不适用。例如:Mysql 官方明确建议主键越短越好,36 个字符长度的 UUID 不符合要求。 +- **信息不安全** - 基于 MAC 地址生成 UUID 的算法可能会造成 MAC 地址泄露,这个漏洞曾被用于寻找梅丽莎病毒的制作者位置。 +- **无序性** - 不能生成递增有序的数字。这对于一些特定场景不利。例如:如果作为数据库主键,在 InnoDB 引擎下,UUID 的无序性可能会引起数据位置频繁变动,严重影响性能。 + +### 适用场景 + +UUID 的适用场景可以为不需要担心过多的空间占用,以及不需要生成有递增趋势的数字。在 Log4j 里 `UuidPatternConverter` 中加入了 UUID 来标识每一条日志。 + +## 数据库自增键 + +提到自增键,最先想到的肯定是直接使用数据库自增键。_各数据库对于该需求也提供了相应的支持,比如 MySQL 的自增键,Oracle 的自增序列等_。 + +### 优点 + +- 非常简单,利用现有数据库的功能实现,成本小,有 DBA 专业维护 +- 有序递增 +- 方便排序和分页 + +### 缺点 + +- 强依赖数据库,当数据库异常时整个系统不可用。配置主从复制可以尽可能的增加可用性,但是数据一致性在特殊情况下难以保证。主从切换时的不一致可能会导致重复发号。 +- 生成 ID 性能瓶颈限制在单台 MySQL 的读写性能。 +- 简单递增容易被其他人分析利用 + - 比如根据用户 ID 来分析注册人数,猜测出服务的一个大概状况。 + +### 适用场景 + +数据量不大,并发不高的场景。 + +### 改进 + +对于 MySQL 性能问题,可用如下方案解决:在分布式系统中我们可以多部署几台机器,每台机器设置不同的初始值,且步长和机器数相等。比如有两台机器。设置步长 step 为 2,TicketServer1 的初始值为 1(1,3,5,7,9,11…)、TicketServer2 的初始值为 2(2,4,6,8,10…)。这是 Flickr 团队在 2010 年撰文介绍的一种主键生成策略([Ticket Servers: Distributed Unique Primary Keys on the Cheap ](http://code.flickr.net/2010/02/08/ticket-servers-distributed-unique-primary-keys-on-the-cheap/))。如下所示,为了实现上述方案分别设置两台机器对应的参数,TicketServer1 从 1 开始发号,TicketServer2 从 2 开始发号,两台机器每次发号之后都递增 2。 + +``` +TicketServer1: +auto-increment-increment = 2 +auto-increment-offset = 1 + +TicketServer2: +auto-increment-increment = 2 +auto-increment-offset = 2 +``` + +![image](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2017/6d2c9ec8.png) + +这种架构存在以下缺点: + +- 系统水平扩展比较困难,比如定义好了步长和机器台数之后,如果要添加机器该怎么做?假设现在只有一台机器发号是 1,2,3,4,5(步长是 1),这个时候需要扩容机器一台。可以这样做:把第二台机器的初始值设置得比第一台超过很多,比如 14(假设在扩容时间之内第一台不可能发到 14),同时设置步长为 2,那么这台机器下发的号码都是 14 以后的偶数。然后摘掉第一台,把 ID 值保留为奇数,比如 7,然后修改第一台的步长为 2。让它符合我们定义的号段标准,对于这个例子来说就是让第一台以后只能产生奇数。扩容方案看起来复杂吗?貌似还好,现在想象一下如果我们线上有 100 台机器,这个时候要扩容该怎么做?简直是噩梦。所以系统水平扩展方案复杂难以实现。 +- ID 没有了单调递增的特性,只能趋势递增,这个缺点对于一般业务需求不是很重要,可以容忍。 +- 数据库压力还是很大,每次获取 ID 都得读写一次数据库,只能靠堆机器来提高性能。 + +## 利用 Redis 生成键 + +Redis 中有两个命令 `Incr`,`IncrBy`。 + +因为 Redis 是单线程的,所以能保证原子性。 + +### 优点 + +- 性能比数据库好 +- 能满足有序递增 + +### 缺点 + +- 由于 Redis 是内存数据库,即使支持持久化(AOF 和 RDB),但是依然可能丢失数据,造成 ID 重复。 +- 依赖于 Redis 生成键,如果 Redis 服务不稳定,会影响 ID 生成。 + +### 适用场景 + +Redis 性能比数据库好,但是有可能会出现 ID 重复和不稳定。如果可以接受,那么就可以使用这种方案。 + +## 雪花算法(Snowflake) + +雪花算法(Snowflake)是由 Twitter 公布的分布式主键生成算法,**它会生成一个 `64 bit` 的整数**,可以保证不同进程主键的不重复性,以及相同进程主键的有序性。 + +在同一个进程中,它首先是通过时间位保证不重复,如果时间相同则是通过序列位保证。 同时由于时间位是单调递增的,且各个服务器如果大体做了时间同步,那么生成的主键在分布式环境可以认为是总体有序的,这就保证了对索引字段的插入的高效性。 + +### 基本原理 + +#### 键的组成 + +使用**雪花算法生成的主键,二进制表示形式包含 4 部分**,从高位到低位分表为:1bit 符号位、41bit 时间戳位、10bit 工作进程位以及 12bit 序列号位。 + +- **符号位(1bit)** + +预留的符号位,恒为零。 + +- **时间戳位(41bit)** + +41 位的时间戳可以容纳的毫秒数是 2 的 41 次幂,一年所使用的毫秒数是:`365 * 24 * 60 * 60 * 1000`。通过计算可知: + +```java +Math.pow(2, 41) / (365 * 24 * 60 * 60 * 1000L); +``` + +结果约等于 69.73 年。ShardingSphere 的雪花算法的时间纪元从 2016 年 11 月 1 日零点开始,可以使用到 2086 年,相信能满足绝大部分系统的要求。 + +- **工作进程位(10bit)** + +该标志在 Java 进程内是唯一的,如果是分布式应用部署应保证每个工作进程的 id 是不同的。该值默认为 0,可通过属性设置。 + +- **序列号位(12bit)** + +该序列是用来在同一个毫秒内生成不同的 ID。如果在这个毫秒内生成的数量超过 4096(2 的 12 次幂),那么生成器会等待到下个毫秒继续生成。 + +雪花算法主键的详细结构见下图: + +![雪花算法](https://shardingsphere.apache.org/document/current/img/sharding/snowflake_cn_v2.png) + +#### 时钟回拨 + +服务器时钟回拨会导致产生重复序列,因此默认分布式主键生成器提供了一个最大容忍的时钟回拨毫秒数。 如果时钟回拨的时间超过最大容忍的毫秒数阈值,则程序报错;如果在可容忍的范围内,默认分布式主键生成器会等待时钟同步到最后一次主键生成的时间后再继续工作。 最大容忍的时钟回拨毫秒数的默认值为 0,可通过属性设置。 + +#### 灵活定制 + +上面只是一个将 `64bit` 划分的标准,当然也不一定这么做,可以根据不同业务的具体场景来划分,比如下面给出一个业务场景: + +- 服务目前 QPS10 万,预计几年之内会发展到百万。 +- 当前机器三地部署,上海,北京,深圳都有。 +- 当前机器 10 台左右,预计未来会增加至百台。 + +这个时候我们根据上面的场景可以再次合理的划分 62bit,QPS 几年之内会发展到百万,那么每毫秒就是千级的请求,目前 10 台机器那么每台机器承担百级的请求,为了保证扩展,后面的循环位可以限制到 1024,也就是 2^10,那么循环位 10 位就足够了。 + +机器三地部署我们可以用 3bit 总共 8 来表示机房位置,当前的机器 10 台,为了保证扩展到百台那么可以用 7bit 128 来表示,时间位依然是 41bit,那么还剩下 64-10-3-7-41-1 = 2bit,还剩下 2bit 可以用来进行扩展。 + +![](https://user-gold-cdn.xitu.io/2018/9/29/16624909d2007c22?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) + +### 优点 + +- 生成的 ID 都是趋势递增的。 +- 不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成 ID 的性能也是非常高的。 +- 可以根据自身业务特性分配 bit 位,非常灵活。 + +### 缺点 + +- 强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。 + +### 适用场景 + +当我们需要无序不能被猜测的 ID,并且需要一定高性能,且需要 long 型,那么就可以使用我们雪花算法。比如常见的订单 ID,用雪花算法别人就无法猜测你每天的订单量是多少。 + +### 防止时钟回拨 + +雪花算法是强依赖于时间的,而如果机器时间发生回拨,有可能会生成重复的 ID。 + +我们可以针对算法做一些优化,来防止时钟回拨生成重复 ID。 + +用当前时间和上一次的时间进行判断,如果当前时间小于上一次的时间那么肯定是发生了回拨。普通的算法会直接抛出异常,这里我们可以对其进行优化,一般分为两个情况: + +- 如果时间回拨时间较短,比如配置 `5ms` 以内,那么可以直接等待一定的时间,让机器的时间追上来。 +- 如果时间的回拨时间较长,我们不能接受这么长的阻塞等待,那么又有两个策略: + - 直接拒绝,抛出异常。打日志,通知 RD 时钟回滚。 + - 利用扩展位。上面我们讨论过,不同业务场景位数可能用不到那么多比特位,那么我们可以把扩展位数利用起来。比如:当这个时间回拨比较长的时候,我们可以不需要等待,直接在扩展位加 1。两位的扩展位允许我们有三次大的时钟回拨,一般来说就够了,如果其超过三次我们还是选择抛出异常,打日志。 + +## Leaf + +> 美团提供了一种分布式 ID 解决方案 Leaf,其本质可以视为数据库分段+服务缓存 ID。 +> +> 详情可以参考 [Leaf——美团点评分布式 ID 生成系统](https://tech.meituan.com/2017/04/21/mt-leaf.html) + +### 基本原理 + +使用数据库生成 ID,但是做了如下改进: + +原方案每次获取 ID 都得读写一次数据库,造成数据库压力大。改为利用 proxy server 批量获取,每次获取一个 segment(step 决定大小)号段的值。用完之后再去数据库获取新的号段,可以大大的减轻数据库的压力。 - 各个业务不同的发号需求用 biz_tag 字段来区分,每个 biz-tag 的 ID 获取相互隔离,互不影响。如果以后有性能需求需要对数据库扩容,不需要上述描述的复杂的扩容操作,只需要对 biz_tag 分库分表就行。 + +数据库表设计如下: + +```sql ++-------------+--------------+------+-----+-------------------+-----------------------------+ +| Field | Type | Null | Key | Default | Extra | ++-------------+--------------+------+-----+-------------------+-----------------------------+ +| biz_tag | varchar(128) | NO | PRI | | | +| max_id | bigint(20) | NO | | 1 | | +| step | int(11) | NO | | NULL | | +| desc | varchar(256) | YES | | NULL | | +| update_time | timestamp | NO | | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP | ++-------------+--------------+------+-----+-------------------+-----------------------------+ +``` + +重要字段说明: + +- `biz_tag` 用来区分业务 +- `max_id` 表示该 `biz_tag` 目前所被分配的 ID 号段的最大值 +- `step` 表示每次分配的号段长度。原来获取 ID 每次都需要写数据库,现在只需要把 `step` 设置得足够大,比如 1000。那么只有当 1000 个号被消耗完了之后才会去重新读写一次数据库。读写数据库的频率从 1 减小到了 1/step。 + +大致架构如下图所示: + +![image](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2017/5e4ff128.png) + +test_tag 在第一台 Leaf 机器上是 `1~1000` 的号段,当这个号段用完时,会去加载另一个长度为 step=1000 的号段,假设另外两台号段都没有更新,这个时候第一台机器新加载的号段就应该是 `3001~4000`。同时数据库对应的 biz_tag 这条数据的 max_id 会从 3000 被更新成 4000,更新号段的 SQL 语句如下: + +```sql +Begin +UPDATE table SET max_id=max_id+step WHERE biz_tag=xxx +SELECT tag, max_id, step FROM table WHERE biz_tag=xxx +Commit +``` + +### 优点 + +- 比数据库自增键性能高 +- 能保证键趋势递增。 +- 如果数据库宕机,由于 proxServer 有缓存,依然可以坚持一段时间。 + +### 缺点 + +- 和主键递增一样,容易被人猜测。 +- 数据库宕机后,虽然能支撑一段时间,但是仍然会造成系统不可用。 + +### 适用场景 + +需要趋势递增,并且 ID 大小可控制的,可以使用这套方案。 + +当然这个方案也可以通过一些手段避免被人猜测,把 ID 变成是无序的,比如把我们生成的数据是一个递增的 long 型,把这个 Long 分成几个部分,比如可以分成几组三位数,几组四位数,然后在建立一个映射表,将我们的数据变成无序。 + +## 参考资料 + +- [百度分布式 ID](https://github.com/baidu/uid-generator/blob/master/README.zh_cn.md) +- [如果再有人问你分布式 ID,这篇文章丢给他](https://juejin.im/post/5bb0217ef265da0ac2567b42) +- [理解分布式 id 生成算法 SnowFlake](https://segmentfault.com/a/1190000011282426) +- [Leaf——美团点评分布式 ID 生成系统](https://tech.meituan.com/2017/04/21/mt-leaf.html) +- [UUID 规范](https://www.ietf.org/rfc/rfc4122.txt) +- [ShardingSphere 分布式主键](https://shardingsphere.apache.org/document/current/cn/features/sharding/other-features/key-generator/) diff --git a/docs/theory/distributed-interview.md b/docs/theory/distributed-interview.md new file mode 100644 index 00000000..f5bfa3e1 --- /dev/null +++ b/docs/theory/distributed-interview.md @@ -0,0 +1,501 @@ +--- +title: 分布式技术面试题 +categories: ['design', 'distributed'] +tags: ['design', 'distributed'] +date: 2018-07-10 16:02 +--- + +# 分布式技术面试题 + + + +- [1. 分布式缓存](#1-分布式缓存) + - [1.1. Redis 有什么数据类型?分别用于什么场景?](#11-redis-有什么数据类型分别用于什么场景) + - [1.2. Redis 的主从复制是如何实现的?](#12-redis-的主从复制是如何实现的) + - [1.3. Redis 的 key 是如何寻址的?](#13-redis-的-key-是如何寻址的) + - [1.4. Redis 的集群模式是如何实现的?](#14-redis-的集群模式是如何实现的) + - [1.5. Redis 如何实现分布式锁?ZooKeeper 如何实现分布式锁?比较二者优劣?](#15-redis-如何实现分布式锁zookeeper-如何实现分布式锁比较二者优劣) + - [1.6. Redis 的持久化方式?有什么优缺点?持久化实现原理?](#16-redis-的持久化方式有什么优缺点持久化实现原理) + - [1.7. Redis 过期策略有哪些?](#17-redis-过期策略有哪些) + - [1.8. Redis 和 Memcached 有什么区别?](#18-redis-和-memcached-有什么区别) + - [1.9. 为什么单线程的 Redis 性能反而优于多线程的 Memcached?](#19-为什么单线程的-redis-性能反而优于多线程的-memcached) +- [2. 分布式消息队列(MQ)](#2-分布式消息队列mq) + - [2.1. 为什么使用 MQ?](#21-为什么使用-mq) + - [2.2. 如何保证 MQ 的高可用?](#22-如何保证-mq-的高可用) + - [2.3. MQ 有哪些常见问题?如何解决这些问题?](#23-mq-有哪些常见问题如何解决这些问题) + - [2.4. Kafka, ActiveMQ, RabbitMQ, RocketMQ 各有什么优缺点?](#24-kafka-activemq-rabbitmq-rocketmq-各有什么优缺点) +- [3. 分布式服务(RPC)](#3-分布式服务rpc) + - [3.1. Dubbo 的实现过程?](#31-dubbo-的实现过程) + - [3.2. Dubbo 负载均衡策略有哪些?](#32-dubbo-负载均衡策略有哪些) + - [3.3. Dubbo 集群容错策略 ?](#33-dubbo-集群容错策略-) + - [3.4. 动态代理策略?](#34-动态代理策略) + - [3.5. Dubbo 支持哪些序列化协议?Hessian?Hessian 的数据结构?](#35-dubbo-支持哪些序列化协议hessianhessian-的数据结构) + - [3.6. Protoco Buffer 是什么?](#36-protoco-buffer-是什么) + - [3.7. 注册中心挂了可以继续通信吗?](#37-注册中心挂了可以继续通信吗) + - [3.8. ZooKeeper 原理是什么?ZooKeeper 有什么用?](#38-zookeeper-原理是什么zookeeper-有什么用) + - [3.9. Netty 有什么用?NIO/BIO/AIO 有什么用?有什么区别?](#39-netty-有什么用niobioaio-有什么用有什么区别) + - [3.10. 为什么要进行系统拆分?拆分不用 Dubbo 可以吗?](#310-为什么要进行系统拆分拆分不用-dubbo-可以吗) + - [3.11. Dubbo 和 Thrift 有什么区别?](#311-dubbo-和-thrift-有什么区别) + + + +## 1. 分布式缓存 + +### 1.1. Redis 有什么数据类型?分别用于什么场景? + +| 数据类型 | 可以存储的值 | 操作 | +| -------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------- | +| STRING | 字符串、整数或者浮点数 | 对整个字符串或者字符串的其中一部分执行操作
    对整数和浮点数执行自增或者自减操作 | +| LIST | 列表 | 从两端压入或者弹出元素
    读取单个或者多个元素
    进行修剪,只保留一个范围内的元素 | +| SET | 无序集合 | 添加、获取、移除单个元素
    检查一个元素是否存在于集合中
    计算交集、并集、差集
    从集合里面随机获取元素 | +| HASH | 包含键值对的无序散列表 | 添加、获取、移除单个键值对
    获取所有键值对
    检查某个键是否存在 | +| ZSET | 有序集合 | 添加、获取、删除元素
    根据分值范围或者成员来获取元素
    计算一个键的排名 | + +> [What Redis data structures look like](https://redislabs.com/ebook/part-1-getting-started/chapter-1-getting-to-know-redis/1-2-what-redis-data-structures-look-like/) + +### 1.2. Redis 的主从复制是如何实现的? + +1. 从服务器连接主服务器,发送 SYNC 命令; +2. 主服务器接收到 SYNC 命名后,开始执行 BGSAVE 命令生成 RDB 文件并使用缓冲区记录此后执行的所有写命令; +3. 主服务器 BGSAVE 执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令; +4. 从服务器收到快照文件后丢弃所有旧数据,载入收到的快照; +5. 主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令; +6. 从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令; + +### 1.3. Redis 的 key 是如何寻址的? + +#### 背景 + +(1)redis 中的每一个数据库,都由一个 redisDb 的结构存储。其中: + +- redisDb.id 存储着 redis 数据库以整数表示的号码。 +- redisDb.dict 存储着该库所有的键值对数据。 +- redisDb.expires 保存着每一个键的过期时间。 + +(2)当 redis 服务器初始化时,会预先分配 16 个数据库(该数量可以通过配置文件配置),所有数据库保存到结构 redisServer 的一个成员 redisServer.db 数组中。当我们选择数据库 select number 时,程序直接通过 redisServer.db[number] 来切换数据库。有时候当程序需要知道自己是在哪个数据库时,直接读取 redisDb.id 即可。 + +(3)redis 的字典使用哈希表作为其底层实现。dict 类型使用的两个指向哈希表的指针,其中 0 号哈希表(ht[0])主要用于存储数据库的所有键值,而 1 号哈希表主要用于程序对 0 号哈希表进行 rehash 时使用,rehash 一般是在添加新值时会触发,这里不做过多的赘述。所以 redis 中查找一个 key,其实就是对进行该 dict 结构中的 ht[0] 进行查找操作。 + +(4)既然是哈希,那么我们知道就会有哈希碰撞,那么当多个键哈希之后为同一个值怎么办呢?redis 采取链表的方式来存储多个哈希碰撞的键。也就是说,当根据 key 的哈希值找到该列表后,如果列表的长度大于 1,那么我们需要遍历该链表来找到我们所查找的 key。当然,一般情况下链表长度都为是 1,所以时间复杂度可看作 o(1)。 + +#### 寻址 key 的步骤 + +1. 当拿到一个 key 后,redis 先判断当前库的 0 号哈希表是否为空,即:if (dict->ht[0].size == 0)。如果为 true 直接返回 NULL。 +2. 判断该 0 号哈希表是否需要 rehash,因为如果在进行 rehash,那么两个表中者有可能存储该 key。如果正在进行 rehash,将调用一次\_dictRehashStep 方法,\_dictRehashStep 用于对数据库字典、以及哈希键的字典进行被动 rehash,这里不作赘述。 +3. 计算哈希表,根据当前字典与 key 进行哈希值的计算。 +4. 根据哈希值与当前字典计算哈希表的索引值。 +5. 根据索引值在哈希表中取出链表,遍历该链表找到 key 的位置。一般情况,该链表长度为 1。 +6. 当 ht[0] 查找完了之后,再进行了次 rehash 判断,如果未在 rehashing,则直接结束,否则对 ht[1]重复 345 步骤。 + +### 1.4. Redis 的集群模式是如何实现的? + +Redis Cluster 是 Redis 的分布式解决方案,在 Redis 3.0 版本正式推出的。 + +Redis Cluster 去中心化,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。 + +#### Redis Cluster 节点分配 + +Redis Cluster 特点: + +1. 所有的 redis 节点彼此互联(PING-PONG 机制),内部使用二进制协议优化传输速度和带宽。 +2. 节点的 fail 是通过集群中超过半数的节点检测失效时才生效。 +3. 客户端与 redis 节点直连,不需要中间 proxy 层。客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。 +4. redis-cluster 把所有的物理节点映射到[0-16383] 哈希槽 (hash slot)上(不一定是平均分配),cluster 负责维护 node<->slot<->value。 +5. Redis 集群预分好 16384 个桶,当需要在 Redis 集群中放置一个 key-value 时,根据 CRC16(key) mod 16384 的值,决定将一个 key 放到哪个桶中。 + +#### Redis Cluster 主从模式 + +Redis Cluster 为了保证数据的高可用性,加入了主从模式。 + +一个主节点对应一个或多个从节点,主节点提供数据存取,从节点则是从主节点拉取数据备份。当这个主节点挂掉后,就会有这个从节点选取一个来充当主节点,从而保证集群不会挂掉。所以,在集群建立的时候,一定要为每个主节点都添加了从节点。 + +#### Redis Sentinel + +Redis Sentinel 用于管理多个 Redis 服务器,它有三个功能: + +- **监控(Monitoring)** - Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。 +- **提醒(Notification)** - 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。 +- **自动故障迁移(Automatic failover)** - 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。 + +Redis 集群中应该有奇数个节点,所以至少有三个节点。 + +哨兵监控集群中的主服务器出现故障时,需要根据 quorum 选举出一个哨兵来执行故障转移。选举需要 majority,即大多数哨兵是运行的(2 个哨兵的 majority=2,3 个哨兵的 majority=2,5 个哨兵的 majority=3,4 个哨兵的 majority=2)。 + +假设集群仅仅部署 2 个节点 + +``` ++----+ +----+ +| M1 |---------| R1 | +| S1 | | S2 | ++----+ +----+ +``` + +如果 M1 和 S1 所在服务器宕机,则哨兵只有 1 个,无法满足 majority 来进行选举,就不能执行故障转移。 + +### 1.5. Redis 如何实现分布式锁?ZooKeeper 如何实现分布式锁?比较二者优劣? + +分布式锁的三种实现: + +- 基于数据库实现分布式锁; +- 基于缓存(Redis 等)实现分布式锁; +- 基于 Zookeeper 实现分布式锁; + +#### 数据库实现 + +#### Redis 实现 + +1. 获取锁的时候,使用 setnx 加锁,并使用 expire 命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的 value 值为一个随机生成的 UUID,通过此在释放锁的时候进行判断。 +2. 获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。 +3. 释放锁的时候,通过 UUID 判断是不是该锁,若是该锁,则执行 delete 进行锁释放。 + +#### ZooKeeper 实现 + +1. 创建一个目录 mylock; +2. 线程 A 想获取锁就在 mylock 目录下创建临时顺序节点; +3. 获取 mylock 目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁; +4. 线程 B 获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点; +5. 线程 A 处理完,删除自己的节点,线程 B 监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。 + +#### 实现对比 + +ZooKeeper 具备高可用、可重入、阻塞锁特性,可解决失效死锁问题。 +但 ZooKeeper 因为需要频繁的创建和删除节点,性能上不如 Redis 方式。 + +### 1.6. Redis 的持久化方式?有什么优缺点?持久化实现原理? + +#### RDB 快照(snapshot) + +将存在于某一时刻的所有数据都写入到硬盘中。 + +##### 快照的原理 + +在默认情况下,Redis 将数据库快照保存在名字为 dump.rdb 的二进制文件中。你可以对 Redis 进行设置, 让它在“N 秒内数据集至少有 M 个改动”这一条件被满足时, 自动保存一次数据集。你也可以通过调用 SAVE 或者 BGSAVE,手动让 Redis 进行数据集保存操作。这种持久化方式被称为快照。 + +当 Redis 需要保存 dump.rdb 文件时, 服务器执行以下操作: + +- Redis 创建一个子进程。 +- 子进程将数据集写入到一个临时快照文件中。 +- 当子进程完成对新快照文件的写入时,Redis 用新快照文件替换原来的快照文件,并删除旧的快照文件。 + +这种工作方式使得 Redis 可以从写时复制(copy-on-write)机制中获益。 + +##### 快照的优点 + +- 它保存了某个时间点的数据集,非常适用于数据集的备份。 +- 很方便传送到另一个远端数据中心或者亚马逊的 S3(可能加密),非常适用于灾难恢复。 +- 快照在保存 RDB 文件时父进程唯一需要做的就是 fork 出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他 IO 操作,所以快照持久化方式可以最大化 redis 的性能。 +- 与 AOF 相比,在恢复大的数据集的时候,DB 方式会更快一些。 + +##### 快照的缺点 + +- 如果你希望在 redis 意外停止工作(例如电源中断)的情况下丢失的数据最少的话,那么快照不适合你。 +- 快照需要经常 fork 子进程来保存数据集到硬盘上。当数据集比较大的时候,fork 的过程是非常耗时的,可能会导致 Redis 在一些毫秒级内不能响应客户端的请求。 + +#### AOF + +AOF 持久化方式记录每次对服务器执行的写操作。当服务器重启的时候会重新执行这些命令来恢复原始的数据。 + +#### AOF 的原理 + +- Redis 创建一个子进程。 +- 子进程开始将新 AOF 文件的内容写入到临时文件。 +- 对于所有新执行的写入命令,父进程一边将它们累积到一个内存缓存中,一边将这些改动追加到现有 AOF 文件的末尾,这样样即使在重写的中途发生停机,现有的 AOF 文件也还是安全的。 +- 当子进程完成重写工作时,它给父进程发送一个信号,父进程在接收到信号之后,将内存缓存中的所有数据追加到新 AOF 文件的末尾。 +- 搞定!现在 Redis 原子地用新文件替换旧文件,之后所有命令都会直接追加到新 AOF 文件的末尾。 + +#### AOF 的优点 + +- 使用默认的每秒 fsync 策略,Redis 的性能依然很好(fsync 是由后台线程进行处理的,主线程会尽力处理客户端请求),一旦出现故障,使用 AOF ,你最多丢失 1 秒的数据。 +- AOF 文件是一个只进行追加的日志文件,所以不需要写入 seek,即使由于某些原因(磁盘空间已满,写的过程中宕机等等)未执行完整的写入命令,你也也可使用 redis-check-aof 工具修复这些问题。 +- Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写:重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。整个重写操作是绝对安全的。 +- AOF 文件有序地保存了对数据库执行的所有写入操作,这些写入操作以 Redis 协议的格式保存。因此 AOF 文件的内容非常容易被人读懂,对文件进行分析(parse)也很轻松。 + +#### AOF 的缺点 + +- 对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。 +- 根据所使用的 fsync 策略,AOF 的速度可能会慢于快照。在一般情况下,每秒 fsync 的性能依然非常高,而关闭 fsync 可以让 AOF 的速度和快照一样快,即使在高负荷之下也是如此。不过在处理巨大的写入载入时,快照可以提供更有保证的最大延迟时间(latency)。 + +### 1.7. Redis 过期策略有哪些? + +- **noeviction** - 当内存使用达到阈值的时候,所有引起申请内存的命令会报错。 +- **allkeys-lru** - 在主键空间中,优先移除最近未使用的 key。 +- **allkeys-random** - 在主键空间中,随机移除某个 key。 +- **volatile-lru** - 在设置了过期时间的键空间中,优先移除最近未使用的 key。 +- **volatile-random** - 在设置了过期时间的键空间中,随机移除某个 key。 +- **volatile-ttl** - 在设置了过期时间的键空间中,具有更早过期时间的 key 优先移除。 + +### 1.8. Redis 和 Memcached 有什么区别? + +两者都是非关系型内存键值数据库。有以下主要不同: + +**数据类型** + +- Memcached 仅支持字符串类型; +- 而 Redis 支持五种不同种类的数据类型,使得它可以更灵活地解决问题。 + +**数据持久化** + +- Memcached 不支持持久化; +- Redis 支持两种持久化策略:RDB 快照和 AOF 日志。 + +**分布式** + +- Memcached 不支持分布式,只能通过在客户端使用像一致性哈希这样的分布式算法来实现分布式存储,这种方式在存储和查询时都需要先在客户端计算一次数据所在的节点。 +- Redis Cluster 实现了分布式的支持。 + +**内存管理机制** + +- Memcached 将内存分割成特定长度的块来存储数据,以完全解决内存碎片的问题,但是这种方式会使得内存的利用率不高,例如块的大小为 128 bytes,只存储 100 bytes 的数据,那么剩下的 28 bytes 就浪费掉了。 +- 在 Redis 中,并不是所有数据都一直存储在内存中,可以将一些很久没用的 value 交换到磁盘。而 Memcached 的数据则会一直在内存中。 + +### 1.9. 为什么单线程的 Redis 性能反而优于多线程的 Memcached? + +Redis 快速的原因: + +1. 绝大部分请求是纯粹的内存操作(非常快速) +2. 采用单线程,避免了不必要的上下文切换和竞争条件 +3. 非阻塞 IO + +内部实现采用 epoll,采用了 epoll+自己实现的简单的事件框架。epoll 中的读、写、关闭、连接都转化成了事件,然后利用 epoll 的多路复用特性,绝不在 io 上浪费一点时间。 + +## 2. 分布式消息队列(MQ) + +### 2.1. 为什么使用 MQ? + +- 异步处理 - 相比于传统的串行、并行方式,提高了系统吞吐量。 +- 应用解耦 - 系统间通过消息通信,不用关心其他系统的处理。 +- 流量削锋 - 可以通过消息队列长度控制请求量;可以缓解短时间内的高并发请求。 +- 日志处理 - 解决大量日志传输。 +- 消息通讯 - 消息队列一般都内置了高效的通信机制,因此也可以用在纯的消息通讯。比如实现点对点消息队列,或者聊天室等。 + +### 2.2. 如何保证 MQ 的高可用? + +#### 数据复制 + +1. 将所有 Broker 和待分配的 Partition 排序 +2. 将第 i 个 Partition 分配到第(i mod n)个 Broker 上 +3. 将第 i 个 Partition 的第 j 个 Replica 分配到第((i + j) mode n)个 Broker 上 + +#### 选举主服务器 + +### 2.3. MQ 有哪些常见问题?如何解决这些问题? + +MQ 的常见问题有: + +1. 消息的顺序问题 +2. 消息的重复问题 + +#### 消息的顺序问题 + +消息有序指的是可以按照消息的发送顺序来消费。 + +假如生产者产生了 2 条消息:M1、M2,假定 M1 发送到 S1,M2 发送到 S2,如果要保证 M1 先于 M2 被消费,怎么做? + +
    + +解决方案: + +(1)保证生产者 - MQServer - 消费者是一对一对一的关系 + +
    + +缺陷: + +- 并行度就会成为消息系统的瓶颈(吞吐量不够) +- 更多的异常处理,比如:只要消费端出现问题,就会导致整个处理流程阻塞,我们不得不花费更多的精力来解决阻塞的问题。 + +(2)通过合理的设计或者将问题分解来规避。 + +- 不关注乱序的应用实际大量存在 +- 队列无序并不意味着消息无序 + +所以从业务层面来保证消息的顺序而不仅仅是依赖于消息系统,是一种更合理的方式。 + +#### 消息的重复问题 + +造成消息重复的根本原因是:网络不可达。 + +所以解决这个问题的办法就是绕过这个问题。那么问题就变成了:如果消费端收到两条一样的消息,应该怎样处理? + +消费端处理消息的业务逻辑保持幂等性。只要保持幂等性,不管来多少条重复消息,最后处理的结果都一样。 +保证每条消息都有唯一编号且保证消息处理成功与去重表的日志同时出现。利用一张日志表来记录已经处理成功的消息的 ID,如果新到的消息 ID 已经在日志表中,那么就不再处理这条消息。 + +### 2.4. Kafka, ActiveMQ, RabbitMQ, RocketMQ 各有什么优缺点? + +
    + +## 3. 分布式服务(RPC) + +### 3.1. Dubbo 的实现过程? + +
    + +
    + +节点角色: + +| 节点 | 角色说明 | +| --------- | -------------------------------------- | +| Provider | 暴露服务的服务提供方 | +| Consumer | 调用远程服务的服务消费方 | +| Registry | 服务注册与发现的注册中心 | +| Monitor | 统计服务的调用次数和调用时间的监控中心 | +| Container | 服务运行容器 | + +调用关系: + +1. 务容器负责启动,加载,运行服务提供者。 +2. 服务提供者在启动时,向注册中心注册自己提供的服务。 +3. 服务消费者在启动时,向注册中心订阅自己所需的服务。 +4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。 +5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。 +6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。 + +### 3.2. Dubbo 负载均衡策略有哪些? + +##### Random + +- 随机,按权重设置随机概率。 +- 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。 + +##### RoundRobin + +- 轮循,按公约后的权重设置轮循比率。 +- 存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。 + +##### LeastActive + +- 最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。 +- 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。 + +##### ConsistentHash + +- 一致性 Hash,相同参数的请求总是发到同一提供者。 +- 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。 +- 算法参见: +- 缺省只对第一个参数 Hash,如果要修改,请配置 `` +- 缺省用 160 份虚拟节点,如果要修改,请配置 `` + +### 3.3. Dubbo 集群容错策略 ? + +
    + +- **Failover** - 失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries="2" 来设置重试次数(不含第一次)。 +- **Failfast** - 快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。 +- **Failsafe** - 失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。 +- **Failback** - 失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。 +- **Forking** - 并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2" 来设置最大并行数。 +- **Broadcast** - 播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。 + +### 3.4. 动态代理策略? + +Dubbo 作为 RPC 框架,首先要完成的就是跨系统,跨网络的服务调用。消费方与提供方遵循统一的接口定义,消费方调用接口时,Dubbo 将其转换成统一格式的数据结构,通过网络传输,提供方根据规则找到接口实现,通过反射完成调用。也就是说,消费方获取的是对远程服务的一个代理(Proxy),而提供方因为要支持不同的接口实现,需要一个包装层(Wrapper)。调用的过程大概是这样: + +
    + +消费方的 Proxy 和提供方的 Wrapper 得以让 Dubbo 构建出复杂、统一的体系。而这种动态代理与包装也是通过基于 SPI 的插件方式实现的,它的接口就是**ProxyFactory**。 + +``` +@SPI("javassist") +public interface ProxyFactory { + + @Adaptive({Constants.PROXY_KEY}) + T getProxy(Invoker invoker) throws RpcException; + + @Adaptive({Constants.PROXY_KEY}) + Invoker getInvoker(T proxy, Class type, URL url) throws RpcException; + +} +``` + +ProxyFactory 有两种实现方式,一种是基于 JDK 的代理实现,一种是基于 javassist 的实现。ProxyFactory 接口上定义了@SPI("javassist"),默认为 javassist 的实现。 + +### 3.5. Dubbo 支持哪些序列化协议?Hessian?Hessian 的数据结构? + +1. dubbo 序列化,阿里尚不成熟的 java 序列化实现。 +2. hessian2 序列化:hessian 是一种跨语言的高效二进制的序列化方式,但这里实际不是原生的 hessian2 序列化,而是阿里修改过的 hessian lite,它是 dubbo RPC 默认启用的序列化方式。 +3. json 序列化:目前有两种实现,一种是采用的阿里的 fastjson 库,另一种是采用 dubbo 中自已实现的简单 json 库,一般情况下,json 这种文本序列化性能不如二进制序列化。 +4. java 序列化:主要是采用 JDK 自带的 java 序列化实现,性能很不理想。 +5. Kryo 和 FST:Kryo 和 FST 的性能依然普遍优于 hessian 和 dubbo 序列化。 + +Hessian 序列化与 Java 默认的序列化区别? + +Hessian 是一个轻量级的 remoting on http 工具,采用的是 Binary RPC 协议,所以它很适合于发送二进制数据,同时又具有防火墙穿透能力。 + +1. Hessian 支持跨语言串行 +2. 比 java 序列化具有更好的性能和易用性 +3. 支持的语言比较多 + +### 3.6. Protoco Buffer 是什么? + +Protocol Buffer 是 Google 出品的一种轻量 & 高效的结构化数据存储格式,性能比 Json、XML 真的强!太!多! + +Protocol Buffer 的序列化 & 反序列化简单 & 速度快的原因是: + +1. 编码 / 解码 方式简单(只需要简单的数学运算 = 位移等等) +2. 采用 Protocol Buffer 自身的框架代码 和 编译器 共同完成 + +Protocol Buffer 的数据压缩效果好(即序列化后的数据量体积小)的原因是: + +1. 采用了独特的编码方式,如 Varint、Zigzag 编码方式等等 +2. 采用 T - L - V 的数据存储方式:减少了分隔符的使用 & 数据存储得紧凑 + +### 3.7. 注册中心挂了可以继续通信吗? + +可以。Dubbo 消费者在应用启动时会从注册中心拉取已注册的生产者的地址接口,并缓存在本地。每次调用时,按照本地存储的地址进行调用。 + +### 3.8. ZooKeeper 原理是什么?ZooKeeper 有什么用? + +ZooKeeper 是一个分布式应用协调系统,已经用到了许多分布式项目中,用来完成统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等工作。 + +
    + +
    + +1. 每个 Server 在内存中存储了一份数据; +2. Zookeeper 启动时,将从实例中选举一个 leader(Paxos 协议); +3. Leader 负责处理数据更新等操作(Zab 协议); +4. 一个更新操作成功,当且仅当大多数 Server 在内存中成功修改数据。 + +### 3.9. Netty 有什么用?NIO/BIO/AIO 有什么用?有什么区别? + +Netty 是一个“网络通讯框架”。 + +Netty 进行事件处理的流程。`Channel`是连接的通道,是 ChannelEvent 的产生者,而`ChannelPipeline`可以理解为 ChannelHandler 的集合。 + +
    + +> 参考:https://github.com/code4craft/netty-learning/blob/master/posts/ch1-overview.md + +IO 的方式通常分为几种: + +- 同步阻塞的 BIO +- 同步非阻塞的 NIO +- 异步非阻塞的 AIO + +在使用同步 I/O 的网络应用中,如果要同时处理多个客户端请求,或是在客户端要同时和多个服务器进行通讯,就必须使用多线程来处理。 + +NIO 基于 Reactor,当 socket 有流可读或可写入 socket 时,操作系统会相应的通知引用程序进行处理,应用再将流读取到缓冲区或写入操作系统。也就是说,这个时候,已经不是一个连接就要对应一个处理线程了,而是有效的请求,对应一个线程,当连接没有数据时,是没有工作线程来处理的。 + +与 NIO 不同,当进行读写操作时,只须直接调用 API 的 read 或 write 方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入 read 方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将 write 方法传递的流写入完毕时,操作系统主动通知应用程序。 即可以理解为,read/write 方法都是异步的,完成后会主动调用回调函数。 + +> 参考:https://blog.csdn.net/skiof007/article/details/52873421 + +### 3.10. 为什么要进行系统拆分?拆分不用 Dubbo 可以吗? + +系统拆分从资源角度分为:应用拆分和数据库拆分。 + +从采用的先后顺序可分为:水平扩展、垂直拆分、业务拆分、水平拆分。 + +
    + +是否使用服务依据实际业务场景来决定。 + +当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。 + +当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。 + +### 3.11. Dubbo 和 Thrift 有什么区别? + +- Thrift 是跨语言的 RPC 框架。 +- Dubbo 支持服务治理,而 Thrift 不支持。 diff --git a/docs/theory/distributed-lock-theory.md b/docs/theory/distributed-lock-theory.md new file mode 100644 index 00000000..d45370fb --- /dev/null +++ b/docs/theory/distributed-lock-theory.md @@ -0,0 +1,450 @@ +# 分布式锁基本原理 + +Java 原生 API 虽然有并发锁,但并没有提供分布式锁的能力,所以针对分布式场景中的锁需要解决的方案。 + +分布式锁的解决方案大致有以下几种: + +- 基于数据库实现 +- 基于缓存(redis,memcached 等)实现 +- 基于 Zookeeper 实现 ✅ + +> 注:推荐基于 ZooKeeper 实现分布式锁,具体原因看完本文即可明了。 + +## 分布式锁思路 + +分布式锁的总体思路大同小异,仅在实现细节上有所不同。 + +分布式锁的主要思路如下: + +1. **互斥** - 创建锁必须是唯一的,表现形式为向数据存储服务器或容器插入一个唯一的 key,一旦有一个线程插入这个 key,其他线程就不能再插入了。 +2. 避免永远不释放锁 - 数据库分布式锁和缓存分布式锁(Redis)的思路都是引入超时机制,即成功申请锁后,超过一定时间,锁失效(删除 key),原因在于它们无法感知申请锁的客户端节点状态。而 ZooKeeper 由于其 znode 以目录、文件形式组织,天然就存在物理空间隔离,只要 znode 存在,即表示客户端节点还在工作,所以不存在这种问题。 +3. 容错(只要大部分 redis 节点创建了这把锁就可以) + +## 数据库分布式锁 + +### 具体实现 + +(1)创建表 + +```sql +CREATE TABLE `methodLock` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', + `method_name` varchar(64) NOT NULL DEFAULT '' COMMENT '锁定的方法名', + `desc` varchar(1024) NOT NULL DEFAULT '备注信息', + `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '保存数据时间,自动生成', + PRIMARY KEY (`id`), + UNIQUE KEY `uidx_method_name` (`method_name `) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='锁定中的方法'; +``` + +(2)获取锁 + +想要锁住某个方法时,执行以下 SQL: + +```sql +insert into methodLock(method_name,desc) values (‘method_name’,‘desc’) +``` + +因为我们对 `method_name` 做了唯一性约束,这里如果有多个请求同时提交到数据库的话,数据库会保证只有一个操作可以成功,那么我们就可以认为操作成功的那个线程获得了该方法的锁,可以执行方法体内容。 + +成功插入则获取锁。 + +(3)释放锁 + +当方法执行完毕之后,想要释放锁的话,需要执行以下 Sql: + +```sql +delete from methodLock where method_name ='method_name' +``` + +### 问题 + +- 这把锁强依赖数据库的可用性。如果数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用。 +- 这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在数据库中,其他线程无法再获得到锁。 +- 这把锁只能是非阻塞的,因为数据的 insert 操作,一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁操作。 +- 这把锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁。因为数据中数据已经存在了。 + +### 解决办法 + +- 单点问题可以用多数据库实例,同时塞 N 个表,N/2+1 个成功就任务锁定成功 +- 写一个定时任务,隔一段时间清除一次过期的数据。 +- 写一个 while 循环,不断的重试插入,直到成功。 +- 在数据库表中加个字段,记录当前获得锁的机器的主机信息和线程信息,那么下次再获取锁的时候先查询数据库,如果当前机器的主机信息和线程信息在数据库可以查到的话,直接把锁分配给他就可以了。 + +### 小结 + +- 优点: 直接借助数据库,容易理解。 +- 缺点: 会有各种各样的问题,在解决问题的过程中会使整个方案变得越来越复杂。操作数据库需要一定的开销,性能问题需要考虑。 + +## Redis 分布式锁 + +相比于用数据库来实现分布式锁,基于缓存实现的分布式锁的性能会更好一些。目前有很多成熟的分布式产品,包括 Redis、memcache、Tair 等。这里以 Redis 举例。 + +### 实现思路 + +这个分布式锁有 3 个重要的考量点: + +1. 互斥(只能有一个客户端获取锁) +2. 不能死锁 +3. 容错(只要大部分 redis 节点创建了这把锁就可以) + +对应的 Redis 指令如下: + +- `setnx` - `setnx key val`:当且仅当 key 不存在时,set 一个 key 为 val 的字符串,返回 1;若 key 存在,则什么都不做,返回 0。 +- `expire` - `expire key timeout`:为 key 设置一个超时时间,单位为 second,超过这个时间锁会自动释放,避免死锁。 +- `delete` - `delete key`:删除 key + +### 具体实现 + +(1)申请锁 + +``` +SET resource_name my_random_value NX PX 30000 +``` + +执行这个命令就 ok。 + +- `NX`:表示只有 `key` 不存在的时候才会设置成功。(如果此时 redis 中存在这个 key,那么设置失败,返回 `nil`) +- `PX 30000`:意思是 30s 后锁自动释放。别人创建的时候如果发现已经有了就不能加锁了。 + +(2)释放锁 + +释放锁就是删除 key ,但是一般可以用 `lua` 脚本删除,判断 value 一样才删除: + +```python +-- 删除锁的时候,找到 key 对应的 value,跟自己传过去的 value 做比较,如果是一样的才删除。 +if redis.call("get",KEYS[1]) == ARGV[1] then + return redis.call("del",KEYS[1]) +else + return 0 +end +``` + +### 小结 + +为啥要用 `random_value` 随机值呢?因为如果某个客户端获取到了锁,但是阻塞了很长时间才执行完,比如说超过了 30s,此时可能已经自动释放锁了,此时可能别的客户端已经获取到了这个锁,要是你这个时候直接删除 key 的话会有问题,所以得用随机值加上面的 `lua` 脚本来释放锁。 + +但是这样是肯定不行的。因为如果是普通的 redis 单实例,那就是单点故障。或者是 redis 普通主从,那 redis 主从异步复制,如果主节点挂了(key 就没有了),key 还没同步到从节点,此时从节点切换为主节点,别人就可以 set key,从而拿到锁。 + +### RedLock 算法 + +这个场景是假设有一个 redis cluster,有 5 个 redis master 实例。然后执行如下步骤获取一把锁: + +1. 获取当前时间戳,单位是毫秒; +2. 跟上面类似,轮流尝试在每个 master 节点上创建锁,过期时间较短,一般就几十毫秒; +3. 尝试在**大多数节点**上建立一个锁,比如 5 个节点就要求是 3 个节点 `n / 2 + 1`; +4. 客户端计算建立好锁的时间,如果建立锁的时间小于超时时间,就算建立成功了; +5. 要是锁建立失败了,那么就依次之前建立过的锁删除; +6. 只要别人建立了一把分布式锁,你就得**不断轮询去尝试获取锁**。 + +[Redis 官方](https://redis.io/)给出了以上两种基于 Redis 实现分布式锁的方法,详细说明可以查看: 。 + +## ZooKeeper 分布式锁 + +### 实现思路 + +这也是 ZooKeeper 客户端 curator 的分布式锁实现。 + +1. 创建一个目录 mylock; +2. 线程 A 想获取锁就在 mylock 目录下创建临时顺序节点; +3. 获取 mylock 目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁; +4. 线程 B 获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点; +5. 线程 A 处理完,删除自己的节点,线程 B 监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。 + +### 具体实现 + +```java +/** + * ZooKeeperSession + * + * @author bingo + * @since 2018/11/29 + * + */ +public class ZooKeeperSession { + + private static CountDownLatch connectedSemaphore = new CountDownLatch(1); + + private ZooKeeper zookeeper; + private CountDownLatch latch; + + public ZooKeeperSession() { + try { + this.zookeeper = new ZooKeeper("192.168.31.187:2181,192.168.31.19:2181,192.168.31.227:2181", 50000, new ZooKeeperWatcher()); + try { + connectedSemaphore.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + System.out.println("ZooKeeper session established......"); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 获取分布式锁 + * + * @param productId + */ + public Boolean acquireDistributedLock(Long productId) { + String path = "/product-lock-" + productId; + + try { + zookeeper.create(path, "".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL); + return true; + } catch (Exception e) { + while (true) { + try { + // 相当于是给node注册一个监听器,去看看这个监听器是否存在 + Stat stat = zk.exists(path, true); + + if (stat != null) { + this.latch = new CountDownLatch(1); + this.latch.await(waitTime, TimeUnit.MILLISECONDS); + this.latch = null; + } + zookeeper.create(path, "".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL); + return true; + } catch (Exception ee) { + continue; + } + } + + } + return true; + } + + /** + * 释放掉一个分布式锁 + * + * @param productId + */ + public void releaseDistributedLock(Long productId) { + String path = "/product-lock-" + productId; + try { + zookeeper.delete(path, -1); + System.out.println("release the lock for product[id=" + productId + "]......"); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 建立zk session的watcher + * + * @author bingo + * @since 2018/11/29 + * + */ + private class ZooKeeperWatcher implements Watcher { + + public void process(WatchedEvent event) { + System.out.println("Receive watched event: " + event.getState()); + + if (KeeperState.SyncConnected == event.getState()) { + connectedSemaphore.countDown(); + } + + if (this.latch != null) { + this.latch.countDown(); + } + } + + } + + /** + * 封装单例的静态内部类 + * + * @author bingo + * @since 2018/11/29 + * + */ + private static class Singleton { + + private static ZooKeeperSession instance; + + static { + instance = new ZooKeeperSession(); + } + + public static ZooKeeperSession getInstance() { + return instance; + } + + } + + /** + * 获取单例 + * + * @return + */ + public static ZooKeeperSession getInstance() { + return Singleton.getInstance(); + } + + /** + * 初始化单例的便捷方法 + */ + public static void init() { + getInstance(); + } + +} +``` + +也可以采用另一种方式,创建临时顺序节点: + +如果有一把锁,被多个人给竞争,此时多个人会排队,第一个拿到锁的人会执行,然后释放锁;后面的每个人都会去监听**排在自己前面**的那个人创建的 node 上,一旦某个人释放了锁,排在自己后面的人就会被 zookeeper 给通知,一旦被通知了之后,就 ok 了,自己就获取到了锁,就可以执行代码了。 + +```java +public class ZooKeeperDistributedLock implements Watcher { + + private ZooKeeper zk; + private String locksRoot = "/locks"; + private String productId; + private String waitNode; + private String lockNode; + private CountDownLatch latch; + private CountDownLatch connectedLatch = new CountDownLatch(1); + private int sessionTimeout = 30000; + + public ZooKeeperDistributedLock(String productId) { + this.productId = productId; + try { + String address = "192.168.31.187:2181,192.168.31.19:2181,192.168.31.227:2181"; + zk = new ZooKeeper(address, sessionTimeout, this); + connectedLatch.await(); + } catch (IOException e) { + throw new LockException(e); + } catch (KeeperException e) { + throw new LockException(e); + } catch (InterruptedException e) { + throw new LockException(e); + } + } + + public void process(WatchedEvent event) { + if (event.getState() == KeeperState.SyncConnected) { + connectedLatch.countDown(); + return; + } + + if (this.latch != null) { + this.latch.countDown(); + } + } + + public void acquireDistributedLock() { + try { + if (this.tryLock()) { + return; + } else { + waitForLock(waitNode, sessionTimeout); + } + } catch (KeeperException e) { + throw new LockException(e); + } catch (InterruptedException e) { + throw new LockException(e); + } + } + + public boolean tryLock() { + try { + // 传入进去的locksRoot + “/” + productId + // 假设productId代表了一个商品id,比如说1 + // locksRoot = locks + // /locks/10000000000,/locks/10000000001,/locks/10000000002 + lockNode = zk.create(locksRoot + "/" + productId, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); + + // 看看刚创建的节点是不是最小的节点 + // locks:10000000000,10000000001,10000000002 + List locks = zk.getChildren(locksRoot, false); + Collections.sort(locks); + + if(lockNode.equals(locksRoot+"/"+ locks.get(0))){ + //如果是最小的节点,则表示取得锁 + return true; + } + + //如果不是最小的节点,找到比自己小1的节点 + int previousLockIndex = -1; + for(int i = 0; i < locks.size(); i++) { + if(lockNode.equals(locksRoot + “/” + locks.get(i))) { + previousLockIndex = i - 1; + break; + } + } + + this.waitNode = locks.get(previousLockIndex); + } catch (KeeperException e) { + throw new LockException(e); + } catch (InterruptedException e) { + throw new LockException(e); + } + return false; + } + + private boolean waitForLock(String waitNode, long waitTime) throws InterruptedException, KeeperException { + Stat stat = zk.exists(locksRoot + "/" + waitNode, true); + if (stat != null) { + this.latch = new CountDownLatch(1); + this.latch.await(waitTime, TimeUnit.MILLISECONDS); + this.latch = null; + } + return true; + } + + public void unlock() { + try { + // 删除/locks/10000000000节点 + // 删除/locks/10000000001节点 + System.out.println("unlock " + lockNode); + zk.delete(lockNode, -1); + lockNode = null; + zk.close(); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (KeeperException e) { + e.printStackTrace(); + } + } + + public class LockException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public LockException(String e) { + super(e); + } + + public LockException(Exception e) { + super(e); + } + } +} +``` + +### 小结 + +ZooKeeper 版本的分布式锁问题相对比较来说少。 + +- 锁的占用时间限制:redis 就有占用时间限制,而 ZooKeeper 则没有,最主要的原因是 redis 目前没有办法知道已经获取锁的客户端的状态,是已经挂了呢还是正在执行耗时较长的业务逻辑。而 ZooKeeper 通过临时节点就能清晰知道,如果临时节点存在说明还在执行业务逻辑,如果临时节点不存在说明已经执行完毕释放锁或者是挂了。由此看来 redis 如果能像 ZooKeeper 一样添加一些与客户端绑定的临时键,也是一大好事。 +- 是否单点故障:redis 本身有很多中玩法,如客户端一致性 hash,服务器端 sentinel 方案或者 cluster 方案,很难做到一种分布式锁方式能应对所有这些方案。而 ZooKeeper 只有一种玩法,多台机器的节点数据是一致的,没有 redis 的那么多的麻烦因素要考虑。 + +总体上来说 ZooKeeper 实现分布式锁更加的简单,可靠性更高。但 ZooKeeper 因为需要频繁的创建和删除节点,性能上不如 Redis 方式。 + +## 分布式锁方案对比 + +- 数据库分布式锁,问题比较多,解决起来比较麻烦,不推荐 + +- redis 分布式锁,其实**需要自己不断去尝试获取锁**,比较消耗性能。 +- zk 分布式锁,获取不到锁,注册个监听器即可,不需要不断主动尝试获取锁,性能开销较小。 + +另外一点就是,如果是 redis 获取锁的那个客户端出现 bug 挂了,那么只能等待超时时间之后才能释放锁;而 zk 的话,因为创建的是临时 znode,只要客户端挂了,znode 就没了,此时就自动释放锁。 + +总体上来说,ZooKeeper 实现分布式锁更加的简单,可靠性更高。 + +## 参考资料 + +- [分布式锁实现汇总](https://juejin.im/post/5a20cd8bf265da43163cdd9a) diff --git a/docs/theory/distributed-session-theory.md b/docs/theory/distributed-session-theory.md new file mode 100644 index 00000000..2faad12a --- /dev/null +++ b/docs/theory/distributed-session-theory.md @@ -0,0 +1,228 @@ +# 分布式会话基本原理 + +> 由于 Http 是一种无状态的协议,服务器单单从网络连接上无从知道客户身份。 +> +> 会话跟踪是 Web 程序中常用的技术,用来跟踪用户的整个会话。常用会话跟踪技术是 Cookie 与 Session。 + +## 简介 + +### 什么是 Cookie + +Cookie 实际上是存储在用户浏览器上的文本信息,并保留了各种跟踪的信息。 + +Cookie 工作步骤: + +1. 客户端请求服务器,如果服务器需要记录该用户的状态,就是用 response 向客户端浏览器颁发一个 Cookie。 +2. 客户端浏览器会把 Cookie 保存下来。 +3. 当浏览器再请求该网站时,浏览器把该请求的网址连同 Cookie 一同提交给服务器。服务器检查该 Cookie,以此来辨认用户状态。 + +Cookie 主要用于以下三个方面: + +- 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息) +- 个性化设置(如用户自定义设置、主题等) +- 浏览器行为跟踪(如跟踪分析用户行为等) + +**_注:Cookie 功能需要浏览器的支持,如果浏览器不支持 Cookie 或者 Cookie 禁用了,Cookie 功能就会失效。_** + +### 什么是 Session + +Session 代表着服务器和客户端一次会话的过程。Session 对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当客户端关闭会话,或者 Session 超时失效时会话结束。 + +### Cookie 和 Session 的区别 + +- **作用范围不同**,Cookie 保存在客户端(浏览器),Session 保存在服务器端。 +- **存取方式的不同**,Cookie 只能保存 ASCII,Session 可以存任意数据类型,一般情况下我们可以在 Session 中保持一些常用变量信息,比如说 UserId 等。 +- **有效期不同**,Cookie 可设置为长时间保持,比如我们经常使用的默认登录功能,Session 一般失效时间较短,客户端关闭或者 Session 超时都会失效。 +- **隐私策略不同**,Cookie 存储在客户端,比较容易遭到不法获取,早期有人将用户的登录名和密码存储在 Cookie 中导致信息被窃取;Session 存储在服务端,安全性相对 Cookie 要好一些。 +- **存储大小不同**, 单个 Cookie 保存的数据不能超过 4K,Session 可存储数据远高于 Cookie。 + +### 为什么需要 Cookie 和 Session,他们有什么关联? + +说起来为什么需要 Cookie ,这就需要从浏览器开始说起,我们都知道浏览器是没有状态的(HTTP 协议无状态),这意味着浏览器并不知道是张三还是李四在和服务端打交道。这个时候就需要有一个机制来告诉服务端,本次操作用户是否登录,是哪个用户在执行的操作,那这套机制的实现就需要 Cookie 和 Session 的配合。 + +那么 Cookie 和 Session 是如何配合的呢?我画了一张图大家可以先了解下。 + +![img](https://user-gold-cdn.xitu.io/2019/5/13/16aafb5d90f398e2?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) + +用户第一次请求服务器的时候,服务器根据用户提交的相关信息,创建创建对应的 Session ,请求返回时将此 Session 的唯一标识信息 SessionID 返回给浏览器,浏览器接收到服务器返回的 SessionID 信息后,会将此信息存入到 Cookie 中,同时 Cookie 记录此 SessionID 属于哪个域名。 + +当用户第二次访问服务器的时候,请求会自动判断此域名下是否存在 Cookie 信息,如果存在自动将 Cookie 信息也发送给服务端,服务端会从 Cookie 中获取 SessionID,再根据 SessionID 查找对应的 Session 信息,如果没有找到说明用户没有登录或者登录失效,如果找到 Session 证明用户已经登录可执行后面操作。 + +根据以上流程可知,SessionID 是连接 Cookie 和 Session 的一道桥梁,大部分系统也是根据此原理来验证用户登录状态。 + +### 如果浏览器禁用 Cookie 怎么办 + +既然服务端是根据 Cookie 中的信息判断用户是否登录,那么如果浏览器中禁止了 Cookie,如何保障整个机制的正常运转。 + +第一种方案,每次请求中都携带一个 SessionID 的参数,也可以 Post 的方式提交,也可以在请求的地址后面拼接 `xxx?SessionID=123456...`。 + +第二种方案,Token 机制。Token 机制多用于 App 客户端和服务器交互的模式,也可以用于 Web 端做用户状态管理。 + +Token 的意思是“令牌”,是服务端生成的一串字符串,作为客户端进行请求的一个标识。Token 机制和 Cookie 和 Session 的使用机制比较类似。 + +当用户第一次登录后,服务器根据提交的用户信息生成一个 Token,响应时将 Token 返回给客户端,以后客户端只需带上这个 Token 前来请求数据即可,无需再次登录验证。 + +## 分布式 Session + +在分布式场景下,一个用户的 Session 如果只存储在一个服务器上,那么当负载均衡器把用户的下一个请求转发到另一个服务器上,该服务器没有用户的 Session,就可能导致用户需要重新进行登录等操作。 + +分布式 Session 的几种实现策略: + +1. 粘性 session +2. 应用服务器间的 session 复制共享 +3. 基于缓存的 session 共享 ✅ + +> 推荐:基于缓存的 session 共享 + +### 粘性 Session + +> 粘性 Session(Sticky Sessions)需要配置负载均衡器,使得一个用户的所有请求都路由到一个服务器节点上,这样就可以把用户的 Session 存放在该服务器节点中。 +> +> 缺点:当服务器节点宕机时,将丢失该服务器节点上的所有 Session。 + +
    + +
    + +### session 复制共享 + +> Session 复制共享(Session Replication)在服务器节点之间进行 Session 同步操作,这样的话用户可以访问任何一个服务器节点。 +> +> 缺点:占用过多内存;同步过程占用网络带宽以及服务器处理器时间。 + +
    + +
    + +### 基于缓存的 session 共享 + +> 使用一个单独的存储服务器存储 Session 数据,可以存在 MySQL 数据库上,也可以存在 Redis 或者 Memcached 这种内存型数据库。 +> +> 缺点:需要去实现存取 Session 的代码。 + +
    + +
    + +## 具体实现 + +### JWT Token + +使用 JWT Token 储存用户身份,然后再从数据库或者 cache 中获取其他的信息。这样无论请求分配到哪个服务器都无所谓。 + +### tomcat + redis + +这个其实还挺方便的,就是使用 session 的代码,跟以前一样,还是基于 tomcat 原生的 session 支持即可,然后就是用一个叫做 `Tomcat RedisSessionManager` 的东西,让所有我们部署的 tomcat 都将 session 数据存储到 redis 即可。 + +在 tomcat 的配置文件中配置: + +```xml + + + +``` + +然后指定 redis 的 host 和 port 就 ok 了。 + +```xml + + +``` + +还可以用上面这种方式基于 redis 哨兵支持的 redis 高可用集群来保存 session 数据,都是 ok 的。 + +### spring session + redis + +上面那种 tomcat + redis 的方式好用,但是会**严重依赖于 web 容器**,不好将代码移植到其他 web 容器上去,尤其是你要是换了技术栈咋整?比如换成了 spring cloud 或者是 spring boot 之类的呢? + +所以现在比较好的还是基于 Java 一站式解决方案,也就是 spring。人家 spring 基本上承包了大部分我们需要使用的框架,spirng cloud 做微服务,spring boot 做脚手架,所以用 [sping session](https://github.com/spring-projects/spring-session) 是一个很好的选择。 + +在 pom.xml 中配置: + +```xml + + org.springframework.session + spring-session-data-redis + 1.2.1.RELEASE + + + redis.clients + jedis + 2.8.1 + +``` + +在 spring 配置文件中配置: + +```xml + + + + + + + + + + + + + + + + + +``` + +在 web.xml 中配置: + +```xml + + springSessionRepositoryFilter + org.springframework.web.filter.DelegatingFilterProxy + + + springSessionRepositoryFilter + /* + +``` + +示例代码: + +```java +@RestController +@RequestMapping("/test") +public class TestController { + + @RequestMapping("/putIntoSession") + public String putIntoSession(HttpServletRequest request, String username) { + request.getSession().setAttribute("name", "leo"); + return "ok"; + } + + @RequestMapping("/getFromSession") + public String getFromSession(HttpServletRequest request, Model model){ + String name = request.getSession().getAttribute("name"); + return name; + } +} +``` + +上面的代码就是 ok 的,给 sping session 配置基于 redis 来存储 session 数据,然后配置了一个 spring session 的过滤器,这样的话,session 相关操作都会交给 spring session 来管了。接着在代码中,就用原生的 session 操作,就是直接基于 spring sesion 从 redis 中获取数据了。 + +实现分布式的会话有很多种方式,我说的只不过是比较常见的几种方式,tomcat + redis 早期比较常用,但是会重耦合到 tomcat 中;近些年,通过 spring session 来实现。 + +## 参考资料 + +- [集群/分布式环境 Session 的几种策略](https://github.com/L316476844/distributed-session) +- [你真的了解 Cookie 和 Session 吗](https://juejin.im/post/5cd9037ee51d456e5c5babca) +- [聊一聊 session 和 cookie](https://juejin.im/post/5aede266f265da0ba266e0ef) diff --git a/docs/theory/distributed-storage-theory.md b/docs/theory/distributed-storage-theory.md new file mode 100644 index 00000000..71003e86 --- /dev/null +++ b/docs/theory/distributed-storage-theory.md @@ -0,0 +1,82 @@ +--- +title: 分布式存储 +categories: ['design', 'distributed'] +tags: ['design', 'distributed'] +date: 2019-08-21 11:30 +--- + +# 分布式存储 + +## 分区规则 + +**分布式数据库** 首先要解决把 **整个数据集** 按照 **分区规则** 映射到 **多个节点** 的问题,即把 **数据集** 划分到 **多个节点** 上,每个节点负责 **整体数据** 的一个 **子集**。 + +
    + +
    + +数据分布通常有 **哈希分区** 和 **顺序分区** 两种方式,对比如下: + +| 分区方式 | 特点 | 相关产品 | +| :------- | :----------------------------------------------- | :------------------------------- | +| 哈希分区 | 离散程度好,数据分布与业务无关,无法顺序访问 | Redis Cluster,Cassandra,Dynamo | +| 顺序分区 | 离散程度易倾斜,数据分布与业务相关,可以顺序访问 | BigTable,HBase,Hypertable | + +由于 `Redis Cluster` 采用 **哈希分区规则**,这里重点讨论 **哈希分区**。常见的 **哈希分区** 规则有几种,下面分别介绍: + +### 节点取余分区 + +使用特定的数据,如 `Redis` 的 **键** 或 **用户** `ID`,再根据 **节点数量** `N` 使用公式:`hash(key)% N` 计算出 **哈希值**,用来决定数据 **映射** 到哪一个节点上。 + +
    + +
    + +- **优点** + +这种方式的突出优点是 **简单性**,常用于 **数据库** 的 **分库分表规则**。一般采用 **预分区** 的方式,提前根据 **数据量** 规划好 **分区数**,比如划分为 `512` 或 `1024` 张表,保证可支撑未来一段时间的 **数据容量**,再根据 **负载情况** 将 **表** 迁移到其他 **数据库** 中。扩容时通常采用 **翻倍扩容**,避免 **数据映射** 全部被 **打乱**,导致 **全量迁移** 的情况。 + +- **缺点** + +当 **节点数量** 变化时,如 **扩容** 或 **收缩** 节点,数据节点 **映射关系** 需要重新计算,会导致数据的 **重新迁移**。 + +### 一致性哈希分区 + +**一致性哈希** 可以很好的解决 **稳定性问题**,可以将所有的 **存储节点** 排列在 **首尾相接** 的 `Hash` 环上,每个 `key` 在计算 `Hash` 后会 **顺时针** 找到 **临接** 的 **存储节点** 存放。而当有节点 **加入** 或 **退出** 时,仅影响该节点在 `Hash` 环上 **顺时针相邻** 的 **后续节点**。 + +
    + +
    + +- **优点** + +**加入** 和 **删除** 节点只影响 **哈希环** 中 **顺时针方向** 的 **相邻的节点**,对其他节点无影响。 + +- **缺点** + +**加减节点** 会造成 **哈希环** 中部分数据 **无法命中**。当使用 **少量节点** 时,**节点变化** 将大范围影响 **哈希环** 中 **数据映射**,不适合 **少量数据节点** 的分布式方案。**普通** 的 **一致性哈希分区** 在增减节点时需要 **增加一倍** 或 **减去一半** 节点才能保证 **数据** 和 **负载的均衡**。 + +> **注意**:因为 **一致性哈希分区** 的这些缺点,一些分布式系统采用 **虚拟槽** 对 **一致性哈希** 进行改进,比如 `Dynamo` 系统。 + +### 虚拟槽分区 + +**虚拟槽分区** 巧妙地使用了 **哈希空间**,使用 **分散度良好** 的 **哈希函数** 把所有数据 **映射** 到一个 **固定范围** 的 **整数集合** 中,整数定义为 **槽**(`slot`)。这个范围一般 **远远大于** 节点数,比如 `Redis Cluster` 槽范围是 `0 ~ 16383`。**槽** 是集群内 **数据管理** 和 **迁移** 的 **基本单位**。采用 **大范围槽** 的主要目的是为了方便 **数据拆分** 和 **集群扩展**。每个节点会负责 **一定数量的槽**,如图所示: + +
    + +
    +当前集群有 `3` 个节点,每个节点平均大约负责 `5460` 个 **槽**。由于采用 **高质量** 的 **哈希算法**,每个槽所映射的数据通常比较 **均匀**,将数据平均划分到 `3` 个节点进行 **数据分区**。`Redis Cluster` 就是采用 **虚拟槽分区**。 + +集群中的每个节点负责一部分哈希槽,比如集群中有3个节点,则: + +- 节点A存储的哈希槽范围是:0 – 5460 +- 节点B存储的哈希槽范围是:5461 – 10922 +- 节点C存储的哈希槽范围是:10923 – 16383 + +这种结构很容易 **添加** 或者 **删除** 节点。如果 **增加** 一个节点 `4`,就需要从节点 `1 ~ 3` 获得部分 **槽** 分配到节点 `4` 上。如果想 **移除** 节点 `1`,需要将节点 `1` 中的 **槽** 移到节点 `2 ~ 3` 上,然后将 **没有任何槽** 的节点 `1` 从集群中 **移除** 即可。 + +> 由于从一个节点将 **哈希槽** 移动到另一个节点并不会 **停止服务**,所以无论 **添加删除** 或者 **改变** 某个节点的 **哈希槽的数量** 都不会造成 **集群不可用** 的状态. + +## 参考资料 + +- [深入剖析 Redis 系列(三) - Redis 集群模式搭建与原理详解](https://juejin.im/post/5b8fc5536fb9a05d2d01fb11) diff --git a/docs/theory/distributed-transaction-theory.md b/docs/theory/distributed-transaction-theory.md new file mode 100644 index 00000000..29816f68 --- /dev/null +++ b/docs/theory/distributed-transaction-theory.md @@ -0,0 +1,185 @@ +# 分布式事务基本原理 + +> **分布式事务指的是事务操作跨越多个节点,并且要求满足事务的 ACID 特性。** + +分布式事务的实现主要有以下 5 种方案: + +- XA 方案 +- TCC 方案 +- 本地消息表 +- 可靠消息最终一致性方案 +- 最大努力通知方案 + +## 两阶段提交 + +> 两阶段提交(Two-phase Commit,2PC)通过引入协调者(Coordinator)来调度参与者的行为,并最终决定这些参与者是否要真正执行事务。 +> +> 参考实现:https://github.com/changmingxie/tcc-transaction + +### 运行过程 + +#### 准备阶段 + +协调者询问参与者事务是否执行成功,参与者发回事务执行结果。 + +
    + +
    + +#### 提交阶段 + +如果事务在每个参与者上都执行成功,事务协调者发送通知让参与者提交事务;否则,协调者发送通知让参与者回滚事务。 + +
    + +
    +需要注意的是,在准备阶段,参与者执行了事务,但是还未提交。只有在提交阶段接收到协调者发来的通知后,才进行提交或者回滚。 + +### 问题 + +- **同步阻塞** - 所有事务参与者在等待其它参与者响应的时候都处于同步阻塞状态,无法进行其它操作。 +- **单点问题** - 协调者在 2PC 中起到非常大的作用,发生故障将会造成很大影响,特别是在阶段二发生故障,所有参与者会一直等待状态,无法完成其它操作。 +- **数据不一致** - 在阶段二,如果协调者只发送了部分 Commit 消息,此时网络发生异常,那么只有部分参与者接收到 Commit 消息,也就是说只有部分参与者提交了事务,使得系统数据不一致。 +- **太过保守** - 任意一个节点失败就会导致整个事务失败,没有完善的容错机制。 + +### 优缺点 + +- 优点:尽量保证了数据的强一致,适合对数据强一致要求很高的关键领域。(其实也不能 100%保证强一致 +- 缺点:实现复杂,牺牲了可用性,对性能影响较大,不适合高并发高性能场景。 + +## 补偿事务 + +> 补偿事务(TCC),全称是:`Try`、`Confirm`、`Cancel`。 +> +> 其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。它分为三个阶段: +> +> TCC 的。 +> +> - Try 阶段:这个阶段说的是对各个服务的资源做检测以及对资源进行**锁定或者预留**。 +> - Confirm 阶段:这个阶段说的是在各个服务中**执行实际的操作**。 +> - Cancel 阶段:如果任何一个服务的业务方法执行出错,那么这里就需要**进行补偿**,就是执行已经执行成功的业务逻辑的回滚操作。(把那些执行成功的回滚) + +举个例子,假设 Bob 要向 Smith 转账,思路大概是: + +1. 首先在 Try 阶段,要先调用远程接口把 Smith 和 Bob 的钱给冻结起来。 +2. 在 Confirm 阶段,执行远程调用的转账的操作,转账成功进行解冻。 +3. 如果第 2 步执行成功,那么转账成功,如果第二步执行失败,则调用远程冻结接口对应的解冻方法 (Cancel)。 + +优缺点: + +- 优点:跟 2PC 比起来,实现以及流程相对简单了一些,但数据的一致性比 2PC 也要差一些。 +- 缺点:缺点还是比较明显的,在 2,3 步中都有可能失败。TCC 属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,一些业务流程可能用 TCC 不太好定义及处理。 + +这种方案几乎很少人使用,因为**事务回滚**实际上是**严重依赖于自己写代码来回滚和补偿**了,会造成补偿代码巨大。 + +但是,也有特殊的使用场景:一般来说跟**钱**相关的,跟钱打交道的,**支付**、**交易**相关的场景,会用 TCC,严格保证分布式事务要么全部成功,要么全部自动回滚,严格保证资金的正确性,保证在资金上不会出现问题。 + +而且最好是你的各个业务执行的时间都比较短。 + +但是说实话,一般尽量别这么搞,自己手写回滚逻辑,或者是补偿逻辑,实在太恶心了,那个业务代码是很难维护的。 + +## 本地消息表(异步确保) + +> 本地消息表与业务数据表处于同一个数据库中,这样就能利用本地事务来保证在对这两个表的操作满足事务特性。 +> +> 1. 在分布式事务操作的一方完成写业务数据的操作之后向本地消息表发送一个消息,本地事务能保证这个消息一定会被写入本地消息表中。 +> 2. 之后将本地消息表中的消息转发到 Kafka 等消息队列(MQ)中,如果转发成功则将消息从本地消息表中删除,否则继续重新转发。 +> 3. 在分布式事务操作的另一方从消息队列中读取一个消息,并执行消息中的操作。 + +
    + +
    + +这种方案遵循 BASE 理论,采用的是最终一致性。 + +本地消息表利用了本地事务来实现分布式事务,并且使用了消息队列来保证最终一致性。 + +### 优缺点 + +- 优点:一种非常经典的实现,避免了分布式事务,实现了最终一致性。 +- 缺点:消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。 + +## MQ 事务消息 + +有一些第三方的 MQ 是支持事务消息的,比如 RocketMQ,他们支持事务消息的方式也是类似于采用的二阶段提交。但是市面上一些主流的 MQ 都是不支持事务消息的,比如 RabbitMQ 和 Kafka 都不支持。 + +以阿里的 RocketMQ 中间件为例,其思路大致为: + +1. Prepared 消息,会拿到消息的地址。 +2. 执行本地事务。 +3. 通过第一阶段拿到的地址去访问消息,并修改状态。 + +也就是说在业务方法内要想消息队列提交两次请求,一次发送消息和一次确认消息。如果确认消息发送失败了 RocketMQ 会定期扫描消息集群中的事务消息,这时候发现了 Prepared 消息,它会向消息发送者确认,所以生产方需要实现一个 check 接口,RocketMQ 会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。 + +### 优缺点 + +- 优点:实现了最终一致性,不需要依赖本地数据库事务。 +- 缺点:实现难度大,主流 MQ 不支持。 + +### 两阶段提交方案/XA 方案 + +所谓的 XA 方案,即:两阶段提交,有一个**事务管理器**的概念,负责协调多个数据库(资源管理器)的事务,事务管理器先问问各个数据库你准备好了吗?如果每个数据库都回复 ok,那么就正式提交事务,在各个数据库上执行操作;如果任何其中一个数据库回答不 ok,那么就回滚事务。 + +这种分布式事务方案,比较适合单块应用里,跨多个库的分布式事务,而且因为严重依赖于数据库层面来搞定复杂的事务,效率很低,绝对不适合高并发的场景。如果要玩儿,那么基于 `Spring + JTA` 就可以搞定,自己随便搜个 demo 看看就知道了。 + +这个方案,我们很少用,一般来说**某个系统内部如果出现跨多个库**的这么一个操作,是**不合规**的。我可以给大家介绍一下, 现在微服务,一个大的系统分成几十个甚至几百个服务。一般来说,我们的规定和规范,是要求**每个服务只能操作自己对应的一个数据库**。 + +如果你要操作别的服务对应的库,不允许直连别的服务的库,违反微服务架构的规范,你随便交叉胡乱访问,几百个服务的话,全体乱套,这样的一套服务是没法管理的,没法治理的,可能会出现数据被别人改错,自己的库被别人写挂等情况。 + +如果你要操作别人的服务的库,你必须是通过**调用别的服务的接口**来实现,绝对不允许交叉访问别人的数据库。 + +
    + +### 本地消息表 + +本地消息表其实是国外的 ebay 搞出来的这么一套思想。 + +这个大概意思是这样的: + +1. A 系统在自己本地一个事务里操作同时,插入一条数据到消息表; +2. 接着 A 系统将这个消息发送到 MQ 中去; +3. B 系统接收到消息之后,在一个事务里,往自己本地消息表里插入一条数据,同时执行其他的业务操作,如果这个消息已经被处理过了,那么此时这个事务会回滚,这样**保证不会重复处理消息**; +4. B 系统执行成功之后,就会更新自己本地消息表的状态以及 A 系统消息表的状态; +5. 如果 B 系统处理失败了,那么就不会更新消息表状态,那么此时 A 系统会定时扫描自己的消息表,如果有未处理的消息,会再次发送到 MQ 中去,让 B 再次处理; +6. 这个方案保证了最终一致性,哪怕 B 事务失败了,但是 A 会不断重发消息,直到 B 那边成功为止。 + +这个方案说实话最大的问题就在于**严重依赖于数据库的消息表来管理事务**啥的,如果是高并发场景咋办呢?咋扩展呢?所以一般确实很少用。 + +
    + +### 可靠消息最终一致性方案 + +这个的意思,就是干脆不要用本地的消息表了,直接基于 MQ 来实现事务。比如阿里的 RocketMQ 就支持消息事务。 + +大概的意思就是: + +1. A 系统先发送一个 prepared 消息到 mq,如果这个 prepared 消息发送失败那么就直接取消操作别执行了; +2. 如果这个消息发送成功过了,那么接着执行本地事务,如果成功就告诉 mq 发送确认消息,如果失败就告诉 mq 回滚消息; +3. 如果发送了确认消息,那么此时 B 系统会接收到确认消息,然后执行本地的事务; +4. mq 会自动**定时轮询**所有 prepared 消息回调你的接口,问你,这个消息是不是本地事务处理失败了,所有没发送确认的消息,是继续重试还是回滚?一般来说这里你就可以查下数据库看之前本地事务是否执行,如果回滚了,那么这里也回滚吧。这个就是避免可能本地事务执行成功了,而确认消息却发送失败了。 +5. 这个方案里,要是系统 B 的事务失败了咋办?重试咯,自动不断重试直到成功,如果实在是不行,要么就是针对重要的资金类业务进行回滚,比如 B 系统本地回滚后,想办法通知系统 A 也回滚;或者是发送报警由人工来手工回滚和补偿。 +6. 这个还是比较合适的,目前国内互联网公司大都是这么玩儿的,要不你举用 RocketMQ 支持的,要不你就自己基于类似 ActiveMQ?RabbitMQ?自己封装一套类似的逻辑出来,总之思路就是这样子的。 + +
    + +### 最大努力通知方案 + +这个方案的大致意思就是: + +1. 系统 A 本地事务执行完之后,发送个消息到 MQ; +2. 这里会有个专门消费 MQ 的**最大努力通知服务**,这个服务会消费 MQ 然后写入数据库中记录下来,或者是放入个内存队列也可以,接着调用系统 B 的接口; +3. 要是系统 B 执行成功就 ok 了;要是系统 B 执行失败了,那么最大努力通知服务就定时尝试重新调用系统 B,反复 N 次,最后还是不行就放弃。 + +### 你们公司是如何处理分布式事务的? + +如果你真的被问到,可以这么说,我们某某特别严格的场景,用的是 TCC 来保证强一致性;然后其他的一些场景基于阿里的 RocketMQ 来实现分布式事务。 + +你找一个严格资金要求绝对不能错的场景,你可以说你是用的 TCC 方案;如果是一般的分布式事务场景,订单插入之后要调用库存服务更新库存,库存数据没有资金那么的敏感,可以用可靠消息最终一致性方案。 + +友情提示一下,RocketMQ 3.2.6 之前的版本,是可以按照上面的思路来的,但是之后接口做了一些改变,我这里不再赘述了。 + +当然如果你愿意,你可以参考可靠消息最终一致性方案来自己实现一套分布式事务,比如基于 RocketMQ 来玩儿。 + +## 参考资料 + +- [聊聊分布式事务,再说说解决方案](https://www.cnblogs.com/savorboard/p/distributed-system-transaction-consistency.html) diff --git a/docs/theory/load-balance-theory.md b/docs/theory/load-balance-theory.md new file mode 100644 index 00000000..80f0f638 --- /dev/null +++ b/docs/theory/load-balance-theory.md @@ -0,0 +1,563 @@ +# 负载均衡基本原理 + +## 负载均衡原理 + +系统的扩展可分为垂直扩展和水平扩展。 + +- 垂直扩展,是从单机的角度通过增加硬件处理能力,比如 CPU 处理能力,内存容量,磁盘等方面,实现服务器处理能力的提升。这种方式不能满足大型分布式系统(网站),大流量,高并发,海量数据的问题。 +- 水平扩展,是通过添加机器来满足大型网站服务的处理能力。比如:一台机器不能满足,则增加两台或者多台机器,共同承担访问压力。 + - 应用集群:将同一应用部署到多台机器上,组成处理集群,接收负载均衡设备分发的请求,进行处理,并返回相应数据。 + - 负载均衡设备:将用户访问的请求,根据负载均衡算法,分发到集群中的一台处理服务器。(一种把网络请求分散到一个服务器集群中的可用服务器上去的设备) + +负载均衡的作用(解决的问题): + +- 解决并发压力,提高应用处理性能(增加吞吐量,加强网络处理能力); +- 提供故障转移,实现高可用; +- 通过添加或减少服务器数量,提供网站伸缩性(扩展性); +- 安全防护;(负载均衡设备上做一些过滤,黑白名单等处理) + +## 负载均衡分类 + +根据实现技术不同,可分为反向代理、HTTP 重定向、DNS 重定向、修改 IP 地址、修改 MAC 地址等。 + +### 反向代理 + +> 正向代理与反向代理的区别: +> +> - 正向代理:发生在客户端,是由用户主动发起的。比如翻墙,客户端通过主动访问代理服务器,让代理服务器获得需要的外网数据,然后转发回客户端。 +> - 反向代理:发生在服务器端,用户不知道代理的存在。 +> +> 反向代理的作用是保护网站安全,所有互联网的请求都必须经过代理服务器,相当于在 web 服务器和可能的网络攻击之间建立了一个屏障。 +> +> 优点:部署简单 +> +> 缺点:可能成为系统瓶颈 + +
    + +
    + +### HTTP 重定向 + +> HTTP 重定向服务器收到 HTTP 请求之后会返回服务器的地址,并将该地址写入 HTTP 重定向响应中返回给浏览器,浏览器收到后需要再次发送请求。 +> +> 缺点: +> +> - 用户访问的延迟会增加; +> - 如果负载均衡器宕机,就无法访问该站点。 + +
    + +
    + +### DNS 重定向 + +> **使用 DNS 作为负载均衡器,根据负载情况返回不同服务器的 IP 地址。** +> +> 大型网站基本使用了这种方式做为第一级负载均衡手段,然后在内部使用其它方式做第二级负载均衡。 +> +> 优点: +> +> - 使用简单:负载均衡工作,交给 DNS 服务器处理,省掉了负载均衡服务器维护的麻烦 +> - 提高性能:可以支持基于地址的域名解析,解析成距离用户最近的服务器地址,可以加快访问速度,改善性能; +> +> 缺点: +> +> - 可用性差:DNS 解析是多级解析,新增/修改 DNS 后,解析时间较长;解析过程中,用户访问网站将失败; +> - 扩展性低:DNS 负载均衡的控制权在域名商那里,无法对其做更多的改善和扩展; +> - 维护性差:也不能反映服务器的当前运行状态;支持的算法少;不能区分服务器的差异(不能根据系统与服务的状态来判断负载) +> +> 建议: +> +> - 将 DNS 作为第一级负载均衡,A 记录对应着内部负载均衡的 IP 地址,通过内部负载均衡将请求分发到真实的 Web 服务器上。一般用于互联网公司,复杂的业务系统不合适使用。 + +
    + +
    + +### 修改 IP 地址 + +> **在网络层修改请求的目的 IP 地址来进行负载均衡。** +> +> 用户请求数据包,到达负载均衡服务器后,负载均衡服务器在操作系统内核进程获取网络数据包,根据负载均衡算法得到一台真实服务器地址,然后将请求目的地址修改为,获得的真实 ip 地址,不需要经过用户进程处理。真实服务器处理完成后,响应数据包回到负载均衡服务器,负载均衡服务器,再将数据包源地址修改为自身的 ip 地址,发送给用户浏览器。 +> +> IP 负载均衡,真实物理服务器返回给负载均衡服务器,存在两种方式: +> +> 1. 负载均衡服务器在修改目的 ip 地址的同时修改源地址。将数据包源地址设为自身盘,即[网络地址转换(NAT)](https://www.nginx.com/resources/glossary/layer-4-load-balancing/)。 +> 2. 将负载均衡服务器同时作为真实物理服务器集群的网关服务器。 +> +> 优点:在内核进程完成数据分发,比在应用层分发性能更好; +> +> 缺点:所有请求响应都需要经过负载均衡服务器,集群最大吞吐量受限于负载均衡服务器网卡带宽; + +
    + +
    + +### 修改 MAC 地址 + +> 使用 LVS(Linux Virtual Server)这种链路层负载均衡器,根据负载情况修改请求的 MAC 地址。 +> +> 实际处理服务器 ip 和数据请求目的 ip 一致,不需要经过负载均衡服务器进行地址转换,可将响应数据包直接返回给用户浏览器,避免负载均衡服务器网卡带宽成为瓶颈。也称为直接路由模式(DR 模式)。 +> +> 优点:性能好; +> +> 缺点:配置复杂; +> +> 建议:DR 模式是目前使用最广泛的一种负载均衡方式。 + +
    + +
    + +### 混合负载均衡 + +由于多个服务器群内硬件设备、各自的规模、提供的服务等的差异,可以考虑给每个服务器群采用最合适的负载均衡方式,然后又在这多个服务器群间再一次负载均衡或群集起来以一个整体向外界提供服务(即把这多个服务器群当做一个新的服务器群),从而达到最佳的性能。将这种方式称之为混合型负载均衡。 + +此种方式有时也用于单台均衡设备的性能不能满足大量连接请求的情况下。是目前大型互联网公司,普遍使用的方式。 + +方式一,如下图: + +
    + +以上模式适合有动静分离的场景,反向代理服务器(集群)可以起到缓存和动态请求分发的作用,当时静态资源缓存在代理服务器时,则直接返回到浏览器。如果动态页面则请求后面的应用负载均衡(应用集群)。 + +方式二,如下图: + +
    + +以上模式,适合动态请求场景。 + +因混合模式,可以根据具体场景,灵活搭配各种方式,以上两种方式仅供参考。 + +## 负载均衡算法 + +常用的负载均衡算法有:轮询、随机、最少连接、源地址散列、加权等方式。 + +### 轮询 + +> 轮询(Round Robin)算法将所有请求,依次分发到每台服务器上,适合服务器硬件同相同的场景。 +> +> - 优点:服务器请求数目相同; +> - 缺点:服务器压力不一样,不适合服务器配置不同的情况; + +下图中,一共有 6 个客户端产生了 6 个请求,这 6 个请求按 (1, 2, 3, 4, 5, 6) 的顺序发送。最后,(1, 3, 5) 的请求会被发送到服务器 1,(2, 4, 6) 的请求会被发送到服务器 2。 + +
    + +
    + +该算法比较适合每个服务器的性能差不多的场景,如果有性能存在差异的情况下,那么性能较差的服务器可能无法承担过大的负载(下图的 Server 2)。 + +
    + +
    + +算法示例: + +```java +private Integer pos = 0; +public void roundRobin() { + List keyList = new ArrayList(serverMap.keySet()); + String server = null; + synchronized (pos) { + if (pos > keyList.size()) { + pos = 0; + } + server = keyList.get(pos); + pos++; + } + System.out.println(server); +} +``` + +#### 加权轮询 + +> 加权轮询(Weighted Round Robbin)算法在轮询算法的基础上,通过加权的方式,进行负载服务器分配。 +> +> - 优点:根据权重,调节转发服务器的请求数目; +> - 缺点:使用相对复杂; + +加权轮询是在轮询的基础上,根据服务器的性能差异,为服务器赋予一定的权值。例如下图中,服务器 1 被赋予的权值为 5,服务器 2 被赋予的权值为 1,那么 (1, 2, 3, 4, 5) 请求会被发送到服务器 1,(6) 请求会被发送到服务器 2。 + +
    + +
    + +算法示例: + +```java +public void weightRoundRobin() { + Set keySet = serverMap.keySet(); + List servers = new ArrayList(); + for (Iterator it = keySet.iterator(); it.hasNext(); ) { + String server = it.next(); + int weithgt = serverMap.get(server); + for (int i = 0; i < weithgt; i++) { + servers.add(server); + } + } + String server = null; + synchronized (pos) { + if (pos > keySet.size()) { + pos = 0; + } + server = servers.get(pos); + pos++; + } + System.out.println(server); +} +``` + +### 最少连接 + +> 最少连接(Least Busy)算法将请求分配到连接数最少的服务器(目前处理请求最少的服务器)。 +> +> - 优点:根据服务器当前的请求处理情况,动态分配; +> - 缺点:算法实现相对复杂,需要监控服务器请求连接数; + +由于每个请求的连接时间不一样,使用轮询或者加权轮询算法的话,可能会让一台服务器当前连接数过大,而另一台服务器的连接过小,造成负载不均衡。例如下图中,(1, 3, 5) 请求会被发送到服务器 1,但是 (1, 3) 很快就断开连接,此时只有 (5) 请求连接服务器 1;(2, 4, 6) 请求被发送到服务器 2,只有 (2) 的连接断开。该系统继续运行时,服务器 2 会承担过大的负载。 + +
    + +
    + +最少连接算法就是将请求发送给当前最少连接数的服务器上。例如下图中,服务器 1 当前连接数最小,那么新到来的请求 6 就会被发送到服务器 1 上。 + +
    + +
    + +#### 加权最少连接 + +> 加权最少连接(Weighted Least Connection)在最少连接的基础上,根据服务器的性能为每台服务器分配权重,再根据权重计算出每台服务器能处理的连接数。 + +
    + +
    +### 随机 + +> 随机(Random)算法将请求随机分配到各个服务器。 +> +> - 优点:使用简单; +> - 缺点:不适合机器配置不同的场景; + +和轮询算法类似,该算法比较适合服务器性能差不多的场景。 + +
    + +
    + +### 哈希(Hash) + +#### 普通 Hash + +> 根据 IP 地址进行 Hash 计算,得到 IP 地址。 +> +> - 优点:将来自同一 IP 地址的请求,同一会话期内,转发到相同的服务器;实现会话粘滞。 +> - 缺点:目标服务器宕机后,会话会丢失; + +
    + +
    + +算法示例: + +```java +public void hash() { + List keyList = new ArrayList(serverMap.keySet()); + String remoteIp = "192.168.2.215"; + int hashCode = remoteIp.hashCode(); + int idx = hashCode % keyList.size(); + String server = keyList.get(Math.abs(idx)); + System.out.println(server); +} +``` + +#### 一致性哈希 + +一致性 Hash,相同参数的请求总是发到同一提供者。当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。 + +## 硬件负载均衡 + +采用硬件的方式实现负载均衡,一般是单独的负载均衡服务器,价格昂贵,一般土豪级公司可以考虑,业界领先的有两款,F5 和 A10。 + +- 使用硬件负载均衡,主要考虑一下几个方面: +- 功能考虑:功能全面支持各层级的负载均衡,支持全面的负载均衡算法,支持全局负载均衡; +- 性能考虑:一般软件负载均衡支持到 5 万级并发已经很困难了,硬件负载均衡可以支持 +- 稳定性:商用硬件负载均衡,经过了良好的严格的测试,从经过大规模使用,在稳定性方面高; +- 安全防护:硬件均衡设备除具备负载均衡功能外,还具备防火墙,防 DDOS 攻击等安全功能; +- 维护角度:提供良好的维护管理界面,售后服务和技术支持; +- 土豪公司:F5 Big Ip 价格:15w\~55w 不等;A10 价格:55w-100w 不等; + +缺点 + +- 价格昂贵; +- 扩展能力差; + +小结 + +- 一般硬件的负载均衡也要做双机高可用,因此成本会比较高。 +- 互联网公司一般使用开源软件,因此大部分应用采用软件负载均衡;部分采用硬件负载均衡。 + +比如某互联网公司,目前是使用几台 F5 做全局负载均衡,内部使用 Nginx 等软件负载均衡。 + +## Ngnix 负载均衡 + +Ngnix 是一款轻量级的 Web 服务器/反向代理服务器,工作在七层 Http 协议的负载均衡系统。具有高性能、高并发、低内存使用等特点。是一个轻量级的 Http 和反向代理服务器。Nginx 使用 epoll and kqueue 作为开发模型。能够支持高达 50,000 个并发连接数的响应。 + +操作系统:Liunx,Windows(Linux、FreeBSD、Solaris、Mac OS X、AIX 以及 Microsoft Windows) + +开发语言:C + +并发性能:官方支持每秒 5 万并发,实际国内一般到每秒 2 万并发,有优化到每秒 10 万并发的。具体性能看应用场景。 + +### Ngnix 特点 + +- 模块化设计:良好的扩展性,可以通过模块方式进行功能扩展。 +- 高可靠性:主控进程和 worker 是同步实现的,一个 worker 出现问题,会立刻启动另一个 worker。 +- 内存消耗低:一万个长连接(keep-alive),仅消耗 2.5MB 内存。 +- 支持热部署:不用停止服务器,实现更新配置文件,更换日志文件、更新服务器程序版本。 +- 并发能力强:官方数据每秒支持 5 万并发; +- 功能丰富:优秀的反向代理功能和灵活的负载均衡策略 + +### Ngnix 功能 + +#### 基本功能 + +- 支持静态资源的 web 服务器。 +- http,smtp,pop3 协议的反向代理服务器、缓存、负载均衡; +- 支持 FASTCGI(fpm) +- 支持模块化,过滤器(让文本可以实现压缩,节约带宽),ssl 及图像大小调整。 +- 内置的健康检查功能 +- 基于名称和 ip 的虚拟主机 +- 定制访问日志 +- 支持平滑升级 +- 支持 KEEPALIVE +- 支持 url rewrite +- 支持路径别名 +- 支持基于 IP 和用户名的访问控制。 +- 支持传输速率限制,支持并发数限制。 + +#### 扩展功能 + +#### 性能 + +Nginx 的高并发,官方测试支持 5 万并发连接。实际生产环境能到 2-3 万并发连接数。10000 个非活跃的 HTTP keep-alive 连接仅占用约 2.5MB 内存。三万并发连接下,10 个 Nginx 进程,消耗内存 150M。淘宝 tengine 团队测试结果是“24G 内存机器上,处理并发请求可达 200 万”。 + +### Ngnix 架构 + +#### Nginx 的基本工作模式 + +
    + +一个 master 进程,生成一个或者多个 worker 进程。但是这里 master 是使用 root 身份启动的,因为 nginx 要工作在 80 端口。而只有管理员才有权限启动小于低于 1023 的端口。master 主要是负责的作用只是启动 worker,加载配置文件,负责系统的平滑升级。其它的工作是交给 worker。那么当 worker 被启动之后,也只是负责一些 web 最简单的工作,而其他的工作都是有 worker 中调用的模块来实现的。 + +模块之间是以流水线的方式实现功能的。流水线,指的是一个用户请求,由多个模块组合各自的功能依次实现完成的。比如:第一个模块只负责分析请求首部,第二个模块只负责查找数据,第三个模块只负责压缩数据,依次完成各自工作。来实现整个工作的完成。 + +他们是如何实现热部署的呢?其实是这样的,我们前面说 master 不负责具体的工作,而是调用 worker 工作,他只是负责读取配置文件,因此当一个模块修改或者配置文件发生变化,是由 master 进行读取,因此此时不会影响到 worker 工作。在 master 进行读取配置文件之后,不会立即的把修改的配置文件告知 worker。而是让被修改的 worker 继续使用老的配置文件工作,当 worker 工作完毕之后,直接当掉这个子进程,更换新的子进程,使用新的规则。 + +#### Nginx 支持的 sendfile 机制 + +Sendfile 机制,用户将请求发给内核,内核根据用户的请求调用相应用户进程,进程在处理时需要资源。此时再把请求发给内核(进程没有直接 IO 的能力),由内核加载数据。内核查找到数据之后,会把数据复制给用户进程,由用户进程对数据进行封装,之后交给内核,内核在进行 tcp/ip 首部的封装,最后再发给客户端。这个功能用户进程只是发生了一个封装报文的过程,却要绕一大圈。因此 nginx 引入了 sendfile 机制,使得内核在接受到数据之后,不再依靠用户进程给予封装,而是自己查找自己封装,减少了一个很长一段时间的浪费,这是一个提升性能的核心点。 + +
    + +以上内容摘自网友发布的文章,简单一句话是资源的处理,直接通过内核层进行数据传递,避免了数据传递到应用层,应用层再传递到内核层的开销。 + +目前高并发的处理,一般都采用 sendfile 模式。通过直接操作内核层数据,减少应用与内核层数据传递。 + +#### Nginx 通信模型(I/O 复用机制) + +开发模型:epoll 和 kqueue。 + +支持的事件机制:kqueue、epoll、rt signals、/dev/poll 、event ports、select 以及 poll。 + +支持的 kqueue 特性包括 EV_CLEAR、EV_DISABLE、NOTE_LOWAT、EV_EOF,可用数据的数量,错误代码. + +支持 sendfile、sendfile64 和 sendfilev;文件 AIO;DIRECTIO;支持 Accept-filters 和 TCP_DEFER_ACCEP. + +以上概念较多,大家自行百度或谷歌,知识领域是网络通信(BIO,NIO,AIO)和多线程方面的知识。 + +### Ngnix 均衡策略 + +nginx 的负载均衡策略可以划分为两大类:内置策略和扩展策略。内置策略包含加权轮询和 ip hash,在默认情况下这两种策略会编译进 nginx 内核,只需在 nginx 配置中指明参数即可。扩展策略有很多,如 fair、通用 hash、consistent hash 等,默认不编译进 nginx 内核。由于在 nginx 版本升级中负载均衡的代码没有本质性的变化,因此下面将以 nginx1.0.15 稳定版为例,从源码角度分析各个策略。 + +#### 加权轮询(weighted round robin) + +轮询的原理很简单,首先我们介绍一下轮询的基本流程。如下是处理一次请求的流程图: + +
    + +图中有两点需要注意,第一,如果可以把加权轮询算法分为先深搜索和先广搜索,那么 nginx 采用的是先深搜索算法,即将首先将请求都分给高权重的机器,直到该机器的权值降到了比其他机器低,才开始将请求分给下一个高权重的机器;第二,当所有后端机器都 down 掉时,nginx 会立即将所有机器的标志位清成初始状态,以避免造成所有的机器都处在 timeout 的状态,从而导致整个前端被夯住。 + +#### ip hash + +ip hash 是 nginx 内置的另一个负载均衡的策略,流程和轮询很类似,只是其中的算法和具体的策略有些变化,如下图所示: + +
    + +#### fair + +fair 策略是扩展策略,默认不被编译进 nginx 内核。其原理是根据后端服务器的响应时间判断负载情况,从中选出负载最轻的机器进行分流。这种策略具有很强的自适应性,但是实际的网络环境往往不是那么简单,因此要慎用。 + +#### 通用 hash、一致性 hash + +这两种也是扩展策略,在具体的实现上有些差别,通用 hash 比较简单,可以以 nginx 内置的变量为 key 进行 hash,一致性 hash 采用了 nginx 内置的一致性 hash 环,可以支持 memcache。 + +### Ngnix 场景 + +Ngnix 一般作为入口负载均衡或内部负载均衡,结合反向代理服务器使用。以下架构示例,仅供参考,具体使用根据场景而定。 + +#### 入口负载均衡架构 + +
    + +Ngnix 服务器在用户访问的最前端。根据用户请求再转发到具体的应用服务器或二级负载均衡服务器(LVS) + +#### 内部负载均衡架构 + +
    + +LVS 作为入口负载均衡,将请求转发到二级 Ngnix 服务器,Ngnix 再根据请求转发到具体的应用服务器。 + +#### Ngnix 高可用 + +
    + +分布式系统中,应用只部署一台服务器会存在单点故障,负载均衡同样有类似的问题。一般可采用主备或负载均衡设备集群的方式节约单点故障或高并发请求分流。 + +Ngnix 高可用,至少包含两个 Ngnix 服务器,一台主服务器,一台备服务器,之间使用 Keepalived 做健康监控和故障检测。开放 VIP 端口,通过防火墙进行外部映射。 + +DNS 解析公网的 IP 实际为 VIP。 + +## LVS 负载均衡 + +LVS 是一个开源的软件,由毕业于国防科技大学的章文嵩博士于 1998 年 5 月创立,用来实现 Linux 平台下的简单负载均衡。LVS 是 Linux Virtual Server 的缩写,意思是 Linux 虚拟服务器。 + +基于 IP 层的负载均衡调度技术,它在操作系统核心层上,将来自 IP 层的 TCP/UDP 请求均衡地转移到不同的 服务器,从而将一组服务器构成一个高性能、高可用的虚拟服务器。 + +操作系统:Liunx + +开发语言:C + +并发性能:默认 4096,可以修改但需要重新编译。 + +### LVS 功能 + +LVS 的主要功能是实现 IP 层(网络层)负载均衡,有 NAT,TUN,DR 三种请求转发模式。 + +#### LVS/NAT 方式的负载均衡集群 + +NAT 是指 Network Address Translation,它的转发流程是:Director 机器收到外界请求,改写数据包的目标地址,按相应的调度算法将其发送到相应 Real Server 上,Real Server 处理完该请求后,将结果数据包返回到其默认网关,即 Director 机器上,Director 机器再改写数据包的源地址,最后将其返回给外界。这样就完成一次负载调度。 + +构架一个最简单的 LVS/NAT 方式的负载均衡集群 Real Server 可以是任何的操作系统,而且无需做任何特殊的设定,惟一要做的就是将其默认网关指向 Director 机器。Real Server 可以使用局域网的内部 IP(192.168.0.0/24)。Director 要有两块网卡,一块网卡绑定一个外部 IP 地址 (10.0.0.1),另一块网卡绑定局域网的内部 IP(192.168.0.254),作为 Real Server 的默认网关。 + +LVS/NAT 方式实现起来最为简单,而且 Real Server 使用的是内部 IP,可以节省 Real IP 的开销。但因为执行 NAT 需要重写流经 Director 的数据包,在速度上有一定延迟; + +当用户的请求非常短,而服务器的回应非常大的情况下,会对 Director 形成很大压力,成为新的瓶颈,从而使整个系统的性能受到限制。 + +#### LVS/TUN 方式的负载均衡集群 + +TUN 是指 IP Tunneling,它的转发流程是:Director 机器收到外界请求,按相应的调度算法,通过 IP 隧道发送到相应 Real Server,Real Server 处理完该请求后,将结果数据包直接返回给客户。至此完成一次负载调度。 + +最简单的 LVS/TUN 方式的负载均衡集群架构使用 IP Tunneling 技术,在 Director 机器和 Real Server 机器之间架设一个 IP Tunnel,通过 IP Tunnel 将负载分配到 Real Server 机器上。Director 和 Real Server 之间的关系比较松散,可以是在同一个网络中,也可以是在不同的网络中,只要两者能够通过 IP Tunnel 相连就行。收到负载分配的 Real Server 机器处理完后会直接将反馈数据送回给客户,而不必通过 Director 机器。实际应用中,服务器必须拥有正式的 IP 地址用于与客户机直接通信,并且所有服务器必须支持 IP 隧道协议。 + +该方式中 Director 将客户请求分配到不同的 Real Server,Real Server 处理请求后直接回应给用户,这样 Director 就只处理客户机与服务器的一半连接,极大地提高了 Director 的调度处理能力,使集群系统能容纳更多的节点数。另外 TUN 方式中的 Real Server 可以在任何 LAN 或 WAN 上运行,这样可以构筑跨地域的集群,其应对灾难的能力也更强,但是服务器需要为 IP 封装付出一定的资源开销,而且后端的 Real Server 必须是支持 IP Tunneling 的操作系统。 + +#### LVS/TUN 方式的负载均衡集群 + +DR 是指 Direct Routing,它的转发流程是:Director 机器收到外界请求,按相应的调度算法将其直接发送到相应 Real Server,Real Server 处理完该请求后,将结果数据包直接返回给客户,完成一次负载调度。 + +构架一个最简单的 LVS/DR 方式的负载均衡集群 Real Server 和 Director 都在同一个物理网段中,Director 的网卡 IP 是 192.168.0.253,再绑定另一个 IP: 192.168.0.254 作为对外界的 virtual IP,外界客户通过该 IP 来访问整个集群系统。Real Server 在 lo 上绑定 IP:192.168.0.254,同时加入相应的路由。 + +LVS/DR 方式与前面的 LVS/TUN 方式有些类似,前台的 Director 机器也是只需要接收和调度外界的请求,而不需要负责返回这些请求的反馈结果,所以能够负载更多的 Real Server,提高 Director 的调度处理能力,使集群系统容纳更多的 Real Server。但 LVS/DR 需要改写请求报文的 MAC 地址,所以所有服务器必须在同一物理网段内。 + +### LVS 架构 + +LVS 架设的服务器集群系统有三个部分组成:最前端的负载均衡层(Loader Balancer),中间的服务器群组层,用 Server Array 表示,最底层的数据共享存储层,用 Shared Storage 表示。在用户看来所有的应用都是透明的,用户只是在使用一个虚拟服务器提供的高性能服务。 + +LVS 的体系架构如图: + +
    + +LVS 的各个层次的详细介绍: + +Load Balancer 层:位于整个集群系统的最前端,有一台或者多台负载调度器(Director Server)组成,LVS 模块就安装在 Director Server 上,而 Director 的主要作用类似于一个路由器,它含有完成 LVS 功能所设定的路由表,通过这些路由表把用户的请求分发给 Server Array 层的应用服务器(Real Server)上。同时,在 Director Server 上还要安装对 Real Server 服务的监控模块 Ldirectord,此模块用于监测各个 Real Server 服务的健康状况。在 Real Server 不可用时把它从 LVS 路由表中剔除,恢复时重新加入。 + +Server Array 层:由一组实际运行应用服务的机器组成,Real Server 可以是 WEB 服务器、MAIL 服务器、FTP 服务器、DNS 服务器、视频服务器中的一个或者多个,每个 Real Server 之间通过高速的 LAN 或分布在各地的 WAN 相连接。在实际的应用中,Director Server 也可以同时兼任 Real Server 的角色。 + +Shared Storage 层:是为所有 Real Server 提供共享存储空间和内容一致性的存储区域,在物理上,一般有磁盘阵列设备组成,为了提供内容的一致性,一般可以通过 NFS 网络文件系统共享数 据,但是 NFS 在繁忙的业务系统中,性能并不是很好,此时可以采用集群文件系统,例如 Red hat 的 GFS 文件系统,oracle 提供的 OCFS2 文件系统等。 + +从整个 LVS 结构可以看出,Director Server 是整个 LVS 的核心,目前,用于 Director Server 的操作系统只能是 Linux 和 FreeBSD,linux2.6 内核不用任何设置就可以支持 LVS 功能,而 FreeBSD 作为 Director Server 的应用还不是很多,性能也不是很好。对于 Real Server,几乎可以是所有的系统平台,Linux、windows、Solaris、AIX、BSD 系列都能很好的支持。 + +### LVS 均衡策略 + +LVS 默认支持八种负载均衡策略,简述如下: + +#### 轮询调度(Round Robin) + +调度器通过“轮询”调度算法将外部请求按顺序轮流分配到集群中的真实服务器上,它均等地对待每一台服务器,而不管服务器上实际的连接数和系统负载。 + +#### 加权轮询(Weighted Round Robin) + +调度器通过“加权轮询”调度算法根据真实服务器的不同处理能力来调度访问请求。这样可以保证处理能力强的服务器能处理更多的访问流量。调度器可以自动问询真实服务器的负载情况,并动态地调整其权值。 + +#### 最少链接(Least Connections) + +调度器通过“最少连接”调度算法动态地将网络请求调度到已建立的链接数最少的服务器上。如果集群系统的真实服务器具有相近的系统性能,采用“最小连接”调度算法可以较好地均衡负载。 + +#### 加权最少链接(Weighted Least Connections) + +在集群系统中的服务器性能差异较大的情况下,调度器采用“加权最少链接”调度算法优化负载均衡性能,具有较高权值的服务器将承受较大比例的活动连接负载。调度器可以自动问询真实服务器的负载情况,并动态地调整其权值。 + +#### 基于局部性的最少链接(Locality-Based Least Connections) + +“基于局部性的最少链接”调度算法是针对目标 IP 地址的负载均衡,目前主要用于 Cache 集群系统。该算法根据请求的目标 IP 地址找出该目标 IP 地址最近使用的服务器,若该服务器是可用的且没有超载,将请求发送到该服务器;若服务器不存在,或者该服务器超载且有服务器处于一半的工作负载,则用“最少链接” 的原则选出一个可用的服务器,将请求发送到该服务器。 + +#### 带复制的基于局部性最少链接(Locality-Based Least Connections with Replication) + +“带复制的基于局部性最少链接”调度算法也是针对目标 IP 地址的负载均衡,目前主要用于 Cache 集群系统。它与 LBLC 算法的不同之处是它要维护从一个目标 IP 地址到一组服务器的映射,而 LBLC 算法维护从一个目标 IP 地址到一台服务器的映射。该算法根据请求的目标 IP 地址找出该目标 IP 地址对应的服务器组,按“最小连接”原则从服务器组中选出一台服务器,若服务器没有超载,将请求发送到该服务器;若服务器超载,则按“最小连接”原则从这个集群中选出一台服务器,将该服务器加入到服务器组中,将请求发送到该服务器。同时,当该服务器组有一段时间没有被修改,将最忙的服务器从服务器组中删除,以降低复制的程度。 + +#### 目标地址散列(Destination Hashing) + +“目标地址散列”调度算法根据请求的目标 IP 地址,作为散列键(Hash Key)从静态分配的散列表找出对应的服务器,若该服务器是可用的且未超载,将请求发送到该服务器,否则返回空。 + +#### 源地址散列(Source Hashing) + +“源地址散列”调度算法根据请求的源 IP 地址,作为散列键(Hash Key)从静态分配的散列表找出对应的服务器,若该服务器是可用的且未超载,将请求发送到该服务器,否则返回空。 + +除具备以上负载均衡算法外,还可以自定义均衡策略。 + +### LVS 场景 + +一般作为入口负载均衡或内部负载均衡,结合反向代理服务器使用。相关架构可参考 Ngnix 场景架构。 + +## HaProxy 负载均衡 + +HAProxy 也是使用较多的一款负载均衡软件。HAProxy 提供高可用性、负载均衡以及基于 TCP 和 HTTP 应用的代理,支持虚拟主机,是免费、快速并且可靠的一种解决方案。特别适用于那些负载特大的 web 站点。运行模式使得它可以很简单安全的整合到当前的架构中,同时可以保护你的 web 服务器不被暴露到网络上。 + +### HaProxy 特点 + +- 支持两种代理模式:TCP(四层)和 HTTP(七层),支持虚拟主机; +- 配置简单,支持 url 检测后端服务器状态; +- 做负载均衡软件使用,在高并发情况下,处理速度高于 nginx; +- TCP 层多用于 Mysql 从(读)服务器负载均衡。 (对 Mysql 进行负载均衡,对后端的 DB 节点进行检测和负载均衡) +- 能够补充 Nginx 的一些缺点比如 Session 的保持,Cookie 引导等工作 + +### HaProxy 均衡策略 + +支持四种常用算法: + +- roundrobin:轮询,轮流分配到后端服务器; +- static-rr:根据后端服务器性能分配; +- leastconn:最小连接者优先处理; +- source:根据请求源 IP,与 Nginx 的 IP_Hash 类似。 + +## 资料 + +- [大型网站架构系列:负载均衡详解(1)](https://www.cnblogs.com/itfly8/p/5043435.html) +- [大型网站架构系列:负载均衡详解(2)](https://www.cnblogs.com/itfly8/p/5043452.html) +- [大型网站架构系列:负载均衡详解(3)](https://www.cnblogs.com/itfly8/p/5080743.html) +- [大型网站架构系列:负载均衡详解(4)](https://www.cnblogs.com/itfly8/p/5080988.html) +- https://segmentfault.com/a/1190000004492447 diff --git a/docs/technology/mq/mq-theory.md b/docs/theory/mq-theory.md similarity index 93% rename from docs/technology/mq/mq-theory.md rename to docs/theory/mq-theory.md index 58f2d5e2..70cc3225 100644 --- a/docs/technology/mq/mq-theory.md +++ b/docs/theory/mq-theory.md @@ -1,36 +1,36 @@ -# 分布式消息队列 +# 分布式消息队列基本原理 > 消息队列是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量削锋等问题。实现高性能,高可用,可伸缩和最终一致性架构。是大型分布式系统不可缺少的中间件。 - - -- [1. 消息队列应用场景](#1-消息队列应用场景) - - [1.1. 异步处理](#11-异步处理) - - [1.2. 应用解耦](#12-应用解耦) - - [1.3. 流量削锋](#13-流量削锋) - - [1.4. 日志处理](#14-日志处理) - - [1.5. 消息通讯](#15-消息通讯) -- [2. JMS 消息服务](#2-jms-消息服务) - - [2.1. 消息模型](#21-消息模型) - - [2.1.1. P2P 模式](#211-p2p-模式) - - [2.1.2. Pub/sub 模式](#212-pubsub-模式) - - [2.2. 消息消费](#22-消息消费) - - [2.3. JMS 编程模型](#23-jms-编程模型) -- [3. 常用 MQ 中间件](#3-常用-mq-中间件) - - [3.1. ActiveMQ](#31-activemq) - - [3.2. RabbitMQ](#32-rabbitmq) - - [3.3. ZeroMQ](#33-zeromq) - - [3.4. Kafka](#34-kafka) -- [4. MQ 示例](#4-mq-示例) - - [4.1. 电商系统](#41-电商系统) - - [4.2. 日志收集系统](#42-日志收集系统) -- [5. 资料](#5-资料) + + +- [消息队列应用场景](#消息队列应用场景) + - [异步处理](#异步处理) + - [应用解耦](#应用解耦) + - [流量削锋](#流量削锋) + - [日志处理](#日志处理) + - [消息通讯](#消息通讯) +- [JMS 消息服务](#jms-消息服务) + - [消息模型](#消息模型) + - [P2P 模式](#p2p-模式) + - [Pub/sub 模式](#pubsub-模式) + - [消息消费](#消息消费) + - [JMS 编程模型](#jms-编程模型) +- [常用 MQ 中间件](#常用-mq-中间件) + - [ActiveMQ](#activemq) + - [RabbitMQ](#rabbitmq) + - [ZeroMQ](#zeromq) + - [Kafka](#kafka) +- [MQ 示例](#mq-示例) + - [电商系统](#电商系统) + - [日志收集系统](#日志收集系统) +- [资料](#资料) -## 1. 消息队列应用场景 +## 消息队列应用场景 -### 1.1. 异步处理 +### 异步处理 场景说明:用户注册后,需要发注册邮件和注册短信。传统的做法有两种 1.串行的方式;2.并行方式。 @@ -54,7 +54,7 @@ 按照以上约定,用户的响应时间相当于是注册信息写入数据库的时间,也就是 50 毫秒。注册邮件,发送短信写入消息队列后,直接返回,因此写入消息队列的速度很快,基本可以忽略,因此用户的响应时间可能是 50 毫秒。因此架构改变后,系统的吞吐量提高到每秒 20 QPS。比串行提高了 3 倍,比并行提高了两倍。 -### 1.2. 应用解耦 +### 应用解耦 场景说明:用户下单后,订单系统需要通知库存系统。传统的做法是,订单系统调用库存系统的接口。如下图: @@ -62,9 +62,9 @@ 传统模式的缺点: -1) 假如库存系统无法访问,则订单减库存将失败,从而导致订单失败; +1) 假如库存系统无法访问,则订单减库存将失败,从而导致订单失败; -2) 订单系统与库存系统耦合; +2) 订单系统与库存系统耦合; 如何解决以上问题呢?引入应用消息队列后的方案,如下图: @@ -74,7 +74,7 @@ - 库存系统:订阅下单的消息,采用拉/推的方式,获取下单信息,库存系统根据下单信息,进行库存操作。 - 假如:在下单时库存系统不能正常使用。也不影响正常下单,因为下单后,订单系统写入消息队列就不再关心其他的后续操作了。实现订单系统与库存系统的应用解耦。 -### 1.3. 流量削锋 +### 流量削锋 流量削锋也是消息队列中的常用场景,一般在秒杀或团抢活动中使用广泛。 @@ -88,7 +88,7 @@ 1. 用户的请求,服务器接收后,首先写入消息队列。假如消息队列长度超过最大数量,则直接抛弃用户请求或跳转到错误页面; 2. 秒杀业务根据消息队列中的请求信息,再做后续处理。 -### 1.4. 日志处理 +### 日志处理 日志处理是指将消息队列用在日志处理中,比如 Kafka 的应用,解决大量日志传输的问题。架构简化如下: @@ -109,7 +109,7 @@ - Elasticsearch - 实时日志分析服务的核心技术,一个 schemaless,实时的数据存储服务,通过 index 组织数据,兼具强大的搜索和统计功能。 - Kibana - 基于 Elasticsearch 的数据可视化组件,超强的数据可视化能力是众多公司选择 ELK stack 的重要原因。 -### 1.5. 消息通讯 +### 消息通讯 消息通讯是指,消息队列一般都内置了高效的通信机制,因此也可以用在纯的消息通讯。比如实现点对点消息队列,或者聊天室等。 @@ -127,20 +127,20 @@ 以上实际是消息队列的两种消息模式,点对点或发布订阅模式。模型为示意图,供参考。 -## 2. JMS 消息服务 +## JMS 消息服务 讲消息队列就不得不提 JMS 。JMS(JAVA Message Service,java 消息服务)API 是一个消息服务的标准/规范,允许应用程序组件基于 JavaEE 平台创建、发送、接收和读取消息。它使分布式通信耦合度更低,消息服务更加可靠以及异步性。 在 EJB 架构中,有消息 bean 可以无缝的与 JM 消息服务集成。在 J2EE 架构模式中,有消息服务者模式,用于实现消息与应用直接的解耦。 -### 2.1. 消息模型 +### 消息模型 在 JMS 标准中,有两种消息模型: - P2P(Point to Point) - Pub/Sub(Publish/Subscribe) -#### 2.1.1. P2P 模式 +#### P2P 模式
    @@ -154,7 +154,7 @@ P2P 的特点 如果希望发送的每个消息都会被成功处理的话,那么需要 P2P 模式。 -#### 2.1.2. Pub/sub 模式 +#### Pub/sub 模式
    @@ -170,7 +170,7 @@ Pub/Sub 的特点 如果希望发送的消息可以不被做任何处理、或者只被一个消息者处理、或者可以被多个消费者处理的话,那么可以采用 Pub/Sub 模型。 -### 2.2. 消息消费 +### 消息消费 在 JMS 中,消息的产生和消费都是异步的。对于消费来说,JMS 的消息者可以通过两种方式来消费消息。 @@ -186,7 +186,7 @@ JNDI:Java 命名和目录接口,是一种标准的 Java 命名系统接口。 JNDI 在 JMS 中起到查找和访问发送目标或消息来源的作用。 -### 2.3. JMS 编程模型 +### JMS 编程模型 (1) ConnectionFactory @@ -220,11 +220,11 @@ Session 是操作消息的接口。可以通过 session 创建生产者、消费 深入学习 JMS 对掌握 JAVA 架构,EJB 架构有很好的帮助,消息中间件也是大型分布式系统必须的组件。本次分享主要做全局性介绍,具体的深入需要大家学习,实践,总结,领会。 -## 3. 常用 MQ 中间件 +## 常用 MQ 中间件 一般商用的容器,比如 WebLogic,JBoss,都支持 JMS 标准,开发上很方便。但免费的比如 Tomcat,Jetty 等则需要使用第三方的消息中间件。本部分内容介绍常用的消息中间件(ActiveMQ、RabbitMQ、RocketMQ、Kafka)以及他们的特点。 -### 3.1. ActiveMQ +### ActiveMQ ActiveMQ 是 Apache 出品,最流行的,能力强劲的开源消息总线。ActiveMQ 是一个完全支持 JMS1.1 和 J2EE 1.4 规范的 JMS Provider 实现,尽管 JMS 规范出台已经是很久的事情了,但是 JMS 在当今的 J2EE 应用中间仍然扮演着特殊的地位。 @@ -250,7 +250,7 @@ ActiveMQ 特性如下: ⒑ 可以很容易得调用内嵌 JMS provider,进行测试 -### 3.2. RabbitMQ +### RabbitMQ RabbitMQ 是流行的开源消息队列系统,用 erlang 语言开发。RabbitMQ 是 AMQP(高级消息队列协议)的标准实现。支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP 等,支持 AJAX,持久化。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。 @@ -292,7 +292,7 @@ channel:消息通道,在客户端的每个连接里,可建立多个 channe exchange 接收到消息后,就根据消息的 key 和已经设置的 binding,进行消息路由,将消息投递到一个或多个队列里。 -### 3.3. ZeroMQ +### ZeroMQ 号称史上最快的消息队列,它实际类似于 Socket 的一系列接口,他跟 Socket 的区别是:普通的 socket 是端到端的(1:1 的关系),而 ZMQ 却是可以 N:M 的关系,人们对 BSD 套接字的了解较多的是点对点的连接,点对点连接需要显式地建立连接、销毁连接、选择协议(TCP/UDP)和处理错误等,而 ZMQ 屏蔽了这些细节,让你的网络编程更为简单。ZMQ 用于 node 与 node 间的通信,node 可以是主机或者是进程。 @@ -322,7 +322,7 @@ ZeroMQ 高性能设计要点: 区别于传统的多线程并发模式,信号量或者临界区, zeroMQ 充分利用多核的优势,每个核绑定运行一个工作者线程,避免多线程之间的 CPU 切换开销。 -### 3.4. Kafka +### Kafka Kafka 是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模的网站中的所有动作流数据。 这种动作(网页浏览,搜索和其他用户的行动)是在现代网络上的许多社会功能的一个关键因素。 这些数据通常是由于吞吐量的要求而通过处理日志和日志聚合来解决。 对于像 Hadoop 的一样的日志数据和离线分析系统,但又要求实时处理的限制,这是一个可行的解决方案。Kafka 的目的是通过 Hadoop 的并行加载机制来统一线上和离线的消息处理,也是为了通过集群机来提供实时的消费。 @@ -361,9 +361,9 @@ Parition 是物理上的概念,每个 Topic 包含一个或多个 Partition. 一般应用在大数据日志处理或对实时性(少量延迟),可靠性(少量丢数据)要求稍低的场景使用。 -## 4. MQ 示例 +## MQ 示例 -### 4.1. 电商系统 +### 电商系统
    @@ -375,7 +375,7 @@ Parition 是物理上的概念,每个 Topic 包含一个或多个 Partition. (3)消息将应用解耦的同时,带来了数据一致性问题,可以采用最终一致性方式解决。比如主数据写入数据库,扩展应用根据消息队列,并结合数据库方式实现基于消息队列的后续处理。 -### 4.2. 日志收集系统 +### 日志收集系统
    @@ -387,7 +387,7 @@ Parition 是物理上的概念,每个 Topic 包含一个或多个 Partition. Storm 集群:与 OtherApp 处于同一级别,采用拉的方式消费队列中的数据; -## 5. 资料 +## 资料 - [大型网站架构系列:分布式消息队列(一)](https://www.cnblogs.com/itfly8/p/5155983.html) - [大型网站架构系列:消息队列(二)](https://www.cnblogs.com/itfly8/p/5156155.html) diff --git a/docs/technology/sharding/sharding-theory.md b/docs/theory/sharding-theory.md similarity index 99% rename from docs/technology/sharding/sharding-theory.md rename to docs/theory/sharding-theory.md index cfc673b6..2fd85378 100644 --- a/docs/technology/sharding/sharding-theory.md +++ b/docs/theory/sharding-theory.md @@ -1,4 +1,4 @@ -# 分库分表原理 +# 分库分表基本原理 分片(Sharding)的基本思想就要把一个数据库切分成多个部分,存储在不同的数据库(server)上,从而缓解单一数据库的性能问题。 @@ -139,7 +139,7 @@ CREATE TABLE `SEQUENCE` ( ## 参考资料 -- https://github.com/apache/incubator-shardingsphere +- [ShardingSphere 官方文档](https://shardingsphere.apache.org/document/current/cn/overview/) - [“分库分表" ?选型和流程要慎重,否则会失控](https://juejin.im/post/5bf778ef5188251b8a26ed8b) - [分库分表需要考虑的问题及方案](https://www.jianshu.com/p/32b3e91aa22c) From 2eafa2207f83df5d747b3b47689caf8e6416dd9c Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Thu, 24 Oct 2019 18:11:41 +0800 Subject: [PATCH 42/75] update docs --- .gitattributes | 2 + README.md | 42 +- assets/cache.eddx | Bin 40288 -> 58252 bytes {docs/assets => assets}/cache.xmind | Bin {docs/assets => assets}/kafka.pptx | Bin assets/test.eddx | Bin 0 -> 16894 bytes docs/README.md | 42 +- docs/assets/cache.eddx | Bin 58252 -> 0 bytes docs/assets/javaweb.eddx | Bin 21817 -> 0 bytes docs/package.json | 6 +- .../{cache-interview.md => CacheInterview.md} | 0 .../cache/{caffeine.md => Caffeine.md} | 0 .../cache/{ehcache.md => Ehcache.md} | 0 docs/technology/cache/{redis.md => Redis.md} | 0 docs/technology/monitor/Arthas.md | 411 ++++++ docs/technology/monitor/Jmeter.md | 175 +++ docs/technology/monitor/README.md | 5 - docs/technology/monitor/SkyWalking.md | 27 + .../{kafka-advanced.md => KafkaAdvanced.md} | 0 .../mq/{kafka-basics.md => KafkaBasics.md} | 2 +- ...afka-cheat-sheet.md => KafkaCheatSheet.md} | 0 .../mq/{mq-interview.md => MqInterview.md} | 0 ...cketmq-advanced.md => RocketmqAdvanced.md} | 17 +- .../{rocketmq-basics.md => RocketmqBasics.md} | 22 +- docs/technology/mq/kafka/kafka-advanced.md | 1144 ----------------- docs/technology/mq/kafka/kafka-basics.md | 1106 ---------------- docs/technology/rpc/{dubbo.md => Dubbo.md} | 0 .../rpc/{zookeeper.md => ZooKeeper.md} | 0 .../security/{shiro.md => Shiro.md} | 0 .../{spring-security.md => SpringSecurity.md} | 0 docs/technology/server/{jetty.md => Jetty.md} | 15 +- docs/technology/server/README.md | 5 - .../server/{tomcat.md => Tomcat.md} | 28 +- docs/technology/test/jmeter.md | 49 - .../security => theory}/security-theory.md | 0 35 files changed, 674 insertions(+), 2424 deletions(-) rename {docs/assets => assets}/cache.xmind (100%) rename {docs/assets => assets}/kafka.pptx (100%) create mode 100644 assets/test.eddx delete mode 100644 docs/assets/cache.eddx delete mode 100644 docs/assets/javaweb.eddx rename docs/technology/cache/{cache-interview.md => CacheInterview.md} (100%) rename docs/technology/cache/{caffeine.md => Caffeine.md} (100%) rename docs/technology/cache/{ehcache.md => Ehcache.md} (100%) rename docs/technology/cache/{redis.md => Redis.md} (100%) create mode 100644 docs/technology/monitor/Arthas.md create mode 100644 docs/technology/monitor/Jmeter.md delete mode 100644 docs/technology/monitor/README.md create mode 100644 docs/technology/monitor/SkyWalking.md rename docs/technology/mq/{kafka-advanced.md => KafkaAdvanced.md} (100%) rename docs/technology/mq/{kafka-basics.md => KafkaBasics.md} (99%) rename docs/technology/mq/{kafka/kafka-cheat-sheet.md => KafkaCheatSheet.md} (100%) rename docs/technology/mq/{mq-interview.md => MqInterview.md} (100%) rename docs/technology/mq/{rocketmq-advanced.md => RocketmqAdvanced.md} (96%) rename docs/technology/mq/{rocketmq-basics.md => RocketmqBasics.md} (95%) delete mode 100644 docs/technology/mq/kafka/kafka-advanced.md delete mode 100644 docs/technology/mq/kafka/kafka-basics.md rename docs/technology/rpc/{dubbo.md => Dubbo.md} (100%) rename docs/technology/rpc/{zookeeper.md => ZooKeeper.md} (100%) rename docs/technology/security/{shiro.md => Shiro.md} (100%) rename docs/technology/security/{spring-security.md => SpringSecurity.md} (100%) rename docs/technology/server/{jetty.md => Jetty.md} (95%) delete mode 100644 docs/technology/server/README.md rename docs/technology/server/{tomcat.md => Tomcat.md} (98%) delete mode 100644 docs/technology/test/jmeter.md rename docs/{technology/security => theory}/security-theory.md (100%) diff --git a/.gitattributes b/.gitattributes index 1e3aabc2..07962a1f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -70,6 +70,8 @@ *.pdf binary *.doc binary *.docx binary +*.ppt binary +*.pptx binary *.xls binary *.xlsx binary *.xmind binary diff --git a/README.md b/README.md index 4537a250..c9562d0e 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ - [分布式 ID 基本原理](docs/theory/distributed-id-theory.md) - [分布式事务基本原理](docs/theory/distributed-transaction-theory.md) - [分库分表基本原理](docs/theory/sharding-theory.md) +- [系统安全原理](docs/theory/security-theory.md) ## ⭐ 主流技术 @@ -52,44 +53,49 @@ ### 服务器 -- [Tomcat 应用指南](docs/technology/server/tomcat.md) -- [Jetty 应用指南](docs/technology/server/jetty.md) +- [Tomcat 应用指南](docs/technology/server/Tomcat.md) +- [Jetty 应用指南](docs/technology/server/Jetty.md) - [Nginx 简易教程](https://github.com/dunwu/nginx-tutorial) 📚 ### 消息队列 -- Kafka - - [Kafka 实战篇](docs/technology/mq/kafka/kafka-basics.md) - - [Kafka 原理篇](docs/technology/mq/kafka/kafka-advanced.md) - - [Kafka Cheat Sheet](docs/technology/mq/kafka/kafka-cheat-sheet.md) -- [RocketMQ 实战篇](docs/technology/mq/rocketmq-basics.md) -- [RocketMQ 原理篇](docs/technology/mq/rocketmq-basics.md) +- [消息队列面经](docs/technology/mq/MqInterview.md) +- [Kafka 基础篇](docs/technology/mq/KafkaBasics.md) +- [Kafka 原理篇](docs/technology/mq/KafkaAdvanced.md) +- [Kafka Cheat Sheet](docs/technology/mq/KafkaCheatSheet.md) +- [RocketMQ 基础篇](docs/technology/mq/RocketmqBasics.md) +- [RocketMQ 进阶篇](docs/technology/mq/RocketmqAdvanced.md) - [ActiveMQ 实战篇](docs/technology/mq/ActiveMQ.md) ### 缓存 -- [缓存面经](docs/technology/cache/cache-interview.md) -- [Redis](docs/technology/cache/redis.md) -- [Ehcache](docs/technology/cache/ehcache.md) -- [Caffeine](docs/technology/cache/caffeine.md) +- [缓存面经](docs/technology/cache/CacheInterview.md) +- [Redis](docs/technology/cache/Redis.md) +- [Ehcache](docs/technology/cache/Ehcache.md) +- [Caffeine](docs/technology/cache/Caffeine.md) ### RPC -- [ZooKeeper 应用指南](docs/technology/rpc/zookeeper.md) -- [Dubbo 应用指南](docs/technology/rpc/dubbo.md) +- [ZooKeeper 应用指南](docs/technology/rpc/ZooKeeper.md) +- [Dubbo 应用指南](docs/technology/rpc/Dubbo.md) - Spring Cloud ### 安全 -- [系统安全原理](docs/technology/security/security-theory.md) -- [Shiro 应用指南](docs/technology/security/shiro.md) -- [Spring Security 应用指南](docs/technology/security/spring-security.md) +- [Shiro 应用指南](docs/technology/security/Shiro.md) +- [Spring Security 应用指南](docs/technology/security/SpringSecurity.md) -### ORM +### 数据 - [Mybatis 应用指南](docs/technology/data/Mybatis.md) - [ShardingSphere 应用指南](docs/technology/data/ShardingSphere.md) +### 监控/诊断/测试 + +- [Arthas 应用指南](docs/technology/monitor/Arthas.md) +- [SkyWalking 应用指南](docs/technology/monitor/SkyWalking.md) +- [Jmeter 应用指南](docs/technology/monitor/Jmeter.md) + ## ☕ JavaEE > [JavaEE](docs/javaee) 技术——Java Web 的基石 diff --git a/assets/cache.eddx b/assets/cache.eddx index b1ee7cc30ba2ae2ba6196460ebef83aa87fd3f5d..7d5bc6b753fb215bb61818d9fd9b725d818de62e 100644 GIT binary patch delta 51455 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 [JavaEE](javaee) 技术——Java Web 的基石 diff --git a/docs/assets/cache.eddx b/docs/assets/cache.eddx deleted file mode 100644 index 7d5bc6b753fb215bb61818d9fd9b725d818de62e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 58252 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%0AVsG|sYU6;%Xzx22*1;t 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 diff --git a/docs/package.json b/docs/package.json index 1c20bfc9..aedcd063 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,9 +1,7 @@ { - "name": "JavaWeb", + "name": "javaweb", "version": "1.0.0", "scripts": { "start": "docsify serve ./ --port 4000" - }, - "dependencies": {}, - "devDependencies": {} + } } diff --git a/docs/technology/cache/cache-interview.md b/docs/technology/cache/CacheInterview.md similarity index 100% rename from docs/technology/cache/cache-interview.md rename to docs/technology/cache/CacheInterview.md diff --git a/docs/technology/cache/caffeine.md b/docs/technology/cache/Caffeine.md similarity index 100% rename from docs/technology/cache/caffeine.md rename to docs/technology/cache/Caffeine.md diff --git a/docs/technology/cache/ehcache.md b/docs/technology/cache/Ehcache.md similarity index 100% rename from docs/technology/cache/ehcache.md rename to docs/technology/cache/Ehcache.md diff --git a/docs/technology/cache/redis.md b/docs/technology/cache/Redis.md similarity index 100% rename from docs/technology/cache/redis.md rename to docs/technology/cache/Redis.md diff --git a/docs/technology/monitor/Arthas.md b/docs/technology/monitor/Arthas.md new file mode 100644 index 00000000..dd5b6450 --- /dev/null +++ b/docs/technology/monitor/Arthas.md @@ -0,0 +1,411 @@ +# Arthas 应用指南 + +> `Arthas` 是 Alibaba 开源的 Java 诊断工具 。 + +## 简介 + +`Arthas`可以解决的问题: + +1. 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception? +2. 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了? +3. 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗? +4. 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现! +5. 是否有一个全局视角来查看系统的运行状况? +6. 有什么办法可以监控到 JVM 的实时运行状态? + +`Arthas`支持 JDK 6+,支持 Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 `Tab` 自动补全功能,进一步方便进行问题的定位和诊断。 + +## 安装 + +### 使用`arthas-boot`(推荐) + +下载`arthas-boot.jar`,然后用`java -jar`的方式启动: + +``` +wget https://alibaba.github.io/arthas/arthas-boot.jar +java -jar arthas-boot.jar +``` + +打印帮助信息: + +``` +java -jar arthas-boot.jar -h +``` + +- 如果下载速度比较慢,可以使用 aliyun 的镜像: + + ``` + java -jar arthas-boot.jar --repo-mirror aliyun --use-http + ``` + +- 如果从 github 下载有问题,可以使用 gitee 镜像 + + ``` + wget https://arthas.gitee.io/arthas-boot.jar + ``` + +### 使用`as.sh` + +Arthas 支持在 Linux/Unix/Mac 等平台上一键安装,请复制以下内容,并粘贴到命令行中,敲 `回车` 执行即可: + +``` +curl -L https://alibaba.github.io/arthas/install.sh | sh +``` + +上述命令会下载启动脚本文件 `as.sh` 到当前目录,你可以放在任何地方或将其加入到 `$PATH` 中。 + +直接在 shell 下面执行`./as.sh`,就会进入交互界面。 + +也可以执行`./as.sh -h`来获取更多参数信息。 + +- 如果从 github 下载有问题,可以使用 gitee 镜像 + + ``` + curl -L https://arthas.gitee.io/install.sh | sh + ``` + +### 全量安装 + +最新版本,点击下载:[下载地址](http://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=com.taobao.arthas&a=arthas-packaging&e=zip&c=bin&v=LATEST) + +解压后,在文件夹里有`arthas-boot.jar`,直接用`java -jar`的方式启动: + +``` +java -jar arthas-boot.jar +``` + +打印帮助信息: + +``` +java -jar arthas-boot.jar -h +``` + +## 基础使用 + +### 启动 Demo + +```bash +wget https://alibaba.github.io/arthas/arthas-demo.jar +java -jar arthas-demo.jar +``` + +`arthas-demo`是一个简单的程序,每隔一秒生成一个随机数,再执行质因式分解,并打印出分解结果。 + +`arthas-demo`源代码:[查看](https://github.com/alibaba/arthas/blob/master/demo/src/main/java/demo/MathGame.java) + +### 启动 arthas + +在命令行下面执行(使用和目标进程一致的用户启动,否则可能 attach 失败): + +``` +wget https://alibaba.github.io/arthas/arthas-boot.jar +java -jar arthas-boot.jar +``` + +- 执行该程序的用户需要和目标进程具有相同的权限。比如以`admin`用户来执行:`sudo su admin && java -jar arthas-boot.jar` 或 `sudo -u admin -EH java -jar arthas-boot.jar`。 +- 如果 attach 不上目标进程,可以查看`~/logs/arthas/` 目录下的日志。 +- 如果下载速度比较慢,可以使用 aliyun 的镜像:`java -jar arthas-boot.jar --repo-mirror aliyun --use-http` +- `java -jar arthas-boot.jar -h` 打印更多参数信息。 + +选择应用 java 进程: + +```bash +$ $ java -jar arthas-boot.jar +* [1]: 35542 + [2]: 71560 arthas-demo.jar +``` + +Demo 进程是第 2 个,则输入 2,再输入`回车/enter`。Arthas 会 attach 到目标进程上,并输出日志: + +```bash +[INFO] Try to attach process 71560 +[INFO] Attach process 71560 success. +[INFO] arthas-client connect 127.0.0.1 3658 + ,---. ,------. ,--------.,--. ,--. ,---. ,---. + / O \ | .--. ''--. .--'| '--' | / O \ ' .-' +| .-. || '--'.' | | | .--. || .-. |`. `-. +| | | || |\ \ | | | | | || | | |.-' | +`--' `--'`--' '--' `--' `--' `--'`--' `--'`-----' + +wiki: https://alibaba.github.io/arthas +version: 3.0.5.20181127201536 +pid: 71560 +time: 2018-11-28 19:16:24 + +$ +``` + +### 查看 dashboard + +输入[dashboard](https://alibaba.github.io/arthas/dashboard.html),按`回车/enter`,会展示当前进程的信息,按`ctrl+c`可以中断执行。 + +```bash +$ dashboard +ID NAME GROUP PRIORI STATE %CPU TIME INTERRU DAEMON +17 pool-2-thread-1 system 5 WAITIN 67 0:0 false false +27 Timer-for-arthas-dashb system 10 RUNNAB 32 0:0 false true +11 AsyncAppender-Worker-a system 9 WAITIN 0 0:0 false true +9 Attach Listener system 9 RUNNAB 0 0:0 false true +3 Finalizer system 8 WAITIN 0 0:0 false true +2 Reference Handler system 10 WAITIN 0 0:0 false true +4 Signal Dispatcher system 9 RUNNAB 0 0:0 false true +26 as-command-execute-dae system 10 TIMED_ 0 0:0 false true +13 job-timeout system 9 TIMED_ 0 0:0 false true +1 main main 5 TIMED_ 0 0:0 false false +14 nioEventLoopGroup-2-1 system 10 RUNNAB 0 0:0 false false +18 nioEventLoopGroup-2-2 system 10 RUNNAB 0 0:0 false false +23 nioEventLoopGroup-2-3 system 10 RUNNAB 0 0:0 false false +15 nioEventLoopGroup-3-1 system 10 RUNNAB 0 0:0 false false +Memory used total max usage GC +heap 32M 155M 1820M 1.77% gc.ps_scavenge.count 4 +ps_eden_space 14M 65M 672M 2.21% gc.ps_scavenge.time(m 166 +ps_survivor_space 4M 5M 5M s) +ps_old_gen 12M 85M 1365M 0.91% gc.ps_marksweep.count 0 +nonheap 20M 23M -1 gc.ps_marksweep.time( 0 +code_cache 3M 5M 240M 1.32% ms) +Runtime +os.name Mac OS X +os.version 10.13.4 +java.version 1.8.0_162 +java.home /Library/Java/JavaVir + tualMachines/jdk1.8.0 + _162.jdk/Contents/Hom + e/jre +``` + +### 通过 thread 命令来获取到`arthas-demo`进程的 Main Class + +`thread 1`会打印线程 ID 1 的栈,通常是 main 函数的线程。 + +```bash +$ thread 1 | grep 'main(' + at demo.MathGame.main(MathGame.java:17) +``` + +### 通过 jad 来反编译 Main Class + +```java +$ jad demo.MathGame + +ClassLoader: ++-sun.misc.Launcher$AppClassLoader@3d4eac69 + +-sun.misc.Launcher$ExtClassLoader@66350f69 + +Location: +/tmp/arthas-demo.jar + +/* + * Decompiled with CFR 0_132. + */ +package demo; + +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +public class MathGame { + private static Random random = new Random(); + private int illegalArgumentCount = 0; + + public static void main(String[] args) throws InterruptedException { + MathGame game = new MathGame(); + do { + game.run(); + TimeUnit.SECONDS.sleep(1L); + } while (true); + } + + public void run() throws InterruptedException { + try { + int number = random.nextInt(); + List primeFactors = this.primeFactors(number); + MathGame.print(number, primeFactors); + } + catch (Exception e) { + System.out.println(String.format("illegalArgumentCount:%3d, ", this.illegalArgumentCount) + e.getMessage()); + } + } + + public static void print(int number, List primeFactors) { + StringBuffer sb = new StringBuffer("" + number + "="); + Iterator iterator = primeFactors.iterator(); + while (iterator.hasNext()) { + int factor = iterator.next(); + sb.append(factor).append('*'); + } + if (sb.charAt(sb.length() - 1) == '*') { + sb.deleteCharAt(sb.length() - 1); + } + System.out.println(sb); + } + + public List primeFactors(int number) { + if (number < 2) { + ++this.illegalArgumentCount; + throw new IllegalArgumentException("number is: " + number + ", need >= 2"); + } + ArrayList result = new ArrayList(); + int i = 2; + while (i <= number) { + if (number % i == 0) { + result.add(i); + number /= i; + i = 2; + continue; + } + ++i; + } + return result; + } +} + +Affect(row-cnt:1) cost in 970 ms. +``` + +### watch + +通过[watch](https://alibaba.github.io/arthas/watch.html)命令来查看`demo.MathGame#primeFactors`函数的返回值: + +```bash +$ watch demo.MathGame primeFactors returnObj +Press Ctrl+C to abort. +Affect(class-cnt:1 , method-cnt:1) cost in 107 ms. +ts=2018-11-28 19:22:30; [cost=1.715367ms] result=null +ts=2018-11-28 19:22:31; [cost=0.185203ms] result=null +ts=2018-11-28 19:22:32; [cost=19.012416ms] result=@ArrayList[ + @Integer[5], + @Integer[47], + @Integer[2675531], +] +ts=2018-11-28 19:22:33; [cost=0.311395ms] result=@ArrayList[ + @Integer[2], + @Integer[5], + @Integer[317], + @Integer[503], + @Integer[887], +] +ts=2018-11-28 19:22:34; [cost=10.136007ms] result=@ArrayList[ + @Integer[2], + @Integer[2], + @Integer[3], + @Integer[3], + @Integer[31], + @Integer[717593], +] +ts=2018-11-28 19:22:35; [cost=29.969732ms] result=@ArrayList[ + @Integer[5], + @Integer[29], + @Integer[7651739], +] +``` + +更多的功能可以查看[进阶使用](https://alibaba.github.io/arthas/advanced-use.html)。 + +### 退出 arthas + +如果只是退出当前的连接,可以用`quit`或者`exit`命令。Attach 到目标进程上的 arthas 还会继续运行,端口会保持开放,下次连接时可以直接连接上。 + +如果想完全退出 arthas,可以执行`shutdown`命令。 + +## 进阶使用 + +### 基础命令 + +- help——查看命令帮助信息 +- [cat](https://alibaba.github.io/arthas/cat.html)——打印文件内容,和linux里的cat命令类似 +- [pwd](https://alibaba.github.io/arthas/pwd.html)——返回当前的工作目录,和linux命令类似 +- cls——清空当前屏幕区域 +- session——查看当前会话的信息 +- [reset](https://alibaba.github.io/arthas/reset.html)——重置增强类,将被 Arthas 增强过的类全部还原,Arthas 服务端关闭时会重置所有增强过的类 +- version——输出当前目标 Java 进程所加载的 Arthas 版本号 +- history——打印命令历史 +- quit——退出当前 Arthas 客户端,其他 Arthas 客户端不受影响 +- shutdown——关闭 Arthas 服务端,所有 Arthas 客户端全部退出 +- [keymap](https://alibaba.github.io/arthas/keymap.html)——Arthas快捷键列表及自定义快捷键 + +### jvm相关 + +- [dashboard](https://alibaba.github.io/arthas/dashboard.html)——当前系统的实时数据面板 +- [thread](https://alibaba.github.io/arthas/thread.html)——查看当前 JVM 的线程堆栈信息 +- [jvm](https://alibaba.github.io/arthas/jvm.html)——查看当前 JVM 的信息 +- [sysprop](https://alibaba.github.io/arthas/sysprop.html)——查看和修改JVM的系统属性 +- [sysenv](https://alibaba.github.io/arthas/sysenv.html)——查看JVM的环境变量 +- [vmoption](https://alibaba.github.io/arthas/vmoption.html)——查看和修改JVM里诊断相关的option +- [logger](https://alibaba.github.io/arthas/logger.html)——查看和修改logger +- [getstatic](https://alibaba.github.io/arthas/getstatic.html)——查看类的静态属性 +- [ognl](https://alibaba.github.io/arthas/ognl.html)——执行ognl表达式 +- [mbean](https://alibaba.github.io/arthas/mbean.html)——查看 Mbean 的信息 +- [heapdump](https://alibaba.github.io/arthas/heapdump.html)——dump java heap, 类似jmap命令的heap dump功能 + +### class/classloader相关 + +- [sc](https://alibaba.github.io/arthas/sc.html)——查看JVM已加载的类信息 +- [sm](https://alibaba.github.io/arthas/sm.html)——查看已加载类的方法信息 +- [jad](https://alibaba.github.io/arthas/jad.html)——反编译指定已加载类的源码 +- [mc](https://alibaba.github.io/arthas/mc.html)——内存编绎器,内存编绎`.java`文件为`.class`文件 +- [redefine](https://alibaba.github.io/arthas/redefine.html)——加载外部的`.class`文件,redefine到JVM里 +- [dump](https://alibaba.github.io/arthas/dump.html)——dump 已加载类的 byte code 到特定目录 +- [classloader](https://alibaba.github.io/arthas/classloader.html)——查看classloader的继承树,urls,类加载信息,使用classloader去getResource + +### monitor/watch/trace相关 + +> 请注意,这些命令,都通过字节码增强技术来实现的,会在指定类的方法中插入一些切面来实现数据统计和观测,因此在线上、预发使用时,请尽量明确需要观测的类、方法以及条件,诊断结束要执行 `shutdown` 或将增强过的类执行 `reset` 命令。 + +- [monitor](https://alibaba.github.io/arthas/monitor.html)——方法执行监控 +- [watch](https://alibaba.github.io/arthas/watch.html)——方法执行数据观测 +- [trace](https://alibaba.github.io/arthas/trace.html)——方法内部调用路径,并输出方法路径上的每个节点上耗时 +- [stack](https://alibaba.github.io/arthas/stack.html)——输出当前方法被调用的调用路径 +- [tt](https://alibaba.github.io/arthas/tt.html)——方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测 + +### options + +- [options](https://alibaba.github.io/arthas/options.html)——查看或设置Arthas全局开关 + +### 管道 + +Arthas支持使用管道对上述命令的结果进行进一步的处理,如`sm java.lang.String * | grep 'index'` + +- grep——搜索满足条件的结果 +- plaintext——将命令的结果去除ANSI颜色 +- wc——按行统计输出结果 + +### 后台异步任务 + +当线上出现偶发的问题,比如需要watch某个条件,而这个条件一天可能才会出现一次时,异步后台任务就派上用场了,详情请参考[这里](https://alibaba.github.io/arthas/async.html) + +- 使用 > 将结果重写向到日志文件,使用 & 指定命令是后台运行,session断开不影响任务执行(生命周期默认为1天) +- jobs——列出所有job +- kill——强制终止任务 +- fg——将暂停的任务拉到前台执行 +- bg——将暂停的任务放到后台执行 + +### Web Console + +通过websocket连接Arthas。 + +- [Web Console](https://alibaba.github.io/arthas/web-console.html) + +### 用户数据回报 + +在`3.1.4`版本后,增加了用户数据回报功能,方便统一做安全或者历史数据统计。 + +在启动时,指定`stat-url`,就会回报执行的每一行命令,比如: `./as.sh --stat-url 'http://192.168.10.11:8080/api/stat'` + +在tunnel server里有一个示例的回报代码,用户可以自己在服务器上实现。 + +[StatController.java](https://github.com/alibaba/arthas/blob/master/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/app/web/StatController.java) + +### 其他特性 + +- [异步命令支持](https://alibaba.github.io/arthas/async.html) +- [执行结果存日志](https://alibaba.github.io/arthas/save-log.html) +- [批处理的支持](https://alibaba.github.io/arthas/batch-support.html) +- [ognl表达式的用法说明](https://github.com/alibaba/arthas/issues/11) + +## 参考资料 + +- [Arthas Github](https://github.com/alibaba/arthas) +- [Arthas 用户文档](https://alibaba.github.io/arthas/index.html) diff --git a/docs/technology/monitor/Jmeter.md b/docs/technology/monitor/Jmeter.md new file mode 100644 index 00000000..75752b65 --- /dev/null +++ b/docs/technology/monitor/Jmeter.md @@ -0,0 +1,175 @@ +# JMeter 应用指南 + +> [Jmeter](https://github.com/apache/jmeter) 是一款使用 Java 开发的功能和性能测试软件。 +> +> 🎁 本文编辑时的最新版本为:5.1.1 + +## 简介 + +[Jmeter](https://github.com/apache/jmeter) 是一款使用 Java 开发的功能和性能测试软件。 + +### 特性 + +Jmeter 能够加载和性能测试许多不同的应用程序/服务器/协议类型: + +- 网络 - HTTP,HTTPS(Java,NodeJS,PHP,ASP.NET 等) +- SOAP / REST Web 服务 +- FTP 文件 +- 通过 JDBC 的数据库 +- LDAP +- 通过 JMS 的面向消息的中间件(MOM) +- 邮件-SMTP(S),POP3(S)和 IMAP(S) +- 本机命令或 Shell 脚本 +- TCP 协议 +- Java 对象 + +### 工作流 + +Jmeter 的工作原理是仿真用户向服务器发送请求,并收集服务器应答信息并计算统计信息。 + +Jmeter 的工作流如下图所示: + +![](http://dunwu.test.upcdn.net/cs/java/javaweb/technology/test/jmeter-workflow.png!zp) + +### 主要元素 + +Jmeter 的主要元素如下: + +- **`测试计划(Test Plan)`** - 可以将测试计划视为 JMeter 的测试脚本 。测试计划由测试元素组成,例如线程组,逻辑控制器,样本生成控制器,监听器,定时器,断言和配置元素。 +- **`线程组(Thread Group)`** - 线程组的作用是:模拟大量用户负载的运行场景。 + - 设置线程数 + - 设置加速期 + - 设置执行测试的次数 +- **`控制器(Controllers)`** - 可以分为两大类: + - **`采样器(Sampler)`** - 采样器的作用是模拟用户对目标服务器发送请求。 采样器是必须将组件添加到测试计划中的,因为它只能让 JMeter 知道需要将哪种类型的请求发送到服务器。 请求可以是 HTTP,HTTP(s),FTP,TCP,SMTP,SOAP 等。 + - **`逻辑控制器`** - 逻辑控制器的作用是:控制多个请求发送的循环次数及顺序等。 +- **`监听器(Listeners)`** - 监听器的作用是:收集测试结果信息。如查看结果树、汇总报告等。 +- **`计时器(Timers)`** - 计时器的作用是:控制多个请求发送的时间频次。 +- **`配置元素(Configuration Elements)`** - 配置元素的工作与采样器的工作类似。但是,它不发送请求,而是提供预备的数据等,如 CSV、函数助手。 +- **`预处理器元素(Pre-Processor Elements)`** - 预处理器元素在采样器发出请求之前执行,如果预处理器附加到采样器元素,那么它将在该采样器元素运行之前执行。预处理器元素用于在运行之前准备环境及参数。 +- **`后处理器元素(Post-Processor Elements)`** - 后处理器元素是在发送采样器请求之后执行的元素,常用于处理响应数据。 + +![](http://dunwu.test.upcdn.net/cs/java/javaweb/technology/test/jmeter-elements.png!zp) + +> 📌 提示: +> +> Jmeter 元素的数量关系大致如下: +> +> 1. 脚本中最多只能有一个测试计划。 +> 2. 测试计划中至少要有一个线程组。 +> 3. 线程组中至少要有一个取样器。 +> 4. 线程组中至少要有一个监听器。 + +## 安装 + +### 环境要求 + +- 必要的。Jmeter 基于 JDK8 开发,所以必须运行在 JDK8 环境。 + + - JDK8 + +- 可选的。有些 jar 包不是 Jmeter 提供的,如果需要相应的功能,需要自行下载并置于 `lib` 目录。 + - JDBC + - JMS + - [Bouncy Castle](http://www.bouncycastle.org/test_releases.html) + +### 下载 + +进入 [**Jmeter 官网下载地址**](https://jmeter.apache.org/download_jmeter.cgi) 选择需要版本进行下载。 + +### 启动 + +解压 Jmeter 压缩包,进入 bin 目录 + +Unix 类系统运行 `jmeter` ;Windows 系统运行 `jmeter.bat` + +![image-20191024104517721](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024104517721.png!zp) + +## 使用 Jmeter + +### 创建测试计划 + +> 🔔 注意: +> +> - 在运行整个测试计划之前,应保存测试计划。 +> +> - JMeter 的测试计划以 `.jmx` 扩展文件的形式保存。 + +#### 创建线程组 + +- 在“测试计划”上右键 【添加】=>【线程(用户)】=>【线程组】。 + +- 设置线程数和循环次数 + +![image-20191024105545736](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024105545736.png!zp) + +#### 配置原件 + +- 在新建的线程组上右键 【添加】=>【配置元件】=>【HTTP 请求默认值】。 + +- 填写协议、服务器名称或 IP、端口号 + +![image-20191024110016264](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024110016264.png!zp) + +#### 构造 HTTP 请求 + +- 在“线程组”上右键 【添加-】=>【取样器】=>【HTTP 请求】。 + +- 填写协议、服务器名称或 IP、端口号(如果配置了 HTTP 请求默认值可以忽略) +- 填写方法、路径 +- 填写参数、消息体数据、文件上传 + +![image-20191024110953063](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024110953063.png!zp) + +#### 添加 HTTP 请求头 + +- 在“线程组”上右键 【添加】=>【配置元件】=>【HTTP 信息头管理器】 +- 由于我的测试例中传输的数据为 json 形式,所以设置键值对 `Content-Type`:`application/json` + +![image-20191024111825226](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024111825226.png!zp) + +#### 添加断言 + +- 在“线程组”上右键 【添加】=>【断言】=>【 响应断言 】 +- 在我的案例中,以 HTTP 应答状态码为 200 来判断请求是否成功 + +![image-20191024112335130](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024112335130.png!zp) + +#### 添加察看结果树 + +- 在“线程组”上右键 【添加】=>【监听器】=>【察看结果树】 +- 直接点击运行,就可以查看测试结果 + +![image-20191024113849270](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024113849270.png!zp) + +#### 添加汇总报告 + +- 在“线程组”上右键 【添加】=>【监听器】=>【汇总报告】 +- 直接点击运行,就可以查看测试结果 + +![image-20191024114016424](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024114016424.png!zp) + +#### 保存测试计划 + +执行测试计划前,GUI 会提示先保存配置为 `jmx` 文件。 + +### 执行测试计划 + +官方建议不要直接使用 GUI 来执行测试计划,这种模式指适用于创建测试计划和 debug。 + +执行测试计划应该使用命令行模式,语法形式如下: + +```bash +jmeter -n -t [jmx file] -l [results file] -e -o [Path to web report folder] +``` + +执行测试计划后,在 `-e -o` 参数后指定的 web 报告目录下,可以找到测试报告内容。在浏览器中打开 `index.html` 文件,可以看到如下报告: + +![image-20191024120233058](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024120233058.png!zp) + +## 参考资料 + +- [Jmeter 官网](https://jmeter.apache.org/) +- [Jmeter Github](https://github.com/apache/jmeter) +- [Jmeter 性能测试入门](https://www.cnblogs.com/TankXiao/p/4045439.html) +- [易百教程 - Jmeter 教程](https://www.yiibai.com/jmeter) diff --git a/docs/technology/monitor/README.md b/docs/technology/monitor/README.md deleted file mode 100644 index 90c13931..00000000 --- a/docs/technology/monitor/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# 监控 - -- [ZipKin](https://zipkin.io/) - 监控、分析应用数据,例如用户每次请求服务的处理时间等,可方便的监测系统中存在的瓶颈。 -- [PinPoint](https://naver.github.io/pinpoint/) - 大规模分布式系统性能管理平台 -- [CAT](https://github.com/dianping/cat) - CAT 是实时应用监控平台,提供系统丰富的性能指标、健康状况、实时告警等。 diff --git a/docs/technology/monitor/SkyWalking.md b/docs/technology/monitor/SkyWalking.md new file mode 100644 index 00000000..7d21f45f --- /dev/null +++ b/docs/technology/monitor/SkyWalking.md @@ -0,0 +1,27 @@ +# SkyWalking 应用指南 + +> SkyWalking 是分布式系统的应用程序性能监视工具,专为微服务、云原生架构和基于容器(Docker、K8s、Mesos)架构而设计。 +> +> SkyWalking 是观察性分析平台和应用性能管理系统。它提供分布式追踪、服务网格遥测分析、度量聚合和可视化一体化解决方案。 + +![img](https://skywalking.apache.org/assets/frame.jpeg) + +## 安装 + +### 环境要求 + +- JDK8 +- Elasticsearch 6.x +- 8080,10800,11800,12800 端口不被占用 + +### 下载 + +[官方下载地址](http://skywalking.apache.org/downloads/) + +### 启动 + +## 参考资料 + +- [SkyWalking 官网](https://skywalking.apache.org/) +- [SkyWalking Github](https://github.com/apache/skywalking) +- [SkyWalking 分布式追踪系统](https://www.jianshu.com/p/2fd56627a3cf) diff --git a/docs/technology/mq/kafka-advanced.md b/docs/technology/mq/KafkaAdvanced.md similarity index 100% rename from docs/technology/mq/kafka-advanced.md rename to docs/technology/mq/KafkaAdvanced.md diff --git a/docs/technology/mq/kafka-basics.md b/docs/technology/mq/KafkaBasics.md similarity index 99% rename from docs/technology/mq/kafka-basics.md rename to docs/technology/mq/KafkaBasics.md index 9450f6c5..21de2022 100644 --- a/docs/technology/mq/kafka-basics.md +++ b/docs/technology/mq/KafkaBasics.md @@ -1,4 +1,4 @@ -# Kafka 实战篇 +# Kafka 基础篇 > Kafka 是一个分布式的、可水平扩展的、基于发布/订阅模式的、支持容错的消息系统。 > diff --git a/docs/technology/mq/kafka/kafka-cheat-sheet.md b/docs/technology/mq/KafkaCheatSheet.md similarity index 100% rename from docs/technology/mq/kafka/kafka-cheat-sheet.md rename to docs/technology/mq/KafkaCheatSheet.md diff --git a/docs/technology/mq/mq-interview.md b/docs/technology/mq/MqInterview.md similarity index 100% rename from docs/technology/mq/mq-interview.md rename to docs/technology/mq/MqInterview.md diff --git a/docs/technology/mq/rocketmq-advanced.md b/docs/technology/mq/RocketmqAdvanced.md similarity index 96% rename from docs/technology/mq/rocketmq-advanced.md rename to docs/technology/mq/RocketmqAdvanced.md index e5ae543c..7c4f73e3 100644 --- a/docs/technology/mq/rocketmq-advanced.md +++ b/docs/technology/mq/RocketmqAdvanced.md @@ -1,19 +1,4 @@ -# RocketMQ 原理篇 - - - -- [架构](#架构) - - [NameServer](#nameserver) - - [Broker](#broker) - - [Producer](#producer) - - [Consumer](#consumer) -- [关键特性以及其实现原理](#关键特性以及其实现原理) - - [顺序消息](#顺序消息) - - [消息重复](#消息重复) - - [事务消息](#事务消息) -- [资料](#资料) - - +# RocketMQ 进阶篇 ## 架构 diff --git a/docs/technology/mq/rocketmq-basics.md b/docs/technology/mq/RocketmqBasics.md similarity index 95% rename from docs/technology/mq/rocketmq-basics.md rename to docs/technology/mq/RocketmqBasics.md index e8b9dc6d..369310ac 100644 --- a/docs/technology/mq/rocketmq-basics.md +++ b/docs/technology/mq/RocketmqBasics.md @@ -1,24 +1,4 @@ -# RocketMQ 实战篇 - - - -- [概述](#概述) - - [简介](#简介) - - [核心概念](#核心概念) -- [安装](#安装) - - [环境要求](#环境要求) - - [下载解压](#下载解压) - - [启动 Name Server](#启动-name-server) - - [启动 Broker](#启动-broker) - - [收发消息](#收发消息) - - [关闭服务器](#关闭服务器) -- [API](#api) - - [Producer](#producer) - - [Consumer](#consumer) - - [FAQ](#faq) -- [资料](#资料) - - +# RocketMQ 基础篇 ## 概述 diff --git a/docs/technology/mq/kafka/kafka-advanced.md b/docs/technology/mq/kafka/kafka-advanced.md deleted file mode 100644 index f51cbd77..00000000 --- a/docs/technology/mq/kafka/kafka-advanced.md +++ /dev/null @@ -1,1144 +0,0 @@ ---- -title: Kafka -date: 2018-06-13 -categories: - - 分布式 -tags: - - java - - javaweb - - 分布式 - - mq ---- - -# Kafka - -> Kafka 是一个分布式的、可水平扩展的、基于发布/订阅模式的、支持容错的消息系统。 - - - -- [1. 概述](#1-概述) - - [1.1. 分布式](#11-分布式) - - [1.2. 容错](#12-容错) - - [1.3. 提交日志](#13-提交日志) - - [1.4. 消息队列](#14-消息队列) - - [1.5. 为什么要使用消息系统](#15-为什么要使用消息系统) - - [1.6. Kafka 的关键功能](#16-kafka-的关键功能) - - [1.7. Kafka 基本概念](#17-kafka-基本概念) - - [1.8. Kafka 核心 API](#18-kafka-核心-api) - - [1.9. Topic 和日志](#19-topic-和日志) -- [2. Kafka 工作原理](#2-kafka-工作原理) -- [3. 持久化](#3-持久化) -- [4. 复制](#4-复制) -- [5. 流处理](#5-流处理) - - [5.1. 无状态处理](#51-无状态处理) - - [5.2. 有状态处理](#52-有状态处理) -- [6. Kafka 应用场景](#6-kafka-应用场景) -- [7. 幂等性](#7-幂等性) - - [7.1. 幂等性实现](#71-幂等性实现) - - [7.2. 幂等性的应用实例](#72-幂等性的应用实例) -- [8. 事务](#8-事务) - - [8.1. 事务属性理解](#81-事务属性理解) - - [8.2. 引入事务目的](#82-引入事务目的) - - [8.3. 事务操作的 API](#83-事务操作的-api) - - [8.4. 事务属性的应用实例](#84-事务属性的应用实例) - - [8.5. 生产者事务的实现](#85-生产者事务的实现) - - [8.6. 其他思考](#86-其他思考) -- [9. 资料](#9-资料) - - [9.1. 官方资料](#91-官方资料) - - [9.2. 第三方资料](#92-第三方资料) - - - -## 1. 概述 - -### 1.1. 分布式 - -分布式系统是一个由多个运行机器组成的系统,所有这些机器在一个集群中一起工作,对最终端用户表现为一个节点。 - -Kafka 的分布式意义在于:它在不同的节点上存储、接收和发送消息。 - -### 1.2. 容错 - -分布式系统一般都会设计容错机制,保证集群中几个节点出现故障时,仍能对外提供服务。 - -### 1.3. 提交日志 - -提交日志(也称为预写日志,事务日志)是仅支持附加的持久有序数据结构。您不能修改或删除记录。它从左到右读取并保证项目排序。 - -Kafka 实际上将所有的消息存储到磁盘,并在结构中对它们进行排序,以便利用顺序磁盘读取。 - -### 1.4. 消息队列 - -消息队列技术是分布式应用间交换信息的一种技术。消息队列可驻留在内存或磁盘上, 队列存储消息直到它们被应用程序读走。通过消息队列,应用程序可独立地执行--它们不需要知道彼此的位置、或在继续执行前不需要等待接收程序接收此消息。在分布式计算环境中,为了集成分布式应用,开发者需要对异构网络环境下的分布式应用提供有效的通信手段。为了管理需要共享的信息,对应用提供公共的信息交换机制是重要的。常用的消息队列技术是 Message Queue。 - -Message Queue 的通信模式: - -- **点对点**:点对点方式是最为传统和常见的通讯方式,它支持一对一、一对多、多对多、多对一等多种配置方式,支持树状、网状等多种拓扑结构。 -- **多点广播**:MQ 适用于不同类型的应用。其中重要的,也是正在发展中的是"多点广播"应用,即能够将消息发送到多个目标站点 (Destination List)。可以使用一条 MQ 指令将单一消息发送到多个目标站点,并确保为每一站点可靠地提供信息。MQ 不仅提供了多点广播的功能,而且还拥有智能消息分发功能,在将一条消息发送到同一系统上的多个用户时,MQ 将消息的一个复制版本和该系统上接收者的名单发送到目标 MQ 系统。目标 MQ 系统在本地复制这些消息,并将它们发送到名单上的队列,从而尽可能减少网络的传输量。 -- **发布/订阅 (Publish/Subscribe)**:发布/订阅功能使消息的分发可以突破目的队列地理指向的限制,使消息按照特定的主题甚至内容进行分发,用户或应用程序可以根据主题或内容接收到所需要的消息。发布/订阅功能使得发送者和接收者之间的耦合关系变得更为松散,发送者不必关心接收者的目的地址,而接收者也不必关心消息的发送地址,而只是根据消息的主题进行消息的收发。 -- **集群 (Cluster)**:为了简化点对点通讯模式中的系统配置,MQ 提供 Cluster(集群) 的解决方案。集群类似于一个域 (Domain),集群内部的队列管理器之间通讯时,不需要两两之间建立消息通道,而是采用集群 (Cluster) 通道与其它成员通讯,从而大大简化了系统配置。此外,集群中的队列管理器之间能够自动进行负载均衡,当某一队列管理器出现故障时,其它队列管理器可以接管它的工作,从而大大提高系统的高可靠性。 - -### 1.5. 为什么要使用消息系统 - -- 解耦 - 在项目启动之初来预测将来项目会碰到什么需求,是极其困难的。消息系统在处理过程中间插入了一个隐含的、基于数据的接口层,两边的处理过程都要实现这一接口。这允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。 -- 冗余 - 有些情况下,处理数据的过程会失败。除非数据被持久化,否则将造成丢失。消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险。许多消息队列所采用的"插入-获取-删除"范式中,在把一个消息从队列中删除之前,需要你的处理系统明确的指出该消息已经被处理完毕,从而确保你的数据被安全的保存直到你使用完毕。 -- 扩展性 - 因为消息队列解耦了你的处理过程,所以增大消息入队和处理的频率是很容易的,只要另外增加处理过程即可。不需要改变代码、不需要调节参数。扩展就像调大电力按钮一样简单。 -- 灵活性 & 峰值处理能力 - 在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见;如果为以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。 -- 可恢复性 - 系统的一部分组件失效时,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。 -- 顺序保证 - 在大多使用场景下,数据处理的顺序都很重要。大部分消息队列本来就是排序的,并且能保证数据会按照特定的顺序来处理。Kafka 保证一个 Partition 内的消息的有序性。 -- 缓冲 - 在任何重要的系统中,都会有需要不同的处理时间的元素。例如,加载一张图片比应用过滤器花费更少的时间。消息队列通过一个缓冲层来帮助任务最高效率的执行———写入队列的处理会尽可能的快速。该缓冲有助于控制和优化数据流经过系统的速度。 -- 异步通信 - 很多时候,用户不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。 - -### 1.6. Kafka 的关键功能 - -- 发布和订阅流记录,类似于消息队列或企业级消息系统。 -- 以容错、持久化的方式存储流记录。 -- 处理流记录。 - -### 1.7. Kafka 基本概念 - -- Kafka 作为一个集群运行在一台或多台可以跨越多个数据中心的服务器上。 -- Kafka 集群在称为 Topic 的类别中存储记录流。 -- Kafka 的每个记录由一个键,一个值和一个时间戳组成。 - -### 1.8. Kafka 核心 API - -
    - -
    - -- Producer - 允许应用程序将记录流发布到一个或多个 Kafka Topic。 -- Consumer - 允许应用程序订阅一个或多个 Topic 并处理为他们生成的记录流。 -- Streams - 允许应用程序充当流处理器,从一个或多个 Topic 中消费输入流,并将输出流生成为一个或多个输出 Topic,从而将输入流有效地转换为输出流。 -- Connector - 允许构建和运行可重复使用的生产者或消费者,将 Kafka Topic 连接到现有的应用程序或数据系统。例如,连接到关系数据库的连接器可能会捕获对表的每个更改。 - -在 Kafka 中,客户端和服务器之间的通信是采用 TCP 协议方式。 - -### 1.9. Topic 和日志 - -Topic 是一个目录名,它保存着发布记录。kafka 的 Topic 始终是多订阅者的,也就是说,一个主题可以有零个,一个或多个订阅写入数据的 Consumer。 - -在 Kafka 中,任意一个 Topic 维护一个 Partition 的日志,类似下图: - -
    - -
    - -每个 Partition 都是一个有序的,不可变的记录序列,不断追加到结构化的提交日志中。Partition 中的记录每个分配一个连续的 id 号,称为偏移量,用于唯一标识 Partition 内的每条记录。 - -Kafka 集群持久化保存(使用可配置的保留期限)所有发布记录——无论它们是否被消费。例如,如果保留期限被设置为两天,则在记录发布后的两天之内,它都可以被消费,超过时间后将被丢弃以释放空间。 - -
    - -
    - -实际上,保留在每个 Consumer 基础上的唯一元数据是该 Consumer 在日志中的抵消或位置。这个偏移量是由 Consumer 控制的:Consumer 通常会在读取记录时线性地推进其偏移量,但实际上,由于位置由 Consumer 控制,因此它可以按照喜欢的任何顺序消费记录。 - -这种功能组合意味着 Kafka Consumer 的开销很小——它们的出现对集群和其他 Consumer 没有多少影响。 - -日志中的 Partition 有多种目的。首先,它们允许日志的大小超出服务器限制的大小。每个单独的 Partition 必须适合承载它的服务器,但是一个主题可能有很多 Partition,因此它可以处理任意数量的数据。其次,它们作为并行的单位。 - -## 2. Kafka 工作原理 - -- **Broker** - Kafka 集群包含一个或多个服务器,这种服务器被称为 broker。 -- **Topic** - 每条发布到 Kafka 集群的消息都有一个类别,这个类别被称为 Topic。(物理上不同 Topic 的消息分开存储,逻辑上一个 Topic 的消息虽然保存于一个或多个 broker 上但用户只需指定消息的 Topic 即可生产或消费数据而不必关心数据存于何处)。 -- **Partition** - Parition 是物理上的概念,每个 Topic 包含一个或多个 Partition。 -- **Producer** - 负责发布消息到 Kafka broker。 -- **Consumer** - 消息消费者,向 Kafka broker 读取消息的客户端。 -- **Consumer Group** - 每个 Consumer 属于一个特定的 Consumer Group(可为每个 Consumer 指定 group name,若不指定 group name 则属于默认的 group)。 - -Producer 将消息(记录)发送到 Kafka 节点(Broker),消息由称为 Consumer 的其他应用程序处理。消息被存储在 Topic 中,并且 Consumer 订阅该主题以接收新消息。 - -随着 Topic 变得日益庞大,它们会被分割成更小的 Partition 以提高性能和可伸缩性。Kafka 保证 Partition 内的所有消息按照它们出现的顺序排序。区分特定消息的方式是通过它的偏移量,您可以将它看作普通数组索引,每个新消息都会增加一个序列号在一个 Partition 中。 - -
    - -
    - -Kafka 遵循发布/订阅模式。这意味着 Kafka 不会跟踪 Kafka 读取哪些记录并删除它们,而是将它们存储一段时间(例如一天)或直到满足某个大小阈值。Consumer 自己对 Kafka 进行新的消息调查并说出他们想要阅读的记录。这使得他们可以按照自己的意愿递增/递减偏移量,从而能够重播和重新处理事件。 - -Kafka 集群持久化保存(使用可配置的保留期限)所有发布记录——无论它们是否被消费。例如,如果保留期限被设置为两天,则在记录发布后的两天之内,它都可以被消费,超过时间后将被丢弃以释放空间。 - -值得注意的是,Consumer 实际上是内部拥有一个或多个 Consumer 流程的 Consumer 群体。为了避免两个进程读两次相同的消息,每个 Partition 仅与每个组的一个 Consumer 进程相关联。 - -
    - -
    - -## 3. 持久化 - -Kafka 实际上将其所有记录存储在磁盘中,并且不会将任何内容保留在 RAM 中。 - -- Kafka 有一个将消息分组在一起的协议。它允许网络请求将消息分组在一起以减少网络开销。服务器一气呵成的将消息的数据块持久化并立即获取较大的线性块。 -- 线性读取/写入磁盘速度很快。现代磁盘速度较慢的概念是由于大量的磁盘搜索,这在大型线性操作中不是问题。 -- 所说的线性操作由操作系统通过预读(预取大块数倍)和后写(将小的逻辑写入大物理写入)技术进行了大量优化。 -- 现代操作系统将磁盘缓存在可用 RAM 中。这被称为 pagecache。 -- 由于 Kafka 在整个流程(生产者 -> 经纪 -> 消费者)中以标准化的二进制格式存储未修改的消息,所以它可以利用零拷贝优化。这就是操作系统将数据从页面缓存直接复制到套接字时,完全绕过了 Kafka 经纪人应用程序。 - -所有这些优化都允许 Kafka 以接近网络速度传递消息。 - -## 4. 复制 - -分区数据在多个代理中复制,以便在一个代理死亡的情况下保存数据。 - -在任何时候,一个代理“拥有”一个分区,并且是应用程序通过该分区读写数据的节点。这被称为分区领导。它将它收到的数据复制到 N 个其他代理(称为追随者)。他们也存储数据,并准备在领导者节点死亡的情况下取代领导者。这就是典型的一主多从模式。 - -
    - -
    - -生产者/消费者如何知道分区的领导者是谁? - -对于生产者/消费者来说,从一个分区写入/读取,他们需要知道它的领导者,对吧?这些信息需要从某处获得。Kafka 将这种元数据存储在一个名为 Zookeeper 的服务中。 - -生产者和消费者都和 Zookeeper 连接并通信。Kafka 一直在摆脱这种耦合,自 0.8 和 0.9 版分别开始,客户端直接从 Kafka 经纪人那里获取元数据信息,他们自己与 Zookeeper 交谈。 - -
    - -
    - -## 5. 流处理 - -在 Kafka 中,流处理器是任何需要从输入主题中持续输入数据流,对该输入执行一些处理并生成输出主题的数据流(或外部服务,数据库,垃圾桶,无论哪里真的......) - -可以直接使用生产者/消费者 API 进行简单处理,但对于更复杂的转换(如将流连接在一起),Kafka 提供了一个集成的 Streams API 库。 - -此 API 旨在用于您自己的代码库中,它不在代理上运行。它与消费者 API 类似,可帮助您扩展多个应用程序的流处理工作(类似于消费者群体)。 - -
    - -
    - -### 5.1. 无状态处理 - -流的无状态处理是确定性处理,不依赖于任何外部。你知道,对于任何给定的数据,你将总是产生独立于其他任何东西的相同输出。 - -一个流可以被解释为一个表,一个表可以被解释为一个流。 - -流可以被解释为数据的一系列更新,其中聚合是表的最终结果。 - -如果您看看如何实现同步数据库复制,您会发现它是通过所谓的流式复制,其中表中的每个更改都发送到副本服务器。 - -Kafka 流可以用同样的方式解释 - 当从最终状态积累时的事件。这样的流聚合被保存在本地的 RocksDB 中(默认情况下),被称为 KTable。 - -
    - -
    - -可以将表格视为流中每个键的最新值的快照。以同样的方式,流记录可以产生一个表,表更新可以产生一个更新日志流。 - -
    - -
    - -### 5.2. 有状态处理 - -一些简单的操作,如 map() 或 filter() 是无状态的,并且不要求您保留有关处理的任何数据。但是,在现实生活中,你要做的大多数操作都是有状态的(例如 count()),因此需要存储当前的累积状态。 - -维护流处理器上的状态的问题是流处理器可能会失败!你需要在哪里保持这个状态才能容错? - -一种天真的做法是简单地将所有状态存储在远程数据库中,并通过网络连接到该存储。问题在于没有数据的地方和大量的网络往返,这两者都会显著减慢你的应用程序。一个更微妙但重要的问题是,您的流处理作业的正常运行时间将与远程数据库紧密耦合,并且作业不会自成体系(数据库中来自另一个团队的更改可能会破坏您的处理过程)。 - -那么更好的方法是什么? - -回想一下表和流的双重性。这使我们能够将数据流转换为与我们的处理共处一地的表格。它还为我们提供了处理容错的机制 - 通过将流存储在 Kafka 代理中。 - -流处理器可以将其状态保存在本地表(例如 RocksDB)中,该表将从输入流更新(可能是某种任意转换之后)。当进程失败时,它可以通过重放流来恢复其数据。 - -您甚至可以让远程数据库成为流的生产者,从而有效地广播更新日志,以便在本地重建表。 - -
    - -
    - -## 6. Kafka 应用场景 - -- 构建实时的流数据管道,在系统或应用间获取可靠数据。 -- 构建实时的流应用程序,用于转换或响应数据流。 - -正如我们已经介绍的那样,Kafka 允许您将大量消息通过集中介质存储并存储,而不用担心性能或数据丢失等问题。 - -这意味着它非常适合用作系统架构的核心,充当连接不同应用程序的集中介质。 Kafka 可以成为事件驱动架构的核心部分,并允许您真正将应用程序彼此分离。 - -
    - -
    - -Kafka 允许您轻松分离不同(微)服务之间的通信。利用 Streams API,现在比以往更容易编写业务逻辑,丰富了 Kafka 主题数据以便服务消费。 - -## 7. 幂等性 - -幂等性引入目的:生产者重复生产消息。生产者进行 retry 会产生重试时,会重复产生消息。有了幂等性之后,在进行 retry 重试时,只会生成一个消息。 - -### 7.1. 幂等性实现 - -#### 7.1.1. PID 和 Sequence Number - -为了实现 Producer 的幂等性,Kafka 引入了 Producer ID(即 PID)和 Sequence Number。 - -- PID。每个新的 Producer 在初始化的时候会被分配一个唯一的 PID,这个 PID 对用户是不可见的。 -- Sequence Numbler。(对于每个 PID,该 Producer 发送数据的每个都对应一个从 0 开始单调递增的 Sequence Number。 - -Broker 端在缓存中保存了这 seq number,对于接收的每条消息,如果其序号比 Broker 缓存中序号大于 1 则接受它,否则将其丢弃。这样就可以实现了消息重复提交了。但是,只能保证单个 Producer 对于同一个的 Exactly Once 语义。不能保证同一个 Producer 一个 topic 不同的 partion 幂等。 - -
    - -实现幂等之后 - -
    - -#### 7.1.2. 生成 PID 的流程 - -在执行创建事务时,如下: - -```java -Producer producer = new KafkaProducer(props); -``` - -会创建一个 Sender,并启动线程,执行如下 run 方法,在 maybeWaitForProducerId()中生成一个 producerId,如下: - -```java -==================================== -类名:Sender -==================================== - -void run(long now) { - if (transactionManager != null) { - try { - ........ - if (!transactionManager.isTransactional()) { - // 为idempotent producer生成一个producer id - maybeWaitForProducerId(); - } else if (transactionManager.hasUnresolvedSequences() && !transactionManager.hasFatalError()) { - ........ -``` - -### 7.2. 幂等性的应用实例 - -(1)配置属性 - -需要设置: - -- enable.idempotence,需要设置为 ture,此时就会默认把 acks 设置为 all,所以不需要再设置 acks 属性了。 - -```java -private Producer buildIdempotProducer(){ - - // create instance for properties to access producer configs - Properties props = new Properties(); - - // bootstrap.servers是Kafka集群的IP地址。多个时,使用逗号隔开 - props.put("bootstrap.servers", "localhost:9092"); - - props.put("enable.idempotence",true); - - //If the request fails, the producer can automatically retry, - props.put("retries", 3); - - //Reduce the no of requests less than 0 - props.put("linger.ms", 1); - - //The buffer.memory controls the total amount of memory available to the producer for buffering. - props.put("buffer.memory", 33554432); - - // Kafka消息是以键值对的形式发送,需要设置key和value类型序列化器 - props.put("key.serializer", - "org.apache.kafka.common.serialization.StringSerializer"); - - props.put("value.serializer", - "org.apache.kafka.common.serialization.StringSerializer"); - - Producer producer = new KafkaProducer(props); - - - return producer; -} -``` - -(2)发送消息 - -跟一般生产者一样,如下 - -```java -public void produceIdempotMessage(String topic, String message) { - // 创建Producer - Producer producer = buildIdempotProducer(); - // 发送消息 - producer.send(new ProducerRecord(topic, message)); - producer.flush(); -} -``` - -此时,因为我们并没有配置 transaction.id 属性,所以不能使用事务相关 API,如下 - -```java -producer.initTransactions(); -``` - -否则会出现如下错误: - -```java -Exception in thread “main” java.lang.IllegalStateException: Transactional method invoked on a non-transactional producer. - at org.apache.kafka.clients.producer.internals.TransactionManager.ensureTransactional(TransactionManager.java:777) - at org.apache.kafka.clients.producer.internals.TransactionManager.initializeTransactions(TransactionManager.java:202) - at org.apache.kafka.clients.producer.KafkaProducer.initTransactions(KafkaProducer.java:544) -``` - -## 8. 事务 - -### 8.1. 事务属性理解 - -事务属性是 2017 年 Kafka 0.11.0.0 引入的新特性。类似于数据库事务,只是这里的数据源是 Kafka,**kafka 事务属性是指一系列的生产者生产消息和消费者提交偏移量的操作在一个事务,或者说是是一个原子操作),同时成功或者失败**。 - -注意:在理解消息的事务时,一直处于一个错误理解就是如下代码中,把操作 db 的业务逻辑跟操作消息当成是一个事务。其实这个是有问题的,操作 DB 数据库的数据源是 DB,消息数据源是 kfaka,这是完全不同两个数据,一种数据源(如 mysql,kafka)对应一个事务,所以它们是两个独立的事务:kafka 事务指 kafka 一系列 生产、消费消息等操作组成一个原子操作;db 事务是指操作数据库的一系列增删改操作组成一个原子操作。 - -```java -void kakfa_in_tranction() { - // 1.kafa的操作:读取消息或者生产消息 - kafkaOperation(); - // 2.db操作 - dbOperation(); -} -``` - -### 8.2. 引入事务目的 - -在事务属性之前先引入了生产者幂等性,它的作用为: - -- 生产者多次发送消息可以封装成一个原子操作,要么都成功,要么失败 -- consumer-transform-producer 模式下,因为消费者提交偏移量出现问题,导致在重复消费消息时,生产者重复生产消息。需要将这个模式下消费者提交偏移量操作和生产者一系列生成消息的操作封装成一个原子操作。 - -**消费者提交偏移量导致重复消费消息的场景**:消费者在消费消息完成提交便宜量 o2 之前挂掉了(假设它最近提交的偏移量是 o1),此时执行再均衡时,其它消费者会重复消费消息(o1 到 o2 之间的消息)。 - -### 8.3. 事务操作的 API - -producer 提供了 initTransactions, beginTransaction, sendOffsets, commitTransaction, abortTransaction 五个事务方法。 - -```java - /** - * 初始化事务。需要注意的有: - * 1、前提 - * 需要保证transation.id属性被配置。 - * 2、这个方法执行逻辑是: - * (1)Ensures any transactions initiated by previous instances of the producer with the same - * transactional.id are completed. If the previous instance had failed with a transaction in - * progress, it will be aborted. If the last transaction had begun completion, - * but not yet finished, this method awaits its completion. - * (2)Gets the internal producer id and epoch, used in all future transactional - * messages issued by the producer. - * - */ - public void initTransactions(); - - /** - * 开启事务 - */ - public void beginTransaction() throws ProducerFencedException ; - - /** - * 为消费者提供的在事务内提交偏移量的操作 - */ - public void sendOffsetsToTransaction(Map offsets, - String consumerGroupId) throws ProducerFencedException ; - - /** - * 提交事务 - */ - public void commitTransaction() throws ProducerFencedException; - - /** - * 放弃事务,类似回滚事务的操作 - */ - public void abortTransaction() throws ProducerFencedException ; -``` - -### 8.4. 事务属性的应用实例 - -在一个原子操作中,根据包含的操作类型,可以分为三种情况,**前两种情况是事务引入的场景**,最后一种情况没有使用价值。 - -只有 Producer 生产消息; - -消费消息和生产消息并存,**这个是事务场景中最常用的情况**,就是我们常说的“consume-transform-produce ”模式 - -只有 consumer 消费消息,这种操作其实没有什么意义,跟使用手动提交效果一样,而且也不是事务属性引入的目的,所以一般不会使用这种情况 - -#### 8.4.1. 相关属性配置 - -使用 kafka 的事务 api 时的一些注意事项: - -- 需要消费者的自动模式设置为 false,并且不能子再手动的进行执行 consumer#commitSync 或者 consumer#commitAsyc -- 生产者配置 transaction.id 属性 -- 生产者不需要再配置 enable.idempotence,因为如果配置了 transaction.id,则此时 enable.idempotence 会被设置为 true -- 消费者需要配置 Isolation.level。在 consume-trnasform-produce 模式下使用事务时,必须设置为 READ_COMMITTED。 - -#### 8.4.2. 只有写 - -创建一个事务,在这个事务操作中,只有生成消息操作。代码如下: - -```java - /** - * 在一个事务只有生产消息操作 - */ - public void onlyProduceInTransaction() { - Producer producer = buildProducer(); - - // 1.初始化事务 - producer.initTransactions(); - - // 2.开启事务 - producer.beginTransaction(); - - try { - // 3.kafka写操作集合 - // 3.1 do业务逻辑 - - // 3.2 发送消息 - producer.send(new ProducerRecord("test", "transaction-data-1")); - - producer.send(new ProducerRecord("test", "transaction-data-2")); - // 3.3 do其他业务逻辑,还可以发送其他topic的消息。 - - // 4.事务提交 - producer.commitTransaction(); - - - } catch (Exception e) { - // 5.放弃事务 - producer.abortTransaction(); - } - - } -``` - -创建生产者,代码如下,需要: - -- 配置 transactional.id 属性 -- 配置 enable.idempotence 属性 - -```java - /** - * 需要: - * 1、设置transactional.id - * 2、设置enable.idempotence - * @return - */ - private Producer buildProducer() { - - // create instance for properties to access producer configs - Properties props = new Properties(); - - // bootstrap.servers是Kafka集群的IP地址。多个时,使用逗号隔开 - props.put("bootstrap.servers", "localhost:9092"); - - // 设置事务id - props.put("transactional.id", "first-transactional"); - - // 设置幂等性 - props.put("enable.idempotence",true); - - //Set acknowledgements for producer requests. - props.put("acks", "all"); - - //If the request fails, the producer can automatically retry, - props.put("retries", 1); - - //Specify buffer size in config,这里不进行设置这个属性,如果设置了,还需要执行producer.flush()来把缓存中消息发送出去 - //props.put("batch.size", 16384); - - //Reduce the no of requests less than 0 - props.put("linger.ms", 1); - - //The buffer.memory controls the total amount of memory available to the producer for buffering. - props.put("buffer.memory", 33554432); - - // Kafka消息是以键值对的形式发送,需要设置key和value类型序列化器 - props.put("key.serializer", - "org.apache.kafka.common.serialization.StringSerializer"); - - props.put("value.serializer", - "org.apache.kafka.common.serialization.StringSerializer"); - - - Producer producer = new KafkaProducer(props); - - return producer; - } -``` - -#### 8.4.3. 消费-生产并存(consume-transform-produce) - -在一个事务中,既有生产消息操作又有消费消息操作,即常说的 Consume-tansform-produce 模式。如下实例代码 - -```java - /** - * 在一个事务内,即有生产消息又有消费消息 - */ - public void consumeTransferProduce() { - // 1.构建上产者 - Producer producer = buildProducer(); - // 2.初始化事务(生成productId),对于一个生产者,只能执行一次初始化事务操作 - producer.initTransactions(); - - // 3.构建消费者和订阅主题 - Consumer consumer = buildConsumer(); - consumer.subscribe(Arrays.asList("test")); - while (true) { - // 4.开启事务 - producer.beginTransaction(); - - // 5.1 接受消息 - ConsumerRecords records = consumer.poll(500); - - try { - // 5.2 do业务逻辑; - System.out.println("customer Message---"); - Map commits = Maps.newHashMap(); - for (ConsumerRecord record : records) { - // 5.2.1 读取消息,并处理消息。print the offset,key and value for the consumer records. - System.out.printf("offset = %d, key = %s, value = %s\n", - record.offset(), record.key(), record.value()); - - // 5.2.2 记录提交的偏移量 - commits.put(new TopicPartition(record.topic(), record.partition()), - new OffsetAndMetadata(record.offset())); - - - // 6.生产新的消息。比如外卖订单状态的消息,如果订单成功,则需要发送跟商家结转消息或者派送员的提成消息 - producer.send(new ProducerRecord("test", "data2")); - } - - // 7.提交偏移量 - producer.sendOffsetsToTransaction(commits, "group0323"); - - // 8.事务提交 - producer.commitTransaction(); - - } catch (Exception e) { - // 7.放弃事务 - producer.abortTransaction(); - } - } - } -``` - -创建消费者代码,需要: - -- 将配置中的自动提交属性(auto.commit)进行关闭 -- 而且在代码里面也不能使用手动提交 commitSync( )或者 commitAsync( ) -- 设置 isolation.level - -```java - /** - * 需要: - * 1、关闭自动提交 enable.auto.commit - * 2、isolation.level为 - * @return - */ - public Consumer buildConsumer() { - Properties props = new Properties(); - // bootstrap.servers是Kafka集群的IP地址。多个时,使用逗号隔开 - props.put("bootstrap.servers", "localhost:9092"); - // 消费者群组 - props.put("group.id", "group0323"); - // 设置隔离级别 - props.put("isolation.level","read_committed"); - // 关闭自动提交 - props.put("enable.auto.commit", "false"); - props.put("session.timeout.ms", "30000"); - props.put("key.deserializer", - "org.apache.kafka.common.serialization.StringDeserializer"); - props.put("value.deserializer", - "org.apache.kafka.common.serialization.StringDeserializer"); - KafkaConsumer consumer = new KafkaConsumer - (props); - - return consumer; - } -``` - -#### 8.4.4. 只有读 - -创建一个事务,在这个事务操作中,只有生成消息操作,如下代码。这种操作其实没有什么意义,跟使用手动提交效果一样,无法保证消费消息操作和提交偏移量操作在一个事务。 - -```java - /** - * 在一个事务只有消息操作 - */ - public void onlyConsumeInTransaction() { - Producer producer = buildProducer(); - - // 1.初始化事务 - producer.initTransactions(); - - // 2.开启事务 - producer.beginTransaction(); - - // 3.kafka读消息的操作集合 - Consumer consumer = buildConsumer(); - while (true) { - // 3.1 接受消息 - ConsumerRecords records = consumer.poll(500); - - try { - // 3.2 do业务逻辑; - System.out.println("customer Message---"); - Map commits = Maps.newHashMap(); - for (ConsumerRecord record : records) { - // 3.2.1 处理消息 print the offset,key and value for the consumer records. - System.out.printf("offset = %d, key = %s, value = %s\n", - record.offset(), record.key(), record.value()); - - // 3.2.2 记录提交偏移量 - commits.put(new TopicPartition(record.topic(), record.partition()), - new OffsetAndMetadata(record.offset())); - } - - // 4.提交偏移量 - producer.sendOffsetsToTransaction(commits, "group0323"); - - // 5.事务提交 - producer.commitTransaction(); - - } catch (Exception e) { - // 6.放弃事务 - producer.abortTransaction(); - } - } - - } -``` - -### 8.5. 生产者事务的实现 - -#### 8.5.1. 相关配置 - -#### 8.5.2. 幂等性和事务性的关系 - -##### 两者关系 - -事务属性实现前提是幂等性,即在配置事务属性 transaction id 时,必须还得配置幂等性;但是幂等性是可以独立使用的,不需要依赖事务属性。 - -- 幂等性引入了 Porducer ID -- 事务属性引入了 Transaction Id 属性。、 - -设置 - -- enable.idempotence = true,transactional.id 不设置:只支持幂等性。 -- enable.idempotence = true,transactional.id 设置:支持事务属性和幂等性 -- enable.idempotence = false,transactional.id 不设置:没有事务属性和幂等性的 kafka -- enable.idempotence = false,transactional.id 设置:无法获取到 PID,此时会报错 - -##### tranaction id 、productid 和 epoch - -**一个 app 有一个 tid,同一个应用的不同实例 PID 是一样的,只是 epoch 的值不同**。如: - -
    - -同一份代码运行两个实例,分步执行如下:_在实例 1 没有进行提交事务前,开始执行实例 2 的初始化事务_ - -
    - -**step1 实例 1-初始化事务**。的打印出对应 productId 和 epoch,信息如下: - -[2018-04-21 20:56:23,106] INFO [TransactionCoordinator id=0] Initialized transactionalId first-transactional with producerId 8000 and producer epoch 123 on partition \_\_transaction_state-12 (kafka.coordinator.transaction.TransactionCoordinator) - -**step2 实例 1-发送消息。** - -**step3 实例 2-初始化事务**。初始化事务时的打印出对应 productId 和 epoch,信息如下: - -18-04-21 20:56:48,373] INFO [TransactionCoordinator id=0] Initialized transactionalId first-transactional with producerId 8000 and producer epoch 124 on partition \_\_transaction_state-12 (kafka.coordinator.transaction.TransactionCoordinator) - -**step4 实例 1-提交事务**,此时报错 - -org.apache.kafka.common.errors.ProducerFencedException: Producer attempted an operation with an old epoch. Either there is a newer producer with the same transactionalId, or the producer’s transaction has been expired by the broker. - -**step5 实例 2-提交事务** - -为了避免这种错误,同一个事务 ID,只有保证如下顺序 epch 小 producer 执行 init-transaction 和 committransaction,然后 epoch 较大的 procuder 才能开始执行 init-transaction 和 commit-transaction,如下顺序: - -
    - -有了 transactionId 后,Kafka 可保证: - -- 跨 Session 的数据幂等发送。当具有相同 Transaction ID 的新的 Producer 实例被创建且工作时,旧的且拥有相同 Transaction ID 的 Producer 将不再工作【上面的实例可以验证】。kafka 保证了关联同一个事务的所有 producer(一个应用有多个实例)必须按照顺序初始化事务、和提交事务,否则就会有问题,这保证了同一事务 ID 中消息是有序的(不同实例得按顺序创建事务和提交事务)。 - -#### 8.5.3. 事务最佳实践-单实例的事务性 - -通过上面实例中可以看到 kafka 是跨 Session 的数据幂等发送,即如果应用部署多个实例时常会遇到上面的问题“_org.apache.kafka.common.errors.ProducerFencedException: Producer attempted an operation with an old epoch. Either there is a newer producer with the same transactionalId, or the producer’s transaction has been expired by the broker_.”,必须保证这些实例生产者的提交事务顺序和创建顺序保持一致才可以,否则就无法成功。其实,在实践中,我们更多的是**如何实现对应用单实例的事务性**。可以通过 spring-kafaka 实现思路来学习,即**每次创建生产者都设置一个不同的 transactionId 的值**,如下代码: - -在 spring-kafka 中,对于一个线程创建一个 producer,事务提交之后,还会关闭这个 producer 并清除,后续同一个线程或者新的线程重新执行事务时,此时就会重新创建 producer。 - -```java -==================================== -类名:ProducerFactoryUtils -==================================== -/** - * Obtain a Producer that is synchronized with the current transaction, if any. - * @param producerFactory the ConnectionFactory to obtain a Channel for - * @param the key type. - * @param the value type. - * @return the resource holder. - */ -public static KafkaResourceHolder getTransactionalResourceHolder( - final ProducerFactory producerFactory) { - - Assert.notNull(producerFactory, "ProducerFactory must not be null"); - - // 1.对于每一个线程会生成一个唯一key,然后根据key去查找resourceHolder - @SuppressWarnings("unchecked") - KafkaResourceHolder resourceHolder = (KafkaResourceHolder) TransactionSynchronizationManager - .getResource(producerFactory); - if (resourceHolder == null) { - // 2.创建一个消费者 - Producer producer = producerFactory.createProducer(); - // 3.开启事务 - producer.beginTransaction(); - resourceHolder = new KafkaResourceHolder(producer); - bindResourceToTransaction(resourceHolder, producerFactory); - } - return resourceHolder; -} -``` - -创建消费者代码 - -```java -==================================== -类名:DefaultKafkaProducerFactory -==================================== -protected Producer createTransactionalProducer() { - Producer producer = this.cache.poll(); - if (producer == null) { - Map configs = new HashMap<>(this.configs); - // 对于每一次生成producer时,都设置一个不同的transactionId - configs.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG, - this.transactionIdPrefix + this.transactionIdSuffix.getAndIncrement()); - producer = new KafkaProducer(configs, this.keySerializer, this.valueSerializer); - // 1.初始化话事务。 - producer.initTransactions(); - return new CloseSafeProducer(producer, this.cache); - } - else { - return producer; - } -} -``` - -#### 8.5.4. Consume-transform-Produce 的流程 - -
    - -**流程 1** **:**查找 Tranaction Corordinator。 - -Producer 向任意一个 brokers 发送 FindCoordinatorRequest 请求来获取 Transaction Coordinator 的地址。 - -**流程 2:**初始化事务 initTransaction - -Producer 发送 InitpidRequest 给事务协调器,获取一个 Pid**。InitpidRequest 的处理过程是同步阻塞的,一旦该调用正确返回,Producer 就可以开始新的事务**。TranactionalId 通过 InitpidRequest 发送给 Tranciton Corordinator,然后在 Tranaciton Log 中记录这的映射关系。除了返回 PID 之外,还具有如下功能: - -- 对 PID 对应的 epoch 进行递增,这样可以保证同一个 app 的不同实例对应的 PID 是一样的,但是 epoch 是不同的。 -- 回滚之前的 Producer 未完成的事务(如果有)。 - -**流程 3:** 开始事务 beginTransaction - -执行 Producer 的 beginTransacion(),它的作用是 Producer 在本地记录下这个 transaction 的状态为开始状态。 - -注意:这个操作并没有通知 Transaction Coordinator。 - -**流程 4:** Consume-transform-produce loop - -**流程 4.0:** 通过 Consumtor 消费消息,处理业务逻辑 - -**流程 4.1:** producer 向 TransactionCordinantro 发送 AddPartitionsToTxnRequest - -在 producer 执行 send 操作时,如果是第一次给发送数据,此时会向 Trasaction Corrdinator 发送一个 AddPartitionsToTxnRequest 请求,Transaction Corrdinator 会在 transaction log 中记录下 tranasactionId 和一个映射关系,并将状态改为 begin。AddPartionsToTxnRequest 的数据结构如下: - -``` -AddPartitionsToTxnRequest => TransactionalId PID Epoch [Topic [Partition]] - TransactionalId => string - PID => int64 - Epoch => int16 - Topic => string - Partition => int32 -``` - -**流程 4.2:** producer#send 发送 ProduceRequst - -生产者发送数据,虽然没有还没有执行 commit 或者 absrot,但是此时消息已经保存到 kafka 上,可以参考如下图断点位置处,此时已经可以查看到消息了,而且即使后面执行 abort,消息也不会删除,只是更改状态字段标识消息为 abort 状态。 - -
    - -**流程 4.3:** AddOffsetCommitsToTxnRequest - -Producer 通过 KafkaProducer.sendOffsetsToTransaction 向事务协调器器发送一个 AddOffesetCommitsToTxnRequests: - -``` -AddOffsetsToTxnRequest => TransactionalId PID Epoch ConsumerGroupID - TransactionalId => string - PID => int64 - Epoch => int16 - ConsumerGroupID => string -``` - -在执行事务提交时,可以根据 ConsumerGroupID 来推断\_customer_offsets 主题中相应的 TopicPartions 信息。这样在 - -**流程 4.4:** TxnOffsetCommitRequest - -Producer 通过 KafkaProducer.sendOffsetsToTransaction 还会向消费者协调器 Cosumer Corrdinator 发送一个 TxnOffsetCommitRequest,在主题\_consumer_offsets 中保存消费者的偏移量信息。 - -``` -TxnOffsetCommitRequest => ConsumerGroupID - PID - Epoch - RetentionTime - OffsetAndMetadata - ConsumerGroupID => string - PID => int64 - Epoch => int32 - RetentionTime => int64 - OffsetAndMetadata => [TopicName [Partition Offset Metadata]] - TopicName => string - Partition => int32 - Offset => int64 - Metadata => string -``` - -**流程 5:** 事务提交和事务终结(放弃事务) - -通过生产者的 commitTransaction 或 abortTransaction 方法来提交事务和终结事务,这两个操作都会发送一个 EndTxnRequest 给 Transaction Coordinator。 - -**流程 5.1**:EndTxnRequest。Producer 发送一个 EndTxnRequest 给 Transaction Coordinator,然后执行如下操作: - -- Transaction Coordinator 会把 PREPARE_COMMIT or PREPARE_ABORT 消息写入到 transaction log 中记录 -- 执行流程 5.2 -- 执行流程 5.3 - -**流程 5.2**:WriteTxnMarkerRequest - -``` -WriteTxnMarkersRequest => [CoorinadorEpoch PID Epoch Marker [Topic [Partition]]] - CoordinatorEpoch => int32 - PID => int64 - Epoch => int16 - Marker => boolean (false(0) means ABORT, true(1) means COMMIT) - Topic => string - Partition => int32 -``` - -- 对于 Producer 生产的消息。Tranaction Coordinator 会发送 WriteTxnMarkerRequest 给当前事务涉及到每个的 leader,leader 收到请求后,会写入一个 COMMIT(PID) 或者 ABORT(PID)的控制信息到 data log 中 -- 对于消费者偏移量信息,如果在这个事务里面包含\_consumer-offsets 主题。Tranaction Coordinator 会发送 WriteTxnMarkerRequest 给 Transaction Coordinartor,Transaction Coordinartor 收到请求后,会写入一个 COMMIT(PID) 或者 ABORT(PID)的控制信息到 data log 中。 - -**流程 5.3:**Transaction Coordinator 会将最终的 COMPLETE_COMMIT 或 COMPLETE_ABORT 消息写入 Transaction Log 中以标明该事务结束。 - -- 只会保留这个事务对应的 PID 和 timstamp。然后把当前事务其他相关消息删除掉,包括 PID 和 tranactionId 的映射关系。 - -##### 文件类型和查看命令 - -kafka 文件主要包括 broker 的 data(主题:test)、事务协调器对应的 transaction_log(主题:\_\_tranaction_state)、偏移量信息(主题:\_consumer_offsets)三种类型。如下图 - -
    - -这三种文件类型其实都是 topic 的分区,所以对于每一个目录都包含*.log、*.index、_.timeindex、_.txnindex 文件(仅这个文件是为了实现事务属性引入的)。segment 和 segmengt 对应 index、timeindex、txnindex 文件命名中序号表示的是第几个消息。如下图中,00000000000000368769.index 和 00000000000000568769.log 中“368969”就是表示文件中存储的第一个消息是 468969 个消息。 - -对于索引文案包含两部分: - -- baseOffset:索引对应 segment 文件中的第几条 message。 -- position:在 segment 中的绝对位置。 - -
    - -查看文件内容: - -bin/kafka-run-class.sh kafka.tools.DumpLogSegments –files /Users/wuzhonghu/data/kafka-logs/firtstopic-0/00000000000000000002.log –print-data-log - -##### ControlMessage 和 Transaction markers - -Trasaction markers 就是 kafka 为了实现事务定义的 Controll Message。这个消息和数据消息都存放在 log 中,在 Consumer 读取事务消息时有用,可以参考下面章节-4.5.1 老版本-读取事务消息顺序。 - -##### Transaction Coordinator 和 Transaction Log - -Transaction Log 如下放置在“\_tranaction_state”主题下面,默认是 50 个分区,每一个分区中文件格式和 broker 存储消息是一样的,都有 log/index/timeindex 文件,如下: - -
    - -#### 8.5.5. 消费读取事务消息(READ_COMMITED) - -Consumer 为了实现事务,新增了一个 isolation.level 配置,有两个值如下, - -- READ_UNCOMMITTED,类似于没有事务属性的消费者。 -- READ_COMMITED,只获取执行了事务提交的消息。 - -在本小节中我们主要讲 READ_COMMITED 模式下读取消息的流程的两种版本的演化 - -##### 老版本-读取事务消息顺序 - -如下图中,按顺序保存到 broker 中消息有:事务 1 消息 T1-M1、对于事务 2 的消息有 T2-M1、事务 1 消息 T1-M2、非事务消息 M1,最终到达 client 端的循序是 M1-> T2-M1 -> T1-M1 -> T1-M2。 - -
    - -具体步骤如下: - -- **step1** Consumer 接受到事务消息 T1-M1、T2-M2、T1-M2 和非事务消息 M1,因为没有收到事务 T1 和 T2 的控制消息,所以此时把事务相关消息 T1-M1、T2-M2、T1-M2 保存到内存,然后只把非事务消息 M1 返回给 client。 -- **step2** Consumer 接受到事务 2 的控制消息 T2-C,此时就把事务消息 T2-M1 发送给 Clinet。 -- **step3** C onsumer 接受到事务 1 的控制消息 T1-C,此时就把事务消息 T1-M1 和 T1-M2 发送给 Client - -##### 新版本-读取事务消息顺序 - -第一种方式,需要在 consumer 客户端缓存消息,当存在耗时比较长的事务时,占用客户端大量的内存资源。为了解决这个问题,通过 LSO 和 Abort Index 文件来解决这个问题,参考: - - - -(1) LSO,Last stable offset。Broker 在缓存中维护了所有处于运行状态的事务对应的 initial offsets,LSO 的值就是这些 offsets 中最小值-1。这样在 LSO 之前数据都是已经 commit 或者 abort 的数据,只有这些数据才对 Consumer 可见,即 consumer 读取数据只能读取到 LSO 的位置。 - -- LSO 并没有持久化某一个位置,而是实时计算出来的,并保存在缓存中。 - -(2)Absort Index 文件 - -Conusmer 发送 FetchRequest 中,新增了 Isolation 字段,表示是那种模式 - -``` -ReplicaId MaxWaitTime MinBytes [TopicName [Partition FetchOffset MaxBytes]] - - ReplicaId => int32 - MaxWaitTime => int32 - MinBytes => int32 - TopicName => string - Partition => int32 - FetchOffset => int64 - MaxBytes => int32 - Isolation => READ_COMMITTED | READ_UNCOMMITTED -``` - -返回数据类型为 FetchResponse 的格式为: - -ThrottleTime [TopicName [Partition ErrorCode HighwaterMarkOffset AbortedTransactions MessageSetSize MessageSet]] - -对应各个给字段类型为 - -``` - ThrottleTime => int32 - TopicName => string - Partition => int32 - ErrorCode => int16 - HighwaterMarkOffset => int64 - AbortedTransactions => [PID FirstOffset] - PID => int64 - FirstOffset => int64 - MessageSetSize => int32 -``` - -- 设置成 READ_UNCOMMITTED 模式时, the AbortedTransactions array is null. -- 设置为 READ_COMMITTED 时,the Last Stable Offset(LSO),当事务提交之后,LSO 向前移动 offset - -数据如下: - -- 存放数据的 log - -
    - -- 存放 Absort Index 的内容如下: - -
    - -执行读取数据流程如下: - -**step1:** 假设 consumer 读取数据的 fetched offsets 的区间是 0 到 4。 - -- 首先,broker 读取 data log 中数据 - -
    - -- 然后,broker 依次读取 abort index 的内容,发现 LSO 大于等于 4 就停止。如上可以获取到 P2 对应的 offset 从 2 到 5 的消息都是被丢弃的: - -
    - -- 最后,broker 将上面 data log 和 abort index 中满足条件的数据返回给 consumer。 - -**step2 :**在 consumer 端根据 absrot index 中返回的内容,过滤丢弃的消息,最终给用户消息为 - -
    - -##### Absorted Transaction Index - -在 broker 中数据中新增一个索引文件,保存 aborted tranasation 对应的 offsets,只有事务执行 abort 时,才会往这个文件新增一个记录,初始这个文件是不存在的,只有第一条 abort 时,才会创建这个文件。 - -
    - -这个索引文件结构的每一行结构是 TransactionEntry: - -``` -Version => int16 - PID => int64 - FirstOffset => int64 - LastOffset => int64 - LastStableOffset => int64 -``` - -当 broker 接受到控制消息(producer 执行 commitTransaction()或者 abortTransaction())时, 执行如下操作: - -(1)计算 LSO。 - -Broker 在缓存中维护了所有处于运行状态的事务对应的 initial offsets,LSO 的值就是这些 offsets 中最小值-1。 - -举例说明下 LSO 的计算,对于一个 data log 中内如如下 - -
    - -对应的 abort index 文件中内如如下:**LSO 是递增的** - -
    - -(2)第二步 如果事务是提交状态,则在索引文件中新增 TransactionEntry。 - -(3)第三步 从 active 的 tranaction set 中移除这个 transaton,然后更新 LSO。 - -##### 问题 - -1、问题 1:producer 通过事务提交消息时抛异常了, 对于使用非事务的消费者,是否可以获取此消息? - -对于事务消息,必须是执行 commit 或者 abstort 之后,消息才对消费者可见,即使是非事务的消费者。只是非事务消费者相比事务消费者区别,在于可以读取执行了 absort 的消息。 - -### 8.6. 其他思考 - -1、如何保证消息不丢。 - -(1)在消费端可以建立一个日志表,和业务处理在一个事务 - -定时扫描没有表发送没有被处理的消息 - -(2)消费端,消费消息之后,修改消息表的中消息状态为已处理成功。 - -2、如何保证消息提交和业务处理在同一个事务内完成 - -在消费端可以建立一个日志表,和业务处理在一个事务 - -3、消费者角度,如何保证消息不被重复消费。 - -(1)通过 seek 操作 - -(2)通过 kafka 事务操作。 - -4、生产者角度,如何保证消息不重复生产 - -(1)kakfka 幂等性 - -## 9. 资料 - -### 9.1. 官方资料 - -[Github](https://github.com/apache/kafka) | [官方文档](https://kafka.apache.org/documentation/) - -### 9.2. 第三方资料 - -- [Kafka Manager](https://github.com/yahoo/kafka-manager) - Kafka 管理工具 -- [Kafka 剖析(一):Kafka 背景及架构介绍](http://www.infoq.com/cn/articles/kafka-analysis-part-1) -- [Thorough Introduction to Apache Kafka](https://hackernoon.com/thorough-introduction-to-apache-kafka-6fbf2989bbc1) -- [Kafak(04) Kafka 生产者事务和幂等](http://www.heartthinkdo.com/?p=2040#43) diff --git a/docs/technology/mq/kafka/kafka-basics.md b/docs/technology/mq/kafka/kafka-basics.md deleted file mode 100644 index 05735c6a..00000000 --- a/docs/technology/mq/kafka/kafka-basics.md +++ /dev/null @@ -1,1106 +0,0 @@ ---- -title: Kafka 实战篇 -date: 2018-07-12 -categories: - - 分布式 -tags: - - java - - javaweb - - 分布式 - - mq ---- - -# Kafka 实战篇 - -> Kafka 是一个分布式的、可水平扩展的、基于发布/订阅模式的、支持容错的消息系统。 -> -> | [**官网**](http://kafka.apache.org/) | [**官方文档**](https://kafka.apache.org/documentation/) | [**Github**](https://github.com/apache/kafka) | - - - -- [1. 概述](#1-概述) - - [1.1. 简介](#11-简介) - - [1.2. 系统结构和存储结构](#12-系统结构和存储结构) - - [1.3. 小结](#13-小结) -- [2. 安装部署](#2-安装部署) - - [2.1. 下载解压](#21-下载解压) - - [2.2. 启动服务器](#22-启动服务器) - - [2.3. 停止服务器](#23-停止服务器) - - [2.4. 创建主题](#24-创建主题) - - [2.5. 生产者生产消息](#25-生产者生产消息) - - [2.6. 消费者消费消息](#26-消费者消费消息) - - [2.7. 集群部署](#27-集群部署) -- [3. API](#3-api) -- [4. 生产者(Producer)](#4-生产者producer) - - [4.1. 发送消息流程](#41-发送消息流程) - - [4.2. 发送消息方式](#42-发送消息方式) -- [5. 消费者(Consumer)](#5-消费者consumer) - - [5.1. 消费者和消费者群组](#51-消费者和消费者群组) - - [5.2. 消费消息流程](#52-消费消息流程) - - [5.3. 提交偏移量](#53-提交偏移量) - - [5.4. 从指定偏移量获取数据](#54-从指定偏移量获取数据) -- [6. Broker](#6-broker) - - [6.1. 集群控制器](#61-集群控制器) - - [6.2. 分区 leader 和 follower](#62-分区-leader-和-follower) - - [6.3. 群组协调器](#63-群组协调器) -- [7. Zookeeper 集群](#7-zookeeper-集群) - - [7.1. 节点信息](#71-节点信息) - - [7.2. zookeeper 一些总结](#72-zookeeper-一些总结) -- [8. 资料](#8-资料) - - - -## 1. 概述 - -### 1.1. 简介 - -Kafka 是一个分布式的、可水平扩展的、基于发布/订阅模式的、支持容错的 MQ 中间件。具有如下特点: - -- 伸缩性。随着数据量增长,可以通过对 broker 集群水平扩展来提高系统性能。 -- 高性能。通过横向扩展生产者、消费者(通过消费者群组实现)和 broker(通过扩展实现系统伸缩性)可以轻松处理巨大的消息流。 -- 消息持久化。基于磁盘的数据存储,消息不会丢失。 - -通过 Partition 来实现多个生产者(同一个 topic 消息根据 key 放置到一个 Partition 中)和多个消费者(一个 Partition 由消费者群组中每一个消费者来负责)。 - -### 1.2. 系统结构和存储结构 - -#### 1.2.1. 系统结构 - -producer 采用 push 方式向 broker 发送消息;customer 采用 pull 方式从 broker 接受消息。 - -- **Producer** - 消息生产者,负责发布消息到 Kafka Broker。 -- **Consumer** - 消息消费者,从 Kafka Broker 读取消息的客户端。 -- **Consumer Group** - 每个 Consumer 属于一个特定的 Consumer Group,若不指定 group name 则属于默认的 group。**在同一个 Group 中,每一个 customer 可以消费多个 Partition,但是一个 Partition 只能指定给一个这个 Group 中一个 Customer**。 -- **Broker** - Kafka 集群包含一个或多个服务器,这种服务器被称为 broker - -
    - -
    - -#### 1.2.2. Topic 的存储结构 - -
    - -
    - -- **Topic** - 每条发布到 Kafka 集群的消息都有一个类别,这个类别被称为 Topic。(物理上不同 Topic 的消息分开存储,逻辑上一个 Topic 的消息虽然保存于一个或多个 broker 上,但用户只需指定消息的 Topic 即可生产或消费数据而不必关心数据存于何处)。 -- **Partition** - Partition 是物理上的概念,每个 Topic 包含一个或多个 Partition。为了使得 Kafka 的吞吐率可以线性提高,物理上把 Topic 分成一个或多个 Partition,每个 Partition 在物理上对应一个文件夹,该文件夹下存储这个 Partition 的所有消息和索引文件。 -- **Segment** - 是 Partition 目录下的文件,保存存储消息。 -- **索引** - 方便查询 segment - -#### 1.2.3. Segment 文件格式 - -可以把 topic 当做一个数据表,表中每一个记录都是 key,value 形式,如下图。 - -注意: 必须要有一个 key,如果没有,则默认会生成一个 key,可以把 key 当做一个消息的标识,同一个 key 可能有多条数据。 - -
    - -
    - -### 1.3. 小结 - -**(1)一个主题存在多个分区,每一分区属于哪个 leader broker?** - -在任意一个 broker 机器都有每一个分区所属 leader 的信息,所以可以通过访问任意一个 broker 获取这些信息。 - -**(2)每个消费者群组对应的分区偏移量的元数据存储在哪里。** - -最新版本保存在 kafka 中,对应的主题是\_consumer_offsets。老版本是在 zookeeper 中。 - -**(3)假设某一个消息处理业务逻辑失败了。是否还可以继续向下执行?如果可以的话,那么此时怎么保证这个消息还会继续被处理呢?** - -答案是:正常情况下无法再处理有问题的消息。 - -这里举一个例子,如 M1->M2->M3->M4,假设第一次 poll 时,得到 M1 和 M2,M1 处理成功,M2 处理失败,我们采用提交方式为处理一个消息就提交一次,此时我们提交偏移量是 offset1,但是当我们第二次执行 poll 时,此时只会获取到 M3 和 M4,因为 poll 的时候是根据本地偏移量来获取的,不是 kafka 中保存的初始偏移量。解决这个问题方法是通过 seek 操作定位到 M2 的位置,此时再执行 poll 时就会获取到 M2 和 M3。 - -**(4)当一个消费者执行了 close 之后,此时会执行再均衡,那么再均衡是在哪里发生的呢?其他同组的消费者如何感知到?** - -是通过群组中成为群主的消费者执行再均衡,执行完毕之后,通过群组协调器把每一个消费者负责分区信息发送给消费者,每一个消费者只能知道它负责的分区信息。 - -**(5)如何保证时序性** - -因为 kafkaf 只保证一个分区内的消息才有时序性,所以只要消息属于同一个 topic 且在同一个分区内,就可以保证 kafka 消费消息是有顺序的了。 - -## 2. 安装部署 - -环境要求:JDK8、ZooKeeper - -### 2.1. 下载解压 - -进入官方下载地址:http://kafka.apache.org/downloads,选择合适版本。 - -解压到本地: - -``` -> tar -xzf kafka_2.11-1.1.0.tgz -> cd kafka_2.11-1.1.0 -``` - -现在您已经在您的机器上下载了最新版本的 Kafka。 - -### 2.2. 启动服务器 - -由于 Kafka 依赖于 ZooKeeper,所以运行前需要先启动 ZooKeeper - -``` -> bin/zookeeper-server-start.sh config/zookeeper.properties -[2013-04-22 15:01:37,495] INFO Reading configuration from: config/zookeeper.properties (org.apache.zookeeper.server.quorum.QuorumPeerConfig) -... -``` - -然后,启动 Kafka - -``` -> bin/kafka-server-start.sh config/server.properties -[2013-04-22 15:01:47,028] INFO Verifying properties (kafka.utils.VerifiableProperties) -[2013-04-22 15:01:47,051] INFO Property socket.send.buffer.bytes is overridden to 1048576 (kafka.utils.VerifiableProperties) -... -``` - -### 2.3. 停止服务器 - -执行所有操作后,可以使用以下命令停止服务器 - -``` -$ bin/kafka-server-stop.sh config/server.properties -``` - -### 2.4. 创建主题 - -创建一个名为 test 的 Topic,这个 Topic 只有一个分区以及一个备份: - -``` -> bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test -``` - -### 2.5. 生产者生产消息 - -运行生产者,然后可以在控制台中输入一些消息,这些消息会发送到服务器: - -``` -> bin/kafka-console-producer.sh --broker-list localhost:9092 --topic test -This is a message -This is another message -``` - -### 2.6. 消费者消费消息 - -启动消费者,然后获得服务器中 Topic 下的消息: - -``` -> bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test --from-beginning -This is a message -This is another message -``` - -### 2.7. 集群部署 - -复制配置为多份(Windows 使用 copy 命令代理): - -``` -> cp config/server.properties config/server-1.properties -> cp config/server.properties config/server-2.properties -``` - -修改配置: - -``` -config/server-1.properties: - broker.id=1 - listeners=PLAINTEXT://:9093 - log.dir=/tmp/kafka-logs-1 - -config/server-2.properties: - broker.id=2 - listeners=PLAINTEXT://:9094 - log.dir=/tmp/kafka-logs-2 -``` - -其中,broker.id 这个参数必须是唯一的。 - -端口故意配置的不一致,是为了可以在一台机器启动多个应用节点。 - -根据这两份配置启动三个服务器节点: - -``` -> bin/kafka-server-start.sh config/server.properties & -... -> bin/kafka-server-start.sh config/server-1.properties & -... -> bin/kafka-server-start.sh config/server-2.properties & -... -``` - -创建一个新的 Topic 使用 三个备份: - -``` -> bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 3 --partitions 1 --topic my-replicated-topic -``` - -查看主题: - -``` -> bin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic my-replicated-topic -Topic:my-replicated-topic PartitionCount:1 ReplicationFactor:3 Configs: - Topic: my-replicated-topic Partition: 0 Leader: 1 Replicas: 1,2,0 Isr: 1,2,0 -``` - -- leader - 负责指定分区的所有读取和写入的节点。每个节点将成为随机选择的分区部分的领导者。 -- replicas - 是复制此分区日志的节点列表,无论它们是否为领导者,或者即使它们当前处于活动状态。 -- isr - 是“同步”复制品的集合。这是副本列表的子集,该列表当前处于活跃状态并且已经被领导者捕获。 - -## 3. API - -Stream API 的 maven 依赖: - -```xml - - org.apache.kafka - kafka-streams - 1.1.0 - -``` - -其他 API 的 maven 依赖: - -```xml - - org.apache.kafka - kafka-clients - 1.1.0 - -``` - -## 4. 生产者(Producer) - -### 4.1. 发送消息流程 - -发送消息流程如下图,需要注意的有: - -- 分区器 Partitioner,分区器决定了一个消息被分配到哪个分区。在我们创建消息时,我们可以选择性指定一个键值 key 或者分区 Partition,如果传入的是 key,则通过图中的分区器 Partitioner 选择一个分区来保存这个消息;如果 key 和 Partition 都没有指定,则会默认生成一个 key。 -- 批次传输。**批次,就是一组消息,这些消息属于同一个主题和分区**。发送时,会把消息分成批次 Batch 传输,如果每一个消息发送一次,会导致大量的网路开销, -- 如果消息成功写入 kafka,就返回一个 RecoredMetaData 对象,它包含了主题和分区信息,以及记录在分区里的偏移量。 -- 如果消息发送失败,可以进行重试,重试次数可以在配置中指定。 - -
    - -
    - -生产者在向 broker 发送消息时是怎么确定向哪一个 broker 发送消息? - -- 生产者客户端会向任一个 broker 发送一个元数据请求(MetadataRequest),获取到每一个分区对应的 leader 信息,并缓存到本地。 -- step2:生产者在发送消息时,会指定 Partition 或者通过 key 得到到一个 Partition,然后根据 Partition 从缓存中获取相应的 leader 信息。 - -
    - -
    - -### 4.2. 发送消息方式 - -#### 4.2.1. 发送并忘记(fire-and-forget) - -代码如下,直接通过 send 方法来发送 - -```java -ProducerRecord record = - new ProducerRecord<>("CustomerCountry", "Precision Products", "France"); - try { - producer.send(record); - } catch (Exception e) { - e.printStackTrace(); -} -``` - -#### 4.2.2. 同步发送 - -代码如下,与“发送并忘记”的方式区别在于多了一个 get()方法,会一直阻塞等待 broker 返回结果: - -```java -ProducerRecord record = - new ProducerRecord<>("CustomerCountry", "Precision Products", "France"); - try { - producer.send(record).get(); - } catch (Exception e) { - e.printStackTrace(); -} -``` - -#### 4.2.3. 异步发送 - -代码如下,异步方式相对于“发送并忘记”的方式的不同在于,在异步返回时可以执行一些操作,如记录错误或者成功日志。 - -首先,定义一个 callback - -```java -private class DemoProducerCallback implements Callback { - @Override - public void onCompletion(RecordMetadata recordMetadata, Exception e) { - if (e != null) { - e.printStackTrace(); - } - } -} -``` - -然后,使用这个 callback - -```java -ProducerRecord record = - new ProducerRecord<>("CustomerCountry", "Biomedical Materials", "USA"); -producer.send(record, new DemoProducerCallback()); -``` - -#### 4.2.4. 发送消息示例 - -```java -import java.util.Properties; -import org.apache.kafka.clients.producer.KafkaProducer; -import org.apache.kafka.clients.producer.Producer; -import org.apache.kafka.clients.producer.ProducerConfig; -import org.apache.kafka.clients.producer.ProducerRecord; - -/** - * Kafka 生产者生产消息示例 生产者配置参考:https://kafka.apache.org/documentation/#producerconfigs - */ -public class ProducerDemo { - private static final String HOST = "localhost:9092"; - - public static void main(String[] args) { - // 1. 指定生产者的配置 - Properties properties = new Properties(); - properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, HOST); - properties.put(ProducerConfig.ACKS_CONFIG, "all"); - properties.put(ProducerConfig.RETRIES_CONFIG, 0); - properties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384); - properties.put(ProducerConfig.LINGER_MS_CONFIG, 1); - properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432); - properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringSerializer"); - properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringSerializer"); - - // 2. 使用配置初始化 Kafka 生产者 - Producer producer = new KafkaProducer<>(properties); - - try { - // 3. 使用 send 方法发送异步消息 - for (int i = 0; i < 100; i++) { - String msg = "Message " + i; - producer.send(new ProducerRecord<>("HelloWorld", msg)); - System.out.println("Sent:" + msg); - } - } catch (Exception e) { - e.printStackTrace(); - } finally { - // 4. 关闭生产者 - producer.close(); - } - } -} -``` - -## 5. 消费者(Consumer) - -### 5.1. 消费者和消费者群组 - -#### 5.1.1. 消费者介绍 - -消费者以**pull 方式**从 broker 拉取消息,消费者可以订阅一个或多个主题,然后按照消息生成顺序(**kafka 只能保证分区中消息的顺序**)读取消息。 - -**一个消息消息只有在所有跟随者节点都进行了同步,才会被消费者获取到**。如下图,只能消费 Message0、Message1、Message2: - -
    - -
    - -#### 5.1.2. 消费者分区组 - -消费者群组可以实现并发的处理消息。一个消费者群组作为消费一个 topic 消息的单元,每一个 Partition 只能隶属于一个消费者群组中一个 customer,如下图 - -
    - -
    - -#### 5.1.3. 消费者区组的再均衡 - -当在群组里面 新增/移除消费者 或者 新增/移除 kafka 集群 broker 节点 时,群组协调器 Broker 会触发再均衡,重新为每一个 Partition 分配消费者。**再均衡期间,消费者无法读取消息,造成整个消费者群组一小段时间的不可用。** - -- 新增消费者。customer 订阅主题之后,第一次执行 poll 方法 -- 移除消费者。执行 customer.close()操作或者消费客户端宕机,就不再通过 poll 向群组协调器发送心跳了,当群组协调器检测次消费者没有心跳,就会触发再均衡。 -- 新增 broker。如重启 broker 节点 -- 移除 broker。如 kill 掉 broker 节点。 - -**再均衡是是通过消费者群组中的称为“群主”消费者客户端进行的**。什么是群主呢?“群主”就是第一个加入群组的消费者。消费者第一次加入群组时,它会向群组协调器发送一个 JoinGroup 的请求,如果是第一个,则此消费者被指定为“群主”(群主是不是和 qq 群很想啊,就是那个第一个进群的人)。 - -群主分配分区的过程如下: - -1. 群主从群组协调器获取群组成员列表,然后给每一个消费者进行分配分区 Partition。 -2. 两个分配策略:Range 和 RoundRobin。 - - Range 策略,就是把若干个连续的分区分配给消费者,如存在分区 1-5,假设有 3 个消费者,则消费者 1 负责分区 1-2,消费者 2 负责分区 3-4,消费者 3 负责分区 5。 - - RoundRoin 策略,就是把所有分区逐个分给消费者,如存在分区 1-5,假设有 3 个消费者,则分区 1->消费 1,分区 2->消费者 2,分区 3>消费者 3,分区 4>消费者 1,分区 5->消费者 2。 -3. 群主分配完成之后,把分配情况发送给群组协调器。 -4. 群组协调器再把这些信息发送给消费者。**每一个消费者只能看到自己的分配信息,只有群主知道所有消费者的分配信息**。 - -
    - -
    - -### 5.2. 消费消息流程 - -#### 5.2.1. 消费流程 demo - -具体步骤如下 - -- step1 创建消费者。 -- step2 订阅主题。除了订阅主题方式外还有使用指定分组的模式,但是常用方式都是订阅主题方式 -- stpe3 轮询消息。通过 poll 方法轮询。 -- stpe4 关闭消费者。在不用消费者之后,会执行 close 操作。close 操作会关闭 socket,并触发当前消费者群组的再均衡。 - -```java - // 1.构建KafkaCustomer - Consumer consumer = buildCustomer(); - - // 2.设置主题 - consumer.subscribe(Arrays.asList(topic)); - - // 3.接受消息 - try { - while (true) { - ConsumerRecords records = consumer.poll(500); - System.out.println("customer Message---"); - for (ConsumerRecord record : records) - - // print the offset,key and value for the consumer records. - System.out.printf("offset = %d, key = %s, value = %s\n", - record.offset(), record.key(), record.value()); - } - } finally { - // 4.关闭消息 - consumer.close(); - } -``` - -创建消费者的代码如下: - -```java -public Consumer buildCustomer() { - Properties props = new Properties(); - // bootstrap.servers是Kafka集群的IP地址。多个时,使用逗号隔开 - props.put("bootstrap.servers", "localhost:9092"); - // 消费者群组 - props.put("group.id", "test"); - props.put("enable.auto.commit", "true"); - props.put("auto.commit.interval.ms", "1000"); - props.put("session.timeout.ms", "30000"); - props.put("key.deserializer", - "org.apache.kafka.common.serialization.StringDeserializer"); - props.put("value.deserializer", - "org.apache.kafka.common.serialization.StringDeserializer"); - KafkaConsumer consumer = new KafkaConsumer - (props); - - return consumer; -} -``` - -#### 5.2.2. 消费消息方式 - -分为订阅主题和指定分组两种方式: - -- 消费者分组模式。通过订阅主题方式时,消费者必须加入到消费者群组中,即消费者必须有一个自己的分组; -- 独立消费者模式。这种模式就是消费者是独立的不属于任何消费者分组,自己指定消费那些 Partition。 - -1、订阅主题方式 - -```java -consumer.subscribe(Arrays.asList(topic)); -``` - -2、独立消费者模式 - -通过 consumer 的 assign(Collection partitions)方法来为消费者指定分区。 - -```java -public void consumeMessageForIndependentConsumer(String topic){ - // 1.构建KafkaCustomer - Consumer consumer = buildCustomer(); - - // 2.指定分区 - // 2.1获取可用分区 - List partitionInfoList = buildCustomer().partitionsFor(topic); - // 2.2指定分区,这里是指定了所有分区,也可以指定个别的分区 - if(null != partitionInfoList){ - List partitions = Lists.newArrayList(); - for(PartitionInfo partitionInfo : partitionInfoList){ - partitions.add(new TopicPartition(partitionInfo.topic(),partitionInfo.partition())); - } - consumer.assign(partitions); - } - - // 3.接受消息 - while (true) { - ConsumerRecords records = consumer.poll(500); - System.out.println("consume Message---"); - for (ConsumerRecord record : records) { - - // print the offset,key and value for the consumer records. - System.out.printf("offset = %d, key = %s, value = %s\n", - record.offset(), record.key(), record.value()); - - // 异步提交 - consumer.commitAsync(); - - - } - } -} -``` - -#### 5.2.3. 轮询获取消息 - -通过 poll 来获取消息,但是获取消息时并不是立刻返回结果,需要考虑两个因素: - -- 消费者通过 customer.poll(time)中设置的等待时间 -- broker 会等待累计一定量数据,然后发送给消费者。这样可以减少网络开销。 - -
    - -
    - -poll 处了获取消息外,还有其他作用,如下: - -- 发送心跳信息。消费者通过向被指派为群组协调器的 broker 发送心跳来维护他和群组的从属关系,当机器宕掉后,群组协调器触发再分配 - -### 5.3. 提交偏移量 - -#### 5.3.1. 偏移量和提交 - -(1)偏移量 - -偏移量 offeset,是指一个消息在分区中位置。在通过生产者向 kafka 推送消息返回的结果中包含了这个偏移量值,或者在消费者拉取信息时,也会包含消息的偏移量信息。 - -(2)提交的解释 - -我们把消息的偏移量提交到 kafka 的操作叫做**提交或提交偏移量。** - -(3)偏移量的应用 - -目前会有两个位置记录这个偏移量: - -a. Kafka Broker 保存。消费者通过提交操作,把读取分区中最新消息的偏移量更新到 kafka 服务器端(老版本的 kafka 是保存在 zookeeper 中),即消费者往一个叫做\_consumer_offset 的特殊主题发送消息,消息里面包消息的偏移量信息,**并且该主题配置清理策略是 compact,即对于每一个 key 只保存最新的值(key 由 groupId、topic 和 partition 组成)**。关于提交操作在本节进行讨论。 - -如果消费者一直处于运行状态,这个偏移量是没有起到作用,只有当加入或者删除一个群组里消费者,然后进行再均衡操作只有,此时为了可以继续之前工作,新的消费者需要知道上一个消费者处理这个分区的位置信息。 - -b. 消费者客户端保存。消费者客户端会保存 poll()每一次执行后的最后一个消息的偏移量,这样每次执行轮询操作 poll 时,都从这个位置获取信息。这个信息修改可以通过后续小节中三个 seek 方法来修改。 - -(4)提交时会遇到两个问题 - -a. 重复处理 - -当提交的偏移量小于客户端处理的最后一个消息的偏移量时,会出现重复处理消息的问题,如下图 - -
    - -
    - -b. 消息丢失 - -当提交的偏移量大于客户端处理的最后端最后一个消息的偏移量,会出现消息丢失的问题,如下图: - -
    - -
    - -(5)提交方式 - -主要分为:自动提交和手动提交。 - -a. 自动提交 - -auto.commit.commit ,默认为 true 自动提交,自动提交时通过轮询方式来做,时间间通过 auto.commit.interval.ms 属性来进行设置。 - -b. 手动提交 - -除了自动提交,还可以进行手动提交,手动提交就是通过代码调用函数的方式提交,在使用手动提交时首先需要将 auto.commit.commit 设置为 false,目前有三种方式:同步提交、异步提交、同步和异步结合。 - -#### 5.3.2. 同步提交 - -可以通过 commitSync 来进行提交,**同步提交会一直提交直到成功**。如下 - -```java -public void customerMessageWithSyncCommit(String topic) { - // 1.构建KafkaCustomer - Consumer consumer = buildCustomer(); - - // 2.设置主题 - consumer.subscribe(Arrays.asList(topic)); - - // 3.接受消息 - while (true) { - ConsumerRecords records = consumer.poll(500); - System.out.println("customer Message---"); - for (ConsumerRecord record : records) { - - // print the offset,key and value for the consumer records. - System.out.printf("offset = %d, key = %s, value = %s\n", - record.offset(), record.key(), record.value()); - - - // 同步提交 - try { - consumer.commitSync(); - } catch (Exception e) { - logger.error("commit error"); - } - } - } -} -``` - -#### 5.3.3. 异步提交 - -同步提交一个缺点是,在进行提交 commitAysnc()会阻塞整个下面流程。所以引入了异步提交 commitAsync(),如下代码,这里定义了 OffsetCommitCallback,也可以只进行 commitAsync(),不设置任何参数。 - -```java -public void customerMessageWithAsyncCommit(String topic) { - // 1.构建KafkaCustomer - Consumer consumer = buildCustomer(); - - // 2.设置主题 - consumer.subscribe(Arrays.asList(topic)); - - // 3.接受消息 - while (true) { - ConsumerRecords records = consumer.poll(500); - System.out.println("customer Message---"); - for (ConsumerRecord record : records) { - - // print the offset,key and value for the consumer records. - System.out.printf("offset = %d, key = %s, value = %s\n", - record.offset(), record.key(), record.value()); - - // 异步提交 - consumer.commitAsync(new OffsetCommitCallback() { - public void onComplete(Map offsets, Exception e) { - if (e != null) { - logger.error("Commit failed for offsets{}", offsets, e); - } - } - }); - - - } - } -} -``` - -#### 5.3.4. 同步和异步提交 - -代码如下: - -```java -public void customerMessageWithSyncAndAsyncCommit(String topic) { - // 1.构建KafkaCustomer - Consumer consumer = buildCustomer(); - - // 2.设置主题 - consumer.subscribe(Arrays.asList(topic)); - - // 3.接受消息 - try { - while (true) { - ConsumerRecords records = consumer.poll(500); - System.out.println("customer Message---"); - for (ConsumerRecord record : records) { - - // print the offset,key and value for the consumer records. - System.out.printf("offset = %d, key = %s, value = %s\n", - record.offset(), record.key(), record.value()); - - // 异步提交 - consumer.commitAsync(); - } - } - } catch (Exception e) { - - } finally { - // 同步提交 - try { - consumer.commitSync(); - } finally { - consumer.close(); - } - } -} -``` - -### 5.4. 从指定偏移量获取数据 - -我们读取消息是通过 poll 方法。它根据消费者客户端本地保存的当前偏移量来获取消息。如果我们需要从指定偏移量位置获取数据,此时就需要修改这个值为我们想要读取消息开始的地方,目前有如下三个方法: - -- seekToBeginning(Collection partitions)。可以修改分区当前偏移量为分区的起始位置、 -- seekToEnd(Collection partitions)。可以修改分区当前偏移量为分区的末尾位置 -- seek(TopicPartition partition, long offset); 可以修改分区当前偏移量为分区的起始位置 - -通过 seek(TopicPartition partition, long offset)可以实现处理消息和提交偏移量在一个事务中完成。思路就是需要在可短建立一个数据表,保证处理消息和和消息偏移量位置写入到这个数据表在一个事务中,此时就可以保证处理消息和记录偏移量要么同时成功,要么同时失败。代码如下: - -```java - consumer.subscribe(topic); - // 1.第一次调用pool,加入消费者群组 - consumer.poll(0); - // 2.获取负责的分区,并从本地数据库读取改分区最新偏移量,并通过seek方法修改poll获取消息的位置 - for (TopicPartition partition: consumer.assignment()) - consumer.seek(partition, getOffsetFromDB(partition)); - - while (true) { - ConsumerRecords records = - consumer.poll(100); - for (ConsumerRecord record : records) - { - processRecord(record); - storeRecordInDB(record); - storeOffsetInDB(record.topic(), record.partition(), - record.offset()); - } - commitDBTransaction(); - } -``` - -## 6. Broker - -### 6.1. 集群控制器 - -控制器除了具有一般 broker 的功能,还负责分区 leader 的选举。 - -### 6.2. 分区 leader 和 follower - -Kafka 在 0.8 以前的版本中,如果一个 broker 机器宕机了,其上面的 Partition 都不能用了。为了实现 High Availablity,引入了复制功能,即一个 Partition 还会在其他的 broker 上面进行备份。为了实现复制功能,引入了分区 leader 和 follower: - -(1)Leader 作用 - -生产者和消费者请求都会经过这个 leader。Producer 和 Concumer 往一个 Partition 写入和读取消息时,都会首先查找这个 Partition 的 leader - -保存那些 follower 节点的状态与自己是一致的。 - -(2)follower 作用: - -定时通过类似消费者的 poll 方法从 leader 中获取消息,进行备份 - -同一个 topic 的不同 Partition 会分布在多个 broker 上,而且一个 Partition 还会在其他的 broker 上面进行备份,Producer 在发布消息到某个 Partition 时,先找到该 Partition 的 Leader,然后向这个 leader 推送消息;每个 Follower 都从 Leader 拉取消息,拉取消息成功之后,向 leader 发送一个 ack 确认。如下一个流程图: - -
    - -
    - -### 6.3. 群组协调器 - -群组协调器,顾名思义就是维护消费者群组 ,消费者通过向被指派为群组协调器的 broker(不同的群组可以有不同的协调器)发送心跳来维护它们和群组的从属关系,以及它们对分区的所有权。 - -1、触发再均衡 - -只要消费者以正常的时间间隔发送心跳,就会被认为是活跃的。消费者是通过 poll 获取消息时,发送的心跳的,当消费者客户端宕机之后,群组协调器在一段内没有收到心跳,则此时会认为消费者已死亡,然后触发一次再均衡。具体再均衡流程,可以参考上面的“再均衡”小节。 - -## 7. Zookeeper 集群 - -### 7.1. 节点信息 - -参考: - -Zookeeper 保存的就是节点信息和节点状态,不会保存 kafka 的消息信息,节点信息包括: - -- broker。broer 启动时在 zookeeper 注册、并通过 watcher 监听 broker 节点变化;并且还记录 topic 和 Partition 的信息。 -- consumers,消费者节点信息 -- admin -- config -- controller 和 controloer_epoch - -
    - -
    - -#### 7.1.1. broker - -(1)Topic 的注册信息 - -作用:在创建 zookeeper 时,注册 topic 的 Partition 信息,包括每一个分区的复制节点 id。 - -路径:/brokers/topics/[topic] - -数据格式: - -```json -Schema: -{ "fields" : - [ {"name": "version", "type": "int", "doc": "version id"}, - {"name": "partitions", - "type": {"type": "map", - "values": {"type": "array", "items": "int", "doc": "a list of replica ids"}, - "doc": "a map from partition id to replica list"}, - } - ] -} -Example: -{ - "version": 1, - "partitions": {"0": [0, 1, 3] } } # 分区0的对应的复制节点是0、1、3. -} -``` - -(2)分区信息 - -作用:记录分区信息,如分区的 leader 信息 - -路径信息:/brokers/topics/[topic]/partitions/[partitionId]/state - -格式: - -```json -Schema: -{ "fields": - [ {"name": "version", "type": "int", "doc": "version id"}, - {"name": "isr", - "type": {"type": "array", - "items": "int", - "doc": "an array of the id of replicas in isr"} - }, - {"name": "leader", "type": "int", "doc": "id of the leader replica"}, - {"name": "controller_epoch", "type": "int", "doc": "epoch of the controller that last updated the leader and isr info"}, - {"name": "leader_epoch", "type": "int", "doc": "epoch of the leader"} - ] -} - -Example: -{ - "version": 1, - "isr": [0,1], - "leader": 0, - "controller_epoch": 1, - "leader_epoch": 0 -} -``` - -(3)broker 信息 - -作用:在 borker 启动时,向 zookeeper 注册节点信息 - -路径:/brokers/ids/[brokerId] - -数据格式: - -```json -Schema: -{ "fields": - [ {"name": "version", "type": "int", "doc": "version id"}, - {"name": "host", "type": "string", "doc": "ip address or host name of the broker"}, - {"name": "port", "type": "int", "doc": "port of the broker"}, - {"name": "jmx_port", "type": "int", "doc": "port for jmx"} - ] -} - -Example: -{ - "version": 1, - "host": "192.168.1.148", - "port": 9092, - "jmx_port": 9999 -} -``` - -#### 7.1.2. controller 和 controller_epoch - -(1)控制器的 epoch: - -/controller_epoch -> int (epoch) - -(2)控制器的注册信息: - -/controller -> int (broker id of the controller) - -#### 7.1.3. consumer - -(1)消费者注册信息: - -路径:/consumers/[groupId]/ids/[consumerId] - -数据格式: - -```json -Schema: -{ "fields": - [ {"name": "version", "type": "int", "doc": "version id"}, - {"name": "pattern", "type": "string", "doc": "can be of static, white_list or black_list"}, - {"name": "subscription", "type" : {"type": "map", "values": {"type": "int"}, - "doc": "a map from a topic or a wildcard pattern to the number of streams"} } ] -} - -Example: -A static subscription: -{ - "version": 1, - "pattern": "static", - "subscription": {"topic1": 1, "topic2": 2} -} - - -A whitelist subscription: -{ - "version": 1, - "pattern": "white_list", - "subscription": {"abc": 1} -} - -A blacklist subscription: -{ - "version": 1, - "pattern": "black_list", - "subscription": {"abc": 1} -} -``` - -#### 7.1.4. admin - -(1)Re-assign partitions - -路径:/admin/reassign_partitions - -数据格式: - -```json -{ - "fields":[ - { - "name":"version", - "type":"int", - "doc":"version id" - }, - { - "name":"partitions", - "type":{ - "type":"array", - "items":{ - "fields":[ - { - "name":"topic", - "type":"string", - "doc":"topic of the partition to be reassigned" - }, - { - "name":"partition", - "type":"int", - "doc":"the partition to be reassigned" - }, - { - "name":"replicas", - "type":"array", - "items":"int", - "doc":"a list of replica ids" - } - ], - } - "doc":"an array of partitions to be reassigned to new replicas" - } - } - ] -} - -Example: -{ - "version": 1, - "partitions": - [ - { - "topic": "Foo", - "partition": 1, - "replicas": [0, 1, 3] - } - ] -} -``` - -(2)Preferred replication election - -路径:/admin/preferred_replica_election - -数据格式: - -```json -{ - "fields":[ - { - "name":"version", - "type":"int", - "doc":"version id" - }, - { - "name":"partitions", - "type":{ - "type":"array", - "items":{ - "fields":[ - { - "name":"topic", - "type":"string", - "doc":"topic of the partition for which preferred replica election should be triggered" - }, - { - "name":"partition", - "type":"int", - "doc":"the partition for which preferred replica election should be triggered" - } - ], - } - "doc":"an array of partitions for which preferred replica election should be triggered" - } - } - ] -} - -Example: - -{ - "version": 1, - "partitions": - [ - { - "topic": "Foo", - "partition": 1 - }, - { - "topic": "Bar", - "partition": 0 - } - ] -} -``` - -(3)Delete topics - -/admin/delete_topics/[topic_to_be_deleted] (the value of the path in empty) - -#### 7.1.5. config - -Topic Configuration - -/config/topics/[topic_name] - -数据格式: - -```json -{ - "version": 1, - "config": { - "config.a": "x", - "config.b": "y", - ... - } -} -``` - -### 7.2. zookeeper 一些总结 - -离开了 Zookeeper, Kafka 不能对 Topic 进行新增操作, 但是仍然可以 produce 和 consume 消息. - -## 8. 资料 - -- [Kafka(03) Kafka 介绍](http://www.heartthinkdo.com/?p=2006#233) diff --git a/docs/technology/rpc/dubbo.md b/docs/technology/rpc/Dubbo.md similarity index 100% rename from docs/technology/rpc/dubbo.md rename to docs/technology/rpc/Dubbo.md diff --git a/docs/technology/rpc/zookeeper.md b/docs/technology/rpc/ZooKeeper.md similarity index 100% rename from docs/technology/rpc/zookeeper.md rename to docs/technology/rpc/ZooKeeper.md diff --git a/docs/technology/security/shiro.md b/docs/technology/security/Shiro.md similarity index 100% rename from docs/technology/security/shiro.md rename to docs/technology/security/Shiro.md diff --git a/docs/technology/security/spring-security.md b/docs/technology/security/SpringSecurity.md similarity index 100% rename from docs/technology/security/spring-security.md rename to docs/technology/security/SpringSecurity.md diff --git a/docs/technology/server/jetty.md b/docs/technology/server/Jetty.md similarity index 95% rename from docs/technology/server/jetty.md rename to docs/technology/server/Jetty.md index ee9b4892..f5c07ec6 100644 --- a/docs/technology/server/jetty.md +++ b/docs/technology/server/Jetty.md @@ -1,15 +1,5 @@ # Jetty 应用指南 - - -- [概述](#概述) -- [jetty 的嵌入式启动](#jetty-的嵌入式启动) - - [API 方式](#api-方式) - - [Maven 插件方式](#maven-插件方式) -- [参考](#参考) - - - ## 概述 **jetty 是什么?** @@ -246,5 +236,6 @@ mvn jetty:run ## 参考 -- [jetty wiki](http://wiki.eclipse.org/Jetty/Reference/jetty-env.xml) -- [jetty 官方文档](http://www.eclipse.org/jetty/documentation/current/) +- [Jetty 官方网址](http://www.eclipse.org/jetty/index.html) +- [Jetty Github](https://github.com/eclipse/jetty.project) +- [Jetty wiki](http://wiki.eclipse.org/Jetty/Reference/jetty-env.xml) diff --git a/docs/technology/server/README.md b/docs/technology/server/README.md deleted file mode 100644 index 4745bc24..00000000 --- a/docs/technology/server/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# 服务器 - -- [Nginx 简易教程](https://github.com/dunwu/nginx-tutorial) - 轻量级的 Web 服务器、反向代理服务器及电子邮件(IMAP/POP3)代理服务器,支持负载均衡。 -- [Tomcat 应用指南](tomcat.md) - 轻量级的应用服务器 -- [Jetty 应用指南](jetty.md) - 比 Tomcat 更轻量级的应用服务器 diff --git a/docs/technology/server/tomcat.md b/docs/technology/server/Tomcat.md similarity index 98% rename from docs/technology/server/tomcat.md rename to docs/technology/server/Tomcat.md index 5d5e5c88..b39aafbb 100644 --- a/docs/technology/server/tomcat.md +++ b/docs/technology/server/Tomcat.md @@ -1,32 +1,10 @@ # Tomcat 应用指南 -> 版本说明 +> 🎁 版本说明 > -> 本文使用 Tomcat 版本为 Tomcat 8.5.24。 +> 当前最新版本:Tomcat 8.5.24 > -> Tomcat 8.5 要求 JDK 版本为 1.7 以上。 - - - -- [简介](#简介) - - [Tomcat 是什么](#tomcat-是什么) - - [Tomcat 重要目录](#tomcat-重要目录) - - [web 工程发布目录结构](#web-工程发布目录结构) -- [QuickStart](#quickstart) - - [安装](#安装) - - [配置](#配置) - - [启动](#启动) -- [Tomcat 工作原理](#tomcat-工作原理) - - [Tomcat 主要组件](#tomcat-主要组件) - - [Tomcat 生命周期](#tomcat-生命周期) - - [Connector](#connector) - - [Comet](#comet) - - [异步 Servlet](#异步-servlet) -- [资料](#资料) - - [官方](#官方) - - [第三方](#第三方) - - +> 环境要求:JDK7+ ## 简介 diff --git a/docs/technology/test/jmeter.md b/docs/technology/test/jmeter.md deleted file mode 100644 index 01c30b98..00000000 --- a/docs/technology/test/jmeter.md +++ /dev/null @@ -1,49 +0,0 @@ -# JMeter 应用指南 - -Jmeter 是一款使用 Java 开发的、开源的,测试工具, 主要用来做功能测试和性能测试(压力测试/负载测试). - -它包括对 HTTP(S), JDBC, JMS, FTP, LDAP,TCP,本机调用等协议的支持,也可以与用户编写的代码轻松扩展。 - -## 运行原理 - -1. 通过线程组来驱动多个线程的方式运行。 -2. 运行在Jvm虚拟机上。 -3. 可以进行分布式测试。(分为控制机和负载机) - - 控制机:运行负载测试、管理远程多台负载机 - - 负载机:运行负载测试、向控制机发送测试结果。 - - 具体运行逻辑:a、启动Agent ——> b、控制机连接负载机 ——> c、控制机发送运行指令 ——> d、 负载机运行并回传测试结果 ——> e、控制机收集测试结果 - -## 测试计划 - -元件:可以理解为每一个菜单。如THHP请求、响应断言。元件组:可以理解为同一类型菜单的集合。如取样器、断言 - -1. 取样器(Sampler):模拟用户的请求操作。如HTTP请求、WebService请求、Java请求等。 - -2. 断言(检查点):验证结果的正确性。如响应断言。 - -3. 监听器:收集测试结果信息。如查看结果树、聚合报告。 - - 下面的4、5、6是为取样器提供各种所需要的数据。 -4. 前置处理器:在请求发送前做的环境及参数的准备。 - -5. 配置元件:提供预备的数据等。如CSV、函数助手。 - -6. 后置处理器:处理服务器的返回结果。通常用正则进行关联。 - -7. 逻辑控制器:控制多个请求发送的循环次数及顺序等。 - -8. 定时器:控制多个请求发送的时间频次。 - -9. 线程组:模拟大量用户负载运行场景。 - -组成关系: - -1.脚本中最多只能有一个测试计划。 -2.测试计划中至少要有一个线程组。 -3.线程组中至少要有一个取样器。 -4.线程组中至少要有一个监听器。 - -## 参考资料 - -- [Jmeter 官网](https://jmeter.apache.org/) -- [Jmeter Github](https://github.com/apache/jmeter) -- [Jmeter 性能测试入门](https://www.cnblogs.com/TankXiao/p/4045439.html) diff --git a/docs/technology/security/security-theory.md b/docs/theory/security-theory.md similarity index 100% rename from docs/technology/security/security-theory.md rename to docs/theory/security-theory.md From 45f7ab34ea2fa0c8f6402889539da48f37857aed Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Tue, 19 Nov 2019 12:01:10 +0800 Subject: [PATCH 43/75] update docs --- README.md | 13 +- docs/README.md | 13 +- docs/index.html | 53 +- docs/network/network-physical.md | 4 +- .../monitor/{Arthas.md => arthas.md} | 1 + .../monitor/{Jmeter.md => jmeter.md} | 0 .../monitor/{SkyWalking.md => skywalking.md} | 0 docs/technology/monitor/zookeeper-ops.md | 468 ++++++++++ docs/technology/monitor/zookeeper.md | 858 ++++++++++++++++++ docs/technology/rpc/ZooKeeper.md | 100 +- 10 files changed, 1368 insertions(+), 142 deletions(-) rename docs/technology/monitor/{Arthas.md => arthas.md} (99%) rename docs/technology/monitor/{Jmeter.md => jmeter.md} (100%) rename docs/technology/monitor/{SkyWalking.md => skywalking.md} (100%) create mode 100644 docs/technology/monitor/zookeeper-ops.md create mode 100644 docs/technology/monitor/zookeeper.md diff --git a/README.md b/README.md index c9562d0e..6a60fca0 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,6 @@ ### RPC -- [ZooKeeper 应用指南](docs/technology/rpc/ZooKeeper.md) - [Dubbo 应用指南](docs/technology/rpc/Dubbo.md) - Spring Cloud @@ -90,11 +89,15 @@ - [Mybatis 应用指南](docs/technology/data/Mybatis.md) - [ShardingSphere 应用指南](docs/technology/data/ShardingSphere.md) -### 监控/诊断/测试 +### 协调/监控/诊断/测试 -- [Arthas 应用指南](docs/technology/monitor/Arthas.md) -- [SkyWalking 应用指南](docs/technology/monitor/SkyWalking.md) -- [Jmeter 应用指南](docs/technology/monitor/Jmeter.md) +- 协调 + - [ZooKeeper 应用指南](docs/technology/monitor/zookeeper.md) + - [ZooKeeper 运维指南](docs/technology/monitor/zookeeper-ops.md) +- 诊断 + - [Arthas 应用指南](docs/technology/monitor/arthas.md) +- 测试 + - [Jmeter 应用指南](docs/technology/monitor/jmeter.md) ## ☕ JavaEE diff --git a/docs/README.md b/docs/README.md index 98b96636..f5db973b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -76,7 +76,6 @@ ### RPC -- [ZooKeeper 应用指南](technology/rpc/ZooKeeper.md) - [Dubbo 应用指南](technology/rpc/Dubbo.md) - Spring Cloud @@ -90,11 +89,15 @@ - [Mybatis 应用指南](technology/data/Mybatis.md) - [ShardingSphere 应用指南](technology/data/ShardingSphere.md) -### 监控/诊断/测试 +### 协调/监控/诊断/测试 -- [Arthas 应用指南](technology/monitor/Arthas.md) -- [SkyWalking 应用指南](technology/monitor/SkyWalking.md) -- [Jmeter 应用指南](technology/monitor/Jmeter.md) +- 协调 + - [ZooKeeper 应用指南](technology/monitor/zookeeper.md) + - [ZooKeeper 运维指南](technology/monitor/zookeeper-ops.md) +- 诊断 + - [Arthas 应用指南](technology/monitor/arthas.md) +- 测试 + - [Jmeter 应用指南](technology/monitor/jmeter.md) ## ☕ JavaEE diff --git a/docs/index.html b/docs/index.html index c46a5566..d447e05c 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,24 +1,16 @@ - - JavaWeb - - + + JavaWeb 教程 + + - - + + - + - + - + - + - - -
    正在加载...
    - - - - - + @media (max-width: 600px) { + pre { + padding-left: 0 !important; + padding-right: 0 !important; + } + } + + + +
    正在加载...
    + + + + + - - - - - - - - + + + + + + + + diff --git a/docs/javaee/README.md b/docs/javaee/README.md index fe37eab1..be0701d0 100644 --- a/docs/javaee/README.md +++ b/docs/javaee/README.md @@ -1,4 +1,4 @@ -# JavaEE 教程 +# ☕ JavaEE ## 知识点 diff --git a/docs/network/README.md b/docs/network/README.md deleted file mode 100644 index 88cc17e9..00000000 --- a/docs/network/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# 网络通信 - -## 知识点 - -- **面试** - - [网络通信面经](network-interview.md) -- **网络分层** - - [计算机网络概述](network-guide.md) - - [计算机网络之物理层](network-physical.md) - - [计算机网络之链路层](network-data-link.md) - - [计算机网络之网络层](network-network.md) - - [计算机网络之传输层](network-transport.md) - - [计算机网络之应用层](network-application.md) -- **重要技术** - - [超文本传输协议 HTTP](http.md) - - [域名系统 DNS](dns.md) - - [传输控制协议 TCP](tcp.md) - - [用户数据报协议 UDP](udp.md) - - [网际控制报文协议 ICMP](icmp.md) - - [网络协议之 WebSocket](websocket.md) - - [CDN 详解](cdn.md) - - [ ] Socket 详解 - -## 资源 - -- 书 - - [HTTP 权威指南](https://book.douban.com/subject/10746113/) - - [TCP/IP 详解 卷 1:协议](https://book.douban.com/subject/1088054/) - - [TCP/IP 详解 卷 2:实现](https://book.douban.com/subject/1087767/) - - [TCP/IP 详解 卷 3:TCP 事务协议、HTTP、NNTP 和 UNIX 域协议](https://book.douban.com/subject/1058634/) - - [Web性能权威指南](https://book.douban.com/subject/25856314/) - - [图解 HTTP](https://book.douban.com/subject/25863515/) - - [图解TCP/IP](https://book.douban.com/subject/24737674/) - - [计算机网络(第7版)](https://book.douban.com/subject/26960678/) - 谢希仁 -- 站点 - - https://www.rfc-editor.org/ - 在线查阅、下载 RFC 文档 -- 工具 - - [WireShark](https://www.wireshark.org/) - - [Postman](https://www.getpostman.com/) diff --git a/docs/network/cdn.md b/docs/network/cdn.md deleted file mode 100644 index 93bbdb0e..00000000 --- a/docs/network/cdn.md +++ /dev/null @@ -1,119 +0,0 @@ -# CDN 详解 - -
    - -## 简介 - -### 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 服务节点分布式缓存和终端本地缓存。任意一个点的存储崩溃或失效,并不影响系统服务的可用性。 - -
    - -如 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-内容推送网络](https://www.cnblogs.com/skynet/archive/2012/12/18/2824141.html) -> - [闲话 CDN](https://zhuanlan.zhihu.com/p/39028766) - -## CDN 访问流程 - -
    - -1. 用户在浏览器中访问域名,域名解析的请求被发往网站的 DNS 域名解析服务器; -2. 由于网站的 DNS 域名解析服务器对此域名的解析设置了 CNAME,请求被指向 CDN 网络中的智能 DNS 负载均衡系统; -3. 智能 DNS 负载均衡系统对域名进行智能解析,将响应速度最快的节点 IP 返回给用户;浏览器在得到速度最快节点的 IP 地址以后,向 CDN 节点发出访问请求; -4. 由于是第一次访问,CDN 节点将回到源站取用户请求的数据并发给用户; -5. 当有其他用户再次访问同样内容时,CDN 将直接将数据返回给客户,完成请求/服务过程。 - -同时,它还与分布在不同地点的所有 CDN 节点保持通信,搜集各节点的健康状态,确保不将用户的请求分配到任何一个已经不可用的节点上。 - -> 参考: -> -> - [CDN-内容推送网络](https://www.cnblogs.com/skynet/archive/2012/12/18/2824141.html) -> - [闲话 CDN](https://zhuanlan.zhihu.com/p/39028766) - -## 推送和拉取 - -CDN 服务有推送和拉取两种方式: - -### CDN 推送 - -当你服务器上内容发生变动时,推送 CDN 接受新内容。直接推送给 CDN 并重写 URL 地址以指向你的内容的 CDN 地址。你可以配置内容到期时间及何时更新。内容只有在更改或新增是才推送,流量最小化,但储存最大化。 - -优点在于节省源站带宽,提前将要分发的内容放到 CDN 节点上了,当某个流量高峰来临时,不会把你的源站带宽占满(源站还要留点带宽提供动态 HTML 啊)。 - -缺点是需要针对 CDN 做接口开发,在被分发内容生成时主动上传给 CDN。 - -### CDN 拉取 - -CDN 拉取是当第一个用户请求该资源时,从服务器上拉取资源。你将内容留在自己的服务器上并重写 URL 指向 CDN 地址。直到内容被缓存在 CDN 上为止,这样请求只会更慢, - -[存活时间(TTL)](https://en.wikipedia.org/wiki/Time_to_live)决定缓存多久时间。CDN 拉取方式最小化 CDN 上的储存空间,但如果过期文件并在实际更改之前被拉取,则会导致冗余的流量。 - -高流量站点使用 CDN 拉取效果不错,因为只有最近请求的内容保存在 CDN 中,流量才能更平衡地分散。 - -优点在于实现简单。 - -> 参考:[推送式与拉取式CDN服务的优劣问题](https://segmentfault.com/q/1010000000119794) - -## 资源 - -- 文章 - - [Wikipedia - CDN](https://en.wikipedia.org/wiki/Content_delivery_network) - - [CDN-内容推送网络](https://www.cnblogs.com/skynet/archive/2012/12/18/2824141.html) - - [闲话 CDN](https://zhuanlan.zhihu.com/p/39028766) - - [CDN 技术详解](https://www.cnblogs.com/losbyday/p/5843960.html) - - [推送式与拉取式CDN服务的优劣问题](https://segmentfault.com/q/1010000000119794) -- CDN 资源 - - https://staticfile.org/ - - https://unpkg.com/ - - https://cdnjs.com/ - - https://github.com/ossrs/srs - - https://github.com/jsdelivr/jsdelivr diff --git a/docs/network/dns.md b/docs/network/dns.md deleted file mode 100644 index eaef1496..00000000 --- a/docs/network/dns.md +++ /dev/null @@ -1,133 +0,0 @@ -# 域名系统 DNS - -> 域名系统(英文:Domain Name System,缩写:DNS)是互联网的一项服务。它作为将域名和 IP 地址相互映射的一个分布式数据库,能够使人更方便地访问互联网。DNS 使用 TCP 和 UDP 端口 53。当前,对于每一级域名长度的限制是 63 个字符,域名总长度则不能超过 253 个字符。 -> -> 关键词:DNS, 域名解析 - - - -- [简介](#简介) - - [什么是 DNS?](#什么是-dns) - - [什么是域名?](#什么是域名) - - [DNS 的分层](#dns-的分层) - - [DNS 服务类型](#dns-服务类型) - - [记录类型](#记录类型) -- [域名解析](#域名解析) -- [Linux 上的域名相关命令](#linux-上的域名相关命令) - - [hostname](#hostname) - - [nslookup](#nslookup) -- [更多内容](#更多内容) - - - -## 简介 - -### 什么是 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](http://www.example.com/) )或映射到一个 `A`记录。 -- **PTR 记录(反向记录)** ─ PTR 记录用于定义与 IP 地址相关联的名称。 PTR 记录是 A 或 AAAA 记录的逆。 PTR 记录是唯一的,因为它们以 .arpa 根开始并被委派给 IP 地址的所有者。 - -> 详细可以参考:[维基百科 - 域名服务器记录类型列表](https://zh.wikipedia.org/wiki/域名服务器记录类型列表) - -## 域名解析 - -主机名到 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` 命令,在其中添加下面的内容来配置域名服务器地址: -> -> ``` -> nameserver 218.2.135.1 -> ``` - -## Linux 上的域名相关命令 - -### hostname - -> hostname 命令用于查看和设置系统的主机名称。环境变量 HOSTNAME 也保存了当前的主机名。在使用 hostname 命令设置主机名后,系统并不会永久保存新的主机名,重新启动机器之后还是原来的主机名。如果需要永久修改主机名,需要同时修改 `/etc/hosts` 和 `/etc/sysconfig/network` 的相关内容。 -> -> 参考:http://man.linuxde.net/hostname - -示例: - -```bash -$ hostname -AY1307311912260196fcZ -``` - -### nslookup - -> nslookup 命令是常用域名查询工具,就是查 DNS 信息用的命令。 -> -> 参考:http://man.linuxde.net/nslookup - -示例: - -```bash -[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 -``` - -## 更多内容 - -- [维基百科 - 域名](https://zh.wikipedia.org/wiki/域名) -- [维基百科 - 域名系统](https://zh.wikipedia.org/wiki/域名系统) -- [维基百科 - 域名服务器记录类型列表](https://zh.wikipedia.org/wiki/域名服务器记录类型列表) -- [什么是 DNS?](https://aws.amazon.com/cn/route53/what-is-dns/) -- [RFC 1034](https://tools.ietf.org/html/rfc1034) -- [RFC 1035](https://tools.ietf.org/html/rfc1035) diff --git a/docs/network/http.md b/docs/network/http.md deleted file mode 100644 index 1dc13b55..00000000 --- a/docs/network/http.md +++ /dev/null @@ -1,214 +0,0 @@ -# 网络协议之 HTTP - - - -- [HTTP 是什么?](#http-是什么) -- [实例](#实例) -- [工作原理](#工作原理) -- [特点](#特点) -- [客户端请求消息](#客户端请求消息) -- [服务器响应消息](#服务器响应消息) -- [HTTP 状态码](#http-状态码) -- [更多内容](#更多内容) - - - -## HTTP 是什么? - -HTTP(HyperText Transfer Protocol)即超文本传输协议,是互联网上应用最为广泛的一种网络协议。所有的 WWW(万维网) 文件都必须遵守这个标准。设计 HTTP 最初的目的是为了提供一种发布和接收 HTML 页面的方法。 - -HTTP 是由 **IETF**(Internet Engineering Task Force,互联网工程工作小组) 和 **W3C**(World Wide Web Consortium,万维网协会) 共同合作制订的,它们发布了一系列的**RFC**(Request For Comments),其中最著名的是 RFC 2616,它定义了**HTTP /1.1**。 - -它是一种应用层协议(OSI 七层模型的最顶层),它基于 TCP/IP 通信协议来传递数据(HTML 文件, 图片文件, 查询结果等)。 - -## 实例 - -如果你学习过计算机网络,熟悉 OSI 模型,那么你可以跳过这个实例了。 - -而不了解 OSI 模型的朋友,不妨通过一个实例来对 HTTP 报文有一个感性的认识。 - -以下是使用 wireshark 抓取的一个实际访问百度首页的 HTTP GET 报文: - -
    - -可以清楚的看到它的层级结构如下图,经过了层层的包装。 - -
    - -## 工作原理 - -HTTP 工作于 **Client/Server** 模型上。 - -客户端和服务器之间的通信采用 **request/response** 机制。 - -客户端是终端(可以是浏览器、爬虫程序等),服务器是网站的 Web 服务器。 - -一次 HTTP 操作称为一个事务,其工作过程大致可分为四步: - -1. **建立连接** - 首先,客户端和服务器需要建立一个到服务器指定端口(默认端口号为 **80**)的 TCP 连接(注:虽然 HTTP 采用 TCP 连接是最流行的方式,但是 RFC 并没有指定一定要采用这种网络传输方式。)。 -2. **发送请求信息** - 客户端向服务器发送请求。请求方式的格式为,统一资源标识符、协议版本号,后边是 MIME 信息包括请求修饰符 -3. **发送响应信息** - 服务器监听指定接口是否收到请求,一旦发现请求,处理后,返回响应结果给客户端。其格式为一个状态行包括信息的协议版本号、一个成功或错误的代码,后边是 MIME 信息包括服务器信息、实体信息和可能的内容。 -4. **关闭连接** - 客户端根据响应,显示结果给用户,最后关闭连接。 - -## 特点 - -- **无连接的** - 无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。 -- **无状态的** - HTTP 协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。 -- **媒体独立的** - 这意味着,只要客户端和服务器知道如何处理的数据内容,任何类型的数据都可以通过 HTTP 发送。客户端以及服务器指定使用适合的 MIME-type 内容类型。 -- **C/S 模型的** - 基于 Client/Server 模型工作。 - -# HTTP 消息结构 - -HTTP 是基于客户端/服务端(C/S)的架构模型,通过一个可靠的链接来交换信息,是一个无状态的请求/响应协议。 - -一个 HTTP"客户端"是一个应用程序(Web 浏览器或其他任何客户端),通过连接到服务器达到向服务器发送一个或多个 HTTP 的请求的目的。 - -一个 HTTP"服务器"同样也是一个应用程序(通常是一个 Web 服务,如 Apache Web 服务器或 IIS 服务器等),通过接收客户端的请求并向客户端发送 HTTP 响应数据。 - -HTTP 使用统一资源标识符(Uniform Resource Identifiers, URI)来传输数据和建立连接。 - -一旦建立连接后,数据消息就通过类似 Internet 邮件所使用的格式[RFC5322]和多用途 Internet 邮件扩展(MIME)[RFC2045]来传送。 - -
    - -## 客户端请求消息 - -客户端发送一个 HTTP 请求到服务器的请求消息包括以下格式:请求行(request line)、请求头部(header)、空行和请求数据四个部分组成,下图给出了请求报文的一般格式。 - -
    - -## 服务器响应消息 - -HTTP 响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。 - -
    - -# HTTP 请求 - -根据 HTTP 标准,HTTP 请求可以使用多种请求方法。 - -**HTTP1.0**定义了三种请求方法: **GET**, **POST** 和 **HEAD**方法。 - -**HTTP1.1**新增了五种请求方法:**OPTIONS**, **PUT**, **DELETE**, **TRACE** 和 **CONNECT**方法。 - -| 方法 | 描述 | -| ------- | ---------------------------------------------------------------------------------------------------------------------------------------- | -| GET | 请求指定的页面信息,并返回实体主体。 | -| HEAD | 类似于 get 请求,只不过返回的响应中没有具体的内容,用于获取报头 | -| POST | 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST 请求可能会导致新的资源的建立和/或已有资源的修改。 | -| PUT | 从客户端向服务器传送的数据取代指定的文档的内容。 | -| DELETE | 请求服务器删除指定的页面。 | -| CONNECT | HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器。 | -| OPTIONS | 允许客户端查看服务器的性能。 | -| TRACE | 回显服务器收到的请求,主要用于测试或诊断。 | - -**HTTP** **请求消息头** - -| **请求消息头** | **说明** | -| --------------- | -------------------------------------------- | -| Accept | 浏览器支持的格式 | -| Accept-Encoding | 支持的编码格式,如(UTF-8,GBK) | -| Accept-Language | 支持的语言类型 | -| User-Agent | 浏览器信息 | -| Cookie | 记录的是用户当前的状态 | -| Referer | 指从哪个页面单击链接进入的页面 | -| HOST | 目的地址对应的主机名 | -| Connection | 连接类型。如 Keep-Alive 表示长连接,不会断开 | -| Content-Length | 内容长度 | -| Content-Type | 内容类型 | - -# HTTP 响应 - -| **响应消息头** | **说明** | -| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Allow | 服务器支持哪些请求方法(如 GET、POST 等)。 | -| Content-Encoding | 文档的编码(Encode)方法。只有在解码之后才可以得到 Content-Type 头指定的内容类型。利用 gzip 压缩文档能够显著地减少 HTML 文档的下载时间。Java 的 GZIPOutputStream 可以很方便地进行 gzip 压缩,但只有 Unix 上的 Netscape 和 Windows 上的 IE 4、IE 5 才支持它。因此,Servlet 应该通过查看 Accept-Encoding 头(即 request.getHeader("Accept-Encoding"))检查浏览器是否支持 gzip,为支持 gzip 的浏览器返回经 gzip 压缩的 HTML 页面,为其他浏览器返回普通页面。 | -| Content-Length | 表示内容长度。只有当浏览器使用持久 HTTP 连接时才需要这个数据。如果你想要利用持久连接的优势,可以把输出文档写入 ByteArrayOutputStram,完成后查看其大小,然后把该值放入 Content-Length 头,最后通过`byteArrayStream.writeTo(response.getOutputStream()` 发送内容。 | -| Content-Type | 表示后面的文档属于什么 MIME 类型。Servlet 默认为 `text/plain`,但通常需要显式地指定为 text/html。由于经常要设置 Content-Type,因此 HttpServletResponse 提供了一个专用的方法 setContentType。 | -| Date | 当前的 GMT 时间。你可以用 setDateHeader 来设置这个头以避免转换时间格式的麻烦。 | -| Expires | 应该在什么时候认为文档已经过期,从而不再缓存它? | -| Last-Modified | 文档的最后改动时间。客户可以通过 If-Modified-Since 请求头提供一个日期,该请求将被视为一个条件 GET,只有改动时间迟于指定时间的文档才会返回,否则返回一个 304(Not Modified)状态。Last-Modified 也可用 setDateHeader 方法来设置。 | -| Location | 表示客户应当到哪里去提取文档。Location 通常不是直接设置的,而是通过 HttpServletResponse 的 sendRedirect 方法,该方法同时设置状态代码为 302。 | -| Refresh | 表示浏览器应该在多少时间之后刷新文档,以秒计。除了刷新当前文档之外,你还可以通过 `response.setHeader("Refresh", "5;URL=http://host/path")`让浏览器读取指定的页面。 注意这种功能通常是通过设置 HTML 页面 HEAD 区的 ``实现,这是因为,自动刷新或重定向对于那些不能使用 CGI 或 Servlet 的 HTML 编写者十分重要。但是,对于 Servlet 来说,直接设置 Refresh 头更加方便。 注意 Refresh 的意义是"N 秒之后刷新本页面或访问指定页面",而不是"每隔 N 秒刷新本页面或访问指定页面"。因此,连续刷新要求每次都发送一个 Refresh 头,而发送 204 状态代码则可以阻止浏览器继续刷新,不管是使用 Refresh 头还是 ``。 注意 Refresh 头不属于 HTTP 1.1 正式规范的一部分,而是一个扩展,但 Netscape 和 IE 都支持它。 | -| Server | 服务器名字。Servlet 一般不设置这个值,而是由 Web 服务器自己设置。 | -| Set-Cookie | 设置和页面关联的 Cookie。Servlet 不应使用`response.setHeader("Set-Cookie", ...)`,而是应使用 HttpServletResponse 提供的专用方法 addCookie。参见下文有关 Cookie 设置的讨论。 | -| WWW-Authenticate | 客户应该在 Authorization 头中提供什么类型的授权信息?在包含 401(Unauthorized)状态行的应答中这个头是必需的。例如,`response.setHeader("WWW-Authenticate", "BASIC realm=\"executives\"")`。 注意 Servlet 一般不进行这方面的处理,而是让 Web 服务器的专门机制来控制受密码保护页面的访问(例如.htaccess)。 | - -## HTTP 状态码 - -当浏览者访问一个网页时,浏览者的浏览器会向网页所在服务器发出请求。当浏览器接收并显示网页前,此网页所在的服务器会返回一个包含 HTTP 状态码的信息头(server header)用以响应浏览器的请求。 - -HTTP 状态码的英文为 HTTP Status Code。 - -下面是常见的 HTTP 状态码: - -- 200 - 请求成功 -- 301 - 资源(网页等)被永久转移到其它 URL -- 404 - 请求的资源(网页等)不存在 -- 500 - 内部服务器错误 - -HTTP 状态码分类 - -HTTP 状态码由三个十进制数字组成,第一个十进制数字定义了状态码的类型,后两个数字没有分类的作用。HTTP 状态码共分为 5 种类型: - -| **分类** | **分类描述** | -| -------- | ---------------------------------------------- | -| 1 | 信息,服务器收到请求,需要请求者继续执行操作 | -| 2 | 成功,操作被成功接收并处理 | -| 3 | 重定向,需要进一步的操作以完成请求 | -| 4 | 客户端错误,请求包含语法错误或无法完成请求 | -| 5 | 服务器错误,服务器在处理请求的过程中发生了错误 | - -**HTTP 状态列表:** - -| 状态码 | 状态码英文名称 | -| ------ | ------------------------------- | -| 100 | Continue | -| 101 | Switching Protocols | -| 200 | OK | -| 201 | Created | -| 202 | Accepted | -| 203 | Non-Authoritative Information | -| 204 | No Content | -| 205 | Reset Content | -| 206 | Partial Content | -| 300 | Multiple Choices | -| 301 | Moved Permanently | -| 302 | Found | -| 303 | See Other | -| 304 | Not Modified | -| 305 | Use Proxy | -| 306 | Unused | -| 307 | Temporary Redirect | -| 400 | Bad Request | -| 401 | Unauthorized | -| 402 | Payment Required | -| 403 | Forbidden | -| 404 | Not Found | -| 405 | Method Not Allowed | -| 406 | Not Acceptable | -| 407 | Proxy Authentication Required | -| 408 | Request Time-out | -| 409 | Conflict | -| 410 | Gone | -| 411 | Length Required | -| 412 | Precondition Failed | -| 413 | Request Entity Too Large | -| 414 | Request-URI Too Large | -| 415 | Unsupported Media Type | -| 416 | Requested range not satisfiable | -| 417 | Expectation Failed | -| 500 | Internal Server Error | -| 501 | Not Implemented | -| 502 | Bad Gateway | -| 503 | Service Unavailable | -| 504 | Gateway Time-out | -| 505 | HTTP Version not supported | - -## 更多内容 - -- [图解 HTTP](https://book.douban.com/subject/25863515/) -- http://blog.csdn.net/gueter/article/details/1524447 -- http://www.runoob.com/http/http-intro.html -- https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol -- https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS diff --git a/docs/network/icmp.md b/docs/network/icmp.md deleted file mode 100644 index e2889d2c..00000000 --- a/docs/network/icmp.md +++ /dev/null @@ -1,242 +0,0 @@ -# 网络协议之 ICMP - - - -- [ICMP 简介](#icmp-简介) -- [ICMP 类型](#icmp-类型) -- [目的不可达(Destination Unreachable Message)](#目的不可达destination-unreachable-message) -- [超时(Time Exceeded Message)](#超时time-exceeded-message) -- [参数错误报文(Parameter Problem Message)](#参数错误报文parameter-problem-message) -- [源冷却(Source Quench Message)](#源冷却source-quench-message) -- [重定向(Redirect Message)](#重定向redirect-message) -- [请求回显或回显应答(Echo or Echo Reply Message)](#请求回显或回显应答echo-or-echo-reply-message) -- [时间戳或时间戳请求(Timestamp or Timestamp Reply Message)](#时间戳或时间戳请求timestamp-or-timestamp-reply-message) -- [信息请求或信息响应](#信息请求或信息响应) -- [总结](#总结) -- [参考](#参考) - - - -## ICMP 简介 - -ICMP 全名为(INTERNET CONTROL MESSAGE PROTOCOL)网络控制消息协议。 - -ICMP 的协议号为**1**。 - -ICMP 报文就像是 IP 报文的小弟,总顶着 IP 报文的名头出来混。因为 ICMP 报文是在 IP 报文内部的,如图: - -
    - -图:IP 数据报 - -## ICMP 类型 - -ICMP 报文主要有两大功能:查询报文和差错报文。 - -## 目的不可达(Destination Unreachable Message) - -0 1 2 3 - -0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - -| Type | Code | Checksum | - -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - -| unused | - -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - -| Internet Header + 64 bits of Original Data Datagram | - -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - -日常生活中,邮寄包裹会经过多个传递环节,任意一环如果无法传下去,都会返回寄件人,并附上无法邮寄的原因。同理,当路由器收到一个无法传递下去的 IP 报文时,会发送 ICMP**目的不可达报文**(Type 为 3)给 IP 报文的源发送方。报文中的 Code 就表示发送失败的原因。 - -Code - -0 = net unreachable; - -1 = host unreachable; - -2 = protocol unreachable; - -3 = port unreachable; - -4 = fragmentation needed and DF set; - -5 = source route failed. - -## 超时(Time Exceeded Message) - -0 1 2 3 - -0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - -| Type | Code | Checksum | - -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - -| unused | - -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - -| Internet Header + 64 bits of Original Data Datagram | - -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - -网络传输 IP 数据报的过程中,如果 IP 数据包的 TTL 值逐渐递减为 0 时,需要丢弃数据报。这时,路由器需要向源发送方发送 **ICMP 超时报文**(Type 为 11),Code 为 0,表示传输过程中超时了。 - -一个 IP 数据报可能会因为过大而被分片,然后在目的主机侧把所有的分片重组。如果主机迟迟没有等到所有的分片报文,就会向源发送方发送一个 ICMP 超时报文,Code 为 1,表示分片重组超时了。 - -## 参数错误报文(Parameter Problem Message) - -0 1 2 3 - -0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - -| Type | Code | Checksum | - -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - -| Pointer | unused | - -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - -| Internet Header + 64 bits of Original Data Datagram | - -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - -当路由器或主机处理数据报时,发现因为报文头的参数错误而不得不丢弃报文时,需要向源发送方发送**参数错误报文**(Type 为 12)。当 Code 为 0 时,报文中的 Pointer 表示错误的字节位置。 - -## 源冷却(Source Quench Message) - -0 1 2 3 - -0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - -| Type | Code | Checksum | - -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - -| unused | - -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - -| Internet Header + 64 bits of Original Data Datagram | - -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - -路由器在处理报文时会有一个缓存队列。如果超过最大缓存队列,将无法处理,从而丢弃报文。并向源发送方发一个 **ICMP 源冷却报文**(Type 为 4),告诉对方:“嘿,我这里客满了,你迟点再来。” - -## 重定向(Redirect Message) - -0 1 2 3 - -0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - -| Type | Code | Checksum | - -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - -| Gateway Internet Address | - -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - -| Internet Header + 64 bits of Original Data Datagram | - -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - -想像一下,在公司中,有人来你的项目组问你某某某在哪儿。你一想,我们组没有这人啊。你肯定就会说,我们组没有这号人,你去其他组看看。当路由收到 IP 数据报,发现数据报的目的地址在路由表上没有,它就会发 **ICMP 重定向报文**(Type 为 5)给源发送方,提醒它想要发送的地址不在,去其他地方找找吧。 - -## 请求回显或回显应答(Echo or Echo Reply Message) - -0 1 2 3 - -0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - -| Type | Code | Checksum | - -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - -| Identifier | Sequence Number | - -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - -| Data ... - -+-+-+-+-+- - -**Type(8)**是**请求回显报文(Echo)**;Type(0)是**回显应答报文(Echo Reply)**。 - -请求回显或回显应答报文属于查询报文。Ping 就是用这种报文进行查询和回应。 - -## 时间戳或时间戳请求(Timestamp or Timestamp Reply Message) - -0 1 2 3 - -0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - -| Type | Code | Checksum | - -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - -| Identifier | Sequence Number | - -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - -| Originate Timestamp | - -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - -| Receive Timestamp | - -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - -| Transmit Timestamp | - -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - -时间戳报文是用来记录收发以及传输时间的报文。Originate Timestamp 记录的是发送方发送报文的时刻;**Receive Timestamp**记录的是接收方收到报文的时刻;**Transmit Timestamp**表示回显这最后发送报文的时刻。 - -## 信息请求或信息响应 - -0 1 2 3 - -0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - -| Type | Code | Checksum | - -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - -| Identifier | Sequence Number | - -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - -这种报文是用来找出一个主机所在的网络个数(一个主机可能会在多个网络中)。报文的 IP 消息头的目的地址会填为全 0,表示 this,源地址会填为源 IP 所在的网络 IP。 - -## 总结 - -**图:ICMP 知识点思维导图** - -
    - -## 参考 - -- [RFC792](http://www.rfc-editor.org/info/rfc792) diff --git a/docs/network/network-application.md b/docs/network/network-application.md deleted file mode 100644 index 171ccb14..00000000 --- a/docs/network/network-application.md +++ /dev/null @@ -1,160 +0,0 @@ -# 计算机网络之应用层 - - - -- [HTTP](#http) -- [DNS](#dns) -- [FTP](#ftp) -- [DHCP](#dhcp) -- [TELNET](#telnet) -- [电子邮件协议](#电子邮件协议) - - [1. SMTP](#1-smtp) - - [2. POP3](#2-pop3) - - [3. IMAP](#3-imap) -- [常用端口](#常用端口) -- [Web 页面请求过程](#web-页面请求过程) - - [1. DHCP 配置主机信息](#1-dhcp-配置主机信息) - - [2. ARP 解析 MAC 地址](#2-arp-解析-mac-地址) - - [3. DNS 解析域名](#3-dns-解析域名) - - [4. HTTP 请求页面](#4-http-请求页面) - - - -## HTTP - -
    - -超文本传输协议(英语:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP 是万维网的数据通信的基础。 - -设计 HTTP 最初的目的是为了提供一种发布和接收 HTML 页面的方法。通过 HTTP 或者 HTTPS 协议请求的资源由统一资源标识符(Uniform Resource Identifiers,URI)来标识。 - -安全套接字层超文本传输协议 HTTPS 为了数据传输的安全,HTTPS 在 HTTP 的基础上加入了 SSL 协议,SSL 依靠证书来验证服务器的身份,并为浏览器和服务器之间的通信加密。 - -> :point_right: 扩展阅读:[HTTP](HTTP.md) - -## DNS - -域名服务器 DNS 是一个分布式数据库,提供了主机名和 IP 地址之间相互转换的服务。这里的分布式数据库是指,每个站点只保留它自己的那部分数据。 - -
    - -域名具有层次结构,从上到下依次为:根域名、顶级域名、二级域名。 - -
    - -DNS 可以使用 UDP 或者 TCP 进行传输,使用的端口号都为 53。大多数情况下 DNS 使用 UDP 进行传输,这就要求域名解析器和域名服务器都必须自己处理超时和重传来保证可靠性。在两种情况下会使用 TCP 进行传输: - -- 如果返回的响应超过的 512 字节(UDP 最大只支持 512 字节的数据)。 -- 区域传送(区域传送是主域名服务器向辅助域名服务器传送变化的那部分数据)。 - -> :point_right: 扩展阅读:[DNS](DNS.md) - -## FTP - -文件传送协议 FTP 使用 TCP 进行连接,它需要两个连接来传送一个文件: - -- 控制连接:服务器打开端口号 21 等待客户端的连接,客户端主动建立连接后,使用这个连接将客户端的命令传送给服务器,并传回服务器的应答。 -- 数据连接:用来传送一个文件数据。 - -根据数据连接是否是服务器端主动建立,FTP 有主动和被动两种模式: - -
    - -- 主动模式:服务器端主动建立数据连接,其中服务器端的端口号为 20,客户端的端口号随机,但是必须大于 1024,因为 0\~1023 是熟知端口号。 -- 被动模式:客户端主动建立数据连接,其中客户端的端口号由客户端自己指定,服务器端的端口号随机。 - -主动模式要求客户端开放端口号给服务器端,需要去配置客户端的防火墙。被动模式只需要服务器端开放端口号即可,无需客户端配置防火墙。但是被动模式会导致服务器端的安全性减弱,因为开放了过多的端口号。 - -FTPS 是一种对常用的文件传输协议(FTP)添加传输层安全(TLS)和安全套接层(SSL)加密协议支持的扩展协议。 - -## DHCP - -动态主机配置协议 DHCP (Dynamic Host Configuration Protocol) 提供了即插即用的连网方式,用户不再需要去手动配置 IP 地址等信息。 - -DHCP 配置的内容不仅是 IP 地址,还包括子网掩码、网关 IP 地址。 - -DHCP 工作过程如下: - -1. 客户端发送 Discover 报文,该报文的目的地址为 255.255.255.255:67,源地址为 0.0.0.0:68,被放入 UDP 中,该报文被广播到同一个子网的所有主机上。如果客户端和 DHCP 服务器不在同一个子网,就需要使用中继代理。 -2. DHCP 服务器收到 Discover 报文之后,发送 Offer 报文给客户端,该报文包含了客户端所需要的信息。因为客户端可能收到多个 DHCP 服务器提供的信息,因此客户端需要进行选择。 -3. 如果客户端选择了某个 DHCP 服务器提供的信息,那么就发送 Request 报文给该 DHCP 服务器。 -4. DHCP 服务器发送 Ack 报文,表示客户端此时可以使用提供给它的信息。 - -
    - -## TELNET - -远程登录协议 TELNET 用于登录到远程主机上,并且远程主机上的输出也会返回。 - -TELNET 可以适应许多计算机和操作系统的差异,例如不同操作系统系统的换行符定义。 - -## 电子邮件协议 - -一个电子邮件系统由三部分组成:用户代理、邮件服务器以及邮件协议。 - -邮件协议包含发送协议和读取协议,发送协议常用 SMTP,读取协议常用 POP3 和 IMAP。 - -### 1. SMTP - -SMTP 只能发送 ASCII 码,而互联网邮件扩充 MIME 可以发送二进制文件。MIME 并没有改动或者取代 SMTP,而是增加邮件主体的结构,定义了非 ASCII 码的编码规则。 - -### 2. POP3 - -POP3 的特点是只要用户从服务器上读取了邮件,就把该邮件删除。 - -### 3. IMAP - -IMAP 协议中客户端和服务器上的邮件保持同步,如果不手动删除邮件,那么服务器上的邮件也不会被删除。IMAP 这种做法可以让用户随时随地去访问服务器上的邮件。 - -## 常用端口 - -| 应用 | 应用层协议 | 端口号 | 传输层协议 | 备注 | -| :--------------: | :--------: | :-----: | :--------: | :-------------------------: | -| 域名解析 | DNS | 53 | UDP/TCP | 长度超过 512 字节时使用 TCP | -| 动态主机配置协议 | DHCP | 67/68 | UDP | | -| 简单网络管理协议 | SNMP | 161/162 | UDP | | -| 文件传送协议 | FTP | 20/21 | TCP | 控制连接 21,数据连接 20 | -| 远程终端协议 | TELNET | 23 | TCP | | -| 超文本传送协议 | HTTP | 80 | TCP | | -| 简单邮件传送协议 | SMTP | 25 | TCP | | -| 邮件读取协议 | POP3 | 110 | TCP | | -| 网际报文存取协议 | IMAP | 143 | TCP | | - -## Web 页面请求过程 - -### 1. DHCP 配置主机信息 - -- 假设主机最开始没有 IP 地址以及其它信息,那么就需要先使用 DHCP 来获取。 -- 主机生成一个 DHCP 请求报文,并将这个报文放入具有目的端口 67 和源端口 68 的 UDP 报文段中。 -- 该报文段则被放入在一个具有广播 IP 目的地址(255.255.255.255) 和源 IP 地址(0.0.0.0)的 IP 数据报中。 -- 该数据报则被放置在 MAC 帧中,该帧具有目的地址 FF:FF:FF:FF:FF:FF,将广播到与交换机连接的所有设备。 -- 连接在交换机的 DHCP 服务器收到广播帧之后,不断地向上分解得到 IP 数据报、UDP 报文段、DHCP 请求报文,之后生成 DHCP ACK 报文,该报文包含以下信息:IP 地址、DNS 服务器的 IP 地址、默认网关路由器的 IP 地址和子网掩码。该报文被放入 UDP 报文段中,UDP 报文段有被放入 IP 数据报中,最后放入 MAC 帧中。 -- 该帧的目的地址是请求主机的 MAC 地址,因为交换机具有自学习能力,之前主机发送了广播帧之后就记录了 MAC 地址到其转发接口的交换表项,因此现在交换机就可以直接知道应该向哪个接口发送该帧。 -- 主机收到该帧后,不断分解得到 DHCP 报文。之后就配置它的 IP 地址、子网掩码和 DNS 服务器的 IP 地址,并在其 IP 转发表中安装默认网关。 - -### 2. ARP 解析 MAC 地址 - -- 主机通过浏览器生成一个 TCP 套接字,套接字向 HTTP 服务器发送 HTTP 请求。为了生成该套接字,主机需要知道网站的域名对应的 IP 地址。 -- 主机生成一个 DNS 查询报文,该报文具有 53 号端口,因为 DNS 服务器的端口号是 53。 -- 该 DNS 查询报文被放入目的地址为 DNS 服务器 IP 地址的 IP 数据报中。 -- 该 IP 数据报被放入一个以太网帧中,该帧将发送到网关路由器。 -- DHCP 过程只知道网关路由器的 IP 地址,为了获取网关路由器的 MAC 地址,需要使用 ARP 协议。 -- 主机生成一个包含目的地址为网关路由器 IP 地址的 ARP 查询报文,将该 ARP 查询报文放入一个具有广播目的地址(FF:FF:FF:FF:FF:FF)的以太网帧中,并向交换机发送该以太网帧,交换机将该帧转发给所有的连接设备,包括网关路由器。 -- 网关路由器接收到该帧后,不断向上分解得到 ARP 报文,发现其中的 IP 地址与其接口的 IP 地址匹配,因此就发送一个 ARP 回答报文,包含了它的 MAC 地址,发回给主机。 - -### 3. DNS 解析域名 - -- 知道了网关路由器的 MAC 地址之后,就可以继续 DNS 的解析过程了。 -- 网关路由器接收到包含 DNS 查询报文的以太网帧后,抽取出 IP 数据报,并根据转发表决定该 IP 数据报应该转发的路由器。 -- 因为路由器具有内部网关协议(RIP、OSPF)和外部网关协议(BGP)这两种路由选择协议,因此路由表中已经配置了网关路由器到达 DNS 服务器的路由表项。 -- 到达 DNS 服务器之后,DNS 服务器抽取出 DNS 查询报文,并在 DNS 数据库中查找待解析的域名。 -- 找到 DNS 记录之后,发送 DNS 回答报文,将该回答报文放入 UDP 报文段中,然后放入 IP 数据报中,通过路由器反向转发回网关路由器,并经过以太网交换机到达主机。 - -### 4. HTTP 请求页面 - -- 有了 HTTP 服务器的 IP 地址之后,主机就能够生成 TCP 套接字,该套接字将用于向 Web 服务器发送 HTTP GET 报文。 -- 在生成 TCP 套接字之前,必须先与 HTTP 服务器进行三次握手来建立连接。生成一个具有目的端口 80 的 TCP SYN 报文段,并向 HTTP 服务器发送该报文段。 -- HTTP 服务器收到该报文段之后,生成 TCP SYN ACK 报文段,发回给主机。 -- 连接建立之后,浏览器生成 HTTP GET 报文,并交付给 HTTP 服务器。 -- HTTP 服务器从 TCP 套接字读取 HTTP GET 报文,生成一个 HTTP 响应报文,将 Web 页面内容放入报文主体中,发回给主机。 -- 浏览器收到 HTTP 响应报文后,抽取出 Web 页面内容,之后进行渲染,显示 Web 页面。 diff --git a/docs/network/network-data-link.md b/docs/network/network-data-link.md deleted file mode 100644 index 2c2ce647..00000000 --- a/docs/network/network-data-link.md +++ /dev/null @@ -1,152 +0,0 @@ -# 计算机网络之数据链路层 - -> **数据链路层(Data Link Layer)** - 网络层针对的还是主机之间的数据传输服务,而主机之间可以有很多链路,链路层协议就是为同一链路的主机提供数据传输服务。数据链路层把网络层传下来的分组封装成帧。 -> -> - 主要协议:`PPP`、`CSMA/CD` 等。 -> - 数据单元:帧(frame)。 -> - 典型设备:二层交换机、网桥、网卡。 - - - -- [简介](#简介) -- [基本问题](#基本问题) - - [封装成帧](#封装成帧) - - [透明传输](#透明传输) - - [差错检测](#差错检测) -- [点对点信道](#点对点信道) - - [PPP 协议](#ppp-协议) -- [广播信道](#广播信道) - - [CSMA/CD 协议](#csmacd-协议) -- [局域网](#局域网) -- [以太网](#以太网) -- [MAC 地址](#mac-地址) -- [设备](#设备) - - [适配器](#适配器) - - [集线器](#集线器) - - [网桥](#网桥) - - [以太网交换机](#以太网交换机) - - - -## 简介 - -- 链路是从一个节点到相邻节点的一段物理线路,数据链路则是在链路的基础上增加了一些必要的硬件(网络适配器)和软件(协议)。 -- 数据链路层三个基本问题:封装成帧、透明传输、差错检测。 -- 数据链路层有两种信道类型:点对点信道(主要使用 `PPP`)和广播信道(主要使用 `CSMA/CD`)。 -- 以太网 MAC 层的地址。 -- 适配器、转发器、集线器、网桥、以太网交换机的作用及使用场合。 - -## 基本问题 - -### 封装成帧 - -为网络层传下来的 IP 数据报添加首部和尾部,用于标记帧的开始和结束。 - -为了提高传输效率,应该让数据部分长度尽可能大于首部和尾部。但是,每种链路层协议都限制了帧的数据部分长度上线——最大传送单元 MTU(Maximum Transfer Unit) - -
    - -### 透明传输 - -**透明**表示:某一个实际存在的事物看起来好像不存在一样。 - -帧使用首部和尾部进行定界,如果帧的数据部分含有和首部尾部相同的内容,那么帧的开始和结束位置就会被错误的判定。需要在数据部分出现首部尾部相同的内容前面插入转义字符。如果数据部分出现转义字符,那么就在转义字符前面再加个转义字符。在接收端进行处理之后可以还原出原始数据。这个过程透明传输的内容是转义字符,用户察觉不到转义字符的存在。 - -
    - -### 差错检测 - -目前数据链路层广泛使用了[**循环冗余检验 CRC(Cyclic redundancy check)**](https://zh.wikipedia.org/wiki/循环冗余检验)来检查比特差错。 - -## 点对点信道 - -点对点信道使用一对一的点对点通信方式。 - -对于点对点的链路,点对点协议 PPP(Point-to-Point Protocol)是使用最广泛的数据链路层协议。 - -### PPP 协议 - -互联网用户通常都要连接到某个 ISP 之后才能接入到互联网,PPP 协议是用户计算机和 ISP 进行通信时所使用的数据链路层协议。 - -
    - -PPP(点到点协议)是为在同等单元之间传输数据包这样的简单链路设计的链路层协议。这种链路提供全双工操作,并按照顺序传递数据包。设计目的主要是用来通过拨号或专线方式建立点对点连接发送数据,使其成为各种主机、网桥和路由器之间简单连接的一种共通的解决方案。 - -
    - -PPP 的帧格式: - -- F 字段为帧的定界符 -- A 和 C 字段暂时没有意义 -- FCS 字段是使用 CRC 的检验序列 -- 信息部分的长度不超过 1500 - -## 广播信道 - -广播信道(broadcast channel)是通过广播的方式传输信息的信息通道。 - -所有的节点都在同一个广播信道上发送数据,因此需要有专门的控制方法进行协调,避免发生冲突(冲突也叫碰撞)。 - -主要有两种控制方法进行协调,一个是使用信道复用技术,一是使用 CSMA/CD 协议。 - -### CSMA/CD 协议 - -CSMA/CD(Carrier Sense Multiple Access with Collision Detection)即带冲突检测的载波监听多路访问技术(载波监听多点接入/碰撞检测)。 - -- **多点接入** :说明这是总线型网络,许多计算机以多点接入的方式连接在一根总线上。 -- **载波监听** :每个主机都必须不停地监听信道。发送前监听,如果忙则等待,如果空闲则发送。 -- **碰撞检测** :即边发送边检测。若检测到信道有干扰信号,则表示产生了碰撞,于是就要停止发送数据,计算出退避等待时间,然后使用 CSMA 方法继续尝试发送。计算退避等待时间采用的是[二进制指数退避算法](https://baike.baidu.com/item/二进制指数退避算法)。 - -## 局域网 - -局域网 LAN(Local Area Network)是指在某一区域内由多台计算机互联成的计算机组。 - -局域网的拓扑结构通常为总线型和环型。 - -局域网技术主要有:以太网、令牌环网、FDDI 网和无线局域网等。 - -## 以太网 - -以太网(Ethernet)是一种星型拓扑结构局域网。 - -以太网是目前应用最广泛的局域网。 - -以太网使用 CSMA/CD 协议。 - -## MAC 地址 - -MAC 地址(Media Access Control Address),也称为以太网地址或物理地址,它是一个用来确认网上设备位置的地址。 - -MAC 地址长度为 6 字节(48 位),用于唯一标识网络适配器(网卡)。 - -一台主机拥有多少个网络适配器就有多少个 MAC 地址。 - -
    - -## 设备 - -### 适配器 - -网络适配器一般指网卡。 - -网卡是工作在链路层的网络组件,是局域网中连接计算机和传输介质的接口,不仅能实现与局域网传输介质之间的物理连接和电信号匹配,还涉及帧的发送与接收、帧的封装与拆封、介质访问控制、数据的编码与解码以及数据缓存的功能等。 - -网卡和局域网之间的通信是通过电缆或双绞线以串行传输方式进行的。而网卡和计算机之间的通信则是通过计算机主板上的 I/O 总线以并行传输方式进行。 - -### 集线器 - -集线器(Hub)的主要功能是对接收到的信号进行再生整形放大,以扩大网络的传输距离,同时把所有节点集中在以它为中心的节点上。 - -使用集线器可以在物理层扩展以太网。 - -### 网桥 - -网桥(Bridge)是早期的两端口二层网络设备,用来连接不同网段。网桥的两个端口分别有一条独立的交换信道,不是共享一条背板总线,可隔离冲突域。网桥比集线器(Hub)性能更好,集线器上各端口都是共享同一条背板总线的。后来,网桥被具有更多端口、同时也可隔离冲突域的交换机(Switch)所取代。 - -### 以太网交换机 - -以太网交换机是基于以太网传输数据的交换机,以太网采用共享总线型传输媒体方式的局域网。以太网交换机的结构是每个端口都直接与主机相连,并且一般都工作在全双工方式。交换机能同时连通许多对端口,使每一对相互通信的主机都能像独占通信媒体那样,进行无冲突地传输数据。 - -- 以太网交换机的每个端口都直接与主机相连,并且一般都工作在全双工方式。 -- 交换机能同时连通许多对的端口,使每一对相互通信的主机都能像独占通信媒体那样,进行无冲突地传输数据。 -- 用户独占传输媒体的带宽,若一个接口到主机的带宽是 10Mbit 每秒,那么有 10 个接口的交换机的总容量是 100Mbit 每秒。这是交换机的最大优点。 diff --git a/docs/network/network-guide.md b/docs/network/network-guide.md deleted file mode 100644 index ae0c5a0f..00000000 --- a/docs/network/network-guide.md +++ /dev/null @@ -1,158 +0,0 @@ ---- -title: 计算机网络概述 -categories: ['communication'] -tags: ['communication', 'network'] -date: 2019-02-20 22:26 ---- - -# 计算机网络概述 - -> 计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。 - -
    - -## 💡 指南 - -学习之前,先看一下入门三问: - -> **一、什么是计算机网络?** -> -> 计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。 -> -> ——摘自百度百科 - -> **二、为什么学习计算机网络?** -> -> 计算机网络是计算机科学的基础课程,也是计算机专业考研必考科目,可见其重要性。作为一名程序员,了解计算机网络,对于 Web 领域,通信领域的开发有莫大的帮助。 -> -> 在浏览器中访问网页的原理是什么?Wifi 是如何工作的?防火墙是如何保障网络安全的?什么是安全证书?Cookie 和 Session 是什么东西?。。。 -> -> 如果你接触过这些技术,如果你想了解这些技术的原理,那么你就有必要学习一下计算机网络了。 - -> **三、如何学习计算机网络?** -> -> 本人有 2 年通信领域开发经验,从事通信设备上的协议开发。就我个人的学习经验来看,学习计算机网络可以分为以下阶段: -> -> - **基础阶段——一般性的了解网络协议分层及各层功能** -> - 了解计算机网络协议分层(OSI)有哪些层,分层的依据是什么(即每层的功能是什么) -> - 了解每层的主要通信设备有哪些; -> - 了解每层有哪些重要网络协议,这些协议有什么作用,基本原理是什么? -> - 了解每层的传输数据形式(如:报文、帧等) -> - **进阶阶段——系统学习计算机网络知识,将各层主要协议功能串联起来** -> - 学习 TCP/IP 详解 卷 1、卷 2、卷 3(内容详实,但文字较为晦涩,不适合初学者) -> - **专业阶段——根据业务领域,有针对性的学习** -> - 网络协议很多,而且专业性非常强。精通所有协议,几乎是不可能的,所以有必要根据自己的业务领域,有针对性的深入学习协议。如果你是做 web 开发,那么你很有必要认真学习一下 HTTP、DNS 协议;如果你是做路由器、交换机领域通信开发,那么你应该更深入学习一下 IP/TCP/UDP 协议。。。 -> - 如何深入学习协议,最好的学习方式,就是深入学习 RFC,并结合实际的协议报文去了解。 - -### 核心概念 - -- **计算机网络** - 计算机网络(computer network),通常也简称网络,是利用通信设备和线路将地理位置不同的、功能独立的多个[计算机](https://zh.wikipedia.org/wiki/%E9%9B%BB%E5%AD%90%E8%A8%88%E7%AE%97%E6%A9%9F)系统连接起来,以功能完善的网络[软件](https://zh.wikipedia.org/wiki/%E8%BD%AF%E4%BB%B6)实现网络的[硬件](https://zh.wikipedia.org/wiki/%E7%A1%AC%E4%BB%B6)、[软件](https://zh.wikipedia.org/wiki/%E8%BB%9F%E4%BB%B6)及资源[共享](https://zh.wikipedia.org/wiki/%E5%85%B1%E4%BA%AB)和[信息](https://zh.wikipedia.org/wiki/%E4%BF%A1%E6%81%AF)传递的系统。简单的说即连接两台或多台计算机进行[通信](https://zh.wikipedia.org/wiki/%E9%80%9A%E4%BF%A1)的系统。 -- **互联网** - 互联网(Internet),即网络的网络。 - -### 拓扑结构 - -计算机网络的拓扑结构可分为: - -
    - -- 网型拓扑网型网(Mesh network) -- 环型拓扑环型网(Ring network) -- 星型拓扑星型网(Star network) -- 树状拓扑树型网(Tree network) -- 总线拓扑总线网(Bus network) - -### 作用范围 - -- 广域网 WAN(Wide Area Network) -- 城域网 MAN(Metropolitan Area Network) -- 局域网 LAN(Local Area Network) -- 个人区域网 PAN(Personal Area Network) - -### 性能指标 - -- **速率** - 速率的单位是 bit/s(比特每秒)。 -- **带宽(bandwidth)** - 带宽有以下两种不同的意义。 - - 信号的带宽是指该信号所包含的各种不同频率成分所占据的频率范围。这种意义的带宽的单位是赫 (或千赫,兆赫,吉赫等)。 - - 网络的带宽表示在单位时间内从网络中的某一点到另一点所能通过的**最高数据率**。这种意义的带宽的单位是 bit/s(比特每秒)。 -- **吞吐量(throughput)** - 吞吐量表示在单位时间内通过某个网络(或信道、接口)的数据量。例如,对于一个 100 Mbit/s 的以太网,其额定速率是 100 Mbit/s。 -- **时延(delay)** - - 总时延 = 排队时延 + 处理时延 + 传输时延 + 传播时延 - -## 网络分层 - -> 计算机网络如何分层?各层的作用是什么?各层的主要协议、设备分别是什么? -> -> 这是学习计算机网络知识宏观层面必须要了解的核心点。知道了这些,对于网络的体系结构就基本上了解了。 - -
    - -计算机网络分层一般有三种划分体系:OSI 分层;五层协议分层;TCP/IP 协议分层。 - -- OSI 的七层体系结构概念清楚,理论完整,但是比较复杂且不实用,所以并不流行。 -- 五层协议分层是一种折中方案,在现实中更为流行。 - -
    - -### 物理层 - -> 物理层(Physical Layer)只接收和发送一串比特(bit)流,不考虑信息的意义和信息结构。 -> -> 扩展阅读:[计算机网络之物理层](network-physical.md) - -- 关键词:调制、解调、数字信号、模拟信号、通信媒介、信道复用 -- 数据单元:比特流。 -- 典型设备:光纤、同轴电缆、双绞线、中继器和集线器。 - -### 数据链路层 - -> 网络层针对的还是主机之间的数据传输服务,而主机之间可以有很多链路,数据链路层(Data Link Layer)就是为同一链路的主机提供数据传输服务。数据链路层把网络层传下来的分组封装成帧。 -> -> 扩展阅读:[计算机网络之数据链路层](network-data-link.md) - -- 关键词:点对点信道、广播信道、`PPP`、`CSMA/CD`、局域网、以太网、`MAC`、适配器、集线器、网桥、交换机 -- 主要协议:`PPP`、`CSMA/CD` 等。 -- 数据单元:帧(frame)。 -- 典型设备:二层交换机、网桥、网卡。 - -### 网络层 - -> 网络层(network layer)为分组交换网上的不同主机提供通信服务。在发送数据时,网络层把运输层产生的报文段或用户数据报封装成分组或包进行传送。 -> -> 扩展阅读:[计算机网络之网络层](network-network.md) - -- 关键词:`IP`、`ICMP`、`ARP`、路由 -- 主要协议:`IP`。 -- 数据单元:IP 数据报(packet)。 -- 典型设备:网关、路由器。 - -### 传输层 - -> 传输层(transport layer)为两台主机中进程间的通信提供通用的数据传输服务。 -> -> 扩展阅读:[计算机网络之网络层](network-network.md) - -- 关键词:`UDP`、`TCP`、滑动窗口、拥塞控制、三次握手 -- 主要协议:`TCP`、`UDP`。 -- 数据单元:报文段(segment)或用户数据报。 - -### \~~会话层\~~ - -> \~~会话层(Session Layer)不参与具体的传输,它提供包括访问验证和会话管理在内的建立和维护应用之间通信的机制。\~~ - -### \~~表示层\~~ - -> \~~表示层(Presentation Layer)是为在应用过程之间传送的信息提供表示方法的服务,它关心的只是发出信息的语法与语义。表示层要完成某些特定的功能,主要有不同数据编码格式的转换,提供数据压缩、解压缩服务,对数据进行加密、解密。\~~ - -### 应用层 - -> 应用层(application layer)通过应用进程间的交互来完成特定网络应用。应用层协议定义的是应用进程间通信和交互的规则。 -> -> 扩展阅读:[计算机网络之应用层](network-application.md) - -- 关键词:`HTTP`、`DNS`、`FTP`、`TELNET`、`DHCP` -- 主要协议:`HTTP`、`DNS`、`SMTP`、`Telnet`、`FTP`、`SNMP` 等。 -- 数据单元:报文(message)。 - -## :door: 传送门 - -| [回首頁](https://github.com/dunwu/blog) | diff --git a/docs/network/network-interview.md b/docs/network/network-interview.md deleted file mode 100644 index ab602b4a..00000000 --- a/docs/network/network-interview.md +++ /dev/null @@ -1,229 +0,0 @@ -# 网络通信面经 - - - -- [1. 综合](#1-综合) - - [1.1. 计算机网络如何分层?](#11-计算机网络如何分层) -- [2. HTTP](#2-http) -- [3. DNS](#3-dns) -- [4. TCP/UDP](#4-tcpudp) - - [4.1. 什么是 TCP?](#41-什么是-tcp) - - [4.2. TCP 的特性是什么?](#42-tcp-的特性是什么) - - [4.3. TCP 三次握手](#43-tcp-三次握手) - - [4.4. TCP 四次挥手](#44-tcp-四次挥手) - - [4.5. TCP 滑动窗口](#45-tcp-滑动窗口) - - [4.6. TCP 重传机制](#46-tcp-重传机制) - - - -## 1. 综合 - -### 1.1. 计算机网络如何分层? - -> ❓ 问题:计算机网络如何分层?各层的作用是什么?各层的主要协议、设备分别是什么? -> -> 这是学习计算机网络知识宏观层面必须要了解的核心点。知道了这些,对于网络的体系结构就基本上了解了。 - -
    - -计算机网络分层一般有三种划分体系:OSI 分层;五层协议分层;TCP/IP 协议分层。 - -- OSI 的七层体系结构概念清楚,理论完整,但是比较复杂且不实用,所以并不流行。 -- 五层协议分层是一种折中方案,在现实中更为流行。 - -
    - -**物理层** - -> 物理层(Physical Layer)只接收和发送一串比特(bit)流,不考虑信息的意义和信息结构。 -> -> 扩展阅读:[计算机网络之物理层](network-physical.md) - -- 关键词:调制、解调、数字信号、模拟信号、通信媒介、信道复用 -- 数据单元:比特流。 -- 典型设备:光纤、同轴电缆、双绞线、中继器和集线器。 - -**数据链路层** - -> 网络层针对的还是主机之间的数据传输服务,而主机之间可以有很多链路,数据链路层(Data Link Layer)就是为同一链路的主机提供数据传输服务。数据链路层把网络层传下来的分组封装成帧。 -> -> 扩展阅读:[计算机网络之数据链路层](network-data-link.md) - -- 关键词:点对点信道、广播信道、`PPP`、`CSMA/CD`、局域网、以太网、`MAC`、适配器、集线器、网桥、交换机 -- 主要协议:`PPP`、`CSMA/CD` 等。 -- 数据单元:帧(frame)。 -- 典型设备:二层交换机、网桥、网卡。 - -**网络层** - -> 网络层(network layer)为分组交换网上的不同主机提供通信服务。在发送数据时,网络层把运输层产生的报文段或用户数据报封装成分组或包进行传送。 -> -> 扩展阅读:[计算机网络之网络层](network-network.md) - -- 关键词:`IP`、`ICMP`、`ARP`、路由 -- 主要协议:`IP`。 -- 数据单元:IP 数据报(packet)。 -- 典型设备:网关、路由器。 - -**传输层** - -> 传输层(transport layer)为两台主机中进程间的通信提供通用的数据传输服务。 -> -> 扩展阅读:[计算机网络之网络层](network-network.md) - -- 关键词:`UDP`、`TCP`、滑动窗口、拥塞控制、三次握手 -- 主要协议:`TCP`、`UDP`。 -- 数据单元:报文段(segment)或用户数据报。 - -~\~**会话层**\~~ - -> \~~会话层(Session Layer)不参与具体的传输,它提供包括访问验证和会话管理在内的建立和维护应用之间通信的机制。\~~ - -~\~**表示层**\~~ - -> \~~表示层(Presentation Layer)是为在应用过程之间传送的信息提供表示方法的服务,它关心的只是发出信息的语法与语义。表示层要完成某些特定的功能,主要有不同数据编码格式的转换,提供数据压缩、解压缩服务,对数据进行加密、解密。\~~ - -**应用层** - -> 应用层(application layer)通过应用进程间的交互来完成特定网络应用。应用层协议定义的是应用进程间通信和交互的规则。 -> -> 扩展阅读:[计算机网络之应用层](network-application.md) - -- 关键词:`HTTP`、`DNS`、`FTP`、`TELNET`、`DHCP` -- 主要协议:`HTTP`、`DNS`、`SMTP`、`Telnet`、`FTP`、`SNMP` 等。 -- 数据单元:报文(message)。 - -## 2. HTTP - -> 扩展阅读:[超文本传输协议 HTTP](http.md) - -## 3. DNS - -> 扩展阅读:[域名系统 DNS](dns.md) - -## 4. TCP/UDP - -> 扩展阅读:[传输控制协议 TCP](tcp.md),[用户数据报协议 UDP](udp.md) - -### 4.1. 什么是 TCP? - -**TCP(Transmission Control Protocol),即传输控制协议,它是一种`面向连接的`、`可靠的`、`基于字节流的`传输层通信协议**。 - -### 4.2. TCP 的特性是什么? - -- `面向连接的` - 面向连接是指 TCP 需要通过三次握手、四次挥手原则建立和断开双向连接。 -- `可靠的` - 可靠是指 TCP 传输的数据包保证以原始顺序到达目的地,且数据包不被损坏。为了实现这点,TCP 通过以下技术来保证: - - 数据包的序列号和校验码 - - 确认包和自动重传 - - 如果发送者没有收到正确的响应,它将重新发送数据包。如果多次超时,连接就会断开。 - - TCP 实行流量控制和拥塞控制。这些确保措施会导致延迟,而且通常导致传输效率比 UDP 低。 -- `基于字节流的` - - 虽然应用程序和 TCP 的交互是一次一个数据块(大小不等),但 TCP 把应用程序看成是一连串的无结构的字节流。TCP 有一个缓冲,当应用程序传送的数据块太长,TCP 就可以把它划分短一些再传送。如果应用程序一次只发送一个字节,TCP 也可以等待积累有足够多的字节后再构成报文段发送出去。 - - 在 TCP 建立连接前两次握手的 SYN 报文中选项字段的 MSS 值,通信双方商定通信的最大报文长度。如果应用层交付下来的数据过大,就会对数据分段,然后发送;否则通过滑动窗口来控制通信双发的数据。 - -### 4.3. TCP 三次握手 - -> ❓ 问题:三次握手有什么用?什么是三次握手?为什么需要三次握手? - -(1)三次握手有什么用? - -- 三次握手负责建立 TCP 双向连接。 - -(2)什么是三次握手? - -
    - -如上图所示,三次握手流程如下: - -1. 第一次握手 - 客户端向服务端发送带有 SYN 标志的数据包。 -2. 第二次握手 - 服务端向客户端发送带有 SYN/ACK 标志的数据包。 -3. 第三次握手 - 客户端向服务端发送带有带有 ACK 标志的数据包。 - -至此,TCP 三次握手完成,客户端与服务端已建立双向连接。 - -> 💡 说明:SYN 为 synchronize 的缩写,ACK 为 acknowledgment 的缩写。 - -(3)为什么需要三次握手? - -为了便于说明,假设客户端为 A, 服务端为 B。 - -1. 第一次握手,A 向 B 发同步消息。B 收到消息后,B 认为:A 发消息没问题;B 收消息没问题。 -2. 第二次握手,B 向 A 发同步消息和确认消息。A 收到消息后,A 认为:A 发消息、收消息都没问题;B 发消息、收消息都没问题。**但是,此时 B 不确定自己发消息是否没问题**,所以就需要第三次握手。 -3. 第三次握手,A 向 B 发确认消息。B 收到消息后。B 认为:B 发消息没问题。 - -### 4.4. TCP 四次挥手 - -> ❓ 问题:四次挥手有什么用?什么是四次挥手?为什么建立连接是三次握手,关闭连接确是四次挥手呢? - -(1)四次挥手有什么用? - -- 四次挥手负责断开 TCP 连接。 - -(2)什么是四次挥手? - -如上图所示,四次挥手流程如下: - -
    - -1. 第一次挥手 - 客户端向服务端发送一个 FIN 包,用来关闭客户端到服务端的数据传送。 -2. 第二次挥手 - 服务端收到这个 FIN 包,向客户端发送一个 ACK 包,确认序号为收到的序号加 1。和 SYN 一样,一个 FIN 将占用一个序号。 -3. 第三次挥手 - 服务端关闭与客户端的连接,向客户端发送一个 FIN 包。 -4. 第四次挥手 - 客户端向服务端发送 ACK 包,并将确认序号设置为收到序号加 1。 - -(3)为什么建立连接是三次握手,关闭连接确是四次挥手呢? - -- 建立连接的时候, 服务器在 LISTEN 状态下,收到建立连接请求的 SYN 报文后,把 ACK 和 SYN 放在一个报文里发送给客户端。 -- 而关闭连接时,服务器收到对方的 FIN 报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以己方可以立即关闭,也可以发送一些数据给对方后,再发送 FIN 报文给对方来表示同意现在关闭连接,因此,己方 ACK 和 FIN 一般都会分开发送,从而导致多了一次。 - -### 4.5. TCP 滑动窗口 - -> ❓ 问题:什么是滑动窗口?滑动窗口原理是什么? - -什么是滑动窗口? - -**滑动窗口是 TCP 的一种控制网络流量的技术。** - -**TCP 必需要解决的可靠传输以及包乱序(reordering)的问题**,所以,TCP 必需要知道网络实际的数据处理带宽或是数据处理速度,这样才不会引起网络拥塞,导致丢包。 - -TCP 头里有一个字段叫 Window,又叫 Advertised-Window,这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据。**于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来**。 - -滑动窗口原理是什么? - -
    - -1. 已发送已确认 - 数据流中最早的字节已经发送并得到确认。这些数据是站在发送端的角度来看的。上图中的 31 个字节已经发送并确认。 -2. 已发送但尚未确认 - 已发送但尚未得到确认的字节。发送方在确认之前,不认为这些数据已经被处理。上图中的 32 \~ 45 字节为第 2 类。 -3. 未发送而接收方已 Ready - 设备尚未将数据发出 ,但接收方根据最近一次关于发送方一次要发送多少字节确认自己有足够空间。发送方会立即尝试发送。上图中的 46 \~ 51 字节为第 3 类。 -4. 未发送而接收方 Not Ready - 由于接收方 not ready,还不允许将这部分数据发出。上图中的 52 以后的字节为第 4 类。 - -
    - -这张图片相对于上一张图片,滑动窗口偏移了 5 个字节,意味着有 5 个已发送的字节得到了确认。 - -### 4.6. TCP 重传机制 - -> ❓ 问题:为什么需要重传机制?TCP 有哪些重传机制,原理是什么? - -TCP 要保证所有的数据包都可以到达,所以,必需要有重传机制。 - -TCP 重传机制主要有两种: - -- 超时重传机制 -- 快速重传机制 - -(1)超时重传机制 - -超时重传机制是指:发送数据包在一定的时间周期内没有收到相应的 ACK,等待一定的时间,超时之后就认为这个数据包丢失,就会重新发送。这个等待时间被称为 RTO(Retransmission TimeOut),即重传超时时间。 - -没有确认的数据包不会从窗口中移走,定时器在重传时间到期内,每个片段的位置不变。 - -这种机制的重点是 RTO 的设置: - -- RTO 设长了,重发就慢,丢了老半天才重发,没有效率,性能差; -- RTO 设短了,会导致可能并没有丢就重发。于是重发的就快,会增加网络拥塞,导致更多的超时,更多的超时导致更多的重发 - -(2)快速重传机制 - -快速重传机制,实现了另外的一种丢包评定标准,即如果连续收到 3 次重复 ACK,发送方就认为这个 seq 的包丢失了,立刻进行重传。 - -当接收方收到乱序片段时,需要重复发送 ACK。 diff --git a/docs/network/network-network.md b/docs/network/network-network.md deleted file mode 100644 index feab661c..00000000 --- a/docs/network/network-network.md +++ /dev/null @@ -1,264 +0,0 @@ -# 计算机网络之网络层 - -> **网络层(network layer)** - 为分组交换网上的不同主机提供通信服务。在发送数据时,网络层把运输层产生的报文段或用户数据报封装成分组或包进行传送。 -> -> - 主要协议:`IP`、`ICMP`。 -> - 数据单元:IP 数据报(packet)。 -> - 典型设备:网关、路由器。 - - - -- [概述](#概述) -- [IP 协议](#ip-协议) - - [相关协议](#相关协议) - - [分类的 IP 地址](#分类的-ip-地址) - - [IP 地址与物理地址](#ip-地址与物理地址) - - [IP 数据报格式](#ip-数据报格式) -- [地址解析协议 ARP](#地址解析协议-arp) -- [网际控制报文协议 ICMP](#网际控制报文协议-icmp) - - [1. Ping](#1-ping) - - [2. Traceroute](#2-traceroute) -- [虚拟专用网 VPN](#虚拟专用网-vpn) -- [网络地址转换 NAT](#网络地址转换-nat) -- [路由器的结构](#路由器的结构) -- [路由器分组转发流程](#路由器分组转发流程) -- [路由选择协议](#路由选择协议) - - [1. 内部网关协议 RIP](#1-内部网关协议-rip) - - [2. 内部网关协议 OSPF](#2-内部网关协议-ospf) - - [3. 外部网关协议 BGP](#3-外部网关协议-bgp) - - - -## 概述 - -网络层向上只提供简单灵活的、无连接的、尽最大努力交付的数据报服务。网络层不提供服务质量的承诺,不保证分组交付的时限,所传送的分组可能出错、丢失、重复和失序。进程间通信的可靠性由运输层负责。 - -## IP 协议 - -网际协议 IP (Internet Protocol) 定义了三种功能: - -1. IP 定义了在 TCP/IP 互联网上数据传送的基本单元和数据格式。 -2. IP 软件完成路由选择功能,选择数据传送的路径。 -3. IP 包含了一组不可靠分组传送的规则,指明了分组处理、差错信息发生以及分组的规则。 - -### 相关协议 - -与 IP 协议配套使用的还有三个协议: - -- 地址解析协议 ARP(Address Resolution Protocol) -- 网际控制报文协议 ICMP(Internet Control Message Protocol) -- 网际组管理协议 IGMP(Internet Group Management Protocol) - -
    - -### 分类的 IP 地址 - -IP 地址的编址方式经历了三个历史阶段: - -- 分类 -- 子网划分 -- 无分类 - -#### 1. 分类 - -由两部分组成,网络号和主机号,其中不同分类具有不同的网络号长度,并且是固定的。 - -``` -IP 地址 ::= {< 网络号 >, < 主机号 >} -``` - -
    - -#### 2. 子网划分 - -通过在主机号字段中拿一部分作为子网号,把两级 IP 地址划分为三级 IP 地址。 - -``` -IP 地址 ::= {< 网络号 >, < 子网号 >, < 主机号 >} -``` - -要使用子网,必须配置子网掩码。一个 B 类地址的默认子网掩码为 255.255.0.0,如果 B 类地址的子网占两个比特,那么子网掩码为 11111111 11111111 11000000 00000000,也就是 255.255.192.0。 - -注意,外部网络看不到子网的存在。 - -#### 3. 无分类 - -无分类编址 CIDR 消除了传统 A 类、B 类和 C 类地址以及划分子网的概念,使用网络前缀和主机号来对 IP 地址进行编码,网络前缀的长度可以根据需要变化。 - -``` -IP 地址 ::= {< 网络前缀号 >, < 主机号 >} -``` - -CIDR 的记法上采用在 IP 地址后面加上网络前缀长度的方法,例如 128.14.35.7/20 表示前 20 位为网络前缀。 - -CIDR 的地址掩码可以继续称为子网掩码,子网掩码首 1 长度为网络前缀的长度。 - -一个 CIDR 地址块中有很多地址,一个 CIDR 表示的网络就可以表示原来的很多个网络,并且在路由表中只需要一个路由就可以代替原来的多个路由,减少了路由表项的数量。把这种通过使用网络前缀来减少路由表项的方式称为路由聚合,也称为 **构成超网** 。 - -在路由表中的项目由“网络前缀”和“下一跳地址”组成,在查找时可能会得到不止一个匹配结果,应当采用最长前缀匹配来确定应该匹配哪一个。 - -### IP 地址与物理地址 - -
    - -- 物理地址是数据链路层和物理层使用的地址。 -- IP 地址是网络层和以上各层使用的地址,是一种逻辑地址。 - -
    - -### IP 数据报格式 - -
    - -- **版本** - 有 4(IPv4)和 6(IPv6)两个值。 -- **首部长度** - 占 4 位,因此最大十进制数值为 15。值为 1 表示的是 1 个 32 位字的长度,也就是 4 字节。因为首部固定长度为 20 字节,因此该值最小为 5。如果可选字段的长度不是 4 字节的整数倍,就用尾部的填充部分来填充。 -- **区分服务** - 用来获得更好的服务,一般情况下不使用。 -- **总长度** - 包括首部长度和数据部分长度。占 16 位,因此数据报的最大长度为 2 16 - 1 = 65535 字节。 -- **生存时间** - TTL,它的存在是为了防止无法交付的数据报在互联网中不断兜圈子。以路由器跳数为单位,当 TTL 为 0 时就丢弃数据报。 -- **协议** - 指出携带的数据应该上交给哪个协议进行处理,例如 ICMP、TCP、UDP 等。 -- **首部检验和** - 因为数据报每经过一个路由器,都要重新计算检验和,因此检验和不包含数据部分可以减少计算的工作量。 -- **标识** - 在数据报长度过长从而发生分片的情况下,相同数据报的不同分片具有相同的标识符。 -- **片偏移** - 和标识符一起,用于发生分片的情况。片偏移的单位为 8 字节。 - -
    - -## 地址解析协议 ARP - -网络层实现主机之间的通信,而链路层实现具体每段链路之间的通信。因此在通信过程中,IP 数据报的源地址和目的地址始终不变,而 MAC 地址随着链路的改变而改变。 - -
    - -ARP 实现由 IP 地址得到 MAC 地址。 - -
    - -每个主机都有一个 ARP 高速缓存,里面有本局域网上的各主机和路由器的 IP 地址到 MAC 地址的映射表。 - -如果主机 A 知道主机 B 的 IP 地址,但是 ARP 高速缓存中没有该 IP 地址到 MAC 地址的映射,此时主机 A 通过广播的方式发送 ARP 请求分组,主机 B 收到该请求后会发送 ARP 响应分组给主机 A 告知其 MAC 地址,随后主机 A 向其高速缓存中写入主机 B 的 IP 地址到 MAC 地址的映射。 - -
    - -## 网际控制报文协议 ICMP - -ICMP 是为了更有效地转发 IP 数据报和提高交付成功的机会。它封装在 IP 数据报中,但是不属于高层协议。 - -
    - -ICMP 报文分为差错报告报文和询问报文。 - -
    - -### 1. Ping - -Ping 是 ICMP 的一个重要应用,主要用来测试两台主机之间的连通性。 - -Ping 的原理是通过向目的主机发送 ICMP Echo 请求报文,目的主机收到之后会发送 Echo 回答报文。Ping 会根据时间和成功响应的次数估算出数据包往返时间以及丢包率。 - -### 2. Traceroute - -Traceroute 是 ICMP 的另一个应用,用来跟踪一个分组从源点到终点的路径。 - -Traceroute 发送的 IP 数据报封装的是无法交付的 UDP 用户数据报,并由目的主机发送终点不可达差错报告报文。 - -- 源主机向目的主机发送一连串的 IP 数据报。第一个数据报 P1 的生存时间 TTL 设置为 1,当 P1 到达路径上的第一个路由器 R1 时,R1 收下它并把 TTL 减 1,此时 TTL 等于 0,R1 就把 P1 丢弃,并向源主机发送一个 ICMP 时间超过差错报告报文; -- 源主机接着发送第二个数据报 P2,并把 TTL 设置为 2。P2 先到达 R1,R1 收下后把 TTL 减 1 再转发给 R2,R2 收下后也把 TTL 减 1,由于此时 TTL 等于 0,R2 就丢弃 P2,并向源主机发送一个 ICMP 时间超过差错报文。 -- 不断执行这样的步骤,直到最后一个数据报刚刚到达目的主机,主机不转发数据报,也不把 TTL 值减 1。但是因为数据报封装的是无法交付的 UDP,因此目的主机要向源主机发送 ICMP 终点不可达差错报告报文。 -- 之后源主机知道了到达目的主机所经过的路由器 IP 地址以及到达每个路由器的往返时间。 - -## 虚拟专用网 VPN - -由于 IP 地址的紧缺,一个机构能申请到的 IP 地址数往往远小于本机构所拥有的主机数。并且一个机构并不需要把所有的主机接入到外部的互联网中,机构内的计算机可以使用仅在本机构有效的 IP 地址(专用地址)。 - -有三个专用地址块: - -- 10.0.0.0 \~ 10.255.255.255 -- 172.16.0.0 \~ 172.31.255.255 -- 192.168.0.0 \~ 192.168.255.255 - -VPN 使用公用的互联网作为本机构各专用网之间的通信载体。专用指机构内的主机只与本机构内的其它主机通信;虚拟指好像是,而实际上并不是,它有经过公用的互联网。 - -下图中,场所 A 和 B 的通信经过互联网,如果场所 A 的主机 X 要和另一个场所 B 的主机 Y 通信,IP 数据报的源地址是 10.1.0.1,目的地址是 10.2.0.3。数据报先发送到与互联网相连的路由器 R1,R1 对内部数据进行加密,然后重新加上数据报的首部,源地址是路由器 R1 的全球地址 125.1.2.3,目的地址是路由器 R2 的全球地址 194.4.5.6。路由器 R2 收到数据报后将数据部分进行解密,恢复原来的数据报,此时目的地址为 10.2.0.3,就交付给 Y。 - -
    - -## 网络地址转换 NAT - -专用网内部的主机使用本地 IP 地址又想和互联网上的主机通信时,可以使用 NAT 来将本地 IP 转换为全球 IP。 - -在以前,NAT 将本地 IP 和全球 IP 一一对应,这种方式下拥有 n 个全球 IP 地址的专用网内最多只可以同时有 n 台主机接入互联网。为了更有效地利用全球 IP 地址,现在常用的 NAT 转换表把传输层的端口号也用上了,使得多个专用网内部的主机共用一个全球 IP 地址。使用端口号的 NAT 也叫做网络地址与端口转换 NAPT。 - -## 路由器的结构 - -路由器从功能上可以划分为:路由选择和分组转发。 - -分组转发结构由三个部分组成:交换结构、一组输入端口和一组输出端口。 - -
    - -## 路由器分组转发流程 - -- 从数据报的首部提取目的主机的 IP 地址 D,得到目的网络地址 N。 -- 若 N 就是与此路由器直接相连的某个网络地址,则进行直接交付; -- 若路由表中有目的地址为 D 的特定主机路由,则把数据报传送给表中所指明的下一跳路由器; -- 若路由表中有到达网络 N 的路由,则把数据报传送给路由表中所指明的下一跳路由器; -- 若路由表中有一个默认路由,则把数据报传送给路由表中所指明的默认路由器; -- 报告转发分组出错。 - -
    - -## 路由选择协议 - -路由选择协议都是自适应的,能随着网络通信量和拓扑结构的变化而自适应地进行调整。 - -互联网可以划分为许多较小的自治系统 AS,一个 AS 可以使用一种和别的 AS 不同的路由选择协议。 - -可以把路由选择协议划分为两大类: - -- 自治系统内部的路由选择:RIP 和 OSPF -- 自治系统间的路由选择:BGP - -### 1. 内部网关协议 RIP - -RIP 是一种基于距离向量的路由选择协议。距离是指跳数,直接相连的路由器跳数为 1。跳数最多为 15,超过 15 表示不可达。 - -RIP 按固定的时间间隔仅和相邻路由器交换自己的路由表,经过若干次交换之后,所有路由器最终会知道到达本自治系统中任何一个网络的最短距离和下一跳路由器地址。 - -距离向量算法: - -- 对地址为 X 的相邻路由器发来的 RIP 报文,先修改报文中的所有项目,把下一跳字段中的地址改为 X,并把所有的距离字段加 1; -- 对修改后的 RIP 报文中的每一个项目,进行以下步骤: -- 若原来的路由表中没有目的网络 N,则把该项目添加到路由表中; -- 否则:若下一跳路由器地址是 X,则把收到的项目替换原来路由表中的项目;否则:若收到的项目中的距离 d 小于路由表中的距离,则进行更新(例如原始路由表项为 Net2, 5, P,新表项为 Net2, 4, X,则更新);否则什么也不做。 -- 若 3 分钟还没有收到相邻路由器的更新路由表,则把该相邻路由器标为不可达,即把距离置为 16。 - -RIP 协议实现简单,开销小。但是 RIP 能使用的最大距离为 15,限制了网络的规模。并且当网络出现故障时,要经过比较长的时间才能将此消息传送到所有路由器。 - -### 2. 内部网关协议 OSPF - -开放最短路径优先 OSPF,是为了克服 RIP 的缺点而开发出来的。 - -开放表示 OSPF 不受某一家厂商控制,而是公开发表的;最短路径优先表示使用了 Dijkstra 提出的最短路径算法 SPF。 - -OSPF 具有以下特点: - -- 向本自治系统中的所有路由器发送信息,这种方法是洪泛法。 -- 发送的信息就是与相邻路由器的链路状态,链路状态包括与哪些路由器相连以及链路的度量,度量用费用、距离、时延、带宽等来表示。 -- 只有当链路状态发生变化时,路由器才会发送信息。 - -所有路由器都具有全网的拓扑结构图,并且是一致的。相比于 RIP,OSPF 的更新过程收敛的很快。 - -### 3. 外部网关协议 BGP - -BGP(Border Gateway Protocol,边界网关协议) - -AS 之间的路由选择很困难,主要是由于: - -- 互联网规模很大; -- 各个 AS 内部使用不同的路由选择协议,无法准确定义路径的度量; -- AS 之间的路由选择必须考虑有关的策略,比如有些 AS 不愿意让其它 AS 经过。 - -BGP 只能寻找一条比较好的路由,而不是最佳路由。 - -每个 AS 都必须配置 BGP 发言人,通过在两个相邻 BGP 发言人之间建立 TCP 连接来交换路由信息。 - -
    diff --git a/docs/network/network-physical.md b/docs/network/network-physical.md deleted file mode 100644 index fb373b22..00000000 --- a/docs/network/network-physical.md +++ /dev/null @@ -1,94 +0,0 @@ -# 计算机网络之物理层 - -> **摘要** -> -> **物理层(Physical Layer)** - 物理层只接收和发送一串比特(bit)流,不考虑信息的意义和信息结构。 -> -> - 数据单元:比特流。 -> - 典型设备:光纤、同轴电缆、双绞线、中继器和集线器。 - - - -- [通信系统模型](#通信系统模型) -- [通信方式](#通信方式) -- [通信信号](#通信信号) -- [调制解调](#调制解调) - - [基本带通调制方法](#基本带通调制方法) -- [通信媒介](#通信媒介) -- [信道复用](#信道复用) - - - -## 通信系统模型 - -
    - -通信系统模型分为三大部分:源系统(包括信源和发送器)、传输系统、目的系统(包括信宿接收器)。 - -重要概念: - -- **信源** - 也叫源点。产生各类信息的实体。 -- **信道** - 通信的通道,是信号传输的媒介。 -- **信宿** - 传输信息的归宿。 -- **码元** - 在数字通信中常常用时间间隔相同的符号来表示一个二进制数字,这样的时间间隔内的信号称为(二进制)码元。 - -## 通信方式 - -
    - -有三种通信方式: - -- 单工通信:单向传输 -- 半双工通信:双向交替传输 -- 全双工通信:双向同时传输 - -## 通信信号 - -通信的目的是传送消息。如语音、文字、图像、视频都是消息。数据时传送消息的实体。信号是数据的电气或电磁的表现。 - -模拟信号和数字信号 - -- **模拟信号** - 模拟信号是连续的信号。 -- **数字信号** - 数字信号是离散的信号。 - -## 调制解调 - -重要概念: - -- **基带信号** - 来自信源的信号叫做基带信号。 -- **调制** - 将各种数字基带信号转换成适于信道传输的数字调制信号(已调信号或频带信号)。简单来说:调制即,数字 -> 模拟。 -- **解调** - 在接收端将收到的数字频带信号还原成数字基带信号。简单来说:解调即,模拟 -> 数字。 - -> 📌 提示:我们上网时所用到的调制解调器(俗称“猫”),指的就是转换数字和模拟信号的机器。 - -信号要在信道上传输就要经过调制。 - -调制分为:基带调制和带通调制 - -### 基本带通调制方法 - -
    - -如果你收听过广播,一定经常听到 AM、FM 这两个关键词,这是什么意思呢?答案如下: - -- **调幅(AM)** - 即载波的**振幅**随基带数字信号而变化。 -- **调频(FM)** - 即载波的**频率**随基带数字信号而变化。 -- **调相(PM)** - 即载波的**初始相位**随基带数字信号而变化。 - -> 📌 提示:我们收听广播时,为了接收不同广播台的信号,就要调整 AM 或 FM,指的就是这里的调制方法。 - -## 通信媒介 - -通信媒介分为两大类: - -- 导引型 - 双绞线、电缆、光纤 -- 非导引型 - 无线、红外线、大气、激光 - -## 信道复用 - -信道复用就是将用于传输信道的总带宽划分成若干个子频带(或称子信道),每一个子信道传输一路信号。 - -- 频分复用 -- 时分复用 -- 波分复用 -- 码分复用 diff --git a/docs/network/network-transport.md b/docs/network/network-transport.md deleted file mode 100644 index 6b0bf68f..00000000 --- a/docs/network/network-transport.md +++ /dev/null @@ -1,168 +0,0 @@ -# 计算机网络之传输层 - -> 网络层只把分组发送到目的主机,但是真正通信的并不是主机而是主机中的进程。传输层提供了进程间的逻辑通信,传输层向高层用户屏蔽了下面网络层的核心细节,使应用程序看起来像是在两个传输层实体之间有一条端到端的逻辑通信信道。 - - - -- [UDP 和 TCP 的特点](#udp-和-tcp-的特点) -- [UDP 首部格式](#udp-首部格式) -- [TCP 首部格式](#tcp-首部格式) -- [TCP 的三次握手](#tcp-的三次握手) -- [TCP 的四次挥手](#tcp-的四次挥手) -- [TCP 可靠传输](#tcp-可靠传输) -- [TCP 滑动窗口](#tcp-滑动窗口) -- [TCP 流量控制](#tcp-流量控制) -- [TCP 拥塞控制](#tcp-拥塞控制) - - [1. 慢开始与拥塞避免](#1-慢开始与拥塞避免) - - [2. 快重传与快恢复](#2-快重传与快恢复) - - - -## UDP 和 TCP 的特点 - -- 用户数据报协议 UDP(User Datagram Protocol)是无连接的,尽最大可能交付,没有拥塞控制,面向报文(对于应用程序传下来的报文不合并也不拆分,只是添加 UDP 首部),支持一对一、一对多、多对一和多对多的交互通信。 - -- 传输控制协议 TCP(Transmission Control Protocol)是面向连接的,提供可靠交付,有流量控制,拥塞控制,提供全双工通信,面向字节流(把应用层传下来的报文看成字节流,把字节流组织成大小不等的数据块),每一条 TCP 连接只能是点对点的(一对一)。 - -## UDP 首部格式 - -
    - -首部字段只有 8 个字节,包括源端口、目的端口、长度、检验和。12 字节的伪首部是为了计算检验和临时添加的。 - -## TCP 首部格式 - -
    - -- **序号** :用于对字节流进行编号,例如序号为 301,表示第一个字节的编号为 301,如果携带的数据长度为 100 字节,那么下一个报文段的序号应为 401。 - -- **确认号** :期望收到的下一个报文段的序号。例如 B 正确收到 A 发送来的一个报文段,序号为 501,携带的数据长度为 200 字节,因此 B 期望下一个报文段的序号为 701,B 发送给 A 的确认报文段中确认号就为 701。 - -- **数据偏移** :指的是数据部分距离报文段起始处的偏移量,实际上指的是首部的长度。 - -- **确认 ACK** :当 ACK=1 时确认号字段有效,否则无效。TCP 规定,在连接建立后所有传送的报文段都必须把 ACK 置 1。 - -- **同步 SYN** :在连接建立时用来同步序号。当 SYN=1,ACK=0 时表示这是一个连接请求报文段。若对方同意建立连接,则响应报文中 SYN=1,ACK=1。 - -- **终止 FIN** :用来释放一个连接,当 FIN=1 时,表示此报文段的发送方的数据已发送完毕,并要求释放连接。 - -- **窗口** :窗口值作为接收方让发送方设置其发送窗口的依据。之所以要有这个限制,是因为接收方的数据缓存空间是有限的。 - -## TCP 的三次握手 - -
    - -假设 A 为客户端,B 为服务器端。 - -- 首先 B 处于 LISTEN(监听)状态,等待客户的连接请求。 - -- A 向 B 发送连接请求报文,SYN=1,ACK=0,选择一个初始的序号 x。 - -- B 收到连接请求报文,如果同意建立连接,则向 A 发送连接确认报文,SYN=1,ACK=1,确认号为 x+1,同时也选择一个初始的序号 y。 - -- A 收到 B 的连接确认报文后,还要向 B 发出确认,确认号为 y+1,序号为 x+1。 - -- B 收到 A 的确认后,连接建立。 - -**三次握手的原因** - -第三次握手是为了防止失效的连接请求到达服务器,让服务器错误打开连接。 - -客户端发送的连接请求如果在网络中滞留,那么就会隔很长一段时间才能收到服务器端发回的连接确认。客户端等待一个超时重传时间之后,就会重新请求连接。但是这个滞留的连接请求最后还是会到达服务器,如果不进行三次握手,那么服务器就会打开两个连接。如果有第三次握手,客户端会忽略服务器之后发送的对滞留连接请求的连接确认,不进行第三次握手,因此就不会再次打开连接。 - -## TCP 的四次挥手 - -
    - -以下描述不讨论序号和确认号,因为序号和确认号的规则比较简单。并且不讨论 ACK,因为 ACK 在连接建立之后都为 1。 - -- A 发送连接释放报文,FIN=1。 - -- B 收到之后发出确认,此时 TCP 属于半关闭状态,B 能向 A 发送数据但是 A 不能向 B 发送数据。 - -- 当 B 不再需要连接时,发送连接释放报文,FIN=1。 - -- A 收到后发出确认,进入 TIME-WAIT 状态,等待 2 MSL(最大报文存活时间)后释放连接。 - -- B 收到 A 的确认后释放连接。 - -**四次挥手的原因** - -客户端发送了 FIN 连接释放报文之后,服务器收到了这个报文,就进入了 CLOSE-WAIT 状态。这个状态是为了让服务器端发送还未传送完毕的数据,传送完毕之后,服务器会发送 FIN 连接释放报文。 - -**TIME_WAIT** - -客户端接收到服务器端的 FIN 报文后进入此状态,此时并不是直接进入 CLOSED 状态,还需要等待一个时间计时器设置的时间 2MSL。这么做有两个理由: - -- 确保最后一个确认报文能够到达。如果 B 没收到 A 发送来的确认报文,那么就会重新发送连接释放请求报文,A 等待一段时间就是为了处理这种情况的发生。 - -- 等待一段时间是为了让本连接持续时间内所产生的所有报文都从网络中消失,使得下一个新的连接不会出现旧的连接请求报文。 - -## TCP 可靠传输 - -TCP 使用超时重传来实现可靠传输:如果一个已经发送的报文段在超时时间内没有收到确认,那么就重传这个报文段。 - -一个报文段从发送再到接收到确认所经过的时间称为往返时间 RTT,加权平均往返时间 RTTs 计算如下: - -

    - -其中,0 ≤ a < 1,RTTs 随着 a 的增加更容易受到 RTT 的影响。 - -超时时间 RTO 应该略大于 RTTs,TCP 使用的超时时间计算如下: - -

    - -其中 RTTd 为偏差的加权平均值。 - -## TCP 滑动窗口 - -
    - -窗口是缓存的一部分,用来暂时存放字节流。发送方和接收方各有一个窗口,接收方通过 TCP 报文段中的窗口字段告诉发送方自己的窗口大小,发送方根据这个值和其它信息设置自己的窗口大小。 - -发送窗口内的字节都允许被发送,接收窗口内的字节都允许被接收。如果发送窗口左部的字节已经发送并且收到了确认,那么就将发送窗口向右滑动一定距离,直到左部第一个字节不是已发送并且已确认的状态;接收窗口的滑动类似,接收窗口左部字节已经发送确认并交付主机,就向右滑动接收窗口。 - -接收窗口只会对窗口内最后一个按序到达的字节进行确认,例如接收窗口已经收到的字节为 {31, 34, 35},其中 {31} 按序到达,而 {34, 35} 就不是,因此只对字节 31 进行确认。发送方得到一个字节的确认之后,就知道这个字节之前的所有字节都已经被接收。 - -## TCP 流量控制 - -流量控制是为了控制发送方发送速率,保证接收方来得及接收。 - -接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。将窗口字段设置为 0,则发送方不能发送数据。 - -## TCP 拥塞控制 - -如果网络出现拥塞,分组将会丢失,此时发送方会继续重传,从而导致网络拥塞程度更高。因此当出现拥塞时,应当控制发送方的速率。这一点和流量控制很像,但是出发点不同。流量控制是为了让接收方能来得及接收,而拥塞控制是为了降低整个网络的拥塞程度。 - -
    - -TCP 主要通过四个算法来进行拥塞控制:慢开始、拥塞避免、快重传、快恢复。 - -发送方需要维护一个叫做拥塞窗口(cwnd)的状态变量,注意拥塞窗口与发送方窗口的区别:拥塞窗口只是一个状态变量,实际决定发送方能发送多少数据的是发送方窗口。 - -为了便于讨论,做如下假设: - -- 接收方有足够大的接收缓存,因此不会发生流量控制; -- 虽然 TCP 的窗口基于字节,但是这里设窗口的大小单位为报文段。 - -
    - -### 1. 慢开始与拥塞避免 - -发送的最初执行慢开始,令 cwnd = 1,发送方只能发送 1 个报文段;当收到确认后,将 cwnd 加倍,因此之后发送方能够发送的报文段数量为:2、4、8 ... - -注意到慢开始每个轮次都将 cwnd 加倍,这样会让 cwnd 增长速度非常快,从而使得发送方发送的速度增长速度过快,网络拥塞的可能性也就更高。设置一个慢开始门限 ssthresh,当 cwnd >= ssthresh 时,进入拥塞避免,每个轮次只将 cwnd 加 1。 - -如果出现了超时,则令 ssthresh = cwnd / 2,然后重新执行慢开始。 - -### 2. 快重传与快恢复 - -在接收方,要求每次接收到报文段都应该对最后一个已收到的有序报文段进行确认。例如已经接收到 M1 和 M2,此时收到 M4,应当发送对 M2 的确认。 - -在发送方,如果收到三个重复确认,那么可以知道下一个报文段丢失,此时执行快重传,立即重传下一个报文段。例如收到三个 M2,则 M3 丢失,立即重传 M3。 - -在这种情况下,只是丢失个别报文段,而不是网络拥塞。因此执行快恢复,令 ssthresh = cwnd / 2 ,cwnd = ssthresh,注意到此时直接进入拥塞避免。 - -慢开始和快恢复的快慢指的是 cwnd 的设定值,而不是 cwnd 的增长速率。慢开始 cwnd 设定为 1,而快恢复 cwnd 设定为 ssthresh。 - -
    diff --git a/docs/network/tcp.md b/docs/network/tcp.md deleted file mode 100644 index 50b44303..00000000 --- a/docs/network/tcp.md +++ /dev/null @@ -1,164 +0,0 @@ -# 传输控制协议 TCP - -## 简介 - -### 什么是 TCP? - -**TCP(Transmission Control Protocol),即传输控制协议,它是一种`面向连接的`、`可靠的`、`基于字节流的`传输层通信协议**。TCP 由 RFC 793 定义。 - -
    -### TCP 的特性 - -- `面向连接的` - 面向连接是指 TCP 需要通过三次握手、四次挥手原则建立和断开双向连接。 -- `可靠的` - 可靠是指 TCP 传输的数据包保证以原始顺序到达目的地,且数据包不被损坏。为了实现这点,TCP 通过以下技术来保证: - - 数据包的序列号和校验码 - - 确认包和自动重传 - - 如果发送者没有收到正确的响应,它将重新发送数据包。如果多次超时,连接就会断开。 - - TCP 实行流量控制和拥塞控制。这些确保措施会导致延迟,而且通常导致传输效率比 UDP 低。 -- `基于字节流的` - - 虽然应用程序和 TCP 的交互是一次一个数据块(大小不等),但 TCP 把应用程序看成是一连串的无结构的字节流。TCP 有一个缓冲,当应用程序传送的数据块太长,TCP 就可以把它划分短一些再传送。如果应用程序一次只发送一个字节,TCP 也可以等待积累有足够多的字节后再构成报文段发送出去。 - - 在 TCP 建立连接前两次握手的 SYN 报文中选项字段的 MSS 值,通信双方商定通信的最大报文长度。如果应用层交付下来的数据过大,就会对数据分段,然后发送;否则通过滑动窗口来控制通信双发的数据。 - -### TCP 的适用场景 - -基于以上特性,为了确保高吞吐量,Web 服务器可以保持大量的 TCP 连接,从而导致高内存使用。但要注意的是,在 Web 服务器线程间拥有大量开放连接可能开销巨大,消耗资源过多,这时可以考虑在适用情况下切换到 UDP。 - -TCP 对于需要高可靠性但时间紧迫的应用程序很有用。比如包括 Web 服务器,数据库信息,SMTP,FTP 和 SSH。 - -以下情况使用 TCP 代替 UDP: - -- 你需要数据完好无损。 -- 你想对网络吞吐量自动进行最佳评估。 - -### TCP 报文 - -
    - -报文字段不一一阐述,重点关注以下几点: - -- TCP 的包是没有 IP 地址的,那是 IP 层上的事。但是有源端口和目标端口。 -- 一个 TCP 连接需要四个元组来表示是同一个连接(src_ip, src_port, dst_ip, dst_port)准确说是五元组,还有一个是协议。但因为这里只是说 TCP 协议,所以,这里我只说四元组。 -- 注意上图中的四个非常重要的东西: - - **Sequence Number**是包的序号,**用来解决网络包乱序(reordering)问题。** - - **Acknowledgement Number**就是 ACK——用于确认收到,**用来解决不丢包的问题**。 - - **Window 又叫 Advertised-Window**,也就是著名的滑动窗口(Sliding Window),**用于解决流控的**。 - - **TCP Flag**,也就是包的类型,**主要是用于操控 TCP 的状态机的**。 - -
    - -## TCP 通信流程 - -
    - -TCP 完整的通信分为三块: - -1. 三次握手建立连接 -2. 数据传输 -3. 四次挥手端口连接 - -### 三次握手 - -(1)三次握手有什么用? - -- 三次握手负责建立 TCP 双向连接。 - -(2)什么是三次握手? - -
    - -如上图所示,三次握手流程如下: - -1. 第一次握手 - 客户端向服务端发送带有 SYN 标志的数据包。 -2. 第二次握手 - 服务端向客户端发送带有 SYN/ACK 标志的数据包。 -3. 第三次握手 - 客户端向服务端发送带有带有 ACK 标志的数据包。 - -至此,TCP 三次握手完成,客户端与服务端已建立双向连接。 - -> 💡 说明:SYN 为 synchronize 的缩写,ACK 为 acknowledgment 的缩写。 - -(3)为什么需要三次握手? - -为了便于说明,假设客户端为 A, 服务端为 B。 - -1. 第一次握手,A 向 B 发同步消息。B 收到消息后,B 认为:A 发消息没问题;B 收消息没问题。 -2. 第二次握手,B 向 A 发同步消息和确认消息。A 收到消息后,A 认为:A 发消息、收消息都没问题;B 发消息、收消息都没问题。**但是,此时 B 不确定自己发消息是否没问题**,所以就需要第三次握手。 -3. 第三次握手,A 向 B 发确认消息。B 收到消息后。B 认为:B 发消息没问题。 - -### 四次挥手 - -(1)四次挥手有什么用? - -- 四次挥手负责断开 TCP 连接。 - -(2)什么是四次挥手? - -如上图所示,四次挥手流程如下: - -
    - -1. 第一次挥手 - 客户端向服务端发送一个 FIN 包,用来关闭客户端到服务端的数据传送。 -2. 第二次挥手 - 服务端收到这个 FIN 包,向客户端发送一个 ACK 包,确认序号为收到的序号加 1。和 SYN 一样,一个 FIN 将占用一个序号。 -3. 第三次挥手 - 服务端关闭与客户端的连接,向客户端发送一个 FIN 包。 -4. 第四次挥手 - 客户端向服务端发送 ACK 包,并将确认序号设置为收到序号加 1。 - -(3)为什么建立连接是三次握手,关闭连接确是四次挥手呢? - -- 建立连接的时候, 服务器在 LISTEN 状态下,收到建立连接请求的 SYN 报文后,把 ACK 和 SYN 放在一个报文里发送给客户端。 -- 而关闭连接时,服务器收到对方的 FIN 报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以己方可以立即关闭,也可以发送一些数据给对方后,再发送 FIN 报文给对方来表示同意现在关闭连接,因此,己方 ACK 和 FIN 一般都会分开发送,从而导致多了一次。 - -## 滑动窗口 - -什么是滑动窗口? - -**滑动窗口是 TCP 的一种控制网络流量的技术。** - -**TCP 必需要解决的可靠传输以及包乱序(reordering)的问题**,所以,TCP 必需要知道网络实际的数据处理带宽或是数据处理速度,这样才不会引起网络拥塞,导致丢包。 - -TCP 头里有一个字段叫 Window,又叫 Advertised-Window,这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据。**于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来**。 - -滑动窗口原理是什么? - -
    - -1. 已发送已确认 - 数据流中最早的字节已经发送并得到确认。这些数据是站在发送端的角度来看的。上图中的 31 个字节已经发送并确认。 -2. 已发送但尚未确认 - 已发送但尚未得到确认的字节。发送方在确认之前,不认为这些数据已经被处理。上图中的 32 \~ 45 字节为第 2 类。 -3. 未发送而接收方已 Ready - 设备尚未将数据发出 ,但接收方根据最近一次关于发送方一次要发送多少字节确认自己有足够空间。发送方会立即尝试发送。上图中的 46 \~ 51 字节为第 3 类。 -4. 未发送而接收方 Not Ready - 由于接收方 not ready,还不允许将这部分数据发出。上图中的 52 以后的字节为第 4 类。 - -
    - -这张图片相对于上一张图片,滑动窗口偏移了 5 个字节,意味着有 5 个已发送的字节得到了确认。 - -## TCP 重传机制 - -TCP 要保证所有的数据包都可以到达,所以,必需要有重传机制。 - -TCP 重传机制主要有两种: - -- 超时重传机制 -- 快速重传机制 - -### 超时重传机制 - -超时重传机制是指:发送数据包在一定的时间周期内没有收到相应的 ACK,等待一定的时间,超时之后就认为这个数据包丢失,就会重新发送。这个等待时间被称为 RTO(Retransmission TimeOut),即重传超时时间。 - -没有确认的数据包不会从窗口中移走,定时器在重传时间到期内,每个片段的位置不变。 - -这种机制的重点是 RTO 的设置: - -- RTO 设长了,重发就慢,丢了老半天才重发,没有效率,性能差; -- RTO 设短了,会导致可能并没有丢就重发。于是重发的就快,会增加网络拥塞,导致更多的超时,更多的超时导致更多的重发 - -### 快速重传机制 - -快速重传机制,实现了另外的一种丢包评定标准,即如果连续收到 3 次重复 ACK,发送方就认为这个 seq 的包丢失了,立刻进行重传。 - -当接收方收到乱序片段时,需要重复发送 ACK。 - -## 参考资料 - -- [TCP 的那些事儿(上)](https://coolshell.cn/articles/11564.html) -- [TCP 的那些事儿(下)](https://coolshell.cn/articles/11609.html) -- [图解 TCP 三次握手与四次分手](https://juejin.im/post/5a7835a46fb9a063606eb801) -- [TCP 的三次握手与四次挥手(详解+动图)](https://blog.csdn.net/qzcsu/article/details/72861891) -- [TCP 详解](https://blog.csdn.net/sinat_36629696/article/details/80740678) diff --git a/docs/network/udp.md b/docs/network/udp.md deleted file mode 100644 index bb4fc50a..00000000 --- a/docs/network/udp.md +++ /dev/null @@ -1,49 +0,0 @@ -# 用户数据报协议(UDP) - -## 简介 - -
    - -UDP 是无连接的。数据报(类似于数据包)只在数据报级别有保证。数据报可能会无序的到达目的地,也有可能会遗失。UDP 不支持拥塞控制。虽然不如 TCP 那样有保证,但 UDP 通常效率更高。 - -UDP 可以通过广播将数据报发送至子网内的所有设备。这对 [DHCP](https://en.wikipedia.org/wiki/Dynamic_Host_Configuration_Protocol) 很有用,因为子网内的设备还没有分配 IP 地址,而 IP 对于 TCP 是必须的。 - -UDP 可靠性更低但适合用在网络电话、视频聊天,流媒体和实时多人游戏上。 - -以下情况使用 UDP 代替 TCP: - -- 你需要低延迟 -- 相对于数据丢失更糟的是数据延迟 -- 你想实现自己的错误校正方法 - -### UDP 特点 - -1. **无连接的**,即发送数据之前不需要建立连接,因此减少了开销和发送数据之前的时延。 -2. **不保证可靠交付**,因此主机不需要为此复杂的连接状态表 -3. **面向报文的**,意思是 UDP 对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界,在添加首部后向下交给 IP 层。 -4. **没有阻塞控制**,因此网络出现的拥塞不会使发送方的发送速率降低。 -5. **支持一对一、一对多、多对一和多对多的交互通信**,也即是提供广播和多播的功能。 -6. **首部开销小**,首部只有 8 个字节,分为四部分。 - -### UDP 应用场景 - -1. 名字转换(DNS) -2. 文件传送(TFTP) -3. 路由选择协议(RIP) -4. IP 地址配置(BOOTP,DHTP) -5. 网络管理(SNMP) -6. 远程文件服务(NFS) -7. IP 电话 -8. 流式多媒体通信 - -## UDP 报文 - -UDP 数据报分为数据字段和首部字段。 -首部字段只有 8 个字节,由四个字段组成,每个字段的长度是 2 个字节。 - -**首部各字段意义**: - -1. **源端口**:源端口号,在需要对方回信时选用,不需要时可全 0. -2. **目的端口**:目的端口号,在终点交付报文时必须要使用到。 -3. **长度**:UDP 用户数据报的长度,在只有首部的情况,其最小值是 8 。 -4. **检验和**:检测 UDP 用户数据报在传输中是否有错,有错就丢弃。 diff --git a/docs/network/websocket.md b/docs/network/websocket.md deleted file mode 100644 index 25d1c753..00000000 --- a/docs/network/websocket.md +++ /dev/null @@ -1,380 +0,0 @@ -# Websocket 应用指南 - -## 概述 - -### WebSocket 是什么? - -[WebSocket](http://websocket.org/) 是一种网络通信协议。[RFC6455](https://tools.ietf.org/html/rfc6455) 定义了它的通信标准。 - -WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。 - -### 为什么需要 WebSocket ? - -了解计算机网络协议的人,应该都知道:HTTP 协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理。 - -这种通信模型有一个弊端:HTTP 协议无法实现服务器主动向客户端发起消息。 - -这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数 Web 应用程序将通过频繁的异步 JavaScript 和 XML(AJAX)请求实现长轮询。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。 - -
    - -因此,工程师们一直在思考,有没有更好的方法。WebSocket 就是这样发明的。WebSocket 连接允许客户端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端。WebSocket 只需要建立一次连接,就可以一直保持连接状态。这相比于轮询方式的不停建立连接显然效率要大大提高。 - -
    - -### WebSocket 如何工作? - -Web 浏览器和服务器都必须实现 WebSockets 协议来建立和维护连接。由于 WebSockets 连接长期存在,与典型的 HTTP 连接不同,对服务器有重要的影响。 - -基于多线程或多进程的服务器无法适用于 WebSockets,因为它旨在打开连接,尽可能快地处理请求,然后关闭连接。任何实际的 WebSockets 服务器端实现都需要一个异步服务器。 - -## WebSocket 客户端 - -在客户端,没有必要为 WebSockets 使用 JavaScript 库。实现 WebSockets 的 Web 浏览器将通过 WebSockets 对象公开所有必需的客户端功能(主要指支持 Html5 的浏览器)。 - -### 客户端 API - -以下 API 用于创建 WebSocket 对象。 - -``` -var Socket = new WebSocket(url, [protocol] ); -``` - -以上代码中的第一个参数 url, 指定连接的 URL。第二个参数 protocol 是可选的,指定了可接受的子协议。 - -#### WebSocket 属性 - -以下是 WebSocket 对象的属性。假定我们使用了以上代码创建了 Socket 对象: - -| 属性 | 描述 | -| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Socket.readyState | 只读属性 **readyState** 表示连接状态,可以是以下值:0 - 表示连接尚未建立。1 - 表示连接已建立,可以进行通信。2 - 表示连接正在进行关闭。3 - 表示连接已经关闭或者连接不能打开。 | -| Socket.bufferedAmount | 只读属性 **bufferedAmount** 已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数。 | - -#### WebSocket 事件 - -以下是 WebSocket 对象的相关事件。假定我们使用了以上代码创建了 Socket 对象: - -| 事件 | 事件处理程序 | 描述 | -| ------- | ---------------- | -------------------------- | -| open | Socket.onopen | 连接建立时触发 | -| message | Socket.onmessage | 客户端接收服务端数据时触发 | -| error | Socket.onerror | 通信发生错误时触发 | -| close | Socket.onclose | 连接关闭时触发 | - -#### WebSocket 方法 - -以下是 WebSocket 对象的相关方法。假定我们使用了以上代码创建了 Socket 对象: - -| 方法 | 描述 | -| -------------- | ---------------- | -| Socket.send() | 使用连接发送数据 | -| Socket.close() | 关闭连接 | - -**示例** - -```js -// 初始化一个 WebSocket 对象 -var ws = new WebSocket('ws://localhost:9998/echo'); - -// 建立 web socket 连接成功触发事件 -ws.onopen = function() { - // 使用 send() 方法发送数据 - ws.send('发送数据'); - alert('数据发送中...'); -}; - -// 接收服务端数据时触发事件 -ws.onmessage = function(evt) { - var received_msg = evt.data; - alert('数据已接收...'); -}; - -// 断开 web socket 连接成功触发事件 -ws.onclose = function() { - alert('连接已关闭...'); -}; -``` - -## WebSocket 服务端 - -WebSocket 在服务端的实现非常丰富。Node.js、Java、C++、Python 等多种语言都有自己的解决方案。 - -以下,介绍我在学习 WebSocket 过程中接触过的 WebSocket 服务端解决方案。 - -### Node.js - -常用的 Node 实现有以下三种。 - -- [µWebSockets](https://github.com/uWebSockets/uWebSockets) -- [Socket.IO](http://socket.io/) -- [WebSocket-Node](https://github.com/theturtle32/WebSocket-Node) - -### Java - -Java 的 web 一般都依托于 servlet 容器。 - -我使用过的 servlet 容器有:Tomcat、Jetty、Resin。其中 Tomcat7、Jetty7 及以上版本均开始支持 WebSocket(推荐较新的版本,因为随着版本的更迭,对 WebSocket 的支持可能有变更)。 - -此外,Spring 框架对 WebSocket 也提供了支持。 - -虽然,以上应用对于 WebSocket 都有各自的实现。但是,它们都遵循[RFC6455](https://tools.ietf.org/html/rfc6455) 的通信标准,并且 Java API 统一遵循 [JSR 356 - JavaTM API for WebSocket ](http://www.jcp.org/en/jsr/detail?id=356) 规范。所以,在实际编码中,API 差异不大。 - -#### Spring - -Spring 对于 WebSocket 的支持基于下面的 jar 包: - -```xml - - org.springframework - spring-websocket - ${spring.version} - -``` - -在 Spring 实现 WebSocket 服务器大概分为以下几步: - -**创建 WebSocket 处理器** - -扩展 `TextWebSocketHandler` 或 `BinaryWebSocketHandler` ,你可以覆写指定的方法。Spring 在收到 WebSocket 事件时,会自动调用事件对应的方法。 - -```java -import org.springframework.web.socket.WebSocketHandler; -import org.springframework.web.socket.WebSocketSession; -import org.springframework.web.socket.TextMessage; - -public class MyHandler extends TextWebSocketHandler { - - @Override - public void handleTextMessage(WebSocketSession session, TextMessage message) { - // ... - } - -} -``` - -`WebSocketHandler` 源码如下,这意味着你的处理器大概可以处理哪些 WebSocket 事件: - -```java -public interface WebSocketHandler { - - /** - * 建立连接后触发的回调 - */ - void afterConnectionEstablished(WebSocketSession session) throws Exception; - - /** - * 收到消息时触发的回调 - */ - void handleMessage(WebSocketSession session, WebSocketMessage message) throws Exception; - - /** - * 传输消息出错时触发的回调 - */ - void handleTransportError(WebSocketSession session, Throwable exception) throws Exception; - - /** - * 断开连接后触发的回调 - */ - void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception; - - /** - * 是否处理分片消息 - */ - boolean supportsPartialMessages(); - -} -``` - -**配置 WebSocket** - -配置有两种方式:注解和 xml 。其作用就是将 WebSocket 处理器添加到注册中心。 - -1. 实现 `WebSocketConfigurer` - -```java -import org.springframework.web.socket.config.annotation.EnableWebSocket; -import org.springframework.web.socket.config.annotation.WebSocketConfigurer; -import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; - -@Configuration -@EnableWebSocket -public class WebSocketConfig implements WebSocketConfigurer { - - @Override - public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { - registry.addHandler(myHandler(), "/myHandler"); - } - - @Bean - public WebSocketHandler myHandler() { - return new MyHandler(); - } - -} -``` - -2. xml 方式 - -```xml - - - - - - - - - -``` - -> 更多配置细节可以参考:[Spring WebSocket 文档](https://docs.spring.io/spring/docs/4.3.12.RELEASE/spring-framework-reference/htmlsingle/#websocket) - -#### javax.websocket - -如果不想使用 Spring 框架的 WebSocket API,你也可以选择基本的 javax.websocket。 - -首先,需要引入 API jar 包。 - -``` - - - javax.websocket - javax.websocket-api - 1.0 - -``` - -如果使用嵌入式 jetty,你还需要引入它的实现包: - -```xml - - - org.eclipse.jetty.websocket - javax-websocket-server-impl - ${jetty-version} - - - - org.eclipse.jetty.websocket - javax-websocket-client-impl - ${jetty-version} - -``` - -**@ServerEndpoint** - -这个注解用来标记一个类是 WebSocket 的处理器。 - -然后,你可以在这个类中使用下面的注解来表明所修饰的方法是触发事件的回调 - -```java -// 收到消息触发事件 -@OnMessage -public void onMessage(String message, Session session) throws IOException, InterruptedException { - ... -} - -// 打开连接触发事件 -@OnOpen -public void onOpen(Session session, EndpointConfig config, @PathParam("id") String id) { - ... -} - -// 关闭连接触发事件 -@OnClose -public void onClose(Session session, CloseReason closeReason) { - ... -} - -// 传输消息错误触发事件 -@OnError -public void onError(Throwable error) { - ... -} -``` - -**ServerEndpointConfig.Configurator** - -编写完处理器,你需要扩展 ServerEndpointConfig.Configurator 类完成配置: - -```java -public class WebSocketServerConfigurator extends ServerEndpointConfig.Configurator { - @Override - public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) { - HttpSession httpSession = (HttpSession) request.getHttpSession(); - sec.getUserProperties().put(HttpSession.class.getName(), httpSession); - } -} -``` - -然后就没有然后了,就是这么简单。 - -## WebSocket 代理 - -如果把 WebSocket 的通信看成是电话连接,Nginx 的角色则像是电话接线员,负责将发起电话连接的电话转接到指定的客服。 - -Nginx 从 [1.3 版](http://nginx.com/blog/websocket-nginx/)开始正式支持 WebSocket 代理。如果你的 web 应用使用了代理服务器 Nginx,那么你还需要为 Nginx 做一些配置,使得它开启 WebSocket 代理功能。 - -以下为参考配置: - -```nginx -server { - # this section is specific to the WebSockets proxying - location /socket.io { - proxy_pass http://app_server_wsgiapp/socket.io; - proxy_redirect off; - - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_read_timeout 600; - } -} -``` - -> 更多配置细节可以参考:[Nginx 官方的 websocket 文档](http://nginx.org/en/docs/http/websocket.html) - -## FAQ - -### HTTP 和 WebSocket 有什么关系? - -Websocket 其实是一个新协议,跟 HTTP 协议基本没有关系,只是为了兼容现有浏览器的握手规范而已,也就是说它是 HTTP 协议上的一种补充。 - -### Html 和 HTTP 有什么关系? - -Html 是超文本标记语言,是一种用于创建网页的标准标记语言。它是一种技术标准。Html5 是它的最新版本。 - -Http 是一种网络通信协议。其本身和 Html 没有直接关系。 - -## 完整示例 - -如果需要完整示例代码,可以参考我的 Github 代码: - -- [Spring 对 WebSocket 支持的示例](https://github.com/dunwu/spring-notes/tree/master/codes/web/websocket) -- [嵌入式 Jetty 服务器的 WebSocket 示例](https://github.com/dunwu/javaee-notes/tree/master/codes/websocket) - -spring-websocket 和 jetty 9.3 版本似乎存在兼容性问题,Tomcat 则木有问题。 - -我尝试了好几次,没有找到解决方案,只好使用 Jetty 官方的嵌入式示例在 Jetty 中使用 WebSocket 。 - -## 资料 - -- [知乎高票答案——WebSocket 是什么原理](https://www.zhihu.com/question/20215561) - 对 WebSocket 原理的阐述简单易懂。 -- [WebSocket 教程](http://www.ruanyifeng.com/blog/2017/05/websocket.html) - 阮一峰大神的科普一如既往的浅显易懂。 -- [WebSockets](https://www.fullstackpython.com/websockets.html) - by _fullstackpython_ -- [Nginx 官方的 websocket 文档](http://nginx.org/en/docs/http/websocket.html) -- [Spring WebSocket 文档](https://docs.spring.io/spring/docs/4.3.12.RELEASE/spring-framework-reference/htmlsingle/#websocket) -- [Tomcat7 WebSocket 文档](http://tomcat.apache.org/tomcat-7.0-doc/web-socket-howto.html) -- [Jetty WebSocket 文档](https://www.eclipse.org/jetty/documentation/9.4.7.v20170914/websocket-intro.html) diff --git a/docs/sidebar.md b/docs/sidebar.md new file mode 100644 index 00000000..ac8ff22c --- /dev/null +++ b/docs/sidebar.md @@ -0,0 +1,69 @@ +- [**🎨 架构设计**](https://github.com/dunwu/blog/tree/master/source/_posts/design/architecture) + - [分布式技术面试题](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/分布式技术面试题.md) + - [分布式技术实现](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/分布式技术实现.md) + - [分布式架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/分布式架构.md) + - [大型分布式网站架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/大型分布式网站架构.md) + - [大型网站架构概述](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/大型网站架构概述.md) + - [网站典型故障](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/网站典型故障.md) + - [网站的伸缩性架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/网站的伸缩性架构.md) + - [网站的可扩展架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/网站的可扩展架构.md) + - [网站的安全架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/网站的安全架构.md) + - [网站的高可用架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/网站的高可用架构.md) + - [网站的高性能架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/网站的高性能架构.md) +- [✨ 系统原理](https://github.com/dunwu/blog/tree/master/source/_posts/design/theory) + - [系统原理面试题](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/system-theory-interview.md) + - [分布式基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/distributed-base-theory.md) + - [负载均衡基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/load-balance-theory.md) + - [缓存基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/cache-theory.md) + - [消息队列基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/mq-theory.md) + - [分布式锁基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/distributed-lock-theory.md) + - [分布式会话基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/distributed-session-theory.md) + - [分布式存储基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/distributed-storage-theory.md) + - [分布式 ID 基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/distributed-id-theory.md) + - [分布式事务基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/distributed-transaction-theory.md) + - [分库分表基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/sharding-theory.md) + - [常见安全手段基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/security-theory.md) +- [**⭐ 主流技术**](technology/README.md) + - Web 框架 + - [Spring 教程 📚](https://dunwu.github.io/spring-tutorial/) + - [Spring Boot 教程 📚](https://dunwu.github.io/spring-boot-tutorial/) + - 服务器 + - [Tomcat 应用指南](technology/server/Tomcat.md) + - [Jetty 应用指南](technology/server/Jetty.md) + - [Nginx 简易教程 📚](https://github.com/dunwu/nginx-tutorial) + - 消息队列 + - [消息队列面经](technology/mq/MqInterview.md) + - [Kafka 应用指南基础篇](technology/mq/kafka-basic.md) + - [Kafka 应用指南进阶篇](technology/mq/kafka-advance.md) + - [Kafka 运维指南](technology/mq/kafka-ops.md) + - [RocketMQ 基础篇](technology/mq/RocketmqBasics.md) + - [RocketMQ 进阶篇](technology/mq/RocketmqAdvanced.md) + - [ActiveMQ 实战篇](technology/mq/ActiveMQ.md) + - 缓存 + - [缓存面经](technology/cache/CacheInterview.md) + - [Redis](technology/cache/Redis.md) + - [Ehcache](technology/cache/Ehcache.md) + - [Caffeine](technology/cache/Caffeine.md) + - RPC + - [Dubbo 应用指南](technology/rpc/Dubbo.md) + - Spring Cloud + - 安全 + - [Shiro 应用指南](technology/security/Shiro.md) + - [Spring Security 应用指南](technology/security/SpringSecurity.md) + - 数据 + - [Mybatis 应用指南](technology/data/Mybatis.md) + - [ShardingSphere 应用指南](technology/data/ShardingSphere.md) + - 协调/监控/诊断/测试 + - 协调 + - [ZooKeeper 应用指南](technology/monitor/zookeeper.md) + - [ZooKeeper 运维指南](technology/monitor/zookeeper-ops.md) + - 诊断 + - [Arthas 应用指南](technology/monitor/arthas.md) + - 测试 + - [Jmeter 应用指南](technology/monitor/jmeter.md) +- [**☕ JavaEE**](javaee/README.md) + - [JavaEE 面经](javaee/javaee-interview.md) + - [JavaEE 之 Servlet 指南](javaee/javaee-servlet.md) + - [JavaEE 之 Jsp 指南](javaee/javaee-jsp.md) + - [JavaEE 之 Filter 和 Listener](javaee/javaee-filter-listener.md) + - [JavaEE 之 Cookie 和 Session](javaee/javaee-cookie-sesion.md) diff --git a/docs/technology/README.md b/docs/technology/README.md new file mode 100644 index 00000000..896cb376 --- /dev/null +++ b/docs/technology/README.md @@ -0,0 +1,41 @@ +# 主流技术 + +> 主流技术汇集 JavaWeb 开发常用的各种主流技术。 + +- **Web 框架** + - [Spring 教程 📚](https://dunwu.github.io/spring-tutorial/) + - [Spring Boot 教程 📚](https://dunwu.github.io/spring-boot-tutorial/) +- **服务器** + - [Tomcat 应用指南](technology/server/Tomcat.md) + - [Jetty 应用指南](technology/server/Jetty.md) + - [Nginx 简易教程 📚](https://github.com/dunwu/nginx-tutorial) +- **消息队列** + - [消息队列面经](technology/mq/MqInterview.md) + - [Kafka 应用指南基础篇](technology/mq/kafka-basic.md) + - [Kafka 应用指南进阶篇](technology/mq/kafka-advance.md) + - [Kafka 运维指南](technology/mq/kafka-ops.md) + - [RocketMQ 基础篇](technology/mq/RocketmqBasics.md) + - [RocketMQ 进阶篇](technology/mq/RocketmqAdvanced.md) + - [ActiveMQ 实战篇](technology/mq/ActiveMQ.md) +- **缓存** + - [缓存面经](technology/cache/CacheInterview.md) + - [Redis](technology/cache/Redis.md) + - [Ehcache](technology/cache/Ehcache.md) + - [Caffeine](technology/cache/Caffeine.md) +- **RPC** + - [Dubbo 应用指南](technology/rpc/Dubbo.md) + - Spring Cloud +- **安全** + - [Shiro 应用指南](technology/security/Shiro.md) + - [Spring Security 应用指南](technology/security/SpringSecurity.md) +- **数据** + - [Mybatis 应用指南](technology/data/Mybatis.md) + - [ShardingSphere 应用指南](technology/data/ShardingSphere.md) +- **协调/监控/诊断/测试** + - **协调** + - [ZooKeeper 应用指南](technology/monitor/zookeeper.md) + - [ZooKeeper 运维指南](technology/monitor/zookeeper-ops.md) + - **诊断** + - [Arthas 应用指南](technology/monitor/arthas.md) + - **测试** + - [Jmeter 应用指南](technology/monitor/jmeter.md) diff --git a/docs/technology/monitor/jmeter.md b/docs/technology/monitor/jmeter.md index 75752b65..c7958534 100644 --- a/docs/technology/monitor/jmeter.md +++ b/docs/technology/monitor/jmeter.md @@ -4,11 +4,11 @@ > > 🎁 本文编辑时的最新版本为:5.1.1 -## 简介 +## 1. 简介 [Jmeter](https://github.com/apache/jmeter) 是一款使用 Java 开发的功能和性能测试软件。 -### 特性 +### 1.1. 特性 Jmeter 能够加载和性能测试许多不同的应用程序/服务器/协议类型: @@ -23,7 +23,7 @@ Jmeter 能够加载和性能测试许多不同的应用程序/服务器/协议 - TCP 协议 - Java 对象 -### 工作流 +### 1.2. 工作流 Jmeter 的工作原理是仿真用户向服务器发送请求,并收集服务器应答信息并计算统计信息。 @@ -31,7 +31,7 @@ Jmeter 的工作流如下图所示: ![](http://dunwu.test.upcdn.net/cs/java/javaweb/technology/test/jmeter-workflow.png!zp) -### 主要元素 +### 1.3. 主要元素 Jmeter 的主要元素如下: @@ -60,9 +60,9 @@ Jmeter 的主要元素如下: > 3. 线程组中至少要有一个取样器。 > 4. 线程组中至少要有一个监听器。 -## 安装 +## 2. 安装 -### 环境要求 +### 2.1. 环境要求 - 必要的。Jmeter 基于 JDK8 开发,所以必须运行在 JDK8 环境。 @@ -73,11 +73,11 @@ Jmeter 的主要元素如下: - JMS - [Bouncy Castle](http://www.bouncycastle.org/test_releases.html) -### 下载 +### 2.2. 下载 进入 [**Jmeter 官网下载地址**](https://jmeter.apache.org/download_jmeter.cgi) 选择需要版本进行下载。 -### 启动 +### 2.3. 启动 解压 Jmeter 压缩包,进入 bin 目录 @@ -85,9 +85,9 @@ Unix 类系统运行 `jmeter` ;Windows 系统运行 `jmeter.bat` ![image-20191024104517721](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024104517721.png!zp) -## 使用 Jmeter +## 3. 使用 Jmeter -### 创建测试计划 +### 3.1. 创建测试计划 > 🔔 注意: > @@ -95,7 +95,7 @@ Unix 类系统运行 `jmeter` ;Windows 系统运行 `jmeter.bat` > > - JMeter 的测试计划以 `.jmx` 扩展文件的形式保存。 -#### 创建线程组 +#### 3.1.1. 创建线程组 - 在“测试计划”上右键 【添加】=>【线程(用户)】=>【线程组】。 @@ -103,7 +103,7 @@ Unix 类系统运行 `jmeter` ;Windows 系统运行 `jmeter.bat` ![image-20191024105545736](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024105545736.png!zp) -#### 配置原件 +#### 3.1.2. 配置原件 - 在新建的线程组上右键 【添加】=>【配置元件】=>【HTTP 请求默认值】。 @@ -111,7 +111,7 @@ Unix 类系统运行 `jmeter` ;Windows 系统运行 `jmeter.bat` ![image-20191024110016264](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024110016264.png!zp) -#### 构造 HTTP 请求 +#### 3.1.3. 构造 HTTP 请求 - 在“线程组”上右键 【添加-】=>【取样器】=>【HTTP 请求】。 @@ -121,39 +121,39 @@ Unix 类系统运行 `jmeter` ;Windows 系统运行 `jmeter.bat` ![image-20191024110953063](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024110953063.png!zp) -#### 添加 HTTP 请求头 +#### 3.1.4. 添加 HTTP 请求头 - 在“线程组”上右键 【添加】=>【配置元件】=>【HTTP 信息头管理器】 - 由于我的测试例中传输的数据为 json 形式,所以设置键值对 `Content-Type`:`application/json` ![image-20191024111825226](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024111825226.png!zp) -#### 添加断言 +#### 3.1.5. 添加断言 - 在“线程组”上右键 【添加】=>【断言】=>【 响应断言 】 - 在我的案例中,以 HTTP 应答状态码为 200 来判断请求是否成功 ![image-20191024112335130](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024112335130.png!zp) -#### 添加察看结果树 +#### 3.1.6. 添加察看结果树 - 在“线程组”上右键 【添加】=>【监听器】=>【察看结果树】 - 直接点击运行,就可以查看测试结果 ![image-20191024113849270](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024113849270.png!zp) -#### 添加汇总报告 +#### 3.1.7. 添加汇总报告 - 在“线程组”上右键 【添加】=>【监听器】=>【汇总报告】 - 直接点击运行,就可以查看测试结果 ![image-20191024114016424](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024114016424.png!zp) -#### 保存测试计划 +#### 3.1.8. 保存测试计划 执行测试计划前,GUI 会提示先保存配置为 `jmx` 文件。 -### 执行测试计划 +### 3.2. 执行测试计划 官方建议不要直接使用 GUI 来执行测试计划,这种模式指适用于创建测试计划和 debug。 @@ -167,7 +167,7 @@ jmeter -n -t [jmx file] -l [results file] -e -o [Path to web report folder] ![image-20191024120233058](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024120233058.png!zp) -## 参考资料 +## 4. 参考资料 - [Jmeter 官网](https://jmeter.apache.org/) - [Jmeter Github](https://github.com/apache/jmeter) diff --git a/docs/technology/mq/kafka-basic.md b/docs/technology/mq/kafka-basic.md index 860d1a0c..b9c45d5e 100644 --- a/docs/technology/mq/kafka-basic.md +++ b/docs/technology/mq/kafka-basic.md @@ -435,6 +435,8 @@ public void consumeMessageForIndependentConsumer(String topic){ - [Github](https://github.com/apache/kafka) - [官网](http://kafka.apache.org/) - [官方文档](https://kafka.apache.org/documentation/) +- **教程** + - [Kafka 中文文档](https://github.com/apachecn/kafka-doc-zh) - **文章** - [Kafka(03) Kafka 介绍](http://www.heartthinkdo.com/?p=2006#233) - [Kafka 剖析(一):Kafka 背景及架构介绍](http://www.infoq.com/cn/articles/kafka-analysis-part-1) diff --git a/docs/theory/cache-theory.md b/docs/theory/cache-theory.md deleted file mode 100644 index 6cfaa59d..00000000 --- a/docs/theory/cache-theory.md +++ /dev/null @@ -1,487 +0,0 @@ -# 缓存基本原理 - -## 缓存概述 - -### 什么是缓存 - -**缓存就是数据交换的缓冲区**。 - -缓存是用于存储数据的硬件或软件的组成部分,以使得后续更快访问相应的数据。缓存中的数据可能是提前计算好的结果、数据的副本等。典型的应用场景:有 cpu cache, 磁盘 cache 等。本文中提及到缓存主要是指互联网应用中所使用的缓存组件。 - -### 为什么引入缓存 - -传统的后端业务场景中,访问量以及对响应时间的要求均不高,通常只使用数据库即可满足要求。这种架构简单,便于快速部署,很多网站发展初期均考虑使用这种架构。但是随着访问量的上升,以及对响应时间的要求提升,一个数据库服务已无法再满足要求。这时候通常会考虑数据库拆分(sharding)、读写分离、甚至硬件升级(SSD)等以满足新的业务需求。但是这种方式仍然会面临很多问题,主要体现在: - -- 性能提升有限,很难达到数量级上的提升,尤其在互联网业务场景下,随着网站的发展,访问量经常会面临十倍、百倍的上涨。 -- 成本高昂,为了承载 N 倍的访问量,通常需要 N 倍的机器,这个代价难以接受。 - -在数据层引入缓存,有以下几个好处: - -- 提升数据读取速度。 -- 提升系统扩展能力,通过扩展缓存,提升系统承载能力。 -- 降低存储成本,Cache+DB 的方式可以承担原有需要多台 DB 才能承担的请求量,节省机器成本。 - -根据业务场景,通常缓存有以下几种使用方式: - -- 懒汉式(读时触发):写入 DB 后, 然后把相关的数据也写入 Cache。 -- 饥饿式(写时触发):先查询 DB 里的数据, 然后把相关的数据写入 Cache。 -- 定期刷新:适合周期性的跑数据的任务,或者列表型的数据,而且不要求绝对实时性。 - -### 缓存的基本原理 - -1. 将数据写入/读取速度更快的存储(设备); -2. 将数据缓存到离应用最近的位置; -3. 将数据缓存到离用户最近的位置。 - -### 缓存淘汰算法 - -常见的缓存淘汰算法有以下几种: - -- **FIFO** - 先进先出,在这种淘汰算法中,先进入缓存的会先被淘汰。这种可谓是最简单的了,但是会导致我们命中率很低。试想一下我们如果有个访问频率很高的数据是所有数据第一个访问的,而那些不是很高的是后面再访问的,那这样就会把我们的首个数据但是他的访问频率很高给挤出。 -- **LRU** - 最近最少使用算法。在这种算法中避免了上面的问题,每次访问数据都会将其放在我们的队尾,如果需要淘汰数据,就只需要淘汰队首即可。但是这个依然有个问题,如果有个数据在 1 个小时的前 59 分钟访问了 1 万次(可见这是个热点数据),再后一分钟没有访问这个数据,但是有其他的数据访问,就导致了我们这个热点数据被淘汰。 -- **LFU** - 最近最少频率使用。在这种算法中又对上面进行了优化,利用额外的空间记录每个数据的使用频率,然后选出频率最低进行淘汰。这样就避免了 LRU 不能处理时间段的问题。 - -这三种缓存淘汰算法,实现复杂度一个比一个高,同样的命中率也是一个比一个好。而我们一般来说选择的方案居中即可,即实现成本不是太高,而命中率也还行的 LRU。 - -### 缓存的分类 - -在分布式系统中,缓存的应用非常广泛,从部署角度有以下几个方面的缓存应用。 - -- **CDN 缓存** - 存放 HTML、CSS、JS 等静态资源。 -- **反向代理缓存** - 动静分离,只缓存用户请求的静态资源。 -- **进程内缓存** - 缓存应用字典等常用数据。 -- **分布式缓存** - 缓存数据库中的热点数据。 - -### 缓存整体架构 - -通常,网站的缓存整体架构如下图所示: - -
    - -请求过程: - -1. 浏览器向客户端发起请求,如果 CDN 有缓存则直接返回; -2. 如果 CDN 无缓存,则访问反向代理服务器; -3. 如果反向代理服务器有缓存则直接返回; -4. 如果反向代理服务器无缓存或动态请求,则访问应用服务器; -5. 应用服务器访问进程内缓存;如果有缓存,则返回代理服务器,并缓存数据;(动态请求不缓存) -6. 如果进程内缓存无数据,则读取分布式缓存;并返回应用服务器;应用服务器将数据缓存到本地缓存(部分); -7. 如果分布式缓存无数据,则应用程序读取数据库数据,并放入分布式缓存; - -## CDN 缓存 - -> **CDN 将数据缓存到离用户物理距离最近的服务器,使得用户可以就近获取请求内容。CDN 一般缓存静态资源文件(页面,脚本,图片,视频,文件等)**。 -> -> 国内网络异常复杂,跨运营商的网络访问会很慢。为了解决跨运营商或各地用户访问问题,可以在重要的城市,部署 CDN 应用。使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。 - -
    - -### CDN 原理 - -CDN 的基本原理是广泛采用各种缓存服务器,将这些缓存服务器分布到用户访问相对集中的地区或网络中,在用户访问网站时,利用全局负载技术将用户的访问指向距离最近的工作正常的缓存服务器上,由缓存服务器直接响应用户请求。 - -(1)未部署 CDN 应用前的网络路径: - -- 请求:本机网络(局域网)=> 运营商网络 => 应用服务器机房 -- 响应:应用服务器机房 => 运营商网络 => 本机网络(局域网) - -在不考虑复杂网络的情况下,从请求到响应需要经过 3 个节点,6 个步骤完成一次用户访问操作。 - -(2)部署 CDN 应用后网络路径: - -- 请求:本机网络(局域网) => 运营商网络 -- 响应:运营商网络 => 本机网络(局域网) - -在不考虑复杂网络的情况下,从请求到响应需要经过 2 个节点,2 个步骤完成一次用户访问操作。 - -与不部署 CDN 服务相比,减少了 1 个节点,4 个步骤的访问。极大的提高的系统的响应速度。 - -### CDN 特点 - -- **优点** - - **本地 Cache 加速** - 提升访问速度,尤其含有大量图片和静态页面站点; - - **镜像服务** - 消除了不同运营商之间互联的瓶颈造成的影响,实现了跨运营商的网络加速,保证不同网络中的用户都能得到良好的访问质量; - - **远程加速** - 远程访问用户根据 DNS 负载均衡技术智能自动选择 Cache 服务器,选择最快的 Cache 服务器,加快远程访问的速度; - - **带宽优化** - 自动生成服务器的远程 Mirror(镜像)cache 服务器,远程用户访问时从 cache 服务器上读取数据,减少远程访问的带宽、分担网络流量、减轻原站点 WEB 服务器负载等功能。 - - **集群抗攻击** - 广泛分布的 CDN 节点加上节点之间的智能冗余机制,可以有效地预防黑客入侵以及降低各种 D.D.o.S 攻击对网站的影响,同时保证较好的服务质量。 -- **缺点** - - **不适宜缓存动态资源** - - 解决方案:主要缓存静态资源,动态资源建立多级缓存或准实时同步; - - **存在数据的一致性问题** - - 解决方案(主要是在性能和数据一致性二者间寻找一个平衡) - - 设置缓存失效时间(1 个小时,过期后同步数据)。 - - 针对资源设置版本号。 - -## 反向代理缓存 - -> **反向代理(Reverse Proxy)方式是指以代理服务器来接受 internet 上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给 internet 上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。** - -
    - -### 缓存原理 - -反向代理位于应用服务器同一网络,处理所有对 WEB 服务器的请求。 - -反向代理缓存的原理: - -- 如果用户请求的页面在代理服务器上有缓存的话,代理服务器直接将缓存内容发送给用户。 -- 如果没有缓存则先向 WEB 服务器发出请求,取回数据,本地缓存后再发送给用户。 - -这种方式通过降低向 WEB 服务器的请求数,从而降低了 WEB 服务器的负载。 - -**反向代理缓存一般针对的是静态资源,而将动态资源请求转发到应用服务器处理**。常用的缓存应用服务器有 Varnish,Ngnix,Squid。 - -### 代理缓存比较 - -常用的代理缓存有 Varnish,Squid,Ngnix,简单比较如下: - -- Varnish 和 Squid 是专业的 cache 服务,Ngnix 需要第三方模块支持; -- Varnish 采用内存型缓存,避免了频繁在内存、磁盘中交换文件,性能比 Squid 高; -- Varnish 由于是内存 cache,所以对小文件如 css、js、小图片的支持很棒,后端的持久化缓存可以采用的是 Squid 或 ATS; -- Squid 功能全而大,适合于各种静态的文件缓存,一般会在前端挂一个 HAProxy 或 Ngnix 做负载均衡跑多个实例; -- Nginx 采用第三方模块 ncache 做的缓冲,性能基本达到 Varnish,一般作为反向代理使用,可以实现简单的缓存。 - -## 进程内缓存 - -> 进程内缓存是指应用内部的缓存,标准的分布式系统,一般有多级缓存构成。本地缓存是离应用最近的缓存,一般可以将数据缓存到硬盘或内存。 - -- `硬盘缓存` - 将数据缓存到硬盘到,读取时从硬盘读取。原理是直接读取本机文件,减少了网络传输消耗,比通过网络读取数据库速度更快。可以应用在对速度要求不是很高,但需要大量缓存存储的场景。 -- `内存缓存` - 直接将数据存储到本机内存中,通过程序直接维护缓存对象,是访问速度最快的方式。 - -常见的本地缓存实现方案:HashMap、Guava Cache、Caffeine、Ehcache。 - -### ConcurrentHashMap - -最简单的进程内缓存可以通过 JDK 自带的 `HashMap` 或 `ConcurrentHashMap` 实现。 - -适用场景:不需要淘汰的缓存数据。 - -缺点:无法进行缓存淘汰,内存会无限制的增长。 - -### LRUHashMap - -可以通过继承 `LinkedHashMap` 来实现一个简单的 `LRUHashMap`。重写 `removeEldestEntry` 方法,即可完成一个简单的最近最少使用算法。 - -- 缺点: - - 锁竞争严重,性能比较低。 - - 不支持过期时间 - - 不支持自动刷新 - -### Guava Cache - -解决了 `LRUHashMap` 中的几个缺点。 - -Guava Cache 采用了类似 `ConcurrentHashMap` 的思想,分段加锁,减少锁竞争。 - -Guava Cache 对于过期的 Entry 并没有马上过期(也就是并没有后台线程一直在扫),而是通过进行读写操作的时候进行过期处理,这样做的好处是避免后台线程扫描的时候进行全局加锁。 - -直接通过查询,判断其是否满足刷新条件,进行刷新。 - -### Caffeine - -Caffeine 实现了 W-TinyLFU(LFU+LRU 算法的变种),其命中率和读写吞吐量大大优于 Guava Cache。 - -其实现原理较复杂,可以参考[你应该知道的缓存进化史](https://juejin.im/post/5b7593496fb9a009b62904fa#comment)。 - -### 选择进程内缓存 - -| 比较项 | ConcurrentHashMap | LRUMap | Ehcache | Guava Cache | Caffeine | -| ------------ | ----------------- | ------------------------ | ----------------------------- | ----------------------------------- | ----------------------- | -| 读写性能 | 很好,分段锁 | 一般,全局加锁 | 好 | 好,需要做淘汰操作 | 很好 | -| 淘汰算法 | 无 | LRU,一般 | 支持多种淘汰算法,LRU,LFU,FIFO | LRU,一般 | W-TinyLFU, 很好 | -| 功能丰富程度 | 功能比较简单 | 功能比较单一 | 功能很丰富 | 功能很丰富,支持刷新和虚引用等 | 功能和 Guava Cache 类似 | -| 工具大小 | jdk 自带类,很小 | 基于 LinkedHashMap,较小 | 很大,最新版本 1.4MB | 是 Guava 工具类中的一个小部分,较小 | 一般,最新版本 644KB | -| 是否持久化 | 否 | 否 | 是 | 否 | 否 | -| 是否支持集群 | 否 | 否 | 是 | 否 | 否 | - -- **`ConcurrentHashMap`** - 比较适合缓存比较固定不变的元素,且缓存的数量较小的。虽然从上面表格中比起来有点逊色,但是其由于是 JDK 自带的类,在各种框架中依然有大量的使用,比如我们可以用来缓存我们反射的 Method,Field 等等;也可以缓存一些链接,防止其重复建立。在 Caffeine 中也是使用的 `ConcurrentHashMap` 来存储元素。 -- **`LRUMap`** - 如果不想引入第三方包,又想使用淘汰算法淘汰数据,可以使用这个。 -- **`Ehcache`** - 由于其 jar 包很大,较重量级。对于需要持久化和集群的一些功能的,可以选择 Ehcache。笔者没怎么使用过这个缓存,如果要选择的话,可以选择分布式缓存来替代 Ehcache。 -- **`Guava Cache`** - Guava 这个 jar 包在很多 Java 应用程序中都有大量的引入,所以很多时候其实是直接用就好了,并且其本身是轻量级的而且功能较为丰富,在不了解 Caffeine 的情况下可以选择 Guava Cache。 -- **`Caffeine`** - 其在命中率,读写性能上都比 Guava Cache 好很多,并且其 API 和 Guava cache 基本一致,甚至会多一点。在真实环境中使用 Caffeine,取得过不错的效果。 - -总结一下:如果不需要淘汰算法则选择 `ConcurrentHashMap`,如果需要淘汰算法和一些丰富的 API,推荐选择 Caffeine。 - -## 分布式缓存 - -> **分布式缓存解决了进程内缓存最大的问题:如果应用是分布式系统,节点之间无法共享彼此的进程内缓存**。 -> -> 分布式缓存的应用场景: -> -> - 缓存经过复杂计算得到的数据 -> - 缓存系统中频繁访问的热点数据,减轻数据库压力 - -不同分布式缓存的实现原理往往有比较大的差异。本文主要针对 Memcached 和 Redis 进行说明。 - -### Memcache - -> [Memcache](https://memcached.org/) 是一个高性能,分布式内存对象缓存系统,通过在内存里维护一个统一的巨大的 hash 表,它能够用来存储各种格式的数据,包括图像、视频、文件以及数据库检索的结果等。 -> -> 简单的说就是:将数据缓存到内存中,然后从内存中读取,从而大大提高读取速度。 - -#### Memcache 特性 - -- **使用物理内存作为缓存区,可独立运行在服务器上**。每个进程最大 2G,如果想缓存更多的数据,可以开辟更多的 Memcache 进程(不同端口)或者使用分布式 Memcache 进行缓存,将数据缓存到不同的物理机或者虚拟机上。 -- **使用 key-value 的方式来存储数据**。这是一种单索引的结构化数据组织形式,可使数据项查询时间复杂度为 O(1)。 -- **协议简单,基于文本行的协议**。直接通过 telnet 在 Memcached 服务器上可进行存取数据操作,简单,方便多种缓存参考此协议; -- **基于 libevent 高性能通信**。Libevent 是一套利用 C 开发的程序库,它将 BSD 系统的 kqueue,Linux 系统的 epoll 等事件处理功能封装成一个接口,与传统的 select 相比,提高了性能。 -- **分布式能力取决于 Memcache 客户端,服务器之间互不通信**。各个 Memcached 服务器之间互不通信,各自独立存取数据,不共享任何信息。服务器并不具有分布式功能,分布式部署取决于 Memcached 客户端。 -- **采用 LRU 缓存淘汰策略**。在 Memcached 内存储数据项时,可以指定它在缓存的失效时间,默认为永久。当 Memcached 服务器用完分配的内时,失效的数据被首先替换,然后也是最近未使用的数据。在 LRU 中,Memcached 使用的是一种 Lazy Expiration 策略,自己不会监控存入的 key/vlue 对是否过期,而是在获取 key 值时查看记录的时间戳,检查 key/value 对空间是否过期,这样可减轻服务器的负载。 -- **内置了一套高效的内存管理算法**。这套内存管理效率很高,而且不会造成内存碎片,但是它最大的缺点就是会导致空间浪费。当内存满后,通过 LRU 算法自动删除不使用的缓存。 -- **不支持持久化**。Memcached 没有考虑数据的容灾问题,重启服务,所有数据会丢失。 - -#### Memcache 工作原理 - -(1)内存管理 - -Memcached 利用 **slab allocation** 机制来分配和管理内存,它按照预先规定的大小,将分配的内存分割成特定长度的内存块,再把尺寸相同的内存块分成组,数据在存放时,根据键值 大小去匹配 slab 大小,找就近的 slab 存放,所以存在空间浪费现象。 - -这套内存管理效率很高,而且不会造成内存碎片,但是它最大的缺点就是会导致空间浪费。 - -(2)缓存淘汰策略 - -Memcached 的缓存淘汰策略是 **LRU** + 到期失效策略。 - -当你在 Memcached 内存储数据项时,你有可能会指定它在缓存的失效时间,默认为永久。当 Memcached 服务器用完分配的内时,失效的数据被首先替换,然后是最近未使用的数据。 - -在 LRU 中,Memcached 使用的是一种 Lazy Expiration 策略:**Memcached 不会监控存入的 key/vlue 对是否过期**,而是在获取 key 值时查看记录的时间戳,**检查 key/value 对空间是否过期**,这样可减轻服务器的负载。 - -(3)分区 - -Memcached 服务器之间彼此不通信,它的分布式能力是依赖客户端来实现。 - -具体来说,就是在客户端实现一种算法,根据 key 来计算出数据应该向哪个服务器节点读/写。 - -而这种选取集群节点的算法常见的有三种: - -- **哈希取余算法** - 使用公式:`hash(key)% N` 计算出 **哈希值** 来决定数据映射到哪一个节点。 -- **一致性哈希算法** - 可以很好的解决 **稳定性问题**,可以将所有的 **存储节点** 排列在 **首尾相接** 的 `Hash` 环上,每个 `key` 在计算 `Hash` 后会 **顺时针** 找到 **临接** 的 **存储节点** 存放。而当有节点 **加入** 或 **退出** 时,仅影响该节点在 `Hash` 环上 **顺时针相邻** 的 **后续节点**。 -- **虚拟 Hash 槽算法** - 使用 **分散度良好** 的 **哈希函数** 把所有数据 **映射** 到一个 **固定范围** 的 **整数集合** 中,整数定义为 **槽**(`slot`),这个范围一般 **远远大于** 节点数。**槽** 是集群内 **数据管理** 和 **迁移** 的 **基本单位**。采用 **大范围槽** 的主要目的是为了方便 **数据拆分** 和 **集群扩展**。每个节点会负责 **一定数量的槽**。 - -### Redis - -> Redis 是一个开源(BSD 许可)的,基于内存的,多数据结构存储系统。可以用作数据库、缓存和消息中间件。 -> -> Redis 还可以使用客户端分片来扩展写性能。内置了 复制(replication),LUA 脚本(Lua scripting),LRU 驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis 哨兵(Sentinel)和自动分区(Cluster)提供高可用性(high availability)。 - -#### Redis 特性 - -- 支持多种数据类型 - string、hash、list、set、sorted set。 -- 支持多种数据淘汰策略 - - - **volatile-lru** - 从已设置过期时间的数据集中挑选最近最少使用的数据淘汰 - - **volatile-ttl** - 从已设置过期时间的数据集中挑选将要过期的数据淘汰 - - **volatile-random** - 从已设置过期时间的数据集中任意选择数据淘汰 - - **allkeys-lru** - 从所有数据集中挑选最近最少使用的数据淘汰 - - **allkeys-random** - 从所有数据集中任意选择数据进行淘汰 - - **noeviction** - 禁止驱逐数据 - -- 提供两种持久化方式 - RDB 和 AOF -- 通过 Redis cluster 提供集群模式。 - -#### Redis 原理 - -- 缓存淘汰 - - Redis 有两种数据淘汰实现 - - 消极方式 - 访问 Redis key 时,如果发现它已经失效,则删除它 - - 积极方式 - 周期性从设置了失效时间的 key 中,根据淘汰策略,选择一部分失效的 key 进行删除。 -- 分区 - - Redis Cluster 集群包含 16384 个虚拟 Hash 槽,它通过一个高效的算法来计算 key 属于哪个 Hash 槽。 - - Redis Cluster 支持请求分发 - 节点在接到一个命令请求时,会先检测这个命令请求要处理的键所在的槽是否由自己负责,如果不是的话,节点将向客户端返回一个 MOVED 错误,MOVED 错误携带的信息可以指引客户端将请求重定向至正在负责相关槽的节点。 -- 主从复制 - - Redis 2.8 后支持异步复制。它有两种模式: - - `完整重同步(full resychronization)` - 用于初次复制。执行步骤与 `SYNC` 命令基本一致。 - - `部分重同步(partial resychronization)` - 用于断线后重复制。如果条件允许,主服务器可以将主从服务器连接断开期间执行的写命令发送给从服务器,从服务器只需接收并执行这些写命令,即可将主从服务器的数据库状态保持一致。 - - 集群中每个节点都会定期向集群中的其他节点发送 PING 消息,以此来检测对方是否在线。 - - 如果一个主节点被认为下线,则在其从节点中,根据 Raft 算法,选举出一个节点,升级为主节点。 -- 数据一致性 - - Redis 不保证强一致性,因为这会使得集群性能大大降低。 - - Redis 是通过异步复制来实现最终一致性。 - -### 选择分布式缓存 - -不同的分布式缓存功能特性和实现原理方面有很大的差异,因此他们所适应的场景也有所不同。 - -这里选取三个比较出名的分布式缓存(MemCache,Redis,Tair)来作为比较: - -| 比较项 | MemCache | Redis | Tair | -| -------- | ---------------------------- | -------------------------------------- | --------------------------------------------------------- | -| 数据结构 | 只支持简单的 Key-Value 结构 | String,Hash, List, Set, Sorted Set | String,HashMap, List,Set | -| 持久化 | 不支持 | 支持 | 支持 | -| 容量大小 | 数据纯内存,数据存储不宜过多 | 数据全内存,资源成本考量不宜超过 100GB | 可以配置全内存或内存+磁盘引擎,数据容量可无限扩充 | -| 读写性能 | 很高 | 很高(RT0.5ms 左右) | String 类型比较高(RT1ms 左右),复杂类型比较慢(RT5ms 左右) | -| 过期策略 | 过期后,不删除缓存 | 有六种策略来处理过期数据 | 支持 | - -- `MemCache` - 这一块接触得比较少,不做过多的推荐。其吞吐量较大,但是支持的数据结构较少,并且不支持持久化。 -- `Redis` - 支持丰富的数据结构,读写性能很高,但是数据全内存,必须要考虑资源成本,支持持久化。 -- `Tair` - 支持丰富的数据结构,读写性能较高,部分类型比较慢,理论上容量可以无限扩充。 - -总结:如果服务对延迟比较敏感,Map/Set 数据也比较多的话,比较适合 Redis。如果服务需要放入缓存量的数据很大,对延迟又不是特别敏感的话,那就可以选择 Tair。 - -## 多级缓存 - -### 使用进程内缓存 - -如果应用服务不是分布式系统,那么进程内缓存当然是缓存的首选方案。 - -对于进程内缓存,其本来受限于内存的大小的限制,以及进程缓存更新后其他缓存无法得知,所以一般来说进程缓存适用于: - -1. 数据量不是很大且更新频率较低的数据。 -2. 如果更新频繁的数据,也想使用进程内缓存,那么可以将其过期时间设置为较短的时间,或者设置较短的自动刷新时间。 - -这种方案存在以下问题: - -- 如果应用服务是分布式系统,应用节点之间无法共享缓存,存在数据不一致问题。 -- 由于进程内缓存受限于内存大小的限制,所以缓存不能无限扩展。 - -### 使用分布式缓存 - -如果应用服务是分布式系统,那么最简单的缓存方案就是直接使用分布式缓存。 - -其应用场景如图所示: - -
    - -Redis 用来存储热点数据,如果缓存不命中,则去查询数据库,并更新缓存。 - -这种方案存在以下问题: - -1. 缓存服务如果挂了,这时应用只能访问数据库,容易造成缓存雪崩。 -2. 访问分布式缓存服务会有一定的 I/O 以及序列化反序列化的开销,虽然性能很高,但是其终究没有在内存中查询快。 - -### 使用多级缓存 - -单纯使用进程内缓存和分布式缓存都存在各自的不足。如果需要更高的性能以及更好的可用性,我们可以将缓存设计为多级结构。将最热的数据使用进程内缓存存储在内存中,进一步提升访问速度。 - -这个设计思路在计算机系统中也存在,比如 CPU 使用 L1、L2、L3 多级缓存,用来减少对内存的直接访问,从而加快访问速度。 - -一般来说,多级缓存架构使用二级缓存已可以满足大部分业务需求,过多的分级会增加系统的复杂度以及维护的成本。因此,多级缓存不是分级越多越好,需要根据实际情况进行权衡。 - -一个典型的二级缓存架构,可以使用进程内缓存(如: Caffeine/Google Guava/Ehcache/HashMap)作为一级缓存;使用分布式缓存(如:Redis/Memcached)作为二级缓存。 - -#### 多级缓存查询 - -
    - -多级缓存查询流程如下: - -1. 首先,查询 L1 缓存,如果缓存命中,直接返回结果;如果没有命中,执行下一步。 -2. 接下来,查询 L2 缓存,如果缓存命中,直接返回结果并回填 L1 缓存;如果没有命中,执行下一步。 -3. 最后,查询数据库,返回结果并依次回填 L2 缓存、L1 缓存。 - -#### 多级缓存更新 - -对于 L1 缓存,如果有数据更新,只能删除并更新所在机器上的缓存,其他机器只能通过超时机制来刷新缓存。超时设定可以有两种策略: - -- 设置成写入后多少时间后过期 -- 设置成写入后多少时间刷新 - -对于 L1 缓存,如果有数据更新,其他机器立马可见。但是,也必须要设置超时时间,其时间应该比 L1 缓存的有效时间长。 - -为了解决进程内缓存不一致的问题,设计可以进一步优化: - -
    - -通过消息队列的发布、订阅机制,可以通知其他应用节点对进程内缓存进行更新。使用这种方案,即使消息队列服务挂了或不可靠,由于先执行了数据库更新,但进程内缓存过期,刷新缓存时,也能保证数据的最终一致性。 - -## 缓存问题 - -### 缓存雪崩 - -> **缓存雪崩是指缓存不可用或者大量缓存由于超时时间相同在同一时间段失效,大量请求直接访问数据库,数据库压力过大导致系统雪崩**。 - -解决方案: - -- 增加缓存系统可用性。通过监控关注缓存的健康程度,根据业务量适当的扩容缓存。 -- 采用多级缓存。不同级别缓存设置的超时时间不同,即使某个级别缓存都过期,也有其他级别缓存兜底。 -- 缓存的过期时间可以取个随机值。比如以前是设置 10 分钟的超时时间,那每个 Key 都可以随机 8-13 分钟过期,尽量让不同 Key 的过期时间不同。 -- 对数据库进行过载保护或应用层限流。 - -### 缓存穿透 - -> **缓存穿透是指:查询的数据在数据库中不存在,那么缓存中自然也不存在。所以,应用在缓存中查不到,则会去查询数据库。当这样的请求多了后,数据库的压力就会增大。** - -解决缓存穿透,一般有两种方法: - -- **对于返回为 NULL 的依然缓存,对于抛出异常的返回不进行缓存**。 - -
    - -采用这种手段的会增加我们缓存的维护成本,需要在插入缓存的时候删除这个空缓存,当然我们可以通过设置较短的超时时间来解决这个问题。 - -- **过滤不可能存在的数据** - -
    - -**制定一些规则过滤一些不可能存在的数据**。小数据用 BitMap,大数据可以用布隆过滤器,比如你的订单 ID 明显是在一个范围 1-1000,如果不是 1-1000 之内的数据那其实可以直接给过滤掉。 - -### 缓存击穿 - -对于某些 key 设置了过期时间,但是其是热点数据,如果某个 key 失效,可能大量的请求打过来,缓存未命中,然后去数据库访问,此时数据库访问量会急剧增加。 - -为了避免这个问题,我们可以采取下面的两个手段: - -- **分布式锁** - 锁住热点数据的 key,避免大量线程同时访问同一个 key。 -- **异步加载** - 可以对部分数据采取到期自动刷新的策略,而不是到期自动淘汰。淘汰其实也是为了数据的时效性,所以采用自动刷新也可以。 - -### 缓存更新 - -一般来说缓存的更新有两种情况: - -- 先删除缓存,再更新数据库。 -- 先更新数据库,再删除缓存。 - -> **为什么是删除缓存,而不是更新缓存呢?** -> -> 你可以想想当有多个并发的请求更新数据,你并不能保证更新数据库的顺序和更新缓存的顺序一致,那就会出现数据库中和缓存中数据不一致的情况。所以一般来说考虑删除缓存。 - -- **先删除缓存,再更新数据库** - -对于一个更新操作简单来说,就是先去各级缓存进行删除,然后更新数据库。 - -这个操作有一个比较大的问题,在对缓存删除完之后,有一个读请求,这个时候由于缓存被删除所以直接会读库,读操作的数据是老的并且会被加载进入缓存当中,后续读请求全部访问的老数据。 - -
    - -对缓存的操作不论成功失败都不能阻塞我们对数据库的操作,那么很多时候删除缓存可以用异步的操作,但是先删除缓存不能很好的适用于这个场景。 - -先删除缓存也有一个好处是,如果对数据库操作失败了,那么由于先删除的缓存,最多只是造成 Cache Miss。 - -- **先更新数据库,再删除缓存** - -> 注:更推荐使用这种策略 - -如果我们使用更新数据库,再删除缓存就能避免上面的问题。 - -但是同样的引入了新的问题:假设执行更新操作时,又接收到查询请求,此时就会返回缓存中的老数据。更麻烦的是,如果数据库更新操作执行失败,则缓存中可能永远是脏数据。 - -- 应该选择哪种更新测录 - -通过上面的内容,我们知道,两种更新策略都存在并发问题。 - -但是建议选择先更新数据库,再删除缓存,因为其并发问题出现的概率可能非常低,因为这个条件需要发生在读缓存时缓存失效,而且并发着有一个写操作。而实际上数据库的写操作会比读操作慢得多,而且还要锁表,而读操作必需在写操作前进入数据库操作,而又要晚于写操作更新缓存,所有的这些条件都具备的概率基本并不大。 - -如果需要数据库和缓存保证强一致性,则可以通过 2PC 或 Paxos 协议来实现。但是 2PC 太慢,而 Paxos 太复杂,所以如果不是非常重要的数据,不建议使用强一致性方案。 - -### 缓存预热 - -缓存预热是指系统启动后,将常用的数据直接缓存。这样就可以避免用户请求的时候,先查询数据库,然后再更新缓存的问题。 - -解决方案: - -- 直接写个缓存刷新页面,上线时手工操作下。 -- 数据量不大,可以在项目启动的时候自动进行加载。 -- 定时刷新缓存。 - -### 缓存降级 - -当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。 - -降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。 - -## 参考资料 - -- 《大型网站技术架构》 -- [你应该知道的缓存进化史](https://link.juejin.im/?target=https%3A%2F%2Fjuejin.im%2Fpost%2F5b7593496fb9a009b62904fa) -- [如何优雅的设计和使用缓存?](https://link.juejin.im/?target=https%3A%2F%2Fjuejin.im%2Fpost%2F5b849878e51d4538c77a974a) -- [理解分布式系统中的缓存架构(上)](https://www.jianshu.com/p/73ce0ef820f9) diff --git a/docs/theory/distributed-base-theory.md b/docs/theory/distributed-base-theory.md deleted file mode 100644 index ec1cfdff..00000000 --- a/docs/theory/distributed-base-theory.md +++ /dev/null @@ -1,283 +0,0 @@ ---- -title: 分布式基本原理 -categories: ['design', 'distributed'] -tags: ['design', 'distributed'] -date: 2018-07-09 17:43 ---- - -# 分布式基本原理 - -## 简介 - -### 性能 - -常见的性能指标: - -- **`吞吐量(Throughput)` - 是指系统在单位时间内处理请求的数量**。 -- **`响应时间(Response Time, RT)` - 是指系统对请求作出响应的时间**。 -- **并发用户数** - 是指系统可以同时承载的正常工作的在线用户数。 -- **`每秒查询率(QPS)`** - 是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。在因特网上,作为域名系统服务器的机器的性能经常用每秒查询率来衡量。对应 fetches/sec,即每秒的响应请求数,也即是最大吞吐能力。 -- **每秒事务数(Transaction Per Second, TPS)** - 衡量系统处理事务或交易的能力,即服务器对客户请求的能力,每秒处理的事务数,一般在 LoadRunner 上使用,设置事务,然后统计单位时间内系统可以成功完成多少个定义的事务。 -- **点击量(Hits Per Second)** - 服务器每秒收到的 HTTP 请求数。 - -``` -QPS = 并发量 / 平均响应时间 -``` - -吞吐量和响应时间这两个指标往往是矛盾的,追求高吞吐的系统,往往很难做到低响应时间,解释如下: - -- 在无并发的系统中,吞吐量为响应时间的倒数,例如响应时间为 10 ms,那么吞吐量为 100 req/s,因此高吞吐也就意味着低响应时间。 -- 但是在并发的系统中,由于一个请求在调用 I/O 资源的时候,需要进行等待。服务器端一般使用的是异步等待方式,即等待的请求被阻塞之后不需要一直占用 CPU 资源。这种方式能大大提高 CPU 资源的利用率,例如上面的例子中,单个请求在无并发的系统中响应时间为 10 ms,如果在并发的系统中,那么吞吐量将大于 100 req/s。因此为了追求高吞吐量,通常会提高并发程度。但是并发程度的增加,会导致请求的平均响应时间也增加,因为请求不能马上被处理,需要和其它请求一起进行并发处理,响应时间自然就会增高。 - -### 可用性 - -可用性指系统在面对各种异常时可以提供正常服务的能力。可以用系统可用时间占总时间的比值来衡量,4 个 9 的可用性表示系统 99.99% 的时间是可用的。 - -重要术语: - -- **异常** - - **服务器宕机** - 内存错误、服务器停电等都会导致服务器宕机,此时节点无法正常工作,称为不可用。服务器宕机会导致节点失去所有内存信息,因此需要将内存信息保存到持久化介质上。 - - **网络异常** - 有一种特殊的网络异常称为——**网络分区** ,即集群的所有节点被划分为多个区域,每个区域内部可以通信,但是区域之间无法通信。 - - **磁盘故障** - 磁盘故障是一种发生概率很高的异常。使用冗余机制,将数据存储到多台服务器。 -- **超时** - 在分布式系统中,一个请求除了成功和失败两种状态,还存在着超时状态。可以将服务器的操作设计为具有 **幂等性** ,即执行多次的结果与执行一次的结果相同。如果使用这种方式,当出现超时的时候,可以不断地重新请求直到成功。 - -### 一致性 - -可以从两个角度理解一致性:从客户端的角度,读写操作是否满足某种特性;从服务器的角度,多个数据副本之间是否一致。 - -### 可扩展性 - -指系统通过扩展集群服务器规模来提高性能的能力。理想的分布式系统需要实现“线性可扩展”,即随着集群规模的增加,系统的整体性能也会线性增加。 - -## 数据分布 - -分布式存储系统的数据分布在多个节点中,常用的数据分布方式有哈希分布和顺序分布。 - -数据库的水平切分(Sharding)也是一种分布式存储方法,下面的数据分布方法同样适用于 Sharding。 - -### 哈希分布 - -哈希分布就是将数据计算哈希值之后,按照哈希值分配到不同的节点上。例如有 N 个节点,数据的主键为 key,则将该数据分配的节点序号为:hash(key)%N。 - -传统的哈希分布算法存在一个问题:当节点数量变化时,也就是 N 值变化,那么几乎所有的数据都需要重新分布,将导致大量的数据迁移。 - -**一致性哈希** - -Distributed Hash Table(DHT):对于哈希空间 [0, 2n-1],将该哈希空间看成一个哈希环,将每个节点都配置到哈希环上。每个数据对象通过哈希取模得到哈希值之后,存放到哈希环中顺时针方向第一个大于等于该哈希值的节点上。 - -一致性哈希的优点是在增加或者删除节点时只会影响到哈希环中相邻的节点,例如下图中新增节点 X,只需要将数据对象 C 重新存放到节点 X 上即可,对于节点 A、B、D 都没有影响。 - -### 顺序分布 - -哈希分布式破坏了数据的有序性,顺序分布则不会。 - -顺序分布的数据划分为多个连续的部分,按数据的 ID 或者时间分布到不同节点上。例如下图中,User 表的 ID 范围为 1 \~ 7000,使用顺序分布可以将其划分成多个子表,对应的主键范围为 1 \~ 1000,1001 \~ 2000,...,6001 \~ 7000。 - -顺序分布的优点是可以充分利用每个节点的空间,而哈希分布很难控制一个节点存储多少数据。 - -但是顺序分布需要使用一个映射表来存储数据到节点的映射,这个映射表通常使用单独的节点来存储。当数据量非常大时,映射表也随着变大,那么一个节点就可能无法存放下整个映射表。并且单个节点维护着整个映射表的开销很大,查找速度也会变慢。为了解决以上问题,引入了一个中间层,也就是 Meta 表,从而分担映射表的维护工作。 - -### 负载均衡 - -衡量负载的因素很多,如 CPU、内存、磁盘等资源使用情况、读写请求数等。 - -分布式系统存储应当能够自动负载均衡,当某个节点的负载较高,将它的部分数据迁移到其它节点。 - -每个集群都有一个总控节点,其它节点为工作节点,由总控节点根据全局负载信息进行整体调度,工作节点定时发送心跳包(Heartbeat)将节点负载相关的信息发送给总控节点。 - -一个新上线的工作节点,由于其负载较低,如果不加控制,总控节点会将大量数据同时迁移到该节点上,造成该节点一段时间内无法工作。因此负载均衡操作需要平滑进行,新加入的节点需要较长的一段时间来达到比较均衡的状态。 - -## 分布式系统理论 - -### CAP - -**分布式系统不可能同时满足一致性(C:Consistency)、可用性(A:Availability)和分区容忍性(P:Partition Tolerance),最多只能同时满足其中两项。** - -
    - -
    - -**(一)一致性** - -**一致性指的是多个数据副本是否能保持一致的特性。** - -- 在一致性的条件下,系统在执行数据更新操作之后能够从一致性状态转移到另一个一致性状态。 -- 对系统的一个数据更新成功之后,如果所有用户都能够读取到最新的值,该系统就被认为具有强一致性。 - -**(二)可用性** - -**可用性指分布式系统在面对各种异常时可以提供正常服务的能力,可以用系统可用时间占总时间的比值来衡量**,如:4 个 9 的可用性表示系统 `99.99%` 的时间是可用的。 - -在可用性条件下,系统提供的服务一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。 - -**(三)分区容忍性** - -**网络分区指分布式系统中的节点被划分为多个区域,每个区域内部可以通信,但是区域之间无法通信。** - -在分区容忍性条件下,分布式系统在遇到任何网络分区故障的时候,仍然需要能对外提供一致性和可用性的服务,除非是整个网络环境都发生了故障。 - -**(四)权衡** - -在分布式系统中,分区容忍性必不可少,因为需要总是假设网络是不可靠的。因此,CAP 理论实际在是要在可用性和一致性之间做权衡。 - -可用性和一致性往往是冲突的,很难都使它们同时满足。在多个节点之间进行数据同步时, - -- 为了保证一致性(CP),就需要让所有节点下线成为不可用的状态,等待同步完成; -- 为了保证可用性(AP),在同步过程中允许读取所有节点的数据,但是数据可能不一致。 - -### BASE - -**BASE 是基本可用(Basically Available)、软状态(Soft State)和最终一致性(Eventually Consistent)三个短语的缩写。** - -BASE 理论是对 CAP 中一致性和可用性权衡的结果,它的理论的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。 - -
    - -
    - -- **基本可用** - **指分布式系统在出现故障的时候,保证核心可用,允许损失部分可用性**。例如,电商在做促销时,为了保证购物系统的稳定性,部分消费者可能会被引导到一个降级的页面。 -- **软状态** - 指允许系统中的数据存在中间状态,并认为该中间状态不会影响系统整体可用性。即**允许系统不同节点的数据副本之间进行同步的过程存在延时**。 -- **最终一致性** - **最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能达到一致的状态**。 - -ACID 要求强一致性,通常运用在传统的数据库系统上。而 BASE 要求最终一致性,通过牺牲强一致性来达到可用性,通常运用在大型分布式系统中。 - -在实际的分布式场景中,不同业务单元和组件对一致性的要求是不同的,因此 ACID 和 BASE 往往会结合在一起使用。 - -## 共识性问题 - -### Paxos - -用于达成共识性问题,即对多个节点产生的值,该算法能保证只选出唯一一个值。 - -主要有三类节点: - -- 提议者(Proposer):提议一个值; -- 接受者(Acceptor):对每个提议进行投票; -- 告知者(Learner):被告知投票的结果,不参与投票过程。 - -算法需要满足 safety 和 liveness 两方面的约束要求(实际上这两个基础属性是大部分分布式算法都该考虑的): - -- safety:保证决议结果是对的,无歧义的,不会出现错误情况。 - - 决议(value)只有在被 proposers 提出的 proposal 才能被最终批准; - - 在一次执行实例中,只批准(chosen)一个最终决议,意味着多数接受(accept)的结果能成为决议; -- liveness:保证决议过程能在有限时间内完成。 - - 决议总会产生,并且 learners 能获得被批准(chosen)的决议。 - -基本过程包括 proposer 提出提案,先争取大多数 acceptor 的支持,超过一半支持时,则发送结案结果给所有人进行确认。一个潜在的问题是 proposer 在此过程中出现故障,可以通过超时机制来解决。极为凑巧的情况下,每次新的一轮提案的 proposer 都恰好故障,系统则永远无法达成一致(概率很小)。 - -Paxos 能保证在超过 $1/2$ 的正常节点存在时,系统能达成共识。 - -#### 单个提案者+多接收者 - -如果系统中限定只有某个特定节点是提案者,那么一致性肯定能达成(只有一个方案,要么达成,要么失败)。提案者只要收到了来自多数接收者的投票,即可认为通过,因为系统中不存在其他的提案。 - -但一旦提案者故障,则系统无法工作。 - -#### 多个提案者+单个接收者 - -限定某个节点作为接收者。这种情况下,共识也很容易达成,接收者收到多个提案,选第一个提案作为决议,拒绝掉后续的提案即可。 - -缺陷也是容易发生单点故障,包括接收者故障或首个提案者节点故障。 - -以上两种情形其实类似主从模式,虽然不那么可靠,但因为原理简单而被广泛采用。 - -当提案者和接收者都推广到多个的情形,会出现一些挑战。 - -#### 多个提案者+多个接收者 - -既然限定单提案者或单接收者都会出现故障,那么就得允许出现多个提案者和多个接收者。问题一下子变得复杂了。 - -一种情况是同一时间片段(如一个提案周期)内只有一个提案者,这时可以退化到单提案者的情形。需要设计一种机制来保障提案者的正确产生,例如按照时间、序列、或者大家猜拳(出一个数字来比较)之类。考虑到分布式系统要处理的工作量很大,这个过程要尽量高效,满足这一条件的机制非常难设计。 - -另一种情况是允许同一时间片段内可以出现多个提案者。那同一个节点可能收到多份提案,怎么对他们进行区分呢?这个时候采用只接受第一个提案而拒绝后续提案的方法也不适用。很自然的,提案需要带上不同的序号。节点需要根据提案序号来判断接受哪个。比如接受其中序号较大(往往意味着是接受新提出的,因为旧提案者故障概率更大)的提案。 - -如何为提案分配序号呢?一种可能方案是每个节点的提案数字区间彼此隔离开,互相不冲突。为了满足递增的需求可以配合用时间戳作为前缀字段。 - -此外,提案者即便收到了多数接收者的投票,也不敢说就一定通过。因为在此过程中系统可能还有其它的提案。 - -### Raft - -Raft 算法是 Paxos 算法的一种简化实现。 - -包括三种角色:leader、candidate 和 follower,其基本过程为: - -- **Leader 选举** - 每个 candidate 随机经过一定时间都会提出选举方案,最近阶段中得票最多者被选为 leader; -- **同步 log** - leader 会找到系统中 log 最新的记录,并强制所有的 follower 来刷新到这个记录; - -_注:此处 log 并非是指日志消息,而是各种事件的发生记录。_ - -#### 单个 Candidate 的竞选 - -有三种节点:Follower、Candidate 和 Leader。Leader 会周期性的发送心跳包给 Follower。每个 Follower 都设置了一个随机的竞选超时时间,一般为 150ms\~300ms,如果在这个时间内没有收到 Leader 的心跳包,就会变成 Candidate,进入竞选阶段。 - -- 下图表示一个分布式系统的最初阶段,此时只有 Follower,没有 Leader。Follower A 等待一个随机的竞选超时时间之后,没收到 Leader 发来的心跳包,因此进入竞选阶段。 - -
    - -
    - -- 此时 A 发送投票请求给其它所有节点。 - -
    - -
    - -- 其它节点会对请求进行回复,如果超过一半的节点回复了,那么该 Candidate 就会变成 Leader。 - -
    - -
    - -- 之后 Leader 会周期性地发送心跳包给 Follower,Follower 接收到心跳包,会重新开始计时。 - -
    - -
    - -#### 多个 Candidate 竞选 - -- 如果有多个 Follower 成为 Candidate,并且所获得票数相同,那么就需要重新开始投票,例如下图中 Candidate B 和 Candidate D 都获得两票,因此需要重新开始投票。 - -
    - -
    - -- 当重新开始投票时,由于每个节点设置的随机竞选超时时间不同,因此能下一次再次出现多个 Candidate 并获得同样票数的概率很低。 - -
    - -
    - -#### 同步日志 - -- 来自客户端的修改都会被传入 Leader。注意该修改还未被提交,只是写入日志中。 - -
    - -
    - -- Leader 会把修改复制到所有 Follower。 - -
    - -
    - -- Leader 会等待大多数的 Follower 也进行了修改,然后才将修改提交。 - -
    - -
    - -- 此时 Leader 会通知的所有 Follower 让它们也提交修改,此时所有节点的值达成一致。 - -
    - -
    - -## 参考资料 - -- 杨传辉. 大规模分布式存储系统: 原理解析与架构实战[M]. 机械工业出版社, 2013. -- [区块链技术指南](https://www.gitbook.com/book/yeasy/blockchain_guide/details) -- [NEAT ALGORITHMS - PAXOS](http://harry.me/blog/2014/12/27/neat-algorithms-paxos/) -- [Raft: Understandable Distributed Consensus](http://thesecretlivesofdata.com/raft) -- [Paxos By Example](https://angus.nyc/2012/paxos-by-example/) diff --git a/docs/theory/distributed-id-theory.md b/docs/theory/distributed-id-theory.md deleted file mode 100644 index 58bccf27..00000000 --- a/docs/theory/distributed-id-theory.md +++ /dev/null @@ -1,276 +0,0 @@ -# 分布式 ID 基本原理 - -> 传统数据库软件开发中,主键自动生成技术是基本需求。而各个数据库对于该需求也提供了相应的支持,比如 MySQL 的自增键,Oracle 的自增序列等。 -> -> 数据分片后,不同数据节点生成全局唯一主键是非常棘手的问题。同一个逻辑表内的不同实际表之间的自增键由于无法互相感知而产生重复主键。 虽然可通过约束自增主键初始值和步长的方式避免碰撞,但需引入额外的运维规则,使解决方案缺乏完整性和可扩展性。 -> -> 为此,需要使用分布式 ID 来解决此问题。本文总结业界常用的分布式 ID 解决方案。 - -## 简介 - -首先,分布式 ID 应该具备哪些特性呢? - -1. **全局唯一性** - 不能出现重复的 ID 号,既然是唯一标识,这是最基本的要求。 -2. **趋势递增** - 在 MySQL InnoDB 引擎中使用的是聚集索引,由于多数 RDBMS 使用 B-tree 的数据结构来存储索引数据,在主键的选择上面我们应该尽量使用有序的主键保证写入性能。 -3. **单调递增** - 保证下一个 ID 一定大于上一个 ID,例如事务版本号、IM 增量消息、排序等特殊需求。 -4. **信息安全** - 如果 ID 是连续的,恶意用户的扒取工作就非常容易做了,直接按照顺序下载指定 URL 即可;如果是订单号就更危险了,竞对可以直接知道我们一天的单量。所以在一些应用场景下,会需要 ID 无规则、不规则。 - -## UUID - -UUID 是通用唯一识别码(Universally Unique Identifier)的缩写,开放软件基金会(OSF)规范定义了包括网卡 MAC 地址、时间戳、名字空间(Namespace)、随机或伪随机数、时序等元素。利用这些元素来生成 UUID。 - -UUID 是由 128 位二进制组成,一般转换成十六进制,然后用 String 表示。在 java 中有个 UUID 类,在他的注释中我们看见这里有 4 种不同的 UUID 的生成策略: - -- random - 基于随机数生成 UUID,由于 Java 中的随机数是伪随机数,其重复的概率是可以被计算出来的。这个一般我们用下面的代码获取基于随机数的 UUID: - -```java -String id = UUID.randomUUID().toString(); -``` - -- time-based - 基于时间的 UUID,这个一般是通过当前时间,随机数,和本地 Mac 地址来计算出来,自带的 JDK 包并没有这个算法的我们在一些 UUIDUtil 中,比如我们的 log4j.core.util,会重新定义 UUID 的高位和低位。 - -```java - public static UUID getTimeBasedUuid() { - long time = System.currentTimeMillis() * 10000L + 122192928000000000L + (long)(COUNT.incrementAndGet() % 10000); - long timeLow = (time & 4294967295L) << 32; - long timeMid = (time & 281470681743360L) >> 16; - long timeHi = (time & 1152640029630136320L) >> 48; - long most = timeLow | timeMid | 4096L | timeHi; - return new UUID(most, LEAST); - } -``` - -- DCE security - DCE 安全的 UUID。 - -- name-based - 基于名字的 UUID,通过计算名字和名字空间的 MD5 来计算 UUID。 - -### UUID 的优点 - -- 通过本地生成,没有经过网络 I/O,性能较快 - -### UUID 的缺点 - -- **长度过长** - UUID 太长,16 字节 128 位,通常以 36 长度的字符串表示,很多场景不适用。例如:Mysql 官方明确建议主键越短越好,36 个字符长度的 UUID 不符合要求。 -- **信息不安全** - 基于 MAC 地址生成 UUID 的算法可能会造成 MAC 地址泄露,这个漏洞曾被用于寻找梅丽莎病毒的制作者位置。 -- **无序性** - 不能生成递增有序的数字。这对于一些特定场景不利。例如:如果作为数据库主键,在 InnoDB 引擎下,UUID 的无序性可能会引起数据位置频繁变动,严重影响性能。 - -### 适用场景 - -UUID 的适用场景可以为不需要担心过多的空间占用,以及不需要生成有递增趋势的数字。在 Log4j 里 `UuidPatternConverter` 中加入了 UUID 来标识每一条日志。 - -## 数据库自增键 - -提到自增键,最先想到的肯定是直接使用数据库自增键。_各数据库对于该需求也提供了相应的支持,比如 MySQL 的自增键,Oracle 的自增序列等_。 - -### 优点 - -- 非常简单,利用现有数据库的功能实现,成本小,有 DBA 专业维护 -- 有序递增 -- 方便排序和分页 - -### 缺点 - -- 强依赖数据库,当数据库异常时整个系统不可用。配置主从复制可以尽可能的增加可用性,但是数据一致性在特殊情况下难以保证。主从切换时的不一致可能会导致重复发号。 -- 生成 ID 性能瓶颈限制在单台 MySQL 的读写性能。 -- 简单递增容易被其他人分析利用 - - 比如根据用户 ID 来分析注册人数,猜测出服务的一个大概状况。 - -### 适用场景 - -数据量不大,并发不高的场景。 - -### 改进 - -对于 MySQL 性能问题,可用如下方案解决:在分布式系统中我们可以多部署几台机器,每台机器设置不同的初始值,且步长和机器数相等。比如有两台机器。设置步长 step 为 2,TicketServer1 的初始值为 1(1,3,5,7,9,11…)、TicketServer2 的初始值为 2(2,4,6,8,10…)。这是 Flickr 团队在 2010 年撰文介绍的一种主键生成策略([Ticket Servers: Distributed Unique Primary Keys on the Cheap ](http://code.flickr.net/2010/02/08/ticket-servers-distributed-unique-primary-keys-on-the-cheap/))。如下所示,为了实现上述方案分别设置两台机器对应的参数,TicketServer1 从 1 开始发号,TicketServer2 从 2 开始发号,两台机器每次发号之后都递增 2。 - -``` -TicketServer1: -auto-increment-increment = 2 -auto-increment-offset = 1 - -TicketServer2: -auto-increment-increment = 2 -auto-increment-offset = 2 -``` - -![image](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2017/6d2c9ec8.png) - -这种架构存在以下缺点: - -- 系统水平扩展比较困难,比如定义好了步长和机器台数之后,如果要添加机器该怎么做?假设现在只有一台机器发号是 1,2,3,4,5(步长是 1),这个时候需要扩容机器一台。可以这样做:把第二台机器的初始值设置得比第一台超过很多,比如 14(假设在扩容时间之内第一台不可能发到 14),同时设置步长为 2,那么这台机器下发的号码都是 14 以后的偶数。然后摘掉第一台,把 ID 值保留为奇数,比如 7,然后修改第一台的步长为 2。让它符合我们定义的号段标准,对于这个例子来说就是让第一台以后只能产生奇数。扩容方案看起来复杂吗?貌似还好,现在想象一下如果我们线上有 100 台机器,这个时候要扩容该怎么做?简直是噩梦。所以系统水平扩展方案复杂难以实现。 -- ID 没有了单调递增的特性,只能趋势递增,这个缺点对于一般业务需求不是很重要,可以容忍。 -- 数据库压力还是很大,每次获取 ID 都得读写一次数据库,只能靠堆机器来提高性能。 - -## 利用 Redis 生成键 - -Redis 中有两个命令 `Incr`,`IncrBy`。 - -因为 Redis 是单线程的,所以能保证原子性。 - -### 优点 - -- 性能比数据库好 -- 能满足有序递增 - -### 缺点 - -- 由于 Redis 是内存数据库,即使支持持久化(AOF 和 RDB),但是依然可能丢失数据,造成 ID 重复。 -- 依赖于 Redis 生成键,如果 Redis 服务不稳定,会影响 ID 生成。 - -### 适用场景 - -Redis 性能比数据库好,但是有可能会出现 ID 重复和不稳定。如果可以接受,那么就可以使用这种方案。 - -## 雪花算法(Snowflake) - -雪花算法(Snowflake)是由 Twitter 公布的分布式主键生成算法,**它会生成一个 `64 bit` 的整数**,可以保证不同进程主键的不重复性,以及相同进程主键的有序性。 - -在同一个进程中,它首先是通过时间位保证不重复,如果时间相同则是通过序列位保证。 同时由于时间位是单调递增的,且各个服务器如果大体做了时间同步,那么生成的主键在分布式环境可以认为是总体有序的,这就保证了对索引字段的插入的高效性。 - -### 基本原理 - -#### 键的组成 - -使用**雪花算法生成的主键,二进制表示形式包含 4 部分**,从高位到低位分表为:1bit 符号位、41bit 时间戳位、10bit 工作进程位以及 12bit 序列号位。 - -- **符号位(1bit)** - -预留的符号位,恒为零。 - -- **时间戳位(41bit)** - -41 位的时间戳可以容纳的毫秒数是 2 的 41 次幂,一年所使用的毫秒数是:`365 * 24 * 60 * 60 * 1000`。通过计算可知: - -```java -Math.pow(2, 41) / (365 * 24 * 60 * 60 * 1000L); -``` - -结果约等于 69.73 年。ShardingSphere 的雪花算法的时间纪元从 2016 年 11 月 1 日零点开始,可以使用到 2086 年,相信能满足绝大部分系统的要求。 - -- **工作进程位(10bit)** - -该标志在 Java 进程内是唯一的,如果是分布式应用部署应保证每个工作进程的 id 是不同的。该值默认为 0,可通过属性设置。 - -- **序列号位(12bit)** - -该序列是用来在同一个毫秒内生成不同的 ID。如果在这个毫秒内生成的数量超过 4096(2 的 12 次幂),那么生成器会等待到下个毫秒继续生成。 - -雪花算法主键的详细结构见下图: - -![雪花算法](https://shardingsphere.apache.org/document/current/img/sharding/snowflake_cn_v2.png) - -#### 时钟回拨 - -服务器时钟回拨会导致产生重复序列,因此默认分布式主键生成器提供了一个最大容忍的时钟回拨毫秒数。 如果时钟回拨的时间超过最大容忍的毫秒数阈值,则程序报错;如果在可容忍的范围内,默认分布式主键生成器会等待时钟同步到最后一次主键生成的时间后再继续工作。 最大容忍的时钟回拨毫秒数的默认值为 0,可通过属性设置。 - -#### 灵活定制 - -上面只是一个将 `64bit` 划分的标准,当然也不一定这么做,可以根据不同业务的具体场景来划分,比如下面给出一个业务场景: - -- 服务目前 QPS10 万,预计几年之内会发展到百万。 -- 当前机器三地部署,上海,北京,深圳都有。 -- 当前机器 10 台左右,预计未来会增加至百台。 - -这个时候我们根据上面的场景可以再次合理的划分 62bit,QPS 几年之内会发展到百万,那么每毫秒就是千级的请求,目前 10 台机器那么每台机器承担百级的请求,为了保证扩展,后面的循环位可以限制到 1024,也就是 2^10,那么循环位 10 位就足够了。 - -机器三地部署我们可以用 3bit 总共 8 来表示机房位置,当前的机器 10 台,为了保证扩展到百台那么可以用 7bit 128 来表示,时间位依然是 41bit,那么还剩下 64-10-3-7-41-1 = 2bit,还剩下 2bit 可以用来进行扩展。 - -![](https://user-gold-cdn.xitu.io/2018/9/29/16624909d2007c22?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) - -### 优点 - -- 生成的 ID 都是趋势递增的。 -- 不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成 ID 的性能也是非常高的。 -- 可以根据自身业务特性分配 bit 位,非常灵活。 - -### 缺点 - -- 强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。 - -### 适用场景 - -当我们需要无序不能被猜测的 ID,并且需要一定高性能,且需要 long 型,那么就可以使用我们雪花算法。比如常见的订单 ID,用雪花算法别人就无法猜测你每天的订单量是多少。 - -### 防止时钟回拨 - -雪花算法是强依赖于时间的,而如果机器时间发生回拨,有可能会生成重复的 ID。 - -我们可以针对算法做一些优化,来防止时钟回拨生成重复 ID。 - -用当前时间和上一次的时间进行判断,如果当前时间小于上一次的时间那么肯定是发生了回拨。普通的算法会直接抛出异常,这里我们可以对其进行优化,一般分为两个情况: - -- 如果时间回拨时间较短,比如配置 `5ms` 以内,那么可以直接等待一定的时间,让机器的时间追上来。 -- 如果时间的回拨时间较长,我们不能接受这么长的阻塞等待,那么又有两个策略: - - 直接拒绝,抛出异常。打日志,通知 RD 时钟回滚。 - - 利用扩展位。上面我们讨论过,不同业务场景位数可能用不到那么多比特位,那么我们可以把扩展位数利用起来。比如:当这个时间回拨比较长的时候,我们可以不需要等待,直接在扩展位加 1。两位的扩展位允许我们有三次大的时钟回拨,一般来说就够了,如果其超过三次我们还是选择抛出异常,打日志。 - -## Leaf - -> 美团提供了一种分布式 ID 解决方案 Leaf,其本质可以视为数据库分段+服务缓存 ID。 -> -> 详情可以参考 [Leaf——美团点评分布式 ID 生成系统](https://tech.meituan.com/2017/04/21/mt-leaf.html) - -### 基本原理 - -使用数据库生成 ID,但是做了如下改进: - -原方案每次获取 ID 都得读写一次数据库,造成数据库压力大。改为利用 proxy server 批量获取,每次获取一个 segment(step 决定大小)号段的值。用完之后再去数据库获取新的号段,可以大大的减轻数据库的压力。 - 各个业务不同的发号需求用 biz_tag 字段来区分,每个 biz-tag 的 ID 获取相互隔离,互不影响。如果以后有性能需求需要对数据库扩容,不需要上述描述的复杂的扩容操作,只需要对 biz_tag 分库分表就行。 - -数据库表设计如下: - -```sql -+-------------+--------------+------+-----+-------------------+-----------------------------+ -| Field | Type | Null | Key | Default | Extra | -+-------------+--------------+------+-----+-------------------+-----------------------------+ -| biz_tag | varchar(128) | NO | PRI | | | -| max_id | bigint(20) | NO | | 1 | | -| step | int(11) | NO | | NULL | | -| desc | varchar(256) | YES | | NULL | | -| update_time | timestamp | NO | | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP | -+-------------+--------------+------+-----+-------------------+-----------------------------+ -``` - -重要字段说明: - -- `biz_tag` 用来区分业务 -- `max_id` 表示该 `biz_tag` 目前所被分配的 ID 号段的最大值 -- `step` 表示每次分配的号段长度。原来获取 ID 每次都需要写数据库,现在只需要把 `step` 设置得足够大,比如 1000。那么只有当 1000 个号被消耗完了之后才会去重新读写一次数据库。读写数据库的频率从 1 减小到了 1/step。 - -大致架构如下图所示: - -![image](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2017/5e4ff128.png) - -test_tag 在第一台 Leaf 机器上是 `1~1000` 的号段,当这个号段用完时,会去加载另一个长度为 step=1000 的号段,假设另外两台号段都没有更新,这个时候第一台机器新加载的号段就应该是 `3001~4000`。同时数据库对应的 biz_tag 这条数据的 max_id 会从 3000 被更新成 4000,更新号段的 SQL 语句如下: - -```sql -Begin -UPDATE table SET max_id=max_id+step WHERE biz_tag=xxx -SELECT tag, max_id, step FROM table WHERE biz_tag=xxx -Commit -``` - -### 优点 - -- 比数据库自增键性能高 -- 能保证键趋势递增。 -- 如果数据库宕机,由于 proxServer 有缓存,依然可以坚持一段时间。 - -### 缺点 - -- 和主键递增一样,容易被人猜测。 -- 数据库宕机后,虽然能支撑一段时间,但是仍然会造成系统不可用。 - -### 适用场景 - -需要趋势递增,并且 ID 大小可控制的,可以使用这套方案。 - -当然这个方案也可以通过一些手段避免被人猜测,把 ID 变成是无序的,比如把我们生成的数据是一个递增的 long 型,把这个 Long 分成几个部分,比如可以分成几组三位数,几组四位数,然后在建立一个映射表,将我们的数据变成无序。 - -## 参考资料 - -- [百度分布式 ID](https://github.com/baidu/uid-generator/blob/master/README.zh_cn.md) -- [如果再有人问你分布式 ID,这篇文章丢给他](https://juejin.im/post/5bb0217ef265da0ac2567b42) -- [理解分布式 id 生成算法 SnowFlake](https://segmentfault.com/a/1190000011282426) -- [Leaf——美团点评分布式 ID 生成系统](https://tech.meituan.com/2017/04/21/mt-leaf.html) -- [UUID 规范](https://www.ietf.org/rfc/rfc4122.txt) -- [ShardingSphere 分布式主键](https://shardingsphere.apache.org/document/current/cn/features/sharding/other-features/key-generator/) diff --git a/docs/theory/distributed-interview.md b/docs/theory/distributed-interview.md deleted file mode 100644 index f5bfa3e1..00000000 --- a/docs/theory/distributed-interview.md +++ /dev/null @@ -1,501 +0,0 @@ ---- -title: 分布式技术面试题 -categories: ['design', 'distributed'] -tags: ['design', 'distributed'] -date: 2018-07-10 16:02 ---- - -# 分布式技术面试题 - - - -- [1. 分布式缓存](#1-分布式缓存) - - [1.1. Redis 有什么数据类型?分别用于什么场景?](#11-redis-有什么数据类型分别用于什么场景) - - [1.2. Redis 的主从复制是如何实现的?](#12-redis-的主从复制是如何实现的) - - [1.3. Redis 的 key 是如何寻址的?](#13-redis-的-key-是如何寻址的) - - [1.4. Redis 的集群模式是如何实现的?](#14-redis-的集群模式是如何实现的) - - [1.5. Redis 如何实现分布式锁?ZooKeeper 如何实现分布式锁?比较二者优劣?](#15-redis-如何实现分布式锁zookeeper-如何实现分布式锁比较二者优劣) - - [1.6. Redis 的持久化方式?有什么优缺点?持久化实现原理?](#16-redis-的持久化方式有什么优缺点持久化实现原理) - - [1.7. Redis 过期策略有哪些?](#17-redis-过期策略有哪些) - - [1.8. Redis 和 Memcached 有什么区别?](#18-redis-和-memcached-有什么区别) - - [1.9. 为什么单线程的 Redis 性能反而优于多线程的 Memcached?](#19-为什么单线程的-redis-性能反而优于多线程的-memcached) -- [2. 分布式消息队列(MQ)](#2-分布式消息队列mq) - - [2.1. 为什么使用 MQ?](#21-为什么使用-mq) - - [2.2. 如何保证 MQ 的高可用?](#22-如何保证-mq-的高可用) - - [2.3. MQ 有哪些常见问题?如何解决这些问题?](#23-mq-有哪些常见问题如何解决这些问题) - - [2.4. Kafka, ActiveMQ, RabbitMQ, RocketMQ 各有什么优缺点?](#24-kafka-activemq-rabbitmq-rocketmq-各有什么优缺点) -- [3. 分布式服务(RPC)](#3-分布式服务rpc) - - [3.1. Dubbo 的实现过程?](#31-dubbo-的实现过程) - - [3.2. Dubbo 负载均衡策略有哪些?](#32-dubbo-负载均衡策略有哪些) - - [3.3. Dubbo 集群容错策略 ?](#33-dubbo-集群容错策略-) - - [3.4. 动态代理策略?](#34-动态代理策略) - - [3.5. Dubbo 支持哪些序列化协议?Hessian?Hessian 的数据结构?](#35-dubbo-支持哪些序列化协议hessianhessian-的数据结构) - - [3.6. Protoco Buffer 是什么?](#36-protoco-buffer-是什么) - - [3.7. 注册中心挂了可以继续通信吗?](#37-注册中心挂了可以继续通信吗) - - [3.8. ZooKeeper 原理是什么?ZooKeeper 有什么用?](#38-zookeeper-原理是什么zookeeper-有什么用) - - [3.9. Netty 有什么用?NIO/BIO/AIO 有什么用?有什么区别?](#39-netty-有什么用niobioaio-有什么用有什么区别) - - [3.10. 为什么要进行系统拆分?拆分不用 Dubbo 可以吗?](#310-为什么要进行系统拆分拆分不用-dubbo-可以吗) - - [3.11. Dubbo 和 Thrift 有什么区别?](#311-dubbo-和-thrift-有什么区别) - - - -## 1. 分布式缓存 - -### 1.1. Redis 有什么数据类型?分别用于什么场景? - -| 数据类型 | 可以存储的值 | 操作 | -| -------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------- | -| STRING | 字符串、整数或者浮点数 | 对整个字符串或者字符串的其中一部分执行操作
    对整数和浮点数执行自增或者自减操作 | -| LIST | 列表 | 从两端压入或者弹出元素
    读取单个或者多个元素
    进行修剪,只保留一个范围内的元素 | -| SET | 无序集合 | 添加、获取、移除单个元素
    检查一个元素是否存在于集合中
    计算交集、并集、差集
    从集合里面随机获取元素 | -| HASH | 包含键值对的无序散列表 | 添加、获取、移除单个键值对
    获取所有键值对
    检查某个键是否存在 | -| ZSET | 有序集合 | 添加、获取、删除元素
    根据分值范围或者成员来获取元素
    计算一个键的排名 | - -> [What Redis data structures look like](https://redislabs.com/ebook/part-1-getting-started/chapter-1-getting-to-know-redis/1-2-what-redis-data-structures-look-like/) - -### 1.2. Redis 的主从复制是如何实现的? - -1. 从服务器连接主服务器,发送 SYNC 命令; -2. 主服务器接收到 SYNC 命名后,开始执行 BGSAVE 命令生成 RDB 文件并使用缓冲区记录此后执行的所有写命令; -3. 主服务器 BGSAVE 执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令; -4. 从服务器收到快照文件后丢弃所有旧数据,载入收到的快照; -5. 主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令; -6. 从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令; - -### 1.3. Redis 的 key 是如何寻址的? - -#### 背景 - -(1)redis 中的每一个数据库,都由一个 redisDb 的结构存储。其中: - -- redisDb.id 存储着 redis 数据库以整数表示的号码。 -- redisDb.dict 存储着该库所有的键值对数据。 -- redisDb.expires 保存着每一个键的过期时间。 - -(2)当 redis 服务器初始化时,会预先分配 16 个数据库(该数量可以通过配置文件配置),所有数据库保存到结构 redisServer 的一个成员 redisServer.db 数组中。当我们选择数据库 select number 时,程序直接通过 redisServer.db[number] 来切换数据库。有时候当程序需要知道自己是在哪个数据库时,直接读取 redisDb.id 即可。 - -(3)redis 的字典使用哈希表作为其底层实现。dict 类型使用的两个指向哈希表的指针,其中 0 号哈希表(ht[0])主要用于存储数据库的所有键值,而 1 号哈希表主要用于程序对 0 号哈希表进行 rehash 时使用,rehash 一般是在添加新值时会触发,这里不做过多的赘述。所以 redis 中查找一个 key,其实就是对进行该 dict 结构中的 ht[0] 进行查找操作。 - -(4)既然是哈希,那么我们知道就会有哈希碰撞,那么当多个键哈希之后为同一个值怎么办呢?redis 采取链表的方式来存储多个哈希碰撞的键。也就是说,当根据 key 的哈希值找到该列表后,如果列表的长度大于 1,那么我们需要遍历该链表来找到我们所查找的 key。当然,一般情况下链表长度都为是 1,所以时间复杂度可看作 o(1)。 - -#### 寻址 key 的步骤 - -1. 当拿到一个 key 后,redis 先判断当前库的 0 号哈希表是否为空,即:if (dict->ht[0].size == 0)。如果为 true 直接返回 NULL。 -2. 判断该 0 号哈希表是否需要 rehash,因为如果在进行 rehash,那么两个表中者有可能存储该 key。如果正在进行 rehash,将调用一次\_dictRehashStep 方法,\_dictRehashStep 用于对数据库字典、以及哈希键的字典进行被动 rehash,这里不作赘述。 -3. 计算哈希表,根据当前字典与 key 进行哈希值的计算。 -4. 根据哈希值与当前字典计算哈希表的索引值。 -5. 根据索引值在哈希表中取出链表,遍历该链表找到 key 的位置。一般情况,该链表长度为 1。 -6. 当 ht[0] 查找完了之后,再进行了次 rehash 判断,如果未在 rehashing,则直接结束,否则对 ht[1]重复 345 步骤。 - -### 1.4. Redis 的集群模式是如何实现的? - -Redis Cluster 是 Redis 的分布式解决方案,在 Redis 3.0 版本正式推出的。 - -Redis Cluster 去中心化,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。 - -#### Redis Cluster 节点分配 - -Redis Cluster 特点: - -1. 所有的 redis 节点彼此互联(PING-PONG 机制),内部使用二进制协议优化传输速度和带宽。 -2. 节点的 fail 是通过集群中超过半数的节点检测失效时才生效。 -3. 客户端与 redis 节点直连,不需要中间 proxy 层。客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。 -4. redis-cluster 把所有的物理节点映射到[0-16383] 哈希槽 (hash slot)上(不一定是平均分配),cluster 负责维护 node<->slot<->value。 -5. Redis 集群预分好 16384 个桶,当需要在 Redis 集群中放置一个 key-value 时,根据 CRC16(key) mod 16384 的值,决定将一个 key 放到哪个桶中。 - -#### Redis Cluster 主从模式 - -Redis Cluster 为了保证数据的高可用性,加入了主从模式。 - -一个主节点对应一个或多个从节点,主节点提供数据存取,从节点则是从主节点拉取数据备份。当这个主节点挂掉后,就会有这个从节点选取一个来充当主节点,从而保证集群不会挂掉。所以,在集群建立的时候,一定要为每个主节点都添加了从节点。 - -#### Redis Sentinel - -Redis Sentinel 用于管理多个 Redis 服务器,它有三个功能: - -- **监控(Monitoring)** - Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。 -- **提醒(Notification)** - 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。 -- **自动故障迁移(Automatic failover)** - 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。 - -Redis 集群中应该有奇数个节点,所以至少有三个节点。 - -哨兵监控集群中的主服务器出现故障时,需要根据 quorum 选举出一个哨兵来执行故障转移。选举需要 majority,即大多数哨兵是运行的(2 个哨兵的 majority=2,3 个哨兵的 majority=2,5 个哨兵的 majority=3,4 个哨兵的 majority=2)。 - -假设集群仅仅部署 2 个节点 - -``` -+----+ +----+ -| M1 |---------| R1 | -| S1 | | S2 | -+----+ +----+ -``` - -如果 M1 和 S1 所在服务器宕机,则哨兵只有 1 个,无法满足 majority 来进行选举,就不能执行故障转移。 - -### 1.5. Redis 如何实现分布式锁?ZooKeeper 如何实现分布式锁?比较二者优劣? - -分布式锁的三种实现: - -- 基于数据库实现分布式锁; -- 基于缓存(Redis 等)实现分布式锁; -- 基于 Zookeeper 实现分布式锁; - -#### 数据库实现 - -#### Redis 实现 - -1. 获取锁的时候,使用 setnx 加锁,并使用 expire 命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的 value 值为一个随机生成的 UUID,通过此在释放锁的时候进行判断。 -2. 获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。 -3. 释放锁的时候,通过 UUID 判断是不是该锁,若是该锁,则执行 delete 进行锁释放。 - -#### ZooKeeper 实现 - -1. 创建一个目录 mylock; -2. 线程 A 想获取锁就在 mylock 目录下创建临时顺序节点; -3. 获取 mylock 目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁; -4. 线程 B 获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点; -5. 线程 A 处理完,删除自己的节点,线程 B 监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。 - -#### 实现对比 - -ZooKeeper 具备高可用、可重入、阻塞锁特性,可解决失效死锁问题。 -但 ZooKeeper 因为需要频繁的创建和删除节点,性能上不如 Redis 方式。 - -### 1.6. Redis 的持久化方式?有什么优缺点?持久化实现原理? - -#### RDB 快照(snapshot) - -将存在于某一时刻的所有数据都写入到硬盘中。 - -##### 快照的原理 - -在默认情况下,Redis 将数据库快照保存在名字为 dump.rdb 的二进制文件中。你可以对 Redis 进行设置, 让它在“N 秒内数据集至少有 M 个改动”这一条件被满足时, 自动保存一次数据集。你也可以通过调用 SAVE 或者 BGSAVE,手动让 Redis 进行数据集保存操作。这种持久化方式被称为快照。 - -当 Redis 需要保存 dump.rdb 文件时, 服务器执行以下操作: - -- Redis 创建一个子进程。 -- 子进程将数据集写入到一个临时快照文件中。 -- 当子进程完成对新快照文件的写入时,Redis 用新快照文件替换原来的快照文件,并删除旧的快照文件。 - -这种工作方式使得 Redis 可以从写时复制(copy-on-write)机制中获益。 - -##### 快照的优点 - -- 它保存了某个时间点的数据集,非常适用于数据集的备份。 -- 很方便传送到另一个远端数据中心或者亚马逊的 S3(可能加密),非常适用于灾难恢复。 -- 快照在保存 RDB 文件时父进程唯一需要做的就是 fork 出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他 IO 操作,所以快照持久化方式可以最大化 redis 的性能。 -- 与 AOF 相比,在恢复大的数据集的时候,DB 方式会更快一些。 - -##### 快照的缺点 - -- 如果你希望在 redis 意外停止工作(例如电源中断)的情况下丢失的数据最少的话,那么快照不适合你。 -- 快照需要经常 fork 子进程来保存数据集到硬盘上。当数据集比较大的时候,fork 的过程是非常耗时的,可能会导致 Redis 在一些毫秒级内不能响应客户端的请求。 - -#### AOF - -AOF 持久化方式记录每次对服务器执行的写操作。当服务器重启的时候会重新执行这些命令来恢复原始的数据。 - -#### AOF 的原理 - -- Redis 创建一个子进程。 -- 子进程开始将新 AOF 文件的内容写入到临时文件。 -- 对于所有新执行的写入命令,父进程一边将它们累积到一个内存缓存中,一边将这些改动追加到现有 AOF 文件的末尾,这样样即使在重写的中途发生停机,现有的 AOF 文件也还是安全的。 -- 当子进程完成重写工作时,它给父进程发送一个信号,父进程在接收到信号之后,将内存缓存中的所有数据追加到新 AOF 文件的末尾。 -- 搞定!现在 Redis 原子地用新文件替换旧文件,之后所有命令都会直接追加到新 AOF 文件的末尾。 - -#### AOF 的优点 - -- 使用默认的每秒 fsync 策略,Redis 的性能依然很好(fsync 是由后台线程进行处理的,主线程会尽力处理客户端请求),一旦出现故障,使用 AOF ,你最多丢失 1 秒的数据。 -- AOF 文件是一个只进行追加的日志文件,所以不需要写入 seek,即使由于某些原因(磁盘空间已满,写的过程中宕机等等)未执行完整的写入命令,你也也可使用 redis-check-aof 工具修复这些问题。 -- Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写:重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。整个重写操作是绝对安全的。 -- AOF 文件有序地保存了对数据库执行的所有写入操作,这些写入操作以 Redis 协议的格式保存。因此 AOF 文件的内容非常容易被人读懂,对文件进行分析(parse)也很轻松。 - -#### AOF 的缺点 - -- 对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。 -- 根据所使用的 fsync 策略,AOF 的速度可能会慢于快照。在一般情况下,每秒 fsync 的性能依然非常高,而关闭 fsync 可以让 AOF 的速度和快照一样快,即使在高负荷之下也是如此。不过在处理巨大的写入载入时,快照可以提供更有保证的最大延迟时间(latency)。 - -### 1.7. Redis 过期策略有哪些? - -- **noeviction** - 当内存使用达到阈值的时候,所有引起申请内存的命令会报错。 -- **allkeys-lru** - 在主键空间中,优先移除最近未使用的 key。 -- **allkeys-random** - 在主键空间中,随机移除某个 key。 -- **volatile-lru** - 在设置了过期时间的键空间中,优先移除最近未使用的 key。 -- **volatile-random** - 在设置了过期时间的键空间中,随机移除某个 key。 -- **volatile-ttl** - 在设置了过期时间的键空间中,具有更早过期时间的 key 优先移除。 - -### 1.8. Redis 和 Memcached 有什么区别? - -两者都是非关系型内存键值数据库。有以下主要不同: - -**数据类型** - -- Memcached 仅支持字符串类型; -- 而 Redis 支持五种不同种类的数据类型,使得它可以更灵活地解决问题。 - -**数据持久化** - -- Memcached 不支持持久化; -- Redis 支持两种持久化策略:RDB 快照和 AOF 日志。 - -**分布式** - -- Memcached 不支持分布式,只能通过在客户端使用像一致性哈希这样的分布式算法来实现分布式存储,这种方式在存储和查询时都需要先在客户端计算一次数据所在的节点。 -- Redis Cluster 实现了分布式的支持。 - -**内存管理机制** - -- Memcached 将内存分割成特定长度的块来存储数据,以完全解决内存碎片的问题,但是这种方式会使得内存的利用率不高,例如块的大小为 128 bytes,只存储 100 bytes 的数据,那么剩下的 28 bytes 就浪费掉了。 -- 在 Redis 中,并不是所有数据都一直存储在内存中,可以将一些很久没用的 value 交换到磁盘。而 Memcached 的数据则会一直在内存中。 - -### 1.9. 为什么单线程的 Redis 性能反而优于多线程的 Memcached? - -Redis 快速的原因: - -1. 绝大部分请求是纯粹的内存操作(非常快速) -2. 采用单线程,避免了不必要的上下文切换和竞争条件 -3. 非阻塞 IO - -内部实现采用 epoll,采用了 epoll+自己实现的简单的事件框架。epoll 中的读、写、关闭、连接都转化成了事件,然后利用 epoll 的多路复用特性,绝不在 io 上浪费一点时间。 - -## 2. 分布式消息队列(MQ) - -### 2.1. 为什么使用 MQ? - -- 异步处理 - 相比于传统的串行、并行方式,提高了系统吞吐量。 -- 应用解耦 - 系统间通过消息通信,不用关心其他系统的处理。 -- 流量削锋 - 可以通过消息队列长度控制请求量;可以缓解短时间内的高并发请求。 -- 日志处理 - 解决大量日志传输。 -- 消息通讯 - 消息队列一般都内置了高效的通信机制,因此也可以用在纯的消息通讯。比如实现点对点消息队列,或者聊天室等。 - -### 2.2. 如何保证 MQ 的高可用? - -#### 数据复制 - -1. 将所有 Broker 和待分配的 Partition 排序 -2. 将第 i 个 Partition 分配到第(i mod n)个 Broker 上 -3. 将第 i 个 Partition 的第 j 个 Replica 分配到第((i + j) mode n)个 Broker 上 - -#### 选举主服务器 - -### 2.3. MQ 有哪些常见问题?如何解决这些问题? - -MQ 的常见问题有: - -1. 消息的顺序问题 -2. 消息的重复问题 - -#### 消息的顺序问题 - -消息有序指的是可以按照消息的发送顺序来消费。 - -假如生产者产生了 2 条消息:M1、M2,假定 M1 发送到 S1,M2 发送到 S2,如果要保证 M1 先于 M2 被消费,怎么做? - -
    - -解决方案: - -(1)保证生产者 - MQServer - 消费者是一对一对一的关系 - -
    - -缺陷: - -- 并行度就会成为消息系统的瓶颈(吞吐量不够) -- 更多的异常处理,比如:只要消费端出现问题,就会导致整个处理流程阻塞,我们不得不花费更多的精力来解决阻塞的问题。 - -(2)通过合理的设计或者将问题分解来规避。 - -- 不关注乱序的应用实际大量存在 -- 队列无序并不意味着消息无序 - -所以从业务层面来保证消息的顺序而不仅仅是依赖于消息系统,是一种更合理的方式。 - -#### 消息的重复问题 - -造成消息重复的根本原因是:网络不可达。 - -所以解决这个问题的办法就是绕过这个问题。那么问题就变成了:如果消费端收到两条一样的消息,应该怎样处理? - -消费端处理消息的业务逻辑保持幂等性。只要保持幂等性,不管来多少条重复消息,最后处理的结果都一样。 -保证每条消息都有唯一编号且保证消息处理成功与去重表的日志同时出现。利用一张日志表来记录已经处理成功的消息的 ID,如果新到的消息 ID 已经在日志表中,那么就不再处理这条消息。 - -### 2.4. Kafka, ActiveMQ, RabbitMQ, RocketMQ 各有什么优缺点? - -
    - -## 3. 分布式服务(RPC) - -### 3.1. Dubbo 的实现过程? - -
    - -
    - -节点角色: - -| 节点 | 角色说明 | -| --------- | -------------------------------------- | -| Provider | 暴露服务的服务提供方 | -| Consumer | 调用远程服务的服务消费方 | -| Registry | 服务注册与发现的注册中心 | -| Monitor | 统计服务的调用次数和调用时间的监控中心 | -| Container | 服务运行容器 | - -调用关系: - -1. 务容器负责启动,加载,运行服务提供者。 -2. 服务提供者在启动时,向注册中心注册自己提供的服务。 -3. 服务消费者在启动时,向注册中心订阅自己所需的服务。 -4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。 -5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。 -6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。 - -### 3.2. Dubbo 负载均衡策略有哪些? - -##### Random - -- 随机,按权重设置随机概率。 -- 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。 - -##### RoundRobin - -- 轮循,按公约后的权重设置轮循比率。 -- 存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。 - -##### LeastActive - -- 最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。 -- 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。 - -##### ConsistentHash - -- 一致性 Hash,相同参数的请求总是发到同一提供者。 -- 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。 -- 算法参见: -- 缺省只对第一个参数 Hash,如果要修改,请配置 `` -- 缺省用 160 份虚拟节点,如果要修改,请配置 `` - -### 3.3. Dubbo 集群容错策略 ? - -
    - -- **Failover** - 失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries="2" 来设置重试次数(不含第一次)。 -- **Failfast** - 快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。 -- **Failsafe** - 失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。 -- **Failback** - 失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。 -- **Forking** - 并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2" 来设置最大并行数。 -- **Broadcast** - 播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。 - -### 3.4. 动态代理策略? - -Dubbo 作为 RPC 框架,首先要完成的就是跨系统,跨网络的服务调用。消费方与提供方遵循统一的接口定义,消费方调用接口时,Dubbo 将其转换成统一格式的数据结构,通过网络传输,提供方根据规则找到接口实现,通过反射完成调用。也就是说,消费方获取的是对远程服务的一个代理(Proxy),而提供方因为要支持不同的接口实现,需要一个包装层(Wrapper)。调用的过程大概是这样: - -
    - -消费方的 Proxy 和提供方的 Wrapper 得以让 Dubbo 构建出复杂、统一的体系。而这种动态代理与包装也是通过基于 SPI 的插件方式实现的,它的接口就是**ProxyFactory**。 - -``` -@SPI("javassist") -public interface ProxyFactory { - - @Adaptive({Constants.PROXY_KEY}) - T getProxy(Invoker invoker) throws RpcException; - - @Adaptive({Constants.PROXY_KEY}) - Invoker getInvoker(T proxy, Class type, URL url) throws RpcException; - -} -``` - -ProxyFactory 有两种实现方式,一种是基于 JDK 的代理实现,一种是基于 javassist 的实现。ProxyFactory 接口上定义了@SPI("javassist"),默认为 javassist 的实现。 - -### 3.5. Dubbo 支持哪些序列化协议?Hessian?Hessian 的数据结构? - -1. dubbo 序列化,阿里尚不成熟的 java 序列化实现。 -2. hessian2 序列化:hessian 是一种跨语言的高效二进制的序列化方式,但这里实际不是原生的 hessian2 序列化,而是阿里修改过的 hessian lite,它是 dubbo RPC 默认启用的序列化方式。 -3. json 序列化:目前有两种实现,一种是采用的阿里的 fastjson 库,另一种是采用 dubbo 中自已实现的简单 json 库,一般情况下,json 这种文本序列化性能不如二进制序列化。 -4. java 序列化:主要是采用 JDK 自带的 java 序列化实现,性能很不理想。 -5. Kryo 和 FST:Kryo 和 FST 的性能依然普遍优于 hessian 和 dubbo 序列化。 - -Hessian 序列化与 Java 默认的序列化区别? - -Hessian 是一个轻量级的 remoting on http 工具,采用的是 Binary RPC 协议,所以它很适合于发送二进制数据,同时又具有防火墙穿透能力。 - -1. Hessian 支持跨语言串行 -2. 比 java 序列化具有更好的性能和易用性 -3. 支持的语言比较多 - -### 3.6. Protoco Buffer 是什么? - -Protocol Buffer 是 Google 出品的一种轻量 & 高效的结构化数据存储格式,性能比 Json、XML 真的强!太!多! - -Protocol Buffer 的序列化 & 反序列化简单 & 速度快的原因是: - -1. 编码 / 解码 方式简单(只需要简单的数学运算 = 位移等等) -2. 采用 Protocol Buffer 自身的框架代码 和 编译器 共同完成 - -Protocol Buffer 的数据压缩效果好(即序列化后的数据量体积小)的原因是: - -1. 采用了独特的编码方式,如 Varint、Zigzag 编码方式等等 -2. 采用 T - L - V 的数据存储方式:减少了分隔符的使用 & 数据存储得紧凑 - -### 3.7. 注册中心挂了可以继续通信吗? - -可以。Dubbo 消费者在应用启动时会从注册中心拉取已注册的生产者的地址接口,并缓存在本地。每次调用时,按照本地存储的地址进行调用。 - -### 3.8. ZooKeeper 原理是什么?ZooKeeper 有什么用? - -ZooKeeper 是一个分布式应用协调系统,已经用到了许多分布式项目中,用来完成统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等工作。 - -
    - -
    - -1. 每个 Server 在内存中存储了一份数据; -2. Zookeeper 启动时,将从实例中选举一个 leader(Paxos 协议); -3. Leader 负责处理数据更新等操作(Zab 协议); -4. 一个更新操作成功,当且仅当大多数 Server 在内存中成功修改数据。 - -### 3.9. Netty 有什么用?NIO/BIO/AIO 有什么用?有什么区别? - -Netty 是一个“网络通讯框架”。 - -Netty 进行事件处理的流程。`Channel`是连接的通道,是 ChannelEvent 的产生者,而`ChannelPipeline`可以理解为 ChannelHandler 的集合。 - -
    - -> 参考:https://github.com/code4craft/netty-learning/blob/master/posts/ch1-overview.md - -IO 的方式通常分为几种: - -- 同步阻塞的 BIO -- 同步非阻塞的 NIO -- 异步非阻塞的 AIO - -在使用同步 I/O 的网络应用中,如果要同时处理多个客户端请求,或是在客户端要同时和多个服务器进行通讯,就必须使用多线程来处理。 - -NIO 基于 Reactor,当 socket 有流可读或可写入 socket 时,操作系统会相应的通知引用程序进行处理,应用再将流读取到缓冲区或写入操作系统。也就是说,这个时候,已经不是一个连接就要对应一个处理线程了,而是有效的请求,对应一个线程,当连接没有数据时,是没有工作线程来处理的。 - -与 NIO 不同,当进行读写操作时,只须直接调用 API 的 read 或 write 方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入 read 方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将 write 方法传递的流写入完毕时,操作系统主动通知应用程序。 即可以理解为,read/write 方法都是异步的,完成后会主动调用回调函数。 - -> 参考:https://blog.csdn.net/skiof007/article/details/52873421 - -### 3.10. 为什么要进行系统拆分?拆分不用 Dubbo 可以吗? - -系统拆分从资源角度分为:应用拆分和数据库拆分。 - -从采用的先后顺序可分为:水平扩展、垂直拆分、业务拆分、水平拆分。 - -
    - -是否使用服务依据实际业务场景来决定。 - -当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。 - -当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。 - -### 3.11. Dubbo 和 Thrift 有什么区别? - -- Thrift 是跨语言的 RPC 框架。 -- Dubbo 支持服务治理,而 Thrift 不支持。 diff --git a/docs/theory/distributed-lock-theory.md b/docs/theory/distributed-lock-theory.md deleted file mode 100644 index d45370fb..00000000 --- a/docs/theory/distributed-lock-theory.md +++ /dev/null @@ -1,450 +0,0 @@ -# 分布式锁基本原理 - -Java 原生 API 虽然有并发锁,但并没有提供分布式锁的能力,所以针对分布式场景中的锁需要解决的方案。 - -分布式锁的解决方案大致有以下几种: - -- 基于数据库实现 -- 基于缓存(redis,memcached 等)实现 -- 基于 Zookeeper 实现 ✅ - -> 注:推荐基于 ZooKeeper 实现分布式锁,具体原因看完本文即可明了。 - -## 分布式锁思路 - -分布式锁的总体思路大同小异,仅在实现细节上有所不同。 - -分布式锁的主要思路如下: - -1. **互斥** - 创建锁必须是唯一的,表现形式为向数据存储服务器或容器插入一个唯一的 key,一旦有一个线程插入这个 key,其他线程就不能再插入了。 -2. 避免永远不释放锁 - 数据库分布式锁和缓存分布式锁(Redis)的思路都是引入超时机制,即成功申请锁后,超过一定时间,锁失效(删除 key),原因在于它们无法感知申请锁的客户端节点状态。而 ZooKeeper 由于其 znode 以目录、文件形式组织,天然就存在物理空间隔离,只要 znode 存在,即表示客户端节点还在工作,所以不存在这种问题。 -3. 容错(只要大部分 redis 节点创建了这把锁就可以) - -## 数据库分布式锁 - -### 具体实现 - -(1)创建表 - -```sql -CREATE TABLE `methodLock` ( - `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', - `method_name` varchar(64) NOT NULL DEFAULT '' COMMENT '锁定的方法名', - `desc` varchar(1024) NOT NULL DEFAULT '备注信息', - `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '保存数据时间,自动生成', - PRIMARY KEY (`id`), - UNIQUE KEY `uidx_method_name` (`method_name `) USING BTREE -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='锁定中的方法'; -``` - -(2)获取锁 - -想要锁住某个方法时,执行以下 SQL: - -```sql -insert into methodLock(method_name,desc) values (‘method_name’,‘desc’) -``` - -因为我们对 `method_name` 做了唯一性约束,这里如果有多个请求同时提交到数据库的话,数据库会保证只有一个操作可以成功,那么我们就可以认为操作成功的那个线程获得了该方法的锁,可以执行方法体内容。 - -成功插入则获取锁。 - -(3)释放锁 - -当方法执行完毕之后,想要释放锁的话,需要执行以下 Sql: - -```sql -delete from methodLock where method_name ='method_name' -``` - -### 问题 - -- 这把锁强依赖数据库的可用性。如果数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用。 -- 这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在数据库中,其他线程无法再获得到锁。 -- 这把锁只能是非阻塞的,因为数据的 insert 操作,一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁操作。 -- 这把锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁。因为数据中数据已经存在了。 - -### 解决办法 - -- 单点问题可以用多数据库实例,同时塞 N 个表,N/2+1 个成功就任务锁定成功 -- 写一个定时任务,隔一段时间清除一次过期的数据。 -- 写一个 while 循环,不断的重试插入,直到成功。 -- 在数据库表中加个字段,记录当前获得锁的机器的主机信息和线程信息,那么下次再获取锁的时候先查询数据库,如果当前机器的主机信息和线程信息在数据库可以查到的话,直接把锁分配给他就可以了。 - -### 小结 - -- 优点: 直接借助数据库,容易理解。 -- 缺点: 会有各种各样的问题,在解决问题的过程中会使整个方案变得越来越复杂。操作数据库需要一定的开销,性能问题需要考虑。 - -## Redis 分布式锁 - -相比于用数据库来实现分布式锁,基于缓存实现的分布式锁的性能会更好一些。目前有很多成熟的分布式产品,包括 Redis、memcache、Tair 等。这里以 Redis 举例。 - -### 实现思路 - -这个分布式锁有 3 个重要的考量点: - -1. 互斥(只能有一个客户端获取锁) -2. 不能死锁 -3. 容错(只要大部分 redis 节点创建了这把锁就可以) - -对应的 Redis 指令如下: - -- `setnx` - `setnx key val`:当且仅当 key 不存在时,set 一个 key 为 val 的字符串,返回 1;若 key 存在,则什么都不做,返回 0。 -- `expire` - `expire key timeout`:为 key 设置一个超时时间,单位为 second,超过这个时间锁会自动释放,避免死锁。 -- `delete` - `delete key`:删除 key - -### 具体实现 - -(1)申请锁 - -``` -SET resource_name my_random_value NX PX 30000 -``` - -执行这个命令就 ok。 - -- `NX`:表示只有 `key` 不存在的时候才会设置成功。(如果此时 redis 中存在这个 key,那么设置失败,返回 `nil`) -- `PX 30000`:意思是 30s 后锁自动释放。别人创建的时候如果发现已经有了就不能加锁了。 - -(2)释放锁 - -释放锁就是删除 key ,但是一般可以用 `lua` 脚本删除,判断 value 一样才删除: - -```python --- 删除锁的时候,找到 key 对应的 value,跟自己传过去的 value 做比较,如果是一样的才删除。 -if redis.call("get",KEYS[1]) == ARGV[1] then - return redis.call("del",KEYS[1]) -else - return 0 -end -``` - -### 小结 - -为啥要用 `random_value` 随机值呢?因为如果某个客户端获取到了锁,但是阻塞了很长时间才执行完,比如说超过了 30s,此时可能已经自动释放锁了,此时可能别的客户端已经获取到了这个锁,要是你这个时候直接删除 key 的话会有问题,所以得用随机值加上面的 `lua` 脚本来释放锁。 - -但是这样是肯定不行的。因为如果是普通的 redis 单实例,那就是单点故障。或者是 redis 普通主从,那 redis 主从异步复制,如果主节点挂了(key 就没有了),key 还没同步到从节点,此时从节点切换为主节点,别人就可以 set key,从而拿到锁。 - -### RedLock 算法 - -这个场景是假设有一个 redis cluster,有 5 个 redis master 实例。然后执行如下步骤获取一把锁: - -1. 获取当前时间戳,单位是毫秒; -2. 跟上面类似,轮流尝试在每个 master 节点上创建锁,过期时间较短,一般就几十毫秒; -3. 尝试在**大多数节点**上建立一个锁,比如 5 个节点就要求是 3 个节点 `n / 2 + 1`; -4. 客户端计算建立好锁的时间,如果建立锁的时间小于超时时间,就算建立成功了; -5. 要是锁建立失败了,那么就依次之前建立过的锁删除; -6. 只要别人建立了一把分布式锁,你就得**不断轮询去尝试获取锁**。 - -[Redis 官方](https://redis.io/)给出了以上两种基于 Redis 实现分布式锁的方法,详细说明可以查看: 。 - -## ZooKeeper 分布式锁 - -### 实现思路 - -这也是 ZooKeeper 客户端 curator 的分布式锁实现。 - -1. 创建一个目录 mylock; -2. 线程 A 想获取锁就在 mylock 目录下创建临时顺序节点; -3. 获取 mylock 目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁; -4. 线程 B 获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点; -5. 线程 A 处理完,删除自己的节点,线程 B 监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。 - -### 具体实现 - -```java -/** - * ZooKeeperSession - * - * @author bingo - * @since 2018/11/29 - * - */ -public class ZooKeeperSession { - - private static CountDownLatch connectedSemaphore = new CountDownLatch(1); - - private ZooKeeper zookeeper; - private CountDownLatch latch; - - public ZooKeeperSession() { - try { - this.zookeeper = new ZooKeeper("192.168.31.187:2181,192.168.31.19:2181,192.168.31.227:2181", 50000, new ZooKeeperWatcher()); - try { - connectedSemaphore.await(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - System.out.println("ZooKeeper session established......"); - } catch (Exception e) { - e.printStackTrace(); - } - } - - /** - * 获取分布式锁 - * - * @param productId - */ - public Boolean acquireDistributedLock(Long productId) { - String path = "/product-lock-" + productId; - - try { - zookeeper.create(path, "".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL); - return true; - } catch (Exception e) { - while (true) { - try { - // 相当于是给node注册一个监听器,去看看这个监听器是否存在 - Stat stat = zk.exists(path, true); - - if (stat != null) { - this.latch = new CountDownLatch(1); - this.latch.await(waitTime, TimeUnit.MILLISECONDS); - this.latch = null; - } - zookeeper.create(path, "".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL); - return true; - } catch (Exception ee) { - continue; - } - } - - } - return true; - } - - /** - * 释放掉一个分布式锁 - * - * @param productId - */ - public void releaseDistributedLock(Long productId) { - String path = "/product-lock-" + productId; - try { - zookeeper.delete(path, -1); - System.out.println("release the lock for product[id=" + productId + "]......"); - } catch (Exception e) { - e.printStackTrace(); - } - } - - /** - * 建立zk session的watcher - * - * @author bingo - * @since 2018/11/29 - * - */ - private class ZooKeeperWatcher implements Watcher { - - public void process(WatchedEvent event) { - System.out.println("Receive watched event: " + event.getState()); - - if (KeeperState.SyncConnected == event.getState()) { - connectedSemaphore.countDown(); - } - - if (this.latch != null) { - this.latch.countDown(); - } - } - - } - - /** - * 封装单例的静态内部类 - * - * @author bingo - * @since 2018/11/29 - * - */ - private static class Singleton { - - private static ZooKeeperSession instance; - - static { - instance = new ZooKeeperSession(); - } - - public static ZooKeeperSession getInstance() { - return instance; - } - - } - - /** - * 获取单例 - * - * @return - */ - public static ZooKeeperSession getInstance() { - return Singleton.getInstance(); - } - - /** - * 初始化单例的便捷方法 - */ - public static void init() { - getInstance(); - } - -} -``` - -也可以采用另一种方式,创建临时顺序节点: - -如果有一把锁,被多个人给竞争,此时多个人会排队,第一个拿到锁的人会执行,然后释放锁;后面的每个人都会去监听**排在自己前面**的那个人创建的 node 上,一旦某个人释放了锁,排在自己后面的人就会被 zookeeper 给通知,一旦被通知了之后,就 ok 了,自己就获取到了锁,就可以执行代码了。 - -```java -public class ZooKeeperDistributedLock implements Watcher { - - private ZooKeeper zk; - private String locksRoot = "/locks"; - private String productId; - private String waitNode; - private String lockNode; - private CountDownLatch latch; - private CountDownLatch connectedLatch = new CountDownLatch(1); - private int sessionTimeout = 30000; - - public ZooKeeperDistributedLock(String productId) { - this.productId = productId; - try { - String address = "192.168.31.187:2181,192.168.31.19:2181,192.168.31.227:2181"; - zk = new ZooKeeper(address, sessionTimeout, this); - connectedLatch.await(); - } catch (IOException e) { - throw new LockException(e); - } catch (KeeperException e) { - throw new LockException(e); - } catch (InterruptedException e) { - throw new LockException(e); - } - } - - public void process(WatchedEvent event) { - if (event.getState() == KeeperState.SyncConnected) { - connectedLatch.countDown(); - return; - } - - if (this.latch != null) { - this.latch.countDown(); - } - } - - public void acquireDistributedLock() { - try { - if (this.tryLock()) { - return; - } else { - waitForLock(waitNode, sessionTimeout); - } - } catch (KeeperException e) { - throw new LockException(e); - } catch (InterruptedException e) { - throw new LockException(e); - } - } - - public boolean tryLock() { - try { - // 传入进去的locksRoot + “/” + productId - // 假设productId代表了一个商品id,比如说1 - // locksRoot = locks - // /locks/10000000000,/locks/10000000001,/locks/10000000002 - lockNode = zk.create(locksRoot + "/" + productId, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); - - // 看看刚创建的节点是不是最小的节点 - // locks:10000000000,10000000001,10000000002 - List locks = zk.getChildren(locksRoot, false); - Collections.sort(locks); - - if(lockNode.equals(locksRoot+"/"+ locks.get(0))){ - //如果是最小的节点,则表示取得锁 - return true; - } - - //如果不是最小的节点,找到比自己小1的节点 - int previousLockIndex = -1; - for(int i = 0; i < locks.size(); i++) { - if(lockNode.equals(locksRoot + “/” + locks.get(i))) { - previousLockIndex = i - 1; - break; - } - } - - this.waitNode = locks.get(previousLockIndex); - } catch (KeeperException e) { - throw new LockException(e); - } catch (InterruptedException e) { - throw new LockException(e); - } - return false; - } - - private boolean waitForLock(String waitNode, long waitTime) throws InterruptedException, KeeperException { - Stat stat = zk.exists(locksRoot + "/" + waitNode, true); - if (stat != null) { - this.latch = new CountDownLatch(1); - this.latch.await(waitTime, TimeUnit.MILLISECONDS); - this.latch = null; - } - return true; - } - - public void unlock() { - try { - // 删除/locks/10000000000节点 - // 删除/locks/10000000001节点 - System.out.println("unlock " + lockNode); - zk.delete(lockNode, -1); - lockNode = null; - zk.close(); - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (KeeperException e) { - e.printStackTrace(); - } - } - - public class LockException extends RuntimeException { - private static final long serialVersionUID = 1L; - - public LockException(String e) { - super(e); - } - - public LockException(Exception e) { - super(e); - } - } -} -``` - -### 小结 - -ZooKeeper 版本的分布式锁问题相对比较来说少。 - -- 锁的占用时间限制:redis 就有占用时间限制,而 ZooKeeper 则没有,最主要的原因是 redis 目前没有办法知道已经获取锁的客户端的状态,是已经挂了呢还是正在执行耗时较长的业务逻辑。而 ZooKeeper 通过临时节点就能清晰知道,如果临时节点存在说明还在执行业务逻辑,如果临时节点不存在说明已经执行完毕释放锁或者是挂了。由此看来 redis 如果能像 ZooKeeper 一样添加一些与客户端绑定的临时键,也是一大好事。 -- 是否单点故障:redis 本身有很多中玩法,如客户端一致性 hash,服务器端 sentinel 方案或者 cluster 方案,很难做到一种分布式锁方式能应对所有这些方案。而 ZooKeeper 只有一种玩法,多台机器的节点数据是一致的,没有 redis 的那么多的麻烦因素要考虑。 - -总体上来说 ZooKeeper 实现分布式锁更加的简单,可靠性更高。但 ZooKeeper 因为需要频繁的创建和删除节点,性能上不如 Redis 方式。 - -## 分布式锁方案对比 - -- 数据库分布式锁,问题比较多,解决起来比较麻烦,不推荐 - -- redis 分布式锁,其实**需要自己不断去尝试获取锁**,比较消耗性能。 -- zk 分布式锁,获取不到锁,注册个监听器即可,不需要不断主动尝试获取锁,性能开销较小。 - -另外一点就是,如果是 redis 获取锁的那个客户端出现 bug 挂了,那么只能等待超时时间之后才能释放锁;而 zk 的话,因为创建的是临时 znode,只要客户端挂了,znode 就没了,此时就自动释放锁。 - -总体上来说,ZooKeeper 实现分布式锁更加的简单,可靠性更高。 - -## 参考资料 - -- [分布式锁实现汇总](https://juejin.im/post/5a20cd8bf265da43163cdd9a) diff --git a/docs/theory/distributed-session-theory.md b/docs/theory/distributed-session-theory.md deleted file mode 100644 index 2faad12a..00000000 --- a/docs/theory/distributed-session-theory.md +++ /dev/null @@ -1,228 +0,0 @@ -# 分布式会话基本原理 - -> 由于 Http 是一种无状态的协议,服务器单单从网络连接上无从知道客户身份。 -> -> 会话跟踪是 Web 程序中常用的技术,用来跟踪用户的整个会话。常用会话跟踪技术是 Cookie 与 Session。 - -## 简介 - -### 什么是 Cookie - -Cookie 实际上是存储在用户浏览器上的文本信息,并保留了各种跟踪的信息。 - -Cookie 工作步骤: - -1. 客户端请求服务器,如果服务器需要记录该用户的状态,就是用 response 向客户端浏览器颁发一个 Cookie。 -2. 客户端浏览器会把 Cookie 保存下来。 -3. 当浏览器再请求该网站时,浏览器把该请求的网址连同 Cookie 一同提交给服务器。服务器检查该 Cookie,以此来辨认用户状态。 - -Cookie 主要用于以下三个方面: - -- 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息) -- 个性化设置(如用户自定义设置、主题等) -- 浏览器行为跟踪(如跟踪分析用户行为等) - -**_注:Cookie 功能需要浏览器的支持,如果浏览器不支持 Cookie 或者 Cookie 禁用了,Cookie 功能就会失效。_** - -### 什么是 Session - -Session 代表着服务器和客户端一次会话的过程。Session 对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当客户端关闭会话,或者 Session 超时失效时会话结束。 - -### Cookie 和 Session 的区别 - -- **作用范围不同**,Cookie 保存在客户端(浏览器),Session 保存在服务器端。 -- **存取方式的不同**,Cookie 只能保存 ASCII,Session 可以存任意数据类型,一般情况下我们可以在 Session 中保持一些常用变量信息,比如说 UserId 等。 -- **有效期不同**,Cookie 可设置为长时间保持,比如我们经常使用的默认登录功能,Session 一般失效时间较短,客户端关闭或者 Session 超时都会失效。 -- **隐私策略不同**,Cookie 存储在客户端,比较容易遭到不法获取,早期有人将用户的登录名和密码存储在 Cookie 中导致信息被窃取;Session 存储在服务端,安全性相对 Cookie 要好一些。 -- **存储大小不同**, 单个 Cookie 保存的数据不能超过 4K,Session 可存储数据远高于 Cookie。 - -### 为什么需要 Cookie 和 Session,他们有什么关联? - -说起来为什么需要 Cookie ,这就需要从浏览器开始说起,我们都知道浏览器是没有状态的(HTTP 协议无状态),这意味着浏览器并不知道是张三还是李四在和服务端打交道。这个时候就需要有一个机制来告诉服务端,本次操作用户是否登录,是哪个用户在执行的操作,那这套机制的实现就需要 Cookie 和 Session 的配合。 - -那么 Cookie 和 Session 是如何配合的呢?我画了一张图大家可以先了解下。 - -![img](https://user-gold-cdn.xitu.io/2019/5/13/16aafb5d90f398e2?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) - -用户第一次请求服务器的时候,服务器根据用户提交的相关信息,创建创建对应的 Session ,请求返回时将此 Session 的唯一标识信息 SessionID 返回给浏览器,浏览器接收到服务器返回的 SessionID 信息后,会将此信息存入到 Cookie 中,同时 Cookie 记录此 SessionID 属于哪个域名。 - -当用户第二次访问服务器的时候,请求会自动判断此域名下是否存在 Cookie 信息,如果存在自动将 Cookie 信息也发送给服务端,服务端会从 Cookie 中获取 SessionID,再根据 SessionID 查找对应的 Session 信息,如果没有找到说明用户没有登录或者登录失效,如果找到 Session 证明用户已经登录可执行后面操作。 - -根据以上流程可知,SessionID 是连接 Cookie 和 Session 的一道桥梁,大部分系统也是根据此原理来验证用户登录状态。 - -### 如果浏览器禁用 Cookie 怎么办 - -既然服务端是根据 Cookie 中的信息判断用户是否登录,那么如果浏览器中禁止了 Cookie,如何保障整个机制的正常运转。 - -第一种方案,每次请求中都携带一个 SessionID 的参数,也可以 Post 的方式提交,也可以在请求的地址后面拼接 `xxx?SessionID=123456...`。 - -第二种方案,Token 机制。Token 机制多用于 App 客户端和服务器交互的模式,也可以用于 Web 端做用户状态管理。 - -Token 的意思是“令牌”,是服务端生成的一串字符串,作为客户端进行请求的一个标识。Token 机制和 Cookie 和 Session 的使用机制比较类似。 - -当用户第一次登录后,服务器根据提交的用户信息生成一个 Token,响应时将 Token 返回给客户端,以后客户端只需带上这个 Token 前来请求数据即可,无需再次登录验证。 - -## 分布式 Session - -在分布式场景下,一个用户的 Session 如果只存储在一个服务器上,那么当负载均衡器把用户的下一个请求转发到另一个服务器上,该服务器没有用户的 Session,就可能导致用户需要重新进行登录等操作。 - -分布式 Session 的几种实现策略: - -1. 粘性 session -2. 应用服务器间的 session 复制共享 -3. 基于缓存的 session 共享 ✅ - -> 推荐:基于缓存的 session 共享 - -### 粘性 Session - -> 粘性 Session(Sticky Sessions)需要配置负载均衡器,使得一个用户的所有请求都路由到一个服务器节点上,这样就可以把用户的 Session 存放在该服务器节点中。 -> -> 缺点:当服务器节点宕机时,将丢失该服务器节点上的所有 Session。 - -
    - -
    - -### session 复制共享 - -> Session 复制共享(Session Replication)在服务器节点之间进行 Session 同步操作,这样的话用户可以访问任何一个服务器节点。 -> -> 缺点:占用过多内存;同步过程占用网络带宽以及服务器处理器时间。 - -
    - -
    - -### 基于缓存的 session 共享 - -> 使用一个单独的存储服务器存储 Session 数据,可以存在 MySQL 数据库上,也可以存在 Redis 或者 Memcached 这种内存型数据库。 -> -> 缺点:需要去实现存取 Session 的代码。 - -
    - -
    - -## 具体实现 - -### JWT Token - -使用 JWT Token 储存用户身份,然后再从数据库或者 cache 中获取其他的信息。这样无论请求分配到哪个服务器都无所谓。 - -### tomcat + redis - -这个其实还挺方便的,就是使用 session 的代码,跟以前一样,还是基于 tomcat 原生的 session 支持即可,然后就是用一个叫做 `Tomcat RedisSessionManager` 的东西,让所有我们部署的 tomcat 都将 session 数据存储到 redis 即可。 - -在 tomcat 的配置文件中配置: - -```xml - - - -``` - -然后指定 redis 的 host 和 port 就 ok 了。 - -```xml - - -``` - -还可以用上面这种方式基于 redis 哨兵支持的 redis 高可用集群来保存 session 数据,都是 ok 的。 - -### spring session + redis - -上面那种 tomcat + redis 的方式好用,但是会**严重依赖于 web 容器**,不好将代码移植到其他 web 容器上去,尤其是你要是换了技术栈咋整?比如换成了 spring cloud 或者是 spring boot 之类的呢? - -所以现在比较好的还是基于 Java 一站式解决方案,也就是 spring。人家 spring 基本上承包了大部分我们需要使用的框架,spirng cloud 做微服务,spring boot 做脚手架,所以用 [sping session](https://github.com/spring-projects/spring-session) 是一个很好的选择。 - -在 pom.xml 中配置: - -```xml - - org.springframework.session - spring-session-data-redis - 1.2.1.RELEASE - - - redis.clients - jedis - 2.8.1 - -``` - -在 spring 配置文件中配置: - -```xml - - - - - - - - - - - - - - - - - -``` - -在 web.xml 中配置: - -```xml - - springSessionRepositoryFilter - org.springframework.web.filter.DelegatingFilterProxy - - - springSessionRepositoryFilter - /* - -``` - -示例代码: - -```java -@RestController -@RequestMapping("/test") -public class TestController { - - @RequestMapping("/putIntoSession") - public String putIntoSession(HttpServletRequest request, String username) { - request.getSession().setAttribute("name", "leo"); - return "ok"; - } - - @RequestMapping("/getFromSession") - public String getFromSession(HttpServletRequest request, Model model){ - String name = request.getSession().getAttribute("name"); - return name; - } -} -``` - -上面的代码就是 ok 的,给 sping session 配置基于 redis 来存储 session 数据,然后配置了一个 spring session 的过滤器,这样的话,session 相关操作都会交给 spring session 来管了。接着在代码中,就用原生的 session 操作,就是直接基于 spring sesion 从 redis 中获取数据了。 - -实现分布式的会话有很多种方式,我说的只不过是比较常见的几种方式,tomcat + redis 早期比较常用,但是会重耦合到 tomcat 中;近些年,通过 spring session 来实现。 - -## 参考资料 - -- [集群/分布式环境 Session 的几种策略](https://github.com/L316476844/distributed-session) -- [你真的了解 Cookie 和 Session 吗](https://juejin.im/post/5cd9037ee51d456e5c5babca) -- [聊一聊 session 和 cookie](https://juejin.im/post/5aede266f265da0ba266e0ef) diff --git a/docs/theory/distributed-storage-theory.md b/docs/theory/distributed-storage-theory.md deleted file mode 100644 index 71003e86..00000000 --- a/docs/theory/distributed-storage-theory.md +++ /dev/null @@ -1,82 +0,0 @@ ---- -title: 分布式存储 -categories: ['design', 'distributed'] -tags: ['design', 'distributed'] -date: 2019-08-21 11:30 ---- - -# 分布式存储 - -## 分区规则 - -**分布式数据库** 首先要解决把 **整个数据集** 按照 **分区规则** 映射到 **多个节点** 的问题,即把 **数据集** 划分到 **多个节点** 上,每个节点负责 **整体数据** 的一个 **子集**。 - -
    - -
    - -数据分布通常有 **哈希分区** 和 **顺序分区** 两种方式,对比如下: - -| 分区方式 | 特点 | 相关产品 | -| :------- | :----------------------------------------------- | :------------------------------- | -| 哈希分区 | 离散程度好,数据分布与业务无关,无法顺序访问 | Redis Cluster,Cassandra,Dynamo | -| 顺序分区 | 离散程度易倾斜,数据分布与业务相关,可以顺序访问 | BigTable,HBase,Hypertable | - -由于 `Redis Cluster` 采用 **哈希分区规则**,这里重点讨论 **哈希分区**。常见的 **哈希分区** 规则有几种,下面分别介绍: - -### 节点取余分区 - -使用特定的数据,如 `Redis` 的 **键** 或 **用户** `ID`,再根据 **节点数量** `N` 使用公式:`hash(key)% N` 计算出 **哈希值**,用来决定数据 **映射** 到哪一个节点上。 - -
    - -
    - -- **优点** - -这种方式的突出优点是 **简单性**,常用于 **数据库** 的 **分库分表规则**。一般采用 **预分区** 的方式,提前根据 **数据量** 规划好 **分区数**,比如划分为 `512` 或 `1024` 张表,保证可支撑未来一段时间的 **数据容量**,再根据 **负载情况** 将 **表** 迁移到其他 **数据库** 中。扩容时通常采用 **翻倍扩容**,避免 **数据映射** 全部被 **打乱**,导致 **全量迁移** 的情况。 - -- **缺点** - -当 **节点数量** 变化时,如 **扩容** 或 **收缩** 节点,数据节点 **映射关系** 需要重新计算,会导致数据的 **重新迁移**。 - -### 一致性哈希分区 - -**一致性哈希** 可以很好的解决 **稳定性问题**,可以将所有的 **存储节点** 排列在 **首尾相接** 的 `Hash` 环上,每个 `key` 在计算 `Hash` 后会 **顺时针** 找到 **临接** 的 **存储节点** 存放。而当有节点 **加入** 或 **退出** 时,仅影响该节点在 `Hash` 环上 **顺时针相邻** 的 **后续节点**。 - -
    - -
    - -- **优点** - -**加入** 和 **删除** 节点只影响 **哈希环** 中 **顺时针方向** 的 **相邻的节点**,对其他节点无影响。 - -- **缺点** - -**加减节点** 会造成 **哈希环** 中部分数据 **无法命中**。当使用 **少量节点** 时,**节点变化** 将大范围影响 **哈希环** 中 **数据映射**,不适合 **少量数据节点** 的分布式方案。**普通** 的 **一致性哈希分区** 在增减节点时需要 **增加一倍** 或 **减去一半** 节点才能保证 **数据** 和 **负载的均衡**。 - -> **注意**:因为 **一致性哈希分区** 的这些缺点,一些分布式系统采用 **虚拟槽** 对 **一致性哈希** 进行改进,比如 `Dynamo` 系统。 - -### 虚拟槽分区 - -**虚拟槽分区** 巧妙地使用了 **哈希空间**,使用 **分散度良好** 的 **哈希函数** 把所有数据 **映射** 到一个 **固定范围** 的 **整数集合** 中,整数定义为 **槽**(`slot`)。这个范围一般 **远远大于** 节点数,比如 `Redis Cluster` 槽范围是 `0 ~ 16383`。**槽** 是集群内 **数据管理** 和 **迁移** 的 **基本单位**。采用 **大范围槽** 的主要目的是为了方便 **数据拆分** 和 **集群扩展**。每个节点会负责 **一定数量的槽**,如图所示: - -
    - -
    -当前集群有 `3` 个节点,每个节点平均大约负责 `5460` 个 **槽**。由于采用 **高质量** 的 **哈希算法**,每个槽所映射的数据通常比较 **均匀**,将数据平均划分到 `3` 个节点进行 **数据分区**。`Redis Cluster` 就是采用 **虚拟槽分区**。 - -集群中的每个节点负责一部分哈希槽,比如集群中有3个节点,则: - -- 节点A存储的哈希槽范围是:0 – 5460 -- 节点B存储的哈希槽范围是:5461 – 10922 -- 节点C存储的哈希槽范围是:10923 – 16383 - -这种结构很容易 **添加** 或者 **删除** 节点。如果 **增加** 一个节点 `4`,就需要从节点 `1 ~ 3` 获得部分 **槽** 分配到节点 `4` 上。如果想 **移除** 节点 `1`,需要将节点 `1` 中的 **槽** 移到节点 `2 ~ 3` 上,然后将 **没有任何槽** 的节点 `1` 从集群中 **移除** 即可。 - -> 由于从一个节点将 **哈希槽** 移动到另一个节点并不会 **停止服务**,所以无论 **添加删除** 或者 **改变** 某个节点的 **哈希槽的数量** 都不会造成 **集群不可用** 的状态. - -## 参考资料 - -- [深入剖析 Redis 系列(三) - Redis 集群模式搭建与原理详解](https://juejin.im/post/5b8fc5536fb9a05d2d01fb11) diff --git a/docs/theory/distributed-transaction-theory.md b/docs/theory/distributed-transaction-theory.md deleted file mode 100644 index 29816f68..00000000 --- a/docs/theory/distributed-transaction-theory.md +++ /dev/null @@ -1,185 +0,0 @@ -# 分布式事务基本原理 - -> **分布式事务指的是事务操作跨越多个节点,并且要求满足事务的 ACID 特性。** - -分布式事务的实现主要有以下 5 种方案: - -- XA 方案 -- TCC 方案 -- 本地消息表 -- 可靠消息最终一致性方案 -- 最大努力通知方案 - -## 两阶段提交 - -> 两阶段提交(Two-phase Commit,2PC)通过引入协调者(Coordinator)来调度参与者的行为,并最终决定这些参与者是否要真正执行事务。 -> -> 参考实现:https://github.com/changmingxie/tcc-transaction - -### 运行过程 - -#### 准备阶段 - -协调者询问参与者事务是否执行成功,参与者发回事务执行结果。 - -
    - -
    - -#### 提交阶段 - -如果事务在每个参与者上都执行成功,事务协调者发送通知让参与者提交事务;否则,协调者发送通知让参与者回滚事务。 - -
    - -
    -需要注意的是,在准备阶段,参与者执行了事务,但是还未提交。只有在提交阶段接收到协调者发来的通知后,才进行提交或者回滚。 - -### 问题 - -- **同步阻塞** - 所有事务参与者在等待其它参与者响应的时候都处于同步阻塞状态,无法进行其它操作。 -- **单点问题** - 协调者在 2PC 中起到非常大的作用,发生故障将会造成很大影响,特别是在阶段二发生故障,所有参与者会一直等待状态,无法完成其它操作。 -- **数据不一致** - 在阶段二,如果协调者只发送了部分 Commit 消息,此时网络发生异常,那么只有部分参与者接收到 Commit 消息,也就是说只有部分参与者提交了事务,使得系统数据不一致。 -- **太过保守** - 任意一个节点失败就会导致整个事务失败,没有完善的容错机制。 - -### 优缺点 - -- 优点:尽量保证了数据的强一致,适合对数据强一致要求很高的关键领域。(其实也不能 100%保证强一致 -- 缺点:实现复杂,牺牲了可用性,对性能影响较大,不适合高并发高性能场景。 - -## 补偿事务 - -> 补偿事务(TCC),全称是:`Try`、`Confirm`、`Cancel`。 -> -> 其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。它分为三个阶段: -> -> TCC 的。 -> -> - Try 阶段:这个阶段说的是对各个服务的资源做检测以及对资源进行**锁定或者预留**。 -> - Confirm 阶段:这个阶段说的是在各个服务中**执行实际的操作**。 -> - Cancel 阶段:如果任何一个服务的业务方法执行出错,那么这里就需要**进行补偿**,就是执行已经执行成功的业务逻辑的回滚操作。(把那些执行成功的回滚) - -举个例子,假设 Bob 要向 Smith 转账,思路大概是: - -1. 首先在 Try 阶段,要先调用远程接口把 Smith 和 Bob 的钱给冻结起来。 -2. 在 Confirm 阶段,执行远程调用的转账的操作,转账成功进行解冻。 -3. 如果第 2 步执行成功,那么转账成功,如果第二步执行失败,则调用远程冻结接口对应的解冻方法 (Cancel)。 - -优缺点: - -- 优点:跟 2PC 比起来,实现以及流程相对简单了一些,但数据的一致性比 2PC 也要差一些。 -- 缺点:缺点还是比较明显的,在 2,3 步中都有可能失败。TCC 属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,一些业务流程可能用 TCC 不太好定义及处理。 - -这种方案几乎很少人使用,因为**事务回滚**实际上是**严重依赖于自己写代码来回滚和补偿**了,会造成补偿代码巨大。 - -但是,也有特殊的使用场景:一般来说跟**钱**相关的,跟钱打交道的,**支付**、**交易**相关的场景,会用 TCC,严格保证分布式事务要么全部成功,要么全部自动回滚,严格保证资金的正确性,保证在资金上不会出现问题。 - -而且最好是你的各个业务执行的时间都比较短。 - -但是说实话,一般尽量别这么搞,自己手写回滚逻辑,或者是补偿逻辑,实在太恶心了,那个业务代码是很难维护的。 - -## 本地消息表(异步确保) - -> 本地消息表与业务数据表处于同一个数据库中,这样就能利用本地事务来保证在对这两个表的操作满足事务特性。 -> -> 1. 在分布式事务操作的一方完成写业务数据的操作之后向本地消息表发送一个消息,本地事务能保证这个消息一定会被写入本地消息表中。 -> 2. 之后将本地消息表中的消息转发到 Kafka 等消息队列(MQ)中,如果转发成功则将消息从本地消息表中删除,否则继续重新转发。 -> 3. 在分布式事务操作的另一方从消息队列中读取一个消息,并执行消息中的操作。 - -
    - -
    - -这种方案遵循 BASE 理论,采用的是最终一致性。 - -本地消息表利用了本地事务来实现分布式事务,并且使用了消息队列来保证最终一致性。 - -### 优缺点 - -- 优点:一种非常经典的实现,避免了分布式事务,实现了最终一致性。 -- 缺点:消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。 - -## MQ 事务消息 - -有一些第三方的 MQ 是支持事务消息的,比如 RocketMQ,他们支持事务消息的方式也是类似于采用的二阶段提交。但是市面上一些主流的 MQ 都是不支持事务消息的,比如 RabbitMQ 和 Kafka 都不支持。 - -以阿里的 RocketMQ 中间件为例,其思路大致为: - -1. Prepared 消息,会拿到消息的地址。 -2. 执行本地事务。 -3. 通过第一阶段拿到的地址去访问消息,并修改状态。 - -也就是说在业务方法内要想消息队列提交两次请求,一次发送消息和一次确认消息。如果确认消息发送失败了 RocketMQ 会定期扫描消息集群中的事务消息,这时候发现了 Prepared 消息,它会向消息发送者确认,所以生产方需要实现一个 check 接口,RocketMQ 会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。 - -### 优缺点 - -- 优点:实现了最终一致性,不需要依赖本地数据库事务。 -- 缺点:实现难度大,主流 MQ 不支持。 - -### 两阶段提交方案/XA 方案 - -所谓的 XA 方案,即:两阶段提交,有一个**事务管理器**的概念,负责协调多个数据库(资源管理器)的事务,事务管理器先问问各个数据库你准备好了吗?如果每个数据库都回复 ok,那么就正式提交事务,在各个数据库上执行操作;如果任何其中一个数据库回答不 ok,那么就回滚事务。 - -这种分布式事务方案,比较适合单块应用里,跨多个库的分布式事务,而且因为严重依赖于数据库层面来搞定复杂的事务,效率很低,绝对不适合高并发的场景。如果要玩儿,那么基于 `Spring + JTA` 就可以搞定,自己随便搜个 demo 看看就知道了。 - -这个方案,我们很少用,一般来说**某个系统内部如果出现跨多个库**的这么一个操作,是**不合规**的。我可以给大家介绍一下, 现在微服务,一个大的系统分成几十个甚至几百个服务。一般来说,我们的规定和规范,是要求**每个服务只能操作自己对应的一个数据库**。 - -如果你要操作别的服务对应的库,不允许直连别的服务的库,违反微服务架构的规范,你随便交叉胡乱访问,几百个服务的话,全体乱套,这样的一套服务是没法管理的,没法治理的,可能会出现数据被别人改错,自己的库被别人写挂等情况。 - -如果你要操作别人的服务的库,你必须是通过**调用别的服务的接口**来实现,绝对不允许交叉访问别人的数据库。 - -
    - -### 本地消息表 - -本地消息表其实是国外的 ebay 搞出来的这么一套思想。 - -这个大概意思是这样的: - -1. A 系统在自己本地一个事务里操作同时,插入一条数据到消息表; -2. 接着 A 系统将这个消息发送到 MQ 中去; -3. B 系统接收到消息之后,在一个事务里,往自己本地消息表里插入一条数据,同时执行其他的业务操作,如果这个消息已经被处理过了,那么此时这个事务会回滚,这样**保证不会重复处理消息**; -4. B 系统执行成功之后,就会更新自己本地消息表的状态以及 A 系统消息表的状态; -5. 如果 B 系统处理失败了,那么就不会更新消息表状态,那么此时 A 系统会定时扫描自己的消息表,如果有未处理的消息,会再次发送到 MQ 中去,让 B 再次处理; -6. 这个方案保证了最终一致性,哪怕 B 事务失败了,但是 A 会不断重发消息,直到 B 那边成功为止。 - -这个方案说实话最大的问题就在于**严重依赖于数据库的消息表来管理事务**啥的,如果是高并发场景咋办呢?咋扩展呢?所以一般确实很少用。 - -
    - -### 可靠消息最终一致性方案 - -这个的意思,就是干脆不要用本地的消息表了,直接基于 MQ 来实现事务。比如阿里的 RocketMQ 就支持消息事务。 - -大概的意思就是: - -1. A 系统先发送一个 prepared 消息到 mq,如果这个 prepared 消息发送失败那么就直接取消操作别执行了; -2. 如果这个消息发送成功过了,那么接着执行本地事务,如果成功就告诉 mq 发送确认消息,如果失败就告诉 mq 回滚消息; -3. 如果发送了确认消息,那么此时 B 系统会接收到确认消息,然后执行本地的事务; -4. mq 会自动**定时轮询**所有 prepared 消息回调你的接口,问你,这个消息是不是本地事务处理失败了,所有没发送确认的消息,是继续重试还是回滚?一般来说这里你就可以查下数据库看之前本地事务是否执行,如果回滚了,那么这里也回滚吧。这个就是避免可能本地事务执行成功了,而确认消息却发送失败了。 -5. 这个方案里,要是系统 B 的事务失败了咋办?重试咯,自动不断重试直到成功,如果实在是不行,要么就是针对重要的资金类业务进行回滚,比如 B 系统本地回滚后,想办法通知系统 A 也回滚;或者是发送报警由人工来手工回滚和补偿。 -6. 这个还是比较合适的,目前国内互联网公司大都是这么玩儿的,要不你举用 RocketMQ 支持的,要不你就自己基于类似 ActiveMQ?RabbitMQ?自己封装一套类似的逻辑出来,总之思路就是这样子的。 - -
    - -### 最大努力通知方案 - -这个方案的大致意思就是: - -1. 系统 A 本地事务执行完之后,发送个消息到 MQ; -2. 这里会有个专门消费 MQ 的**最大努力通知服务**,这个服务会消费 MQ 然后写入数据库中记录下来,或者是放入个内存队列也可以,接着调用系统 B 的接口; -3. 要是系统 B 执行成功就 ok 了;要是系统 B 执行失败了,那么最大努力通知服务就定时尝试重新调用系统 B,反复 N 次,最后还是不行就放弃。 - -### 你们公司是如何处理分布式事务的? - -如果你真的被问到,可以这么说,我们某某特别严格的场景,用的是 TCC 来保证强一致性;然后其他的一些场景基于阿里的 RocketMQ 来实现分布式事务。 - -你找一个严格资金要求绝对不能错的场景,你可以说你是用的 TCC 方案;如果是一般的分布式事务场景,订单插入之后要调用库存服务更新库存,库存数据没有资金那么的敏感,可以用可靠消息最终一致性方案。 - -友情提示一下,RocketMQ 3.2.6 之前的版本,是可以按照上面的思路来的,但是之后接口做了一些改变,我这里不再赘述了。 - -当然如果你愿意,你可以参考可靠消息最终一致性方案来自己实现一套分布式事务,比如基于 RocketMQ 来玩儿。 - -## 参考资料 - -- [聊聊分布式事务,再说说解决方案](https://www.cnblogs.com/savorboard/p/distributed-system-transaction-consistency.html) diff --git a/docs/theory/load-balance-theory.md b/docs/theory/load-balance-theory.md deleted file mode 100644 index 80f0f638..00000000 --- a/docs/theory/load-balance-theory.md +++ /dev/null @@ -1,563 +0,0 @@ -# 负载均衡基本原理 - -## 负载均衡原理 - -系统的扩展可分为垂直扩展和水平扩展。 - -- 垂直扩展,是从单机的角度通过增加硬件处理能力,比如 CPU 处理能力,内存容量,磁盘等方面,实现服务器处理能力的提升。这种方式不能满足大型分布式系统(网站),大流量,高并发,海量数据的问题。 -- 水平扩展,是通过添加机器来满足大型网站服务的处理能力。比如:一台机器不能满足,则增加两台或者多台机器,共同承担访问压力。 - - 应用集群:将同一应用部署到多台机器上,组成处理集群,接收负载均衡设备分发的请求,进行处理,并返回相应数据。 - - 负载均衡设备:将用户访问的请求,根据负载均衡算法,分发到集群中的一台处理服务器。(一种把网络请求分散到一个服务器集群中的可用服务器上去的设备) - -负载均衡的作用(解决的问题): - -- 解决并发压力,提高应用处理性能(增加吞吐量,加强网络处理能力); -- 提供故障转移,实现高可用; -- 通过添加或减少服务器数量,提供网站伸缩性(扩展性); -- 安全防护;(负载均衡设备上做一些过滤,黑白名单等处理) - -## 负载均衡分类 - -根据实现技术不同,可分为反向代理、HTTP 重定向、DNS 重定向、修改 IP 地址、修改 MAC 地址等。 - -### 反向代理 - -> 正向代理与反向代理的区别: -> -> - 正向代理:发生在客户端,是由用户主动发起的。比如翻墙,客户端通过主动访问代理服务器,让代理服务器获得需要的外网数据,然后转发回客户端。 -> - 反向代理:发生在服务器端,用户不知道代理的存在。 -> -> 反向代理的作用是保护网站安全,所有互联网的请求都必须经过代理服务器,相当于在 web 服务器和可能的网络攻击之间建立了一个屏障。 -> -> 优点:部署简单 -> -> 缺点:可能成为系统瓶颈 - -
    - -
    - -### HTTP 重定向 - -> HTTP 重定向服务器收到 HTTP 请求之后会返回服务器的地址,并将该地址写入 HTTP 重定向响应中返回给浏览器,浏览器收到后需要再次发送请求。 -> -> 缺点: -> -> - 用户访问的延迟会增加; -> - 如果负载均衡器宕机,就无法访问该站点。 - -
    - -
    - -### DNS 重定向 - -> **使用 DNS 作为负载均衡器,根据负载情况返回不同服务器的 IP 地址。** -> -> 大型网站基本使用了这种方式做为第一级负载均衡手段,然后在内部使用其它方式做第二级负载均衡。 -> -> 优点: -> -> - 使用简单:负载均衡工作,交给 DNS 服务器处理,省掉了负载均衡服务器维护的麻烦 -> - 提高性能:可以支持基于地址的域名解析,解析成距离用户最近的服务器地址,可以加快访问速度,改善性能; -> -> 缺点: -> -> - 可用性差:DNS 解析是多级解析,新增/修改 DNS 后,解析时间较长;解析过程中,用户访问网站将失败; -> - 扩展性低:DNS 负载均衡的控制权在域名商那里,无法对其做更多的改善和扩展; -> - 维护性差:也不能反映服务器的当前运行状态;支持的算法少;不能区分服务器的差异(不能根据系统与服务的状态来判断负载) -> -> 建议: -> -> - 将 DNS 作为第一级负载均衡,A 记录对应着内部负载均衡的 IP 地址,通过内部负载均衡将请求分发到真实的 Web 服务器上。一般用于互联网公司,复杂的业务系统不合适使用。 - -
    - -
    - -### 修改 IP 地址 - -> **在网络层修改请求的目的 IP 地址来进行负载均衡。** -> -> 用户请求数据包,到达负载均衡服务器后,负载均衡服务器在操作系统内核进程获取网络数据包,根据负载均衡算法得到一台真实服务器地址,然后将请求目的地址修改为,获得的真实 ip 地址,不需要经过用户进程处理。真实服务器处理完成后,响应数据包回到负载均衡服务器,负载均衡服务器,再将数据包源地址修改为自身的 ip 地址,发送给用户浏览器。 -> -> IP 负载均衡,真实物理服务器返回给负载均衡服务器,存在两种方式: -> -> 1. 负载均衡服务器在修改目的 ip 地址的同时修改源地址。将数据包源地址设为自身盘,即[网络地址转换(NAT)](https://www.nginx.com/resources/glossary/layer-4-load-balancing/)。 -> 2. 将负载均衡服务器同时作为真实物理服务器集群的网关服务器。 -> -> 优点:在内核进程完成数据分发,比在应用层分发性能更好; -> -> 缺点:所有请求响应都需要经过负载均衡服务器,集群最大吞吐量受限于负载均衡服务器网卡带宽; - -
    - -
    - -### 修改 MAC 地址 - -> 使用 LVS(Linux Virtual Server)这种链路层负载均衡器,根据负载情况修改请求的 MAC 地址。 -> -> 实际处理服务器 ip 和数据请求目的 ip 一致,不需要经过负载均衡服务器进行地址转换,可将响应数据包直接返回给用户浏览器,避免负载均衡服务器网卡带宽成为瓶颈。也称为直接路由模式(DR 模式)。 -> -> 优点:性能好; -> -> 缺点:配置复杂; -> -> 建议:DR 模式是目前使用最广泛的一种负载均衡方式。 - -
    - -
    - -### 混合负载均衡 - -由于多个服务器群内硬件设备、各自的规模、提供的服务等的差异,可以考虑给每个服务器群采用最合适的负载均衡方式,然后又在这多个服务器群间再一次负载均衡或群集起来以一个整体向外界提供服务(即把这多个服务器群当做一个新的服务器群),从而达到最佳的性能。将这种方式称之为混合型负载均衡。 - -此种方式有时也用于单台均衡设备的性能不能满足大量连接请求的情况下。是目前大型互联网公司,普遍使用的方式。 - -方式一,如下图: - -
    - -以上模式适合有动静分离的场景,反向代理服务器(集群)可以起到缓存和动态请求分发的作用,当时静态资源缓存在代理服务器时,则直接返回到浏览器。如果动态页面则请求后面的应用负载均衡(应用集群)。 - -方式二,如下图: - -
    - -以上模式,适合动态请求场景。 - -因混合模式,可以根据具体场景,灵活搭配各种方式,以上两种方式仅供参考。 - -## 负载均衡算法 - -常用的负载均衡算法有:轮询、随机、最少连接、源地址散列、加权等方式。 - -### 轮询 - -> 轮询(Round Robin)算法将所有请求,依次分发到每台服务器上,适合服务器硬件同相同的场景。 -> -> - 优点:服务器请求数目相同; -> - 缺点:服务器压力不一样,不适合服务器配置不同的情况; - -下图中,一共有 6 个客户端产生了 6 个请求,这 6 个请求按 (1, 2, 3, 4, 5, 6) 的顺序发送。最后,(1, 3, 5) 的请求会被发送到服务器 1,(2, 4, 6) 的请求会被发送到服务器 2。 - -
    - -
    - -该算法比较适合每个服务器的性能差不多的场景,如果有性能存在差异的情况下,那么性能较差的服务器可能无法承担过大的负载(下图的 Server 2)。 - -
    - -
    - -算法示例: - -```java -private Integer pos = 0; -public void roundRobin() { - List keyList = new ArrayList(serverMap.keySet()); - String server = null; - synchronized (pos) { - if (pos > keyList.size()) { - pos = 0; - } - server = keyList.get(pos); - pos++; - } - System.out.println(server); -} -``` - -#### 加权轮询 - -> 加权轮询(Weighted Round Robbin)算法在轮询算法的基础上,通过加权的方式,进行负载服务器分配。 -> -> - 优点:根据权重,调节转发服务器的请求数目; -> - 缺点:使用相对复杂; - -加权轮询是在轮询的基础上,根据服务器的性能差异,为服务器赋予一定的权值。例如下图中,服务器 1 被赋予的权值为 5,服务器 2 被赋予的权值为 1,那么 (1, 2, 3, 4, 5) 请求会被发送到服务器 1,(6) 请求会被发送到服务器 2。 - -
    - -
    - -算法示例: - -```java -public void weightRoundRobin() { - Set keySet = serverMap.keySet(); - List servers = new ArrayList(); - for (Iterator it = keySet.iterator(); it.hasNext(); ) { - String server = it.next(); - int weithgt = serverMap.get(server); - for (int i = 0; i < weithgt; i++) { - servers.add(server); - } - } - String server = null; - synchronized (pos) { - if (pos > keySet.size()) { - pos = 0; - } - server = servers.get(pos); - pos++; - } - System.out.println(server); -} -``` - -### 最少连接 - -> 最少连接(Least Busy)算法将请求分配到连接数最少的服务器(目前处理请求最少的服务器)。 -> -> - 优点:根据服务器当前的请求处理情况,动态分配; -> - 缺点:算法实现相对复杂,需要监控服务器请求连接数; - -由于每个请求的连接时间不一样,使用轮询或者加权轮询算法的话,可能会让一台服务器当前连接数过大,而另一台服务器的连接过小,造成负载不均衡。例如下图中,(1, 3, 5) 请求会被发送到服务器 1,但是 (1, 3) 很快就断开连接,此时只有 (5) 请求连接服务器 1;(2, 4, 6) 请求被发送到服务器 2,只有 (2) 的连接断开。该系统继续运行时,服务器 2 会承担过大的负载。 - -
    - -
    - -最少连接算法就是将请求发送给当前最少连接数的服务器上。例如下图中,服务器 1 当前连接数最小,那么新到来的请求 6 就会被发送到服务器 1 上。 - -
    - -
    - -#### 加权最少连接 - -> 加权最少连接(Weighted Least Connection)在最少连接的基础上,根据服务器的性能为每台服务器分配权重,再根据权重计算出每台服务器能处理的连接数。 - -
    - -
    -### 随机 - -> 随机(Random)算法将请求随机分配到各个服务器。 -> -> - 优点:使用简单; -> - 缺点:不适合机器配置不同的场景; - -和轮询算法类似,该算法比较适合服务器性能差不多的场景。 - -
    - -
    - -### 哈希(Hash) - -#### 普通 Hash - -> 根据 IP 地址进行 Hash 计算,得到 IP 地址。 -> -> - 优点:将来自同一 IP 地址的请求,同一会话期内,转发到相同的服务器;实现会话粘滞。 -> - 缺点:目标服务器宕机后,会话会丢失; - -
    - -
    - -算法示例: - -```java -public void hash() { - List keyList = new ArrayList(serverMap.keySet()); - String remoteIp = "192.168.2.215"; - int hashCode = remoteIp.hashCode(); - int idx = hashCode % keyList.size(); - String server = keyList.get(Math.abs(idx)); - System.out.println(server); -} -``` - -#### 一致性哈希 - -一致性 Hash,相同参数的请求总是发到同一提供者。当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。 - -## 硬件负载均衡 - -采用硬件的方式实现负载均衡,一般是单独的负载均衡服务器,价格昂贵,一般土豪级公司可以考虑,业界领先的有两款,F5 和 A10。 - -- 使用硬件负载均衡,主要考虑一下几个方面: -- 功能考虑:功能全面支持各层级的负载均衡,支持全面的负载均衡算法,支持全局负载均衡; -- 性能考虑:一般软件负载均衡支持到 5 万级并发已经很困难了,硬件负载均衡可以支持 -- 稳定性:商用硬件负载均衡,经过了良好的严格的测试,从经过大规模使用,在稳定性方面高; -- 安全防护:硬件均衡设备除具备负载均衡功能外,还具备防火墙,防 DDOS 攻击等安全功能; -- 维护角度:提供良好的维护管理界面,售后服务和技术支持; -- 土豪公司:F5 Big Ip 价格:15w\~55w 不等;A10 价格:55w-100w 不等; - -缺点 - -- 价格昂贵; -- 扩展能力差; - -小结 - -- 一般硬件的负载均衡也要做双机高可用,因此成本会比较高。 -- 互联网公司一般使用开源软件,因此大部分应用采用软件负载均衡;部分采用硬件负载均衡。 - -比如某互联网公司,目前是使用几台 F5 做全局负载均衡,内部使用 Nginx 等软件负载均衡。 - -## Ngnix 负载均衡 - -Ngnix 是一款轻量级的 Web 服务器/反向代理服务器,工作在七层 Http 协议的负载均衡系统。具有高性能、高并发、低内存使用等特点。是一个轻量级的 Http 和反向代理服务器。Nginx 使用 epoll and kqueue 作为开发模型。能够支持高达 50,000 个并发连接数的响应。 - -操作系统:Liunx,Windows(Linux、FreeBSD、Solaris、Mac OS X、AIX 以及 Microsoft Windows) - -开发语言:C - -并发性能:官方支持每秒 5 万并发,实际国内一般到每秒 2 万并发,有优化到每秒 10 万并发的。具体性能看应用场景。 - -### Ngnix 特点 - -- 模块化设计:良好的扩展性,可以通过模块方式进行功能扩展。 -- 高可靠性:主控进程和 worker 是同步实现的,一个 worker 出现问题,会立刻启动另一个 worker。 -- 内存消耗低:一万个长连接(keep-alive),仅消耗 2.5MB 内存。 -- 支持热部署:不用停止服务器,实现更新配置文件,更换日志文件、更新服务器程序版本。 -- 并发能力强:官方数据每秒支持 5 万并发; -- 功能丰富:优秀的反向代理功能和灵活的负载均衡策略 - -### Ngnix 功能 - -#### 基本功能 - -- 支持静态资源的 web 服务器。 -- http,smtp,pop3 协议的反向代理服务器、缓存、负载均衡; -- 支持 FASTCGI(fpm) -- 支持模块化,过滤器(让文本可以实现压缩,节约带宽),ssl 及图像大小调整。 -- 内置的健康检查功能 -- 基于名称和 ip 的虚拟主机 -- 定制访问日志 -- 支持平滑升级 -- 支持 KEEPALIVE -- 支持 url rewrite -- 支持路径别名 -- 支持基于 IP 和用户名的访问控制。 -- 支持传输速率限制,支持并发数限制。 - -#### 扩展功能 - -#### 性能 - -Nginx 的高并发,官方测试支持 5 万并发连接。实际生产环境能到 2-3 万并发连接数。10000 个非活跃的 HTTP keep-alive 连接仅占用约 2.5MB 内存。三万并发连接下,10 个 Nginx 进程,消耗内存 150M。淘宝 tengine 团队测试结果是“24G 内存机器上,处理并发请求可达 200 万”。 - -### Ngnix 架构 - -#### Nginx 的基本工作模式 - -
    - -一个 master 进程,生成一个或者多个 worker 进程。但是这里 master 是使用 root 身份启动的,因为 nginx 要工作在 80 端口。而只有管理员才有权限启动小于低于 1023 的端口。master 主要是负责的作用只是启动 worker,加载配置文件,负责系统的平滑升级。其它的工作是交给 worker。那么当 worker 被启动之后,也只是负责一些 web 最简单的工作,而其他的工作都是有 worker 中调用的模块来实现的。 - -模块之间是以流水线的方式实现功能的。流水线,指的是一个用户请求,由多个模块组合各自的功能依次实现完成的。比如:第一个模块只负责分析请求首部,第二个模块只负责查找数据,第三个模块只负责压缩数据,依次完成各自工作。来实现整个工作的完成。 - -他们是如何实现热部署的呢?其实是这样的,我们前面说 master 不负责具体的工作,而是调用 worker 工作,他只是负责读取配置文件,因此当一个模块修改或者配置文件发生变化,是由 master 进行读取,因此此时不会影响到 worker 工作。在 master 进行读取配置文件之后,不会立即的把修改的配置文件告知 worker。而是让被修改的 worker 继续使用老的配置文件工作,当 worker 工作完毕之后,直接当掉这个子进程,更换新的子进程,使用新的规则。 - -#### Nginx 支持的 sendfile 机制 - -Sendfile 机制,用户将请求发给内核,内核根据用户的请求调用相应用户进程,进程在处理时需要资源。此时再把请求发给内核(进程没有直接 IO 的能力),由内核加载数据。内核查找到数据之后,会把数据复制给用户进程,由用户进程对数据进行封装,之后交给内核,内核在进行 tcp/ip 首部的封装,最后再发给客户端。这个功能用户进程只是发生了一个封装报文的过程,却要绕一大圈。因此 nginx 引入了 sendfile 机制,使得内核在接受到数据之后,不再依靠用户进程给予封装,而是自己查找自己封装,减少了一个很长一段时间的浪费,这是一个提升性能的核心点。 - -
    - -以上内容摘自网友发布的文章,简单一句话是资源的处理,直接通过内核层进行数据传递,避免了数据传递到应用层,应用层再传递到内核层的开销。 - -目前高并发的处理,一般都采用 sendfile 模式。通过直接操作内核层数据,减少应用与内核层数据传递。 - -#### Nginx 通信模型(I/O 复用机制) - -开发模型:epoll 和 kqueue。 - -支持的事件机制:kqueue、epoll、rt signals、/dev/poll 、event ports、select 以及 poll。 - -支持的 kqueue 特性包括 EV_CLEAR、EV_DISABLE、NOTE_LOWAT、EV_EOF,可用数据的数量,错误代码. - -支持 sendfile、sendfile64 和 sendfilev;文件 AIO;DIRECTIO;支持 Accept-filters 和 TCP_DEFER_ACCEP. - -以上概念较多,大家自行百度或谷歌,知识领域是网络通信(BIO,NIO,AIO)和多线程方面的知识。 - -### Ngnix 均衡策略 - -nginx 的负载均衡策略可以划分为两大类:内置策略和扩展策略。内置策略包含加权轮询和 ip hash,在默认情况下这两种策略会编译进 nginx 内核,只需在 nginx 配置中指明参数即可。扩展策略有很多,如 fair、通用 hash、consistent hash 等,默认不编译进 nginx 内核。由于在 nginx 版本升级中负载均衡的代码没有本质性的变化,因此下面将以 nginx1.0.15 稳定版为例,从源码角度分析各个策略。 - -#### 加权轮询(weighted round robin) - -轮询的原理很简单,首先我们介绍一下轮询的基本流程。如下是处理一次请求的流程图: - -
    - -图中有两点需要注意,第一,如果可以把加权轮询算法分为先深搜索和先广搜索,那么 nginx 采用的是先深搜索算法,即将首先将请求都分给高权重的机器,直到该机器的权值降到了比其他机器低,才开始将请求分给下一个高权重的机器;第二,当所有后端机器都 down 掉时,nginx 会立即将所有机器的标志位清成初始状态,以避免造成所有的机器都处在 timeout 的状态,从而导致整个前端被夯住。 - -#### ip hash - -ip hash 是 nginx 内置的另一个负载均衡的策略,流程和轮询很类似,只是其中的算法和具体的策略有些变化,如下图所示: - -
    - -#### fair - -fair 策略是扩展策略,默认不被编译进 nginx 内核。其原理是根据后端服务器的响应时间判断负载情况,从中选出负载最轻的机器进行分流。这种策略具有很强的自适应性,但是实际的网络环境往往不是那么简单,因此要慎用。 - -#### 通用 hash、一致性 hash - -这两种也是扩展策略,在具体的实现上有些差别,通用 hash 比较简单,可以以 nginx 内置的变量为 key 进行 hash,一致性 hash 采用了 nginx 内置的一致性 hash 环,可以支持 memcache。 - -### Ngnix 场景 - -Ngnix 一般作为入口负载均衡或内部负载均衡,结合反向代理服务器使用。以下架构示例,仅供参考,具体使用根据场景而定。 - -#### 入口负载均衡架构 - -
    - -Ngnix 服务器在用户访问的最前端。根据用户请求再转发到具体的应用服务器或二级负载均衡服务器(LVS) - -#### 内部负载均衡架构 - -
    - -LVS 作为入口负载均衡,将请求转发到二级 Ngnix 服务器,Ngnix 再根据请求转发到具体的应用服务器。 - -#### Ngnix 高可用 - -
    - -分布式系统中,应用只部署一台服务器会存在单点故障,负载均衡同样有类似的问题。一般可采用主备或负载均衡设备集群的方式节约单点故障或高并发请求分流。 - -Ngnix 高可用,至少包含两个 Ngnix 服务器,一台主服务器,一台备服务器,之间使用 Keepalived 做健康监控和故障检测。开放 VIP 端口,通过防火墙进行外部映射。 - -DNS 解析公网的 IP 实际为 VIP。 - -## LVS 负载均衡 - -LVS 是一个开源的软件,由毕业于国防科技大学的章文嵩博士于 1998 年 5 月创立,用来实现 Linux 平台下的简单负载均衡。LVS 是 Linux Virtual Server 的缩写,意思是 Linux 虚拟服务器。 - -基于 IP 层的负载均衡调度技术,它在操作系统核心层上,将来自 IP 层的 TCP/UDP 请求均衡地转移到不同的 服务器,从而将一组服务器构成一个高性能、高可用的虚拟服务器。 - -操作系统:Liunx - -开发语言:C - -并发性能:默认 4096,可以修改但需要重新编译。 - -### LVS 功能 - -LVS 的主要功能是实现 IP 层(网络层)负载均衡,有 NAT,TUN,DR 三种请求转发模式。 - -#### LVS/NAT 方式的负载均衡集群 - -NAT 是指 Network Address Translation,它的转发流程是:Director 机器收到外界请求,改写数据包的目标地址,按相应的调度算法将其发送到相应 Real Server 上,Real Server 处理完该请求后,将结果数据包返回到其默认网关,即 Director 机器上,Director 机器再改写数据包的源地址,最后将其返回给外界。这样就完成一次负载调度。 - -构架一个最简单的 LVS/NAT 方式的负载均衡集群 Real Server 可以是任何的操作系统,而且无需做任何特殊的设定,惟一要做的就是将其默认网关指向 Director 机器。Real Server 可以使用局域网的内部 IP(192.168.0.0/24)。Director 要有两块网卡,一块网卡绑定一个外部 IP 地址 (10.0.0.1),另一块网卡绑定局域网的内部 IP(192.168.0.254),作为 Real Server 的默认网关。 - -LVS/NAT 方式实现起来最为简单,而且 Real Server 使用的是内部 IP,可以节省 Real IP 的开销。但因为执行 NAT 需要重写流经 Director 的数据包,在速度上有一定延迟; - -当用户的请求非常短,而服务器的回应非常大的情况下,会对 Director 形成很大压力,成为新的瓶颈,从而使整个系统的性能受到限制。 - -#### LVS/TUN 方式的负载均衡集群 - -TUN 是指 IP Tunneling,它的转发流程是:Director 机器收到外界请求,按相应的调度算法,通过 IP 隧道发送到相应 Real Server,Real Server 处理完该请求后,将结果数据包直接返回给客户。至此完成一次负载调度。 - -最简单的 LVS/TUN 方式的负载均衡集群架构使用 IP Tunneling 技术,在 Director 机器和 Real Server 机器之间架设一个 IP Tunnel,通过 IP Tunnel 将负载分配到 Real Server 机器上。Director 和 Real Server 之间的关系比较松散,可以是在同一个网络中,也可以是在不同的网络中,只要两者能够通过 IP Tunnel 相连就行。收到负载分配的 Real Server 机器处理完后会直接将反馈数据送回给客户,而不必通过 Director 机器。实际应用中,服务器必须拥有正式的 IP 地址用于与客户机直接通信,并且所有服务器必须支持 IP 隧道协议。 - -该方式中 Director 将客户请求分配到不同的 Real Server,Real Server 处理请求后直接回应给用户,这样 Director 就只处理客户机与服务器的一半连接,极大地提高了 Director 的调度处理能力,使集群系统能容纳更多的节点数。另外 TUN 方式中的 Real Server 可以在任何 LAN 或 WAN 上运行,这样可以构筑跨地域的集群,其应对灾难的能力也更强,但是服务器需要为 IP 封装付出一定的资源开销,而且后端的 Real Server 必须是支持 IP Tunneling 的操作系统。 - -#### LVS/TUN 方式的负载均衡集群 - -DR 是指 Direct Routing,它的转发流程是:Director 机器收到外界请求,按相应的调度算法将其直接发送到相应 Real Server,Real Server 处理完该请求后,将结果数据包直接返回给客户,完成一次负载调度。 - -构架一个最简单的 LVS/DR 方式的负载均衡集群 Real Server 和 Director 都在同一个物理网段中,Director 的网卡 IP 是 192.168.0.253,再绑定另一个 IP: 192.168.0.254 作为对外界的 virtual IP,外界客户通过该 IP 来访问整个集群系统。Real Server 在 lo 上绑定 IP:192.168.0.254,同时加入相应的路由。 - -LVS/DR 方式与前面的 LVS/TUN 方式有些类似,前台的 Director 机器也是只需要接收和调度外界的请求,而不需要负责返回这些请求的反馈结果,所以能够负载更多的 Real Server,提高 Director 的调度处理能力,使集群系统容纳更多的 Real Server。但 LVS/DR 需要改写请求报文的 MAC 地址,所以所有服务器必须在同一物理网段内。 - -### LVS 架构 - -LVS 架设的服务器集群系统有三个部分组成:最前端的负载均衡层(Loader Balancer),中间的服务器群组层,用 Server Array 表示,最底层的数据共享存储层,用 Shared Storage 表示。在用户看来所有的应用都是透明的,用户只是在使用一个虚拟服务器提供的高性能服务。 - -LVS 的体系架构如图: - -
    - -LVS 的各个层次的详细介绍: - -Load Balancer 层:位于整个集群系统的最前端,有一台或者多台负载调度器(Director Server)组成,LVS 模块就安装在 Director Server 上,而 Director 的主要作用类似于一个路由器,它含有完成 LVS 功能所设定的路由表,通过这些路由表把用户的请求分发给 Server Array 层的应用服务器(Real Server)上。同时,在 Director Server 上还要安装对 Real Server 服务的监控模块 Ldirectord,此模块用于监测各个 Real Server 服务的健康状况。在 Real Server 不可用时把它从 LVS 路由表中剔除,恢复时重新加入。 - -Server Array 层:由一组实际运行应用服务的机器组成,Real Server 可以是 WEB 服务器、MAIL 服务器、FTP 服务器、DNS 服务器、视频服务器中的一个或者多个,每个 Real Server 之间通过高速的 LAN 或分布在各地的 WAN 相连接。在实际的应用中,Director Server 也可以同时兼任 Real Server 的角色。 - -Shared Storage 层:是为所有 Real Server 提供共享存储空间和内容一致性的存储区域,在物理上,一般有磁盘阵列设备组成,为了提供内容的一致性,一般可以通过 NFS 网络文件系统共享数 据,但是 NFS 在繁忙的业务系统中,性能并不是很好,此时可以采用集群文件系统,例如 Red hat 的 GFS 文件系统,oracle 提供的 OCFS2 文件系统等。 - -从整个 LVS 结构可以看出,Director Server 是整个 LVS 的核心,目前,用于 Director Server 的操作系统只能是 Linux 和 FreeBSD,linux2.6 内核不用任何设置就可以支持 LVS 功能,而 FreeBSD 作为 Director Server 的应用还不是很多,性能也不是很好。对于 Real Server,几乎可以是所有的系统平台,Linux、windows、Solaris、AIX、BSD 系列都能很好的支持。 - -### LVS 均衡策略 - -LVS 默认支持八种负载均衡策略,简述如下: - -#### 轮询调度(Round Robin) - -调度器通过“轮询”调度算法将外部请求按顺序轮流分配到集群中的真实服务器上,它均等地对待每一台服务器,而不管服务器上实际的连接数和系统负载。 - -#### 加权轮询(Weighted Round Robin) - -调度器通过“加权轮询”调度算法根据真实服务器的不同处理能力来调度访问请求。这样可以保证处理能力强的服务器能处理更多的访问流量。调度器可以自动问询真实服务器的负载情况,并动态地调整其权值。 - -#### 最少链接(Least Connections) - -调度器通过“最少连接”调度算法动态地将网络请求调度到已建立的链接数最少的服务器上。如果集群系统的真实服务器具有相近的系统性能,采用“最小连接”调度算法可以较好地均衡负载。 - -#### 加权最少链接(Weighted Least Connections) - -在集群系统中的服务器性能差异较大的情况下,调度器采用“加权最少链接”调度算法优化负载均衡性能,具有较高权值的服务器将承受较大比例的活动连接负载。调度器可以自动问询真实服务器的负载情况,并动态地调整其权值。 - -#### 基于局部性的最少链接(Locality-Based Least Connections) - -“基于局部性的最少链接”调度算法是针对目标 IP 地址的负载均衡,目前主要用于 Cache 集群系统。该算法根据请求的目标 IP 地址找出该目标 IP 地址最近使用的服务器,若该服务器是可用的且没有超载,将请求发送到该服务器;若服务器不存在,或者该服务器超载且有服务器处于一半的工作负载,则用“最少链接” 的原则选出一个可用的服务器,将请求发送到该服务器。 - -#### 带复制的基于局部性最少链接(Locality-Based Least Connections with Replication) - -“带复制的基于局部性最少链接”调度算法也是针对目标 IP 地址的负载均衡,目前主要用于 Cache 集群系统。它与 LBLC 算法的不同之处是它要维护从一个目标 IP 地址到一组服务器的映射,而 LBLC 算法维护从一个目标 IP 地址到一台服务器的映射。该算法根据请求的目标 IP 地址找出该目标 IP 地址对应的服务器组,按“最小连接”原则从服务器组中选出一台服务器,若服务器没有超载,将请求发送到该服务器;若服务器超载,则按“最小连接”原则从这个集群中选出一台服务器,将该服务器加入到服务器组中,将请求发送到该服务器。同时,当该服务器组有一段时间没有被修改,将最忙的服务器从服务器组中删除,以降低复制的程度。 - -#### 目标地址散列(Destination Hashing) - -“目标地址散列”调度算法根据请求的目标 IP 地址,作为散列键(Hash Key)从静态分配的散列表找出对应的服务器,若该服务器是可用的且未超载,将请求发送到该服务器,否则返回空。 - -#### 源地址散列(Source Hashing) - -“源地址散列”调度算法根据请求的源 IP 地址,作为散列键(Hash Key)从静态分配的散列表找出对应的服务器,若该服务器是可用的且未超载,将请求发送到该服务器,否则返回空。 - -除具备以上负载均衡算法外,还可以自定义均衡策略。 - -### LVS 场景 - -一般作为入口负载均衡或内部负载均衡,结合反向代理服务器使用。相关架构可参考 Ngnix 场景架构。 - -## HaProxy 负载均衡 - -HAProxy 也是使用较多的一款负载均衡软件。HAProxy 提供高可用性、负载均衡以及基于 TCP 和 HTTP 应用的代理,支持虚拟主机,是免费、快速并且可靠的一种解决方案。特别适用于那些负载特大的 web 站点。运行模式使得它可以很简单安全的整合到当前的架构中,同时可以保护你的 web 服务器不被暴露到网络上。 - -### HaProxy 特点 - -- 支持两种代理模式:TCP(四层)和 HTTP(七层),支持虚拟主机; -- 配置简单,支持 url 检测后端服务器状态; -- 做负载均衡软件使用,在高并发情况下,处理速度高于 nginx; -- TCP 层多用于 Mysql 从(读)服务器负载均衡。 (对 Mysql 进行负载均衡,对后端的 DB 节点进行检测和负载均衡) -- 能够补充 Nginx 的一些缺点比如 Session 的保持,Cookie 引导等工作 - -### HaProxy 均衡策略 - -支持四种常用算法: - -- roundrobin:轮询,轮流分配到后端服务器; -- static-rr:根据后端服务器性能分配; -- leastconn:最小连接者优先处理; -- source:根据请求源 IP,与 Nginx 的 IP_Hash 类似。 - -## 资料 - -- [大型网站架构系列:负载均衡详解(1)](https://www.cnblogs.com/itfly8/p/5043435.html) -- [大型网站架构系列:负载均衡详解(2)](https://www.cnblogs.com/itfly8/p/5043452.html) -- [大型网站架构系列:负载均衡详解(3)](https://www.cnblogs.com/itfly8/p/5080743.html) -- [大型网站架构系列:负载均衡详解(4)](https://www.cnblogs.com/itfly8/p/5080988.html) -- https://segmentfault.com/a/1190000004492447 diff --git a/docs/theory/mq-theory.md b/docs/theory/mq-theory.md deleted file mode 100644 index fec35a7a..00000000 --- a/docs/theory/mq-theory.md +++ /dev/null @@ -1,260 +0,0 @@ -# 消息队列基本原理 - -> 消息队列(Message Queue,简称 MQ)技术是分布式应用间交换信息的一种技术。 -> -> 消息队列主要解决应用耦合,异步消息,流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构。是大型分布式系统不可缺少的中间件。 -> -> 注意:_为了简便,下文中除了文章标题,一律使用 MQ 简称_。 - - - -- [1. 为什么要使用 MQ](#1-为什么要使用-mq) -- [2. 消息队列通信模式](#2-消息队列通信模式) -- [3. JMS](#3-jms) - - [3.1. 消息模型](#31-消息模型) - - [3.2. 消息消费](#32-消息消费) - - [3.3. JMS 编程模型](#33-jms-编程模型) -- [4. 常用 MQ 中间件](#4-常用-mq-中间件) - - [4.1. ActiveMQ](#41-activemq) - - [4.2. RabbitMQ](#42-rabbitmq) - - [4.3. ZeroMQ](#43-zeromq) - - [4.4. Kafka](#44-kafka) -- [5. MQ 示例](#5-mq-示例) - - [5.1. 电商系统](#51-电商系统) - - [5.2. 日志收集系统](#52-日志收集系统) -- [6. 参考资料](#6-参考资料) - - - -## 1. 为什么要使用 MQ - -- **异步通信** - 很多时候,用户不想也不需要立即处理消息。MQ 提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。这种处理模式能够提高系统的处理速度。 -- **解耦** - 在项目启动之初来预测将来项目会碰到什么需求,是极其困难的。MQ 在处理过程中间插入了一个隐含的、基于数据的接口层,两边的处理过程都要实现这一接口。这允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。 -- **冗余** - 有些情况下,处理数据的过程会失败。除非数据被持久化,否则将造成丢失。MQ 把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险。许多 MQ 所采用的"插入-获取-删除"范式中,在把一个消息从队列中删除之前,需要你的处理系统明确的指出该消息已经被处理完毕,从而确保你的数据被安全的保存直到你使用完毕。 -- **扩展性** - 因为 MQ 解耦了处理过程,所以增大消息入队和处理的频率是很容易的,只要另外增加处理过程即可。不需要改变代码、不需要调节参数。扩展就像调大电力按钮一样简单。 -- **流量削锋** - 在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见;如果以处理这类峰值访问为标准来投入资源,随时待命无疑是巨大的浪费。使用 MQ 能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。常见应用:秒杀系统、抢购活动。 -- **可恢复性** - 系统的一部分组件失效时,不会影响到整个系统。MQ 降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。 -- **顺序性** - 在大多使用场景下,数据处理的顺序都很重要。大部分 MQ 本来就是排序的,并且能保证数据会按照特定的顺序来处理。 - -## 2. 消息队列通信模式 - -MQ 可驻留在内存或磁盘上,队列存储消息直到它们被应用程序读取。通过 MQ,应用程序可独立地执行,它们不需要知道彼此的位置,不需要等待接收程序接收此消息。在分布式计算环境中,为了集成分布式应用,开发者需要对异构网络环境下的分布式应用提供有效的通信手段。为了管理需要共享的信息,对应用提供公共的信息交换机制是重要的。 - -- **点对点** - 点对点方式是最为传统和常见的通讯方式,它支持一对一、一对多、多对多、多对一等多种配置方式,支持树状、网状等多种拓扑结构。 -- **多点广播** - MQ 适用于不同类型的应用。其中重要的,也是正在发展中的是"多点广播"应用,即能够将消息发送到多个目标站点 (Destination List)。可以使用一条 MQ 指令将单一消息发送到多个目标站点,并确保为每一站点可靠地提供信息。MQ 不仅提供了多点广播的功能,而且还拥有智能消息分发功能,在将一条消息发送到同一系统上的多个用户时,MQ 将消息的一个复制版本和该系统上接收者的名单发送到目标 MQ 系统。目标 MQ 系统在本地复制这些消息,并将它们发送到名单上的队列,从而尽可能减少网络的传输量。 -- **发布/订阅 (Publish/Subscribe)** - 发布/订阅模式使消息的分发可以突破目的队列地理位置的限制,使消息按照特定的主题甚至内容进行分发,用户或应用程序可以根据主题或内容接收到所需要的消息。发布/订阅模式使得发送者和接收者之间的耦合关系变得更为松散,发送者不必关心接收者的目的地址,而接收者也不必关心消息的发送地址,而只是根据消息的主题进行消息的收发。 -- **集群 (Cluster)** - 为了简化点对点通讯模式中的系统配置,MQ 提供 Cluster(集群) 的解决方案。集群类似于一个域 (Domain),集群内部的队列管理器之间通讯时,不需要两两之间建立消息通道,而是采用集群 (Cluster) 通道与其它成员通讯,从而大大简化了系统配置。此外,集群中的队列管理器之间能够自动进行负载均衡,当某一队列管理器出现故障时,其它队列管理器可以接管它的工作,从而大大提高系统的高可靠性。 - -## 3. JMS - -谈 MQ 就不得不提一下 JMS 。 - -**JMS(JAVA Message Service,java 消息服务)API 是一个消息服务的标准/规范,允许应用程序组件基于 JavaEE 平台创建、发送、接收和读取消息**。它使分布式通信耦合度更低,消息服务更加可靠以及异步性。 - -在 EJB 架构中,有消息 bean 可以无缝的与 JM 消息服务集成。在 J2EE 架构模式中,有消息服务者模式,用于实现消息与应用直接的解耦。 - -### 3.1. 消息模型 - -在 JMS 标准中,有两种消息模型: - -- P2P(Point to Point) -- Pub/Sub(Publish/Subscribe) - -#### P2P 模式 - -
    - -P2P 模式包含三个角色:MQ(Queue),发送者(Sender),接收者(Receiver)。每个消息都被发送到一个特定的队列,接收者从队列中获取消息。队列保留着消息,直到他们被消费或超时。 - -P2P 的特点 - -- 每个消息只有一个消费者(Consumer)(即一旦被消费,消息就不再在 MQ 中) -- 发送者和接收者之间在时间上没有依赖性,也就是说当发送者发送了消息之后,不管接收者有没有正在运行,它不会影响到消息被发送到队列 -- 接收者在成功接收消息之后需向队列应答成功 - -如果希望发送的每个消息都会被成功处理的话,那么需要 P2P 模式。 - -#### Pub/sub 模式 - -
    - -包含三个角色主题(Topic),发布者(Publisher),订阅者(Subscriber) 。多个发布者将消息发送到 Topic,系统将这些消息传递给多个订阅者。 - -Pub/Sub 的特点 - -- 每个消息可以有多个消费者 -- 发布者和订阅者之间有时间上的依赖性。针对某个主题(Topic)的订阅者,它必须创建一个订阅者之后,才能消费发布者的消息。 -- 为了消费消息,订阅者必须保持运行的状态。 - -为了缓和这样严格的时间相关性,JMS 允许订阅者创建一个可持久化的订阅。这样,即使订阅者没有被激活(运行),它也能接收到发布者的消息。 - -如果希望发送的消息可以不被做任何处理、或者只被一个消息者处理、或者可以被多个消费者处理的话,那么可以采用 Pub/Sub 模型。 - -### 3.2. 消息消费 - -在 JMS 中,消息的产生和消费都是异步的。对于消费来说,JMS 的消息者可以通过两种方式来消费消息。 - -- **同步** - 订阅者或接收者通过 `receive` 方法来接收消息,`receive` 方法在接收到消息之前(或超时之前)将一直阻塞; -- **异步** - 订阅者或接收者可以注册为一个消息监听器。当消息到达之后,系统自动调用监听器的 `onMessage` 方法。 - -`JNDI` - Java 命名和目录接口,是一种标准的 Java 命名系统接口。可以在网络上查找和访问服务。通过指定一个资源名称,该名称对应于数据库或命名服务中的一个记录,同时返回资源连接建立所必须的信息。 - -JNDI 在 JMS 中起到查找和访问发送目标或消息来源的作用。 - -### 3.3. JMS 编程模型 - -#### ConnectionFactory - -创建 Connection 对象的工厂,针对两种不同的 jms 消息模型,分别有 QueueConnectionFactory 和 TopicConnectionFactory 两种。可以通过 JNDI 来查找 ConnectionFactory 对象。 - -#### Destination - -Destination 的意思是消息生产者的消息发送目标或者说消息消费者的消息来源。对于消息生产者来说,它的 Destination 是某个队列(Queue)或某个主题(Topic);对于消息消费者来说,它的 Destination 也是某个队列或主题(即消息来源)。 - -所以,Destination 实际上就是两种类型的对象:Queue、Topic。可以通过 JNDI 来查找 Destination。 - -#### (3) Connection - -Connection 表示在客户端和 JMS 系统之间建立的链接(对 TCP/IP socket 的包装)。Connection 可以产生一个或多个 Session。跟 ConnectionFactory 一样,Connection 也有两种类型:QueueConnection 和 TopicConnection。 - -#### (4) Session - -Session 是操作消息的接口。可以通过 session 创建生产者、消费者、消息等。Session 提供了事务的功能。当需要使用 session 发送/接收多个消息时,可以将这些发送/接收动作放到一个事务中。同样,也分 QueueSession 和 TopicSession。 - -#### 消息的生产者 - -消息生产者由 Session 创建,并用于将消息发送到 Destination。同样,消息生产者分两种类型:QueueSender 和 TopicPublisher。可以调用消息生产者的方法(send 或 publish 方法)发送消息。 - -#### 消息消费者 - -消息消费者由 Session 创建,用于接收被发送到 Destination 的消息。两种类型:QueueReceiver 和 TopicSubscriber。可分别通过 session 的 createReceiver(Queue)或 createSubscriber(Topic)来创建。当然,也可以 session 的 creatDurableSubscriber 方法来创建持久化的订阅者。 - -#### MessageListener - -消息监听器。如果注册了消息监听器,一旦消息到达,将自动调用监听器的 onMessage 方法。EJB 中的 MDB(Message-Driven Bean)就是一种 MessageListener。 - -深入学习 JMS 对掌握 JAVA 架构,EJB 架构有很好的帮助,消息中间件也是大型分布式系统必须的组件。本次分享主要做全局性介绍,具体的深入需要大家学习,实践,总结,领会。 - -## 4. 常用 MQ 中间件 - -一般商用的容器,比如 WebLogic,JBoss,都支持 JMS 标准,开发上很方便。但免费的比如 Tomcat,Jetty 等则需要使用第三方的消息中间件。本部分内容介绍常用的消息中间件(ActiveMQ、RabbitMQ、RocketMQ、Kafka)以及他们的特点。 - -### 4.1. ActiveMQ - -ActiveMQ 是 Apache 出品,最流行的,能力强劲的开源消息总线。ActiveMQ 是一个完全支持 JMS1.1 和 J2EE 1.4 规范的 JMS Provider 实现,尽管 JMS 规范出台已经是很久的事情了,但是 JMS 在当今的 J2EE 应用中间仍然扮演着特殊的地位。 - -ActiveMQ 特性如下: - -⒈ 多种语言和协议编写客户端。语言: Java,C,C++,C#,Ruby,Perl,Python,PHP。应用协议: OpenWire,Stomp REST,WS Notification,XMPP,AMQP -⒉ 完全支持 JMS1.1 和 J2EE 1.4 规范 (持久化,XA 消息,事务) -⒊ 对 Spring 的支持,ActiveMQ 可以很容易内嵌到使用 Spring 的系统里面去,而且也支持 Spring2.0 的特性 -⒋ 通过了常见 J2EE 服务器(如 Geronimo,JBoss 4,GlassFish,WebLogic)的测试,其中通过 JCA 1.5 resource adaptors 的配置,可以让 ActiveMQ 可以自动的部署到任何兼容 J2EE 1.4 商业服务器上 -⒌ 支持多种传送协议:in-VM,TCP,SSL,NIO,UDP,JGroups,JXTA -⒍ 支持通过 JDBC 和 journal 提供高速的消息持久化 -⒎ 从设计上保证了高性能的集群,客户端-服务器,点对点 -⒏ 支持 Ajax -⒐ 支持与 Axis 的整合 -⒑ 可以很容易得调用内嵌 JMS provider,进行测试 - -### 4.2. RabbitMQ - -RabbitMQ 是流行的开源 MQ 系统,用 erlang 语言开发。RabbitMQ 是 AMQP(高级 MQ 协议)的标准实现。支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP 等,支持 AJAX,持久化。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。 - -结构图如下: - -
    - -几个重要概念: - -- **Broker** - 简单来说就是 MQ 服务器实体。 -- **Exchange** - 消息交换机,它指定消息按什么规则,路由到哪个队列。 -- **Queue** - MQ 载体,每个消息都会被投入到一个或多个队列。 -- **Binding** - 绑定,它的作用就是把 exchange 和 queue 按照路由规则绑定起来。 -- **Routing Key** - 路由关键字,exchange 根据这个关键字进行消息投递。 -- **vhost** - 虚拟主机,一个 broker 里可以开设多个 vhost,用作不同用户的权限分离。 -- **producer** - 消息生产者,就是投递消息的程序。 -- **consumer** - 消息消费者,就是接受消息的程序。 -- **channel** - 消息通道,在客户端的每个连接里,可建立多个 channel,每个 channel 代表一个会话任务。 - -MQ 的使用过程,如下: - -1. 客户端连接到 MQ 服务器,打开一个 channel。 -2. 客户端声明一个 exchange,并设置相关属性。 -3. 客户端声明一个 queue,并设置相关属性。 -4. 客户端使用 routing key,在 exchange 和 queue 之间建立好绑定关系。 -5. 客户端投递消息到 exchange。 - -exchange 接收到消息后,就根据消息的 key 和已经设置的 binding,进行消息路由,将消息投递到一个或多个队列里。 - -### 4.3. ZeroMQ - -号称史上最快的 MQ,它实际类似于 Socket 的一系列接口,他跟 Socket 的区别是:普通的 socket 是端到端的(1:1 的关系),而 ZMQ 却是可以 N:M 的关系,人们对 BSD 套接字的了解较多的是点对点的连接,点对点连接需要显式地建立连接、销毁连接、选择协议(TCP/UDP)和处理错误等,而 ZMQ 屏蔽了这些细节,让你的网络编程更为简单。ZMQ 用于 node 与 node 间的通信,node 可以是主机或者是进程。 - -引用官方的说法: “ZMQ(以下 ZeroMQ 简称 ZMQ)是一个简单好用的传输层,像框架一样的一个 socket library,他使得 Socket 编程更加简单、简洁和性能更高。是一个消息处理队列库,可在多个线程、内核和主机盒之间弹性伸缩。ZMQ 的明确目标是“成为标准网络协议栈的一部分,之后进入 Linux 内核”。现在还未看到它们的成功。但是,它无疑是极具前景的、并且是人们更加需要的“传统”BSD 套接字之上的一 层封装。ZMQ 让编写高性能网络应用程序极为简单和有趣。” - -特点是: - -- 高性能,非持久化; -- 跨平台:支持 Linux、Windows、OS X 等。 -- 多语言支持; C、C++、Java、.NET、Python 等 30 多种开发语言。 -- 可单独部署或集成到应用中使用; -- 可作为 Socket 通信库使用。 - -与 RabbitMQ 相比,ZMQ 并不像是一个传统意义上的 MQ 服务器,事实上,它也根本不是一个服务器,更像一个底层的网络通讯库,在 Socket API 之上做了一层封装,将网络通讯、进程通讯和线程通讯抽象为统一的 API 接口。支持“Request-Reply “,”Publisher-Subscriber“,”Parallel Pipeline”三种基本模型和扩展模型。 - -ZeroMQ 高性能设计要点: - -- **无锁的队列模型** - 对于跨线程间的交互(用户端和 session)之间的数据交换通道 pipe,采用无锁的队列算法 CAS;在 pipe 两端注册有异步事件,在读或者写消息到 pipe 的时,会自动触发读写事件。 -- **批量处理的算法** - 对于传统的消息处理,每个消息在发送和接收的时候,都需要系统的调用,这样对于大量的消息,系统的开销比较大,zeroMQ 对于批量的消息,进行了适应性的优化,可以批量的接收和发送消息。 -- **多核下的线程绑定,无须 CPU 切换** - 区别于传统的多线程并发模式,信号量或者临界区, zeroMQ 充分利用多核的优势,每个核绑定运行一个工作者线程,避免多线程之间的 CPU 切换开销。 - -### 4.4. Kafka - -Kafka 是一种高吞吐量的分布式发布订阅 MQ,它可以处理消费者规模的网站中的所有动作流数据。 这种动作(网页浏览,搜索和其他用户的行动)是在现代网络上的许多社会功能的一个关键因素。 这些数据通常是由于吞吐量的要求而通过处理日志和日志聚合来解决。 对于像 Hadoop 的一样的日志数据和离线分析系统,但又要求实时处理的限制,这是一个可行的解决方案。Kafka 的目的是通过 Hadoop 的并行加载机制来统一线上和离线的消息处理,也是为了通过集群机来提供实时的消费。 - -Kafka 是一种高吞吐量的分布式发布订阅 MQ,有如下特性: - -- 通过 O(1)的磁盘数据结构提供消息的持久化,这种结构对于即使数以 TB 的消息存储也能够保持长时间的稳定性能。(文件追加的方式写入数据,过期的数据定期删除) -- 高吞吐量:即使是非常普通的硬件 Kafka 也可以支持每秒数百万的消息。 -- 支持通过 Kafka 服务器和消费机集群来分区消息。 -- 支持 Hadoop 并行数据加载。 - -Kafka 核心概念 - -- **Broker** - Kafka 集群包含一个或多个服务器,这种服务器被称为 broker[5] -- **Topic** - 每条发布到 Kafka 集群的消息都有一个类别,这个类别被称为 Topic。(物理上不同 Topic 的消息分开存储,逻辑上一个 Topic 的消息虽然保存于一个或多个 broker 上但用户只需指定消息的 Topic 即可生产或消费数据而不必关心数据存于何处) -- **Partition** - Parition 是物理上的概念,每个 Topic 包含一个或多个 Partition. -- **Producer** - 负责发布消息到 Kafka broker -- **Consumer** - 消息消费者,向 Kafka broker 读取消息的客户端。 -- **Consumer** Group - 每个 Consumer 属于一个特定的 Consumer Group(可为每个 Consumer 指定 group name,若不指定 group name 则属于默认的 group)。一般应用在大数据日志处理或对实时性(少量延迟),可靠性(少量丢数据)要求稍低的场景使用。 - -## 5. MQ 示例 - -### 5.1. 电商系统 - -
    - -MQ 采用高可用,可持久化的消息中间件。比如 Active MQ,Rabbit MQ,Rocket MQ。 - -1. 应用将主干逻辑处理完成后,写入 MQ。消息发送是否成功可以开启消息的确认模式。(MQ 返回消息接收成功状态后,应用再返回,这样保障消息的完整性) -2. 扩展流程(发短信,配送处理)订阅队列消息。采用推或拉的方式获取消息并处理。 -3. 消息将应用解耦的同时,带来了数据一致性问题,可以采用最终一致性方式解决。比如主数据写入数据库,扩展应用根据 MQ,并结合数据库方式实现基于 MQ 的后续处理。 - -### 5.2. 日志收集系统 - -
    - -分为 Zookeeper 注册中心,日志收集客户端,Kafka 集群和 Storm 集群(OtherApp)四部分组成。 - -- Zookeeper 注册中心,提出负载均衡和地址查找服务; -- 日志收集客户端,用于采集应用系统的日志,并将数据推送到 kafka 队列; -- Kafka 集群:接收,路由,存储,转发等消息处理; - -Storm 集群:与 OtherApp 处于同一级别,采用拉的方式消费队列中的数据; - -## 6. 参考资料 - -- [大型网站架构系列:分布式 MQ(一)](https://www.cnblogs.com/itfly8/p/5155983.html) -- [大型网站架构系列:MQ(二)](https://www.cnblogs.com/itfly8/p/5156155.html) -- [分布式开放 MQ(RocketMQ)的原理与实践](https://www.jianshu.com/p/453c6e7ff81c) -- [阿里 RocketMQ 优势对比](https://juejin.im/entry/5a0abfb5f265da43062a4a91) diff --git a/docs/theory/security-theory.md b/docs/theory/security-theory.md deleted file mode 100644 index 72b689b4..00000000 --- a/docs/theory/security-theory.md +++ /dev/null @@ -1,325 +0,0 @@ -# 安全领域技术 - -## 认证 - -### SSO - -SSO(Single Sign On),即单点登录。SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。 - -### Oauth 2.0 - -#### Oauth 2.0 思路 - -OAuth在"客户端"与"服务提供商"之间,设置了一个授权层(authorization layer)。"客户端"不能直接登录"服务提供商",只能登录授权层,以此将用户与客户端区分开来。"客户端"登录授权层所用的令牌(token),与用户的密码不同。用户可以在登录的时候,指定授权层令牌的权限范围和有效期。 - -"客户端"登录授权层以后,"服务提供商"根据令牌的权限范围和有效期,向"客户端"开放用户储存的资料。 - -OAuth 2.0的运行流程如下图,摘自RFC 6749。 - -
    - -> (A)用户打开客户端以后,客户端要求用户给予授权。 -> -> (B)用户同意给予客户端授权。 -> -> (C)客户端使用上一步获得的授权,向认证服务器申请令牌。 -> -> (D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。 -> -> (E)客户端使用令牌,向资源服务器申请获取资源。 -> -> (F)资源服务器确认令牌无误,同意向客户端开放资源。 - -不难看出来,上面六个步骤之中,B是关键,即用户怎样才能给于客户端授权。有了这个授权以后,客户端就可以获取令牌,进而凭令牌获取资源。 - -#### 授权模式 - -客户端必须得到用户的授权(authorization grant),才能获得令牌(access token)。OAuth 2.0定义了四种授权方式。 - -- 授权码模式(authorization code) -- 简化模式(implicit) -- 密码模式(resource owner password credentials) -- 客户端模式(client credentials) - -#### 授权码模式 - -授权码模式(authorization code)是功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与"服务提供商"的认证服务器进行互动。 - -
    - -它的步骤如下: - -> (A)用户访问客户端,后者将前者导向认证服务器。 -> -> (B)用户选择是否给予客户端授权。 -> -> (C)假设用户给予授权,认证服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个授权码。 -> -> (D)客户端收到授权码,附上早先的"重定向URI",向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。 -> -> (E)认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。 - -下面是上面这些步骤所需要的参数。 - -A步骤中,客户端申请认证的URI,包含以下参数: - -- response_type:表示授权类型,必选项,此处的值固定为"code" -- client_id:表示客户端的ID,必选项 -- redirect_uri:表示重定向URI,可选项 -- scope:表示申请的权限范围,可选项 -- state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。 - -下面是一个例子。 - -> ```http -> GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz -> &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1 -> Host: server.example.com -> ``` - -C步骤中,服务器回应客户端的URI,包含以下参数: - -- code:表示授权码,必选项。该码的有效期应该很短,通常设为10分钟,客户端只能使用该码一次,否则会被授权服务器拒绝。该码与客户端ID和重定向URI,是一一对应关系。 -- state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。 - -下面是一个例子。 - -> ```http -> HTTP/1.1 302 Found -> Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA -> &state=xyz -> ``` - -D步骤中,客户端向认证服务器申请令牌的HTTP请求,包含以下参数: - -- grant_type:表示使用的授权模式,必选项,此处的值固定为"authorization_code"。 -- code:表示上一步获得的授权码,必选项。 -- redirect_uri:表示重定向URI,必选项,且必须与A步骤中的该参数值保持一致。 -- client_id:表示客户端ID,必选项。 - -下面是一个例子。 - -> ```http -> POST /token HTTP/1.1 -> Host: server.example.com -> Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW -> Content-Type: application/x-www-form-urlencoded -> -> grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA -> &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb -> ``` - -E步骤中,认证服务器发送的HTTP回复,包含以下参数: - -- access_token:表示访问令牌,必选项。 -- token_type:表示令牌类型,该值大小写不敏感,必选项,可以是bearer类型或mac类型。 -- expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。 -- refresh_token:表示更新令牌,用来获取下一次的访问令牌,可选项。 -- scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。 - -下面是一个例子。 - -> ```http -> HTTP/1.1 200 OK -> Content-Type: application/json;charset=UTF-8 -> Cache-Control: no-store -> Pragma: no-cache -> -> { -> "access_token":"2YotnFZFEjr1zCsicMWpAA", -> "token_type":"example", -> "expires_in":3600, -> "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA", -> "example_parameter":"example_value" -> } -> ``` - -从上面代码可以看到,相关参数使用JSON格式发送(Content-Type: application/json)。此外,HTTP头信息中明确指定不得缓存。 - -#### 简化模式 - -简化模式(implicit grant type)不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了"授权码"这个步骤,因此得名。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。 - -
    - -它的步骤如下: - -> (A)客户端将用户导向认证服务器。 -> -> (B)用户决定是否给于客户端授权。 -> -> (C)假设用户给予授权,认证服务器将用户导向客户端指定的"重定向URI",并在URI的Hash部分包含了访问令牌。 -> -> (D)浏览器向资源服务器发出请求,其中不包括上一步收到的Hash值。 -> -> (E)资源服务器返回一个网页,其中包含的代码可以获取Hash值中的令牌。 -> -> (F)浏览器执行上一步获得的脚本,提取出令牌。 -> -> (G)浏览器将令牌发给客户端。 - -下面是上面这些步骤所需要的参数。 - -A步骤中,客户端发出的HTTP请求,包含以下参数: - -- response_type:表示授权类型,此处的值固定为"token",必选项。 -- client_id:表示客户端的ID,必选项。 -- redirect_uri:表示重定向的URI,可选项。 -- scope:表示权限范围,可选项。 -- state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。 - -下面是一个例子。 - -> ```http -> GET /authorize?response_type=token&client_id=s6BhdRkqt3&state=xyz -> &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1 -> Host: server.example.com -> ``` - -C步骤中,认证服务器回应客户端的URI,包含以下参数: - -- access_token:表示访问令牌,必选项。 -- token_type:表示令牌类型,该值大小写不敏感,必选项。 -- expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。 -- scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。 -- state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。 - -下面是一个例子。 - -> ```http -> HTTP/1.1 302 Found -> Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA -> &state=xyz&token_type=example&expires_in=3600 -> ``` - -在上面的例子中,认证服务器用HTTP头信息的Location栏,指定浏览器重定向的网址。注意,在这个网址的Hash部分包含了令牌。 - -根据上面的D步骤,下一步浏览器会访问Location指定的网址,但是Hash部分不会发送。接下来的E步骤,服务提供商的资源服务器发送过来的代码,会提取出Hash中的令牌。 - -密码模式 - -密码模式(Resource Owner Password Credentials Grant)中,用户向客户端提供自己的用户名和密码。客户端使用这些信息,向"服务商提供商"索要授权。 - -在这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码。这通常用在用户对客户端高度信任的情况下,比如客户端是操作系统的一部分,或者由一个著名公司出品。而认证服务器只有在其他授权模式无法执行的情况下,才能考虑使用这种模式。 - -
    - -它的步骤如下: - -> (A)用户向客户端提供用户名和密码。 -> -> (B)客户端将用户名和密码发给认证服务器,向后者请求令牌。 -> -> (C)认证服务器确认无误后,向客户端提供访问令牌。 - -B步骤中,客户端发出的HTTP请求,包含以下参数: - -- grant_type:表示授权类型,此处的值固定为"password",必选项。 -- username:表示用户名,必选项。 -- password:表示用户的密码,必选项。 -- scope:表示权限范围,可选项。 - -下面是一个例子。 - -> ```http -> POST /token HTTP/1.1 -> Host: server.example.com -> Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW -> Content-Type: application/x-www-form-urlencoded -> -> grant_type=password&username=johndoe&password=A3ddj3w -> ``` - -C步骤中,认证服务器向客户端发送访问令牌,下面是一个例子。 - -> ```http -> HTTP/1.1 200 OK -> Content-Type: application/json;charset=UTF-8 -> Cache-Control: no-store -> Pragma: no-cache -> -> { -> "access_token":"2YotnFZFEjr1zCsicMWpAA", -> "token_type":"example", -> "expires_in":3600, -> "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA", -> "example_parameter":"example_value" -> } -> ``` - -上面代码中,各个参数的含义参见《授权码模式》一节。 - -整个过程中,客户端不得保存用户的密码。 - -客户端模式 - -客户端模式(Client Credentials Grant)指客户端以自己的名义,而不是以用户的名义,向"服务提供商"进行认证。严格地说,客户端模式并不属于OAuth框架所要解决的问题。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求"服务提供商"提供服务,其实不存在授权问题。 - -
    - -它的步骤如下: - -> (A)客户端向认证服务器进行身份认证,并要求一个访问令牌。 -> -> (B)认证服务器确认无误后,向客户端提供访问令牌。 - -A步骤中,客户端发出的HTTP请求,包含以下参数: - -- grant*type:表示授权类型,此处的值固定为"client*credentials",必选项。 -- scope:表示权限范围,可选项。 - -> ```http -> POST /token HTTP/1.1 -> Host: server.example.com -> Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW -> Content-Type: application/x-www-form-urlencoded -> -> grant_type=client_credentials -> ``` - -认证服务器必须以某种方式,验证客户端身份。 - -B步骤中,认证服务器向客户端发送访问令牌,下面是一个例子。 - -> ```http -> HTTP/1.1 200 OK -> Content-Type: application/json;charset=UTF-8 -> Cache-Control: no-store -> Pragma: no-cache -> -> { -> "access_token":"2YotnFZFEjr1zCsicMWpAA", -> "token_type":"example", -> "expires_in":3600, -> "example_parameter":"example_value" -> } -> ``` - -上面代码中,各个参数的含义参见《授权码模式》一节。 - -#### 更新令牌 - -如果用户访问的时候,客户端的"访问令牌"已经过期,则需要使用"更新令牌"申请一个新的访问令牌。 - -客户端发出更新令牌的HTTP请求,包含以下参数: - -- grant*type:表示使用的授权模式,此处的值固定为"refresh*token",必选项。 -- refresh_token:表示早前收到的更新令牌,必选项。 -- scope:表示申请的授权范围,不可以超出上一次申请的范围,如果省略该参数,则表示与上一次一致。 - -下面是一个例子。 - -> ```http -> POST /token HTTP/1.1 -> Host: server.example.com -> Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW -> Content-Type: application/x-www-form-urlencoded -> -> grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA -> ``` - -#### 参考资料 - -http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html - -## 权限 diff --git a/docs/theory/sharding-theory.md b/docs/theory/sharding-theory.md deleted file mode 100644 index 2fd85378..00000000 --- a/docs/theory/sharding-theory.md +++ /dev/null @@ -1,145 +0,0 @@ -# 分库分表基本原理 - -分片(Sharding)的基本思想就要把一个数据库切分成多个部分,存储在不同的数据库(server)上,从而缓解单一数据库的性能问题。 - -## 数据分片 - -数据分片指按照某个维度将存放在单一数据库中的数据分散地存放至多个数据库或表中以达到提升性能瓶颈以及可用性的效果。 数据分片的有效手段是对关系型数据库进行分库和分表。分库和分表均可以有效的避免由数据量超过可承受阈值而产生的查询瓶颈。 除此之外,分库还能够用于有效的分散对数据库单点的访问量;分表虽然无法缓解数据库压力,但却能够提供尽量将分布式事务转化为本地事务的可能,一旦涉及到跨库的更新操作,分布式事务往往会使问题变得复杂。 使用多主多从的分片方式,可以有效的避免数据单点,从而提升数据架构的可用性。 - -通过分库和分表进行数据的拆分来使得各个表的数据量保持在阈值以下,以及对流量进行疏导应对高访问量,是应对高并发和海量数据系统的有效手段。 数据分片的拆分方式又分为垂直分片和水平分片。 - -### 垂直分片 - -**按照业务拆分的方式称为垂直分片**,又称为纵向拆分,它的核心理念是专库专用。 - -在拆分之前,一个数据库由多个数据表构成,每个表对应着不同的业务。而拆分之后,则是**按照业务将表进行归类,分布到不同的数据库中**,从而将压力分散至不同的数据库。下图展示了根据业务需要,将用户表和订单表垂直分片到不同的数据库的方案。 - -![垂直分片](https://shardingsphere.apache.org/document/current/img/sharding/vertical_sharding.png) - -垂直分片往往需要对架构和设计进行调整。通常来讲,是来不及应对互联网业务需求快速变化的;而且,它也并无法真正的解决单点瓶颈。**垂直拆分可以缓解数据量和访问量带来的问题,但无法根治。如果垂直拆分之后,表中的数据量依然超过单节点所能承载的阈值,则需要水平分片来进一步处理**。 - -### 水平分片 - -水平分片又称为横向拆分。 相对于垂直分片,**水平分片不再将数据根据业务逻辑分类,而是通过某个字段(或某几个字段),根据某种规则将数据分散至多个库或表中,每个分片仅包含数据的一部分**。 例如:根据主键分片,偶数主键的记录放入 0 库(或表),奇数主键的记录放入 1 库(或表),如下图所示。 - -![水平分片](https://shardingsphere.apache.org/document/current/img/sharding/horizontal_sharding.png) - -**水平分片从理论上突破了单机数据量处理的瓶颈,并且扩展相对自由,是分库分表的标准解决方案**。 - -## 分库分表的问题 - -### 事务问题 - -解决事务问题目前有两种可行的方案:分布式事务和通过应用程序与数据库共同控制实现事务下面对两套方案进行一个简单的对比。 - -- 方案一:使用分布式事务 - - 优点:交由数据库管理,简单有效 - - 缺点:性能代价高,特别是 sharding 越来越多时 -- 方案二:由应用程序和数据库共同控制 - - 原理:将一个跨多个数据库的分布式事务分拆成多个仅处于单个数据库上面的小事务,并通过应用程序来总控各个小事务。 - - 优点:性能上有优势 - - 缺点:需要应用程序在事务控制上做灵活设计。如果使用 了[spring](http://lib.csdn.net/base/javaee)的事务管理,改动起来会面临一定的困难。 - -跨库事务也是分布式的数据库集群要面对的棘手事情。 合理采用分表,可以在降低单表数据量的情况下,尽量使用本地事务,善于使用同库不同表可有效避免分布式事务带来的麻烦。在不能避免跨库事务的场景,有些业务仍然需要保持事务的一致性。 而基于 XA 的分布式事务由于在并发度高的场景中性能无法满足需要,并未被互联网巨头大规模使用,他们大多采用最终一致性的柔性事务代替强一致事务。 - -### 跨节点 Join 的问题 - -只要是进行切分,跨节点 Join 的问题是不可避免的。但是良好的设计和切分却可以减少此类情况的发生。解决这一问题的普遍做法是分两次查询实现。在第一次查询的结果集中找出关联数据的 id,根据这些 id 发起第二次请求得到关联数据。 - -### 跨节点的 count,order by,group by 以及聚合函数问题 - -这些是一类问题,因为它们都需要基于全部数据集合进行计算。多数的代理都不会自动处理合并工作。解决方案:与解决跨节点 join 问题的类似,分别在各个节点上得到结果后在应用程序端进行合并。和 join 不同的是每个结点的查询可以并行执行,因此很多时候它的速度要比单一大表快很多。但如果结果集很大,对应用程序内存的消耗是一个问题。 - -### 数据迁移,容量规划,扩容等问题 - -来自淘宝综合业务平台团队,它利用对 2 的倍数取余具有向前兼容的特性(如对 4 取余得 1 的数对 2 取余也是 1)来分配数据,避免了**行级别的数据迁移**,但是依然需要进行**表级别的迁移**,同时对扩容规模和分表数量都有限制。总得来说,这些方案都不是十分的理想,多多少少都存在一些缺点,这也从一个侧面反映出了 Sharding 扩容的难度。 - -### ID 问题 - -一旦数据库被切分到多个物理结点上,我们将不能再依赖数据库自身的主键生成机制。一方面,某个分区数据库自生成的 ID 无法保证在全局上是唯一的;另一方面,应用程序在插入数据之前需要先获得 ID,以便进行 SQL 路由. 一些常见的主键生成策略 - -#### UUID - -使用 UUID 作主键是最简单的方案,但是缺点也是非常明显的。由于 UUID 非常的长,除占用大量存储空间外,最主要的问题是在索引上,在建立索引和基于索引进行查询时都存在性能问题。 - -#### 结合数据库维护一个 Sequence 表 - -此方案的思路也很简单,在数据库中建立一个 Sequence 表,表的结构类似于: - -```SQL -CREATE TABLE `SEQUENCE` ( - `table_name` varchar(18) NOT NULL, - `nextid` bigint(20) NOT NULL, - PRIMARY KEY (`table_name`) -) ENGINE=InnoDB -``` - -每当需要为某个表的新纪录生成 ID 时就从 Sequence 表中取出对应表的 nextid,并将 nextid 的值加 1 后更新到数据库中以备下次使用。此方案也较简单,但缺点同样明显:由于所有插入任何都需要访问该表,该表很容易成为系统性能瓶颈,同时它也存在单点问题,一旦该表数据库失效,整个应用程序将无法工作。有人提出使用 Master-Slave 进行主从同步,但这也只能解决单点问题,并不能解决读写比为 1:1 的访问压力问题。 - -#### [Twitter 的分布式自增 ID 算法 Snowflake](http://blog.sina.com.cn/s/blog_6b7c2e660102vbi2.html) - -在分布式系统中,需要生成全局 UID 的场合还是比较多的,twitter 的 snowflake 解决了这种需求,实现也还是很简单的,除去配置信息,核心代码就是毫秒级时间 41 位 机器 ID 10 位 毫秒内序列 12 位。 - -- 10---0000000000 0000000000 0000000000 0000000000 0 --- 00000 ---00000 ---000000000000 在上面的字符串中,第一位为未使用(实际上也可作为 long 的符号位),接下来的 41 位为毫秒级时间,然后 5 位 datacenter 标识位,5 位机器 ID(并不算标识符,实际是为线程标识),然后 12 位该毫秒内的当前毫秒内的计数,加起来刚好 64 位,为一个 Long 型。 - -这样的好处是,整体上按照时间自增排序,并且整个分布式系统内不会产生 ID 碰撞(由 datacenter 和机器 ID 作区分),并且效率较高,经测试,snowflake 每秒能够产生 26 万 ID 左右,完全满足需要。 - -### 跨分片的排序分页 - -一般来讲,分页时需要按照指定字段进行排序。当排序字段就是分片字段的时候,我们通过分片规则可以比较容易定位到指定的分片,而当排序字段非分片字段的时候,情况就会变得比较复杂了。为了最终结果的准确性,我们需要在不同的分片节点中将数据进行排序并返回,并将不同分片返回的结果集进行汇总和再次排序,最后再返回给用户。如下图所示: - -![img](https://upload-images.jianshu.io/upload_images/3710706-925381b9a478c8df.png?imageMogr2/auto-orient/strip|imageView2/2/w/640/format/webp) - -上面图中所描述的只是最简单的一种情况(取第一页数据),看起来对性能的影响并不大。但是,如果想取出第 10 页数据,情况又将变得复杂很多,如下图所示: - -![img](https://upload-images.jianshu.io/upload_images/3710706-9a7cfbdb95bb9b70.png?imageMogr2/auto-orient/strip|imageView2/2/w/640/format/webp) - -有些读者可能并不太理解,为什么不能像获取第一页数据那样简单处理(排序取出前 10 条再合并、排序)。其实并不难理解,因为各分片节点中的数据可能是随机的,为了排序的准确性,必须把所有分片节点的前 N 页数据都排序好后做合并,最后再进行整体的排序。很显然,这样的操作是比较消耗资源的,用户越往后翻页,系统性能将会越差。 - -那如何解决分库情况下的分页问题呢?有以下几种办法: - -如果是在前台应用提供分页,则限定用户只能看前面 n 页,这个限制在业务上也是合理的,一般看后面的分页意义不大(如果一定要看,可以要求用户缩小范围重新查询)。 - -如果是后台批处理任务要求分批获取数据,则可以加大 page size,比如每次获取 5000 条记录,有效减少分页数(当然离线访问一般走备库,避免冲击主库)。 - -分库设计时,一般还有配套大数据平台汇总所有分库的记录,有些分页查询可以考虑走大数据平台。 - -### 分库策略 - -- 根据数值范围,比如用户 Id 为 1-9999 的记录分到第一个库,10000-20000 的分到第二个库,以此类推。 -- 根据数值取模,比如用户 Id mod n,余数为 0 的记录放到第一个库,余数为 1 的放到第二个库,以此类推。 - -优劣比较:评价指标按照范围分库按照 Mod 分库库数量前期数目比较小,可以随用户/业务按需增长前期即根据 mode 因子确定库数量,数目一般比较大访问性能前期库数量小,全库查询消耗资源少,单库查询性能略差前期库数量大,全库查询消耗资源多,单库查询性能略好调整库数量比较容易,一般只需为新用户增加库,老库拆分也只影响单个库困难,改变 mod 因子导致数据在所有库之间迁移数据热点新旧用户购物频率有差异,有数据热点问题新旧用户均匀到分布到各个库,无热点实践中,为了处理简单,选择 mod 分库的比较多。同时二次分库时,为了数据迁移方便,一般是按倍数增加,比如初始 4 个库,二次分裂为 8 个,再 16 个。这样对于某个库的数据,一半数据移到新库,剩余不动,对比每次只增加一个库,所有数据都要大规模变动。补充下,mod 分库一般每个库记录数比较均匀,但也有些数据库,存在超级 Id,这些 Id 的记录远远超过其他 Id,比如在广告场景下,某个大广告主的广告数可能占总体很大比例。如果按照广告主 Id 取模分库,某些库的记录数会特别多,对于这些超级 Id,需要提供单独库来存储记录。 - -### 分库数量 - -分库数量首先和单库能处理的记录数有关,一般来说,Mysql 单库超过 5000 万条记录,Oracle 单库超过 1 亿条记录,DB 压力就很大(当然处理能力和字段数量/访问模式/记录长度有进一步关系)。 - -在满足上述前提下,如果分库数量少,达不到分散存储和减轻 DB 性能压力的目的;如果分库的数量多,好处是每个库记录少,单库访问性能好,但对于跨多个库的访问,应用程序需要访问多个库,如果是并发模式,要消耗宝贵的线程资源;如果是串行模式,执行时间会急剧增加。 - -最后分库数量还直接影响硬件的投入,一般每个分库跑在单独物理机上,多一个库意味多一台设备。所以具体分多少个库,要综合评估,一般初次分库建议分 4-8 个库。 - -## 读写分离 - -面对日益增加的系统访问量,数据库的吞吐量面临着巨大瓶颈。 对于同一时刻有大量并发读操作和较少写操作类型的应用系统来说,将数据库拆分为主库和从库,主库负责处理事务性的增删改操作,从库负责处理查询操作,能够有效的避免由数据更新导致的行锁,使得整个系统的查询性能得到极大的改善。 - -通过一主多从的配置方式,可以将查询请求均匀的分散到多个数据副本,能够进一步的提升系统的处理能力。 使用多主多从的方式,不但能够提升系统的吞吐量,还能够提升系统的可用性,可以达到在任何一个数据库宕机,甚至磁盘物理损坏的情况下仍然不影响系统的正常运行。 - -与将数据根据分片键打散至各个数据节点的水平分片不同,读写分离则是根据 SQL 语义的分析,将读操作和写操作分别路由至主库与从库。 - -![读写分离](https://shardingsphere.apache.org/document/current/img/read-write-split/read-write-split.png) - -读写分离的数据节点中的数据内容是一致的,而水平分片的每个数据节点的数据内容却并不相同。将水平分片和读写分离联合使用,能够更加有效的提升系统性能。 - -### 挑战 - -读写分离虽然可以提升系统的吞吐量和可用性,但同时也带来了数据不一致的问题。 这包括多个主库之间的数据一致性,以及主库与从库之间的数据一致性的问题。 并且,读写分离也带来了与数据分片同样的问题,它同样会使得应用开发和运维人员对数据库的操作和运维变得更加复杂。 下图展现了将分库分表与读写分离一同使用时,应用程序与数据库集群之间的复杂拓扑关系。 - -![数据分片 + 读写分离](https://shardingsphere.apache.org/document/current/img/read-write-split/sharding-read-write-split.png) - -## 参考资料 - -- [ShardingSphere 官方文档](https://shardingsphere.apache.org/document/current/cn/overview/) -- [“分库分表" ?选型和流程要慎重,否则会失控](https://juejin.im/post/5bf778ef5188251b8a26ed8b) -- [分库分表需要考虑的问题及方案](https://www.jianshu.com/p/32b3e91aa22c) - diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 00000000..eb6bb1f5 --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,7 @@ +/** + * @see https://prettier.io/docs/en/options.html + * @see https://prettier.io/docs/en/configuration.html + */ +module.exports = { + tabWidth: 2, semi: false, singleQuote: true +} From ea10ac4dc91bc4cf7088e1ff88f58ea64f12538b Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Wed, 27 Nov 2019 18:36:09 +0800 Subject: [PATCH 47/75] update docs --- README.md | 2 - docs/README.md | 2 - docs/sidebar.md | 2 - docs/technology/monitor/jmeter.md | 175 ------------------------------ 4 files changed, 181 deletions(-) delete mode 100644 docs/technology/monitor/jmeter.md diff --git a/README.md b/README.md index bbbc11c8..8156000a 100644 --- a/README.md +++ b/README.md @@ -123,8 +123,6 @@ - [ZooKeeper 运维指南](docs/technology/monitor/zookeeper-ops.md) - 诊断 - [Arthas 应用指南](docs/technology/monitor/arthas.md) -- 测试 - - [Jmeter 应用指南](docs/technology/monitor/jmeter.md) ## ☕ JavaEE diff --git a/docs/README.md b/docs/README.md index 8e71fcef..69e56572 100644 --- a/docs/README.md +++ b/docs/README.md @@ -123,8 +123,6 @@ - [ZooKeeper 运维指南](technology/monitor/zookeeper-ops.md) - 诊断 - [Arthas 应用指南](technology/monitor/arthas.md) -- 测试 - - [Jmeter 应用指南](technology/monitor/jmeter.md) ## ☕ JavaEE diff --git a/docs/sidebar.md b/docs/sidebar.md index ac8ff22c..8bad0ca9 100644 --- a/docs/sidebar.md +++ b/docs/sidebar.md @@ -59,8 +59,6 @@ - [ZooKeeper 运维指南](technology/monitor/zookeeper-ops.md) - 诊断 - [Arthas 应用指南](technology/monitor/arthas.md) - - 测试 - - [Jmeter 应用指南](technology/monitor/jmeter.md) - [**☕ JavaEE**](javaee/README.md) - [JavaEE 面经](javaee/javaee-interview.md) - [JavaEE 之 Servlet 指南](javaee/javaee-servlet.md) diff --git a/docs/technology/monitor/jmeter.md b/docs/technology/monitor/jmeter.md deleted file mode 100644 index c7958534..00000000 --- a/docs/technology/monitor/jmeter.md +++ /dev/null @@ -1,175 +0,0 @@ -# JMeter 应用指南 - -> [Jmeter](https://github.com/apache/jmeter) 是一款使用 Java 开发的功能和性能测试软件。 -> -> 🎁 本文编辑时的最新版本为:5.1.1 - -## 1. 简介 - -[Jmeter](https://github.com/apache/jmeter) 是一款使用 Java 开发的功能和性能测试软件。 - -### 1.1. 特性 - -Jmeter 能够加载和性能测试许多不同的应用程序/服务器/协议类型: - -- 网络 - HTTP,HTTPS(Java,NodeJS,PHP,ASP.NET 等) -- SOAP / REST Web 服务 -- FTP 文件 -- 通过 JDBC 的数据库 -- LDAP -- 通过 JMS 的面向消息的中间件(MOM) -- 邮件-SMTP(S),POP3(S)和 IMAP(S) -- 本机命令或 Shell 脚本 -- TCP 协议 -- Java 对象 - -### 1.2. 工作流 - -Jmeter 的工作原理是仿真用户向服务器发送请求,并收集服务器应答信息并计算统计信息。 - -Jmeter 的工作流如下图所示: - -![](http://dunwu.test.upcdn.net/cs/java/javaweb/technology/test/jmeter-workflow.png!zp) - -### 1.3. 主要元素 - -Jmeter 的主要元素如下: - -- **`测试计划(Test Plan)`** - 可以将测试计划视为 JMeter 的测试脚本 。测试计划由测试元素组成,例如线程组,逻辑控制器,样本生成控制器,监听器,定时器,断言和配置元素。 -- **`线程组(Thread Group)`** - 线程组的作用是:模拟大量用户负载的运行场景。 - - 设置线程数 - - 设置加速期 - - 设置执行测试的次数 -- **`控制器(Controllers)`** - 可以分为两大类: - - **`采样器(Sampler)`** - 采样器的作用是模拟用户对目标服务器发送请求。 采样器是必须将组件添加到测试计划中的,因为它只能让 JMeter 知道需要将哪种类型的请求发送到服务器。 请求可以是 HTTP,HTTP(s),FTP,TCP,SMTP,SOAP 等。 - - **`逻辑控制器`** - 逻辑控制器的作用是:控制多个请求发送的循环次数及顺序等。 -- **`监听器(Listeners)`** - 监听器的作用是:收集测试结果信息。如查看结果树、汇总报告等。 -- **`计时器(Timers)`** - 计时器的作用是:控制多个请求发送的时间频次。 -- **`配置元素(Configuration Elements)`** - 配置元素的工作与采样器的工作类似。但是,它不发送请求,而是提供预备的数据等,如 CSV、函数助手。 -- **`预处理器元素(Pre-Processor Elements)`** - 预处理器元素在采样器发出请求之前执行,如果预处理器附加到采样器元素,那么它将在该采样器元素运行之前执行。预处理器元素用于在运行之前准备环境及参数。 -- **`后处理器元素(Post-Processor Elements)`** - 后处理器元素是在发送采样器请求之后执行的元素,常用于处理响应数据。 - -![](http://dunwu.test.upcdn.net/cs/java/javaweb/technology/test/jmeter-elements.png!zp) - -> 📌 提示: -> -> Jmeter 元素的数量关系大致如下: -> -> 1. 脚本中最多只能有一个测试计划。 -> 2. 测试计划中至少要有一个线程组。 -> 3. 线程组中至少要有一个取样器。 -> 4. 线程组中至少要有一个监听器。 - -## 2. 安装 - -### 2.1. 环境要求 - -- 必要的。Jmeter 基于 JDK8 开发,所以必须运行在 JDK8 环境。 - - - JDK8 - -- 可选的。有些 jar 包不是 Jmeter 提供的,如果需要相应的功能,需要自行下载并置于 `lib` 目录。 - - JDBC - - JMS - - [Bouncy Castle](http://www.bouncycastle.org/test_releases.html) - -### 2.2. 下载 - -进入 [**Jmeter 官网下载地址**](https://jmeter.apache.org/download_jmeter.cgi) 选择需要版本进行下载。 - -### 2.3. 启动 - -解压 Jmeter 压缩包,进入 bin 目录 - -Unix 类系统运行 `jmeter` ;Windows 系统运行 `jmeter.bat` - -![image-20191024104517721](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024104517721.png!zp) - -## 3. 使用 Jmeter - -### 3.1. 创建测试计划 - -> 🔔 注意: -> -> - 在运行整个测试计划之前,应保存测试计划。 -> -> - JMeter 的测试计划以 `.jmx` 扩展文件的形式保存。 - -#### 3.1.1. 创建线程组 - -- 在“测试计划”上右键 【添加】=>【线程(用户)】=>【线程组】。 - -- 设置线程数和循环次数 - -![image-20191024105545736](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024105545736.png!zp) - -#### 3.1.2. 配置原件 - -- 在新建的线程组上右键 【添加】=>【配置元件】=>【HTTP 请求默认值】。 - -- 填写协议、服务器名称或 IP、端口号 - -![image-20191024110016264](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024110016264.png!zp) - -#### 3.1.3. 构造 HTTP 请求 - -- 在“线程组”上右键 【添加-】=>【取样器】=>【HTTP 请求】。 - -- 填写协议、服务器名称或 IP、端口号(如果配置了 HTTP 请求默认值可以忽略) -- 填写方法、路径 -- 填写参数、消息体数据、文件上传 - -![image-20191024110953063](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024110953063.png!zp) - -#### 3.1.4. 添加 HTTP 请求头 - -- 在“线程组”上右键 【添加】=>【配置元件】=>【HTTP 信息头管理器】 -- 由于我的测试例中传输的数据为 json 形式,所以设置键值对 `Content-Type`:`application/json` - -![image-20191024111825226](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024111825226.png!zp) - -#### 3.1.5. 添加断言 - -- 在“线程组”上右键 【添加】=>【断言】=>【 响应断言 】 -- 在我的案例中,以 HTTP 应答状态码为 200 来判断请求是否成功 - -![image-20191024112335130](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024112335130.png!zp) - -#### 3.1.6. 添加察看结果树 - -- 在“线程组”上右键 【添加】=>【监听器】=>【察看结果树】 -- 直接点击运行,就可以查看测试结果 - -![image-20191024113849270](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024113849270.png!zp) - -#### 3.1.7. 添加汇总报告 - -- 在“线程组”上右键 【添加】=>【监听器】=>【汇总报告】 -- 直接点击运行,就可以查看测试结果 - -![image-20191024114016424](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024114016424.png!zp) - -#### 3.1.8. 保存测试计划 - -执行测试计划前,GUI 会提示先保存配置为 `jmx` 文件。 - -### 3.2. 执行测试计划 - -官方建议不要直接使用 GUI 来执行测试计划,这种模式指适用于创建测试计划和 debug。 - -执行测试计划应该使用命令行模式,语法形式如下: - -```bash -jmeter -n -t [jmx file] -l [results file] -e -o [Path to web report folder] -``` - -执行测试计划后,在 `-e -o` 参数后指定的 web 报告目录下,可以找到测试报告内容。在浏览器中打开 `index.html` 文件,可以看到如下报告: - -![image-20191024120233058](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024120233058.png!zp) - -## 4. 参考资料 - -- [Jmeter 官网](https://jmeter.apache.org/) -- [Jmeter Github](https://github.com/apache/jmeter) -- [Jmeter 性能测试入门](https://www.cnblogs.com/TankXiao/p/4045439.html) -- [易百教程 - Jmeter 教程](https://www.yiibai.com/jmeter) From 92849175777b1a6894573009b1d0b23ec8f90db3 Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Tue, 7 Jan 2020 14:18:21 +0800 Subject: [PATCH 48/75] update --- .editorconfig | 4 +- .gitignore | 2 + README.md | 43 +- assets/cache.eddx | Bin 58252 -> 0 bytes assets/cache.xmind | Bin 552684 -> 0 bytes assets/zookeeper.eddx | Bin 0 -> 58478 bytes docs/README.md | 43 +- docs/book.json | 69 +++ docs/coverpage.md | 7 +- docs/index.html | 441 ++++++--------- docs/javaee/javaee-filter-listener.md | 2 +- docs/javaee/javaee-interview.md | 4 +- docs/javaee/javaee-jsp.md | 18 +- docs/javaee/javaee-servlet.md | 2 +- docs/sidebar.md | 29 +- docs/technology/cache/CacheInterview.md | 22 +- docs/technology/cache/Ehcache.md | 2 +- docs/technology/data/Mybatis.md | 10 +- .../{monitor => distributed}/zookeeper-ops.md | 48 +- .../{monitor => distributed}/zookeeper.md | 503 ++++++++++-------- docs/technology/mq/ActiveMQ.md | 6 +- docs/technology/mq/MqInterview.md | 36 +- docs/technology/mq/RocketmqAdvanced.md | 4 +- docs/technology/mq/kafka-advance.md | 38 +- docs/technology/mq/kafka-basic.md | 18 +- docs/technology/server/Tomcat.md | 4 +- 26 files changed, 683 insertions(+), 672 deletions(-) delete mode 100644 assets/cache.eddx delete mode 100644 assets/cache.xmind create mode 100644 assets/zookeeper.eddx create mode 100644 docs/book.json rename docs/technology/{monitor => distributed}/zookeeper-ops.md (90%) rename docs/technology/{monitor => distributed}/zookeeper.md (52%) diff --git a/.editorconfig b/.editorconfig index c7ca29b2..d72a75ea 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,7 +10,7 @@ root = true [*] end_of_line = lf indent_size = 2 -indent_style = tab +indent_style = space max_line_length = 120 charset = utf-8 trim_trailing_whitespace = true @@ -19,7 +19,7 @@ insert_final_newline = true [*.{bat, cmd}] end_of_line = crlf -[*.{java, groovy, kt, sh}] +[*.{java, gradle, groovy, kt, sh}] indent_size = 4 [*.md] diff --git a/.gitignore b/.gitignore index b8875768..4a99239d 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ hs_err_pid* # maven plugin temp files .flattened-pom.xml +package-lock.json # ------------------------------- javascript ------------------------------- @@ -47,6 +48,7 @@ npm-debug.log* yarn-debug.log* yarn-error.log* bundle*.js +book.pdf # ------------------------------- intellij ------------------------------- diff --git a/README.md b/README.md index 8156000a..8998168f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,16 @@ -# JavaWeb +

    + + logo + +

    -> JavaWeb 开发之路经验总结。 +

    + license +

    + +

    JavaWeb

    + +> ☕ **JavaWeb** 是一个 Java Web 领域开发技术教程。 > > - 🔁 项目同步维护:[Github](https://github.com/dunwu/javaweb/) | [Gitee](https://gitee.com/turnon/javaweb/) > - 📖 电子书阅读:[Github Pages](https://dunwu.github.io/javaweb/) | [Gitee Pages](http://turnon.gitee.io/javaweb/) @@ -40,17 +50,17 @@ > [🎨架构设计](https://github.com/dunwu/blog/tree/master/source/_posts/design/architecture) 整理架构设计方面的一些学习总结和心得。 -- [分布式技术面试题](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/分布式技术面试题.md) -- [分布式技术实现](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/分布式技术实现.md) +- [系统架构面试题](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/system-architecture-interview.md) +- [分布式技术实现](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/system-core-technologies.md) - [分布式架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/分布式架构.md) - [大型分布式网站架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/大型分布式网站架构.md) -- [大型网站架构概述](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/大型网站架构概述.md) +- [系统架构概述](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/system-architecture-overview.md) - [网站典型故障](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/网站典型故障.md) -- [网站的伸缩性架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/网站的伸缩性架构.md) -- [网站的可扩展架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/网站的可扩展架构.md) -- [网站的安全架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/网站的安全架构.md) -- [网站的高可用架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/网站的高可用架构.md) -- [网站的高性能架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/网站的高性能架构.md) +- [伸缩性架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/scalable-architecture.md) +- [扩展性架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/extensible-architecture.md) +- [安全性架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/secure-architecture.md) +- [高可用架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/high-availability-architecture.md) +- [高性能架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/high-performance-architecture.md) ## ✨ 系统原理 @@ -116,13 +126,14 @@ - [Mybatis 应用指南](docs/technology/data/Mybatis.md) - [ShardingSphere 应用指南](docs/technology/data/ShardingSphere.md) -### 协调/监控/诊断/测试 +### 分布式 + +- [ZooKeeper 应用指南](docs/technology/distributed/zookeeper.md) +- [ZooKeeper 运维指南](docs/technology/distributed/zookeeper-ops.md) + +### 监控/诊断 -- 协调 - - [ZooKeeper 应用指南](docs/technology/monitor/zookeeper.md) - - [ZooKeeper 运维指南](docs/technology/monitor/zookeeper-ops.md) -- 诊断 - - [Arthas 应用指南](docs/technology/monitor/arthas.md) +- [Arthas 应用指南](docs/technology/monitor/arthas.md) ## ☕ JavaEE diff --git a/assets/cache.eddx b/assets/cache.eddx deleted file mode 100644 index 7d5bc6b753fb215bb61818d9fd9b725d818de62e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 58252 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%0AVsG|sYU6;%Xzx22*1;t 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

    #zCtzV&0u)$4E=f+rNG!#4xj9BtMnNDG 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 diff --git a/assets/zookeeper.eddx b/assets/zookeeper.eddx new file mode 100644 index 0000000000000000000000000000000000000000..0bf0fca87fbce940680b4e298fd89d7c58d8cd5d GIT binary patch literal 58478 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 w<{E{r~^~ literal 0 HcmV?d00001 diff --git a/docs/README.md b/docs/README.md index 69e56572..a044a4a6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,16 @@ -# JavaWeb +

    + + logo + +

    -> JavaWeb 开发之路经验总结。 +

    + license +

    + +

    JavaWeb

    + +> ☕ **JavaWeb** 是一个 Java Web 领域开发技术教程。 > > - 🔁 项目同步维护:[Github](https://github.com/dunwu/javaweb/) | [Gitee](https://gitee.com/turnon/javaweb/) > - 📖 电子书阅读:[Github Pages](https://dunwu.github.io/javaweb/) | [Gitee Pages](http://turnon.gitee.io/javaweb/) @@ -40,17 +50,17 @@ > [🎨架构设计](https://github.com/dunwu/blog/tree/master/source/_posts/design/architecture) 整理架构设计方面的一些学习总结和心得。 -- [分布式技术面试题](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/分布式技术面试题.md) -- [分布式技术实现](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/分布式技术实现.md) +- [系统架构面试题](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/system-architecture-interview.md) +- [分布式技术实现](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/system-core-technologies.md) - [分布式架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/分布式架构.md) - [大型分布式网站架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/大型分布式网站架构.md) -- [大型网站架构概述](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/大型网站架构概述.md) +- [系统架构概述](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/system-architecture-overview.md) - [网站典型故障](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/网站典型故障.md) -- [网站的伸缩性架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/网站的伸缩性架构.md) -- [网站的可扩展架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/网站的可扩展架构.md) -- [网站的安全架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/网站的安全架构.md) -- [网站的高可用架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/网站的高可用架构.md) -- [网站的高性能架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/网站的高性能架构.md) +- [伸缩性架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/scalable-architecture.md) +- [扩展性架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/extensible-architecture.md) +- [安全性架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/secure-architecture.md) +- [高可用架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/high-availability-architecture.md) +- [高性能架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/high-performance-architecture.md) ## ✨ 系统原理 @@ -116,13 +126,14 @@ - [Mybatis 应用指南](technology/data/Mybatis.md) - [ShardingSphere 应用指南](technology/data/ShardingSphere.md) -### 协调/监控/诊断/测试 +### 分布式 + +- [ZooKeeper 应用指南](technology/distributed/zookeeper.md) +- [ZooKeeper 运维指南](technology/distributed/zookeeper-ops.md) + +### 监控/诊断 -- 协调 - - [ZooKeeper 应用指南](technology/monitor/zookeeper.md) - - [ZooKeeper 运维指南](technology/monitor/zookeeper-ops.md) -- 诊断 - - [Arthas 应用指南](technology/monitor/arthas.md) +- [Arthas 应用指南](technology/monitor/arthas.md) ## ☕ JavaEE diff --git a/docs/book.json b/docs/book.json new file mode 100644 index 00000000..c27d74f3 --- /dev/null +++ b/docs/book.json @@ -0,0 +1,69 @@ +{ + "gitbook": "3.2.2", + "title": "javacore", + "language": "zh-hans", + "root": "./", + "structure": { + "summary": "sidebar.md" + }, + "links": { + "sidebar": { + "javacore": "https://github.com/dunwu/javacore" + } + }, + "plugins": [ + "-lunr", + "-search", + "advanced-emoji@^0.2.2", + "anchor-navigation-ex@1.0.10", + "anchors@^0.7.1", + "edit-link@^2.0.2", + "expandable-chapters-small@^0.1.7", + "github@^2.0.0", + "search-plus@^0.0.11", + "simple-page-toc@^0.1.1", + "splitter@^0.0.8", + "tbfed-pagefooter@^0.0.1" + ], + "pluginsConfig": { + "anchor-navigation-ex": { + "showLevel": false, + "associatedWithSummary": true, + "multipleH1": true, + "mode": "float", + "isRewritePageTitle": false, + "float": { + "showLevelIcon": false, + "level1Icon": "fa fa-hand-o-right", + "level2Icon": "fa fa-hand-o-right", + "level3Icon": "fa fa-hand-o-right" + }, + "pageTop": { + "showLevelIcon": false, + "level1Icon": "fa fa-hand-o-right", + "level2Icon": "fa fa-hand-o-right", + "level3Icon": "fa fa-hand-o-right" + } + }, + "edit-link": { + "base": "https://github.com/dunwu/javacore/blob/master/docs", + "label": "编辑此页面" + }, + "github": { + "url": "https://github.com/dunwu" + }, + "simple-page-toc": { + "maxDepth": 4, + "skipFirstH1": true + }, + "sharing": { + "weibo": true, + "all": ["weibo"] + }, + "tbfed-pagefooter": { + "copyright": "Copyright © Zhang Peng 2017", + "modify_label": "该文件上次修订时间:", + "modify_format": "YYYY-MM-DD HH:mm:ss" + } + } +} diff --git a/docs/coverpage.md b/docs/coverpage.md index 9d621514..821437aa 100644 --- a/docs/coverpage.md +++ b/docs/coverpage.md @@ -1,8 +1,9 @@ -
    +
    # JavaWeb -> JavaWeb 开发之路经验总结 +> ☕ **JavaWeb** 是一个 Java Web 领域开发技术教程。 + +[Java 教程](https://dunwu.github.io/java-tutorial/) 🔹 [JavaCore 教程](https://dunwu.github.io/javacore/) 🔹 [JavaWeb 教程](https://dunwu.github.io/javaweb/) 🔹 [Spring 教程](https://dunwu.github.io/spring-tutorial/) 🔹 [Spring Boot 教程](https://dunwu.github.io/spring-boot-tutorial/) -[Java Tutorial](https://dunwu.github.io/java-tutorial/) [开始阅读](README.md) diff --git a/docs/index.html b/docs/index.html index abd4e9dd..683b22c5 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,292 +1,155 @@ - - - JavaWeb - - - - - - - - - - - - -
    正在加载...
    - - - - - - - - - - - - - - + + + javaweb + + + + + + + + + + +
    正在加载...
    + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/javaee/javaee-filter-listener.md b/docs/javaee/javaee-filter-listener.md index 643a2ed8..f144b7f5 100644 --- a/docs/javaee/javaee-filter-listener.md +++ b/docs/javaee/javaee-filter-listener.md @@ -19,7 +19,7 @@ 过滤器(Filter)用于在 Servlet 之外对 request 或 response 进行修改。Filter 提供了过滤链(Filter Chain)的概念,一个过滤链包括多个 Filter。客户端请求 request 在抵达 Servlet 之前会经过过滤链的所有 Filter,服务器响应 response 从 Servlet 抵达客户端浏览器之前也会经过过滤链的所有 FIlter。 -
    +![img](http://dunwu.test.upcdn.net/snap/1559054413341.png!zp" style="width: 300px) ### 过滤器方法 diff --git a/docs/javaee/javaee-interview.md b/docs/javaee/javaee-interview.md index 5cddf5a0..a3c0995f 100644 --- a/docs/javaee/javaee-interview.md +++ b/docs/javaee/javaee-interview.md @@ -70,7 +70,7 @@ Servlet 技术出现之前,Web 主要使用 CGI 技术。它们的区别如下 ### 1.5. 简述 Servlet 生命周期 -
    +![img](http://www.runoob.com/wp-content/uploads/2014/07/Servlet-LifeCycle.jpg) Servlet 生命周期如下: @@ -172,7 +172,7 @@ Servlet 生命周期如下: ### 3.2. get 请求和 post 请求的区别? -
    +![img](https://upload-images.jianshu.io/upload_images/7779232-5be5ae990207f9d2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/814/format/webp) - GET: - 从服务器上获取数据,一般不能使用在写操作接口 diff --git a/docs/javaee/javaee-jsp.md b/docs/javaee/javaee-jsp.md index 8c4c636f..76373f6e 100644 --- a/docs/javaee/javaee-jsp.md +++ b/docs/javaee/javaee-jsp.md @@ -119,7 +119,7 @@ JSP 容器与 Web 服务器协同合作,为 JSP 的正常运行提供必要的 下图显示了 JSP 容器和 JSP 文件在 Web 应用中所处的位置。 -
    +![img](http://www.runoob.com/wp-content/uploads/2014/01/jsp-arch.jpg) #### 工作步骤 @@ -152,7 +152,7 @@ JSP 生命周期就是从创建到销毁的整个过程,类似于 servlet 生 很明显,JSP 生命周期的四个主要阶段和 servlet 生命周期非常相似,下面给出图示: -
    +![img](http://www.runoob.com/wp-content/uploads/2014/01/jsp_life_cycle.jpg) #### JSP 编译 @@ -249,7 +249,7 @@ out.println("Your IP address is " + request.getRemoteAddr()); 将以上代码保存在 hello.jsp 中,然后将它放置在 C:\apache-tomcat-7.0.2\webapps\ROOT 目录下,打开浏览器并在地址栏中输入http://localhost:8080/hello.jsp。运行后得到以下结果: -
    +![img](http://www.runoob.com/wp-content/uploads/2014/01/jsp_hello_world.jpg) #### 中文编码问题 @@ -515,7 +515,7 @@ SWITCH...CASE 实例 运行后得到以下结果: -
    +![img](http://www.runoob.com/wp-content/uploads/2014/01/7B4B85CF-FE4B-43CB-AAFF-F8594AD4342C.jpg) 将上例改用 while 循环来写: @@ -543,7 +543,7 @@ SWITCH...CASE 实例 浏览器访问,输出结果为(fontSize 初始化为 0,所以多输出了一行): -
    +![img](http://www.runoob.com/wp-content/uploads/2014/01/4F744CC9-E484-45BA-AF18-27AFCF4AD45C.jpg) JSP 运算符 @@ -884,7 +884,7 @@ $ javac TestBean.java 下面是一个 Eclipse 中目录结构图: -
    +![img](http://www.runoob.com/wp-content/uploads/2014/01/6AC33FBA-0B76-4BFD-A690-E856E9E01900.jpg) 下面是一个很简单的例子,它的功能是装载一个 Bean,然后设置/读取它的 message 属性。 @@ -918,7 +918,7 @@ $ javac TestBean.java 浏览器访问,执行以上文件,输出如下所示: -
    +![img](http://www.runoob.com/wp-content/uploads/2014/01/D7AD87A8-3392-4D4E-8731-18806B0644CD.jpg) ### `` @@ -1028,7 +1028,7 @@ plugin 动作有多个对应 HTML 元素的属性用于格式化 Java 组件。p 浏览器访问以下页面,输出结果如下所示: -
    +![img](http://www.runoob.com/wp-content/uploads/2014/01/7D8C47F0-0DDE-4F1D-8BE1-B2C9C955683E.jpg) ### `` @@ -1362,7 +1362,7 @@ header 和 headerValues 对象用来访问信息头,通过使用 request.getHe 运行结果如下: -
    +![img](http://www.runoob.com/wp-content/uploads/2014/01/jsp-expression-language.jpg) header 对象返回单一值,而 headerValues 则返回一个字符串数组。 diff --git a/docs/javaee/javaee-servlet.md b/docs/javaee/javaee-servlet.md index a2038c8e..6bd5adb0 100644 --- a/docs/javaee/javaee-servlet.md +++ b/docs/javaee/javaee-servlet.md @@ -90,7 +90,7 @@ Servlet 执行以下主要任务: ### Servlet 生命周期 -
    +![img](http://www.runoob.com/wp-content/uploads/2014/07/Servlet-LifeCycle.jpg) Servlet 生命周期如下: diff --git a/docs/sidebar.md b/docs/sidebar.md index 8bad0ca9..9de0bdc8 100644 --- a/docs/sidebar.md +++ b/docs/sidebar.md @@ -1,15 +1,17 @@ +# JavaWeb + - [**🎨 架构设计**](https://github.com/dunwu/blog/tree/master/source/_posts/design/architecture) - - [分布式技术面试题](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/分布式技术面试题.md) - - [分布式技术实现](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/分布式技术实现.md) + - [系统架构面试题](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/system-architecture-interview.md) + - [分布式技术实现](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/system-core-technologies.md) - [分布式架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/分布式架构.md) - [大型分布式网站架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/大型分布式网站架构.md) - - [大型网站架构概述](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/大型网站架构概述.md) + - [系统架构概述](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/system-architecture-overview.md) - [网站典型故障](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/网站典型故障.md) - - [网站的伸缩性架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/网站的伸缩性架构.md) - - [网站的可扩展架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/网站的可扩展架构.md) - - [网站的安全架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/网站的安全架构.md) - - [网站的高可用架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/网站的高可用架构.md) - - [网站的高性能架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/网站的高性能架构.md) + - [伸缩性架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/scalable-architecture.md) + - [扩展性架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/extensible-architecture.md) + - [安全性架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/secure-architecture.md) + - [高可用架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/high-availability-architecture.md) + - [高性能架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/high-performance-architecture.md) - [✨ 系统原理](https://github.com/dunwu/blog/tree/master/source/_posts/design/theory) - [系统原理面试题](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/system-theory-interview.md) - [分布式基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/distributed-base-theory.md) @@ -53,12 +55,11 @@ - 数据 - [Mybatis 应用指南](technology/data/Mybatis.md) - [ShardingSphere 应用指南](technology/data/ShardingSphere.md) - - 协调/监控/诊断/测试 - - 协调 - - [ZooKeeper 应用指南](technology/monitor/zookeeper.md) - - [ZooKeeper 运维指南](technology/monitor/zookeeper-ops.md) - - 诊断 - - [Arthas 应用指南](technology/monitor/arthas.md) + - 分布式 + - [ZooKeeper 应用指南](technology/distributed/zookeeper.md) + - [ZooKeeper 运维指南](technology/distributed/zookeeper-ops.md) + - 监控/诊断 + - [Arthas 应用指南](technology/monitor/arthas.md) - [**☕ JavaEE**](javaee/README.md) - [JavaEE 面经](javaee/javaee-interview.md) - [JavaEE 之 Servlet 指南](javaee/javaee-servlet.md) diff --git a/docs/technology/cache/CacheInterview.md b/docs/technology/cache/CacheInterview.md index b6f4b31d..cb619713 100644 --- a/docs/technology/cache/CacheInterview.md +++ b/docs/technology/cache/CacheInterview.md @@ -61,7 +61,7 @@ redis 内部使用文件事件处理器 `file event handler`,这个文件事 来看客户端与 redis 的一次通信过程: -
    +![img](https://github.com/doocs/advanced-java/blob/master/images/redis-single-thread-model.png) 要明白,通信是通过 socket 来完成的,不懂的同学可以先去看一看 socket 网络编程。 @@ -306,11 +306,11 @@ redis cluster,主要是针对**海量数据+高并发+高可用**的场景。r **集中式**是将集群元数据(节点信息、故障等等)几种存储在某个节点上。集中式元数据集中存储的一个典型代表,就是大数据领域的 `storm`。它是分布式的大数据实时计算引擎,是集中式的元数据存储的结构,底层基于 zookeeper(分布式协调的中间件)对所有元数据进行存储维护。 -
    +![img](https://github.com/doocs/advanced-java/blob/master/images/zookeeper-centralized-storage.png) redis 维护集群元数据采用另一个方式, `gossip` 协议,所有节点都持有一份元数据,不同的节点如果出现了元数据的变更,就不断将元数据发送给其它的节点,让其它节点也进行元数据的变更。 -
    +![img](https://github.com/doocs/advanced-java/blob/master/images/redis-gossip.png) **集中式**的**好处**在于,元数据的读取和更新,时效性非常好,一旦元数据出现了变更,就立即更新到集中式的存储中,其它节点读取的时候就可以感知到;**不好**在于,所有的元数据的更新压力全部集中在一个地方,可能会导致元数据的存储有压力。 @@ -353,7 +353,7 @@ ping 时要携带一些元数据,如果很频繁,可能会加重网络负担 来了一个 key,首先计算 hash 值,然后对节点数取模。然后打在不同的 master 节点上。一旦某一个 master 节点宕机,所有请求过来,都会基于最新的剩余 master 节点数去取模,尝试去取数据。这会导致**大部分的请求过来,全部无法拿到有效的缓存**,导致大量的流量涌入数据库。 -
    +![img](https://github.com/doocs/advanced-java/blob/master/images/hash.png) #### 一致性 hash 算法 @@ -365,7 +365,7 @@ ping 时要携带一些元数据,如果很频繁,可能会加重网络负担 燃鹅,一致性哈希算法在节点太少时,容易因为节点分布不均匀而造成**缓存热点**的问题。为了解决这种热点问题,一致性 hash 算法引入了虚拟节点机制,即对每一个节点计算多个 hash,每个计算结果位置都放置一个虚拟节点。这样就实现了数据的均匀分布,负载均衡。 -
    +![img](https://github.com/doocs/advanced-java/blob/master/images/consistent-hashing-algorithm.png) #### redis cluster 的 hash slot 算法 @@ -375,7 +375,7 @@ redis cluster 中每个 master 都会持有部分 slot,比如有 3 个 master 任何一台机器宕机,另外两个节点,不影响的。因为 key 找的是 hash slot,不是机器。 -
    +![img](https://github.com/doocs/advanced-java/blob/master/images/hash-slot.png) ### redis cluster 的高可用与主备切换原理 @@ -436,7 +436,7 @@ redis cluster 的高可用的原理,几乎跟哨兵是类似的。 问题:先更新数据库,再删除缓存。如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据就出现了不一致。 -
    +![img](https://github.com/doocs/advanced-java/blob/master/images/redis-junior-inconsistent.png) 解决思路:先删除缓存,再更新数据库。如果数据库更新失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致。因为读的时候缓存没有,所以去读了数据库中的旧数据,然后更新到缓存中。 @@ -506,7 +506,7 @@ redis cluster 的高可用的原理,几乎跟哨兵是类似的。 这就是缓存雪崩。 -
    +![img](https://github.com/doocs/advanced-java/blob/master/images/redis-caching-avalanche.png) 大约在 3 年前,国内比较知名的一个互联网公司,曾因为缓存事故,导致雪崩,后台系统全部崩溃,事故从当天下午持续到晚上凌晨 3\~4 点,公司损失了几千万。 @@ -516,7 +516,7 @@ redis cluster 的高可用的原理,几乎跟哨兵是类似的。 - 事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。 - 事后:redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。 -
    +![img](https://github.com/doocs/advanced-java/blob/master/images/redis-caching-avalanche-solution.png) 用户发送一个请求,系统 A 收到请求后,先查本地 ehcache 缓存,如果没查到再查 redis。如果 ehcache 和 redis 都没有,再查数据库,将数据库中的结果,写入 ehcache 和 redis 中。 @@ -536,7 +536,7 @@ redis cluster 的高可用的原理,几乎跟哨兵是类似的。 举个栗子。数据库 id 是从 1 开始的,结果黑客发过来的请求 id 全部都是负数。这样的话,缓存中不会有,请求每次都“视缓存于无物”,直接查询数据库。这种恶意攻击场景的缓存穿透就会直接把数据库给打死。 -
    +![img](https://github.com/doocs/advanced-java/blob/master/images/redis-caching-penetration.png) 解决方式很简单,每次系统 A 从数据库中只要没查到,就写一个空值到缓存里去,比如 `set -999 UNKNOWN`。然后设置一个过期时间,这样的话,下次有相同的 key 来访问的时候,在缓存失效之前,都可以直接从缓存中取数据。 @@ -550,7 +550,7 @@ redis cluster 的高可用的原理,几乎跟哨兵是类似的。 某个时刻,多个系统实例都去更新某个 key。可以基于 zookeeper 实现分布式锁。每个系统通过 zookeeper 获取分布式锁,确保同一时间,只能有一个系统实例在操作某个 key,别人都不允许读和写。 -
    +![img](https://github.com/doocs/advanced-java/blob/master/images/zookeeper-distributed-lock.png) 你要写入缓存的数据,都是从 mysql 里查出来的,都得写入 mysql 中,写入 mysql 中的时候必须保存一个时间戳,从 mysql 查出来的时候,时间戳也查出来。 diff --git a/docs/technology/cache/Ehcache.md b/docs/technology/cache/Ehcache.md index 592fbcf9..74f16a65 100644 --- a/docs/technology/cache/Ehcache.md +++ b/docs/technology/cache/Ehcache.md @@ -2,7 +2,7 @@ > EhCache 是一个纯 Java 的进程内缓存框架,具有快速、精干等特点,是 Hibernate 中默认的 CacheProvider。 -
    +![img](http://dunwu.test.upcdn.net/cs/java/javaweb/technology/cache/ehcache-architecture.png) ## 特点 diff --git a/docs/technology/data/Mybatis.md b/docs/technology/data/Mybatis.md index bff0c065..0dacf460 100644 --- a/docs/technology/data/Mybatis.md +++ b/docs/technology/data/Mybatis.md @@ -64,7 +64,7 @@ MyBatis 和 Hibernate 都是 Java 世界中比较流行的 ORM 框架。我们 > 核心 API 请参考:「 [Mybatis 官方文档之 Java API](http://www.mybatis.org/mybatis-3/zh/java-api.html) 」 -
    +![img](http://dunwu.test.upcdn.net/snap/1556022483200.png!zp) ### SqlSessionFactoryBuilder @@ -72,7 +72,7 @@ MyBatis 和 Hibernate 都是 Java 世界中比较流行的 ORM 框架。我们 **`SqlSessionFactoryBuilder` 负责创建 `SqlSessionFactory` 实例。而 `SqlSessionFactoryBuilder` 则可以从 XML 配置文件或一个预先定制的 `Configuration` 的实例构建出 `SqlSessionFactory` 的实例。** -
    +![img](http://dunwu.test.upcdn.net/snap/1556072725320.png!zp) - **SqlSessionFactoryBuilder 生命周期** @@ -84,7 +84,7 @@ MyBatis 和 Hibernate 都是 Java 世界中比较流行的 ORM 框架。我们 **SqlSessionFactory 负责创建 SqlSession 实例。** -
    +![img](http://dunwu.test.upcdn.net/snap/1556074378879.png!zp) - **SqlSessionFactory 生命周期** @@ -92,7 +92,7 @@ MyBatis 和 Hibernate 都是 Java 世界中比较流行的 ORM 框架。我们 ### SqlSession -
    +![img](http://dunwu.test.upcdn.net/snap/1556105326238.png!zp) - **SqlSession 职责** @@ -180,7 +180,7 @@ Mybatis 支持诸如 @Insert、@Update、@Delete、@Select、@Result 等注解 ### MyBatis 的架构 -
    +![img](http://dunwu.test.upcdn.net/snap/1556104494030.png!zp) ### 接口层 diff --git a/docs/technology/monitor/zookeeper-ops.md b/docs/technology/distributed/zookeeper-ops.md similarity index 90% rename from docs/technology/monitor/zookeeper-ops.md rename to docs/technology/distributed/zookeeper-ops.md index 009b9553..69df9a36 100644 --- a/docs/technology/monitor/zookeeper-ops.md +++ b/docs/technology/distributed/zookeeper-ops.md @@ -295,7 +295,7 @@ Created /tmp ```bash # 格式 -get path [watch] +get path [watch] [zk: localhost:2181(CONNECTED) 31] get /hadoop 123456 #节点数据 cZxid = 0x14b @@ -313,19 +313,19 @@ numChildren = 0 节点各个属性如下表。其中一个重要的概念是 Zxid(ZooKeeper Transaction Id),ZooKeeper 节点的每一次更改都具有唯一的 Zxid,如果 Zxid1 小于 Zxid2,则 Zxid1 的更改发生在 Zxid2 更改之前。 -| **状态属性** | **说明** | -| -------------- | ------------------------------------------------------------ | -| cZxid | 数据节点创建时的事务 ID | -| ctime | 数据节点创建时的时间 | -| mZxid | 数据节点最后一次更新时的事务 ID | -| mtime | 数据节点最后一次更新时的时间 | -| pZxid | 数据节点的子节点最后一次被修改时的事务 ID | -| cversion | 子节点的更改次数 | -| dataVersion | 节点数据的更改次数 | -| aclVersion | 节点的 ACL 的更改次数 | +| **状态属性** | **说明** | +| -------------- | ------------------------------------------------------------------------------------------ | +| cZxid | 数据节点创建时的事务 ID | +| ctime | 数据节点创建时的时间 | +| mZxid | 数据节点最后一次更新时的事务 ID | +| mtime | 数据节点最后一次更新时的时间 | +| pZxid | 数据节点的子节点最后一次被修改时的事务 ID | +| cversion | 子节点的更改次数 | +| dataVersion | 节点数据的更改次数 | +| aclVersion | 节点的 ACL 的更改次数 | | ephemeralOwner | 如果节点是临时节点,则表示创建该节点的会话的 SessionID;如果节点是持久节点,则该属性值为 0 | -| dataLength | 数据内容的长度 | -| numChildren | 数据节点当前的子节点个数 | +| dataLength | 数据内容的长度 | +| numChildren | 数据节点当前的子节点个数 | ##### 查看节点状态 @@ -429,17 +429,17 @@ WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/hadoop ### 3.4. zookeeper 四字命令 -| 命令 | 功能描述 | -| ---- | ------------------------------------------------------------ | -| conf | 打印服务配置的详细信息。 | +| 命令 | 功能描述 | +| ---- | --------------------------------------------------------------------------------------------------------------------------- | +| conf | 打印服务配置的详细信息。 | | cons | 列出连接到此服务器的所有客户端的完整连接/会话详细信息。包括接收/发送的数据包数量,会话 ID,操作延迟,上次执行的操作等信息。 | -| dump | 列出未完成的会话和临时节点。这只适用于 Leader 节点。 | -| envi | 打印服务环境的详细信息。 | -| ruok | 测试服务是否处于正确状态。如果正确则返回“imok”,否则不做任何相应。 | -| stat | 列出服务器和连接客户端的简要详细信息。 | -| wchs | 列出所有 watch 的简单信息。 | -| wchc | 按会话列出服务器 watch 的详细信息。 | -| wchp | 按路径列出服务器 watch 的详细信息。 | +| dump | 列出未完成的会话和临时节点。这只适用于 Leader 节点。 | +| envi | 打印服务环境的详细信息。 | +| ruok | 测试服务是否处于正确状态。如果正确则返回“imok”,否则不做任何相应。 | +| stat | 列出服务器和连接客户端的简要详细信息。 | +| wchs | 列出所有 watch 的简单信息。 | +| wchc | 按会话列出服务器 watch 的详细信息。 | +| wchp | 按路径列出服务器 watch 的详细信息。 | > 更多四字命令可以参阅官方文档:https://zookeeper.apache.org/doc/current/zookeeperAdmin.html @@ -447,7 +447,7 @@ WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/hadoop ```bash [root@hadoop001 bin]# echo stat | nc localhost 2181 -Zookeeper version: 3.4.13-2d71af4dbe22557fda74f9a9b4309b15a7487f03, +Zookeeper version: 3.4.13-2d71af4dbe22557fda74f9a9b4309b15a7487f03, built on 06/29/2018 04:05 GMT Clients: /0:0:0:0:0:0:0:1:50584[1](queued=0,recved=371,sent=371) diff --git a/docs/technology/monitor/zookeeper.md b/docs/technology/distributed/zookeeper.md similarity index 52% rename from docs/technology/monitor/zookeeper.md rename to docs/technology/distributed/zookeeper.md index 8a5ca43e..c4b1598b 100644 --- a/docs/technology/monitor/zookeeper.md +++ b/docs/technology/distributed/zookeeper.md @@ -2,137 +2,99 @@ > **ZooKeeper 是一个分布式应用协调服务** ,由 Apache 进行维护。 > -> Zookeeper 可以用于发布/订阅、负载均衡、命令服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能 。 - - - -- [1. 简介](#1-简介) - - [1.1. ZooKeeper 是什么](#11-zookeeper-是什么) - - [1.2. ZooKeeper 提供了什么](#12-zookeeper-提供了什么) - - [1.3. Zookeeper 的特性](#13-zookeeper-的特性) -- [2. 设计目标](#2-设计目标) - - [2.1. 简单的数据模型](#21-简单的数据模型) - - [2.2. 分布式](#22-分布式) - - [2.3. 顺序访问](#23-顺序访问) - - [2.4. 高性能高可用](#24-高性能高可用) -- [3. 核心概念](#3-核心概念) - - [3.1. 集群角色](#31-集群角色) - - [3.2. 会话](#32-会话) - - [3.3. 数据节点](#33-数据节点) - - [3.4. 节点信息](#34-节点信息) - - [3.5. Watcher](#35-watcher) - - [3.6. ACL](#36-acl) -- [4. 文件系统](#4-文件系统) -- [5. ZAB 协议](#5-zab-协议) -- [6. 高可用](#6-高可用) - - [6.1. 复制](#61-复制) - - [6.2. 选举流程](#62-选举流程) - - [6.3. 同步流程](#63-同步流程) -- [7. 应用场景](#7-应用场景) - - [7.1. 数据的发布/订阅](#71-数据的发布订阅) - - [7.2. 命名服务](#72-命名服务) - - [7.3. 配置管理](#73-配置管理) - - [7.4. 集群管理](#74-集群管理) - - [7.5. 分布式锁](#75-分布式锁) - - [7.6. Master 选举](#76-master-选举) - - [7.7. 队列管理](#77-队列管理) -- [8. Client API](#8-client-api) - - [8.1. ZooKeeper API 的基础知识](#81-zookeeper-api-的基础知识) - - [8.2. Java 绑定](#82-java-绑定) - - [8.3. 连接到 ZooKeeper 集合](#83-连接到-zookeeper-集合) - - [8.4. 创建 Znode](#84-创建-znode) - - [8.5. Exists - 检查 Znode 的存在](#85-exists---检查-znode-的存在) - - [8.6. getData 方法](#86-getdata-方法) - - [8.7. setData 方法](#87-setdata-方法) - - [8.8. getChildren 方法](#88-getchildren-方法) - - [8.9. 删除 Znode](#89-删除-znode) -- [9. 运维](#9-运维) -- [10. 参考资料](#10-参考资料) - - - -## 1. 简介 - -### 1.1. ZooKeeper 是什么 - -**ZooKeeper 是一个分布式应用协调服务** ,由 Apache 进行维护。 +> ZooKeeper 可以用于发布/订阅、负载均衡、命令服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能 。 -ZooKeeper 主要用来解决分布式集群中应用系统的一致性问题,它能提供基于类似于文件系统的目录节点树方式的数据存储。但是 ZooKeeper 并不是用来专门存储数据的,它的作用主要是用来**维护和监控存储数据的状态变化。通过监控这些数据状态的变化,从而可以达到基于数据的集群管理**。 +## 简介 + +### ZooKeeper 是什么 -### 1.2. ZooKeeper 提供了什么 +ZooKeeper 主要用来解决分布式集群中应用系统的一致性问题,它能提供基于类似于文件系统的目录节点树方式的数据存储。但是 ZooKeeper 并不是用来专门存储数据的,它的作用主要是用来**维护和监控存储数据的状态变化。通过监控这些数据状态的变化,从而可以达到基于数据的集群管理**。 -- **文件系统** -- **通知机制** +很多大名鼎鼎的框架都基于 ZooKeeper 来实现分布式高可用,如:Dubbo、Kafka 等。 -### 1.3. Zookeeper 的特性 +### ZooKeeper 的特性 -Zookeeper 具有以下特性: +ZooKeeper 具有以下特性: -- **顺序一致性**:从一个客户端发起的事务请求,最终都会严格按照其发起顺序被应用到 Zookeeper 中; -- **原子性**:所有事务请求的处理结果在整个集群中所有机器上都是一致的;不存在部分机器应用了该事务,而另一部分没有应用的情况; -- **单一视图**:所有客户端看到的服务端数据模型都是一致的; -- **可靠性**:一旦服务端成功应用了一个事务,则其引起的改变会一直保留,直到被另外一个事务所更改; -- **实时性**:一旦一个事务被成功应用后,Zookeeper 可以保证客户端立即可以读取到这个事务变更后的最新状态的数据。 +- **高性能** - ZooKeeper 将数据全量存储在内存中,所以其性能很高。_需要注意的是:由于 ZooKeeper 的所有更新和删除都是基于事务的,因此 ZooKeeper 在读多写少的应用场景中有性能表现较好,如果写操作频繁,性能会大大下滑_。 +- **顺序一致性**:所有客户端看到的服务端数据模型都是一致的;从一个客户端发起的事务请求,最终都会严格按照其发起顺序被应用到 ZooKeeper 中。具体的实现可见:[原子广播](#原子广播) +- **原子性** - 所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,即整个集群要么都成功应用了某个事务,要么都没有应用。 实现方式可见:[事务](#事务) +- **高可用** - ZooKeeper 的高可用是基于副本机制实现的,此外 ZooKeeper 支持故障恢复,可见:[选举 Leader](#选举-Leader) +- **单一视图** - 无论客户端连接的是哪个 Zookeeper 服务器,其看到的服务端数据模型都是一致的。 -## 2. 设计目标 +## 架构 -Zookeeper 致力于为那些高吞吐的大型分布式系统提供一个高性能、高可用、且具有严格顺序访问控制能力的分布式协调服务。 +Zookeeper 集群是一个基于主从复制的高可用集群,每个服务器承担如下三种角色中的一种 -### 2.1. 简单的数据模型 +- **Leader** - 一个 Zookeeper 集群同一时间只会有一个实际工作的 Leader,它会发起并维护与各 Follwer 及 Observer 间的心跳。所有的写操作必须要通过 Leader 完成再由 Leader 将写操作广播给其它服务器。 +- **Follower** - 一个 Zookeeper 集群可能同时存在多个 Follower,它会响应 Leader 的心跳。Follower 可直接处理并返回客户端的读请求,同时会将写请求转发给 Leader 处理,并且负责在 Leader 处理写请求时对请求进行投票。 +- **Observer** - 角色与 Follower 类似,但是无投票权。 -Zookeeper 通过树形结构来存储数据,它由一系列被称为 ZNode 的数据节点组成,类似于常见的文件系统。不过和常见的文件系统不同,Zookeeper 将数据全量存储在内存中,以此来实现高吞吐,减少访问延迟。 +### 读操作 -### 2.2. 分布式 +Leader/Follower/Observer 都可直接处理读请求,从本地内存中读取数据并返回给客户端即可。 -Zookeeper 支持集群模式。集群中每个 Zookeeper 节点都会单独在内存中维护自身的状态,并且每台机器之间都保持着通讯,只要集群中有半数机器能够正常工作,那么整个集群就可以正常提供服务。 +由于处理读请求不需要服务器之间的交互,Follower/Observer 越多,整体可处理的读请求量越大,也即读性能越好。
    - +
    -- ZooKeeper 服务器节点间的同步机制是基于原子广播。实现这个机制的协议叫做 **Zab 协议**。 -- 客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变、被删除、子目录节点增加删除)时,ZooKeeper 服务会通知客户端。 +### 写操作 -### 2.3. 顺序访问 +#### 写 Leader -对于来自客户端的每个更新请求,ZooKeeper 具备严格的顺序访问控制能力。 +
    + +
    -**为了保证事务的顺序一致性,ZooKeeper 采用了递增的事务 id 号(zxid)来标识事务**。 +由上图可见,通过 Leader 进行写操作,主要分为五步: -所有的提议(proposal)都在被提出的时候加上了 zxid。实现中 zxid 是一个 64 位的数字,它高 32 位是 epoch 用来标识 leader 关系是否改变,每次一个 leader 被选出来,它都会有一个新的 epoch,标识当前属于那个 leader 的统治时期。低 32 位用于递增计数。 +1. 客户端向 Leader 发起写请求 +2. Leader 将写请求以 Proposal 的形式发给所有 Follower 并等待 ACK +3. Follower 收到 Leader 的 Proposal 后返回 ACK +4. Leader 得到过半数的 ACK(Leader 对自己默认有一个 ACK)后向所有的 Follower 和 Observer 发送 Commmit +5. Leader 将处理结果返回给客户端 -### 2.4. 高性能高可用 +> 注意 +> +> - Leader 并不需要得到 Observer 的 ACK,即 Observer 无投票权 +> - Leader 不需要得到所有 Follower 的 ACK,只要收到过半的 ACK 即可,同时 Leader 本身对自己有一个 ACK。上图中有 4 个 Follower,只需其中两个返回 ACK 即可,因为(2+1) / (4+1) > 1/2 +> - Observer 虽然无投票权,但仍须同步 Leader 的数据从而在处理读请求时可以返回尽可能新的数据 -ZooKeeper 将数据存全量储在内存中以保持高性能,并通过服务集群来实现高可用,由于 Zookeeper 的所有更新和删除都是基于事务的,所以其在读多写少的应用场景中有着很高的性能表现。 +#### 写 Follower/Observer -## 3. 核心概念 +
    + +
    -### 3.1. 集群角色 +- Follower/Observer 均可接受写请求,但不能直接处理,而需要将写请求转发给 Leader 处理 +- 除了多了一步请求转发,其它流程与直接写 Leader 无任何区别 -Zookeeper 集群中的机器分为以下三种角色: +## 原理 -- **`Leader`** :为客户端提供读写服务,并维护集群状态,它是由集群选举所产生的; -- **`Follower`** :为客户端提供读写服务,并定期向 Leader 汇报自己的节点状态。同时也参与写操作“过半写成功”的策略和 Leader 的选举; -- **`Observer`** :为客户端提供读写服务,并定期向 Leader 汇报自己的节点状态,但不参与写操作“过半写成功”的策略和 Leader 的选举,因此 Observer 可以在不影响写性能的情况下提升集群的读性能。 +### 数据模型 -### 3.2. 会话 +**ZooKeeper 可以视为一个高可用的文件系统**。 -Zookeeper 客户端通过 TCP 长连接连接到服务集群,会话 (Session) 从第一次连接开始就已经建立,之后通过心跳检测机制来保持有效的会话状态。通过这个连接,客户端可以发送请求并接收响应,同时也可以接收到 Watch 事件的通知。 +ZooKeeper 维护着一个树形层次结构,树中的节点被称为 **`znode`**。其中根节点为 `/`,每个节点上都会保存自己的数据和节点信息。znode 可以用于存储数据,并且有一个与之相关联的 ACL(详情可见 [ACL](#ACL))。ZooKeeper 的设计目标是实现协调服务,而不是真的作为一个文件存储,因此 znode 存储数据的大小被限制在 1MB 以内。 -关于会话中另外一个核心的概念是 sessionTimeOut(会话超时时间),当由于网络故障或者客户端主动断开等原因,导致连接断开,此时只要在会话超时时间之内重新建立连接,则之前创建的会话依然有效。 +
    + +
    -### 3.3. 数据节点 +ZooKeeper 这种数据结构有如下这些特点: -Zookeeper 数据模型是由一系列基本数据单元 `Znode`(数据节点) 组成的节点树,其中根节点为 `/`。每个节点上都会保存自己的数据和节点信息。Zookeeper 中节点可以分为两大类: +**ZooKeeper 的数据访问具有原子性**。客户端在读取一个 znode 数据时,要么成功读取所有数据,要么读取失败。**znode 节点路径必须是绝对路径**。 -- **持久节点** :节点一旦创建,除非被主动删除,否则一直存在; -- **临时节点** :一旦创建该节点的客户端会话失效,则所有该客户端创建的临时节点都会被删除。 +znode 有两种类型: -临时节点和持久节点都可以添加一个特殊的属性:`SEQUENTIAL`,代表该节点是否具有递增属性。如果指定该属性,那么在这个节点创建时,Zookeeper 会自动在其节点名称后面追加一个由父节点维护的递增数字。 +- **临时的( `PERSISTENT` )** - 户端会话结束时,ZooKeeper 就会删除临时的 znode。 +- **持久的( `EPHEMERAL` )** - 除非客户端主动执行删除操作,否则 ZooKeeper 不会删除持久的 znode。 -### 3.4. 节点信息 +znode 上有一个**顺序标志( `SEQUENTIAL` )**。如果在创建 znode 时,设置了**顺序标志( `SEQUENTIAL` )**,那么 ZooKeeper 会使用计数器为 znode 添加一个单调递增的数值,即 zxid。ZooKeeper 正是利用 zxid 实现了严格的顺序访问控制能力。 -每个 ZNode 节点在存储数据的同时,都会维护一个叫做 `Stat` 的数据结构,里面存储了关于该节点的全部状态信息。如下: +每个 znode 节点在存储数据的同时,都会维护一个叫做 `Stat` 的数据结构,里面存储了关于该节点的全部状态信息。如下: | **状态属性** | **说明** | | -------------- | ------------------------------------------------------------------------------------------ | @@ -148,157 +110,234 @@ Zookeeper 数据模型是由一系列基本数据单元 `Znode`(数据节点) | dataLength | 数据内容的长度 | | numChildren | 数据节点当前的子节点个数 | -### 3.5. Watcher +### 事务 -Zookeeper 中一个常用的功能是 Watcher(事件监听器),它允许用户在指定节点上针对感兴趣的事件注册监听,当事件发生时,监听器会被触发,并将事件信息推送到客户端。该机制是 Zookeeper 实现分布式协调服务的重要特性。 +对于来自客户端的每个更新请求,ZooKeeper 具备严格的顺序访问控制能力。 -### 3.6. ACL +**为了保证事务的顺序一致性,ZooKeeper 采用了递增的事务 id 号(zxid)来标识事务**。 -Zookeeper 采用 ACL(Access Control Lists) 策略来进行权限控制,类似于 UNIX 文件系统的权限控制。它定义了如下五种权限: +Leader 服务会为每一个 Follower 服务器分配一个单独的队列,然后将事务 Proposal 依次放入队列中,并根据 FIFO(先进先出) 的策略进行消息发送。Follower 服务在接收到 Proposal 后,会将其以事务日志的形式写入本地磁盘中,并在写入成功后反馈给 Leader 一个 Ack 响应。当 Leader 接收到超过半数 Follower 的 Ack 响应后,就会广播一个 Commit 消息给所有的 Follower 以通知其进行事务提交,之后 Leader 自身也会完成对事务的提交。而每一个 Follower 则在接收到 Commit 消息后,完成事务的提交。 -- **CREATE**:允许创建子节点; -- **READ**:允许从节点获取数据并列出其子节点; -- **WRITE**:允许为节点设置数据; -- **DELETE**:允许删除子节点; -- **ADMIN**:允许为节点设置权限。 +所有的提议(**`proposal`**)都在被提出的时候加上了 zxid。zxid 是一个 64 位的数字,它的高 32 位是 **`epoch`** 用来标识 Leader 关系是否改变,每次一个 Leader 被选出来,它都会有一个新的 epoch,标识当前属于那个 leader 的统治时期。低 32 位用于递增计数。 -## 4. 文件系统 +详细过程如下: -ZooKeeper 会维护一个具有层次关系的数据结构,它非常类似于一个标准的文件系统,如下图所示: +1. Leader 等待 server 连接; +2. Follower 连接 Leader,将最大的 zxid 发送给 Leader; +3. Leader 根据 Follower 的 zxid 确定同步点; +4. 完成同步后通知 follower 已经成为 uptodate 状态; +5. Follower 收到 uptodate 消息后,又可以重新接受 client 的请求进行服务了。 -
    - -
    +### 通知 -ZooKeeper 这种数据结构有如下这些特点: +客户端注册监听它关心的 znode,当 znode 状态发生变化(数据改变、被删除、子目录节点增加删除)时,ZooKeeper 服务会通知客户端。 + +客户端和服务端保持连接一般有两种形式: -- 每个子目录项如 NameService 都被称作为 znode,这个 znode 是被它所在的路径唯一标识,如 Server1 这个 znode 的标识为 /NameService/Server1 -- znode 可以有子节点目录,并且每个 znode 可以存储数据,注意 EPHEMERAL 类型的目录节点不能有子节点目录 -- znode 是有版本的,每个 znode 中存储的数据可以有多个版本,也就是一个访问路径中可以存储多份数据 -- znode 可以是临时节点,一旦创建这个 znode 的客户端与服务器失去联系,这个 znode 也将自动删除,ZooKeeper 的客户端和服务器通信采用长连接方式,每个客户端和服务器通过心跳来保持连接,这个连接状态称为 session,如果 znode 是临时节点,这个 session 失效,znode 也就删除了 -- znode 的目录名可以自动编号,如 App1 已经存在,再创建的话,将会自动命名为 App2 -- znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个是 ZooKeeper 的核心特性,ZooKeeper 的很多功能都是基于这个特性实现的,后面在典型的应用场景中会有实例介绍 +- **客户端向服务端不断轮询** +- **服务端向客户端推送状态** -znode 类型: +Zookeeper 的选择是服务端主动推送状态,也就是观察机制( `Watch` )。 -1. **`PERSISTENT(持久化目录节点)`** - 客户端与 zookeeper 断开连接后,该节点依旧存在 -2. **`PERSISTENT_SEQUENTIAL(持久化顺序编号目录节点)`** - 客户端与 zookeeper 断开连接后,该节点依旧存在,只是 Zookeeper 给该节点名称进行顺序编号 -3. **`EPHEMERAL(临时目录节点)`** - 客户端与 zookeeper 断开连接后,该节点被删除 -4. **`EPHEMERAL_SEQUENTIAL(临时顺序编号目录节点)`** - 客户端与 zookeeper 断开连接后,该节点被删除,只是 Zookeeper 给该节点名称进行顺序编号 +ZooKeeper 的观察机制允许用户在指定节点上针对感兴趣的事件注册监听,当事件发生时,监听器会被触发,并将事件信息推送到客户端。 -## 5. ZAB 协议 +客户端使用 `getData` 等接口获取 znode 状态时传入了一个用于处理节点变更的回调,那么服务端就会主动向客户端推送节点的变更: + +```java +public byte[] getData(final String path, Watcher watcher, Stat stat) +``` -ZAB 协议包括两种基本的模式,分别是 **`崩溃恢复(选主)`**和 **`消息广播(同步)`**: +从这个方法中传入的 `Watcher` 对象实现了相应的 `process` 方法,每次对应节点出现了状态的改变,`WatchManager` 都会通过以下的方式调用传入 `Watcher` 的方法: -#### 1. 崩溃恢复 +```java +Set triggerWatch(String path, EventType type, Set supress) { + WatchedEvent e = new WatchedEvent(type, KeeperState.SyncConnected, path); + Set watchers; + synchronized (this) { + watchers = watchTable.remove(path); + } + for (Watcher w : watchers) { + w.process(e); + } + return watchers; +} +``` -当整个服务框架在启动过程中,或者当 Leader 服务器出现异常时,ZAB 协议就会进入恢复模式,通过过半选举机制产生新的 Leader,之后其他机器将从新的 Leader 上同步状态,当有过半机器完成状态同步后,就退出恢复模式,进入消息广播模式。 +Zookeeper 中的所有数据其实都是由一个名为 `DataTree` 的数据结构管理的,所有的读写数据的请求最终都会改变这颗树的内容,在发出读请求时可能会传入 `Watcher` 注册一个回调函数,而写请求就可能会触发相应的回调,由 `WatchManager` 通知客户端数据的变化。 -#### 2. 消息广播 +通知机制的实现其实还是比较简单的,通过读请求设置 `Watcher` 监听事件,写请求在触发事件时就能将通知发送给指定的客户端。 -ZAB 协议的消息广播过程使用的是原子广播协议。在整个消息的广播过程中,Leader 服务器会每个事物请求生成对应的 Proposal,并为其分配一个全局唯一的递增的事务 ID(ZXID),之后再对其进行广播。具体过程如下: +### 会话 -Leader 服务会为每一个 Follower 服务器分配一个单独的队列,然后将事务 Proposal 依次放入队列中,并根据 FIFO(先进先出) 的策略进行消息发送。Follower 服务在接收到 Proposal 后,会将其以事务日志的形式写入本地磁盘中,并在写入成功后反馈给 Leader 一个 Ack 响应。当 Leader 接收到超过半数 Follower 的 Ack 响应后,就会广播一个 Commit 消息给所有的 Follower 以通知其进行事务提交,之后 Leader 自身也会完成对事务的提交。而每一个 Follower 则在接收到 Commit 消息后,完成事务的提交。 +ZooKeeper 客户端通过 TCP 长连接连接到 ZooKeeper 服务集群,会话 (Session) 从第一次连接开始就已经建立,之后通过心跳检测机制来保持有效的会话状态。通过这个连接,客户端可以发送请求并接收响应,同时也可以接收到 Watch 事件的通知。 + +每个 ZooKeeper 客户端配置中都配置了 ZooKeeper 服务器集群列表。启动时,客户端会遍历列表去尝试建立连接。 + +一旦一台客户端与一台服务器建立连接,这台服务器会为这个客户端创建一个新的会话。**每个会话都会有一个超时时间,若服务器在超时时间内没有收到任何请求,则相应会话被视为过期**。一旦会话过期,就无法再重新打开,且任何与该会话相关的临时 znode 都会被删除。 + +通常来说,会话应该长期存在,而这需要由客户端来保证。客户端可以通过心跳方式(ping)来保持会话不过期。 + +ZooKeeper 的会话具有四个属性: + +- `sessionID` - 会话 ID,唯一标识一个会话,每次客户端创建新的会话时,Zookeeper 都会为其分配一个全局唯一的 sessionID。 +- `TimeOut` - 会话超时时间,客户端在构造 Zookeeper 实例时,会配置 sessionTimeout 参数用于指定会话的超时时间,Zookeeper 客户端向服务端发送这个超时时间后,服务端会根据自己的超时时间限制最终确定会话的超时时间。 +- `TickTime` - 下次会话超时时间点,为了便于 Zookeeper 对会话实行”分桶策略”管理,同时为了高效低耗地实现会话的超时检查与清理,Zookeeper 会为每个会话标记一个下次会话超时时间点,其值大致等于当前时间加上 TimeOut。 +- `isClosing` - 标记一个会话是否已经被关闭,当服务端检测到会话已经超时失效时,会将该会话的 isClosing 标记为”已关闭”,这样就能确保不再处理来自该会话的心情求了。 + +Zookeeper 的会话管理主要是通过 `SessionTracker` 来负责,其采用了**分桶策略**(将类似的会话放在同一区块中进行管理)进行管理,以便 Zookeeper 对会话进行不同区块的隔离处理以及同一区块的统一处理。 + +### ZAB 协议 + +ZooKeeper 实现数据一致性和高可用这两个分布式系统标配特性是基于 Zab 协议。 + +> 注意:Zab 协议不是 Paxos 算法,只是比较类似,二者在操作上并不相同。 -## 6. 高可用 +Zab 协议定义了两个可以无限循环的流程: -### 6.1. 复制 +- 原子广播 - 用于主从同步,从而保证数据一致性 +- 选举 Leader - 用于故障恢复,从而保证高可用 -ZooKeeper 作为一个集群提供一致的数据服务,自然,它要在所有机器间做数据复制。 +#### 选举 Leader -从客户端读写访问的透明度来看,数据复制集群系统分下面两种: +> **ZooKeeper 的故障恢复** +> +> ZooKeeper 集群采用一主(称为 Leader)多从(称为 Follower)模式,主从节点通过副本机制保证数据一致。 +> +> - **如果 Follower 节点挂了** - ZooKeeper 集群中的每个节点都会单独在内存中维护自身的状态,并且各节点之间都保持着通讯,**只要集群中有半数机器能够正常工作,那么整个集群就可以正常提供服务**。 +> - **如果 Leader 节点挂了** - 如果 Leader 节点挂了,系统就不能正常工作了。此时,需要通过 Zab 协议的选举 Leader 机制来进行故障恢复。 +> +> Zab 协议的选举 Leader 机制简单来说,就是:基于过半选举机制产生新的 Leader,之后其他机器将从新的 Leader 上同步状态,当有过半机器完成状态同步后,就退出选举 Leader 模式,进入原子广播模式。 + +##### 术语 + +- **myid** - 每个 Zookeeper 服务器,都需要在数据文件夹下创建一个名为 myid 的文件,该文件包含整个 Zookeeper 集群唯一的 ID(整数)。 +- **zxid** - 类似于 RDBMS 中的事务 ID,用于标识一次更新操作的 Proposal ID。为了保证顺序性,该 zkid 必须单调递增。因此 Zookeeper 使用一个 64 位的数来表示,高 32 位是 Leader 的 epoch,从 1 开始,每次选出新的 Leader,epoch 加一。低 32 位为该 epoch 内的序号,每次 epoch 变化,都将低 32 位的序号重置。这样保证了 zkid 的全局递增性。 + +##### 服务器状态 + +- **_LOOKING_** - 不确定 Leader 状态。该状态下的服务器认为当前集群中没有 Leader,会发起 Leader 选举 +- **_FOLLOWING_** - 跟随者状态。表明当前服务器角色是 Follower,并且它知道 Leader 是谁 +- **_LEADING_** - 领导者状态。表明当前服务器角色是 Leader,它会维护与 Follower 间的心跳 +- **_OBSERVING_** - 观察者状态。表明当前服务器角色是 Observer,与 Folower 唯一的不同在于不参与选举,也不参与集群写操作时的投票 + +##### 选票数据结构 + +每个服务器在进行领导选举时,会发送如下关键信息 + +- **_logicClock_** 每个服务器会维护一个自增的整数,名为 logicClock,它表示这是该服务器发起的第多少轮投票 +- **_state_** 当前服务器的状态 +- **_self_id_** 当前服务器的 myid +- **_self_zxid_** 当前服务器上所保存的数据的最大 zxid +- **_vote_id_** 被推举的服务器的 myid +- **_vote_zxid_** 被推举的服务器上所保存的数据的最大 zxid -- 写主(WriteMaster) :对数据的修改提交给指定的节点。读无此限制,可以读取任何一个节点。这种情况下客户端需要对读与写进行区别,俗称读写分离; -- 写任意(Write Any):对数据的修改可提交给任意的节点,跟读一样。这种情况下,客户端对集群节点的角色与变化透明。 +##### 投票流程 -对 ZooKeeper 来说,它采用的方式是写任意。通过增加机器,它的读吞吐能力和响应能力扩展性非常好,而写,随着机器的增多吞吐能力肯定下降(这也是它建立 observer 的原因),而响应能力则取决于具体实现方式,是延迟复制保持最终一致性,还是立即复制快速响应。 +- **_自增选举轮次_** - Zookeeper 规定所有有效的投票都必须在同一轮次中。每个服务器在开始新一轮投票时,会先对自己维护的 logicClock 进行自增操作。 +- **_初始化选票_** - 每个服务器在广播自己的选票前,会将自己的投票箱清空。该投票箱记录了所收到的选票。例:服务器 2 投票给服务器 3,服务器 3 投票给服务器 1,则服务器 1 的投票箱为(2, 3), (3, 1), (1, 1)。票箱中只会记录每一投票者的最后一票,如投票者更新自己的选票,则其它服务器收到该新选票后会在自己票箱中更新该服务器的选票。 -### 6.2. 选举流程 +- **_发送初始化选票_** - 每个服务器最开始都是通过广播把票投给自己。 -选举状态: +- **_接收外部投票_** - 服务器会尝试从其它服务器获取投票,并记入自己的投票箱内。如果无法获取任何外部投票,则会确认自己是否与集群中其它服务器保持着有效连接。如果是,则再次发送自己的投票;如果否,则马上与之建立连接。 -- LOOKING,竞选状态。 -- FOLLOWING,随从状态,同步 leader 状态,参与投票。 -- OBSERVING,观察状态,同步 leader 状态,不参与投票。 -- LEADING,领导者状态。 +- **_判断选举轮次_** - 收到外部投票后,首先会根据投票信息中所包含的 logicClock 来进行不同处理 -ZooKeeper 选举流程基于 Paxos 算法。 + - 外部投票的 logicClock 大于自己的 logicClock。说明该服务器的选举轮次落后于其它服务器的选举轮次,立即清空自己的投票箱并将自己的 logicClock 更新为收到的 logicClock,然后再对比自己之前的投票与收到的投票以确定是否需要变更自己的投票,最终再次将自己的投票广播出去。 + - 外部投票的 logicClock 小于自己的 logicClock。当前服务器直接忽略该投票,继续处理下一个投票。 + - 外部投票的 logickClock 与自己的相等。当时进行选票 PK。 +- **_选票 PK_** - 选票 PK 是基于(self_id, self_zxid)与(vote_id, vote_zxid)的对比 + - 外部投票的 logicClock 大于自己的 logicClock,则将自己的 logicClock 及自己的选票的 logicClock 变更为收到的 logicClock + - 若 logicClock 一致,则对比二者的 vote_zxid,若外部投票的 vote_zxid 比较大,则将自己的票中的 vote_zxid 与 vote_myid 更新为收到的票中的 vote_zxid 与 vote_myid 并广播出去,另外将收到的票及自己更新后的票放入自己的票箱。如果票箱内已存在(self_myid, self_zxid)相同的选票,则直接覆盖 + - 若二者 vote_zxid 一致,则比较二者的 vote_myid,若外部投票的 vote_myid 比较大,则将自己的票中的 vote_myid 更新为收到的票中的 vote_myid 并广播出去,另外将收到的票及自己更新后的票放入自己的票箱 +- **_统计选票_** - 如果已经确定有过半服务器认可了自己的投票(可能是更新后的投票),则终止投票。否则继续接收其它服务器的投票。 + +- **_更新服务器状态_** - 投票终止后,服务器开始更新自身状态。若过半的票投给了自己,则将自己的服务器状态更新为 LEADING,否则将自己的状态更新为 FOLLOWING + +通过以上流程分析,我们不难看出:要使 Leader 获得多数 Server 的支持,则 **ZooKeeper 集群节点数必须是奇数。且存活的节点数目不得少于 `N + 1`** 。 + +每个 Server 启动后都会重复以上流程。在恢复模式下,如果是刚从崩溃状态恢复的或者刚启动的 server 还会从磁盘快照中恢复数据和会话信息,zk 会记录事务日志并定期进行快照,方便在恢复时进行状态恢复。 + +#### 原子广播 + +**ZooKeeper 通过副本机制来实现高可用**。只要集群中半数以上机器处于可用状态,就能正常工作。 + +简单来说:ZooKeeper 会确保对 znode 所做的每一个修改都会被复制到集群中超过半数以上的机器上。如果少于半数的机器出现故障,则最少有一台机器保存了最新状态,其余的副本最终也会更新到这个状态。 + +那么,ZooKeeper 是如何实现副本机制的呢?答案是:Zab 协议的原子广播。
    - +
    -1. 选举线程由当前 Server 发起选举的线程担任,其主要功能是对投票结果进行统计,并选出推荐的 Server; -2. 选举线程首先向所有 Server 发起一次询问(包括自己); -3. 选举线程收到回复后,验证是否是自己发起的询问(验证 zxid 是否一致),然后获取对方的 id(myid),并存储到当前询问对象列表中,最后获取对方提议的 leader 相关信息(id,zxid),并将这些信息存储到当次选举的投票记录表中; -4. 收到所有 Server 回复以后,就计算出 zxid 最大的那个 Server,并将这个 Server 相关信息设置成下一次要投票的 Server; -5. 线程将当前 zxid 最大的 Server 设置为当前 Server 要推荐的 Leader,如果此时获胜的 Server 获得 n/2 + 1 的 Server 票数,设置当前推荐的 leader 为获胜的 Server,将根据获胜的 Server 相关信息设置自己的状态,否则,继续这个过程,直到 leader 被选举出来。 通过流程分析我们可以得出:要使 Leader 获得多数 Server 的支持,则 Server 总数必须是奇数 2n+1,且存活的 Server 的数目不得少于 n+1. 每个 Server 启动后都会重复以上流程。在恢复模式下,如果是刚从崩溃状态恢复的或者刚启动的 server 还会从磁盘快照中恢复数据和会话信息,zk 会记录事务日志并定期进行快照,方便在恢复时进行状态恢复。 +Zab 协议的原子广播要求: -述 Leader 选择过程中的状态变化,这是假设全部实例中均没有数据,假设服务器启动顺序分别为:A,B,C。 +所有的写请求都会被转发给 Leader,Leader 会以原子广播的方式通知 Follow。当半数以上的 Follow 已经更新状态持久化后,Leader 才会提交这个更新,然后客户端才会收到一个更新成功的响应。这有些类似数据库中的两阶段提交协议。 -### 6.3. 同步流程 +在整个消息的广播过程中,Leader 服务器会每个事物请求生成对应的 Proposal,并为其分配一个全局唯一的递增的事务 ID(ZXID),之后再对其进行广播。 -选完 Leader 以后,zk 就进入状态同步过程。 +### ACL -1. Leader 等待 server 连接; -2. Follower 连接 leader,将最大的 zxid 发送给 leader; -3. Leader 根据 follower 的 zxid 确定同步点; -4. 完成同步后通知 follower 已经成为 uptodate 状态; -5. Follower 收到 uptodate 消息后,又可以重新接受 client 的请求进行服务了。 +每个 znode 创建时都会带有一个 ACL 列表,用于决定谁可以对它执行何种操作。 -## 7. 应用场景 +ACL 依赖于 ZooKeeper 的客户端认证机制。ZooKeeper 提供了以下几种认证方式: -> **Zookeeper 可以用于发布/订阅、负载均衡、命令服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能** 。 +- **digest** - 用户名和密码 来识别客户端 +- **sasl** - 通过 kerberos 来识别客户端 +- **ip** - 通过 IP 来识别客户端 -### 7.1. 数据的发布/订阅 +ZooKeeper 定义了如下五种权限: + +- **CREATE** - 允许创建子节点; +- **READ** - 允许从节点获取数据并列出其子节点; +- **WRITE** - 允许为节点设置数据; +- **DELETE** - 允许删除子节点; +- **ADMIN** - 允许为节点设置权限。 + +## 应用 + +> **ZooKeeper 可以用于发布/订阅、负载均衡、命令服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能** 。 + +### 数据的发布/订阅 数据的发布/订阅系统,通常也用作配置中心。在分布式系统中,你可能有成千上万个服务节点,如果想要对所有服务的某项配置进行更改,由于数据节点过多,你不可逐台进行修改,而应该在设计时采用统一的配置中心。之后发布者只需要将新的配置发送到配置中心,所有服务节点即可自动下载并进行更新,从而实现配置的集中管理和动态更新。 -Zookeeper 通过 Watcher 机制可以实现数据的发布和订阅。分布式系统的所有的服务节点可以对某个 ZNode 注册监听,之后只需要将新的配置写入该 ZNode,所有服务节点都会收到该事件。 +ZooKeeper 通过 Watcher 机制可以实现数据的发布和订阅。分布式系统的所有的服务节点可以对某个 ZNode 注册监听,之后只需要将新的配置写入该 ZNode,所有服务节点都会收到该事件。 -### 7.2. 命名服务 +### 命名服务 -在分布式系统中,通常需要一个全局唯一的名字,如生成全局唯一的订单号等,Zookeeper 可以通过顺序节点的特性来生成全局唯一 ID,从而可以对分布式系统提供命名服务。 +在分布式系统中,通常需要一个全局唯一的名字,如生成全局唯一的订单号等,ZooKeeper 可以通过顺序节点的特性来生成全局唯一 ID,从而可以对分布式系统提供命名服务。 -### 7.3. 配置管理 +### 配置管理 配置的管理在分布式应用环境中很常见,例如同一个应用系统需要多台 PC Server 运行,但是它们运行的应用系统的某些配置项是相同的,如果要修改这些相同的配置项,那么就必须同时修改每台运行这个应用系统的 PC Server,这样非常麻烦而且容易出错。 像这样的配置信息完全可以交给 ZooKeeper 来管理,将配置信息保存在 ZooKeeper 的某个目录节点中,然后将所有需要修改的应用机器监控配置信息的状态,一旦配置信息发生变化,每台应用机器就会收到 ZooKeeper 的通知,然后从 ZooKeeper 获取新的配置信息应用到系统中。 -
    - -
    - -### 7.4. 集群管理 +### 集群管理 -Zookeeper 还能解决大多数分布式系统中的问题: +ZooKeeper 还能解决大多数分布式系统中的问题: - 如可以通过创建临时节点来建立心跳检测机制。如果分布式系统的某个服务节点宕机了,则其持有的会话会超时,此时该临时节点会被删除,相应的监听事件就会被触发。 - 分布式系统的每个服务节点还可以将自己的节点状态写入临时节点,从而完成状态报告或节点工作进度汇报。 -- 通过数据的订阅和发布功能,Zookeeper 还能对分布式系统进行模块的解耦和任务的调度。 +- 通过数据的订阅和发布功能,ZooKeeper 还能对分布式系统进行模块的解耦和任务的调度。 - 通过监听机制,还能对分布式系统的服务节点进行动态上下线,从而实现服务的动态扩容。 -
    - -
    - -### 7.5. 分布式锁 +### 分布式锁 -可以通过 Zookeeper 的临时节点和 Watcher 机制来实现分布式锁,这里以排它锁为例进行说明: +可以通过 ZooKeeper 的临时节点和 Watcher 机制来实现分布式锁,这里以排它锁为例进行说明: -分布式系统的所有服务节点可以竞争性地去创建同一个临时 ZNode,由于 Zookeeper 不能有路径相同的 ZNode,必然只有一个服务节点能够创建成功,此时可以认为该节点获得了锁。其他没有获得锁的服务节点通过在该 ZNode 上注册监听,从而当锁释放时再去竞争获得锁。锁的释放情况有以下两种: +分布式系统的所有服务节点可以竞争性地去创建同一个临时 ZNode,由于 ZooKeeper 不能有路径相同的 ZNode,必然只有一个服务节点能够创建成功,此时可以认为该节点获得了锁。其他没有获得锁的服务节点通过在该 ZNode 上注册监听,从而当锁释放时再去竞争获得锁。锁的释放情况有以下两种: - 当正常执行完业务逻辑后,客户端主动将临时 ZNode 删除,此时锁被释放; - 当获得锁的客户端发生宕机时,临时 ZNode 会被自动删除,此时认为锁已经释放。 当锁被释放后,其他服务节点则再次去竞争性地进行创建,但每次都只有一个服务节点能够获取到锁,这就是排他锁。 -### 7.6. Master 选举 +### 选举 Leader -分布式系统一个重要的模式就是主从模式 (Master/Salves),Zookeeper 可以用于该模式下的 Matser 选举。可以让所有服务节点去竞争性地创建同一个 ZNode,由于 Zookeeper 不能有路径相同的 ZNode,必然只有一个服务节点能够创建成功,这样该服务节点就可以成为 Master 节点。 +分布式系统一个重要的模式就是主从模式 (Master/Salves),ZooKeeper 可以用于该模式下的 Matser 选举。可以让所有服务节点去竞争性地创建同一个 ZNode,由于 ZooKeeper 不能有路径相同的 ZNode,必然只有一个服务节点能够创建成功,这样该服务节点就可以成为 Master 节点。 -### 7.7. 队列管理 +### 队列管理 ZooKeeper 可以处理两种类型的队列: @@ -309,53 +348,59 @@ ZooKeeper 可以处理两种类型的队列: 创建一个父目录 /synchronizing,每个成员都监控标志(Set Watch)位目录 /synchronizing/start 是否存在,然后每个成员都加入这个队列,加入队列的方式就是创建 /synchronizing/member_i 的临时目录节点,然后每个成员获取 / synchronizing 目录的所有目录节点,也就是 member_i。判断 i 的值是否已经是成员的个数,如果小于成员个数等待 /synchronizing/start 的出现,如果已经相等就创建 /synchronizing/start。 -## 8. Client API +## Client API -ZooKeeper 有一个绑定 Java 和 C 的官方 API。Zookeeper 社区为大多数语言(.NET,python 等)提供非官方 API。 - -使用 ZooKeeper API,应用程序可以连接,交互,操作数据,协调,最后断开与 ZooKeeper 集合的连接。 - -ZooKeeper API 具有丰富的功能,以简单和安全的方式获得 ZooKeeper 集合的所有功能。ZooKeeper API 提供同步和异步方法。 - -ZooKeeper 集合和 ZooKeeper API 在各个方面都完全相辅相成,对开发人员有很大的帮助。让我们在本章讨论 Java 绑定。 +> ZooKeeper 官方支持 Java 和 C 的 Client API。ZooKeeper 社区为大多数语言(.NET,python 等)提供非官方 API。 +> +> 本文以 Java Client API 来进行介绍。 -### 8.1. ZooKeeper API 的基础知识 +### Client API 简介 -与 ZooKeeper 集合进行交互的应用程序称为 **ZooKeeper 客户端**。 +客户端和服务端交互遵循以下基本步骤: -Znode 是 ZooKeeper 集合的核心组件,ZooKeeper API 提供了一小组方法使用 ZooKeeper 集合来操纵 znode 的所有细节。 +1. 客户端连接 ZooKeeper 服务端集群任意工作节点,该节点为客户端分配会话 ID。 +2. 为了保持通信,客户端需要和服务端保持心跳(实质上就是 ping )。否则,ZooKeeper 服务会话超时时间内未收到客户端请求,会将会话视为过期。这种情况下,客户端如果要通信,就需要重新连接。 +3. 只要会话 ID 处于活动状态,就可以执行读写 znode 操作。 +4. 所有任务完成后,客户端断开与 ZooKeeper 服务端集群的连接。如果客户端长时间不活动,则 ZooKeeper 集合将自动断开客户端。 -客户端应该遵循以步骤,与 ZooKeeper 集合进行清晰和干净的交互。 +ZooKeeper Client API 的核心是 **`ZooKeeper` 类**。它在其构造函数中提供了连接 ZooKeeper 服务的配置选项,并提供了访问 ZooKeeper 数据的方法。 -- 连接到 ZooKeeper 集合。ZooKeeper 集合为客户端分配会话 ID。 -- 定期向服务器发送心跳。否则,ZooKeeper 集合将过期会话 ID,客户端需要重新连接。 -- 只要会话 ID 处于活动状态,就可以获取/设置 znode。 -- 所有任务完成后,断开与 ZooKeeper 集合的连接。如果客户端长时间不活动,则 ZooKeeper 集合将自动断开客户端。 +> 其主要操作如下: +> +> - **`connect`** - 连接 ZooKeeper 服务 +> - **`create`** - 创建 znode +> - **`exists`** - 检查 znode 是否存在及其信息 +> - **`getData`** - 从特定的 znode 获取数据 +> - **`setData`** - 在特定的 znode 中设置数据 +> - **`getChildren`** - 获取特定 znode 中的所有子节点 +> - **`delete`** - 删除特定的 znode 及其所有子项 +> - **`close`** - 关闭连接 -### 8.2. Java 绑定 +### 引入依赖 -让我们来了解本章中最重要的一组 ZooKeeper API。ZooKeeper API 的核心部分是**ZooKeeper 类**。它提供了在其构造函数中连接 ZooKeeper 集合的选项,并具有以下方法: +maven 项目使用 ZooKeeper Client,只需在 pom.xml 中添加: -- **connect** - 连接到 ZooKeeper 集合 -- **create**- 创建 znode -- **exists**- 检查 znode 是否存在及其信息 -- **getData** - 从特定的 znode 获取数据 -- **setData** - 在特定的 znode 中设置数据 -- **getChildren** - 获取特定 znode 中的所有子节点 -- **delete** - 删除特定的 znode 及其所有子项 -- **close** - 关闭连接 +```xml + + org.apache.curator + curator-recipes + 4.2.0 + +``` -### 8.3. 连接到 ZooKeeper 集合 +### 连接 ZooKeeper -ZooKeeper 类通过其构造函数提供 connect 功能。构造函数的签名如下 : +ZooKeeper 类通过其构造函数提供连接 ZooKeeper 服务的功能。其构造函数的定义如下: -``` +```java ZooKeeper(String connectionString, int sessionTimeout, Watcher watcher) ``` -- **connectionString** - ZooKeeper 集合主机。 -- **sessionTimeout** - 会话超时(以毫秒为单位)。 -- **watcher** - 实现“监视器”界面的对象。ZooKeeper 集合通过监视器对象返回连接状态。 +> 参数说明: +> +> - **`connectionString`** - ZooKeeper 集群的主机列表。 +> - **`sessionTimeout`** - 会话超时时间(以毫秒为单位)。 +> - **watcher** - 实现监视机制的回调。当被监控的 znode 状态发生变化时,ZooKeeper 服务端的 `WatcherManager` 会主动调用传入的 Watcher ,推送状态变化给客户端。 让我们创建一个新的帮助类 **ZooKeeperConnection** ,并添加一个方法 **connect** 。 **connect** 方法创建一个 ZooKeeper 对象,连接到 ZooKeeper 集合,然后返回对象。 @@ -414,9 +459,9 @@ public class ZooKeeperConnection { 保存上面的代码,它将在下一节中用于连接 ZooKeeper 集合。 -### 8.4. 创建 Znode +### 创建 znode -ZooKeeper 类提供了在 ZooKeeper 集合中创建一个新的 znode 的**create**方法。 **create** 方法的签名如下: +ZooKeeper 类提供了在 ZooKeeper 集合中创建一个新的 znode 的 **`create`** 方法。 **create** 方法的签名如下: ``` create(String path, byte[] data, List acl, CreateMode createMode) @@ -488,7 +533,7 @@ bin/zkCli.sh >>> get /MyFirstZnode ``` -### 8.5. Exists - 检查 Znode 的存在 +### 检查 znode 是否存在 ZooKeeper 类提供了 **exists** 方法来检查 znode 的存在。如果指定的 znode 存在,则返回一个 znode 的元数据。**exists**方法的签名如下: @@ -551,7 +596,7 @@ public class ZKExists { Node exists and the node version is 1. ``` -### 8.6. getData 方法 +### getData 方法 ZooKeeper 类提供 **getData** 方法来获取附加在指定 znode 中的数据及其状态。 **getData** 方法的签名如下: @@ -662,7 +707,7 @@ bin/zkCli.sh Hello ``` -### 8.7. setData 方法 +### setData 方法 ZooKeeper 类提供 **setData** 方法来修改指定 znode 中附加的数据。 **setData** 方法的签名如下: @@ -722,7 +767,7 @@ bin/zkCli.sh >>> get /MyFirstZnode ``` -### 8.8. getChildren 方法 +### getChildren 方法 ZooKeeper 类提供 **getChildren** 方法来获取特定 znode 的所有子节点。 **getChildren** 方法的签名如下: @@ -798,7 +843,7 @@ myfirstsubnode mysecondsubnode ``` -### 8.9. 删除 Znode +### 删除 Znode ZooKeeper 类提供了 **delete** 方法来删除指定的 znode。 **delete** 方法的签名如下: @@ -842,17 +887,21 @@ public class ZKDelete { } ``` -## 9. 运维 +## 运维 > 安装、配置、命令可以参考:[ZooKeeper 运维指南](zookeeper-ops.md) -## 10. 参考资料 +## 参考资料 -- **ZooKeeper 官方** - - [官网](http://zookeeper.apache.org/) - - [官网文档](https://cwiki.apache.org/confluence/display/ZOOKEEPER) - - [Github](https://github.com/apache/zookeeper) +- **官方** + - [ZooKeeper 官网](http://zookeeper.apache.org/) + - [ZooKeeper 官方文档](https://cwiki.apache.org/confluence/display/ZOOKEEPER) + - [ZooKeeper Github](https://github.com/apache/zookeeper) +- **书籍** + - [Hadoop 权威指南(第四版)](https://item.jd.com/12109713.html) - **文章** - [分布式服务框架 ZooKeeper -- 管理分布式环境中的数据](https://www.ibm.com/developerworks/cn/opensource/os-cn-zookeeper/index.html) - [ZooKeeper 的功能以及工作原理](https://www.cnblogs.com/felixzh/p/5869212.html) - - [Zookeeper 简介及核心概念](https://github.com/heibaiying/BigData-Notes/blob/master/notes/Zookeeper%E7%AE%80%E4%BB%8B%E5%8F%8A%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5.md) + - [ZooKeeper 简介及核心概念](https://github.com/heibaiying/BigData-Notes/blob/master/notes/ZooKeeper%E7%AE%80%E4%BB%8B%E5%8F%8A%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5.md) + - [详解分布式协调服务 ZooKeeper](https://draveness.me/zookeeper-chubby) + - [深入浅出 Zookeeper(一) Zookeeper 架构及 FastLeaderElection 机制](http://www.jasongj.com/zookeeper/fastleaderelection/) diff --git a/docs/technology/mq/ActiveMQ.md b/docs/technology/mq/ActiveMQ.md index 59e56f56..2ee8e043 100644 --- a/docs/technology/mq/ActiveMQ.md +++ b/docs/technology/mq/ActiveMQ.md @@ -24,7 +24,7 @@ JMS 有两种消息模型: #### P2P 的特点 -
    +![img](http://dunwu.test.upcdn.net/cs/java/javalib/jms/jms-pointToPoint.gif!zp) 在点对点的消息系统中,消息分发给一个单独的使用者。点对点消息往往与队列 `javax.jms.Queue` 相关联。 @@ -38,7 +38,7 @@ JMS 有两种消息模型: #### Pub/Sub 的特点 -
    +![img](http://dunwu.test.upcdn.net/cs/java/javalib/jms/jms-publishSubscribe.gif!zp) 发布/订阅消息系统支持一个事件驱动模型,消息生产者和消费者都参与消息的传递。生产者发布事件,而使用者订阅感兴趣的事件,并使用事件。该类型消息一般与特定的主题 `javax.jms.Topic` 关联。 @@ -52,7 +52,7 @@ JMS 有两种消息模型: ### 1.2. JMS 编程模型 -
    +![img](http://dunwu.test.upcdn.net/cs/java/javalib/jms/jms-publishSubscribe.gif!zp) #### ConnectionFactory diff --git a/docs/technology/mq/MqInterview.md b/docs/technology/mq/MqInterview.md index 326214ba..2ded8479 100644 --- a/docs/technology/mq/MqInterview.md +++ b/docs/technology/mq/MqInterview.md @@ -20,13 +20,13 @@ 看这么个场景。A 系统发送数据到 BCD 三个系统,通过接口调用发送。如果 E 系统也要这个数据呢?那如果 C 系统现在不需要了呢?A 系统负责人几乎崩溃...... -
    +![img](https://github.com/doocs/advanced-java/blob/master/images/mq-1.png) 在这个场景中,A 系统跟其它各种乱七八糟的系统严重耦合,A 系统产生一条比较关键的数据,很多系统都需要 A 系统将这个数据发送过来。A 系统要时时刻刻考虑 BCDE 四个系统如果挂了该咋办?要不要重发,要不要把消息存起来?头发都白了啊! 如果使用 MQ,A 系统产生一条数据,发送到 MQ 里面去,哪个系统需要数据自己去 MQ 里面消费。如果新系统需要数据,直接从 MQ 里消费即可;如果某个系统不需要这条数据了,就取消对 MQ 消息的消费即可。这样下来,A 系统压根儿不需要去考虑要给谁发送数据,不需要维护这个代码,也不需要考虑人家是否调用成功、失败超时等情况。 -
    +![img](https://github.com/doocs/advanced-java/blob/master/images/mq-2.png) **总结**:通过一个 MQ,Pub/Sub 发布订阅消息这么一个模型,A 系统就跟其它系统彻底解耦了。 @@ -36,13 +36,13 @@ 再来看一个场景,A 系统接收一个请求,需要在自己本地写库,还需要在 BCD 三个系统写库,自己本地写库要 3ms,BCD 三个系统分别写库要 300ms、450ms、200ms。最终请求总延时是 3 + 300 + 450 + 200 = 953ms,接近 1s,用户感觉搞个什么东西,慢死了慢死了。用户通过浏览器发起请求,等待个 1s,这几乎是不可接受的。 -
    +![img](https://github.com/doocs/advanced-java/blob/master/images/mq-3.png) 一般互联网类的企业,对于用户直接的操作,一般要求是每个请求都必须在 200 ms 以内完成,对用户几乎是无感知的。 如果**使用 MQ**,那么 A 系统连续发送 3 条消息到 MQ 队列中,假如耗时 5ms,A 系统从接受一个请求到返回响应给用户,总时长是 3 + 5 = 8ms,对于用户而言,其实感觉上就是点个按钮,8ms 以后就直接返回了,爽!网站做得真好,真快! -
    +![img](https://github.com/doocs/advanced-java/blob/master/images/mq-4.png) ### 削峰 @@ -52,11 +52,11 @@ 但是高峰期一过,到了下午的时候,就成了低峰期,可能也就 1w 的用户同时在网站上操作,每秒中的请求数量可能也就 50 个请求,对整个系统几乎没有任何的压力。 -
    +![img](https://github.com/doocs/advanced-java/blob/master/images/mq-5.png) 如果使用 MQ,每秒 5k 个请求写入 MQ,A 系统每秒钟最多处理 2k 个请求,因为 MySQL 每秒钟最多处理 2k 个。A 系统从 MQ 中慢慢拉取请求,每秒钟就拉取 2k 个请求,不要超过自己每秒能处理的最大请求数量就 ok,这样下来,哪怕是高峰期的时候,A 系统也绝对不会挂掉。而 MQ 每秒钟 5k 个请求进来,就 2k 个请求出去,结果就导致在中午高峰期(1 个小时),可能有几十万甚至几百万的请求积压在 MQ 中。 -
    +![img](https://github.com/doocs/advanced-java/blob/master/images/mq-6.png) 这个短暂的高峰期积压是 ok 的,因为高峰期过了之后,每秒钟就 50 个请求进 MQ,但是 A 系统依然会按照每秒 2k 个请求的速度在处理。所以说,只要高峰期一过,A 系统就会快速将积压的消息给解决掉。 @@ -110,7 +110,7 @@ RabbitMQ 有三种模式:单机模式、普通集群模式、镜像集群模 普通集群模式,意思就是在多台机器上启动多个 RabbitMQ 实例,每个机器启动一个。你**创建的 queue,只会放在一个 RabbitMQ 实例上**,但是每个实例都同步 queue 的元数据(元数据可以认为是 queue 的一些配置信息,通过元数据,可以找到 queue 所在实例)。你消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从 queue 所在实例上拉取数据过来。 -
    +![img](https://github.com/doocs/advanced-java/blob/master/images/mq-7.png) 这种方式确实很麻烦,也不怎么好,**没做到所谓的分布式**,就是个普通集群。因为这导致你要么消费者每次随机连接一个实例然后拉取数据,要么固定连接那个 queue 所在实例消费数据,前者有**数据拉取的开销**,后者导致**单实例性能瓶颈**。 @@ -122,7 +122,7 @@ RabbitMQ 有三种模式:单机模式、普通集群模式、镜像集群模 这种模式,才是所谓的 RabbitMQ 的高可用模式。跟普通集群模式不一样的是,在镜像集群模式下,你创建的 queue,无论元数据还是 queue 里的消息都会**存在于多个实例上**,就是说,每个 RabbitMQ 节点都有这个 queue 的一个**完整镜像**,包含 queue 的全部数据的意思。然后每次你写消息到 queue 的时候,都会自动把**消息同步**到多个实例的 queue 上。 -
    +![img](https://github.com/doocs/advanced-java/blob/master/images/mq-8.png) 那么**如何开启这个镜像集群模式**呢?其实很简单,RabbitMQ 有很好的管理控制台,就是在后台新增一个策略,这个策略是**镜像集群模式的策略**,指定的时候是可以要求数据同步到所有节点的,也可以要求同步到指定数量的节点,再次创建 queue 的时候,应用这个策略,就会自动将数据同步到其他的节点上去了。 @@ -140,11 +140,11 @@ Kafka 0.8 以前,是没有 HA 机制的,就是任何一个 broker 宕机了 比如说,我们假设创建了一个 topic,指定其 partition 数量是 3 个,分别在三台机器上。但是,如果第二台机器宕机了,会导致这个 topic 的 1/3 的数据就丢了,因此这个是做不到高可用的。 -
    +![img](https://github.com/doocs/advanced-java/blob/master/images/kafka-before.png) Kafka 0.8 以后,提供了 HA 机制,就是 replica(复制品) 副本机制。每个 partition 的数据都会同步到其它机器上,形成自己的多个 replica 副本。所有 replica 会选举一个 leader 出来,那么生产和消费都跟这个 leader 打交道,然后其他 replica 就是 follower。写的时候,leader 会负责把数据同步到所有 follower 上去,读的时候就直接读 leader 上的数据即可。只能读写 leader?很简单,**要是你可以随意读写每个 follower,那么就要 care 数据一致性的问题**,系统复杂度太高,很容易出问题。Kafka 会均匀地将一个 partition 的所有 replica 分布在不同的机器上,这样才可以提高容错性。 -
    +![img](https://github.com/doocs/advanced-java/blob/master/images/kafka-after.png) 这么搞,就有所谓的**高可用性**了,因为如果某个 broker 宕机了,没事儿,那个 broker 上面的 partition 在其他机器上都有副本的。如果这个宕机的 broker 上面有某个 partition 的 leader,那么此时会从 follower 中**重新选举**一个新的 leader 出来,大家继续读写那个新的 leader 即可。这就有所谓的高可用性了。 @@ -166,7 +166,7 @@ Kafka 实际上有个 offset 的概念,就是每个消息写进去,都有一 有这么个场景。数据 1/2/3 依次进入 kafka,kafka 会给这三条数据每条分配一个 offset,代表这条数据的序号,我们就假设分配的 offset 依次是 152/153/154。消费者从 kafka 去消费的时候,也是按照这个顺序去消费。假如当消费者消费了 `offset=153` 的这条数据,刚准备去提交 offset 到 zookeeper,此时消费者进程被重启了。那么此时消费过的数据 1/2 的 offset 并没有提交,kafka 也就不知道你已经消费了 `offset=153` 这条数据。那么重启之后,消费者会找 kafka 说,嘿,哥儿们,你给我接着把上次我消费到的那个地方后面的数据继续给我传递过来。由于之前的 offset 没有提交成功,那么数据 1/2 会再次传过来,如果此时消费者没有去重的话,那么就会导致重复消费。 -
    +![img](https://github.com/doocs/advanced-java/blob/master/images/mq-10.png) 如果消费者干的事儿是拿一条数据就往数据库里写一条,会导致说,你可能就把数据 1/2 在数据库里插入了 2 次,那么数据就错啦。 @@ -187,7 +187,7 @@ Kafka 实际上有个 offset 的概念,就是每个消息写进去,都有一 - 比如你不是上面两个场景,那做的稍微复杂一点,你需要让生产者发送每条数据的时候,里面加一个全局唯一的 id,类似订单 id 之类的东西,然后你这里消费到了之后,先根据这个 id 去比如 Redis 里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个 id 写 Redis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可。 - 比如基于数据库的唯一键来保证重复数据不会重复插入多条。因为有唯一键约束了,重复数据插入只会报错,不会导致数据库中出现脏数据。 -
    +![img](https://github.com/doocs/advanced-java/blob/master/images/mq-11.png) 当然,如何保证 MQ 的消费是幂等性的,需要结合具体的业务来看。 @@ -197,7 +197,7 @@ Kafka 实际上有个 offset 的概念,就是每个消息写进去,都有一 ### RabbitMQ -
    +![img](https://github.com/doocs/advanced-java/blob/master/images/rabbitmq-message-lose.png) #### 生产者弄丢了数据 @@ -251,7 +251,7 @@ RabbitMQ 如果丢失了数据,主要是因为你消费的时候,**刚消费 这个时候得用 RabbitMQ 提供的 `ack` 机制,简单来说,就是你必须关闭 RabbitMQ 的自动 `ack`,可以通过一个 api 来调用就行,然后每次你自己代码里确保处理完的时候,再在程序里 `ack` 一把。这样的话,如果你还没处理完,不就没有 `ack` 了?那 RabbitMQ 就认为你还没处理完,这个时候 RabbitMQ 会把这个消费分配给别的 consumer 去处理,消息是不会丢的。 -
    +![img](https://github.com/doocs/advanced-java/blob/master/images/rabbitmq-message-lose-solution.png) ### Kafka @@ -294,25 +294,25 @@ RabbitMQ 如果丢失了数据,主要是因为你消费的时候,**刚消费 - **RabbitMQ**:一个 queue,多个 consumer。比如,生产者向 RabbitMQ 里发送了三条数据,顺序依次是 data1/data2/data3,压入的是 RabbitMQ 的一个内存队列。有三个消费者分别从 MQ 中消费这三条数据中的一条,结果消费者2先执行完操作,把 data2 存入数据库,然后是 data1/data3。这不明显乱了。 -
    +![img](https://github.com/doocs/advanced-java/blob/master/images/rabbitmq-order-01.png) - **Kafka**:比如说我们建了一个 topic,有三个 partition。生产者在写的时候,其实可以指定一个 key,比如说我们指定了某个订单 id 作为 key,那么这个订单相关的数据,一定会被分发到同一个 partition 中去,而且这个 partition 中的数据一定是有顺序的。 消费者从 partition 中取出来数据的时候,也一定是有顺序的。到这里,顺序还是 ok 的,没有错乱。接着,我们在消费者里可能会搞**多个线程来并发处理消息**。因为如果消费者是单线程消费处理,而处理比较耗时的话,比如处理一条消息耗时几十 ms,那么 1 秒钟只能处理几十条消息,这吞吐量太低了。而多个线程并发跑的话,顺序可能就乱掉了。 -
    +![img](https://github.com/doocs/advanced-java/blob/master/images/kafka-order-01.png) ### 解决方案 #### RabbitMQ -
    +![img](https://github.com/doocs/advanced-java/blob/master/images/rabbitmq-order-02.png) #### Kafka - 一个 topic,一个 partition,一个 consumer,内部单线程消费,单线程吞吐量太低,一般不会用这个。 - 写 N 个内存 queue,具有相同 key 的数据都到同一个内存 queue;然后对于 N 个线程,每个线程分别消费一个内存 queue 即可,这样就能保证顺序性。 -
    +![img](https://github.com/doocs/advanced-java/blob/master/images/kafka-order-02.png) ## 8. 如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决? diff --git a/docs/technology/mq/RocketmqAdvanced.md b/docs/technology/mq/RocketmqAdvanced.md index 7c4f73e3..ece19698 100644 --- a/docs/technology/mq/RocketmqAdvanced.md +++ b/docs/technology/mq/RocketmqAdvanced.md @@ -174,11 +174,11 @@ RocketMQ 除了支持普通消息,顺序消息,另外还支持事务消息 假设这样的场景: -
    +![img](https://upload-images.jianshu.io/upload_images/3101171-253d8bd65736694f.png) 图中执行本地事务(Bob 账户扣款)和发送异步消息应该保证同时成功或者同时失败,也就是扣款成功了,发送消息一定要成功,如果扣款失败了,就不能再发送消息。那问题是:我们是先扣款还是先发送消息呢? -
    +![img](http://upload-images.jianshu.io/upload_images/3101171-088dc074c4ecd192) RocketMQ 分布式事务步骤: diff --git a/docs/technology/mq/kafka-advance.md b/docs/technology/mq/kafka-advance.md index 28ab9159..89fcd73c 100644 --- a/docs/technology/mq/kafka-advance.md +++ b/docs/technology/mq/kafka-advance.md @@ -321,10 +321,10 @@ public void customerMessageWithSyncAndAsyncCommit(String topic) { Broker 端在缓存中保存了这 seq number,对于接收的每条消息,如果其序号比 Broker 缓存中序号大于 1 则接受它,否则将其丢弃。这样就可以实现了消息重复提交了。但是,只能保证单个 Producer 对于同一个的 Exactly Once 语义。不能保证同一个 Producer 一个 topic 不同的 partion 幂等。 -
    +![img](http://www.heartthinkdo.com/wp-content/uploads/2018/05/1-1.png) 实现幂等之后: -
    +![img](http://www.heartthinkdo.com/wp-content/uploads/2018/05/2.png) ### 3.2. 生成 PID 的流程 在执行创建事务时,如下: @@ -779,10 +779,10 @@ public void onlyConsumeInTransaction() { **一个 app 有一个 tid,同一个应用的不同实例 PID 是一样的,只是 epoch 的值不同**。如: -
    +![img](http://www.heartthinkdo.com/wp-content/uploads/2018/05/3-1.png) 同一份代码运行两个实例,分步执行如下:_在实例 1 没有进行提交事务前,开始执行实例 2 的初始化事务_ -
    +![img](http://www.heartthinkdo.com/wp-content/uploads/2018/05/4-1-1024x458.png) **step1 实例 1-初始化事务**。的打印出对应 productId 和 epoch,信息如下: [2018-04-21 20:56:23,106] INFO [TransactionCoordinator id=0] Initialized transactionalId first-transactional with producerId 8000 and producer epoch 123 on partition \_\_transaction_state-12 (kafka.coordinator.transaction.TransactionCoordinator) @@ -801,7 +801,7 @@ org.apache.kafka.common.errors.ProducerFencedException: Producer attempted an op 为了避免这种错误,同一个事务 ID,只有保证如下顺序 epch 小 producer 执行 init-transaction 和 committransaction,然后 epoch 较大的 procuder 才能开始执行 init-transaction 和 commit-transaction,如下顺序: -
    +![img](http://www.heartthinkdo.com/wp-content/uploads/2018/05/80061024.png) 有了 transactionId 后,Kafka 可保证: - 跨 Session 的数据幂等发送。当具有相同 Transaction ID 的新的 Producer 实例被创建且工作时,旧的且拥有相同 Transaction ID 的 Producer 将不再工作【上面的实例可以验证】。kafka 保证了关联同一个事务的所有 producer(一个应用有多个实例)必须按照顺序初始化事务、和提交事务,否则就会有问题,这保证了同一事务 ID 中消息是有序的(不同实例得按顺序创建事务和提交事务)。 @@ -870,7 +870,7 @@ protected Producer createTransactionalProducer() { #### Consume-transform-Produce 的流程 -
    +![img](http://www.heartthinkdo.com/wp-content/uploads/2018/05/Snip20180504_56.png) **流程 1** **:**查找 Tranaction Corordinator。 Producer 向任意一个 brokers 发送 FindCoordinatorRequest 请求来获取 Transaction Coordinator 的地址。 @@ -909,7 +909,7 @@ AddPartitionsToTxnRequest => TransactionalId PID Epoch [Topic [Partition]] 生产者发送数据,虽然没有还没有执行 commit 或者 absrot,但是此时消息已经保存到 kafka 上,可以参考如下图断点位置处,此时已经可以查看到消息了,而且即使后面执行 abort,消息也不会删除,只是更改状态字段标识消息为 abort 状态。 -
    +![img](http://www.heartthinkdo.com/wp-content/uploads/2018/05/62059279-1024x437.png) **流程 4.3:** AddOffsetCommitsToTxnRequest Producer 通过 KafkaProducer.sendOffsetsToTransaction 向事务协调器器发送一个 AddOffesetCommitsToTxnRequests: @@ -978,7 +978,7 @@ WriteTxnMarkersRequest => [CoorinadorEpoch PID Epoch Marker [Topic [Partition]]] kafka 文件主要包括 broker 的 data(主题:test)、事务协调器对应的 transaction_log(主题:\_\_tranaction_state)、偏移量信息(主题:\_consumer_offsets)三种类型。如下图 -
    +![img](http://www.heartthinkdo.com/wp-content/uploads/2018/05/1-2-207x300.png) 这三种文件类型其实都是 topic 的分区,所以对于每一个目录都包含*.log、*.index、_.timeindex、_.txnindex 文件(仅这个文件是为了实现事务属性引入的)。segment 和 segmengt 对应 index、timeindex、txnindex 文件命名中序号表示的是第几个消息。如下图中,00000000000000368769.index 和 00000000000000568769.log 中“368969”就是表示文件中存储的第一个消息是 468969 个消息。 对于索引文案包含两部分: @@ -986,7 +986,7 @@ kafka 文件主要包括 broker 的 data(主题:test)、事务协调器对 - baseOffset:索引对应 segment 文件中的第几条 message。 - position:在 segment 中的绝对位置。 -
    +![img](http://www.heartthinkdo.com/wp-content/uploads/2018/05/67930538-300x179.png) 查看文件内容: bin/kafka-run-class.sh kafka.tools.DumpLogSegments –files /Users/wuzhonghu/data/kafka-logs/firtstopic-0/00000000000000000002.log –print-data-log @@ -999,7 +999,7 @@ Trasaction markers 就是 kafka 为了实现事务定义的 Controll Message。 Transaction Log 如下放置在“\_tranaction_state”主题下面,默认是 50 个分区,每一个分区中文件格式和 broker 存储消息是一样的,都有 log/index/timeindex 文件,如下: -
    +![img](http://www.heartthinkdo.com/wp-content/uploads/2018/05/57646045.png) #### 消费读取事务消息(READ_COMMITED) Consumer 为了实现事务,新增了一个 isolation.level 配置,有两个值如下, @@ -1013,7 +1013,7 @@ Consumer 为了实现事务,新增了一个 isolation.level 配置,有两个 如下图中,按顺序保存到 broker 中消息有:事务 1 消息 T1-M1、对于事务 2 的消息有 T2-M1、事务 1 消息 T1-M2、非事务消息 M1,最终到达 client 端的循序是 M1-> T2-M1 -> T1-M1 -> T1-M2。 -
    +![img](http://www.heartthinkdo.com/wp-content/uploads/2018/05/84999567.png) 具体步骤如下: - **step1** Consumer 接受到事务消息 T1-M1、T2-M2、T1-M2 和非事务消息 M1,因为没有收到事务 T1 和 T2 的控制消息,所以此时把事务相关消息 T1-M1、T2-M2、T1-M2 保存到内存,然后只把非事务消息 M1 返回给 client。 @@ -1072,30 +1072,30 @@ ThrottleTime [TopicName [Partition ErrorCode HighwaterMarkOffset AbortedTransact - 存放数据的 log -
    +![img](http://www.heartthinkdo.com/wp-content/uploads/2018/05/1-3.png) - 存放 Absort Index 的内容如下: -
    +![img](http://www.heartthinkdo.com/wp-content/uploads/2018/05/3-2.png) 执行读取数据流程如下: **step1:** 假设 consumer 读取数据的 fetched offsets 的区间是 0 到 4。 - 首先,broker 读取 data log 中数据 -
    +![img](http://www.heartthinkdo.com/wp-content/uploads/2018/05/11-1.png) - 然后,broker 依次读取 abort index 的内容,发现 LSO 大于等于 4 就停止。如上可以获取到 P2 对应的 offset 从 2 到 5 的消息都是被丢弃的: -
    +![img](http://www.heartthinkdo.com/wp-content/uploads/2018/05/12-1.png) - 最后,broker 将上面 data log 和 abort index 中满足条件的数据返回给 consumer。 **step2 :**在 consumer 端根据 absrot index 中返回的内容,过滤丢弃的消息,最终给用户消息为 -
    +![img](http://www.heartthinkdo.com/wp-content/uploads/2018/05/13-300x103.png) ##### Absorted Transaction Index 在 broker 中数据中新增一个索引文件,保存 aborted tranasation 对应的 offsets,只有事务执行 abort 时,才会往这个文件新增一个记录,初始这个文件是不存在的,只有第一条 abort 时,才会创建这个文件。 -
    +![img](http://www.heartthinkdo.com/wp-content/uploads/2018/05/2-1-300x149.png) 这个索引文件结构的每一行结构是 TransactionEntry: ``` @@ -1114,10 +1114,10 @@ Broker 在缓存中维护了所有处于运行状态的事务对应的 initial o 举例说明下 LSO 的计算,对于一个 data log 中内如如下 -
    +![img](http://www.heartthinkdo.com/wp-content/uploads/2018/05/31.png) 对应的 abort index 文件中内如如下:**LSO 是递增的** -
    +![img](http://www.heartthinkdo.com/wp-content/uploads/2018/05/32.png) (2)第二步 如果事务是提交状态,则在索引文件中新增 TransactionEntry。 (3)第三步 从 active 的 tranaction set 中移除这个 transaton,然后更新 LSO。 diff --git a/docs/technology/mq/kafka-basic.md b/docs/technology/mq/kafka-basic.md index b9c45d5e..74d27b30 100644 --- a/docs/technology/mq/kafka-basic.md +++ b/docs/technology/mq/kafka-basic.md @@ -153,18 +153,14 @@ Kafka 集群是典型的**一主多从模式**。 Kafka 在 0.8 以前的版本中,如果一个 Broker 机器宕机了,其上面的 Partition 都不能用了。 -为了实现高可用,Kafka 引入了复制功能。 +为了实现高可用,Kafka 引入了副本机制。 -每个 Partition 都有一个 Broker 作为 Leader,零个或者多个 Broker 作为 Follower。 +每个 Partition 都有一个 Broker 作为 Leader,零个或者多个 Broker 作为 Follower。每个 Broker 都会成为某些分区的 Leader 和某些分区的 Follower,因此集群的负载是平衡的。 - **Leader 处理一切对 Partition (分区)的读写请求**; - **而 Follower 只需被动的同步 Leader 上的数据**。 -当 Leader 宕机了,Follower 中的一台服务器会自动成为新的 Leader。每台 server 都会成为某些分区的 Leader 和某些分区的 Follower,因此集群的负载是平衡的。 - -同一个 Topic 的不同 Partition 会分布在多个 Broker 上,而且一个 Partition 还会在其他的 Broker 上面进行备份,Producer 在发布消息到某个 Partition 时,先找到该 Partition 的 Leader,然后向这个 Leader 推送消息;每个 Follower 都从 Leader 拉取消息,拉取消息成功之后,向 Leader 发送一个 ack 确认。 - -如下一个流程图: +同一个 Topic 的不同 Partition 会分布在多个 Broker 上,而且一个 Partition 还会在其他的 Broker 上面进行备份,Producer 在发布消息到某个 Partition 时,先找到该 Partition 的 Leader,然后向这个 Leader 推送消息;每个 Follower 都从 Leader 拉取消息,拉取消息成功之后,向 Leader 发送一个 ACK 确认。
    @@ -172,6 +168,14 @@ Kafka 在 0.8 以前的版本中,如果一个 Broker 机器宕机了,其上 ### 4.2. 选举 Leader +由上文可知,Partition 在多个 Broker 上存在副本。 + +如果某个 Follower 宕机,啥事儿没有,正常工作。 + +如果 Leader 宕机了,会从 Follower 中**重新选举**一个新的 Leader。 + +当 Leader 宕机了,Follower 中的一台服务器会自动成为新的 Leader。 +
    diff --git a/docs/technology/server/Tomcat.md b/docs/technology/server/Tomcat.md index b39aafbb..d5046352 100644 --- a/docs/technology/server/Tomcat.md +++ b/docs/technology/server/Tomcat.md @@ -82,7 +82,7 @@ tar -zxf apache-tomcat-8.5.24.tar.gz 启动后,访问 http://localhost:8080 ,可以看到 Tomcat 安装成功的测试页面。 -
    +![img](http://dunwu.test.upcdn.net/cs/java/javaweb/tools/tomcat/tomcat.png!zp) ### 配置 @@ -335,7 +335,7 @@ public class SimpleTomcatServer { - 设置启动应用的端口、JVM 参数、启动浏览器等。 - 成功后,可以访问 http://localhost:8080/(当然,你也可以在 url 中设置上下文名称)。 -
    +![img](http://dunwu.test.upcdn.net/cs/java/javaweb/tools/tomcat/tomcat-intellij-run-config.png!zp) > **说明** > From ea6cde3025bfe6dd84ad33a634b775466e3f342a Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Mon, 13 Jan 2020 23:38:58 +0800 Subject: [PATCH 49/75] =?UTF-8?q?update=20zookeeper=20=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../javaweb-rpc/javaweb-rpc-zookeeper/pom.xml | 27 ------- .../dunwu/javaweb/zookeeper/ZKConnection.java | 51 ------------ .../dunwu/javaweb/zookeeper/ZKCreate.java | 47 ----------- .../dunwu/javaweb/zookeeper/ZKDelete.java | 35 --------- .../dunwu/javaweb/zookeeper/ZKExists.java | 38 --------- .../javaweb/zookeeper/ZKGetChildren.java | 48 ------------ .../dunwu/javaweb/zookeeper/ZKGetData.java | 71 ----------------- .../dunwu/javaweb/zookeeper/ZKSetData.java | 36 --------- codes/javaweb/javaweb-rpc/pom.xml | 14 ---- codes/javaweb/javaweb-zookeeper/pom.xml | 37 +++++++++ .../example/ZooKeeperConnection.java | 56 +++++++++++++ .../zookeeper/example/ZooKeeperCreate.java | 50 ++++++++++++ .../zookeeper/example/ZooKeeperDelete.java | 41 ++++++++++ .../zookeeper/example/ZooKeeperExists.java | 44 +++++++++++ .../example/ZooKeeperGetChildren.java | 55 +++++++++++++ .../zookeeper/example/ZooKeeperGetData.java | 78 +++++++++++++++++++ .../zookeeper/example/ZooKeeperSetData.java | 42 ++++++++++ .../zookeeper/example/package-info.java | 7 ++ 18 files changed, 410 insertions(+), 367 deletions(-) delete mode 100644 codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/pom.xml delete mode 100644 codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKConnection.java delete mode 100644 codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKCreate.java delete mode 100644 codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKDelete.java delete mode 100644 codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKExists.java delete mode 100644 codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKGetChildren.java delete mode 100644 codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKGetData.java delete mode 100644 codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKSetData.java delete mode 100644 codes/javaweb/javaweb-rpc/pom.xml create mode 100644 codes/javaweb/javaweb-zookeeper/pom.xml create mode 100644 codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperConnection.java create mode 100644 codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperCreate.java create mode 100644 codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperDelete.java create mode 100644 codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperExists.java create mode 100644 codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperGetChildren.java create mode 100644 codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperGetData.java create mode 100644 codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperSetData.java create mode 100644 codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/package-info.java diff --git a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/pom.xml b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/pom.xml deleted file mode 100644 index 45b2a6c5..00000000 --- a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/pom.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - 4.0.0 - - io.github.dunwu.javaweb - javaweb-rpc-zookeeper - 1.0.0 - jar - - - UTF-8 - 1.8 - ${java.version} - ${java.version} - - - - - org.apache.zookeeper - zookeeper - 3.4.12 - - - - diff --git a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKConnection.java b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKConnection.java deleted file mode 100644 index 68b8e1bc..00000000 --- a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKConnection.java +++ /dev/null @@ -1,51 +0,0 @@ -package io.github.dunwu.javaweb.zookeeper; - -import java.io.IOException; -import java.util.concurrent.CountDownLatch; -import org.apache.zookeeper.WatchedEvent; -import org.apache.zookeeper.Watcher; -import org.apache.zookeeper.Watcher.Event.KeeperState; -import org.apache.zookeeper.ZooKeeper; -import org.apache.zookeeper.ZooKeeper.States; - -/** - * ZooKeeper 连接示例 - */ -public class ZKConnection { - - private static final String HOST = "localhost"; - - final CountDownLatch connectedSignal = new CountDownLatch(1); - - // declare zookeeper instance to access ZooKeeper ensemble - private ZooKeeper zoo; - - public static void main(String[] args) throws IOException, InterruptedException { - ZKConnection zkConnection = new ZKConnection(); - ZooKeeper zk = zkConnection.connect(HOST); - States state = zk.getState(); - System.out.println("ZooKeeper isAlive:" + state.isAlive()); - zk.close(); - } - - // Method to connect zookeeper ensemble. - public ZooKeeper connect(String host) throws IOException, InterruptedException { - - zoo = new ZooKeeper(host, 5000, new Watcher() { - public void process(WatchedEvent we) { - if (we.getState() == KeeperState.SyncConnected) { - connectedSignal.countDown(); - } - } - }); - - connectedSignal.await(); - return zoo; - } - - // Method to disconnect from zookeeper server - public void close() throws InterruptedException { - zoo.close(); - } - -} diff --git a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKCreate.java b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKCreate.java deleted file mode 100644 index 712b53ff..00000000 --- a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKCreate.java +++ /dev/null @@ -1,47 +0,0 @@ -package io.github.dunwu.javaweb.zookeeper; - -import org.apache.zookeeper.CreateMode; -import org.apache.zookeeper.KeeperException; -import org.apache.zookeeper.ZooDefs; -import org.apache.zookeeper.ZooKeeper; - -/** - * ZooKeeper 添加 Znode 示例 - */ -public class ZKCreate { - - private static final String HOST = "localhost"; - - // create static instance for zookeeper class. - private static ZooKeeper zk; - - // create static instance for ZooKeeperConnection class. - private static ZKConnection conn; - - public static void main(String[] args) throws InterruptedException { - - // znode path - String path = "/MyFirstZnode"; // Assign path to znode - - // data in byte array - byte[] data = "My first zookeeper app".getBytes(); // Declare data - - try { - conn = new ZKConnection(); - zk = conn.connect(HOST); - create(path, data); // Create the data to the specified path - } catch (Exception e) { - System.out.println(e.getMessage()); // Catch error message - } finally { - if (conn != null) { - conn.close(); - } - } - } - - // Method to create znode in zookeeper ensemble - public static void create(String path, byte[] data) throws KeeperException, InterruptedException { - zk.create(path, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); - } - -} diff --git a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKDelete.java b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKDelete.java deleted file mode 100644 index 20272ef3..00000000 --- a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKDelete.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.github.dunwu.javaweb.zookeeper; - -import org.apache.zookeeper.KeeperException; -import org.apache.zookeeper.ZooKeeper; - -public class ZKDelete { - - private static final String HOST = "localhost"; - - private static ZooKeeper zk; - - private static ZKConnection conn; - - public static void main(String[] args) throws InterruptedException, KeeperException { - String path = "/MyFirstZnode"; // Assign path to the znode - - try { - conn = new ZKConnection(); - zk = conn.connect(HOST); - delete(path); // delete the node with the specified path - } catch (Exception e) { - System.out.println(e.getMessage()); // catches error messages - } finally { - if (conn != null) { - conn.close(); - } - } - } - - // Method to check existence of znode and its status, if znode is available. - public static void delete(String path) throws KeeperException, InterruptedException { - zk.delete(path, zk.exists(path, true).getVersion()); - } - -} diff --git a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKExists.java b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKExists.java deleted file mode 100644 index d3d2f2c9..00000000 --- a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKExists.java +++ /dev/null @@ -1,38 +0,0 @@ -package io.github.dunwu.javaweb.zookeeper; - -import org.apache.zookeeper.KeeperException; -import org.apache.zookeeper.ZooKeeper; -import org.apache.zookeeper.data.Stat; - -public class ZKExists { - - private static final String HOST = "localhost"; - - private static ZooKeeper zk; - - private static ZKConnection conn; - - public static void main(String[] args) throws InterruptedException, KeeperException { - String path = "/MyFirstZnode"; // Assign znode to the specified path - - try { - conn = new ZKConnection(); - zk = conn.connect(HOST); - Stat stat = znode_exists(path); // Stat checks the path of the znode - - if (stat != null) { - System.out.println("Node exists and the node version is " + stat.getVersion()); - } else { - System.out.println("Node does not exists"); - } - } catch (Exception e) { - System.out.println(e.getMessage()); // Catches error messages - } - } - - // Method to check existence of znode and its status, if znode is available. - public static Stat znode_exists(String path) throws KeeperException, InterruptedException { - return zk.exists(path, true); - } - -} diff --git a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKGetChildren.java b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKGetChildren.java deleted file mode 100644 index 9f110865..00000000 --- a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKGetChildren.java +++ /dev/null @@ -1,48 +0,0 @@ -package io.github.dunwu.javaweb.zookeeper; - -import java.util.List; -import org.apache.zookeeper.KeeperException; -import org.apache.zookeeper.ZooKeeper; -import org.apache.zookeeper.data.Stat; - -public class ZKGetChildren { - - private static final String HOST = "localhost"; - - private static ZooKeeper zk; - - private static ZKConnection conn; - - public static void main(String[] args) throws InterruptedException, KeeperException { - String path = "/MyFirstZnode"; // Assign path to the znode - - try { - conn = new ZKConnection(); - zk = conn.connect(HOST); - Stat stat = znode_exists(path); // Stat checks the path - - if (stat != null) { - // getChildren method - get all the children of znode.It has two args, - // path and watch - List children = zk.getChildren(path, false); - for (int i = 0; i < children.size(); i++) { - System.out.println(children.get(i)); // Print children's - } - } else { - System.out.println("Node does not exists"); - } - } catch (Exception e) { - System.out.println(e.getMessage()); - } finally { - if (conn != null) { - conn.close(); - } - } - } - - // Method to check existence of znode and its status, if znode is available. - public static Stat znode_exists(String path) throws KeeperException, InterruptedException { - return zk.exists(path, true); - } - -} diff --git a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKGetData.java b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKGetData.java deleted file mode 100644 index 9b5ec589..00000000 --- a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKGetData.java +++ /dev/null @@ -1,71 +0,0 @@ -package io.github.dunwu.javaweb.zookeeper; - -import java.util.concurrent.CountDownLatch; -import org.apache.zookeeper.KeeperException; -import org.apache.zookeeper.WatchedEvent; -import org.apache.zookeeper.Watcher; -import org.apache.zookeeper.ZooKeeper; -import org.apache.zookeeper.data.Stat; - -public class ZKGetData { - - private static final String HOST = "localhost"; - - private static ZooKeeper zk; - - private static ZKConnection conn; - - public static void main(String[] args) throws InterruptedException { - String path = "/MyFirstZnode"; - final CountDownLatch connectedSignal = new CountDownLatch(1); - - try { - conn = new ZKConnection(); - zk = conn.connect(HOST); - Stat stat = existsZnode(path); - - if (stat != null) { - byte[] b = zk.getData(path, new Watcher() { - public void process(WatchedEvent we) { - - if (we.getType() == Event.EventType.None) { - switch (we.getState()) { - case Expired: - connectedSignal.countDown(); - break; - } - } else { - String path = "/MyFirstZnode"; - - try { - byte[] bn = zk.getData(path, false, null); - String data = new String(bn, "UTF-8"); - System.out.println(data); - connectedSignal.countDown(); - } catch (Exception ex) { - System.out.println(ex.getMessage()); - } - } - } - }, null); - - String data = new String(b, "UTF-8"); - System.out.println(data); - connectedSignal.await(); - } else { - System.out.println("Node does not exists"); - } - } catch (Exception e) { - System.out.println(e.getMessage()); - } finally { - if (conn != null) { - conn.close(); - } - } - } - - public static Stat existsZnode(String path) throws KeeperException, InterruptedException { - return zk.exists(path, true); - } - -} diff --git a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKSetData.java b/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKSetData.java deleted file mode 100644 index c2bb3740..00000000 --- a/codes/javaweb/javaweb-rpc/javaweb-rpc-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/ZKSetData.java +++ /dev/null @@ -1,36 +0,0 @@ -package io.github.dunwu.javaweb.zookeeper; - -import org.apache.zookeeper.KeeperException; -import org.apache.zookeeper.ZooKeeper; - -public class ZKSetData { - - private static final String HOST = "localhost"; - - private static ZooKeeper zk; - - private static ZKConnection conn; - - public static void main(String[] args) throws InterruptedException { - String path = "/MyFirstZnode"; - byte[] data = "Success".getBytes(); // Assign data which is to be updated. - - try { - conn = new ZKConnection(); - zk = conn.connect(HOST); - update(path, data); // Update znode data to the specified path - } catch (Exception e) { - System.out.println(e.getMessage()); - } finally { - if (conn != null) { - conn.close(); - } - } - } - - // Method to update the data in a znode. Similar to getData but without watcher. - public static void update(String path, byte[] data) throws KeeperException, InterruptedException { - zk.setData(path, data, zk.exists(path, true).getVersion()); - } - -} diff --git a/codes/javaweb/javaweb-rpc/pom.xml b/codes/javaweb/javaweb-rpc/pom.xml deleted file mode 100644 index 9c83f47f..00000000 --- a/codes/javaweb/javaweb-rpc/pom.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - 4.0.0 - - io.github.dunwu.javaweb - javaweb-rpc - 1.0.0 - pom - - - javaweb-rpc-zookeeper - - diff --git a/codes/javaweb/javaweb-zookeeper/pom.xml b/codes/javaweb/javaweb-zookeeper/pom.xml new file mode 100644 index 00000000..2084b766 --- /dev/null +++ b/codes/javaweb/javaweb-zookeeper/pom.xml @@ -0,0 +1,37 @@ + + + 4.0.0 + + + io.github.dunwu + dunwu-parent + 0.5.0 + + + io.github.dunwu.javaweb + javaweb-zookeeper + 1.0.0 + jar + + + UTF-8 + 1.8 + ${java.version} + ${java.version} + + + + + org.apache.zookeeper + zookeeper + 3.5.6 + + + io.github.dunwu + dunwu-common-core + 0.5.0 + + + diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperConnection.java b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperConnection.java new file mode 100644 index 00000000..c9bc4fb5 --- /dev/null +++ b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperConnection.java @@ -0,0 +1,56 @@ +package io.github.dunwu.javaweb.zookeeper.example; + +import org.apache.zookeeper.WatchedEvent; +import org.apache.zookeeper.Watcher; +import org.apache.zookeeper.Watcher.Event.KeeperState; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.ZooKeeper.States; + +import java.io.IOException; +import java.util.concurrent.CountDownLatch; + +/** + * ZooKeeper 连接示例 + * + * @author Zhang Peng + * @since 2018-07-12 + */ +public class ZooKeeperConnection { + + private static final String HOST = "localhost"; + + final CountDownLatch connectedSignal = new CountDownLatch(1); + + // declare zookeeper instance to access ZooKeeper ensemble + private ZooKeeper zoo; + + public static void main(String[] args) throws IOException, InterruptedException { + ZooKeeperConnection zooKeeperConnection = new ZooKeeperConnection(); + ZooKeeper zk = zooKeeperConnection.connect(HOST); + States state = zk.getState(); + System.out.println("ZooKeeper isAlive:" + state.isAlive()); + zk.close(); + } + + // Method to connect zookeeper ensemble. + public ZooKeeper connect(String host) throws IOException, InterruptedException { + + zoo = new ZooKeeper(host, 5000, new Watcher() { + @Override + public void process(WatchedEvent we) { + if (we.getState() == KeeperState.SyncConnected) { + connectedSignal.countDown(); + } + } + }); + + connectedSignal.await(); + return zoo; + } + + // Method to disconnect from zookeeper server + public void close() throws InterruptedException { + zoo.close(); + } + +} diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperCreate.java b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperCreate.java new file mode 100644 index 00000000..cd435424 --- /dev/null +++ b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperCreate.java @@ -0,0 +1,50 @@ +package io.github.dunwu.javaweb.zookeeper.example; + +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.ZooKeeper; + +/** + * ZooKeeper 添加 Znode 示例 + * + * @author Zhang Peng + * @since 2018-07-12 + */ +public class ZooKeeperCreate { + + private static final String HOST = "localhost"; + + // create static instance for zookeeper class. + private static ZooKeeper zk; + + // create static instance for ZooKeeperConnection class. + private static ZooKeeperConnection conn; + + public static void main(String[] args) throws InterruptedException { + + // znode path + String path = "/MyFirstZnode"; // Assign path to znode + + // data in byte array + byte[] data = "My first zookeeper app".getBytes(); // Declare data + + try { + conn = new ZooKeeperConnection(); + zk = conn.connect(HOST); + create(path, data); // Create the data to the specified path + } catch (Exception e) { + System.out.println(e.getMessage()); // Catch error message + } finally { + if (conn != null) { + conn.close(); + } + } + } + + // Method to create znode in zookeeper ensemble + public static void create(String path, byte[] data) throws KeeperException, InterruptedException { + zk.create(path, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + } + +} diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperDelete.java b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperDelete.java new file mode 100644 index 00000000..a25a990e --- /dev/null +++ b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperDelete.java @@ -0,0 +1,41 @@ +package io.github.dunwu.javaweb.zookeeper.example; + +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.ZooKeeper; + +/** + * ZooKeeper 删除 Znode 示例 + * + * @author Zhang Peng + * @since 2018-07-12 + */ +public class ZooKeeperDelete { + + private static final String HOST = "localhost"; + + private static ZooKeeper zk; + + private static ZooKeeperConnection conn; + + public static void main(String[] args) throws InterruptedException, KeeperException { + String path = "/MyFirstZnode"; // Assign path to the znode + + try { + conn = new ZooKeeperConnection(); + zk = conn.connect(HOST); + delete(path); // delete the node with the specified path + } catch (Exception e) { + System.out.println(e.getMessage()); // catches error messages + } finally { + if (conn != null) { + conn.close(); + } + } + } + + // Method to check existence of znode and its status, if znode is available. + public static void delete(String path) throws KeeperException, InterruptedException { + zk.delete(path, zk.exists(path, true).getVersion()); + } + +} diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperExists.java b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperExists.java new file mode 100644 index 00000000..ae37348f --- /dev/null +++ b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperExists.java @@ -0,0 +1,44 @@ +package io.github.dunwu.javaweb.zookeeper.example; + +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.data.Stat; + +/** + * ZooKeeper 判断 Znode 是否存在示例 + * + * @author Zhang Peng + * @since 2018-07-12 + */ +public class ZooKeeperExists { + + private static final String HOST = "localhost"; + + private static ZooKeeper zk; + + private static ZooKeeperConnection conn; + + public static void main(String[] args) throws InterruptedException, KeeperException { + String path = "/MyFirstZnode"; // Assign znode to the specified path + + try { + conn = new ZooKeeperConnection(); + zk = conn.connect(HOST); + Stat stat = znodeExists(path); // Stat checks the path of the znode + + if (stat != null) { + System.out.println("Node exists and the node version is " + stat.getVersion()); + } else { + System.out.println("Node does not exists"); + } + } catch (Exception e) { + System.out.println(e.getMessage()); // Catches error messages + } + } + + // Method to check existence of znode and its status, if znode is available. + public static Stat znodeExists(String path) throws KeeperException, InterruptedException { + return zk.exists(path, true); + } + +} diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperGetChildren.java b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperGetChildren.java new file mode 100644 index 00000000..c4536d97 --- /dev/null +++ b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperGetChildren.java @@ -0,0 +1,55 @@ +package io.github.dunwu.javaweb.zookeeper.example; + +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.data.Stat; + +import java.util.List; + +/** + * ZooKeeper 获取 znode 的所有子节点示例 + * + * @author Zhang Peng + * @since 2018-07-12 + */ +public class ZooKeeperGetChildren { + + private static final String HOST = "localhost"; + + private static ZooKeeper zk; + + private static ZooKeeperConnection conn; + + public static void main(String[] args) throws InterruptedException, KeeperException { + String path = "/MyFirstZnode"; // Assign path to the znode + + try { + conn = new ZooKeeperConnection(); + zk = conn.connect(HOST); + Stat stat = znode_exists(path); // Stat checks the path + + if (stat != null) { + // getChildren method - get all the children of znode.It has two args, + // path and watch + List children = zk.getChildren(path, false); + for (int i = 0; i < children.size(); i++) { + System.out.println(children.get(i)); // Print children's + } + } else { + System.out.println("Node does not exists"); + } + } catch (Exception e) { + System.out.println(e.getMessage()); + } finally { + if (conn != null) { + conn.close(); + } + } + } + + // Method to check existence of znode and its status, if znode is available. + public static Stat znode_exists(String path) throws KeeperException, InterruptedException { + return zk.exists(path, true); + } + +} diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperGetData.java b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperGetData.java new file mode 100644 index 00000000..e6758add --- /dev/null +++ b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperGetData.java @@ -0,0 +1,78 @@ +package io.github.dunwu.javaweb.zookeeper.example; + +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.WatchedEvent; +import org.apache.zookeeper.Watcher; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.data.Stat; + +import java.util.concurrent.CountDownLatch; + +/** + * ZooKeeper 获取数据示例 + * + * @author Zhang Peng + * @since 2018-07-12 + */ +public class ZooKeeperGetData { + + private static final String HOST = "localhost"; + + private static ZooKeeper zk; + + private static ZooKeeperConnection conn; + + public static void main(String[] args) throws InterruptedException { + String path = "/MyFirstZnode"; + final CountDownLatch connectedSignal = new CountDownLatch(1); + + try { + conn = new ZooKeeperConnection(); + zk = conn.connect(HOST); + Stat stat = existsZnode(path); + + if (stat != null) { + byte[] b = zk.getData(path, new Watcher() { + public void process(WatchedEvent we) { + + if (we.getType() == Event.EventType.None) { + switch (we.getState()) { + case Expired: + connectedSignal.countDown(); + break; + } + } else { + String path = "/MyFirstZnode"; + + try { + byte[] bn = zk.getData(path, false, null); + String data = new String(bn, "UTF-8"); + System.out.println(data); + connectedSignal.countDown(); + } catch (Exception ex) { + System.out.println(ex.getMessage()); + } + } + } + }, null); + + String data = new String(b, "UTF-8"); + System.out.println(data); + connectedSignal.await(); + } else { + System.out.println("Node does not exists"); + } + } catch (Exception e) { + System.out.println(e.getMessage()); + } finally { + if (conn != null) { + conn.close(); + } + } + } + + public static Stat existsZnode(String path) throws KeeperException, InterruptedException { + return zk.exists(path, true); + } + +} diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperSetData.java b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperSetData.java new file mode 100644 index 00000000..6d74bcba --- /dev/null +++ b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperSetData.java @@ -0,0 +1,42 @@ +package io.github.dunwu.javaweb.zookeeper.example; + +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.ZooKeeper; + +/** + * ZooKeeper 设置数据示例 + * + * @author Zhang Peng + * @since 2018-07-12 + */ +public class ZooKeeperSetData { + + private static final String HOST = "localhost"; + + private static ZooKeeper zk; + + private static ZooKeeperConnection conn; + + public static void main(String[] args) throws InterruptedException { + String path = "/MyFirstZnode"; + byte[] data = "Success".getBytes(); // Assign data which is to be updated. + + try { + conn = new ZooKeeperConnection(); + zk = conn.connect(HOST); + update(path, data); // Update znode data to the specified path + } catch (Exception e) { + System.out.println(e.getMessage()); + } finally { + if (conn != null) { + conn.close(); + } + } + } + + // Method to update the data in a znode. Similar to getData but without watcher. + public static void update(String path, byte[] data) throws KeeperException, InterruptedException { + zk.setData(path, data, zk.exists(path, true).getVersion()); + } + +} diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/package-info.java b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/package-info.java new file mode 100644 index 00000000..97b9b369 --- /dev/null +++ b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/package-info.java @@ -0,0 +1,7 @@ +/** + * ZooKeeper 最基本操作示例 + * + * @author Zhang Peng + * @since 2020-01-13 + */ +package io.github.dunwu.javaweb.zookeeper.example; From 231ad5a28b36734b974beb288ad55823ff53a5bf Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Mon, 20 Jan 2020 19:41:38 +0800 Subject: [PATCH 50/75] =?UTF-8?q?zookeeper=20=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codes/javaweb/javaweb-zookeeper/pom.xml | 20 ++++ .../dunwu/javaweb/zk/dlock/Callback.java | 12 ++ .../dunwu/javaweb/zk/dlock/DLockTemplate.java | 16 +++ .../javaweb/zk/dlock/DistributedLock.java | 19 +++ .../javaweb/zk/dlock/TimeoutHandler.java | 11 ++ .../javaweb/zk/dlock/ZkDLockTemplate.java | 51 ++++++++ .../zk/dlock/ZkReentrantLockCleanerTask.java | 83 +++++++++++++ .../ZookeeperReentrantDistributedLock.java | 112 ++++++++++++++++++ .../example/ZooKeeperConnection.java | 2 +- .../example/ZooKeeperCreate.java | 2 +- .../example/ZooKeeperDelete.java | 2 +- .../example/ZooKeeperExists.java | 2 +- .../example/ZooKeeperGetChildren.java | 2 +- .../example/ZooKeeperGetData.java | 2 +- .../example/ZooKeeperSetData.java | 2 +- .../example/package-info.java | 2 +- .../zk/sequence/DistributedSequence.java | 9 ++ .../zk/sequence/ZkDistributedSequence.java | 64 ++++++++++ .../zk/dlock/ZkReentrantLockTemplateTest.java | 83 +++++++++++++ .../src/test/resources/logback.xml | 62 ++++++++++ 20 files changed, 550 insertions(+), 8 deletions(-) create mode 100644 codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/Callback.java create mode 100644 codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/DLockTemplate.java create mode 100644 codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/DistributedLock.java create mode 100644 codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/TimeoutHandler.java create mode 100644 codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZkDLockTemplate.java create mode 100644 codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZkReentrantLockCleanerTask.java create mode 100644 codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZookeeperReentrantDistributedLock.java rename codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/{zookeeper => zk}/example/ZooKeeperConnection.java (96%) rename codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/{zookeeper => zk}/example/ZooKeeperCreate.java (96%) rename codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/{zookeeper => zk}/example/ZooKeeperDelete.java (95%) rename codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/{zookeeper => zk}/example/ZooKeeperExists.java (96%) rename codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/{zookeeper => zk}/example/ZooKeeperGetChildren.java (97%) rename codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/{zookeeper => zk}/example/ZooKeeperGetData.java (98%) rename codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/{zookeeper => zk}/example/ZooKeeperSetData.java (95%) rename codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/{zookeeper => zk}/example/package-info.java (71%) create mode 100644 codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/sequence/DistributedSequence.java create mode 100644 codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/sequence/ZkDistributedSequence.java create mode 100644 codes/javaweb/javaweb-zookeeper/src/test/java/io/github/dunwu/javaweb/zk/dlock/ZkReentrantLockTemplateTest.java create mode 100644 codes/javaweb/javaweb-zookeeper/src/test/resources/logback.xml diff --git a/codes/javaweb/javaweb-zookeeper/pom.xml b/codes/javaweb/javaweb-zookeeper/pom.xml index 2084b766..cb029670 100644 --- a/codes/javaweb/javaweb-zookeeper/pom.xml +++ b/codes/javaweb/javaweb-zookeeper/pom.xml @@ -28,10 +28,30 @@ zookeeper 3.5.6 + + org.apache.curator + curator-recipes + 4.2.0 + io.github.dunwu dunwu-common-core 0.5.0 + + io.github.dunwu + dunwu-tool-core + 0.5.0 + + + com.google.guava + guava + 28.2-jre + + + junit + junit + test + diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/Callback.java b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/Callback.java new file mode 100644 index 00000000..e514ea55 --- /dev/null +++ b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/Callback.java @@ -0,0 +1,12 @@ +package io.github.dunwu.javaweb.zk.dlock; + +/** + * Created by sunyujia@aliyun.com on 2016/2/23. + */ +public interface Callback { + + V onGetLock() throws InterruptedException; + + V onTimeout() throws InterruptedException; + +} diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/DLockTemplate.java b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/DLockTemplate.java new file mode 100644 index 00000000..673e5cb7 --- /dev/null +++ b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/DLockTemplate.java @@ -0,0 +1,16 @@ +package io.github.dunwu.javaweb.zk.dlock; + +/** + * 分布式锁模板类 Created by sunyujia@aliyun.com on 2016/2/23. + */ +public interface DLockTemplate { + + /** + * @param lockId 锁id(对应业务唯一ID) + * @param timeout 单位毫秒 + * @param callback 回调函数 + * @return + */ + V execute(String lockId, long timeout, Callback callback); + +} diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/DistributedLock.java b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/DistributedLock.java new file mode 100644 index 00000000..d487720d --- /dev/null +++ b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/DistributedLock.java @@ -0,0 +1,19 @@ +package io.github.dunwu.javaweb.zk.dlock; + +import java.util.concurrent.TimeUnit; + +/** + * 分布式锁接口 + * + * @author Zhang Peng + * @since 2020-01-14 + */ +public interface DistributedLock { + + void lock(); + + boolean tryLock(long timeout, TimeUnit unit); + + void unlock(); + +} diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/TimeoutHandler.java b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/TimeoutHandler.java new file mode 100644 index 00000000..807bb848 --- /dev/null +++ b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/TimeoutHandler.java @@ -0,0 +1,11 @@ +package io.github.dunwu.javaweb.zk.dlock; + +/** + * @author Zhang Peng + * @since 2020-01-14 + */ +public interface TimeoutHandler { + + V onTimeout() throws InterruptedException; + +} diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZkDLockTemplate.java b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZkDLockTemplate.java new file mode 100644 index 00000000..523c488e --- /dev/null +++ b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZkDLockTemplate.java @@ -0,0 +1,51 @@ +package io.github.dunwu.javaweb.zk.dlock; + +import org.apache.curator.framework.CuratorFramework; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.TimeUnit; + +/** + * Created by sunyujia@aliyun.com on 2016/2/26. + */ +public class ZkDLockTemplate implements DLockTemplate { + + private static final Logger log = LoggerFactory.getLogger(ZkDLockTemplate.class); + + private CuratorFramework client; + + public ZkDLockTemplate(CuratorFramework client) { + this.client = client; + } + + @Override + public V execute(String lockId, long timeout, Callback callback) { + ZookeeperReentrantDistributedLock distributedReentrantLock = null; + boolean getLock = false; + try { + distributedReentrantLock = new ZookeeperReentrantDistributedLock(client, lockId); + if (tryLock(distributedReentrantLock, timeout)) { + getLock = true; + return callback.onGetLock(); + } else { + return callback.onTimeout(); + } + } catch (InterruptedException ex) { + log.error(ex.getMessage(), ex); + Thread.currentThread().interrupt(); + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + if (getLock) { + distributedReentrantLock.unlock(); + } + } + return null; + } + + private boolean tryLock(ZookeeperReentrantDistributedLock distributedReentrantLock, long timeout) { + return distributedReentrantLock.tryLock(timeout, TimeUnit.MILLISECONDS); + } + +} diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZkReentrantLockCleanerTask.java b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZkReentrantLockCleanerTask.java new file mode 100644 index 00000000..5d0a0ab7 --- /dev/null +++ b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZkReentrantLockCleanerTask.java @@ -0,0 +1,83 @@ +package io.github.dunwu.javaweb.zk.dlock; + +import org.apache.curator.RetryPolicy; +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.framework.CuratorFrameworkFactory; +import org.apache.curator.retry.ExponentialBackoffRetry; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.TimerTask; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; + +/** + * Created by sunyujia@aliyun.com on 2016/2/25. + */ +public class ZkReentrantLockCleanerTask extends TimerTask { + + private static final org.slf4j.Logger log = LoggerFactory.getLogger(ZkReentrantLockCleanerTask.class); + + private CuratorFramework client; + + private ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3); + + /** + * 检查周期 + */ + private long period = 5000; + + /** + * Curator RetryPolicy maxRetries + */ + private int maxRetries = 3; + + /** + * Curator RetryPolicy baseSleepTimeMs + */ + private final int baseSleepTimeMs = 1000; + + public ZkReentrantLockCleanerTask(String zookeeperAddress) { + try { + RetryPolicy retryPolicy = new ExponentialBackoffRetry(baseSleepTimeMs, maxRetries); + client = CuratorFrameworkFactory.newClient(zookeeperAddress, retryPolicy); + client.start(); + } catch (Exception e) { + log.error(e.getMessage(), e); + } catch (Throwable ex) { + ex.printStackTrace(); + log.error(ex.getMessage(), ex); + } + } + + public void start() { + executorService.execute(this); + } + + private boolean isEmpty(List list) { + return list == null || list.isEmpty(); + } + + @Override + public void run() { + try { + List childrenPaths = this.client.getChildren().forPath(ZookeeperReentrantDistributedLock.ROOT_PATH); + for (String path : childrenPaths) { + cleanNode(path); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void cleanNode(String path) { + try { + if (isEmpty(this.client.getChildren().forPath(path))) { + this.client.delete().forPath(path);//利用存在子节点无法删除和zk的原子性这两个特性. + } + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZookeeperReentrantDistributedLock.java b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZookeeperReentrantDistributedLock.java new file mode 100644 index 00000000..ed8045ba --- /dev/null +++ b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZookeeperReentrantDistributedLock.java @@ -0,0 +1,112 @@ +package io.github.dunwu.javaweb.zk.dlock; + +import io.github.dunwu.tool.collection.CollectionUtil; +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.framework.recipes.locks.InterProcessMutex; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * 基于Zookeeper的可重入互斥锁(关于重入:仅限于持有zk锁的jvm内重入) Created by sunyujia@aliyun.com on 2016/2/24. + */ +public class ZookeeperReentrantDistributedLock implements DistributedLock { + + private static final Logger log = LoggerFactory.getLogger(ZookeeperReentrantDistributedLock.class); + + /** + * 线程池 + */ + private static final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3); + + /** + * 所有PERSISTENT锁节点的根位置 + */ + public static final String ROOT_PATH = "/distributed_lock/"; + + /** + * 每次延迟清理PERSISTENT节点的时间 Unit:MILLISECONDS + */ + private static final long DELAY_TIME_FOR_CLEAN = 1000; + + /** + * zk 共享锁实现 + */ + private InterProcessMutex interProcessMutex; + + /** + * 锁的ID,对应zk一个PERSISTENT节点,下挂EPHEMERAL节点. + */ + private String path; + + /** + * zk的客户端 + */ + private CuratorFramework client; + + public ZookeeperReentrantDistributedLock(CuratorFramework client, String lockId) { + this.client = client; + this.path = ROOT_PATH + lockId; + interProcessMutex = new InterProcessMutex(this.client, this.path); + } + + @Override + public void lock() { + try { + interProcessMutex.acquire(); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + + @Override + public boolean tryLock(long timeout, TimeUnit unit) { + try { + return interProcessMutex.acquire(timeout, unit); + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } + } + + @Override + public void unlock() { + try { + interProcessMutex.release(); + } catch (Throwable e) { + log.error(e.getMessage(), e); + } finally { + executorService.schedule(new Cleaner(client, path), DELAY_TIME_FOR_CLEAN, TimeUnit.MILLISECONDS); + } + } + + static class Cleaner implements Runnable { + + private String path; + + private CuratorFramework client; + + public Cleaner(CuratorFramework client, String path) { + this.path = path; + this.client = client; + } + + @Override + public void run() { + try { + List list = client.getChildren().forPath(path); + if (CollectionUtil.isEmpty(list)) { + client.delete().forPath(path); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + + } + +} diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperConnection.java b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperConnection.java similarity index 96% rename from codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperConnection.java rename to codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperConnection.java index c9bc4fb5..521d464d 100644 --- a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperConnection.java +++ b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperConnection.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.zookeeper.example; +package io.github.dunwu.javaweb.zk.example; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperCreate.java b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperCreate.java similarity index 96% rename from codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperCreate.java rename to codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperCreate.java index cd435424..6ee7855c 100644 --- a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperCreate.java +++ b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperCreate.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.zookeeper.example; +package io.github.dunwu.javaweb.zk.example; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperDelete.java b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperDelete.java similarity index 95% rename from codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperDelete.java rename to codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperDelete.java index a25a990e..67d38178 100644 --- a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperDelete.java +++ b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperDelete.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.zookeeper.example; +package io.github.dunwu.javaweb.zk.example; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.ZooKeeper; diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperExists.java b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperExists.java similarity index 96% rename from codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperExists.java rename to codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperExists.java index ae37348f..a903793e 100644 --- a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperExists.java +++ b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperExists.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.zookeeper.example; +package io.github.dunwu.javaweb.zk.example; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.ZooKeeper; diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperGetChildren.java b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperGetChildren.java similarity index 97% rename from codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperGetChildren.java rename to codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperGetChildren.java index c4536d97..7b4f82e7 100644 --- a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperGetChildren.java +++ b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperGetChildren.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.zookeeper.example; +package io.github.dunwu.javaweb.zk.example; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.ZooKeeper; diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperGetData.java b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperGetData.java similarity index 98% rename from codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperGetData.java rename to codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperGetData.java index e6758add..bc888cf3 100644 --- a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperGetData.java +++ b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperGetData.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.zookeeper.example; +package io.github.dunwu.javaweb.zk.example; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.WatchedEvent; diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperSetData.java b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperSetData.java similarity index 95% rename from codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperSetData.java rename to codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperSetData.java index 6d74bcba..44153cd3 100644 --- a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/ZooKeeperSetData.java +++ b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperSetData.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.zookeeper.example; +package io.github.dunwu.javaweb.zk.example; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.ZooKeeper; diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/package-info.java b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/package-info.java similarity index 71% rename from codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/package-info.java rename to codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/package-info.java index 97b9b369..7b356a38 100644 --- a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zookeeper/example/package-info.java +++ b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/package-info.java @@ -4,4 +4,4 @@ * @author Zhang Peng * @since 2020-01-13 */ -package io.github.dunwu.javaweb.zookeeper.example; +package io.github.dunwu.javaweb.zk.example; diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/sequence/DistributedSequence.java b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/sequence/DistributedSequence.java new file mode 100644 index 00000000..aa28f666 --- /dev/null +++ b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/sequence/DistributedSequence.java @@ -0,0 +1,9 @@ +package io.github.dunwu.javaweb.zk.sequence; + +/** + * Created by sunyujia@aliyun.com on 2016/2/25. + */ +public interface DistributedSequence { + + public Long sequence(String sequenceName); +} diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/sequence/ZkDistributedSequence.java b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/sequence/ZkDistributedSequence.java new file mode 100644 index 00000000..1c8920fa --- /dev/null +++ b/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/sequence/ZkDistributedSequence.java @@ -0,0 +1,64 @@ +package io.github.dunwu.javaweb.zk.sequence; + +import io.github.dunwu.javaweb.zk.dlock.ZkReentrantLockCleanerTask; +import org.apache.curator.RetryPolicy; +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.framework.CuratorFrameworkFactory; +import org.apache.curator.retry.ExponentialBackoffRetry; +import org.slf4j.LoggerFactory; + +/** + * Created by sunyujia@aliyun.com on 2016/2/25. + */ +public class ZkDistributedSequence implements DistributedSequence { + + private static final org.slf4j.Logger log = LoggerFactory.getLogger(ZkReentrantLockCleanerTask.class); + + private CuratorFramework client; + + /** + * Curator RetryPolicy maxRetries + */ + private int maxRetries = 3; + + /** + * Curator RetryPolicy baseSleepTimeMs + */ + private final int baseSleepTimeMs = 1000; + + public ZkDistributedSequence(String zookeeperAddress) { + try { + RetryPolicy retryPolicy = new ExponentialBackoffRetry(baseSleepTimeMs, maxRetries); + client = CuratorFrameworkFactory.newClient(zookeeperAddress, retryPolicy); + client.start(); + } catch (Exception e) { + log.error(e.getMessage(), e); + } catch (Throwable ex) { + ex.printStackTrace(); + log.error(ex.getMessage(), ex); + } + } + + public int getMaxRetries() { + return maxRetries; + } + + public void setMaxRetries(int maxRetries) { + this.maxRetries = maxRetries; + } + + public int getBaseSleepTimeMs() { + return baseSleepTimeMs; + } + + public Long sequence(String sequenceName) { + try { + int value = client.setData().withVersion(-1).forPath("/" + sequenceName, "".getBytes()).getVersion(); + return new Long(value); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + +} diff --git a/codes/javaweb/javaweb-zookeeper/src/test/java/io/github/dunwu/javaweb/zk/dlock/ZkReentrantLockTemplateTest.java b/codes/javaweb/javaweb-zookeeper/src/test/java/io/github/dunwu/javaweb/zk/dlock/ZkReentrantLockTemplateTest.java new file mode 100644 index 00000000..52f5c65a --- /dev/null +++ b/codes/javaweb/javaweb-zookeeper/src/test/java/io/github/dunwu/javaweb/zk/dlock/ZkReentrantLockTemplateTest.java @@ -0,0 +1,83 @@ +package io.github.dunwu.javaweb.zk.dlock; + +import org.apache.curator.RetryPolicy; +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.framework.CuratorFrameworkFactory; +import org.apache.curator.retry.ExponentialBackoffRetry; +import org.junit.Test; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ThreadLocalRandom; + +/** + * Created by sunyujia@aliyun.com on 2016/2/24. + */ + +public class ZkReentrantLockTemplateTest { + + @Test + public void testTry() throws InterruptedException { + RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3); + CuratorFramework client = CuratorFrameworkFactory.newClient("127.0.0.1:2181", retryPolicy); + client.start(); + + final ZkDLockTemplate template = new ZkDLockTemplate(client); + int size = 100; + final CountDownLatch startCountDownLatch = new CountDownLatch(1); + final CountDownLatch endDownLatch = new CountDownLatch(size); + for (int i = 0; i < size; i++) { + new Thread(() -> { + try { + startCountDownLatch.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + final int sleepTime = ThreadLocalRandom.current().nextInt(3) * 1000; + + template.execute("test", 3000, new Callback() { + @Override + public Object onGetLock() throws InterruptedException { + System.out.println(Thread.currentThread().getName() + " 获取锁"); + Thread.currentThread().sleep(sleepTime); + System.out.println(Thread.currentThread().getName() + ":sleeped:" + sleepTime); + endDownLatch.countDown(); + return null; + } + + @Override + public Object onTimeout() throws InterruptedException { + System.out.println(Thread.currentThread().getName() + " 获取锁"); + Thread.currentThread().sleep(sleepTime); + System.out.println(Thread.currentThread().getName() + ":sleeped:" + sleepTime); + endDownLatch.countDown(); + return null; + } + }); + }).start(); + } + startCountDownLatch.countDown(); + endDownLatch.await(); + } + + public static void main(String[] args) { + RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3); + CuratorFramework client = CuratorFrameworkFactory.newClient("127.0.0.1:2181", retryPolicy); + client.start(); + + final ZkDLockTemplate template = new ZkDLockTemplate(client);//本类多线程安全,可通过spring注入 + template.execute("订单流水号", 5000, new Callback() { + @Override + public Object onGetLock() throws InterruptedException { + //TODO 获得锁后要做的事 + return null; + } + + @Override + public Object onTimeout() throws InterruptedException { + //TODO 获得锁超时后要做的事 + return null; + } + }); + } + +} diff --git a/codes/javaweb/javaweb-zookeeper/src/test/resources/logback.xml b/codes/javaweb/javaweb-zookeeper/src/test/resources/logback.xml new file mode 100644 index 00000000..e94627b4 --- /dev/null +++ b/codes/javaweb/javaweb-zookeeper/src/test/resources/logback.xml @@ -0,0 +1,62 @@ + + + + + + + + ${LOG_MSG} + + + + + DEBUG + + ${USER_HOME}/debug.log + + ${LOG_DIR}/debug%i.log + + 20MB + + + + ${LOG_MSG} + + + + ${USER_HOME}/info.log + + INFO + + + ${LOG_DIR}/info%i.log + + 20MB + + + + ${LOG_MSG} + + + + ${USER_HOME}/error.log + + ERROR + + + ${LOG_DIR}/error%i.log + + 20MB + + + + ${LOG_MSG} + + + + + + + + + From bb3dc806b7a7bc22d449409ca03228484e475600 Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Wed, 22 Jan 2020 23:31:51 +0800 Subject: [PATCH 51/75] update --- README.md | 10 +- assets/xmind/dubbo.xmind | Bin 0 -> 120845 bytes assets/xmind/mybatis.xmind | Bin 0 -> 128361 bytes assets/xmind/shiro.xmind | Bin 0 -> 131886 bytes .../javaweb-load-balance/pom.xml | 43 +++++ .../javaweb/ConsistentHashLoadBalancer.java | 75 ++++++++ .../dunwu/javaweb/ConsistentHashRouter.java | 172 ++++++++++++++++++ .../io/github/dunwu/javaweb/LoadBalance.java | 17 ++ .../dunwu/javaweb/RandomLoadBalance.java | 85 +++++++++ .../dunwu/javaweb/RoundRobinLoadBalance.java | 85 +++++++++ .../github/dunwu/javaweb/StatisticsUtil.java | 35 ++++ .../dunwu/javaweb/WeightLoadBalance.java | 15 ++ .../io/github/dunwu/javaweb/package-info.java | 7 + .../javaweb/support/CRCHashStrategy.java | 69 +++++++ .../javaweb/support/FnvHashStrategy.java | 24 +++ .../dunwu/javaweb/support/HashStrategy.java | 7 + .../javaweb/support/JdkHashCodeStrategy.java | 10 + .../javaweb/support/KetamaHashStrategy.java | 42 +++++ .../javaweb/support/MurmurHashStrategy.java | 51 ++++++ .../dunwu/javaweb/LoadBalanceTests.java | 133 ++++++++++++++ .../javaweb-mq-kafka-spring-boot/pom.xml | 0 .../github/dunwu/javaweb/KafkaConsumer.java | 0 .../github/dunwu/javaweb/KafkaProducer.java | 0 .../javaweb/KafkaProducerController.java | 0 .../dunwu/javaweb/MsgKafkaApplication.java | 0 .../javaweb/config/KafkaConsumerConfig.java | 0 .../javaweb/config/KafkaProducerConfig.java | 0 .../src/main/resources/application.properties | 0 .../src/main/resources/logback.xml | 0 .../dunwu/javaweb/KafkaProducerTest.java | 0 .../javaweb-mq/javaweb-mq-kafka/pom.xml | 0 .../dunwu/javaweb/kafka/ConsumerAOC.java | 0 .../dunwu/javaweb/kafka/ConsumerManual.java | 0 .../kafka/ConsumerManualPartition.java | 0 .../dunwu/javaweb/kafka/ProducerDemo.java | 0 .../javaweb/kafka/ProducerInTransaction.java | 0 .../dunwu/javaweb/kafka/StreamDemo.java | 0 .../src/main/resources/logback.xml | 0 .../javaweb-mq/javaweb-mq-rocketmq/pom.xml | 0 .../dunwu/javaweb/rocketmq/AsyncProducer.java | 0 .../dunwu/javaweb/rocketmq/BatchProducer.java | 0 .../javaweb/rocketmq/BroadcastConsumer.java | 0 .../javaweb/rocketmq/BroadcastProducer.java | 0 .../javaweb/rocketmq/OnewayProducer.java | 0 .../javaweb/rocketmq/OrderedConsumer.java | 0 .../javaweb/rocketmq/OrderedProducer.java | 0 .../dunwu/javaweb/rocketmq/RocketConfig.java | 0 .../rocketmq/ScheduledMessageConsumer.java | 0 .../rocketmq/ScheduledMessageProducer.java | 0 .../dunwu/javaweb/rocketmq/SyncProducer.java | 0 .../src/main/resources/logback.xml | 0 .../javaweb-mq/pom.xml | 0 .../javaweb-zookeeper/pom.xml | 0 .../dunwu/javaweb/zk/dlock/Callback.java | 0 .../dunwu/javaweb/zk/dlock/DLockTemplate.java | 0 .../javaweb/zk/dlock/DistributedLock.java | 0 .../javaweb/zk/dlock/TimeoutHandler.java | 0 .../javaweb/zk/dlock/ZkDLockTemplate.java | 0 .../zk/dlock/ZkReentrantLockCleanerTask.java | 0 .../ZookeeperReentrantDistributedLock.java | 0 .../zk/example/ZooKeeperConnection.java | 0 .../javaweb/zk/example/ZooKeeperCreate.java | 0 .../javaweb/zk/example/ZooKeeperDelete.java | 0 .../javaweb/zk/example/ZooKeeperExists.java | 0 .../zk/example/ZooKeeperGetChildren.java | 0 .../javaweb/zk/example/ZooKeeperGetData.java | 0 .../javaweb/zk/example/ZooKeeperSetData.java | 0 .../javaweb/zk/example/package-info.java | 0 .../zk/sequence/DistributedSequence.java | 0 .../zk/sequence/ZkDistributedSequence.java | 0 .../zk/dlock/ZkReentrantLockTemplateTest.java | 0 .../src/test/resources/logback.xml | 0 codes/javaweb-example/pom.xml | 19 ++ docs/README.md | 8 +- docs/sidebar.md | 8 +- .../data/{Mybatis.md => mybatis.md} | 68 +++---- .../{rpc/Dubbo.md => distributed/dubbo.md} | 94 ++++------ docs/technology/distributed/zookeeper.md | 82 ++++----- docs/technology/security/Shiro.md | 53 +++--- .../{SpringSecurity.md => spring-security.md} | 0 codes/javaweb/pom.xml => pom.xml | 15 +- prettier.config.js | 7 - 82 files changed, 1043 insertions(+), 191 deletions(-) create mode 100644 assets/xmind/dubbo.xmind create mode 100644 assets/xmind/mybatis.xmind create mode 100644 assets/xmind/shiro.xmind create mode 100644 codes/javaweb-example/javaweb-load-balance/pom.xml create mode 100644 codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/ConsistentHashLoadBalancer.java create mode 100644 codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/ConsistentHashRouter.java create mode 100644 codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/LoadBalance.java create mode 100644 codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/RandomLoadBalance.java create mode 100644 codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/RoundRobinLoadBalance.java create mode 100644 codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/StatisticsUtil.java create mode 100644 codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/WeightLoadBalance.java create mode 100644 codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/package-info.java create mode 100644 codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/CRCHashStrategy.java create mode 100644 codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/FnvHashStrategy.java create mode 100644 codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/HashStrategy.java create mode 100644 codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/JdkHashCodeStrategy.java create mode 100644 codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/KetamaHashStrategy.java create mode 100644 codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/MurmurHashStrategy.java create mode 100644 codes/javaweb-example/javaweb-load-balance/src/test/java/io/github/dunwu/javaweb/LoadBalanceTests.java rename codes/{javaweb => javaweb-example}/javaweb-mq/javaweb-mq-kafka-spring-boot/pom.xml (100%) rename codes/{javaweb => javaweb-example}/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaConsumer.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaProducer.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaProducerController.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/MsgKafkaApplication.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/config/KafkaConsumerConfig.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/config/KafkaProducerConfig.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/resources/application.properties (100%) rename codes/{javaweb => javaweb-example}/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/resources/logback.xml (100%) rename codes/{javaweb => javaweb-example}/javaweb-mq/javaweb-mq-kafka-spring-boot/src/test/java/io/github/dunwu/javaweb/KafkaProducerTest.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-mq/javaweb-mq-kafka/pom.xml (100%) rename codes/{javaweb => javaweb-example}/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerAOC.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManual.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManualPartition.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerDemo.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerInTransaction.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/StreamDemo.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-mq/javaweb-mq-kafka/src/main/resources/logback.xml (100%) rename codes/{javaweb => javaweb-example}/javaweb-mq/javaweb-mq-rocketmq/pom.xml (100%) rename codes/{javaweb => javaweb-example}/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/AsyncProducer.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BatchProducer.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastConsumer.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastProducer.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OnewayProducer.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedConsumer.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedProducer.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/RocketConfig.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageConsumer.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageProducer.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/SyncProducer.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-mq/javaweb-mq-rocketmq/src/main/resources/logback.xml (100%) rename codes/{javaweb => javaweb-example}/javaweb-mq/pom.xml (100%) rename codes/{javaweb => javaweb-example}/javaweb-zookeeper/pom.xml (100%) rename codes/{javaweb => javaweb-example}/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/Callback.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/DLockTemplate.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/DistributedLock.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/TimeoutHandler.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZkDLockTemplate.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZkReentrantLockCleanerTask.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZookeeperReentrantDistributedLock.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperConnection.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperCreate.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperDelete.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperExists.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperGetChildren.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperGetData.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperSetData.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/package-info.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/sequence/DistributedSequence.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/sequence/ZkDistributedSequence.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-zookeeper/src/test/java/io/github/dunwu/javaweb/zk/dlock/ZkReentrantLockTemplateTest.java (100%) rename codes/{javaweb => javaweb-example}/javaweb-zookeeper/src/test/resources/logback.xml (100%) create mode 100644 codes/javaweb-example/pom.xml rename docs/technology/data/{Mybatis.md => mybatis.md} (92%) rename docs/technology/{rpc/Dubbo.md => distributed/dubbo.md} (92%) rename docs/technology/security/{SpringSecurity.md => spring-security.md} (100%) rename codes/javaweb/pom.xml => pom.xml (57%) delete mode 100644 prettier.config.js diff --git a/README.md b/README.md index 8998168f..5d33deea 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,6 @@

    JavaWeb

    - > ☕ **JavaWeb** 是一个 Java Web 领域开发技术教程。 > > - 🔁 项目同步维护:[Github](https://github.com/dunwu/javaweb/) | [Gitee](https://gitee.com/turnon/javaweb/) @@ -77,7 +76,6 @@ - [分布式 ID 基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/distributed-id-theory.md) - [分布式事务基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/distributed-transaction-theory.md) - [分库分表基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/sharding-theory.md) -- [常见安全手段基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/security-theory.md) ## ⭐ 主流技术 @@ -113,17 +111,17 @@ ### RPC -- [Dubbo 应用指南](docs/technology/rpc/Dubbo.md) +- [Dubbo 应用指南](docs/technology/distributed/dubbo.md) - Spring Cloud ### 安全 -- [Shiro 应用指南](docs/technology/security/Shiro.md) -- [Spring Security 应用指南](docs/technology/security/SpringSecurity.md) +- [Shiro 应用指南](docs/technology/security/shiro.md) +- [Spring Security 应用指南](docs/technology/security/spring-security.md) ### 数据 -- [Mybatis 应用指南](docs/technology/data/Mybatis.md) +- [Mybatis 应用指南](docs/technology/data/mybatis.md) - [ShardingSphere 应用指南](docs/technology/data/ShardingSphere.md) ### 分布式 diff --git a/assets/xmind/dubbo.xmind b/assets/xmind/dubbo.xmind new file mode 100644 index 0000000000000000000000000000000000000000..3b5d1dc12db75aeac808d8af058c762bbcb8832c GIT binary patch literal 120845 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 NDIm-8J+Q1|002oKiTnTn literal 0 HcmV?d00001 diff --git a/assets/xmind/shiro.xmind b/assets/xmind/shiro.xmind new file mode 100644 index 0000000000000000000000000000000000000000..014d8b3e51757b7419290af23a5570c54a79c224 GIT binary patch literal 131886 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

    (O_dKkjlpBe1zN6D${?eFFH54PbA%ce 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 literal 0 HcmV?d00001 diff --git a/codes/javaweb-example/javaweb-load-balance/pom.xml b/codes/javaweb-example/javaweb-load-balance/pom.xml new file mode 100644 index 00000000..9520a7fd --- /dev/null +++ b/codes/javaweb-example/javaweb-load-balance/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + + io.github.dunwu + dunwu-parent + 0.5.0 + + + io.github.dunwu.javaweb + javaweb-load-balance + 1.0.0 + jar + ${project.artifactId} + + + UTF-8 + 1.8 + ${java.version} + ${java.version} + + + + + io.github.dunwu + dunwu-common-core + 0.5.1 + + + io.github.dunwu + dunwu-tool-core + 0.5.1 + + + junit + junit + test + + + diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/ConsistentHashLoadBalancer.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/ConsistentHashLoadBalancer.java new file mode 100644 index 00000000..6f174383 --- /dev/null +++ b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/ConsistentHashLoadBalancer.java @@ -0,0 +1,75 @@ +package io.github.dunwu.javaweb; + +import io.github.dunwu.javaweb.support.HashStrategy; +import io.github.dunwu.javaweb.support.MurmurHashStrategy; +import lombok.Data; + +import java.util.*; + +public class ConsistentHashLoadBalancer { + + private HashStrategy hashStrategy = new MurmurHashStrategy(); + + private final static int VIRTUAL_NODE_SIZE = 100; + + private final static String VIRTUAL_NODE_SUFFIX = "&&"; + + private List nodeList = Collections.emptyList(); + + public void buildInList(final Collection collection) { + this.nodeList = new ArrayList<>(collection); + // collection.forEach(item -> { + // keyWeightMap.put(item, DEFAULT_WEIGHT); + // }); + } + + public V select(Invocation invocation) { + int hashCode = hashStrategy.hashCode(invocation.getHashKey()); + TreeMap hashRing = buildConsistentHashRing(); + return locate(hashRing, hashCode); + } + + private V locate(TreeMap ring, int hashCode) { + // 向右找到第一个 key + Map.Entry entry = ring.ceilingEntry(hashCode); + if (entry == null) { + // 想象成一个环,超过尾部则取第一个 key + entry = ring.firstEntry(); + } + return entry.getValue(); + } + + private TreeMap buildConsistentHashRing() { + TreeMap hashRing = new TreeMap<>(); + for (V node : nodeList) { + for (int i = 0; i < VIRTUAL_NODE_SIZE; i++) { + // 新增虚拟节点的方式如果有影响,也可以抽象出一个由物理节点扩展虚拟节点的类 + hashRing.put(hashStrategy.hashCode(node + VIRTUAL_NODE_SUFFIX + i), node); + } + } + return hashRing; + } + + @Data + public static class Server { + + private String url; + + public Server(String url) { + this.url = url; + } + + } + + @Data + public static class Invocation { + + private String hashKey; + + public Invocation(String hashKey) { + this.hashKey = hashKey; + } + + } + +} diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/ConsistentHashRouter.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/ConsistentHashRouter.java new file mode 100644 index 00000000..5eeccb60 --- /dev/null +++ b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/ConsistentHashRouter.java @@ -0,0 +1,172 @@ +// /* +// * Licensed to the Apache Software Foundation (ASF) under one or more +// * contributor license agreements. See the NOTICE file distributed with +// * this work for additional information regarding copyright ownership. +// * The ASF licenses this file to You under the Apache License, Version 2.0 +// * (the "License"); you may not use this file except in compliance with +// * the License. You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ +// package io.github.dunwu.javaweb; +// +// import java.security.MessageDigest; +// import java.security.NoSuchAlgorithmException; +// import java.util.Collection; +// import java.util.Iterator; +// import java.util.SortedMap; +// import java.util.TreeMap; +// +// /** +// * @param +// * @author linjunjie1103@gmail.com +// *

    -## 配置 +## Mybatis 配置 > 配置的详细内容请参考:「 [Mybatis 官方文档之配置](http://www.mybatis.org/mybatis-3/zh/configuration.html) 」 。 @@ -331,26 +331,26 @@ XML 映射文件只有几个顶级元素: - `cache` – 对给定命名空间的缓存配置。 - `cache-ref` – 对其他命名空间缓存配置的引用。 - `resultMap` – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。 -- \~\~`parameterMap` – 已被废弃!老式风格的参数映射。更好的办法是使用内联参数,此元素可能在将来被移除。文档中不会介绍此元素。\~\~ +- ~~`parameterMap` – 已被废弃!老式风格的参数映射。更好的办法是使用内联参数,此元素可能在将来被移除。文档中不会介绍此元素。~~ - `sql` – 可被其他语句引用的可重用语句块。 - `insert` – 映射插入语句 - `update` – 映射更新语句 - `delete` – 映射删除语句 - `select` – 映射查询语句 -## Mybatis 扩展 - -- [mybatis-plus](https://github.com/baomidou/mybatis-plus) - CRUD、代码生成器、分页器等多功能 -- [Mapper](https://github.com/abel533/Mapper) - CRUD -- [Mybatis-PageHelper](https://github.com/pagehelper/Mybatis-PageHelper) - Mybatis 通用分页插件 - -## 资料 +## 参考资料 - **官方** - -| [Github](https://github.com/mybatis/mybatis-3) | [官网](http://www.mybatis.org/mybatis-3/) | [MyBatis Generator](https://github.com/mybatis/generator) | [Spring 集成](https://github.com/mybatis/spring) | [Spring Boot 集成](https://github.com/mybatis/spring-boot-starter) | - + - [Mybatis Github](https://github.com/mybatis/mybatis-3) + - [Mybatis 官网](http://www.mybatis.org/mybatis-3/) + - [MyBatis Generator](https://github.com/mybatis/generator) + - [Spring 集成](https://github.com/mybatis/spring) + - [Spring Boot 集成](https://github.com/mybatis/spring-boot-starter) +- **扩展插件** + - [mybatis-plus](https://github.com/baomidou/mybatis-plus) - CRUD 扩展插件、代码生成器、分页器等多功能 + - [Mapper](https://github.com/abel533/Mapper) - CRUD 扩展插件 + - [Mybatis-PageHelper](https://github.com/pagehelper/Mybatis-PageHelper) - Mybatis 通用分页插件 - **文章** - [深入理解 mybatis 原理](https://blog.csdn.net/luanlouis/article/details/40422941) - - [mybatis 源码中文注释](https://github.com/tuguangquan/mybatis) - mybatis 源码解读 + - [mybatis 源码中文注释](https://github.com/tuguangquan/mybatis) - [MyBatis Generator 详解](https://blog.csdn.net/isea533/article/details/42102297) diff --git a/docs/technology/rpc/Dubbo.md b/docs/technology/distributed/dubbo.md similarity index 92% rename from docs/technology/rpc/Dubbo.md rename to docs/technology/distributed/dubbo.md index 987ca222..67bd9a7d 100644 --- a/docs/technology/rpc/Dubbo.md +++ b/docs/technology/distributed/dubbo.md @@ -1,39 +1,39 @@ # Dubbo -> Dubbo 是一个基于 Java 开发的高性能 RPC 框架。 +> [Apache Dubbo](https://dubbo.apache.org/zh-cn/) 是一款高性能、轻量级的开源 Java RPC 框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。 -- [概述](#概述) -- [QuickStart](#quickstart) -- [Dubbo 配置](#dubbo-配置) - - [配置方式](#配置方式) - - [配置项](#配置项) -- [Dubbo 支持的协议](#dubbo-支持的协议) -- [服务治理](#服务治理) - - [集群容错](#集群容错) - - [负载均衡](#负载均衡) - - [路由规则](#路由规则) - - [服务降级](#服务降级) - - [访问控制](#访问控制) - - [动态配置](#动态配置) -- [Dubbo 架构](#dubbo-架构) - - [整体设计](#整体设计) -- [资料](#资料) +- [一、Dubbo 简介](#一dubbo-简介) +- [二、QuickStart](#二quickstart) +- [三、Dubbo 配置](#三dubbo-配置) + - [配置方式](#配置方式) + - [配置项](#配置项) +- [四、Dubbo 支持的协议](#四dubbo-支持的协议) +- [五、服务治理](#五服务治理) + - [集群容错](#集群容错) + - [负载均衡](#负载均衡) + - [路由规则](#路由规则) + - [服务降级](#服务降级) + - [访问控制](#访问控制) + - [动态配置](#动态配置) +- [六、Dubbo 架构](#六dubbo-架构) + - [整体设计](#整体设计) +- [参考资料](#参考资料) -## 概述 +## 一、Dubbo 简介 -Dubbo 是一个基于 Java 开发的高性能 RPC 框架。 +[Apache Dubbo](https://dubbo.apache.org/zh-cn/) 是一款高性能、轻量级的开源 Java RPC 框架。 -Dubbo 的三个关键功能: +Dubbo 提供了三大核心能力: -1. 基于接口的远程调用; -2. 容错机制以及负载均衡; -3. 自动服务注册以及自动服务发现。 +- 面向接口的远程方法调用 +- 智能容错和负载均衡 +- 服务自动注册和发现 -## QuickStart +## 二、QuickStart (1)添加 maven 依赖 @@ -135,7 +135,7 @@ public class Consumer { } ``` -## Dubbo 配置 +## 三、Dubbo 配置 dubbo 所有配置最终都将转换为 URL 表示,并由服务提供方生成,经注册中心传递给消费方,各属性对应 URL 的参数,参见配置项一览表中的 "对应 URL 参数" 列。 @@ -175,12 +175,12 @@ Dubbo 支持多种配置方式: - - - - - + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd"> + + + + + ``` @@ -239,28 +239,7 @@ dubbo.registry.address=10.20.153.10:9090 -## Dubbo 支持的协议 - -Dubbo 支持以下通信协议: - -- dubbo -- rmi -- hessian -- http -- webservice -- thrift -- memcached -- redis - -不同协议适合不同的服务场景,可以根据实际应用场景来选择合适的协议。 - -dubbo 协议是 dubbo 默认的协议。dubbo 协议采用单一长连接和 NIO 异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。反之,dubbo 缺省协议不适合传送大数据量的服务,比如传文件,传视频等,除非请求量很低。 - -选用哪个协议,可以通过 `` 标签配置。 - -> 更多详情请参考:[Dubbo 官方协议参考手册](https://dubbo.gitbooks.io/dubbo-user-book/references/protocol/introduction.html) - -## 服务治理 +## 四、服务治理 - 当服务越来越多时,服务 URL 配置管理变得非常困难,F5 硬件负载均衡器的单点压力也越来越大。 - 当进一步发展,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。 @@ -486,7 +465,7 @@ override://10.20.153.10/com.foo.BarService?category=configurators&dynamic=false& override://0.0.0.0/com.foo.BarService?category=configurators&dynamic=false&application=foo&mock=force:return+null ``` -## Dubbo 架构 +## 五、Dubbo 架构
    @@ -546,6 +525,9 @@ override://0.0.0.0/com.foo.BarService?category=configurators&dynamic=false&appli - 而 Remoting 实现是 Dubbo 协议的实现,如果你选择 RMI 协议,整个 Remoting 都不会用上,Remoting 内部再划为 Transport 传输层和 Exchange 信息交换层,Transport 层只负责单向消息传输,是对 Mina, Netty, Grizzly 的抽象,它也可以扩展 UDP 传输,而 Exchange 层是在传输层之上封装了 Request-Response 语义。 - Registry 和 Monitor 实际上不算一层,而是一个独立的节点,只是为了全局概览,用层的方式画在一起。 -## 资料 +## 参考资料 -[Github](https://github.com/apache/incubator-dubbo) | [用户手册](https://dubbo.gitbooks.io/dubbo-user-book/content/) | [开发手册](https://dubbo.gitbooks.io/dubbo-dev-book/content/) | [管理员手册](https://dubbo.gitbooks.io/dubbo-admin-book/content/) +- **官方** + - [Dubbo Github](https://github.com/apache/dubbo) + - [Dubbo 官方文档](https://dubbo.apache.org/zh-cn/) + - [管理员手册](https://dubbo.gitbooks.io/dubbo-admin-book/content/) diff --git a/docs/technology/distributed/zookeeper.md b/docs/technology/distributed/zookeeper.md index c4b1598b..6a6da91b 100644 --- a/docs/technology/distributed/zookeeper.md +++ b/docs/technology/distributed/zookeeper.md @@ -4,7 +4,7 @@ > > ZooKeeper 可以用于发布/订阅、负载均衡、命令服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能 。 -## 简介 +## ZooKeeper 简介 ### ZooKeeper 是什么 @@ -22,7 +22,46 @@ ZooKeeper 具有以下特性: - **高可用** - ZooKeeper 的高可用是基于副本机制实现的,此外 ZooKeeper 支持故障恢复,可见:[选举 Leader](#选举-Leader) - **单一视图** - 无论客户端连接的是哪个 Zookeeper 服务器,其看到的服务端数据模型都是一致的。 -## 架构 +## ZooKeeper 原理 + +### 数据模型 + +**ZooKeeper 可以视为一个高可用的文件系统**。 + +ZooKeeper 维护着一个树形层次结构,树中的节点被称为 **`znode`**。其中根节点为 `/`,每个节点上都会保存自己的数据和节点信息。znode 可以用于存储数据,并且有一个与之相关联的 ACL(详情可见 [ACL](#ACL))。ZooKeeper 的设计目标是实现协调服务,而不是真的作为一个文件存储,因此 znode 存储数据的大小被限制在 1MB 以内。 + +
    + +
    + +ZooKeeper 这种数据结构有如下这些特点: + +**ZooKeeper 的数据访问具有原子性**。客户端在读取一个 znode 数据时,要么成功读取所有数据,要么读取失败。**znode 节点路径必须是绝对路径**。 + +znode 有两种类型: + +- **临时的( `PERSISTENT` )** - 户端会话结束时,ZooKeeper 就会删除临时的 znode。 +- **持久的( `EPHEMERAL` )** - 除非客户端主动执行删除操作,否则 ZooKeeper 不会删除持久的 znode。 + +znode 上有一个**顺序标志( `SEQUENTIAL` )**。如果在创建 znode 时,设置了**顺序标志( `SEQUENTIAL` )**,那么 ZooKeeper 会使用计数器为 znode 添加一个单调递增的数值,即 zxid。ZooKeeper 正是利用 zxid 实现了严格的顺序访问控制能力。 + +每个 znode 节点在存储数据的同时,都会维护一个叫做 `Stat` 的数据结构,里面存储了关于该节点的全部状态信息。如下: + +| **状态属性** | **说明** | +| -------------- | ------------------------------------------------------------ | +| czxid | 数据节点创建时的事务 ID | +| ctime | 数据节点创建时的时间 | +| mzxid | 数据节点最后一次更新时的事务 ID | +| mtime | 数据节点最后一次更新时的时间 | +| pzxid | 数据节点的子节点最后一次被修改时的事务 ID | +| cversion | 子节点的更改次数 | +| version | 节点数据的更改次数 | +| aversion | 节点的 ACL 的更改次数 | +| ephemeralOwner | 如果节点是临时节点,则表示创建该节点的会话的 SessionID;如果节点是持久节点,则该属性值为 0 | +| dataLength | 数据内容的长度 | +| numChildren | 数据节点当前的子节点个数 | + +### 集群角色 Zookeeper 集群是一个基于主从复制的高可用集群,每个服务器承担如下三种角色中的一种 @@ -71,45 +110,6 @@ Leader/Follower/Observer 都可直接处理读请求,从本地内存中读取 - Follower/Observer 均可接受写请求,但不能直接处理,而需要将写请求转发给 Leader 处理 - 除了多了一步请求转发,其它流程与直接写 Leader 无任何区别 -## 原理 - -### 数据模型 - -**ZooKeeper 可以视为一个高可用的文件系统**。 - -ZooKeeper 维护着一个树形层次结构,树中的节点被称为 **`znode`**。其中根节点为 `/`,每个节点上都会保存自己的数据和节点信息。znode 可以用于存储数据,并且有一个与之相关联的 ACL(详情可见 [ACL](#ACL))。ZooKeeper 的设计目标是实现协调服务,而不是真的作为一个文件存储,因此 znode 存储数据的大小被限制在 1MB 以内。 - -
    - -
    - -ZooKeeper 这种数据结构有如下这些特点: - -**ZooKeeper 的数据访问具有原子性**。客户端在读取一个 znode 数据时,要么成功读取所有数据,要么读取失败。**znode 节点路径必须是绝对路径**。 - -znode 有两种类型: - -- **临时的( `PERSISTENT` )** - 户端会话结束时,ZooKeeper 就会删除临时的 znode。 -- **持久的( `EPHEMERAL` )** - 除非客户端主动执行删除操作,否则 ZooKeeper 不会删除持久的 znode。 - -znode 上有一个**顺序标志( `SEQUENTIAL` )**。如果在创建 znode 时,设置了**顺序标志( `SEQUENTIAL` )**,那么 ZooKeeper 会使用计数器为 znode 添加一个单调递增的数值,即 zxid。ZooKeeper 正是利用 zxid 实现了严格的顺序访问控制能力。 - -每个 znode 节点在存储数据的同时,都会维护一个叫做 `Stat` 的数据结构,里面存储了关于该节点的全部状态信息。如下: - -| **状态属性** | **说明** | -| -------------- | ------------------------------------------------------------------------------------------ | -| czxid | 数据节点创建时的事务 ID | -| ctime | 数据节点创建时的时间 | -| mzxid | 数据节点最后一次更新时的事务 ID | -| mtime | 数据节点最后一次更新时的时间 | -| pzxid | 数据节点的子节点最后一次被修改时的事务 ID | -| cversion | 子节点的更改次数 | -| version | 节点数据的更改次数 | -| aversion | 节点的 ACL 的更改次数 | -| ephemeralOwner | 如果节点是临时节点,则表示创建该节点的会话的 SessionID;如果节点是持久节点,则该属性值为 0 | -| dataLength | 数据内容的长度 | -| numChildren | 数据节点当前的子节点个数 | - ### 事务 对于来自客户端的每个更新请求,ZooKeeper 具备严格的顺序访问控制能力。 diff --git a/docs/technology/security/Shiro.md b/docs/technology/security/Shiro.md index 2b68e489..7cbb6e40 100644 --- a/docs/technology/security/Shiro.md +++ b/docs/technology/security/Shiro.md @@ -2,31 +2,26 @@ title: Shiro date: 2018-06-13 categories: -- security + - security tags: -- java -- security -- shiro + - java + - security + - shiro --- -# Shiro +# Shiro 应用指南 > Shiro 是一个安全框架,具有认证、授权、加密、会话管理功能。 -- [概述](#概述) - - [Shiro 功能](#shiro-功能) - - [Shiro 架构](#shiro-架构) -- [认证](#认证) -- [授权](#授权) -- [资料](#资料) +- [Shiro 核心组件](#shiro-核心组件) +- [Shiro 架构](#shiro-架构) +- [参考资料](#参考资料) -## 概述 - -### Shiro 功能 +## Shiro 核心组件

    @@ -45,35 +40,29 @@ tags: 记住一点,Shiro 不会去维护用户、维护权限;这些需要我们自己去提供;然后通过相应的接口注入给 Shiro 即可。 -### Shiro 架构 +## Shiro 架构

    -- **Subject** - 主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等;即一个抽象概念;所有 Subject 都绑定到 SecurityManager,与 Subject 的所有交互都会委托给 SecurityManager;可以把 Subject 认为是一个门面;SecurityManager 才是实际的执行者; -- **SecurityManager** - 安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject;可以看出它是 Shiro 的核心,它负责与后边介绍的其他组件进行交互,如果学习过 SpringMVC,你可以把它看成 DispatcherServlet 前端控制器; - **Realm** - 域,Shiro 从从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色/权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。

    -- **Subject** - 主体,可以看到主体可以是任何可以与应用交互的“用户”; -- **SecurityManager** - 相当于 SpringMVC 中的 DispatcherServlet 或者 Struts2 中的 FilterDispatcher;是 Shiro 的心脏;所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理。 -- **Authenticator** - 认证器,负责主体认证的,这是一个扩展点,如果用户觉得 Shiro 默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了; -- **Authrizer** - 授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能; -- **Realm** - 可以有 1 个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是 JDBC 实现,也可以是 LDAP 实现,或者内存实现等等;由用户提供;注意 - Shiro 不知道你的用户/权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的 Realm; -- **SessionManager** - 如果写过 Servlet 就应该知道 Session 的概念,Session 呢需要有人去管理它的生命周期,这个组件就是 SessionManager;而 Shiro 并不仅仅可以用在 Web 环境,也可以用在如普通的 JavaSE 环境、EJB 等环境;所有呢,Shiro 就抽象了一个自己的 Session 来管理主体与应用之间交互的数据;这样的话,比如我们在 Web 环境用,刚开始是一台 Web 服务器;接着又上了台 EJB 服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到 Memcached 服务器); -- **SessionDAO** - DAO 大家都用过,数据访问对象,用于会话的 CRUD,比如我们想把 Session 保存到数据库,那么可以实现自己的 SessionDAO,通过如 JDBC 写到数据库;比如想把 Session 放到 Memcached 中,可以实现自己的 Memcached SessionDAO;另外 SessionDAO 中可以使用 Cache 进行缓存,以提高性能; -- **CacheManager** - 缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能 -- **Cryptography** - 密码模块,Shiro 提高了一些常见的加密组件用于如密码加密/解密的。 - -## 认证 - -## 授权 - -## 资料 +- **Subject** - 主题,负责 Shiro 的入口。 +- **Realm** - Realm 可以视为数据源,所有安全相关的数据都从 Realm 获取。可以有一个或多个 Realm,可以是 JDBC 实现,也可以是 LDAP 实现,或者内存实现等等,由用户提供。注意:Shiro 不知道你的用户/权限存储在哪及以何种格式存储,所以用户需要在应用中都现自己的 Realm。 +- **SecurityManager** - 它是 Shiro 的核心,所有与安全有关的操作(认证、授权、及会话、缓存的管理)都与 `SecurityManager` 交互,且它管理着所有 `Subject`。 + - **Authenticator** - 认证器,负责认证。如果用户需要定制认证策略,可以实现此接口。 + - **Authorizer** - 授权器,负责权限控制。用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能; + - **SessionManager** - 会话管理器。Shiro 抽象了一个自己的 Session 来管理主体与应用之间交互的数据。 + - **SessionDAO** - 会话 DAO 用于实现用户定制化的会话 DAO,需要用户自己实现。 + - **CacheManager** - 缓存控制器。用于管理如用户、角色、权限等信息的缓存。 + - **Cryptography** - 密码器。用于对数据加密、解密。 + +## 参考资料 - [shiro 官方文档](http://shiro.apache.org/reference.html) - [跟我学 Shiro](http://jinnianshilongnian.iteye.com/category/305053) diff --git a/docs/technology/security/SpringSecurity.md b/docs/technology/security/spring-security.md similarity index 100% rename from docs/technology/security/SpringSecurity.md rename to docs/technology/security/spring-security.md diff --git a/codes/javaweb/pom.xml b/pom.xml similarity index 57% rename from codes/javaweb/pom.xml rename to pom.xml index fd39b2b8..1839eef9 100644 --- a/codes/javaweb/pom.xml +++ b/pom.xml @@ -1,15 +1,16 @@ - javaweb + 4.0.0 io.github.dunwu.javaweb - 4.0.0 + javaweb + 1.0.0 + pom + JAVAWEB + JavaWeb 示例源码 + - javaweb-mq - javaweb-rpc + codes/javaweb-example - pom - - 1.0.0 diff --git a/prettier.config.js b/prettier.config.js deleted file mode 100644 index eb6bb1f5..00000000 --- a/prettier.config.js +++ /dev/null @@ -1,7 +0,0 @@ -/** - * @see https://prettier.io/docs/en/options.html - * @see https://prettier.io/docs/en/configuration.html - */ -module.exports = { - tabWidth: 2, semi: false, singleQuote: true -} From ce0c6d0c2b9c57979f34467ff227a2f5e479d483 Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Thu, 23 Jan 2020 23:25:40 +0800 Subject: [PATCH 52/75] =?UTF-8?q?=E8=B4=9F=E8=BD=BD=E5=9D=87=E8=A1=A1?= =?UTF-8?q?=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...er.java => ConsistentHashLoadBalance.java} | 63 +++---- .../dunwu/javaweb/ConsistentHashRouter.java | 172 ------------------ .../io/github/dunwu/javaweb/LoadBalance.java | 6 +- .../java/io/github/dunwu/javaweb/Node.java | 77 ++++++++ .../dunwu/javaweb/RandomLoadBalance.java | 30 ++- .../dunwu/javaweb/RoundRobinLoadBalance.java | 36 ++-- .../dunwu/javaweb/WeightLoadBalance.java | 15 -- .../javaweb/support/CRCHashStrategy.java | 4 +- .../javaweb/support/FnvHashStrategy.java | 6 +- .../dunwu/javaweb/support/HashStrategy.java | 2 +- .../javaweb/support/JdkHashCodeStrategy.java | 4 +- .../javaweb/support/KetamaHashStrategy.java | 4 +- .../javaweb/support/MurmurHashStrategy.java | 4 +- .../dunwu/javaweb/LoadBalanceTests.java | 130 ++++++------- .../github/dunwu/javaweb/StatisticsUtil.java | 0 15 files changed, 211 insertions(+), 342 deletions(-) rename codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/{ConsistentHashLoadBalancer.java => ConsistentHashLoadBalance.java} (52%) delete mode 100644 codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/ConsistentHashRouter.java create mode 100644 codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/Node.java delete mode 100644 codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/WeightLoadBalance.java rename codes/javaweb-example/javaweb-load-balance/src/{main => test}/java/io/github/dunwu/javaweb/StatisticsUtil.java (100%) diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/ConsistentHashLoadBalancer.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/ConsistentHashLoadBalance.java similarity index 52% rename from codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/ConsistentHashLoadBalancer.java rename to codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/ConsistentHashLoadBalance.java index 6f174383..10adadc9 100644 --- a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/ConsistentHashLoadBalancer.java +++ b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/ConsistentHashLoadBalance.java @@ -2,44 +2,57 @@ import io.github.dunwu.javaweb.support.HashStrategy; import io.github.dunwu.javaweb.support.MurmurHashStrategy; -import lombok.Data; import java.util.*; +import java.util.function.Predicate; -public class ConsistentHashLoadBalancer { +public class ConsistentHashLoadBalance implements LoadBalance { private HashStrategy hashStrategy = new MurmurHashStrategy(); - private final static int VIRTUAL_NODE_SIZE = 100; + private final static int VIRTUAL_NODE_SIZE = 1000; private final static String VIRTUAL_NODE_SUFFIX = "&&"; private List nodeList = Collections.emptyList(); + private TreeMap hashRing; + + @Override public void buildInList(final Collection collection) { this.nodeList = new ArrayList<>(collection); - // collection.forEach(item -> { - // keyWeightMap.put(item, DEFAULT_WEIGHT); - // }); + this.hashRing = buildConsistentHashRing(this.nodeList); + } + + @Override + public void addNode(V node) { + this.nodeList.add(node); + this.hashRing = buildConsistentHashRing(this.nodeList); + } + + @Override + public void removeNode(V node) { + this.nodeList.removeIf(v -> v.equals(node)); + this.hashRing = buildConsistentHashRing(this.nodeList); } - public V select(Invocation invocation) { - int hashCode = hashStrategy.hashCode(invocation.getHashKey()); - TreeMap hashRing = buildConsistentHashRing(); - return locate(hashRing, hashCode); + @Override + public V next() { + return next(UUID.randomUUID().toString()); } - private V locate(TreeMap ring, int hashCode) { + public V next(String key) { + int hashCode = hashStrategy.hashCode(key); // 向右找到第一个 key - Map.Entry entry = ring.ceilingEntry(hashCode); + Map.Entry entry = hashRing.ceilingEntry(hashCode); if (entry == null) { // 想象成一个环,超过尾部则取第一个 key - entry = ring.firstEntry(); + entry = hashRing.firstEntry(); } return entry.getValue(); } - private TreeMap buildConsistentHashRing() { + private TreeMap buildConsistentHashRing(List nodeList) { TreeMap hashRing = new TreeMap<>(); for (V node : nodeList) { for (int i = 0; i < VIRTUAL_NODE_SIZE; i++) { @@ -50,26 +63,4 @@ private TreeMap buildConsistentHashRing() { return hashRing; } - @Data - public static class Server { - - private String url; - - public Server(String url) { - this.url = url; - } - - } - - @Data - public static class Invocation { - - private String hashKey; - - public Invocation(String hashKey) { - this.hashKey = hashKey; - } - - } - } diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/ConsistentHashRouter.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/ConsistentHashRouter.java deleted file mode 100644 index 5eeccb60..00000000 --- a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/ConsistentHashRouter.java +++ /dev/null @@ -1,172 +0,0 @@ -// /* -// * Licensed to the Apache Software Foundation (ASF) under one or more -// * contributor license agreements. See the NOTICE file distributed with -// * this work for additional information regarding copyright ownership. -// * The ASF licenses this file to You under the Apache License, Version 2.0 -// * (the "License"); you may not use this file except in compliance with -// * the License. You may obtain a copy of the License at -// * -// * http://www.apache.org/licenses/LICENSE-2.0 -// * -// * Unless required by applicable law or agreed to in writing, software -// * distributed under the License is distributed on an "AS IS" BASIS, -// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// * See the License for the specific language governing permissions and -// * limitations under the License. -// */ -// package io.github.dunwu.javaweb; -// -// import java.security.MessageDigest; -// import java.security.NoSuchAlgorithmException; -// import java.util.Collection; -// import java.util.Iterator; -// import java.util.SortedMap; -// import java.util.TreeMap; -// -// /** -// * @param -// * @author linjunjie1103@gmail.com -// *

    -// * To hash Node objects to a hash ring with a certain amount of virtual node. Method routeNode will return a Node -// * instance which the object key should be allocated to according to consistent hash algorithm -// */ -// public class ConsistentHashRouter { -// -// private final static int VIRTUAL_NODE_SIZE = 10; -// -// private final SortedMap> hashRing = new TreeMap<>(); -// -// private final HashStrategy hashStrategy; -// -// public ConsistentHashRouter(Collection physicalNodes, int count) { -// this(physicalNodes, count, new MD5Hash()); -// } -// -// /** -// * @param nodes collections of physical nodes -// * @param count amounts of virtual nodes -// * @param hashStrategy hash Function to hash Node instances -// */ -// public ConsistentHashRouter(Collection nodes, int count, HashStrategy hashStrategy) { -// if (hashStrategy == null) { -// throw new NullPointerException("Hash Function is null"); -// } -// this.hashStrategy = hashStrategy; -// if (nodes != null) { -// for (T node : nodes) { -// addNode(node, count); -// } -// } -// } -// -// /** -// * add physic node to the hash ring with some virtual nodes -// * -// * @param node physical node needs added to hash ring -// * @param count the number of virtual node of the physical node. Value should be greater than or equals to 0 -// */ -// public void addNode(T node, int count) { -// if (count < 0) throw new IllegalArgumentException("illegal virtual node counts :" + count); -// int existingReplicas = getExistingReplicas(node); -// for (int i = 0; i < count; i++) { -// VirtualNode nodes = new VirtualNode<>(node, i + existingReplicas); -// hashRing.put(hashStrategy.hash(nodes.getKey()), nodes); -// } -// } -// -// /** -// * remove the physical node from the hash ring -// * -// * @param node -// */ -// public void removeNode(T node) { -// Iterator it = hashRing.keySet().iterator(); -// while (it.hasNext()) { -// Long key = it.next(); -// VirtualNode virtualNode = hashRing.get(key); -// if (virtualNode.isVirtualNodeOf(node)) { -// it.remove(); -// } -// } -// } -// -// /** -// * with a specified key, route the nearest Node instance in the current hash ring -// * -// * @param objectKey the object key to find a nearest Node -// * @return -// */ -// public T next(String objectKey) { -// if (hashRing.isEmpty()) return null; -// long hashcode = hashStrategy.hash(objectKey); -// SortedMap> tailMap = hashRing.tailMap(hashcode); -// Long nodeHashVal = !tailMap.isEmpty() ? tailMap.firstKey() : hashRing.firstKey(); -// return hashRing.get(nodeHashVal).getPhysicalNode(); -// } -// -// public int getExistingReplicas(T pNode) { -// int replicas = 0; -// for (VirtualNode node : hashRing.values()) { -// if (node.isVirtualNodeOf(pNode)) { -// replicas++; -// } -// } -// return replicas; -// } -// -// //default hash function -// static class MD5Hash implements HashStrategy { -// -// MessageDigest instance; -// -// public MD5Hash() { -// try { -// instance = MessageDigest.getInstance("MD5"); -// } catch (NoSuchAlgorithmException e) { -// e.printStackTrace(); -// } -// } -// -// @Override -// public long hash(String key) { -// instance.reset(); -// instance.update(key.getBytes()); -// byte[] digest = instance.digest(); -// -// long h = 0; -// for (int i = 0; i < 4; i++) { -// h <<= 8; -// h |= ((int) digest[i]) & 0xFF; -// } -// return h; -// } -// -// } -// -// static class VirtualNode implements Node { -// -// final T physicalNode; -// -// final int replicaIndex; -// -// public VirtualNode(T physicalNode, int replicaIndex) { -// this.replicaIndex = replicaIndex; -// this.physicalNode = physicalNode; -// } -// -// @Override -// public String getKey() { -// return physicalNode.getKey() + "-" + replicaIndex; -// } -// -// public boolean isVirtualNodeOf(T pNode) { -// return physicalNode.getKey().equals(pNode.getKey()); -// } -// -// public T getPhysicalNode() { -// return physicalNode; -// } -// -// } -// -// } diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/LoadBalance.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/LoadBalance.java index 4f57f247..772b0fe2 100644 --- a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/LoadBalance.java +++ b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/LoadBalance.java @@ -8,10 +8,14 @@ * @author Zhang Peng * @since 2020-01-21 */ -public interface LoadBalance { +public interface LoadBalance { void buildInList(Collection collection); + void addNode(V node); + + void removeNode(V node); + V next(); } diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/Node.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/Node.java new file mode 100644 index 00000000..41a1f630 --- /dev/null +++ b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/Node.java @@ -0,0 +1,77 @@ +package io.github.dunwu.javaweb; + +import java.util.Objects; + +/** + * 负载均衡节点 + * + * @author Zhang Peng + * @since 2020-01-23 + */ +public class Node implements Comparable { + + public static final Integer DEFAULT_WEIGHT = 1; + + protected String url; + + protected Integer weight; + + public Node(String url) { + this.url = url; + this.weight = DEFAULT_WEIGHT; + } + + public Node(String url, Integer weight) { + this.url = url; + this.weight = weight; + } + + @Override + public int compareTo(Node o) { + return url.compareTo(o.url); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Node)) { + return false; + } + Node node = (Node) o; + return url.equals(node.url); + } + + @Override + public int hashCode() { + return Objects.hash(url); + } + + @Override + public String toString() { + return "Node{" + + "url='" + url + '\'' + + ", weight=" + weight + + '}'; + } + + public String getUrl() { + return url; + } + + public Node setUrl(String url) { + this.url = url; + return this; + } + + public Integer getWeight() { + return weight; + } + + public Node setWeight(Integer weight) { + this.weight = weight; + return this; + } + +} diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/RandomLoadBalance.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/RandomLoadBalance.java index 579b76b7..35a437e8 100644 --- a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/RandomLoadBalance.java +++ b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/RandomLoadBalance.java @@ -1,7 +1,6 @@ package io.github.dunwu.javaweb; import io.github.dunwu.tool.collection.CollectionUtil; -import io.github.dunwu.tool.map.MapUtil; import java.util.*; import java.util.concurrent.ThreadLocalRandom; @@ -13,9 +12,7 @@ * @see Zhang Peng * @since 2020-01-20 */ -public class RandomLoadBalance implements WeightLoadBalance { - - public static final Integer DEFAULT_WEIGHT = 1; +public class RandomLoadBalance implements LoadBalance { private boolean weightMode; @@ -23,8 +20,6 @@ public class RandomLoadBalance implements WeightLoadBalance { private List nodeList = Collections.emptyList(); - private final Map keyWeightMap = new HashMap<>(); - public RandomLoadBalance() { this.weightMode = false; } @@ -36,15 +31,16 @@ public RandomLoadBalance(boolean weightMode) { @Override public void buildInList(final Collection collection) { this.nodeList = new ArrayList<>(collection); - collection.forEach(item -> { - keyWeightMap.put(item, DEFAULT_WEIGHT); - }); } @Override - public void buildInMap(final Map map) { - this.nodeList = new ArrayList<>(map.keySet()); - this.keyWeightMap.putAll(map); + public void addNode(V node) { + this.nodeList.add(node); + } + + @Override + public void removeNode(V node) { + this.nodeList.remove(node); } @Override @@ -57,18 +53,18 @@ public V next() { } private V getNextInWeightMode() { - if (MapUtil.isEmpty(keyWeightMap)) { + if (CollectionUtil.isEmpty(nodeList)) { return null; } List list = new ArrayList<>(); - for (Map.Entry item : keyWeightMap.entrySet()) { - for (int i = 0; i < item.getValue(); i++) { - list.add(item.getKey()); + for (V node : nodeList) { + for (int i = 0; i < node.getWeight(); i++) { + list.add(node); } } - int totalWeight = keyWeightMap.values().stream().mapToInt(a -> a).sum(); + int totalWeight = nodeList.stream().mapToInt(Node::getWeight).sum(); int number = random.nextInt(totalWeight); return list.get(number); } diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/RoundRobinLoadBalance.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/RoundRobinLoadBalance.java index 741756cf..6bea9def 100644 --- a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/RoundRobinLoadBalance.java +++ b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/RoundRobinLoadBalance.java @@ -1,9 +1,11 @@ package io.github.dunwu.javaweb; import io.github.dunwu.tool.collection.CollectionUtil; -import io.github.dunwu.tool.map.MapUtil; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; import java.util.concurrent.atomic.AtomicInteger; /** @@ -12,7 +14,7 @@ * @author Zhang Peng * @since 2020-01-20 */ -public class RoundRobinLoadBalance implements WeightLoadBalance { +public class RoundRobinLoadBalance implements LoadBalance { private boolean weightMode; @@ -20,8 +22,6 @@ public class RoundRobinLoadBalance implements WeightLoadBalance { private List nodeList = Collections.emptyList(); - private Map nodeMap = new LinkedHashMap<>(); - public RoundRobinLoadBalance() { this.weightMode = false; } @@ -34,16 +34,16 @@ public RoundRobinLoadBalance(boolean weightMode) { public void buildInList(final Collection collection) { this.offset = new AtomicInteger(0); this.nodeList = new ArrayList<>(collection); - this.nodeMap = new LinkedHashMap<>(collection.size()); - collection.forEach(item -> { - nodeMap.putIfAbsent(item, 1); - }); } @Override - public void buildInMap(final Map map) { - this.nodeList = new ArrayList<>(map.keySet()); - this.nodeMap = new LinkedHashMap<>(map); + public void addNode(V node) { + this.nodeList.add(node); + } + + @Override + public void removeNode(V node) { + this.nodeList.remove(node); } @Override @@ -56,18 +56,18 @@ public V next() { } private V getNextInWeightMode() { - if (MapUtil.isEmpty(nodeMap)) { + if (CollectionUtil.isEmpty(nodeList)) { return null; } - int totalWeight = nodeMap.values().stream().mapToInt(a -> a).sum(); + int totalWeight = nodeList.stream().mapToInt(Node::getWeight).sum(); int number = offset.getAndIncrement() % totalWeight; - for (Map.Entry item : nodeMap.entrySet()) { - if (item.getValue() > number) { - return item.getKey(); + for (V node : nodeList) { + if (node.getWeight() > number) { + return node; } - number -= item.getValue(); + number -= node.getWeight(); } return null; } diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/WeightLoadBalance.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/WeightLoadBalance.java deleted file mode 100644 index d36cd3ad..00000000 --- a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/WeightLoadBalance.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.github.dunwu.javaweb; - -import java.util.Map; - -/** - * 加权负载均衡策略接口 - * - * @author Zhang Peng - * @since 2020-01-20 - */ -public interface WeightLoadBalance extends LoadBalance { - - void buildInMap(Map map); - -} diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/CRCHashStrategy.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/CRCHashStrategy.java index 5fbd64c6..4e4dc412 100644 --- a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/CRCHashStrategy.java +++ b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/CRCHashStrategy.java @@ -60,10 +60,10 @@ public static int getCRC16(String key) { } @Override - public int hashCode(String origin) { + public int hashCode(String key) { // optimization with modulo operator with power of 2 // equivalent to getCRC16(key) % 16384 - return getCRC16(origin) & (16384 - 1); + return getCRC16(key) & (16384 - 1); } } diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/FnvHashStrategy.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/FnvHashStrategy.java index 7b20a23d..541332ba 100644 --- a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/FnvHashStrategy.java +++ b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/FnvHashStrategy.java @@ -7,11 +7,11 @@ public class FnvHashStrategy implements HashStrategy { private static final int FNV_32_PRIME = 16777619; @Override - public int hashCode(String origin) { + public int hashCode(String key) { final int p = FNV_32_PRIME; int hash = (int) FNV_32_INIT; - for (int i = 0; i < origin.length(); i++) - hash = (hash ^ origin.charAt(i)) * p; + for (int i = 0; i < key.length(); i++) + hash = (hash ^ key.charAt(i)) * p; hash += hash << 13; hash ^= hash >> 7; hash += hash << 3; diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/HashStrategy.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/HashStrategy.java index 898f354e..689f0d74 100644 --- a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/HashStrategy.java +++ b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/HashStrategy.java @@ -2,6 +2,6 @@ public interface HashStrategy { - int hashCode(String origin); + int hashCode(String key); } diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/JdkHashCodeStrategy.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/JdkHashCodeStrategy.java index fa6f0a45..b08328cd 100644 --- a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/JdkHashCodeStrategy.java +++ b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/JdkHashCodeStrategy.java @@ -3,8 +3,8 @@ public class JdkHashCodeStrategy implements HashStrategy { @Override - public int hashCode(String origin) { - return origin.hashCode(); + public int hashCode(String key) { + return key.hashCode(); } } diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/KetamaHashStrategy.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/KetamaHashStrategy.java index 4c551e0f..c8dd7079 100644 --- a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/KetamaHashStrategy.java +++ b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/KetamaHashStrategy.java @@ -16,8 +16,8 @@ public class KetamaHashStrategy implements HashStrategy { } @Override - public int hashCode(String origin) { - byte[] bKey = computeMd5(origin); + public int hashCode(String key) { + byte[] bKey = computeMd5(key); long rv = ((long) (bKey[3] & 0xFF) << 24) | ((long) (bKey[2] & 0xFF) << 16) | ((long) (bKey[1] & 0xFF) << 8) diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/MurmurHashStrategy.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/MurmurHashStrategy.java index 44376f8e..35d4a968 100644 --- a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/MurmurHashStrategy.java +++ b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/MurmurHashStrategy.java @@ -6,9 +6,9 @@ public class MurmurHashStrategy implements HashStrategy { @Override - public int hashCode(String origin) { + public int hashCode(String key) { - ByteBuffer buf = ByteBuffer.wrap(origin.getBytes()); + ByteBuffer buf = ByteBuffer.wrap(key.getBytes()); int seed = 0x1234ABCD; ByteOrder byteOrder = buf.order(); diff --git a/codes/javaweb-example/javaweb-load-balance/src/test/java/io/github/dunwu/javaweb/LoadBalanceTests.java b/codes/javaweb-example/javaweb-load-balance/src/test/java/io/github/dunwu/javaweb/LoadBalanceTests.java index 9d9aa35a..118a57fc 100644 --- a/codes/javaweb-example/javaweb-load-balance/src/test/java/io/github/dunwu/javaweb/LoadBalanceTests.java +++ b/codes/javaweb-example/javaweb-load-balance/src/test/java/io/github/dunwu/javaweb/LoadBalanceTests.java @@ -12,91 +12,78 @@ */ public class LoadBalanceTests { - private Map initSample() { - Map map = new LinkedHashMap<>(); - map.put("192.168.1.1", 1); - map.put("192.168.1.2", 1); - map.put("192.168.1.3", 2); - map.put("192.168.1.4", 3); - map.put("192.168.1.5", 3); - return map; + private List initSample() { + Random random = new Random(); + List nodes = new ArrayList<>(); + for (int i = 1; i <= 100; i++) { + Node node = new Node("192.168.0." + i, random.nextInt(10)); + nodes.add(node); + } + return nodes; } /** * 统计负载均衡命中次数,样本数为 100000 次访问 - * - * @param algorithm */ - private void countLoadBalance(LoadBalance algorithm) { - Map countMap = new TreeMap<>(); + private Map countLoadBalance(LoadBalance algorithm) { + Map staticMap = new TreeMap<>(); for (int i = 0; i < 100000; i++) { - String node = algorithm.next(); - if (countMap.containsKey(node)) { - Integer value = countMap.get(node); - countMap.put(node, ++value); + Node node = algorithm.next(); + if (staticMap.containsKey(node)) { + Long value = staticMap.get(node); + staticMap.put(node, ++value); } else { - countMap.put(node, 1); + staticMap.put(node, 1L); } } - countMap.forEach((key, value) -> { + staticMap.forEach((key, value) -> { System.out.printf("key = %s, value = %s\n", key, value); }); + + System.out.println("方差:" + StatisticsUtil.variance(staticMap.values().toArray(new Long[0]))); + System.out.println("标准差:" + StatisticsUtil.standardDeviation(staticMap.values().toArray(new Long[] {}))); + return staticMap; } @Test public void randomLoadBalanceTest() { - RandomLoadBalance algorithm = new RandomLoadBalance<>(); - algorithm.buildInList(initSample().keySet()); - countLoadBalance(algorithm); + LoadBalance loadBalance = new RandomLoadBalance<>(); + loadBalance.buildInList(initSample()); + System.out.println("======================= 随机负载均衡 ======================="); + countLoadBalance(loadBalance); + } - RandomLoadBalance algorithm2 = new RandomLoadBalance<>(true); - algorithm2.buildInMap(initSample()); - countLoadBalance(algorithm2); + @Test + public void randomWeightLoadBalanceTest() { + LoadBalance loadBalance = new RandomLoadBalance<>(true); + loadBalance.buildInList(initSample()); + System.out.println("======================= 加权随机负载均衡 ======================="); + countLoadBalance(loadBalance); } @Test public void roundRobinLoadBalanceTest() { - Map map = initSample(); - - LoadBalance algorithm = new RoundRobinLoadBalance<>(); - algorithm.buildInList(map.keySet()); - countLoadBalance(algorithm); - - WeightLoadBalance algorithm2 = new RoundRobinLoadBalance<>(true); - algorithm2.buildInMap(map); - countLoadBalance(algorithm2); + LoadBalance loadBalance = new RoundRobinLoadBalance<>(); + loadBalance.buildInList(initSample()); + System.out.println("======================= 轮询负载均衡 ======================="); + countLoadBalance(loadBalance); } @Test - public void testDistribution() { - - List nodes = new ArrayList<>(100); - for (int i = 1; i <= 100; i++) { - String item = "192.168.0." + "i"; - nodes.add(item); - } + public void roundRobinWeightLoadBalanceTest() { + LoadBalance loadBalance = new RoundRobinLoadBalance<>(true); + loadBalance.buildInList(initSample()); + System.out.println("======================= 加权轮询负载均衡 ======================="); + countLoadBalance(loadBalance); + } - ConsistentHashLoadBalancer loadBalancer = new ConsistentHashLoadBalancer(); - // 构造 10000 随机请求 - List invocations = new ArrayList<>(); - for (int i = 0; i < 10000; i++) { - invocations.add(new ConsistentHashLoadBalancer.Invocation(UUID.randomUUID().toString())); - } - // 统计分布 - Map map = new LinkedHashMap<>(); - for (String node : nodes) { - map.put(node, 0L); - } - for (ConsistentHashLoadBalancer.Invocation invocation : invocations) { - String selectNode = loadBalancer.select(invocation); - System.out.printf("server = %s, key = %s\n", selectNode, invocation.getHashKey()); - Long count = map.get(selectNode); - count++; - map.put(selectNode, count); - } - System.out.println(StatisticsUtil.variance(map.values().toArray(new Long[0]))); - System.out.println(StatisticsUtil.standardDeviation(map.values().toArray(new Long[] {}))); + @Test + public void consistentHashLoadBalanceTest() { + LoadBalance loadBalance = new ConsistentHashLoadBalance<>(); + loadBalance.buildInList(initSample()); + System.out.println("======================= 一致性 Hash 负载均衡 ======================="); + countLoadBalance(loadBalance); } /** @@ -105,26 +92,27 @@ public void testDistribution() { @Test public void testNodeAddAndRemove() { // 构造 10000 随机请求 - List invocations = new ArrayList<>(); + List keys = new ArrayList<>(); for (int i = 0; i < 10000; i++) { - invocations.add(new ConsistentHashLoadBalancer.Invocation(UUID.randomUUID().toString())); + keys.add(UUID.randomUUID().toString()); } - List nodes = new ArrayList<>(100); + List nodes = new ArrayList<>(); for (int i = 1; i <= 100; i++) { - String item = "192.168.0." + "i"; - nodes.add(item); + Node node = new Node("192.168.0." + i); + nodes.add(node); } - List newNodes = nodes.subList(0, 80); - ConsistentHashLoadBalancer oldLoadBalance = new ConsistentHashLoadBalancer<>(); + + List newNodes = nodes.subList(0, 80); + ConsistentHashLoadBalance oldLoadBalance = new ConsistentHashLoadBalance<>(); oldLoadBalance.buildInList(nodes); - ConsistentHashLoadBalancer newLoadBalance = new ConsistentHashLoadBalancer<>(); + ConsistentHashLoadBalance newLoadBalance = new ConsistentHashLoadBalance<>(); newLoadBalance.buildInList(newNodes); int count = 0; - for (ConsistentHashLoadBalancer.Invocation invocation : invocations) { - String oldNode = oldLoadBalance.select(invocation); - String newNode = newLoadBalance.select(invocation); + for (String key : keys) { + Node oldNode = oldLoadBalance.next(key); + Node newNode = newLoadBalance.next(key); if (oldNode.equals(newNode)) count++; } System.out.println(count / 10000D); diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/StatisticsUtil.java b/codes/javaweb-example/javaweb-load-balance/src/test/java/io/github/dunwu/javaweb/StatisticsUtil.java similarity index 100% rename from codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/StatisticsUtil.java rename to codes/javaweb-example/javaweb-load-balance/src/test/java/io/github/dunwu/javaweb/StatisticsUtil.java From 394ce4a269329b8c1f2a9c77d5a2acf225c1f8a3 Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Thu, 23 Jan 2020 23:56:39 +0800 Subject: [PATCH 53/75] =?UTF-8?q?=E8=B4=9F=E8=BD=BD=E5=9D=87=E8=A1=A1?= =?UTF-8?q?=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../javaweb/ConsistentHashLoadBalance.java | 21 ++++++----- .../dunwu/javaweb/RandomLoadBalance.java | 24 +++++++------ .../dunwu/javaweb/RoundRobinLoadBalance.java | 30 ++++++++-------- .../dunwu/javaweb/LoadBalanceTests.java | 35 +++++++++++-------- 4 files changed, 60 insertions(+), 50 deletions(-) diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/ConsistentHashLoadBalance.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/ConsistentHashLoadBalance.java index 10adadc9..8cdfc12c 100644 --- a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/ConsistentHashLoadBalance.java +++ b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/ConsistentHashLoadBalance.java @@ -4,7 +4,6 @@ import io.github.dunwu.javaweb.support.MurmurHashStrategy; import java.util.*; -import java.util.function.Predicate; public class ConsistentHashLoadBalance implements LoadBalance { @@ -14,26 +13,26 @@ public class ConsistentHashLoadBalance implements LoadBalance private final static String VIRTUAL_NODE_SUFFIX = "&&"; - private List nodeList = Collections.emptyList(); + private Set nodes = new LinkedHashSet<>(); - private TreeMap hashRing; + private TreeMap hashRing = new TreeMap<>(); @Override public void buildInList(final Collection collection) { - this.nodeList = new ArrayList<>(collection); - this.hashRing = buildConsistentHashRing(this.nodeList); + this.nodes = new LinkedHashSet<>(collection); + this.hashRing = buildConsistentHashRing(this.nodes); } @Override public void addNode(V node) { - this.nodeList.add(node); - this.hashRing = buildConsistentHashRing(this.nodeList); + this.nodes.add(node); + this.hashRing = buildConsistentHashRing(this.nodes); } @Override public void removeNode(V node) { - this.nodeList.removeIf(v -> v.equals(node)); - this.hashRing = buildConsistentHashRing(this.nodeList); + this.nodes.removeIf(v -> v.equals(node)); + this.hashRing = buildConsistentHashRing(this.nodes); } @Override @@ -52,9 +51,9 @@ public V next(String key) { return entry.getValue(); } - private TreeMap buildConsistentHashRing(List nodeList) { + private TreeMap buildConsistentHashRing(Set nodes) { TreeMap hashRing = new TreeMap<>(); - for (V node : nodeList) { + for (V node : nodes) { for (int i = 0; i < VIRTUAL_NODE_SIZE; i++) { // 新增虚拟节点的方式如果有影响,也可以抽象出一个由物理节点扩展虚拟节点的类 hashRing.put(hashStrategy.hashCode(node + VIRTUAL_NODE_SUFFIX + i), node); diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/RandomLoadBalance.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/RandomLoadBalance.java index 35a437e8..3577ff2f 100644 --- a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/RandomLoadBalance.java +++ b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/RandomLoadBalance.java @@ -18,7 +18,7 @@ public class RandomLoadBalance implements LoadBalance { private final Random random = ThreadLocalRandom.current(); - private List nodeList = Collections.emptyList(); + private Set nodes = Collections.emptyNavigableSet(); public RandomLoadBalance() { this.weightMode = false; @@ -30,17 +30,17 @@ public RandomLoadBalance(boolean weightMode) { @Override public void buildInList(final Collection collection) { - this.nodeList = new ArrayList<>(collection); + this.nodes = new LinkedHashSet<>(collection); } @Override public void addNode(V node) { - this.nodeList.add(node); + this.nodes.add(node); } @Override public void removeNode(V node) { - this.nodeList.remove(node); + this.nodes.remove(node); } @Override @@ -53,29 +53,33 @@ public V next() { } private V getNextInWeightMode() { - if (CollectionUtil.isEmpty(nodeList)) { + if (CollectionUtil.isEmpty(nodes)) { return null; } List list = new ArrayList<>(); - for (V node : nodeList) { + for (V node : nodes) { for (int i = 0; i < node.getWeight(); i++) { list.add(node); } } - int totalWeight = nodeList.stream().mapToInt(Node::getWeight).sum(); + int totalWeight = nodes.stream().mapToInt(Node::getWeight).sum(); int number = random.nextInt(totalWeight); return list.get(number); } private V getNextInNormalMode() { - if (CollectionUtil.isEmpty(nodeList)) { + if (CollectionUtil.isEmpty(nodes)) { return null; } - int offset = random.nextInt(nodeList.size()); - return nodeList.get(offset); + int number = random.nextInt(nodes.size()); + Iterator iterator = nodes.iterator(); + while (number-- > 0) { + iterator.next(); + } + return iterator.next(); } } diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/RoundRobinLoadBalance.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/RoundRobinLoadBalance.java index 6bea9def..fdb0f2ef 100644 --- a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/RoundRobinLoadBalance.java +++ b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/RoundRobinLoadBalance.java @@ -2,10 +2,7 @@ import io.github.dunwu.tool.collection.CollectionUtil; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; +import java.util.*; import java.util.concurrent.atomic.AtomicInteger; /** @@ -20,7 +17,7 @@ public class RoundRobinLoadBalance implements LoadBalance { private AtomicInteger offset = new AtomicInteger(0); - private List nodeList = Collections.emptyList(); + private Set nodes = Collections.emptyNavigableSet(); public RoundRobinLoadBalance() { this.weightMode = false; @@ -33,17 +30,17 @@ public RoundRobinLoadBalance(boolean weightMode) { @Override public void buildInList(final Collection collection) { this.offset = new AtomicInteger(0); - this.nodeList = new ArrayList<>(collection); + this.nodes = new LinkedHashSet<>(collection); } @Override public void addNode(V node) { - this.nodeList.add(node); + this.nodes.add(node); } @Override public void removeNode(V node) { - this.nodeList.remove(node); + this.nodes.remove(node); } @Override @@ -56,14 +53,14 @@ public V next() { } private V getNextInWeightMode() { - if (CollectionUtil.isEmpty(nodeList)) { + if (CollectionUtil.isEmpty(nodes)) { return null; } - int totalWeight = nodeList.stream().mapToInt(Node::getWeight).sum(); + int totalWeight = nodes.stream().mapToInt(Node::getWeight).sum(); int number = offset.getAndIncrement() % totalWeight; - for (V node : nodeList) { + for (V node : nodes) { if (node.getWeight() > number) { return node; } @@ -73,13 +70,18 @@ private V getNextInWeightMode() { } private V getNextInNormalMode() { - if (CollectionUtil.isEmpty(this.nodeList)) { + if (CollectionUtil.isEmpty(this.nodes)) { return null; } - int size = this.nodeList.size(); + int size = this.nodes.size(); offset.compareAndSet(size, 0); - return nodeList.get(offset.getAndIncrement()); + int number = offset.getAndIncrement(); + Iterator iterator = nodes.iterator(); + while (number-- > 0) { + iterator.next(); + } + return iterator.next(); } } diff --git a/codes/javaweb-example/javaweb-load-balance/src/test/java/io/github/dunwu/javaweb/LoadBalanceTests.java b/codes/javaweb-example/javaweb-load-balance/src/test/java/io/github/dunwu/javaweb/LoadBalanceTests.java index 118a57fc..23262156 100644 --- a/codes/javaweb-example/javaweb-load-balance/src/test/java/io/github/dunwu/javaweb/LoadBalanceTests.java +++ b/codes/javaweb-example/javaweb-load-balance/src/test/java/io/github/dunwu/javaweb/LoadBalanceTests.java @@ -12,7 +12,10 @@ */ public class LoadBalanceTests { - private List initSample() { + /** + * 生成 100 个样本节点,权重值为 10 以内的随机数 + */ + private List initNodes() { Random random = new Random(); List nodes = new ArrayList<>(); for (int i = 1; i <= 100; i++) { @@ -25,11 +28,12 @@ private List initSample() { /** * 统计负载均衡命中次数,样本数为 100000 次访问 */ - private Map countLoadBalance(LoadBalance algorithm) { + private Map staticLoadBalance(LoadBalance algorithm) { Map staticMap = new TreeMap<>(); for (int i = 0; i < 100000; i++) { Node node = algorithm.next(); + // System.out.printf(">>>> url = %s\n", node.url); if (staticMap.containsKey(node)) { Long value = staticMap.get(node); staticMap.put(node, ++value); @@ -37,53 +41,54 @@ private Map countLoadBalance(LoadBalance algorithm) { staticMap.put(node, 1L); } } + + System.out.println("======================= 统计数据 ======================="); staticMap.forEach((key, value) -> { System.out.printf("key = %s, value = %s\n", key, value); }); - - System.out.println("方差:" + StatisticsUtil.variance(staticMap.values().toArray(new Long[0]))); - System.out.println("标准差:" + StatisticsUtil.standardDeviation(staticMap.values().toArray(new Long[] {}))); + System.out.printf("方差:%s, ", StatisticsUtil.variance(staticMap.values().toArray(new Long[0]))); + System.out.printf("标准差:%s\n", StatisticsUtil.standardDeviation(staticMap.values().toArray(new Long[] {}))); return staticMap; } @Test public void randomLoadBalanceTest() { LoadBalance loadBalance = new RandomLoadBalance<>(); - loadBalance.buildInList(initSample()); + loadBalance.buildInList(initNodes()); System.out.println("======================= 随机负载均衡 ======================="); - countLoadBalance(loadBalance); + staticLoadBalance(loadBalance); } @Test public void randomWeightLoadBalanceTest() { LoadBalance loadBalance = new RandomLoadBalance<>(true); - loadBalance.buildInList(initSample()); + loadBalance.buildInList(initNodes()); System.out.println("======================= 加权随机负载均衡 ======================="); - countLoadBalance(loadBalance); + staticLoadBalance(loadBalance); } @Test public void roundRobinLoadBalanceTest() { LoadBalance loadBalance = new RoundRobinLoadBalance<>(); - loadBalance.buildInList(initSample()); + loadBalance.buildInList(initNodes()); System.out.println("======================= 轮询负载均衡 ======================="); - countLoadBalance(loadBalance); + staticLoadBalance(loadBalance); } @Test public void roundRobinWeightLoadBalanceTest() { LoadBalance loadBalance = new RoundRobinLoadBalance<>(true); - loadBalance.buildInList(initSample()); + loadBalance.buildInList(initNodes()); System.out.println("======================= 加权轮询负载均衡 ======================="); - countLoadBalance(loadBalance); + staticLoadBalance(loadBalance); } @Test public void consistentHashLoadBalanceTest() { LoadBalance loadBalance = new ConsistentHashLoadBalance<>(); - loadBalance.buildInList(initSample()); + loadBalance.buildInList(initNodes()); System.out.println("======================= 一致性 Hash 负载均衡 ======================="); - countLoadBalance(loadBalance); + staticLoadBalance(loadBalance); } /** From 8dd2f33f133cff55a95d13c48c239d09e2f0fe74 Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Fri, 24 Jan 2020 00:12:34 +0800 Subject: [PATCH 54/75] =?UTF-8?q?=E8=B4=9F=E8=BD=BD=E5=9D=87=E8=A1=A1?= =?UTF-8?q?=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dunwu/javaweb/LoadBalanceTests.java | 72 ++++++++++++++----- 1 file changed, 55 insertions(+), 17 deletions(-) diff --git a/codes/javaweb-example/javaweb-load-balance/src/test/java/io/github/dunwu/javaweb/LoadBalanceTests.java b/codes/javaweb-example/javaweb-load-balance/src/test/java/io/github/dunwu/javaweb/LoadBalanceTests.java index 23262156..8c2a2054 100644 --- a/codes/javaweb-example/javaweb-load-balance/src/test/java/io/github/dunwu/javaweb/LoadBalanceTests.java +++ b/codes/javaweb-example/javaweb-load-balance/src/test/java/io/github/dunwu/javaweb/LoadBalanceTests.java @@ -26,12 +26,12 @@ private List initNodes() { } /** - * 统计负载均衡命中次数,样本数为 100000 次访问 + * 统计负载均衡命中次数,样本数为 10000 次访问 */ private Map staticLoadBalance(LoadBalance algorithm) { Map staticMap = new TreeMap<>(); - for (int i = 0; i < 100000; i++) { + for (int i = 0; i < 10000; i++) { Node node = algorithm.next(); // System.out.printf(">>>> url = %s\n", node.url); if (staticMap.containsKey(node)) { @@ -52,39 +52,77 @@ private Map staticLoadBalance(LoadBalance algorithm) { } @Test - public void randomLoadBalanceTest() { + public void randomLoadBalanceDistribution() { + List nodes = initNodes(); + LoadBalance loadBalance = new RandomLoadBalance<>(); - loadBalance.buildInList(initNodes()); + loadBalance.buildInList(nodes); System.out.println("======================= 随机负载均衡 ======================="); staticLoadBalance(loadBalance); + + LoadBalance loadBalance2 = new RandomLoadBalance<>(true); + loadBalance2.buildInList(nodes); + System.out.println("======================= 加权随机负载均衡 ======================="); + staticLoadBalance(loadBalance2); } @Test - public void randomWeightLoadBalanceTest() { - LoadBalance loadBalance = new RandomLoadBalance<>(true); - loadBalance.buildInList(initNodes()); - System.out.println("======================= 加权随机负载均衡 ======================="); - staticLoadBalance(loadBalance); + public void randomLoadBalanceUpdateNodes() { + List oldNodes = initNodes(); + List newNodes = oldNodes.subList(0, 80); + + LoadBalance oldLoadBalance = new RandomLoadBalance<>(); + oldLoadBalance.buildInList(oldNodes); + LoadBalance newLoadBalance = new RandomLoadBalance<>(); + newLoadBalance.buildInList(newNodes); + + double count = 0.0d; + int size = newNodes.size(); + for (int i = 0; i < newNodes.size(); i++) { + Node oldNode = oldLoadBalance.next(); + Node newNode = newLoadBalance.next(); + if (oldNode.equals(newNode)) count++; + } + System.out.println(count / size); } @Test - public void roundRobinLoadBalanceTest() { + public void roundRobinLoadBalanceDistribution() { + List nodes = initNodes(); + LoadBalance loadBalance = new RoundRobinLoadBalance<>(); - loadBalance.buildInList(initNodes()); + loadBalance.buildInList(nodes); System.out.println("======================= 轮询负载均衡 ======================="); staticLoadBalance(loadBalance); + + LoadBalance loadBalance2 = new RoundRobinLoadBalance<>(true); + loadBalance2.buildInList(nodes); + System.out.println("======================= 加权轮询负载均衡 ======================="); + staticLoadBalance(loadBalance2); } @Test - public void roundRobinWeightLoadBalanceTest() { - LoadBalance loadBalance = new RoundRobinLoadBalance<>(true); - loadBalance.buildInList(initNodes()); - System.out.println("======================= 加权轮询负载均衡 ======================="); - staticLoadBalance(loadBalance); + public void roundRobinLoadBalanceUpdateNodes() { + List oldNodes = initNodes(); + List newNodes = oldNodes.subList(0, 80); + + LoadBalance oldLoadBalance = new RoundRobinLoadBalance<>(); + oldLoadBalance.buildInList(oldNodes); + LoadBalance newLoadBalance = new RoundRobinLoadBalance<>(); + newLoadBalance.buildInList(newNodes); + + double count = 0.0d; + int size = newNodes.size(); + for (int i = 0; i < newNodes.size(); i++) { + Node oldNode = oldLoadBalance.next(); + Node newNode = newLoadBalance.next(); + if (oldNode.equals(newNode)) count++; + } + System.out.println(count / size); } @Test - public void consistentHashLoadBalanceTest() { + public void consistentHashLoadBalanceDistribution() { LoadBalance loadBalance = new ConsistentHashLoadBalance<>(); loadBalance.buildInList(initNodes()); System.out.println("======================= 一致性 Hash 负载均衡 ======================="); From 62d1d53b1338a1dede3bd42d4d5d09783ca51ecd Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Thu, 6 Feb 2020 21:41:33 +0800 Subject: [PATCH 55/75] update docs --- .gitignore | 1 + README.md | 196 ++--- assets/{ => eddx}/javaweb.eddx | Bin assets/{ => eddx}/kafka.pptx | Bin assets/{ => eddx}/test.eddx | Bin assets/{ => eddx}/zookeeper.eddx | Bin docs/.remarkrc | 9 + docs/.textlint.terms.json | 8 + docs/.textlintrc.js | 23 + docs/.vuepress/config.js | 160 ++++ docs/.vuepress/enhanceApp.js | 7 + docs/.vuepress/public/favicon.ico | Bin 0 -> 16958 bytes .../public/images/dunwu-logo-100.png | Bin 0 -> 7664 bytes .../public/images/dunwu-logo-200.png | Bin 0 -> 18730 bytes .../.vuepress/public/images/dunwu-logo-50.png | Bin 0 -> 2804 bytes docs/.vuepress/public/images/dunwu-logo.png | Bin 0 -> 15561 bytes docs/README.md | 213 ++--- docs/book.json | 69 -- docs/coverpage.md | 9 - docs/{technology => ecology}/README.md | 0 .../cache/CacheInterview.md | 0 .../{technology => ecology}/cache/Caffeine.md | 0 docs/{technology => ecology}/cache/Ehcache.md | 0 docs/ecology/cache/README.md | 6 + docs/{technology => ecology}/cache/Redis.md | 0 docs/ecology/framework/README.md | 6 + .../data => ecology/framework}/mybatis.md | 0 docs/ecology/io/dozer.md | 793 ++++++++++++++++++ docs/ecology/io/javalib-binary.md | 338 ++++++++ docs/ecology/io/javalib-json.md | 551 ++++++++++++ docs/ecology/io/javalib-log.md | 762 +++++++++++++++++ docs/ecology/microservices/README.md | 4 + .../microservices}/dubbo.md | 0 .../microservices}/zookeeper-ops.md | 0 .../microservices}/zookeeper.md | 0 docs/{technology => ecology}/mq/ActiveMQ.md | 0 .../{technology => ecology}/mq/MqInterview.md | 0 docs/ecology/mq/README.md | 9 + .../mq/RocketmqAdvanced.md | 0 .../mq/RocketmqBasics.md | 0 .../mq/kafka-advance.md | 0 .../{technology => ecology}/mq/kafka-basic.md | 0 docs/{technology => ecology}/mq/kafka-ops.md | 0 docs/ecology/other/README.md | 15 + docs/ecology/other/freemark.md | 240 ++++++ docs/ecology/other/javalib-util.md | 7 + docs/ecology/other/javamail.md | 466 ++++++++++ docs/ecology/other/jsoup.md | 461 ++++++++++ docs/ecology/other/lombok.md | 373 ++++++++ docs/ecology/other/reflections.md | 100 +++ docs/ecology/other/thumbnailator.md | 244 ++++++ docs/ecology/other/zxing.md | 93 ++ docs/ecology/search/README.md | 49 ++ docs/ecology/search/elastic-beats-ops.md | 238 ++++++ docs/ecology/search/elastic-beats.md | 296 +++++++ .../search/elastic-elasticsearch-ops.md | 208 +++++ docs/ecology/search/elastic-elasticsearch.md | 536 ++++++++++++ docs/ecology/search/elastic-kibana-ops.md | 346 ++++++++ docs/ecology/search/elastic-kibana.md | 307 +++++++ docs/ecology/search/elastic-logstash-ops.md | 495 +++++++++++ docs/ecology/search/elastic-logstash.md | 519 ++++++++++++ docs/ecology/search/elastic-quickstart.md | 276 ++++++ docs/ecology/security/README.md | 4 + .../Shiro.md => ecology/security/shiro.md} | 0 .../security/spring-security.md | 0 docs/ecology/server/README.md | 5 + .../Jetty.md => ecology/server/jetty.md} | 0 .../Tomcat.md => ecology/server/tomcat.md} | 0 docs/ecology/storage/README.md | 12 + .../storage/shardingsphere.md} | 0 docs/ecology/test/README.md | 5 + docs/ecology/test/jmh.md | 325 +++++++ docs/ecology/test/junit.md | 582 +++++++++++++ docs/ecology/test/mockito.md | 562 +++++++++++++ docs/index.html | 155 ---- docs/javaee/javaee-cookie-sesion.md | 61 +- docs/javaee/javaee-filter-listener.md | 27 +- docs/javaee/javaee-interview.md | 52 +- docs/javaee/javaee-jsp.md | 755 +++++++---------- docs/javaee/javaee-servlet.md | 34 +- docs/package.json | 44 +- docs/sidebar.md | 68 -- docs/tool/README.md | 21 + docs/tool/ide/README.md | 12 + docs/tool/ide/eclipse.md | 233 +++++ docs/tool/ide/intellij-idea.md | 269 ++++++ docs/tool/ide/vscode.md | 47 ++ docs/tool/jmeter.md | 204 +++++ docs/{technology => tool}/monitor/arthas.md | 0 .../monitor/skywalking.md | 0 pom.xml | 6 +- 91 files changed, 10815 insertions(+), 1101 deletions(-) rename assets/{ => eddx}/javaweb.eddx (100%) rename assets/{ => eddx}/kafka.pptx (100%) rename assets/{ => eddx}/test.eddx (100%) rename assets/{ => eddx}/zookeeper.eddx (100%) create mode 100644 docs/.remarkrc create mode 100644 docs/.textlint.terms.json create mode 100644 docs/.textlintrc.js create mode 100644 docs/.vuepress/config.js create mode 100644 docs/.vuepress/enhanceApp.js create mode 100644 docs/.vuepress/public/favicon.ico create mode 100644 docs/.vuepress/public/images/dunwu-logo-100.png create mode 100644 docs/.vuepress/public/images/dunwu-logo-200.png create mode 100644 docs/.vuepress/public/images/dunwu-logo-50.png create mode 100644 docs/.vuepress/public/images/dunwu-logo.png delete mode 100644 docs/book.json delete mode 100644 docs/coverpage.md rename docs/{technology => ecology}/README.md (100%) rename docs/{technology => ecology}/cache/CacheInterview.md (100%) rename docs/{technology => ecology}/cache/Caffeine.md (100%) rename docs/{technology => ecology}/cache/Ehcache.md (100%) create mode 100644 docs/ecology/cache/README.md rename docs/{technology => ecology}/cache/Redis.md (100%) create mode 100644 docs/ecology/framework/README.md rename docs/{technology/data => ecology/framework}/mybatis.md (100%) create mode 100644 docs/ecology/io/dozer.md create mode 100644 docs/ecology/io/javalib-binary.md create mode 100644 docs/ecology/io/javalib-json.md create mode 100644 docs/ecology/io/javalib-log.md create mode 100644 docs/ecology/microservices/README.md rename docs/{technology/distributed => ecology/microservices}/dubbo.md (100%) rename docs/{technology/distributed => ecology/microservices}/zookeeper-ops.md (100%) rename docs/{technology/distributed => ecology/microservices}/zookeeper.md (100%) rename docs/{technology => ecology}/mq/ActiveMQ.md (100%) rename docs/{technology => ecology}/mq/MqInterview.md (100%) create mode 100644 docs/ecology/mq/README.md rename docs/{technology => ecology}/mq/RocketmqAdvanced.md (100%) rename docs/{technology => ecology}/mq/RocketmqBasics.md (100%) rename docs/{technology => ecology}/mq/kafka-advance.md (100%) rename docs/{technology => ecology}/mq/kafka-basic.md (100%) rename docs/{technology => ecology}/mq/kafka-ops.md (100%) create mode 100644 docs/ecology/other/README.md create mode 100644 docs/ecology/other/freemark.md create mode 100644 docs/ecology/other/javalib-util.md create mode 100644 docs/ecology/other/javamail.md create mode 100644 docs/ecology/other/jsoup.md create mode 100644 docs/ecology/other/lombok.md create mode 100644 docs/ecology/other/reflections.md create mode 100644 docs/ecology/other/thumbnailator.md create mode 100644 docs/ecology/other/zxing.md create mode 100644 docs/ecology/search/README.md create mode 100644 docs/ecology/search/elastic-beats-ops.md create mode 100644 docs/ecology/search/elastic-beats.md create mode 100644 docs/ecology/search/elastic-elasticsearch-ops.md create mode 100644 docs/ecology/search/elastic-elasticsearch.md create mode 100644 docs/ecology/search/elastic-kibana-ops.md create mode 100644 docs/ecology/search/elastic-kibana.md create mode 100644 docs/ecology/search/elastic-logstash-ops.md create mode 100644 docs/ecology/search/elastic-logstash.md create mode 100644 docs/ecology/search/elastic-quickstart.md create mode 100644 docs/ecology/security/README.md rename docs/{technology/security/Shiro.md => ecology/security/shiro.md} (100%) rename docs/{technology => ecology}/security/spring-security.md (100%) create mode 100644 docs/ecology/server/README.md rename docs/{technology/server/Jetty.md => ecology/server/jetty.md} (100%) rename docs/{technology/server/Tomcat.md => ecology/server/tomcat.md} (100%) create mode 100644 docs/ecology/storage/README.md rename docs/{technology/data/ShardingSphere.md => ecology/storage/shardingsphere.md} (100%) create mode 100644 docs/ecology/test/README.md create mode 100644 docs/ecology/test/jmh.md create mode 100644 docs/ecology/test/junit.md create mode 100644 docs/ecology/test/mockito.md delete mode 100644 docs/index.html delete mode 100644 docs/sidebar.md create mode 100644 docs/tool/README.md create mode 100644 docs/tool/ide/README.md create mode 100644 docs/tool/ide/eclipse.md create mode 100644 docs/tool/ide/intellij-idea.md create mode 100644 docs/tool/ide/vscode.md create mode 100644 docs/tool/jmeter.md rename docs/{technology => tool}/monitor/arthas.md (100%) rename docs/{technology => tool}/monitor/skywalking.md (100%) diff --git a/.gitignore b/.gitignore index 4a99239d..7381f180 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ node_modules # temp folders build +.temp dist _book _jsdoc diff --git a/README.md b/README.md index 5d33deea..b3a24805 100644 --- a/README.md +++ b/README.md @@ -1,144 +1,118 @@

    - + logo

    - license + license

    -

    JavaWeb

    -> ☕ **JavaWeb** 是一个 Java Web 领域开发技术教程。 +

    JavaTech

    + +> ☕ **javatech** 汇总了 Java 开发中常见的主流技术的应用、特性、原理。 +> +> - 🔁 项目同步维护:[Github](https://github.com/dunwu/javatech/) | [Gitee](https://gitee.com/turnon/javaweb/) +> - 📖 电子书阅读:[Github Pages](https://dunwu.github.io/javatech/) | [Gitee Pages](http://turnon.gitee.io/javaweb/) > -> - 🔁 项目同步维护:[Github](https://github.com/dunwu/javaweb/) | [Gitee](https://gitee.com/turnon/javaweb/) -> - 📖 电子书阅读:[Github Pages](https://dunwu.github.io/javaweb/) | [Gitee Pages](http://turnon.gitee.io/javaweb/) - -| 🔰 | 🎨 | ✨ | ⭐️ | ☕ | -| :---------------: | :----------------------: | :----------------------: | :----------------------: | :------------------: | -| [准备](#🔰️-准备) | [架构设计](#🎨-架构设计) | [系统原理](#✨-系统原理) | [主流技术](#⭐-主流技术) | [JavaEE](#☕-JavaEE) | - -## 🔰️ 准备 - -作为 Java Web 工程师,你应该多多少少掌握一些的知识: - -- [JavaCore](https://dunwu.github.io/javacore/) - Java 核心技术 -- [前端技术指南](https://github.com/dunwu/frontend-tutorial) - 即使是后端工程师,也难免会接触到前端技术。前端技术五花八门,如:React、Vue、Angular、Webpack、ES6、Babel、Node.js 等等。不说掌握,至少也应该知道这些技术是什么。 - -### 网络通信 - -- **面试** - - [网络通信面经](https://github.com/dunwu/blog/blob/master/source/_posts/communication/network-interview.md) -- **网络分层** - - [计算机网络指南](https://github.com/dunwu/blog/blob/master/source/_posts/communication/network-guide.md) - 关键词:核心概念、拓扑结构、作用范围、性能指标、体系结构 - - [计算机网络之物理层](https://github.com/dunwu/blog/blob/master/source/_posts/communication/network-physical.md) - 关键词:调制、解调、数字信号、模拟信号、通信媒介、信道复用 - - [计算机网络之链路层](https://github.com/dunwu/blog/blob/master/source/_posts/communication/network-data-link.md) - 关键词:点对点信道、广播信道、`PPP`、`CSMA/CD`、局域网、以太网、`MAC`、适配器、集线器、网桥、交换机 - - [计算机网络之网络层](https://github.com/dunwu/blog/blob/master/source/_posts/communication/network-network.md) - 关键词:`IP`、`ICMP`、`ARP`、路由 - - [计算机网络之传输层](https://github.com/dunwu/blog/blob/master/source/_posts/communication/network-transport.md) - 关键词:`UDP`、`TCP`、滑动窗口、拥塞控制、三次握手 - - [计算机网络之应用层](https://github.com/dunwu/blog/blob/master/source/_posts/communication/network-application.md) - 关键词:`HTTP`、`DNS`、`FTP`、`TELNET`、`DHCP` -- **重要技术** - - [网络协议之 HTTP](https://github.com/dunwu/blog/blob/master/source/_posts/communication/http.md) - - [网络协议之 DNS](https://github.com/dunwu/blog/blob/master/source/_posts/communication/dns.md) - - [网络协议之 TCP](https://github.com/dunwu/blog/blob/master/source/_posts/communication/tcp.md) - - [网络协议之 UDP](https://github.com/dunwu/blog/blob/master/source/_posts/communication/udp.md) - - [网络协议之 ICMP](https://github.com/dunwu/blog/blob/master/source/_posts/communication/icmp.md) - - [网络协议之 WebSocket](https://github.com/dunwu/blog/blob/master/source/_posts/communication/websocket.md) - - [网络通信之 CDN](https://github.com/dunwu/blog/blob/master/source/_posts/communication/cdn.md) - -## 🎨 架构设计 - -> [🎨架构设计](https://github.com/dunwu/blog/tree/master/source/_posts/design/architecture) 整理架构设计方面的一些学习总结和心得。 - -- [系统架构面试题](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/system-architecture-interview.md) -- [分布式技术实现](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/system-core-technologies.md) -- [分布式架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/分布式架构.md) -- [大型分布式网站架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/大型分布式网站架构.md) -- [系统架构概述](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/system-architecture-overview.md) -- [网站典型故障](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/网站典型故障.md) -- [伸缩性架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/scalable-architecture.md) -- [扩展性架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/extensible-architecture.md) -- [安全性架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/secure-architecture.md) -- [高可用架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/high-availability-architecture.md) -- [高性能架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/high-performance-architecture.md) - -## ✨ 系统原理 - -> [✨系统原理](https://github.com/dunwu/blog/tree/master/source/_posts/design/theory) 整理系统的基本原理,为架构设计,技术应用提供理论支撑。 - -- [系统原理面试题](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/system-theory-interview.md) -- [分布式基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/distributed-base-theory.md) -- [负载均衡基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/load-balance-theory.md) -- [缓存基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/cache-theory.md) -- [消息队列基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/mq-theory.md) -- [分布式锁基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/distributed-lock-theory.md) -- [分布式会话基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/distributed-session-theory.md) -- [分布式存储基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/distributed-storage-theory.md) -- [分布式 ID 基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/distributed-id-theory.md) -- [分布式事务基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/distributed-transaction-theory.md) -- [分库分表基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/sharding-theory.md) - -## ⭐ 主流技术 - -> [主流技术](docs/technology) 汇集 JavaWeb 开发常用的各种主流技术。 - -### Web 框架 - -- [Spring 教程 📚](https://dunwu.github.io/spring-tutorial/) -- [Spring Boot 教程 📚](https://dunwu.github.io/spring-boot-tutorial/) +> 说明:下面的内容清单中,凡是有 📚 标记的技术,都已整理成详细的教程。 -### 服务器 +## Java 生态 -- [Tomcat 应用指南](docs/technology/server/Tomcat.md) -- [Jetty 应用指南](docs/technology/server/Jetty.md) -- [Nginx 简易教程 📚](https://github.com/dunwu/nginx-tutorial) +### 框架 + +- [Spring](https://dunwu.github.io/spring-tutorial/) 📚 +- [Spring Boot](https://dunwu.github.io/spring-boot-tutorial/) 📚 +- [Mybatis](docs/ecology/framework/mybatis.md) +- [ShardingSphere](docs/ecology/storage/shardingsphere.md) ### 消息队列 -- [消息队列面经](docs/technology/mq/MqInterview.md) -- [Kafka 应用指南基础篇](docs/technology/mq/kafka-basic.md) -- [Kafka 应用指南进阶篇](docs/technology/mq/kafka-advance.md) -- [Kafka 运维指南](docs/technology/mq/kafka-ops.md) -- [RocketMQ 基础篇](docs/technology/mq/RocketmqBasics.md) -- [RocketMQ 进阶篇](docs/technology/mq/RocketmqAdvanced.md) -- [ActiveMQ 实战篇](docs/technology/mq/ActiveMQ.md) +- [消息队列面经](docs/ecology/mq/MqInterview.md) +- [Kafka 应用指南基础篇](docs/ecology/mq/kafka-basic.md) +- [Kafka 应用指南进阶篇](docs/ecology/mq/kafka-advance.md) +- [Kafka 运维指南](docs/ecology/mq/kafka-ops.md) +- [RocketMQ 基础篇](docs/ecology/mq/RocketmqBasics.md) +- [RocketMQ 进阶篇](docs/ecology/mq/RocketmqAdvanced.md) +- [ActiveMQ 实战篇](docs/ecology/mq/ActiveMQ.md) ### 缓存 -- [缓存面经](docs/technology/cache/CacheInterview.md) -- [Redis](docs/technology/cache/Redis.md) -- [Ehcache](docs/technology/cache/Ehcache.md) -- [Caffeine](docs/technology/cache/Caffeine.md) +- [缓存面经](docs/ecology/cache/CacheInterview.md) +- [Redis](docs/ecology/cache/Redis.md) +- [Ehcache](docs/ecology/cache/Ehcache.md) +- [Caffeine](docs/ecology/cache/Caffeine.md) -### RPC +### 安全 -- [Dubbo 应用指南](docs/technology/distributed/dubbo.md) +> Java 领域比较流行的安全框架就是 shiro 和 spring-security。 +> +> shiro 更为简单、轻便,容易理解,能满足大多数基本安全场景下的需要。 +> +> spring-security 功能更丰富,也比 shiro 更复杂。值得一提的是由于 spring-security 是 spring 团队开发,所以集成 spring 和 spring-boot 框架更容易。 + +- [shiro](docs/ecology/security/shiro.md) +- [spring-security](docs/ecology/security/spring-security.md) + +### 微服务 + +- [Dubbo](docs/ecology/microservices/dubbo.md) - Spring Cloud +- [ZooKeeper 应用指南](docs/ecology/microservices/zookeeper.md) +- [ZooKeeper 运维指南](docs/ecology/microservices/zookeeper-ops.md) -### 安全 +### 测试 -- [Shiro 应用指南](docs/technology/security/shiro.md) -- [Spring Security 应用指南](docs/technology/security/spring-security.md) +- [Junit](docs/ecology/test/junit.md) +- [Mockito](docs/ecology/test/mockito.md) +- [JMH](docs/ecology/test/jmh.md) -### 数据 +### 服务器 + +> Tomcat 和 Jetty 都是 Java 比较流行的轻量级服务器。 +> +> Nginx 是目前最流行的反向代理服务器,也常用于负载均衡。 + +- [Tomcat](docs/ecology/server/tomcat.md) +- [Jetty](docs/ecology/server/jetty.md) +- [Nginx](https://github.com/dunwu/nginx-tutorial) 📚 + +## Java 工具 -- [Mybatis 应用指南](docs/technology/data/mybatis.md) -- [ShardingSphere 应用指南](docs/technology/data/ShardingSphere.md) +### 构建 -### 分布式 +> Java 项目需要通过 [**构建工具**](docs/tool/build) 来管理项目依赖,完成编译、打包、发布、生成 JavaDoc 等任务。 +> +> - 目前最主流的构建工具是 Maven,它的功能非常强大。 +> - Gradle 号称是要替代 Maven 等构件工具,它的版本管理确实简洁,但是需要学习 Groovy,学习成本比 Maven 高。 +> - Ant 功能比 Maven 和 Gradle 要弱,现代 Java 项目基本不用了,但也有一些传统的 Java 项目还在使用。 + +- [Maven](docs/tool/build/maven) 📚 +- [Ant](docs/tool/build/ant.md) + +### IDE + +> 自动有了 [**IDE**](docs/tool/ide),写代码从此就告别了刀耕火种的蛮荒时代。 +> +> - [Eclipse](docs/tool/ide/eclipse.md) 是久负盛名的开源 Java IDE,我的学生时代一直使用它写 Java。 +> - 曾经抗拒从转 [Intellij Idea](docs/tool/ide/intellij-idea.md) ,但后来发现真香,不得不说,确实是目前最优秀的 Java IDE。 +> - 你可以在 [vscode](docs/tool/ide/vscode.md) 中写各种语言,只要安装相应插件即可。如果你的项目中使用了很多种编程语言,又懒得在多个 IDE 之间切换,那么就用 vscode 来一网打尽吧。 -- [ZooKeeper 应用指南](docs/technology/distributed/zookeeper.md) -- [ZooKeeper 运维指南](docs/technology/distributed/zookeeper-ops.md) +- [Intellij Idea](docs/tool/ide/intellij-idea.md) +- [Eclipse](docs/tool/ide/eclipse.md) +- [vscode](docs/tool/ide/vscode.md) -### 监控/诊断 +### 监控 -- [Arthas 应用指南](docs/technology/monitor/arthas.md) +- [Arthas](docs/tool/monitor/arthas.md) +- [SkyWalking](docs/tool/monitor/skywalking.md) -## ☕ JavaEE +## JavaEE > [☕ JavaEE](docs/javaee/README.md) 技术是 Java Web 的基石 - [JavaEE 面经](docs/javaee/javaee-interview.md) -- [JavaEE 之 Servlet 指南](docs/javaee/javaee-servlet.md) -- [JavaEE 之 Jsp 指南](docs/javaee/javaee-jsp.md) -- [JavaEE 之 Filter 和 Listener](docs/javaee/javaee-filter-listener.md) -- [JavaEE 之 Cookie 和 Session](docs/javaee/javaee-cookie-sesion.md) +- [Servlet](docs/javaee/javaee-servlet.md) +- [Jsp](docs/javaee/javaee-jsp.md) +- [Filter 和 Listener](docs/javaee/javaee-filter-listener.md) +- [Cookie 和 Session](docs/javaee/javaee-cookie-sesion.md) diff --git a/assets/javaweb.eddx b/assets/eddx/javaweb.eddx similarity index 100% rename from assets/javaweb.eddx rename to assets/eddx/javaweb.eddx diff --git a/assets/kafka.pptx b/assets/eddx/kafka.pptx similarity index 100% rename from assets/kafka.pptx rename to assets/eddx/kafka.pptx diff --git a/assets/test.eddx b/assets/eddx/test.eddx similarity index 100% rename from assets/test.eddx rename to assets/eddx/test.eddx diff --git a/assets/zookeeper.eddx b/assets/eddx/zookeeper.eddx similarity index 100% rename from assets/zookeeper.eddx rename to assets/eddx/zookeeper.eddx diff --git a/docs/.remarkrc b/docs/.remarkrc new file mode 100644 index 00000000..5accdbba --- /dev/null +++ b/docs/.remarkrc @@ -0,0 +1,9 @@ +{ + "plugins": [ + "preset-lint-recommended", + "preset-lint-consistent", + + ["lint-list-item-indent", "space"], + ["lint-heading-style", false] + ] +} diff --git a/docs/.textlint.terms.json b/docs/.textlint.terms.json new file mode 100644 index 00000000..7ea8ecd7 --- /dev/null +++ b/docs/.textlint.terms.json @@ -0,0 +1,8 @@ +[ + "Stylus", + "VuePress", + [ + "front[- ]matter", + "frontmatter" + ] +] diff --git a/docs/.textlintrc.js b/docs/.textlintrc.js new file mode 100644 index 00000000..068b522b --- /dev/null +++ b/docs/.textlintrc.js @@ -0,0 +1,23 @@ +module.exports = { + rules: { + '@textlint-rule/no-unmatched-pair': true, + apostrophe: true, + 'common-misspellings': true, + diacritics: true, + 'en-capitalization': { + allowHeading: false + }, + 'stop-words': { + severity: 'warning' + }, + terminology: { + terms: `${__dirname}/.textlint.terms.json` + }, + 'write-good': { + severity: 'warning' + } + }, + filters: { + comments: true + } +} diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js new file mode 100644 index 00000000..efa1f886 --- /dev/null +++ b/docs/.vuepress/config.js @@ -0,0 +1,160 @@ +/** + * @see https://vuepress.vuejs.org/zh/ + */ +module.exports = { + port: "4000", + dest: "dist", + base: "/javatech/", + title: "JAVATECH", + description: "Java 教程", + head: [["link", { rel: "icon", href: `/favicon.ico` }]], + markdown: { + externalLinks: { + target: "_blank", + rel: "noopener noreferrer" + } + }, + themeConfig: { + logo: "https://raw.githubusercontent.com/dunwu/images/master/common/dunwu-logo-200.png", + repo: "dunwu/javatech", + repoLabel: "Github", + editLinks: true, + smoothScroll: true, + locales: { + "/": { + label: "简体中文", + selectText: "Languages", + editLinkText: "帮助我们改善此页面!", + lastUpdated: "上次更新", + nav: [ + { + text: "Java生态", + link: "/ecology/", + items: [ + { + text: "核心框架", + link: "/ecology/framework/", + }, + { + text: "缓存", + link: "/ecology/cache/", + }, + { + text: "消息队列", + link: "/ecology/mq/", + }, + { + text: "搜索引擎", + link: "/ecology/search/", + }, + { + text: "存储", + link: "/ecology/storage/", + }, + { + text: "微服务", + link: "/ecology/microservices/", + }, + { + text: "安全", + link: "/ecology/security/", + }, + { + text: "测试", + link: "/ecology/test/", + }, + { + text: "服务器", + link: "/ecology/server/", + }, + ] + }, + { + text: "Java工具", + link: "/tool/", + items: [ + { + text: "构建", + link: "/tool/build/", + }, + { + text: "IDE", + link: "/tool/ide/", + }, + ] + }, + { + text: "JavaEE", + link: "/javaee/" + }, + { + text: "Java系列", + ariaLabel: "Java", + items: [ + { + text: "JavaCore 教程 📚", + link: "https://dunwu.github.io/javacore/", + target: "_blank", + rel: "" + }, + { + text: "JavaTech 教程 📚", + link: "https://dunwu.github.io/javatech/", + target: "_blank", + rel: "" + }, + { + text: "Spring 教程 📚", + link: "https://dunwu.github.io/spring-tutorial/", + target: "_blank", + rel: "" + }, + { + text: "Spring Boot 教程 📚", + link: "https://dunwu.github.io/spring-boot-tutorial/", + target: "_blank", + rel: "" + } + ] + }, + { + text: "博客", + link: "https://github.com/dunwu/blog", + target: "_blank", + rel: "" + } + ], + sidebar: "auto", + sidebarDepth: 2 + } + } + }, + plugins: [ + ["@vuepress/back-to-top", true], + [ + "@vuepress/pwa", + { + serviceWorker: true, + updatePopup: true + } + ], + ["@vuepress/medium-zoom", true], + [ + "container", + { + type: "vue", + before: '
    ',
    +        after: "
    " + } + ], + [ + "container", + { + type: "upgrade", + before: info => ``, + after: "" + } + ], + ["flowchart"] + ] +}; diff --git a/docs/.vuepress/enhanceApp.js b/docs/.vuepress/enhanceApp.js new file mode 100644 index 00000000..7b3605fc --- /dev/null +++ b/docs/.vuepress/enhanceApp.js @@ -0,0 +1,7 @@ +export default ({ Vue, isServer }) => { + if (!isServer) { + import('vue-toasted' /* webpackChunkName: "notification" */).then(module => { + Vue.use(module.default) + }) + } +} diff --git a/docs/.vuepress/public/favicon.ico b/docs/.vuepress/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..51e9bfa0baf0e75bbf1c8ccc49bc0ec656e60a7c GIT binary patch literal 16958 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

    ~k$!O4qv?+8^ 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 literal 0 HcmV?d00001 diff --git a/docs/README.md b/docs/README.md index 5b6039cb..4c2d412f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,146 +1,117 @@ -

    - - logo - -

    - -

    - license -

    - -

    JavaWeb

    - -> ☕ **JavaWeb** 是一个 Java Web 领域开发技术教程。 +--- +home: true +heroImage: http://dunwu.test.upcdn.net/common/logo/java-logo.png +heroText: JavaTech +tagline: ☕ javatech 汇总了 Java 开发中常见的主流技术的应用、特性、原理。 +actionLink: / +features: + - title: Java 生态 + details: Java 开发中常见应用技术,如:框架、缓存、消息队列、存储、安全、微服务、测试、服务器等。 + - title: Java 工具 + details: Java 开发中常用工具,如:构建、IDE、监控等。 + - title: JavaEE + details: Java Web 开发的基础技术:Servlet、Jsp、Filter、Listener 等。 +footer: MIT Licensed | Copyright © 2018-present Dunwu +--- + +# JavaTech + +![license](https://badgen.net/github/license/dunwu/javatech) + +> ☕ **javatech** 汇总了 Java 开发中常见的主流技术的应用、特性、原理。 > -> - 🔁 项目同步维护:[Github](https://github.com/dunwu/javaweb/) | [Gitee](https://gitee.com/turnon/javaweb/) -> - 📖 电子书阅读:[Github Pages](https://dunwu.github.io/javaweb/) | [Gitee Pages](http://turnon.gitee.io/javaweb/) - -| 🔰 | 🎨 | ✨ | ⭐️ | ☕ | -| :---------------: | :----------------------: | :----------------------: | :----------------------: | :------------------: | -| [准备](#🔰️-准备) | [架构设计](#🎨-架构设计) | [系统原理](#✨-系统原理) | [主流技术](#⭐-主流技术) | [JavaEE](#☕-JavaEE) | - -## 🔰️ 准备 - -作为 Java Web 工程师,你应该多多少少掌握一些的知识: - -- [JavaCore](https://dunwu.github.io/javacore/) - Java 核心技术 -- [前端技术指南](https://github.com/dunwu/frontend-tutorial) - 即使是后端工程师,也难免会接触到前端技术。前端技术五花八门,如:React、Vue、Angular、Webpack、ES6、Babel、Node.js 等等。不说掌握,至少也应该知道这些技术是什么。 - -### 网络通信 - -- **面试** - - [网络通信面经](https://github.com/dunwu/blog/blob/master/source/_posts/communication/network-interview.md) -- **网络分层** - - [计算机网络指南](https://github.com/dunwu/blog/blob/master/source/_posts/communication/network-guide.md) - 关键词:核心概念、拓扑结构、作用范围、性能指标、体系结构 - - [计算机网络之物理层](https://github.com/dunwu/blog/blob/master/source/_posts/communication/network-physical.md) - 关键词:调制、解调、数字信号、模拟信号、通信媒介、信道复用 - - [计算机网络之链路层](https://github.com/dunwu/blog/blob/master/source/_posts/communication/network-data-link.md) - 关键词:点对点信道、广播信道、`PPP`、`CSMA/CD`、局域网、以太网、`MAC`、适配器、集线器、网桥、交换机 - - [计算机网络之网络层](https://github.com/dunwu/blog/blob/master/source/_posts/communication/network-network.md) - 关键词:`IP`、`ICMP`、`ARP`、路由 - - [计算机网络之传输层](https://github.com/dunwu/blog/blob/master/source/_posts/communication/network-transport.md) - 关键词:`UDP`、`TCP`、滑动窗口、拥塞控制、三次握手 - - [计算机网络之应用层](https://github.com/dunwu/blog/blob/master/source/_posts/communication/network-application.md) - 关键词:`HTTP`、`DNS`、`FTP`、`TELNET`、`DHCP` -- **重要技术** - - [网络协议之 HTTP](https://github.com/dunwu/blog/blob/master/source/_posts/communication/http.md) - - [网络协议之 DNS](https://github.com/dunwu/blog/blob/master/source/_posts/communication/dns.md) - - [网络协议之 TCP](https://github.com/dunwu/blog/blob/master/source/_posts/communication/tcp.md) - - [网络协议之 UDP](https://github.com/dunwu/blog/blob/master/source/_posts/communication/udp.md) - - [网络协议之 ICMP](https://github.com/dunwu/blog/blob/master/source/_posts/communication/icmp.md) - - [网络协议之 WebSocket](https://github.com/dunwu/blog/blob/master/source/_posts/communication/websocket.md) - - [网络通信之 CDN](https://github.com/dunwu/blog/blob/master/source/_posts/communication/cdn.md) - -## 🎨 架构设计 - -> [🎨架构设计](https://github.com/dunwu/blog/tree/master/source/_posts/design/architecture) 整理架构设计方面的一些学习总结和心得。 - -- [系统架构面试题](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/system-architecture-interview.md) -- [分布式技术实现](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/system-core-technologies.md) -- [分布式架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/分布式架构.md) -- [大型分布式网站架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/大型分布式网站架构.md) -- [系统架构概述](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/system-architecture-overview.md) -- [网站典型故障](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/网站典型故障.md) -- [伸缩性架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/scalable-architecture.md) -- [扩展性架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/extensible-architecture.md) -- [安全性架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/secure-architecture.md) -- [高可用架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/high-availability-architecture.md) -- [高性能架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/high-performance-architecture.md) - -## ✨ 系统原理 - -> [✨系统原理](https://github.com/dunwu/blog/tree/master/source/_posts/design/theory) 整理系统的基本原理,为架构设计,技术应用提供理论支撑。 - -- [系统原理面试题](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/system-theory-interview.md) -- [分布式基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/distributed-base-theory.md) -- [负载均衡基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/load-balance-theory.md) -- [缓存基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/cache-theory.md) -- [消息队列基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/mq-theory.md) -- [分布式锁基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/distributed-lock-theory.md) -- [分布式会话基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/distributed-session-theory.md) -- [分布式存储基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/distributed-storage-theory.md) -- [分布式 ID 基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/distributed-id-theory.md) -- [分布式事务基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/distributed-transaction-theory.md) -- [分库分表基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/sharding-theory.md) -- [常见安全手段基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/security-theory.md) - -## ⭐ 主流技术 - -> [主流技术](technology) 汇集 JavaWeb 开发常用的各种主流技术。 - -### Web 框架 - -- [Spring 教程 📚](https://dunwu.github.io/spring-tutorial/) -- [Spring Boot 教程 📚](https://dunwu.github.io/spring-boot-tutorial/) +> - 🔁 项目同步维护:[Github](https://github.com/dunwu/javatech/) | [Gitee](https://gitee.com/turnon/javaweb/) +> - 📖 电子书阅读:[Github Pages](https://dunwu.github.io/javatech/) | [Gitee Pages](http://turnon.gitee.io/javaweb/) -### 服务器 +## Java 生态 + +### 框架 -- [Tomcat 应用指南](technology/server/Tomcat.md) -- [Jetty 应用指南](technology/server/Jetty.md) -- [Nginx 简易教程 📚](https://github.com/dunwu/nginx-tutorial) +- [Spring](https://dunwu.github.io/spring-tutorial/) 📚 +- [Spring Boot](https://dunwu.github.io/spring-boot-tutorial/) 📚 +- [Mybatis](ecology/framework/mybatis.md) +- [ShardingSphere](ecology/storage/shardingsphere.md) ### 消息队列 -- [消息队列面经](technology/mq/MqInterview.md) -- [Kafka 应用指南基础篇](technology/mq/kafka-basic.md) -- [Kafka 应用指南进阶篇](technology/mq/kafka-advance.md) -- [Kafka 运维指南](technology/mq/kafka-ops.md) -- [RocketMQ 基础篇](technology/mq/RocketmqBasics.md) -- [RocketMQ 进阶篇](technology/mq/RocketmqAdvanced.md) -- [ActiveMQ 实战篇](technology/mq/ActiveMQ.md) +- [消息队列面经](ecology/mq/MqInterview.md) +- [Kafka 应用指南基础篇](ecology/mq/kafka-basic.md) +- [Kafka 应用指南进阶篇](ecology/mq/kafka-advance.md) +- [Kafka 运维指南](ecology/mq/kafka-ops.md) +- [RocketMQ 基础篇](ecology/mq/RocketmqBasics.md) +- [RocketMQ 进阶篇](ecology/mq/RocketmqAdvanced.md) +- [ActiveMQ 实战篇](ecology/mq/ActiveMQ.md) ### 缓存 -- [缓存面经](technology/cache/CacheInterview.md) -- [Redis](technology/cache/Redis.md) -- [Ehcache](technology/cache/Ehcache.md) -- [Caffeine](technology/cache/Caffeine.md) +- [缓存面经](ecology/cache/CacheInterview.md) +- [Redis](ecology/cache/Redis.md) +- [Ehcache](ecology/cache/Ehcache.md) +- [Caffeine](ecology/cache/Caffeine.md) + +### 安全 + +> Java 领域比较流行的安全框架就是 shiro 和 spring-security。 +> +> shiro 更为简单、轻便,容易理解,能满足大多数基本安全场景下的需要。 +> +> spring-security 功能更丰富,也比 shiro 更复杂。值得一提的是由于 spring-security 是 spring 团队开发,所以集成 spring 和 spring-boot 框架更容易。 + +- [shiro](ecology/security/shiro.md) +- [spring-security](ecology/security/spring-security.md) -### RPC +### 微服务 -- [Dubbo 应用指南](technology/distributed/dubbo.md) +- [Dubbo](ecology/microservices/dubbo.md) - Spring Cloud +- [ZooKeeper 应用指南](ecology/microservices/zookeeper.md) +- [ZooKeeper 运维指南](ecology/microservices/zookeeper-ops.md) -### 安全 +### 测试 + +- [Junit](ecology/test/junit.md) +- [Mockito](ecology/test/mockito.md) +- [JMH](ecology/test/jmh.md) + +### 服务器 + +> Tomcat 和 Jetty 都是 Java 比较流行的轻量级服务器。 +> +> Nginx 是目前最流行的反向代理服务器,也常用于负载均衡。 + +- [Tomcat](ecology/server/tomcat.md) +- [Jetty](ecology/server/jetty.md) +- [Nginx](https://github.com/dunwu/nginx-tutorial) 📚 -- [Shiro 应用指南](technology/security/shiro.md) -- [Spring Security 应用指南](technology/security/spring-security.md) +## Java 工具 -### 数据 +### 构建 -- [Mybatis 应用指南](technology/data/mybatis.md) -- [ShardingSphere 应用指南](technology/data/ShardingSphere.md) +> [构建工具](tool/build) -### 分布式 +- [Maven 教程 📚](tool/build/maven/README.md) +- [Maven 快速指南](tool/build/maven/maven-quickstart.md) +- [Maven 教程之 pom.xml 详解](tool/build/maven/maven-pom.md) +- [Maven 教程之 settings.xml 详解](tool/build/maven/maven-settings.md) +- [Maven 实战问题和最佳实践](tool/build/maven/maven-action.md) +- [Maven 教程之发布 jar 到私服或中央仓库](tool/build/maven/maven-deploy.md) +- [Maven 插件之代码检查](tool/build/maven/maven-checkstyle-plugin.md) +- [Ant 简易教程](tool/build/ant.md) -- [ZooKeeper 应用指南](technology/distributed/zookeeper.md) -- [ZooKeeper 运维指南](technology/distributed/zookeeper-ops.md) +### IDE -### 监控/诊断 +> [IDE](tool/ide) -- [Arthas 应用指南](technology/monitor/arthas.md) +- [Intellij Idea](tool/ide/intellij-idea.md) +- [Eclipse](tool/ide/eclipse.md) +- [vscode](tool/ide/vscode.md) -## ☕ JavaEE +## JavaEE > [☕ JavaEE](javaee/README.md) 技术是 Java Web 的基石 - [JavaEE 面经](javaee/javaee-interview.md) -- [JavaEE 之 Servlet 指南](javaee/javaee-servlet.md) -- [JavaEE 之 Jsp 指南](javaee/javaee-jsp.md) -- [JavaEE 之 Filter 和 Listener](javaee/javaee-filter-listener.md) -- [JavaEE 之 Cookie 和 Session](javaee/javaee-cookie-sesion.md) +- [Servlet](javaee/javaee-servlet.md) +- [Jsp](javaee/javaee-jsp.md) +- [Filter 和 Listener](javaee/javaee-filter-listener.md) +- [Cookie 和 Session](javaee/javaee-cookie-sesion.md) diff --git a/docs/book.json b/docs/book.json deleted file mode 100644 index c27d74f3..00000000 --- a/docs/book.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "gitbook": "3.2.2", - "title": "javacore", - "language": "zh-hans", - "root": "./", - "structure": { - "summary": "sidebar.md" - }, - "links": { - "sidebar": { - "javacore": "https://github.com/dunwu/javacore" - } - }, - "plugins": [ - "-lunr", - "-search", - "advanced-emoji@^0.2.2", - "anchor-navigation-ex@1.0.10", - "anchors@^0.7.1", - "edit-link@^2.0.2", - "expandable-chapters-small@^0.1.7", - "github@^2.0.0", - "search-plus@^0.0.11", - "simple-page-toc@^0.1.1", - "splitter@^0.0.8", - "tbfed-pagefooter@^0.0.1" - ], - "pluginsConfig": { - "anchor-navigation-ex": { - "showLevel": false, - "associatedWithSummary": true, - "multipleH1": true, - "mode": "float", - "isRewritePageTitle": false, - "float": { - "showLevelIcon": false, - "level1Icon": "fa fa-hand-o-right", - "level2Icon": "fa fa-hand-o-right", - "level3Icon": "fa fa-hand-o-right" - }, - "pageTop": { - "showLevelIcon": false, - "level1Icon": "fa fa-hand-o-right", - "level2Icon": "fa fa-hand-o-right", - "level3Icon": "fa fa-hand-o-right" - } - }, - "edit-link": { - "base": "https://github.com/dunwu/javacore/blob/master/docs", - "label": "编辑此页面" - }, - "github": { - "url": "https://github.com/dunwu" - }, - "simple-page-toc": { - "maxDepth": 4, - "skipFirstH1": true - }, - "sharing": { - "weibo": true, - "all": ["weibo"] - }, - "tbfed-pagefooter": { - "copyright": "Copyright © Zhang Peng 2017", - "modify_label": "该文件上次修订时间:", - "modify_format": "YYYY-MM-DD HH:mm:ss" - } - } -} diff --git a/docs/coverpage.md b/docs/coverpage.md deleted file mode 100644 index 821437aa..00000000 --- a/docs/coverpage.md +++ /dev/null @@ -1,9 +0,0 @@ -
    - -# JavaWeb - -> ☕ **JavaWeb** 是一个 Java Web 领域开发技术教程。 - -[Java 教程](https://dunwu.github.io/java-tutorial/) 🔹 [JavaCore 教程](https://dunwu.github.io/javacore/) 🔹 [JavaWeb 教程](https://dunwu.github.io/javaweb/) 🔹 [Spring 教程](https://dunwu.github.io/spring-tutorial/) 🔹 [Spring Boot 教程](https://dunwu.github.io/spring-boot-tutorial/) - -[开始阅读](README.md) diff --git a/docs/technology/README.md b/docs/ecology/README.md similarity index 100% rename from docs/technology/README.md rename to docs/ecology/README.md diff --git a/docs/technology/cache/CacheInterview.md b/docs/ecology/cache/CacheInterview.md similarity index 100% rename from docs/technology/cache/CacheInterview.md rename to docs/ecology/cache/CacheInterview.md diff --git a/docs/technology/cache/Caffeine.md b/docs/ecology/cache/Caffeine.md similarity index 100% rename from docs/technology/cache/Caffeine.md rename to docs/ecology/cache/Caffeine.md diff --git a/docs/technology/cache/Ehcache.md b/docs/ecology/cache/Ehcache.md similarity index 100% rename from docs/technology/cache/Ehcache.md rename to docs/ecology/cache/Ehcache.md diff --git a/docs/ecology/cache/README.md b/docs/ecology/cache/README.md new file mode 100644 index 00000000..f8cbfc22 --- /dev/null +++ b/docs/ecology/cache/README.md @@ -0,0 +1,6 @@ +# Java 和缓存 + +- [缓存面经](CacheInterview.md) +- [Redis](Redis.md) +- [Ehcache](Ehcache.md) +- [Caffeine](Caffeine.md) diff --git a/docs/technology/cache/Redis.md b/docs/ecology/cache/Redis.md similarity index 100% rename from docs/technology/cache/Redis.md rename to docs/ecology/cache/Redis.md diff --git a/docs/ecology/framework/README.md b/docs/ecology/framework/README.md new file mode 100644 index 00000000..b2e8c6f1 --- /dev/null +++ b/docs/ecology/framework/README.md @@ -0,0 +1,6 @@ +# Java 和框架 + +- [Spring](https://dunwu.github.io/spring-tutorial/) 📚 +- [Spring Boot](https://dunwu.github.io/spring-boot-tutorial/) 📚 +- [Mybatis](javatech/data/mybatis.md) +- [ShardingSphere](javatech/data/ShardingSphere.md) diff --git a/docs/technology/data/mybatis.md b/docs/ecology/framework/mybatis.md similarity index 100% rename from docs/technology/data/mybatis.md rename to docs/ecology/framework/mybatis.md diff --git a/docs/ecology/io/dozer.md b/docs/ecology/io/dozer.md new file mode 100644 index 00000000..154d3329 --- /dev/null +++ b/docs/ecology/io/dozer.md @@ -0,0 +1,793 @@ +# Dozer 应用指南 + +这篇文章是本人在阅读 Dozer 官方文档(5.5.1 版本,官网已经一年多没更新了)的过程中,整理下来我认为比较基础的应用场景。 + +本文中提到的例子应该能覆盖 JavaBean 映射的大部分场景,希望对你有所帮助。 + + + +- [简介](#简介) +- [安装](#安装) + - [引入 jar 包](#引入-jar-包) + - [Eclipse 插件](#eclipse-插件) +- [使用](#使用) + - [准备](#准备) + - [Dozer 的配置](#dozer-的配置) + - [与 Spring 整合](#与-spring-整合) +- [Dozer 支持的数据类型转换](#dozer-支持的数据类型转换) +- [Dozer 的映射配置](#dozer-的映射配置) + - [用注解来配置映射](#用注解来配置映射) + - [用 API 来配置映射](#用-api-来配置映射) + - [用 XML 来配置映射](#用-xml-来配置映射) +- [参考](#参考) + + + +## 简介 + +**Dozer 是什么?** + +**Dozer 是一个 JavaBean 映射工具库。** + +它支持简单的属性映射,复杂类型映射,双向映射,隐式显式的映射,以及递归映射。 + +它支持三种映射方式:注解、API、XML。 + +它是开源的,遵从[Apache 2.0 协议](http://www.apache.org/licenses/LICENSE-2.0) + +## 安装 + +### 引入 jar 包 + +**maven 方式** + +如果你的项目使用 maven,添加以下依赖到你的 pom.xml 即可: + +```xml + + net.sf.dozer + dozer + 5.4.0 + +``` + +**非 maven 方式** + +如果你的项目不使用 maven,那就只能发扬不怕苦不怕累的精神了。 + +使用 Dozer 需要引入 Dozer 的 jar 包以及其依赖的第三方 jar 包。 + +- [Dozer](http://sourceforge.net/project/showfiles.php?group_id=133517) +- [Dozer 依赖的第三方 jar 包](http://dozer.sourceforge.net/dependencies.html) + +### Eclipse 插件 + +Dozer 有插件可以在 Eclipse 中使用(不知道是否好用,反正我没用过) + +插件地址: http://dozer.sourceforge.net/eclipse-plugin + +## 使用 + +将 Dozer 引入到工程中后,我们就可以来小试一番了。 + +实践出真知,先以一个最简单的例子来展示 Dozer 映射的处理过程。 + +### 准备 + +我们先准备两个要互相映射的类 + +NotSameAttributeA.java + +```java +public class NotSameAttributeA { + private long id; + private String name; + private Date date; + + // 省略getter/setter +} +``` + +NotSameAttributeB.java + +```java +public class NotSameAttributeB { + private long id; + private String value; + private Date date; + + // 省略getter/setter +} +``` + +这两个类存在属性名不完全相同的情况:name 和 value。 + +### Dozer 的配置 + +#### 为什么要有映射配置? + +如果要映射的两个对象有完全相同的属性名,那么一切都很简单。 + +只需要直接使用 Dozer 的 API 即可: + +```java +Mapper mapper = new DozerBeanMapper(); +DestinationObject destObject = + mapper.map(sourceObject, DestinationObject.class); +``` + +但实际映射时,往往存在属性名不同的情况。 + +所以,你需要一些配置来告诉 Dozer 应该转换什么,怎么转换。 + +**_注:官网着重建议:在现实应用中,最好不要每次映射对象时都创建一个`Mapper`实例来工作,这样会产生不必要的开销。如果你不使用 IoC 容器(如:spring)来管理你的项目,那么,最好将`Mapper`定义为单例模式。_** + +#### 映射配置文件 + +在`src/test/resources`目录下添加`dozer/dozer-mapping.xml`文件。 +``标签中允许你定义``和``,对应着相互映射的类。 +``标签里定义要映射的特殊属性。需要注意``和``对应,``和``对应,聪明的你,猜也猜出来了吧。 + +```xml + + + + org.zp.notes.spring.common.dozer.vo.NotSameAttributeA + org.zp.notes.spring.common.dozer.vo.NotSameAttributeB + + name + value + + + +``` + +### 与 Spring 整合 + +#### 配置 DozerBeanMapperFactoryBean + +在`src/test/resources`目录下添加`spring/spring-dozer.xml`文件。 + +Dozer 与 Spring 的整合很便利,你只需要声明一个`DozerBeanMapperFactoryBean`, +将所有的 dozer 映射配置文件作为属性注入到`mappingFiles`, +`DozerBeanMapperFactoryBean`会加载这些规则。 + +spring-dozer.xml 文件范例 + +```xml + + + + + + + classpath*:dozer/dozer-mapping.xml + + + + +``` + +#### 自动装配 + +至此,万事具备,你只需要自动装配`mapper`。 + +```java +RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = {"classpath:spring/spring-dozer.xml"}) +@TransactionConfiguration(defaultRollback = false) +public class DozerTest extends TestCase { + @Autowired + Mapper mapper; + + @Test + public void testNotSameAttributeMapping() { + NotSameAttributeA src = new NotSameAttributeA(); + src.setId(007); + src.setName("邦德"); + src.setDate(new Date()); + + NotSameAttributeB desc = mapper.map(src, NotSameAttributeB.class); + Assert.assertNotNull(desc); + } +} +``` + +运行一下单元测试,绿灯通过。 + +## Dozer 支持的数据类型转换 + +Dozer 可以自动做数据类型转换。当前,Dozer 支持以下数据类型转换(都是双向的) + +- **Primitive to Primitive Wrapper** + + 原型(int、long 等)和原型包装类(Integer、Long) + +- **Primitive to Custom Wrapper** + + 原型和定制的包装 + +- **Primitive Wrapper to Primitive Wrapper** + + 原型包装类和包装类 + +- **Primitive to Primitive** + + 原型和原型 + +- **Complex Type to Complex Type** + + 复杂类型和复杂类型 + +- **String to Primitive** + + 字符串和原型 + +- **String to Primitive Wrapper** + + 字符串和原型包装类 + +- **String to Complex Type if the Complex Type contains a String constructor** + + 字符串和有字符串构造器的复杂类型(类) + +- **String to Map** + + 字符串和 Map + +- **Collection to Collection** + + 集合和集合 + +- **Collection to Array** + + 集合和数组 + +- **Map to Complex Type** + + Map 和复杂类型 + +- **Map to Custom Map Type** + + Map 和定制 Map 类型 + +- **Enum to Enum** + + 枚举和枚举 + +- **Each of these can be mapped to one another: java.util.Date, java.sql.Date, java.sql.Time, java.sql.Timestamp, java.util.Calendar, java.util.GregorianCalendar** + + 这些时间相关的常见类可以互换:java.util.Date, java.sql.Date, java.sql.Time, java.sql.Timestamp, java.util.Calendar, java.util.GregorianCalendar + +- **String to any of the supported Date/Calendar Objects.** + + 字符串和支持 Date/Calendar 的对象 + +- **Objects containing a toString() method that produces a long representing time in (ms) to any supported Date/Calendar object. ** + + 如果一个对象的 toString()方法返回的是一个代表 long 型的时间数值(单位:ms),就可以和任何支持 Date/Calendar 的对象转换。 + +## Dozer 的映射配置 + +在前面的简单例子中,我们体验了一把 Dozer 的映射流程。但是两个类进行映射,有很多复杂的情况,相应的,你也需要一些更复杂的配置。 + +Dozer 有三种映射配置方式: + +- **注解方式** +- **API 方式** +- **XML 方式** + +### 用注解来配置映射 + +**Dozer 5.3.2**版本开始支持注解方式配置映射(只有一个注解:`@Mapping`)。可以应对一些简单的映射处理,复杂的就玩不转了。 + +看一下`@Mapping`的声明就可以知道,这个注解只能用于元素和方法。 + +```java +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.METHOD}) +public @interface Mapping { + String value() default ""; +} +``` + +让我们来试试吧: + +TargetBean.java + +```java +public class SourceBean { + + private Long id; + + private String name; + + @Mapping("binaryData") + private String data; + + @Mapping("pk") + public Long getId() { + return this.id; + } + + //其余getter/setter方法略 +} +``` + +TargetBean.java + +```java +public class TargetBean { + + private String pk; + + private String name; + + private String binaryData; + + //getter/setter方法略 +} +``` + +定义了两个相互映射的 Java 类,只需要在源类中用`@Mapping`标记和目标类中对应的属性就可以了。 + +```java +@Test +public void testAnnotationMapping() { + SourceBean src = new SourceBean(); + src.setId(7L); + src.setName("邦德"); + src.setData("00000111"); + + TargetBean desc = mapper.map(src, TargetBean.class); + Assert.assertNotNull(desc); +} +``` + +测试一下,绿灯通过。 + +官方文档说,虽然当前版本(文档的版本对应 Dozer 5.5.1)仅支持`@Mapping`,但是在未来的发布版本会提供其他的注解功能,那就敬请期待吧(再次吐槽一下:一年多没更新了)。 + +### 用 API 来配置映射 + +个人觉得这种方式比较麻烦,不推荐,也不想多做介绍,就是这么任性。 + +### 用 XML 来配置映射 + +需要**强调**的是:如果两个类的所有属性都能很好的互转,可以你中有我,我中有你,不分彼此,那么就不要画蛇添足的在 xml 中去声明映射规则了。 + +#### 属性名不同时的映射(Basic Property Mapping) + +**Dozer 会自动映射属性名相同的属性,所以不必添加在 xml 文件中。** + +```xml + + one + onePrime + +``` + +#### 字符串和日期映射(String to Date Mapping) + +字符串在和日期进行映射时,允许用户指定日期的格式。 + +格式的设置分为三个作用域级别: + +**属性级别** + +对当前属性有效(这个属性必须是日期字符串) + +```xml + + dateString + dateObject + +``` + +**类级别** + +对这个类中的所有日期相关的属性有效 + +```xml + + org.dozer.vo.TestObject + org.dozer.vo.TestObjectPrime + + dateString + dateObject + + +``` + +**全局级别** + +对整个文件中的所有日期相关的属性有效。 + +```xml + + + MM/dd/yyyy HH:mm + + + + org.dozer.vo.TestObject + org.dozer.vo.TestObjectPrime + + dateString + dateObject + + + +``` + +#### 集合和数组映射(Collection and Array Mapping) + +Dozer 可以自动处理以下类型的双向转换。 + +- List to List +- List to Array +- Array to Array +- Set to Set +- Set to Array +- Set to List + +**使用 hint** + +如果使用泛型或数组,没有必要使用 hint。 + +如果不使用泛型或数组。在处理集合或数组之间的转换时,你需要用`hint`指定目标列表的数据类型。 + +若你不指定`hint`,Dozer 将认为目标集合和源集合的类型是一致的。 + +使用 Hints 的范例: + +```xml + + hintList + hintList + org.dozer.vo.TheFirstSubClassPrime + +``` + +**累计映射和非累计映射(Cumulative vs. Non-Cumulative List Mapping)** + +如果你要转换的目标类已经初始化,你可以选择让 Dozer 添加或更新对象到你的集合中。 + +而这取决于`relationship-type`配置,默认是累计。 + +它的设置有作用域级别: + +- 全局级 + +``` + + + non-cumulative + + +``` + +- 类级别 + +```xml + + + + + +``` + +- 属性级别 + +```xml + + hintList + hintList + org.dozer.vo.TheFirstSubClass + org.dozer.vo.TheFirstSubClassPrime + +``` + +**移动孤儿(Removing Orphans)** + +这里的孤儿是指目标集合中存在,但是源集合中不存在的元素。 + +你可以使用`remove-orphans`开关来选择是否移除这样的元素。 + +```xml + + srcList + destList + +``` + +#### 深度映射(Deep Mapping) + +所谓深度映射,是指允许你指定属性的属性(比如一个类的属性本身也是一个类)。举例来说 + +Source.java + +```java +public class Source { + private long id; + private String info; +} +``` + +Dest.java + +```java +public class Dest { + private long id; + private Info info; +} +``` + +```java +public class Info { + private String content; +} +``` + +映射规则 + +```xml + + org.zp.notes.spring.common.dozer.vo.Source + org.zp.notes.spring.common.dozer.vo.Dest + + info + info.content + + +``` + +#### 排除属性(Excluding Fields) + +就像任何团体都有捣乱分子,类之间转换时也有想要排除的因子。 + +如何在做类型转换时,自动排除一些属性,Dozer 提供了几种方法,这里只介绍一种比较通用的方法。 + +更多详情参考[官网](http://dozer.sourceforge.net/documentation/exclude.html)。 + +field-exclude 可以排除不需要映射的属性。 + +```xml + + fieldToExclude + fieldToExclude + +``` + +#### 单向映射(One-Way Mapping) + +**_注:本文的映射方式,无特殊说明,都是双向映射的。_** + +有的场景可能希望转换过程不可逆,即单向转换。 + +单向转换可以通过使用`one-way`来开启 + +类级别 + +```xml + + org.dozer.vo.TestObjectFoo + org.dozer.vo.TestObjectFooPrime + + oneFoo + oneFooPrime + + +``` + +属性级别 + +```xml + + org.dozer.vo.TestObjectFoo2 + org.dozer.vo.TestObjectFooPrime2 + + oneFoo2 + oneFooPrime2 + + + + oneFoo3.prime + oneFooPrime3 + +``` + +#### 全局配置(Global Configuration) + +全局配置用来设置全局的配置信息。此外,任何定制转换都是在这里定义的。 + +全局配置都是可选的。 + +- ``表示日期格式 +- ``错误处理开关 +- ``通配符 +- ``裁剪字符串开关 + +```xml + + + MM/dd/yyyy HH:mm + true + true + false + + + + org.dozer.vo.TestCustomConverterObject + another.type.to.Associate + + + + +``` + +全局配置的作用是帮助你少配置一些参数,如果个别类的映射规则需要变更,你可以 mapping 中覆盖它。 + +覆盖的范例如下 + +```xml + + + + + + + + + + + + + + + +``` + +#### 定制转换(Custom Converters) + +如果 Dozer 默认的转换规则不能满足实际需要,你可以选择定制转换。 + +定制转换通过配置 XML 来告诉 Dozer 如何去转换两个指定的类。当 Dozer 转换这两个指定类的时候,会调用你的映射规则去替换标准映射规则。 + +为了让 Dozer 识别,你必须实现`org.dozer.CustomConverter`接口。否则,Dozer 会抛异常。 + +具体做法: + +(1) 创建一个类实现`org.dozer.CustomConverter`接口。 + +```java +public class TestCustomConverter implements CustomConverter { + + public Object convert(Object destination, Object source, + Class destClass, Class sourceClass) { + if (source == null) { + return null; + } + CustomDoubleObject dest = null; + if (source instanceof Double) { + // check to see if the object already exists + if (destination == null) { + dest = new CustomDoubleObject(); + } else { + dest = (CustomDoubleObject) destination; + } + dest.setTheDouble(((Double) source).doubleValue()); + return dest; + } else if (source instanceof CustomDoubleObject) { + double sourceObj = + ((CustomDoubleObject) source).getTheDouble(); + return new Double(sourceObj); + } else { + throw new MappingException("Converter TestCustomConverter " + + "used incorrectly. Arguments passed in were:" + + destination + " and " + source); + } + } +``` + +(2) 在 xml 中引用定制的映射规则 + +引用定制的映射规则也是分级的,你可以酌情使用。 + +- 全局级 + +```xml + + + + + + + org.dozer.vo.CustomDoubleObject + java.lang.Double + + + + + org.dozer.vo.TestCustomConverterHashMapObject + org.dozer.vo.TestCustomConverterHashMapPrimeObject + + + + +``` + +- 属性级 + +```xml + + org.dozer.vo.SimpleObj + org.dozer.vo.SimpleObjPrime2 + + field1 + field1Prime + + +``` + +#### 映射的继承(Inheritance Mapping) + +Dozer 支持映射规则的继承机制。 + +属性如果有着相同的名字则不需要在 xml 中配置,除非使用了`hint` + +我们来看一个例子 + +```xml + + org.dozer.vo.SuperClass + org.dozer.vo.SuperClassPrime + + + superAttribute + superAttr + + + + + org.dozer.vo.SubClass + org.dozer.vo.SubClassPrime + + + attribute + attributePrime + + + + + org.dozer.vo.SubClass2 + org.dozer.vo.SubClassPrime2 + + + attribute2 + attributePrime2 + + +``` + +在上面的例子中 SubClass、SubClass2 是 SuperClass 的子类; + +SubClassPrime 和 SubClassPrime2 是 SuperClassPrime 的子类。 + +superAttribute 和 superAttr 的映射规则会被子类所继承,所以不必再重复的在子类中去声明。 + +## 参考 + +[Dozer 官方文档](http://dozer.sourceforge.net/documentation/gettingstarted.html) | [Dozer 源码地址](https://github.com/DozerMapper/dozer) diff --git a/docs/ecology/io/javalib-binary.md b/docs/ecology/io/javalib-binary.md new file mode 100644 index 00000000..6374a93b --- /dev/null +++ b/docs/ecology/io/javalib-binary.md @@ -0,0 +1,338 @@ +# Java 二进制序列化库 + +## 简介 + +### 为什么需要二进制序列化库 + +原因很简单,就是 Java 默认的序列化机制(`ObjectInputStream` 和 `ObjectOutputStream`)具有很多缺点。 + +> 不了解 Java 默认的序列化机制,可以参考:[Java 序列化](https://github.com/dunwu/javacore/blob/master/docs/io/Java序列化.md) + +Java 自身的序列化方式具有以下缺点: + +- **无法跨语言使用 **。这点最为致命,对于很多需要跨语言通信的异构系统来说,不能跨语言序列化,即意味着完全无法通信(彼此数据不能识别,当然无法交互了)。 +- **序列化的性能不高**。序列化后的数据体积较大,这大大影响存储和传输的效率。 +- 序列化一定需要实现 `Serializable` 接口。 +- 需要关注 `serialVersionUID`。 + +引入二进制序列化库就是为了解决这些问题,这在 RPC 应用中尤为常见。 + +### 主流序列化库简介 + +#### Protobuf + +[Protobuf](https://developers.google.com/protocol-buffers/) 是 Google 开发的结构序列化库。 + +它具有以下特性: + +- 结构化数据存储格式(xml,json 等) +- 高性能编解码技术 +- 语言和平台无关,扩展性好 +- 支持 Java, C++, Python 三种语言 + +#### Thrift + +> [Thrift](https://github.com/apache/thrift) 是 apache 开源项目,是一个点对点的 RPC 实现。 + +它具有以下特性: + +- 支持多种语言(目前支持 28 种语言,如:C++、go、Java、Php、Python、Ruby 等等)。 +- 使用了组建大型数据交换及存储工具,对于大型系统中的内部数据传输,相对于 Json 和 xml 在性能上和传输大小上都有明显的优势。 +- 支持三种比较典型的编码方式(通用二进制编码,压缩二进制编码,优化的可选字段压缩编解码)。 + +#### Hessian + +> [Hessian](http://hessian.caucho.com/) 是一种二进制传输协议。 +> +> RPC 框架 Dubbo 就支持 Thrift 和 Hession。 + +它具有以下特性: + +- 支持多种语言。如:Java、Python、C++、C#、PHP、Ruby 等。 +- 相对其他二进制序列化库较慢。 + +#### Kryo + +> [Kryo](https://github.com/EsotericSoftware/kryo) 是用于 Java 的快速高效的二进制对象图序列化框架。Kryo 还可以执行自动的深拷贝和浅拷贝。 这是从对象到对象的直接复制,而不是从对象到字节的复制。 + +它具有以下特性: + +- 速度快,序列化体积小 +- 官方不支持 Java 以外的其他语言 + +#### FST + +> [FST](https://github.com/RuedigerMoeller/fast-serialization) 是一个 Java 实现二进制序列化库。 + +它具有以下特性: + +- 近乎于 100% 兼容 JDK 序列化,且比 JDK 原序列化方式快 10 倍 +- 2.17 开始与 Android 兼容 +- (可选)2.29 开始支持将任何可序列化的对象图编码/解码为 JSON(包括共享引用) + +#### 小结 + +了解了以上这些常见的二进制序列化库的特性。在技术选型时,我们就可以做到有的放矢。 + +**(1)选型参考依据** + +对于二进制序列化库,我们的选型考量一般有以下几点: + +- **是否支持跨语言** + - 根据业务实际需求来决定。一般来说,支持跨语言,为了兼容,使用复杂度上一般会更高一些。 +- **序列化、反序列化的性能** +- **类库是否轻量化,API 是否简单易懂** + +**(2)选型建议** + +- 如果需要跨语言通信,那么可以考虑:Protobuf、Thrift、Hession。 + + - [thrift](https://github.com/apache/thrift)、[protobuf](https://github.com/protocolbuffers/protobuf) - 适用于对性能敏感,对开发体验要求不高的内部系统。 + - [hessian](http://hessian.caucho.com/doc/hessian-overview.xtp) - 适用于对开发体验敏感,性能有要求的内外部系统。 + +- 如果不需要跨语言通信,可以考虑:[Kryo](https://github.com/EsotericSoftware/kryo) 和 [FST](https://github.com/RuedigerMoeller/fast-serialization),性能不错,且 API 十分简单。 + +## FST 应用 + +### 引入依赖 + +```xml + + de.ruedigermoeller + fst + 2.56 + +``` + +### FST API + +示例: + +```java +import org.nustaq.serialization.FSTConfiguration; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +public class FstDemo { + + private static FSTConfiguration DEFAULT_CONFIG = FSTConfiguration.createDefaultConfiguration(); + + /** + * 将对象序列化为 byte 数组 + * + * @param obj 任意对象 + * @param 对象的类型 + * @return 序列化后的 byte 数组 + */ + public static byte[] writeToBytes(T obj) { + return DEFAULT_CONFIG.asByteArray(obj); + } + + /** + * 将对象序列化为 byte 数组后,再使用 Base64 编码 + * + * @param obj 任意对象 + * @param 对象的类型 + * @return 序列化后的字符串 + */ + public static String writeToString(T obj) { + byte[] bytes = writeToBytes(obj); + return new String(Base64.getEncoder().encode(bytes), StandardCharsets.UTF_8); + } + + /** + * 将 byte 数组反序列化为原对象 + * + * @param bytes {@link #writeToBytes} 方法序列化后的 byte 数组 + * @param clazz 原对象的类型 + * @param 原对象的类型 + * @return 原对象 + */ + public static T readFromBytes(byte[] bytes, Class clazz) throws IOException { + Object obj = DEFAULT_CONFIG.asObject(bytes); + if (clazz.isInstance(obj)) { + return (T) obj; + } else { + throw new IOException("derialize failed"); + } + } + + /** + * 将字符串反序列化为原对象,先使用 Base64 解码 + * + * @param str {@link #writeToString} 方法序列化后的字符串 + * @param clazz 原对象的类型 + * @param 原对象的类型 + * @return 原对象 + */ + public static T readFromString(String str, Class clazz) throws IOException { + byte[] bytes = str.getBytes(StandardCharsets.UTF_8); + return readFromBytes(Base64.getDecoder().decode(bytes), clazz); + } + +} +``` + +测试: + +```java +long begin = System.currentTimeMillis(); +for (int i = 0; i < BATCH_SIZE; i++) { + TestBean oldBean = BeanUtils.initJdk8Bean(); + byte[] bytes = FstDemo.writeToBytes(oldBean); + TestBean newBean = FstDemo.readFromBytes(bytes, TestBean.class); +} +long end = System.currentTimeMillis(); +System.out.printf("FST 序列化/反序列化耗时:%s", (end - begin)); +``` + +## Kryo 应用 + +### 引入依赖 + +```xml + + com.esotericsoftware + kryo + 5.0.0-RC4 + +``` + +### Kryo API + +示例: + +```java +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; +import com.esotericsoftware.kryo.util.DefaultInstantiatorStrategy; +import org.objenesis.strategy.StdInstantiatorStrategy; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +public class KryoDemo { + + // 每个线程的 Kryo 实例 + private static final ThreadLocal kryoLocal = ThreadLocal.withInitial(() -> { + Kryo kryo = new Kryo(); + + /** + * 不要轻易改变这里的配置!更改之后,序列化的格式就会发生变化, + * 上线的同时就必须清除 Redis 里的所有缓存, + * 否则那些缓存再回来反序列化的时候,就会报错 + */ + //支持对象循环引用(否则会栈溢出) + kryo.setReferences(true); //默认值就是 true,添加此行的目的是为了提醒维护者,不要改变这个配置 + + //不强制要求注册类(注册行为无法保证多个 JVM 内同一个类的注册编号相同;而且业务系统中大量的 Class 也难以一一注册) + kryo.setRegistrationRequired(false); //默认值就是 false,添加此行的目的是为了提醒维护者,不要改变这个配置 + + //Fix the NPE bug when deserializing Collections. + ((DefaultInstantiatorStrategy) kryo.getInstantiatorStrategy()) + .setFallbackInstantiatorStrategy(new StdInstantiatorStrategy()); + + return kryo; + }); + + /** + * 获得当前线程的 Kryo 实例 + * + * @return 当前线程的 Kryo 实例 + */ + public static Kryo getInstance() { + return kryoLocal.get(); + } + + /** + * 将对象序列化为 byte 数组 + * + * @param obj 任意对象 + * @param 对象的类型 + * @return 序列化后的 byte 数组 + */ + public static byte[] writeToBytes(T obj) { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + Output output = new Output(byteArrayOutputStream); + + Kryo kryo = getInstance(); + kryo.writeObject(output, obj); + output.flush(); + + return byteArrayOutputStream.toByteArray(); + } + + /** + * 将对象序列化为 byte 数组后,再使用 Base64 编码 + * + * @param obj 任意对象 + * @param 对象的类型 + * @return 序列化后的字符串 + */ + public static String writeToString(T obj) { + byte[] bytes = writeToBytes(obj); + return new String(Base64.getEncoder().encode(bytes), StandardCharsets.UTF_8); + } + + /** + * 将 byte 数组反序列化为原对象 + * + * @param bytes {@link #writeToBytes} 方法序列化后的 byte 数组 + * @param clazz 原对象的类型 + * @param 原对象的类型 + * @return 原对象 + */ + @SuppressWarnings("unchecked") + public static T readFromBytes(byte[] bytes, Class clazz) { + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); + Input input = new Input(byteArrayInputStream); + + Kryo kryo = getInstance(); + return (T) kryo.readObject(input, clazz); + } + + /** + * 将字符串反序列化为原对象,先使用 Base64 解码 + * + * @param str {@link #writeToString} 方法序列化后的字符串 + * @param clazz 原对象的类型 + * @param 原对象的类型 + * @return 原对象 + */ + public static T readFromString(String str, Class clazz) { + byte[] bytes = str.getBytes(StandardCharsets.UTF_8); + return readFromBytes(Base64.getDecoder().decode(bytes), clazz); + } + +} +``` + +测试: + +```java +long begin = System.currentTimeMillis(); +for (int i = 0; i < BATCH_SIZE; i++) { + TestBean oldBean = BeanUtils.initJdk8Bean(); + byte[] bytes = KryoDemo.writeToBytes(oldBean); + TestBean newBean = KryoDemo.readFromBytes(bytes, TestBean.class); +} +long end = System.currentTimeMillis(); +System.out.printf("Kryo 序列化/反序列化耗时:%s", (end - begin)); +``` + +## 参考资料 + +- **官方** + - [Protobuf 官网](https://developers.google.com/protocol-buffers/) + - [Protobuf Github](https://github.com/protocolbuffers/protobuf) + - [Thrift Github](https://github.com/apache/thrift) + - [Kryo Github](https://github.com/EsotericSoftware/kryo) + - [Hessian 官网](http://hessian.caucho.com/) + - [FST Github](https://github.com/RuedigerMoeller/fast-serialization) +- **文章** + - [java 序列化框架对比](https://www.jianshu.com/p/937883b6b2e5) diff --git a/docs/ecology/io/javalib-json.md b/docs/ecology/io/javalib-json.md new file mode 100644 index 00000000..62d2f9d1 --- /dev/null +++ b/docs/ecology/io/javalib-json.md @@ -0,0 +1,551 @@ +# Java 与 JSON + +> JSON(JavaScript Object Notation)是一种基于文本的数据交换格式。几乎所有的编程语言都有很好的库或第三方工具来提供基于 JSON 的 API 支持,因此你可以非常方便地使用任何自己喜欢的编程语言来处理 JSON 数据。 +> +> 本文主要从 Java 语言的角度来讲解 JSON 的应用。 + + + +- [1. JSON 简介](#1-json-简介) + - [JSON 是什么](#json-是什么) + - [优缺点、标准与 schema](#优缺点标准与-schema) + - [相关技术以及与 XML 的关系](#相关技术以及与-xml-的关系) + - [工具](#工具) + - [1.2. Java JSON 库](#12-java-json-库) +- [JSON 编码指南](#json-编码指南) + - [4.1 Google JSON 风格指南](#41-google-json-风格指南) + - [4.2 使用 JSON 实现 API](#42-使用-json-实现-api) +- [2. Fastjson 应用](#2-fastjson-应用) + - [2.1. 添加 maven 依赖](#21-添加-maven-依赖) + - [2.2. Fastjson API](#22-fastjson-api) + - [2.3. Fastjson 注解](#23-fastjson-注解) +- [3. Jackson 应用](#3-jackson-应用) + - [3.1. 添加 maven 依赖](#31-添加-maven-依赖) + - [3.2. Jackson API](#32-jackson-api) + - [3.3. Jackson 注解](#33-jackson-注解) +- [4. Gson 应用](#4-gson-应用) + - [4.1. 添加 maven 依赖](#41-添加-maven-依赖) + - [4.2. Gson API](#42-gson-api) + - [4.3. Gson 注解](#43-gson-注解) +- [5. 示例源码](#5-示例源码) +- [6. 参考资料](#6-参考资料) + + + +## 1. JSON 简介 + +### JSON 是什么 + +JSON 起源于 1999 年的 [JS 语言规范 ECMA262 的一个子集](http://javascript.crockford.com/)(即 15.12 章节描述了格式与解析),后来 2003 年作为一个数据格式[ECMA404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf)(很囧的序号有不有?)发布。 +2006 年,作为 [rfc4627](http://www.ietf.org/rfc/rfc4627.txt) 发布,这时规范增加到 18 页,去掉没用的部分,十页不到。 + +JSON 的应用很广泛,这里有超过 100 种语言下的 JSON 库:[json.org](http://www.json.org/)。 + +更多的可以参考这里,[关于 json 的一切](https://github.com/burningtree/awesome-json)。 + +### 优缺点、标准与 schema + +#### 结构与类型 + +这估计是最简单标准规范之一: + +- 只有两种结构:对象内的键值对集合结构和数组,对象用 `{}` 表示、内部是”key”:”value”,数组用 `[]` 表示,不同值用逗号分开 +- 基本数值有 7 个: false / null / true / object / array / number / string +- 再加上结构可以嵌套,进而可以用来表达复杂的数据 +- 一个简单实例: + +```json +{ + "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] + } +} +``` + +> 扩展阅读: +> +> - http://www.json.org/json-zh.html - 图文并茂介绍 json 数据形式 +> +> - [json 的 RFC 文档](http://tools.ietf.org/html/rfc4627) + +#### 2.2 优点 + +- 基于纯文本,所以对于人类阅读是很友好的。 +- 规范简单,所以容易处理,开箱即用,特别是 JS 类的 ECMA 脚本里是内建支持的,可以直接作为对象使用。 +- 平台无关性,因为类型和结构都是平台无关的,而且好处理,容易实现不同语言的处理类库,可以作为多个不同异构系统之间的数据传输格式协议,特别是在 HTTP/REST 下的数据格式。 + +#### 2.3 缺点 + +缺点也很明显: + +- 性能一般,文本表示的数据一般来说比二进制大得多,在数据传输上和解析处理上都要更影响性能。 +- 缺乏 schema,跟同是文本数据格式的 XML 比,在类型的严格性和丰富性上要差很多。XML 可以借由 XSD 或 DTD 来定义复杂的格式,并由此来验证 XML 文档是否符合格式要求,甚至进一步的,可以基于 XSD 来生成具体语言的操作代码,例如 apache xmlbeans。并且这些工具组合到一起,形成一套庞大的生态,例如基于 XML 可以实现 SOAP 和 WSDL,一系列的 ws-\*规范。但是我们也可以看到 JSON 在缺乏规范的情况下,实际上有更大一些的灵活性,特别是近年来 REST 的快速发展,已经有一些 schema 相关的发展(例如[理解 JSON Schema](https://spacetelescope.github.io/understanding-json-schema/index.html),[使用 JSON Schema](http://usingjsonschema.com/downloads/), [在线 schema 测试](http://azimi.me/json-schema-view/demo/demo.html)),也有类似于 WSDL 的[WADL](https://www.w3.org/Submission/wadl/)出现。 + +### 相关技术以及与 XML 的关系 + +- 使用 JSON 实现 RPC(类似 XML-RPC):[JSON-RPC](http://www.jsonrpc.org/) +- 使用 JSON 实现 path 查询操作(类似 XML-PATH):[JsonPATH](https://github.com/json-path/JsonPath) +- 在线查询工具:[JsonPATH](http://jsonpath.com/) + +### 工具 + +- 格式化工具:[jsbeautifier](http://jsbeautifier.org/) +- chrome 插件:[5 个 Json View 插件](http://www.cnplugins.com/zhuanti/five-chrome-json-plugins.html) +- 在线 Mock: [在线 mock](https://www.easy-mock.com/) +- 其他 Mock:[SoapUI](https://www.soapui.org/rest-testing-mocking/rest-service-mocking.html)可以支持,SwaggerUI 也可以,[RestMock](https://github.com/andrzejchm/RESTMock)也可以。 + +### 1.2. Java JSON 库 + +Java 中比较流行的 JSON 库有: + +- [Fastjson](https://github.com/alibaba/fastjson) - 阿里巴巴开发的 JSON 库,性能十分优秀。 +- [Jackson](http://wiki.fasterxml.com/JacksonHome) - 社区十分活跃且更新速度很快。Spring 框架默认 JSON 库。 +- [Gson](https://github.com/google/gson) - 谷歌开发的 JSON 库,目前功能最全的 JSON 库 。 + +从性能上来看,一般情况下:Fastjson > Jackson > Gson + +## JSON 编码指南 + +### 4.1 Google JSON 风格指南 + +遵循好的设计与编码风格,能提前解决 80%的问题: + +- 英文版[Google JSON Style Guide](https://google.github.io/styleguide/jsoncstyleguide.xml):https://google.github.io/styleguide/jsoncstyleguide.xml +- 中文版[Google JSON 风格指南](https://github.com/darcyliu/google-styleguide/blob/master/JSONStyleGuide.md):https://github.com/darcyliu/google-styleguide/blob/master/JSONStyleGuide.md + +简单摘录如下: + +- 属性名和值都是用双引号,不要把注释写到对象里面,对象数据要简洁 +- 不要随意结构化分组对象,推荐是用扁平化方式,层次不要太复杂 +- 命名方式要有意义,比如单复数表示 +- 驼峰式命名,遵循 Bean 规范 +- 使用版本来控制变更冲突 +- 对于一些关键字,不要拿来做 key +- 如果一个属性是可选的或者包含空值或 null 值,考虑从 JSON 中去掉该属性,除非它的存在有很强的语义原因 +- 序列化枚举类型时,使用 name 而不是 value +- 日期要用标准格式处理 +- 设计好通用的分页参数 +- 设计好异常处理 + +### 4.2 使用 JSON 实现 API + +[JSON API](http://jsonapi.org.cn/format/)与 Google JSON 风格指南有很多可以相互参照之处。 + +[JSON API](http://jsonapi.org.cn/format/)是数据交互规范,用以定义客户端如何获取与修改资源,以及服务器如何响应对应请求。 + +JSON API 设计用来最小化请求的数量,以及客户端与服务器间传输的数据量。在高效实现的同时,无需牺牲可读性、灵活性和可发现性。 + +## 2. Fastjson 应用 + +> 扩展阅读:更多 API 使用细节可以参考:[JSONField 用法](https://github.com/alibaba/fastjson/wiki/JSONField) + +### 2.1. 添加 maven 依赖 + +```xml + + com.alibaba + fastjson + x.x.x + +``` + +### 2.2. Fastjson API + +#### 定义 Bean + +**Group.java** + +```java +public class Group { + + private Long id; + private String name; + private List users = new ArrayList(); + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getUsers() { + return users; + } + + public void setUsers(List users) { + this.users = users; + } + + public void addUser(User user) { + users.add(user); + } +} +``` + +**User.java** + +```java +public class User { + + private Long id; + private String name; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} +``` + +**初始化 Bean** + +```java +Group group = new Group(); +group.setId(0L); +group.setName("admin"); + +User guestUser = new User(); +guestUser.setId(2L); +guestUser.setName("guest"); + +User rootUser = new User(); +rootUser.setId(3L); +rootUser.setName("root"); + +group.addUser(guestUser); +group.addUser(rootUser); +``` + +#### 序列化 + +```java +String jsonString = JSON.toJSONString(group); +System.out.println(jsonString); +``` + +#### 反序列化 + +```java +Group bean = JSON.parseObject(jsonString, Group.class); +``` + +### 2.3. Fastjson 注解 + +#### `@JSONField` + +> 扩展阅读:更多 API 使用细节可以参考:[JSONField 用法](https://github.com/alibaba/fastjson/wiki/JSONField),这里介绍基本用法。 + +可以配置在属性(setter、getter)和字段(必须是 public field)上。 + +```java +@JSONField(name="ID") +public int getId() {return id;} + +// 配置date序列化和反序列使用yyyyMMdd日期格式 +@JSONField(format="yyyyMMdd") +public Date date1; + +// 不序列化 +@JSONField(serialize=false) +public Date date2; + +// 不反序列化 +@JSONField(deserialize=false) +public Date date3; + +// 按ordinal排序 +@JSONField(ordinal = 2) +private int f1; + +@JSONField(ordinal = 1) +private int f2; +``` + +#### `@JSONType` + +- 自定义序列化:[ObjectSerializer](https://github.com/alibaba/fastjson/wiki/JSONType_serializer) +- 子类型处理:[SeeAlso](https://github.com/alibaba/fastjson/wiki/JSONType_seeAlso_cn) + +JSONType.alphabetic 属性: fastjson 缺省时会使用字母序序列化,如果你是希望按照 java fields/getters 的自然顺序序列化,可以配置 JSONType.alphabetic,使用方法如下: + +```java +@JSONType(alphabetic = false) +public static class B { + public int f2; + public int f1; + public int f0; +} +``` + +## 3. Jackson 应用 + +> 扩展阅读:更多 API 使用细节可以参考 [jackson-databind 官方说明](https://github.com/FasterXML/jackson-databind) + +### 3.1. 添加 maven 依赖 + +```xml + + com.fasterxml.jackson.core + jackson-databind + 2.9.8 + +``` + +### 3.2. Jackson API + +#### 序列化 + +```java +ObjectMapper mapper = new ObjectMapper(); + +mapper.writeValue(new File("result.json"), myResultObject); +// or: +byte[] jsonBytes = mapper.writeValueAsBytes(myResultObject); +// or: +String jsonString = mapper.writeValueAsString(myResultObject); +``` + +#### 反序列化 + +```java +ObjectMapper mapper = new ObjectMapper(); + +MyValue value = mapper.readValue(new File("data.json"), MyValue.class); +// or: +value = mapper.readValue(new URL("http://some.com/api/entry.json"), MyValue.class); +// or: +value = mapper.readValue("{\"name\":\"Bob\", \"age\":13}", MyValue.class); +``` + +#### 容器的序列化和反序列化 + +```java +Person p = new Person("Tom", 20); +Person p2 = new Person("Jack", 22); +Person p3 = new Person("Mary", 18); + +List persons = new LinkedList<>(); +persons.add(p); +persons.add(p2); +persons.add(p3); + +Map map = new HashMap<>(); +map.put("persons", persons); + +String json = null; +try { + json = mapper.writeValueAsString(map); +} catch (JsonProcessingException e) { + e.printStackTrace(); +} +``` + +### 3.3. Jackson 注解 + +> 扩展阅读:更多注解使用细节可以参考 [jackson-annotations 官方说明](https://github.com/FasterXML/jackson-annotations/wiki/Jackson-Annotations) + +#### `@JsonProperty` + +```java +public class MyBean { + private String _name; + + // without annotation, we'd get "theName", but we want "name": + @JsonProperty("name") + public String getTheName() { return _name; } + + // note: it is enough to add annotation on just getter OR setter; + // so we can omit it here + public void setTheName(String n) { _name = n; } +} +``` + +#### `@JsonIgnoreProperties` 和 `@JsonIgnore` + +```java +// means that if we see "foo" or "bar" in JSON, they will be quietly skipped +// regardless of whether POJO has such properties +@JsonIgnoreProperties({ "foo", "bar" }) +public class MyBean { + // will not be written as JSON; nor assigned from JSON: + @JsonIgnore + public String internal; + + // no annotation, public field is read/written normally + public String external; + + @JsonIgnore + public void setCode(int c) { _code = c; } + + // note: will also be ignored because setter has annotation! + public int getCode() { return _code; } +} +``` + +#### `@JsonCreator` + +```java +public class CtorBean { + public final String name; + public final int age; + + @JsonCreator // constructor can be public, private, whatever + private CtorBean(@JsonProperty("name") String name, + @JsonProperty("age") int age) + { + this.name = name; + this.age = age; + } +} +``` + +#### `@JsonPropertyOrder` + +alphabetic 设为 true 表示,json 字段按自然顺序排列,默认为 false。 + +```java +@JsonPropertyOrder(alphabetic = true) +public class JacksonAnnotationBean {} +``` + +## 4. Gson 应用 + +> 详细内容可以参考官方文档:[Gson 用户指南](https://github.com/google/gson/blob/master/UserGuide.md) + +### 4.1. 添加 maven 依赖 + +```xml + + com.google.code.gson + gson + 2.8.6 + +``` + +### 4.2. Gson API + +#### 序列化 + +```java +Gson gson = new Gson(); +gson.toJson(1); // ==> 1 +gson.toJson("abcd"); // ==> "abcd" +gson.toJson(10L); // ==> 10 +int[] values = { 1 }; +gson.toJson(values); // ==> [1] +``` + +#### 反序列化 + +```java +int i1 = gson.fromJson("1", int.class); +Integer i2 = gson.fromJson("1", Integer.class); +Long l1 = gson.fromJson("1", Long.class); +Boolean b1 = gson.fromJson("false", Boolean.class); +String str = gson.fromJson("\"abc\"", String.class); +String[] anotherStr = gson.fromJson("[\"abc\"]", String[].class); +``` + +#### GsonBuilder + +`Gson` 实例可以通过 `GsonBuilder` 来定制实例化,以控制其序列化、反序列化行为。 + +```java +Gson gson = new GsonBuilder() + .setPrettyPrinting() + .setDateFormat("yyyy-MM-dd HH:mm:ss") + .excludeFieldsWithModifiers(Modifier.STATIC, Modifier.TRANSIENT, Modifier.VOLATILE) + .create(); +``` + +### 4.3. Gson 注解 + +#### `@Since` + +[`@Since`](https://github.com/google/gson/blob/master/gson/src/main/java/com/google/gson/annotations/Since.java) 用于控制对象的序列化版本。示例: + +```java +public class VersionedClass { + @Since(1.1) private final String newerField; + @Since(1.0) private final String newField; + private final String field; + + public VersionedClass() { + this.newerField = "newer"; + this.newField = "new"; + this.field = "old"; + } +} + +VersionedClass versionedObject = new VersionedClass(); +Gson gson = new GsonBuilder().setVersion(1.0).create(); +String jsonOutput = gson.toJson(versionedObject); +System.out.println(jsonOutput); +System.out.println(); + +gson = new Gson(); +jsonOutput = gson.toJson(versionedObject); +System.out.println(jsonOutput); +``` + +#### `@SerializedName` + +`@SerializedName` 用于将类成员按照指定名称序列化、反序列化。示例: + +```java +private class SomeObject { + @SerializedName("custom_naming") private final String someField; + private final String someOtherField; + + public SomeObject(String a, String b) { + this.someField = a; + this.someOtherField = b; + } +} +``` + +## 5. 示例源码 + +> 示例源码:[javalib-io-json](https://github.com/dunwu/java-tutorial/tree/master/javalib-io-json) + +## 6. 参考资料 + +- **官方** + - [Fastjson Github](https://github.com/alibaba/fastjson) + - [Gson Github](https://github.com/google/gson) + - [jackson 官方文档](https://github.com/FasterXML/jackson-docs) + - [jackson-databind](https://github.com/FasterXML/jackson-databind) +- **文章** + - http://www.json.org/json-zh.html + - [json 的 RFC 文档](http://tools.ietf.org/html/rfc4627) + - [JSON 最佳实践](https://kimmking.github.io/2017/06/06/json-best-practice/) + - [【简明教程】JSON](https://www.jianshu.com/p/8b428e1d1564) diff --git a/docs/ecology/io/javalib-log.md b/docs/ecology/io/javalib-log.md new file mode 100644 index 00000000..363cc43b --- /dev/null +++ b/docs/ecology/io/javalib-log.md @@ -0,0 +1,762 @@ +# 细说 Java 主流日志工具库 + +> 在项目开发中,为了跟踪代码的运行情况,常常要使用日志来记录信息。 +> +> 在 Java 世界,有很多的日志工具库来实现日志功能,避免了我们重复造轮子。 +> +> 我们先来逐一了解一下主流日志工具。 +> +> 📦 本文已归档到:「[blog](https://github.com/dunwu/blog)」 + + + +- [日志框架](#日志框架) + - [java.util.logging (JUL)](#javautillogging-jul) + - [Log4j](#log4j) + - [Logback](#logback) + - [Log4j2](#log4j2) + - [Log4j vs Logback vs Log4j2](#log4j-vs-logback-vs-log4j2) +- [日志门面](#日志门面) + - [common-logging](#common-logging) + - [slf4j](#slf4j) + - [common-logging vs slf4j](#common-logging-vs-slf4j) + - [总结](#总结) +- [实施日志解决方案](#实施日志解决方案) + - [引入 jar 包](#引入-jar-包) + - [使用 API](#使用-api) +- [log4j2 配置](#log4j2-配置) +- [logback 配置](#logback-配置) + - [``](#configuration) + - [``](#appender) + - [``](#logger) + - [``](#root) + - [完整的 logback.xml 参考示例](#完整的-logbackxml-参考示例) +- [log4j 配置](#log4j-配置) + - [完整的 log4j.xml 参考示例](#完整的-log4jxml-参考示例) +- [参考](#参考) + + + +## 日志框架 + +### java.util.logging (JUL) + +JDK1.4 开始,通过 `java.util.logging` 提供日志功能。 + +它能满足基本的日志需要,但是功能没有 Log4j 强大,而且使用范围也没有 Log4j 广泛。 + +### Log4j + +Log4j 是 apache 的一个开源项目,创始人 Ceki Gulcu。 + +Log4j 应该说是 Java 领域资格最老,应用最广的日志工具。从诞生之日到现在一直广受业界欢迎。 + +Log4j 是高度可配置的,并可通过在运行时的外部文件配置。它根据记录的优先级别,并提供机制,以指示记录信息到许多的目的地,诸如:数据库,文件,控制台,UNIX 系统日志等。 + +Log4j 中有三个主要组成部分: + +- **loggers** - 负责捕获记录信息。 +- **appenders** - 负责发布日志信息,以不同的首选目的地。 +- **layouts** - 负责格式化不同风格的日志信息。 + +[官网地址](http://logging.apache.org/log4j/2.x/) + +### Logback + +Logback 是由 log4j 创始人 Ceki Gulcu 设计的又一个开源日记组件,目标是替代 log4j。 + +logback 当前分成三个模块:`logback-core`、`logback-classic` 和 `logback-access`。 + +- `logback-core` - 是其它两个模块的基础模块。 +- `logback-classic` - 是 log4j 的一个 改良版本。此外 `logback-classic` 完整实现 SLF4J API 使你可以很方便地更换成其它日记系统如 log4j 或 JDK14 Logging。 +- `logback-access` - 访问模块与 Servlet 容器集成提供通过 Http 来访问日记的功能。 + +[官网地址](http://logback.qos.ch/) + +### Log4j2 + +[官网地址](http://logging.apache.org/log4j/2.x/) + +按照官方的说法,Log4j2 是 Log4j 和 Logback 的替代。 + +Log4j2 架构: + +![img](http://dunwu.test.upcdn.net/cs/java/javalib/log/log4j2-architecture.jpg!zp) + +### Log4j vs Logback vs Log4j2 + +按照官方的说法,**Log4j2 大大优于 Log4j 和 Logback。** + +那么,Log4j2 相比于先问世的 Log4j 和 Logback,它具有哪些优势呢? + +1. Log4j2 旨在用作审计日志记录框架。 Log4j 1.x 和 Logback 都会在重新配置时丢失事件。 Log4j 2 不会。在 Logback 中,Appender 中的异常永远不会对应用程序可见。在 Log4j 中,可以将 Appender 配置为允许异常渗透到应用程序。 +2. Log4j2 在多线程场景中,[异步 Loggers](https://logging.apache.org/log4j/2.x/manual/async.html) 的吞吐量比 Log4j 1.x 和 Logback 高 10 倍,延迟低几个数量级。 +3. Log4j2 对于独立应用程序是无垃圾的,对于稳定状态日志记录期间的 Web 应用程序来说是低垃圾。这减少了垃圾收集器的压力,并且可以提供更好的响应时间性能。 +4. Log4j2 使用插件系统,通过添加新的 Appender、Filter、Layout、Lookup 和 Pattern Converter,可以非常轻松地扩展框架,而无需对 Log4j 进行任何更改。 +5. 由于插件系统配置更简单。配置中的条目不需要指定类名。 +6. 支持[自定义日志等级](https://logging.apache.org/log4j/2.x/manual/customloglevels.html)。 +7. 支持 [lambda 表达式](https://logging.apache.org/log4j/2.x/manual/api.html#LambdaSupport)。 +8. 支持[消息对象](https://logging.apache.org/log4j/2.x/manual/messages.html)。 +9. Log4j 和 Logback 的 Layout 返回的是字符串,而 Log4j2 返回的是二进制数组,这使得它能被各种 Appender 使用。 +10. Syslog Appender 支持 TCP 和 UDP 并且支持 BSD 系统日志。 +11. Log4j2 利用 Java5 并发特性,尽量小粒度的使用锁,减少锁的开销。 + +## 日志门面 + +何谓日志门面? + +日志门面是对不同日志框架提供的一个 API 封装,可以在部署的时候不修改任何配置即可接入一种日志实现方案。 + +### common-logging + +common-logging 是 apache 的一个开源项目。也称**Jakarta Commons Logging,缩写 JCL**。 + +common-logging 的功能是提供日志功能的 API 接口,本身并不提供日志的具体实现(当然,common-logging 内部有一个 Simple logger 的简单实现,但是功能很弱,直接忽略),而是在**运行时**动态的绑定日志实现组件来工作(如 log4j、java.util.loggin)。 + +[官网地址](http://commons.apache.org/proper/commons-logging/) + +### slf4j + +全称为 Simple Logging Facade for Java,即 java 简单日志门面。 + +什么,作者又是 Ceki Gulcu!这位大神写了 Log4j、Logback 和 slf4j,专注日志组件开发五百年,一直只能超越自己。 + +类似于 Common-Logging,slf4j 是对不同日志框架提供的一个 API 封装,可以在部署的时候不修改任何配置即可接入一种日志实现方案。但是,slf4j 在**编译时**静态绑定真正的 Log 库。使用 SLF4J 时,如果你需要使用某一种日志实现,那么你必须选择正确的 SLF4J 的 jar 包的集合(各种桥接包)。 + +[官网地址](http://www.slf4j.org/) + +![img](http://dunwu.test.upcdn.net/cs/java/javalib/log/slf4j-to-other-log.png!zp) + +### common-logging vs slf4j + +slf4j 库类似于 Apache Common-Logging。但是,他在编译时静态绑定真正的日志库。这点似乎很麻烦,其实也不过是导入桥接 jar 包而已。 + +slf4j 一大亮点是提供了更方便的日志记录方式: + +不需要使用`logger.isDebugEnabled()`来解决日志因为字符拼接产生的性能问题。slf4j 的方式是使用`{}`作为字符串替换符,形式如下: + +```java +logger.debug("id: {}, name: {} ", id, name); +``` + +### 总结 + +综上所述,使用 slf4j + Logback 可谓是目前最理想的日志解决方案了。 + +接下来,就是如何在项目中实施了。 + +## 实施日志解决方案 + +使用日志解决方案基本可分为三步: + +1. 引入 jar 包 +2. 配置 +3. 使用 API + +常见的各种日志解决方案的第 2 步和第 3 步基本一样,实施上的差别主要在第 1 步,也就是使用不同的库。 + +### 引入 jar 包 + +这里首选推荐使用 slf4j + logback 的组合。 + +如果你习惯了 common-logging,可以选择 common-logging+log4j。 + +强烈建议不要直接使用日志实现组件(logback、log4j、java.util.logging),理由前面也说过,就是无法灵活替换日志库。 + +还有一种情况:你的老项目使用了 common-logging,或是直接使用日志实现组件。如果修改老的代码,工作量太大,需要兼容处理。在下文,都将看到各种应对方法。 + +**_注:据我所知,当前仍没有方法可以将 slf4j 桥接到 common-logging。如果我孤陋寡闻了,请不吝赐教。_** + +#### slf4j 直接绑定日志组件 + +**slf4j + logback** + +添加依赖到 pom.xml 中即可。 + +_logback-classic-1.0.13.jar_ 会自动将 _slf4j-api-1.7.21.jar_ 和 _logback-core-1.0.13.jar_ 也添加到你的项目中。 + +```xml + + ch.qos.logback + logback-classic + 1.0.13 + +``` + +**slf4j + log4j** + +添加依赖到 pom.xml 中即可。 + +_slf4j-log4j12-1.7.21.jar_ 会自动将 _slf4j-api-1.7.21.jar_ 和 _log4j-1.2.17.jar_ 也添加到你的项目中。 + +```xml + + org.slf4j + slf4j-log4j12 + 1.7.21 + +``` + +**slf4j + java.util.logging** + +添加依赖到 pom.xml 中即可。 + +_slf4j-jdk14-1.7.21.jar_ 会自动将 _slf4j-api-1.7.21.jar_ 也添加到你的项目中。 + +```xml + + org.slf4j + slf4j-jdk14 + 1.7.21 + +``` + +#### slf4j 兼容非 slf4j 日志组件 + +在介绍解决方案前,先提一个概念——桥接 + +**什么是桥接呢** + +假如你正在开发应用程序所调用的组件当中已经使用了 common-logging,这时你需要 jcl-over-slf4j.jar 把日志信息输出重定向到 slf4j-api,slf4j-api 再去调用 slf4j 实际依赖的日志组件。这个过程称为桥接。下图是官方的 slf4j 桥接策略图: + +![img](http://dunwu.test.upcdn.net/cs/java/javalib/log/slf4j-bind-strategy.png!zp) + +从图中应该可以看出,无论你的老项目中使用的是 common-logging 或是直接使用 log4j、java.util.logging,都可以使用对应的桥接 jar 包来解决兼容问题。 + +**slf4j 兼容 common-logging** + +```xml + + org.slf4j + jcl-over-slf4j + 1.7.12 + +``` + +**slf4j 兼容 log4j** + +```xml + + org.slf4j + log4j-over-slf4j + 1.7.12 + +``` + +**slf4j 兼容 java.util.logging** + +```xml + + org.slf4j + jul-to-slf4j + 1.7.12 + +``` + +#### spring 集成 slf4j + +做 java web 开发,基本离不开 spring 框架。很遗憾,spring 使用的日志解决方案是 common-logging + log4j。 + +所以,你需要一个桥接 jar 包:_logback-ext-spring_。 + +```xml + + ch.qos.logback + logback-classic + 1.1.3 + + + org.logback-extensions + logback-ext-spring + 0.1.2 + + + org.slf4j + jcl-over-slf4j + 1.7.12 + +``` + +#### common-logging 绑定日志组件 + +**common-logging + log4j** + +添加依赖到 pom.xml 中即可。 + +```xml + + commons-logging + commons-logging + 1.2 + + + log4j + log4j + 1.2.17 + +``` + +### 使用 API + +#### slf4j 用法 + +使用 slf4j 的 API 很简单。使用`LoggerFactory`初始化一个`Logger`实例,然后调用 Logger 对应的打印等级函数就行了。 + +```java +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class App { + private static final Logger log = LoggerFactory.getLogger(App.class); + public static void main(String[] args) { + String msg = "print log, current level: {}"; + log.trace(msg, "trace"); + log.debug(msg, "debug"); + log.info(msg, "info"); + log.warn(msg, "warn"); + log.error(msg, "error"); + } +} +``` + +#### common-logging 用法 + +common-logging 用法和 slf4j 几乎一样,但是支持的打印等级多了一个更高级别的:**fatal**。 + +此外,common-logging 不支持`{}`替换参数,你只能选择拼接字符串这种方式了。 + +```java +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +public class JclTest { + private static final Log log = LogFactory.getLog(JclTest.class); + + public static void main(String[] args) { + String msg = "print log, current level: "; + log.trace(msg + "trace"); + log.debug(msg + "debug"); + log.info(msg + "info"); + log.warn(msg + "warn"); + log.error(msg + "error"); + log.fatal(msg + "fatal"); + } +} +``` + +## log4j2 配置 + +log4j2 基本配置形式如下: + +```xml +; + + + value + + + + + + + + ... + + + + + + ... + + + + + +``` + +配置示例: + +```xml + + + + target/test.log + + + + + + + + + + + + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + + + + + + + +``` + +## logback 配置 + +### `` + +- 作用:`` 是 logback 配置文件的根元素。 +- 要点 + - 它有 ``、``、`` 三个子元素。 + +![img](http://dunwu.test.upcdn.net/cs/java/javalib/log/logback-configuration.png!zp) + +### `` + +- 作用:将记录日志的任务委托给名为 appender 的组件。 +- 要点 + - 可以配置零个或多个。 + - 它有 ``、``、``、`` 四个子元素。 +- 属性 + - name:设置 appender 名称。 + - class:设置具体的实例化类。 + +#### `` + +- 作用:设置日志文件路径。 + +#### `` + +- 作用:设置过滤器。 +- 要点 + - 可以配置零个或多个。 + +#### `` + +- 作用:设置 appender。 +- 要点 + - 可以配置零个或一个。 +- 属性 + - class:设置具体的实例化类。 + +#### `` + +- 作用:设置编码。 +- 要点 + - 可以配置零个或多个。 +- 属性 + - class:设置具体的实例化类。 + +![img](http://dunwu.test.upcdn.net/cs/java/javalib/log/logback-appender.png!zp) + +### `` + +- 作用:设置 logger。 +- 要点 + - 可以配置零个或多个。 +- 属性 + - name + - level:设置日志级别。不区分大小写。可选值:TRACE、DEBUG、INFO、WARN、ERROR、ALL、OFF。 + - additivity:可选值:true 或 false。 + +#### `` + +- 作用:appender 引用。 +- 要点 + - 可以配置零个或多个。 + +### `` + +- 作用:设置根 logger。 +- 要点 + - 只能配置一个。 + - 除了 level,不支持任何属性。level 属性和 `` 中的相同。 + - 有一个子元素 ``,与 `` 中的相同。 + +### 完整的 logback.xml 参考示例 + +在下面的配置文件中,我为自己的项目代码(根目录:org.zp.notes.spring)设置了五种等级: + +TRACE、DEBUG、INFO、WARN、ERROR,优先级依次从低到高。 + +因为关注 spring 框架本身的一些信息,我增加了专门打印 spring WARN 及以上等级的日志。 + +```xml + + + + + + + + + + + %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n + + + + + + + + ${user.dir}/logs/${DIR_NAME}/all.%d{yyyy-MM-dd}.log + 30 + + + + + 30MB + + + + %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n + + + + + + + ${user.dir}/logs/${DIR_NAME}/error.%d{yyyy-MM-dd}.log + 30 + + + + + 10MB + + + + ERROR + ACCEPT + DENY + + + + %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n + + + + + + + ${user.dir}/logs/${DIR_NAME}/warn.%d{yyyy-MM-dd}.log + 30 + + + + + 10MB + + + + WARN + ACCEPT + DENY + + + + %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n + + + + + + + ${user.dir}/logs/${DIR_NAME}/info.%d{yyyy-MM-dd}.log + 30 + + + + + 10MB + + + + INFO + ACCEPT + DENY + + + + %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n + + + + + + + ${user.dir}/logs/${DIR_NAME}/debug.%d{yyyy-MM-dd}.log + 30 + + + + + 10MB + + + + DEBUG + ACCEPT + DENY + + + + %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n + + + + + + + ${user.dir}/logs/${DIR_NAME}/trace.%d{yyyy-MM-dd}.log + 30 + + + + + 10MB + + + + TRACE + ACCEPT + DENY + + + + %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n + + + + + + + ${user.dir}/logs/${DIR_NAME}/springframework.%d{yyyy-MM-dd}.log + + 30 + + + + + 10MB + + + + %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +## log4j 配置 + +### 完整的 log4j.xml 参考示例 + +log4j 的配置文件一般有 xml 格式或 properties 格式。这里为了和 logback.xml 做个对比,就不介绍 properties 了,其实也没太大差别。 + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +## 参考 + +- [slf4 官方文档](http://www.slf4j.org/manual.html) +- [logback 官方文档](http://logback.qos.ch/) +- [log4j 官方文档](http://logging.apache.org/log4j/1.2/) +- [commons-logging 官方文档](http://commons.apache.org/proper/commons-logging/) +- http://blog.csdn.net/yycdaizi/article/details/8276265 diff --git a/docs/ecology/microservices/README.md b/docs/ecology/microservices/README.md new file mode 100644 index 00000000..69439d9e --- /dev/null +++ b/docs/ecology/microservices/README.md @@ -0,0 +1,4 @@ +# Java 和 RPC + +- [Dubbo](dubbo.md) +- Spring Cloud diff --git a/docs/technology/distributed/dubbo.md b/docs/ecology/microservices/dubbo.md similarity index 100% rename from docs/technology/distributed/dubbo.md rename to docs/ecology/microservices/dubbo.md diff --git a/docs/technology/distributed/zookeeper-ops.md b/docs/ecology/microservices/zookeeper-ops.md similarity index 100% rename from docs/technology/distributed/zookeeper-ops.md rename to docs/ecology/microservices/zookeeper-ops.md diff --git a/docs/technology/distributed/zookeeper.md b/docs/ecology/microservices/zookeeper.md similarity index 100% rename from docs/technology/distributed/zookeeper.md rename to docs/ecology/microservices/zookeeper.md diff --git a/docs/technology/mq/ActiveMQ.md b/docs/ecology/mq/ActiveMQ.md similarity index 100% rename from docs/technology/mq/ActiveMQ.md rename to docs/ecology/mq/ActiveMQ.md diff --git a/docs/technology/mq/MqInterview.md b/docs/ecology/mq/MqInterview.md similarity index 100% rename from docs/technology/mq/MqInterview.md rename to docs/ecology/mq/MqInterview.md diff --git a/docs/ecology/mq/README.md b/docs/ecology/mq/README.md new file mode 100644 index 00000000..106224c3 --- /dev/null +++ b/docs/ecology/mq/README.md @@ -0,0 +1,9 @@ +# Java 和消息队列 + +- [消息队列面经](MqInterview.md) +- [Kafka 应用指南基础篇](kafka-basic.md) +- [Kafka 应用指南进阶篇](kafka-advance.md) +- [Kafka 运维指南](kafka-ops.md) +- [RocketMQ 基础篇](RocketmqBasics.md) +- [RocketMQ 进阶篇](RocketmqAdvanced.md) +- [ActiveMQ 实战篇](ActiveMQ.md) diff --git a/docs/technology/mq/RocketmqAdvanced.md b/docs/ecology/mq/RocketmqAdvanced.md similarity index 100% rename from docs/technology/mq/RocketmqAdvanced.md rename to docs/ecology/mq/RocketmqAdvanced.md diff --git a/docs/technology/mq/RocketmqBasics.md b/docs/ecology/mq/RocketmqBasics.md similarity index 100% rename from docs/technology/mq/RocketmqBasics.md rename to docs/ecology/mq/RocketmqBasics.md diff --git a/docs/technology/mq/kafka-advance.md b/docs/ecology/mq/kafka-advance.md similarity index 100% rename from docs/technology/mq/kafka-advance.md rename to docs/ecology/mq/kafka-advance.md diff --git a/docs/technology/mq/kafka-basic.md b/docs/ecology/mq/kafka-basic.md similarity index 100% rename from docs/technology/mq/kafka-basic.md rename to docs/ecology/mq/kafka-basic.md diff --git a/docs/technology/mq/kafka-ops.md b/docs/ecology/mq/kafka-ops.md similarity index 100% rename from docs/technology/mq/kafka-ops.md rename to docs/ecology/mq/kafka-ops.md diff --git a/docs/ecology/other/README.md b/docs/ecology/other/README.md new file mode 100644 index 00000000..00f7bce5 --- /dev/null +++ b/docs/ecology/other/README.md @@ -0,0 +1,15 @@ +# Java 库 + +- [Dozer 应用指南](../io/dozer.md) +- [Freemark 应用指南](freemark.md) +- [Java 与 JSON](../io/javalib-json.md) +- [细说 Java 主流日志工具库](../io/javalib-log.md) +- [细说 Java 主流工具包](javalib-util.md) +- [JavaMail 应用指南](javamail.md) +- [Jsoup 应用指南](jsoup.md) +- [JUnit5 应用指南](../test/junit.md) +- [Lombok 应用指南](lombok.md) +- [Mockito 应用指南](../test/mockito.md) +- [Reflections 应用指南](reflections.md) +- [Thumbnailator 应用指南](thumbnailator.md) +- [ZXing 应用指南](zxing.md) diff --git a/docs/ecology/other/freemark.md b/docs/ecology/other/freemark.md new file mode 100644 index 00000000..aa631293 --- /dev/null +++ b/docs/ecology/other/freemark.md @@ -0,0 +1,240 @@ +# Freemark 应用指南 + +> FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML 网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个 Java 类库,是一款程序员可以嵌入他们所开发产品的组件。 + + + +- [简介](#简介) + - [什么是 Freemark?](#什么是-freemark) +- [入门](#入门) + - [创建 Configuration 实例](#创建-configuration-实例) + - [创建数据模型](#创建数据模型) + - [获取模板](#获取模板) + - [合并模板和数据模型](#合并模板和数据模型) + - [完整示例](#完整示例) +- [基础](#基础) + - [数值](#数值) + - [类型](#类型) + - [模板](#模板) +- [参考资料](#参考资料) + + + +## 简介 + +### 什么是 Freemark? + +FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML 网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个 Java 类库,是一款程序员可以嵌入他们所开发产品的组件。 + +简言之:模板 + 数据 = 输出 + +![img](http://freemarker.foofun.cn/figures/overview.png) + +## 入门 + +### 创建 Configuration 实例 + +首先,你应该创建一个 `freemarker.template.Configuration` 实例, 然后调整它的设置。Configuration 实例是存储 FreeMarker 应用级设置的核心部分。同时,它也处理创建和缓存预解析模板(比如 Template 对象)的工作。 + +```java +// Create your Configuration instance, and specify if up to what FreeMarker +// version (here 2.3.22) do you want to apply the fixes that are not 100% +// backward-compatible. See the Configuration JavaDoc for details. +Configuration cfg = new Configuration(Configuration.VERSION_2_3_22); + +// Specify the source where the template files come from. Here I set a +// plain directory for it, but non-file-system sources are possible too: +cfg.setDirectoryForTemplateLoading(new File("/where/you/store/templates")); + +// Set the preferred charset template files are stored in. UTF-8 is +// a good choice in most applications: +cfg.setDefaultEncoding("UTF-8"); + +// Sets how errors will appear. +// During web page *development* TemplateExceptionHandler.HTML_DEBUG_HANDLER is better. +cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); +``` + +> 注:不需要重复创建 Configuration 实例;它的代价很高,尤其是会丢失缓存。Configuration 实例就是应用级别的单例。 + +### 创建数据模型 + +在简单的示例中你可以使用 `java.lang` 和 `java.util` 包中的类, 还有用户自定义的 Java Bean 来构建数据对象: + +- 使用 `java.lang.String` 来构建字符串。 +- 使用 `java.lang.Number` 来派生数字类型。 +- 使用 `java.lang.Boolean` 来构建布尔值。 +- 使用 `java.util.List` 或 Java 数组来构建序列。 +- 使用 `java.util.Map` 来构建哈希表。 +- 使用自定义的 bean 类来构建哈希表,bean 中的项和 bean 的属性对应。比如, `product` 的 `price` 属性 (`getProperty()`)可以通过 `product.price` 获取。(bean 的 action 也可以通过这种方式拿到; 要了解更多可以参看 [这里](http://freemarker.foofun.cn/pgui_misc_beanwrapper.html)) + +示例: + +```java +// Create the root hash +Map root = new HashMap<>(); +// Put string ``user'' into the root +root.put("user", "Big Joe"); +// Create the hash for ``latestProduct'' +Map latest = new HashMap<>(); +// and put it into the root +root.put("latestProduct", latest); +// put ``url'' and ``name'' into latest +latest.put("url", "products/greenmouse.html"); +latest.put("name", "green mouse"); +``` + +### 获取模板 + +模板代表了 `freemarker.template.Template` 实例。典型的做法是从 `Configuration` 实例中获取一个 `Template` 实例。无论什么时候你需要一个模板实例, 都可以使用它的 `getTemplate` 方法来获取。在 [之前](http://freemarker.foofun.cn/pgui_quickstart_createconfiguration.html) 设置的目录中的 `test.ftl` 文件中存储 [示例模板](http://freemarker.foofun.cn/dgui_quickstart_basics.html#example.first),那么就可以这样来做: + +```java +Template temp = cfg.getTemplate("test.ftl"); +``` + +当调用这个方法的时候,将会创建一个 `test.ftl` 的 `Template` 实例,通过读取 `*/where/you/store/templates/*test.ftl` 文件,之后解析(编译)它。`Template` 实例以解析后的形式存储模板, 而不是以源文件的文本形式。 + +`Configuration` 缓存 `Template` 实例,当再次获得 `test.ftl` 的时候,它可能再读取和解析模板文件了, 而只是返回第一次的 `Template`实例。 + +### 合并模板和数据模型 + +我们已经知道,数据模型+模板=输出,我们有了一个数据模型 (`root`) 和一个模板 (`temp`), 为了得到输出就需要合并它们。这是由模板的 `process` 方法完成的。它用数据模型 root 和 `Writer` 对象作为参数,然后向 `Writer` 对象写入产生的内容。 为简单起见,这里我们只做标准的输出: + +``` +Writer out = new OutputStreamWriter(System.out); +temp.process(root, out); +``` + +这会向你的终端输出你在模板开发指南部分的 [第一个示例](http://freemarker.foofun.cn/dgui_quickstart_basics.html#example.first) 中看到的内容。 + +Java I/O 相关注意事项:基于 `out` 对象,必须保证 `out.close()` 最后被调用。当 `out` 对象被打开并将模板的输出写入文件时,这是很电影的做法。其它时候, 比如典型的 Web 应用程序,那就 _不能_ 关闭 `out` 对象。FreeMarker 会在模板执行成功后 (也可以在 `Configuration` 中禁用) 调用 `out.flush()`,所以不必为此担心。 + +请注意,一旦获得了 `Template` 实例, 就能将它和不同的数据模型进行不限次数 (`Template`实例是无状态的)的合并。此外, 当 `Template`实例创建之后 `test.ftl` 文件才能访问,而不是在调用处理方法时。 + +### 完整示例 + +[**源码**](https://github.com/dunwu/spring-boot-tutorial/tree/master/codes/template/sbe-tmpl-freemark) + +## 基础 + +### 数值 + +注意观察每个数据模型的例子你也许能发现:被"(root)"所标识的内容就是哈希表类型的值。 当编写如 `user` 这样的代码时,那就意味着要把"user"变量存储在哈希表的根上。 就像编写 `root.user`一样,这里但并没有名"root"为的变量, 那么这就起不到任何作用了。 + +某些人也许会被这种数据模型的例子所困惑,也就是说,根哈希表包含更多的哈希表或序列 (`lotteryNumbers` and `cargo`)。其它就没有更特殊的内容了。 哈希表包含其他变量,那些变量包含其它值,这些数值可以是字符串,数字等变量, 当然也可以是哈希表或序列变量。最初我们解释过的,就像字符串和数字, 序列或哈希表也是一种值的表示形式。 + +### 类型 + +Freemark 支持的类型有: + +- 标量: + - 字符串 + - 数字 + - 布尔值 + - 日期/时间 (日期,时间或日期时间) +- 容器: + - 哈希表 + - 序列 + - 集合 +- 子程序: + - [方法和函数](http://freemarker.foofun.cn/dgui_datamodel_types.html#dgui_datamodel_method) + - [用户自定义指令](http://freemarker.foofun.cn/dgui_datamodel_types.html#dgui_datamodel_userdefdir) +- 其它/很少使用: + - [结点](http://freemarker.foofun.cn/dgui_datamodel_types.html#dgui_datamodel_node) + +### 模板 + +#### 总体结构 + +模板(FTL 编程)是由如下部分混合而成的: + +- **文本**:文本会照着原样来输出。 +- **插值**:这部分的输出会被计算的值来替换。插值由 `${` and `}` 所分隔。 +- **FTL 标签**:FTL 标签和 HTML 标签很相似,但是它们却是给 FreeMarker 的指示, 而且不会打印在输出内容中。 +- **注释**:注释和 HTML 的注释也很相似,但它们是由 `<#--` 和 `-->`来分隔的。注释会被 FreeMarker 直接忽略, 更不会在输出内容中显示。 + +![img](http://dunwu.test.upcdn.net/snap/ftl-template.png!zp) + +> 🔔 注意: +> +> - FTL 是区分大小写的。 +> - `插值` 仅仅可以在 `文本` 中使用。 +> - `FTL 标签` 不可以在其他 `FTL 标签` 和 `插值` 中使用。 +> - `注释` 可以放在 `FTL 标签` 和 `插值` 中。 + +#### 指令 + +使用 FTL 标签来调用 **指令**。 + +FTL 标签分为两种: + +- 开始标签: `<#*directivename* *parameters*>` +- 结束标签: `` + +除了标签以 `#` 开头外,其他都和 HTML,XML 的语法很相似。 如果标签没有嵌套内容(在开始标签和结束标签之间的内容),那么可以只使用开始标签。 例如 `<#if *something*>*...*`, 而 FreeMarker 知道 `<#include *something*>` 中的 `include` 指令没有可嵌套的内容。 + +`*parameters*` 的格式由 `*directivename*`来决定。 + +事实上,指令有两种类型: [预定义指令](http://freemarker.foofun.cn/gloss.html#gloss.predefinedDirective) 和 [用户自定义指令](http://freemarker.foofun.cn/gloss.html#gloss.userDefinedDirective)。 对于用户自定义的指令使用 `@` 来代替 `#`。 + +> 🔔 注意: +> +> - FreeMarker 仅仅关心 FTL 标签的嵌套而不关心 HTML 标签的嵌套。 它只会把 HTML 看做是文本,不会来解释 HTML。 +> - 如果你尝试使用一个不存在的指令(比如,输错了指令的名称), FreeMarker 就会拒绝执行模板,同时抛出错误信息。 +> - FreeMarker 会忽略 FTL 标签中多余的 [空白标记](http://freemarker.foofun.cn/gloss.html#gloss.whiteSpace)。 + +#### 表达式 + +以下为快速浏览清单,如果需要了解更多细节,请参考[**这里**](http://freemarker.foofun.cn/dgui_template_exp.html)。 + +- [直接指定值](http://freemarker.foofun.cn/dgui_template_exp.html#dgui_template_exp_direct) + - [字符串](http://freemarker.foofun.cn/dgui_template_exp.html#dgui_template_exp_direct_string): `"Foo"` 或者 `'Foo'` 或者 `"It's \"quoted\""` 或者 `'It\'s "quoted"'` 或者 `r"C:\raw\string"` + - [数字](http://freemarker.foofun.cn/dgui_template_exp.html#dgui_template_exp_direct_number): `123.45` + - [布尔值](http://freemarker.foofun.cn/dgui_template_exp.html#dgui_template_exp_direct_boolean): `true`, `false` + - [序列](http://freemarker.foofun.cn/dgui_template_exp.html#dgui_template_exp_direct_seuqence): `["foo", "bar", 123.45]`; 值域: `0..9`, `0..<10` (或 `0..!10`), `0..` + - [哈希表](http://freemarker.foofun.cn/dgui_template_exp.html#dgui_template_exp_direct_hash): `{"name":"green mouse", "price":150}` +- [检索变量](http://freemarker.foofun.cn/dgui_template_exp.html#dgui_template_exp_var) + - [顶层变量](http://freemarker.foofun.cn/dgui_template_exp.html#dgui_template_exp_var_toplevel): `user` + - [从哈希表中检索数据](http://freemarker.foofun.cn/dgui_template_exp.html#dgui_template_exp_var_hash): `user.name`, `user["name"]` + - [从序列中检索数据](http://freemarker.foofun.cn/dgui_template_exp.html#dgui_template_exp_var_sequence): `products[5]` + - [特殊变量](http://freemarker.foofun.cn/dgui_template_exp.html#dgui_template_exp_var_special): `.main` +- [字符串操作](http://freemarker.foofun.cn/dgui_template_exp.html#dgui_template_exp_stringop) + - [插值(或连接)](http://freemarker.foofun.cn/dgui_template_exp.html#dgui_template_exp_stringop_interpolation): `"Hello ${user}!"` (或 `"Hello " + user + "!"`) + - [获取一个字符](http://freemarker.foofun.cn/dgui_template_exp.html#dgui_template_exp_get_character): `name[0]` + - [字符串切分:](http://freemarker.foofun.cn/dgui_template_exp.html#dgui_template_exp_stringop_slice) 包含结尾: `name[0..4]`,不包含结尾: `name[0..<5]`,基于长度(宽容处理): `name[0..*5]`,去除开头:`name[5..]` +- [序列操作](http://freemarker.foofun.cn/dgui_template_exp.html#dgui_template_exp_sequenceop) + - [连接](http://freemarker.foofun.cn/dgui_template_exp.html#dgui_template_exp_sequenceop_cat): `users + ["guest"]` + - [序列切分](http://freemarker.foofun.cn/dgui_template_exp.html#dgui_template_exp_seqenceop_slice):包含结尾: `products[20..29]`, 不包含结尾: `products[20..<30]`,基于长度(宽容处理):`products[20..*10]`,去除开头: `products[20..]` +- [哈希表操作](http://freemarker.foofun.cn/dgui_template_exp.html#dgui_template_exp_hashop) + - [连接](http://freemarker.foofun.cn/dgui_template_exp.html#dgui_template_exp_hashop_cat): `passwords + { "joe": "secret42" }` +- [算术运算](http://freemarker.foofun.cn/dgui_template_exp.html#dgui_template_exp_arit): `(x * 1.5 + 10) / 2 - y % 100` +- [比较运算](http://freemarker.foofun.cn/dgui_template_exp.html#dgui_template_exp_comparison): `x == y`, `x != y`, `x < y`, `x > y`, `x >= y`, `x <= y`, `x lt y`, `x lte y`, `x gt y`, `x gte y`, 等等。。。。。。 +- [逻辑操作](http://freemarker.foofun.cn/dgui_template_exp.html#dgui_template_exp_logicalop): `!registered && (firstVisit || fromEurope)` +- [内建函数](http://freemarker.foofun.cn/dgui_template_exp.html#dgui_template_exp_builtin): `name?upper_case`, `path?ensure_starts_with('/')` +- [方法调用](http://freemarker.foofun.cn/dgui_template_exp.html#dgui_template_exp_methodcall): `repeat("What", 3)` +- [处理不存在的值](http://freemarker.foofun.cn/dgui_template_exp.html#dgui_template_exp_missing) + - [默认值](http://freemarker.foofun.cn/dgui_template_exp.html#dgui_template_exp_missing_default): `name!"unknown"` 或者 `(user.name)!"unknown"` 或者 `name!` 或者 `(user.name)!` + - [检测不存在的值](http://freemarker.foofun.cn/dgui_template_exp.html#dgui_template_exp_missing_test): `name??` 或者 `(user.name)??` +- [赋值操作](http://freemarker.foofun.cn/dgui_template_exp.html#dgui_template_exp_assignment): `=`, `+=`, `-=`, `*=`, `/=`, `%=`, `++`, `--` + +#### 插值 + +插值的使用格式是: `${*expression*}`,这里的 `*expression*` 可以是所有种类的表达式(比如 `${100 + x}`)。 + +插值是用来给 `*表达式*` 插入具体值然后转换为文本(字符串)。插值仅仅可以在两种位置使用:在 [文本 区](http://freemarker.foofun.cn/dgui_template_overallstructure.html) (比如 `

    Hello ${name}!

    `) 和 [字符串表达式](http://freemarker.foofun.cn/dgui_template_exp.html#dgui_template_exp_stringop_interpolation) (比如 `<#include "/footer/${company}.html">`)中。 + +表达式的结果必须是字符串,数字或者日期/时间/日期-时间值, 因为(默认是这样)仅仅这些值可以被插值自动转换为字符串。其它类型的值 (比如布尔值,序列)必须 "手动地" 转换成字符串(后续会有一些建议), 否则就会发生错误,中止模板执行。 + +注意:插值 _仅仅_ 在 [文本区](http://freemarker.foofun.cn/dgui_template_overallstructure.html) (比如 `

    Hello ${name}!

    `) 和 [字符串](http://freemarker.foofun.cn/dgui_template_exp.html#dgui_template_exp_direct_string) 中起作用。 + +⭕ `<#include "/footer/${company}.html">` + +⭕ `<#if big>...` + +❌ `<#if ${big}>...` + +❌ `<#if "${big}">...` + +## 参考资料 + +- http://freemarker.foofun.cn/ diff --git a/docs/ecology/other/javalib-util.md b/docs/ecology/other/javalib-util.md new file mode 100644 index 00000000..005aec39 --- /dev/null +++ b/docs/ecology/other/javalib-util.md @@ -0,0 +1,7 @@ +# 细说 Java 主流工具包 + +- apache.commons + - [commons-lang](https://github.com/apache/commons-lang) + - [commons-collections](https://github.com/apache/commons-collections) + - [common-io](https://github.com/apache/commons-io) +- [guava](https://github.com/google/guava) diff --git a/docs/ecology/other/javamail.md b/docs/ecology/other/javamail.md new file mode 100644 index 00000000..30cdc1ba --- /dev/null +++ b/docs/ecology/other/javamail.md @@ -0,0 +1,466 @@ +# JavaMail 应用指南 + + + +- [简介](#简介) + - [邮件相关的标准](#邮件相关的标准) + - [JavaMail 简介](#javamail-简介) + - [邮件传输过程](#邮件传输过程) + - [Message 结构](#message-结构) +- [JavaMail 的核心类](#javamail-的核心类) + - [java.util.Properties 类(属性对象)](#javautilproperties-类属性对象) + - [javax.mail.Session 类(会话对象)](#javaxmailsession-类会话对象) + - [javax.mail.Transport 类(邮件传输)](#javaxmailtransport-类邮件传输) + - [javax.mail.Store 类(邮件存储 )](#javaxmailstore-类邮件存储-) + - [javax.mail.Message 类(消息对象)](#javaxmailmessage-类消息对象) + - [javax.mail.Address 类(地址)](#javaxmailaddress-类地址) + - [Authenticator 类(认证者)](#authenticator-类认证者) +- [实例](#实例) + - [发送文本邮件](#发送文本邮件) + - [发送 HTML 格式的邮件](#发送-html-格式的邮件) + - [发送带附件的邮件](#发送带附件的邮件) + - [获取邮箱中的邮件](#获取邮箱中的邮件) + - [转发邮件](#转发邮件) + + + +## 简介 + +### 邮件相关的标准 + +厂商所提供的 JavaMail 服务程序可以有选择地实现某些邮件协议,常见的邮件协议包括: + +- `SMTP(Simple Mail Transfer Protocol)` :即简单邮件传输协议,它是一组用于由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式。 +- `POP3(Post Office Protocol - Version 3)` :即邮局协议版本 3 ,用于接收电子邮件的标准协议。 +- `IMAP(Internet Mail Access Protocol)` :即 Internet 邮件访问协议。是 POP3 的替代协议。 + +这三种协议都有对应 SSL 加密传输的协议,分别是 **SMTPS **, **POP3S **和 **IMAPS **。 + +`MIME(Multipurpose Internet Mail Extensions)` :即多用途因特网邮件扩展标准。它不是邮件传输协议。但对传输内容的消息、附件及其它的内容定义了格式。 + +### JavaMail 简介 + +JavaMail 是由 Sun 发布的用来处理 email 的 API 。它并没有包含在 Java SE 中,而是作为 Java EE 的一部分。 + +- `mail.jar` :此 JAR 文件包含 JavaMail API 和 Sun 提供的 SMTP 、 IMAP 和 POP3 服务提供程序; +- `activation.jar` :此 JAR 文件包含 JAF API 和 Sun 的实现。 + +JavaMail 包中用于处理电子邮件的核心类是: `Properties` 、 `Session` 、 `Message` 、 `Address` 、 `Authenticator` 、 `Transport` 、 `Store` 等。 + +### 邮件传输过程 + +如上图,电子邮件的处理步骤如下: + +1. 创建一个 Session 对象。 +2. Session 对象创建一个 Transport 对象 /Store 对象,用来发送 / 保存邮件。 +3. Transport 对象 /Store 对象连接邮件服务器。 +4. Transport 对象 /Store 对象创建一个 Message 对象 ( 也就是邮件内容 ) 。 +5. Transport 对象发送邮件; Store 对象获取邮箱的邮件。 + +### Message 结构 + +- `MimeMessage` 类:代表整封邮件。 +- `MimeBodyPart` 类:代表邮件的一个 MIME 信息。 +- `MimeMultipart` 类:代表一个由多个 MIME 信息组合成的组合 MIME 信息。 + +![img](http://upload-images.jianshu.io/upload_images/3101171-948230d2f5c7a620.png) + +## JavaMail 的核心类 + +JavaMail 对收发邮件进行了高级的抽象,形成了一些关键的的接口和类,它们构成了程序的基础,下面我们分别来了解一下这些最常见的对象。 + +### java.util.Properties 类(属性对象) + +java.util.Properties 类代表一组属性集合。 + +它的每一个键和值都是 String **类型。** + +由于 JavaMail 需要和邮件服务器进行通信,这就要求程序提供许多诸如服务器地址、端口、用户名、密码等信息, JavaMail 通过 Properties 对象封装这些属性信息。 + +例: 如下面的代码封装了几个属性信息: + +```java +Properties prop = new Properties(); +prop.setProperty("mail.debug", "true"); +prop.setProperty("mail.host", "[email protected]"); +prop.setProperty("mail.transport.protocol", "smtp"); +prop.setProperty("mail.smtp.auth", "true"); +``` + +针对不同的的邮件协议, JavaMail 规定了服务提供者必须支持一系列属性, + +下表是一些常见属性(属性值都以 String 类型进行设置,属性类型栏仅表示属性是如何被解析的): + +| 关键词 | 类型 | 描述 | +| ----------------------- | ------- | --------------------------------------------- | +| mail.debug | boolean | debug 开关。 | +| mail.host | String | 指定发送、接收邮件的默认邮箱服务器。 | +| mail.store.protocol | String | 指定接收邮件的协议。 | +| mail.transport.protocol | String | 指定发送邮件的协议。 | +| mail.debug.auth | boolean | debug 输出中是否包含认证命令。默认是 false 。 | + +详情请参考官方 API 文档: + +https://javamail.java.net/nonav/docs/api/ 。 + +### javax.mail.Session 类(会话对象) + +`Session` 表示一个邮件会话。 + +Session 的主要作用包括两个方面: + +- 接收各种配置属性信息:通过 Properties 对象设置的属性信息; +- 初始化 JavaMail 环境:根据 JavaMail 的配置文件,初始化 JavaMail 环境,以便通过 Session 对象创建其他重要类的实例。 + +JavaMail 在 Jar 包的 META-INF 目录下,通过以下文件提供了基本配置信息,以便 session 能够根据这个配置文件加载提供者的实现类: + +- javamail.default.providers +- javamail.default.address.map + +![img](http://upload-images.jianshu.io/upload_images/3101171-b59382c69385df45.png) + +**例:** + +```java +Properties props = new Properties(); +props.setProperty("mail.transport.protocol", "smtp"); +Session session = Session.getInstance(props); +``` + +### javax.mail.Transport 类(邮件传输) + +邮件操作只有发送或接收两种处理方式。 + +JavaMail 将这两种不同操作描述为传输( javax.mail.Transport )和存储( javax.mail.Store ),传输对应邮件的发送,而存储对应邮件的接收。 + +- `getTransport` - Session 类中的 getTransport **() **有多个重载方法,可以用来创建 Transport 对象。 +- `connect` - 如果设置了认证命令—— mail.smtp.auth ,那么使用 Transport 类的 connect 方法连接服务器时,则必须加上用户名和密码。 +- `sendMessage` - Transport 类的 sendMessage 方法用来发送邮件消息。 +- `close` - Transport 类的 close 方法用来关闭和邮件服务器的连接。 + +### javax.mail.Store 类(邮件存储 ) + +- `getStore` - Session 类中的 getStore () 有多个重载方法,可以用来创建 Store 对象。 +- `connect` - 如果设置了认证命令—— mail.smtp.auth ,那么使用 Store 类的 connect 方法连接服务器时,则必须加上用户名和密码。 +- `getFolder` - Store 类的 getFolder 方法可以 获取邮箱内的邮件夹 Folder 对象 +- `close` - Store 类的 close 方法用来关闭和邮件服务器的连接。 + +### javax.mail.Message 类(消息对象) + +- `javax.mail.Message` - 是个抽象类,只能用子类去实例化,多数情况下为 `javax.mail.internet.MimeMessage`。 +- `MimeMessage` - 代表 MIME 类型的电子邮件消息。 + +要创建一个 Message ,需要将 Session 对象传递给 `MimeMessage` 构造器: + +```java +MimeMessage message = new MimeMessage(session); +``` + +注意:还存在其它构造器,如用按 RFC822 格式的输入流来创建消息。 + +- setFrom - 设置邮件的发件人 +- setRecipient - 设置邮件的发送人、抄送人、密送人 + +三种预定义的地址类型是: + +- `Message.RecipientType.TO` - 收件人 +- `Message.RecipientType.CC` - 抄送人 +- `Message.RecipientType.BCC` - 密送人 +- `setSubject` - 设置邮件的主题 +- `setContent` - 设置邮件内容 +- `setText` - 如果邮件内容是纯文本,可以使用此接口设置文本内容。 + +### javax.mail.Address 类(地址) + +一旦您创建了 Session 和 Message ,并将内容填入消息后,就可以用 Address 确定信件地址了。和 Message 一样, Address 也是个抽象类。您用的是 javax.mail.internet.InternetAddress 类。 + +若创建的地址只包含电子邮件地址,只要传递电子邮件地址到构造器就行了。 + +**例:** + +```java +Address address = new InternetAddress("[email protected]"); +``` + +### Authenticator 类(认证者) + +与 java.net 类一样, JavaMail API 也可以利用 `Authenticator` 通过用户名和密码访问受保护的资源。对于 JavaMail API 来说,这些资源就是邮件服务器。`Authenticator` 在 javax.mail 包中,而且它和 java.net 中同名的类 Authenticator 不同。两者并不共享同一个 Authenticator ,因为 JavaMail API 用于 Java 1.1 ,它没有 java.net 类别。 + +要使用 Authenticator ,先创建一个抽象类的子类,并从 `getPasswordAuthentication()` 方法中返回 `PasswordAuthentication` 实例。创建完成后,您必需向 session 注册 `Authenticator` 。然后,在需要认证的时候,就会通知 `Authenticator` 。您可以弹出窗口,也可以从配置文件中(虽然没有加密是不安全的)读取用户名和密码,将它们作为 `PasswordAuthentication` 对象返回给调用程序。 + +**例:** + +```java +Properties props = new Properties(); +Authenticator auth = new MyAuthenticator(); +Session session = Session.getDefaultInstance(props, auth); +``` + +## 实例 + +### 发送文本邮件 + +```java +public static void main(String[] args) throws Exception { + Properties prop = new Properties(); + prop.setProperty("mail.debug", "true"); + prop.setProperty("mail.host", MAIL_SERVER_HOST); + prop.setProperty("mail.transport.protocol", "smtp"); + prop.setProperty("mail.smtp.auth", "true"); + + // 1、创建session + Session session = Session.getInstance(prop); + Transport ts = null; + + // 2、通过session得到transport对象 + ts = session.getTransport(); + + // 3、连上邮件服务器 + ts.connect(MAIL_SERVER_HOST, USER, PASSWORD); + + // 4、创建邮件 + MimeMessage message = new MimeMessage(session); + + // 邮件消息头 + message.setFrom(new InternetAddress(MAIL_FROM)); // 邮件的发件人 + message.setRecipient(Message.RecipientType.TO, new InternetAddress(MAIL_TO)); // 邮件的收件人 + message.setRecipient(Message.RecipientType.CC, new InternetAddress(MAIL_CC)); // 邮件的抄送人 + message.setRecipient(Message.RecipientType.BCC, new InternetAddress(MAIL_BCC)); // 邮件的密送人 + message.setSubject("测试文本邮件"); // 邮件的标题 + + // 邮件消息体 + message.setText("天下无双。"); + + // 5、发送邮件 + ts.sendMessage(message, message.getAllRecipients()); + ts.close(); +} +``` + +### 发送 HTML 格式的邮件 + +```java +public static void main(String[] args) throws Exception { + Properties prop = new Properties(); + prop.setProperty("mail.debug", "true"); + prop.setProperty("mail.host", MAIL_SERVER_HOST); + prop.setProperty("mail.transport.protocol", "smtp"); + prop.setProperty("mail.smtp.auth", "true"); + + // 1、创建session + Session session = Session.getInstance(prop); + Transport ts = null; + + // 2、通过session得到transport对象 + ts = session.getTransport(); + + // 3、连上邮件服务器 + ts.connect(MAIL_SERVER_HOST, USER, PASSWORD); + + // 4、创建邮件 + MimeMessage message = new MimeMessage(session); + + // 邮件消息头 + message.setFrom(new InternetAddress(MAIL_FROM)); // 邮件的发件人 + message.setRecipient(Message.RecipientType.TO, new InternetAddress(MAIL_TO)); // 邮件的收件人 + message.setRecipient(Message.RecipientType.CC, new InternetAddress(MAIL_CC)); // 邮件的抄送人 + message.setRecipient(Message.RecipientType.BCC, new InternetAddress(MAIL_BCC)); // 邮件的密送人 + message.setSubject("测试HTML邮件"); // 邮件的标题 + + String htmlContent = "

    Hello

    " + "

    显示图片1.jpg

    "; + MimeBodyPart text = new MimeBodyPart(); + text.setContent(htmlContent, "text/html;charset=UTF-8"); + MimeBodyPart image = new MimeBodyPart(); + DataHandler dh = new DataHandler(new FileDataSource("D:\\05_Datas\\图库\\吉他少年背影.png")); + image.setDataHandler(dh); + image.setContentID("abc.jpg"); + + // 描述数据关系 + MimeMultipart mm = new MimeMultipart(); + mm.addBodyPart(text); + mm.addBodyPart(image); + mm.setSubType("related"); + message.setContent(mm); + message.saveChanges(); + + // 5、发送邮件 + ts.sendMessage(message, message.getAllRecipients()); + ts.close(); +} +``` + +### 发送带附件的邮件 + +```java +public static void main(String[] args) throws Exception { + Properties prop = new Properties(); + prop.setProperty("mail.debug", "true"); + prop.setProperty("mail.host", MAIL_SERVER_HOST); + prop.setProperty("mail.transport.protocol", "smtp"); + prop.setProperty("mail.smtp.auth", "true"); + + // 1、创建session + Session session = Session.getInstance(prop); + + // 2、通过session得到transport对象 + Transport ts = session.getTransport(); + + // 3、连上邮件服务器 + ts.connect(MAIL_SERVER_HOST, USER, PASSWORD); + + // 4、创建邮件 + MimeMessage message = new MimeMessage(session); + + // 邮件消息头 + message.setFrom(new InternetAddress(MAIL_FROM)); // 邮件的发件人 + message.setRecipient(Message.RecipientType.TO, new InternetAddress(MAIL_TO)); // 邮件的收件人 + message.setRecipient(Message.RecipientType.CC, new InternetAddress(MAIL_CC)); // 邮件的抄送人 + message.setRecipient(Message.RecipientType.BCC, new InternetAddress(MAIL_BCC)); // 邮件的密送人 + message.setSubject("测试带附件邮件"); // 邮件的标题 + + MimeBodyPart text = new MimeBodyPart(); + text.setContent("邮件中有两个附件。", "text/html;charset=UTF-8"); + + // 描述数据关系 + MimeMultipart mm = new MimeMultipart(); + mm.setSubType("related"); + mm.addBodyPart(text); + String[] files = { + "D:\\00_Temp\\temp\\1.jpg", "D:\\00_Temp\\temp\\2.png" + }; + + // 添加邮件附件 + for (String filename : files) { + MimeBodyPart attachPart = new MimeBodyPart(); + attachPart.attachFile(filename); + mm.addBodyPart(attachPart); + } + + message.setContent(mm); + message.saveChanges(); + + // 5、发送邮件 + ts.sendMessage(message, message.getAllRecipients()); + ts.close(); +} +``` + +### 获取邮箱中的邮件 + +```java + public static void main(String[] args) throws Exception { + + // 创建一个有具体连接信息的Properties对象 + Properties prop = new Properties(); + prop.setProperty("mail.debug", "true"); + prop.setProperty("mail.store.protocol", "pop3"); + prop.setProperty("mail.pop3.host", MAIL_SERVER_HOST); + + // 1、创建session + Session session = Session.getInstance(prop); + + // 2、通过session得到Store对象 + Store store = session.getStore(); + + // 3、连上邮件服务器 + store.connect(MAIL_SERVER_HOST, USER, PASSWORD); + + // 4、获得邮箱内的邮件夹 + Folder folder = store.getFolder("inbox"); + folder.open(Folder.READ_ONLY); + + // 获得邮件夹Folder内的所有邮件Message对象 + Message[] messages = folder.getMessages(); + for (int i = 0; i < messages.length; i++) { + String subject = messages[i].getSubject(); + String from = (messages[i].getFrom()[0]).toString(); + System.out.println("第 " + (i + 1) + "封邮件的主题:" + subject); + System.out.println("第 " + (i + 1) + "封邮件的发件人地址:" + from); + } + + // 5、关闭 + folder.close(false); + store.close(); +} +``` + +### 转发邮件 + +例:获取指定邮件夹下的第一封邮件并转发 + +```java + public static void main(String[] args) throws Exception { + Properties prop = new Properties(); + prop.put("mail.store.protocol", "pop3"); + prop.put("mail.pop3.host", MAIL_SERVER_POP3); + prop.put("mail.pop3.starttls.enable", "true"); + prop.put("mail.smtp.auth", "true"); + prop.put("mail.smtp.host", MAIL_SERVER_SMTP); + + // 1、创建session + Session session = Session.getDefaultInstance(prop); + + // 2、读取邮件夹 + Store store = session.getStore("pop3"); + store.connect(MAIL_SERVER_POP3, USER, PASSWORD); + Folder folder = store.getFolder("inbox"); + folder.open(Folder.READ_ONLY); + + // 获取邮件夹中第1封邮件信息 + Message[] messages = folder.getMessages(); + if (messages.length <= 0) { + return; + } + Message message = messages[0]; + + // 打印邮件关键信息 + String from = InternetAddress.toString(message.getFrom()); + if (from != null) { + System.out.println("From: " + from); + } + + String replyTo = InternetAddress.toString(message.getReplyTo()); + if (replyTo != null) { + System.out.println("Reply-to: " + replyTo); + } + + String to = InternetAddress.toString(message.getRecipients(Message.RecipientType.TO)); + if (to != null) { + System.out.println("To: " + to); + } + + String subject = message.getSubject(); + if (subject != null) { + System.out.println("Subject: " + subject); + } + + Date sent = message.getSentDate(); + if (sent != null) { + System.out.println("Sent: " + sent); + } + + // 设置转发邮件信息头 + Message forward = new MimeMessage(session); + forward.setFrom(new InternetAddress(MAIL_FROM)); + forward.setRecipient(Message.RecipientType.TO, new InternetAddress(MAIL_TO)); + forward.setSubject("Fwd: " + message.getSubject()); + + // 设置转发邮件内容 + MimeBodyPart bodyPart = new MimeBodyPart(); + bodyPart.setContent(message, "message/rfc822"); + + Multipart multipart = new MimeMultipart(); + multipart.addBodyPart(bodyPart); + forward.setContent(multipart); + forward.saveChanges(); + + Transport ts = session.getTransport("smtp"); + ts.connect(USER, PASSWORD); + ts.sendMessage(forward, forward.getAllRecipients()); + + folder.close(false); + store.close(); + ts.close(); + System.out.println("message forwarded successfully...."); +} +``` diff --git a/docs/ecology/other/jsoup.md b/docs/ecology/other/jsoup.md new file mode 100644 index 00000000..8cdcd0c4 --- /dev/null +++ b/docs/ecology/other/jsoup.md @@ -0,0 +1,461 @@ +# Jsoup 应用指南 + + + +- [简介](#简介) +- [加载](#加载) + - [从 HTML 字符串加载一个文档](#从-html-字符串加载一个文档) + - [解析一个 body 片断](#解析一个-body-片断) + - [从 URL 加载一个文档](#从-url-加载一个文档) + - [从一个文件加载一个文档](#从一个文件加载一个文档) +- [解析](#解析) + - [使用 DOM 方法来遍历一个文档](#使用-dom-方法来遍历一个文档) + - [使用选择器语法来查找元素](#使用选择器语法来查找元素) + - [从元素抽取属性,文本和 HTML](#从元素抽取属性文本和-html) + - [处理 URLs](#处理-urls) +- [数据修改](#数据修改) + - [设置属性的值](#设置属性的值) + - [设置一个元素的 HTML 内容](#设置一个元素的-html-内容) + - [设置元素的文本内容](#设置元素的文本内容) +- [HTML 清理](#html-清理) + - [消除不受信任的 HTML (来防止 XSS 攻击)](#消除不受信任的-html-来防止-xss-攻击) +- [参考](#参考) + + + +## 简介 + +jsoup 是一款 Java 的 HTML 解析器,可直接解析某个 URL 地址、HTML 文本内容。它提供了一套非常省力的 API,可通过 DOM,CSS 以及类似于 JQuery 的操作方法来取出和操作数据。 + +jsoup 工作的流程主要如下: + +1. 从一个 URL,文件或字符串中解析 HTML,并加载为一个 `Document` 对象。 +2. 使用 DOM 或 CSS 选择器来取出数据; +3. 可操作 HTML 元素、属性、文本。 + +jsoup 是基于 MIT 协议发布的,可放心使用于商业项目。 + +## 加载 + +### 从 HTML 字符串加载一个文档 + +使用静态 `Jsoup.parse(String html)` 方法或 `Jsoup.parse(String html, String baseUri)` 示例代码: + +```java +String html = "First parse" + + "

    Parsed HTML into a doc.

    "; +Document doc = Jsoup.parse(html); +``` + +> **说明** +> +> `parse(String html, String baseUri)` 这方法能够将输入的 HTML 解析为一个新的文档 (Document),参数 baseUri 是用来将相对 URL 转成绝对 URL,并指定从哪个网站获取文档。如这个方法不适用,你可以使用 `parse(String html)` 方法来解析成 HTML 字符串如上面的示例。 +> +> 只要解析的不是空字符串,就能返回一个结构合理的文档,其中包含(至少) 一个 head 和一个 body 元素。 +> +> 一旦拥有了一个 Document,你就可以使用 Document 中适当的方法或它父类 `Element`和`Node`中的方法来取得相关数据。 + +### 解析一个 body 片断 + +**问题** + +假如你有一个 HTML 片断 (比如. 一个 `div` 包含一对 `p` 标签; 一个不完整的 HTML 文档) 想对它进行解析。这个 HTML 片断可以是用户提交的一条评论或在一个 CMS 页面中编辑 body 部分。 + +**办法** + +使用`Jsoup.parseBodyFragment(String html)`方法. + +``` +String html = "

    Lorem ipsum.

    "; +Document doc = Jsoup.parseBodyFragment(html); +Element body = doc.body(); +``` + +> **说明** +> +> `parseBodyFragment` 方法创建一个空壳的文档,并插入解析过的 HTML 到`body`元素中。假如你使用正常的 `Jsoup.parse(String html)` 方法,通常你也可以得到相同的结果,但是明确将用户输入作为 body 片段处理,以确保用户所提供的任何糟糕的 HTML 都将被解析成 body 元素。 +> +> `Document.body()` 方法能够取得文档 body 元素的所有子元素,与 `doc.getElementsByTag("body")`相同。 + +#### 保证安全 Stay safe + +假如你可以让用户输入 HTML 内容,那么要小心避免跨站脚本攻击。利用基于 `Whitelist` 的清除器和 `clean(String bodyHtml, Whitelist whitelist)`方法来清除用户输入的恶意内容。 + +### 从 URL 加载一个文档 + +使用 `Jsoup.connect(String url)`方法 + +```java +Document doc = Jsoup.connect("http://example.com/").get(); +``` + +> **说明** +> +> `connect(String url)` 方法创建一个新的 `Connection`, 和 `get()` 取得和解析一个 HTML 文件。如果从该 URL 获取 HTML 时发生错误,便会抛出 IOException,应适当处理。 + +`Connection` 接口还提供一个方法链来解决特殊请求,具体如下: + +```java +Document doc = Jsoup.connect("http://example.com") + .data("query", "Java") + .userAgent("Mozilla") + .cookie("auth", "token") + .timeout(3000) + .post(); +``` + +### 从一个文件加载一个文档 + +可以使用静态 `Jsoup.parse(File in, String charsetName, String baseUri)` 方法 + +```java +File input = new File("/tmp/input.html"); +Document doc = Jsoup.parse(input, "UTF-8", "http://example.com/"); +``` + +> **说明** +> +> `parse(File in, String charsetName, String baseUri)` 这个方法用来加载和解析一个 HTML 文件。如在加载文件的时候发生错误,将抛出 IOException,应作适当处理。 +> +> `baseUri` 参数用于解决文件中 URLs 是相对路径的问题。如果不需要可以传入一个空的字符串。 +> +> 另外还有一个方法`parse(File in, String charsetName)` ,它使用文件的路径做为 `baseUri`。 这个方法适用于如果被解析文件位于网站的本地文件系统,且相关链接也指向该文件系统。 + +## 解析 + +### 使用 DOM 方法来遍历一个文档 + +**问题** + +你有一个 HTML 文档要从中提取数据,并了解这个 HTML 文档的结构。 + +**方法** + +将 HTML 解析成一个`Document`之后,就可以使用类似于 DOM 的方法进行操作。示例代码: + +```java +File input = new File("/tmp/input.html"); +Document doc = Jsoup.parse(input, "UTF-8", "http://example.com/"); + +Element content = doc.getElementById("content"); +Elements links = content.getElementsByTag("a"); +for (Element link : links) { + String linkHref = link.attr("href"); + String linkText = link.text(); +} +``` + +**说明** + +`Elements` 这个对象提供了一系列类似于 DOM 的方法来查找元素,抽取并处理其中的数据。 + +具体如下: + +####查找元素 + +- `getElementById(String id)` +- `getElementsByTag(String tag)` +- `getElementsByClass(String className)` +- `getElementsByAttribute(String key)` (and related methods) +- Element siblings: `siblingElements()`, `firstElementSibling()`, `lastElementSibling()`;`nextElementSibling()`, `previousElementSibling()` +- Graph: `parent()`, `children()`, `child(int index)` + +####元素数据 + +- `attr(String key)`获取属性`attr(String key, String value)`设置属性 +- `attributes()`获取所有属性 +- `id()`, `className()` and `classNames()` +- `text()`获取文本内容`text(String value)` 设置文本内容 +- `html()`获取元素内 HTML`html(String value)`设置元素内的 HTML 内容 +- `outerHtml()`获取元素外 HTML 内容 +- `data()`获取数据内容(例如:script 和 style 标签) +- `tag()` and `tagName()` + +####操作 HTML 和文本 + +- `append(String html)`, `prepend(String html)` +- `appendText(String text)`, `prependText(String text)` +- `appendElement(String tagName)`, `prependElement(String tagName)` +- `html(String value)` + +### 使用选择器语法来查找元素 + +**问题** + +你想使用类似于 CSS 或 jQuery 的语法来查找和操作元素。 + +**方法** + +可以使用`Element.select(String selector)` 和 `Elements.select(String selector)` 方法实现: + +```java +File input = new File("/tmp/input.html"); +Document doc = Jsoup.parse(input, "UTF-8", "http://example.com/"); + +Elements links = doc.select("a[href]"); //带有href属性的a元素 +Elements pngs = doc.select("img[src$=.png]"); + //扩展名为.png的图片 + +Element masthead = doc.select("div.masthead").first(); + //class等于masthead的div标签 + +Elements resultLinks = doc.select("h3.r > a"); //在h3元素之后的a元素 +``` + +> **说明** +> +> jsoup elements 对象支持类似于[CSS](http://www.w3.org/TR/2009/PR-css3-selectors-20091215/) (或[jquery](http://jquery.com/))的选择器语法,来实现非常强大和灵活的查找功能。. +> +> 这个`select` 方法在`Document`, `Element`,或`Elements`对象中都可以使用。且是上下文相关的,因此可实现指定元素的过滤,或者链式选择访问。 +> +> Select 方法将返回一个`Elements`集合,并提供一组方法来抽取和处理结果。 + +####Selector 选择器概述 + +- `tagname`: 通过标签查找元素,比如:`a` +- `ns|tag`: 通过标签在命名空间查找元素,比如:可以用 `fb|name` 语法来查找 `` 元素 +- `#id`: 通过 ID 查找元素,比如:`#logo` +- `.class`: 通过 class 名称查找元素,比如:`.masthead` +- `[attribute]`: 利用属性查找元素,比如:`[href]` +- `[^attr]`: 利用属性名前缀来查找元素,比如:可以用`[^data-]` 来查找带有 HTML5 Dataset 属性的元素 +- `[attr=value]`: 利用属性值来查找元素,比如:`[width=500]` +- `[attr^=value]`, `[attr$=value]`, `[attr*=value]`: 利用匹配属性值开头、结尾或包含属性值来查找元素,比如:`[href*=/path/]` +- `[attr\~=regex]`: 利用属性值匹配正则表达式来查找元素,比如: `img[src\~=(?i)\.(png|jpe?g)]` +- `*`: 这个符号将匹配所有元素 + +####Selector 选择器组合使用 + +- `el##id`: 元素+ID,比如: `div##logo` +- `el.class`: 元素+class,比如: `div.masthead` +- `el[attr]`: 元素+class,比如: `a[href]` +- 任意组合,比如:`a[href].highlight` +- `ancestor child`: 查找某个元素下子元素,比如:可以用`.body p` 查找在"body"元素下的所有`p`元素 +- `parent > child`: 查找某个父元素下的直接子元素,比如:可以用`div.content > p` 查找 `p` 元素,也可以用`body > *` 查找 body 标签下所有直接子元素 +- `siblingA + siblingB`: 查找在 A 元素之前第一个同级元素 B,比如:`div.head + div` +- `siblingA \~ siblingX`: 查找 A 元素之前的同级 X 元素,比如:`h1 \~ p` +- `el, el, el`:多个选择器组合,查找匹配任一选择器的唯一元素,例如:`div.masthead, div.logo` + +####伪选择器 selectors + +- `:lt(n)`: 查找哪些元素的同级索引值(它的位置在 DOM 树中是相对于它的父节点)小于 n,比如:`td:lt(3)` 表示小于三列的元素 +- `:gt(n)`:查找哪些元素的同级索引值大于` n``,比如 `: `div p:gt(2)`表示哪些 div 中有包含 2 个以上的 p 元素 +- `:eq(n)`: 查找哪些元素的同级索引值与`n`相等,比如:`form input:eq(1)`表示包含一个 input 标签的 Form 元素 +- `:has(seletor)`: 查找匹配选择器包含元素的元素,比如:`div:has(p)`表示哪些 div 包含了 p 元素 +- `:not(selector)`: 查找与选择器不匹配的元素,比如: `div:not(.logo)` 表示不包含 class=logo 元素的所有 div 列表 +- `:contains(text)`: 查找包含给定文本的元素,搜索不区分大不写,比如: `p:contains(jsoup)` +- `:containsOwn(text)`: 查找直接包含给定文本的元素 +- `:matches(regex)`: 查找哪些元素的文本匹配指定的正则表达式,比如:`div:matches((?i)login)` +- `:matchesOwn(regex)`: 查找自身包含文本匹配指定正则表达式的元素 +- 注意:上述伪选择器索引是从 0 开始的,也就是说第一个元素索引值为 0,第二个元素 index 为 1 等 + +可以查看`Selector` API 参考来了解更详细的内容 + +### 从元素抽取属性,文本和 HTML + +**问题** + +在解析获得一个 Document 实例对象,并查找到一些元素之后,你希望取得在这些元素中的数据。 + +**方法** + +- 要取得一个属性的值,可以使用`Node.attr(String key)` 方法 +- 对于一个元素中的文本,可以使用`Element.text()`方法 +- 对于要取得元素或属性中的 HTML 内容,可以使用`Element.html()`, 或 `Node.outerHtml()`方法 + +示例: + +```java +String html = "

    An example link.

    "; +Document doc = Jsoup.parse(html);//解析HTML字符串返回一个Document实现 +Element link = doc.select("a").first();//查找第一个a元素 + +String text = doc.body().text(); // "An example link"//取得字符串中的文本 +String linkHref = link.attr("href"); // "http://example.com/"//取得链接地址 +String linkText = link.text(); // "example""//取得链接地址中的文本 + +String linkOuterH = link.outerHtml(); + // "example" +String linkInnerH = link.html(); // "example"//取得链接内的html内容 +``` + +> **说明** +> +> 上述方法是元素数据访问的核心办法。此外还其它一些方法可以使用: +> +> - `Element.id()` +> - `Element.tagName()` +> - `Element.className()` and `Element.hasClass(String className)` +> +> 这些访问器方法都有相应的 setter 方法来更改数据 + +**参见** + +- `Element`和`Elements`集合类的参考文档 +- [URLs 处理](http://www.open-open.com/jsoup/working-with-urls.htm) +- [使用 CSS 选择器语法来查找元素](http://www.open-open.com/jsoup/selector-syntax.htm) + +### 处理 URLs + +**问题** + +你有一个包含相对 URLs 路径的 HTML 文档,需要将这些相对路径转换成绝对路径的 URLs。 + +**方法** + +1. 在你解析文档时确保有指定`base URI`,然后 +2. 使用 `abs:` 属性前缀来取得包含`base URI`的绝对路径。代码如下: + +```java +Document doc = Jsoup.connect("http://www.open-open.com").get(); + +Element link = doc.select("a").first(); +String relHref = link.attr("href"); // == "/" +String absHref = link.attr("abs:href"); // "http://www.open-open.com/" + +``` + +> **说明** +> +> 在 HTML 元素中,URLs 经常写成相对于文档位置的相对路径: `...`. 当你使用 `Node.attr(String key)` 方法来取得 a 元素的 href 属性时,它将直接返回在 HTML 源码中指定定的值。 +> +> 假如你需要取得一个绝对路径,需要在属性名前加 `abs:` 前缀。这样就可以返回包含根路径的 URL 地址`attr("abs:href")` +> +> 因此,在解析 HTML 文档时,定义 base URI 非常重要。 +> +> 如果你不想使用`abs:` 前缀,还有一个方法能够实现同样的功能 `Node.absUrl(String key)`。 + +## 数据修改 + +### 设置属性的值 + +**问题** + +在你解析一个 `Document` 之后可能想修改其中的某些属性值,然后再保存到磁盘或都输出到前台页面。 + +**方法** + +可以使用属性设置方法 `Element.attr(String key, String value)`, 和 `Elements.attr(String key, String value)`. + +假如你需要修改一个元素的 `class` 属性,可以使用 `Element.addClass(String className)` 和`Element.removeClass(String className)` 方法。 + +`Elements` 提供了批量操作元素属性和 class 的方法,比如:要为 div 中的每一个 a 元素都添加一个`rel="nofollow"` 可以使用如下方法: + +``` +doc.select("div.comments a").attr("rel", "nofollow"); + +``` + +> **说明** +> +> 与`Element`中的其它方法一样,`attr` 方法也是返回当 `Element` (或在使用选择器是返回 `Elements`集合)。这样能够很方便使用方法连用的书写方式。比如: +> +> ``` +> doc.select("div.masthead").attr("title", "jsoup").addClass("round-box"); +> ``` + +### 设置一个元素的 HTML 内容 + +**问题** + +你需要一个元素中的 HTML 内容 + +**方法** + +可以使用`Element`中的 HTML 设置方法具体如下: + +```java +Element div = doc.select("div").first(); //
    +div.html("

    lorem ipsum

    "); //

    lorem ipsum

    +div.prepend("

    First

    ");//在div前添加html内容 +div.append("

    Last

    ");//在div之后添加html内容 +// 添完后的结果:

    First

    lorem ipsum

    Last

    + +Element span = doc.select("span").first(); // One +span.wrap("
  • "); +// 添完后的结果:
  • One
  • +``` + +> **说明** +> +> - `Element.html(String html)` 这个方法将先清除元素中的 HTML 内容,然后用传入的 HTML 代替。 +> - `Element.prepend(String first)` 和 `Element.append(String last)` 方法用于在分别在元素内部 HTML 的前面和后面添加 HTML 内容 +> - `Element.wrap(String around)` 对元素包裹一个外部 HTML 内容。 +> +> **参见** +> +> 可以查看 API 参考文档中 `Element.prependElement(String tag)`和`Element.appendElement(String tag)` 方法来创建新的元素并作为文档的子元素插入其中。 + +### 设置元素的文本内容 + +**问题** + +你需要修改一个 HTML 文档中的文本内容 + +**方法** + +可以使用`Element`的设置方法:: + +``` +Element div = doc.select("div").first(); //
    +div.text("five > four"); //
    five > four
    +div.prepend("First "); +div.append(" Last"); +// now:
    First five > four Last
    +``` + +> **说明** +> +> 文本设置方法与 [HTML setter](http://jsoup.org/cookbook/modifying-data/set-html) 方法一样: +> +> - `Element.text(String text)` 将清除一个元素中的内部 HTML 内容,然后提供的文本进行代替 +> - `Element.prepend(String first)` 和 `Element.append(String last)` 将分别在元素的内部 html 前后添加文本节点。 +> +> 对于传入的文本如果含有像 `<`, `>` 等这样的字符,将以文本处理,而非 HTML。 + +## HTML 清理 + +### 消除不受信任的 HTML (来防止 XSS 攻击) + +**问题** + +在做网站的时候,经常会提供用户评论的功能。有些不怀好意的用户,会搞一些脚本到评论内容中,而这些脚本可能会破坏整个页面的行为,更严重的是获取一些机要信息,此时需要清理该 HTML,以避免跨站脚本[cross-site scripting](http://en.wikipedia.org/wiki/Cross-site_scripting)攻击(XSS)。 + +**方法** + +使用 jsoup HTML `Cleaner` 方法进行清除,但需要指定一个可配置的 `Whitelist`。 + +```java +String unsafe = + "

    Link

    "; +String safe = Jsoup.clean(unsafe, Whitelist.basic()); +// now:

    Link

    +``` + +**说明** + +XSS 又叫 CSS (Cross Site Script) ,跨站脚本攻击。它指的是恶意攻击者往 Web 页面里插入恶意 html 代码,当用户浏览该页之时,嵌入其中 Web 里面的 html 代码会被执行,从而达到恶意攻击用户的特殊目的。XSS 属于被动式的攻击,因为其被动且不好利用,所以许多人常忽略其危害性。所以我们经常只让用户输入纯文本的内容,但这样用户体验就比较差了。 + +一个更好的解决方法就是使用一个富文本编辑器 WYSIWYG 如 [CKEditor](http://ckeditor.com/) 和 [TinyMCE](http://tinymce.moxiecode.com/)。这些可以输出 HTML 并能够让用户可视化编辑。虽然他们可以在客户端进行校验,但是这样还不够安全,需要在服务器端进行校验并清除有害的 HTML 代码,这样才能确保输入到你网站的 HTML 是安全的。否则,攻击者能够绕过客户端的 Javascript 验证,并注入不安全的 HMTL 直接进入您的网站。 + +jsoup 的 whitelist 清理器能够在服务器端对用户输入的 HTML 进行过滤,只输出一些安全的标签和属性。 + +jsoup 提供了一系列的 `Whitelist` 基本配置,能够满足大多数要求;但如有必要,也可以进行修改,不过要小心。 + +这个 cleaner 非常好用不仅可以避免 XSS 攻击,还可以限制用户可以输入的标签范围。 + +**参见** + +- 参阅[XSS cheat sheet](http://ha.ckers.org/xss.html) ,有一个例子可以了解为什么不能使用正则表达式,而采用安全的 whitelist parser-based 清理器才是正确的选择。 +- 参阅`Cleaner` ,了解如何返回一个 `Document` 对象,而不是字符串 +- 参阅`Whitelist`,了解如何创建一个自定义的 whitelist +- [nofollow](http://en.wikipedia.org/wiki/Nofollow) 链接属性了解 + +## 参考 + +- [jsoup github 托管代码](https://github.com/jhy/jsoup) + +- [jsoup Cookbook](https://jsoup.org/cookbook/) + +- [jsoup Cookbook(中文版)](http://www.open-open.com/jsoup/) + +- [不错的 jsoup 学习笔记](https://github.com/code4craft/jsoup-learning) diff --git a/docs/ecology/other/lombok.md b/docs/ecology/other/lombok.md new file mode 100644 index 00000000..3b9709fa --- /dev/null +++ b/docs/ecology/other/lombok.md @@ -0,0 +1,373 @@ +# Lombok 应用指南 + + + +- [1. Lombok 简介](#1-lombok-简介) +- [2. Lombok 安装](#2-lombok-安装) +- [3. Lombok 使用](#3-lombok-使用) + - [3.1. @Getter and @Setter](#31-getter-and-setter) + - [3.2. @NonNull](#32-nonnull) + - [3.3. @ToString](#33-tostring) + - [3.4. @EqualsAndHashCode](#34-equalsandhashcode) + - [3.5. @Data](#35-data) + - [3.6. @Cleanup](#36-cleanup) + - [3.7. @Synchronized](#37-synchronized) + - [3.8. @SneakyThrows](#38-sneakythrows) + - [3.9. 示例源码](#39-示例源码) +- [4. 参考资料](#4-参考资料) + + + +## 1. Lombok 简介 + +Lombok 是一种 Java 实用工具,可用来帮助开发人员消除 Java 的冗长,尤其是对于简单的 Java 对象(POJO)。它通过注释实现这一目的。通过在开发环境中实现 Lombok,开发人员可以节省构建诸如 `hashCode()` 和 `equals()` 、`getter / setter` 这样的方法以及以往用来分类各种 accessor 和 mutator 的大量时间。 + +## 2. Lombok 安装 + +使 IntelliJ IDEA 支持 Lombok 方式如下: + +- **Intellij 设置支持注解处理** + - 点击 File > Settings > Build > Annotation Processors + - 勾选 Enable annotation processing +- **安装插件** + - 点击 Settings > Plugins > Browse repositories + - 查找 Lombok Plugin 并进行安装 + - 重启 IntelliJ IDEA +- **将 lombok 添加到 pom 文件** + +```xml + + org.projectlombok + lombok + 1.16.8 + +``` + +## 3. Lombok 使用 + +Lombok 提供注解 API 来修饰指定的类: + +### 3.1. @Getter and @Setter + +[@Getter and @Setter](http://jnb.ociweb.com/jnb/jnbJan2010.html#gettersetter) Lombok 代码: + +```java +@Getter @Setter private boolean employed = true; +@Setter(AccessLevel.PROTECTED) private String name; +``` + +等价于 Java 源码: + +```java +private boolean employed = true; +private String name; + +public boolean isEmployed() { + return employed; +} + +public void setEmployed(final boolean employed) { + this.employed = employed; +} + +protected void setName(final String name) { + this.name = name; +} +``` + +### 3.2. @NonNull + +[@NonNull](http://jnb.ociweb.com/jnb/jnbJan2010.html#nonnull) Lombok 代码: + +```java +@Getter @Setter @NonNull +private List members; +``` + +等价于 Java 源码: + +```java +@NonNull +private List members; + +public Family(@NonNull final List members) { + if (members == null) throw new java.lang.NullPointerException("members"); + this.members = members; +} + +@NonNull +public List getMembers() { + return members; +} + +public void setMembers(@NonNull final List members) { + if (members == null) throw new java.lang.NullPointerException("members"); + this.members = members; +} +``` + +### 3.3. @ToString + +[@ToString](http://jnb.ociweb.com/jnb/jnbJan2010.html#tostring) Lombok 代码: + +```java +@ToString(callSuper=true,exclude="someExcludedField") +public class Foo extends Bar { + private boolean someBoolean = true; + private String someStringField; + private float someExcludedField; +} +``` + +等价于 Java 源码: + +```java +public class Foo extends Bar { + private boolean someBoolean = true; + private String someStringField; + private float someExcludedField; + + @java.lang.Override + public java.lang.String toString() { + return "Foo(super=" + super.toString() + + ", someBoolean=" + someBoolean + + ", someStringField=" + someStringField + ")"; + } +} +``` + +### 3.4. @EqualsAndHashCode + +[@EqualsAndHashCode](http://jnb.ociweb.com/jnb/jnbJan2010.html#equals) Lombok 代码: + +```java +@EqualsAndHashCode(callSuper=true,exclude={"address","city","state","zip"}) +public class Person extends SentientBeing { + enum Gender { Male, Female } + + @NonNull private String name; + @NonNull private Gender gender; + + private String ssn; + private String address; + private String city; + private String state; + private String zip; +} +``` + +等价于 Java 源码: + +```java +public class Person extends SentientBeing { + + enum Gender { + /*public static final*/ Male /* = new Gender() */, + /*public static final*/ Female /* = new Gender() */; + } + @NonNull + private String name; + @NonNull + private Gender gender; + private String ssn; + private String address; + private String city; + private String state; + private String zip; + + @java.lang.Override + public boolean equals(final java.lang.Object o) { + if (o == this) return true; + if (o == null) return false; + if (o.getClass() != this.getClass()) return false; + if (!super.equals(o)) return false; + final Person other = (Person)o; + if (this.name == null ? other.name != null : !this.name.equals(other.name)) return false; + if (this.gender == null ? other.gender != null : !this.gender.equals(other.gender)) return false; + if (this.ssn == null ? other.ssn != null : !this.ssn.equals(other.ssn)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = result * PRIME + super.hashCode(); + result = result * PRIME + (this.name == null ? 0 : this.name.hashCode()); + result = result * PRIME + (this.gender == null ? 0 : this.gender.hashCode()); + result = result * PRIME + (this.ssn == null ? 0 : this.ssn.hashCode()); + return result; + } +} +``` + +### 3.5. @Data + +[@Data](http://jnb.ociweb.com/jnb/jnbJan2010.html#data) Lombok 代码: + +```java +@Data(staticConstructor="of") +public class Company { + private final Person founder; + private String name; + private List employees; +} +``` + +等价于 Java 源码: + +```java +public class Company { + private final Person founder; + private String name; + private List employees; + + private Company(final Person founder) { + this.founder = founder; + } + + public static Company of(final Person founder) { + return new Company(founder); + } + + public Person getFounder() { + return founder; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public List getEmployees() { + return employees; + } + + public void setEmployees(final List employees) { + this.employees = employees; + } + + @java.lang.Override + public boolean equals(final java.lang.Object o) { + if (o == this) return true; + if (o == null) return false; + if (o.getClass() != this.getClass()) return false; + final Company other = (Company)o; + if (this.founder == null ? other.founder != null : !this.founder.equals(other.founder)) return false; + if (this.name == null ? other.name != null : !this.name.equals(other.name)) return false; + if (this.employees == null ? other.employees != null : !this.employees.equals(other.employees)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = result * PRIME + (this.founder == null ? 0 : this.founder.hashCode()); + result = result * PRIME + (this.name == null ? 0 : this.name.hashCode()); + result = result * PRIME + (this.employees == null ? 0 : this.employees.hashCode()); + return result; + } + + @java.lang.Override + public java.lang.String toString() { + return "Company(founder=" + founder + ", name=" + name + ", employees=" + employees + ")"; + } +} +``` + +### 3.6. @Cleanup + +[@Cleanup](http://jnb.ociweb.com/jnb/jnbJan2010.html#cleanup) Lombok 代码: + +```java +public void testCleanUp() { + try { + @Cleanup ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(new byte[] {'Y','e','s'}); + System.out.println(baos.toString()); + } catch (IOException e) { + e.printStackTrace(); + } +} +``` + +等价于 Java 源码: + +```java +public void testCleanUp() { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + baos.write(new byte[]{'Y', 'e', 's'}); + System.out.println(baos.toString()); + } finally { + baos.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } +} +``` + +### 3.7. @Synchronized + +[@Synchronized](http://jnb.ociweb.com/jnb/jnbJan2010.html#synchronized) Lombok 代码: + +```java +private DateFormat format = new SimpleDateFormat("MM-dd-YYYY"); + +@Synchronized +public String synchronizedFormat(Date date) { + return format.format(date); +} +``` + +等价于 Java 源码: + +```java +private final java.lang.Object $lock = new java.lang.Object[0]; +private DateFormat format = new SimpleDateFormat("MM-dd-YYYY"); + +public String synchronizedFormat(Date date) { + synchronized ($lock) { + return format.format(date); + } +} +``` + +### 3.8. @SneakyThrows + +[@SneakyThrows](http://jnb.ociweb.com/jnb/jnbJan2010.html#sneaky) Lombok 代码: + +```java +@SneakyThrows +public void testSneakyThrows() { + throw new IllegalAccessException(); +} +``` + +等价于 Java 源码: + +```java +public void testSneakyThrows() { + try { + throw new IllegalAccessException(); + } catch (java.lang.Throwable $ex) { + throw lombok.Lombok.sneakyThrow($ex); + } +} +``` + +### 3.9. 示例源码 + +> 示例源码:[javalib-bean](https://github.com/dunwu/java-tutorial/tree/master/javalib-bean) + +## 4. 参考资料 + +- [Lombok 官网](https://projectlombok.org/) +- [Lombok Github](https://github.com/rzwitserloot/lombok) +- [IntelliJ IDEA - Lombok Plugin](http://plugins.jetbrains.com/plugin/6317-lombok-plugin) diff --git a/docs/ecology/other/reflections.md b/docs/ecology/other/reflections.md new file mode 100644 index 00000000..e24c081d --- /dev/null +++ b/docs/ecology/other/reflections.md @@ -0,0 +1,100 @@ +# Reflections 应用指南 + + + +- [使用](#使用) +- [ReflectionUtils](#reflectionutils) + + + +引入 + +```xml + + org.reflections + reflections + 0.9.11 + +``` + +典型应用 + +```java +Reflections reflections = new Reflections("my.project"); +Set> subTypes = reflections.getSubTypesOf(SomeType.class); +Set> annotated = reflections.getTypesAnnotatedWith(SomeAnnotation.class); +``` + +## 使用 + +基本上,使用 Reflections 首先使用 urls 和 scanners 对其进行实例化 + +```java +//scan urls that contain 'my.package', include inputs starting with 'my.package', use the default scanners +Reflections reflections = new Reflections("my.package"); + +//or using ConfigurationBuilder +new Reflections(new ConfigurationBuilder() + .setUrls(ClasspathHelper.forPackage("my.project.prefix")) + .setScanners(new SubTypesScanner(), + new TypeAnnotationsScanner().filterResultsBy(optionalFilter), ...), + .filterInputsBy(new FilterBuilder().includePackage("my.project.prefix")) + ...); +``` + +然后,使用方便的查询方法 + +```java +// 子类型扫描 +Set> modules = + reflections.getSubTypesOf(com.google.inject.Module.class); +// 类型注解扫描 +Set> singletons = + reflections.getTypesAnnotatedWith(javax.inject.Singleton.class); +// 资源扫描 +Set properties = + reflections.getResources(Pattern.compile(".*\\.properties")); +// 方法注解扫描 +Set resources = + reflections.getMethodsAnnotatedWith(javax.ws.rs.Path.class); +Set injectables = + reflections.getConstructorsAnnotatedWith(javax.inject.Inject.class); +// 字段注解扫描 +Set ids = + reflections.getFieldsAnnotatedWith(javax.persistence.Id.class); +// 方法参数扫描 +Set someMethods = + reflections.getMethodsMatchParams(long.class, int.class); +Set voidMethods = + reflections.getMethodsReturn(void.class); +Set pathParamMethods = + reflections.getMethodsWithAnyParamAnnotated(PathParam.class); +// 方法参数名扫描 +List parameterNames = + reflections.getMethodParamNames(Method.class) +// 方法使用扫描 +Set usages = + reflections.getMethodUsages(Method.class) +``` + +说明: + +- 如果未配置扫描程序,则将使用默认值 - SubTypesScanner 和 TypeAnnotationsScanner。 +- 还可以配置类加载器,它将用于从名称中解析运行时类。 +- Reflection 默认情况下会扩展超类型。 这解决了传输 URL 不被扫描的一些问题。 + +## ReflectionUtils + +```java +import static org.reflections.ReflectionUtils.*; + +Set getters = getAllMethods(someClass, + withModifier(Modifier.PUBLIC), withPrefix("get"), withParametersCount(0)); + +//or +Set listMethodsFromCollectionToBoolean = + getAllMethods(List.class, + withParametersAssignableTo(Collection.class), withReturnType(boolean.class)); + +Set fields = getAllFields(SomeClass.class, withAnnotation(annotation), withTypeAssignableTo(type)); +``` diff --git a/docs/ecology/other/thumbnailator.md b/docs/ecology/other/thumbnailator.md new file mode 100644 index 00000000..f47d9bc3 --- /dev/null +++ b/docs/ecology/other/thumbnailator.md @@ -0,0 +1,244 @@ +# Thumbnailator 应用指南 + + + +- [简介](#简介) +- [核心 API](#核心-api) + - [Thumbnails](#thumbnails) + - [Thumbnails.Builder](#thumbnailsbuilder) + - [工作流](#工作流) +- [实战](#实战) + - [安装](#安装) + - [图片缩放](#图片缩放) + - [图片旋转](#图片旋转) + - [加水印](#加水印) + - [批量处理图片](#批量处理图片) +- [参考](#参考) + + + +## 简介 + +`Thumbnailator` 是一个开源的 **Java** 项目,它提供了非常简单的 API 来对图片进行缩放、旋转以及加水印的处理。 + +有多简单呢?简单到一行代码就可以完成图片处理。形式如下: + +```java +Thumbnails.of(new File("path/to/directory").listFiles()) + .size(640, 480) + .outputFormat("jpg") + .toFiles(Rename.PREFIX_DOT_THUMBNAIL); +``` + +当然,Thumbnailator 还有一些使用细节,下面我会一一道来。 + +## 核心 API + +### Thumbnails + +`Thumbnails` 是使用 Thumbnailator 创建缩略图的主入口。 + +它提供了一组初始化 `Thumbnails.Builder` 的接口。 + +先看下这组接口的声明: + +```java +// 可变长度参数列表 +public static Builder of(String... files) {...} +public static Builder of(File... files) {...} +public static Builder of(URL... urls) {...} +public static Builder of(InputStream... inputStreams) {...} +public static Builder of(BufferedImage... images) {...} +// 迭代器(所有实现 Iterable 接口的 Java 对象都可以,当然也包括 List、Set) +public static Builder fromFilenames(Iterable files) {...} +public static Builder fromFiles(Iterable files) {...} +public static Builder fromURLs(Iterable urls) {...} +public static Builder fromInputStreams(Iterable inputStreams) {...} +public static Builder fromImages(Iterable images) {...} +``` + +很显然,**Thumbnails 允许通过传入文件名、文件、网络图的 URL、图片流、图片缓存多种方式来初始化构造器**。 + +因此,你可以根据实际需求来灵活的选择图片的输入方式。 + +需要注意一点:**如果输入是多个对象(无论你是直接输入容器对象或使用可变参数方式传入多个对象),则输出也必须选用输出多个对象的方式,否则会报异常。** + +### Thumbnails.Builder + +`Thumbnails.Builder` 是 `Thumbnails` 的内部静态类。它用于设置生成缩略图任务的相关参数。 + +**_注:`Thumbnails.Builder` 的构造函数是私有函数。所以,它只允许通过 `Thumbnails` 的实例化函数来进行初始化。_** + +#### 设置参数的函数 + +`Thumbnails.Builder` 提供了一组函数链形式的接口来设置缩放图参数。 + +以设置大小函数为例: + +```java +public Builder size(int width, int height) +{ + updateStatus(Properties.SIZE, Status.ALREADY_SET); + updateStatus(Properties.SCALE, Status.CANNOT_SET); + + validateDimensions(width, height); + this.width = width; + this.height = height; + + return this; +} +``` + +通过返回 this 指针,使得设置参数函数可以以链式调用的方式来使用,形式如下: + +```java +Thumbnails.of(new File("original.jpg")) + .size(160, 160) + .rotate(90) + .watermark(Positions.BOTTOM_RIGHT, ImageIO.read(new File("watermark.png")), 0.5f) + .outputQuality(0.8) + .toFile(new File("image-with-watermark.jpg")); +``` + +好处,不言自明:那就是大大简化了代码。 + +#### 输出函数 + +`Thumbnails.Builder` 提供了一组重载函数来输出生成的缩放图。 + +函数声明如下: + +```java +// 返回图片缓存 +public List asBufferedImages() throws IOException {...} +public BufferedImage asBufferedImage() throws IOException {...} +// 返回文件列表 +public List asFiles(Iterable iterable) throws IOException {...} +public List asFiles(Rename rename) throws IOException {...} +public List asFiles(File destinationDir, Rename rename) throws IOException {...} +// 创建文件 +public void toFile(File outFile) throws IOException {...} +public void toFile(String outFilepath) throws IOException {...} +public void toFiles(Iterable iterable) throws IOException {...} +public void toFiles(Rename rename) throws IOException {...} +public void toFiles(File destinationDir, Rename rename) throws IOException {...} +// 创建输出流 +public void toOutputStream(OutputStream os) throws IOException {...} +public void toOutputStreams(Iterable iterable) throws IOException {...} +``` + +### 工作流 + +Thumbnailator 的工作步骤十分简单,可分为三步: + +1. **输入**:`Thumbnails` 根据输入初始化构造器—— `Thumbnails.Builder` 。 + +2. **设置**:`Thumbnails.Builder` 设置缩放图片的参数。 + +3. **输出**:`Thumbnails.Builder` 输出图片文件或图片流。 + +> 更多详情可以参考: [Thumbnailator 官网 javadoc](https://coobird.github.io/thumbnailator/javadoc/0.4.8/) + +## 实战 + +前文介绍了 Thumbnailator 的核心 API,接下来我们就可以通过实战来看看 Thumbnailator 究竟可以做些什么。 + +Thumbnailator 生成什么样的图片,是根据设置参数来决定的。 + +### 安装 + +maven 项目中引入依赖: + +```xml + + net.coobird + thumbnailator + [0.4, 0.5) + +``` + +### 图片缩放 + +`Thumbnails.Builder` 的 `size` 函数可以设置新图片精确的宽度和高度,也可以用 `scale` 函数设置缩放比例。 + +```java +Thumbnails.of("oldFile.png") + .size(16, 16) + .toFile("newFile_16_16.png"); + +Thumbnails.of("oldFile.png") + .scale(2.0) + .toFile("newFile_scale_2.0.png"); + +Thumbnails.of("oldFile.png") + .scale(1.0, 0.5) + .toFile("newFile_scale_1.0_0.5.png"); +``` + +**oldFile.png** + +![img](http://upload-images.jianshu.io/upload_images/3101171-ba63439898602e8f.png) + +**newFile_scale_1.0_0.5.png** + +![img](http://upload-images.jianshu.io/upload_images/3101171-a01ea4515fff865d.png) + +### 图片旋转 + +`Thumbnails.Builder` 的 `size` 函数可以设置新图片的旋转角度。 + +```java +Thumbnails.of("oldFile.png") + .scale(0.8) + .rotate(90) + .toFile("newFile_rotate_90.png"); + +Thumbnails.of("oldFile.png") + .scale(0.8) + .rotate(180) + .toFile("newFile_rotate_180.png"); +``` + +**newFile_rotate_90.png** + +![img](http://upload-images.jianshu.io/upload_images/3101171-17d54bc33b38d45b.png) + +### 加水印 + +`Thumbnails.Builder` 的 `watermark` 函数可以为图片添加水印图片。第一个参数是水印的位置;第二个参数是水印图片的缓存数据;第三个参数是透明度。 + +```java +BufferedImage watermarkImage = ImageIO.read(new File("wartermarkFile.png")); +Thumbnails.of("oldFile.png") + .scale(0.8) + .watermark(Positions.BOTTOM_LEFT, watermarkImage, 0.5f) + .toFile("newFile_watermark.png"); +``` + +**wartermarkFile.png** + +![img](http://upload-images.jianshu.io/upload_images/3101171-97909ee6c066c195.png?imageMogr2/auto-orient/strip) + +**newFile_watermark.png** + +![img](http://upload-images.jianshu.io/upload_images/3101171-93eb7ef71b811a0c.png) + +### 批量处理图片 + +下面以批量给图片加水印来展示一下如何处理多个图片文件。 + +```java +BufferedImage watermarkImage = ImageIO.read(new File("wartermarkFile.png")); + +File destinationDir = new File("D:\\watermark\\"); +Thumbnails.of("oldFile.png", "oldFile2.png") + .scale(0.8) + .watermark(Positions.BOTTOM_LEFT, watermarkImage, 0.5f) + .toFiles(destinationDir, Rename.PREFIX_DOT_THUMBNAIL); +``` + +> **需要参考完整测试例代码请** [**点击这里**](https://github.com/dunwu/JavaParty/blob/master/toolbox/image/src/test/java/org/zp/image/ThumbnailatorTest.java) + +## 参考 + +[Thumbnailator 官方示例文档](https://github.com/coobird/thumbnailator/wiki/Examples) diff --git a/docs/ecology/other/zxing.md b/docs/ecology/other/zxing.md new file mode 100644 index 00000000..d7055a55 --- /dev/null +++ b/docs/ecology/other/zxing.md @@ -0,0 +1,93 @@ +# ZXing 应用指南 + + + +- [简介](#简介) +- [实战](#实战) + - [安装](#安装) + - [生成二维码图片](#生成二维码图片) + - [解析二维码图片](#解析二维码图片) +- [参考](#参考) + + + +## 简介 + +`ZXing` 是一个开源 Java 类库用于解析多种格式的 1D/2D 条形码。目标是能够对 QR 编码、Data Matrix、UPC 的 1D 条形码进行解码。 其提供了多种平台下的客户端包括:J2ME、J2SE 和 Android。 + +官网:[ZXing github 仓库](https://github.com/zxing/zxing) + +## 实战 + +**_本例演示如何在一个非 android 的 Java 项目中使用 ZXing 来生成、解析二维码图片。_** + +### 安装 + +maven 项目只需引入依赖: + +```xml + + com.google.zxing + core + 3.3.0 + + + com.google.zxing + javase + 3.3.0 + +``` + +如果非 maven 项目,就去官网下载发布版本:[下载地址](https://github.com/zxing/zxing/releases) + +### 生成二维码图片 + +ZXing 生成二维码图片有以下步骤: + +1. `com.google.zxing.MultiFormatWriter` 根据内容以及图像编码参数生成图像 2D 矩阵。 +2. ​ `com.google.zxing.client.j2se.MatrixToImageWriter` 根据图像矩阵生成图片文件或图片缓存 `BufferedImage` 。 + +```java +public void encode(String content, String filepath) throws WriterException, IOException { + int width = 100; + int height = 100; + Map encodeHints = new HashMap(); + encodeHints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); + BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, width, height, encodeHints); + Path path = FileSystems.getDefault().getPath(filepath); + MatrixToImageWriter.writeToPath(bitMatrix, "png", path); +} +``` + +### 解析二维码图片 + +ZXing 解析二维码图片有以下步骤: + +1. 使用 `javax.imageio.ImageIO` 读取图片文件,并存为一个 `java.awt.image.BufferedImage` 对象。 + +2. 将 `java.awt.image.BufferedImage` 转换为 ZXing 能识别的 `com.google.zxing.BinaryBitmap` 对象。 + +3. `com.google.zxing.MultiFormatReader` 根据图像解码参数来解析 `com.google.zxing.BinaryBitmap` 。 + +```java +public String decode(String filepath) throws IOException, NotFoundException { + BufferedImage bufferedImage = ImageIO.read(new FileInputStream(filepath)); + LuminanceSource source = new BufferedImageLuminanceSource(bufferedImage); + Binarizer binarizer = new HybridBinarizer(source); + BinaryBitmap bitmap = new BinaryBitmap(binarizer); + HashMap decodeHints = new HashMap(); + decodeHints.put(DecodeHintType.CHARACTER_SET, "UTF-8"); + Result result = new MultiFormatReader().decode(bitmap, decodeHints); + return result.getText(); +} +``` + +完整参考示例:[测试例代码](https://github.com/dunwu/JavaParty/blob/master/toolbox/image/src/test/java/org/zp/image/QRCodeUtilTest.java) + +以下是一个生成的二维码图片示例: + +![img](http://upload-images.jianshu.io/upload_images/3101171-26b73730088f0ab8.png) + +## 参考 + +[ZXing github 仓库](https://github.com/zxing/zxing) diff --git a/docs/ecology/search/README.md b/docs/ecology/search/README.md new file mode 100644 index 00000000..1cbcdf08 --- /dev/null +++ b/docs/ecology/search/README.md @@ -0,0 +1,49 @@ +# Java 和搜索引擎 + +## Elastic 技术栈 + +> **Elastic 技术栈通常被用来作为日志中心。** +> +> ELK 是 elastic 公司旗下三款产品 [ElasticSearch](https://www.elastic.co/products/elasticsearch) 、[Logstash](https://www.elastic.co/products/logstash) 、[Kibana](https://www.elastic.co/products/kibana) 的首字母组合。 +> +> [ElasticSearch](https://www.elastic.co/products/elasticsearch) 是一个基于 [Lucene](http://lucene.apache.org/core/documentation.html) 构建的开源,分布式,RESTful 搜索引擎。 +> +> [Logstash](https://www.elastic.co/products/logstash) 传输和处理你的日志、事务或其他数据。 +> +> [Kibana](https://www.elastic.co/products/kibana) 将 Elasticsearch 的数据分析并渲染为可视化的报表。 +> +> Elastic 技术栈,在 ELK 的基础上扩展了一些新的产品,如:[Beats](https://www.elastic.co/products/beats) 、[X-Pack](https://www.elastic.co/products/x-pack) 。 + +- [Elastic 技术栈快速入门](elastic-quickstart.md) +- [ElasticSearch](elastic-elasticsearch.md) +- [Beats](elastic-beats.md) +- [Kibana](elastic-kibana.md) +- [Logstash](elastic-logstash.md) +- [ElasticSearch 运维](elastic-elasticsearch-ops.md) +- [Beats 运维](elastic-beats-ops.md) +- [Kibana 运维](elastic-kibana-ops.md) +- [Logstash 运维](elastic-logstash-ops.md) + +### 学习资源 + +- **官方资源** + - [Elasticsearch 官网](https://www.elastic.co/cn/products/elasticsearch) + - [Elasticsearch Github](https://github.com/elastic/elasticsearch) + - [Elasticsearch 官方文档](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html) + - [Logstash 官网](https://www.elastic.co/cn/products/logstash) + - [Logstash Github](https://github.com/elastic/logstash) + - [Logstash 官方文档](https://www.elastic.co/guide/en/logstash/current/index.html) + - [Kibana 官网](https://www.elastic.co/cn/products/kibana) + - [Kibana Github](https://github.com/elastic/kibana) + - [Kibana 官方文档](https://www.elastic.co/guide/en/kibana/current/index.html) + - [Beats 官网](https://www.elastic.co/cn/products/beats) + - [Beats Github](https://github.com/elastic/beats) + - [Beats 官方文档](https://www.elastic.co/guide/en/beats/libbeat/current/index.html) +- **第三方工具** + - [logstash-logback-encoder](https://github.com/logstash/logstash-logback-encoder) +- **教程** + - [Elasticsearch 权威指南(中文版)](https://es.xiaoleilu.com/index.html) + - [ELK Stack 权威指南](https://github.com/chenryn/logstash-best-practice-cn) +- **博文** + - [Elasticsearch+Logstash+Kibana 教程](https://www.cnblogs.com/xing901022/p/4704319.html) + - [ELK(Elasticsearch、Logstash、Kibana)安装和配置](https://github.com/judasn/Linux-Tutorial/blob/master/ELK-Install-And-Settings.md) diff --git a/docs/ecology/search/elastic-beats-ops.md b/docs/ecology/search/elastic-beats-ops.md new file mode 100644 index 00000000..19ad8fd9 --- /dev/null +++ b/docs/ecology/search/elastic-beats-ops.md @@ -0,0 +1,238 @@ +# Filebeat 运维 + +> Beats 平台集合了多种单一用途数据采集器。它们从成百上千或成千上万台机器和系统向 Logstash 或 Elasticsearch 发送数据。 +> +> 因为我只接触过 Filebeat,所有本文仅介绍 Filebeat 的日常运维。 + +## 1. Filebeat 安装 + +### 1.1. 环境要求 + +> 版本:Elastic Stack 7.4 + +### 1.2. 安装步骤 + +Unix / Linux 系统建议使用下面方式安装,因为比较通用。 + +``` +wget https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-6.1.1-linux-x86_64.tar.gz +tar -zxf filebeat-6.1.1-linux-x86_64.tar.gz +``` + +> 更多内容可以参考:[filebeat-installation](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-installation.html) + +## 2. Filebeat 配置 + +> 首先,必须要知道的是:`filebeat.yml` 是 filebeat 的配置文件。其路径会因为你安装方式而有所不同。 +> +> Beat 所有系列产品的配置文件都基于 [YAML](http://www.yaml.org/) 格式,FileBeat 当然也不例外。 +> +> 更多 filebeat 配置内容可以参考:[配置 filebeat](https://www.elastic.co/guide/en/beats/filebeat/current/configuring-howto-filebeat.html) +> +> 更多 filebeat.yml 文件格式内容可以参考:[filebeat.yml 文件格式](https://www.elastic.co/guide/en/beats/libbeat/6.1/config-file-format.html) + +filebeat.yml 部分配置示例: + +```yml +filebeat: + prospectors: + - type: log + paths: + - /var/log/*.log + multiline: + pattern: '^[' + match: after +``` + +### 2.1. 重要配置项 + +> 下面我将列举 Filebeat 的较为重要的配置项。 +> +> 如果想了解更多配置信息,可以参考: +> +> 更多 filebeat 配置内容可以参考:[配置 filebeat](https://www.elastic.co/guide/en/beats/filebeat/current/configuring-howto-filebeat.html) +> +> 更多 filebeat.yml 文件格式内容可以参考:[filebeat.yml 文件格式](https://www.elastic.co/guide/en/beats/libbeat/6.1/config-file-format.html) + +#### 2.1.1. filebeat.prospectors + +(文件监视器)用于指定需要关注的文件。 + +**示例** + +```yaml +filebeat.prospectors: + - type: log + enabled: true + paths: + - /var/log/*.log +``` + +#### 2.1.2. output.elasticsearch + +如果你希望使用 filebeat 直接向 elasticsearch 输出数据,需要配置 output.elasticsearch 。 + +**示例** + +```yaml +output.elasticsearch: + hosts: ['192.168.1.42:9200'] +``` + +#### 2.1.3. output.logstash + +如果你希望使用 filebeat 向 logstash 输出数据,然后由 logstash 再向 elasticsearch 输出数据,需要配置 output.logstash。 + +> **注意** +> +> 相比于向 elasticsearch 输出数据,个人更推荐向 logstash 输出数据。 +> +> 因为 logstash 和 filebeat 一起工作时,如果 logstash 忙于处理数据,会通知 FileBeat 放慢读取速度。一旦拥塞得到解决,FileBeat 将恢复到原来的速度并继续传播。这样,可以减少管道超负荷的情况。 + +**示例** + +```yaml +output.logstash: + hosts: ['127.0.0.1:5044'] +``` + +此外,还需要在 logstash 的配置文件(如 logstash.conf)中指定 beats input 插件: + +```yaml +input { + beats { + port => 5044 # 此端口需要与 filebeat.yml 中的端口相同 + } +} + +# The filter part of this file is commented out to indicate that it is +# optional. +# filter { +# +# } + +output { + elasticsearch { + hosts => "localhost:9200" + manage_template => false + index => "%{[@metadata][beat]}-%{[@metadata][version]}-%{+YYYY.MM.dd}" + document_type => "%{[@metadata][type]}" + } +} +``` + +#### 2.1.4. setup.kibana + +如果打算使用 Filebeat 提供的 Kibana 仪表板,需要配置 setup.kibana 。 + +**示例** + +```yaml +setup.kibana: + host: 'localhost:5601' +``` + +#### 2.1.5. setup.template.settings + +在 Elasticsearch 中,[索引模板](https://www.elastic.co/guide/en/elasticsearch/reference/6.1/indices-templates.html)用于定义设置和映射,以确定如何分析字段。 + +在 Filebeat 中,setup.template.settings 用于配置索引模板。 + +Filebeat 推荐的索引模板文件由 Filebeat 软件包安装。如果您接受 filebeat.yml 配置文件中的默认配置,Filebeat 在成功连接到 Elasticsearch 后自动加载模板。 + +您可以通过在 Filebeat 配置文件中配置模板加载选项来禁用自动模板加载,或加载自己的模板。您还可以设置选项来更改索引和索引模板的名称。 + +> **参考** +> +> 更多内容可以参考:[filebeat-template](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-template.html) +> +> **说明** +> +> 如无必要,使用 Filebeat 配置文件中的默认索引模板即可。 + +#### 2.1.6. setup.dashboards + +Filebeat 附带了示例 Kibana 仪表板。在使用仪表板之前,您需要创建索引模式 `filebeat- *`,并将仪表板加载到 Kibana 中。为此,您可以运行 `setup` 命令或在 `filebeat.yml` 配置文件中配置仪表板加载。 + +为了在 Kibana 中加载 Filebeat 的仪表盘,需要在 `filebeat.yml` 配置中启动开关: + +``` +setup.dashboards.enabled: true +``` + +> **参考** +> +> 更多内容可以参考:[configuration-dashboards](https://www.elastic.co/guide/en/beats/filebeat/current/configuration-dashboards.html) + +## 3. Filebeat 命令 + +filebeat 提供了一系列命令来完成各种功能。 + +执行命令方式: + +```bash +./filebeat COMMAND +``` + +> **参考** +> +> 更多内容可以参考:[command-line-options](https://www.elastic.co/guide/en/beats/filebeat/current/command-line-options.html) +> +> **说明** +> +> 个人认为命令行没有必要一一掌握,因为绝大部分功能都可以通过配置来完成。且通过命令行指定功能这种方式要求每次输入同样参数,不利于固化启动方式。 +> +> 最重要的当然是启动命令 run 了。 +> +> **示例** 指定配置文件启动 +> +> ```bash +> ./filebeat run -e -c filebeat.yml -d "publish" +> ./filebeat -e -c filebeat.yml -d "publish" # run 可以省略 +> ``` + +## 4. Filebeat 模块 + +> [Filebeat](https://www.elastic.co/cn/products/beats/filebeat) 和 [Metricbeat](https://www.elastic.co/cn/products/beats/metricbeat) 内部集成了一系列模块,用以简化常见日志格式(例如 NGINX、Apache 或诸如 Redis 或 Docker 等系统指标)的收集、解析和可视化过程。 + +- 配置 elasticsearch 和 kibana + +``` +output.elasticsearch: + hosts: ["myEShost:9200"] + username: "elastic" + password: "elastic" +setup.kibana: + host: "mykibanahost:5601" + username: "elastic" + password: "elastic +``` + +> username 和 password 是可选的,如果不需要认证则不填。 + +- 初始化环境 + +执行下面命令,filebeat 会加载推荐索引模板。 + +``` +./filebeat setup -e +``` + +- 指定模块 + +执行下面命令,指定希望加载的模块。 + +``` +./filebeat -e --modules system,nginx,mysql +``` + +> 更多内容可以参考: +> +> - [配置 filebeat 模块](https://www.elastic.co/guide/en/beats/filebeat/current/configuration-filebeat-modules.html) +> - [filebeat 支持模块](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-modules.html) + +## 5. 参考资料 + +- [Beats 官网](https://www.elastic.co/cn/products/beats) +- [Beats Github](https://github.com/elastic/beats) +- [Beats 官方文档](https://www.elastic.co/guide/en/beats/libbeat/current/index.html) diff --git a/docs/ecology/search/elastic-beats.md b/docs/ecology/search/elastic-beats.md new file mode 100644 index 00000000..b6bce288 --- /dev/null +++ b/docs/ecology/search/elastic-beats.md @@ -0,0 +1,296 @@ +--- +title: Elastic 技术栈之 Filebeat +date: 2017-01-03 +categories: +- javatool +tags: +- java +- javatool +- log +- elastic +--- + +# Elastic 技术栈之 Filebeat + +## 简介 + +Beats 是安装在服务器上的数据中转代理。 + +Beats 可以将数据直接传输到 Elasticsearch 或传输到 Logstash 。 + +
    ![img](https://www.elastic.co/guide/en/beats/libbeat/current/images/beats-platform.png)
    + +Beats 有多种类型,可以根据实际应用需要选择合适的类型。 + +常用的类型有: + +- **Packetbeat:**网络数据包分析器,提供有关您的应用程序服务器之间交换的事务的信息。 +- **Filebeat:**从您的服务器发送日志文件。 +- **Metricbeat:**是一个服务器监视代理程序,它定期从服务器上运行的操作系统和服务收集指标。 +- **Winlogbeat:**提供Windows事件日志。 + +> **参考** +> +> 更多 Beats 类型可以参考:[community-beats](https://www.elastic.co/guide/en/beats/libbeat/current/community-beats.html) +> +> **说明** +> +> 由于本人工作中只应用了 FileBeat,所以后面内容仅介绍 FileBeat 。 + +### FileBeat 的作用 + +相比 Logstash,FileBeat 更加轻量化。 + +在任何环境下,应用程序都有停机的可能性。 Filebeat 读取并转发日志行,如果中断,则会记住所有事件恢复联机状态时所在位置。 + +Filebeat带有内部模块(auditd,Apache,Nginx,System和MySQL),可通过一个指定命令来简化通用日志格式的收集,解析和可视化。 + +FileBeat 不会让你的管道超负荷。FileBeat 如果是向 Logstash 传输数据,当 Logstash 忙于处理数据,会通知 FileBeat 放慢读取速度。一旦拥塞得到解决,FileBeat 将恢复到原来的速度并继续传播。 + +
    ![img](https://www.elastic.co/guide/en/beats/filebeat/current/images/filebeat.png)
    + +## 安装 + +Unix / Linux 系统建议使用下面方式安装,因为比较通用。 + +``` +wget https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-6.1.1-linux-x86_64.tar.gz +tar -zxf filebeat-6.1.1-linux-x86_64.tar.gz +``` + +> **参考** +> +> 更多内容可以参考:[filebeat-installation](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-installation.html) + +## 配置 + +### 配置文件 + +首先,需要知道的是:`filebeat.yml` 是 filebeat 的配置文件。配置文件的路径会因为你安装方式的不同而变化。 + +Beat 所有系列产品的配置文件都基于 [YAML](http://www.yaml.org/) 格式,FileBeat 当然也不例外。 + +filebeat.yml 部分配置示例: + +```yaml +filebeat: + prospectors: + - type: log + paths: + - /var/log/*.log + multiline: + pattern: '^[' + match: after +``` + +> **参考** +> +> 更多 filebeat 配置内容可以参考:[配置 filebeat](https://www.elastic.co/guide/en/beats/filebeat/current/configuring-howto-filebeat.html) +> +> 更多 filebeat.yml 文件格式内容可以参考:[filebeat.yml 文件格式](https://www.elastic.co/guide/en/beats/libbeat/6.1/config-file-format.html) + +### 重要配置项 + +#### filebeat.prospectors + +(文件监视器)用于指定需要关注的文件。 + +**示例** + +```yaml +filebeat.prospectors: +- type: log + enabled: true + paths: + - /var/log/*.log +``` + +#### output.elasticsearch + +如果你希望使用 filebeat 直接向 elasticsearch 输出数据,需要配置 output.elasticsearch 。 + +**示例** + +```yaml +output.elasticsearch: + hosts: ["192.168.1.42:9200"] +``` + +#### output.logstash + +如果你希望使用 filebeat 向 logstash输出数据,然后由 logstash 再向elasticsearch 输出数据,需要配置 output.logstash。 + +> **注意** +> +> 相比于向 elasticsearch 输出数据,个人更推荐向 logstash 输出数据。 +> +> 因为 logstash 和 filebeat 一起工作时,如果 logstash 忙于处理数据,会通知 FileBeat 放慢读取速度。一旦拥塞得到解决,FileBeat 将恢复到原来的速度并继续传播。这样,可以减少管道超负荷的情况。 + +**示例** + +```yaml +output.logstash: + hosts: ["127.0.0.1:5044"] +``` + +此外,还需要在 logstash 的配置文件(如 logstash.conf)中指定 beats input 插件: + +```yaml +input { + beats { + port => 5044 # 此端口需要与 filebeat.yml 中的端口相同 + } +} + +# The filter part of this file is commented out to indicate that it is +# optional. +# filter { +# +# } + +output { + elasticsearch { + hosts => "localhost:9200" + manage_template => false + index => "%{[@metadata][beat]}-%{[@metadata][version]}-%{+YYYY.MM.dd}" + document_type => "%{[@metadata][type]}" + } +} +``` + +#### setup.kibana + +如果打算使用 Filebeat 提供的 Kibana 仪表板,需要配置 setup.kibana 。 + +**示例** + +```yaml +setup.kibana: + host: "localhost:5601" +``` + +#### setup.template.settings + +在 Elasticsearch 中,[索引模板](https://www.elastic.co/guide/en/elasticsearch/reference/6.1/indices-templates.html)用于定义设置和映射,以确定如何分析字段。 + +在 Filebeat 中,setup.template.settings 用于配置索引模板。 + +Filebeat 推荐的索引模板文件由 Filebeat 软件包安装。如果您接受 filebeat.yml 配置文件中的默认配置,Filebeat在成功连接到 Elasticsearch 后自动加载模板。 + +您可以通过在 Filebeat 配置文件中配置模板加载选项来禁用自动模板加载,或加载自己的模板。您还可以设置选项来更改索引和索引模板的名称。 + +> **参考** +> +> 更多内容可以参考:[filebeat-template](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-template.html) +> +> **说明** +> +> 如无必要,使用 Filebeat 配置文件中的默认索引模板即可。 + +#### setup.dashboards + +Filebeat 附带了示例 Kibana 仪表板。在使用仪表板之前,您需要创建索引模式 `filebeat- *`,并将仪表板加载到Kibana 中。为此,您可以运行 `setup` 命令或在 `filebeat.yml` 配置文件中配置仪表板加载。 + +为了在 Kibana 中加载 Filebeat 的仪表盘,需要在 `filebeat.yml` 配置中启动开关: + +``` +setup.dashboards.enabled: true +``` + +> **参考** +> +> 更多内容可以参考:[configuration-dashboards](https://www.elastic.co/guide/en/beats/filebeat/current/configuration-dashboards.html) +> + +## 命令 + +filebeat 提供了一系列命令来完成各种功能。 + +执行命令方式: + +```bash +./filebeat COMMAND +``` + +> **参考** +> +> 更多内容可以参考:[command-line-options](https://www.elastic.co/guide/en/beats/filebeat/current/command-line-options.html) +> +> **说明** +> +> 个人认为命令行没有必要一一掌握,因为绝大部分功能都可以通过配置来完成。且通过命令行指定功能这种方式要求每次输入同样参数,不利于固化启动方式。 +> +> 最重要的当然是启动命令 run 了。 +> +> **示例** 指定配置文件启动 +> +> ```bash +> ./filebeat run -e -c filebeat.yml -d "publish" +> ./filebeat -e -c filebeat.yml -d "publish" # run 可以省略 +> ``` + +## 模块 + +Filebeat 提供了一套预构建的模块,让您可以快速实施和部署日志监视解决方案,并附带示例仪表板和数据可视化。这些模块支持常见的日志格式,例如Nginx,Apache2和MySQL 等。 + +### 运行模块的步骤 + +- 配置 elasticsearch 和 kibana + +``` +output.elasticsearch: + hosts: ["myEShost:9200"] + username: "elastic" + password: "elastic" +setup.kibana: + host: "mykibanahost:5601" + username: "elastic" + password: "elastic +``` + +> username 和 password 是可选的,如果不需要认证则不填。 + +- 初始化环境 + +执行下面命令,filebeat 会加载推荐索引模板。 + +``` +./filebeat setup -e +``` + +- 指定模块 + +执行下面命令,指定希望加载的模块。 + +``` +./filebeat -e --modules system,nginx,mysql +``` + +> **参考** +> +> 更多内容可以参考: [配置 filebeat 模块](https://www.elastic.co/guide/en/beats/filebeat/current/configuration-filebeat-modules.html) | [filebeat 支持模块](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-modules.html) + +## 原理 + +Filebeat 有两个主要组件: + +harvester:负责读取一个文件的内容。它会逐行读取文件内容,并将内容发送到输出目的地。 + +prospector:负责管理 harvester 并找到所有需要读取的文件源。比如类型是日志,prospector 就会遍历制定路径下的所有匹配要求的文件。 + +```yaml +filebeat.prospectors: +- type: log + paths: + - /var/log/*.log + - /var/path2/*.log +``` + +Filebeat保持每个文件的状态,并经常刷新注册表文件中的磁盘状态。状态用于记住 harvester 正在读取的最后偏移量,并确保发送所有日志行。 + +Filebeat 将每个事件的传递状态存储在注册表文件中。所以它能保证事件至少传递一次到配置的输出,没有数据丢失。 + +## 资料 + +[Beats 官方文档](https://www.elastic.co/guide/en/beats/libbeat/current/index.html) + diff --git a/docs/ecology/search/elastic-elasticsearch-ops.md b/docs/ecology/search/elastic-elasticsearch-ops.md new file mode 100644 index 00000000..a27f2626 --- /dev/null +++ b/docs/ecology/search/elastic-elasticsearch-ops.md @@ -0,0 +1,208 @@ +# Elasticsearch 运维 + +> [Elasticsearch](https://github.com/elastic/elasticsearch) 是一个分布式、RESTful 风格的搜索和数据分析引擎,能够解决不断涌现出的各种用例。 作为 Elastic Stack 的核心,它集中存储您的数据,帮助您发现意料之中以及意料之外的情况。 + +## 部署 + +### 安装步骤 + +> [Elasticsearch 官方开源版本安装说明](https://www.elastic.co/cn/downloads/elasticsearch-oss) + +(1)下载解压 + +访问 [官方下载地址](https://www.elastic.co/cn/downloads/elasticsearch-oss) ,选择需要的版本,下载解压到本地。 + +(2)运行 + +运行 `bin/elasticsearch` (Windows 系统上运行 `bin\elasticsearch.bat` ) + +(3)访问 + +执行 `curl http://localhost:9200/` 测试服务是否启动 + +### 集群规划 + +ElasticSearch 集群需要根据业务实际情况去合理规划。 + +需要考虑的问题点: + +- 集群部署几个节点? +- 有多少个索引? +- 每个索引有多大数据量? +- 每个索引有多少个分片? + +一个参考规划: + +- 3 台机器,每台机器是 6 核 64G 的。 +- 我们 es 集群的日增量数据大概是 2000 万条,每天日增量数据大概是 500MB,每月增量数据大概是 6 亿,15G。目前系统已经运行了几个月,现在 es 集群里数据总量大概是 100G 左右。 +- 目前线上有 5 个索引(这个结合你们自己业务来,看看自己有哪些数据可以放 es 的),每个索引的数据量大概是 20G,所以这个数据量之内,我们每个索引分配的是 8 个 shard,比默认的 5 个 shard 多了 3 个 shard。 + +## ES 配置 + +ES 的默认配置文件为 `config/elasticsearch.yml` + +基本配置说明如下: + +```yml +cluster.name: elasticsearch +#配置es的集群名称,默认是elasticsearch,es会自动发现在同一网段下的es,如果在同一网段下有多个集群,就可以用这个属性来区分不同的集群。 +node.name: "Franz Kafka" +#节点名,默认随机指定一个name列表中名字,该列表在es的jar包中config文件夹里name.txt文件中,其中有很多作者添加的有趣名字。 +node.master: true +#指定该节点是否有资格被选举成为node,默认是true,es是默认集群中的第一台机器为master,如果这台机挂了就会重新选举master。 +node.data: true +#指定该节点是否存储索引数据,默认为true。 +index.number_of_shards: 5 +#设置默认索引分片个数,默认为5片。 +index.number_of_replicas: 1 +#设置默认索引副本个数,默认为1个副本。 +path.conf: /path/to/conf +#设置配置文件的存储路径,默认是es根目录下的config文件夹。 +path.data: /path/to/data +#设置索引数据的存储路径,默认是es根目录下的data文件夹,可以设置多个存储路径,用逗号隔开,例: +#path.data: /path/to/data1,/path/to/data2 +path.work: /path/to/work +#设置临时文件的存储路径,默认是es根目录下的work文件夹。 +path.logs: /path/to/logs +#设置日志文件的存储路径,默认是es根目录下的logs文件夹 +path.plugins: /path/to/plugins +#设置插件的存放路径,默认是es根目录下的plugins文件夹 +bootstrap.mlockall: true +#设置为true来锁住内存。因为当jvm开始swapping时es的效率会降低,所以要保证它不swap,可以把#ES_MIN_MEM和ES_MAX_MEM两个环境变量设置成同一个值,并且保证机器有足够的内存分配给es。同时也要#允许elasticsearch的进程可以锁住内存,linux下可以通过`ulimit -l unlimited`命令。 +network.bind_host: 192.168.0.1 +#设置绑定的ip地址,可以是ipv4或ipv6的,默认为0.0.0.0。 +network.publish_host: 192.168.0.1 +#设置其它节点和该节点交互的ip地址,如果不设置它会自动判断,值必须是个真实的ip地址。 +network.host: 192.168.0.1 +#这个参数是用来同时设置bind_host和publish_host上面两个参数。 +transport.tcp.port: 9300 +#设置节点间交互的tcp端口,默认是9300。 +transport.tcp.compress: true +#设置是否压缩tcp传输时的数据,默认为false,不压缩。 +http.port: 9200 +#设置对外服务的http端口,默认为9200。 +http.max_content_length: 100mb +#设置内容的最大容量,默认100mb +http.enabled: false +#是否使用http协议对外提供服务,默认为true,开启。 +gateway.type: local +#gateway的类型,默认为local即为本地文件系统,可以设置为本地文件系统,分布式文件系统,hadoop的#HDFS,和amazon的s3服务器,其它文件系统的设置方法下次再详细说。 +gateway.recover_after_nodes: 1 +#设置集群中N个节点启动时进行数据恢复,默认为1。 +gateway.recover_after_time: 5m +#设置初始化数据恢复进程的超时时间,默认是5分钟。 +gateway.expected_nodes: 2 +#设置这个集群中节点的数量,默认为2,一旦这N个节点启动,就会立即进行数据恢复。 +cluster.routing.allocation.node_initial_primaries_recoveries: 4 +#初始化数据恢复时,并发恢复线程的个数,默认为4。 +cluster.routing.allocation.node_concurrent_recoveries: 2 +#添加删除节点或负载均衡时并发恢复线程的个数,默认为4。 +indices.recovery.max_size_per_sec: 0 +#设置数据恢复时限制的带宽,如入100mb,默认为0,即无限制。 +indices.recovery.concurrent_streams: 5 +#设置这个参数来限制从其它分片恢复数据时最大同时打开并发流的个数,默认为5。 +discovery.zen.minimum_master_nodes: 1 +#设置这个参数来保证集群中的节点可以知道其它N个有master资格的节点。默认为1,对于大的集群来说,可以设置大一点的值(2-4) +discovery.zen.ping.timeout: 3s +#设置集群中自动发现其它节点时ping连接超时时间,默认为3秒,对于比较差的网络环境可以高点的值来防止自动发现时出错。 +discovery.zen.ping.multicast.enabled: false +#设置是否打开多播发现节点,默认是true。 +discovery.zen.ping.unicast.hosts: ["host1", "host2:port", "host3[portX-portY]"] +#设置集群中master节点的初始列表,可以通过这些节点来自动发现新加入集群的节点。 +``` + +## FAQ + +### elasticsearch 不允许以 root 权限来运行 + +**问题:**在 Linux 环境中,elasticsearch 不允许以 root 权限来运行。 + +如果以 root 身份运行 elasticsearch,会提示这样的错误: + +``` +can not run elasticsearch as root +``` + +**解决方法:**使用非 root 权限账号运行 elasticsearch + +```bash +# 创建用户组 +groupadd elk +# 创建新用户,-g elk 设置其用户组为 elk,-p elk 设置其密码为 elk +useradd elk -g elk -p elk +# 更改 /opt 文件夹及内部文件的所属用户及组为 elk:elk +chown -R elk:elk /opt # 假设你的 elasticsearch 安装在 opt 目录下 +# 切换账号 +su elk +``` + +### vm.max_map_count 不低于 262144 + +**问题:**`vm.max_map_count` 表示虚拟内存大小,它是一个内核参数。elasticsearch 默认要求 `vm.max_map_count` 不低于 262144。 + +``` +max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144] +``` + +**解决方法:** + +你可以执行以下命令,设置 `vm.max_map_count` ,但是重启后又会恢复为原值。 + +``` +sysctl -w vm.max_map_count=262144 +``` + +持久性的做法是在 `/etc/sysctl.conf` 文件中修改 `vm.max_map_count` 参数: + +``` +echo "vm.max_map_count=262144" > /etc/sysctl.conf +sysctl -p +``` + +> **注意** +> +> 如果运行环境为 docker 容器,可能会限制执行 sysctl 来修改内核参数。 +> +> 这种情况下,你只能选择直接修改宿主机上的参数了。 + +### nofile 不低于 65536 + +**问题:** `nofile` 表示进程允许打开的最大文件数。elasticsearch 进程要求可以打开的最大文件数不低于 65536。 + +``` +max file descriptors [4096] for elasticsearch process is too low, increase to at least [65536] +``` + +**解决方法:** + +在 `/etc/security/limits.conf` 文件中修改 `nofile` 参数: + +``` +echo "* soft nofile 65536" > /etc/security/limits.conf +echo "* hard nofile 131072" > /etc/security/limits.conf +``` + +### nproc 不低于 2048 + +**问题:** `nproc` 表示最大线程数。elasticsearch 要求最大线程数不低于 2048。 + +``` +max number of threads [1024] for user [user] is too low, increase to at least [2048] +``` + +**解决方法:** + +在 `/etc/security/limits.conf` 文件中修改 `nproc` 参数: + +``` +echo "* soft nproc 2048" > /etc/security/limits.conf +echo "* hard nproc 4096" > /etc/security/limits.conf +``` + +## 参考资料 + +- [Elasticsearch 官网](https://www.elastic.co/cn/products/elasticsearch) +- [Elasticsearch Github](https://github.com/elastic/elasticsearch) +- [Elasticsearch 官方文档](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html) +- [Install Elasticsearch with RPM]( https://www.elastic.co/guide/en/elasticsearch/reference/current/rpm.html#rpm) +- [Elasticsearch使用积累](http://siye1982.github.io/2015/09/17/es-optimize/) \ No newline at end of file diff --git a/docs/ecology/search/elastic-elasticsearch.md b/docs/ecology/search/elastic-elasticsearch.md new file mode 100644 index 00000000..a0c648ce --- /dev/null +++ b/docs/ecology/search/elastic-elasticsearch.md @@ -0,0 +1,536 @@ +# Elasticsearch + +> **[Elasticsearch](https://github.com/elastic/elasticsearch) 是一个分布式、RESTful 风格的搜索和数据分析引擎**,能够解决不断涌现出的各种用例。 作为 Elastic Stack 的核心,它集中存储您的数据,帮助您发现意料之中以及意料之外的情况。 +> +> [Elasticsearch](https://github.com/elastic/elasticsearch) 基于搜索库 [Lucene](https://github.com/apache/lucene-solr) 开发。ElasticSearch 隐藏了 Lucene 的复杂性,提供了简单易用的 REST API / Java API 接口(另外还有其他语言的 API 接口)。 +> +> _以下简称 ES。_ + +## 一、Elasticsearch 简介 + +### 1.1. 什么是 Elasticsearch + +**[Elasticsearch](https://github.com/elastic/elasticsearch) 是一个分布式、RESTful 风格的搜索和数据分析引擎**,能够解决不断涌现出的各种用例。 作为 Elastic Stack 的核心,它集中存储您的数据,帮助您发现意料之中以及意料之外的情况。 + +[Elasticsearch](https://github.com/elastic/elasticsearch) **基于搜索库 [Lucene](https://github.com/apache/lucene-solr) 开发**。ElasticSearch 隐藏了 Lucene 的复杂性,提供了简单易用的 REST API / Java API 接口(另外还有其他语言的 API 接口)。 + +ElasticSearch 可以视为一个文档存储,它**将复杂数据结构序列化为 JSON 存储**。 + +**ElasticSearch 是近乎于实时的全文搜素**,这是指: + +- 从写入数据到数据可以被搜索,存在较小的延迟(大概是 1s) +- 基于 ES 执行搜索和分析可以达到秒级 + +### 1.2. 核心概念 + +#### 1.2.1. Index + +**可以认为是文档(document)的优化集合。** + +ES 会为所有字段建立索引,经过处理后写入一个反向索引(Inverted Index)。查找数据的时候,直接查找该索引。 + +所以,ES 数据管理的顶层单位就叫做 Index(索引)。它是单个数据库的同义词。每个 Index (即数据库)的名字必须是小写。 + +#### 1.2.2. Document + +Index 里面单条的记录称为 Document(文档)。许多条 Document 构成了一个 Index。 + +每个 **`文档(document)`** 都是字段(field)的集合。 + +Document 使用 JSON 格式表示,下面是一个例子。 + +```javascript +{ +"user": "张三", +"title": "工程师", +"desc": "数据库管理" +} +``` + +同一个 Index 里面的 Document,不要求有相同的结构(scheme),但是最好保持相同,这样有利于提高搜索效率。 + +#### 1.2.3. Field + +**`字段(field)`** 是包含数据的键值对。 + +默认情况下,Elasticsearch 对每个字段中的所有数据建立索引,并且每个索引字段都具有专用的优化数据结构。 + +#### 1.2.4. Type + +每个索引里可以有一个或者多个类型(type)。`类型(type)` 是 index 的一个逻辑分类。 + +不同的 Type 应该有相似的结构(schema),举例来说,`id`字段不能在这个组是字符串,在另一个组是数值。这是与关系型数据库的表的[一个区别](https://www.elastic.co/guide/en/elasticsearch/guide/current/mapping.html)。性质完全不同的数据(比如`products`和`logs`)应该存成两个 Index,而不是一个 Index 里面的两个 Type(虽然可以做到)。 + +> 注意:根据[规划](https://www.elastic.co/blog/index-type-parent-child-join-now-future-in-elasticsearch),Elastic 6.x 版只允许每个 Index 包含一个 Type,7.x 版将会彻底移除 Type。 + +#### 1.2.5. Shard + +当单台机器不足以存储大量数据时,Elasticsearch 可以将一个索引中的数据切分为多个 **`分片(shard)`** 。 **`分片(shard)`** 分布在多台服务器上存储。有了 shard 就可以横向扩展,存储更多数据,让搜索和分析等操作分布到多台服务器上去执行,提升吞吐量和性能。每个 shard 都是一个 lucene index。 + +#### 1.2.6. Replica + +任何一个服务器随时可能故障或宕机,此时 shard 可能就会丢失,因此可以为每个 shard 创建多个 **`副本(replica)`**。replica 可以在 shard 故障时提供备用服务,保证数据不丢失,多个 replica 还可以提升搜索操作的吞吐量和性能。primary shard(建立索引时一次设置,不能修改,默认 5 个),replica shard(随时修改数量,默认 1 个),默认每个索引 10 个 shard,5 个 primary shard,5 个 replica shard,最小的高可用配置,是 2 台服务器。 + +## 二、REST API + +> REST API 最详尽的文档应该参考:[ES 官方 REST API](https://www.elastic.co/guide/en/elasticsearch/reference/current/rest-apis.html) + +### 3.1. 索引 + +新建 Index,可以直接向 ES 服务器发出 `PUT` 请求。 + +#### 3.1.1. 创建索引 + +示例:直接创建索引 + +```bash +$ curl -X POST 'localhost:9200/user' +``` + +服务器返回一个 JSON 对象,里面的 `acknowledged` 字段表示操作成功。 + +```javascript +{"acknowledged":true,"shards_acknowledged":true,"index":"user"} +``` + +示例:创建索引时指定配置 + +```bash +$ curl -X PUT -H 'Content-Type: application/json' 'localhost:9200/user' -d ' +{ + "settings" : { + "index" : { + "number_of_shards" : 3, + "number_of_replicas" : 2 + } + } +}' +``` + +示例:创建索引时指定 `mappings` + +```bash +$ curl -X PUT -H 'Content-Type: application/json' 'localhost:9200/user' -d ' +{ + "settings" : { + "index" : { + "number_of_shards" : 3, + "number_of_replicas" : 2 + } + } +}' +``` + +#### 3.1.2. 删除索引 + +然后,我们可以通过发送 `DELETE` 请求,删除这个 Index。 + +```bash +$ curl -X DELETE 'localhost:9200/user' +``` + +#### 3.1.3. 查看索引 + +可以通过 GET 请求查看索引信息 + +```bash +$ curl -X GET 'localhost:9200/user' +``` + +#### 3.1.4. 打开/关闭索引 + +通过在 `POST` 中添加 `_close` 或 `_open` 可以打开、关闭索引。 +关闭索引 + +```bash +$ curl -X POST 'localhost:9200/user/_close' +``` + +打开索引 + +```bash +$ curl -X POST 'localhost:9200/user/_open' +``` + +### 3.2. 文档 + +#### 3.2.1. 新增记录 + +向指定的 `/Index/type` 发送 PUT 请求,就可以在 Index 里面新增一条记录。比如,向 `/user/admin` 发送请求,就可以新增一条人员记录。 + +```bash +$ curl -X PUT -H 'Content-Type: application/json' 'localhost:9200/user/admin/1' -d ' +{ +"user": "张三", +"title": "工程师", +"desc": "数据库管理" +}' +``` + +服务器返回的 JSON 对象,会给出 Index、Type、Id、Version 等信息。 + +```json +{ + "_index": "user", + "_type": "admin", + "_id": "1", + "_version": 1, + "result": "created", + "_shards": { "total": 3, "successful": 1, "failed": 0 }, + "_seq_no": 0, + "_primary_term": 2 +} +``` + +如果你仔细看,会发现请求路径是`/user/admin/1`,最后的`1`是该条记录的 Id。它不一定是数字,任意字符串(比如`abc`)都可以。 + +新增记录的时候,也可以不指定 Id,这时要改成 POST 请求。 + +```bash +$ curl -X POST -H 'Content-Type: application/json' 'localhost:9200/user/admin' -d ' +{ +"user": "李四", +"title": "工程师", +"desc": "系统管理" +}' +``` + +上面代码中,向`/user/admin`发出一个 POST 请求,添加一个记录。这时,服务器返回的 JSON 对象里面,`_id`字段就是一个随机字符串。 + +```json +{ + "_index": "user", + "_type": "admin", + "_id": "WWuoDG8BHwECs7SiYn93", + "_version": 1, + "result": "created", + "_shards": { "total": 3, "successful": 1, "failed": 0 }, + "_seq_no": 1, + "_primary_term": 2 +} +``` + +注意,如果没有先创建 Index(这个例子是`accounts`),直接执行上面的命令,Elastic 也不会报错,而是直接生成指定的 Index。所以,打字的时候要小心,不要写错 Index 的名称。 + +#### 3.2.2. 删除记录 + +删除记录就是发出 `DELETE` 请求。 + +```bash +$ curl -X DELETE 'localhost:9200/user/admin/2' +``` + +#### 3.2.3. 更新记录 + +更新记录就是使用 `PUT` 请求,重新发送一次数据。 + +```bash +$ curl -X PUT -H 'Content-Type: application/json' 'localhost:9200/user/admin/1' -d ' +{ +"user": "张三", +"title": "工程师", +"desc": "超级管理员" +}' +``` + +#### 3.2.4. 查询记录 + +向`/Index/Type/Id`发出 GET 请求,就可以查看这条记录。 + +```bash +$ curl 'localhost:9200/user/admin/1?pretty' +``` + +上面代码请求查看 `/user/admin/1` 这条记录,URL 的参数 `pretty=true` 表示以易读的格式返回。 + +返回的数据中,`found` 字段表示查询成功,`_source`字段返回原始记录。 + +```json +{ + "_index": "user", + "_type": "admin", + "_id": "1", + "_version": 2, + "found": true, + "_source": { + "user": "张三", + "title": "工程师", + "desc": "超级管理员" + } +} +``` + +如果 Id 不正确,就查不到数据,`found` 字段就是 `false` + +#### 3.2.5. 查询所有记录 + +使用 `GET` 方法,直接请求 `/index/type/_search`,就会返回所有记录。 + +```bash +$ curl 'localhost:9200/user/admin/_search?pretty' +{ + "took" : 1, + "timed_out" : false, + "_shards" : { + "total" : 3, + "successful" : 3, + "skipped" : 0, + "failed" : 0 + }, + "hits" : { + "total" : 2, + "max_score" : 1.0, + "hits" : [ + { + "_index" : "user", + "_type" : "admin", + "_id" : "WWuoDG8BHwECs7SiYn93", + "_score" : 1.0, + "_source" : { + "user" : "李四", + "title" : "工程师", + "desc" : "系统管理" + } + }, + { + "_index" : "user", + "_type" : "admin", + "_id" : "1", + "_score" : 1.0, + "_source" : { + "user" : "张三", + "title" : "工程师", + "desc" : "超级管理员" + } + } + ] + } +} +``` + +上面代码中,返回结果的 `took`字段表示该操作的耗时(单位为毫秒),`timed_out`字段表示是否超时,`hits`字段表示命中的记录,里面子字段的含义如下。 + +- `total`:返回记录数,本例是 2 条。 +- `max_score`:最高的匹配程度,本例是`1.0`。 +- `hits`:返回的记录组成的数组。 + +返回的记录中,每条记录都有一个`_score`字段,表示匹配的程序,默认是按照这个字段降序排列。 + +#### 3.2.6. 全文搜索 + +ES 的查询非常特别,使用自己的[查询语法](https://www.elastic.co/guide/en/elasticsearch/reference/5.5/query-dsl.html),要求 GET 请求带有数据体。 + +```bash +$ curl -H 'Content-Type: application/json' 'localhost:9200/user/admin/_search?pretty' -d ' +{ +"query" : { "match" : { "desc" : "管理" }} +}' +``` + +上面代码使用 [Match 查询](https://www.elastic.co/guide/en/elasticsearch/reference/5.5/query-dsl-match-query.html),指定的匹配条件是`desc`字段里面包含"软件"这个词。返回结果如下。 + +```javascript +{ + "took" : 2, + "timed_out" : false, + "_shards" : { + "total" : 3, + "successful" : 3, + "skipped" : 0, + "failed" : 0 + }, + "hits" : { + "total" : 2, + "max_score" : 0.38200712, + "hits" : [ + { + "_index" : "user", + "_type" : "admin", + "_id" : "WWuoDG8BHwECs7SiYn93", + "_score" : 0.38200712, + "_source" : { + "user" : "李四", + "title" : "工程师", + "desc" : "系统管理" + } + }, + { + "_index" : "user", + "_type" : "admin", + "_id" : "1", + "_score" : 0.3487891, + "_source" : { + "user" : "张三", + "title" : "工程师", + "desc" : "超级管理员" + } + } + ] + } +} +``` + +Elastic 默认一次返回 10 条结果,可以通过`size`字段改变这个设置,还可以通过`from`字段,指定位移。 + +```bash +$ curl 'localhost:9200/user/admin/_search' -d ' +{ + "query" : { "match" : { "desc" : "管理" }}, + "from": 1, + "size": 1 +}' +``` + +上面代码指定,从位置 1 开始(默认是从位置 0 开始),只返回一条结果。 + +#### 3.2.7. 逻辑运算 + +如果有多个搜索关键字, Elastic 认为它们是`or`关系。 + +```bash +$ curl 'localhost:9200/user/admin/_search' -d ' +{ +"query" : { "match" : { "desc" : "软件 系统" }} +}' +``` + +上面代码搜索的是`软件 or 系统`。 + +如果要执行多个关键词的`and`搜索,必须使用[布尔查询](https://www.elastic.co/guide/en/elasticsearch/reference/5.5/query-dsl-bool-query.html)。 + +```bash +$ curl -H 'Content-Type: application/json' 'localhost:9200/user/admin/_search?pretty' -d ' +{ + "query": { + "bool": { + "must": [ + { "match": { "desc": "管理" } }, + { "match": { "desc": "超级" } } + ] + } + } +}' +``` + +## 三、ElasticSearch 基本原理 + +### 2.1. ES 写数据过程 + +- 客户端选择一个 node 发送请求过去,这个 node 就是 `coordinating node`(协调节点)。 +- `coordinating node` 对 document 进行**路由**,将请求转发给对应的 node(有 primary shard)。 +- 实际的 node 上的 `primary shard` 处理请求,然后将数据同步到 `replica node`。 +- `coordinating node` 如果发现 `primary node` 和所有 `replica node` 都搞定之后,就返回响应结果给客户端。 + +### 2.2. ES 读数据过程 + +可以通过 `doc id` 来查询,会根据 `doc id` 进行 hash,判断出来当时把 `doc id` 分配到了哪个 shard 上面去,从那个 shard 去查询。 + +- 客户端发送请求到**任意**一个 node,成为 `coordinate node`。 +- `coordinate node` 对 `doc id` 进行哈希路由,将请求转发到对应的 node,此时会使用 `round-robin` **随机轮询算法**,在 `primary shard` 以及其所有 replica 中随机选择一个,让读请求负载均衡。 +- 接收请求的 node 返回 document 给 `coordinate node`。 +- `coordinate node` 返回 document 给客户端。 + +### 2.3. 写数据底层原理 + +先写入内存 buffer,在 buffer 里的时候数据是搜索不到的;同时将数据写入 translog 日志文件。 + +如果 buffer 快满了,或者到一定时间,就会将内存 buffer 数据 `refresh` 到一个新的 `segment file` 中,但是此时数据不是直接进入 `segment file` 磁盘文件,而是先进入 `os cache` 。这个过程就是 `refresh`。 + +每隔 1 秒钟,es 将 buffer 中的数据写入一个**新的** `segment file`,每秒钟会产生一个**新的磁盘文件** `segment file`,这个 `segment file` 中就存储最近 1 秒内 buffer 中写入的数据。 + +但是如果 buffer 里面此时没有数据,那当然不会执行 refresh 操作,如果 buffer 里面有数据,默认 1 秒钟执行一次 refresh 操作,刷入一个新的 segment file 中。 + +操作系统里面,磁盘文件其实都有一个东西,叫做 `os cache`,即操作系统缓存,就是说数据写入磁盘文件之前,会先进入 `os cache`,先进入操作系统级别的一个内存缓存中去。只要 `buffer` 中的数据被 refresh 操作刷入 `os cache`中,这个数据就可以被搜索到了。 + +为什么叫 es 是**准实时**的? `NRT`,全称 `near real-time`。默认是每隔 1 秒 refresh 一次的,所以 es 是准实时的,因为写入的数据 1 秒之后才能被看到。可以通过 es 的 `restful api` 或者 `java api`,**手动**执行一次 refresh 操作,就是手动将 buffer 中的数据刷入 `os cache`中,让数据立马就可以被搜索到。只要数据被输入 `os cache` 中,buffer 就会被清空了,因为不需要保留 buffer 了,数据在 translog 里面已经持久化到磁盘去一份了。 + +重复上面的步骤,新的数据不断进入 buffer 和 translog,不断将 `buffer` 数据写入一个又一个新的 `segment file` 中去,每次 `refresh` 完 buffer 清空,translog 保留。随着这个过程推进,translog 会变得越来越大。当 translog 达到一定长度的时候,就会触发 `commit` 操作。 + +commit 操作发生第一步,就是将 buffer 中现有数据 `refresh` 到 `os cache` 中去,清空 buffer。然后,将一个 `commit point` 写入磁盘文件,里面标识着这个 `commit point` 对应的所有 `segment file`,同时强行将 `os cache` 中目前所有的数据都 `fsync` 到磁盘文件中去。最后**清空** 现有 translog 日志文件,重启一个 translog,此时 commit 操作完成。 + +这个 commit 操作叫做 `flush`。默认 30 分钟自动执行一次 `flush`,但如果 translog 过大,也会触发 `flush`。flush 操作就对应着 commit 的全过程,我们可以通过 es api,手动执行 flush 操作,手动将 os cache 中的数据 fsync 强刷到磁盘上去。 + +translog 日志文件的作用是什么?你执行 commit 操作之前,数据要么是停留在 buffer 中,要么是停留在 os cache 中,无论是 buffer 还是 os cache 都是内存,一旦这台机器死了,内存中的数据就全丢了。所以需要将数据对应的操作写入一个专门的日志文件 `translog` 中,一旦此时机器宕机,再次重启的时候,es 会自动读取 translog 日志文件中的数据,恢复到内存 buffer 和 os cache 中去。 + +translog 其实也是先写入 os cache 的,默认每隔 5 秒刷一次到磁盘中去,所以默认情况下,可能有 5 秒的数据会仅仅停留在 buffer 或者 translog 文件的 os cache 中,如果此时机器挂了,会**丢失** 5 秒钟的数据。但是这样性能比较好,最多丢 5 秒的数据。也可以将 translog 设置成每次写操作必须是直接 `fsync` 到磁盘,但是性能会差很多。 + +实际上你在这里,如果面试官没有问你 es 丢数据的问题,你可以在这里给面试官炫一把,你说,其实 es 第一是准实时的,数据写入 1 秒后可以搜索到;可能会丢失数据的。有 5 秒的数据,停留在 buffer、translog os cache、segment file os cache 中,而不在磁盘上,此时如果宕机,会导致 5 秒的**数据丢失**。 + +**总结一下**,数据先写入内存 buffer,然后每隔 1s,将数据 refresh 到 os cache,到了 os cache 数据就能被搜索到(所以我们才说 es 从写入到能被搜索到,中间有 1s 的延迟)。每隔 5s,将数据写入 translog 文件(这样如果机器宕机,内存数据全没,最多会有 5s 的数据丢失),translog 大到一定程度,或者默认每隔 30mins,会触发 commit 操作,将缓冲区的数据都 flush 到 segment file 磁盘文件中。 + +> 数据写入 segment file 之后,同时就建立好了倒排索引。 + +### 2.4. 删除/更新数据底层原理 + +如果是删除操作,commit 的时候会生成一个 `.del` 文件,里面将某个 doc 标识为 `deleted` 状态,那么搜索的时候根据 `.del` 文件就知道这个 doc 是否被删除了。 + +如果是更新操作,就是将原来的 doc 标识为 `deleted` 状态,然后新写入一条数据。 + +buffer 每 refresh 一次,就会产生一个 `segment file`,所以默认情况下是 1 秒钟一个 `segment file`,这样下来 `segment file` 会越来越多,此时会定期执行 merge。每次 merge 的时候,会将多个 `segment file` 合并成一个,同时这里会将标识为 `deleted` 的 doc 给**物理删除掉**,然后将新的 `segment file` 写入磁盘,这里会写一个 `commit point`,标识所有新的 `segment file`,然后打开 `segment file` 供搜索使用,同时删除旧的 `segment file`。 + +### 2.5. 底层 lucene + +简单来说,lucene 就是一个 jar 包,里面包含了封装好的各种建立倒排索引的算法代码。我们用 Java 开发的时候,引入 lucene jar,然后基于 lucene 的 api 去开发就可以了。 + +通过 lucene,我们可以将已有的数据建立索引,lucene 会在本地磁盘上面,给我们组织索引的数据结构。 + +### 2.6. 倒排索引 + +在搜索引擎中,每个文档都有一个对应的文档 ID,文档内容被表示为一系列关键词的集合。例如,文档 1 经过分词,提取了 20 个关键词,每个关键词都会记录它在文档中出现的次数和出现位置。 + +那么,倒排索引就是**关键词到文档** ID 的映射,每个关键词都对应着一系列的文件,这些文件中都出现了关键词。 + +举个栗子。 + +有以下文档: + +| DocId | Doc | +| ----- | ---------------------------------------------- | +| 1 | 谷歌地图之父跳槽 Facebook | +| 2 | 谷歌地图之父加盟 Facebook | +| 3 | 谷歌地图创始人拉斯离开谷歌加盟 Facebook | +| 4 | 谷歌地图之父跳槽 Facebook 与 Wave 项目取消有关 | +| 5 | 谷歌地图之父拉斯加盟社交网站 Facebook | + +对文档进行分词之后,得到以下**倒排索引**。 + +| WordId | Word | DocIds | +| ------ | -------- | --------- | +| 1 | 谷歌 | 1,2,3,4,5 | +| 2 | 地图 | 1,2,3,4,5 | +| 3 | 之父 | 1,2,4,5 | +| 4 | 跳槽 | 1,4 | +| 5 | Facebook | 1,2,3,4,5 | +| 6 | 加盟 | 2,3,5 | +| 7 | 创始人 | 3 | +| 8 | 拉斯 | 3,5 | +| 9 | 离开 | 3 | +| 10 | 与 | 4 | +| .. | .. | .. | + +另外,实用的倒排索引还可以记录更多的信息,比如文档频率信息,表示在文档集合中有多少个文档包含某个单词。 + +那么,有了倒排索引,搜索引擎可以很方便地响应用户的查询。比如用户输入查询 `Facebook`,搜索系统查找倒排索引,从中读出包含这个单词的文档,这些文档就是提供给用户的搜索结果。 + +要注意倒排索引的两个重要细节: + +- 倒排索引中的所有词项对应一个或多个文档; +- 倒排索引中的词项**根据字典顺序升序排列** + +> 上面只是一个简单的栗子,并没有严格按照字典顺序升序排列。 + +## Elastic 技术栈系列 + +- [ElasticSearch 运维](elastic-elasticsearch-ops.md) - ElasticSearch 安装、配置、命令详解。 + +## 参考资料 + +- **官方** + - [Elasticsearch 官网](https://www.elastic.co/cn/products/elasticsearch) + - [Elasticsearch Github](https://github.com/elastic/elasticsearch) + - [Elasticsearch 官方文档](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html) +- **文章** + - [Install Elasticsearch with RPM](https://www.elastic.co/guide/en/elasticsearch/reference/current/rpm.html#rpm) + - https://www.ruanyifeng.com/blog/2017/08/elasticsearch.html + - [es-introduction](https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/es-introduction.md) + - [es-write-query-search](https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/es-write-query-search.md) diff --git a/docs/ecology/search/elastic-kibana-ops.md b/docs/ecology/search/elastic-kibana-ops.md new file mode 100644 index 00000000..f2771252 --- /dev/null +++ b/docs/ecology/search/elastic-kibana-ops.md @@ -0,0 +1,346 @@ +# Kibana 运维 + +> 通过 Kibana,您可以对自己的 Elasticsearch 进行可视化,还可以在 Elastic Stack 中进行导航,这样您便可以进行各种操作了,从跟踪查询负载,到理解请求如何流经您的整个应用,都能轻松完成。 + +## 1. 安装 + +### 1.1. 环境要求 + +> 版本:Elastic Stack 7.4 + +### 1.2. 安装步骤 + +安装步骤如下: + +1. 在 [kibana 官方下载地址](https://www.elastic.co/downloads/kibana)下载所需版本包并解压到本地。 +2. 修改 `config/kibana.yml` 配置文件,设置 `elasticsearch.url` 指向 Elasticsearch 实例。 +3. 运行 `bin/kibana` (Windows 上运行 `bin\kibana.bat`) +4. 在浏览器上访问 http://localhost:5601 + +## 2. 使用 + +### 2.1. 检索 + +单击侧面导航栏中的 `检索(Discover)` ,可以显示 `Kibana` 的数据查询功能功能。 + +
    ![img](https://www.elastic.co/guide/en/kibana/current/images/tutorial-discover.png)
    + +在搜索栏中,您可以输入 Elasticsearch 查询条件来搜索您的数据。您可以在 `Discover` 页面中浏览结果并在 `Visualize` 页面中创建已保存搜索条件的可视化。 + +当前索引模式显示在查询栏下方。索引模式确定提交查询时搜索哪些索引。要搜索一组不同的索引,请从下拉菜单中选择不同的模式。要添加索引模式(index pattern),请转至 `Management/Kibana/Index Patterns` 并单击 `Add New`。 + +您可以使用字段名称和您感兴趣的值构建搜索。对于数字字段,可以使用比较运算符,如大于(>),小于(<)或等于(=)。您可以将元素与逻辑运算符 `AND`,`OR` 和 `NOT` 链接,全部使用大写。 + +默认情况下,每个匹配文档都显示所有字段。要选择要显示的文档字段,请将鼠标悬停在“可用字段”列表上,然后单击要包含的每个字段旁边的添加按钮。例如,如果只添加 account_number,则显示将更改为包含五个帐号的简单列表: + +
    ![img](https://www.elastic.co/guide/en/kibana/6.1/images/tutorial-discover-3.png)
    + +kibana 的搜索栏遵循 [query-string-syntax](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html#query-string-syntax) 文档中所说明的查询语义。 + +这里说明一些最基本的查询语义。 + +查询字符串会被解析为一系列的术语和运算符。一个术语可以是一个单词(如:quick、brown)或用双引号包围的短语(如"quick brown")。 + +查询操作允许您自定义搜索 - 下面介绍了可用的选项。 + +#### 2.1.1. 字段名称 + +正如查询字符串查询中所述,将在搜索条件中搜索 default_field,但可以在查询语法中指定其他字段: + +例如: + +- 查询 `status` 字段中包含 `active` 关键字 + +``` +status:active +``` + +- `title` 字段包含 `quick` 或 `brown` 关键字。如果您省略 `OR` 运算符,则将使用默认运算符 + +``` +title:(quick OR brown) +title:(quick brown) +``` + +- author 字段查找精确的短语 "john smith",即精确查找。 + +``` +author:"John Smith" +``` + +- 任意字段 `book.title`,`book.content` 或 `book.date` 都包含 `quick` 或 `brown`(注意我们需要如何使用 `\*` 表示通配符) + +``` +book.\*:(quick brown) +``` + +- title 字段包含任意非 null 值 + +``` +_exists_:title +``` + +#### 2.1.2. 通配符 + +ELK 提供了 ? 和 \* 两个通配符。 + +- `?` 表示任意单个字符; +- `*` 表示任意零个或多个字符。 + +``` +qu?ck bro* +``` + +> **注意:通配符查询会使用大量的内存并且执行性能较为糟糕,所以请慎用。** > **提示**:纯通配符 \* 被写入 [exsits](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-exists-query.html) 查询,从而提高了查询效率。因此,通配符 `field:*` 将匹配包含空值的文档,如:`{“field”:“”}`,但是如果字段丢失或显示将值置为 null 则不匹配,如:`“field”:null}` > **提示**:在一个单词的开头(例如:`*ing`)使用通配符这种方式的查询量特别大,因为索引中的所有术语都需要检查,以防万一匹配。通过将 `allow_leading_wildcard` 设置为 `false`,可以禁用。 + +#### 2.1.3. 正则表达式 + +可以通过 `/` 将正则表达式包裹在查询字符串中进行查询 + +例: + +``` +name:/joh?n(ath[oa]n)/ +``` + +支持的正则表达式语义可以参考:[Regular expression syntax](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html#regexp-syntax) + +#### 2.1.4. 模糊查询 + +我们可以使用 `~` 运算符来进行模糊查询。 + +例: + +假设我们实际想查询 + +``` +quick brown forks +``` + +但是,由于拼写错误,我们的查询关键字变成如下情况,依然可以查到想要的结果。 + +``` +quikc\~ brwn\~ foks\~ +``` + +这种模糊查询使用 Damerau-Levenshtein 距离来查找所有匹配最多两个更改的项。所谓的更改是指单个字符的插入,删除或替换,或者两个相邻字符的换位。 + +默认编辑距离为 `2`,但编辑距离为 `1` 应足以捕捉所有人类拼写错误的 80%。它可以被指定为: + +``` +quikc\~1 +``` + +#### 2.1.5. 近似检索 + +尽管短语查询(例如,`john smith`)期望所有的词条都是完全相同的顺序,但是近似查询允许指定的单词进一步分开或以不同的顺序排列。与模糊查询可以为单词中的字符指定最大编辑距离一样,近似搜索也允许我们指定短语中单词的最大编辑距离: + +例 + +``` +"fox quick"\~5 +``` + +字段中的文本越接近查询字符串中指定的原始顺序,该文档就越被认为是相关的。当与上面的示例查询相比时,短语 `"quick fox"` 将被认为比 `"quick brown fox"` 更近似查询条件。 + +#### 2.1.6. 范围 + +可以为日期,数字或字符串字段指定范围。闭区间范围用方括号 `[min TO max]` 和开区间范围用花括号 `{min TO max}` 来指定。 + +我们不妨来看一些示例。 + +- 2012 年的所有日子 + +``` +date:[2012-01-01 TO 2012-12-31] +``` + +- 数字 1 到 5 + +``` +count:[1 TO 5] +``` + +- 在 `alpha` 和 `omega` 之间的标签,不包括 `alpha` 和 `omega` + +``` +tag:{alpha TO omega} +``` + +- 10 以上的数字 + +``` +count:[10 TO *] +``` + +- 2012 年以前的所有日期 + +``` +date:{* TO 2012-01-01} +``` + +此外,开区间和闭区间也可以组合使用 + +- 数组 1 到 5,但不包括 5 + +``` +count:[1 TO 5} +``` + +一边无界的范围也可以使用以下语法: + +``` +age:>10 +age:>=10 +age:<10 +age:<=10 +``` + +当然,你也可以使用 AND 运算符来得到连个查询结果的交集 + +``` +age:(>=10 AND <20) +age:(+>=10 +<20) +``` + +#### 2.1.7. Boosting + +使用操作符 `^` 使一个术语比另一个术语更相关。例如,如果我们想查找所有有关狐狸的文档,但我们对狐狸特别感兴趣: + +``` +quick^2 fox +``` + +默认提升值是 1,但可以是任何正浮点数。 0 到 1 之间的提升减少了相关性。 + +增强也可以应用于短语或组: + +``` +"john smith"^2 (foo bar)^4 +``` + +#### 2.1.8. 布尔操作 + +默认情况下,只要一个词匹配,所有词都是可选的。搜索 `foo bar baz` 将查找包含 `foo` 或 `bar` 或 `baz` 中的一个或多个的任何文档。我们已经讨论了上面的`default_operator`,它允许你强制要求所有的项,但也有布尔运算符可以在查询字符串本身中使用,以提供更多的控制。 + +首选的操作符是 `+`(此术语必须存在)和 `-` (此术语不得存在)。所有其他条款是可选的。例如,这个查询: + +``` +quick brown +fox -news +``` + +这条查询意味着: + +- fox 必须存在 +- news 必须不存在 +- quick 和 brown 是可有可无的 + +熟悉的运算符 `AND`,`OR` 和 `NOT`(也写成 `&&`,`||` 和 `!`)也被支持。然而,这些操作符有一定的优先级:`NOT` 优先于 `AND`,`AND` 优先于 `OR`。虽然 `+` 和 `-` 仅影响运算符右侧的术语,但 `AND` 和 `OR` 会影响左侧和右侧的术语。 + +#### 2.1.9. 分组 + +多个术语或子句可以用圆括号组合在一起,形成子查询 + +``` +(quick OR brown) AND fox +``` + +可以使用组来定位特定的字段,或者增强子查询的结果: + +``` +status:(active OR pending) title:(full text search)^2 +``` + +#### 2.1.10. 保留字 + +如果你需要使用任何在你的查询本身中作为操作符的字符(而不是作为操作符),那么你应该用一个反斜杠来转义它们。例如,要搜索(1 + 1)= 2,您需要将查询写为 `\(1\+1\)\=2` + +保留字符是:`+ - = && || > < ! ( ) { } [ ] ^ " ~ * ? : \ /` + +无法正确地转义这些特殊字符可能会导致语法错误,从而阻止您的查询运行。 + +#### 2.1.11. 空查询 + +如果查询字符串为空或仅包含空格,则查询将生成一个空的结果集。 + +### 2.2. 可视化 + +要想使用可视化的方式展示您的数据,请单击侧面导航栏中的 `可视化(Visualize)`。 + +Visualize 工具使您能够以多种方式(如饼图、柱状图、曲线图、分布图等)查看数据。要开始使用,请点击蓝色的 `Create a visualization` 或 `+` 按钮。 + +![https://www.elastic.co/guide/en/kibana/6.1/images/tutorial-visualize-landing.png](https://www.elastic.co/guide/en/kibana/6.1/images/tutorial-visualize-landing.png) + +有许多可视化类型可供选择。 + +![https://www.elastic.co/guide/en/kibana/6.1/images/tutorial-visualize-wizard-step-1.png](https://www.elastic.co/guide/en/kibana/6.1/images/tutorial-visualize-wizard-step-1.png) + +下面,我们来看创建几个图标示例: + +#### 2.2.1. Pie + +您可以从保存的搜索中构建可视化文件,也可以输入新的搜索条件。要输入新的搜索条件,首先需要选择一个索引模式来指定要搜索的索引。 + +默认搜索匹配所有文档。最初,一个“切片”包含整个饼图: + +![https://www.elastic.co/guide/en/kibana/6.1/images/tutorial-visualize-pie-1.png](https://www.elastic.co/guide/en/kibana/6.1/images/tutorial-visualize-pie-1.png) + +要指定在图表中展示哪些数据,请使用 Elasticsearch 存储桶聚合。分组汇总只是将与您的搜索条件相匹配的文档分类到不同的分类中,也称为分组。 + +为每个范围定义一个存储桶: + +1. 单击 `Split Slices`。 +2. 在 `Aggregation` 列表中选择 `Terms`。_注意:这里的 Terms 是 Elk 采集数据时定义好的字段或标签。_ +3. 在 `Field` 列表中选择 `level.keyword`。 +4. 点击 ![images/apply-changes-button.png](https://www.elastic.co/guide/en/kibana/6.1/images/apply-changes-button.png) 按钮来更新图表。 + +![image.png](https://upload-images.jianshu.io/upload_images/3101171-7fb2042dc6d59520.png) + +完成后,如果想要保存这个图表,可以点击页面最上方一栏中的 `Save` 按钮。 + +#### 2.2.2. Vertical Bar + +我们在展示一下如何创建柱状图。 + +1. 点击蓝色的 `Create a visualization` 或 `+` 按钮。选择 `Vertical Bar` +2. 选择索引模式。由于您尚未定义任何 bucket ,因此您会看到一个大栏,显示与默认通配符查询匹配的文档总数。 +3. 指定 Y 轴所代表的字段 +4. 指定 X 轴所代表的字段 +5. 点击 ![images/apply-changes-button.png](https://www.elastic.co/guide/en/kibana/6.1/images/apply-changes-button.png) 按钮来更新图表。 + +![image.png](https://upload-images.jianshu.io/upload_images/3101171-5aa7627284c19a56.png) + +完成后,如果想要保存这个图表,可以点击页面最上方一栏中的 `Save` 按钮。 + +### 2.3. 报表 + +`报表(Dashboard)` 可以整合和共享 `Visualize` 集合。 + +1. 点击侧面导航栏中的 Dashboard。 +2. 点击添加显示保存的可视化列表。 +3. 点击之前保存的 `Visualize`,然后点击列表底部的小向上箭头关闭可视化列表。 +4. 将鼠标悬停在可视化对象上会显示允许您编辑,移动,删除和调整可视化对象大小的容器控件。 + +## 3. FAQ + +### 3.1. Kibana No Default Index Pattern Warning + +**问题:**安装 ELK 后,访问 kibana 页面时,提示以下错误信息: + +``` +Warning No default index pattern. You must select or create one to continue. +... +Unable to fetch mapping. Do you have indices matching the pattern? +``` + +这就说明 logstash 没有把日志写入到 elasticsearch。 + +**解决方法:** + +检查 logstash 与 elasticsearch 之间的通讯是否有问题,一般问题就出在这。 + +## 4. 参考资料 + +- [Kibana 官网](https://www.elastic.co/cn/products/kibana) +- [Kibana Github](https://github.com/elastic/kibana) +- [Kibana 官方文档](https://www.elastic.co/guide/en/kibana/current/index.html) diff --git a/docs/ecology/search/elastic-kibana.md b/docs/ecology/search/elastic-kibana.md new file mode 100644 index 00000000..41a43a44 --- /dev/null +++ b/docs/ecology/search/elastic-kibana.md @@ -0,0 +1,307 @@ +# Elastic 技术栈之 Kibana + +## Discover + +单击侧面导航栏中的 `Discover` ,可以显示 `Kibana` 的数据查询功能功能。 + +
    ![img](https://www.elastic.co/guide/en/kibana/current/images/tutorial-discover.png)
    + +在搜索栏中,您可以输入Elasticsearch查询条件来搜索您的数据。您可以在 `Discover` 页面中浏览结果并在 `Visualize` 页面中创建已保存搜索条件的可视化。 + +当前索引模式显示在查询栏下方。索引模式确定提交查询时搜索哪些索引。要搜索一组不同的索引,请从下拉菜单中选择不同的模式。要添加索引模式(index pattern),请转至 `Management/Kibana/Index Patterns` 并单击 `Add New`。 + +您可以使用字段名称和您感兴趣的值构建搜索。对于数字字段,可以使用比较运算符,如大于(>),小于(<)或等于(=)。您可以将元素与逻辑运算符 `AND`,`OR` 和 `NOT` 链接,全部使用大写。 + +默认情况下,每个匹配文档都显示所有字段。要选择要显示的文档字段,请将鼠标悬停在“可用字段”列表上,然后单击要包含的每个字段旁边的添加按钮。例如,如果只添加account_number,则显示将更改为包含五个帐号的简单列表: + +
    ![img](https://www.elastic.co/guide/en/kibana/6.1/images/tutorial-discover-3.png)
    + +### 查询语义 + +kibana 的搜索栏遵循 [query-string-syntax](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html#query-string-syntax) 文档中所说明的查询语义。 + +这里说明一些最基本的查询语义。 + +查询字符串会被解析为一系列的术语和运算符。一个术语可以是一个单词(如:quick、brown)或用双引号包围的短语(如"quick brown")。 + +查询操作允许您自定义搜索 - 下面介绍了可用的选项。 + +#### 字段名称 + +正如查询字符串查询中所述,将在搜索条件中搜索default_field,但可以在查询语法中指定其他字段: + +例如: + +* 查询 `status` 字段中包含 `active` 关键字 + +``` +status:active +``` + +* `title` 字段包含 `quick` 或 `brown` 关键字。如果您省略 `OR` 运算符,则将使用默认运算符 + +``` +title:(quick OR brown) +title:(quick brown) +``` + +* author 字段查找精确的短语 "john smith",即精确查找。 + +``` +author:"John Smith" +``` + +* 任意字段 `book.title`,`book.content` 或 `book.date` 都包含 `quick` 或 `brown`(注意我们需要如何使用 `\*` 表示通配符) + +``` +book.\*:(quick brown) +``` + +* title 字段包含任意非 null 值 + +``` +_exists_:title +``` + +#### 通配符 + +ELK 提供了 ? 和 * 两个通配符。 + +* `?` 表示任意单个字符; +* `*` 表示任意零个或多个字符。 + +``` +qu?ck bro* +``` + +> **注意:通配符查询会使用大量的内存并且执行性能较为糟糕,所以请慎用。** +> **提示**:纯通配符 \* 被写入 [exsits](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-exists-query.html) 查询,从而提高了查询效率。因此,通配符 `field:*` 将匹配包含空值的文档,如:```{“field”:“”}```,但是如果字段丢失或显示将值置为null则不匹配,如:```“field”:null}``` +> **提示**:在一个单词的开头(例如:`*ing`)使用通配符这种方式的查询量特别大,因为索引中的所有术语都需要检查,以防万一匹配。通过将 `allow_leading_wildcard` 设置为 `false`,可以禁用。 + +#### 正则表达式 + +可以通过 `/` 将正则表达式包裹在查询字符串中进行查询 + +例: + +``` +name:/joh?n(ath[oa]n)/ +``` + +支持的正则表达式语义可以参考:[Regular expression syntax](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html#regexp-syntax) + +#### 模糊查询 + +我们可以使用 `~` 运算符来进行模糊查询。 + +例: + +假设我们实际想查询 + +``` +quick brown forks +``` + +但是,由于拼写错误,我们的查询关键字变成如下情况,依然可以查到想要的结果。 + +``` +quikc\~ brwn\~ foks\~ +``` + +这种模糊查询使用 Damerau-Levenshtein 距离来查找所有匹配最多两个更改的项。所谓的更改是指单个字符的插入,删除或替换,或者两个相邻字符的换位。 + +默认编辑距离为 `2`,但编辑距离为 `1` 应足以捕捉所有人类拼写错误的80%。它可以被指定为: + +``` +quikc\~1 +``` + +#### 近似检索 + +尽管短语查询(例如,`john smith`)期望所有的词条都是完全相同的顺序,但是近似查询允许指定的单词进一步分开或以不同的顺序排列。与模糊查询可以为单词中的字符指定最大编辑距离一样,近似搜索也允许我们指定短语中单词的最大编辑距离: + +例 + +``` +"fox quick"\~5 +``` + +字段中的文本越接近查询字符串中指定的原始顺序,该文档就越被认为是相关的。当与上面的示例查询相比时,短语 `"quick fox"` 将被认为比 `"quick brown fox"` 更近似查询条件。 + +#### 范围 + +可以为日期,数字或字符串字段指定范围。闭区间范围用方括号 `[min TO max]` 和开区间范围用花括号 `{min TO max}` 来指定。 + +我们不妨来看一些示例。 + +* 2012 年的所有日子 + +``` +date:[2012-01-01 TO 2012-12-31] +``` + +* 数字 1 到 5 + +``` +count:[1 TO 5] +``` + +* 在 `alpha` 和 `omega` 之间的标签,不包括 `alpha` 和 `omega` + +``` +tag:{alpha TO omega} +``` + +* 10 以上的数字 + +``` +count:[10 TO *] +``` + +* 2012 年以前的所有日期 + +``` +date:{* TO 2012-01-01} +``` + +此外,开区间和闭区间也可以组合使用 + +* 数组 1 到 5,但不包括 5 + +``` +count:[1 TO 5} +``` + +一边无界的范围也可以使用以下语法: + +``` +age:>10 +age:>=10 +age:<10 +age:<=10 +``` + +当然,你也可以使用 AND 运算符来得到连个查询结果的交集 + +``` +age:(>=10 AND <20) +age:(+>=10 +<20) +``` + +#### Boosting + +使用操作符 `^` 使一个术语比另一个术语更相关。例如,如果我们想查找所有有关狐狸的文档,但我们对狐狸特别感兴趣: + +``` +quick^2 fox +``` + +默认提升值是1,但可以是任何正浮点数。 0到1之间的提升减少了相关性。 + +增强也可以应用于短语或组: + +``` +"john smith"^2 (foo bar)^4 +``` + +#### 布尔操作 + +默认情况下,只要一个词匹配,所有词都是可选的。搜索 `foo bar baz` 将查找包含 `foo` 或 `bar` 或 `baz` 中的一个或多个的任何文档。我们已经讨论了上面的`default_operator`,它允许你强制要求所有的项,但也有布尔运算符可以在查询字符串本身中使用,以提供更多的控制。 + +首选的操作符是 `+`(此术语必须存在)和 `-` (此术语不得存在)。所有其他条款是可选的。例如,这个查询: + +``` +quick brown +fox -news +``` + +这条查询意味着: + +* fox 必须存在 +* news 必须不存在 +* quick 和 brown 是可有可无的 + +熟悉的运算符 `AND`,`OR` 和 `NOT`(也写成 `&&`,`||` 和 `!`)也被支持。然而,这些操作符有一定的优先级:`NOT` 优先于 `AND`,`AND` 优先于 `OR`。虽然 `+` 和 `-` 仅影响运算符右侧的术语,但 `AND` 和 `OR` 会影响左侧和右侧的术语。 + +#### 分组 + +多个术语或子句可以用圆括号组合在一起,形成子查询 + +``` +(quick OR brown) AND fox +``` + +可以使用组来定位特定的字段,或者增强子查询的结果: + +``` +status:(active OR pending) title:(full text search)^2 +``` + +#### 保留字 + +如果你需要使用任何在你的查询本身中作为操作符的字符(而不是作为操作符),那么你应该用一个反斜杠来转义它们。例如,要搜索(1 + 1)= 2,您需要将查询写为 `\(1\+1\)\=2` + +保留字符是:`+ - = && || > < ! ( ) { } [ ] ^ " ~ * ? : \ /` + +无法正确地转义这些特殊字符可能会导致语法错误,从而阻止您的查询运行。 + +#### 空查询 + +如果查询字符串为空或仅包含空格,则查询将生成一个空的结果集。 + +## Visualize + +要想使用可视化的方式展示您的数据,请单击侧面导航栏中的 `Visualize`。 + +Visualize工具使您能够以多种方式(如饼图、柱状图、曲线图、分布图等)查看数据。要开始使用,请点击蓝色的 `Create a visualization` 或 `+` 按钮。 + +![https://www.elastic.co/guide/en/kibana/6.1/images/tutorial-visualize-landing.png](https://www.elastic.co/guide/en/kibana/6.1/images/tutorial-visualize-landing.png) + +有许多可视化类型可供选择。 + +![https://www.elastic.co/guide/en/kibana/6.1/images/tutorial-visualize-wizard-step-1.png](https://www.elastic.co/guide/en/kibana/6.1/images/tutorial-visualize-wizard-step-1.png) + +下面,我们来看创建几个图标示例: + +### Pie + +您可以从保存的搜索中构建可视化文件,也可以输入新的搜索条件。要输入新的搜索条件,首先需要选择一个索引模式来指定要搜索的索引。 + +默认搜索匹配所有文档。最初,一个“切片”包含整个饼图: + +![https://www.elastic.co/guide/en/kibana/6.1/images/tutorial-visualize-pie-1.png](https://www.elastic.co/guide/en/kibana/6.1/images/tutorial-visualize-pie-1.png) + +要指定在图表中展示哪些数据,请使用Elasticsearch存储桶聚合。分组汇总只是将与您的搜索条件相匹配的文档分类到不同的分类中,也称为分组。 + +为每个范围定义一个存储桶: + +1. 单击 `Split Slices`。 +2. 在 `Aggregation` 列表中选择 `Terms`。_注意:这里的 Terms 是 Elk 采集数据时定义好的字段或标签。_ +3. 在 `Field` 列表中选择 `level.keyword`。 +4. 点击 ![images/apply-changes-button.png](https://www.elastic.co/guide/en/kibana/6.1/images/apply-changes-button.png) 按钮来更新图表。 + +![image.png](https://upload-images.jianshu.io/upload_images/3101171-7fb2042dc6d59520.png) + +完成后,如果想要保存这个图表,可以点击页面最上方一栏中的 `Save` 按钮。 + +### Vertical Bar + +我们在展示一下如何创建柱状图。 + +1. 点击蓝色的 `Create a visualization` 或 `+` 按钮。选择 `Vertical Bar` +2. 选择索引模式。由于您尚未定义任何 bucket ,因此您会看到一个大栏,显示与默认通配符查询匹配的文档总数。 +3. 指定 Y 轴所代表的字段 +4. 指定 X 轴所代表的字段 +5. 点击 ![images/apply-changes-button.png](https://www.elastic.co/guide/en/kibana/6.1/images/apply-changes-button.png) 按钮来更新图表。 + +![image.png](https://upload-images.jianshu.io/upload_images/3101171-5aa7627284c19a56.png) + +完成后,如果想要保存这个图表,可以点击页面最上方一栏中的 `Save` 按钮。 + +## Dashboard + +`Dashboard` 可以整合和共享 `Visualize` 集合。 + +1. 点击侧面导航栏中的 Dashboard。 +2. 点击添加显示保存的可视化列表。 +3. 点击之前保存的 `Visualize`,然后点击列表底部的小向上箭头关闭可视化列表。 +4. 将鼠标悬停在可视化对象上会显示允许您编辑,移动,删除和调整可视化对象大小的容器控件。 diff --git a/docs/ecology/search/elastic-logstash-ops.md b/docs/ecology/search/elastic-logstash-ops.md new file mode 100644 index 00000000..46a9cc2e --- /dev/null +++ b/docs/ecology/search/elastic-logstash-ops.md @@ -0,0 +1,495 @@ +# Logstash 运维 + +> [Logstash](https://github.com/elastic/logstash) 是开源的服务器端数据处理管道,能够同时从多个来源采集数据,转换数据,然后将数据发送到您最喜欢的“存储库”中。 + +## 1. 安装 + +### 1.1. 安装步骤 + +安装步骤如下: + +1. 在 [logstash 官方下载地址](https://www.elastic.co/downloads/logstash)下载所需版本包并解压到本地。 + +2. 添加一个 `logstash.conf` 文件,指定要使用的插件以及每个插件的设置。举个简单的例子: + +``` +input { stdin { } } +output { + elasticsearch { hosts => ["localhost:9200"] } + stdout { codec => rubydebug } +} +``` + +3. 运行 `bin/logstash -f logstash.conf` (Windows 上运行`bin/logstash.bat -f logstash.conf`) + +## 2. 配置 + +### 2.1. 设置文件 + +- **`logstash.yml`**:logstash 的默认启动配置文件 +- **`jvm.options`**:logstash 的 JVM 配置文件。 +- **`startup.options`** (Linux):包含系统安装脚本在 `/usr/share/logstash/bin` 中使用的选项为您的系统构建适当的启动脚本。安装 Logstash 软件包时,系统安装脚本将在安装过程结束时执行,并使用 `startup.options` 中指定的设置来设置用户,组,服务名称和服务描述等选项。 + +### 2.2. logstash.yml 设置项 + +节选部分设置项,更多项请参考:https://www.elastic.co/guide/en/logstash/current/logstash-settings-file.html + +| 参数 | 描述 | 默认值 | +| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| `node.name` | 节点名 | 机器的主机名 | +| `path.data` | Logstash 及其插件用于任何持久性需求的目录。 | `LOGSTASH_HOME/data` | +| `pipeline.workers` | 同时执行管道的过滤器和输出阶段的工作任务数量。如果发现事件正在备份,或 CPU 未饱和,请考虑增加此数字以更好地利用机器处理能力。 | Number of the host’s CPU cores | +| `pipeline.batch.size` | 尝试执行过滤器和输出之前,单个工作线程从输入收集的最大事件数量。较大的批量处理大小一般来说效率更高,但是以增加的内存开销为代价。您可能必须通过设置 `LS_HEAP_SIZE` 变量来有效使用该选项来增加 JVM 堆大小。 | `125` | +| `pipeline.batch.delay` | 创建管道事件批处理时,在将一个尺寸过小的批次发送给管道工作任务之前,等待每个事件需要多长时间(毫秒)。 | `5` | +| `pipeline.unsafe_shutdown` | 如果设置为 true,则即使在内存中仍存在 inflight 事件时,也会强制 Logstash 在关闭期间退出。默认情况下,Logstash 将拒绝退出,直到所有接收到的事件都被推送到输出。启用此选项可能会导致关机期间数据丢失。 | `false` | +| `path.config` | 主管道的 Logstash 配置路径。如果您指定一个目录或通配符,配置文件将按字母顺序从目录中读取。 | Platform-specific. See [[dir-layout\]](https://github.com/elastic/logstash/blob/6.1/docs/static/settings-file.asciidoc#dir-layout). | +| `config.string` | 包含用于主管道的管道配置的字符串。使用与配置文件相同的语法。 | None | +| `config.test_and_exit` | 设置为 true 时,检查配置是否有效,然后退出。请注意,使用此设置不会检查 grok 模式的正确性。 Logstash 可以从目录中读取多个配置文件。如果将此设置与 log.level:debug 结合使用,则 Logstash 将记录组合的配置文件,并注掉其源文件的配置块。 | `false` | +| `config.reload.automatic` | 设置为 true 时,定期检查配置是否已更改,并在配置更改时重新加载配置。这也可以通过 SIGHUP 信号手动触发。 | `false` | +| `config.reload.interval` | Logstash 检查配置文件更改的时间间隔。 | `3s` | +| `config.debug` | 设置为 true 时,将完全编译的配置显示为调试日志消息。您还必须设置`log.level:debug`。警告:日志消息将包括任何传递给插件配置作为明文的“密码”选项,并可能导致明文密码出现在您的日志! | `false` | +| `config.support_escapes` | 当设置为 true 时,带引号的字符串将处理转义字符。 | `false` | +| `modules` | 配置时,模块必须处于上表所述的嵌套 YAML 结构中。 | None | +| `http.host` | 绑定地址 | `"127.0.0.1"` | +| `http.port` | 绑定端口 | `9600` | +| `log.level` | 日志级别。有效选项:fatal > error > warn > info > debug > trace | `info` | +| `log.format` | 日志格式。json (JSON 格式)或 plain (原对象) | `plain` | +| `path.logs` | Logstash 自身日志的存储路径 | `LOGSTASH_HOME/logs` | +| `path.plugins` | 在哪里可以找到自定义的插件。您可以多次指定此设置以包含多个路径。 | | + +## 3. 启动 + +### 3.1. 命令行 + +通过命令行启动 logstash 的方式如下: + +``` +bin/logstash [options] +``` + +其中 [options] 是您可以指定用于控制 Logstash 执行的命令行标志。 + +在命令行上设置的任何标志都会覆盖 Logstash 设置文件(`logstash.yml`)中的相应设置,但设置文件本身不会更改。 + +> **注** +> +> 虽然可以通过指定命令行参数的方式,来控制 logstash 的运行方式,但显然这么做很麻烦。 +> +> 建议通过指定配置文件的方式,来控制 logstash 运行,启动命令如下: +> +> ``` +> bin/logstash -f logstash.conf +> ``` +> +> 若想了解更多的命令行参数细节,请参考:https://www.elastic.co/guide/en/logstash/current/running-logstash-command-line.html + +### 3.2. 配置文件 + +上节,我们了解到,logstash 可以执行 `bin/logstash -f logstash.conf` ,按照配置文件中的参数去覆盖默认设置文件(`logstash.yml`)中的设置。 + +这节,我们就来学习一下这个配置文件如何配置参数。 + +#### 3.2.1. 配置文件结构 + +在工作原理一节中,我们已经知道了 Logstash 主要有三个工作阶段 input 、filter、output。而 logstash 配置文件文件结构也与之相对应: + +``` +input {} + +filter {} + +output {} +``` + +> 每个部分都包含一个或多个插件的配置选项。如果指定了多个过滤器,则会按照它们在配置文件中的显示顺序应用它们。 + +#### 3.2.2. 插件配置 + +插件的配置由插件名称和插件的一个设置块组成。 + +下面的例子中配置了两个输入文件配置: + +``` +input { + file { + path => "/var/log/messages" + type => "syslog" + } + + file { + path => "/var/log/apache/access.log" + type => "apache" + } +} +``` + +您可以配置的设置因插件类型而异。你可以参考: [Input Plugins](https://www.elastic.co/guide/en/logstash/current/input-plugins.html), [Output Plugins](https://www.elastic.co/guide/en/logstash/current/output-plugins.html), [Filter Plugins](https://www.elastic.co/guide/en/logstash/current/filter-plugins.html), 和 [Codec Plugins](https://www.elastic.co/guide/en/logstash/current/codec-plugins.html) 。 + +#### 3.2.3. 值类型 + +一个插件可以要求设置的值是一个特定的类型,比如布尔值,列表或哈希值。以下值类型受支持。 + +- Array + +``` + users => [ {id => 1, name => bob}, {id => 2, name => jane} ] +``` + +- Lists + +``` + path => [ "/var/log/messages", "/var/log/*.log" ] + uris => [ "http://elastic.co", "http://example.net" ] +``` + +- Boolean + +``` + ssl_enable => true +``` + +- Bytes + +``` + my_bytes => "1113" # 1113 bytes + my_bytes => "10MiB" # 10485760 bytes + my_bytes => "100kib" # 102400 bytes + my_bytes => "180 mb" # 180000000 bytes +``` + +- Codec + +``` + codec => "json" +``` + +- Hash + +``` +match => { + "field1" => "value1" + "field2" => "value2" + ... +} +``` + +- Number + +``` + port => 33 +``` + +- Password + +``` + my_password => "password" +``` + +- URI + +``` + my_uri => "http://foo:bar@example.net" +``` + +- Path + +``` + my_path => "/tmp/logstash" +``` + +- String + +* 转义字符 + +## 4. 插件 + +### 4.1. input + +> Logstash 支持各种输入选择 ,可以在同一时间从众多常用来源捕捉事件。能够以连续的流式传输方式,轻松地从您的日志、指标、Web 应用、数据存储以及各种 AWS 服务采集数据。 + +#### 4.1.1. 常用 input 插件 + +- **file**:从文件系统上的文件读取,就像 UNIX 命令 `tail -0F` 一样 +- **syslog:**在众所周知的端口 514 上侦听系统日志消息,并根据 RFC3164 格式进行解析 +- **redis:**从 redis 服务器读取,使用 redis 通道和 redis 列表。 Redis 经常用作集中式 Logstash 安装中的“代理”,它将来自远程 Logstash“托运人”的 Logstash 事件排队。 +- **beats:**处理由 Filebeat 发送的事件。 + +更多详情请见:[Input Plugins](https://www.elastic.co/guide/en/logstash/current/input-plugins.html) + +### 4.2. filter + +> 过滤器是 Logstash 管道中的中间处理设备。如果符合特定条件,您可以将条件过滤器组合在一起,对事件执行操作。 + +#### 4.2.1. 常用 filter 插件 + +- **grok:**解析和结构任意文本。 Grok 目前是 Logstash 中将非结构化日志数据解析为结构化和可查询的最佳方法。 +- **mutate:**对事件字段执行一般转换。您可以重命名,删除,替换和修改事件中的字段。 + +- **drop:**完全放弃一个事件,例如调试事件。 + +- **clone:**制作一个事件的副本,可能会添加或删除字段。 + +- **geoip:**添加有关 IP 地址的地理位置的信息(也可以在 Kibana 中显示惊人的图表!) + +更多详情请见:[Filter Plugins](https://www.elastic.co/guide/en/logstash/current/filter-plugins.html) + +### 4.3. output + +> 输出是 Logstash 管道的最后阶段。一个事件可以通过多个输出,但是一旦所有输出处理完成,事件就完成了执行。 + +#### 4.3.1. 常用 output 插件 + +- **elasticsearch:**将事件数据发送给 Elasticsearch(推荐模式)。 +- **file:**将事件数据写入文件或磁盘。 +- **graphite:**将事件数据发送给 graphite(一个流行的开源工具,存储和绘制指标。 http://graphite.readthedocs.io/en/latest/)。 +- **statsd:**将事件数据发送到 statsd (这是一种侦听统计数据的服务,如计数器和定时器,通过 UDP 发送并将聚合发送到一个或多个可插入的后端服务)。 + +更多详情请见:[Output Plugins](https://www.elastic.co/guide/en/logstash/current/output-plugins.html) + +### 4.4. codec + +用于格式化对应的内容。 + +#### 4.4.1. 常用 codec 插件 + +- **json:**以 JSON 格式对数据进行编码或解码。 +- **multiline:**将多行文本事件(如 java 异常和堆栈跟踪消息)合并为单个事件。 + +更多插件请见:[Codec Plugins](https://www.elastic.co/guide/en/logstash/current/codec-plugins.html) + +## 5. 实战 + +前面的内容都是对 Logstash 的介绍和原理说明。接下来,我们来实战一些常见的应用场景。 + +### 5.1. 传输控制台数据 + +> stdin input 插件从标准输入读取事件。这是最简单的 input 插件,一般用于测试场景。 + +**应用** + +(1)创建 `logstash-input-stdin.conf` : + +``` +input { stdin { } } +output { + elasticsearch { hosts => ["localhost:9200"] } + stdout { codec => rubydebug } +} +``` + +更多配置项可以参考:https://www.elastic.co/guide/en/logstash/current/plugins-inputs-stdin.html + +(2)执行 logstash,使用 `-f` 来指定你的配置文件: + +``` +bin/logstash -f logstash-input-stdin.conf +``` + +### 5.2. 传输 logback 日志 + +> elk 默认使用的 Java 日志工具是 log4j2 ,并不支持 logback 和 log4j。 +> +> 想使用 logback + logstash ,可以使用 [logstash-logback-encoder](https://github.com/logstash/logstash-logback-encoder) 。[logstash-logback-encoder](https://github.com/logstash/logstash-logback-encoder) 提供了 UDP / TCP / 异步方式来传输日志数据到 logstash。 +> +> 如果你使用的是 log4j ,也不是不可以用这种方式,只要引入桥接 jar 包即可。如果你对 log4j 、logback ,或是桥接 jar 包不太了解,可以参考我的这篇博文:[细说 Java 主流日志工具库](https://github.com/dunwu/JavaStack/blob/master/docs/javalib/java-log.md) 。 + +#### 5.2.1. TCP 应用 + +1. logstash 配置 + + (1)创建 `logstash-input-tcp.conf` : + +``` +input { + # stdin { } + tcp { + # host:port就是上面appender中的 destination, + # 这里其实把logstash作为服务,开启9250端口接收logback发出的消息 + host => "127.0.0.1" port => 9250 mode => "server" tags => ["tags"] codec => json_lines + } +} +output { + elasticsearch { hosts => ["localhost:9200"] } + stdout { codec => rubydebug } +} +``` + +更多配置项可以参考:https://www.elastic.co/guide/en/logstash/current/plugins-inputs-tcp.html + +(2)执行 logstash,使用 `-f` 来指定你的配置文件:`bin/logstash -f logstash-input-udp.conf` + +2. java 应用配置 + + (1)在 Java 应用的 pom.xml 中引入 jar 包: + +```xml + + net.logstash.logback + logstash-logback-encoder + 4.11 + + + + + ch.qos.logback + logback-core + 1.2.3 + + + ch.qos.logback + logback-classic + 1.2.3 + + + ch.qos.logback + logback-access + 1.2.3 + +``` + +(2)接着,在 logback.xml 中添加 appender + +```xml + + + 192.168.28.32:9251 + + + + + +``` + +大功告成,此后,`io.github.dunwu.spring` 包中的 TRACE 及以上级别的日志信息都会被定向输出到 logstash 服务。 + +
    ![img](http://upload-images.jianshu.io/upload_images/3101171-cd876d79a14955b0.png)
    + +接下来,就是 logback 的具体使用 ,如果对此不了解,不妨参考一下我的这篇博文:[细说 Java 主流日志工具库](https://github.com/dunwu/JavaStack/blob/master/docs/javalib/java-log.md) 。 + +**实例:**[我的 logback.xml](https://github.com/dunwu/JavaStack/blob/master/codes/javatool/src/main/resources/logback.xml) + +#### 5.2.2. UDP 应用 + +UDP 和 TCP 的使用方式大同小异。 + +1. logstash 配置 + + (1)创建 `logstash-input-udp.conf` : + +``` +input { +udp { + port => 9250 + codec => json +} +} +output { + elasticsearch { hosts => ["localhost:9200"] } + stdout { codec => rubydebug } +} +``` + +更多配置项可以参考:https://www.elastic.co/guide/en/logstash/current/plugins-inputs-udp.html + +(2)执行 logstash,使用 `-f` 来指定你的配置文件:`bin/logstash -f logstash-input-udp.conf` + +2. java 应用配置 + + (1)在 Java 应用的 pom.xml 中引入 jar 包: + + 与 **TCP 应用** 一节中的引入依赖包完全相同。 + + (2)接着,在 logback.xml 中添加 appender + +```xml + + 192.168.28.32 + 9250 + + + + +``` + +(3)接下来,就是 logback 的具体使用 ,如果对此不了解,不妨参考一下我的这篇博文:[细说 Java 主流日志工具库](https://github.com/dunwu/JavaStack/blob/master/docs/javalib/java-log.md) 。 + +**实例:**[我的 logback.xml](https://github.com/dunwu/JavaStack/blob/master/codes/javatool/src/main/resources/logback.xml) + +### 5.3. 传输文件 + +> 在 Java Web 领域,需要用到一些重要的工具,例如 Tomcat 、Nginx 、Mysql 等。这些不属于业务应用,但是它们的日志数据对于定位问题、分析统计同样很重要。这时无法使用 logback 方式将它们的日志传输到 logstash。 +> +> 如何采集这些日志文件呢?别急,你可以使用 logstash 的 file input 插件。 +> +> 需要注意的是,传输文件这种方式,必须在日志所在的机器上部署 logstash 。 + +**应用** + +logstash 配置 + +(1)创建 `logstash-input-file.conf` : + +``` +input { + file { + path => ["/var/log/nginx/access.log"] + type => "nginx-access-log" + start_position => "beginning" + } +} + +output { + if [type] == "nginx-access-log" { + elasticsearch { + hosts => ["localhost:9200"] + index => "nginx-access-log" + } + } +} +``` + +(2)执行 logstash,使用 `-f` 来指定你的配置文件:`bin/logstash -f logstash-input-file.conf` + +更多配置项可以参考:https://www.elastic.co/guide/en/logstash/current/plugins-inputs-file.html + +## 6. 小技巧 + +### 6.1. 启动、终止应用 + +如果你的 logstash 每次都是通过指定配置文件方式启动。不妨建立一个启动脚本。 + +``` +# cd xxx 进入 logstash 安装目录下的 bin 目录 +logstash -f logstash.conf +``` + +如果你的 logstash 运行在 linux 系统下,不妨使用 nohup 来启动一个守护进程。这样做的好处在于,即使关闭终端,应用仍会运行。 + +**创建 startup.sh** + +``` +nohup ./logstash -f logstash.conf >> nohup.out 2>&1 & +``` + +终止应用没有什么好方法,你只能使用 ps -ef | grep logstash ,查出进程,将其 kill 。不过,我们可以写一个脚本来干这件事: + +**创建 shutdown.sh** + +脚本不多解释,请自行领会作用。 + +``` +PID=`ps -ef | grep logstash | awk '{ print $2}' | head -n 1` +kill -9 ${PID} +``` + +## 7. 参考资料 + +- [Logstash 官网](https://www.elastic.co/cn/products/logstash) +- [Logstash Github](https://github.com/elastic/logstash) +- [Logstash 官方文档](https://www.elastic.co/guide/en/logstash/current/index.html) +- [logstash-logback-encoder](https://github.com/logstash/logstash-logback-encoder) +- [ELK Stack 权威指南](https://github.com/chenryn/logstash-best-practice-cn) +- [ELK(Elasticsearch、Logstash、Kibana)安装和配置](https://github.com/judasn/Linux-Tutorial/blob/master/ELK-Install-And-Settings.md) diff --git a/docs/ecology/search/elastic-logstash.md b/docs/ecology/search/elastic-logstash.md new file mode 100644 index 00000000..5c4abb2c --- /dev/null +++ b/docs/ecology/search/elastic-logstash.md @@ -0,0 +1,519 @@ +--- +title: Elastic 技术栈之 Logstash 基础 +date: 2017-12-26 +categories: +- javatool +tags: +- java +- javatool +- log +- elastic +--- + +# Elastic 技术栈之 Logstash 基础 + +> 本文是 Elastic 技术栈(ELK)的 Logstash 应用。 +> +> 如果不了解 Elastic 的安装、配置、部署,可以参考:[Elastic 技术栈之快速入门](https://github.com/dunwu/JavaStack/blob/master/docs/javatool/elastic/elastic-quickstart.md) + +## 简介 + +Logstash 可以传输和处理你的日志、事务或其他数据。 + +### 功能 + +Logstash 是 Elasticsearch 的最佳数据管道。 + +Logstash 是插件式管理模式,在输入、过滤、输出以及编码过程中都可以使用插件进行定制。Logstash 社区有超过 200 种可用插件。 + +### 工作原理 + +Logstash 有两个必要元素:`input` 和 `output` ,一个可选元素:`filter`。 + +这三个元素,分别代表 Logstash 事件处理的三个阶段:输入 > 过滤器 > 输出。 + +
    ![img](https://www.elastic.co/guide/en/logstash/current/static/images/basic_logstash_pipeline.png)
    + +- input 负责从数据源采集数据。 +- filter 将数据修改为你指定的格式或内容。 +- output 将数据传输到目的地。 + +在实际应用场景中,通常输入、输出、过滤器不止一个。Logstash 的这三个元素都使用插件式管理方式,用户可以根据应用需要,灵活的选用各阶段需要的插件,并组合使用。 + +后面将对插件展开讲解,暂且不表。 + +## 设置 + +### 设置文件 + +- **`logstash.yml`**:logstash 的默认启动配置文件 +- **`jvm.options`**:logstash 的 JVM 配置文件。 +- **`startup.options`** (Linux):包含系统安装脚本在 `/usr/share/logstash/bin` 中使用的选项为您的系统构建适当的启动脚本。安装 Logstash 软件包时,系统安装脚本将在安装过程结束时执行,并使用 `startup.options` 中指定的设置来设置用户,组,服务名称和服务描述等选项。 + + +### logstash.yml 设置项 + +节选部分设置项,更多项请参考:https://www.elastic.co/guide/en/logstash/current/logstash-settings-file.html + +| 参数 | 描述 | 默认值 | +| -------------------------- | ---------------------------------------- | ---------------------------------------- | +| `node.name` | 节点名 | 机器的主机名 | +| `path.data` | Logstash及其插件用于任何持久性需求的目录。 | `LOGSTASH_HOME/data` | +| `pipeline.workers` | 同时执行管道的过滤器和输出阶段的工作任务数量。如果发现事件正在备份,或CPU未饱和,请考虑增加此数字以更好地利用机器处理能力。 | Number of the host’s CPU cores | +| `pipeline.batch.size` | 尝试执行过滤器和输出之前,单个工作线程从输入收集的最大事件数量。较大的批量处理大小一般来说效率更高,但是以增加的内存开销为代价。您可能必须通过设置 `LS_HEAP_SIZE` 变量来有效使用该选项来增加JVM堆大小。 | `125` | +| `pipeline.batch.delay` | 创建管道事件批处理时,在将一个尺寸过小的批次发送给管道工作任务之前,等待每个事件需要多长时间(毫秒)。 | `5` | +| `pipeline.unsafe_shutdown` | 如果设置为true,则即使在内存中仍存在inflight事件时,也会强制Logstash在关闭期间退出。默认情况下,Logstash将拒绝退出,直到所有接收到的事件都被推送到输出。启用此选项可能会导致关机期间数据丢失。 | `false` | +| `path.config` | 主管道的Logstash配置路径。如果您指定一个目录或通配符,配置文件将按字母顺序从目录中读取。 | Platform-specific. See [[dir-layout\]](https://github.com/elastic/logstash/blob/6.1/docs/static/settings-file.asciidoc#dir-layout). | +| `config.string` | 包含用于主管道的管道配置的字符串。使用与配置文件相同的语法。 | None | +| `config.test_and_exit` | 设置为true时,检查配置是否有效,然后退出。请注意,使用此设置不会检查grok模式的正确性。 Logstash可以从目录中读取多个配置文件。如果将此设置与log.level:debug结合使用,则Logstash将记录组合的配置文件,并注掉其源文件的配置块。 | `false` | +| `config.reload.automatic` | 设置为true时,定期检查配置是否已更改,并在配置更改时重新加载配置。这也可以通过SIGHUP信号手动触发。 | `false` | +| `config.reload.interval` | Logstash 检查配置文件更改的时间间隔。 | `3s` | +| `config.debug` | 设置为true时,将完全编译的配置显示为调试日志消息。您还必须设置`log.level:debug`。警告:日志消息将包括任何传递给插件配置作为明文的“密码”选项,并可能导致明文密码出现在您的日志! | `false` | +| `config.support_escapes` | 当设置为true时,带引号的字符串将处理转义字符。 | `false` | +| `modules` | 配置时,模块必须处于上表所述的嵌套YAML结构中。 | None | +| `http.host` | 绑定地址 | `"127.0.0.1"` | +| `http.port` | 绑定端口 | `9600` | +| `log.level` | 日志级别。有效选项:fatal > error > warn > info > debug > trace | `info` | +| `log.format` | 日志格式。json (JSON 格式)或 plain (原对象) | `plain` | +| `path.logs` | Logstash 自身日志的存储路径 | `LOGSTASH_HOME/logs` | +| `path.plugins` | 在哪里可以找到自定义的插件。您可以多次指定此设置以包含多个路径。 | | + +## 启动 + +### 命令行 + +通过命令行启动 logstash 的方式如下: + +``` +bin/logstash [options] +``` + +其中 [options] 是您可以指定用于控制 Logstash 执行的命令行标志。 + +在命令行上设置的任何标志都会覆盖 Logstash 设置文件(`logstash.yml`)中的相应设置,但设置文件本身不会更改。 + +> **注** +> +> 虽然可以通过指定命令行参数的方式,来控制 logstash 的运行方式,但显然这么做很麻烦。 +> +> 建议通过指定配置文件的方式,来控制 logstash 运行,启动命令如下: +> +> ``` +> bin/logstash -f logstash.conf +> ``` +> 若想了解更多的命令行参数细节,请参考:https://www.elastic.co/guide/en/logstash/current/running-logstash-command-line.html +> + +### 配置文件 + +上节,我们了解到,logstash 可以执行 `bin/logstash -f logstash.conf` ,按照配置文件中的参数去覆盖默认设置文件(`logstash.yml`)中的设置。 + +这节,我们就来学习一下这个配置文件如何配置参数。 + +#### 配置文件结构 + +在工作原理一节中,我们已经知道了 Logstash 主要有三个工作阶段 input 、filter、output。而 logstash 配置文件文件结构也与之相对应: + +``` +input {} + +filter {} + +output {} +``` + +> 每个部分都包含一个或多个插件的配置选项。如果指定了多个过滤器,则会按照它们在配置文件中的显示顺序应用它们。 + +#### 插件配置 + +插件的配置由插件名称和插件的一个设置块组成。 + +下面的例子中配置了两个输入文件配置: + +``` +input { + file { + path => "/var/log/messages" + type => "syslog" + } + + file { + path => "/var/log/apache/access.log" + type => "apache" + } +} +``` + +您可以配置的设置因插件类型而异。你可以参考: [Input Plugins](https://www.elastic.co/guide/en/logstash/current/input-plugins.html), [Output Plugins](https://www.elastic.co/guide/en/logstash/current/output-plugins.html), [Filter Plugins](https://www.elastic.co/guide/en/logstash/current/filter-plugins.html), 和 [Codec Plugins](https://www.elastic.co/guide/en/logstash/current/codec-plugins.html) 。 + +#### 值类型 + +一个插件可以要求设置的值是一个特定的类型,比如布尔值,列表或哈希值。以下值类型受支持。 + +- Array + +``` + users => [ {id => 1, name => bob}, {id => 2, name => jane} ] +``` + +- Lists + +``` + path => [ "/var/log/messages", "/var/log/*.log" ] + uris => [ "http://elastic.co", "http://example.net" ] +``` + +- Boolean + +``` + ssl_enable => true +``` + +- Bytes + +``` + my_bytes => "1113" # 1113 bytes + my_bytes => "10MiB" # 10485760 bytes + my_bytes => "100kib" # 102400 bytes + my_bytes => "180 mb" # 180000000 bytes +``` + +- Codec + +``` + codec => "json" +``` + +- Hash + +``` +match => { + "field1" => "value1" + "field2" => "value2" + ... +} +``` + +- Number + +``` + port => 33 +``` + +- Password + +``` + my_password => "password" +``` + +- URI + +``` + my_uri => "http://foo:bar@example.net" +``` + +- Path + +``` + my_path => "/tmp/logstash" +``` + +- String + + +- 转义字符 + +## 插件 + +### input + +> Logstash 支持各种输入选择 ,可以在同一时间从众多常用来源捕捉事件。能够以连续的流式传输方式,轻松地从您的日志、指标、Web 应用、数据存储以及各种 AWS 服务采集数据。 + +#### 常用 input 插件 + +- **file**:从文件系统上的文件读取,就像UNIX命令 `tail -0F` 一样 +- **syslog:**在众所周知的端口514上侦听系统日志消息,并根据RFC3164格式进行解析 +- **redis:**从redis服务器读取,使用redis通道和redis列表。 Redis经常用作集中式Logstash安装中的“代理”,它将来自远程Logstash“托运人”的Logstash事件排队。 +- **beats:**处理由Filebeat发送的事件。 + +更多详情请见:[Input Plugins](https://www.elastic.co/guide/en/logstash/current/input-plugins.html) + +### filter + +> 过滤器是Logstash管道中的中间处理设备。如果符合特定条件,您可以将条件过滤器组合在一起,对事件执行操作。 + +#### 常用 filter 插件 + +- **grok:**解析和结构任意文本。 Grok目前是Logstash中将非结构化日志数据解析为结构化和可查询的最佳方法。 +- **mutate:**对事件字段执行一般转换。您可以重命名,删除,替换和修改事件中的字段。 + +- **drop:**完全放弃一个事件,例如调试事件。 + +- **clone:**制作一个事件的副本,可能会添加或删除字段。 + +- **geoip:**添加有关IP地址的地理位置的信息(也可以在Kibana中显示惊人的图表!) + + +更多详情请见:[Filter Plugins](https://www.elastic.co/guide/en/logstash/current/filter-plugins.html) + +### output + +> 输出是Logstash管道的最后阶段。一个事件可以通过多个输出,但是一旦所有输出处理完成,事件就完成了执行。 + +#### 常用 output 插件 + +- **elasticsearch:**将事件数据发送给 Elasticsearch(推荐模式)。 +- **file:**将事件数据写入文件或磁盘。 +- **graphite:**将事件数据发送给 graphite(一个流行的开源工具,存储和绘制指标。 http://graphite.readthedocs.io/en/latest/)。 +- **statsd:**将事件数据发送到 statsd (这是一种侦听统计数据的服务,如计数器和定时器,通过UDP发送并将聚合发送到一个或多个可插入的后端服务)。 + +更多详情请见:[Output Plugins](https://www.elastic.co/guide/en/logstash/current/output-plugins.html) + +### codec + +用于格式化对应的内容。 + +#### 常用 codec 插件 + +- **json:**以JSON格式对数据进行编码或解码。 +- **multiline:**将多行文本事件(如java异常和堆栈跟踪消息)合并为单个事件。 + +更多插件请见:[Codec Plugins](https://www.elastic.co/guide/en/logstash/current/codec-plugins.html) + +## 实战 + +前面的内容都是对 Logstash 的介绍和原理说明。接下来,我们来实战一些常见的应用场景。 + +### 传输控制台数据 + +> stdin input 插件从标准输入读取事件。这是最简单的 input 插件,一般用于测试场景。 +> + +**应用** + +(1)创建 `logstash-input-stdin.conf` : + +``` +input { stdin { } } +output { + elasticsearch { hosts => ["localhost:9200"] } + stdout { codec => rubydebug } +} +``` + +更多配置项可以参考:https://www.elastic.co/guide/en/logstash/current/plugins-inputs-stdin.html + +(2)执行 logstash,使用 `-f` 来指定你的配置文件: + +``` +bin/logstash -f logstash-input-stdin.conf +``` + +### 传输 logback 日志 + +> elk 默认使用的 Java 日志工具是 log4j2 ,并不支持 logback 和 log4j。 +> +> 想使用 logback + logstash ,可以使用 [logstash-logback-encoder](https://github.com/logstash/logstash-logback-encoder) 。[logstash-logback-encoder](https://github.com/logstash/logstash-logback-encoder) 提供了 UDP / TCP / 异步方式来传输日志数据到 logstash。 +> +> 如果你使用的是 log4j ,也不是不可以用这种方式,只要引入桥接 jar 包即可。如果你对 log4j 、logback ,或是桥接 jar 包不太了解,可以参考我的这篇博文:[细说 Java 主流日志工具库](https://github.com/dunwu/JavaStack/blob/master/docs/javalib/java-log.md) 。 + +#### TCP 应用 + +1. logstash 配置 + + (1)创建 `logstash-input-tcp.conf` : + +``` +input { +tcp { + port => 9251 + codec => json_lines + mode => server +} +} +output { + elasticsearch { hosts => ["localhost:9200"] } + stdout { codec => rubydebug } +} +``` + + 更多配置项可以参考:https://www.elastic.co/guide/en/logstash/current/plugins-inputs-tcp.html + + (2)执行 logstash,使用 `-f` 来指定你的配置文件:`bin/logstash -f logstash-input-udp.conf` + + +2. java 应用配置 + + (1)在 Java 应用的 pom.xml 中引入 jar 包: + +```xml + + net.logstash.logback + logstash-logback-encoder + 4.11 + + + + + ch.qos.logback + logback-core + 1.2.3 + + + ch.qos.logback + logback-classic + 1.2.3 + + + ch.qos.logback + logback-access + 1.2.3 + +``` + + (2)接着,在 logback.xml 中添加 appender + +```xml + + + 192.168.28.32:9251 + + + + + +``` + + (3)接下来,就是 logback 的具体使用 ,如果对此不了解,不妨参考一下我的这篇博文:[细说 Java 主流日志工具库](https://github.com/dunwu/JavaStack/blob/master/docs/javalib/java-log.md) 。 + + **实例:**[我的logback.xml](https://github.com/dunwu/JavaStack/blob/master/codes/javatool/src/main/resources/logback.xml) + +#### UDP 应用 + +UDP 和 TCP 的使用方式大同小异。 + +1. logstash 配置 + + (1)创建 `logstash-input-udp.conf` : + +``` +input { +udp { + port => 9250 + codec => json +} +} +output { + elasticsearch { hosts => ["localhost:9200"] } + stdout { codec => rubydebug } +} +``` + + 更多配置项可以参考:https://www.elastic.co/guide/en/logstash/current/plugins-inputs-udp.html + + (2)执行 logstash,使用 `-f` 来指定你的配置文件:`bin/logstash -f logstash-input-udp.conf` + + +2. java 应用配置 + + (1)在 Java 应用的 pom.xml 中引入 jar 包: + + 与 **TCP 应用** 一节中的引入依赖包完全相同。 + + (2)接着,在 logback.xml 中添加 appender + + ```xml + + 192.168.28.32 + 9250 + + + + + ``` + + (3)接下来,就是 logback 的具体使用 ,如果对此不了解,不妨参考一下我的这篇博文:[细说 Java 主流日志工具库](https://github.com/dunwu/JavaStack/blob/master/docs/javalib/java-log.md) 。 + + **实例:**[我的logback.xml](https://github.com/dunwu/JavaStack/blob/master/codes/javatool/src/main/resources/logback.xml) + +### 传输文件 + +> 在 Java Web 领域,需要用到一些重要的工具,例如 Tomcat 、Nginx 、Mysql 等。这些不属于业务应用,但是它们的日志数据对于定位问题、分析统计同样很重要。这时无法使用 logback 方式将它们的日志传输到 logstash。 +> +> 如何采集这些日志文件呢?别急,你可以使用 logstash 的 file input 插件。 +> +> 需要注意的是,传输文件这种方式,必须在日志所在的机器上部署 logstash 。 + +**应用** + +logstash 配置 + +(1)创建 `logstash-input-file.conf` : + +``` +input { + file { + path => ["/var/log/nginx/access.log"] + type => "nginx-access-log" + start_position => "beginning" + } +} + +output { + if [type] == "nginx-access-log" { + elasticsearch { + hosts => ["localhost:9200"] + index => "nginx-access-log" + } + } +} +``` + +(2)执行 logstash,使用 `-f` 来指定你的配置文件:`bin/logstash -f logstash-input-file.conf` + +更多配置项可以参考:https://www.elastic.co/guide/en/logstash/current/plugins-inputs-file.html + +## 小技巧 + +### 启动、终止应用 + +如果你的 logstash 每次都是通过指定配置文件方式启动。不妨建立一个启动脚本。 + +``` +# cd xxx 进入 logstash 安装目录下的 bin 目录 +logstash -f logstash.conf +``` + +如果你的 logstash 运行在 linux 系统下,不妨使用 nohup 来启动一个守护进程。这样做的好处在于,即使关闭终端,应用仍会运行。 + +**创建 startup.sh** + +``` +nohup ./logstash -f logstash.conf >> nohup.out 2>&1 & +``` + +终止应用没有什么好方法,你只能使用 ps -ef | grep logstash ,查出进程,将其kill 。不过,我们可以写一个脚本来干这件事: + +**创建 shutdown.sh** + +脚本不多解释,请自行领会作用。 + +``` +PID=`ps -ef | grep logstash | awk '{ print $2}' | head -n 1` +kill -9 ${PID} +``` + +## 资料 + +- [Logstash 官方文档](https://www.elastic.co/guide/en/logstash/current/index.html) +- [logstash-logback-encoder](https://github.com/logstash/logstash-logback-encoder) +- [ELK Stack权威指南](https://github.com/chenryn/logstash-best-practice-cn) +- [ELK(Elasticsearch、Logstash、Kibana)安装和配置](https://github.com/judasn/Linux-Tutorial/blob/master/ELK-Install-And-Settings.md) + +## 推荐阅读 + +- [Elastic 技术栈](https://github.com/dunwu/JavaStack/blob/master/docs/javatool/elastic/README.md) +- [JavaStack](https://github.com/dunwu/JavaStack) diff --git a/docs/ecology/search/elastic-quickstart.md b/docs/ecology/search/elastic-quickstart.md new file mode 100644 index 00000000..ca228ed2 --- /dev/null +++ b/docs/ecology/search/elastic-quickstart.md @@ -0,0 +1,276 @@ +# Elastic 快速入门 + +> 开源协议:[Apache 2.0](https://github.com/elastic/elasticsearch/tree/7.4/licenses/APACHE-LICENSE-2.0.txt) + +## 1. 简介 + +### 1.1. Elastic Stack 是什么 + +**Elastic Stack** 即 **ELK Stack**。 + +ELK 是指 Elastic 公司旗下三款产品 [ElasticSearch](https://www.elastic.co/cn/products/elasticsearch) 、[Logstash](https://www.elastic.co/cn/products/logstash) 、[Kibana](https://www.elastic.co/cn/products/kibana) 的首字母组合。 + +- Elasticsearch 是一个搜索和分析引擎。 +- Logstash 是服务器端数据处理管道,能够同时从多个来源采集数据,转换数据,然后将数据发送到诸如 Elasticsearch 等“存储库”中。 +- Kibana 则可以让用户在 Elasticsearch 中使用图形和图表对数据进行可视化。 + +而 Elastic Stack 是 ELK Stack 的更新换代产品,最新产品引入了轻量型的单一功能数据采集器,并把它们叫做 [Beats](https://www.elastic.co/cn/products/beats)。 + +### 1.2. 为什么使用 Elastic Stack + +对于有一定规模的公司来说,通常会很多个应用,并部署在大量的服务器上。运维和开发人员常常需要通过查看日志来定位问题。如果应用是集群化部署,试想如果登录一台台服务器去查看日志,是多么费时费力。 + +而通过 ELK 这套解决方案,可以同时实现日志收集、日志搜索和日志分析的功能。 + +### 1.3. Elastic Stack 架构 + +
    ![img](https://www.elastic.co/guide/en/logstash/current/static/images/deploy3.png)
    + +> **说明** +> +> 以上是 Elastic Stack 的一个架构图。从图中可以清楚的看到数据流向。 +> +> - [Beats](https://www.elastic.co/products/beats) 是单一用途的数据传输平台,它可以将多台机器的数据发送到 Logstash 或 ElasticSearch。但 Beats 并不是不可或缺的一环,所以本文中暂不介绍。 +> - [Logstash](https://www.elastic.co/products/logstash) 是一个动态数据收集管道。支持以 TCP/UDP/HTTP 多种方式收集数据(也可以接受 Beats 传输来的数据),并对数据做进一步丰富或提取字段处理。 +> - [ElasticSearch](https://www.elastic.co/products/elasticsearch) 是一个基于 JSON 的分布式的搜索和分析引擎。作为 ELK 的核心,它集中存储数据。 +> +> - [Kibana](https://www.elastic.co/products/kibana) 是 ELK 的用户界面。它将收集的数据进行可视化展示(各种报表、图形化数据),并提供配置、管理 ELK 的界面。 + +## 2. ElasticSearch + +> [Elasticsearch](https://github.com/elastic/elasticsearch) 是一个分布式、RESTful 风格的搜索和数据分析引擎,能够解决不断涌现出的各种用例。 作为 Elastic Stack 的核心,它集中存储您的数据,帮助您发现意料之中以及意料之外的情况。 + +### 2.1. ElasticSearch 简介 + +[Elasticsearch](https://github.com/elastic/elasticsearch) 基于搜索库 [Lucene](https://github.com/apache/lucene-solr) 开发。ElasticSearch 隐藏了 Lucene 的复杂性,提供了简单易用的 REST API / Java API 接口(另外还有其他语言的 API 接口)。 + +ElasticSearch 可以视为一个文档存储,它**将复杂数据结构序列化为 JSON 存储**。 + +**ElasticSearch 是近乎于实时的全文搜素**,这是指: + +- 从写入数据到数据可以被搜索,存在较小的延迟(大概是 1s) +- 基于 ES 执行搜索和分析可以达到秒级 + +#### 2.1.1. 核心概念 + +- **`索引(Index)`** 可以认为是文档(document)的优化集合。 +- 每个 **`文档(document)`** 都是字段(field)的集合。 +- **`字段(field)`** 是包含数据的键值对。 +- 默认情况下,Elasticsearch 对每个字段中的所有数据建立索引,并且每个索引字段都具有专用的优化数据结构。 +- 每个索引里可以有一个或者多个类型(type)。`类型(type)` 是 index 的一个逻辑分类, +- 当单台机器不足以存储大量数据时,Elasticsearch 可以将一个索引中的数据切分为多个 **`分片(shard)`** 。 **`分片(shard)`** 分布在多台服务器上存储。有了 shard 就可以横向扩展,存储更多数据,让搜索和分析等操作分布到多台服务器上去执行,提升吞吐量和性能。每个 shard 都是一个 lucene index。 +- 任何一个服务器随时可能故障或宕机,此时 shard 可能就会丢失,因此可以为每个 shard 创建多个 **`副本(replica)`**。replica 可以在 shard 故障时提供备用服务,保证数据不丢失,多个 replica 还可以提升搜索操作的吞吐量和性能。primary shard(建立索引时一次设置,不能修改,默认 5 个),replica shard(随时修改数量,默认 1 个),默认每个索引 10 个 shard,5 个 primary shard,5 个 replica shard,最小的高可用配置,是 2 台服务器。 + +### 2.2. ElasticSearch 原理 + +#### 2.2.1. ES 写数据过程 + +- 客户端选择一个 node 发送请求过去,这个 node 就是 `coordinating node`(协调节点)。 +- `coordinating node` 对 document 进行**路由**,将请求转发给对应的 node(有 primary shard)。 +- 实际的 node 上的 `primary shard` 处理请求,然后将数据同步到 `replica node`。 +- `coordinating node` 如果发现 `primary node` 和所有 `replica node` 都搞定之后,就返回响应结果给客户端。 + +![es-write](https://github.com/doocs/advanced-java/raw/master/images/es-write.png) + +#### 2.2.2. es 读数据过程 + +可以通过 `doc id` 来查询,会根据 `doc id` 进行 hash,判断出来当时把 `doc id` 分配到了哪个 shard 上面去,从那个 shard 去查询。 + +- 客户端发送请求到**任意**一个 node,成为 `coordinate node`。 +- `coordinate node` 对 `doc id` 进行哈希路由,将请求转发到对应的 node,此时会使用 `round-robin` **随机轮询算法**,在 `primary shard` 以及其所有 replica 中随机选择一个,让读请求负载均衡。 +- 接收请求的 node 返回 document 给 `coordinate node`。 +- `coordinate node` 返回 document 给客户端。 + +#### 2.2.3. 写数据底层原理 + +![es-write-detail](https://github.com/doocs/advanced-java/raw/master/images/es-write-detail.png) + +先写入内存 buffer,在 buffer 里的时候数据是搜索不到的;同时将数据写入 translog 日志文件。 + +如果 buffer 快满了,或者到一定时间,就会将内存 buffer 数据 `refresh` 到一个新的 `segment file` 中,但是此时数据不是直接进入 `segment file` 磁盘文件,而是先进入 `os cache` 。这个过程就是 `refresh`。 + +每隔 1 秒钟,es 将 buffer 中的数据写入一个**新的** `segment file`,每秒钟会产生一个**新的磁盘文件** `segment file`,这个 `segment file` 中就存储最近 1 秒内 buffer 中写入的数据。 + +但是如果 buffer 里面此时没有数据,那当然不会执行 refresh 操作,如果 buffer 里面有数据,默认 1 秒钟执行一次 refresh 操作,刷入一个新的 segment file 中。 + +操作系统里面,磁盘文件其实都有一个东西,叫做 `os cache`,即操作系统缓存,就是说数据写入磁盘文件之前,会先进入 `os cache`,先进入操作系统级别的一个内存缓存中去。只要 `buffer` 中的数据被 refresh 操作刷入 `os cache`中,这个数据就可以被搜索到了。 + +为什么叫 es 是**准实时**的? `NRT`,全称 `near real-time`。默认是每隔 1 秒 refresh 一次的,所以 es 是准实时的,因为写入的数据 1 秒之后才能被看到。可以通过 es 的 `restful api` 或者 `java api`,**手动**执行一次 refresh 操作,就是手动将 buffer 中的数据刷入 `os cache`中,让数据立马就可以被搜索到。只要数据被输入 `os cache` 中,buffer 就会被清空了,因为不需要保留 buffer 了,数据在 translog 里面已经持久化到磁盘去一份了。 + +重复上面的步骤,新的数据不断进入 buffer 和 translog,不断将 `buffer` 数据写入一个又一个新的 `segment file` 中去,每次 `refresh` 完 buffer 清空,translog 保留。随着这个过程推进,translog 会变得越来越大。当 translog 达到一定长度的时候,就会触发 `commit` 操作。 + +commit 操作发生第一步,就是将 buffer 中现有数据 `refresh` 到 `os cache` 中去,清空 buffer。然后,将一个 `commit point` 写入磁盘文件,里面标识着这个 `commit point` 对应的所有 `segment file`,同时强行将 `os cache` 中目前所有的数据都 `fsync` 到磁盘文件中去。最后**清空** 现有 translog 日志文件,重启一个 translog,此时 commit 操作完成。 + +这个 commit 操作叫做 `flush`。默认 30 分钟自动执行一次 `flush`,但如果 translog 过大,也会触发 `flush`。flush 操作就对应着 commit 的全过程,我们可以通过 es api,手动执行 flush 操作,手动将 os cache 中的数据 fsync 强刷到磁盘上去。 + +translog 日志文件的作用是什么?你执行 commit 操作之前,数据要么是停留在 buffer 中,要么是停留在 os cache 中,无论是 buffer 还是 os cache 都是内存,一旦这台机器死了,内存中的数据就全丢了。所以需要将数据对应的操作写入一个专门的日志文件 `translog` 中,一旦此时机器宕机,再次重启的时候,es 会自动读取 translog 日志文件中的数据,恢复到内存 buffer 和 os cache 中去。 + +translog 其实也是先写入 os cache 的,默认每隔 5 秒刷一次到磁盘中去,所以默认情况下,可能有 5 秒的数据会仅仅停留在 buffer 或者 translog 文件的 os cache 中,如果此时机器挂了,会**丢失** 5 秒钟的数据。但是这样性能比较好,最多丢 5 秒的数据。也可以将 translog 设置成每次写操作必须是直接 `fsync` 到磁盘,但是性能会差很多。 + +实际上你在这里,如果面试官没有问你 es 丢数据的问题,你可以在这里给面试官炫一把,你说,其实 es 第一是准实时的,数据写入 1 秒后可以搜索到;可能会丢失数据的。有 5 秒的数据,停留在 buffer、translog os cache、segment file os cache 中,而不在磁盘上,此时如果宕机,会导致 5 秒的**数据丢失**。 + +**总结一下**,数据先写入内存 buffer,然后每隔 1s,将数据 refresh 到 os cache,到了 os cache 数据就能被搜索到(所以我们才说 es 从写入到能被搜索到,中间有 1s 的延迟)。每隔 5s,将数据写入 translog 文件(这样如果机器宕机,内存数据全没,最多会有 5s 的数据丢失),translog 大到一定程度,或者默认每隔 30mins,会触发 commit 操作,将缓冲区的数据都 flush 到 segment file 磁盘文件中。 + +> 数据写入 segment file 之后,同时就建立好了倒排索引。 + +#### 2.2.4. 删除/更新数据底层原理 + +如果是删除操作,commit 的时候会生成一个 `.del` 文件,里面将某个 doc 标识为 `deleted` 状态,那么搜索的时候根据 `.del` 文件就知道这个 doc 是否被删除了。 + +如果是更新操作,就是将原来的 doc 标识为 `deleted` 状态,然后新写入一条数据。 + +buffer 每 refresh 一次,就会产生一个 `segment file`,所以默认情况下是 1 秒钟一个 `segment file`,这样下来 `segment file` 会越来越多,此时会定期执行 merge。每次 merge 的时候,会将多个 `segment file` 合并成一个,同时这里会将标识为 `deleted` 的 doc 给**物理删除掉**,然后将新的 `segment file` 写入磁盘,这里会写一个 `commit point`,标识所有新的 `segment file`,然后打开 `segment file` 供搜索使用,同时删除旧的 `segment file`。 + +#### 2.2.5. 底层 lucene + +简单来说,lucene 就是一个 jar 包,里面包含了封装好的各种建立倒排索引的算法代码。我们用 Java 开发的时候,引入 lucene jar,然后基于 lucene 的 api 去开发就可以了。 + +通过 lucene,我们可以将已有的数据建立索引,lucene 会在本地磁盘上面,给我们组织索引的数据结构。 + +#### 2.2.6. 倒排索引 + +在搜索引擎中,每个文档都有一个对应的文档 ID,文档内容被表示为一系列关键词的集合。例如,文档 1 经过分词,提取了 20 个关键词,每个关键词都会记录它在文档中出现的次数和出现位置。 + +那么,倒排索引就是**关键词到文档** ID 的映射,每个关键词都对应着一系列的文件,这些文件中都出现了关键词。 + +举个栗子。 + +有以下文档: + +| DocId | Doc | +| ----- | ---------------------------------------------- | +| 1 | 谷歌地图之父跳槽 Facebook | +| 2 | 谷歌地图之父加盟 Facebook | +| 3 | 谷歌地图创始人拉斯离开谷歌加盟 Facebook | +| 4 | 谷歌地图之父跳槽 Facebook 与 Wave 项目取消有关 | +| 5 | 谷歌地图之父拉斯加盟社交网站 Facebook | + +对文档进行分词之后,得到以下**倒排索引**。 + +| WordId | Word | DocIds | +| ------ | -------- | --------- | +| 1 | 谷歌 | 1,2,3,4,5 | +| 2 | 地图 | 1,2,3,4,5 | +| 3 | 之父 | 1,2,4,5 | +| 4 | 跳槽 | 1,4 | +| 5 | Facebook | 1,2,3,4,5 | +| 6 | 加盟 | 2,3,5 | +| 7 | 创始人 | 3 | +| 8 | 拉斯 | 3,5 | +| 9 | 离开 | 3 | +| 10 | 与 | 4 | +| .. | .. | .. | + +另外,实用的倒排索引还可以记录更多的信息,比如文档频率信息,表示在文档集合中有多少个文档包含某个单词。 + +那么,有了倒排索引,搜索引擎可以很方便地响应用户的查询。比如用户输入查询 `Facebook`,搜索系统查找倒排索引,从中读出包含这个单词的文档,这些文档就是提供给用户的搜索结果。 + +要注意倒排索引的两个重要细节: + +- 倒排索引中的所有词项对应一个或多个文档; +- 倒排索引中的词项**根据字典顺序升序排列** + +> 上面只是一个简单的栗子,并没有严格按照字典顺序升序排列。 + +## 3. Logstash + +> [Logstash](https://github.com/elastic/logstash) 是开源的服务器端数据处理管道,能够同时从多个来源采集数据,转换数据,然后将数据发送到您最喜欢的“存储库”中。 + +### 3.1. Logstash 简介 + +Logstash 可以传输和处理你的日志、事务或其他数据。 + +Logstash 是 Elasticsearch 的最佳数据管道。 + +Logstash 是插件式管理模式,在输入、过滤、输出以及编码过程中都可以使用插件进行定制。Logstash 社区有超过 200 种可用插件。 + +### 3.2. Logstash 原理 + +Logstash 有两个必要元素:`input` 和 `output` ,一个可选元素:`filter`。 + +这三个元素,分别代表 Logstash 事件处理的三个阶段:输入 > 过滤器 > 输出。 + +
    ![img](https://www.elastic.co/guide/en/logstash/current/static/images/basic_logstash_pipeline.png)
    + +- **input** - 负责从数据源采集数据。 +- **`filter`** - 将数据修改为你指定的格式或内容。 +- **`output`** - 将数据传输到目的地。 + +在实际应用场景中,通常输入、输出、过滤器不止一个。Logstash 的这三个元素都使用插件式管理方式,用户可以根据应用需要,灵活的选用各阶段需要的插件,并组合使用。 + +## 4. Beats + +> **[Beats](https://github.com/elastic/beats) 是安装在服务器上的数据中转代理**。 +> +> Beats 可以将数据直接传输到 Elasticsearch 或传输到 Logstash 。 + +
    ![img](https://www.elastic.co/guide/en/beats/libbeat/current/images/beats-platform.png)
    + +Beats 有多种类型,可以根据实际应用需要选择合适的类型。 + +常用的类型有: + +- **Packetbeat:**网络数据包分析器,提供有关您的应用程序服务器之间交换的事务的信息。 +- **Filebeat:**从您的服务器发送日志文件。 +- **Metricbeat:**是一个服务器监视代理程序,它定期从服务器上运行的操作系统和服务收集指标。 +- **Winlogbeat:**提供 Windows 事件日志。 + +### 4.1. Filebeat 简介 + +> _由于本人仅接触过 Filebeat,所以本文只介绍 Beats 组件中的 Filebeat。_ + +相比 Logstash,FileBeat 更加轻量化。 + +在任何环境下,应用程序都有停机的可能性。 Filebeat 读取并转发日志行,如果中断,则会记住所有事件恢复联机状态时所在位置。 + +Filebeat 带有内部模块(auditd,Apache,Nginx,System 和 MySQL),可通过一个指定命令来简化通用日志格式的收集,解析和可视化。 + +FileBeat 不会让你的管道超负荷。FileBeat 如果是向 Logstash 传输数据,当 Logstash 忙于处理数据,会通知 FileBeat 放慢读取速度。一旦拥塞得到解决,FileBeat 将恢复到原来的速度并继续传播。 + +
    ![img](https://www.elastic.co/guide/en/beats/filebeat/current/images/filebeat.png)
    + +### 4.2. Filebeat 原理 + +Filebeat 有两个主要组件: + +- `harvester`:负责读取一个文件的内容。它会逐行读取文件内容,并将内容发送到输出目的地。 +- `prospector`:负责管理 harvester 并找到所有需要读取的文件源。比如类型是日志,prospector 就会遍历制定路径下的所有匹配要求的文件。 + +```yaml +filebeat.prospectors: + - type: log + paths: + - /var/log/*.log + - /var/path2/*.log +``` + +Filebeat 保持每个文件的状态,并经常刷新注册表文件中的磁盘状态。状态用于记住 harvester 正在读取的最后偏移量,并确保发送所有日志行。 + +Filebeat 将每个事件的传递状态存储在注册表文件中。所以它能保证事件至少传递一次到配置的输出,没有数据丢失。 + +## 5. 运维 + +- [ElasticSearch 运维](elastic-elasticsearch-ops.md) +- [Logstash 运维](elastic-logstash-ops.md) +- [Kibana 运维](elastic-kibana-ops.md) +- [Beats 运维](elastic-beats-ops.md) + +## 6. 参考资料 + +- **官方资源** + - [Elasticsearch 官网](https://www.elastic.co/cn/products/elasticsearch) + - [Elasticsearch Github](https://github.com/elastic/elasticsearch) + - [Elasticsearch 官方文档](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html) + - [Logstash 官网](https://www.elastic.co/cn/products/logstash) + - [Logstash Github](https://github.com/elastic/logstash) + - [Logstash 官方文档](https://www.elastic.co/guide/en/logstash/current/index.html) + - [Kibana 官网](https://www.elastic.co/cn/products/kibana) + - [Kibana Github](https://github.com/elastic/kibana) + - [Kibana 官方文档](https://www.elastic.co/guide/en/kibana/current/index.html) + - [Beats 官网](https://www.elastic.co/cn/products/beats) + - [Beats Github](https://github.com/elastic/beats) + - [Beats 官方文档](https://www.elastic.co/guide/en/beats/libbeat/current/index.html) +- **文章** + - [什么是 ELK Stack?](https://www.elastic.co/cn/what-is/elk-stack) + - https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/es-introduction.md + - [es-write-query-search](https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/es-write-query-search.md) diff --git a/docs/ecology/security/README.md b/docs/ecology/security/README.md new file mode 100644 index 00000000..f972ad2f --- /dev/null +++ b/docs/ecology/security/README.md @@ -0,0 +1,4 @@ +# Java 和安全 + +- [shiro](shiro.md) +- [spring-security](spring-security.md) diff --git a/docs/technology/security/Shiro.md b/docs/ecology/security/shiro.md similarity index 100% rename from docs/technology/security/Shiro.md rename to docs/ecology/security/shiro.md diff --git a/docs/technology/security/spring-security.md b/docs/ecology/security/spring-security.md similarity index 100% rename from docs/technology/security/spring-security.md rename to docs/ecology/security/spring-security.md diff --git a/docs/ecology/server/README.md b/docs/ecology/server/README.md new file mode 100644 index 00000000..4ab52b5e --- /dev/null +++ b/docs/ecology/server/README.md @@ -0,0 +1,5 @@ +# Java 和服务器 + +- [Tomcat](tomcat.md) +- [Jetty](jetty.md) +- [Nginx](https://github.com/dunwu/nginx-tutorial) 📚 diff --git a/docs/technology/server/Jetty.md b/docs/ecology/server/jetty.md similarity index 100% rename from docs/technology/server/Jetty.md rename to docs/ecology/server/jetty.md diff --git a/docs/technology/server/Tomcat.md b/docs/ecology/server/tomcat.md similarity index 100% rename from docs/technology/server/Tomcat.md rename to docs/ecology/server/tomcat.md diff --git a/docs/ecology/storage/README.md b/docs/ecology/storage/README.md new file mode 100644 index 00000000..1d6ef2d2 --- /dev/null +++ b/docs/ecology/storage/README.md @@ -0,0 +1,12 @@ +# Java 和存储 + +## 数据库存储 + +### 分库分表中间件 + +- [shardingsphere](shardingsphere.md) + +## 分布式文件存储 + +- [ ] HDFS +- [ ] FastDFS diff --git a/docs/technology/data/ShardingSphere.md b/docs/ecology/storage/shardingsphere.md similarity index 100% rename from docs/technology/data/ShardingSphere.md rename to docs/ecology/storage/shardingsphere.md diff --git a/docs/ecology/test/README.md b/docs/ecology/test/README.md new file mode 100644 index 00000000..7b075d01 --- /dev/null +++ b/docs/ecology/test/README.md @@ -0,0 +1,5 @@ +# Java 和测试 + +- [Junit](junit.md) +- [Mockito](mockito.md) +- [JMH](jmh.md) diff --git a/docs/ecology/test/jmh.md b/docs/ecology/test/jmh.md new file mode 100644 index 00000000..36b23beb --- /dev/null +++ b/docs/ecology/test/jmh.md @@ -0,0 +1,325 @@ +# JMH 应用指南 + +## 简介 + +### 什么是 JMH + +[JMH](http://openjdk.java.net/projects/code-tools/jmh/) 即 Java Microbenchmark Harness,这是专门用于进行代码的微基准测试的一套工具 API。 + +JMH 由 OpenJDK/Oracle 里面那群开发了 Java 编译器的大牛们所开发 。何谓 Micro Benchmark 呢? 简单地说就是在 method 层面上的 benchmark,精度可以精确到 **微秒级**。 + +### 为什么需要 JMH + +#### 死码消除 + +所谓死码,是指注释的代码,不可达的代码块,可达但不被使用的代码等等 。 + +#### 常量折叠与常量传播 + +[常量折叠](https://zh.wikipedia.org/wiki/常數折疊#常數傳播) (Constant folding) 是一个在编译时期简化常数的一个过程,常数在表示式中仅仅代表一个简单的数值,就像是整数 `2`,若是一个变数从未被修改也可作为常数,或者直接将一个变数被明确地被标注为常数,例如下面的描述: + +### JMH 的注意点 + +- 测试前需要预热。 +- 防止无用代码进入测试方法中。 +- 并发测试。 +- 测试结果呈现。 + +### 应用场景 + +1. 当你已经找出了热点函数,而需要对热点函数进行进一步的优化时,就可以使用 JMH 对优化的效果进行定量的分析。 +2. 想定量地知道某个函数需要执行多长时间,以及执行时间和输入 n 的相关性 +3. 一个函数有两种不同实现(例如 JSON 序列化/反序列化有 Jackson 和 Gson 实现),不知道哪种实现性能更好 + +### JMH 概念 + +- `Iteration` - iteration 是 JMH 进行测试的最小单位,包含一组 invocations。 +- `Invocation` - 一次 benchmark 方法调用。 +- `Operation` - benchmark 方法中,被测量操作的执行。如果被测试的操作在 benchmark 方法中循环执行,可以使用`@OperationsPerInvocation`表明循环次数,使测试结果为单次 operation 的性能。 +- `Warmup` - 在实际进行 benchmark 前先进行预热。因为某个函数被调用多次之后,JIT 会对其进行编译,通过预热可以使测量结果更加接近真实情况。 + +## 快速入门 + +### 添加 maven 依赖 + +```xml + + org.openjdk.jmh + jmh-core + ${jmh.version} + + + org.openjdk.jmh + jmh-generator-annprocess + ${jmh.version} + provided + +``` + +### 测试代码 + +```java +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.runner.*; + +import java.util.concurrent.TimeUnit; + +@BenchmarkMode(Mode.Throughput) +@Warmup(iterations = 3) +@Measurement(iterations = 10, time = 5, timeUnit = TimeUnit.SECONDS) +@Threads(8) +@Fork(2) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +public class StringBuilderBenchmark { + + @Benchmark + public void testStringAdd() { + String a = ""; + for (int i = 0; i < 10; i++) { + a += i; + } + // System.out.println(a); + } + + @Benchmark + public void testStringBuilderAdd() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 10; i++) { + sb.append(i); + } + // System.out.println(sb.toString()); + } + + public static void main(String[] args) throws RunnerException { + Options options = new OptionsBuilder() + .include(StringBuilderBenchmark.class.getSimpleName()) + .output("d:/Benchmark.log") + .build(); + new Runner(options).run(); + } + +} +``` + +### 执行 JMH + +#### 命令行 + +(1)初始化 **benchmarking** 工程 + +``` +$ mvn archetype:generate \ + -DinteractiveMode=false \ + -DarchetypeGroupId=org.openjdk.jmh \ + -DarchetypeArtifactId=jmh-java-benchmark-archetype \ + -DgroupId=org.sample \ + -DartifactId=test \ + -Dversion=1.0 +``` + +(2)构建 benchmark + +``` +$ cd test/ +$ mvn clean install +``` + +(3)运行 benchmark + +``` +$ java -jar target/benchmarks.jar +``` + +#### 执行 main 方法 + +执行 main 方法,耐心等待测试结果,最终会生成一个测试报告,内容大致如下; + +``` +# JMH version: 1.22 +# VM version: JDK 1.8.0_181, Java HotSpot(TM) 64-Bit Server VM, 25.181-b13 +# VM invoker: C:\Program Files\Java\jdk1.8.0_181\jre\bin\java.exe +# VM options: -javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2019.2.3\lib\idea_rt.jar=58635:D:\Program Files\JetBrains\IntelliJ IDEA 2019.2.3\bin -Dfile.encoding=UTF-8 +# Warmup: 3 iterations, 10 s each +# Measurement: 10 iterations, 5 s each +# Timeout: 10 min per iteration +# Threads: 8 threads, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: io.github.dunwu.javalib.jmh.StringBuilderBenchmark.testStringAdd + +# Run progress: 0.00% complete, ETA 00:05:20 +# Fork: 1 of 2 +# Warmup Iteration 1: 21803.050 ops/ms +# Warmup Iteration 2: 22501.860 ops/ms +# Warmup Iteration 3: 20953.944 ops/ms +Iteration 1: 21627.645 ops/ms +Iteration 2: 21215.269 ops/ms +Iteration 3: 20863.282 ops/ms +Iteration 4: 21617.715 ops/ms +Iteration 5: 21695.645 ops/ms +Iteration 6: 21886.784 ops/ms +Iteration 7: 21986.899 ops/ms +Iteration 8: 22389.540 ops/ms +Iteration 9: 22507.313 ops/ms +Iteration 10: 22124.133 ops/ms + +# Run progress: 25.00% complete, ETA 00:04:02 +# Fork: 2 of 2 +# Warmup Iteration 1: 22262.108 ops/ms +# Warmup Iteration 2: 21567.804 ops/ms +# Warmup Iteration 3: 21787.002 ops/ms +Iteration 1: 21598.970 ops/ms +Iteration 2: 22486.133 ops/ms +Iteration 3: 22157.834 ops/ms +Iteration 4: 22321.827 ops/ms +Iteration 5: 22477.063 ops/ms +Iteration 6: 22154.760 ops/ms +Iteration 7: 21561.095 ops/ms +Iteration 8: 22194.863 ops/ms +Iteration 9: 22493.844 ops/ms +Iteration 10: 22568.078 ops/ms + + +Result "io.github.dunwu.javalib.jmh.StringBuilderBenchmark.testStringAdd": + 21996.435 ±(99.9%) 412.955 ops/ms [Average] + (min, avg, max) = (20863.282, 21996.435, 22568.078), stdev = 475.560 + CI (99.9%): [21583.480, 22409.390] (assumes normal distribution) + + +# JMH version: 1.22 +# VM version: JDK 1.8.0_181, Java HotSpot(TM) 64-Bit Server VM, 25.181-b13 +# VM invoker: C:\Program Files\Java\jdk1.8.0_181\jre\bin\java.exe +# VM options: -javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2019.2.3\lib\idea_rt.jar=58635:D:\Program Files\JetBrains\IntelliJ IDEA 2019.2.3\bin -Dfile.encoding=UTF-8 +# Warmup: 3 iterations, 10 s each +# Measurement: 10 iterations, 5 s each +# Timeout: 10 min per iteration +# Threads: 8 threads, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: io.github.dunwu.javalib.jmh.StringBuilderBenchmark.testStringBuilderAdd + +# Run progress: 50.00% complete, ETA 00:02:41 +# Fork: 1 of 2 +# Warmup Iteration 1: 241500.886 ops/ms +# Warmup Iteration 2: 134206.032 ops/ms +# Warmup Iteration 3: 86907.846 ops/ms +Iteration 1: 86143.339 ops/ms +Iteration 2: 74725.356 ops/ms +Iteration 3: 72316.121 ops/ms +Iteration 4: 77319.716 ops/ms +Iteration 5: 83469.256 ops/ms +Iteration 6: 87712.360 ops/ms +Iteration 7: 79421.899 ops/ms +Iteration 8: 80867.839 ops/ms +Iteration 9: 82619.163 ops/ms +Iteration 10: 87026.928 ops/ms + +# Run progress: 75.00% complete, ETA 00:01:20 +# Fork: 2 of 2 +# Warmup Iteration 1: 228342.337 ops/ms +# Warmup Iteration 2: 124737.248 ops/ms +# Warmup Iteration 3: 82598.851 ops/ms +Iteration 1: 86877.318 ops/ms +Iteration 2: 89388.624 ops/ms +Iteration 3: 88523.558 ops/ms +Iteration 4: 87547.332 ops/ms +Iteration 5: 88376.087 ops/ms +Iteration 6: 88848.837 ops/ms +Iteration 7: 85998.124 ops/ms +Iteration 8: 86796.998 ops/ms +Iteration 9: 87994.726 ops/ms +Iteration 10: 87784.453 ops/ms + + +Result "io.github.dunwu.javalib.jmh.StringBuilderBenchmark.testStringBuilderAdd": + 84487.902 ±(99.9%) 4355.525 ops/ms [Average] + (min, avg, max) = (72316.121, 84487.902, 89388.624), stdev = 5015.829 + CI (99.9%): [80132.377, 88843.427] (assumes normal distribution) + + +# Run complete. Total time: 00:05:23 + +REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on +why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial +experiments, perform baseline and negative tests that provide experimental control, make sure +the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts. +Do not assume the numbers tell you what you want them to tell. + +Benchmark Mode Cnt Score Error Units +StringBuilderBenchmark.testStringAdd thrpt 20 21996.435 ± 412.955 ops/ms +StringBuilderBenchmark.testStringBuilderAdd thrpt 20 84487.902 ± 4355.525 ops/ms +``` + +## API + +下面来了解一下 jmh 常用 API + +### @BenchmarkMode + +基准测试类型。这里选择的是 `Throughput` 也就是吞吐量。根据源码点进去,每种类型后面都有对应的解释,比较好理解,吞吐量会得到单位时间内可以进行的操作数。 + +- `Throughput` - 整体吞吐量,例如“1 秒内可以执行多少次调用”。 +- `AverageTime` - 调用的平均时间,例如“每次调用平均耗时 xxx 毫秒”。 +- `SampleTime` - 随机取样,最后输出取样结果的分布,例如“99%的调用在 xxx 毫秒以内,99.99%的调用在 xxx 毫秒以内” +- `SingleShotTime` - 以上模式都是默认一次 iteration 是 1s,唯有 SingleShotTime 是只运行一次。往往同时把 warmup 次数设为 0,用于测试冷启动时的性能。 +- `All` - 所有模式 + +### @Warmup + +上面我们提到了,进行基准测试前需要进行预热。一般我们前几次进行程序测试的时候都会比较慢, 所以要让程序进行几轮预热,保证测试的准确性。其中的参数 iterations 也就非常好理解了,就是预热轮数。 + +为什么需要预热?因为 JVM 的 JIT 机制的存在,如果某个函数被调用多次之后,JVM 会尝试将其编译成为机器码从而提高执行速度。所以为了让 benchmark 的结果更加接近真实情况就需要进行预热。 + +### @Measurement + +度量,其实就是一些基本的测试参数。 + +- `iterations` - 进行测试的轮次 +- `time` - 每轮进行的时长 +- `timeUnit` - 时长单位 + +都是一些基本的参数,可以根据具体情况调整。一般比较重的东西可以进行大量的测试,放到服务器上运行。 + +### @Threads + +每个进程中的测试线程,这个非常好理解,根据具体情况选择,一般为 cpu 乘以 2。 + +### @Fork + +进行 fork 的次数。如果 fork 数是 2 的话,则 JMH 会 fork 出两个进程来进行测试。 + +### @OutputTimeUnit + +这个比较简单了,基准测试结果的时间类型。一般选择秒、毫秒、微秒。 + +### @Benchmark + +方法级注解,表示该方法是需要进行 benchmark 的对象,用法和 JUnit 的 @Test 类似。 + +### @Param + +属性级注解,@Param 可以用来指定某项参数的多种情况。特别适合用来测试一个函数在不同的参数输入的情况下的性能。 + +### @Setup + +方法级注解,这个注解的作用就是我们需要在测试之前进行一些准备工作,比如对一些数据的初始化之类的。 + +### @TearDown + +方法级注解,这个注解的作用就是我们需要在测试之后进行一些结束工作,比如关闭线程池,数据库连接等的,主要用于资源的回收等。 + +### @State + +当使用 @Setup 参数的时候,必须在类上加这个参数,不然会提示无法运行。 + +State 用于声明某个类是一个“状态”,然后接受一个 Scope 参数用来表示该状态的共享范围。 因为很多 benchmark 会需要一些表示状态的类,JMH 允许你把这些类以依赖注入的方式注入到 benchmark 函数里。Scope 主要分为三种。 + +- `Thread` - 该状态为每个线程独享。 +- `Group` - 该状态为同一个组里面所有线程共享。 +- `Benchmark` - 该状态在所有线程间共享。 + +关于 State 的用法,官方的 code sample 里有比较好的[例子](http://hg.openjdk.java.net/code-tools/jmh/file/cb9aa824b55a/jmh-samples/src/main/java/org/openjdk/jmh/samples/JMHSample_03_States.java)。 + +## 参考资料 + +- [jmh 官方示例](http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/) +- [Java 微基准测试框架 JMH](https://www.xncoding.com/2018/01/07/java/jmh.html) +- [JAVA 拾遗 — JMH 与 8 个测试陷阱](https://www.cnkirito.moe/java-jmh/) diff --git a/docs/ecology/test/junit.md b/docs/ecology/test/junit.md new file mode 100644 index 00000000..b56ce2e2 --- /dev/null +++ b/docs/ecology/test/junit.md @@ -0,0 +1,582 @@ +# JUnit5 应用指南 + +> version: junit5 + + + +- [1. 安装](#1-安装) +- [2. JUnit 注解](#2-junit-注解) +- [3. 编写单元测试](#3-编写单元测试) + - [3.1. 基本的单元测试类和方法](#31-基本的单元测试类和方法) + - [3.2. 定制测试类和方法的显示名称](#32-定制测试类和方法的显示名称) + - [3.3. 断言(Assertions)](#33-断言assertions) + - [3.4. 假想(Assumptions)](#34-假想assumptions) + - [3.5. 禁用](#35-禁用) + - [3.6. 测试条件](#36-测试条件) + - [3.7. 嵌套测试](#37-嵌套测试) + - [3.8. 重复测试](#38-重复测试) + - [3.9. 参数化测试](#39-参数化测试) +- [4. 引用和引申](#4-引用和引申) + + + +## 1. 安装 + +在 pom 中添加依赖 + +```xml + + 5.3.2 + + + + + org.junit.jupiter + junit-jupiter-api + ${junit.jupiter.version} + test + + + org.junit.jupiter + junit-jupiter-params + ${junit.jupiter.version} + test + + + org.junit.jupiter + junit-jupiter-engine + ${junit.jupiter.version} + test + + +``` + +组件间依赖关系: + +![img](http://dunwu.test.upcdn.net/cs/java/javalib/test/junit/junit5-components.png!zp) + +## 2. JUnit 注解 + +| Annotation | Description | +| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `@Test` | Denotes that a method is a test method. Unlike JUnit 4’s `@Test` annotation, this annotation does not declare any attributes, since test extensions in JUnit Jupiter operate based on their own dedicated annotations. Such methods are _inherited_ unless they are _overridden_. | +| `@ParameterizedTest` | Denotes that a method is a [parameterized test](https://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests). Such methods are _inherited_ unless they are _overridden_. | +| `@RepeatedTest` | Denotes that a method is a test template for a [repeated test](https://junit.org/junit5/docs/current/user-guide/#writing-tests-repeated-tests). Such methods are _inherited_ unless they are _overridden_. | +| `@TestFactory` | Denotes that a method is a test factory for [dynamic tests](https://junit.org/junit5/docs/current/user-guide/#writing-tests-dynamic-tests). Such methods are _inherited_ unless they are _overridden_. | +| `@TestInstance` | Used to configure the [test instance lifecycle](https://junit.org/junit5/docs/current/user-guide/#writing-tests-test-instance-lifecycle) for the annotated test class. Such annotations are _inherited_. | +| `@TestTemplate` | Denotes that a method is a [template for test cases](https://junit.org/junit5/docs/current/user-guide/#writing-tests-test-templates) designed to be invoked multiple times depending on the number of invocation contexts returned by the registered [providers](https://junit.org/junit5/docs/current/user-guide/#extensions-test-templates). Such methods are _inherited_ unless they are _overridden_. | +| `@DisplayName` | Declares a custom display name for the test class or test method. Such annotations are not _inherited_. | +| `@BeforeEach` | Denotes that the annotated method should be executed _before_ **each** `@Test`, `@RepeatedTest`, `@ParameterizedTest`, or `@TestFactory` method in the current class; analogous to JUnit 4’s `@Before`. Such methods are _inherited_ unless they are _overridden_. | +| `@AfterEach` | Denotes that the annotated method should be executed _after_ **each** `@Test`, `@RepeatedTest`, `@ParameterizedTest`, or `@TestFactory` method in the current class; analogous to JUnit 4’s `@After`. Such methods are _inherited_ unless they are _overridden_. | +| `@BeforeAll` | Denotes that the annotated method should be executed _before_ **all** `@Test`, `@RepeatedTest`, `@ParameterizedTest`, and `@TestFactory` methods in the current class; analogous to JUnit 4’s `@BeforeClass`. Such methods are _inherited_ (unless they are _hidden_ or _overridden_) and must be `static` (unless the "per-class" [test instance lifecycle](https://junit.org/junit5/docs/current/user-guide/#writing-tests-test-instance-lifecycle) is used). | +| `@AfterAll` | Denotes that the annotated method should be executed _after_ **all** `@Test`, `@RepeatedTest`, `@ParameterizedTest`, and `@TestFactory` methods in the current class; analogous to JUnit 4’s `@AfterClass`. Such methods are _inherited_ (unless they are _hidden_ or _overridden_) and must be `static` (unless the "per-class" [test instance lifecycle](https://junit.org/junit5/docs/current/user-guide/#writing-tests-test-instance-lifecycle) is used). | +| `@Nested` | Denotes that the annotated class is a nested, non-static test class. `@BeforeAll` and `@AfterAll`methods cannot be used directly in a `@Nested` test class unless the "per-class" [test instance lifecycle](https://junit.org/junit5/docs/current/user-guide/#writing-tests-test-instance-lifecycle) is used. Such annotations are not _inherited_. | +| `@Tag` | Used to declare _tags_ for filtering tests, either at the class or method level; analogous to test groups in TestNG or Categories in JUnit 4. Such annotations are _inherited_ at the class level but not at the method level. | +| `@Disabled` | Used to _disable_ a test class or test method; analogous to JUnit 4’s `@Ignore`. Such annotations are not _inherited_. | +| `@ExtendWith` | Used to register custom [extensions](https://junit.org/junit5/docs/current/user-guide/#extensions). Such annotations are _inherited_. | + +## 3. 编写单元测试 + +### 3.1. 基本的单元测试类和方法 + +```java +import org.junit.jupiter.api.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class Junit5StandardTests { + + private static final Logger LOGGER = LoggerFactory.getLogger(Junit5StandardTests.class); + + @BeforeAll + static void beforeAll() { + LOGGER.info("call beforeAll()"); + } + + @BeforeEach + void beforeEach() { + LOGGER.info("call beforeEach()"); + } + + @Test + void succeedingTest() { + LOGGER.info("call succeedingTest()"); + } + + @Test + void failingTest() { + LOGGER.info("call failingTest()"); + // fail("a failing test"); + } + + @Test + @Disabled("for demonstration purposes") + void skippedTest() { + LOGGER.info("call skippedTest()"); + // not executed + } + + @AfterEach + void afterEach() { + LOGGER.info("call afterEach()"); + } + + @AfterAll + static void afterAll() { + LOGGER.info("call afterAll()"); + } +} +``` + +### 3.2. 定制测试类和方法的显示名称 + +支持普通字符、特殊符号、emoji + +```java +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@DisplayName("A special test case") +class JunitDisplayNameDemo { + + @Test + @DisplayName("Custom test name containing spaces") + void testWithDisplayNameContainingSpaces() { } + + @Test + @DisplayName("╯°□°)╯") + void testWithDisplayNameContainingSpecialCharacters() { } + + @Test + @DisplayName("😱") + void testWithDisplayNameContainingEmoji() { } +} +``` + +### 3.3. 断言(Assertions) + +```java +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static java.time.Duration.ofMillis; +import static java.time.Duration.ofMinutes; +import static org.junit.jupiter.api.Assertions.*; + +class AssertionsDemo { + + private static Person person; + + @BeforeAll + public static void beforeAll() { + person = new Person("John", "Doe"); + } + + @Test + void standardAssertions() { + assertEquals(2, 2); + assertEquals(4, 4, "The optional assertion message is now the last parameter."); + assertTrue('a' < 'b', () -> "Assertion messages can be lazily evaluated -- " + + "to avoid constructing complex messages unnecessarily."); + } + + @Test + void groupedAssertions() { + // In a grouped assertion all assertions are executed, and any + // failures will be reported together. + assertAll("person", () -> assertEquals("John", person.getFirstName()), + () -> assertEquals("Doe", person.getLastName())); + } + + @Test + void dependentAssertions() { + // Within a code block, if an assertion fails the + // subsequent code in the same block will be skipped. + assertAll("properties", () -> { + String firstName = person.getFirstName(); + assertNotNull(firstName); + + // Executed only if the previous assertion is valid. + assertAll("first name", () -> assertTrue(firstName.startsWith("J")), + () -> assertTrue(firstName.endsWith("n"))); + }, () -> { + // Grouped assertion, so processed independently + // of results of first name assertions. + String lastName = person.getLastName(); + assertNotNull(lastName); + + // Executed only if the previous assertion is valid. + assertAll("last name", () -> assertTrue(lastName.startsWith("D")), + () -> assertTrue(lastName.endsWith("e"))); + }); + } + + @Test + void exceptionTesting() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> { + throw new IllegalArgumentException("a message"); + }); + assertEquals("a message", exception.getMessage()); + } + + @Test + void timeoutNotExceeded() { + // The following assertion succeeds. + assertTimeout(ofMinutes(2), () -> { + // Perform task that takes less than 2 minutes. + }); + } + + @Test + void timeoutNotExceededWithResult() { + // The following assertion succeeds, and returns the supplied object. + String actualResult = assertTimeout(ofMinutes(2), () -> { + return "a result"; + }); + assertEquals("a result", actualResult); + } + + @Test + void timeoutNotExceededWithMethod() { + // The following assertion invokes a method reference and returns an object. + String actualGreeting = assertTimeout(ofMinutes(2), AssertionsDemo::greeting); + assertEquals("Hello, World!", actualGreeting); + } + + @Test + void timeoutExceeded() { + // The following assertion fails with an error message similar to: + // execution exceeded timeout of 10 ms by 91 ms + assertTimeout(ofMillis(10), () -> { + // Simulate task that takes more than 10 ms. + Thread.sleep(100); + }); + } + + @Test + void timeoutExceededWithPreemptiveTermination() { + // The following assertion fails with an error message similar to: + // execution timed out after 10 ms + assertTimeoutPreemptively(ofMillis(10), () -> { + // Simulate task that takes more than 10 ms. + Thread.sleep(100); + }); + } + + private static String greeting() { + return "Hello, World!"; + } + +} +``` + +### 3.4. 假想(Assumptions) + +```java +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assumptions.assumeTrue; +import static org.junit.jupiter.api.Assumptions.assumingThat; + +import org.junit.jupiter.api.Test; + +class AssumptionsDemo { + + @Test + void testOnlyOnCiServer() { + assumeTrue("CI".equals(System.getenv("ENV"))); + // remainder of test + } + + @Test + void testOnlyOnDeveloperWorkstation() { + assumeTrue("DEV".equals(System.getenv("ENV")), + () -> "Aborting test: not on developer workstation"); + // remainder of test + } + + @Test + void testInAllEnvironments() { + assumingThat("CI".equals(System.getenv("ENV")), + () -> { + // perform these assertions only on the CI server + assertEquals(2, 2); + }); + + // perform these assertions in all environments + assertEquals("a string", "a string"); + } + +} +``` + +### 3.5. 禁用 + +禁用单元测试类示例: + +```java +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +@Disabled +class DisabledClassDemo { + @Test + void testWillBeSkipped() { + } +} +``` + +禁用单元测试方法示例: + +```java +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +class DisabledTestsDemo { + + @Disabled + @Test + void testWillBeSkipped() { + } + + @Test + void testWillBeExecuted() { + } +} +``` + +### 3.6. 测试条件 + +#### 操作系统条件 + +```java +@Test +@EnabledOnOs(MAC) +void onlyOnMacOs() { + // ... +} + +@TestOnMac +void testOnMac() { + // ... +} + +@Test +@EnabledOnOs({ LINUX, MAC }) +void onLinuxOrMac() { + // ... +} + +@Test +@DisabledOnOs(WINDOWS) +void notOnWindows() { + // ... +} + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Test +@EnabledOnOs(MAC) +@interface TestOnMac { +} +``` + +#### Java 运行时版本条件 + +```java +@Test +@EnabledOnJre(JAVA_8) +void onlyOnJava8() { + // ... +} + +@Test +@EnabledOnJre({ JAVA_9, JAVA_10 }) +void onJava9Or10() { + // ... +} + +@Test +@DisabledOnJre(JAVA_9) +void notOnJava9() { + // ... +} +``` + +#### 系统属性条件 + +```java +@Test +@EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*") +void onlyOn64BitArchitectures() { + // ... +} + +@Test +@DisabledIfSystemProperty(named = "ci-server", matches = "true") +void notOnCiServer() { + // ... +} +``` + +### 3.7. 嵌套测试 + +```java +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.EmptyStackException; +import java.util.Stack; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +@DisplayName("A stack") +class TestingAStackDemo { + + Stack stack; + + @Test + @DisplayName("is instantiated with new Stack()") + void isInstantiatedWithNew() { + new Stack<>(); + } + + @Nested + @DisplayName("when new") + class WhenNew { + + @BeforeEach + void createNewStack() { + stack = new Stack<>(); + } + + @Test + @DisplayName("is empty") + void isEmpty() { + assertTrue(stack.isEmpty()); + } + + @Test + @DisplayName("throws EmptyStackException when popped") + void throwsExceptionWhenPopped() { + assertThrows(EmptyStackException.class, () -> stack.pop()); + } + + @Test + @DisplayName("throws EmptyStackException when peeked") + void throwsExceptionWhenPeeked() { + assertThrows(EmptyStackException.class, () -> stack.peek()); + } + + @Nested + @DisplayName("after pushing an element") + class AfterPushing { + + String anElement = "an element"; + + @BeforeEach + void pushAnElement() { + stack.push(anElement); + } + + @Test + @DisplayName("it is no longer empty") + void isNotEmpty() { + assertFalse(stack.isEmpty()); + } + + @Test + @DisplayName("returns the element when popped and is empty") + void returnElementWhenPopped() { + assertEquals(anElement, stack.pop()); + assertTrue(stack.isEmpty()); + } + + @Test + @DisplayName("returns the element when peeked but remains not empty") + void returnElementWhenPeeked() { + assertEquals(anElement, stack.peek()); + assertFalse(stack.isEmpty()); + } + } + } +} +``` + +### 3.8. 重复测试 + +```java +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.logging.Logger; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.RepetitionInfo; +import org.junit.jupiter.api.TestInfo; + +class RepeatedTestsDemo { + + private Logger logger = // ... + + @BeforeEach + void beforeEach(TestInfo testInfo, RepetitionInfo repetitionInfo) { + int currentRepetition = repetitionInfo.getCurrentRepetition(); + int totalRepetitions = repetitionInfo.getTotalRepetitions(); + String methodName = testInfo.getTestMethod().get().getName(); + logger.info(String.format("About to execute repetition %d of %d for %s", // + currentRepetition, totalRepetitions, methodName)); + } + + @RepeatedTest(10) + void repeatedTest() { + // ... + } + + @RepeatedTest(5) + void repeatedTestWithRepetitionInfo(RepetitionInfo repetitionInfo) { + assertEquals(5, repetitionInfo.getTotalRepetitions()); + } + + @RepeatedTest(value = 1, name = "{displayName} {currentRepetition}/{totalRepetitions}") + @DisplayName("Repeat!") + void customDisplayName(TestInfo testInfo) { + assertEquals(testInfo.getDisplayName(), "Repeat! 1/1"); + } + + @RepeatedTest(value = 1, name = RepeatedTest.LONG_DISPLAY_NAME) + @DisplayName("Details...") + void customDisplayNameWithLongPattern(TestInfo testInfo) { + assertEquals(testInfo.getDisplayName(), "Details... :: repetition 1 of 1"); + } + + @RepeatedTest(value = 5, name = "Wiederholung {currentRepetition} von {totalRepetitions}") + void repeatedTestInGerman() { + // ... + } + +} +``` + +### 3.9. 参数化测试 + +```java +@ParameterizedTest +@ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" }) +void palindromes(String candidate) { + assertTrue(isPalindrome(candidate)); +} +``` + +## 4. 引用和引申 + +- [Github](https://github.com/junit-team/junit5) +- [官方用户手册](https://junit.org/junit5/docs/current/user-guide/) +- [Javadoc](https://junit.org/junit5/docs/current/api/) +- [版本声明](https://junit.org/junit5/docs/current/release-notes/) +- [官方示例](https://github.com/junit-team/junit5-samples) diff --git a/docs/ecology/test/mockito.md b/docs/ecology/test/mockito.md new file mode 100644 index 00000000..ca3657e7 --- /dev/null +++ b/docs/ecology/test/mockito.md @@ -0,0 +1,562 @@ +# Mockito 应用指南 + +> Mockito 是一个针对 Java 的 mock 框架。 + +## 预备知识 + +如果需要往下学习,你需要先理解 Junit 框架中的单元测试。 + +如果你不熟悉 JUnit,请看 [Junit 教程](http://www.vogella.com/tutorials/JUnit/article.html) + +## 使用 mock 对象来进行测试 + +### 单元测试的目标和挑战 + +单元测试的思路是在不涉及依赖关系的情况下测试代码(隔离性),所以测试代码与其他类或者系统的关系应该尽量被消除。一个可行的消除方法是替换掉依赖类(测试替换),也就是说我们可以使用替身来替换掉真正的依赖对象。 + +### 测试类的分类 + +- **dummy object** 做为参数传递给方法但是绝对不会被使用。譬如说,这种测试类内部的方法不会被调用,或者是用来填充某个方法的参数。 +- **Fake** 是真正接口或抽象类的实现体,但给对象内部实现很简单。譬如说,它存在内存中而不是真正的数据库中。(译者注:**Fake** 实现了真正的逻辑,但它的存在只是为了测试,而不适合于用在产品中。) +- **stub** 类是依赖类的部分方法实现,而这些方法在你测试类和接口的时候会被用到,也就是说 **stub** 类在测试中会被实例化。**stub** 类会回应任何外部测试的调用。**stub** 类有时候还会记录调用的一些信息。 +- **mock object** 是指类或者接口的模拟实现,你可以自定义这个对象中某个方法的输出结果。 + +测试替代技术能够在测试中模拟测试类以外对象。因此你可以验证测试类是否响应正常。譬如说,你可以验证在 Mock 对象的某一个方法是否被调用。这可以确保隔离了外部依赖的干扰只测试测试类。 + +我们选择 Mock 对象的原因是因为 Mock 对象只需要少量代码的配置。 + +### Mock 对象的产生 + +你可以手动创建一个 Mock 对象或者使用 Mock 框架来模拟这些类,Mock 框架允许你在运行时创建 Mock 对象并且定义它的行为。 + +一个典型的例子是把 Mock 对象模拟成数据的提供者。在正式的生产环境中它会被实现用来连接数据源。但是我们在测试的时候 Mock 对象将会模拟成数据提供者来确保我们的测试环境始终是相同的。 + +Mock 对象可以被提供来进行测试。因此,我们测试的类应该避免任何外部数据的强依赖。 + +通过 Mock 对象或者 Mock 框架,我们可以测试代码中期望的行为。譬如说,验证只有某个存在 Mock 对象的方法是否被调用了。 + +### 使用 Mockito 生成 Mock 对象 + +_Mockito_ 是一个流行 mock 框架,可以和 JUnit 结合起来使用。Mockito 允许你创建和配置 mock 对象。使用 Mockito 可以明显的简化对外部依赖的测试类的开发。 + +一般使用 Mockito 需要执行下面三步 + +1. 模拟并替换测试代码中外部依赖 +2. 执行测试代码 +3. 验证测试代码是否被正确的执行 0 + +## 为自己的项目添加 Mockito 依赖 + +### 在 Gradle 添加 Mockito 依赖 + +如果你的项目使用 Gradle 构建,将下面代码加入 Gradle 的构建文件中为自己项目添加 Mockito 依赖 + +``` +repositories { jcenter() } +dependencies { testCompile "org.mockito:mockito-core:2.0.57-beta" } +``` + +### 在 Maven 添加 Mockito 依赖 + +需要在 Maven 声明依赖,您可以在 [http://search.maven.org](http://search.maven.org/) 网站中搜索 `g:"org.mockito", a:"mockito-core"` 来得到具体的声明方式。 + +### 在 Eclipse IDE 使用 Mockito + +Eclipse IDE 支持 Gradle 和 Maven 两种构建工具,所以在 Eclipse IDE 添加依赖取决你使用的是哪一个构建工具。 + +### 以 OSGi 或者 Eclipse 插件形式添加 Mockito 依赖 + +在 Eclipse RCP 应用依赖通常可以在 p2 update 上得到。Orbit 是一个很好的第三方仓库,我们可以在里面寻找能在 Eclipse 上使用的应用和插件。 + +Orbit 仓库地址:[http://download.eclipse.org/tools/orbit/downloads](http://download.eclipse.org/tools/orbit/downloads) + +## 使用 Mockito API + +### 静态引用 + +如果在代码中静态引用了`org.mockito.Mockito.*;`,那你你就可以直接调用静态方法和静态变量而不用创建对象,譬如直接调用 mock() 方法。 + +### 使用 Mockito 创建和配置 mock 对象 + +除了上面所说的使用 mock() 静态方法外,Mockito 还支持通过 `@Mock` 注解的方式来创建 mock 对象。 + +如果你使用注解,那么必须要实例化 mock 对象。Mockito 在遇到使用注解的字段的时候,会调用`MockitoAnnotations.initMocks(this)` 来初始化该 mock 对象。另外也可以通过使用`@RunWith(MockitoJUnitRunner.class)`来达到相同的效果。 + +通过下面的例子我们可以了解到使用`@Mock` 的方法和`MockitoRule`规则。 + +```java +import static org.mockito.Mockito.*; + +public class MockitoTest { + + @Mock + MyDatabase databaseMock; (1) + + @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); (2) + + @Test + public void testQuery() { + ClassToTest t = new ClassToTest(databaseMock); (3) + boolean check = t.query("* from t"); (4) + assertTrue(check); (5) + verify(databaseMock).query("* from t"); (6) + } +} +``` + +1. 告诉 Mockito 模拟 databaseMock 实例 +2. Mockito 通过 @mock 注解创建 mock 对象 +3. 使用已经创建的 mock 初始化这个类 +4. 在测试环境下,执行测试类中的代码 +5. 使用断言确保调用的方法返回值为 true +6. 验证 query 方法是否被 `MyDatabase` 的 mock 对象调用 + +### 配置 mock + +当我们需要配置某个方法的返回值的时候,Mockito 提供了链式的 API 供我们方便的调用 + +`when(….).thenReturn(….)`可以被用来定义当条件满足时函数的返回值,如果你需要定义多个返回值,可以多次定义。当你多次调用函数的时候,Mockito 会根据你定义的先后顺序来返回返回值。Mocks 还可以根据传入参数的不同来定义不同的返回值。譬如说你的函数可以将`anyString` 或者 `anyInt`作为输入参数,然后定义其特定的放回值。 + +```java +import static org.mockito.Mockito.*; +import static org.junit.Assert.*; + +@Test +public void test1() { + // 创建 mock + MyClass test = Mockito.mock(MyClass.class); + + // 自定义 getUniqueId() 的返回值 + when(test.getUniqueId()).thenReturn(43); + + // 在测试中使用mock对象 + assertEquals(test.getUniqueId(), 43); +} + +// 返回多个值 +@Test +public void testMoreThanOneReturnValue() { + Iterator i= mock(Iterator.class); + when(i.next()).thenReturn("Mockito").thenReturn("rocks"); + String result=i.next()+" "+i.next(); + // 断言 + assertEquals("Mockito rocks", result); +} + +// 如何根据输入来返回值 +@Test +public void testReturnValueDependentOnMethodParameter() { + Comparable c= mock(Comparable.class); + when(c.compareTo("Mockito")).thenReturn(1); + when(c.compareTo("Eclipse")).thenReturn(2); + // 断言 + assertEquals(1,c.compareTo("Mockito")); +} + +// 如何让返回值不依赖于输入 +@Test +public void testReturnValueInDependentOnMethodParameter() { + Comparable c= mock(Comparable.class); + when(c.compareTo(anyInt())).thenReturn(-1); + // 断言 + assertEquals(-1 ,c.compareTo(9)); +} + +// 根据参数类型来返回值 +@Test +public void testReturnValueInDependentOnMethodParameter() { + Comparable c= mock(Comparable.class); + when(c.compareTo(isA(Todo.class))).thenReturn(0); + // 断言 + Todo todo = new Todo(5); + assertEquals(todo ,c.compareTo(new Todo(1))); +} +``` + +对于无返回值的函数,我们可以使用`doReturn(…).when(…).methodCall`来获得类似的效果。例如我们想在调用某些无返回值函数的时候抛出异常,那么可以使用`doThrow` 方法。如下面代码片段所示 + +```java +import static org.mockito.Mockito.*; +import static org.junit.Assert.*; + +// 下面测试用例描述了如何使用doThrow()方法 + +@Test(expected=IOException.class) +public void testForIOException() { + // 创建并配置 mock 对象 + OutputStream mockStream = mock(OutputStream.class); + doThrow(new IOException()).when(mockStream).close(); + + // 使用 mock + OutputStreamWriter streamWriter= new OutputStreamWriter(mockStream); + streamWriter.close(); +} +``` + +### 验证 mock 对象方法是否被调用 + +Mockito 会跟踪 mock 对象里面所有的方法和变量。所以我们可以用来验证函数在传入特定参数的时候是否被调用。这种方式的测试称行为测试,行为测试并不会检查函数的返回值,而是检查在传入正确参数时候函数是否被调用。 + +```java +import static org.mockito.Mockito.*; + +@Test +public void testVerify() { + // 创建并配置 mock 对象 + MyClass test = Mockito.mock(MyClass.class); + when(test.getUniqueId()).thenReturn(43); + + // 调用mock对象里面的方法并传入参数为12 + test.testing(12); + test.getUniqueId(); + test.getUniqueId(); + + // 查看在传入参数为12的时候方法是否被调用 + verify(test).testing(Matchers.eq(12)); + + // 方法是否被调用两次 + verify(test, times(2)).getUniqueId(); + + // 其他用来验证函数是否被调用的方法 + verify(mock, never()).someMethod("never called"); + verify(mock, atLeastOnce()).someMethod("called at least once"); + verify(mock, atLeast(2)).someMethod("called at least twice"); + verify(mock, times(5)).someMethod("called five times"); + verify(mock, atMost(3)).someMethod("called at most 3 times"); +} +``` + +### 使用 Spy 封装 java 对象 + +@Spy 或者`spy()`方法可以被用来封装 java 对象。被封装后,除非特殊声明(打桩 _stub_),否则都会真正的调用对象里面的每一个方法 + +```java +import static org.mockito.Mockito.*; + +// Lets mock a LinkedList +List list = new LinkedList(); +List spy = spy(list); + +// 可用 doReturn() 来打桩 +doReturn("foo").when(spy).get(0); + +// 下面代码不生效 +// 真正的方法会被调用 +// 将会抛出 IndexOutOfBoundsException 的异常,因为 List 为空 +when(spy.get(0)).thenReturn("foo"); +``` + +方法`verifyNoMoreInteractions()`允许你检查没有其他的方法被调用了。 + +### 使用 @InjectMocks 在 Mockito 中进行依赖注入 + +我们也可以使用`@InjectMocks` 注解来创建对象,它会根据类型来注入对象里面的成员方法和变量。假定我们有 ArticleManager 类 + +```java +public class ArticleManager { + private User user; + private ArticleDatabase database; + + ArticleManager(User user) { + this.user = user; + } + + void setDatabase(ArticleDatabase database) { } +} +``` + +这个类会被 Mockito 构造,而类的成员方法和变量都会被 mock 对象所代替,正如下面的代码片段所示: + +```java +@RunWith(MockitoJUnitRunner.class) +public class ArticleManagerTest { + + @Mock ArticleCalculator calculator; + @Mock ArticleDatabase database; + @Most User user; + + @Spy private UserProvider userProvider = new ConsumerUserProvider(); + + @InjectMocks private ArticleManager manager; (1) + + @Test public void shouldDoSomething() { + // 假定 ArticleManager 有一个叫 initialize() 的方法被调用了 + // 使用 ArticleListener 来调用 addListener 方法 + manager.initialize(); + + // 验证 addListener 方法被调用 + verify(database).addListener(any(ArticleListener.class)); + } +} +``` + +1. 创建 ArticleManager 实例并注入 Mock 对象 + +更多的详情可以查看 [http://docs.mockito.googlecode.com/hg/1.9.5/org/mockito/InjectMocks.html](http://docs.mockito.googlecode.com/hg/1.9.5/org/mockito/InjectMocks.html) + +### 捕捉参数 + +`ArgumentCaptor`类允许我们在 verification 期间访问方法的参数。得到方法的参数后我们可以使用它进行测试。 + +```java +import static org.hamcrest.Matchers.hasItem; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +public class MockitoTests { + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + + @Captor + private ArgumentCaptor> captor; + + @Test + public final void shouldContainCertainListItem() { + List asList = Arrays.asList("someElement_test", "someElement"); + final List mockedList = mock(List.class); + mockedList.addAll(asList); + + verify(mockedList).addAll(captor.capture()); + final List capturedArgument = captor.getValue(); + assertThat(capturedArgument, hasItem("someElement")); + } +} +``` + +### Mockito 的限制 + +Mockito 当然也有一定的限制。而下面三种数据类型则不能够被测试 + +- final classes +- anonymous classes +- primitive types + +## 在 Android 中使用 Mockito + +在 Android 中的 Gradle 构建文件中加入 Mockito 依赖后就可以直接使用 Mockito 了。若想使用 Android Instrumented tests 的话,还需要添加 dexmaker 和 dexmaker-mockito 依赖到 Gradle 的构建文件中。(需要 Mockito 1.9.5 版本以上) + +```java +dependencies { + testCompile 'junit:junit:4.12' + // Mockito unit test 的依赖 + testCompile 'org.mockito:mockito-core:1.+' + // Mockito Android instrumentation tests 的依赖 + androidTestCompile 'org.mockito:mockito-core:1.+' + androidTestCompile "com.google.dexmaker:dexmaker:1.2" + androidTestCompile "com.google.dexmaker:dexmaker-mockito:1.2" +} +``` + +## 实例:使用 Mockito 写一个 Instrumented Unit Test + +### 创建一个测试的 Android 应用 + +创建一个包名为`com.vogella.android.testing.mockito.contextmock`的 Android 应用,添加一个静态方法 ,方法里面创建一个包含参数的 Intent,如下代码所示: + +```java +public static Intent createQuery(Context context, String query, String value) { + // 简单起见,重用MainActivity + Intent i = new Intent(context, MainActivity.class); + i.putExtra("QUERY", query); + i.putExtra("VALUE", value); + return i; +} +``` + +### 在 app/build.gradle 文件中添加 Mockito 依赖 + +```java +dependencies { + // Mockito 和 JUnit 的依赖 + // instrumentation unit tests on the JVM + androidTestCompile 'junit:junit:4.12' + androidTestCompile 'org.mockito:mockito-core:2.0.57-beta' + androidTestCompile 'com.android.support.test:runner:0.3' + androidTestCompile "com.google.dexmaker:dexmaker:1.2" + androidTestCompile "com.google.dexmaker:dexmaker-mockito:1.2" + + // Mockito 和 JUnit 的依赖 + // tests on the JVM + testCompile 'junit:junit:4.12' + testCompile 'org.mockito:mockito-core:1.+' + +} +``` + +### 创建测试 + +使用 Mockito 创建一个单元测试来验证在传递正确 extra data 的情况下,intent 是否被触发。 + +因此我们需要使用 Mockito 来 mock 一个`Context`对象,如下代码所示: + +```java +package com.vogella.android.testing.mockitocontextmock; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class TextIntentCreation { + + @Test + public void testIntentShouldBeCreated() { + Context context = Mockito.mock(Context.class); + Intent intent = MainActivity.createQuery(context, "query", "value"); + assertNotNull(intent); + Bundle extras = intent.getExtras(); + assertNotNull(extras); + assertEquals("query", extras.getString("QUERY")); + assertEquals("value", extras.getString("VALUE")); + } +} +``` + +## 实例:使用 Mockito 创建一个 mock 对象 + +### 目标 + +创建一个 Api,它可以被 Mockito 来模拟并做一些工作 + +### 创建一个 Twitter API 的例子 + +实现 `TwitterClient`类,它内部使用到了 `ITweet` 的实现。但是`ITweet`实例很难得到,譬如说他需要启动一个很复杂的服务来得到。 + +```java +public interface ITweet { + + String getMessage(); +} + + +public class TwitterClient { + + public void sendTweet(ITweet tweet) { + String message = tweet.getMessage(); + + // send the message to Twitter + } +} +``` + +### 模拟 ITweet 的实例 + +为了能够不启动复杂的服务来得到 `ITweet`,我们可以使用 Mockito 来模拟得到该实例。 + +```java +@Test +public void testSendingTweet() { + TwitterClient twitterClient = new TwitterClient(); + + ITweet iTweet = mock(ITweet.class); + + when(iTweet.getMessage()).thenReturn("Using mockito is great"); + + twitterClient.sendTweet(iTweet); +} +``` + +现在 `TwitterClient` 可以使用 `ITweet` 接口的实现,当调用 `getMessage()` 方法的时候将会打印 "Using Mockito is great" 信息。 + +### 验证方法调用 + +确保 getMessage() 方法至少调用一次。 + +```java +@Test +public void testSendingTweet() { + TwitterClient twitterClient = new TwitterClient(); + + ITweet iTweet = mock(ITweet.class); + + when(iTweet.getMessage()).thenReturn("Using mockito is great"); + + twitterClient.sendTweet(iTweet); + + verify(iTweet, atLeastOnce()).getMessage(); +} +``` + +### 验证 + +运行测试,查看代码是否测试通过。 + +## 模拟静态方法 + +### 使用 Powermock 来模拟静态方法 + +因为 Mockito 不能够 mock 静态方法,因此我们可以使用 `Powermock`。 + +```java +import java.net.InetAddress; +import java.net.UnknownHostException; + +public final class NetworkReader { + public static String getLocalHostname() { + String hostname = ""; + try { + InetAddress addr = InetAddress.getLocalHost(); + // Get hostname + hostname = addr.getHostName(); + } catch ( UnknownHostException e ) { + } + return hostname; + } +} +``` + +我们模拟了 NetworkReader 的依赖,如下代码所示: + +```java +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PrepareForTest; + +@RunWith( PowerMockRunner.class ) +@PrepareForTest( NetworkReader.class ) +public class MyTest { + +// 测试代码 + + @Test +public void testSomething() { + mockStatic( NetworkUtil.class ); + when( NetworkReader.getLocalHostname() ).andReturn( "localhost" ); + + // 与 NetworkReader 协作的测试 +} +``` + +### 用封装的方法代替 Powermock + +有时候我们可以在静态方法周围包含非静态的方法来达到和 Powermock 同样的效果。 + +```java +class FooWraper { + void someMethod() { + Foo.someStaticMethod() + } +} +``` + +## 引用和引申 + +- [官网](https://site.mockito.org/) +- [Github](https://github.com/mockito/mockito) +- [使用强大的 Mockito 测试框架来测试你的代码](https://github.com/xitu/gold-miner/blob/master/TODO/Unit-tests-with-Mockito.md) diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 683b22c5..00000000 --- a/docs/index.html +++ /dev/null @@ -1,155 +0,0 @@ - - - - - javaweb - - - - - - - - - - -
    正在加载...
    - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/javaee/javaee-cookie-sesion.md b/docs/javaee/javaee-cookie-sesion.md index 56a6fe8c..829d8ecf 100644 --- a/docs/javaee/javaee-cookie-sesion.md +++ b/docs/javaee/javaee-cookie-sesion.md @@ -1,46 +1,12 @@ # JavaEE 之 Cookie 和 Session - - -- [Cookie](#cookie) - - [Cookie 是什么?](#cookie-是什么) - - [Cookie 剖析](#cookie-剖析) - - [Cookie 类中的方法](#cookie-类中的方法) - - [Cookie 的有效期](#cookie-的有效期) - - [Cookie 的域名](#cookie-的域名) - - [Cookie 的路径](#cookie-的路径) - - [Cookie 的安全属性](#cookie-的安全属性) - - [实例](#实例) - - [添加 Cookie](#添加-cookie) - - [显示 Cookie](#显示-cookie) - - [删除 Cookie](#删除-cookie) -- [Session](#session) - - [Session 是什么?](#session-是什么) - - [Session 类中的方法](#session-类中的方法) - - [Session 的有效期](#session-的有效期) - - [Session 对浏览器的要求](#session-对浏览器的要求) - - [URL 地址重写](#url-地址重写) - - [Session 中禁用 Cookie](#session-中禁用-cookie) - - [实例](#实例) - - [Session 跟踪](#session-跟踪) - - [删除 Session 会话数据](#删除-session-会话数据) -- [Cookie vs Session](#cookie-vs-session) - - [存取方式](#存取方式) - - [隐私安全](#隐私安全) - - [有效期](#有效期) - - [服务器的开销](#服务器的开销) - - [浏览器的支持](#浏览器的支持) - - [跨域名](#跨域名) - - - ## Cookie 由于 Http 是一种无状态的协议,服务器单从网络连接上无从知道客户身份。 会话跟踪是 Web 程序中常用的技术,用来跟踪用户的整个会话。常用会话跟踪技术是 Cookie 与 Session。 -### Cookie 是什么? +### Cookie 是什么 Cookie 实际上是存储在客户端上的文本信息,并保留了各种跟踪的信息。 @@ -60,7 +26,7 @@ Cookies 通常设置在 HTTP 头信息中(虽然 JavaScript 也可以直接在 设置 Cookie 的 Servlet 会发送如下的头信息: -``` +```http HTTP/1.1 200 OK Date: Fri, 04 Feb 2000 21:03:38 GMT Server: Apache/1.3.9 (UNIX) PHP/4.0b3 @@ -74,7 +40,7 @@ Content-Type: text/html 如果浏览器被配置为存储 Cookies,它将会保留此信息直到到期日期。如果用户的浏览器指向任何匹配该 Cookie 的路径和域的页面,它会重新发送 Cookie 到服务器。浏览器的头信息可能如下所示: -``` +```http GET / HTTP/1.0 Connection: Keep-Alive User-Agent: Mozilla/4.6 (X11; I; Linux 2.2.6-15apmac ppc) @@ -135,7 +101,7 @@ HTTP 协议不仅是无状态的,而且是不安全的。 Java 中使用`setSecure(booleanflag)`和`getSecure ()`方法来设置、获取 Secure。 -### 实例 +### Cookie 实例 #### 添加 Cookie @@ -215,7 +181,7 @@ public class AddCookies extends HttpServlet { addCookies.jsp -```jsp +```java <%@ page language="java" pageEncoding="UTF-8" %> @@ -395,7 +361,7 @@ public class DeleteCookies extends HttpServlet { ## Session -### Session 是什么? +### Session 是什么 不同于 Cookie 保存在客户端浏览器中,Session 保存在服务器上。 @@ -460,7 +426,7 @@ URL 地址重写的原理是将该用户 Session 的 id 信息重写到 URL 地 部署后,TOMCAT 便不会自动生成名 JESSIONID 的 Cookie,Session 也不会以 Cookie 为识别标志,而仅仅以重写后的 URL 地址为识别标志了。 -### 实例 +### Session 实例 #### Session 跟踪 @@ -535,12 +501,12 @@ web.xml ```xml - SessionTrackServlet - SessionTrackServlet + SessionTrackServlet + SessionTrackServlet - SessionTrackServlet - /servlet/SessionTrackServlet + SessionTrackServlet + /servlet/SessionTrackServlet ``` @@ -604,6 +570,5 @@ Cookie 需要浏览器支持才能使用。 ### 跨域名 -Cookie 支持跨域名。 - -Session 不支持跨域名。 +* Cookie 支持跨域名。 +* Session 不支持跨域名。 diff --git a/docs/javaee/javaee-filter-listener.md b/docs/javaee/javaee-filter-listener.md index f144b7f5..9d6cf977 100644 --- a/docs/javaee/javaee-filter-listener.md +++ b/docs/javaee/javaee-filter-listener.md @@ -1,25 +1,10 @@ # JavaEE 之 Filter 和 Listener - - -- [Filter](#filter) - - [过滤器方法](#过滤器方法) - - [过滤器配置](#过滤器配置) -- [Listener](#listener) - - [监听器的分类](#监听器的分类) - - [监听对象的创建和销毁](#监听对象的创建和销毁) - - [监听对象的属性变化](#监听对象的属性变化) - - [监听 Session 内的对象](#监听-session-内的对象) -- [示例代码](#示例代码) -- [参考资料](#参考资料) - - - ## Filter 过滤器(Filter)用于在 Servlet 之外对 request 或 response 进行修改。Filter 提供了过滤链(Filter Chain)的概念,一个过滤链包括多个 Filter。客户端请求 request 在抵达 Servlet 之前会经过过滤链的所有 Filter,服务器响应 response 从 Servlet 抵达客户端浏览器之前也会经过过滤链的所有 FIlter。 -![img](http://dunwu.test.upcdn.net/snap/1559054413341.png!zp" style="width: 300px) +![img](http://dunwu.test.upcdn.net/snap/1559054413341.png) ### 过滤器方法 @@ -91,9 +76,9 @@ public interface Filter { 在 Servlet 规范中定义了多种类型的监听器,它们用于监听的事件源分别为`ServletContext`,`HttpSession`和`ServletRequest`这三个域对象 Servlet 规范针对这三个对象上的操作,又把多种类型的监听器划分为三种类型: -1. 监听域对象自身的创建和销毁的事件监听器。 -2. 监听域对象中的属性的增加和删除的事件监听器。 -3. 监听绑定到 HttpSession 域中的某个对象的状态的事件监听器。 +1. 监听域对象自身的创建和销毁的事件监听器。 +2. 监听域对象中的属性的增加和删除的事件监听器。 +3. 监听绑定到 HttpSession 域中的某个对象的状态的事件监听器。 ### 监听对象的创建和销毁 @@ -197,8 +182,8 @@ Servlet 规范中定义了两个特殊的监听器接口 `HttpSessionBindingList ## 示例代码 -- `Filter` 的示例源码:[源码](https://github.com/dunwu/javaweb/tree/master/codes/javaee-tutorial/javaee-tutorial-filter) -- `Listener` 的示例源码:[源码](https://github.com/dunwu/javaweb/tree/master/codes/javaee-tutorial/javaee-tutorial-listener) +- `Filter` 的示例源码:[源码](https://github.com/dunwu/javatech/tree/master/codes/javaee-tutorial/javaee-tutorial-filter) +- `Listener` 的示例源码:[源码](https://github.com/dunwu/javatech/tree/master/codes/javaee-tutorial/javaee-tutorial-listener) ## 参考资料 diff --git a/docs/javaee/javaee-interview.md b/docs/javaee/javaee-interview.md index a3c0995f..0f1be619 100644 --- a/docs/javaee/javaee-interview.md +++ b/docs/javaee/javaee-interview.md @@ -1,36 +1,8 @@ # JavaEE 面经 - - -- [1. Servlet](#1-servlet) - - [1.1. 什么是 Servlet?](#11-什么是-servlet) - - [1.2. Servlet 和 CGI 的区别?](#12-servlet-和-cgi-的区别) - - [1.3. Servlet 版本以及主要特性?](#13-servlet-版本以及主要特性) - - [1.4. Servlet 和 JSP 的区别?](#14-servlet-和-jsp-的区别) - - [1.5. 简述 Servlet 生命周期](#15-简述-servlet-生命周期) - - [1.6. 如何现实 servlet 的单线程模式?](#16-如何现实-servlet-的单线程模式) - - [1.7. Servlet 中如何获取用户提交的查询参数或者表单数据](#17-servlet-中如何获取用户提交的查询参数或者表单数据) - - [1.8. request 的主要方法](#18-request-的主要方法) -- [2. JSP](#2-jsp) - - [2.1. JSP 的内置对象?](#21-jsp-的内置对象) - - [2.2. JSP 的作用域?](#22-jsp-的作用域) - - [2.3. JSP 中 7 个动作指令和作用?](#23-jsp-中-7-个动作指令和作用) - - [2.4. JSP 中动态 INCLUDE 和静态 INCLUDE 有什么区别?](#24-jsp-中动态-include-和静态-include-有什么区别) -- [3. 原理](#3-原理) - - [3.1. 请求转发(forward)和重定向(redirect)的区别?](#31-请求转发forward和重定向redirect的区别) - - [3.2. get 请求和 post 请求的区别?](#32-get-请求和-post-请求的区别) - - [3.3. 用户在浏览器中输入 URL 之后,发什么了什么?写出请求和响应的流程](#33-用户在浏览器中输入-url-之后发什么了什么写出请求和响应的流程) - - [3.4. 什么是 Web Service?](#34-什么是-web-service) - - [3.5. 会话跟踪技术有哪些?](#35-会话跟踪技术有哪些) - - [3.6. 响应结果状态码有哪些,并给出中文含义?](#36-响应结果状态码有哪些并给出中文含义) - - [3.7. XML 文档定义有几种形式?它们之间有何本质区别?解析 XML 文档有哪几种方式?](#37-xml-文档定义有几种形式它们之间有何本质区别解析-xml-文档有哪几种方式) -- [4. 参考资料](#4-参考资料) - - - ## 1. Servlet -### 1.1. 什么是 Servlet? +### 1.1. 什么是 Servlet Servlet(Server Applet),即小服务程序或服务连接器。Servlet 是 Java 编写的服务器端程序,具有独立于平台和协议的特性,主要功能在于交互式地浏览和生成数据,生成动态 Web 内容。 @@ -39,14 +11,14 @@ Servlet(Server Applet),即小服务程序或服务连接器。Servlet 是 Servlet 运行于支持 Java 的应用服务器中。从原理上讲,Servlet 可以响应任何类型的请求,但绝大多数情况下 Servlet 只用来扩展基于 HTTP 协议的 Web 服务器。 -### 1.2. Servlet 和 CGI 的区别? +### 1.2. Servlet 和 CGI 的区别 Servlet 技术出现之前,Web 主要使用 CGI 技术。它们的区别如下: - Servlet 是基于 Java 编写的,处于服务器进程中,他能够通过多线程方式运行 service() 方法,一个实例可以服务于多个请求,而且一般不会销毁; - CGI(Common Gateway Interface),即通用网关接口。它会为每个请求产生新的进程,服务完成后销毁,所以效率上低于 Servlet。 -### 1.3. Servlet 版本以及主要特性? +### 1.3. Servlet 版本以及主要特性 | 版本 | 日期 | JAVA EE/JDK 版本 | 特性 | | ----------- | ------------- | ------------------ | --------------------------------------------------------------------- | @@ -61,7 +33,7 @@ Servlet 技术出现之前,Web 主要使用 CGI 技术。它们的区别如下 | Servlet 2.0 | | JDK 1.1 | Part of Java Servlet Development Kit 2.0 | | Servlet 1.0 | 1997 年 6 月 | | | -### 1.4. Servlet 和 JSP 的区别? +### 1.4. Servlet 和 JSP 的区别 1. Servlet 是一个运行在服务器上的 Java 类,依靠服务器支持向浏览器传输数据。 2. **JSP 本质上就是 Servlet**,每次运行的时候 JSP 都会被编译成 .java 文件,然后再被编译成 .class 文件。 @@ -80,9 +52,9 @@ Servlet 生命周期如下: 4. **销毁** - Servlet 通过调用 **destroy()** 方法终止(结束)。 5. **卸载** - Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。 -### 1.6. 如何现实 servlet 的单线程模式? +### 1.6. 如何现实 servlet 的单线程模式 -```jsp +```java <%@ page isThreadSafe="false" %> ``` @@ -120,7 +92,7 @@ Servlet 生命周期如下: ## 2. JSP -### 2.1. JSP 的内置对象? +### 2.1. JSP 的内置对象 1. **request**:包含**客户端请求的信息**; 2. **response**:包含**服务器传回客户端的响应信息**; @@ -132,14 +104,14 @@ Servlet 生命周期如下: 8. **page**:指**网页本身**; 9. **exception**:处理 JSP 文件执行时发生的错误和异常,只要在**错误页面**里才能使用。 -### 2.2. JSP 的作用域? +### 2.2. JSP 的作用域 1. **page**:一个页面; 2. **request**:一次请求; 3. **session**:一次会话; 4. **application**:服务器从启动到停止。 -### 2.3. JSP 中 7 个动作指令和作用? +### 2.3. JSP 中 7 个动作指令和作用 1. **jsp:forward** - 执行页面转向,把请求转发到下一个页面; 2. **jsp:param** - 用于传递参数,必须与其他支持参数的标签一起使用; @@ -149,14 +121,14 @@ Servlet 生命周期如下: 6. **jsp:setProperty** - 设置 JavaBean 的属性值; 7. **jsp:getProperty** - 获取 JavaBean 的属性值。 -### 2.4. JSP 中动态 INCLUDE 和静态 INCLUDE 有什么区别? +### 2.4. JSP 中动态 INCLUDE 和静态 INCLUDE 有什么区别 - **静态 INCLUDE**:用 include 伪码实现,**不会检查所含文件的变化**,适用于包含**静态页面<%@ include file="页面名称.html" %>**。**先合并再编译**。 - **动态 INCLUDE**:用 jsp:include 动作实现 **** 它总是**会检查文件中的变化**,适用于包含**动态页面**,并且可以**带参数**。**先编译再合并**。 ## 3. 原理 -### 3.1. 请求转发(forward)和重定向(redirect)的区别? +### 3.1. 请求转发(forward)和重定向(redirect)的区别 - 效率上 - 转发(forward) > 重定向(redirect) @@ -170,7 +142,7 @@ Servlet 生命周期如下: - 重定向(redirect)是两次 - 转发(forward)是一次 -### 3.2. get 请求和 post 请求的区别? +### 3.2. get 请求和 post 请求的区别 ![img](https://upload-images.jianshu.io/upload_images/7779232-5be5ae990207f9d2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/814/format/webp) diff --git a/docs/javaee/javaee-jsp.md b/docs/javaee/javaee-jsp.md index 76373f6e..8e426d1f 100644 --- a/docs/javaee/javaee-jsp.md +++ b/docs/javaee/javaee-jsp.md @@ -1,72 +1,8 @@ # JSP 指南 - - -- [简介](#简介) - - [什么是 Java Server Pages?](#什么是-java-server-pages) - - [为什么使用 JSP?](#为什么使用-jsp) - - [JSP 的优势](#jsp-的优势) -- [JSP 工作原理](#jsp-工作原理) - - [JSP 工作流程](#jsp-工作流程) - - [JSP 生命周期](#jsp-生命周期) -- [语法](#语法) - - [脚本](#脚本) - - [JSP 声明](#jsp-声明) - - [JSP 表达式](#jsp-表达式) - - [JSP 注释](#jsp-注释) - - [控制语句](#控制语句) - - [JSP 字面量](#jsp-字面量) -- [指令](#指令) - - [Page 指令](#page-指令) - - [Include 指令](#include-指令) - - [Taglib 指令](#taglib-指令) -- [JSP 动作元素](#jsp-动作元素) - - [常见的属性](#常见的属性) - - [``](#jspinclude) - - [``](#jspusebean) - - [``](#jspsetproperty) - - [``](#jspgetproperty) - - [``](#jspforward) - - [``](#jspplugin) - - [`` 、 ``、``](#jspelement--jspattributejspbody) - - [``](#jsptext) -- [JSP 隐式对象](#jsp-隐式对象) - - [request 对象](#request-对象) - - [response 对象](#response-对象) - - [out 对象](#out-对象) - - [session 对象](#session-对象) - - [application 对象](#application-对象) - - [config 对象](#config-对象) - - [pageContext 对象](#pagecontext-对象) - - [page 对象](#page-对象) - - [exception 对象](#exception-对象) -- [EL 表达式](#el-表达式) - - [一个简单的语法](#一个简单的语法) - - [EL 中的基础操作符](#el-中的基础操作符) - - [JSP EL 中的函数](#jsp-el-中的函数) - - [JSP EL 隐含对象](#jsp-el-隐含对象) - - [pageContext 对象](#pagecontext-对象-1) - - [Scope 对象](#scope-对象) - - [param 和 paramValues 对象](#param-和-paramvalues-对象) - - [header 和 headerValues 对象](#header-和-headervalues-对象) -- [JSTL](#jstl) - - [JSTL 库安装](#jstl-库安装) - - [核心标签](#核心标签) - - [格式化标签](#格式化标签) - - [SQL 标签](#sql-标签) - - [XML 标签](#xml-标签) - - [JSTL 函数](#jstl-函数) -- [Taglib](#taglib) - - [JSP 自定义标签](#jsp-自定义标签) - - [创建"Hello"标签](#创建hello标签) - - [访问标签体](#访问标签体) - - [自定义标签属性](#自定义标签属性) - - - ## 简介 -### 什么是 Java Server Pages? +### 什么是 Java Server Pages `JSP`全称`Java Server Pages`,是一种动态网页开发技术。 @@ -78,7 +14,7 @@ JSP 通过网页表单获取用户输入数据、访问数据库及其他数据 JSP 标签有多种功能,比如访问数据库、记录用户选择信息、访问 JavaBeans 组件等,还可以在不同的网页中传递控制信息和共享信息。 -### 为什么使用 JSP? +### 为什么使用 JSP JSP 也是一种 Servlet,因此 JSP 能够完成 Servlet 能完成的任何工作。 @@ -168,7 +104,7 @@ JSP 生命周期就是从创建到销毁的整个过程,类似于 servlet 生 容器载入 JSP 文件后,它会在为请求提供任何服务前调用 jspInit()方法。如果您需要执行自定义的 JSP 初始化任务,复写 jspInit()方法就行了,就像下面这样: -``` +```java public void jspInit(){ // 初始化代码 } @@ -180,14 +116,13 @@ public void jspInit(){ 这一阶段描述了 JSP 生命周期中一切与请求相关的交互行为,直到被销毁。 -当 JSP 网页完成初始化后,JSP 引擎将会调用\_jspService()方法。 +当 JSP 网页完成初始化后,JSP 引擎将会调用 `_jspService()` 方法。 -\_jspService()方法需要一个 HttpServletRequest 对象和一个 HttpServletResponse 对象作为它的参数,就像下面这样: +`_jspService()` 方法需要一个 HttpServletRequest 对象和一个 HttpServletResponse 对象作为它的参数,就像下面这样: -``` +```java void _jspService(HttpServletRequest request, - HttpServletResponse response) -{ + HttpServletResponse response) { // 服务端处理代码 } ``` @@ -202,9 +137,8 @@ jspDestroy()方法在 JSP 中等价于 servlet 中的销毁方法。当您需要 jspDestroy()方法的格式如下: -``` -public void jspDestroy() -{ +```java +public void jspDestroy() { // 清理代码 } ``` @@ -217,15 +151,15 @@ public void jspDestroy() 脚本程序的语法格式: -```jsp +``` <% 代码片段 %> ``` 或者,您也可以编写与其等价的 XML 语句,就像下面这样: -```jsp +``` - 代码片段 + 代码片段 ``` @@ -233,50 +167,48 @@ public void jspDestroy() 下面给出一个示例,同时也是本教程的第一个 JSP 示例: -```jsp +``` -Hello World - -Hello World!
    -<% -out.println("Your IP address is " + request.getRemoteAddr()); -%> - + + Hello World + + + Hello World!
    + <% out.println("Your IP address is " + request.getRemoteAddr()); %> + ``` **注意:**请确保 Apache Tomcat 已经安装在 C:\apache-tomcat-7.0.2 目录下并且运行环境已经正确设置。 -将以上代码保存在 hello.jsp 中,然后将它放置在 C:\apache-tomcat-7.0.2\webapps\ROOT 目录下,打开浏览器并在地址栏中输入http://localhost:8080/hello.jsp。运行后得到以下结果: +将以上代码保存在 hello.jsp 中,然后将它放置在 C:\apache-tomcat-7.0.2\webapps\ROOT 目录下,打开浏览器并在地址栏中输入 `http://localhost:8080/hello.jsp` 。运行后得到以下结果: ![img](http://www.runoob.com/wp-content/uploads/2014/01/jsp_hello_world.jpg) #### 中文编码问题 -如果我们要在页面正常显示中文,我们需要在 JSP 文件头部添加以下代码:<> +如果我们要在页面正常显示中文,我们需要在 JSP 文件头部添加以下代码:`<>` -```jsp +``` <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> +pageEncoding="UTF-8"%> ``` 接下来我们将以上程序修改为: -```jsp +``` <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> +pageEncoding="UTF-8"%> - - -菜鸟教程(runoob.com) - - -Hello World!
    -<% -out.println("你的 IP 地址 " + request.getRemoteAddr()); -%> - + + + 菜鸟教程(runoob.com) + + + Hello World!
    + <% out.println("你的 IP 地址 " + request.getRemoteAddr()); %> + ``` @@ -288,24 +220,22 @@ out.println("你的 IP 地址 " + request.getRemoteAddr()); JSP 声明的语法格式: -```jsp +``` <%! declaration; [ declaration; ]+ ... %> ``` 或者,您也可以编写与其等价的 XML 语句,就像下面这样: -```jsp +``` - 代码片段 + 代码片段 ``` 程序示例: -```jsp -<%! int i = 0; %> -<%! int a, b, c; %> -<%! Circle a = new Circle(2.0); %> +``` +<%! int i = 0; %> <%! int a, b, c; %> <%! Circle a = new Circle(2.0); %> ``` ### JSP 表达式 @@ -326,7 +256,7 @@ JSP 表达式的语法格式: ``` - 表达式 + 表达式 ``` @@ -334,18 +264,18 @@ JSP 表达式的语法格式: ``` <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> +pageEncoding="UTF-8"%> - - -菜鸟教程(runoob.com) - - -

    - 今天的日期是: <%= (new java.util.Date()).toLocaleString()%> -

    - + + + 菜鸟教程(runoob.com) + + +

    + 今天的日期是: <%= (new java.util.Date()).toLocaleString()%> +

    + ``` @@ -363,21 +293,21 @@ JSP 注释主要有两个作用:为代码作注释以及将某段代码注释 JSP 注释的语法格式: -```jsp +``` <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> +pageEncoding="UTF-8"%> - - -JSP注释示例 - - -<%-- 该部分注释在网页中不会被显示--%> -

    - 今天的日期是: <%= (new java.util.Date()).toLocaleString()%> -

    - + + + JSP注释示例 + + + <%-- 该部分注释在网页中不会被显示--%> +

    + 今天的日期是: <%= (new java.util.Date()).toLocaleString()%> +

    + ``` @@ -389,14 +319,14 @@ JSP 注释的语法格式: 不同情况下使用注释的语法规则: -| **语法** | 描述 | -| -------------- | ----------------------------------------------------- | -| <%-- 注释 --%> | JSP 注释,注释内容不会被发送至浏览器甚至不会被编译 | -| | HTML 注释,通过浏览器查看网页源代码时可以看见注释内容 | -| <\% | 代表静态 <%常量 | -| %\> | 代表静态 %> 常量 | -| \' | 在属性中使用的单引号 | -| \" | 在属性中使用的双引号 | +| **语法** | 描述 | +| ---------------- | ----------------------------------------------------- | +| `<%-- 注释 --%>` | JSP 注释,注释内容不会被发送至浏览器甚至不会被编译 | +| `` | HTML 注释,通过浏览器查看网页源代码时可以看见注释内容 | +| `<%` | 代表静态 <%常量 | +| `%>` | 代表静态 %> 常量 | +| `'` | 在属性中使用的单引号 | +| `"` | 在属性中使用的双引号 | ### 控制语句 @@ -406,24 +336,23 @@ JSP 提供对 Java 语言的全面支持。您可以在 JSP 程序中使用 Java `If…else`块,请看下面这个例子: -```jsp +``` <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> -<%! int day = 1; %> +pageEncoding="UTF-8"%> <%! int day = 1; %> - - - 02.JSP语法 - if...else示例 - - -

    IF...ELSE 实例

    -<% if (day == 1 | day == 7) { %> -

    今天是周末

    -<% } else { %> -

    今天不是周末

    -<% } %> - + + + 02.JSP语法 - if...else示例 + + +

    IF...ELSE 实例

    + <% if (day == 1 | day == 7) { %> +

    今天是周末

    + <% } else { %> +

    今天不是周末

    + <% } %> + ``` @@ -438,43 +367,22 @@ IF...ELSE 实例 现在来看看 switch…case 块,与 if…else 块有很大的不同,它使用 out.println(),并且整个都装在脚本程序的标签中,就像下面这样: -```jsp +``` <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> -<%! int day = 3; %> +pageEncoding="UTF-8"%> <%! int day = 3; %> - - - 02.JSP语法 - switch...case示例 - - -

    Sswitch...case示例

    -<% - switch(day) { - case 0: - out.println("星期天"); - break; - case 1: - out.println("星期一"); - break; - case 2: - out.println("星期二"); - break; - case 3: - out.println("星期三"); - break; - case 4: - out.println("星期四"); - break; - case 5: - out.println("星期五"); - break; - default: - out.println("星期六"); - } -%> - + + + 02.JSP语法 - switch...case示例 + + +

    Sswitch...case示例

    + <% switch(day) { case 0: out.println("星期天"); break; case 1: + out.println("星期一"); break; case 2: out.println("星期二"); break; case 3: + out.println("星期三"); break; case 4: out.println("星期四"); break; case 5: + out.println("星期五"); break; default: out.println("星期六"); } %> + ``` @@ -494,22 +402,21 @@ SWITCH...CASE 实例 ``` <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> -<%! int fontSize; %> +pageEncoding="UTF-8"%> <%! int fontSize; %> - - -菜鸟教程(runoob.com) - - -

    For 循环实例

    -<%for ( fontSize = 1; fontSize <= 3; fontSize++){ %> - - 菜鸟教程 -
    -<%}%> - + + + 菜鸟教程(runoob.com) + + +

    For 循环实例

    + <%for ( fontSize = 1; fontSize <= 3; fontSize++){ %> + + 菜鸟教程
    + <%}%> + ``` @@ -521,23 +428,21 @@ SWITCH...CASE 实例 ``` <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> -<%! int fontSize; %> +pageEncoding="UTF-8"%> <%! int fontSize; %> - - -菜鸟教程(runoob.com) - - -

    While 循环实例

    -<%while ( fontSize <= 3){ %> - - 菜鸟教程 -
    -<%fontSize++;%> -<%}%> - + + + 菜鸟教程(runoob.com) + + +

    While 循环实例

    + <%while ( fontSize <= 3){ %> + + 菜鸟教程
    + <%fontSize++;%> <%}%> + ``` @@ -553,21 +458,21 @@ JSP 支持所有 Java 逻辑和算术运算符。 | **类别** | **操作符** | **结合性** | | --------- | ----------------------------------- | ---------- | -| 后缀 | () [] . (点运算符) | 左到右 | -| 一元 | ++ - - ! \~ | 右到左 | -| 可乘性 | \* / % | 左到右 | -| 可加性 | + - | 左到右 | -| 移位 | >> >>> << | 左到右 | -| 关系 | > >= < <= | 左到右 | -| 相等/不等 | == != | 左到右 | -| 位与 | & | 左到右 | -| 位异或 | ^ | 左到右 | -| 位或 | \| | 左到右 | -| 逻辑与 | && | 左到右 | -| 逻辑或 | \|\| | 左到右 | -| 条件判断 | ?: | 右到左 | -| 赋值 | = += -= \*= /= %= >>= <<= &= ^= \|= | 右到左 | -| 逗号 | , | 左到右 | +| 后缀 | `() [] .` (点运算符) | 左到右 | +| 一元 | `++ - - ! ~` | 右到左 | +| 可乘性 | `* / %` | 左到右 | +| 可加性 | `+ -` | 左到右 | +| 移位 | `>> >>> <<` | 左到右 | +| 关系 | `> >= < <=` | 左到右 | +| 相等/不等 | `== !=` | 左到右 | +| 位与 | `&` | 左到右 | +| 位异或 | `^` | 左到右 | +| 位或 | `|` | 左到右 | +| 逻辑与 | `&&` | 左到右 | +| 逻辑或 | `||` | 左到右 | +| 条件判断 | `?:` | 右到左 | +| 赋值 | `= += -= *= /= %= >>= <<= &= ^= |=` | 右到左 | +| 逗号 | `,` | 左到右 | ### JSP 字面量 @@ -587,7 +492,7 @@ JSP 指令以开`<%@`开始,以`%>`结束。 JSP 指令语法格式如下: -```jsp +``` <%@ directive attribute="value" %> ``` @@ -607,20 +512,21 @@ Page 指令为容器提供当前页面的使用说明。一个 JSP 页面可以 Page 指令的语法格式: -```jsp +``` <%@ page attribute="value" %> ``` 等价的 XML 格式: -```jsp +``` ``` 例: -```jsp -<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> +``` +<%@ page language="java" contentType="text/html; charset=UTF-8" +pageEncoding="UTF-8" %> ``` #### 属性 @@ -651,7 +557,7 @@ JSP 可以通过`include`指令来包含其他文件。 Include 指令的语法格式如下: -```jsp +``` <%@ include file="文件相对 url 地址" %> ``` @@ -661,7 +567,7 @@ Include 指令的语法格式如下: 等价的 XML 语法: -```jsp +``` ``` @@ -673,7 +579,7 @@ JSP 允许用户自定义标签,一个自定义标签库就是自定义标签 `taglib`指令的语法: -```jsp +``` <%@ taglib uri="uri" prefix="prefixOfTag" %> ``` @@ -681,7 +587,7 @@ uri 属性确定标签库的位置,prefix 属性指定标签库的前缀。 等价的 XML 语法: -```jsp +``` ``` @@ -695,7 +601,7 @@ JSP 动作元素是一组 JSP 内置的标签,只需要书写很少的标记 动作元素只有一种语法,它符合 XML 标准: -```jsp +``` ``` @@ -742,37 +648,35 @@ JSP 动作元素是一组 JSP 内置的标签,只需要书写很少的标记 | page | 包含在页面中的相对 URL 地址。 | | flush | 布尔属性,定义在包含资源前是否刷新缓存区。 | -**例** +示例: 以下我们定义了两个文件 **date.jsp** 和 **main.jsp**,代码如下所示: date.jsp 文件代码: -```c++ +``` <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> +pageEncoding="UTF-8"%>

    - 今天的日期是: <%= (new java.util.Date())%> + 今天的日期是: <%= (new java.util.Date())%>

    ``` main.jsp 文件代码: -```jsp +``` <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> +pageEncoding="UTF-8"%> - - -菜鸟教程(runoob.com) - - - -

    include 动作实例

    - - - + + + 菜鸟教程(runoob.com) + + +

    include 动作实例

    + + ``` @@ -859,7 +763,7 @@ jsp:getProperty 动作提取指定 Bean 属性的值,转换成字符串,然 以下实例我们使用了 Bean: -``` +```java package com.runoob.main; public class TestBean { @@ -892,27 +796,23 @@ $ javac TestBean.java ``` <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> +pageEncoding="UTF-8"%> - - -菜鸟教程(runoob.com) - - - -

    Jsp 使用 JavaBean 实例

    - - - + + + 菜鸟教程(runoob.com) + + +

    Jsp 使用 JavaBean 实例

    + -

    输出信息....

    + - +

    输出信息....

    - + + ``` @@ -942,9 +842,9 @@ date.jsp 文件代码如下: ``` <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> +pageEncoding="UTF-8"%>

    - 今天的日期是: <%= (new java.util.Date()).toLocaleString()%> + 今天的日期是: <%= (new java.util.Date()).toLocaleString()%>

    ``` @@ -952,18 +852,17 @@ main.jsp 文件代码: ``` <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> +pageEncoding="UTF-8"%> - - -菜鸟教程(runoob.com) - - - -

    forward 动作实例

    - - + + + 菜鸟教程(runoob.com) + + +

    forward 动作实例

    + + ``` @@ -996,33 +895,33 @@ plugin 动作有多个对应 HTML 元素的属性用于格式化 Java 组件。p
    ``` -如果你有兴趣可以尝试使用 applet 来测试 jsp:plugin 动作元素,元素是一个新元素,在组件出现故障的错误是发送给用户错误信息。 +如果你有兴趣可以尝试使用 applet 来测试 `jsp:plugin` 动作元素,`` 元素是一个新元素,在组件出现故障的错误是发送给用户错误信息。 ### `` 、 ``、`` -动作元素动态定义 XML 元素。动态是非常重要的,这就意味着 XML 元素在编译时是动态生成的而非静态。 +`` 、 ``、`` 动作元素动态定义 XML 元素。动态是非常重要的,这就意味着 XML 元素在编译时是动态生成的而非静态。 以下实例动态定义了 XML 元素: ``` <%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> +pageEncoding="UTF-8"%> - - -菜鸟教程(runoob.com) - - - - - 属性值 - - - XML 元素的主体 - - - + + + 菜鸟教程(runoob.com) + + + + + 属性值 + + + XML 元素的主体 + + + ``` @@ -1044,7 +943,7 @@ plugin 动作有多个对应 HTML 元素的属性用于格式化 Java 组件。p ]]> ``` -如果你需要在 XHTML 中声明 DOCTYPE,必须使用到动作元素,实例如下: +如果你需要在 XHTML 中声明 DOCTYPE,必须使用到 `` 动作元素,实例如下: ``` +``` + ``` JSP EL 允许您指定一个表达式来表示属性值。一个简单的表达式语法如下: -```jsp +``` ${expr} ``` 其中,expr 指的是表达式。在 JSP EL 中通用的操作符是"."和"[]"。这两个操作符允许您通过内嵌的 JSP 对象访问各种各样的 JavaBean 属性。 -举例来说,上面的标签可以使用表达式语言改写成如下形式: +举例来说,上面的 `` 标签可以使用表达式语言改写成如下形式: -```jsp - +``` + ``` 当 JSP 编译器在属性中见到"\${}"格式后,它会产生代码来计算这个表达式,并且产生一个替代品来代替表达式的值。 -您也可以在标签的模板文本中使用表达式语言。比如标签简单地将其主体中的文本插入到 JSP 输出中: +您也可以在标签的模板文本中使用表达式语言。比如 `` 标签简单地将其主体中的文本插入到 JSP 输出中: -```jsp +``` -

    Hello JSP!

    +

    Hello JSP!

    ``` 现在,在标签主体中使用表达式,就像这样: -```jsp +``` -Box Perimeter is: ${2*box.width + 2*box.height} + Box Perimeter is: ${2*box.width + 2*box.height} ``` @@ -1221,7 +1123,7 @@ Box Perimeter is: ${2*box.width + 2*box.height} 想要停用对 EL 表达式的评估的话,需要使用 page 指令将 isELIgnored 属性值设为 true: -```jsp +``` <%@ page isELIgnored ="true|false" %> ``` @@ -1256,13 +1158,13 @@ EL 表达式支持大部分 Java 所提供的算术和逻辑操作符: JSP EL 允许您在表达式中使用函数。这些函数必须被定义在自定义标签库中。函数的使用语法如下: -```jsp +``` ${ns:func(param1, param2, ...)} ``` ns 指的是命名空间(namespace),func 指的是函数的名称,param1 指的是第一个参数,param2 指的是第二个参数,以此类推。比如,有函数 fn:length,在 JSTL 库中定义,可以像下面这样来获取一个字符串的长度: -```jsp +``` ${fn:length("Get my length")} ``` @@ -1292,7 +1194,7 @@ JSP EL 支持下表列出的隐含对象: pageContext 对象是 JSP 中 pageContext 对象的引用。通过 pageContext 对象,您可以访问 request 对象。比如,访问 request 对象传入的查询字符串,就像这样: -```jsp +``` ${pageContext.request.queryString} ``` @@ -1310,23 +1212,21 @@ param 和 paramValues 对象用来访问参数值,通过使用 request.getPara 接下来的例子表明了如何访问 request 中的 username 参数: -```jsp -<%@ page import="java.io.*,java.util.*" %> -<% - String title = "Accessing Request Param"; -%> +``` +<%@ page import="java.io.*,java.util.*" %> <% String title = "Accessing Request +Param"; %> - -<% out.print(title); %> - - -
    -

    <% out.print(title); %>

    -
    -
    -

    ${param["username"]}

    -
    - + + <% out.print(title); %> + + +
    +

    <% out.print(title); %>

    +
    +
    +

    ${param["username"]}

    +
    + ``` @@ -1340,23 +1240,21 @@ header 和 headerValues 对象用来访问信息头,通过使用 request.getHe 接下来的例子表明了如何访问 user-agent 信息头: -```jsp -<%@ page import="java.io.*,java.util.*" %> -<% - String title = "User Agent Example"; -%> +``` +<%@ page import="java.io.*,java.util.*" %> <% String title = "User Agent +Example"; %> - -<% out.print(title); %> - - -
    -

    <% out.print(title); %>

    -
    -
    -

    ${header["user-agent"]}

    -
    - + + <% out.print(title); %> + + +
    +

    <% out.print(title); %>

    +
    +
    +

    ${header["user-agent"]}

    +
    + ``` @@ -1395,47 +1293,49 @@ Apache Tomcat 安装 JSTL 库步骤如下: 接下来我们在 web.xml 文件中添加以下配置: -```xml +``` - - + + - http://java.sun.com/jsp/jstl/fmt - /WEB-INF/fmt.tld + http://java.sun.com/jsp/jstl/fmt + /WEB-INF/fmt.tld - http://java.sun.com/jsp/jstl/fmt-rt - /WEB-INF/fmt-rt.tld + http://java.sun.com/jsp/jstl/fmt-rt + /WEB-INF/fmt-rt.tld - http://java.sun.com/jsp/jstl/core - /WEB-INF/c.tld + http://java.sun.com/jsp/jstl/core + /WEB-INF/c.tld - http://java.sun.com/jsp/jstl/core-rt - /WEB-INF/c-rt.tld + http://java.sun.com/jsp/jstl/core-rt + /WEB-INF/c-rt.tld - http://java.sun.com/jsp/jstl/sql - /WEB-INF/sql.tld + http://java.sun.com/jsp/jstl/sql + /WEB-INF/sql.tld - http://java.sun.com/jsp/jstl/sql-rt - /WEB-INF/sql-rt.tld + http://java.sun.com/jsp/jstl/sql-rt + /WEB-INF/sql-rt.tld - http://java.sun.com/jsp/jstl/x - /WEB-INF/x.tld + http://java.sun.com/jsp/jstl/x + /WEB-INF/x.tld - http://java.sun.com/jsp/jstl/x-rt - /WEB-INF/x-rt.tld + http://java.sun.com/jsp/jstl/x-rt + /WEB-INF/x-rt.tld - + ``` @@ -1471,8 +1371,7 @@ Apache Tomcat 安装 JSTL 库步骤如下: JSTL 格式化标签用来格式化并输出文本、日期、时间、数字。引用格式化标签库的语法如下: ``` -<%@ taglib prefix="fmt" - uri="http://java.sun.com/jsp/jstl/fmt" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> ``` | 标签 | 描述 | @@ -1493,9 +1392,8 @@ JSTL 格式化标签用来格式化并输出文本、日期、时间、数字。 JSTL SQL 标签库提供了与关系型数据库(Oracle,MySQL,SQL Server 等等)进行交互的标签。引用 SQL 标签库的语法如下: -```jsp -<%@ taglib prefix="sql" - uri="http://java.sun.com/jsp/jstl/sql" %> +``` +<%@ taglib prefix="sql" uri="http://java.sun.com/jsp/jstl/sql" %> ``` | 标签 | 描述 | @@ -1512,11 +1410,10 @@ JSTL SQL 标签库提供了与关系型数据库(Oracle,MySQL,SQL Server JSTL XML 标签库提供了创建和操作 XML 文档的标签。引用 XML 标签库的语法如下: ``` -<%@ taglib prefix="x" - uri="http://java.sun.com/jsp/jstl/xml" %> +<%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %> ``` -在使用 xml 标签前,你必须将 XML 和 XPath 的相关包拷贝至你的\lib 下: +在使用 xml 标签前,你必须将 XML 和 XPath 的相关包拷贝至你的 `\lib` 下: - XercesImpl.jar @@ -1544,8 +1441,7 @@ JSTL XML 标签库提供了创建和操作 XML 文档的标签。引用 XML 标 JSTL 包含一系列标准函数,大部分是通用的字符串处理函数。引用 JSTL 函数库的语法如下: ``` -<%@ taglib prefix="fn" - uri="http://java.sun.com/jsp/jstl/functions" %> +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> ``` | 函数 | 描述 | @@ -1581,33 +1477,24 @@ JSP 标签扩展可以让你创建新的标签并且可以直接插入到一个 接下来,我们想创建一个自定义标签叫作,标签格式为: -```jsp +``` ``` 要创建自定义的 JSP 标签,你首先必须创建处理标签的 Java 类。所以,让我们创建一个 HelloTag 类,如下所示: -```java -package com.runoob; - -import javax.servlet.jsp.tagext.*; -import javax.servlet.jsp.*; -import java.io.*; - -public class HelloTag extends SimpleTagSupport { - - public void doTag() throws JspException, IOException { - JspWriter out = getJspContext().getOut(); - out.println("Hello Custom Tag!"); - } -} +``` +package com.runoob; import javax.servlet.jsp.tagext.*; import +javax.servlet.jsp.*; import java.io.*; public class HelloTag extends +SimpleTagSupport { public void doTag() throws JspException, IOException { +JspWriter out = getJspContext().getOut(); out.println("Hello Custom Tag!"); } } ``` 以下代码重写了 doTag()方法,方法中使用了 getJspContext()方法来获取当前的 JspContext 对象,并将"Hello Custom Tag!"传递给 JspWriter 对象。 编译以上类,并将其复制到环境变量 CLASSPATH 目录中。最后创建如下标签库:`webapps\ROOT\WEB-INF\custom.tld`。 -```xml +``` 1.0 2.0 @@ -1622,14 +1509,14 @@ public class HelloTag extends SimpleTagSupport { 接下来,我们就可以在 JSP 文件中使用 Hello 标签: -```jsp +``` <%@ taglib prefix="ex" uri="WEB-INF/custom.tld"%> A sample custom tag - + ``` @@ -1646,7 +1533,7 @@ Hello Custom Tag! ``` - This is message body + This is message body ``` @@ -1674,7 +1561,7 @@ public class HelloTag extends SimpleTagSupport { 接下来我们需要修改 TLD 文件,如下所示: -```xml +``` 1.0 2.0 @@ -1689,7 +1576,7 @@ public class HelloTag extends SimpleTagSupport { 现在我们可以在 JSP 使用修改后的标签,如下所示: -```jsp +``` <%@ taglib prefix="ex" uri="WEB-INF/custom.tld"%> @@ -1697,7 +1584,7 @@ public class HelloTag extends SimpleTagSupport { - This is message body + This is message body @@ -1750,7 +1637,7 @@ public class HelloTag extends SimpleTagSupport { 属性的名称是"message",所以 setter 方法是的 setMessage()。现在让我们在 TLD 文件中使用的元素添加此属性: -```xml +``` 1.0 2.0 @@ -1760,7 +1647,7 @@ public class HelloTag extends SimpleTagSupport { com.runoob.HelloTag scriptless - message + message @@ -1768,7 +1655,7 @@ public class HelloTag extends SimpleTagSupport { 现在我们就可以在 JSP 文件中使用 message 属性了,如下所示: -```jsp +``` <%@ taglib prefix="ex" uri="WEB-INF/custom.tld"%> @@ -1799,31 +1686,31 @@ This is custom tag 以下是指定相关的属性实例: -```xml +``` ..... - - attribute_name - false - java.util.Date - false - + + attribute_name + false + java.util.Date + false + ..... ``` 如果你使用了两个属性,修改 TLD 文件,如下所示: -```xml +``` ..... - - attribute_name1 - false - java.util.Boolean - false - - - attribute_name2 - true - java.util.Date - + + attribute_name1 + false + java.util.Boolean + false + + + attribute_name2 + true + java.util.Date + ..... ``` diff --git a/docs/javaee/javaee-servlet.md b/docs/javaee/javaee-servlet.md index 6bd5adb0..5ba4cf8f 100644 --- a/docs/javaee/javaee-servlet.md +++ b/docs/javaee/javaee-servlet.md @@ -3,21 +3,21 @@ - [JavaWeb 简介](#javaweb-简介) - - [Web 应用程序](#web-应用程序) - - [常见 Web 服务器](#常见-web-服务器) + - [Web 应用程序](#web-应用程序) + - [常见 Web 服务器](#常见-web-服务器) - [Servlet 简介](#servlet-简介) - - [什么是 Servlet?](#什么是-servlet) - - [Servlet 和 CGI 的区别?](#servlet-和-cgi-的区别) - - [Servlet 版本以及主要特性?](#servlet-版本以及主要特性) - - [Servlet 任务](#servlet-任务) - - [Servlet 生命周期](#servlet-生命周期) + - [什么是 Servlet](#什么是-servlet) + - [Servlet 和 CGI 的区别](#servlet-和-cgi-的区别) + - [Servlet 版本以及主要特性](#servlet-版本以及主要特性) + - [Servlet 任务](#servlet-任务) + - [Servlet 生命周期](#servlet-生命周期) - [Servlet API](#servlet-api) - - [Servlet 包](#servlet-包) - - [Servlet 接口](#servlet-接口) + - [Servlet 包](#servlet-包) + - [Servlet 接口](#servlet-接口) - [Servlet 和 HTTP 状态码](#servlet-和-http-状态码) - - [HTTP 状态码](#http-状态码) - - [设置 HTTP 状态码的方法](#设置-http-状态码的方法) - - [HTTP 状态码实例](#http-状态码实例) + - [HTTP 状态码](#http-状态码) + - [设置 HTTP 状态码的方法](#设置-http-状态码的方法) + - [HTTP 状态码实例](#http-状态码实例) @@ -47,7 +47,7 @@ Internet 上供外界访问的 Web 资源分为: ## Servlet 简介 -### 什么是 Servlet? +### 什么是 Servlet Servlet(Server Applet),即小服务程序或服务连接器。Servlet 是 Java 编写的服务器端程序,具有独立于平台和协议的特性,主要功能在于交互式地浏览和生成数据,生成动态 Web 内容。 @@ -56,14 +56,14 @@ Servlet(Server Applet),即小服务程序或服务连接器。Servlet 是 Servlet 运行于支持 Java 的应用服务器中。从原理上讲,Servlet 可以响应任何类型的请求,但绝大多数情况下 Servlet 只用来扩展基于 HTTP 协议的 Web 服务器。 -### Servlet 和 CGI 的区别? +### Servlet 和 CGI 的区别 Servlet 技术出现之前,Web 主要使用 CGI 技术。它们的区别如下: - Servlet 是基于 Java 编写的,处于服务器进程中,他能够通过多线程方式运行 service() 方法,一个实例可以服务于多个请求,而且一般不会销毁; - CGI(Common Gateway Interface),即通用网关接口。它会为每个请求产生新的进程,服务完成后销毁,所以效率上低于 Servlet。 -### Servlet 版本以及主要特性? +### Servlet 版本以及主要特性 | 版本 | 日期 | JAVA EE/JDK 版本 | 特性 | | ----------- | ------------- | ------------------ | --------------------------------------------------------------------- | @@ -206,7 +206,7 @@ HTTP 请求和 HTTP 响应消息的格式是类似的,结构如下: 例如,服务器的响应头如下所示: -``` +```http HTTP/1.1 200 OK Content-Type: text/html Header2: ... @@ -287,7 +287,7 @@ public class showError extends HttpServlet { 现在,调用上面的 Servlet 将显示以下结果: -``` +```http HTTP Status 407 - Need authentication!!! type Status report message Need authentication!!! diff --git a/docs/package.json b/docs/package.json index aedcd063..879b2c78 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,7 +1,41 @@ { - "name": "javaweb", - "version": "1.0.0", - "scripts": { - "start": "docsify serve ./ --port 4000" - } + "name": "javatech", + "version": "1.0.0", + "private": true, + "scripts": { + "clean": "rimraf dist && rimraf .temp", + "build": "npm run clean && vuepress build ./ --temp .temp", + "dev": "vuepress dev ./ --temp .temp", + "lint-md": "npm run lint-md:style && npm run lint-md:wording", + "lint-md:style": "remark --quiet --frail .", + "lint-md:wording": "textlint ./**/*.md", + "show-help": "vuepress --help", + "view-info": "vuepress view-info ./ --temp .temp", + "deploy": "gh-pages -d dist" + }, + "devDependencies": { + "@textlint-rule/textlint-rule-no-unmatched-pair": "^1.0.7", + "@vuepress/plugin-back-to-top": "^1.3.0", + "@vuepress/plugin-medium-zoom": "^1.3.0", + "@vuepress/plugin-pwa": "^1.3.0", + "@vuepress/theme-vue": "^1.3.0", + "gh-pages": "^2.2.0", + "remark-cli": "^7.0.0", + "remark-lint": "^6.0.5", + "remark-preset-lint-consistent": "^2.0.3", + "remark-preset-lint-recommended": "^3.0.3", + "rimraf": "^3.0.1", + "textlint": "^11.3.1", + "textlint-filter-rule-comments": "^1.2.2", + "textlint-rule-apostrophe": "^1.0.0", + "textlint-rule-common-misspellings": "^1.0.1", + "textlint-rule-diacritics": "^1.0.0", + "textlint-rule-en-capitalization": "^2.0.2", + "textlint-rule-stop-words": "^1.0.17", + "textlint-rule-terminology": "^1.1.30", + "textlint-rule-write-good": "^1.6.2", + "vue-toasted": "^1.1.25", + "vuepress": "^1.3.0", + "vuepress-plugin-flowchart": "^1.4.2" + } } diff --git a/docs/sidebar.md b/docs/sidebar.md deleted file mode 100644 index 6a50ff01..00000000 --- a/docs/sidebar.md +++ /dev/null @@ -1,68 +0,0 @@ -# JavaWeb - -- [**🎨 架构设计**](https://github.com/dunwu/blog/tree/master/source/_posts/design/architecture) - - [系统架构面试题](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/system-architecture-interview.md) - - [分布式技术实现](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/system-core-technologies.md) - - [分布式架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/分布式架构.md) - - [大型分布式网站架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/大型分布式网站架构.md) - - [系统架构概述](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/system-architecture-overview.md) - - [网站典型故障](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/网站典型故障.md) - - [伸缩性架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/scalable-architecture.md) - - [扩展性架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/extensible-architecture.md) - - [安全性架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/secure-architecture.md) - - [高可用架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/high-availability-architecture.md) - - [高性能架构](https://github.com/dunwu/blog/blob/master/source/_posts/design/architecture/high-performance-architecture.md) -- [✨ 系统原理](https://github.com/dunwu/blog/tree/master/source/_posts/design/theory) - - [系统原理面试题](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/system-theory-interview.md) - - [分布式基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/distributed-base-theory.md) - - [负载均衡基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/load-balance-theory.md) - - [缓存基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/cache-theory.md) - - [消息队列基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/mq-theory.md) - - [分布式锁基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/distributed-lock-theory.md) - - [分布式会话基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/distributed-session-theory.md) - - [分布式存储基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/distributed-storage-theory.md) - - [分布式 ID 基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/distributed-id-theory.md) - - [分布式事务基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/distributed-transaction-theory.md) - - [分库分表基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/sharding-theory.md) - - [常见安全手段基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/design/theory/security-theory.md) -- [**⭐ 主流技术**](technology/README.md) - - Web 框架 - - [Spring 教程 📚](https://dunwu.github.io/spring-tutorial/) - - [Spring Boot 教程 📚](https://dunwu.github.io/spring-boot-tutorial/) - - 服务器 - - [Tomcat 应用指南](technology/server/Tomcat.md) - - [Jetty 应用指南](technology/server/Jetty.md) - - [Nginx 简易教程 📚](https://github.com/dunwu/nginx-tutorial) - - 消息队列 - - [消息队列面经](technology/mq/MqInterview.md) - - [Kafka 应用指南基础篇](technology/mq/kafka-basic.md) - - [Kafka 应用指南进阶篇](technology/mq/kafka-advance.md) - - [Kafka 运维指南](technology/mq/kafka-ops.md) - - [RocketMQ 基础篇](technology/mq/RocketmqBasics.md) - - [RocketMQ 进阶篇](technology/mq/RocketmqAdvanced.md) - - [ActiveMQ 实战篇](technology/mq/ActiveMQ.md) - - 缓存 - - [缓存面经](technology/cache/CacheInterview.md) - - [Redis](technology/cache/Redis.md) - - [Ehcache](technology/cache/Ehcache.md) - - [Caffeine](technology/cache/Caffeine.md) - - RPC - - [Dubbo 应用指南](technology/distributed/dubbo.md) - - Spring Cloud - - 安全 - - [Shiro 应用指南](technology/security/shiro.md) - - [Spring Security 应用指南](technology/security/spring-security.md) - - 数据 - - [Mybatis 应用指南](technology/data/mybatis.md) - - [ShardingSphere 应用指南](technology/data/ShardingSphere.md) - - 分布式 - - [ZooKeeper 应用指南](technology/distributed/zookeeper.md) - - [ZooKeeper 运维指南](technology/distributed/zookeeper-ops.md) - - 监控/诊断 - - [Arthas 应用指南](technology/monitor/arthas.md) -- [**☕ JavaEE**](javaee/README.md) - - [JavaEE 面经](javaee/javaee-interview.md) - - [JavaEE 之 Servlet 指南](javaee/javaee-servlet.md) - - [JavaEE 之 Jsp 指南](javaee/javaee-jsp.md) - - [JavaEE 之 Filter 和 Listener](javaee/javaee-filter-listener.md) - - [JavaEE 之 Cookie 和 Session](javaee/javaee-cookie-sesion.md) diff --git a/docs/tool/README.md b/docs/tool/README.md new file mode 100644 index 00000000..e52d37bf --- /dev/null +++ b/docs/tool/README.md @@ -0,0 +1,21 @@ +# Java 工具 + +> 本部分内容主要是 Java 开发领域使用的一些 Java 工具,如构建工具、IDE、服务器、日志中心等等。 + +## 构建 + +> [构建工具](build/README.md) + +- [Maven 快速指南](build/maven/maven-quickstart.md) +- [Maven 教程之 pom.xml 详解](build/maven/maven-pom.md) +- [Maven 教程之 settings.xml 详解](build/maven/maven-settings.md) +- [Maven 实战问题和最佳实践](build/maven/maven-action.md) +- [Maven 教程之发布 jar 到私服或中央仓库](build/maven/maven-deploy.md) +- [Ant](build/ant.md) + +## IDE + +> [Java IDE](ide/README.md) + +- [IDEA](ide/intellij-idea.md) +- [Eclipse](ide/eclipse.md) diff --git a/docs/tool/ide/README.md b/docs/tool/ide/README.md new file mode 100644 index 00000000..bf58c9a8 --- /dev/null +++ b/docs/tool/ide/README.md @@ -0,0 +1,12 @@ +# Java IDE + + +> 自动有了 **IDE**,写代码从此就告别了刀耕火种的蛮荒时代。 +> +> - [Eclipse](eclipse.md) 是久负盛名的开源 Java IDE,我的学生时代一直使用它写 Java。 +> - 曾经抗拒从转 [Intellij Idea](intellij-idea.md) ,但后来发现真香,不得不说,确实是目前最优秀的 Java IDE。 +> - 你可以在 [vscode](vscode.md) 中写各种语言,只要安装相应插件即可。如果你的项目中使用了很多种编程语言,又懒得在多个 IDE 之间切换,那么就用 vscode 来一网打尽吧。 + +- [Intellij IDEA 应用指南](intellij-idea.md) +- [Eclipse 应用指南](eclipse.md) +- [Vscode 应用指南](vscode.md) diff --git a/docs/tool/ide/eclipse.md b/docs/tool/ide/eclipse.md new file mode 100644 index 00000000..15a6ca98 --- /dev/null +++ b/docs/tool/ide/eclipse.md @@ -0,0 +1,233 @@ +# Eclipse 应用指南 + +## 代码智能提示 + +### Java 智能提示 + +Window -> Preferences -> Java -> Editor -> Content Assist -> Auto Activation + +![img](http://upload-images.jianshu.io/upload_images/3101171-1a0d2206870c6542.jpg) + +delay 是自动弹出提示框的延时时间,我们可以修改成 100 毫秒;triggers 这里默认是".",只要加上"abcdefghijklmnopqrstuvwxyz"或者"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",嘿嘿!这下就能做到和 VS 一样的输入每个字母都能提示啦: + +![img](http://upload-images.jianshu.io/upload_images/3101171-5eb9ad6afbe05289.jpg) + +其它类型的文件比如 HTML、JavaScript、JSP 如果也能提供提示那不是更爽了?有了第二点设置的基础,其实这些设置都是一样的。 + +### JavaScript 智能提示 + +Window -> Preferences -> JavaScript-> Editor -> Content Assist -> Auto-Activation + +![img](http://upload-images.jianshu.io/upload_images/3101171-31bca3ed1b0d0050.jpg) + +### HTML 智能提示 + +Window -> Preferences -> Web -> HTML Files -> Editor -> Content Assist -> Auto-Activation + +![img](http://upload-images.jianshu.io/upload_images/3101171-7c1ce7a35793f234.jpg) + +保存后,我们再来输入看看,感觉真是不错呀: + +![img](http://images2015.cnblogs.com/blog/318837/201605/318837-20160519135330638-166066199.jpg) + +## 插件安装 + +很多教科书上说到 Eclipse 的插件安装都是通过 Help -> Install New SoftWare 这种自动检索的方式,操作起来固然是方便,不过当我们不需要某种插件时不太容易找到要删除哪些内容,而且以后 Eclipse 版本升级的时候,通过这种方式安装过的插件都得再重新装一次。另外一种通过 Link 链接方式,就可以解决这些问题。 + +我们以 Eclipse 的中文汉化包插件为例,先到官方提供的汉化包地址下载一个:[http://www.eclipse.org/babel/downloads.php](http://www.eclipse.org/babel/downloads.php),注意选好自己的 Eclipse 版本: + +![img](http://upload-images.jianshu.io/upload_images/3101171-d8a662eaba3550e3.jpg) + +我的版本是 Kepler,然后进入下载页面,单击红框框中的链接,即可下载汉化包了: + +![img](http://upload-images.jianshu.io/upload_images/3101171-3544d5393f4e298f.jpg) + +下载完解压缩后,会有个包含 features 和 plugin 目录的 eclipse 文件夹,把这个 eclipse 放在我们的 Eclipse 安装根目录,也就是和 eclipse.exe 同一级目录下。然后仍然在这一级目录下,新建一个 links 文件夹,并在该文件夹内,建一个 language.link 的文本文件。该文本文件的名字是可以任取的,后缀名是.link,而不是.txt 哟。好了,最后一步,编辑该文件,在里面写入刚才放入的语言包的地址,并用“\\”表示路径,一定要有 path= 这个前缀。 + +![img](http://upload-images.jianshu.io/upload_images/3101171-18f74bf3080d2c1b.jpg) + +保存文件后,重新打开 Eclipse,熟悉的中文界面终于看到了。虽然汉化不完全,不过也够用了不是么。如果仍然出现的是英文,说明汉化失败,重新检查下 language.link 文件中配置的信息是否和汉化包的目录一致。  其它的插件安装方法也是如此,当不需要某个插件时,只需删除存放插件的目录和 links 目录下相应的 link 文件,或者改变下 link 文件里面的路径变成无效路径即可;对 Eclipse 做高版本升级时,也只需把老版存放插件的目录和 links 目录复制过去就行了。 + +## 基本设置 + +在 Preference 的搜索项中搜索 Text Editors。 +可以参考我的设置: +Show line numbers +Show print margin +Insert spaces for tabs + +![img](http://upload-images.jianshu.io/upload_images/3101171-91aa025fbe7592f8.png) +设置代码的字体类型和大小: + +Window -> Preferences -> General -> Appearance -> Content Assist -> Colors and Fornts,只需修改 Basic 里面的 Text Font 就可以了。 + +推荐 Courier New。 + +![img](http://upload-images.jianshu.io/upload_images/3101171-49b9126aa0dd58c0.jpg) + +![img](http://upload-images.jianshu.io/upload_images/3101171-b7d6d71b2c211321.jpg) + +## 设置文本文件及 JSP 文件编码 + +Window -> Preferences -> General -> Workspace -> Text file encoding -> Other: + +![img](http://upload-images.jianshu.io/upload_images/3101171-b7aa010673565c88.jpg) + +![img](http://upload-images.jianshu.io/upload_images/3101171-b83fa3476fddde46.jpg) + +## 设置 JDK 本地 JavaDOC API 路径及源码路径 + +![img](http://upload-images.jianshu.io/upload_images/3101171-45b5dee3d3ce917a.jpg) + +![img](http://upload-images.jianshu.io/upload_images/3101171-f960daf4839662e3.jpg) + +还都生成的是无意义的变量名,这样可能会对含有相同类型的变量参数的调用顺序造成干扰; + +这种问题,我们把 JDK 或者相应 Jar 包的源码导入进去就能避免了: + +Window -> Preferences -> Java -> Installed JREs -> Edit: + +![img](http://upload-images.jianshu.io/upload_images/3101171-a08c9166dbbf4361.jpg) + +选中设置好的 JRE 目录,编辑,然后全选 JRE system libraries 下的所有 Jar 包,点击右边的 Source Attachment; + +![img](http://upload-images.jianshu.io/upload_images/3101171-4e6c78afa8e3e50b.jpg) + +External location 下,选中 JDK 安装目录下的 src.zip 文件,一路 OK 下来。 + +![img](http://upload-images.jianshu.io/upload_images/3101171-e82d03ce88f64312.jpg) + +设置完,我们再来看看,幸福来的好突然有木有! + +![img](http://upload-images.jianshu.io/upload_images/3101171-400b3952aa60cb8e.jpg) + +## 设置 Servlet 源码或其它 Jar 包源码 + +![img](http://upload-images.jianshu.io/upload_images/3101171-ec1980f58297e42c.jpg) + +上一步已经设置过了 JDK 的源码或 JavaDoc 路径,为啥现在又出来了呢?其实这个不难理解,因为我们使用到的类的源码并不在 JDK 的源码包中。 + +仔细看,我们会发现这些 Jar 包其实都在 Tomcat 根目录下的 lib 文件夹中,但是翻遍了 Tomcat 目录也没有相应的 jar 或 zip 文件呀。既然本地没有,那就去官网上找找: + +http://tomcat.apache.org/download-70.cgi这里有Tomcat的安装包和源码包; + +![img](http://upload-images.jianshu.io/upload_images/3101171-2962b9cd48422373.jpg) + +可以自定义一个专门用于存放 JavaSource 和 JavaDoc 的文件夹,把下载文件放到该目录下, + +然后再切换到 Eclipse 下,选中没有代码提示的类或者函数, 按下 F3,点击 Change Attached Source: + +![img](http://upload-images.jianshu.io/upload_images/3101171-e8c7cc17364206cf.jpg) + +选择我们刚才下载好的 tomcat 源码文件,一路 OK。 + +![img](http://upload-images.jianshu.io/upload_images/3101171-138cb66d553d9306.jpg) + +然后再回过头看看我们的代码提示,友好多了: + +![img](http://upload-images.jianshu.io/upload_images/3101171-b27652e5ff1d6eab.jpg) + +其它 Jar 包源码的设置方式也一样。 + +## 反编译插件 JD-Eclipse + +无论是开发还是调试,反编译必不可少,每次都用 jd-gui 打开去看,多麻烦,干脆配置下 JD 插件,自动关联.class: + +先从 http://jd.benow.ca/ 上下载离线安装包 jdeclipse_update_site.zip,解压缩后把 features、plugins 这 2 个文件夹复制到 新建文件夹 jdeclipse,然后把 jdeclipse 文件夹整个复制到 Eclipse 根目录的 dropins 文件夹下,重启 Eclipse 即可。这种方式是不是比建 link 文件更方便了? + +![img](http://upload-images.jianshu.io/upload_images/3101171-403597b1b46607ef.jpg) + +打开 Eclipse,Window -> Preferences -> General - > Editors ,把 .class 文件设置关联成 jd 插件的 editor + +![img](http://upload-images.jianshu.io/upload_images/3101171-d95625fcf362526c.jpg) + +## Validate 优化 + +我们在 eclipse 里经常看到这个进程,validating... 逐个的检查每一个文件。那么如何关闭一些 validate 操作呢? + +![img](http://upload-images.jianshu.io/upload_images/3101171-8323d6f595f96fdd.png) + +打开 eclipse,点击【window】菜单,选择【preferences】选项。 + +![img](http://upload-images.jianshu.io/upload_images/3101171-88bd81ece1b3f29f.png) + +在左侧点击【validation】选项,在右侧可以看到 eclipse 进行的自动检查都有哪些内容。 + +![img](http://upload-images.jianshu.io/upload_images/3101171-0c3cf67c6e954dd6.png) + +将 Manual(手动)保持不动,将 build 里面只留下 classpath dependency Validator,其他的全部去掉。 + +![img](http://upload-images.jianshu.io/upload_images/3101171-e1a3051fde4828d8.png) + +最后点击【OK】按钮,保存设置。 + +![img](http://upload-images.jianshu.io/upload_images/3101171-7c6803eed8cf618a.png) + +以后如果需要对文件进行校验检查的时候,在文件上点击右键,点击【Validate】进行检查。 + +## 常用快捷键 + +| 快捷键 | 描述 | +| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Ctrl+1 | 快速修复(最经典的快捷键,就不用多说了,可以解决很多问题,比如 import 类、try catch 包围等) | +| Ctrl+Shift+F | 格式化当前代码 | +| Ctrl+Shift+M | 添加类的 import 导入 | +| Ctrl+Shift+O | 组织类的 import 导入(既有 Ctrl+Shift+M 的作用,又可以帮你去除没用的导入,很有用) | +| Ctrl+Y | 重做(与撤销 Ctrl+Z 相反) | +| Alt+/ | 内容辅助(帮你省了多少次键盘敲打,太常用了) | +| Ctrl+D | 删除当前行或者多行 | +| Alt+↓ | 当前行和下面一行交互位置(特别实用,可以省去先剪切,再粘贴了) | +| Alt+↑ | 当前行和上面一行交互位置(同上) | +| Ctrl+Alt+↓ | 复制当前行到下一行(复制增加) | +| Ctrl+Alt+↑ | 复制当前行到上一行(复制增加) | +| Shift+Enter | 在当前行的下一行插入空行(这时鼠标可以在当前行的任一位置,不一定是最后) | +| Ctrl+/ | 注释当前行,再按则取消注释 | +| Alt+Shift+↑ | 选择封装元素 | +| Alt+Shift+← | 选择上一个元素 | +| Alt+Shift+→ | 选择下一个元素 | +| Shift+← | 从光标处开始往左选择字符 | +| Shift+→ | 从光标处开始往右选择字符 | +| Ctrl+Shift+← | 选中光标左边的单词 | +| Ctrl+Shift+→ | 选中光标又边的单词 | +| Ctrl+← | 光标移到左边单词的开头,相当于 vim 的 b | +| Ctrl+→ | 光标移到右边单词的末尾,相当于 vim 的 e | +| Ctrl+K | 参照选中的 Word 快速定位到下一个(如果没有选中 word,则搜索上一次使用搜索的 word) | +| Ctrl+Shift+K | 参照选中的 Word 快速定位到上一个 | +| Ctrl+J | 正向增量查找(按下 Ctrl+J 后,你所输入的每个字母编辑器都提供快速匹配定位到某个单词,如果没有,则在状态栏中显示没有找到了,查一个单词时,特别实用,要退出这个模式,按 escape 建) | +| Ctrl+Shift+J | 反向增量查找(和上条相同,只不过是从后往前查) | +| Ctrl+Shift+U | 列出所有包含字符串的行 | +| Ctrl+H | 打开搜索对话框 | +| Ctrl+G | 工作区中的声明 | +| Ctrl+Shift+G | 工作区中的引用 | +| Ctrl+Shift+T | 搜索类(包括工程和关联的第三 jar 包) | +| Ctrl+Shift+R | 搜索工程中的文件 | +| Ctrl+E | 快速显示当前 Editer 的下拉列表(如果当前页面没有显示的用黑体表示) | +| F4 | 打开类型层次结构 | +| F3 | 跳转到声明处 | +| Alt+← | 前一个编辑的页面 | +| Alt+→ | 下一个编辑的页面(当然是针对上面那条来说了) | +| Ctrl+PageUp/PageDown | 在编辑器中,切换已经打开的文件 | +| F5 | 单步跳入 | +| F6 | 单步跳过 | +| F7 | 单步返回 | +| F8 | 继续 | +| Ctrl+Shift+D | 显示变量的值 | +| Ctrl+Shift+B | 在当前行设置或者去掉断点 | +| Ctrl+R | 运行至行(超好用,可以节省好多的断点) | +| Alt+Shift+R | 重命名方法名、属性或者变量名 (是我自己最爱用的一个了,尤其是变量和类的 Rename,比手工方法能节省很多劳动力) | +| Alt+Shift+M | 把一段函数内的代码抽取成方法 (这是重构里面最常用的方法之一了,尤其是对一大堆泥团代码有用) | +| Alt+Shift+C | 修改函数结构(比较实用,有 N 个函数调用了这个方法,修改一次搞定) | +| Alt+Shift+L | 抽取本地变量( 可以直接把一些魔法数字和字符串抽取成一个变量,尤其是多处调用的时候) | +| Alt+Shift+F | 把 Class 中的 local 变量变为 field 变量 (比较实用的功能) | +| Alt+Shift+I | 合并变量(可能这样说有点不妥 Inline) | +| Alt+Shift+V | 移动函数和变量(不怎么常用) | +| Alt+Shift+Z | 重构的后悔药(Undo) | +| Alt+Enter | 显示当前选择资源的属性,windows 下的查看文件的属性就是这个快捷键,通常用来查看文件在 windows 中的实际路径 | +| Ctrl+↑ | 文本编辑器 上滚行 | +| Ctrl+↓ | 文本编辑器 下滚行 | +| Ctrl+M | 最大化当前的 Edit 或 View (再按则反之) | +| Ctrl+O | 快速显示 OutLine | +| Ctrl+T | 快速显示当前类的继承结构 | +| Ctrl+W | 关闭当前 Editer | +| Ctrl+L | 文本编辑器 转至行 | +| F2 | 显示工具提示描述 | diff --git a/docs/tool/ide/intellij-idea.md b/docs/tool/ide/intellij-idea.md new file mode 100644 index 00000000..e1bac2d4 --- /dev/null +++ b/docs/tool/ide/intellij-idea.md @@ -0,0 +1,269 @@ +# Intellij IDEA 应用指南 + +## 快捷键 + +### 核心快捷键 + +IntelliJ IDEA 作为一个以快捷键为中心的 IDE,为大多数操作建议了键盘快捷键。在这个主题中,您可以找到最不可缺少的列表,使 IntelliJ IDEA 轻松实现第一步。 + +核心快捷键表: + +| 操作 | 快捷键 | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------- | +| [根据名称查找操作](https://www.jetbrains.com/help/idea/navigating-to-action.html) | `Ctrl+Shift+A` | +| 显示可用 [意图操作](https://www.jetbrains.com/help/idea/intention-actions.html) 列表 | `Alt+Enter` | +| 切换视图 ([Project](https://www.jetbrains.com/help/idea/project-tool-window.html),[Structure](https://www.jetbrains.com/help/idea/structure-tool-window-file-structure-popup.html), etc.). | `Alt+F1` | +| [切换](https://www.jetbrains.com/help/idea/navigating-between-files-and-tool-windows.html)工具窗口和在编辑器中打开的文件 | `Ctrl+Tab` | +| 显示 [导航栏](https://www.jetbrains.com/help/idea/navigation-bar.html). | `Alt+Home` | +| [插入代码模板](https://www.jetbrains.com/help/idea/generating-code.html). | `Ctrl+J` | +| [在周围插入代码模板](https://www.jetbrains.com/help/idea/creating-code-constructs-using-surround-templates.html). | `Ctrl+Alt+J` | +| [Edit an item from the Project or another tree view](https://www.jetbrains.com/help/idea/opening-and-reopening-files-in-the-editor.html). | `F4` | +| [注释](https://www.jetbrains.com/help/idea/commenting-and-uncommenting-blocks-of-code.html) | `Ctrl+/` `Ctrl+Shift+/` | +| [根据名称查找类或文件](https://www.jetbrains.com/help/idea/navigating-to-class-file-or-symbol-by-name.html). | `Ctrl+N` `Ctrl+Shift+N` | +| [拷贝当前行或指定的行](https://www.jetbrains.com/help/idea/adding-deleting-and-moving-code-elements.html#duplicate). | `Ctrl+D` | +| [增加或减少选中的表达式](https://www.jetbrains.com/help/idea/selecting-text-in-the-editor.html). | `Ctrl+W` and `Ctrl+Shift+W` | +| [在当前文件查找或替换](https://www.jetbrains.com/help/idea/finding-and-replacing-text-in-file.html). | `Ctrl+F` `Ctrl+R` | +| [在项目中或指定的目录中查找或替换](https://www.jetbrains.com/help/idea/finding-and-replacing-text-in-project.html) | `Ctrl+Shift+F` `Ctrl+Shift+R` | +| [全局搜索](https://www.jetbrains.com/help/idea/searching-everywhere.htmls) | 双击 `Shift` | +| [快速查看选中对象的引用](https://www.jetbrains.com/help/idea/highlighting-usages.html). | `Ctrl+Shift+F7` | +| [展开或折叠编辑器中的代码块](https://www.jetbrains.com/help/idea/code-folding.html). | `Ctrl+NumPad Plus` `Ctrl+NumPad -` | +| [调用代码完成](https://www.jetbrains.com/help/idea/auto-completing-code.html#basic_completion). | `Ctrl+Space` | +| [智能声明完成](https://www.jetbrains.com/help/idea/auto-completing-code.html#statements_completion). | `Ctrl+Shift+Enter` | +| [智能补全代码](https://www.jetbrains.com/help/idea/auto-completing-code.html#smart_completion) | `Ctrl+Shift+Space` | +| 显示可用的[重构](https://www.jetbrains.com/help/idea/refactoring-source-code.html)方法列表 | `Ctrl+Shift+Alt+T` | + +### 快捷键分类 + +#### Tradition + +| 快捷键 | 介绍 | +| ---------------- | ------------------------------- | +| Ctrl + Z | 撤销 | +| Ctrl + Shift + Z | 取消撤销 | +| Ctrl + X | 剪切 | +| Ctrl + C | 复制 | +| Ctrl + S | 保存 | +| Tab | 缩进 | +| Shift + Tab | 取消缩进 | +| Shift + Home/End | 选中光标到当前行头位置/行尾位置 | +| Ctrl + Home/End | 跳到文件头/文件尾 | + +#### Editing + +| 快捷键 | 介绍 | +| ----------------------- | -------------------------------------------------------------------------------------------------------- | +| Ctrl + Space | 基础代码补全,默认在 Windows 系统上被输入法占用,需要进行修改,建议修改为 Ctrl + 逗号`(必备)` | +| Ctrl + Alt + Space | 类名自动完成 | +| Ctrl + Shift + Enter | 自动结束代码,行末自动添加分号`(必备)` | +| Ctrl + P | 方法参数提示显示 | +| Ctrl + Q | 光标所在的变量/类名/方法名等上面(也可以在提示补充的时候按),显示文档内容 | +| Shift + F1 | 如果有外部文档可以连接外部文档 | +| Ctrl + F1 | 在光标所在的错误代码处显示错误信息`(必备)` | +| Alt + Insert | 代码自动生成,如生成对象的 set/get 方法,构造函数,toString() 等`(必备)` | +| Ctrl + O | 选择可重写的方法 | +| Ctrl + I | 选择可继承的方法 | +| Ctrl + Alt + T | 对选中的代码弹出环绕选项弹出层`(必备)` | +| Ctrl + / | 注释光标所在行代码,会根据当前不同文件类型使用不同的注释符号`(必备)` | +| Ctrl + Shift + / | 代码块注释`(必备)` | +| Ctrl + W | 递进式选择代码块。可选中光标所在的单词或段落,连续按会在原有选中的基础上再扩展选中范围`(必备)` | +| Ctrl + Shift + W | 递进式取消选择代码块。可选中光标所在的单词或段落,连续按会在原有选中的基础上再扩展取消选中范围`(必备)` | +| Alt + Q | 弹出一个提示,显示当前类的声明/上下文信息 | +| Alt + Enter | IntelliJ IDEA 根据光标所在问题,提供快速修复选择,光标放在的位置不同提示的结果也不同`(必备)` | +| Ctrl + Alt + L | 格式化代码,可以对当前文件和整个包目录使用`(必备)` | +| Ctrl + Alt + O | 优化导入的类,可以对当前文件和整个包目录使用`(必备)` | +| Ctrl + Alt + I | 光标所在行 或 选中部分进行自动代码缩进,有点类似格式化 | +| Ctrl + Shift + C | 复制当前文件磁盘路径到剪贴板`(必备)` | +| Ctrl + Shift + V | 弹出缓存的最近拷贝的内容管理器弹出层 | +| Ctrl + Alt + Shift + C | 复制参考信息 | +| Ctrl + Alt + Shift + V | 无格式黏贴`(必备)` | +| Ctrl + D | 复制光标所在行 或 复制选择内容,并把复制内容插入光标位置下面`(必备)` | +| Ctrl + Y | 删除光标所在行 或 删除选中的行`(必备)` | +| Ctrl + Shift + J | 自动将下一行合并到当前行末尾`(必备)` | +| Shift + Enter | 开始新一行。光标所在行下空出一行,光标定位到新行位置`(必备)` | +| Ctrl + Shift + U | 对选中的代码进行大/小写轮流转换`(必备)` | +| Ctrl + Shift + ]/[ | 选中从光标所在位置到它的底部/顶部的中括号位置`(必备)` | +| Ctrl + Delete | 删除光标后面的单词或是中文句`(必备)` | +| Ctrl + BackSpace | 删除光标前面的单词或是中文句`(必备)` | +| Ctrl + +/- | 展开/折叠代码块 | +| Ctrl + Shift + +/- | 展开/折叠所有代码`(必备)` | +| Ctrl + F4 | 关闭当前编辑文件 | +| Ctrl + Shift + Up/Down | 光标放在方法名上,将方法移动到上一个/下一个方法前面,调整方法排序`(必备)` | +| Alt + Shift + Up/Down | 移动光标所在行向上移动/向下移动`(必备)` | +| Ctrl + Shift + 左键单击 | 把光标放在某个类变量上,按此快捷键可以直接定位到该类中`(必备)` | +| Alt + Shift + 左键双击 | 选择被双击的单词/中文句,按住不放,可以同时选择其他单词/中文句`(必备)` | +| Ctrl + Shift + T | 对当前类生成单元测试类,如果已经存在的单元测试类则可以进行选择`(必备)` | + +#### Search/Replace + +| 快捷键 | 介绍 | +| ---------------- | -------------------------------------------------------------------- | +| Double Shift | 弹出 Search Everywhere 弹出层 | +| F3 | 在查找模式下,定位到下一个匹配处 | +| Shift + F3 | 在查找模式下,查找匹配上一个 | +| Ctrl + F | 在当前文件进行文本查找`(必备)` | +| Ctrl + R | 在当前文件进行文本替换`(必备)` | +| Ctrl + Shift + F | 根据输入内容查找整个项目 或 指定目录内文件`(必备)` | +| Ctrl + Shift + R | 根据输入内容替换对应内容,范围为整个项目 或 指定目录内文件`(必备)` | + +#### Usage Search + +| 快捷键 | 介绍 | +| ----------------- | -------------------------------------------------------------------- | +| Alt + F7 | 查找光标所在的方法/变量/类被调用的地方 | +| Ctrl + Alt + F7 | 显示使用的地方。寻找被该类或是变量被调用的地方,用弹出框的方式找出来 | +| Ctrl + Shift + F7 | 高亮显示所有该选中文本,按 Esc 高亮消失`(必备)` | + +#### Compile and Run + +| 快捷键 | 介绍 | +| ----------------- | ------------------------ | +| Ctrl + F9 | 执行 Make Project 操作 | +| Ctrl + Shift + F9 | 编译选中的文件/包/Module | +| Shift + F9 | Debug | +| Shift + F10 | Run | +| Alt + Shift + F9 | 弹出 Debug 的可选择菜单 | +| Alt + Shift + F10 | 弹出 Run 的可选择菜单 | + +#### Debugging + +| 快捷键 | 介绍 | +| ----------------- | ------------------------------------------------------------------------------------------------------------------------- | +| F7 | 在 Debug 模式下,进入下一步,如果当前行断点是一个方法,则进入当前方法体内,如果该方法体还有方法,则不会进入该内嵌的方法中 | +| F8 | 在 Debug 模式下,进入下一步,如果当前行断点是一个方法,则不进入当前方法体内 | +| Shift + F7 | 在 Debug 模式下,智能步入。断点所在行上有多个方法调用,会弹出进入哪个方法 | +| Shift + F8 | 在 Debug 模式下,跳出,表现出来的效果跟 F9 一样 | +| Alt + F8 | 在 Debug 模式下,选中对象,弹出可输入计算表达式调试框,查看该输入内容的调试结果 | +| Alt + F9 | 在 Debug 模式下,执行到光标处 | +| F9 | 在 Debug 模式下,恢复程序运行,但是如果该断点下面代码还有断点则停在下一个断点上 | +| Ctrl + F8 | 在 Debug 模式下,设置光标当前行为断点,如果当前已经是断点则去掉断点 | +| Ctrl + Shift + F8 | 在 Debug 模式下,指定断点进入条件 | + +#### Navigation + +| 快捷键 | 介绍 | +| ------------------------ | ----------------------------------------------------------------------------------------------------------------------- | +| Ctrl + N | 跳转到类`(必备)` | +| Ctrl + Shift + N | 跳转到文件`(必备)` | +| Ctrl + Alt + Shift + N | 跳转到符号`(必备)` | +| Alt + Left/Right | 切换当前已打开的窗口中的子视图,比如 Debug 窗口中有 Output、Debugger 等子视图,用此快捷键就可以在子视图中切换`(必备)` | +| F12 | 回到前一个工具窗口`(必备)` | +| ESC | 从工具窗口进入代码文件窗口`(必备)` | +| Shift + ESC | 隐藏当前 或 最后一个激活的工具窗口 | +| Ctrl + G | 跳转到当前文件的指定行处 | +| Ctrl + E | 显示最近打开的文件记录列表`(必备)` | +| Ctrl + Shift + E | 显示最近编辑的文件记录列表`(必备)` | +| Ctrl + Alt + Left/Right | 跳转到上一个/下一个操作的地方`(必备)` | +| Ctrl + Shift + Backspace | 退回到上次修改的地方`(必备)` | +| Alt + F1 | 显示当前文件选择目标弹出层,弹出层中有很多目标可以进行选择`(必备)` | +| Ctrl + B/Ctrl + 左键单击 | 跳转到声明处 | +| Ctrl + Alt + B | 在某个调用的方法名上使用会跳到具体的实现处,可以跳过接口 | +| Ctrl + Shift + B | 跳转到类型声明处`(必备)` | +| Ctrl + Shift + I | 快速查看光标所在的方法 或 类的定义 | +| Ctrl + U | 前往当前光标所在的方法的父类的方法/接口定义`(必备)` | +| Alt + Up/Down | 跳转到当前文件的前一个/后一个方法`(必备)` | +| Ctrl + ]/[ | 跳转到当前所在代码的花括号结束位置/开始位置 | +| Ctrl + F12 | 弹出当前文件结构层,可以在弹出的层上直接输入,进行筛选 | +| Ctrl + H | 显示当前类的层次结构 | +| Ctrl + Shift + H | 显示方法层次结构 | +| Ctrl + Alt + H | 调用层次 | +| F2/Shift + F2 | 跳转到下一个/上一个高亮错误 或 警告位置`(必备)` | +| F4 | 编辑源`(必备)` | +| Alt + Home | 定位/显示到当前文件的 Navigation Bar | +| F11 | 添加书签`(必备)` | +| Ctrl + F11 | 选中文件/文件夹,使用助记符设定/取消书签`(必备)` | +| Shift + F11 | 弹出书签显示层`(必备)` | +| Alt + 1,2,3...9 | 显示对应数值的选项卡,其中 1 是 Project 用得最多`(必备)` | +| Ctrl + 1,2,3...9 | 定位到对应数值的书签位置`(必备)` | + +#### Refactoring + +| 快捷键 | 介绍 | +| ---------------------- | ------------------------------ | +| Shift + F6 | 对文件/文件夹 重命名`(必备)` | +| Ctrl + Alt + Shift + T | 打开重构菜单`(必备)` | + +#### VCS/Local History + +| 快捷键 | 介绍 | +| --------------- | -------------------------------------------------- | +| Ctrl + K | 版本控制提交项目,需要此项目有加入到版本控制才可用 | +| Ctrl + T | 版本控制更新项目,需要此项目有加入到版本控制才可用 | +| `Alt + |` | 显示版本控制常用操作菜单弹出层`(必备)` | +| Alt + Shift + C | 查看最近操作项目的变化情况列表 | +| Alt + Shift + N | 选择/添加 task`(必备)` | + +#### Live Templates + +| 快捷键 | 介绍 | +| -------------- | -------------------------------------------- | +| Ctrl + J | 插入自定义动态代码模板`(必备)` | +| Ctrl + Alt + J | 弹出模板选择窗口,将选定的代码加入动态模板中 | + +#### General + +| 快捷键 | 介绍 | +| ---------------------- | --------------------------------------------------------------------- | +| Ctrl + Tab | 编辑窗口切换,如果在切换的过程又加按上 delete,则是关闭对应选中的窗口 | +| Ctrl + Alt + Y | 同步、刷新 | +| Ctrl + Alt + S | 打开 IntelliJ IDEA 系统设置`(必备)` | +| Ctrl + Alt + Shift + S | 打开当前项目设置`(必备)` | +| Ctrl + Shift + A | 查找动作/设置`(必备)` | +| Ctrl + Shift + F12 | 编辑器最大化`(必备)` | +| Alt + Shift + F | 显示添加到收藏夹弹出层/添加到收藏夹 | +| Alt + Shift + I | 查看项目当前文件 | + +### Intellij IDEA 官方快捷键表 + +![img](http://upload-images.jianshu.io/upload_images/3101171-6a44121ae280a10e.png) + +## 插件 + +推荐几个比较好用的插件 + +- [Key promoter](https://plugins.jetbrains.com/plugin/4455?pr=idea) [快捷键提示](https://plugins.jetbrains.com/plugin/4455?pr=idea) +- [CamelCase](https://plugins.jetbrains.com/plugin/7160?pr=idea) 驼峰式命名和下划线命名交替变化 +- [CheckStyle-IDEA](https://plugins.jetbrains.com/plugin/1065?pr=idea) 代码规范检查 +- [FindBugs-IDEA](https://plugins.jetbrains.com/plugin/3847?pr=idea)潜在 Bug 检查 +- [MetricsReloaded](https://plugins.jetbrains.com/plugin/93?pr=idea) 代码复杂度检查 +- [Statistic](https://plugins.jetbrains.com/plugin/4509?pr=idea) 代码统计 +- [JRebel Plugin](https://plugins.jetbrains.com/plugin/?id=4441) 热部署 +- [GsonFormat](https://plugins.jetbrains.com/plugin/7654?pr=idea) 把 JSON 字符串直接实例化成类 +- [Eclipse Code Formatter](https://plugins.jetbrains.com/plugin/6546-eclipse-code-formatter) 如果你以前用的是 IDE,并有自己的一套代码风格配置,可以通过此插件导入到 IDEA +- [Alibaba Java Coding Guidelines](https://plugins.jetbrains.com/plugin/10046-alibaba-java-coding-guidelines) 阿里 Java 开发规范的静态检查工具 +- [IDE Features Trainer](https://plugins.jetbrains.com/plugin/8554-ide-features-trainer) 官方的新手训练插件 +- [Markdown Navigator](https://plugins.jetbrains.com/plugin/7896-markdown-navigator) Markdown 插件,适用于喜欢用 markdown 写文档的人 + +## 个性化 + +### 颜色主题 + +[intellij-colors-solarized](https://github.com/jkaving/intellij-colors-solarized) 个人觉得这种色彩搭配十分优雅 + +[下载地址](https://github.com/altercation/solarized) + +## 破解 + +Intellij 是一个收费的 IDE,坦白说有点小贵,买不起。 + +所以,很惭愧,只好用下破解方法了。网上有很多使用注册码的网文,但是注册码不稳定,随时可能被封。还是自行搭建一个注册服务器比较稳定。我使用了 [ilanyu](http://blog.lanyus.com/) 博文 [IntelliJ IDEA License Server 本地搭建教程](http://blog.lanyus.com/archives/174.html) 的方法,亲测十分有效。 + +我的备用地址:[百度云盘](https://yun.baidu.com/disk/home?#list/vmode=list&path=%2F%E8%BD%AF%E4%BB%B6%2F%E5%BC%80%E5%8F%91%E8%BD%AF%E4%BB%B6%2FIDE) + +下载并解压文中的压缩包到本地,选择适合操作系统的版本运行。 + +如果是在 Linux 上运行,推荐创建一个脚本,代码如下: + +```bash +# 使用 nohup 创建守护进程,运行 IntelliJIDEALicenseServer_linux_amd64 +# 如果运行在其他 Linux 发行版本,替换执行的脚本即可 +nohup sh IntelliJIDEALicenseServer_linux_amd64 2>&1 +``` + +这样做是因为:大部分人使用 linux 是使用仿真器连接虚拟机,如果断开连接,进程也会被 kill,每次启动这个注册服务器很麻烦不是吗?而启动了守护进程,则不会出现这种情况,只有你主动 kill 进程才能将其干掉。 + +Windows 版本是 exe 程序,将其设为开机自动启动即可,别告诉我你不知道怎么设置开机自动启动。 + +## 参考资料 + +- [IntelliJ-IDEA-Tutorial](https://github.com/judasn/IntelliJ-IDEA-Tutorial) +- [极客学院 - Intellij IDEA 使用教程](http://wiki.jikexueyuan.com/project/intellij-idea-tutorial/) diff --git a/docs/tool/ide/vscode.md b/docs/tool/ide/vscode.md new file mode 100644 index 00000000..3e0b7ac0 --- /dev/null +++ b/docs/tool/ide/vscode.md @@ -0,0 +1,47 @@ +# 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 + +## 更多内容 + +- 官方 + - https://github.com/Microsoft/vscode + - https://github.com/Microsoft/vscode-docs + - https://github.com/Microsoft/vscode-tips-and-tricks +- 更多资源 + - https://github.com/viatsko/awesome-vscode diff --git a/docs/tool/jmeter.md b/docs/tool/jmeter.md new file mode 100644 index 00000000..d5d7630d --- /dev/null +++ b/docs/tool/jmeter.md @@ -0,0 +1,204 @@ +# JMeter 应用指南 + +> [Jmeter](https://github.com/apache/jmeter) 是一款基于 Java 开发的功能和性能测试软件。 +> +> 🎁 本文编辑时的最新版本为:5.1.1 + +## 1. 简介 + +[Jmeter](https://github.com/apache/jmeter) 是一款使用 Java 开发的功能和性能测试软件。 + +### 1.1. 特性 + +Jmeter 能够加载和性能测试许多不同的应用程序/服务器/协议类型: + +- 网络 - HTTP,HTTPS(Java,NodeJS,PHP,ASP.NET 等) +- SOAP / REST Web 服务 +- FTP 文件 +- 通过 JDBC 的数据库 +- LDAP +- 通过 JMS 的面向消息的中间件(MOM) +- 邮件-SMTP(S),POP3(S)和 IMAP(S) +- 本机命令或 Shell 脚本 +- TCP 协议 +- Java 对象 + +### 1.2. 工作流 + +Jmeter 的工作原理是仿真用户向服务器发送请求,并收集服务器应答信息并计算统计信息。 + +Jmeter 的工作流如下图所示: + +![](http://dunwu.test.upcdn.net/cs/java/javaweb/technology/test/jmeter-workflow.png!zp) + +### 1.3. 主要元素 + +Jmeter 的主要元素如下: + +- **`测试计划(Test Plan)`** - 可以将测试计划视为 JMeter 的测试脚本 。测试计划由测试元素组成,例如线程组,逻辑控制器,样本生成控制器,监听器,定时器,断言和配置元素。 +- **`线程组(Thread Group)`** - 线程组的作用是:模拟大量用户负载的运行场景。 + - 设置线程数 + - 设置加速期 + - 设置执行测试的次数 +- **`控制器(Controllers)`** - 可以分为两大类: + - **`采样器(Sampler)`** - 采样器的作用是模拟用户对目标服务器发送请求。 采样器是必须将组件添加到测试计划中的,因为它只能让 JMeter 知道需要将哪种类型的请求发送到服务器。 请求可以是 HTTP,HTTP(s),FTP,TCP,SMTP,SOAP 等。 + - **`逻辑控制器`** - 逻辑控制器的作用是:控制多个请求发送的循环次数及顺序等。 +- **`监听器(Listeners)`** - 监听器的作用是:收集测试结果信息。如查看结果树、汇总报告等。 +- **`计时器(Timers)`** - 计时器的作用是:控制多个请求发送的时间频次。 +- **`配置元素(Configuration Elements)`** - 配置元素的工作与采样器的工作类似。但是,它不发送请求,而是提供预备的数据等,如 CSV、函数助手。 +- **`预处理器元素(Pre-Processor Elements)`** - 预处理器元素在采样器发出请求之前执行,如果预处理器附加到采样器元素,那么它将在该采样器元素运行之前执行。预处理器元素用于在运行之前准备环境及参数。 +- **`后处理器元素(Post-Processor Elements)`** - 后处理器元素是在发送采样器请求之后执行的元素,常用于处理响应数据。 + +![](http://dunwu.test.upcdn.net/cs/java/javaweb/technology/test/jmeter-elements.png!zp) + +> 📌 提示: +> +> Jmeter 元素的数量关系大致如下: +> +> 1. 脚本中最多只能有一个测试计划。 +> 2. 测试计划中至少要有一个线程组。 +> 3. 线程组中至少要有一个取样器。 +> 4. 线程组中至少要有一个监听器。 + +## 2. 安装 + +### 2.1. 环境要求 + +- 必要的。Jmeter 基于 JDK8 开发,所以必须运行在 JDK8 环境。 + + - JDK8 + +- 可选的。有些 jar 包不是 Jmeter 提供的,如果需要相应的功能,需要自行下载并置于 `lib` 目录。 + - JDBC + - JMS + - [Bouncy Castle](http://www.bouncycastle.org/test_releases.html) + +### 2.2. 下载 + +进入 [**Jmeter 官网下载地址**](https://jmeter.apache.org/download_jmeter.cgi) 选择需要版本进行下载。 + +### 2.3. 启动 + +解压 Jmeter 压缩包,进入 bin 目录 + +Unix 类系统运行 `jmeter` ;Windows 系统运行 `jmeter.bat` + +![image-20191024104517721](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024104517721.png!zp) + +## 3. 使用 + +### 3.1. 创建测试计划 + +> 🔔 注意: +> +> - 在运行整个测试计划之前,应保存测试计划。 +> +> - JMeter 的测试计划以 `.jmx` 扩展文件的形式保存。 + +#### 3.1.1. 创建线程组 + +- 在“测试计划”上右键 【添加】=>【线程(用户)】=>【线程组】。 + +- 设置线程数和循环次数 + +![image-20191024105545736](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024105545736.png!zp) + +#### 3.1.2. 配置原件 + +- 在新建的线程组上右键 【添加】=>【配置元件】=>【HTTP 请求默认值】。 + +- 填写协议、服务器名称或 IP、端口号 + +![image-20191024110016264](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024110016264.png!zp) + +#### 3.1.3. 构造 HTTP 请求 + +- 在“线程组”上右键 【添加-】=>【取样器】=>【HTTP 请求】。 + +- 填写协议、服务器名称或 IP、端口号(如果配置了 HTTP 请求默认值可以忽略) +- 填写方法、路径 +- 填写参数、消息体数据、文件上传 + +![image-20191024110953063](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024110953063.png!zp) + +#### 3.1.4. 添加 HTTP 请求头 + +- 在“线程组”上右键 【添加】=>【配置元件】=>【HTTP 信息头管理器】 +- 由于我的测试例中传输的数据为 json 形式,所以设置键值对 `Content-Type`:`application/json` + +![image-20191024111825226](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024111825226.png!zp) + +#### 3.1.5. 添加断言 + +- 在“线程组”上右键 【添加】=>【断言】=>【 响应断言 】 +- 在我的案例中,以 HTTP 应答状态码为 200 来判断请求是否成功 + +![image-20191024112335130](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024112335130.png!zp) + +#### 3.1.6. 添加察看结果树 + +- 在“线程组”上右键 【添加】=>【监听器】=>【察看结果树】 +- 直接点击运行,就可以查看测试结果 + +![image-20191024113849270](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024113849270.png!zp) + +#### 3.1.7. 添加汇总报告 + +- 在“线程组”上右键 【添加】=>【监听器】=>【汇总报告】 +- 直接点击运行,就可以查看测试结果 + +![image-20191024114016424](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024114016424.png!zp) + +#### 3.1.8. 保存测试计划 + +执行测试计划前,GUI 会提示先保存配置为 `jmx` 文件。 + +### 3.2. 执行测试计划 + +官方建议不要直接使用 GUI 来执行测试计划,这种模式指适用于创建测试计划和 debug。 + +执行测试计划应该使用命令行模式,语法形式如下: + +```bash +jmeter -n -t [jmx file] -l [results file] -e -o [Path to web report folder] +``` + +执行测试计划后,在 `-e -o` 参数后指定的 web 报告目录下,可以找到测试报告内容。在浏览器中打开 `index.html` 文件,可以看到如下报告: + +![image-20191024120233058](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024120233058.png!zp) + +## 4. 问题 + +### 4.1. 如何读取本地 txt/csv 文件作为请求参数 + +参考:[Jmeter 读取本地 txt/csv 文件作为请求参数,实现接口自动化](https://www.jianshu.com/p/3b2d3b643415) + +(1)依次点击【添加】=>【配置元件】=>【CSV 数据文件设置】 + +配置如下所示: + +![image-20191127175820747](http://dunwu.test.upcdn.net/snap/image-20191127175820747.png) + +重要配置说明(其他配置根据实际情况填): + +- 文件名:输入需要导入的数据文件位置。 +- 文件编码:设为 UTF-8,避免乱码。 +- 变量名称:使用 `,` 分隔输入变量列表。如截图中设置了两个变量 `a` 和 `b` + +(2)在 HTTP 请求的消息体数据中配置参数 + +``` +[{"a":"${a}","b":"${b}"}] +``` + +### 4.2. 如何有序发送数据 + +依次点击【添加】=>【逻辑控制器】=>【事务控制器】 + +## 5. 参考资料 + +- [Jmeter 官网](https://jmeter.apache.org/) +- [Jmeter Github](https://github.com/apache/jmeter) +- [Jmeter 性能测试入门](https://www.cnblogs.com/TankXiao/p/4045439.html) +- [易百教程 - Jmeter 教程](https://www.yiibai.com/jmeter) +- [Jmeter 读取本地 txt/csv 文件作为请求参数,实现接口自动化](https://www.jianshu.com/p/3b2d3b643415) diff --git a/docs/technology/monitor/arthas.md b/docs/tool/monitor/arthas.md similarity index 100% rename from docs/technology/monitor/arthas.md rename to docs/tool/monitor/arthas.md diff --git a/docs/technology/monitor/skywalking.md b/docs/tool/monitor/skywalking.md similarity index 100% rename from docs/technology/monitor/skywalking.md rename to docs/tool/monitor/skywalking.md diff --git a/pom.xml b/pom.xml index 1839eef9..ed49002b 100644 --- a/pom.xml +++ b/pom.xml @@ -4,11 +4,11 @@ 4.0.0 io.github.dunwu.javaweb - javaweb + javatech 1.0.0 pom - JAVAWEB - JavaWeb 示例源码 + JAVATECH + Java 技术示例源码 codes/javaweb-example From d394b37054151bdee2c19441ba550cc9e14214dd Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Fri, 7 Feb 2020 23:21:18 +0800 Subject: [PATCH 56/75] =?UTF-8?q?=E6=95=B4=E7=90=86=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 - README.md | 125 +- .../java-ecology-bean/README.md | 0 .../java-ecology-bean}/pom.xml | 25 +- .../dunwu/javatech/lombok/DataDemo.java | 23 + .../lombok/EqualsAndHashCodeDemo.java | 57 + .../javatech/lombok/GetterAndSetterDemo.java | 23 + .../dunwu/javatech/lombok/NonNullDemo.java | 23 + .../github/dunwu/javatech/lombok/Person.java | 26 + .../dunwu/javatech/lombok/ToStringDemo.java | 24 + .../dunwu/javatech/lombok/package-info.java | 8 + .../dunwu/javatech/lombok/LombokTest.java | 116 + .../java-ecology-io/java-ecology-file/pom.xml | 67 + .../dunwu/javatech/serialize/FstDemo.java | 28 + .../javatech/serialize/JdkSerializeDemo.java | 36 + .../io/github/dunwu/javatech/bean/Person.java | 42 + .../json/fastjson/FastjsonAnnotationBean.java | 95 + .../javatech/json/fastjson/FastjsonTests.java | 51 + .../json/jackson/JacksonAnnotationBean.java | 55 + .../javatech/json/jackson/JacksonTests.java | 101 + .../javatech/serialize/SerializeTest.java | 47 + .../java-ecology-io/java-ecology-json/pom.xml | 69 + .../dunwu/javatech/json/bean/Group.java | 42 + .../github/dunwu/javatech/json/bean/User.java | 25 + .../json/fastjson/FastjsonFeildBean.java | 140 + .../json/fastjson/JacksonAnnotationBean.java | 55 + .../json/gson/GsonAnnotationBean.java | 52 + .../javatech/json/gson/VersionedClass.java | 21 + .../github/dunwu/javatech/json/TestBean.java | 56 + .../github/dunwu/javatech/json/TestBean2.java | 50 + .../json/fastjson/FastjsonCaseTests.java | 56 + .../fastjson/FastjsonPerformanceTests.java | 50 + .../javatech/json/gson/GsonCaseTests.java | 80 + .../json/gson/GsonPerformanceTests.java | 53 + .../json/jackson/JacksonPerformanceTests.java | 53 + .../dunwu/javatech/json/util/BeanUtils.java | 74 + .../java-ecology-serialize/pom.xml | 62 + .../io/github/dunwu/javatech/io/FstDemo.java | 73 + .../dunwu/javatech/io/JdkSerializeDemo.java | 78 + .../io/github/dunwu/javatech/io/KryoDemo.java | 114 + .../dunwu/javatech/io/bean/BeanUtils.java | 33 + .../dunwu/javatech/io/bean/TestBean.java | 59 + .../javatech/io/SerializePerformanceTest.java | 63 + codes/java-ecology/java-ecology-io/pom.xml | 18 + .../java-ecology-log4j/pom.xml | 50 + .../github/dunwu/javatech/log/Log4jDemo.java | 27 + .../src/main/resources/log4j.xml | 44 + .../java-ecology-log4j2/pom.xml | 49 + .../github/dunwu/javatech/log/Log4j2Demo.java | 27 + .../src/main/resources/log4j2.xml | 45 + .../java-ecology-logback/pom.xml | 45 + .../dunwu/javatech/log/LogbackDemo.java | 27 + .../src/main/resources/logback.xml | 44 + .../java-ecology-logstash/pom.xml | 38 + .../io/github/dunwu/javatech/ElasticDemo.java | 47 + .../src/main/resources/log4j.xml | 55 + .../src/main/resources/logback.xml | 66 + codes/java-ecology/java-ecology-log/pom.xml | 18 + .../java-ecology-kafka-springboot}/pom.xml | 14 +- .../github/dunwu/javatech}/KafkaConsumer.java | 2 +- .../github/dunwu/javatech}/KafkaProducer.java | 2 +- .../javatech}/KafkaProducerController.java | 2 +- .../dunwu/javatech}/MsgKafkaApplication.java | 2 +- .../javatech}/config/KafkaConsumerConfig.java | 2 +- .../javatech}/config/KafkaProducerConfig.java | 2 +- .../src/main/resources/application.properties | 0 .../src/main/resources/logback.xml | 0 .../dunwu/javatech}/KafkaProducerTest.java | 2 +- .../java-ecology-kafka/pom.xml | 40 + .../dunwu/javatech}/kafka/ConsumerAOC.java | 2 +- .../dunwu/javatech}/kafka/ConsumerManual.java | 2 +- .../kafka/ConsumerManualPartition.java | 2 +- .../dunwu/javatech}/kafka/ProducerDemo.java | 2 +- .../kafka/ProducerInTransaction.java | 2 +- .../dunwu/javatech}/kafka/StreamDemo.java | 2 +- .../src/main/resources/logback.xml | 4 +- .../java-ecology-rocketmq/pom.xml | 37 + .../javatech}/rocketmq/AsyncProducer.java | 2 +- .../javatech}/rocketmq/BatchProducer.java | 2 +- .../javatech}/rocketmq/BroadcastConsumer.java | 2 +- .../javatech}/rocketmq/BroadcastProducer.java | 2 +- .../javatech}/rocketmq/OnewayProducer.java | 2 +- .../javatech}/rocketmq/OrderedConsumer.java | 2 +- .../javatech/rocketmq/OrderedProducer.java | 47 + .../javatech}/rocketmq/RocketConfig.java | 2 +- .../rocketmq/ScheduledMessageConsumer.java | 2 +- .../rocketmq/ScheduledMessageProducer.java | 2 +- .../javatech}/rocketmq/SyncProducer.java | 2 +- .../src/main/resources/logback.xml | 4 +- codes/java-ecology/java-ecology-mq/pom.xml | 17 + .../java-ecology-cli/pom.xml | 75 + .../io/github/dunwu/javatech/AnsiSystem.java | 244 + .../io/github/dunwu/javatech/CliDemo.java | 56 + .../io/github/dunwu/javatech/CliUtil.java | 28 + .../github/dunwu/javatech/SystemInfoUtil.java | 265 + .../dunwu/javatech/constant/AnsiBgColor.java | 44 + .../dunwu/javatech/constant/AnsiColor.java | 44 + .../dunwu/javatech/constant/AnsiElement.java | 11 + .../dunwu/javatech/constant/AnsiSgr.java | 42 + .../github/dunwu/javatech/constant/Color.java | 36 + .../github/dunwu/javatech/CliUtilTests.java | 17 + .../github/dunwu/javatech/SystemInfoTest.java | 320 + .../java-ecology-mvel/pom.xml | 51 + .../github/dunwu/javatech/mvel/BasicRule.java | 128 + .../javatech/mvel/DefaultRuleEngine.java | 163 + .../github/dunwu/javatech/mvel/MvelRule.java | 34 + .../dunwu/javatech/mvel/MvelRuleSet.java | 34 + .../io/github/dunwu/javatech/mvel/Rule.java | 61 + .../dunwu/javatech/mvel/RuleConstant.java | 33 + .../dunwu/javatech/mvel/RuleContext.java | 8 + .../dunwu/javatech/mvel/RuleEngine.java | 54 + .../javatech/mvel/RuleEngineBuilder.java | 45 + .../dunwu/javatech/mvel/RuleEngineParams.java | 93 + .../src/main/resources/logback.xml | 143 + .../dunwu/javatech/mvel/ClassTests.java | 44 + .../dunwu/javatech/mvel/SalaryRuleTest.java | 79 + .../io/github/dunwu/javatech/mvel/demo.mvel | 56 + .../src/test/resources/SalaryRule.json | 51 + .../java-ecology/java-ecology-others/pom.xml | 16 + codes/java-ecology/java-ecology-test/pom.xml | 72 + .../github/dunwu/javatech/jmh/QuickStart.java | 43 + .../javatech/jmh/StringBuilderBenchmark.java | 45 + .../dunwu/javatech/lombok/Calculator.java | 9 + .../github/dunwu/javatech/lombok/Person.java | 34 + .../src/main/resources/logback.xml | 15 + .../dunwu/javatech/junit4/JUnitTest.java | 71 + .../javatech/junit5/AssertionsTests.java | 151 + .../javatech/junit5/AssumptionsTests.java | 43 + .../javatech/junit5/DisplayNameTests.java | 32 + .../dunwu/javatech/junit5/DynamicTests.java | 117 + .../dunwu/javatech/junit5/NestedTests.java | 84 + .../javatech/junit5/ParameterizedTests.java | 32 + .../dunwu/javatech/junit5/RepeatedTests.java | 46 + .../dunwu/javatech/junit5/StandardTests.java | 52 + .../dunwu/javatech/mockito/MockitoTest.java | 225 + codes/java-ecology/java-ecology-text/pom.xml | 58 + .../dunwu/javatech/office/PinyinDemo.java | 15 + .../dunwu/javatech/office/WordUtil.java | 263 + .../dunwu/javatech/office/WordUtilTest.java | 111 + .../java-ecology-tomcat/README.md | 50 + .../java-ecology/java-ecology-tomcat/pom.xml | 88 + .../javatech/controller/HelloController.java | 56 + .../javatech/controller/IndexController.java | 31 + .../javatech/server/SimpleTomcatServer.java | 41 + .../dunwu/javatech/server/TomcatServer.java | 133 + .../src/main/resources/logback.xml | 47 + .../properties/application-develop.properties | 11 + .../properties/application-test.properties | 11 + .../main/resources/spring/spring-servlet.xml | 22 + .../src/main/resources/tomcat/conf/server.xml | 37 + .../src/main/resources/tomcat/conf/web.xml | 4703 ++++++++++++ .../src/main/webapp/META-INF/MANIFEST.MF | 0 .../src/main/webapp/WEB-INF/web.xml | 47 + .../src/main/webapp/views/jsp/hello.jsp | 16 + .../src/main/webapp/views/jsp/index.jsp | 28 + .../java-ecology-zookeeper}/pom.xml | 18 +- .../dunwu/javatech}/zk/dlock/Callback.java | 2 +- .../javatech}/zk/dlock/DLockTemplate.java | 2 +- .../javatech}/zk/dlock/DistributedLock.java | 2 +- .../javatech}/zk/dlock/TimeoutHandler.java | 2 +- .../javatech}/zk/dlock/ZkDLockTemplate.java | 2 +- .../zk/dlock/ZkReentrantLockCleanerTask.java | 2 +- .../ZookeeperReentrantDistributedLock.java | 2 +- .../zk/example/ZooKeeperConnection.java | 2 +- .../javatech}/zk/example/ZooKeeperCreate.java | 2 +- .../javatech}/zk/example/ZooKeeperDelete.java | 2 +- .../javatech}/zk/example/ZooKeeperExists.java | 2 +- .../zk/example/ZooKeeperGetChildren.java | 2 +- .../zk/example/ZooKeeperGetData.java | 2 +- .../zk/example/ZooKeeperSetData.java | 2 +- .../javatech}/zk/example/package-info.java | 2 +- .../zk/sequence/DistributedSequence.java | 2 +- .../zk/sequence/ZkDistributedSequence.java | 4 +- .../zk/dlock/ZkReentrantLockTemplateTest.java | 2 +- .../src/test/resources/logback.xml | 0 codes/java-ecology/pom.xml | 24 + codes/javaee-tutorial/README.md | 1 - .../javaee-tutorial-filter/pom.xml | 113 - .../dunwu/javaee/filter/CacheFilter.java | 94 - .../filter/CharacterEncodingFilter.java | 60 - .../javaee/filter/ExceptionHandlerFilter.java | 48 - .../dunwu/javaee/filter/FilterImpl.java | 35 - .../dunwu/javaee/filter/GZipFilter.java | 44 - .../javaee/filter/ImageRedirectFilter.java | 44 - .../github/dunwu/javaee/filter/LogFilter.java | 36 - .../github/dunwu/javaee/filter/MyFilter.java | 37 - .../javaee/filter/OutputReplaceFilter.java | 56 - .../dunwu/javaee/filter/PrivilegeFilter.java | 77 - .../dunwu/javaee/filter/UploadFilter.java | 24 - .../dunwu/javaee/filter/WaterMarkFilter.java | 47 - .../dunwu/javaee/filter/XSLTFilter.java | 68 - .../filter/exception/AccountException.java | 11 - .../filter/exception/BusinessException.java | 11 - .../dunwu/javaee/filter/test/Download.java | 36 - .../dunwu/javaee/filter/test/GZipTest.java | 34 - .../filter/wrapper/CacheResponseWrapper.java | 44 - .../filter/wrapper/GZipOutputStream.java | 74 - .../filter/wrapper/GZipResponseWrapper.java | 61 - .../wrapper/HttpCharacterResponseWrapper.java | 30 - .../filter/wrapper/UploadRequestWrapper.java | 105 - .../filter/wrapper/WaterMarkOutputStream.java | 55 - .../wrapper/WaterMarkResponseWrapper.java | 60 - .../src/main/resources/logback.xml | 45 - .../src/main/webapp/WEB-INF/logo.png | Bin 10276 -> 0 bytes .../main/webapp/WEB-INF/privilege.properties | 8 - .../main/webapp/WEB-INF/sensitive.properties | 7 - .../src/main/webapp/WEB-INF/web.xml | 182 - .../src/main/webapp/views/images/error.gif | Bin 351 -> 0 bytes .../src/main/webapp/views/images/mm.jpg | Bin 241856 -> 0 bytes .../src/main/webapp/views/images/sunset.jpg | Bin 71189 -> 0 bytes .../src/main/webapp/views/images/winter.jpg | Bin 105542 -> 0 bytes .../src/main/webapp/views/js/dojo.js | 6812 ----------------- .../webapp/views/jsp/accountException.jsp | 44 - .../webapp/views/jsp/businessException.jsp | 24 - .../src/main/webapp/views/jsp/dispatcher.jsp | 13 - .../src/main/webapp/views/jsp/exception.jsp | 24 - .../src/main/webapp/views/jsp/image.jsp | 19 - .../src/main/webapp/views/jsp/index.jsp | 52 - .../main/webapp/views/jsp/testCacheFilter.jsp | 71 - .../views/jsp/testCharacterEncodingFilter.jsp | 28 - .../views/jsp/testExceptionHandlerFilter.jsp | 29 - .../main/webapp/views/jsp/testGZipFilter.jsp | 66 - .../views/jsp/testOutputReplaceFilter.jsp | 19 - .../webapp/views/jsp/testUploadFilter.jsp | 43 - .../webapp/views/jsp/testWaterMarkFilter.jsp | 16 - .../src/main/webapp/views/xml/book.xml | 6 - .../src/main/webapp/views/xml/book.xsl | 40 - .../src/main/webapp/views/xml/demo.xml | 78 - .../src/main/webapp/views/xml/messageLog.xsl | Bin 9852 -> 0 bytes .../src/main/webapp/views/xml/thinkInJava.xml | 7 - .../src/main/webapp/views/xml/xml.xsl | 18 - .../javaee/server/FilterDemosBootstrap.java | 35 - .../dunwu/javaee/server/JettyFactory.java | 131 - .../src/test/resources/jetty/webdefault.xml | 534 -- .../javaee-tutorial-jsp/pom.xml | 97 - .../com/sun/products/applet/demo/Graph.java | 467 -- .../github/dunwu/javaee/jsp/bean/Counter.java | 15 - .../github/dunwu/javaee/jsp/bean/Message.java | 15 - .../github/dunwu/javaee/jsp/bean/Person.java | 35 - .../github/dunwu/javaee/jsp/util/IpUtil.java | 16 - .../dunwu/javaee/jsp/util/ip/IPEntry.java | 51 - .../dunwu/javaee/jsp/util/ip/IPSeeker.java | 709 -- .../github/dunwu/javaee/jsp/util/ip/QQWry.dat | Bin 824560 -> 0 bytes .../github/dunwu/javaee/jsp/util/ip/Test.java | 33 - .../dunwu/javaee/jsp/util/ip/Utils.java | 106 - .../src/main/resources/logback.xml | 46 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../main/webapp/WEB-INF/META-INF/MANIFEST.MF | 3 - .../webapp/WEB-INF/resources/jsp/index.jsp | 16 - .../src/main/webapp/WEB-INF/web.xml | 25 - .../main/webapp/examples/02.grammar/break.jsp | 26 - .../main/webapp/examples/02.grammar/for.jsp | 54 - .../main/webapp/examples/02.grammar/if.jsp | 68 - .../main/webapp/examples/02.grammar/if2.jsp | 17 - .../webapp/examples/02.grammar/scriptlet.jsp | 16 - .../webapp/examples/02.grammar/switch.jsp | 37 - .../main/webapp/examples/02.grammar/while.jsp | 23 - .../webapp/examples/03.directive/foot.jsp | 13 - .../webapp/examples/03.directive/head.jsp | 20 - .../webapp/examples/03.directive/include.jsp | 18 - .../webapp/examples/03.directive/page.jsp | 17 - .../main/webapp/examples/04.action/date.jsp | 4 - .../webapp/examples/04.action/jspforward.jsp | 23 - .../webapp/examples/04.action/jspinclude.jsp | 14 - .../webapp/examples/04.action/jspplugin.jsp | 33 - .../webapp/examples/04.action/jspuseBean.jsp | 55 - .../webapp/examples/04.action/jspuseBean2.jsp | 43 - .../webapp/examples/04.action/useBean.html | 42 - .../examples/05.implicit_object/error.jsp | 11 - .../examples/05.implicit_object/exception.jsp | 14 - .../webapp/examples/cookie/readCookie.jsp | 31 - .../main/webapp/examples/cookie/session.jsp | 71 - .../webapp/examples/cookie/writeCookie.jsp | 41 - .../main/webapp/examples/locale/language.jsp | 23 - .../main/webapp/examples/locale/language2.jsp | 20 - .../webapp/examples/locale/localeCurrency.jsp | 20 - .../webapp/examples/locale/localeDate.jsp | 20 - .../webapp/examples/locale/localePercent.jsp | 20 - .../main/webapp/examples/practice/date.jsp | 24 - .../examples/practice/form/checkbox.html | 17 - .../examples/practice/form/checkbox.jsp | 23 - .../examples/practice/form/checkbox2.html | 17 - .../examples/practice/form/checkbox2.jsp | 29 - .../examples/practice/form/formGet.html | 17 - .../webapp/examples/practice/form/formGet.jsp | 20 - .../examples/practice/form/formPost.html | 17 - .../examples/practice/form/formPost.jsp | 24 - .../webapp/examples/practice/hitCount.jsp | 27 - .../examples/practice/mail/sendMail.jsp | 55 - .../webapp/examples/practice/redirect.jsp | 20 - .../main/webapp/examples/practice/refresh.jsp | 30 - .../webapp/examples/uncheck/01.helloWorld.jsp | 10 - .../examples/uncheck/01.helloWorld_zh.jsp | 13 - .../main/webapp/examples/uncheck/02.life.jsp | 45 - .../webapp/examples/uncheck/03.yourIp.jsp | 15 - .../webapp/examples/uncheck/configuration.jsp | 14 - .../src/main/webapp/images/bg-btn-blue.gif | Bin 827 -> 0 bytes .../src/main/webapp/images/mail.gif | Bin 226 -> 0 bytes .../src/main/webapp/images/vertical_line.gif | Bin 301 -> 0 bytes .../src/main/webapp/views/css/style.css | 51 - .../src/main/webapp/views/jsp/action.jsp | 7 - .../src/main/webapp/views/jsp/action/date.jsp | 4 - .../main/webapp/views/jsp/action/forward.jsp | 12 - .../webapp/views/jsp/action/getProperty.jsp | 19 - .../main/webapp/views/jsp/action/include.jsp | 12 - .../main/webapp/views/jsp/action/useBean.html | 38 - .../main/webapp/views/jsp/action/useBean.jsp | 53 - .../src/main/webapp/views/jsp/application.jsp | 15 - .../src/main/webapp/views/jsp/break.jsp | 27 - .../src/main/webapp/views/jsp/comment.jsp | 21 - .../src/main/webapp/views/jsp/counter.jsp | 43 - .../src/main/webapp/views/jsp/directive.jsp | 4 - .../views/jsp/directive/include/foot.jsp | 13 - .../views/jsp/directive/include/head.jsp | 23 - .../views/jsp/directive/include/include.jsp | 15 - .../views/jsp/directive/page/contentType.jsp | 12 - .../views/jsp/directive/page/errorPage.jsp | 13 - .../views/jsp/directive/page/isErrorPage.jsp | 12 - .../src/main/webapp/views/jsp/el.jsp | 26 - .../src/main/webapp/views/jsp/error.jsp | 11 - .../src/main/webapp/views/jsp/exception.jsp | 14 - .../src/main/webapp/views/jsp/grammar/for.jsp | 53 - .../src/main/webapp/views/jsp/grammar/if.jsp | 48 - .../main/webapp/views/jsp/grammar/while.jsp | 23 - .../src/main/webapp/views/jsp/greeting.jsp | 44 - .../src/main/webapp/views/jsp/index.jsp | 25 - .../src/main/webapp/views/jsp/life.jsp | 15 - .../src/main/webapp/views/jsp/method.jsp | 84 - .../src/main/webapp/views/jsp/plugin.jsp | 33 - .../src/main/webapp/views/jsp/return.jsp | 27 - .../src/main/webapp/views/jsp/scriptlet.jsp | 17 - .../src/main/webapp/views/jsp/taglib.jsp | 14 - .../dunwu/javaee/server/JettyFactory.java | 114 - .../github/dunwu/javaee/server/Profiles.java | 34 - .../dunwu/javaee/server/QuickStartServer.java | 35 - .../src/test/resources/jetty/webdefault.xml | 534 -- .../src/test/resources/logback.xml | 46 - .../javaee-tutorial-jstl/pom.xml | 123 - .../io/github/dunwu/javaee/bean/Person.java | 107 - .../io/github/dunwu/javaee/util/Example.java | 13 - .../dunwu/javaee/util/Example_es_ES.java | 13 - .../github/dunwu/javaee/util/Pagination.java | 204 - .../src/main/resources/init.sql | 45 - .../src/main/resources/logback.xml | 46 - .../src/main/resources/messages.properties | 2 - .../main/resources/messages_zh_CN.properties | 2 - .../main/resources/sql/create_employees.sql | 12 - .../main/resources/sql/create_students.sql | 12 - .../src/main/webapp/WEB-INF/web.xml | 25 - .../src/main/webapp/examples/core/c_catch.jsp | 20 - .../main/webapp/examples/core/c_choose.jsp | 23 - .../main/webapp/examples/core/c_forEach.jsp | 13 - .../main/webapp/examples/core/c_forTokens.jsp | 13 - .../src/main/webapp/examples/core/c_if.jsp | 14 - .../main/webapp/examples/core/c_import.jsp | 12 - .../src/main/webapp/examples/core/c_out.jsp | 20 - .../src/main/webapp/examples/core/c_param.jsp | 21 - .../main/webapp/examples/core/c_redirect.jsp | 11 - .../main/webapp/examples/core/c_remove.jsp | 14 - .../src/main/webapp/examples/core/c_set.jsp | 12 - .../src/main/webapp/examples/core/c_url.jsp | 14 - .../main/webapp/examples/fmt/fmt_bundle.jsp | 18 - .../webapp/examples/fmt/fmt_formatDate.jsp | 33 - .../webapp/examples/fmt/fmt_formatNumber.jsp | 33 - .../main/webapp/examples/fmt/fmt_message.jsp | 26 - .../webapp/examples/fmt/fmt_parseDate.jsp | 19 - .../webapp/examples/fmt/fmt_parseNumber.jsp | 20 - .../examples/fmt/fmt_requestEncoding.jsp | 20 - .../webapp/examples/fmt/fmt_setLocale.jsp | 26 - .../webapp/examples/fmt/fmt_setTimeZone.jsp | 18 - .../main/webapp/examples/fmt/fmt_timeZone.jsp | 42 - .../webapp/examples/function/fn_contains.jsp | 24 - .../function/fn_containsIgnoreCase.jsp | 24 - .../webapp/examples/function/fn_endsWith.jsp | 24 - .../webapp/examples/function/fn_escapeXml.jsp | 23 - .../webapp/examples/function/fn_indexOf.jsp | 18 - .../main/webapp/examples/function/fn_join.jsp | 18 - .../webapp/examples/function/fn_length.jsp | 19 - .../webapp/examples/function/fn_replace.jsp | 19 - .../webapp/examples/function/fn_split.jsp | 23 - .../examples/function/fn_startsWith.jsp | 21 - .../webapp/examples/function/fn_substring.jsp | 17 - .../examples/function/fn_substringAfter.jsp | 17 - .../examples/function/fn_substringBefore.jsp | 17 - .../examples/function/fn_toLowerCase.jsp | 17 - .../examples/function/fn_toUpperCase.jsp | 17 - .../main/webapp/examples/function/fn_trim.jsp | 19 - .../webapp/examples/sql/sql_dateParam.jsp | 52 - .../main/webapp/examples/sql/sql_param.jsp | 47 - .../main/webapp/examples/sql/sql_query.jsp | 40 - .../webapp/examples/sql/sql_setDataSource.jsp | 18 - .../webapp/examples/sql/sql_transaction.jsp | 60 - .../main/webapp/examples/sql/sql_update.jsp | 44 - .../main/webapp/examples/uncheck/bundle.jsp | 41 - .../main/webapp/examples/uncheck/catch.jsp | 26 - .../main/webapp/examples/uncheck/choose.jsp | 21 - .../main/webapp/examples/uncheck/contains.jsp | 35 - .../examples/uncheck/containsIgnoreCase.jsp | 33 - .../webapp/examples/uncheck/dateParam.jsp | 80 - .../main/webapp/examples/uncheck/endsWith.jsp | 70 - .../webapp/examples/uncheck/escapeXml.jsp | 21 - .../src/main/webapp/examples/uncheck/fmt.jsp | 14 - .../src/main/webapp/examples/uncheck/fn.jsp | 14 - .../main/webapp/examples/uncheck/forEach.jsp | 76 - .../examples/uncheck/forEachWithList.jsp | 196 - .../webapp/examples/uncheck/forTokens.jsp | 39 - .../webapp/examples/uncheck/formatDate.jsp | 77 - .../webapp/examples/uncheck/formatNumber.jsp | 89 - .../src/main/webapp/examples/uncheck/if.jsp | 56 - .../main/webapp/examples/uncheck/import.jsp | 25 - .../main/webapp/examples/uncheck/index.jsp | 16 - .../main/webapp/examples/uncheck/indexOf.jsp | 16 - .../src/main/webapp/examples/uncheck/join.jsp | 20 - .../main/webapp/examples/uncheck/length.jsp | 18 - .../src/main/webapp/examples/uncheck/out.jsp | 14 - .../main/webapp/examples/uncheck/param.jsp | 17 - .../main/webapp/examples/uncheck/parse.jsp | 51 - .../webapp/examples/uncheck/parseDate.jsp | 16 - .../webapp/examples/uncheck/parseNumber.jsp | 16 - .../main/webapp/examples/uncheck/query.jsp | 57 - .../examples/uncheck/queryPagination.jsp | 79 - .../webapp/examples/uncheck/queryReflect.jsp | 75 - .../main/webapp/examples/uncheck/redirect.jsp | 14 - .../main/webapp/examples/uncheck/remove.jsp | 21 - .../examples/uncheck/requestEncoding.jsp | 26 - .../src/main/webapp/examples/uncheck/set.jsp | 34 - .../webapp/examples/uncheck/setBundle.jsp | 23 - .../webapp/examples/uncheck/setDataSource.jsp | 27 - .../webapp/examples/uncheck/setLocale.jsp | 68 - .../webapp/examples/uncheck/setTimeZone.jsp | 43 - .../src/main/webapp/examples/uncheck/sina.xml | 300 - .../main/webapp/examples/uncheck/split.jsp | 21 - .../src/main/webapp/examples/uncheck/sql.jsp | 16 - .../webapp/examples/uncheck/substring.jsp | 21 - .../main/webapp/examples/uncheck/timeZone.jsp | 81 - .../webapp/examples/uncheck/transaction.jsp | 91 - .../main/webapp/examples/uncheck/update.jsp | 65 - .../src/main/webapp/examples/uncheck/url.jsp | 15 - .../main/webapp/examples/uncheck/x_choose.jsp | 43 - .../webapp/examples/uncheck/x_forEach.jsp | 44 - .../src/main/webapp/examples/uncheck/x_if.jsp | 42 - .../main/webapp/examples/uncheck/x_out.jsp | 25 - .../main/webapp/examples/uncheck/x_set.jsp | 38 - .../webapp/examples/uncheck/x_transform.jsp | 107 - .../src/main/webapp/examples/uncheck/xml.jsp | 25 - .../src/main/webapp/examples/xml/x_choose.jsp | 42 - .../main/webapp/examples/xml/x_forEach.jsp | 36 - .../src/main/webapp/examples/xml/x_if.jsp | 40 - .../src/main/webapp/examples/xml/x_out.jsp | 35 - .../src/main/webapp/examples/xml/x_param.jsp | 33 - .../src/main/webapp/examples/xml/x_parse.jsp | 22 - .../src/main/webapp/examples/xml/x_set.jsp | 31 - .../main/webapp/examples/xml/x_transform.jsp | 31 - .../src/main/webapp/xml/books.xml | 12 - .../src/main/webapp/xml/style.xsl | 32 - .../dunwu/javaee/server/JettyFactory.java | 114 - .../github/dunwu/javaee/server/Profiles.java | 34 - .../dunwu/javaee/server/QuickStartServer.java | 35 - .../src/test/resources/jetty/webdefault.xml | 534 -- .../javaee-tutorial-listener/pom.xml | 102 - .../javaee/listener/LoginSessionListener.java | 89 - .../MyHttpSessionActivationListener.java | 46 - .../MyHttpSessionAttributeListener.java | 44 - .../MyHttpSessionBindingListener.java | 28 - .../listener/MyHttpSessionListener.java | 48 - .../MyServletContextAttributeListener.java | 33 - .../listener/MyServletContextListener.java | 38 - .../MyServletRequestAttributeListener.java | 33 - .../listener/MyServletRequestListener.java | 61 - .../javaee/listener/PersonInfoListener.java | 72 - .../javaee/listener/bean/PersonInfo.java | 51 - .../listener/util/ApplicationConstants.java | 28 - .../src/main/resources/logback.xml | 46 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/META-INF/context.xml | 5 - .../webapp/WEB-INF/resources/jsp/index.jsp | 16 - .../src/main/webapp/WEB-INF/web.xml | 57 - .../src/main/webapp/a.gif | Bin 120 -> 0 bytes .../src/main/webapp/aa.html | 17 - .../src/main/webapp/active.jsp | 24 - .../src/main/webapp/b.gif | Bin 128 -> 0 bytes .../src/main/webapp/index.jsp | 22 - .../src/main/webapp/listener.jsp | 91 - .../src/main/webapp/online.jsp | 55 - .../main/webapp/testLoginSessionListener.jsp | 70 - .../dunwu/javaee/server/JettyFactory.java | 131 - .../javaee/server/ListenerDemosBootstrap.java | 35 - .../src/test/resources/jetty/webdefault.xml | 534 -- .../javaee-tutorial-oss/pom.xml | 154 - .../javaee/oss/encode/digest/DsaCoder.java | 79 - .../javaee/oss/encode/digest/HmacCoder.java | 38 - .../javaee/oss/encode/digest/MdCoder.java | 46 - .../javaee/oss/encode/digest/ShaCoder.java | 56 - .../javaee/oss/encode/encrypt/AESCoder.java | 131 - .../javaee/oss/encode/encrypt/Base64Demo.java | 25 - .../javaee/oss/encode/encrypt/DESCoder.java | 133 - .../oss/encode/encrypt/DESedeCoder.java | 73 - .../javaee/oss/encode/encrypt/PBECoder.java | 91 - .../javaee/oss/encode/encrypt/RSACoder.java | 138 - .../oss/encode/sample/DownloadPolicy.java | 111 - .../oss/encode/sample/ModifyPolicy.java | 95 - .../javaee/oss/encode/sample/TokenUtil.java | 158 - .../oss/encode/sample/UploadConstant.java | 35 - .../oss/encode/sample/UploadPolicy.java | 153 - .../dunwu/javaee/oss/html/CnblogParser.java | 52 - .../dunwu/javaee/oss/html/XiamiParser.java | 109 - .../dunwu/javaee/oss/image/ImageUtil.java | 143 - .../dunwu/javaee/oss/image/QRCodeUtil.java | 60 - .../javaee/oss/image/dto/BarcodeParamDTO.java | 87 - .../javaee/oss/image/dto/ImageParamDTO.java | 181 - .../dunwu/javaee/oss/jms/JMSHelloWorld.java | 140 - .../dunwu/javaee/oss/jms/JMSReceiver.java | 62 - .../dunwu/javaee/oss/jms/JMSSender.java | 77 - .../dunwu/javaee/oss/logging/JclDemo.java | 29 - .../dunwu/javaee/oss/logging/Slf4jDemo.java | 28 - .../dunwu/javaee/oss/mail/ForwardMail.java | 110 - .../dunwu/javaee/oss/mail/MailConfigDTO.java | 72 - .../github/dunwu/javaee/oss/mail/MailDTO.java | 104 - .../dunwu/javaee/oss/mail/MailUtil.java | 204 - .../javaee/oss/mail/SendAttachmentMail.java | 84 - .../javaee/oss/mail/SendTemplateMail.java | 81 - .../dunwu/javaee/oss/mail/SendTextMail.java | 67 - .../dunwu/javaee/oss/mail/StoreMail.java | 66 - .../dunwu/javaee/oss/mail/sendHtmlMail.java | 84 - .../javaee/oss/template/LoadVelocityDemo.java | 94 - .../oss/template/VelocityHelloWorld.java | 42 - .../oss/template/VelocityHelloWorld2.java | 47 - .../javaee/oss/template/VelocityUtil.java | 46 - .../src/main/resources/html/example.html | 8 - .../src/main/resources/images/lion.jpg | Bin 16791 -> 0 bytes .../src/main/resources/images/lion2.jpg | Bin 31726 -> 0 bytes .../src/main/resources/log4j.xml | 46 - .../src/main/resources/logback.xml | 188 - .../src/main/resources/mail/mail.properties | 5 - .../src/main/resources/template/footer.vm | 16 - .../src/main/resources/template/header.vm | 6 - .../src/main/resources/template/hello.vm | 3 - .../src/main/resources/template/mail.vm | 140 - .../resources/template/velocity.properties | 4 - .../dunwu/javaee/oss/html/JsoupTest.java | 105 - .../dunwu/javaee/oss/image/ImageUtilTest.java | 67 - .../javaee/oss/image/QRCodeUtilTest.java | 99 - .../javaee/oss/image/ThumbnailatorTest.java | 143 - .../javaee/oss/template/VelocityUtilTest.java | 23 - .../dunwu/javaee/oss/test/JUnitExecTest.java | 89 - .../test/resources/html/jsoup-cookbook.html | 107 - .../src/test/resources/images/lion.jpg | Bin 16791 -> 0 bytes .../src/test/resources/images/lion2.jpg | Bin 31726 -> 0 bytes .../test/resources/images/lion2_watermark.png | Bin 139930 -> 0 bytes .../src/test/resources/images/wartermark.png | Bin 16722 -> 0 bytes .../src/test/resources/logback.xml | 68 - .../javaee-tutorial-servlet/pom.xml | 89 - .../javaee/servlet/AnnotationServlet.java | 85 - .../javaee/servlet/ContextParamServlet.java | 64 - .../dunwu/javaee/servlet/FirstServlet.java | 92 - .../dunwu/javaee/servlet/ForwardServlet.java | 47 - .../dunwu/javaee/servlet/HelloServlet.java | 108 - .../dunwu/javaee/servlet/ImageServlet.java | 91 - .../javaee/servlet/InitParamServlet.java | 81 - .../javaee/servlet/InjectionServlet.java | 54 - .../javaee/servlet/LifeCycleServlet.java | 138 - .../dunwu/javaee/servlet/PostServlet.java | 153 - .../dunwu/javaee/servlet/RedirectServlet.java | 78 - .../dunwu/javaee/servlet/RequestServlet.java | 187 - .../javaee/servlet/ThreadSafetyServlet.java | 34 - .../dunwu/javaee/servlet/UploadServlet.java | 171 - .../servlet/upload/ProgressUploadServlet.java | 117 - .../javaee/servlet/upload/UploadListener.java | 19 - .../javaee/servlet/upload/UploadStatus.java | 45 - .../src/main/resources/logback.xml | 46 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/notice.html | 116 - .../src/main/webapp/WEB-INF/web.xml | 216 - .../src/main/webapp/index.jsp | 33 - .../views/jsp/postPersonalInformation.html | 152 - .../dunwu/javaee/server/JettyFactory.java | 114 - .../github/dunwu/javaee/server/Profiles.java | 34 - .../dunwu/javaee/server/QuickStartServer.java | 35 - .../src/test/resources/jetty/webdefault.xml | 534 -- .../src/test/resources/logback.xml | 46 - .../javaee-tutorial-session/pom.xml | 93 - .../dunwu/javaee/cookie/AddCookies.java | 68 - .../dunwu/javaee/cookie/DeleteCookies.java | 75 - .../dunwu/javaee/cookie/ReadCookies.java | 76 - .../dunwu/javaee/cookie/bean/Person.java | 65 - .../dunwu/javaee/cookie/bean/Topic.java | 42 - .../javaee/session/SessionTrackServlet.java | 69 - .../src/main/resources/logback.xml | 45 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/META-INF/context.xml | 4 - .../src/main/webapp/WEB-INF/web.xml | 49 - .../src/main/webapp/encodeURL.jsp | 15 - .../src/main/webapp/index.jsp | 26 - .../src/main/webapp/views/css/style.css | 51 - .../main/webapp/views/images/bg-btn-blue.gif | Bin 827 -> 0 bytes .../src/main/webapp/views/images/cookie.gif | Bin 592 -> 0 bytes .../main/webapp/views/images/errorstate.gif | Bin 609 -> 0 bytes .../src/main/webapp/views/images/mail.gif | Bin 226 -> 0 bytes .../webapp/views/images/vertical_line.gif | Bin 301 -> 0 bytes .../webapp/views/jsp/cookie/addCookies.jsp | 16 - .../main/webapp/views/jsp/cookie/base64.jsp | 35 - .../webapp/views/jsp/cookie/base64_decode.jsp | 32 - .../main/webapp/views/jsp/cookie/cookie.gif | Bin 592 -> 0 bytes .../main/webapp/views/jsp/cookie/cookie.jsp | 81 - .../views/jsp/cookie/cookieAttribute.jsp | 30 - .../webapp/views/jsp/cookie/cookieDomain.jsp | 27 - .../main/webapp/views/jsp/cookie/encoding.jsp | 33 - .../main/webapp/views/jsp/cookie/history.js | 27 - .../main/webapp/views/jsp/cookie/history.jsp | 64 - .../webapp/views/jsp/cookie/javascript.jsp | 106 - .../main/webapp/views/jsp/cookie/login.jsp | 68 - .../webapp/views/jsp/cookie/loginCookie.jsp | 165 - .../main/webapp/views/jsp/cookie/maxAge.jsp | 26 - .../webapp/views/jsp/cookie/setCookie.jsp | 109 - .../main/webapp/views/jsp/session/session.jsp | 94 - .../main/webapp/views/jsp/session/vote.jsp | 52 - .../main/webapp/views/jsp/session/welcome.jsp | 63 - .../dunwu/javaee/server/JettyFactory.java | 114 - .../github/dunwu/javaee/server/Profiles.java | 34 - .../dunwu/javaee/server/QuickStartServer.java | 35 - .../src/test/resources/jetty/webdefault.xml | 534 -- .../javaee-tutorial-taglib/pom.xml | 123 - .../github/dunwu/javaee/taglib/Copyright.java | 62 - .../github/dunwu/javaee/taglib/HelloTag.java | 24 - .../github/dunwu/javaee/taglib/HelloTag2.java | 24 - .../github/dunwu/javaee/taglib/HelloTag3.java | 38 - .../dunwu/javaee/taglib/bean/Person.java | 105 - .../javaee/taglib/function/Function.java | 62 - .../dunwu/javaee/taglib/tags/AddTag.java | 32 - .../dunwu/javaee/taglib/tags/Copyright.java | 57 - .../dunwu/javaee/taglib/tags/Copyright2.java | 33 - .../taglib/tags/DynamicAttributeTag.java | 58 - .../dunwu/javaee/taglib/tags/HelloTag.java | 32 - .../dunwu/javaee/taglib/tags/IteratorTag.java | 31 - .../dunwu/javaee/taglib/tags/LoopTag.java | 40 - .../javaee/taglib/tags/ToLowerCaseTag.java | 23 - .../javaee/taglib/tags/table/Column.java | 64 - .../dunwu/javaee/taglib/tags/table/Table.java | 203 - .../taglib/tags2/MultiAttributeTag.java | 44 - .../dunwu/javaee/taglib/tags2/MultiTag.java | 34 - .../dunwu/javaee/taglib/tags2/RepeatTag.java | 17 - .../javaee/taglib/tags2/ToUpperCaseTag.java | 28 - .../dunwu/javaee/taglib/test/Messages.java | 34 - .../dunwu/javaee/taglib/test/TestMessage.java | 9 - .../javaee/taglib/test/messages.properties | 1 - .../src/main/resources/copyright.properties | 1 - .../src/main/resources/logback.xml | 46 - .../src/main/webapp/WEB-INF/tld/function.tld | 44 - .../src/main/webapp/WEB-INF/tld/hello.tld | 10 - .../src/main/webapp/WEB-INF/tld/hello2.tld | 10 - .../src/main/webapp/WEB-INF/tld/hello3.tld | 13 - .../src/main/webapp/WEB-INF/tld/taglib.tld | 149 - .../src/main/webapp/WEB-INF/web.xml | 25 - .../src/main/webapp/dynamic.jsp | 18 - .../src/main/webapp/function.jsp | 46 - .../src/main/webapp/hello.jsp | 15 - .../src/main/webapp/images/I.png | Bin 148 -> 0 bytes .../src/main/webapp/images/L.png | Bin 153 -> 0 bytes .../src/main/webapp/images/Lminus.png | Bin 219 -> 0 bytes .../src/main/webapp/images/Lplus.png | Bin 224 -> 0 bytes .../src/main/webapp/images/T.png | Bin 152 -> 0 bytes .../src/main/webapp/images/Thumbs.db | Bin 56832 -> 0 bytes .../src/main/webapp/images/Tminus.png | Bin 207 -> 0 bytes .../src/main/webapp/images/Tplus.png | Bin 222 -> 0 bytes .../src/main/webapp/images/asc.gif | Bin 118 -> 0 bytes .../src/main/webapp/images/bg-btn-blue.gif | Bin 827 -> 0 bytes .../src/main/webapp/images/bg_4.jpg | Bin 16432 -> 0 bytes .../src/main/webapp/images/blank.png | Bin 144 -> 0 bytes .../src/main/webapp/images/bottom-left.gif | Bin 916 -> 0 bytes .../src/main/webapp/images/bottom-right.gif | Bin 916 -> 0 bytes .../src/main/webapp/images/btn-go-dark.gif | Bin 276 -> 0 bytes .../src/main/webapp/images/delete.gif | Bin 74 -> 0 bytes .../src/main/webapp/images/delimiter.gif | Bin 43 -> 0 bytes .../src/main/webapp/images/desc.gif | Bin 118 -> 0 bytes .../src/main/webapp/images/edit.gif | Bin 162 -> 0 bytes .../src/main/webapp/images/element.gif | Bin 330 -> 0 bytes .../src/main/webapp/images/file.png | Bin 251 -> 0 bytes .../src/main/webapp/images/foldericon.png | Bin 229 -> 0 bytes .../main/webapp/images/ibm-tab-background.gif | Bin 2538 -> 0 bytes .../src/main/webapp/images/ibm_logo.gif | Bin 360 -> 0 bytes .../main/webapp/images/left-nav-corner.gif | Bin 909 -> 0 bytes .../images/leftnav-overview-highlight.gif | Bin 128 -> 0 bytes .../src/main/webapp/images/line01.gif | Bin 46 -> 0 bytes .../webapp/images/masthead-links-gradient.gif | Bin 264 -> 0 bytes .../src/main/webapp/images/memo.gif | Bin 902 -> 0 bytes .../src/main/webapp/images/new.png | Bin 251 -> 0 bytes .../src/main/webapp/images/next.gif | Bin 302 -> 0 bytes .../src/main/webapp/images/openfoldericon.png | Bin 232 -> 0 bytes .../main/webapp/images/pagetools-gradient.gif | Bin 986 -> 0 bytes .../webapp/images/pagetools_gradient_a.gif | Bin 946 -> 0 bytes .../src/main/webapp/images/password.gif | Bin 239 -> 0 bytes .../src/main/webapp/images/prev.gif | Bin 303 -> 0 bytes .../src/main/webapp/images/project.png | Bin 742 -> 0 bytes .../src/main/webapp/images/pspbrwse.jbf | Bin 5674 -> 0 bytes .../src/main/webapp/images/rl-bullet.gif | Bin 46 -> 0 bytes .../src/main/webapp/images/role.gif | Bin 597 -> 0 bytes .../src/main/webapp/images/team.gif | Bin 159 -> 0 bytes .../main/webapp/images/template-gradient.gif | Bin 659 -> 0 bytes .../main/webapp/images/top-content-shadow.gif | Bin 325 -> 0 bytes .../src/main/webapp/images/top-left.gif | Bin 917 -> 0 bytes .../src/main/webapp/images/top-right.gif | Bin 922 -> 0 bytes .../src/main/webapp/images/trans.gif | Bin 43 -> 0 bytes .../src/main/webapp/index.jsp | 83 - .../src/main/webapp/loop.jsp | 15 - .../src/main/webapp/multi.jsp | 13 - .../src/main/webapp/multiAttribute.jsp | 21 - .../src/main/webapp/style/style.css | 66 - .../src/main/webapp/table.jsp | 184 - .../src/main/webapp/taglib/copyright.jsp | 13 - .../src/main/webapp/taglib/helloTag.jsp | 9 - .../src/main/webapp/taglib/helloTag2.jsp | 11 - .../src/main/webapp/taglib/helloTag3.jsp | 9 - .../src/main/webapp/test.jsp | 38 - .../src/main/webapp/toLowerCase.jsp | 13 - .../src/main/webapp/toUpperCase.jsp | 15 - .../src/main/webapp/vote.gif | Bin 65 -> 0 bytes .../dunwu/javaee/server/JettyFactory.java | 114 - .../github/dunwu/javaee/server/Profiles.java | 34 - .../dunwu/javaee/server/QuickStartServer.java | 35 - .../src/test/resources/jetty/webdefault.xml | 534 -- .../javaee-tutorial-websocket/pom.xml | 125 - .../dunwu/javaee/servlet/SocketServlet.java | 58 - .../javaee/websocket/WebSocketServer.java | 116 - .../WebSocketServerConfigurator.java | 19 - .../src/main/resources/logback.xml | 43 - .../src/main/webapp/WEB-INF/web.xml | 23 - .../src/main/webapp/websocketA.html | 60 - .../src/main/webapp/websocketB.html | 60 - .../github/dunwu/javaee/server/DevServer.java | 136 - .../src/test/resources/jetty/webdefault.xml | 534 -- .../src/test/resources/logback.xml | 43 - codes/javaee-tutorial/pom.xml | 121 - .../javaweb/ConsistentHashLoadBalance.java | 65 - .../io/github/dunwu/javaweb/LoadBalance.java | 21 - .../java/io/github/dunwu/javaweb/Node.java | 77 - .../dunwu/javaweb/RandomLoadBalance.java | 85 - .../dunwu/javaweb/RoundRobinLoadBalance.java | 87 - .../io/github/dunwu/javaweb/package-info.java | 7 - .../javaweb/support/CRCHashStrategy.java | 69 - .../javaweb/support/FnvHashStrategy.java | 24 - .../dunwu/javaweb/support/HashStrategy.java | 7 - .../javaweb/support/JdkHashCodeStrategy.java | 10 - .../javaweb/support/KetamaHashStrategy.java | 42 - .../javaweb/support/MurmurHashStrategy.java | 51 - .../dunwu/javaweb/LoadBalanceTests.java | 164 - .../github/dunwu/javaweb/StatisticsUtil.java | 35 - .../javaweb-mq/javaweb-mq-kafka/pom.xml | 36 - .../javaweb-mq/javaweb-mq-rocketmq/pom.xml | 32 - .../javaweb/rocketmq/OrderedProducer.java | 46 - codes/javaweb-example/javaweb-mq/pom.xml | 16 - codes/javaweb-example/pom.xml | 19 - docs/.vuepress/config.js | 90 +- docs/README.md | 122 +- docs/{ecology => }/cache/CacheInterview.md | 0 docs/{ecology => }/cache/Caffeine.md | 0 docs/{ecology => }/cache/Ehcache.md | 0 docs/{ecology => }/cache/README.md | 0 docs/{ecology => }/cache/Redis.md | 0 docs/ecology/README.md | 41 - docs/ecology/microservices/README.md | 4 - docs/ecology/mq/README.md | 9 - docs/ecology/mq/RocketmqAdvanced.md | 192 - docs/{ecology => }/framework/README.md | 0 docs/{ecology => }/framework/mybatis.md | 0 docs/{ecology => }/io/dozer.md | 0 docs/{ecology => }/io/javalib-binary.md | 0 docs/{ecology => }/io/javalib-json.md | 0 docs/{ecology => }/io/javalib-log.md | 0 docs/javaee/README.md | 19 - docs/javaee/javaee-cookie-sesion.md | 574 -- docs/javaee/javaee-filter-listener.md | 190 - docs/javaee/javaee-interview.md | 254 - docs/javaee/javaee-jsp.md | 1716 ----- docs/javaee/javaee-servlet.md | 296 - docs/mq/README.md | 15 + .../mq/ActiveMQ.md => mq/activemq.md} | 23 +- docs/mq/kafka/README.md | 5 + .../{ecology/mq => mq/kafka}/kafka-advance.md | 0 docs/{ecology/mq => mq/kafka}/kafka-basic.md | 0 docs/{ecology/mq => mq/kafka}/kafka-ops.md | 0 .../mq/MqInterview.md => mq/mq-interview.md} | 0 .../mq/RocketmqBasics.md => mq/rocketmq.md} | 197 +- docs/{ecology => }/other/README.md | 0 docs/{ecology => }/other/freemark.md | 0 docs/{ecology => }/other/javalib-util.md | 0 docs/{ecology => }/other/javamail.md | 0 docs/{ecology => }/other/jsoup.md | 0 docs/other/lombok.md | 373 + docs/{ecology => }/other/reflections.md | 0 docs/{ecology => }/other/thumbnailator.md | 0 docs/{ecology => }/other/zxing.md | 0 docs/{ecology => }/search/README.md | 21 +- .../{ecology => }/search/elastic-beats-ops.md | 0 docs/{ecology => }/search/elastic-beats.md | 0 .../search/elastic-kibana-ops.md | 0 docs/{ecology => }/search/elastic-kibana.md | 0 .../search/elastic-logstash-ops.md | 0 docs/{ecology => }/search/elastic-logstash.md | 0 .../search/elastic-quickstart.md | 2 +- docs/search/elasticsearch/README.md | 15 + .../elasticsearch/elasticsearch-ops.md} | 0 .../elasticsearch/elasticsearch.md} | 2 +- docs/{ecology => }/security/README.md | 0 docs/{ecology => }/security/shiro.md | 0 .../{ecology => }/security/spring-security.md | 0 docs/{ecology => }/server/README.md | 0 docs/{ecology => }/server/jetty.md | 0 docs/{ecology => }/server/tomcat.md | 0 docs/soa/README.md | 13 + docs/{ecology/microservices => soa}/dubbo.md | 0 docs/soa/zookeeper/README.md | 4 + .../zookeeper}/zookeeper-ops.md | 0 .../zookeeper}/zookeeper.md | 0 docs/{ecology => }/storage/README.md | 0 docs/{ecology => }/storage/shardingsphere.md | 0 docs/{ecology => }/test/README.md | 0 docs/{ecology => }/test/jmh.md | 8 +- docs/{ecology => }/test/junit.md | 0 docs/{ecology => }/test/mockito.md | 0 docs/tool/README.md | 21 - docs/tool/ide/README.md | 12 - docs/tool/ide/eclipse.md | 233 - docs/tool/ide/intellij-idea.md | 269 - docs/tool/ide/vscode.md | 47 - docs/tool/jmeter.md | 204 - docs/tool/monitor/arthas.md | 412 - docs/tool/monitor/skywalking.md | 27 - pom.xml | 4 +- 828 files changed, 12911 insertions(+), 40967 deletions(-) rename docs/ecology/other/lombok.md => codes/java-ecology/java-ecology-io/java-ecology-bean/README.md (100%) rename codes/{javaweb-example/javaweb-load-balance => java-ecology/java-ecology-io/java-ecology-bean}/pom.xml (70%) create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-bean/src/main/java/io/github/dunwu/javatech/lombok/DataDemo.java create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-bean/src/main/java/io/github/dunwu/javatech/lombok/EqualsAndHashCodeDemo.java create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-bean/src/main/java/io/github/dunwu/javatech/lombok/GetterAndSetterDemo.java create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-bean/src/main/java/io/github/dunwu/javatech/lombok/NonNullDemo.java create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-bean/src/main/java/io/github/dunwu/javatech/lombok/Person.java create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-bean/src/main/java/io/github/dunwu/javatech/lombok/ToStringDemo.java create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-bean/src/main/java/io/github/dunwu/javatech/lombok/package-info.java create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-bean/src/test/java/io/github/dunwu/javatech/lombok/LombokTest.java create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-file/pom.xml create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-file/src/main/java/io/github/dunwu/javatech/serialize/FstDemo.java create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-file/src/main/java/io/github/dunwu/javatech/serialize/JdkSerializeDemo.java create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-file/src/test/java/io/github/dunwu/javatech/bean/Person.java create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-file/src/test/java/io/github/dunwu/javatech/json/fastjson/FastjsonAnnotationBean.java create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-file/src/test/java/io/github/dunwu/javatech/json/fastjson/FastjsonTests.java create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-file/src/test/java/io/github/dunwu/javatech/json/jackson/JacksonAnnotationBean.java create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-file/src/test/java/io/github/dunwu/javatech/json/jackson/JacksonTests.java create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-file/src/test/java/io/github/dunwu/javatech/serialize/SerializeTest.java create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-json/pom.xml create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-json/src/main/java/io/github/dunwu/javatech/json/bean/Group.java create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-json/src/main/java/io/github/dunwu/javatech/json/bean/User.java create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-json/src/main/java/io/github/dunwu/javatech/json/fastjson/FastjsonFeildBean.java create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-json/src/main/java/io/github/dunwu/javatech/json/fastjson/JacksonAnnotationBean.java create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-json/src/main/java/io/github/dunwu/javatech/json/gson/GsonAnnotationBean.java create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-json/src/main/java/io/github/dunwu/javatech/json/gson/VersionedClass.java create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-json/src/test/java/io/github/dunwu/javatech/json/TestBean.java create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-json/src/test/java/io/github/dunwu/javatech/json/TestBean2.java create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-json/src/test/java/io/github/dunwu/javatech/json/fastjson/FastjsonCaseTests.java create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-json/src/test/java/io/github/dunwu/javatech/json/fastjson/FastjsonPerformanceTests.java create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-json/src/test/java/io/github/dunwu/javatech/json/gson/GsonCaseTests.java create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-json/src/test/java/io/github/dunwu/javatech/json/gson/GsonPerformanceTests.java create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-json/src/test/java/io/github/dunwu/javatech/json/jackson/JacksonPerformanceTests.java create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-json/src/test/java/io/github/dunwu/javatech/json/util/BeanUtils.java create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-serialize/pom.xml create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-serialize/src/main/java/io/github/dunwu/javatech/io/FstDemo.java create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-serialize/src/main/java/io/github/dunwu/javatech/io/JdkSerializeDemo.java create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-serialize/src/main/java/io/github/dunwu/javatech/io/KryoDemo.java create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-serialize/src/main/java/io/github/dunwu/javatech/io/bean/BeanUtils.java create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-serialize/src/main/java/io/github/dunwu/javatech/io/bean/TestBean.java create mode 100644 codes/java-ecology/java-ecology-io/java-ecology-serialize/src/test/java/io/github/dunwu/javatech/io/SerializePerformanceTest.java create mode 100644 codes/java-ecology/java-ecology-io/pom.xml create mode 100644 codes/java-ecology/java-ecology-log/java-ecology-log4j/pom.xml create mode 100644 codes/java-ecology/java-ecology-log/java-ecology-log4j/src/main/java/io/github/dunwu/javatech/log/Log4jDemo.java create mode 100644 codes/java-ecology/java-ecology-log/java-ecology-log4j/src/main/resources/log4j.xml create mode 100644 codes/java-ecology/java-ecology-log/java-ecology-log4j2/pom.xml create mode 100644 codes/java-ecology/java-ecology-log/java-ecology-log4j2/src/main/java/io/github/dunwu/javatech/log/Log4j2Demo.java create mode 100644 codes/java-ecology/java-ecology-log/java-ecology-log4j2/src/main/resources/log4j2.xml create mode 100644 codes/java-ecology/java-ecology-log/java-ecology-logback/pom.xml create mode 100644 codes/java-ecology/java-ecology-log/java-ecology-logback/src/main/java/io/github/dunwu/javatech/log/LogbackDemo.java create mode 100644 codes/java-ecology/java-ecology-log/java-ecology-logback/src/main/resources/logback.xml create mode 100644 codes/java-ecology/java-ecology-log/java-ecology-logstash/pom.xml create mode 100644 codes/java-ecology/java-ecology-log/java-ecology-logstash/src/main/java/io/github/dunwu/javatech/ElasticDemo.java create mode 100644 codes/java-ecology/java-ecology-log/java-ecology-logstash/src/main/resources/log4j.xml create mode 100644 codes/java-ecology/java-ecology-log/java-ecology-logstash/src/main/resources/logback.xml create mode 100644 codes/java-ecology/java-ecology-log/pom.xml rename codes/{javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot => java-ecology/java-ecology-mq/java-ecology-kafka-springboot}/pom.xml (84%) rename codes/{javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-mq/java-ecology-kafka-springboot/src/main/java/io/github/dunwu/javatech}/KafkaConsumer.java (93%) rename codes/{javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-mq/java-ecology-kafka-springboot/src/main/java/io/github/dunwu/javatech}/KafkaProducer.java (96%) rename codes/{javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-mq/java-ecology-kafka-springboot/src/main/java/io/github/dunwu/javatech}/KafkaProducerController.java (95%) rename codes/{javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-mq/java-ecology-kafka-springboot/src/main/java/io/github/dunwu/javatech}/MsgKafkaApplication.java (89%) rename codes/{javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-mq/java-ecology-kafka-springboot/src/main/java/io/github/dunwu/javatech}/config/KafkaConsumerConfig.java (98%) rename codes/{javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-mq/java-ecology-kafka-springboot/src/main/java/io/github/dunwu/javatech}/config/KafkaProducerConfig.java (97%) rename codes/{javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot => java-ecology/java-ecology-mq/java-ecology-kafka-springboot}/src/main/resources/application.properties (100%) rename codes/{javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot => java-ecology/java-ecology-mq/java-ecology-kafka-springboot}/src/main/resources/logback.xml (100%) rename codes/{javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/test/java/io/github/dunwu/javaweb => java-ecology/java-ecology-mq/java-ecology-kafka-springboot/src/test/java/io/github/dunwu/javatech}/KafkaProducerTest.java (94%) create mode 100644 codes/java-ecology/java-ecology-mq/java-ecology-kafka/pom.xml rename codes/{javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-mq/java-ecology-kafka/src/main/java/io/github/dunwu/javatech}/kafka/ConsumerAOC.java (97%) rename codes/{javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-mq/java-ecology-kafka/src/main/java/io/github/dunwu/javatech}/kafka/ConsumerManual.java (97%) rename codes/{javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-mq/java-ecology-kafka/src/main/java/io/github/dunwu/javatech}/kafka/ConsumerManualPartition.java (97%) rename codes/{javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-mq/java-ecology-kafka/src/main/java/io/github/dunwu/javatech}/kafka/ProducerDemo.java (97%) rename codes/{javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-mq/java-ecology-kafka/src/main/java/io/github/dunwu/javatech}/kafka/ProducerInTransaction.java (99%) rename codes/{javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-mq/java-ecology-kafka/src/main/java/io/github/dunwu/javatech}/kafka/StreamDemo.java (97%) rename codes/{javaweb-example/javaweb-mq/javaweb-mq-kafka => java-ecology/java-ecology-mq/java-ecology-kafka}/src/main/resources/logback.xml (93%) create mode 100644 codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/pom.xml rename codes/{javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech}/rocketmq/AsyncProducer.java (97%) rename codes/{javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech}/rocketmq/BatchProducer.java (98%) rename codes/{javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech}/rocketmq/BroadcastConsumer.java (97%) rename codes/{javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech}/rocketmq/BroadcastProducer.java (94%) rename codes/{javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech}/rocketmq/OnewayProducer.java (96%) rename codes/{javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech}/rocketmq/OrderedConsumer.java (97%) create mode 100644 codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech/rocketmq/OrderedProducer.java rename codes/{javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech}/rocketmq/RocketConfig.java (73%) rename codes/{javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech}/rocketmq/ScheduledMessageConsumer.java (96%) rename codes/{javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech}/rocketmq/ScheduledMessageProducer.java (95%) rename codes/{javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech}/rocketmq/SyncProducer.java (96%) rename codes/{javaweb-example/javaweb-mq/javaweb-mq-rocketmq => java-ecology/java-ecology-mq/java-ecology-rocketmq}/src/main/resources/logback.xml (93%) create mode 100644 codes/java-ecology/java-ecology-mq/pom.xml create mode 100644 codes/java-ecology/java-ecology-others/java-ecology-cli/pom.xml create mode 100644 codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/AnsiSystem.java create mode 100644 codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/CliDemo.java create mode 100644 codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/CliUtil.java create mode 100644 codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/SystemInfoUtil.java create mode 100644 codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/constant/AnsiBgColor.java create mode 100644 codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/constant/AnsiColor.java create mode 100644 codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/constant/AnsiElement.java create mode 100644 codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/constant/AnsiSgr.java create mode 100644 codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/constant/Color.java create mode 100644 codes/java-ecology/java-ecology-others/java-ecology-cli/src/test/java/io/github/dunwu/javatech/CliUtilTests.java create mode 100644 codes/java-ecology/java-ecology-others/java-ecology-cli/src/test/java/io/github/dunwu/javatech/SystemInfoTest.java create mode 100644 codes/java-ecology/java-ecology-others/java-ecology-mvel/pom.xml create mode 100644 codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/BasicRule.java create mode 100644 codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/DefaultRuleEngine.java create mode 100644 codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/MvelRule.java create mode 100644 codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/MvelRuleSet.java create mode 100644 codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/Rule.java create mode 100644 codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/RuleConstant.java create mode 100644 codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/RuleContext.java create mode 100644 codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/RuleEngine.java create mode 100644 codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/RuleEngineBuilder.java create mode 100644 codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/RuleEngineParams.java create mode 100644 codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/resources/logback.xml create mode 100644 codes/java-ecology/java-ecology-others/java-ecology-mvel/src/test/java/io/github/dunwu/javatech/mvel/ClassTests.java create mode 100644 codes/java-ecology/java-ecology-others/java-ecology-mvel/src/test/java/io/github/dunwu/javatech/mvel/SalaryRuleTest.java create mode 100644 codes/java-ecology/java-ecology-others/java-ecology-mvel/src/test/java/io/github/dunwu/javatech/mvel/demo.mvel create mode 100644 codes/java-ecology/java-ecology-others/java-ecology-mvel/src/test/resources/SalaryRule.json create mode 100644 codes/java-ecology/java-ecology-others/pom.xml create mode 100644 codes/java-ecology/java-ecology-test/pom.xml create mode 100644 codes/java-ecology/java-ecology-test/src/main/java/io/github/dunwu/javatech/jmh/QuickStart.java create mode 100644 codes/java-ecology/java-ecology-test/src/main/java/io/github/dunwu/javatech/jmh/StringBuilderBenchmark.java create mode 100644 codes/java-ecology/java-ecology-test/src/main/java/io/github/dunwu/javatech/lombok/Calculator.java create mode 100644 codes/java-ecology/java-ecology-test/src/main/java/io/github/dunwu/javatech/lombok/Person.java create mode 100644 codes/java-ecology/java-ecology-test/src/main/resources/logback.xml create mode 100644 codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit4/JUnitTest.java create mode 100644 codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit5/AssertionsTests.java create mode 100644 codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit5/AssumptionsTests.java create mode 100644 codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit5/DisplayNameTests.java create mode 100644 codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit5/DynamicTests.java create mode 100644 codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit5/NestedTests.java create mode 100644 codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit5/ParameterizedTests.java create mode 100644 codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit5/RepeatedTests.java create mode 100644 codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit5/StandardTests.java create mode 100644 codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/mockito/MockitoTest.java create mode 100644 codes/java-ecology/java-ecology-text/pom.xml create mode 100644 codes/java-ecology/java-ecology-text/src/main/java/io/github/dunwu/javatech/office/PinyinDemo.java create mode 100644 codes/java-ecology/java-ecology-text/src/main/java/io/github/dunwu/javatech/office/WordUtil.java create mode 100644 codes/java-ecology/java-ecology-text/src/test/java/io/github/dunwu/javatech/office/WordUtilTest.java create mode 100644 codes/java-ecology/java-ecology-tomcat/README.md create mode 100644 codes/java-ecology/java-ecology-tomcat/pom.xml create mode 100644 codes/java-ecology/java-ecology-tomcat/src/main/java/io/github/dunwu/javatech/controller/HelloController.java create mode 100644 codes/java-ecology/java-ecology-tomcat/src/main/java/io/github/dunwu/javatech/controller/IndexController.java create mode 100644 codes/java-ecology/java-ecology-tomcat/src/main/java/io/github/dunwu/javatech/server/SimpleTomcatServer.java create mode 100644 codes/java-ecology/java-ecology-tomcat/src/main/java/io/github/dunwu/javatech/server/TomcatServer.java create mode 100644 codes/java-ecology/java-ecology-tomcat/src/main/resources/logback.xml create mode 100644 codes/java-ecology/java-ecology-tomcat/src/main/resources/properties/application-develop.properties create mode 100644 codes/java-ecology/java-ecology-tomcat/src/main/resources/properties/application-test.properties create mode 100644 codes/java-ecology/java-ecology-tomcat/src/main/resources/spring/spring-servlet.xml create mode 100644 codes/java-ecology/java-ecology-tomcat/src/main/resources/tomcat/conf/server.xml create mode 100644 codes/java-ecology/java-ecology-tomcat/src/main/resources/tomcat/conf/web.xml rename codes/{javaee-tutorial/javaee-tutorial-filter => java-ecology/java-ecology-tomcat}/src/main/webapp/META-INF/MANIFEST.MF (100%) create mode 100644 codes/java-ecology/java-ecology-tomcat/src/main/webapp/WEB-INF/web.xml create mode 100644 codes/java-ecology/java-ecology-tomcat/src/main/webapp/views/jsp/hello.jsp create mode 100644 codes/java-ecology/java-ecology-tomcat/src/main/webapp/views/jsp/index.jsp rename codes/{javaweb-example/javaweb-zookeeper => java-ecology/java-ecology-zookeeper}/pom.xml (74%) rename codes/{javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech}/zk/dlock/Callback.java (81%) rename codes/{javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech}/zk/dlock/DLockTemplate.java (88%) rename codes/{javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech}/zk/dlock/DistributedLock.java (86%) rename codes/{javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech}/zk/dlock/TimeoutHandler.java (80%) rename codes/{javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech}/zk/dlock/ZkDLockTemplate.java (97%) rename codes/{javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech}/zk/dlock/ZkReentrantLockCleanerTask.java (98%) rename codes/{javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech}/zk/dlock/ZookeeperReentrantDistributedLock.java (98%) rename codes/{javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech}/zk/example/ZooKeeperConnection.java (97%) rename codes/{javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech}/zk/example/ZooKeeperCreate.java (97%) rename codes/{javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech}/zk/example/ZooKeeperDelete.java (96%) rename codes/{javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech}/zk/example/ZooKeeperExists.java (96%) rename codes/{javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech}/zk/example/ZooKeeperGetChildren.java (97%) rename codes/{javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech}/zk/example/ZooKeeperGetData.java (98%) rename codes/{javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech}/zk/example/ZooKeeperSetData.java (96%) rename codes/{javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech}/zk/example/package-info.java (73%) rename codes/{javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech}/zk/sequence/DistributedSequence.java (76%) rename codes/{javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb => java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech}/zk/sequence/ZkDistributedSequence.java (93%) rename codes/{javaweb-example/javaweb-zookeeper/src/test/java/io/github/dunwu/javaweb => java-ecology/java-ecology-zookeeper/src/test/java/io/github/dunwu/javatech}/zk/dlock/ZkReentrantLockTemplateTest.java (98%) rename codes/{javaweb-example/javaweb-zookeeper => java-ecology/java-ecology-zookeeper}/src/test/resources/logback.xml (100%) create mode 100644 codes/java-ecology/pom.xml delete mode 100644 codes/javaee-tutorial/README.md delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/pom.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/CacheFilter.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/CharacterEncodingFilter.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/ExceptionHandlerFilter.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/FilterImpl.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/GZipFilter.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/ImageRedirectFilter.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/LogFilter.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/MyFilter.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/OutputReplaceFilter.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/PrivilegeFilter.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/UploadFilter.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/WaterMarkFilter.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/XSLTFilter.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/exception/AccountException.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/exception/BusinessException.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/test/Download.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/test/GZipTest.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/CacheResponseWrapper.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/GZipOutputStream.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/GZipResponseWrapper.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/HttpCharacterResponseWrapper.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/UploadRequestWrapper.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/WaterMarkOutputStream.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/WaterMarkResponseWrapper.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/resources/logback.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/WEB-INF/logo.png delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/WEB-INF/privilege.properties delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/WEB-INF/sensitive.properties delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/WEB-INF/web.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/images/error.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/images/mm.jpg delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/images/sunset.jpg delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/images/winter.jpg delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/js/dojo.js delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/accountException.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/businessException.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/dispatcher.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/exception.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/image.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/index.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testCacheFilter.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testCharacterEncodingFilter.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testExceptionHandlerFilter.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testGZipFilter.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testOutputReplaceFilter.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testUploadFilter.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testWaterMarkFilter.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/book.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/book.xsl delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/demo.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/messageLog.xsl delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/thinkInJava.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/xml.xsl delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/FilterDemosBootstrap.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-filter/src/test/resources/jetty/webdefault.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/pom.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/com/sun/products/applet/demo/Graph.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Counter.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Message.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Person.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/IpUtil.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/IPEntry.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/IPSeeker.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/QQWry.dat delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/Test.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/Utils.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/resources/logback.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/WEB-INF/META-INF/MANIFEST.MF delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/WEB-INF/resources/jsp/index.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/WEB-INF/web.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/break.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/for.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/if.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/if2.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/scriptlet.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/switch.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/while.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/foot.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/head.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/include.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/page.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/date.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspforward.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspinclude.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspplugin.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspuseBean.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspuseBean2.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/useBean.html delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/05.implicit_object/error.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/05.implicit_object/exception.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/cookie/readCookie.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/cookie/session.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/cookie/writeCookie.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/language.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/language2.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/localeCurrency.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/localeDate.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/localePercent.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/date.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox.html delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox2.html delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox2.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formGet.html delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formGet.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formPost.html delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formPost.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/hitCount.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/mail/sendMail.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/redirect.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/refresh.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/01.helloWorld.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/01.helloWorld_zh.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/02.life.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/03.yourIp.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/configuration.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/images/bg-btn-blue.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/images/mail.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/images/vertical_line.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/css/style.css delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/date.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/forward.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/getProperty.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/include.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/useBean.html delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/useBean.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/application.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/break.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/comment.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/counter.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/include/foot.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/include/head.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/include/include.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/page/contentType.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/page/errorPage.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/page/isErrorPage.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/el.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/error.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/exception.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/grammar/for.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/grammar/if.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/grammar/while.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/greeting.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/index.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/life.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/method.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/plugin.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/return.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/scriptlet.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/taglib.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/Profiles.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/test/resources/jetty/webdefault.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jsp/src/test/resources/logback.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/pom.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/bean/Person.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/util/Example.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/util/Example_es_ES.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/util/Pagination.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/init.sql delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/logback.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/messages.properties delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/messages_zh_CN.properties delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/sql/create_employees.sql delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/sql/create_students.sql delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/WEB-INF/web.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_catch.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_choose.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_forEach.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_forTokens.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_if.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_import.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_out.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_param.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_redirect.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_remove.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_set.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_url.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_bundle.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_formatDate.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_formatNumber.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_message.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_parseDate.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_parseNumber.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_requestEncoding.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_setLocale.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_setTimeZone.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_timeZone.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_contains.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_containsIgnoreCase.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_endsWith.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_escapeXml.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_indexOf.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_join.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_length.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_replace.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_split.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_startsWith.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substring.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substringAfter.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substringBefore.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_toLowerCase.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_toUpperCase.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_trim.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_dateParam.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_param.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_query.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_setDataSource.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_transaction.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_update.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/bundle.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/catch.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/choose.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/contains.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/containsIgnoreCase.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/dateParam.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/endsWith.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/escapeXml.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/fmt.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/fn.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forEach.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forEachWithList.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forTokens.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/formatDate.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/formatNumber.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/if.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/import.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/index.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/indexOf.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/join.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/length.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/out.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/param.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parse.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parseDate.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parseNumber.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/query.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/queryPagination.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/queryReflect.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/redirect.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/remove.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/requestEncoding.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/set.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setBundle.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setDataSource.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setLocale.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setTimeZone.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/sina.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/split.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/sql.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/substring.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/timeZone.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/transaction.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/update.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/url.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_choose.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_forEach.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_if.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_out.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_set.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_transform.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/xml.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_choose.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_forEach.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_if.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_out.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_param.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_parse.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_set.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_transform.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/xml/books.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/xml/style.xsl delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/Profiles.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-jstl/src/test/resources/jetty/webdefault.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-listener/pom.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/LoginSessionListener.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionActivationListener.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionAttributeListener.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionBindingListener.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionListener.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextAttributeListener.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextListener.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestAttributeListener.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestListener.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/PersonInfoListener.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/bean/PersonInfo.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/util/ApplicationConstants.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-listener/src/main/resources/logback.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/META-INF/context.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/WEB-INF/resources/jsp/index.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/WEB-INF/web.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/a.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/aa.html delete mode 100644 codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/active.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/b.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/index.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/listener.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/online.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/testLoginSessionListener.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/ListenerDemosBootstrap.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-listener/src/test/resources/jetty/webdefault.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/pom.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/DsaCoder.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/HmacCoder.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/MdCoder.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/ShaCoder.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/AESCoder.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/Base64Demo.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/DESCoder.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/DESedeCoder.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/PBECoder.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/RSACoder.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/DownloadPolicy.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/ModifyPolicy.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/TokenUtil.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/UploadConstant.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/UploadPolicy.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/html/CnblogParser.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/html/XiamiParser.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/ImageUtil.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/QRCodeUtil.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/dto/BarcodeParamDTO.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/dto/ImageParamDTO.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSHelloWorld.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSReceiver.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSSender.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/logging/JclDemo.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/logging/Slf4jDemo.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/ForwardMail.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailConfigDTO.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailDTO.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailUtil.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendAttachmentMail.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendTemplateMail.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendTextMail.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/StoreMail.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/sendHtmlMail.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/LoadVelocityDemo.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityHelloWorld.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityHelloWorld2.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityUtil.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/html/example.html delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/images/lion.jpg delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/images/lion2.jpg delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/log4j.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/logback.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/mail/mail.properties delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/footer.vm delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/header.vm delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/hello.vm delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/mail.vm delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/velocity.properties delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/html/JsoupTest.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/ImageUtilTest.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/QRCodeUtilTest.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/ThumbnailatorTest.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/template/VelocityUtilTest.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/test/JUnitExecTest.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/html/jsoup-cookbook.html delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/images/lion.jpg delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/images/lion2.jpg delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/images/lion2_watermark.png delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/images/wartermark.png delete mode 100644 codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/logback.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-servlet/pom.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/AnnotationServlet.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ContextParamServlet.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/FirstServlet.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ForwardServlet.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/HelloServlet.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ImageServlet.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/InitParamServlet.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/InjectionServlet.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/LifeCycleServlet.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/PostServlet.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/RedirectServlet.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/RequestServlet.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ThreadSafetyServlet.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/UploadServlet.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/ProgressUploadServlet.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/UploadListener.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/UploadStatus.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-servlet/src/main/resources/logback.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/WEB-INF/notice.html delete mode 100644 codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/WEB-INF/web.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/index.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/views/jsp/postPersonalInformation.html delete mode 100644 codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/Profiles.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-servlet/src/test/resources/jetty/webdefault.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-servlet/src/test/resources/logback.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/pom.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/AddCookies.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/DeleteCookies.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/ReadCookies.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Person.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Topic.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/session/SessionTrackServlet.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/resources/logback.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/META-INF/context.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/WEB-INF/web.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/encodeURL.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/index.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/css/style.css delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/images/bg-btn-blue.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/images/cookie.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/images/errorstate.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/images/mail.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/images/vertical_line.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/addCookies.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/base64.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/base64_decode.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/cookie.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/cookie.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/cookieAttribute.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/cookieDomain.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/encoding.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/history.js delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/history.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/javascript.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/login.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/loginCookie.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/maxAge.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/setCookie.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/session/session.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/session/vote.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/session/welcome.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/Profiles.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-session/src/test/resources/jetty/webdefault.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/pom.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/Copyright.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag2.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag3.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/bean/Person.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/function/Function.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/AddTag.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/Copyright.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/Copyright2.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/DynamicAttributeTag.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/HelloTag.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/IteratorTag.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/LoopTag.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/ToLowerCaseTag.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/table/Column.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/table/Table.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/MultiAttributeTag.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/MultiTag.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/RepeatTag.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/ToUpperCaseTag.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/Messages.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/TestMessage.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/messages.properties delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/resources/copyright.properties delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/resources/logback.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/function.tld delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello.tld delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello2.tld delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello3.tld delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/taglib.tld delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/web.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/dynamic.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/function.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/hello.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/I.png delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/L.png delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/Lminus.png delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/Lplus.png delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/T.png delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/Thumbs.db delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/Tminus.png delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/Tplus.png delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/asc.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/bg-btn-blue.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/bg_4.jpg delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/blank.png delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/bottom-left.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/bottom-right.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/btn-go-dark.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/delete.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/delimiter.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/desc.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/edit.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/element.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/file.png delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/foldericon.png delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/ibm-tab-background.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/ibm_logo.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/left-nav-corner.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/leftnav-overview-highlight.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/line01.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/masthead-links-gradient.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/memo.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/new.png delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/next.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/openfoldericon.png delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/pagetools-gradient.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/pagetools_gradient_a.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/password.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/prev.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/project.png delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/pspbrwse.jbf delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/rl-bullet.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/role.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/team.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/template-gradient.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/top-content-shadow.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/top-left.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/top-right.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/trans.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/index.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/loop.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/multi.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/multiAttribute.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/style/style.css delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/table.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/taglib/copyright.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/taglib/helloTag.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/taglib/helloTag2.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/taglib/helloTag3.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/test.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/toLowerCase.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/toUpperCase.jsp delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/vote.gif delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/Profiles.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-taglib/src/test/resources/jetty/webdefault.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-websocket/pom.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/servlet/SocketServlet.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/websocket/WebSocketServer.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/websocket/WebSocketServerConfigurator.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-websocket/src/main/resources/logback.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-websocket/src/main/webapp/WEB-INF/web.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-websocket/src/main/webapp/websocketA.html delete mode 100644 codes/javaee-tutorial/javaee-tutorial-websocket/src/main/webapp/websocketB.html delete mode 100644 codes/javaee-tutorial/javaee-tutorial-websocket/src/test/java/io/github/dunwu/javaee/server/DevServer.java delete mode 100644 codes/javaee-tutorial/javaee-tutorial-websocket/src/test/resources/jetty/webdefault.xml delete mode 100644 codes/javaee-tutorial/javaee-tutorial-websocket/src/test/resources/logback.xml delete mode 100644 codes/javaee-tutorial/pom.xml delete mode 100644 codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/ConsistentHashLoadBalance.java delete mode 100644 codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/LoadBalance.java delete mode 100644 codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/Node.java delete mode 100644 codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/RandomLoadBalance.java delete mode 100644 codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/RoundRobinLoadBalance.java delete mode 100644 codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/package-info.java delete mode 100644 codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/CRCHashStrategy.java delete mode 100644 codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/FnvHashStrategy.java delete mode 100644 codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/HashStrategy.java delete mode 100644 codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/JdkHashCodeStrategy.java delete mode 100644 codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/KetamaHashStrategy.java delete mode 100644 codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/MurmurHashStrategy.java delete mode 100644 codes/javaweb-example/javaweb-load-balance/src/test/java/io/github/dunwu/javaweb/LoadBalanceTests.java delete mode 100644 codes/javaweb-example/javaweb-load-balance/src/test/java/io/github/dunwu/javaweb/StatisticsUtil.java delete mode 100644 codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/pom.xml delete mode 100644 codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/pom.xml delete mode 100644 codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedProducer.java delete mode 100644 codes/javaweb-example/javaweb-mq/pom.xml delete mode 100644 codes/javaweb-example/pom.xml rename docs/{ecology => }/cache/CacheInterview.md (100%) rename docs/{ecology => }/cache/Caffeine.md (100%) rename docs/{ecology => }/cache/Ehcache.md (100%) rename docs/{ecology => }/cache/README.md (100%) rename docs/{ecology => }/cache/Redis.md (100%) delete mode 100644 docs/ecology/README.md delete mode 100644 docs/ecology/microservices/README.md delete mode 100644 docs/ecology/mq/README.md delete mode 100644 docs/ecology/mq/RocketmqAdvanced.md rename docs/{ecology => }/framework/README.md (100%) rename docs/{ecology => }/framework/mybatis.md (100%) rename docs/{ecology => }/io/dozer.md (100%) rename docs/{ecology => }/io/javalib-binary.md (100%) rename docs/{ecology => }/io/javalib-json.md (100%) rename docs/{ecology => }/io/javalib-log.md (100%) delete mode 100644 docs/javaee/README.md delete mode 100644 docs/javaee/javaee-cookie-sesion.md delete mode 100644 docs/javaee/javaee-filter-listener.md delete mode 100644 docs/javaee/javaee-interview.md delete mode 100644 docs/javaee/javaee-jsp.md delete mode 100644 docs/javaee/javaee-servlet.md create mode 100644 docs/mq/README.md rename docs/{ecology/mq/ActiveMQ.md => mq/activemq.md} (96%) create mode 100644 docs/mq/kafka/README.md rename docs/{ecology/mq => mq/kafka}/kafka-advance.md (100%) rename docs/{ecology/mq => mq/kafka}/kafka-basic.md (100%) rename docs/{ecology/mq => mq/kafka}/kafka-ops.md (100%) rename docs/{ecology/mq/MqInterview.md => mq/mq-interview.md} (100%) rename docs/{ecology/mq/RocketmqBasics.md => mq/rocketmq.md} (52%) rename docs/{ecology => }/other/README.md (100%) rename docs/{ecology => }/other/freemark.md (100%) rename docs/{ecology => }/other/javalib-util.md (100%) rename docs/{ecology => }/other/javamail.md (100%) rename docs/{ecology => }/other/jsoup.md (100%) create mode 100644 docs/other/lombok.md rename docs/{ecology => }/other/reflections.md (100%) rename docs/{ecology => }/other/thumbnailator.md (100%) rename docs/{ecology => }/other/zxing.md (100%) rename docs/{ecology => }/search/README.md (86%) rename docs/{ecology => }/search/elastic-beats-ops.md (100%) rename docs/{ecology => }/search/elastic-beats.md (100%) rename docs/{ecology => }/search/elastic-kibana-ops.md (100%) rename docs/{ecology => }/search/elastic-kibana.md (100%) rename docs/{ecology => }/search/elastic-logstash-ops.md (100%) rename docs/{ecology => }/search/elastic-logstash.md (100%) rename docs/{ecology => }/search/elastic-quickstart.md (99%) create mode 100644 docs/search/elasticsearch/README.md rename docs/{ecology/search/elastic-elasticsearch-ops.md => search/elasticsearch/elasticsearch-ops.md} (100%) rename docs/{ecology/search/elastic-elasticsearch.md => search/elasticsearch/elasticsearch.md} (99%) rename docs/{ecology => }/security/README.md (100%) rename docs/{ecology => }/security/shiro.md (100%) rename docs/{ecology => }/security/spring-security.md (100%) rename docs/{ecology => }/server/README.md (100%) rename docs/{ecology => }/server/jetty.md (100%) rename docs/{ecology => }/server/tomcat.md (100%) create mode 100644 docs/soa/README.md rename docs/{ecology/microservices => soa}/dubbo.md (100%) create mode 100644 docs/soa/zookeeper/README.md rename docs/{ecology/microservices => soa/zookeeper}/zookeeper-ops.md (100%) rename docs/{ecology/microservices => soa/zookeeper}/zookeeper.md (100%) rename docs/{ecology => }/storage/README.md (100%) rename docs/{ecology => }/storage/shardingsphere.md (100%) rename docs/{ecology => }/test/README.md (100%) rename docs/{ecology => }/test/jmh.md (97%) rename docs/{ecology => }/test/junit.md (100%) rename docs/{ecology => }/test/mockito.md (100%) delete mode 100644 docs/tool/README.md delete mode 100644 docs/tool/ide/README.md delete mode 100644 docs/tool/ide/eclipse.md delete mode 100644 docs/tool/ide/intellij-idea.md delete mode 100644 docs/tool/ide/vscode.md delete mode 100644 docs/tool/jmeter.md delete mode 100644 docs/tool/monitor/arthas.md delete mode 100644 docs/tool/monitor/skywalking.md diff --git a/.gitignore b/.gitignore index 7381f180..83948575 100644 --- a/.gitignore +++ b/.gitignore @@ -37,7 +37,6 @@ package-lock.json node_modules # temp folders -build .temp dist _book diff --git a/README.md b/README.md index b3a24805..cde4e90f 100644 --- a/README.md +++ b/README.md @@ -12,107 +12,84 @@ > ☕ **javatech** 汇总了 Java 开发中常见的主流技术的应用、特性、原理。 > -> - 🔁 项目同步维护:[Github](https://github.com/dunwu/javatech/) | [Gitee](https://gitee.com/turnon/javaweb/) -> - 📖 电子书阅读:[Github Pages](https://dunwu.github.io/javatech/) | [Gitee Pages](http://turnon.gitee.io/javaweb/) +> - 🔁 项目同步维护:[Github](https://github.com/dunwu/javatech/) | [Gitee](https://gitee.com/turnon/javatech/) +> - 📖 电子书阅读:[Github Pages](https://dunwu.github.io/javatech/) | [Gitee Pages](http://turnon.gitee.io/javatech/) > > 说明:下面的内容清单中,凡是有 📚 标记的技术,都已整理成详细的教程。 ## Java 生态 -### 框架 +## 框架 - [Spring](https://dunwu.github.io/spring-tutorial/) 📚 - [Spring Boot](https://dunwu.github.io/spring-boot-tutorial/) 📚 -- [Mybatis](docs/ecology/framework/mybatis.md) -- [ShardingSphere](docs/ecology/storage/shardingsphere.md) +- [Mybatis](docs/framework/mybatis.md) +- [ShardingSphere](docs/storage/shardingsphere.md) -### 消息队列 +## 消息队列 -- [消息队列面经](docs/ecology/mq/MqInterview.md) -- [Kafka 应用指南基础篇](docs/ecology/mq/kafka-basic.md) -- [Kafka 应用指南进阶篇](docs/ecology/mq/kafka-advance.md) -- [Kafka 运维指南](docs/ecology/mq/kafka-ops.md) -- [RocketMQ 基础篇](docs/ecology/mq/RocketmqBasics.md) -- [RocketMQ 进阶篇](docs/ecology/mq/RocketmqAdvanced.md) -- [ActiveMQ 实战篇](docs/ecology/mq/ActiveMQ.md) - -### 缓存 - -- [缓存面经](docs/ecology/cache/CacheInterview.md) -- [Redis](docs/ecology/cache/Redis.md) -- [Ehcache](docs/ecology/cache/Ehcache.md) -- [Caffeine](docs/ecology/cache/Caffeine.md) - -### 安全 - -> Java 领域比较流行的安全框架就是 shiro 和 spring-security。 +> 消息队列(Message Queue,简称 MQ)技术是分布式应用间交换信息的一种技术。 > -> shiro 更为简单、轻便,容易理解,能满足大多数基本安全场景下的需要。 +> 消息队列主要解决应用耦合,异步消息,流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构。是大型分布式系统不可缺少的中间件。 > -> spring-security 功能更丰富,也比 shiro 更复杂。值得一提的是由于 spring-security 是 spring 团队开发,所以集成 spring 和 spring-boot 框架更容易。 +> 如果想深入学习各种消息队列产品,建议先了解一下 [消息队列基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/distributed/mq-theory.md) ,有助于理解消息队列特性的实现和设计思路。 -- [shiro](docs/ecology/security/shiro.md) -- [spring-security](docs/ecology/security/spring-security.md) +- [消息队列面经](docs/mq/mq-interview.md) +- [Kafka](docs/mq/kafka) +- [RocketMQ](docs/mq/rocketmq.md) +- [ActiveMQ](docs/mq/activemq.md) -### 微服务 +## 缓存 -- [Dubbo](docs/ecology/microservices/dubbo.md) -- Spring Cloud -- [ZooKeeper 应用指南](docs/ecology/microservices/zookeeper.md) -- [ZooKeeper 运维指南](docs/ecology/microservices/zookeeper-ops.md) - -### 测试 - -- [Junit](docs/ecology/test/junit.md) -- [Mockito](docs/ecology/test/mockito.md) -- [JMH](docs/ecology/test/jmh.md) - -### 服务器 - -> Tomcat 和 Jetty 都是 Java 比较流行的轻量级服务器。 +> 缓存可以说是优化系统性能的第一手段,在各种技术中都会有缓存的应用。 > -> Nginx 是目前最流行的反向代理服务器,也常用于负载均衡。 +> 如果想深入学习缓存,建议先了解一下 [缓存基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/distributed/cache-theory.md),有助于理解缓存的特性、原理,使用缓存常见的问题及解决方案。 -- [Tomcat](docs/ecology/server/tomcat.md) -- [Jetty](docs/ecology/server/jetty.md) -- [Nginx](https://github.com/dunwu/nginx-tutorial) 📚 +- [缓存面经](docs/cache/CacheInterview.md) +- [Redis](docs/cache/Redis.md) +- [Ehcache](docs/cache/Ehcache.md) +- [Caffeine](docs/cache/Caffeine.md) -## Java 工具 +## 微服务 -### 构建 +- [Dubbo](docs/soa/dubbo.md) +- Spring Cloud +- [ZooKeeper](docs/soa/zookeeper) +- Eureka +- Consul +- Nacos +- Zuul +- Gateway -> Java 项目需要通过 [**构建工具**](docs/tool/build) 来管理项目依赖,完成编译、打包、发布、生成 JavaDoc 等任务。 -> -> - 目前最主流的构建工具是 Maven,它的功能非常强大。 -> - Gradle 号称是要替代 Maven 等构件工具,它的版本管理确实简洁,但是需要学习 Groovy,学习成本比 Maven 高。 -> - Ant 功能比 Maven 和 Gradle 要弱,现代 Java 项目基本不用了,但也有一些传统的 Java 项目还在使用。 +## 搜索引擎 -- [Maven](docs/tool/build/maven) 📚 -- [Ant](docs/tool/build/ant.md) +- [ElasticSearch](docs/search/elasticsearch) +- Solr +- Lucene -### IDE +## 安全 -> 自动有了 [**IDE**](docs/tool/ide),写代码从此就告别了刀耕火种的蛮荒时代。 +> Java 领域比较流行的安全框架就是 shiro 和 spring-security。 +> +> shiro 更为简单、轻便,容易理解,能满足大多数基本安全场景下的需要。 > -> - [Eclipse](docs/tool/ide/eclipse.md) 是久负盛名的开源 Java IDE,我的学生时代一直使用它写 Java。 -> - 曾经抗拒从转 [Intellij Idea](docs/tool/ide/intellij-idea.md) ,但后来发现真香,不得不说,确实是目前最优秀的 Java IDE。 -> - 你可以在 [vscode](docs/tool/ide/vscode.md) 中写各种语言,只要安装相应插件即可。如果你的项目中使用了很多种编程语言,又懒得在多个 IDE 之间切换,那么就用 vscode 来一网打尽吧。 +> spring-security 功能更丰富,也比 shiro 更复杂。值得一提的是由于 spring-security 是 spring 团队开发,所以集成 spring 和 spring-boot 框架更容易。 -- [Intellij Idea](docs/tool/ide/intellij-idea.md) -- [Eclipse](docs/tool/ide/eclipse.md) -- [vscode](docs/tool/ide/vscode.md) +- [Shiro](docs/security/shiro.md) +- [Spring Security](docs/security/spring-security.md) -### 监控 +## 测试 -- [Arthas](docs/tool/monitor/arthas.md) -- [SkyWalking](docs/tool/monitor/skywalking.md) +- [Junit](docs/test/junit.md) +- [Mockito](docs/test/mockito.md) +- [JMH](docs/test/jmh.md) -## JavaEE +## 服务器 -> [☕ JavaEE](docs/javaee/README.md) 技术是 Java Web 的基石 +> Tomcat 和 Jetty 都是 Java 比较流行的轻量级服务器。 +> +> Nginx 是目前最流行的反向代理服务器,也常用于负载均衡。 -- [JavaEE 面经](docs/javaee/javaee-interview.md) -- [Servlet](docs/javaee/javaee-servlet.md) -- [Jsp](docs/javaee/javaee-jsp.md) -- [Filter 和 Listener](docs/javaee/javaee-filter-listener.md) -- [Cookie 和 Session](docs/javaee/javaee-cookie-sesion.md) +- [Tomcat](docs/server/tomcat.md) +- [Jetty](docs/server/jetty.md) +- [Nginx](https://github.com/dunwu/nginx-tutorial) 📚 diff --git a/docs/ecology/other/lombok.md b/codes/java-ecology/java-ecology-io/java-ecology-bean/README.md similarity index 100% rename from docs/ecology/other/lombok.md rename to codes/java-ecology/java-ecology-io/java-ecology-bean/README.md diff --git a/codes/javaweb-example/javaweb-load-balance/pom.xml b/codes/java-ecology/java-ecology-io/java-ecology-bean/pom.xml similarity index 70% rename from codes/javaweb-example/javaweb-load-balance/pom.xml rename to codes/java-ecology/java-ecology-io/java-ecology-bean/pom.xml index 9520a7fd..294eff9d 100644 --- a/codes/javaweb-example/javaweb-load-balance/pom.xml +++ b/codes/java-ecology/java-ecology-io/java-ecology-bean/pom.xml @@ -7,14 +7,14 @@ io.github.dunwu dunwu-parent - 0.5.0 + 0.5.1 - io.github.dunwu.javaweb - javaweb-load-balance + io.github.dunwu.javatech + java-ecology-bean 1.0.0 jar - ${project.artifactId} + java-ecology-bean UTF-8 @@ -25,19 +25,20 @@ - io.github.dunwu - dunwu-common-core - 0.5.1 - - - io.github.dunwu - dunwu-tool-core - 0.5.1 + org.projectlombok + lombok + junit junit test + + org.assertj + assertj-core + test + + diff --git a/codes/java-ecology/java-ecology-io/java-ecology-bean/src/main/java/io/github/dunwu/javatech/lombok/DataDemo.java b/codes/java-ecology/java-ecology-io/java-ecology-bean/src/main/java/io/github/dunwu/javatech/lombok/DataDemo.java new file mode 100644 index 00000000..6af4f3bb --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-bean/src/main/java/io/github/dunwu/javatech/lombok/DataDemo.java @@ -0,0 +1,23 @@ +package io.github.dunwu.javatech.lombok; + +import lombok.Data; + +import java.util.List; + +/** + * @Data 示例 + * + * @author Zhang Peng + * @see @Data + * @since 2019-11-22 + */ +@Data(staticConstructor = "of") +public class DataDemo { + + private final Person founder; + + protected List employees; + + private String name; + +} diff --git a/codes/java-ecology/java-ecology-io/java-ecology-bean/src/main/java/io/github/dunwu/javatech/lombok/EqualsAndHashCodeDemo.java b/codes/java-ecology/java-ecology-io/java-ecology-bean/src/main/java/io/github/dunwu/javatech/lombok/EqualsAndHashCodeDemo.java new file mode 100644 index 00000000..4eaf3ef1 --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-bean/src/main/java/io/github/dunwu/javatech/lombok/EqualsAndHashCodeDemo.java @@ -0,0 +1,57 @@ +package io.github.dunwu.javatech.lombok; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NonNull; + +class Parent {} + +/** + * @EqualsAndHashCode 示例 + * + * @author Zhang Peng + * @see @EqualsAndHashCode + * @since 2019-11-22 + */ +@Data +@EqualsAndHashCode(callSuper = true, exclude = { "address", "city", "state", "zip" }) +public class EqualsAndHashCodeDemo extends Person { + + @NonNull + private String name; + + @NonNull + private Gender gender; + + private String ssn; + + private String address; + + private String city; + + private String state; + + private String zip; + + public EqualsAndHashCodeDemo(@NonNull String name, @NonNull Gender gender) { + this.name = name; + this.gender = gender; + } + + public EqualsAndHashCodeDemo(@NonNull String name, @NonNull Gender gender, + String ssn, String address, String city, String state, String zip) { + this.name = name; + this.gender = gender; + this.ssn = ssn; + this.address = address; + this.city = city; + this.state = state; + this.zip = zip; + } + + enum Gender { + Male, + Female + } + +} diff --git a/codes/java-ecology/java-ecology-io/java-ecology-bean/src/main/java/io/github/dunwu/javatech/lombok/GetterAndSetterDemo.java b/codes/java-ecology/java-ecology-io/java-ecology-bean/src/main/java/io/github/dunwu/javatech/lombok/GetterAndSetterDemo.java new file mode 100644 index 00000000..1d5f86dd --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-bean/src/main/java/io/github/dunwu/javatech/lombok/GetterAndSetterDemo.java @@ -0,0 +1,23 @@ +package io.github.dunwu.javatech.lombok; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; + +/** + * @Getter@Setter 示例 + * + * @author Zhang Peng + * @see @Getter and @Setter + * @since 2019-11-22 + */ +public class GetterAndSetterDemo { + + @Getter + @Setter + private boolean employed = true; + + @Setter(AccessLevel.PROTECTED) + private String name; + +} diff --git a/codes/java-ecology/java-ecology-io/java-ecology-bean/src/main/java/io/github/dunwu/javatech/lombok/NonNullDemo.java b/codes/java-ecology/java-ecology-io/java-ecology-bean/src/main/java/io/github/dunwu/javatech/lombok/NonNullDemo.java new file mode 100644 index 00000000..02db07e5 --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-bean/src/main/java/io/github/dunwu/javatech/lombok/NonNullDemo.java @@ -0,0 +1,23 @@ +package io.github.dunwu.javatech.lombok; + +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; + +import java.util.List; + +/** + * @NonNull 示例 + * + * @author Zhang Peng + * @see @NonNull + * @since 2019-11-22 + */ +public class NonNullDemo { + + @Getter + @Setter + @NonNull + private List members; + +} diff --git a/codes/java-ecology/java-ecology-io/java-ecology-bean/src/main/java/io/github/dunwu/javatech/lombok/Person.java b/codes/java-ecology/java-ecology-io/java-ecology-bean/src/main/java/io/github/dunwu/javatech/lombok/Person.java new file mode 100644 index 00000000..cf4cfaf4 --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-bean/src/main/java/io/github/dunwu/javatech/lombok/Person.java @@ -0,0 +1,26 @@ +package io.github.dunwu.javatech.lombok; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +/** + * @Data@ToString@EqualsAndHashCode 示例 + * + * @author Zhang Peng + * @see @Data + * 、@ToString@EqualsAndHashCode + * @since 2019-11-22 + */ +@Data +@ToString(exclude = "age") +@EqualsAndHashCode(exclude = { "age", "sex" }) +public class Person { + + private String name; + + private Integer age; + + private String sex; + +} diff --git a/codes/java-ecology/java-ecology-io/java-ecology-bean/src/main/java/io/github/dunwu/javatech/lombok/ToStringDemo.java b/codes/java-ecology/java-ecology-io/java-ecology-bean/src/main/java/io/github/dunwu/javatech/lombok/ToStringDemo.java new file mode 100644 index 00000000..9eb8afd4 --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-bean/src/main/java/io/github/dunwu/javatech/lombok/ToStringDemo.java @@ -0,0 +1,24 @@ +package io.github.dunwu.javatech.lombok; + +import lombok.ToString; + +/** + * @author Zhang Peng + * @since 2019-11-22 + */ +@ToString(callSuper = true, exclude = "someExcludedField") +public class ToStringDemo { + + private boolean someBoolean = true; + + private String someStringField; + + private float someExcludedField; + + public ToStringDemo(boolean someBoolean, String someStringField, float someExcludedField) { + this.someBoolean = someBoolean; + this.someStringField = someStringField; + this.someExcludedField = someExcludedField; + } + +} diff --git a/codes/java-ecology/java-ecology-io/java-ecology-bean/src/main/java/io/github/dunwu/javatech/lombok/package-info.java b/codes/java-ecology/java-ecology-io/java-ecology-bean/src/main/java/io/github/dunwu/javatech/lombok/package-info.java new file mode 100644 index 00000000..19b652e5 --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-bean/src/main/java/io/github/dunwu/javatech/lombok/package-info.java @@ -0,0 +1,8 @@ +/** + * 本 package 下所有代码均为 Lombok 示例 + * + * @author Zhang Peng + * @see Reducing Boilerplate Code with Project Lombok + * @since 2019-11-22 + */ +package io.github.dunwu.javatech.lombok; diff --git a/codes/java-ecology/java-ecology-io/java-ecology-bean/src/test/java/io/github/dunwu/javatech/lombok/LombokTest.java b/codes/java-ecology/java-ecology-io/java-ecology-bean/src/test/java/io/github/dunwu/javatech/lombok/LombokTest.java new file mode 100644 index 00000000..f4bb87a2 --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-bean/src/test/java/io/github/dunwu/javatech/lombok/LombokTest.java @@ -0,0 +1,116 @@ +package io.github.dunwu.javatech.lombok; + +import lombok.Cleanup; +import org.junit.Test; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Lombok 单元测试 + * + * @author Zhang Peng + * @see Reducing Boilerplate Code with Project Lombok + */ +public class LombokTest { + + @Test + public void testGetterAndSetterDemo() { + GetterAndSetterDemo demo = new GetterAndSetterDemo(); + demo.setEmployed(true); + demo.setName("xxx"); + assertThat(demo.isEmployed()).isTrue(); + } + + @Test + public void testToStringDemo() { + ToStringDemo demo = new ToStringDemo(true, "abc", 0.5f); + System.out.println(demo.toString()); + assertThat(demo.toString()).contains("someBoolean=true, someStringField=abc"); + + Person person = new Person(); + person.setName("张三"); + person.setAge(20); + person.setSex("男"); + System.out.println(person.toString()); + assertThat(person.toString()).isEqualTo("Person(name=张三, sex=男)"); + } + + @Test + public void testEqualsAndHashCodeDemo() { + EqualsAndHashCodeDemo demo1 = + new EqualsAndHashCodeDemo("name1", EqualsAndHashCodeDemo.Gender.Female, "ssn", "xxx", "xxx", "xxx", "xxx"); + EqualsAndHashCodeDemo demo2 = + new EqualsAndHashCodeDemo("name1", EqualsAndHashCodeDemo.Gender.Female, "ssn", "ooo", "ooo", "ooo", "ooo"); + assertThat(demo1.equals(demo2)).isTrue(); + + Person person = new Person(); + person.setName("张三"); + person.setAge(20); + person.setSex("男"); + + Person person2 = new Person(); + person2.setName("张三"); + person2.setAge(18); + person2.setSex("男"); + + Person person3 = new Person(); + person3.setName("李四"); + person3.setAge(20); + person3.setSex("男"); + + assertThat(person.equals(person2)).isTrue(); + assertThat(person.equals(person3)).isFalse(); + } + + @Test + public void testDataDemo() { + Person huangshiren = new Person(); + huangshiren.setName("黄世仁"); + huangshiren.setAge(30); + huangshiren.setSex("男"); + Person yangbailao = new Person(); + yangbailao.setName("杨白劳"); + yangbailao.setAge(50); + yangbailao.setSex("男"); + Person xiaobaicai = new Person(); + xiaobaicai.setName("小白菜"); + xiaobaicai.setAge(20); + xiaobaicai.setSex("女"); + + List personList = new ArrayList<>(); + personList.add(yangbailao); + personList.add(xiaobaicai); + + DataDemo demo = DataDemo.of(huangshiren); + demo.setName("黑心农产品公司"); + demo.setEmployees(personList); + assertThat(demo.getName()).isEqualTo("黑心农产品公司"); + assertThat(demo.getFounder()).isEqualTo(huangshiren); + + System.out.println("公司名:" + demo.getName()); + System.out.println("创始人:" + demo.getFounder()); + System.out.println("员工信息"); + assertThat(demo.getEmployees()).contains(yangbailao, xiaobaicai); + demo.getEmployees().forEach(person -> { + System.out.println(person.toString()); + }); + } + + @Test + public void testCleanUp() { + try { + @Cleanup + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(new byte[] { 'Y', 'e', 's' }); + System.out.println(baos.toString()); + } catch (IOException e) { + e.printStackTrace(); + } + } + +} diff --git a/codes/java-ecology/java-ecology-io/java-ecology-file/pom.xml b/codes/java-ecology/java-ecology-io/java-ecology-file/pom.xml new file mode 100644 index 00000000..2fdefc14 --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-file/pom.xml @@ -0,0 +1,67 @@ + + + 4.0.0 + + + io.github.dunwu + dunwu-parent + 0.5.1 + + + io.github.dunwu.javatech + java-ecology-file + 1.0.0 + jar + java-ecology-file + + + UTF-8 + 1.8 + ${java.version} + ${java.version} + + + + + + de.ruedigermoeller + fst + 2.56 + + + + com.alibaba + fastjson + 1.2.56 + + + com.fasterxml.jackson.core + jackson-databind + 2.9.10.1 + + + + + io.github.dunwu + dunwu-tool-core + 0.5.1 + + + + + junit + junit + 4.12 + test + + + org.assertj + assertj-core + 3.14.0 + test + + + + diff --git a/codes/java-ecology/java-ecology-io/java-ecology-file/src/main/java/io/github/dunwu/javatech/serialize/FstDemo.java b/codes/java-ecology/java-ecology-io/java-ecology-file/src/main/java/io/github/dunwu/javatech/serialize/FstDemo.java new file mode 100644 index 00000000..c9365889 --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-file/src/main/java/io/github/dunwu/javatech/serialize/FstDemo.java @@ -0,0 +1,28 @@ +package io.github.dunwu.javatech.serialize; + +import org.nustaq.serialization.FSTConfiguration; + +import java.io.IOException; + +/** + * @author Zhang Peng + * @since 2019-11-22 + */ +public class FstDemo { + + private static FSTConfiguration DEFAULT_CONFIG = FSTConfiguration.createDefaultConfiguration(); + + public static byte[] serializ(T obj) { + return DEFAULT_CONFIG.asByteArray(obj); + } + + public static T derialize(byte[] bytes, Class clazz) throws IOException { + Object obj = DEFAULT_CONFIG.asObject(bytes); + if (clazz.isInstance(obj)) { + return (T) obj; + } else { + throw new IOException("derialize failed"); + } + } + +} diff --git a/codes/java-ecology/java-ecology-io/java-ecology-file/src/main/java/io/github/dunwu/javatech/serialize/JdkSerializeDemo.java b/codes/java-ecology/java-ecology-io/java-ecology-file/src/main/java/io/github/dunwu/javatech/serialize/JdkSerializeDemo.java new file mode 100644 index 00000000..61765ac5 --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-file/src/main/java/io/github/dunwu/javatech/serialize/JdkSerializeDemo.java @@ -0,0 +1,36 @@ +package io.github.dunwu.javatech.serialize; + +import java.io.*; + +/** + * JDK 原生序列化、反序列化示例 + * + * @author Zhang Peng + * @since 2019-11-22 + */ +public class JdkSerializeDemo { + + public static byte[] serializ(T obj) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(obj); + byte[] bytes = baos.toByteArray(); + baos.close(); + oos.close(); + return bytes; + } + + public static T derialize(byte[] bytes, Class clazz) throws IOException, ClassNotFoundException { + ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + ObjectInputStream ois = new ObjectInputStream(bais); + Object obj = ois.readObject(); + bais.close(); + ois.close(); + if (clazz.isInstance(obj)) { + return (T) obj; + } else { + throw new IOException("derialize failed"); + } + } + +} diff --git a/codes/java-ecology/java-ecology-io/java-ecology-file/src/test/java/io/github/dunwu/javatech/bean/Person.java b/codes/java-ecology/java-ecology-io/java-ecology-file/src/test/java/io/github/dunwu/javatech/bean/Person.java new file mode 100644 index 00000000..eb951279 --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-file/src/test/java/io/github/dunwu/javatech/bean/Person.java @@ -0,0 +1,42 @@ +package io.github.dunwu.javatech.bean; + +import java.io.Serializable; + +public class Person implements Serializable { + + private static final long serialVersionUID = -210388541252854256L; + + private String name; + + private int age; + + public Person() { + } + + public Person(String name, int age) { + this.name = name; + this.age = age; + } + + @Override + public String toString() { + return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + +} diff --git a/codes/java-ecology/java-ecology-io/java-ecology-file/src/test/java/io/github/dunwu/javatech/json/fastjson/FastjsonAnnotationBean.java b/codes/java-ecology/java-ecology-io/java-ecology-file/src/test/java/io/github/dunwu/javatech/json/fastjson/FastjsonAnnotationBean.java new file mode 100644 index 00000000..66d85040 --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-file/src/test/java/io/github/dunwu/javatech/json/fastjson/FastjsonAnnotationBean.java @@ -0,0 +1,95 @@ +package io.github.dunwu.javatech.json.fastjson; + +import com.alibaba.fastjson.annotation.JSONField; + +import java.util.Date; + +/** + * @author Zhang Peng + * @since 2019-03-18 + */ +public class FastjsonAnnotationBean { + + private int id; + + // 配置date序列化和反序列使用yyyyMMdd日期格式 + @JSONField(format = "yyyy-MM-dd") + private Date date1; + + // 不序列化 + @JSONField(serialize = false) + private Date date2; + + // 不反序列化 + @JSONField(deserialize = false) + private Date date3; + + // 按ordinal排序 + @JSONField(ordinal = 2) + private int f1; + + @JSONField(ordinal = 1) + private int f2; + + public FastjsonAnnotationBean() { + } + + public FastjsonAnnotationBean(int id, Date date1, Date date2, Date date3, int f1, int f2) { + this.id = id; + this.date1 = date1; + this.date2 = date2; + this.date3 = date3; + this.f1 = f1; + this.f2 = f2; + } + + @JSONField(name = "ID") + private int getId() { + return id; + } + + private void setId(int id) { + this.id = id; + } + + private Date getDate1() { + return date1; + } + + private void setDate1(Date date1) { + this.date1 = date1; + } + + private Date getDate2() { + return date2; + } + + private void setDate2(Date date2) { + this.date2 = date2; + } + + private Date getDate3() { + return date3; + } + + private void setDate3(Date date3) { + this.date3 = date3; + } + + private int getF1() { + return f1; + } + + private void setF1(int f1) { + this.f1 = f1; + } + + private int getF2() { + return f2; + } + + private void setF2(int f2) { + this.f2 = f2; + } + +} diff --git a/codes/java-ecology/java-ecology-io/java-ecology-file/src/test/java/io/github/dunwu/javatech/json/fastjson/FastjsonTests.java b/codes/java-ecology/java-ecology-io/java-ecology-file/src/test/java/io/github/dunwu/javatech/json/fastjson/FastjsonTests.java new file mode 100644 index 00000000..9c716f1f --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-file/src/test/java/io/github/dunwu/javatech/json/fastjson/FastjsonTests.java @@ -0,0 +1,51 @@ +package io.github.dunwu.javatech.json.fastjson; + +import com.alibaba.fastjson.JSON; +import io.github.dunwu.javatech.bean.Person; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Date; + +/** + * Fastjson 使用示例 + * + * @author Zhang Peng + * @since 2019-03-18 + */ +public class FastjsonTests { + + /** + * 序列化测试 + */ + @Test + public void serialize() { + Person p = new Person("Tom", 20); + String json = JSON.toJSONString(p); + Assert.assertNotNull(json); + System.out.println("json = [" + json + "]"); + } + + /** + * 反序列化测试 + */ + @Test + public void deserialize() { + final String json = "{\"age\":20,\"name\":\"Tom\"}"; + Person p = JSON.parseObject(json, Person.class); + Assert.assertNotNull(p); + System.out.println("p = [" + p + "]"); + } + + /** + * 序列化测试 + */ + @Test + public void serializeAnnotation() { + FastjsonAnnotationBean bean = new FastjsonAnnotationBean(1, new Date(), new Date(), new Date(), 10, 20); + String json = JSON.toJSONString(bean); + Assert.assertNotNull(json); + System.out.println("json = [" + json + "]"); + } + +} diff --git a/codes/java-ecology/java-ecology-io/java-ecology-file/src/test/java/io/github/dunwu/javatech/json/jackson/JacksonAnnotationBean.java b/codes/java-ecology/java-ecology-io/java-ecology-file/src/test/java/io/github/dunwu/javatech/json/jackson/JacksonAnnotationBean.java new file mode 100644 index 00000000..bf5f6f0f --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-file/src/test/java/io/github/dunwu/javatech/json/jackson/JacksonAnnotationBean.java @@ -0,0 +1,55 @@ +package io.github.dunwu.javatech.json.jackson; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +/** + * @author Zhang Peng + * @since 2019-03-18 + */ +@JsonPropertyOrder(alphabetic = true) +public class JacksonAnnotationBean { + + private String Name; + + private int Age; + + @JsonIgnore + private String Sex; + + public JacksonAnnotationBean() { + } + + public JacksonAnnotationBean(String name, int age, String sex) { + Name = name; + Age = age; + Sex = sex; + } + + @JsonProperty("username") + public String getName() { + return Name; + } + + public void setName(String name) { + Name = name; + } + + public int getAge() { + return Age; + } + + public void setAge(int age) { + Age = age; + } + + public String getSex() { + return Sex; + } + + public void setSex(String sex) { + Sex = sex; + } + +} diff --git a/codes/java-ecology/java-ecology-io/java-ecology-file/src/test/java/io/github/dunwu/javatech/json/jackson/JacksonTests.java b/codes/java-ecology/java-ecology-io/java-ecology-file/src/test/java/io/github/dunwu/javatech/json/jackson/JacksonTests.java new file mode 100644 index 00000000..3387e0b9 --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-file/src/test/java/io/github/dunwu/javatech/json/jackson/JacksonTests.java @@ -0,0 +1,101 @@ +package io.github.dunwu.javatech.json.jackson; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.github.dunwu.javatech.bean.Person; +import org.junit.Assert; +import org.junit.Test; + +import java.io.IOException; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * Jackson 使用示例 + * + * @author Zhang Peng + * @since 2019-03-18 + */ +public class JacksonTests { + + final ObjectMapper mapper = new ObjectMapper(); + + /** + * 序列化测试 + */ + @Test + public void serialize() { + Person p = new Person("Tom", 20); + String json = null; + try { + json = mapper.writeValueAsString(p); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + Assert.assertNotNull(json); + System.out.println("json = [" + json + "]"); + } + + /** + * 反序列化测试 + */ + @Test + public void deserialize() { + final String json = "{\"age\":20,\"name\":\"Tom\"}"; + Person p = null; + try { + p = mapper.readValue(json, Person.class); + } catch (IOException e) { + e.printStackTrace(); + } + Assert.assertNotNull(p); + System.out.println("p = [" + p + "]"); + } + + /** + * 序列化测试 + */ + @Test + public void serialize2() { + Person p = new Person("Tom", 20); + Person p2 = new Person("Jack", 22); + Person p3 = new Person("Mary", 18); + + List persons = new LinkedList<>(); + persons.add(p); + persons.add(p2); + persons.add(p3); + + Map map = new HashMap<>(); + map.put("persons", persons); + + String json = null; + try { + json = mapper.writeValueAsString(map); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + + Assert.assertNotNull(json); + System.out.println("json = [" + json + "]"); + } + + /** + * 序列化测试 + */ + @Test + public void serialize3() { + JacksonAnnotationBean jacksonAnnotationBean = new JacksonAnnotationBean("jack", 19, "男"); + String json = null; + try { + json = mapper.writeValueAsString(jacksonAnnotationBean); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + Assert.assertNotNull(json); + System.out.println("json = [" + json + "]"); + } + +} diff --git a/codes/java-ecology/java-ecology-io/java-ecology-file/src/test/java/io/github/dunwu/javatech/serialize/SerializeTest.java b/codes/java-ecology/java-ecology-io/java-ecology-file/src/test/java/io/github/dunwu/javatech/serialize/SerializeTest.java new file mode 100644 index 00000000..5b60f26e --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-file/src/test/java/io/github/dunwu/javatech/serialize/SerializeTest.java @@ -0,0 +1,47 @@ +package io.github.dunwu.javatech.serialize; + +import io.github.dunwu.javatech.bean.Person; +import io.github.dunwu.tool.util.RandomUtil; +import org.junit.Test; + +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Zhang Peng + * @since 2019-11-22 + */ +public class SerializeTest { + + public static final int BATCH_SIZE = 100000; + + @Test + public void testJdkSerialize() throws IOException, ClassNotFoundException { + long begin = System.currentTimeMillis(); + for (int i = 0; i < BATCH_SIZE; i++) { + Person oldPerson = new Person(RandomUtil.randomChineseName(), RandomUtil.randomInt(0, 100)); + byte[] bytes = JdkSerializeDemo.serializ(oldPerson); + assertThat(bytes).isNotEmpty(); + Person newPerson = JdkSerializeDemo.derialize(bytes, Person.class); + assertThat(newPerson).isNotNull(); + } + long end = System.currentTimeMillis(); + System.out.printf("耗时:%s", (end - begin)); + } + + @Test + public void testFst() throws IOException { + long begin = System.currentTimeMillis(); + for (int i = 0; i < BATCH_SIZE; i++) { + Person oldPerson = new Person(RandomUtil.randomChineseName(), RandomUtil.randomInt(0, 100)); + byte[] bytes = FstDemo.serializ(oldPerson); + assertThat(bytes).isNotEmpty(); + Person newPerson = FstDemo.derialize(bytes, Person.class); + assertThat(newPerson).isNotNull(); + } + long end = System.currentTimeMillis(); + System.out.printf("耗时:%s", (end - begin)); + } + +} diff --git a/codes/java-ecology/java-ecology-io/java-ecology-json/pom.xml b/codes/java-ecology/java-ecology-io/java-ecology-json/pom.xml new file mode 100644 index 00000000..4fe22ed6 --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-json/pom.xml @@ -0,0 +1,69 @@ + + + 4.0.0 + + + io.github.dunwu + dunwu-parent + 0.5.1 + + + io.github.dunwu.javatech + java-ecology-json + 1.0.0 + jar + java-ecology-json + + + UTF-8 + 1.8 + ${java.version} + ${java.version} + + + + + + de.ruedigermoeller + fst + + + + com.alibaba + fastjson + + + com.fasterxml.jackson.core + jackson-databind + + + com.google.code.gson + gson + + + + org.projectlombok + lombok + + + io.github.dunwu + dunwu-tool-core + 0.5.1 + + + + + junit + junit + test + + + org.assertj + assertj-core + test + + + + diff --git a/codes/java-ecology/java-ecology-io/java-ecology-json/src/main/java/io/github/dunwu/javatech/json/bean/Group.java b/codes/java-ecology/java-ecology-io/java-ecology-json/src/main/java/io/github/dunwu/javatech/json/bean/Group.java new file mode 100644 index 00000000..aeb26674 --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-json/src/main/java/io/github/dunwu/javatech/json/bean/Group.java @@ -0,0 +1,42 @@ +package io.github.dunwu.javatech.json.bean; + +import java.util.ArrayList; +import java.util.List; + +public class Group { + + private Long id; + + private String name; + + private List users = new ArrayList(); + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getUsers() { + return users; + } + + public void setUsers(List users) { + this.users = users; + } + + public void addUser(User user) { + users.add(user); + } + +} diff --git a/codes/java-ecology/java-ecology-io/java-ecology-json/src/main/java/io/github/dunwu/javatech/json/bean/User.java b/codes/java-ecology/java-ecology-io/java-ecology-json/src/main/java/io/github/dunwu/javatech/json/bean/User.java new file mode 100644 index 00000000..4e595afd --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-json/src/main/java/io/github/dunwu/javatech/json/bean/User.java @@ -0,0 +1,25 @@ +package io.github.dunwu.javatech.json.bean; + +public class User { + + private Long id; + + private String name; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + +} diff --git a/codes/java-ecology/java-ecology-io/java-ecology-json/src/main/java/io/github/dunwu/javatech/json/fastjson/FastjsonFeildBean.java b/codes/java-ecology/java-ecology-io/java-ecology-json/src/main/java/io/github/dunwu/javatech/json/fastjson/FastjsonFeildBean.java new file mode 100644 index 00000000..a5db98cf --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-json/src/main/java/io/github/dunwu/javatech/json/fastjson/FastjsonFeildBean.java @@ -0,0 +1,140 @@ +package io.github.dunwu.javatech.json.fastjson; + +import com.alibaba.fastjson.annotation.JSONField; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Date; +import java.util.Objects; + +/** + * @JSONField 的使用 + *

    + * @JSONField 可以配置在 getter/setter 方法上 + *

    + * @JSONField 可以配置在字段上,但是要求字段必须是 public + * + * @author Zhang Peng + * @see @JSONField + * @since 2019-03-18 + */ +public class FastjsonFeildBean { + + private int id; + + // 配置date序列化和反序列使用yyyyMMdd日期格式 + @JSONField(format = "yyyyMMdd") + private Date date1; + + // 不序列化 + @JSONField(serialize = false, format = "yyyy-MM-dd hh:mm:ss") + private LocalDate date2; + + // 不反序列化 + @JSONField(deserialize = false, format = "yyyy-MM-dd") + private LocalDateTime date3; + + @JSONField(ordinal = 1) + private Double d1; + + // 按ordinal排序 + @JSONField(ordinal = 2) + private float f1; + + public FastjsonFeildBean() { + } + + public FastjsonFeildBean(int id, Date date1, LocalDate date2, LocalDateTime date3, float f1, Double d1) { + this.id = id; + this.date1 = date1; + this.date2 = date2; + this.date3 = date3; + this.f1 = f1; + this.d1 = d1; + } + + @Override + public int hashCode() { + return Objects.hash(id, date1, date2, date3, f1, d1); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof FastjsonFeildBean)) { + return false; + } + FastjsonFeildBean that = (FastjsonFeildBean) o; + return id == that.id && + Float.compare(that.f1, f1) == 0 && + Objects.equals(date1, that.date1) && + Objects.equals(date2, that.date2) && + Objects.equals(date3, that.date3) && + Objects.equals(d1, that.d1); + } + + @Override + public String toString() { + return "FastjsonAnnotationBean{" + + "id=" + id + + ", date1=" + date1 + + ", date2=" + date2 + + ", date3=" + date3 + + ", f1=" + f1 + + ", f2=" + d1 + + '}'; + } + + @JSONField(name = "ID") + public int getId() { + return id; + } + + @JSONField(name = "ID") + public void setId(int id) { + this.id = id; + } + + public Date getDate1() { + return date1; + } + + public void setDate1(Date date1) { + this.date1 = date1; + } + + public LocalDate getDate2() { + return date2; + } + + public void setDate2(LocalDate date2) { + this.date2 = date2; + } + + public LocalDateTime getDate3() { + return date3; + } + + public void setDate3(LocalDateTime date3) { + this.date3 = date3; + } + + public float getF1() { + return f1; + } + + public void setF1(float f1) { + this.f1 = f1; + } + + public Double getD1() { + return d1; + } + + public void setD1(Double d1) { + this.d1 = d1; + } + +} diff --git a/codes/java-ecology/java-ecology-io/java-ecology-json/src/main/java/io/github/dunwu/javatech/json/fastjson/JacksonAnnotationBean.java b/codes/java-ecology/java-ecology-io/java-ecology-json/src/main/java/io/github/dunwu/javatech/json/fastjson/JacksonAnnotationBean.java new file mode 100644 index 00000000..1364b88f --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-json/src/main/java/io/github/dunwu/javatech/json/fastjson/JacksonAnnotationBean.java @@ -0,0 +1,55 @@ +package io.github.dunwu.javatech.json.fastjson; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +/** + * @author Zhang Peng + * @since 2019-03-18 + */ +@JsonPropertyOrder(alphabetic = true) +public class JacksonAnnotationBean { + + private String Name; + + private int Age; + + @JsonIgnore + private String Sex; + + public JacksonAnnotationBean() { + } + + public JacksonAnnotationBean(String name, int age, String sex) { + Name = name; + Age = age; + Sex = sex; + } + + @JsonProperty("username") + public String getName() { + return Name; + } + + public void setName(String name) { + Name = name; + } + + public int getAge() { + return Age; + } + + public void setAge(int age) { + Age = age; + } + + public String getSex() { + return Sex; + } + + public void setSex(String sex) { + Sex = sex; + } + +} diff --git a/codes/java-ecology/java-ecology-io/java-ecology-json/src/main/java/io/github/dunwu/javatech/json/gson/GsonAnnotationBean.java b/codes/java-ecology/java-ecology-io/java-ecology-json/src/main/java/io/github/dunwu/javatech/json/gson/GsonAnnotationBean.java new file mode 100644 index 00000000..f8b81504 --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-json/src/main/java/io/github/dunwu/javatech/json/gson/GsonAnnotationBean.java @@ -0,0 +1,52 @@ +package io.github.dunwu.javatech.json.gson; + +import com.google.gson.annotations.SerializedName; + +import java.util.Objects; + +/** + * @author Zhang Peng + * @since 2019-11-24 + */ +public class GsonAnnotationBean { + + @SerializedName("custom_naming") + private String someField; + + private String someOtherField; + + @Override + public int hashCode() { + return Objects.hash(someField, someOtherField); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof GsonAnnotationBean)) { + return false; + } + GsonAnnotationBean that = (GsonAnnotationBean) o; + return Objects.equals(someField, that.someField) && + Objects.equals(someOtherField, that.someOtherField); + } + + public String getSomeField() { + return someField; + } + + public void setSomeField(String someField) { + this.someField = someField; + } + + public String getSomeOtherField() { + return someOtherField; + } + + public void setSomeOtherField(String someOtherField) { + this.someOtherField = someOtherField; + } + +} diff --git a/codes/java-ecology/java-ecology-io/java-ecology-json/src/main/java/io/github/dunwu/javatech/json/gson/VersionedClass.java b/codes/java-ecology/java-ecology-io/java-ecology-json/src/main/java/io/github/dunwu/javatech/json/gson/VersionedClass.java new file mode 100644 index 00000000..14e79f1d --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-json/src/main/java/io/github/dunwu/javatech/json/gson/VersionedClass.java @@ -0,0 +1,21 @@ +package io.github.dunwu.javatech.json.gson; + +import com.google.gson.annotations.Since; + +public class VersionedClass { + + @Since(1.1) + private final String newerField; + + @Since(1.0) + private final String newField; + + private final String field; + + public VersionedClass() { + this.newerField = "newer"; + this.newField = "new"; + this.field = "old"; + } + +} diff --git a/codes/java-ecology/java-ecology-io/java-ecology-json/src/test/java/io/github/dunwu/javatech/json/TestBean.java b/codes/java-ecology/java-ecology-io/java-ecology-json/src/test/java/io/github/dunwu/javatech/json/TestBean.java new file mode 100644 index 00000000..d99d3c7e --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-json/src/test/java/io/github/dunwu/javatech/json/TestBean.java @@ -0,0 +1,56 @@ +package io.github.dunwu.javatech.json; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; +import lombok.experimental.Accessors; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * 定义一个满足大多数情况的 Bean 结构(含 JDK8 数据类型),使得各种 Json 库测试性能时能相对公平 + * + * @author Zhang Peng + * @since 2019-11-22 + */ +@Data +@ToString +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +public class TestBean { + + private int i1; + + private Integer i2; + + private float f1; + + private Double d1; + + private Date date1; + + private LocalDateTime date2; + + private LocalDate date3; + + private Color color; + + private String[] strArray; + + private List intList; + + private Map map; + + public static enum Color { + RED, + YELLOW, + BLUE + } + +} diff --git a/codes/java-ecology/java-ecology-io/java-ecology-json/src/test/java/io/github/dunwu/javatech/json/TestBean2.java b/codes/java-ecology/java-ecology-io/java-ecology-json/src/test/java/io/github/dunwu/javatech/json/TestBean2.java new file mode 100644 index 00000000..160b938f --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-json/src/test/java/io/github/dunwu/javatech/json/TestBean2.java @@ -0,0 +1,50 @@ +package io.github.dunwu.javatech.json; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; +import lombok.experimental.Accessors; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * 定义一个满足大多数情况的 Bean 结构(不含 JDK8 数据类型),使得各种 Json 库测试性能时能相对公平 + * + * @author Zhang Peng + * @since 2019-11-22 + */ +@Data +@ToString +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +public class TestBean2 { + + private int i1; + + private Integer i2; + + private float f1; + + private Double d1; + + private Date date1; + + private Color color; + + private String[] strArray; + + private List intList; + + private Map map; + + public static enum Color { + RED, + YELLOW, + BLUE + } + +} diff --git a/codes/java-ecology/java-ecology-io/java-ecology-json/src/test/java/io/github/dunwu/javatech/json/fastjson/FastjsonCaseTests.java b/codes/java-ecology/java-ecology-io/java-ecology-json/src/test/java/io/github/dunwu/javatech/json/fastjson/FastjsonCaseTests.java new file mode 100644 index 00000000..f9d3745a --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-json/src/test/java/io/github/dunwu/javatech/json/fastjson/FastjsonCaseTests.java @@ -0,0 +1,56 @@ +package io.github.dunwu.javatech.json.fastjson; + +import com.alibaba.fastjson.JSON; +import io.github.dunwu.javatech.json.bean.Group; +import io.github.dunwu.javatech.json.util.BeanUtils; +import io.github.dunwu.tool.date.DateUtil; +import org.junit.Test; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Date; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Zhang Peng + * @since 2019-11-22 + */ +public class FastjsonCaseTests { + + @Test + public void test() { + Group oldGroup = BeanUtils.initGroupBean(); + String jsonString = JSON.toJSONString(oldGroup); + System.out.println(jsonString); + Group newGroup = JSON.parseObject(jsonString, Group.class); + assertThat(newGroup).isNotNull(); + } + + /** + * 序列化测试 + */ + @Test + public void serialize() { + LocalDate localDate = LocalDate.of(1949, 10, 1); + LocalDateTime localDateTime = LocalDateTime.of(2000, 1, 1, 12, 0, 0); + Date date = DateUtil.toDate(localDateTime); + FastjsonFeildBean bean = new FastjsonFeildBean(1, date, localDate, localDateTime, 0.5f, 100.0); + String json = JSON.toJSONString(bean); + assertThat(json).isEqualTo( + "{\"ID\":1,\"date1\":\"20000101\",\"date3\":\"2000-01-01\",\"d1\":100.0,\"f1\":0.5}"); + System.out.println("json = [" + json + "]"); + } + + /** + * 反序列化测试 + */ + @Test + public void deserialize() { + final String json = "{\"ID\":1,\"date1\":\"20000101\",\"date3\":\"2000-01-01\",\"d1\":100.0,\"f1\":0.5}"; + FastjsonFeildBean actualBean = JSON.parseObject(json, FastjsonFeildBean.class); + System.out.printf("deserialize result: %s", actualBean.toString()); + assertThat(actualBean).isNotNull(); + } + +} diff --git a/codes/java-ecology/java-ecology-io/java-ecology-json/src/test/java/io/github/dunwu/javatech/json/fastjson/FastjsonPerformanceTests.java b/codes/java-ecology/java-ecology-io/java-ecology-json/src/test/java/io/github/dunwu/javatech/json/fastjson/FastjsonPerformanceTests.java new file mode 100644 index 00000000..5e6cd209 --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-json/src/test/java/io/github/dunwu/javatech/json/fastjson/FastjsonPerformanceTests.java @@ -0,0 +1,50 @@ +package io.github.dunwu.javatech.json.fastjson; + +import com.alibaba.fastjson.JSON; +import io.github.dunwu.javatech.json.TestBean2; +import io.github.dunwu.javatech.json.util.BeanUtils; +import org.junit.Test; + +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Fastjson 性能测试 + * + * @author Zhang Peng + * @since 2019-03-18 + */ +public class FastjsonPerformanceTests { + + private static final int BATCH_SIZE = 100000; + + /** + * 测试十次,每次序列化、反序列化 100000 条数据,平均耗时约 380 ms + */ + @Test + public void testPerformance() { + long time = 0L; + for (int i = 0; i < 10; i++) { + time += donSerializeAndDeserialize(); + } + System.out.println(String.format("time: %d ms", TimeUnit.NANOSECONDS.toMillis(time / 10))); + } + + /** + * 循环序列化、反序列 {@link #BATCH_SIZE} 条数据,测试性能 + */ + private long donSerializeAndDeserialize() { + TestBean2 bean = BeanUtils.initNotJdk8Bean(); + long begin = System.nanoTime(); + for (int i = 0; i < BATCH_SIZE; i++) { + String json = JSON.toJSONString(bean); + assertThat(json).isNotBlank(); + TestBean2 newBean = JSON.parseObject(json, TestBean2.class); + assertThat(newBean).isNotNull(); + } + long end = System.nanoTime(); + return (end - begin); + } + +} diff --git a/codes/java-ecology/java-ecology-io/java-ecology-json/src/test/java/io/github/dunwu/javatech/json/gson/GsonCaseTests.java b/codes/java-ecology/java-ecology-io/java-ecology-json/src/test/java/io/github/dunwu/javatech/json/gson/GsonCaseTests.java new file mode 100644 index 00000000..ab6c1494 --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-json/src/test/java/io/github/dunwu/javatech/json/gson/GsonCaseTests.java @@ -0,0 +1,80 @@ +package io.github.dunwu.javatech.json.gson; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import org.junit.Test; + +import java.lang.reflect.Modifier; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Zhang Peng + * @since 2019-11-24 + */ +public class GsonCaseTests { + + private Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create(); + + private Gson gson2 = new GsonBuilder() + .setVersion(1.0) + .setPrettyPrinting() + .setDateFormat("yyyy-MM-dd HH:mm:ss") + .excludeFieldsWithModifiers(Modifier.STATIC, Modifier.TRANSIENT, Modifier.VOLATILE) + .create(); + + @Test + public void test() { + // Serialization + Gson gson = new Gson(); + gson.toJson(1); // ==> 1 + gson.toJson("abcd"); // ==> "abcd" + gson.toJson(10L); // ==> 10 + int[] values = { 1 }; + gson.toJson(values); // ==> [1] + + // Deserialization + int i1 = gson.fromJson("1", int.class); + Integer i2 = gson.fromJson("1", Integer.class); + Long l1 = gson.fromJson("1", Long.class); + Boolean b1 = gson.fromJson("false", Boolean.class); + String str = gson.fromJson("\"abc\"", String.class); + String[] anotherStr = gson.fromJson("[\"abc\"]", String[].class); + + assertThat(i1).isEqualTo(1); + assertThat(i2).isEqualTo(1); + assertThat(l1).isEqualTo(1L); + assertThat(b1).isFalse(); + assertThat(str).isEqualTo("abc"); + } + + @Test + public void testAnnotation() { + GsonAnnotationBean oldBean = new GsonAnnotationBean(); + oldBean.setSomeField("hello"); + oldBean.setSomeOtherField("world"); + + String expectStr = "{\"custom_naming\":\"hello\",\"someOtherField\":\"world\"}"; + String json = gson.toJson(oldBean); + assertThat(json).isEqualTo(expectStr); + + GsonAnnotationBean newBean = gson.fromJson(expectStr, GsonAnnotationBean.class); + assertThat(newBean).isEqualTo(oldBean); + } + + @Test + public void testVersionedClass() { + VersionedClass versionedObject = new VersionedClass(); + String jsonOutput = gson2.toJson(versionedObject); + System.out.println(jsonOutput); + assertThat(jsonOutput).isEqualTo("{\n" + + " \"newField\": \"new\",\n" + + " \"field\": \"old\"\n" + + "}"); + + jsonOutput = gson.toJson(versionedObject); + System.out.println(jsonOutput); + assertThat(jsonOutput).isEqualTo("{\"newerField\":\"newer\",\"newField\":\"new\",\"field\":\"old\"}"); + } + +} diff --git a/codes/java-ecology/java-ecology-io/java-ecology-json/src/test/java/io/github/dunwu/javatech/json/gson/GsonPerformanceTests.java b/codes/java-ecology/java-ecology-io/java-ecology-json/src/test/java/io/github/dunwu/javatech/json/gson/GsonPerformanceTests.java new file mode 100644 index 00000000..f073cd1f --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-json/src/test/java/io/github/dunwu/javatech/json/gson/GsonPerformanceTests.java @@ -0,0 +1,53 @@ +package io.github.dunwu.javatech.json.gson; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import io.github.dunwu.javatech.json.TestBean2; +import io.github.dunwu.javatech.json.util.BeanUtils; +import org.junit.Test; + +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Gson 性能测试 + * + * @author Zhang Peng + * @since 2019-11-22 + */ +public class GsonPerformanceTests { + + private static final int BATCH_SIZE = 100000; + + private Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create(); + + /** + * 测试十次,每次序列化、反序列化 100000 条数据,平均耗时约 704 ms + */ + @Test + public void testPerformance() { + long time = 0L; + for (int i = 0; i < 10; i++) { + time += donSerializeAndDeserialize(); + } + System.out.println(String.format("time: %d ms", TimeUnit.NANOSECONDS.toMillis(time / 10))); + } + + /** + * 循环序列化、反序列 {@link #BATCH_SIZE} 条数据,测试性能 + */ + private long donSerializeAndDeserialize() { + TestBean2 bean = BeanUtils.initNotJdk8Bean(); + long begin = System.nanoTime(); + for (int i = 0; i < BATCH_SIZE; i++) { + String json = gson.toJson(bean); + assertThat(json).isNotBlank(); + TestBean2 newBean = gson.fromJson(json, TestBean2.class); + assertThat(newBean).isNotNull(); + } + long end = System.nanoTime(); + return (end - begin); + } + +} diff --git a/codes/java-ecology/java-ecology-io/java-ecology-json/src/test/java/io/github/dunwu/javatech/json/jackson/JacksonPerformanceTests.java b/codes/java-ecology/java-ecology-io/java-ecology-json/src/test/java/io/github/dunwu/javatech/json/jackson/JacksonPerformanceTests.java new file mode 100644 index 00000000..80392b5e --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-json/src/test/java/io/github/dunwu/javatech/json/jackson/JacksonPerformanceTests.java @@ -0,0 +1,53 @@ +package io.github.dunwu.javatech.json.jackson; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.github.dunwu.javatech.json.TestBean2; +import io.github.dunwu.javatech.json.util.BeanUtils; +import org.junit.Test; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Jackson 性能测试 + * + * @author Zhang Peng + * @since 2019-03-18 + */ +public class JacksonPerformanceTests { + + private static final int BATCH_SIZE = 100000; + + private final ObjectMapper mapper = new ObjectMapper(); + + /** + * 测试十次,每次序列化、反序列化 100000 条数据,平均耗时约 334 ms + */ + @Test + public void testPerformance() throws IOException { + long time = 0L; + for (int i = 0; i < 10; i++) { + time += donSerializeAndDeserialize(); + } + System.out.println(String.format("time: %d ms", TimeUnit.NANOSECONDS.toMillis(time / 10))); + } + + /** + * 循环序列化、反序列 {@link #BATCH_SIZE} 条数据,测试性能 + */ + private long donSerializeAndDeserialize() throws IOException { + TestBean2 bean = BeanUtils.initNotJdk8Bean(); + long begin = System.nanoTime(); + for (int i = 0; i < BATCH_SIZE; i++) { + String json = mapper.writeValueAsString(bean); + assertThat(json).isNotBlank(); + TestBean2 newBean = mapper.readValue(json, TestBean2.class); + assertThat(newBean).isNotNull(); + } + long end = System.nanoTime(); + return (end - begin); + } + +} diff --git a/codes/java-ecology/java-ecology-io/java-ecology-json/src/test/java/io/github/dunwu/javatech/json/util/BeanUtils.java b/codes/java-ecology/java-ecology-io/java-ecology-json/src/test/java/io/github/dunwu/javatech/json/util/BeanUtils.java new file mode 100644 index 00000000..40c150dc --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-json/src/test/java/io/github/dunwu/javatech/json/util/BeanUtils.java @@ -0,0 +1,74 @@ +package io.github.dunwu.javatech.json.util; + +import io.github.dunwu.javatech.json.TestBean; +import io.github.dunwu.javatech.json.TestBean2; +import io.github.dunwu.javatech.json.bean.Group; +import io.github.dunwu.javatech.json.bean.User; + +import java.sql.Date; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.*; + +/** + * @author Zhang Peng + * @since 2019-11-22 + */ +public class BeanUtils { + + public static Group initGroupBean() { + Group group = new Group(); + group.setId(0L); + group.setName("admin"); + + User guestUser = new User(); + guestUser.setId(2L); + guestUser.setName("guest"); + + User rootUser = new User(); + rootUser.setId(3L); + rootUser.setName("root"); + + group.addUser(guestUser); + group.addUser(rootUser); + + return group; + } + + public static TestBean initJdk8Bean() { + String[] strArray = { "a", "b", "c" }; + Integer[] intArray = { 1, 2, 3, 4, 5 }; + List intList = new ArrayList<>(); + intList.addAll(Arrays.asList(intArray)); + Map map = new HashMap<>(); + map.put("name", "jack"); + map.put("age", 18); + map.put("length", 175.3f); + TestBean bean = new TestBean(); + Date date = Date.valueOf("2019-11-22"); + LocalDateTime localDateTime = LocalDateTime.of(2000, 1, 1, 12, 0, 0); + LocalDate localDate = LocalDate.of(1949, 10, 1); + bean.setI1(10).setI2(1024).setF1(0.5f).setD1(100.0) + .setDate1(date).setDate2(localDateTime).setDate3(localDate) + .setColor(TestBean.Color.BLUE).setStrArray(strArray).setIntList(intList).setMap(map); + return bean; + } + + public static TestBean2 initNotJdk8Bean() { + String[] strArray = { "a", "b", "c" }; + Integer[] intArray = { 1, 2, 3, 4, 5 }; + List intList = new ArrayList<>(); + intList.addAll(Arrays.asList(intArray)); + Map map = new HashMap<>(); + map.put("name", "jack"); + map.put("age", 18); + map.put("length", 175.3f); + TestBean2 bean = new TestBean2(); + Date date = Date.valueOf("2019-11-22"); + bean.setI1(10).setI2(1024).setF1(0.5f).setD1(100.0) + .setDate1(date) + .setColor(TestBean2.Color.BLUE).setStrArray(strArray).setIntList(intList).setMap(map); + return bean; + } + +} diff --git a/codes/java-ecology/java-ecology-io/java-ecology-serialize/pom.xml b/codes/java-ecology/java-ecology-io/java-ecology-serialize/pom.xml new file mode 100644 index 00000000..fdc44ddd --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-serialize/pom.xml @@ -0,0 +1,62 @@ + + + 4.0.0 + + + io.github.dunwu + dunwu-parent + 0.5.1 + + + io.github.dunwu.javatech + java-ecology-serialize + 1.0.0 + jar + java-ecology-serialize + + + UTF-8 + 1.8 + ${java.version} + ${java.version} + + + + + + de.ruedigermoeller + fst + 2.56 + + + com.esotericsoftware + kryo + 5.0.0-RC4 + + + + io.github.dunwu + dunwu-tool-core + 0.5.1 + + + org.projectlombok + lombok + + + + + junit + junit + test + + + org.assertj + assertj-core + test + + + + diff --git a/codes/java-ecology/java-ecology-io/java-ecology-serialize/src/main/java/io/github/dunwu/javatech/io/FstDemo.java b/codes/java-ecology/java-ecology-io/java-ecology-serialize/src/main/java/io/github/dunwu/javatech/io/FstDemo.java new file mode 100644 index 00000000..881e0f59 --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-serialize/src/main/java/io/github/dunwu/javatech/io/FstDemo.java @@ -0,0 +1,73 @@ +package io.github.dunwu.javatech.io; + +import org.nustaq.serialization.FSTConfiguration; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +/** + * FST 序列化/反序列化示例 + * + * @author Zhang Peng + * @see FST + * @since 2019-11-22 + */ +public class FstDemo { + + private static FSTConfiguration DEFAULT_CONFIG = FSTConfiguration.createDefaultConfiguration(); + + /** + * 将对象序列化为 byte 数组后,再使用 Base64 编码 + * + * @param obj 任意对象 + * @param 对象的类型 + * @return 序列化后的字符串 + */ + public static String writeToString(T obj) { + byte[] bytes = writeToBytes(obj); + return new String(Base64.getEncoder().encode(bytes), StandardCharsets.UTF_8); + } + + /** + * 将对象序列化为 byte 数组 + * + * @param obj 任意对象 + * @param 对象的类型 + * @return 序列化后的 byte 数组 + */ + public static byte[] writeToBytes(T obj) { + return DEFAULT_CONFIG.asByteArray(obj); + } + + /** + * 将字符串反序列化为原对象,先使用 Base64 解码 + * + * @param str {@link #writeToString} 方法序列化后的字符串 + * @param clazz 原对象的类型 + * @param 原对象的类型 + * @return 原对象 + */ + public static T readFromString(String str, Class clazz) throws IOException { + byte[] bytes = str.getBytes(StandardCharsets.UTF_8); + return readFromBytes(Base64.getDecoder().decode(bytes), clazz); + } + + /** + * 将 byte 数组反序列化为原对象 + * + * @param bytes {@link #writeToBytes} 方法序列化后的 byte 数组 + * @param clazz 原对象的类型 + * @param 原对象的类型 + * @return 原对象 + */ + public static T readFromBytes(byte[] bytes, Class clazz) throws IOException { + Object obj = DEFAULT_CONFIG.asObject(bytes); + if (clazz.isInstance(obj)) { + return (T) obj; + } else { + throw new IOException("derialize failed"); + } + } + +} diff --git a/codes/java-ecology/java-ecology-io/java-ecology-serialize/src/main/java/io/github/dunwu/javatech/io/JdkSerializeDemo.java b/codes/java-ecology/java-ecology-io/java-ecology-serialize/src/main/java/io/github/dunwu/javatech/io/JdkSerializeDemo.java new file mode 100644 index 00000000..fad6a17c --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-serialize/src/main/java/io/github/dunwu/javatech/io/JdkSerializeDemo.java @@ -0,0 +1,78 @@ +package io.github.dunwu.javatech.io; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +/** + * JDK 默认序列化、反序列化机制示例 + * + * @author Zhang Peng + * @since 2019-11-22 + */ +public class JdkSerializeDemo { + + /** + * 将对象序列化为 byte 数组后,再使用 Base64 编码 + * + * @param obj 任意对象 + * @param 对象的类型 + * @return 序列化后的字符串 + */ + public static String writeToString(T obj) throws IOException { + byte[] bytes = writeToBytes(obj); + return new String(Base64.getEncoder().encode(bytes), StandardCharsets.UTF_8); + } + + /** + * 将对象序列化为 byte 数组 + * + * @param obj 任意对象 + * @param 对象的类型 + * @return 序列化后的 byte 数组 + */ + public static byte[] writeToBytes(T obj) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(obj); + byte[] bytes = baos.toByteArray(); + baos.close(); + oos.close(); + return bytes; + } + + /** + * 将字符串反序列化为原对象,先使用 Base64 解码 + * + * @param str {@link #writeToString} 方法序列化后的字符串 + * @param clazz 原对象的类型 + * @param 原对象的类型 + * @return 原对象 + */ + public static T readFromString(String str, Class clazz) throws IOException, ClassNotFoundException { + byte[] bytes = str.getBytes(StandardCharsets.UTF_8); + return readFromBytes(Base64.getDecoder().decode(bytes), clazz); + } + + /** + * 将 byte 数组反序列化为原对象 + * + * @param bytes {@link #writeToBytes} 方法序列化后的 byte 数组 + * @param clazz 原对象的类型 + * @param 原对象的类型 + * @return 原对象 + */ + public static T readFromBytes(byte[] bytes, Class clazz) throws IOException, ClassNotFoundException { + ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + ObjectInputStream ois = new ObjectInputStream(bais); + Object obj = ois.readObject(); + bais.close(); + ois.close(); + if (clazz.isInstance(obj)) { + return (T) obj; + } else { + throw new IOException("derialize failed"); + } + } + +} diff --git a/codes/java-ecology/java-ecology-io/java-ecology-serialize/src/main/java/io/github/dunwu/javatech/io/KryoDemo.java b/codes/java-ecology/java-ecology-io/java-ecology-serialize/src/main/java/io/github/dunwu/javatech/io/KryoDemo.java new file mode 100644 index 00000000..78512548 --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-serialize/src/main/java/io/github/dunwu/javatech/io/KryoDemo.java @@ -0,0 +1,114 @@ +package io.github.dunwu.javatech.io; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; +import com.esotericsoftware.kryo.util.DefaultInstantiatorStrategy; +import org.objenesis.strategy.StdInstantiatorStrategy; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +/** + * Kyro 序列化/反序列化示例 + * + * @author Zhang Peng + * @author Kryo 应用指南 + * @since 2019-11-26 + */ +public class KryoDemo { + + // 每个线程的 Kryo 实例 + private static final ThreadLocal kryoLocal = ThreadLocal.withInitial(() -> { + Kryo kryo = new Kryo(); + + /** + * 不要轻易改变这里的配置!更改之后,序列化的格式就会发生变化, + * 上线的同时就必须清除 Redis 里的所有缓存, + * 否则那些缓存再回来反序列化的时候,就会报错 + */ + //支持对象循环引用(否则会栈溢出) + kryo.setReferences(true); //默认值就是 true,添加此行的目的是为了提醒维护者,不要改变这个配置 + + //不强制要求注册类(注册行为无法保证多个 JVM 内同一个类的注册编号相同;而且业务系统中大量的 Class 也难以一一注册) + kryo.setRegistrationRequired(false); //默认值就是 false,添加此行的目的是为了提醒维护者,不要改变这个配置 + + //Fix the NPE bug when deserializing Collections. + ((DefaultInstantiatorStrategy) kryo.getInstantiatorStrategy()) + .setFallbackInstantiatorStrategy(new StdInstantiatorStrategy()); + + return kryo; + }); + + /** + * 将对象序列化为 byte 数组后,再使用 Base64 编码 + * + * @param obj 任意对象 + * @param 对象的类型 + * @return 序列化后的字符串 + */ + public static String writeToString(T obj) { + byte[] bytes = writeToBytes(obj); + return new String(Base64.getEncoder().encode(bytes), StandardCharsets.UTF_8); + } + + /** + * 将对象序列化为 byte 数组 + * + * @param obj 任意对象 + * @param 对象的类型 + * @return 序列化后的 byte 数组 + */ + public static byte[] writeToBytes(T obj) { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + Output output = new Output(byteArrayOutputStream); + + Kryo kryo = getInstance(); + kryo.writeObject(output, obj); + output.flush(); + + return byteArrayOutputStream.toByteArray(); + } + + /** + * 获得当前线程的 Kryo 实例 + * + * @return 当前线程的 Kryo 实例 + */ + public static Kryo getInstance() { + return kryoLocal.get(); + } + + /** + * 将字符串反序列化为原对象,先使用 Base64 解码 + * + * @param str {@link #writeToString} 方法序列化后的字符串 + * @param clazz 原对象的类型 + * @param 原对象的类型 + * @return 原对象 + */ + public static T readFromString(String str, Class clazz) { + byte[] bytes = str.getBytes(StandardCharsets.UTF_8); + return readFromBytes(Base64.getDecoder().decode(bytes), clazz); + } + + /** + * 将 byte 数组反序列化为原对象 + * + * @param bytes {@link #writeToBytes} 方法序列化后的 byte 数组 + * @param clazz 原对象的类型 + * @param 原对象的类型 + * @return 原对象 + */ + @SuppressWarnings("unchecked") + public static T readFromBytes(byte[] bytes, Class clazz) { + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); + Input input = new Input(byteArrayInputStream); + + Kryo kryo = getInstance(); + return (T) kryo.readObject(input, clazz); + } + +} diff --git a/codes/java-ecology/java-ecology-io/java-ecology-serialize/src/main/java/io/github/dunwu/javatech/io/bean/BeanUtils.java b/codes/java-ecology/java-ecology-io/java-ecology-serialize/src/main/java/io/github/dunwu/javatech/io/bean/BeanUtils.java new file mode 100644 index 00000000..b58a6e7b --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-serialize/src/main/java/io/github/dunwu/javatech/io/bean/BeanUtils.java @@ -0,0 +1,33 @@ +package io.github.dunwu.javatech.io.bean; + +import io.github.dunwu.tool.date.DateUtil; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.*; + +/** + * @author Zhang Peng + * @since 2019-11-22 + */ +public class BeanUtils { + + public static TestBean initJdk8Bean() { + String[] strArray = { "a", "b", "c" }; + Integer[] intArray = { 1, 2, 3, 4, 5 }; + List intList = new ArrayList<>(Arrays.asList(intArray)); + Map map = new HashMap<>(); + map.put("name", "jack"); + map.put("age", 18); + map.put("length", 175.3f); + TestBean bean = new TestBean(); + LocalDateTime localDateTime = LocalDateTime.of(2000, 1, 1, 12, 0, 0); + Date date = DateUtil.toDate(localDateTime); + LocalDate localDate = LocalDate.of(1949, 10, 1); + bean.setI1(10).setI2(1024).setF1(0.5f).setD1(100.0) + .setDate1(date).setDate2(localDateTime).setDate3(localDate) + .setColor(TestBean.Color.BLUE).setStrArray(strArray).setIntList(intList).setMap(map); + return bean; + } + +} diff --git a/codes/java-ecology/java-ecology-io/java-ecology-serialize/src/main/java/io/github/dunwu/javatech/io/bean/TestBean.java b/codes/java-ecology/java-ecology-io/java-ecology-serialize/src/main/java/io/github/dunwu/javatech/io/bean/TestBean.java new file mode 100644 index 00000000..805becbb --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-serialize/src/main/java/io/github/dunwu/javatech/io/bean/TestBean.java @@ -0,0 +1,59 @@ +package io.github.dunwu.javatech.io.bean; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * 定义一个满足大多数情况的 Bean 结构(含 JDK8 数据类型),使得各种 Json 库测试性能时能相对公平 + * + * @author Zhang Peng + * @since 2019-11-22 + */ +@Data +@ToString +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +public class TestBean implements Serializable { + + private static final long serialVersionUID = -6473181683996762084L; + + private int i1; + + private Integer i2; + + private float f1; + + private Double d1; + + private Date date1; + + private LocalDateTime date2; + + private LocalDate date3; + + private Color color; + + private String[] strArray; + + private List intList; + + private Map map; + + public enum Color { + RED, + YELLOW, + BLUE + } + +} diff --git a/codes/java-ecology/java-ecology-io/java-ecology-serialize/src/test/java/io/github/dunwu/javatech/io/SerializePerformanceTest.java b/codes/java-ecology/java-ecology-io/java-ecology-serialize/src/test/java/io/github/dunwu/javatech/io/SerializePerformanceTest.java new file mode 100644 index 00000000..a37257c7 --- /dev/null +++ b/codes/java-ecology/java-ecology-io/java-ecology-serialize/src/test/java/io/github/dunwu/javatech/io/SerializePerformanceTest.java @@ -0,0 +1,63 @@ +package io.github.dunwu.javatech.io; + +import io.github.dunwu.javatech.io.bean.BeanUtils; +import io.github.dunwu.javatech.io.bean.TestBean; +import org.junit.Test; + +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * 序列化、反序列化性能测试 + * + * @author Zhang Peng + * @since 2019-11-22 + */ +public class SerializePerformanceTest { + + private static final int BATCH_SIZE = 100000; + + @Test + public void testJdkSerialize() throws IOException, ClassNotFoundException { + long begin = System.currentTimeMillis(); + for (int i = 0; i < BATCH_SIZE; i++) { + TestBean oldBean = BeanUtils.initJdk8Bean(); + byte[] bytes = JdkSerializeDemo.writeToBytes(oldBean); + assertThat(bytes).isNotEmpty(); + TestBean newBean = JdkSerializeDemo.readFromBytes(bytes, TestBean.class); + assertThat(newBean).isNotNull(); + } + long end = System.currentTimeMillis(); + System.out.printf("JDK 默认序列化/反序列化耗时:%s", (end - begin)); + } + + @Test + public void testFst() throws IOException { + long begin = System.currentTimeMillis(); + for (int i = 0; i < BATCH_SIZE; i++) { + TestBean oldBean = BeanUtils.initJdk8Bean(); + byte[] bytes = FstDemo.writeToBytes(oldBean); + assertThat(bytes).isNotEmpty(); + TestBean newBean = FstDemo.readFromBytes(bytes, TestBean.class); + assertThat(newBean).isNotNull(); + } + long end = System.currentTimeMillis(); + System.out.printf("FST 序列化/反序列化耗时:%s", (end - begin)); + } + + @Test + public void testKryo() throws IOException { + long begin = System.currentTimeMillis(); + for (int i = 0; i < BATCH_SIZE; i++) { + TestBean oldBean = BeanUtils.initJdk8Bean(); + byte[] bytes = KryoDemo.writeToBytes(oldBean); + assertThat(bytes).isNotEmpty(); + TestBean newBean = KryoDemo.readFromBytes(bytes, TestBean.class); + assertThat(newBean).isNotNull(); + } + long end = System.currentTimeMillis(); + System.out.printf("Kryo 序列化/反序列化耗时:%s", (end - begin)); + } + +} diff --git a/codes/java-ecology/java-ecology-io/pom.xml b/codes/java-ecology/java-ecology-io/pom.xml new file mode 100644 index 00000000..5487ec11 --- /dev/null +++ b/codes/java-ecology/java-ecology-io/pom.xml @@ -0,0 +1,18 @@ + + + 4.0.0 + + io.github.dunwu.javatech + java-ecology-io + 1.0.0 + pom + + + java-ecology-bean + java-ecology-file + java-ecology-json + java-ecology-serialize + + diff --git a/codes/java-ecology/java-ecology-log/java-ecology-log4j/pom.xml b/codes/java-ecology/java-ecology-log/java-ecology-log4j/pom.xml new file mode 100644 index 00000000..bab0c313 --- /dev/null +++ b/codes/java-ecology/java-ecology-log/java-ecology-log4j/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + + io.github.dunwu + dunwu-parent + 0.5.1 + + + io.github.dunwu.javatech + java-ecology-log4j + 1.0.0 + jar + java-ecology-log4j + + + UTF-8 + 1.8 + ${java.version} + ${java.version} + + + + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-log4j12 + + + log4j + log4j + 1.2.17 + + + + + junit + junit + test + + + + diff --git a/codes/java-ecology/java-ecology-log/java-ecology-log4j/src/main/java/io/github/dunwu/javatech/log/Log4jDemo.java b/codes/java-ecology/java-ecology-log/java-ecology-log4j/src/main/java/io/github/dunwu/javatech/log/Log4jDemo.java new file mode 100644 index 00000000..575a9d0e --- /dev/null +++ b/codes/java-ecology/java-ecology-log/java-ecology-log4j/src/main/java/io/github/dunwu/javatech/log/Log4jDemo.java @@ -0,0 +1,27 @@ +package io.github.dunwu.javatech.log; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * log4j 示例 + * + * @author Zhang Peng + * @see log4j 官网 + * @since 2018/3/29 + */ +public class Log4jDemo { + + private static final Logger logger = LoggerFactory.getLogger(Log4jDemo.class); + + public static void main(String[] args) { + for (int i = 0; i < 10; i++) { + logger.trace("NO.{} 这是一条 {} 日志记录", i, "trace"); + logger.debug("NO.{} 这是一条 {} 日志记录", i, "debug"); + logger.info("NO.{} 这是一条 {} 日志记录", i, "info"); + logger.warn("NO.{} 这是一条 {} 日志记录", i, "warn"); + logger.error("NO.{} 这是一条 {} 日志记录", i, "error"); + } + } + +} diff --git a/codes/java-ecology/java-ecology-log/java-ecology-log4j/src/main/resources/log4j.xml b/codes/java-ecology/java-ecology-log/java-ecology-log4j/src/main/resources/log4j.xml new file mode 100644 index 00000000..e86941cd --- /dev/null +++ b/codes/java-ecology/java-ecology-log/java-ecology-log4j/src/main/resources/log4j.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/codes/java-ecology/java-ecology-log/java-ecology-log4j2/pom.xml b/codes/java-ecology/java-ecology-log/java-ecology-log4j2/pom.xml new file mode 100644 index 00000000..385eabab --- /dev/null +++ b/codes/java-ecology/java-ecology-log/java-ecology-log4j2/pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + + + io.github.dunwu + dunwu-parent + 0.5.1 + + + io.github.dunwu.javatech + java-ecology-log4j2 + 1.0.0 + jar + java-ecology-log4j2 + + + UTF-8 + 1.8 + ${java.version} + ${java.version} + + + + + + org.slf4j + slf4j-api + + + org.apache.logging.log4j + log4j-slf4j-impl + + + org.apache.logging.log4j + log4j-core + + + + + junit + junit + test + + + + diff --git a/codes/java-ecology/java-ecology-log/java-ecology-log4j2/src/main/java/io/github/dunwu/javatech/log/Log4j2Demo.java b/codes/java-ecology/java-ecology-log/java-ecology-log4j2/src/main/java/io/github/dunwu/javatech/log/Log4j2Demo.java new file mode 100644 index 00000000..71e47b86 --- /dev/null +++ b/codes/java-ecology/java-ecology-log/java-ecology-log4j2/src/main/java/io/github/dunwu/javatech/log/Log4j2Demo.java @@ -0,0 +1,27 @@ +package io.github.dunwu.javatech.log; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Log4j2 示例 + * + * @author Zhang Peng + * @see Log4j2 官网 + * @since 2018/3/29 + */ +public class Log4j2Demo { + + private static final Logger logger = LoggerFactory.getLogger(Log4j2Demo.class); + + public static void main(String[] args) { + for (int i = 0; i < 10; i++) { + logger.trace("NO.{} 这是一条 {} 日志记录", i, "trace"); + logger.debug("NO.{} 这是一条 {} 日志记录", i, "debug"); + logger.info("NO.{} 这是一条 {} 日志记录", i, "info"); + logger.warn("NO.{} 这是一条 {} 日志记录", i, "warn"); + logger.error("NO.{} 这是一条 {} 日志记录", i, "error"); + } + } + +} diff --git a/codes/java-ecology/java-ecology-log/java-ecology-log4j2/src/main/resources/log4j2.xml b/codes/java-ecology/java-ecology-log/java-ecology-log4j2/src/main/resources/log4j2.xml new file mode 100644 index 00000000..12650afa --- /dev/null +++ b/codes/java-ecology/java-ecology-log/java-ecology-log4j2/src/main/resources/log4j2.xml @@ -0,0 +1,45 @@ + + + + ???? + + + + + + + + + + + + + + + ${PATTERN} + + + + + + + + + + + + + + + + + + + + + + diff --git a/codes/java-ecology/java-ecology-log/java-ecology-logback/pom.xml b/codes/java-ecology/java-ecology-log/java-ecology-logback/pom.xml new file mode 100644 index 00000000..8b2d4559 --- /dev/null +++ b/codes/java-ecology/java-ecology-log/java-ecology-logback/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + + + io.github.dunwu + dunwu-parent + 0.5.1 + + + io.github.dunwu.javatech + java-ecology-logback + 1.0.0 + jar + java-ecology-logback + + + UTF-8 + 1.8 + ${java.version} + ${java.version} + + + + + + ch.qos.logback + logback-classic + + + + + + + + + junit + junit + test + + + + diff --git a/codes/java-ecology/java-ecology-log/java-ecology-logback/src/main/java/io/github/dunwu/javatech/log/LogbackDemo.java b/codes/java-ecology/java-ecology-log/java-ecology-logback/src/main/java/io/github/dunwu/javatech/log/LogbackDemo.java new file mode 100644 index 00000000..5db50a71 --- /dev/null +++ b/codes/java-ecology/java-ecology-log/java-ecology-logback/src/main/java/io/github/dunwu/javatech/log/LogbackDemo.java @@ -0,0 +1,27 @@ +package io.github.dunwu.javatech.log; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Logback 示例 + * + * @author Zhang Peng + * @see logback 官网 + * @since 2018/3/29 + */ +public class LogbackDemo { + + private static final Logger logger = LoggerFactory.getLogger(LogbackDemo.class); + + public static void main(String[] args) { + for (int i = 0; i < 10; i++) { + logger.trace("NO.{} 这是一条 {} 日志记录", i, "trace"); + logger.debug("NO.{} 这是一条 {} 日志记录", i, "debug"); + logger.info("NO.{} 这是一条 {} 日志记录", i, "info"); + logger.warn("NO.{} 这是一条 {} 日志记录", i, "warn"); + logger.error("NO.{} 这是一条 {} 日志记录", i, "error"); + } + } + +} diff --git a/codes/java-ecology/java-ecology-log/java-ecology-logback/src/main/resources/logback.xml b/codes/java-ecology/java-ecology-log/java-ecology-logback/src/main/resources/logback.xml new file mode 100644 index 00000000..28cb1f7e --- /dev/null +++ b/codes/java-ecology/java-ecology-log/java-ecology-logback/src/main/resources/logback.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + ${PATTERN} + + + + + + + + logs/${DIR_NAME}/%d{yyyy-MM,aux}/${FILE_NAME}.%d{yyyy-MM-dd}.%i.log + + 30 + + 100MB + + + + ${PATTERN} + + + + + + + + + + + + diff --git a/codes/java-ecology/java-ecology-log/java-ecology-logstash/pom.xml b/codes/java-ecology/java-ecology-log/java-ecology-logstash/pom.xml new file mode 100644 index 00000000..91ff5ca1 --- /dev/null +++ b/codes/java-ecology/java-ecology-log/java-ecology-logstash/pom.xml @@ -0,0 +1,38 @@ + + + 4.0.0 + + + io.github.dunwu + dunwu-parent + 0.5.1 + + + io.github.dunwu.javatech + java-ecology-logstash + 1.0.0 + jar + java-ecology-logstash + + + UTF-8 + 1.8 + ${java.version} + ${java.version} + + + + + log4j + log4j + 1.2.17 + + + ch.qos.logback + logback-classic + + + + diff --git a/codes/java-ecology/java-ecology-log/java-ecology-logstash/src/main/java/io/github/dunwu/javatech/ElasticDemo.java b/codes/java-ecology/java-ecology-log/java-ecology-logstash/src/main/java/io/github/dunwu/javatech/ElasticDemo.java new file mode 100644 index 00000000..256e3146 --- /dev/null +++ b/codes/java-ecology/java-ecology-log/java-ecology-logstash/src/main/java/io/github/dunwu/javatech/ElasticDemo.java @@ -0,0 +1,47 @@ +package io.github.dunwu.javatech; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * 向 Elastic 日志中心传输日志 Logback logstash-logback-encoder jar 包会根据 logback 中的配置,将日志数据定向传输到 logstash 详见 + * src/main/resources/logback.xml appender 配置 使用 udp 方式传输时,有丢失日志的情况(ELK-UDP) 使用 tcp 方式传输时,不会丢失日志(ELK-TCP) Log4j 通过 + * org.apache.log4j.net.SocketAppender 发送 TCP 数据,Logstash 服务器使用 log4j input 插件 接收 + * + * @author Zhang Peng + */ +public class ElasticDemo { + + private static final Logger logger = LoggerFactory.getLogger(ElasticDemo.class); + + private static final org.apache.log4j.Logger log4jLog = org.apache.log4j.Logger.getLogger(ElasticDemo.class); + + private static volatile int index = 0; + + public static void main(String[] args) { + // sendLog4jLog(); + sendLogbackLog(); + } + + private static void sendLogbackLog() { + ExecutorService executorService = Executors.newFixedThreadPool(100); + for (int i = 0; i < 10000; i++) { + executorService.submit(new Runnable() { + @Override + public void run() { + logger.info("这是第 {} 条日志", ++index); + } + }); + } + } + + private static void sendLog4jLog() { + for (int i = 0; i < 100; i++) { + log4jLog.info(String.format("这是第 %d 条日志", ++index)); + } + } + +} diff --git a/codes/java-ecology/java-ecology-log/java-ecology-logstash/src/main/resources/log4j.xml b/codes/java-ecology/java-ecology-log/java-ecology-logstash/src/main/resources/log4j.xml new file mode 100644 index 00000000..68949a9b --- /dev/null +++ b/codes/java-ecology/java-ecology-log/java-ecology-logstash/src/main/resources/log4j.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/codes/java-ecology/java-ecology-log/java-ecology-logstash/src/main/resources/logback.xml b/codes/java-ecology/java-ecology-log/java-ecology-logstash/src/main/resources/logback.xml new file mode 100644 index 00000000..c917bd86 --- /dev/null +++ b/codes/java-ecology/java-ecology-log/java-ecology-logstash/src/main/resources/logback.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n + + + + + + + + ${user.dir}/logs/${FILE_NAME}-all.%d{yyyy-MM-dd}.log + 30 + + + + + 30MB + + + + %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n + + + + 192.168.28.32 + 9250 + + + + 192.168.28.32:9251 + 5 minutes + 6 second + 16384 + + {"appname":"metis"} + {"subappname":"metis-vdisk"} + + + + + + + + + + + + + + + + + + + diff --git a/codes/java-ecology/java-ecology-log/pom.xml b/codes/java-ecology/java-ecology-log/pom.xml new file mode 100644 index 00000000..65b27179 --- /dev/null +++ b/codes/java-ecology/java-ecology-log/pom.xml @@ -0,0 +1,18 @@ + + + 4.0.0 + + io.github.dunwu.javatech + java-ecology-log + 1.0.0 + pom + + + java-ecology-log4j + java-ecology-log4j2 + java-ecology-logback + java-ecology-logstash + + diff --git a/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/pom.xml b/codes/java-ecology/java-ecology-mq/java-ecology-kafka-springboot/pom.xml similarity index 84% rename from codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/pom.xml rename to codes/java-ecology/java-ecology-mq/java-ecology-kafka-springboot/pom.xml index ac5eebd6..7b547c41 100644 --- a/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/pom.xml +++ b/codes/java-ecology/java-ecology-mq/java-ecology-kafka-springboot/pom.xml @@ -3,14 +3,14 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 2.1.9.RELEASE - + + io.github.dunwu + dunwu-starter-parent + 0.5.1 + - io.github.dunwu.javaweb - javaweb-mq-kafka-spring-boot + io.github.dunwu.javatech + java-ecology-kafka-springboot 1.0.0 jar diff --git a/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaConsumer.java b/codes/java-ecology/java-ecology-mq/java-ecology-kafka-springboot/src/main/java/io/github/dunwu/javatech/KafkaConsumer.java similarity index 93% rename from codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaConsumer.java rename to codes/java-ecology/java-ecology-mq/java-ecology-kafka-springboot/src/main/java/io/github/dunwu/javatech/KafkaConsumer.java index 16bceae6..071b4247 100644 --- a/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaConsumer.java +++ b/codes/java-ecology/java-ecology-mq/java-ecology-kafka-springboot/src/main/java/io/github/dunwu/javatech/KafkaConsumer.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb; +package io.github.dunwu.javatech; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaProducer.java b/codes/java-ecology/java-ecology-mq/java-ecology-kafka-springboot/src/main/java/io/github/dunwu/javatech/KafkaProducer.java similarity index 96% rename from codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaProducer.java rename to codes/java-ecology/java-ecology-mq/java-ecology-kafka-springboot/src/main/java/io/github/dunwu/javatech/KafkaProducer.java index 14cb19b4..246104f2 100644 --- a/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaProducer.java +++ b/codes/java-ecology/java-ecology-mq/java-ecology-kafka-springboot/src/main/java/io/github/dunwu/javatech/KafkaProducer.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb; +package io.github.dunwu.javatech; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaProducerController.java b/codes/java-ecology/java-ecology-mq/java-ecology-kafka-springboot/src/main/java/io/github/dunwu/javatech/KafkaProducerController.java similarity index 95% rename from codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaProducerController.java rename to codes/java-ecology/java-ecology-mq/java-ecology-kafka-springboot/src/main/java/io/github/dunwu/javatech/KafkaProducerController.java index 86c8f9c2..70aebb9f 100644 --- a/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaProducerController.java +++ b/codes/java-ecology/java-ecology-mq/java-ecology-kafka-springboot/src/main/java/io/github/dunwu/javatech/KafkaProducerController.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb; +package io.github.dunwu.javatech; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; diff --git a/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/MsgKafkaApplication.java b/codes/java-ecology/java-ecology-mq/java-ecology-kafka-springboot/src/main/java/io/github/dunwu/javatech/MsgKafkaApplication.java similarity index 89% rename from codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/MsgKafkaApplication.java rename to codes/java-ecology/java-ecology-mq/java-ecology-kafka-springboot/src/main/java/io/github/dunwu/javatech/MsgKafkaApplication.java index 002ad216..48144045 100644 --- a/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/MsgKafkaApplication.java +++ b/codes/java-ecology/java-ecology-mq/java-ecology-kafka-springboot/src/main/java/io/github/dunwu/javatech/MsgKafkaApplication.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb; +package io.github.dunwu.javatech; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/config/KafkaConsumerConfig.java b/codes/java-ecology/java-ecology-mq/java-ecology-kafka-springboot/src/main/java/io/github/dunwu/javatech/config/KafkaConsumerConfig.java similarity index 98% rename from codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/config/KafkaConsumerConfig.java rename to codes/java-ecology/java-ecology-mq/java-ecology-kafka-springboot/src/main/java/io/github/dunwu/javatech/config/KafkaConsumerConfig.java index f7dbca23..31ae3462 100644 --- a/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/config/KafkaConsumerConfig.java +++ b/codes/java-ecology/java-ecology-mq/java-ecology-kafka-springboot/src/main/java/io/github/dunwu/javatech/config/KafkaConsumerConfig.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.config; +package io.github.dunwu.javatech.config; import java.util.HashMap; import java.util.Map; diff --git a/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/config/KafkaProducerConfig.java b/codes/java-ecology/java-ecology-mq/java-ecology-kafka-springboot/src/main/java/io/github/dunwu/javatech/config/KafkaProducerConfig.java similarity index 97% rename from codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/config/KafkaProducerConfig.java rename to codes/java-ecology/java-ecology-mq/java-ecology-kafka-springboot/src/main/java/io/github/dunwu/javatech/config/KafkaProducerConfig.java index b8e1d2c4..06ab586b 100644 --- a/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/config/KafkaProducerConfig.java +++ b/codes/java-ecology/java-ecology-mq/java-ecology-kafka-springboot/src/main/java/io/github/dunwu/javatech/config/KafkaProducerConfig.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.config; +package io.github.dunwu.javatech.config; import java.util.HashMap; import java.util.Map; diff --git a/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/resources/application.properties b/codes/java-ecology/java-ecology-mq/java-ecology-kafka-springboot/src/main/resources/application.properties similarity index 100% rename from codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/resources/application.properties rename to codes/java-ecology/java-ecology-mq/java-ecology-kafka-springboot/src/main/resources/application.properties diff --git a/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/resources/logback.xml b/codes/java-ecology/java-ecology-mq/java-ecology-kafka-springboot/src/main/resources/logback.xml similarity index 100% rename from codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/resources/logback.xml rename to codes/java-ecology/java-ecology-mq/java-ecology-kafka-springboot/src/main/resources/logback.xml diff --git a/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/test/java/io/github/dunwu/javaweb/KafkaProducerTest.java b/codes/java-ecology/java-ecology-mq/java-ecology-kafka-springboot/src/test/java/io/github/dunwu/javatech/KafkaProducerTest.java similarity index 94% rename from codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/test/java/io/github/dunwu/javaweb/KafkaProducerTest.java rename to codes/java-ecology/java-ecology-mq/java-ecology-kafka-springboot/src/test/java/io/github/dunwu/javatech/KafkaProducerTest.java index ae4edf1a..e73fe65f 100644 --- a/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/test/java/io/github/dunwu/javaweb/KafkaProducerTest.java +++ b/codes/java-ecology/java-ecology-mq/java-ecology-kafka-springboot/src/test/java/io/github/dunwu/javatech/KafkaProducerTest.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb; +package io.github.dunwu.javatech; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/codes/java-ecology/java-ecology-mq/java-ecology-kafka/pom.xml b/codes/java-ecology/java-ecology-mq/java-ecology-kafka/pom.xml new file mode 100644 index 00000000..5518b022 --- /dev/null +++ b/codes/java-ecology/java-ecology-mq/java-ecology-kafka/pom.xml @@ -0,0 +1,40 @@ + + + 4.0.0 + + + io.github.dunwu + dunwu-parent + 0.5.1 + + + io.github.dunwu.javatech + java-ecology-kafka + 1.0.0 + jar + java-ecology-kafka + + + UTF-8 + 1.8 + ${java.version} + ${java.version} + + + + + org.apache.kafka + kafka-clients + + + org.apache.kafka + kafka-streams + + + ch.qos.logback + logback-classic + + + diff --git a/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerAOC.java b/codes/java-ecology/java-ecology-mq/java-ecology-kafka/src/main/java/io/github/dunwu/javatech/kafka/ConsumerAOC.java similarity index 97% rename from codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerAOC.java rename to codes/java-ecology/java-ecology-mq/java-ecology-kafka/src/main/java/io/github/dunwu/javatech/kafka/ConsumerAOC.java index cc8268fa..52cffae0 100644 --- a/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerAOC.java +++ b/codes/java-ecology/java-ecology-mq/java-ecology-kafka/src/main/java/io/github/dunwu/javatech/kafka/ConsumerAOC.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.kafka; +package io.github.dunwu.javatech.kafka; import java.util.Arrays; import java.util.Properties; diff --git a/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManual.java b/codes/java-ecology/java-ecology-mq/java-ecology-kafka/src/main/java/io/github/dunwu/javatech/kafka/ConsumerManual.java similarity index 97% rename from codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManual.java rename to codes/java-ecology/java-ecology-mq/java-ecology-kafka/src/main/java/io/github/dunwu/javatech/kafka/ConsumerManual.java index 02060366..43c7b091 100644 --- a/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManual.java +++ b/codes/java-ecology/java-ecology-mq/java-ecology-kafka/src/main/java/io/github/dunwu/javatech/kafka/ConsumerManual.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.kafka; +package io.github.dunwu.javatech.kafka; import java.util.ArrayList; import java.util.Arrays; diff --git a/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManualPartition.java b/codes/java-ecology/java-ecology-mq/java-ecology-kafka/src/main/java/io/github/dunwu/javatech/kafka/ConsumerManualPartition.java similarity index 97% rename from codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManualPartition.java rename to codes/java-ecology/java-ecology-mq/java-ecology-kafka/src/main/java/io/github/dunwu/javatech/kafka/ConsumerManualPartition.java index 66a0980c..1c1f8665 100644 --- a/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManualPartition.java +++ b/codes/java-ecology/java-ecology-mq/java-ecology-kafka/src/main/java/io/github/dunwu/javatech/kafka/ConsumerManualPartition.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.kafka; +package io.github.dunwu.javatech.kafka; import java.util.Arrays; import java.util.Collections; diff --git a/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerDemo.java b/codes/java-ecology/java-ecology-mq/java-ecology-kafka/src/main/java/io/github/dunwu/javatech/kafka/ProducerDemo.java similarity index 97% rename from codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerDemo.java rename to codes/java-ecology/java-ecology-mq/java-ecology-kafka/src/main/java/io/github/dunwu/javatech/kafka/ProducerDemo.java index b816055a..7c1d2434 100644 --- a/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerDemo.java +++ b/codes/java-ecology/java-ecology-mq/java-ecology-kafka/src/main/java/io/github/dunwu/javatech/kafka/ProducerDemo.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.kafka; +package io.github.dunwu.javatech.kafka; import java.util.Properties; import org.apache.kafka.clients.producer.KafkaProducer; diff --git a/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerInTransaction.java b/codes/java-ecology/java-ecology-mq/java-ecology-kafka/src/main/java/io/github/dunwu/javatech/kafka/ProducerInTransaction.java similarity index 99% rename from codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerInTransaction.java rename to codes/java-ecology/java-ecology-mq/java-ecology-kafka/src/main/java/io/github/dunwu/javatech/kafka/ProducerInTransaction.java index 8e3af131..cff270ca 100644 --- a/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerInTransaction.java +++ b/codes/java-ecology/java-ecology-mq/java-ecology-kafka/src/main/java/io/github/dunwu/javatech/kafka/ProducerInTransaction.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.kafka; +package io.github.dunwu.javatech.kafka; import java.util.Arrays; import java.util.HashMap; diff --git a/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/StreamDemo.java b/codes/java-ecology/java-ecology-mq/java-ecology-kafka/src/main/java/io/github/dunwu/javatech/kafka/StreamDemo.java similarity index 97% rename from codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/StreamDemo.java rename to codes/java-ecology/java-ecology-mq/java-ecology-kafka/src/main/java/io/github/dunwu/javatech/kafka/StreamDemo.java index 9efe0918..2d9c701b 100644 --- a/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/StreamDemo.java +++ b/codes/java-ecology/java-ecology-mq/java-ecology-kafka/src/main/java/io/github/dunwu/javatech/kafka/StreamDemo.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.kafka; +package io.github.dunwu.javatech.kafka; import java.util.Arrays; import java.util.Properties; diff --git a/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/resources/logback.xml b/codes/java-ecology/java-ecology-mq/java-ecology-kafka/src/main/resources/logback.xml similarity index 93% rename from codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/resources/logback.xml rename to codes/java-ecology/java-ecology-mq/java-ecology-kafka/src/main/resources/logback.xml index 44ab53b1..72b2ddb5 100644 --- a/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/resources/logback.xml +++ b/codes/java-ecology/java-ecology-mq/java-ecology-kafka/src/main/resources/logback.xml @@ -3,7 +3,7 @@ - + @@ -40,7 +40,7 @@ - + diff --git a/codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/pom.xml b/codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/pom.xml new file mode 100644 index 00000000..d8ee8ca3 --- /dev/null +++ b/codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/pom.xml @@ -0,0 +1,37 @@ + + + 4.0.0 + + + io.github.dunwu + dunwu-parent + 0.5.1 + + + io.github.dunwu.javatech + java-ecology-rocketmq + 1.0.0 + jar + + + UTF-8 + 1.8 + ${java.version} + ${java.version} + + + + + org.apache.rocketmq + rocketmq-client + 4.2.0 + + + ch.qos.logback + logback-classic + + + + diff --git a/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/AsyncProducer.java b/codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech/rocketmq/AsyncProducer.java similarity index 97% rename from codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/AsyncProducer.java rename to codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech/rocketmq/AsyncProducer.java index 2e42c4b1..75ee2eb6 100644 --- a/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/AsyncProducer.java +++ b/codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech/rocketmq/AsyncProducer.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.rocketmq; +package io.github.dunwu.javatech.rocketmq; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendCallback; diff --git a/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BatchProducer.java b/codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech/rocketmq/BatchProducer.java similarity index 98% rename from codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BatchProducer.java rename to codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech/rocketmq/BatchProducer.java index 2243f3f5..5d0095d1 100644 --- a/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BatchProducer.java +++ b/codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech/rocketmq/BatchProducer.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.rocketmq; +package io.github.dunwu.javatech.rocketmq; import java.util.ArrayList; import java.util.Iterator; diff --git a/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastConsumer.java b/codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech/rocketmq/BroadcastConsumer.java similarity index 97% rename from codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastConsumer.java rename to codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech/rocketmq/BroadcastConsumer.java index c14a290c..9a8c6acb 100644 --- a/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastConsumer.java +++ b/codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech/rocketmq/BroadcastConsumer.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.rocketmq; +package io.github.dunwu.javatech.rocketmq; import java.util.List; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; diff --git a/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastProducer.java b/codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech/rocketmq/BroadcastProducer.java similarity index 94% rename from codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastProducer.java rename to codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech/rocketmq/BroadcastProducer.java index f045af23..b35b37eb 100644 --- a/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastProducer.java +++ b/codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech/rocketmq/BroadcastProducer.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.rocketmq; +package io.github.dunwu.javatech.rocketmq; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; diff --git a/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OnewayProducer.java b/codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech/rocketmq/OnewayProducer.java similarity index 96% rename from codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OnewayProducer.java rename to codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech/rocketmq/OnewayProducer.java index bc0f9e63..b231aa5e 100644 --- a/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OnewayProducer.java +++ b/codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech/rocketmq/OnewayProducer.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.rocketmq; +package io.github.dunwu.javatech.rocketmq; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.common.message.Message; diff --git a/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedConsumer.java b/codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech/rocketmq/OrderedConsumer.java similarity index 97% rename from codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedConsumer.java rename to codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech/rocketmq/OrderedConsumer.java index 4cc068eb..f7abeca9 100644 --- a/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedConsumer.java +++ b/codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech/rocketmq/OrderedConsumer.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.rocketmq; +package io.github.dunwu.javatech.rocketmq; import java.util.List; import java.util.concurrent.atomic.AtomicLong; diff --git a/codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech/rocketmq/OrderedProducer.java b/codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech/rocketmq/OrderedProducer.java new file mode 100644 index 00000000..e736448a --- /dev/null +++ b/codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech/rocketmq/OrderedProducer.java @@ -0,0 +1,47 @@ +package io.github.dunwu.javatech.rocketmq; + +import org.apache.rocketmq.client.producer.DefaultMQProducer; +import org.apache.rocketmq.client.producer.MQProducer; +import org.apache.rocketmq.client.producer.MessageQueueSelector; +import org.apache.rocketmq.client.producer.SendResult; +import org.apache.rocketmq.common.message.Message; +import org.apache.rocketmq.common.message.MessageQueue; +import org.apache.rocketmq.remoting.common.RemotingHelper; + +import java.util.List; + +/** + * 发送全局和分区排序的消息。 + * + * @author Zhang Peng + */ +public class OrderedProducer { + + public static void main(String[] args) throws Exception { + // Instantiate with a producer group name. + MQProducer producer = new DefaultMQProducer("example_group_name"); + ((DefaultMQProducer) producer).setNamesrvAddr(RocketConfig.HOST); + // Launch the instance. + producer.start(); + String[] tags = new String[] { "TagA", "TagB", "TagC", "TagD", "TagE" }; + for (int i = 0; i < 100; i++) { + int orderId = i % 10; + // Create a message instance, specifying topic, tag and message body. + Message msg = new Message("TopicTestjjj", tags[i % tags.length], "KEY" + i, + ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET)); + SendResult sendResult = producer.send(msg, new MessageQueueSelector() { + @Override + public MessageQueue select(List mqs, Message msg, Object arg) { + Integer id = (Integer) arg; + int index = id % mqs.size(); + return mqs.get(index); + } + }, orderId); + + System.out.printf("%s%n", sendResult); + } + // server shutdown + producer.shutdown(); + } + +} diff --git a/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/RocketConfig.java b/codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech/rocketmq/RocketConfig.java similarity index 73% rename from codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/RocketConfig.java rename to codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech/rocketmq/RocketConfig.java index 3c9fa344..a9e00062 100644 --- a/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/RocketConfig.java +++ b/codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech/rocketmq/RocketConfig.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.rocketmq; +package io.github.dunwu.javatech.rocketmq; /** * @author Zhang Peng diff --git a/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageConsumer.java b/codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech/rocketmq/ScheduledMessageConsumer.java similarity index 96% rename from codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageConsumer.java rename to codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech/rocketmq/ScheduledMessageConsumer.java index 7e58fd35..e89183e4 100644 --- a/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageConsumer.java +++ b/codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech/rocketmq/ScheduledMessageConsumer.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.rocketmq; +package io.github.dunwu.javatech.rocketmq; import java.util.List; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; diff --git a/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageProducer.java b/codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech/rocketmq/ScheduledMessageProducer.java similarity index 95% rename from codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageProducer.java rename to codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech/rocketmq/ScheduledMessageProducer.java index 83b53096..9096412f 100644 --- a/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageProducer.java +++ b/codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech/rocketmq/ScheduledMessageProducer.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.rocketmq; +package io.github.dunwu.javatech.rocketmq; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.common.message.Message; diff --git a/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/SyncProducer.java b/codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech/rocketmq/SyncProducer.java similarity index 96% rename from codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/SyncProducer.java rename to codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech/rocketmq/SyncProducer.java index 8ba82ae6..6d60e61f 100644 --- a/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/SyncProducer.java +++ b/codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/java/io/github/dunwu/javatech/rocketmq/SyncProducer.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.rocketmq; +package io.github.dunwu.javatech.rocketmq; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; diff --git a/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/resources/logback.xml b/codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/resources/logback.xml similarity index 93% rename from codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/resources/logback.xml rename to codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/resources/logback.xml index ff488e4b..3be789cb 100644 --- a/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/resources/logback.xml +++ b/codes/java-ecology/java-ecology-mq/java-ecology-rocketmq/src/main/resources/logback.xml @@ -3,7 +3,7 @@ - + @@ -40,7 +40,7 @@ - + diff --git a/codes/java-ecology/java-ecology-mq/pom.xml b/codes/java-ecology/java-ecology-mq/pom.xml new file mode 100644 index 00000000..0de69f4a --- /dev/null +++ b/codes/java-ecology/java-ecology-mq/pom.xml @@ -0,0 +1,17 @@ + + + 4.0.0 + + io.github.dunwu.javatech + java-ecology-mq + 1.0.0 + pom + + + java-ecology-kafka + java-ecology-kafka-springboot + java-ecology-rocketmq + + diff --git a/codes/java-ecology/java-ecology-others/java-ecology-cli/pom.xml b/codes/java-ecology/java-ecology-others/java-ecology-cli/pom.xml new file mode 100644 index 00000000..7f44f981 --- /dev/null +++ b/codes/java-ecology/java-ecology-others/java-ecology-cli/pom.xml @@ -0,0 +1,75 @@ + + + 4.0.0 + + + io.github.dunwu + dunwu-parent + 0.5.1 + + + io.github.dunwu.javatech + java-ecology-cli + 1.0.0 + jar + java-ecology-cli + + + UTF-8 + 1.8 + ${java.version} + ${java.version} + 5.4.0 + + + + + org.apache.commons + commons-lang3 + + + commons-cli + commons-cli + 1.4 + + + com.github.oshi + oshi-core + 4.1.0 + + + net.java.dev.jna + jna + + + net.java.dev.jna + jna-platform + + + + + net.java.dev.jna + jna + ${jna.version} + + + net.java.dev.jna + jna-platform + ${jna.version} + + + net.java.dev.jna + jna + + + + + junit + junit + test + + + + diff --git a/codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/AnsiSystem.java b/codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/AnsiSystem.java new file mode 100644 index 00000000..dd2df754 --- /dev/null +++ b/codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/AnsiSystem.java @@ -0,0 +1,244 @@ +package io.github.dunwu.javatech; + +import io.github.dunwu.javatech.constant.AnsiBgColor; +import io.github.dunwu.javatech.constant.AnsiColor; +import io.github.dunwu.javatech.constant.AnsiSgr; +import io.github.dunwu.javatech.constant.Color; +import org.apache.commons.lang3.StringUtils; + +/** + * 以 Ansi 方式在控制台输出(输出彩色字体、粗体、斜体、下划线等) + * + * @author Zhang Peng + * @see ANSI escape code + * @since 2019/10/30 + */ +public class AnsiSystem { + + public static final AnsiSystem RED = new AnsiSystem("\033[;31m"); + + public static final AnsiSystem GREEN = new AnsiSystem("\033[;32m"); + + public static final AnsiSystem YELLOW = new AnsiSystem("\033[;33m"); + + public static final AnsiSystem BLUE = new AnsiSystem("\033[;34m"); + + public static final AnsiSystem MAGENTA = new AnsiSystem("\033[;35m"); + + public static final AnsiSystem CYAN = new AnsiSystem("\033[;36m"); + + public static final AnsiSystem WHITE = new AnsiSystem("\033[;37m"); + + private static final String ENCODE_JOIN = ";"; + + private static final String ENCODE_START = "\033["; + + private static final String ENCODE_END = "m"; + + private static final String RESET = "\033[0;m"; + + private String code; + + public AnsiSystem(String code) { + this.code = code; + } + + public AnsiSystem(AnsiConfig config) { + this.code = encode(config); + } + + private String encode(AnsiConfig config) { + StringBuilder sb = new StringBuilder(); + sb.append(ENCODE_START); + if (config.isBold()) { + sb.append(ENCODE_JOIN).append(AnsiSgr.BOLD.getCode()); + } + if (config.isItalic()) { + sb.append(ENCODE_JOIN).append(AnsiSgr.ITALIC.getCode()); + } + if (config.isUnderline()) { + sb.append(ENCODE_JOIN).append(AnsiSgr.UNDERLINE.getCode()); + } + if (config.isSlowBlink()) { + sb.append(ENCODE_JOIN).append(AnsiSgr.SLOW_BLINK.getCode()); + } else { + if (config.isRapidBlink()) { + sb.append(ENCODE_JOIN).append(AnsiSgr.RAPID_BLINK.getCode()); + } + } + if (config.isReverseVideo()) { + sb.append(ENCODE_JOIN).append(AnsiSgr.REVERSE_VIDEO.getCode()); + } + if (config.isCanceal()) { + sb.append(ENCODE_JOIN).append(AnsiSgr.CONCEAL.getCode()); + } + if (config.getColor() != null) { + AnsiColor color = AnsiColor.valueOf(config.getColor().name()); + if (StringUtils.isNotBlank(color.getCode())) { + sb.append(ENCODE_JOIN).append(color.getCode()); + } + } + if (config.getBgColor() != null) { + AnsiBgColor color = AnsiBgColor.valueOf(config.getBgColor().name()); + if (StringUtils.isNotBlank(color.getCode())) { + sb.append(ENCODE_JOIN).append(color.getCode()); + } + } + sb.append(ENCODE_END); + return sb.toString(); + } + + public void print(String message) { + System.out.print(code + message + RESET); + } + + public void println(String message) { + System.out.println(code + message + RESET); + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + /** + * Ansi 配置 + */ + public static class AnsiConfig { + + private boolean bold; + + private boolean italic; + + private boolean underline; + + private boolean slowBlink; + + private boolean rapidBlink; + + private boolean reverseVideo; + + private boolean canceal; + + private Color color; + + private Color bgColor; + + public AnsiConfig() { + this.bold = false; + this.italic = false; + this.underline = false; + this.slowBlink = false; + this.rapidBlink = false; + this.reverseVideo = false; + this.canceal = false; + this.color = Color.DEFAULT; + this.bgColor = Color.DEFAULT; + } + + public AnsiConfig(boolean bold, boolean italic, boolean underline, boolean slowBlink, boolean rapidBlink, + boolean reverseVideo, boolean canceal, Color color, Color bgColor) { + this.bold = bold; + this.italic = italic; + this.underline = underline; + this.slowBlink = slowBlink; + this.rapidBlink = rapidBlink; + this.reverseVideo = reverseVideo; + this.canceal = canceal; + this.color = color; + this.bgColor = bgColor; + } + + @Override + public String toString() { + return "AnsiParam{" + + "bold=" + bold + + ", italic=" + italic + + ", underline=" + underline + + ", slowBlink=" + slowBlink + + ", rapidBlink=" + rapidBlink + + ", reverseVideo=" + reverseVideo + + ", canceal=" + canceal + + ", color=" + color + + ", bgColor=" + bgColor + + '}'; + } + + public boolean isBold() { + return bold; + } + + public void setBold(boolean bold) { + this.bold = bold; + } + + public boolean isItalic() { + return italic; + } + + public void setItalic(boolean italic) { + this.italic = italic; + } + + public boolean isUnderline() { + return underline; + } + + public void setUnderline(boolean underline) { + this.underline = underline; + } + + public boolean isSlowBlink() { + return slowBlink; + } + + public void setSlowBlink(boolean slowBlink) { + this.slowBlink = slowBlink; + } + + public boolean isRapidBlink() { + return rapidBlink; + } + + public void setRapidBlink(boolean rapidBlink) { + this.rapidBlink = rapidBlink; + } + + public boolean isReverseVideo() { + return reverseVideo; + } + + public void setReverseVideo(boolean reverseVideo) { + this.reverseVideo = reverseVideo; + } + + public boolean isCanceal() { + return canceal; + } + + public void setCanceal(boolean canceal) { + this.canceal = canceal; + } + + public Color getColor() { + return color; + } + + public void setColor(Color color) { + this.color = color; + } + + public Color getBgColor() { + return bgColor; + } + + public void setBgColor(Color bgColor) { + this.bgColor = bgColor; + } + + } + +} diff --git a/codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/CliDemo.java b/codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/CliDemo.java new file mode 100644 index 00000000..a6037c7d --- /dev/null +++ b/codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/CliDemo.java @@ -0,0 +1,56 @@ +package io.github.dunwu.javatech; + +import org.apache.commons.cli.ParseException; + +import java.util.Date; +import java.util.Properties; +import java.util.Scanner; + +/** + * @author Zhang Peng + * @since 2019/10/29 + */ +public class CliDemo { + + public static void main(String[] args) throws ParseException { + + while (true) { + Scanner scanner = new Scanner(System.in); + String param = ""; + if (scanner.hasNext()) { + param = scanner.next(); + } + + switch (param) { + case "date": + AnsiSystem.BLUE.println("date = " + new Date()); + break; + case "area": + AnsiSystem.BLUE.println("area = " + "China"); + break; + case "system": + Properties props = System.getProperties(); + System.out.println("Java的运行环境版本:" + props.getProperty("java.version")); + System.out.println("默认的临时文件路径:" + props.getProperty("java.io.tmpdir")); + System.out.println("操作系统的名称:" + props.getProperty("os.name")); + System.out.println("操作系统的构架:" + props.getProperty("os.arch")); + System.out.println("操作系统的版本:" + props.getProperty("os.version")); + System.out.println("用户的账户名称:" + props.getProperty("user.name")); + System.out.println("用户的主目录:" + props.getProperty("user.home")); + System.out.println("用户的当前工作目录:" + props.getProperty("user.dir")); + System.out.println("操作系统:" + props.getProperty("sun.desktop")); + System.out.println("CPU个数:" + Runtime.getRuntime().availableProcessors()); + System.out.println("虚拟机内存总量:" + Runtime.getRuntime().totalMemory()); + System.out.println("虚拟机空闲内存量:" + Runtime.getRuntime().freeMemory()); + System.out.println("虚拟机使用最大内存量:" + Runtime.getRuntime().maxMemory()); + break; + case "exit": + return; + default: + System.err.println("invalid param"); + break; + } + } + } + +} diff --git a/codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/CliUtil.java b/codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/CliUtil.java new file mode 100644 index 00000000..0878bdd6 --- /dev/null +++ b/codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/CliUtil.java @@ -0,0 +1,28 @@ +package io.github.dunwu.javatech; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.Options; + +public class CliUtil { + + public static void prepare(String[] args) throws Exception { + // commons-cli命令行参数,需要带参数值 + Options options = new Options(); + // sql文件路径 + options.addOption("sql", true, "sql config"); + // 任务名称 + options.addOption("name", true, "job name"); + + // 解析命令行参数 + CommandLineParser parser = new DefaultParser(); + CommandLine cl = parser.parse(options, args); + String sql = cl.getOptionValue("sql"); + String name = cl.getOptionValue("name"); + + System.out.println("sql : " + sql); + System.out.println("name : " + name); + } + +} diff --git a/codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/SystemInfoUtil.java b/codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/SystemInfoUtil.java new file mode 100644 index 00000000..58363700 --- /dev/null +++ b/codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/SystemInfoUtil.java @@ -0,0 +1,265 @@ +package io.github.dunwu.javatech; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import oshi.SystemInfo; +import oshi.hardware.*; +import oshi.software.os.*; +import oshi.util.FormatUtil; +import oshi.util.Util; + +import java.util.Arrays; +import java.util.List; + +/** + * @author Zhang Peng + * @since 2019/10/30 + */ +public class SystemInfoUtil { + + private static Logger logger = LoggerFactory.getLogger(SystemInfoUtil.class); + + public static void main(String[] args) { + + logger.info("Initializing System..."); + SystemInfo systemInfo = new SystemInfo(); + HardwareAbstractionLayer hal = systemInfo.getHardware(); + logger.info("Checking computer system..."); + printComputerSystem(hal.getComputerSystem()); + logger.info("Checking Processor..."); + printProcessor(hal.getProcessor()); + logger.info("Checking Memory..."); + printMemory(hal.getMemory()); + logger.info("Checking CPU..."); + printCpu(hal.getProcessor()); + logger.info("Checking Sensors..."); + printSensors(hal.getSensors()); + logger.info("Checking Power sources..."); + printPowerSources(hal.getPowerSources()); + logger.info("Checking Disks..."); + printDisks(hal.getDiskStores()); + logger.info("Checking Network interfaces..."); + printNetworkInterfaces(hal.getNetworkIFs()); + // hardware: displays + logger.info("Checking Displays..."); + printDisplays(hal.getDisplays()); + // hardware: USB devices + logger.info("Checking USB Devices..."); + printUsbDevices(hal.getUsbDevices(true)); + OperatingSystem os = systemInfo.getOperatingSystem(); + System.out.println(os); + logger.info("Checking Processes..."); + printProcesses(os, hal.getMemory()); + logger.info("Checking File System..."); + printFileSystem(os.getFileSystem()); + logger.info("Checking Network parameterss..."); + printNetworkParameters(os.getNetworkParams()); + } + + public static void printComputerSystem(final ComputerSystem computerSystem) { + System.out.println("manufacturer: " + computerSystem.getManufacturer()); + System.out.println("model: " + computerSystem.getModel()); + System.out.println("serialnumber: " + computerSystem.getSerialNumber()); + final Firmware firmware = computerSystem.getFirmware(); + System.out.println("firmware:"); + System.out.println(" manufacturer: " + firmware.getManufacturer()); + System.out.println(" name: " + firmware.getName()); + System.out.println(" description: " + firmware.getDescription()); + System.out.println(" version: " + firmware.getVersion()); + System.out.println(" release date: " + (firmware.getReleaseDate() == null ? "unknown" + : firmware.getReleaseDate() == null ? "unknown" : firmware.getReleaseDate())); + final Baseboard baseboard = computerSystem.getBaseboard(); + System.out.println("baseboard:"); + System.out.println(" manufacturer: " + baseboard.getManufacturer()); + System.out.println(" model: " + baseboard.getModel()); + System.out.println(" version: " + baseboard.getVersion()); + System.out.println(" serialnumber: " + baseboard.getSerialNumber()); + } + + public static void printProcessor(CentralProcessor processor) { + System.out.println(processor); + System.out.println(" " + processor.getPhysicalProcessorCount() + " physical CPU(s)"); + System.out.println(" " + processor.getLogicalProcessorCount() + " logical CPU(s)"); + System.out.println("Identifier: " + processor.getIdentifier()); + System.out.println("ProcessorID: " + processor.getProcessorID()); + } + + public static void printMemory(GlobalMemory memory) { + System.out.println("以使用内存: " + FormatUtil.formatBytes(memory.getAvailable()) + "总共内存" + + FormatUtil.formatBytes(memory.getTotal())); + } + + public static void printCpu(CentralProcessor processor) { + long[] prevTicks = processor.getSystemCpuLoadTicks(); + System.out.println("CPU, IOWait, and IRQ ticks @ 0 sec:" + Arrays.toString(prevTicks)); + // Wait a second... + Util.sleep(1000); + long[] ticks = processor.getSystemCpuLoadTicks(); + System.out.println("CPU, IOWait, and IRQ ticks @ 1 sec:" + Arrays.toString(ticks)); + long user = + ticks[CentralProcessor.TickType.USER.getIndex()] - prevTicks[CentralProcessor.TickType.USER.getIndex()]; + long nice = + ticks[CentralProcessor.TickType.NICE.getIndex()] - prevTicks[CentralProcessor.TickType.NICE.getIndex()]; + long sys = + ticks[CentralProcessor.TickType.SYSTEM.getIndex()] - prevTicks[CentralProcessor.TickType.SYSTEM.getIndex()]; + long idle = + ticks[CentralProcessor.TickType.IDLE.getIndex()] - prevTicks[CentralProcessor.TickType.IDLE.getIndex()]; + long iowait = + ticks[CentralProcessor.TickType.IOWAIT.getIndex()] - prevTicks[CentralProcessor.TickType.IOWAIT.getIndex()]; + long irq = + ticks[CentralProcessor.TickType.IRQ.getIndex()] - prevTicks[CentralProcessor.TickType.IRQ.getIndex()]; + long softirq = ticks[CentralProcessor.TickType.SOFTIRQ.getIndex()] + - prevTicks[CentralProcessor.TickType.SOFTIRQ.getIndex()]; + long steal = + ticks[CentralProcessor.TickType.STEAL.getIndex()] - prevTicks[CentralProcessor.TickType.STEAL.getIndex()]; + long totalCpu = user + nice + sys + idle + iowait + irq + softirq + steal; + System.out.format( + "User: %.1f%% Nice: %.1f%% System: %.1f%% Idle: %.1f%% IOwait: %.1f%% IRQ: %.1f%% SoftIRQ: %.1f%% Steal: %.1f%%%n", + 100d * user / totalCpu, 100d * nice / totalCpu, 100d * sys / totalCpu, 100d * idle / totalCpu, + 100d * iowait / totalCpu, 100d * irq / totalCpu, 100d * softirq / totalCpu, 100d * steal / totalCpu); + // System.out.format("CPU load: %.1f%% (counting ticks)%n", processor.getSystemCpuLoadTicks() * 100); + // System.out.format("CPU load: %.1f%% (OS MXBean)%n", processor.getProcessorCpuLoadBetweenTicks() * 100); + double[] loadAverage = processor.getSystemLoadAverage(3); + System.out.println("CPU load averages:" + (loadAverage[0] < 0 ? " N/A" : String.format(" %.2f", loadAverage[0])) + + (loadAverage[1] < 0 ? " N/A" : String.format(" %.2f", loadAverage[1])) + + (loadAverage[2] < 0 ? " N/A" : String.format(" %.2f", loadAverage[2]))); + // per core CPU + StringBuilder procCpu = new StringBuilder("CPU load per processor:"); + double[] load = processor.getProcessorCpuLoadBetweenTicks(processor.getProcessorCpuLoadTicks()); + for (double avg : load) { + procCpu.append(String.format(" %.1f%%", avg * 100)); + } + System.out.println(procCpu.toString()); + } + + public static void printSensors(Sensors sensors) { + System.out.println("Sensors:"); + System.out.format(" CPU Temperature: %.1f°C%n", sensors.getCpuTemperature()); + System.out.println(" Fan Speeds: " + Arrays.toString(sensors.getFanSpeeds())); + System.out.format(" CPU Voltage: %.1fV%n", sensors.getCpuVoltage()); + } + + public static void printPowerSources(PowerSource[] powerSources) { + StringBuilder sb = new StringBuilder("Power: "); + if (powerSources.length == 0) { + sb.append("Unknown"); + } else { + double timeRemaining = powerSources[0].getTimeRemaining(); + if (timeRemaining < -1d) { + sb.append("Charging"); + } else if (timeRemaining < 0d) { + sb.append("Calculating time remaining"); + } else { + sb.append(String.format("%d:%02d remaining", (int) (timeRemaining / 3600), + (int) (timeRemaining / 60) % 60)); + } + } + for (PowerSource pSource : powerSources) { + sb.append(String.format("%n %s @ %.1f%%", pSource.getName(), pSource.getRemainingCapacity() * 100d)); + } + System.out.println(sb.toString()); + } + + public static void printDisks(HWDiskStore[] diskStores) { + System.out.println("Disks:"); + for (HWDiskStore disk : diskStores) { + boolean readwrite = disk.getReads() > 0 || disk.getWrites() > 0; + System.out.format(" %s: (model: %s - S/N: %s) size: %s, reads: %s (%s), writes: %s (%s), xfer: %s ms%n", + disk.getName(), disk.getModel(), disk.getSerial(), + disk.getSize() > 0 ? FormatUtil.formatBytesDecimal(disk.getSize()) : "?", + readwrite ? disk.getReads() : "?", readwrite ? FormatUtil.formatBytes(disk.getReadBytes()) : "?", + readwrite ? disk.getWrites() : "?", readwrite ? FormatUtil.formatBytes(disk.getWriteBytes()) : "?", + readwrite ? disk.getTransferTime() : "?"); + HWPartition[] partitions = disk.getPartitions(); + if (partitions == null) { + // TODO Remove when all OS's implemented + continue; + } + for (HWPartition part : partitions) { + System.out.format(" |-- %s: %s (%s) Maj:Min=%d:%d, size: %s%s%n", part.getIdentification(), + part.getName(), part.getType(), part.getMajor(), part.getMinor(), + FormatUtil.formatBytesDecimal(part.getSize()), + part.getMountPoint().isEmpty() ? "" : " @ " + part.getMountPoint()); + } + } + } + + public static void printNetworkInterfaces(NetworkIF[] networkIFs) { + System.out.println("Network interfaces:"); + for (NetworkIF net : networkIFs) { + System.out.format(" Name: %s (%s)%n", net.getName(), net.getDisplayName()); + System.out.format(" MAC Address: %s %n", net.getMacaddr()); + System.out.format(" MTU: %s, Speed: %s %n", net.getMTU(), FormatUtil.formatValue(net.getSpeed(), "bps")); + System.out.format(" IPv4: %s %n", Arrays.toString(net.getIPv4addr())); + System.out.format(" IPv6: %s %n", Arrays.toString(net.getIPv6addr())); + boolean hasData = net.getBytesRecv() > 0 || net.getBytesSent() > 0 || net.getPacketsRecv() > 0 + || net.getPacketsSent() > 0; + System.out.format(" Traffic: received %s/%s%s; transmitted %s/%s%s %n", + hasData ? net.getPacketsRecv() + " packets" : "?", + hasData ? FormatUtil.formatBytes(net.getBytesRecv()) : "?", + hasData ? " (" + net.getInErrors() + " err)" : "", + hasData ? net.getPacketsSent() + " packets" : "?", + hasData ? FormatUtil.formatBytes(net.getBytesSent()) : "?", + hasData ? " (" + net.getOutErrors() + " err)" : ""); + } + } + + public static void printDisplays(Display[] displays) { + System.out.println("Displays:"); + int i = 0; + for (Display display : displays) { + System.out.println(" Display " + i + ":"); + System.out.println(display.toString()); + i++; + } + } + + public static void printUsbDevices(UsbDevice[] usbDevices) { + System.out.println("USB Devices:"); + for (UsbDevice usbDevice : usbDevices) { + System.out.println(usbDevice.toString()); + } + } + + public static void printProcesses(OperatingSystem os, GlobalMemory memory) { + System.out.println("Processes: " + os.getProcessCount() + ", Threads: " + os.getThreadCount()); + // Sort by highest CPU + List procs = Arrays.asList(os.getProcesses(5, OperatingSystem.ProcessSort.CPU)); + System.out.println(" PID %CPU %MEM VSZ RSS Name"); + for (int i = 0; i < procs.size() && i < 5; i++) { + OSProcess p = procs.get(i); + System.out.format(" %5d %5.1f %4.1f %9s %9s %s%n", p.getProcessID(), + 100d * (p.getKernelTime() + p.getUserTime()) / p.getUpTime(), + 100d * p.getResidentSetSize() / memory.getTotal(), FormatUtil.formatBytes(p.getVirtualSize()), + FormatUtil.formatBytes(p.getResidentSetSize()), p.getName()); + } + } + + public static void printFileSystem(FileSystem fileSystem) { + System.out.println("File System:"); + System.out.format(" File Descriptors: %d/%d%n", fileSystem.getOpenFileDescriptors(), + fileSystem.getMaxFileDescriptors()); + OSFileStore[] fsArray = fileSystem.getFileStores(); + for (OSFileStore fs : fsArray) { + long usable = fs.getUsableSpace(); + long total = fs.getTotalSpace(); + System.out.format( + " %s (%s) [%s] %s of %s free (%.1f%%) is %s " + + (fs.getLogicalVolume() != null && fs.getLogicalVolume().length() > 0 ? "[%s]" : "%s") + + " and is mounted at %s%n", + fs.getName(), fs.getDescription().isEmpty() ? "file system" : fs.getDescription(), fs.getType(), + FormatUtil.formatBytes(usable), FormatUtil.formatBytes(fs.getTotalSpace()), 100d * usable / total, + fs.getVolume(), fs.getLogicalVolume(), fs.getMount()); + } + } + + public static void printNetworkParameters(NetworkParams networkParams) { + System.out.println("Network parameters:"); + System.out.format(" Host name: %s%n", networkParams.getHostName()); + System.out.format(" Domain name: %s%n", networkParams.getDomainName()); + System.out.format(" DNS servers: %s%n", Arrays.toString(networkParams.getDnsServers())); + System.out.format(" IPv4 Gateway: %s%n", networkParams.getIpv4DefaultGateway()); + System.out.format(" IPv6 Gateway: %s%n", networkParams.getIpv6DefaultGateway()); + } + +} diff --git a/codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/constant/AnsiBgColor.java b/codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/constant/AnsiBgColor.java new file mode 100644 index 00000000..86f99a0c --- /dev/null +++ b/codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/constant/AnsiBgColor.java @@ -0,0 +1,44 @@ +package io.github.dunwu.javatech.constant; + +/** + * ANSI 背景显示颜色枚举 + * + * @author Zhang Peng + * @see ANSI Colors + * @since 2019/10/30 + */ +public enum AnsiBgColor implements AnsiElement { + + DEFAULT(""), + BLACK("40"), + RED("41"), + GREEN("42"), + YELLOW("43"), + BLUE("44"), + MAGENTA("45"), + CYAN("46"), + WHITE("47"), + BRIGHT_BLACK("100"), + BRIGHT_RED("101"), + BRIGHT_GREEN("102"), + BRIGHT_YELLOW("109"), + BRIGHT_BLUE("104"), + BRIGHT_MAGENTA("105"), + BRIGHT_CYAN("106"), + BRIGHT_WHITE("107"); + + private final String code; + + AnsiBgColor(String code) { + this.code = code; + } + + @Override + public String toString() { + return code; + } + + public String getCode() { + return code; + } +} diff --git a/codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/constant/AnsiColor.java b/codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/constant/AnsiColor.java new file mode 100644 index 00000000..38689728 --- /dev/null +++ b/codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/constant/AnsiColor.java @@ -0,0 +1,44 @@ +package io.github.dunwu.javatech.constant; + +/** + * ANSI 字体显示颜色枚举 + * + * @author Zhang Peng + * @see ANSI Colors + * @since 2019/10/30 + */ +public enum AnsiColor implements AnsiElement { + + DEFAULT(""), + BLACK("30"), + RED("31"), + GREEN("32"), + YELLOW("33"), + BLUE("34"), + MAGENTA("35"), + CYAN("36"), + WHITE("37"), + BRIGHT_BLACK("90"), + BRIGHT_RED("91"), + BRIGHT_GREEN("92"), + BRIGHT_YELLOW("99"), + BRIGHT_BLUE("94"), + BRIGHT_MAGENTA("95"), + BRIGHT_CYAN("96"), + BRIGHT_WHITE("97"); + + private final String code; + + AnsiColor(String code) { + this.code = code; + } + + @Override + public String toString() { + return code; + } + + public String getCode() { + return code; + } +} diff --git a/codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/constant/AnsiElement.java b/codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/constant/AnsiElement.java new file mode 100644 index 00000000..0b24d5e4 --- /dev/null +++ b/codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/constant/AnsiElement.java @@ -0,0 +1,11 @@ +package io.github.dunwu.javatech.constant; + +public interface AnsiElement { + + /** + * @return the ANSI escape code + */ + @Override + String toString(); + +} diff --git a/codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/constant/AnsiSgr.java b/codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/constant/AnsiSgr.java new file mode 100644 index 00000000..f4db6744 --- /dev/null +++ b/codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/constant/AnsiSgr.java @@ -0,0 +1,42 @@ +package io.github.dunwu.javatech.constant; + +/** + * SGR (Select Graphic Rendition) 设置显示属性。 + *

    + * 可以按相同的顺序设置多个属性,并用分号隔开。 + *

    + * 每个显示属性一直有效,直到随后发生SGR重置它为止。 + *

    + * 如果未给出代码,则将CSI m视为CSI 0 m(重置/正常)。 + * + * @author Zhang Peng + * @see SGR + * @since 2019/10/30 + */ +public enum AnsiSgr implements AnsiElement { + + NORMAL("0"), + BOLD("1"), + FAINT("2"), + ITALIC("3"), + UNDERLINE("4"), + SLOW_BLINK("5"), + RAPID_BLINK("6"), + REVERSE_VIDEO("7"), + CONCEAL("8"); + + private final String code; + + AnsiSgr(String code) { + this.code = code; + } + + @Override + public String toString() { + return code; + } + + public String getCode() { + return code; + } +} diff --git a/codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/constant/Color.java b/codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/constant/Color.java new file mode 100644 index 00000000..31b58103 --- /dev/null +++ b/codes/java-ecology/java-ecology-others/java-ecology-cli/src/main/java/io/github/dunwu/javatech/constant/Color.java @@ -0,0 +1,36 @@ +package io.github.dunwu.javatech.constant; + +/** + * @author Zhang Peng + * @since 2019/10/30 + */ +public enum Color { + + DEFAULT("默认"), + BLACK("黑色"), + RED("红色"), + GREEN("绿色"), + YELLOW("黄色"), + BLUE("蓝色"), + MAGENTA("紫色"), + CYAN("青色"), + WHITE("白色"), + BRIGHT_BLACK("亮黑色"), + BRIGHT_RED("亮红色"), + BRIGHT_GREEN("亮绿色"), + BRIGHT_YELLOW("亮黄色"), + BRIGHT_BLUE("亮蓝色"), + BRIGHT_MAGENTA("亮紫色"), + BRIGHT_CYAN("亮青色"), + BRIGHT_WHITE("亮白色"); + + private String desc; + + Color(String desc) { + this.desc = desc; + } + + public String getDesc() { + return desc; + } +} diff --git a/codes/java-ecology/java-ecology-others/java-ecology-cli/src/test/java/io/github/dunwu/javatech/CliUtilTests.java b/codes/java-ecology/java-ecology-others/java-ecology-cli/src/test/java/io/github/dunwu/javatech/CliUtilTests.java new file mode 100644 index 00000000..13286c15 --- /dev/null +++ b/codes/java-ecology/java-ecology-others/java-ecology-cli/src/test/java/io/github/dunwu/javatech/CliUtilTests.java @@ -0,0 +1,17 @@ +package io.github.dunwu.javatech; + +import org.junit.Test; + +/** + * @author Zhang Peng + * @since 2019/10/29 + */ +public class CliUtilTests { + + @Test + public void prepare() throws Exception { + String[] args = { "-sql", "select * from aa", "-name", "测试" }; + CliUtil.prepare(args); + } + +} diff --git a/codes/java-ecology/java-ecology-others/java-ecology-cli/src/test/java/io/github/dunwu/javatech/SystemInfoTest.java b/codes/java-ecology/java-ecology-others/java-ecology-cli/src/test/java/io/github/dunwu/javatech/SystemInfoTest.java new file mode 100644 index 00000000..05e945a5 --- /dev/null +++ b/codes/java-ecology/java-ecology-others/java-ecology-cli/src/test/java/io/github/dunwu/javatech/SystemInfoTest.java @@ -0,0 +1,320 @@ +package io.github.dunwu.javatech; + +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import oshi.PlatformEnum; +import oshi.SystemInfo; +import oshi.hardware.*; +import oshi.hardware.CentralProcessor.TickType; +import oshi.software.os.*; +import oshi.software.os.OperatingSystem.ProcessSort; +import oshi.util.FormatUtil; +import oshi.util.Util; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertFalse; + +/** + * A demonstration of access to many of OSHI's capabilities + */ +public class SystemInfoTest { + + private static final Logger logger = LoggerFactory.getLogger(SystemInfoTest.class); + + static List oshi = new ArrayList<>(); + + /** + * Test that this platform is implemented.. + */ + @Test + public void testPlatformEnum() { + assertFalse(PlatformEnum.UNKNOWN.equals(SystemInfo.getCurrentPlatformEnum())); + // Exercise the main method + main(null); + } + + /** + * The main method, demonstrating use of classes. + * + * @param args the arguments (unused) + */ + public static void main(String[] args) { + logger.info("Initializing System..."); + SystemInfo si = new SystemInfo(); + + HardwareAbstractionLayer hal = si.getHardware(); + OperatingSystem os = si.getOperatingSystem(); + + printOperatingSystem(os); + + logger.info("Checking computer system..."); + printComputerSystem(hal.getComputerSystem()); + + logger.info("Checking Processor..."); + printProcessor(hal.getProcessor()); + + logger.info("Checking Memory..."); + printMemory(hal.getMemory()); + + logger.info("Checking CPU..."); + printCpu(hal.getProcessor()); + + logger.info("Checking Processes..."); + printProcesses(os, hal.getMemory()); + + logger.info("Checking Services..."); + printServices(os); + + logger.info("Checking Sensors..."); + printSensors(hal.getSensors()); + + logger.info("Checking Power sources..."); + printPowerSources(hal.getPowerSources()); + + logger.info("Checking Disks..."); + printDisks(hal.getDiskStores()); + + logger.info("Checking File System..."); + printFileSystem(os.getFileSystem()); + + logger.info("Checking Network interfaces..."); + printNetworkInterfaces(hal.getNetworkIFs()); + + logger.info("Checking Network parameters..."); + printNetworkParameters(os.getNetworkParams()); + + // hardware: displays + logger.info("Checking Displays..."); + printDisplays(hal.getDisplays()); + + // hardware: USB devices + logger.info("Checking USB Devices..."); + printUsbDevices(hal.getUsbDevices(true)); + + logger.info("Checking Sound Cards..."); + printSoundCards(hal.getSoundCards()); + + StringBuilder output = new StringBuilder(); + for (int i = 0; i < oshi.size(); i++) { + output.append(oshi.get(i)); + if (oshi.get(i) != null && !oshi.get(i).endsWith("\n")) { + output.append('\n'); + } + } + logger.info("Printing Operating System and Hardware Info:{}{}", '\n', output); + } + + private static void printOperatingSystem(final OperatingSystem os) { + oshi.add(String.valueOf(os)); + oshi.add("Booted: " + Instant.ofEpochSecond(os.getSystemBootTime())); + oshi.add("Uptime: " + FormatUtil.formatElapsedSecs(os.getSystemUptime())); + oshi.add("Running with" + (os.isElevated() ? "" : "out") + " elevated permissions."); + } + + private static void printComputerSystem(final ComputerSystem computerSystem) { + oshi.add("system: " + computerSystem.toString()); + oshi.add(" firmware: " + computerSystem.getFirmware().toString()); + oshi.add(" baseboard: " + computerSystem.getBaseboard().toString()); + } + + private static void printProcessor(CentralProcessor processor) { + oshi.add(processor.toString()); + } + + private static void printMemory(GlobalMemory memory) { + oshi.add("Memory: \n " + memory.toString()); + VirtualMemory vm = memory.getVirtualMemory(); + oshi.add("Swap: \n " + vm.toString()); + PhysicalMemory[] pmArray = memory.getPhysicalMemory(); + if (pmArray.length > 0) { + oshi.add("Physical Memory: "); + for (PhysicalMemory pm : pmArray) { + oshi.add(" " + pm.toString()); + } + } + } + + private static void printCpu(CentralProcessor processor) { + oshi.add("Context Switches/Interrupts: " + processor.getContextSwitches() + " / " + processor.getInterrupts()); + + long[] prevTicks = processor.getSystemCpuLoadTicks(); + long[][] prevProcTicks = processor.getProcessorCpuLoadTicks(); + oshi.add("CPU, IOWait, and IRQ ticks @ 0 sec:" + Arrays.toString(prevTicks)); + // Wait a second... + Util.sleep(1000); + long[] ticks = processor.getSystemCpuLoadTicks(); + oshi.add("CPU, IOWait, and IRQ ticks @ 1 sec:" + Arrays.toString(ticks)); + long user = ticks[TickType.USER.getIndex()] - prevTicks[TickType.USER.getIndex()]; + long nice = ticks[TickType.NICE.getIndex()] - prevTicks[TickType.NICE.getIndex()]; + long sys = ticks[TickType.SYSTEM.getIndex()] - prevTicks[TickType.SYSTEM.getIndex()]; + long idle = ticks[TickType.IDLE.getIndex()] - prevTicks[TickType.IDLE.getIndex()]; + long iowait = ticks[TickType.IOWAIT.getIndex()] - prevTicks[TickType.IOWAIT.getIndex()]; + long irq = ticks[TickType.IRQ.getIndex()] - prevTicks[TickType.IRQ.getIndex()]; + long softirq = ticks[TickType.SOFTIRQ.getIndex()] - prevTicks[TickType.SOFTIRQ.getIndex()]; + long steal = ticks[TickType.STEAL.getIndex()] - prevTicks[TickType.STEAL.getIndex()]; + long totalCpu = user + nice + sys + idle + iowait + irq + softirq + steal; + + oshi.add(String.format( + "User: %.1f%% Nice: %.1f%% System: %.1f%% Idle: %.1f%% IOwait: %.1f%% IRQ: %.1f%% SoftIRQ: %.1f%% Steal: %.1f%%", + 100d * user / totalCpu, 100d * nice / totalCpu, 100d * sys / totalCpu, 100d * idle / totalCpu, + 100d * iowait / totalCpu, 100d * irq / totalCpu, 100d * softirq / totalCpu, 100d * steal / totalCpu)); + oshi.add(String.format("CPU load: %.1f%%", processor.getSystemCpuLoadBetweenTicks(prevTicks) * 100)); + double[] loadAverage = processor.getSystemLoadAverage(3); + oshi.add("CPU load averages:" + (loadAverage[0] < 0 ? " N/A" : String.format(" %.2f", loadAverage[0])) + + (loadAverage[1] < 0 ? " N/A" : String.format(" %.2f", loadAverage[1])) + + (loadAverage[2] < 0 ? " N/A" : String.format(" %.2f", loadAverage[2]))); + // per core CPU + StringBuilder procCpu = new StringBuilder("CPU load per processor:"); + double[] load = processor.getProcessorCpuLoadBetweenTicks(prevProcTicks); + for (double avg : load) { + procCpu.append(String.format(" %.1f%%", avg * 100)); + } + oshi.add(procCpu.toString()); + long freq = processor.getProcessorIdentifier().getVendorFreq(); + if (freq > 0) { + oshi.add("Vendor Frequency: " + FormatUtil.formatHertz(freq)); + } + freq = processor.getMaxFreq(); + if (freq > 0) { + oshi.add("Max Frequency: " + FormatUtil.formatHertz(freq)); + } + long[] freqs = processor.getCurrentFreq(); + if (freqs[0] > 0) { + StringBuilder sb = new StringBuilder("Current Frequencies: "); + for (int i = 0; i < freqs.length; i++) { + if (i > 0) { + sb.append(", "); + } + sb.append(FormatUtil.formatHertz(freqs[i])); + } + oshi.add(sb.toString()); + } + } + + private static void printProcesses(OperatingSystem os, GlobalMemory memory) { + oshi.add("Processes: " + os.getProcessCount() + ", Threads: " + os.getThreadCount()); + // Sort by highest CPU + List procs = Arrays.asList(os.getProcesses(5, ProcessSort.CPU)); + + oshi.add(" PID %CPU %MEM VSZ RSS Name"); + for (int i = 0; i < procs.size() && i < 5; i++) { + OSProcess p = procs.get(i); + oshi.add(String.format(" %5d %5.1f %4.1f %9s %9s %s", p.getProcessID(), + 100d * (p.getKernelTime() + p.getUserTime()) / p.getUpTime(), + 100d * p.getResidentSetSize() / memory.getTotal(), FormatUtil.formatBytes(p.getVirtualSize()), + FormatUtil.formatBytes(p.getResidentSetSize()), p.getName())); + } + } + + private static void printServices(OperatingSystem os) { + oshi.add("Services: "); + oshi.add(" PID State Name"); + // DO 5 each of running and stopped + int i = 0; + for (OSService s : os.getServices()) { + if (s.getState().equals(OSService.State.RUNNING) && i++ < 5) { + oshi.add(String.format(" %5d %7s %s", s.getProcessID(), s.getState(), s.getName())); + } + } + i = 0; + for (OSService s : os.getServices()) { + if (s.getState().equals(OSService.State.STOPPED) && i++ < 5) { + oshi.add(String.format(" %5d %7s %s", s.getProcessID(), s.getState(), s.getName())); + } + } + } + + private static void printSensors(Sensors sensors) { + oshi.add("Sensors: " + sensors.toString()); + } + + private static void printPowerSources(PowerSource[] powerSources) { + StringBuilder sb = new StringBuilder("Power Sources: "); + if (powerSources.length == 0) { + sb.append("Unknown"); + } + for (PowerSource powerSource : powerSources) { + sb.append("\n ").append(powerSource.toString()); + } + oshi.add(sb.toString()); + } + + private static void printDisks(HWDiskStore[] diskStores) { + oshi.add("Disks:"); + for (HWDiskStore disk : diskStores) { + oshi.add(" " + disk.toString()); + + HWPartition[] partitions = disk.getPartitions(); + for (HWPartition part : partitions) { + oshi.add(" |-- " + part.toString()); + } + } + } + + private static void printFileSystem(FileSystem fileSystem) { + oshi.add("File System:"); + + oshi.add(String.format(" File Descriptors: %d/%d", fileSystem.getOpenFileDescriptors(), + fileSystem.getMaxFileDescriptors())); + + OSFileStore[] fsArray = fileSystem.getFileStores(); + for (OSFileStore fs : fsArray) { + long usable = fs.getUsableSpace(); + long total = fs.getTotalSpace(); + oshi.add(String.format( + " %s (%s) [%s] %s of %s free (%.1f%%), %s of %s files free (%.1f%%) is %s " + + (fs.getLogicalVolume() != null && fs.getLogicalVolume().length() > 0 ? "[%s]" : "%s") + + " and is mounted at %s", + fs.getName(), fs.getDescription().isEmpty() ? "file system" : fs.getDescription(), fs.getType(), + FormatUtil.formatBytes(usable), FormatUtil.formatBytes(fs.getTotalSpace()), 100d * usable / total, + FormatUtil.formatValue(fs.getFreeInodes(), ""), FormatUtil.formatValue(fs.getTotalInodes(), ""), + 100d * fs.getFreeInodes() / fs.getTotalInodes(), fs.getVolume(), fs.getLogicalVolume(), + fs.getMount())); + } + } + + private static void printNetworkInterfaces(NetworkIF[] networkIFs) { + StringBuilder sb = new StringBuilder("Network Interfaces:"); + if (networkIFs.length == 0) { + sb.append(" Unknown"); + } + for (NetworkIF net : networkIFs) { + sb.append("\n ").append(net.toString()); + } + oshi.add(sb.toString()); + } + + private static void printNetworkParameters(NetworkParams networkParams) { + oshi.add("Network parameters:\n " + networkParams.toString()); + } + + private static void printDisplays(Display[] displays) { + oshi.add("Displays:"); + int i = 0; + for (Display display : displays) { + oshi.add(" Display " + i + ":"); + oshi.add(String.valueOf(display)); + i++; + } + } + + private static void printUsbDevices(UsbDevice[] usbDevices) { + oshi.add("USB Devices:"); + for (UsbDevice usbDevice : usbDevices) { + oshi.add(String.valueOf(usbDevice)); + } + } + + private static void printSoundCards(SoundCard[] cards) { + oshi.add("Sound Cards:"); + for (SoundCard card : cards) { + oshi.add(" " + String.valueOf(card)); + } + } + +} diff --git a/codes/java-ecology/java-ecology-others/java-ecology-mvel/pom.xml b/codes/java-ecology/java-ecology-others/java-ecology-mvel/pom.xml new file mode 100644 index 00000000..a5b6fc49 --- /dev/null +++ b/codes/java-ecology/java-ecology-others/java-ecology-mvel/pom.xml @@ -0,0 +1,51 @@ + + + 4.0.0 + + + io.github.dunwu + dunwu-parent + 0.5.1 + + + io.github.dunwu.javatech + java-ecology-mvel + 1.0.0 + jar + java-ecology-mvel + + + UTF-8 + 1.8 + ${java.version} + ${java.version} + + + + + org.mvel + mvel2 + 2.4.2.Final + + + com.alibaba + fastjson + + + commons-io + commons-io + + + ch.qos.logback + logback-classic + + + junit + junit + test + + + + diff --git a/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/BasicRule.java b/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/BasicRule.java new file mode 100644 index 00000000..be9309dd --- /dev/null +++ b/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/BasicRule.java @@ -0,0 +1,128 @@ +package io.github.dunwu.javatech.mvel; + +public class BasicRule implements Rule, Comparable { + + protected String name; + + private String description; + + private int priority; + + private String condition; + + private String action; + + public BasicRule() { + } + + @Override + public int hashCode() { + int result = name.hashCode(); + result = 31 * result + (description != null ? description.hashCode() : 0); + result = 31 * result + priority; + return result; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + BasicRule basicRule = (BasicRule) o; + + if (priority != basicRule.priority) { + return false; + } + if (!name.equals(basicRule.name)) { + return false; + } + return !(description != null ? !description.equals(basicRule.description) : basicRule.description != null); + } + + @Override + public String toString() { + return name; + } + + @Override + public int compareTo(Rule rule) { + if (priority < rule.getPriority()) { + return -1; + } else if (priority > rule.getPriority()) { + return 1; + } else { + return getName().compareTo(rule.getName()); + } + } + + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + @Override + public int getPriority() { + return priority; + } + + public void setPriority(int priority) { + this.priority = priority; + } + + @Override + public String getCondition() { + return condition; + } + + public void setCondition(String condition) { + this.condition = condition; + } + + @Override + public String getAction() { + return action; + } + + @Override + public boolean validate() { + if (condition == null || condition.length() == 0) { + throw new IllegalArgumentException("The rule condition must not be null or empty"); + } + if (action == null || action.length() == 0) { + throw new IllegalArgumentException("The rule action must not be null or empty"); + } + return true; + } + + @Override + public boolean evaluate(RuleContext ruleContext) { + return false; + } + + @Override + public void execute(RuleContext ruleContext) { + // do nothing + } + + public void setAction(String action) { + this.action = action; + } + +} diff --git a/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/DefaultRuleEngine.java b/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/DefaultRuleEngine.java new file mode 100644 index 00000000..ae0c92ac --- /dev/null +++ b/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/DefaultRuleEngine.java @@ -0,0 +1,163 @@ +package io.github.dunwu.javatech.mvel; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; + +public class DefaultRuleEngine implements RuleEngine { + + protected Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** + * The rules set. + */ + protected Set rules; + + /** + * The engine parameters + */ + protected RuleEngineParams params; + + /** + * The rule fact + */ + protected RuleContext fact; + + /** + * ruleSet Map + */ + private Map> ruleSetMap = new ConcurrentHashMap<>(); + + public DefaultRuleEngine(RuleEngineParams params) { + this.params = params; + this.rules = new TreeSet<>(); + if (params.isSilentMode()) { + // cancle log + } + } + + @Override + public RuleEngineParams getParams() { + return params; + } + + @Override + public void registerRule(Rule rule) { + // 检查规则 + if (rule.validate()) { + rules.add(rule); + } + } + + @Override + public void registerRule(MvelRuleSet ruleSet) { + ruleSet.getRules().forEach(rule -> registerRule(rule)); + logRegisteredRules(); + } + + private void logRegisteredRules() { + logger.info("Registered rules:"); + for (Rule rule : rules) { + logger.info("Rule { name = {}, description = {}, priority = {}}", rule.getName(), rule.getDescription(), + rule.getPriority()); + } + } + + @Override + public void unregisterRule(Rule rule) { + rules.remove(rule); + } + + @Override + public void clearRules() { + ruleSetMap.clear(); + } + + @Override + public Set getRules() { + return rules; + } + + @Override + public Map checkRules() { + logger.info("Checking rules"); + sortRules(); + Map result = new HashMap<>(); + for (Rule rule : rules) { + result.put(rule, rule.evaluate(fact)); + } + return result; + } + + @Override + public void launch(RuleContext fact) { + if (rules.isEmpty()) { + logger.warn("No rules registered! Nothing to apply"); + return; + } + + logEngineParams(); + sortRules(); + applyRules(fact); + } + + private void logEngineParams() { + logger.info("----- Params -----"); + logger.info("Engine name: {}", params.getName()); + logger.info("Rule priority threshold: {}", params.getPriorityThreshold()); + logger.info("Skip on first applied rule: {}", params.isSkipOnFirstAppliedRule()); + logger.info("Skip on first unapplied rule: {}", params.isSkipOnFirstUnAppliedRule()); + logger.info("Skip on first failed rule: {}", params.isSkipOnFirstFailedRule()); + } + + private void applyRules(RuleContext fact) { + logger.info("Rules evaluation started"); + + for (Rule rule : rules) { + final String name = rule.getName(); + final int priority = rule.getPriority(); + + if (priority > params.getPriorityThreshold()) { + logger.info( + "Rule priority threshold ({}) exceeded at rule {} with priority={}, next rules will be skipped", + new Object[] { params.getPriorityThreshold(), name, priority }); + break; + } + + if (rule.evaluate(fact)) { + logger.info("Rule [{}] triggered", name); + try { + rule.execute(fact); + logger.info("Rule {} performed successfully", name); + if (params.isSkipOnFirstAppliedRule()) { + logger.info("Next rules will be skipped since parameter skipOnFirstAppliedRule is set"); + break; + } + if (params.isSkipOnFirstUnAppliedRule()) { + logger.info("Next rules will be skipped since parameter skipOnFirstUnAppliedRule is set"); + break; + } + } catch (Exception exception) { + logger.error("Rule [{}] performed with error {}", name, exception); + + if (params.isSkipOnFirstFailedRule()) { + logger.info("Next rules will be skipped since parameter skipOnFirstFailedRule is set"); + break; + } + } + } else { + logger.info("Rule [{}] has been evaluated to false, it has not been executed", name); + } + } + } + + private void sortRules() { + rules = new TreeSet<>(rules); + } + +} diff --git a/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/MvelRule.java b/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/MvelRule.java new file mode 100644 index 00000000..dd2b0d36 --- /dev/null +++ b/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/MvelRule.java @@ -0,0 +1,34 @@ +package io.github.dunwu.javatech.mvel; + +import org.mvel2.MVEL; + +import java.io.Serializable; + +public class MvelRule extends BasicRule { + + /** + * 判断条件是否匹配 + */ + @Override + public boolean evaluate(RuleContext ruleContext) { + try { + return (Boolean) MVEL.eval(getCondition(), ruleContext); + } catch (Exception e) { + throw new RuntimeException(String.format("条件[%s]匹配发生异常:", getCondition()), e); + } + } + + /** + * 执行条件匹配后的操作 + */ + @Override + public void execute(RuleContext ruleContext) { + try { + Serializable exp = MVEL.compileExpression(getAction(), ruleContext); + MVEL.executeExpression(exp, ruleContext); + } catch (Exception e) { + throw new RuntimeException(String.format("后续操作[%s]执行发生异常:", getAction()), e); + } + } + +} diff --git a/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/MvelRuleSet.java b/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/MvelRuleSet.java new file mode 100644 index 00000000..3bedee75 --- /dev/null +++ b/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/MvelRuleSet.java @@ -0,0 +1,34 @@ +package io.github.dunwu.javatech.mvel; + +import java.util.Set; +import java.util.TreeSet; + +public class MvelRuleSet { + + private String name; + + private TreeSet rules; + + public String getName() { + return name; + } + + public void setName(String name) { + if (name == null || name.length() == 0) { + name = RuleConstant.DEFAULT_RULE_NAME; + } + this.name = name; + } + + public Set getRules() { + if (rules == null) { + rules = new TreeSet<>(); + } + return rules; + } + + public void setRules(TreeSet rules) { + this.rules = rules; + } + +} diff --git a/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/Rule.java b/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/Rule.java new file mode 100644 index 00000000..298ff5f1 --- /dev/null +++ b/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/Rule.java @@ -0,0 +1,61 @@ +package io.github.dunwu.javatech.mvel; + +public interface Rule { + + /** + * Getter for rule name. + * + * @return the rule name + */ + String getName(); + + /** + * Getter for rule description. + * + * @return rule description + */ + String getDescription(); + + /** + * Getter for rule priority. + * + * @return rule priority + */ + int getPriority(); + + /** + * Getter for the rule condition + * + * @return rule condition + */ + String getCondition(); + + /** + * Getter for the rule action + * + * @return rule action + */ + String getAction(); + + /** + * validate + * + * @return boolean + */ + boolean validate(); + + /** + * Rule conditions abstraction : this method encapsulates the rule's conditions. + * + * @return true if the rule should be applied, false else + */ + boolean evaluate(RuleContext ruleContext); + + /** + * Rule actions abstraction : this method encapsulates the rule's actions. + * + * @throws Exception thrown if an exception occurs during actions performing + */ + void execute(RuleContext ruleContext) throws Exception; + +} diff --git a/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/RuleConstant.java b/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/RuleConstant.java new file mode 100644 index 00000000..bff0394f --- /dev/null +++ b/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/RuleConstant.java @@ -0,0 +1,33 @@ +package io.github.dunwu.javatech.mvel; + +/** + * 规则常量 + */ +public final class RuleConstant { + + /** + * Default rule name. + */ + public static final String DEFAULT_RULE_NAME = "rule"; + + /** + * Default engine name. + */ + public static final String DEFAULT_ENGINE_NAME = "engine"; + + /** + * Default rule description. + */ + public static final String DEFAULT_RULE_DESCRIPTION = "description"; + + /** + * Default rule priority. + */ + public static final int DEFAULT_RULE_PRIORITY = Integer.MAX_VALUE - 1; + + /** + * Default rule priority threshold. + */ + public static final int DEFAULT_RULE_PRIORITY_THRESHOLD = Integer.MAX_VALUE; + +} diff --git a/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/RuleContext.java b/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/RuleContext.java new file mode 100644 index 00000000..26733893 --- /dev/null +++ b/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/RuleContext.java @@ -0,0 +1,8 @@ +package io.github.dunwu.javatech.mvel; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class RuleContext extends ConcurrentHashMap implements Map { + +} diff --git a/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/RuleEngine.java b/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/RuleEngine.java new file mode 100644 index 00000000..d7c57beb --- /dev/null +++ b/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/RuleEngine.java @@ -0,0 +1,54 @@ +package io.github.dunwu.javatech.mvel; + +import java.util.Map; +import java.util.Set; + +public interface RuleEngine { + + /** + * 规则引擎 设置参数 + * + * @return The rules engine parameters + */ + RuleEngineParams getParams(); + + /** + * 注册rule + */ + void registerRule(Rule rule); + + /** + * 注册ruleSet + */ + void registerRule(MvelRuleSet ruleSet); + + /** + * 取消注册rule + */ + void unregisterRule(Rule rule); + + /** + * 清空规则列表 + */ + void clearRules(); + + /** + * Return the set of registered rules. + * + * @return the set of registered rules + */ + Set getRules(); + + /** + * Check rules without firing them. + * + * @return a map with the result of evaluation of each rule + */ + Map checkRules(); + + /** + * Launch all registered rules. + */ + void launch(RuleContext ruleContext); + +} diff --git a/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/RuleEngineBuilder.java b/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/RuleEngineBuilder.java new file mode 100644 index 00000000..fe737205 --- /dev/null +++ b/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/RuleEngineBuilder.java @@ -0,0 +1,45 @@ +package io.github.dunwu.javatech.mvel; + +public class RuleEngineBuilder { + + private RuleEngineParams params; + + private RuleEngineBuilder() { + params = new RuleEngineParams(RuleConstant.DEFAULT_ENGINE_NAME, false, false, false, + RuleConstant.DEFAULT_RULE_PRIORITY_THRESHOLD, false); + } + + public static RuleEngineBuilder newRuleEngine() { + return new RuleEngineBuilder(); + } + + public RuleEngine build() { + return new DefaultRuleEngine(params); + } + + public RuleEngineBuilder named(final String name) { + params.setName(name); + return this; + } + + public RuleEngineBuilder withSkipOnFirstAppliedRule(final boolean skipOnFirstAppliedRule) { + params.setSkipOnFirstAppliedRule(skipOnFirstAppliedRule); + return this; + } + + public RuleEngineBuilder withSkipOnFirstFailedRule(final boolean skipOnFirstFailedRule) { + params.setSkipOnFirstFailedRule(skipOnFirstFailedRule); + return this; + } + + public RuleEngineBuilder withRulePriorityThreshold(final int priorityThreshold) { + params.setPriorityThreshold(priorityThreshold); + return this; + } + + public RuleEngineBuilder withSilentMode(final boolean silentMode) { + params.setSilentMode(silentMode); + return this; + } + +} diff --git a/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/RuleEngineParams.java b/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/RuleEngineParams.java new file mode 100644 index 00000000..c759da00 --- /dev/null +++ b/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/java/io/github/dunwu/javatech/mvel/RuleEngineParams.java @@ -0,0 +1,93 @@ +package io.github.dunwu.javatech.mvel; + +public class RuleEngineParams { + + /** + * The engine name. + */ + protected String name; + + /** + * 满足任意条件(即遇到第一个匹配规则时停止) Parameter to skip next applicable rules when a rule is applied. + */ + private boolean skipOnFirstAppliedRule; + + /** + * 满足所有条件(即遇到第第一个未匹配规则时停止) Parameter to skip next applicable rules when a rule has failed. + */ + private boolean skipOnFirstUnAppliedRule; + + /** + * Parameter to skip next applicable rules when a rule has failed. + */ + private boolean skipOnFirstFailedRule; + + /** + * Parameter to skip next rules if priority exceeds a user defined threshold. + */ + private int priorityThreshold; + + /** + * Parameter to mute loggers. + */ + private boolean silentMode; + + public RuleEngineParams(String name, boolean skipOnFirstAppliedRule, boolean skipOnFirstUnAppliedRule, + boolean skipOnFirstFailedRule, int priorityThreshold, boolean silentMode) { + this.name = name; + this.skipOnFirstAppliedRule = skipOnFirstAppliedRule; + this.skipOnFirstUnAppliedRule = skipOnFirstUnAppliedRule; + this.skipOnFirstFailedRule = skipOnFirstFailedRule; + this.priorityThreshold = priorityThreshold; + this.silentMode = silentMode; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getPriorityThreshold() { + return priorityThreshold; + } + + public void setPriorityThreshold(int priorityThreshold) { + this.priorityThreshold = priorityThreshold; + } + + public boolean isSilentMode() { + return silentMode; + } + + public void setSilentMode(boolean silentMode) { + this.silentMode = silentMode; + } + + public boolean isSkipOnFirstAppliedRule() { + return skipOnFirstAppliedRule; + } + + public void setSkipOnFirstAppliedRule(boolean skipOnFirstAppliedRule) { + this.skipOnFirstAppliedRule = skipOnFirstAppliedRule; + } + + public boolean isSkipOnFirstFailedRule() { + return skipOnFirstFailedRule; + } + + public void setSkipOnFirstFailedRule(boolean skipOnFirstFailedRule) { + this.skipOnFirstFailedRule = skipOnFirstFailedRule; + } + + public boolean isSkipOnFirstUnAppliedRule() { + return skipOnFirstUnAppliedRule; + } + + public void setSkipOnFirstUnAppliedRule(boolean skipOnFirstUnAppliedRule) { + this.skipOnFirstUnAppliedRule = skipOnFirstUnAppliedRule; + } + +} diff --git a/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/resources/logback.xml b/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/resources/logback.xml new file mode 100644 index 00000000..2d74f309 --- /dev/null +++ b/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/main/resources/logback.xml @@ -0,0 +1,143 @@ + + + + + + + + + + + 10 + 100 + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [javalib-mvel] [%thread] [%p] %c{36}#%M - %m%n + + + + + + + ${LOG_PATH}/logs/${FILE_NAME}-error.%d{yyyy-MM-dd}.log + 30 + + + + + 10MB + + + + ERROR + ACCEPT + DENY + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [javalib-mvel] [%thread] [%p] %c{36}#%M - %m%n + + + + + + ${LOG_PATH}/logs/${FILE_NAME}-warn.%d{yyyy-MM-dd}.log + 30 + + + + + 10MB + + + + WARN + ACCEPT + DENY + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [javalib-mvel] [%thread] [%p] %c{36}#%M - %m%n + + + + + + ${LOG_PATH}/logs/${FILE_NAME}-info.%d{yyyy-MM-dd}.log + 30 + + + + + 10MB + + + + INFO + ACCEPT + DENY + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [javalib-mvel] [%thread] [%p] %c{36}#%M - %m%n + + + + + + ${LOG_PATH}/logs/${FILE_NAME}-debug.%d{yyyy-MM-dd}.log + 30 + + + + + 10MB + + + + DEBUG + ACCEPT + DENY + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [javalib-mvel] [%thread] [%p] %c{36}#%M - %m%n + + + + + + ${LOG_PATH}/logs/${FILE_NAME}-trace.%d{yyyy-MM-dd}.log + 30 + + + + + 10MB + + + + TRACE + ACCEPT + DENY + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [javalib-mvel] [%thread] [%p] %c{36}#%M - %m%n + + + + + + + + + + + + + + diff --git a/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/test/java/io/github/dunwu/javatech/mvel/ClassTests.java b/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/test/java/io/github/dunwu/javatech/mvel/ClassTests.java new file mode 100644 index 00000000..a03dd72d --- /dev/null +++ b/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/test/java/io/github/dunwu/javatech/mvel/ClassTests.java @@ -0,0 +1,44 @@ +package io.github.dunwu.javatech.mvel; + +import org.junit.Assert; +import org.junit.Test; +import org.mvel2.MVEL; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Mike Brock + */ +public class ClassTests { + + private final String dir = "src/test/java/" + getClass().getPackage().getName().replaceAll("\\.", "/"); + + @Test + public void testScript() throws IOException { + final Object o = MVEL.evalFile(new File(dir + "/demo.mvel"), new HashMap()); + } + + @Test + public void testEval() { + String expression = "foobar > 99"; + Map vars = new HashMap(); + vars.put("foobar", new Integer(100)); + Boolean result = (Boolean) MVEL.eval(expression, vars); + Assert.assertEquals(true, result); + } + + @Test + public void testCompileExpression() { + String expression = "foobar > 99"; + Serializable compiled = MVEL.compileExpression(expression); + Map vars = new HashMap(); + vars.put("foobar", new Integer(100)); + Boolean result = (Boolean) MVEL.executeExpression(compiled, vars); + Assert.assertEquals(true, result); + } + +} diff --git a/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/test/java/io/github/dunwu/javatech/mvel/SalaryRuleTest.java b/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/test/java/io/github/dunwu/javatech/mvel/SalaryRuleTest.java new file mode 100644 index 00000000..58a2a921 --- /dev/null +++ b/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/test/java/io/github/dunwu/javatech/mvel/SalaryRuleTest.java @@ -0,0 +1,79 @@ +package io.github.dunwu.javatech.mvel; + +import com.alibaba.fastjson.JSON; +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; + +import static org.junit.Assert.assertEquals; + +public class SalaryRuleTest { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private final String SALARY_RULE_PATH = System.getProperty("user.dir") + "\\src\\test\\resources\\SalaryRule.json"; + + private RuleEngine ruleEngine; + + @Before + public void before() throws IOException { + logger.info("Begin"); + RuleEngineParams params = new RuleEngineParams("SalaryEngine", true, false, true, + RuleConstant.DEFAULT_RULE_PRIORITY_THRESHOLD, false); + ruleEngine = new DefaultRuleEngine(params); + + String json = FileUtils.readFileToString(new File(SALARY_RULE_PATH), "utf-8"); + MvelRuleSet ruleSet = JSON.parseObject(json, MvelRuleSet.class); + ruleEngine.registerRule(ruleSet); + } + + @Test + public void test_salaryRule() { + RuleContext ruleContext = new RuleContext(); + ruleContext.put("fee", 0.0); + + ruleContext.put("salary", 1000); + ruleEngine.launch(ruleContext); + assertEquals(0, ruleContext.get("fee")); + + ruleContext.put("salary", 4000); + ruleEngine.launch(ruleContext); + assertEquals(15.0, ruleContext.get("fee")); + + ruleContext.put("salary", 7000); + ruleEngine.launch(ruleContext); + assertEquals(245.0, ruleContext.get("fee")); + + ruleContext.put("salary", 10000); + ruleEngine.launch(ruleContext); + assertEquals(745.0, ruleContext.get("fee")); + + ruleContext.put("salary", 18000); + ruleEngine.launch(ruleContext); + assertEquals(2620.0, ruleContext.get("fee")); + + ruleContext.put("salary", 40005); + ruleEngine.launch(ruleContext); + assertEquals(8196.50, ruleContext.get("fee")); + + ruleContext.put("salary", 70005); + ruleEngine.launch(ruleContext); + assertEquals(17771.75, ruleContext.get("fee")); + + ruleContext.put("salary", 100000); + ruleEngine.launch(ruleContext); + assertEquals(29920.00, ruleContext.get("fee")); + } + + @After + public void after() { + logger.info("End"); + } + +} diff --git a/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/test/java/io/github/dunwu/javatech/mvel/demo.mvel b/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/test/java/io/github/dunwu/javatech/mvel/demo.mvel new file mode 100644 index 00000000..ec1733ee --- /dev/null +++ b/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/test/java/io/github/dunwu/javatech/mvel/demo.mvel @@ -0,0 +1,56 @@ +/** + * This is an MVEL script. + */ + +def Person { + String name; + int age; + String color; + + def speak() { + System.out.println("My name is " + name + " and I am " + age + " years old. I like the color " + color + "."); + } + + def makeUpperCase() { + name = name.toUpperCase(); + } + + def sayName(amount) { + for (int i = 0; i < amount; i++) { + System.out.println((i + 1) + ". " + name); + } + } +} + +tm = System.currentTimeMillis; + +def print(str) { + System.out.println(str); +} + +var p = new Person(); + +p.{ + name = "Bob", + age = 5, + color = "blue" +}; + +p.speak(); +p.makeUpperCase(); +p.speak(); + +print("\n---------\n"); + +p.sayName(10); + +for (a : "gorkem") { + print("->" + a); +} + +var list = ["cow", "pig", "lion"]; +var blah = ($.toUpperCase() in list if $.length() == 3); + +print(blah); +print(tm()); + diff --git a/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/test/resources/SalaryRule.json b/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/test/resources/SalaryRule.json new file mode 100644 index 00000000..6a43150e --- /dev/null +++ b/codes/java-ecology/java-ecology-others/java-ecology-mvel/src/test/resources/SalaryRule.json @@ -0,0 +1,51 @@ +{ + "name": "salaryRule", + "rules": [ + { + "name": "step1", + "action": "fee=0", + "condition": "salary<=3500" + }, + { + "name": "step2", + "action": "fee=(salary-3500)*0.03", + "condition": "salary>3500 && salary<=5000" + }, + { + "name": "step3", + "action": "fee=(salary-3500)*0.1-105", + "condition": "salary>5000 && salary<=8000", + "priority": 3 + }, + { + "name": "step4", + "action": "fee=(salary-3500)*0.2-555", + "condition": "salary>8000 && salary<=12500", + "priority": 4 + }, + { + "name": "step5", + "action": "fee=(salary-3500)*0.25-1005", + "condition": "salary>12500 && salary<=38500", + "priority": 5 + }, + { + "name": "step6", + "action": "fee=(salary-3500)*0.3-2755", + "condition": "salary>38500 && salary<=58500", + "priority": 6 + }, + { + "name": "step7", + "action": "fee=(salary-3500)*0.35-5505", + "condition": "salary>58500 && salary<=83500", + "priority": 7 + }, + { + "name": "step8", + "action": "fee=(salary-3500)*0.45-13505", + "condition": "salary>83500", + "priority": 8 + } + ] +} diff --git a/codes/java-ecology/java-ecology-others/pom.xml b/codes/java-ecology/java-ecology-others/pom.xml new file mode 100644 index 00000000..19accc42 --- /dev/null +++ b/codes/java-ecology/java-ecology-others/pom.xml @@ -0,0 +1,16 @@ + + + 4.0.0 + + io.github.dunwu.javatech + java-ecology-others + 1.0.0 + pom + + + java-ecology-cli + java-ecology-mvel + + diff --git a/codes/java-ecology/java-ecology-test/pom.xml b/codes/java-ecology/java-ecology-test/pom.xml new file mode 100644 index 00000000..00fcef81 --- /dev/null +++ b/codes/java-ecology/java-ecology-test/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + + + io.github.dunwu + dunwu-parent + 0.5.1 + + + io.github.dunwu.javatech + java-ecology-test + 1.0.0 + jar + java-ecology-test + + + UTF-8 + 1.8 + ${java.version} + ${java.version} + 1.22 + + + + + ch.qos.logback + logback-classic + + + + + org.junit.jupiter + junit-jupiter + test + + + + + junit + junit + test + + + + + org.assertj + assertj-core + test + + + org.mockito + mockito-core + test + + + + org.openjdk.jmh + jmh-core + ${jmh.version} + + + org.openjdk.jmh + jmh-generator-annprocess + ${jmh.version} + provided + + + + diff --git a/codes/java-ecology/java-ecology-test/src/main/java/io/github/dunwu/javatech/jmh/QuickStart.java b/codes/java-ecology/java-ecology-test/src/main/java/io/github/dunwu/javatech/jmh/QuickStart.java new file mode 100644 index 00000000..7b4a63ac --- /dev/null +++ b/codes/java-ecology/java-ecology-test/src/main/java/io/github/dunwu/javatech/jmh/QuickStart.java @@ -0,0 +1,43 @@ +package io.github.dunwu.javatech.jmh; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +import java.util.concurrent.TimeUnit; + +@BenchmarkMode(Mode.Throughput) +@Warmup(iterations = 3) +@Measurement(iterations = 10, time = 5, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +public class QuickStart { + + public static void main(String[] args) throws RunnerException { + Options opt = new OptionsBuilder() + .include(QuickStart.class.getSimpleName()) + .forks(1) + .build(); + new Runner(opt).run(); + } + + @Benchmark + public String testStringAdd() { + String a = ""; + for (int i = 0; i < 10; i++) { + a += i; + } + return a; + } + + @Benchmark + public String testStringBuilderAdd() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 10; i++) { + sb.append(i); + } + return sb.toString(); + } + +} diff --git a/codes/java-ecology/java-ecology-test/src/main/java/io/github/dunwu/javatech/jmh/StringBuilderBenchmark.java b/codes/java-ecology/java-ecology-test/src/main/java/io/github/dunwu/javatech/jmh/StringBuilderBenchmark.java new file mode 100644 index 00000000..91198d83 --- /dev/null +++ b/codes/java-ecology/java-ecology-test/src/main/java/io/github/dunwu/javatech/jmh/StringBuilderBenchmark.java @@ -0,0 +1,45 @@ +package io.github.dunwu.javatech.jmh; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +import java.util.concurrent.TimeUnit; + +@BenchmarkMode(Mode.Throughput) +@Warmup(iterations = 3) +@Measurement(iterations = 10, time = 5, timeUnit = TimeUnit.SECONDS) +@Threads(8) +@Fork(2) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +public class StringBuilderBenchmark { + + public static void main(String[] args) throws RunnerException { + Options options = new OptionsBuilder() + .include(StringBuilderBenchmark.class.getSimpleName()) + .output("d:/Benchmark.log") + .build(); + new Runner(options).run(); + } + + @Benchmark + public void testStringAdd() { + String a = ""; + for (int i = 0; i < 10; i++) { + a += i; + } + // System.out.println(a); + } + + @Benchmark + public void testStringBuilderAdd() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 10; i++) { + sb.append(i); + } + // System.out.println(sb.toString()); + } + +} diff --git a/codes/java-ecology/java-ecology-test/src/main/java/io/github/dunwu/javatech/lombok/Calculator.java b/codes/java-ecology/java-ecology-test/src/main/java/io/github/dunwu/javatech/lombok/Calculator.java new file mode 100644 index 00000000..bc042ccc --- /dev/null +++ b/codes/java-ecology/java-ecology-test/src/main/java/io/github/dunwu/javatech/lombok/Calculator.java @@ -0,0 +1,9 @@ +package io.github.dunwu.javatech.lombok; + +public class Calculator { + + public int add(int a, int b) { + return a + b; + } + +} diff --git a/codes/java-ecology/java-ecology-test/src/main/java/io/github/dunwu/javatech/lombok/Person.java b/codes/java-ecology/java-ecology-test/src/main/java/io/github/dunwu/javatech/lombok/Person.java new file mode 100644 index 00000000..c44a29de --- /dev/null +++ b/codes/java-ecology/java-ecology-test/src/main/java/io/github/dunwu/javatech/lombok/Person.java @@ -0,0 +1,34 @@ +package io.github.dunwu.javatech.lombok; + +/** + * @author Zhang Peng + * @since 2018-11-29 + */ +public class Person { + + private String firstName; + + private String lastName; + + public Person(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + +} diff --git a/codes/java-ecology/java-ecology-test/src/main/resources/logback.xml b/codes/java-ecology/java-ecology-test/src/main/resources/logback.xml new file mode 100644 index 00000000..240ee4c6 --- /dev/null +++ b/codes/java-ecology/java-ecology-test/src/main/resources/logback.xml @@ -0,0 +1,15 @@ + + + + + %d{HH:mm:ss.SSS} [%boldYellow(%thread)] [%highlight(%-5level)] %boldGreen(%c{36}.%M) - %boldBlue(%m%n) + + + + + + + + + + diff --git a/codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit4/JUnitTest.java b/codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit4/JUnitTest.java new file mode 100644 index 00000000..7f914913 --- /dev/null +++ b/codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit4/JUnitTest.java @@ -0,0 +1,71 @@ +package io.github.dunwu.javatech.junit4; + +import org.junit.*; +import org.junit.runners.MethodSorters; + +/** + * JUnit 使用示例。 请注意各个方法的执行顺序。 + * + * @author Zhang Peng + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class JUnitTest { + + /** + * @BeforeClass 注解指出这是附着在静态方法必须执行一次并在类的所有测试之前。 一般用于共享配置方法(如连接到数据库)。 + */ + @BeforeClass + public static void beforeClass() { + System.out.println("call @BeforeClass"); + } + + /** + * 当需要执行所有的测试在JUnit测试用例类后执行,@AfterClass 注解可以使用以清理建立方法,(从数据库如断开连接)。 注意:附有此批注(类似于BeforeClass)的方法必须定义为静态。 + */ + @AfterClass + public static void afterClass() { + System.out.println("call @AfterClass"); + } + + @Test + public void testA() { + System.out.println("call @Test testA"); + int sum = 1 + 2 + 3; + Assert.assertEquals(6, sum); + } + + @Test + public void testC() { + System.out.println("call @Test testC"); + } + + @Test + public void testB() { + System.out.println("call @Test testB"); + } + + /** + * @Before 注解修饰的方法必须在类中的每个测试之前执行,以便执行测试某些必要的先决条件。 + */ + @Before + public void before() { + System.out.println("call @Before"); + } + + /** + * @After 注解修饰的方法在执行每项测试后执行(如执行每一个测试后重置某些变量 , 删除临时变量等) + */ + @After + public void after() { + System.out.println("call @After"); + } + + /** + * 当想暂时禁用特定的测试执行可以使用忽略注释。每个被注解为 @Ignore 的方法将不被执行。 + */ + @Ignore + public void ignore() { + System.out.println("call @Ignore"); + } + +} diff --git a/codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit5/AssertionsTests.java b/codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit5/AssertionsTests.java new file mode 100644 index 00000000..3ad5d90a --- /dev/null +++ b/codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit5/AssertionsTests.java @@ -0,0 +1,151 @@ +package io.github.dunwu.javatech.junit5; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import static java.time.Duration.ofMillis; +import static java.time.Duration.ofMinutes; +import static org.junit.jupiter.api.Assertions.*; + +/** + * Junit5 断言示例 + * + * @author Zhang Peng + * @since 2018-11-29 + */ +@Disabled +class AssertionsTests { + + private static Person person; + + @BeforeAll + static void beforeAll() { + person = new Person("John", "Doe"); + } + + @Test + void dependentAssertions() { + // Within a code block, if an assertion fails the + // subsequent code in the same block will be skipped. + assertAll("properties", () -> { + String firstName = person.getFirstName(); + assertNotNull(firstName); + + // Executed only if the previous assertion is valid. + assertAll("first name", () -> assertTrue(firstName.startsWith("J")), + () -> assertTrue(firstName.endsWith("n"))); + }, () -> { + // Grouped assertion, so processed independently + // of results of first name assertions. + String lastName = person.getLastName(); + assertNotNull(lastName); + + // Executed only if the previous assertion is valid. + assertAll("last name", () -> assertTrue(lastName.startsWith("D")), + () -> assertTrue(lastName.endsWith("e"))); + }); + } + + @Test + void exceptionTesting() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> { + throw new IllegalArgumentException("a message"); + }); + assertEquals("a message", exception.getMessage()); + } + + @Test + void groupedAssertions() { + // In a grouped assertion all assertions are executed, and any + // failures will be reported together. + assertAll("person", () -> assertEquals("John", person.getFirstName()), + () -> assertEquals("Doe", person.getLastName())); + } + + @Test + void standardAssertions() { + assertEquals(2, 2); + assertEquals(4, 4, "The optional assertion message is now the last parameter."); + assertTrue('a' < 'b', () -> "Assertion messages can be lazily evaluated -- " + + "to avoid constructing complex messages unnecessarily."); + } + + @Test + void timeoutExceeded() { + // The following assertion fails with an error message similar to: + // execution exceeded timeout of 10 ms by 91 ms + assertTimeout(ofMillis(10), () -> { + // Simulate task that takes more than 10 ms. + Thread.sleep(100); + }); + } + + @Test + void timeoutExceededWithPreemptiveTermination() { + // The following assertion fails with an error message similar to: + // execution timed out after 10 ms + assertTimeoutPreemptively(ofMillis(10), () -> { + // Simulate task that takes more than 10 ms. + Thread.sleep(100); + }); + } + + @Test + void timeoutNotExceeded() { + // The following assertion succeeds. + assertTimeout(ofMinutes(2), () -> { + // Perform task that takes less than 2 minutes. + }); + } + + @Test + void timeoutNotExceededWithMethod() { + // The following assertion invokes a method reference and returns an object. + String actualGreeting = assertTimeout(ofMinutes(2), AssertionsTests::greeting); + assertEquals("Hello, World!", actualGreeting); + } + + private static String greeting() { + return "Hello, World!"; + } + + @Test + void timeoutNotExceededWithResult() { + // The following assertion succeeds, and returns the supplied object. + String actualResult = assertTimeout(ofMinutes(2), () -> { + return "a result"; + }); + assertEquals("a result", actualResult); + } + + static class Person { + + private String firstName; + + private String lastName; + + Person(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + String getFirstName() { + return firstName; + } + + void setFirstName(String firstName) { + this.firstName = firstName; + } + + String getLastName() { + return lastName; + } + + void setLastName(String lastName) { + this.lastName = lastName; + } + + } + +} diff --git a/codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit5/AssumptionsTests.java b/codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit5/AssumptionsTests.java new file mode 100644 index 00000000..023120c6 --- /dev/null +++ b/codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit5/AssumptionsTests.java @@ -0,0 +1,43 @@ +package io.github.dunwu.javatech.junit5; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assumptions.assumeTrue; +import static org.junit.jupiter.api.Assumptions.assumingThat; + +/** + * Junit5 断言示例 + * + * @author Zhang Peng + * @since 2018-11-29 + */ +@Disabled +class AssumptionsTests { + + @Test + void testInAllEnvironments() { + assumingThat("CI".equals(System.getenv("ENV")), () -> { + // perform these assertions only on the CI server + assertEquals(2, 2); + }); + + // perform these assertions in all environments + assertEquals("a string", "a string"); + } + + @Test + void testOnlyOnCiServer() { + assumeTrue("CI".equals(System.getenv("ENV"))); + // remainder of test + } + + @Test + void testOnlyOnDeveloperWorkstation() { + assumeTrue("DEV".equals(System.getenv("ENV")), + () -> "Aborting test: not on developer workstation"); + // remainder of test + } + +} diff --git a/codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit5/DisplayNameTests.java b/codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit5/DisplayNameTests.java new file mode 100644 index 00000000..d6308357 --- /dev/null +++ b/codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit5/DisplayNameTests.java @@ -0,0 +1,32 @@ +package io.github.dunwu.javatech.junit5; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Junit5 定制测试类和方法的显示名称 + * + * @author Zhang Peng + * @since 2018-11-29 + */ +@Disabled +@DisplayName("A special test case") +class DisplayNameTests { + + @Test + @DisplayName("😱") + void testWithDisplayNameContainingEmoji() { + } + + @Test + @DisplayName("Custom test name containing spaces") + void testWithDisplayNameContainingSpaces() { + } + + @Test + @DisplayName("╯°□°)╯") + void testWithDisplayNameContainingSpecialCharacters() { + } + +} diff --git a/codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit5/DynamicTests.java b/codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit5/DynamicTests.java new file mode 100644 index 00000000..ce447000 --- /dev/null +++ b/codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit5/DynamicTests.java @@ -0,0 +1,117 @@ +package io.github.dunwu.javatech.junit5; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DynamicNode; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.function.ThrowingConsumer; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; + +@Disabled +class DynamicTests { + + @TestFactory + DynamicTest[] dynamicTestsFromArray() { + return new DynamicTest[] { + dynamicTest("7th dynamic test", () -> assertTrue(true)), + dynamicTest("8th dynamic test", () -> assertEquals(4, 2 * 2)) }; + } + + @TestFactory + Collection dynamicTestsFromCollection() { + return Arrays.asList(dynamicTest("1st dynamic test", () -> assertTrue(true)), + dynamicTest("2nd dynamic test", () -> assertEquals(4, 2 * 2))); + } + + @TestFactory + Stream dynamicTestsFromIntStream() { + // Generates tests for the first 10 even integers. + return IntStream.iterate(0, n -> n + 2).limit(10) + .mapToObj(n -> dynamicTest("test" + n, () -> assertTrue(n % 2 == 0))); + } + + @TestFactory + Iterable dynamicTestsFromIterable() { + return Arrays.asList(dynamicTest("3rd dynamic test", () -> assertTrue(true)), + dynamicTest("4th dynamic test", () -> assertEquals(4, 2 * 2))); + } + + @TestFactory + Iterator dynamicTestsFromIterator() { + return Arrays + .asList(dynamicTest("5th dynamic test", () -> assertTrue(true)), + dynamicTest("6th dynamic test", () -> assertEquals(4, 2 * 2))) + .iterator(); + } + + @TestFactory + Stream dynamicTestsFromStream() { + return Stream.of("A", "B", "C").map(str -> dynamicTest("test" + str, () -> { + /* ... */ + })); + } + + @TestFactory + Stream dynamicTestsWithContainers() { + return Stream + .of("A", "B", "C").map( + input -> dynamicContainer("Container " + input, Stream + .of(dynamicTest("not null", () -> assertNotNull(input)), + dynamicContainer("properties", + Stream.of( + dynamicTest("length > 0", + () -> assertTrue(input + .length() > 0)), + dynamicTest("not empty", + () -> assertFalse(input + .isEmpty()))))))); + } + + // This will result in a JUnitException! + @TestFactory + List dynamicTestsWithInvalidReturnType() { + return Arrays.asList("Hello"); + } + + @TestFactory + Stream generateRandomNumberOfTests() { + + // Generates random positive integers between 0 and 100 until + // a number evenly divisible by 7 is encountered. + Iterator inputGenerator = new Iterator() { + + Random random = new Random(); + + int current; + + @Override + public boolean hasNext() { + current = random.nextInt(100); + return current % 7 != 0; + } + + @Override + public Integer next() { + return current; + } + }; + + // Generates display names like: input:5, input:37, input:85, etc. + Function displayNameGenerator = (input) -> "input:" + input; + + // Executes tests based on the current input value. + ThrowingConsumer testExecutor = (input) -> assertTrue(input % 7 != 0); + + // Returns a stream of dynamic tests. + return DynamicTest.stream(inputGenerator, displayNameGenerator, testExecutor); + } + +} diff --git a/codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit5/NestedTests.java b/codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit5/NestedTests.java new file mode 100644 index 00000000..d8da2a48 --- /dev/null +++ b/codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit5/NestedTests.java @@ -0,0 +1,84 @@ +package io.github.dunwu.javatech.junit5; + +import org.junit.jupiter.api.*; + +import java.util.EmptyStackException; +import java.util.Stack; + +import static org.junit.jupiter.api.Assertions.*; + +@Disabled +@DisplayName("A stack") +class NestedTests { + + Stack stack; + + @Test + @DisplayName("is instantiated with new Stack()") + void isInstantiatedWithNew() { + new Stack<>(); + } + + @Nested + @DisplayName("when new") + class WhenNew { + + @BeforeEach + void createNewStack() { + stack = new Stack<>(); + } + + @Test + @DisplayName("is empty") + void isEmpty() { + assertTrue(stack.isEmpty()); + } + + @Test + @DisplayName("throws EmptyStackException when peeked") + void throwsExceptionWhenPeeked() { + assertThrows(EmptyStackException.class, () -> stack.peek()); + } + + @Test + @DisplayName("throws EmptyStackException when popped") + void throwsExceptionWhenPopped() { + assertThrows(EmptyStackException.class, () -> stack.pop()); + } + + @Nested + @DisplayName("after pushing an element") + class AfterPushing { + + String anElement = "an element"; + + @Test + @DisplayName("it is no longer empty") + void isNotEmpty() { + assertFalse(stack.isEmpty()); + } + + @BeforeEach + void pushAnElement() { + stack.push(anElement); + } + + @Test + @DisplayName("returns the element when peeked but remains not empty") + void returnElementWhenPeeked() { + assertEquals(anElement, stack.peek()); + assertFalse(stack.isEmpty()); + } + + @Test + @DisplayName("returns the element when popped and is empty") + void returnElementWhenPopped() { + assertEquals(anElement, stack.pop()); + assertTrue(stack.isEmpty()); + } + + } + + } + +} diff --git a/codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit5/ParameterizedTests.java b/codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit5/ParameterizedTests.java new file mode 100644 index 00000000..43c2c278 --- /dev/null +++ b/codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit5/ParameterizedTests.java @@ -0,0 +1,32 @@ +package io.github.dunwu.javatech.junit5; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Zhang Peng + * @since 2018-11-29 + */ +@Disabled +class ParameterizedTests { + + @ParameterizedTest(name = "{0} + {1} = {2}") + @CsvSource({ "0, 1, 1", "1, 2, 3", "49, 51, 100", "1, 100, 101" }) + void add(int first, int second, int expectedResult) { + Calculator calculator = new Calculator(); + assertEquals(expectedResult, calculator.add(first, second), + () -> first + " + " + second + " should equal " + expectedResult); + } + + class Calculator { + + public int add(int a, int b) { + return a + b; + } + + } + +} diff --git a/codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit5/RepeatedTests.java b/codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit5/RepeatedTests.java new file mode 100644 index 00000000..d83fc813 --- /dev/null +++ b/codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit5/RepeatedTests.java @@ -0,0 +1,46 @@ +package io.github.dunwu.javatech.junit5; + +import org.junit.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@Disabled +class RepeatedTests { + + @BeforeEach + void beforeEach(TestInfo testInfo, RepetitionInfo repetitionInfo) { + int currentRepetition = repetitionInfo.getCurrentRepetition(); + int totalRepetitions = repetitionInfo.getTotalRepetitions(); + String methodName = testInfo.getTestMethod().get().getName(); + System.out.println(String.format("About to execute repetition %d of %d for %s", // + currentRepetition, totalRepetitions, methodName)); + } + + @RepeatedTest(value = 1, name = "{displayName} {currentRepetition}/{totalRepetitions}") + @DisplayName("Repeat!") + void customDisplayName(TestInfo testInfo) { + assertEquals(testInfo.getDisplayName(), "Repeat! 1/1"); + } + + @RepeatedTest(value = 1, name = RepeatedTest.LONG_DISPLAY_NAME) + @DisplayName("Details...") + void customDisplayNameWithLongPattern(TestInfo testInfo) { + assertEquals(testInfo.getDisplayName(), "Details... :: repetition 1 of 1"); + } + + @RepeatedTest(10) + void repeatedTest() { + // ... + } + + @RepeatedTest(value = 5, name = "Wiederholung {currentRepetition} von {totalRepetitions}") + void repeatedTestInGerman() { + // ... + } + + @RepeatedTest(5) + void repeatedTestWithRepetitionInfo(RepetitionInfo repetitionInfo) { + assertEquals(5, repetitionInfo.getTotalRepetitions()); + } + +} diff --git a/codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit5/StandardTests.java b/codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit5/StandardTests.java new file mode 100644 index 00000000..e342eea6 --- /dev/null +++ b/codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/junit5/StandardTests.java @@ -0,0 +1,52 @@ +package io.github.dunwu.javatech.junit5; + +import org.junit.jupiter.api.*; + +/** + * Junit5 标准测试 + * + * @author Zhang Peng + * @since 2018-11-29 + */ +@Disabled +class StandardTests { + + @AfterAll + static void afterAll() { + System.out.println("call afterAll()"); + } + + @BeforeAll + static void beforeAll() { + System.out.println("call beforeAll()"); + } + + @AfterEach + void afterEach() { + System.out.println("call afterEach()"); + } + + @BeforeEach + void beforeEach() { + System.out.println("call beforeEach()"); + } + + @Test + void failingTest() { + System.out.println("call failingTest()"); + // fail("a failing test"); + } + + @Test + @Disabled("for demonstration purposes") + void skippedTest() { + System.out.println("call skippedTest()"); + // not executed + } + + @Test + void succeedingTest() { + System.out.println("call succeedingTest()"); + } + +} diff --git a/codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/mockito/MockitoTest.java b/codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/mockito/MockitoTest.java new file mode 100644 index 00000000..6211d1a7 --- /dev/null +++ b/codes/java-ecology/java-ecology-test/src/test/java/io/github/dunwu/javatech/mockito/MockitoTest.java @@ -0,0 +1,225 @@ +package io.github.dunwu.javatech.mockito; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InOrder; + +import java.util.LinkedList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; + +class MockitoTest { + + // 模拟 LinkedList 的一个对象 + LinkedList mockedList = mock(LinkedList.class); + + @BeforeEach + void beforeEach() { + mockedList.clear(); + } + + @Test + void test() { + // using mock object - it does not throw any "unexpected interaction" exception + mockedList.add("one"); + // selective, explicit, highly readable verification + verify(mockedList).add("one"); + } + + /** + * 模拟对象 + */ + @Test + void test01() { + // 此时调用get方法,会返回null,因为还没有对方法调用的返回值做模拟 + System.out.println(mockedList.get(0)); + } + + /** + * 模拟方法调用的返回值 + */ + @Test + void test02() { + // 模拟获取第一个元素时,返回字符串first。给特定的方法调用返回固定值在官方说法中称为stub。 + when(mockedList.get(0)).thenReturn("first"); + // 此时打印输出first + System.out.println(mockedList.get(0)); + } + + /** + * 模拟方法调用抛出异常 + */ + @Test + void test03() { + // 模拟获取第二个元素时,抛出RuntimeException + when(mockedList.get(1)).thenThrow(new RuntimeException()); + try { + // 此时将会抛出RuntimeException + System.out.println(mockedList.get(1)); + } catch (RuntimeException e) { + System.err.println(e.getMessage()); + assertThat(e.getMessage()).isEqualTo(null); + } + } + + /** + * 模拟方法调用抛出异常2 + */ + @Test + void test04() { + doThrow(new RuntimeException("clear exception")).when(mockedList).clear(); + try { + mockedList.clear(); + } catch (RuntimeException e) { + System.err.println(e.getMessage()); + assertThat(e.getMessage()).contains("clear exception"); + } + } + + /** + * 模拟调用方法时的参数匹配 + */ + @Test + void test05() { + // anyInt()匹配任何int参数,这意味着参数为任意值,其返回值均是element + when(mockedList.get(anyInt())).thenReturn("element"); + // 此时打印是element + System.out.println(mockedList.get(999)); + } + + /** + * 模拟方法调用次数 + */ + @Test + void test06() { + // 调用add一次 + mockedList.add("once"); + // 下面两个写法验证效果一样,均验证add方法是否被调用了一次 + verify(mockedList).add("once"); + verify(mockedList, times(1)).add("once"); + } + + /** + * 校验行为 + */ + @Test + void test07() { + // using mock object + mockedList.add("one"); + // verification + verify(mockedList).add("one"); + verify(mockedList).clear(); + } + + /** + * 模拟方法调用(Stubbing) + */ + @Test + void test08() { + // stubbing + when(mockedList.get(0)).thenReturn("first"); + when(mockedList.get(1)).thenThrow(new RuntimeException()); + // following prints "first" + System.out.println(mockedList.get(0)); + try { + // following throws runtime exception + System.out.println(mockedList.get(1)); + } catch (RuntimeException e) { + System.out.println(e.getMessage()); + } + // following prints "null" because get(999) was not stubbed + System.out.println(mockedList.get(999)); + + verify(mockedList).get(0); + } + + /** + * 校验方法调用次数 + */ + @Test + void test09() { + // using mock + mockedList.add("once"); + + mockedList.add("twice"); + mockedList.add("twice"); + + mockedList.add("three"); + mockedList.add("three"); + mockedList.add("three"); + // following two verifications work exactly the same - times(1) is used by default + verify(mockedList).add("once"); + verify(mockedList, times(1)).add("once"); + // exact number of invocations verification + verify(mockedList, times(2)).add("twice"); + verify(mockedList, times(3)).add("three"); + // verification using never(). never() is an alias to times(0) + verify(mockedList, never()).add("never happened"); + // verification using atLeast()/atMost() + verify(mockedList, atLeastOnce()).add("three"); + verify(mockedList, atLeast(2)).add("twice"); + verify(mockedList, atMost(5)).add("three"); + } + + /** + * 校验方法调用顺序 + */ + @Test + void test10() { + // A. Single mock whose methods must be invoked in a particular order + List singleMock = mock(List.class); + // using a single mock + singleMock.add("was added first"); + singleMock.add("was added second"); + // create an inOrder verifier for a single mock + InOrder inOrder = inOrder(singleMock); + // following will make sure that add is first called with "was added first, then + // with "was added second" + inOrder.verify(singleMock).add("was added first"); + inOrder.verify(singleMock).add("was added second"); + + // B. Multiple mocks that must be used in a particular order + List firstMock = mock(List.class); + List secondMock = mock(List.class); + // using mocks + firstMock.add("was called first"); + secondMock.add("was called second"); + // create inOrder object passing any mocks that need to be verified in order + inOrder = inOrder(firstMock, secondMock); + // following will make sure that firstMock was called before secondMock + inOrder.verify(firstMock).add("was called first"); + inOrder.verify(secondMock).add("was called second"); + // Oh, and A + B can be mixed together at will + } + + /** + * 校验方法是否从未调用 + */ + @Test + void test11() { + List mockOne = mock(List.class); + List mockTwo = mock(List.class); + List mockThree = mock(List.class); + // using mocks - only mockOne is interacted + mockOne.add("one"); + // ordinary verification + verify(mockOne).add("one"); + // verify that method was never called on a mock + verify(mockOne, never()).add("two"); + // verify that other mocks were not interacted + verifyZeroInteractions(mockTwo, mockThree); + } + + /** + * 重置Mock + */ + void test12() { + List mock = mock(List.class); + when(mock.size()).thenReturn(10); + mock.add(1); + reset(mock); + } + +} diff --git a/codes/java-ecology/java-ecology-text/pom.xml b/codes/java-ecology/java-ecology-text/pom.xml new file mode 100644 index 00000000..a87776c4 --- /dev/null +++ b/codes/java-ecology/java-ecology-text/pom.xml @@ -0,0 +1,58 @@ + + + 4.0.0 + + + io.github.dunwu + dunwu-parent + 0.5.1 + + + io.github.dunwu.javatech + java-ecology-text + 1.0.0 + jar + java-ecology-text + + + UTF-8 + 1.8 + ${java.version} + ${java.version} + 4.0.0 + + + + + + org.apache.poi + poi + ${poi.version} + + + org.apache.poi + poi-ooxml + ${poi.version} + + + org.apache.poi + poi-scratchpad + ${poi.version} + + + + + io.github.biezhi + TinyPinyin + 2.0.3.RELEASE + + + + junit + junit + test + + + diff --git a/codes/java-ecology/java-ecology-text/src/main/java/io/github/dunwu/javatech/office/PinyinDemo.java b/codes/java-ecology/java-ecology-text/src/main/java/io/github/dunwu/javatech/office/PinyinDemo.java new file mode 100644 index 00000000..b3742bdd --- /dev/null +++ b/codes/java-ecology/java-ecology-text/src/main/java/io/github/dunwu/javatech/office/PinyinDemo.java @@ -0,0 +1,15 @@ +package io.github.dunwu.javatech.office; + +import com.github.promeg.pinyinhelper.Pinyin; + +/** + * @author Zhang Peng + * @since 2019-12-09 + */ +public class PinyinDemo { + + public static void main(String[] args) { + System.out.println("args = " + Pinyin.toPinyin('中')); + } + +} diff --git a/codes/java-ecology/java-ecology-text/src/main/java/io/github/dunwu/javatech/office/WordUtil.java b/codes/java-ecology/java-ecology-text/src/main/java/io/github/dunwu/javatech/office/WordUtil.java new file mode 100644 index 00000000..64cae3f1 --- /dev/null +++ b/codes/java-ecology/java-ecology-text/src/main/java/io/github/dunwu/javatech/office/WordUtil.java @@ -0,0 +1,263 @@ +package io.github.dunwu.javatech.office; + +import org.apache.poi.hpsf.DocumentSummaryInformation; +import org.apache.poi.hpsf.SummaryInformation; +import org.apache.poi.hwpf.HWPFDocument; +import org.apache.poi.ooxml.POIXMLProperties; +import org.apache.poi.xwpf.extractor.XWPFWordExtractor; +import org.apache.poi.xwpf.usermodel.*; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +/** + * @author Zhang Peng + * @see https://poi.apache.org/ + * @see https://www.w3cschool.cn/apache_poi_word/apache_poi_word_overview.html + * @since 2018-11-08 + */ +public class WordUtil { + + /** + * 创建空白文档 + * + * @param filename + * @throws IOException + */ + public static void create(String filename) throws IOException { + // Blank Document + XWPFDocument document = new XWPFDocument(); + // Write the Document in file system + FileOutputStream out = new FileOutputStream(new File(filename)); + document.write(out); + out.close(); + System.out.printf("create %s written successully\n", filename); + } + + /** + * 创建 *.docx 文档,包含 content 内容 + * + * @param filename + * @throws IOException + */ + public static void create(String filename, String content) throws IOException { + // Blank Document + XWPFDocument document = new XWPFDocument(); + // Write the Document in file system + FileOutputStream out = new FileOutputStream(new File(filename)); + + // create Paragraph + XWPFParagraph paragraph = document.createParagraph(); + XWPFRun run = paragraph.createRun(); + run.setText(content); + document.write(out); + out.close(); + System.out.printf("create %s written successully\n", filename); + } + + /** + * 创建 *.docx 文档,包含 content 内容,content 内容置于边框中 + * + * @param filename + * @throws IOException + */ + public static void createWithBorders(String filename, String content) throws IOException { + // Blank Document + XWPFDocument document = new XWPFDocument(); + + // Write the Document in file system + FileOutputStream out = new FileOutputStream(new File(filename)); + + // create paragraph + XWPFParagraph paragraph = document.createParagraph(); + + // Set bottom border to paragraph + paragraph.setBorderBottom(Borders.BASIC_BLACK_DASHES); + + // Set left border to paragraph + paragraph.setBorderLeft(Borders.BASIC_BLACK_DASHES); + + // Set right border to paragraph + paragraph.setBorderRight(Borders.BASIC_BLACK_DASHES); + + // Set top border to paragraph + paragraph.setBorderTop(Borders.BASIC_BLACK_DASHES); + + XWPFRun run = paragraph.createRun(); + run.setText(content); + + document.write(out); + out.close(); + System.out.printf("create %s written successully\n", filename); + } + + /** + * 表格 + * + * @param filename + * @throws IOException + */ + public static void createWithTable(String filename) throws IOException { + // Blank Document + XWPFDocument document = new XWPFDocument(); + + // Write the Document in file system + FileOutputStream out = new FileOutputStream(new File(filename)); + + // create table + XWPFTable table = document.createTable(); + // create first row + XWPFTableRow tableRowOne = table.getRow(0); + tableRowOne.getCell(0).setText("col one, row one"); + tableRowOne.addNewTableCell().setText("col two, row one"); + tableRowOne.addNewTableCell().setText("col three, row one"); + // create second row + XWPFTableRow tableRowTwo = table.createRow(); + tableRowTwo.getCell(0).setText("col one, row two"); + tableRowTwo.getCell(1).setText("col two, row two"); + tableRowTwo.getCell(2).setText("col three, row two"); + // create third row + XWPFTableRow tableRowThree = table.createRow(); + tableRowThree.getCell(0).setText("col one, row three"); + tableRowThree.getCell(1).setText("col two, row three"); + tableRowThree.getCell(2).setText("col three, row three"); + + document.write(out); + out.close(); + System.out.printf("create %s written successully\n", filename); + } + + /** + * 字体样式 + * + * @param filename + * @throws IOException + */ + public static void createWithFontStyle(String filename) throws IOException { + // Blank Document + XWPFDocument document = new XWPFDocument(); + + // Write the Document in file system + FileOutputStream out = new FileOutputStream(new File(filename)); + + // create paragraph + XWPFParagraph paragraph = document.createParagraph(); + + // Set Bold an Italic + XWPFRun paragraphOneRunOne = paragraph.createRun(); + paragraphOneRunOne.setBold(true); + paragraphOneRunOne.setItalic(true); + paragraphOneRunOne.setText("Font Style"); + paragraphOneRunOne.addBreak(); + + // Set text Position + XWPFRun paragraphOneRunTwo = paragraph.createRun(); + paragraphOneRunTwo.setText("Font Style two"); + paragraphOneRunTwo.setTextPosition(100); + + // Set Strike through and Font Size and Subscript + XWPFRun paragraphOneRunThree = paragraph.createRun(); + paragraphOneRunThree.setStrike(true); + paragraphOneRunThree.setFontSize(20); + paragraphOneRunThree.setSubscript(VerticalAlign.SUBSCRIPT); + paragraphOneRunThree.setText(" Different Font Styles"); + + document.write(out); + out.close(); + System.out.printf("create %s written successully\n", filename); + } + + /** + * 对齐方式 + * + * @param filename + * @throws IOException + */ + public static void createWithAlign(String filename) throws IOException { + // Blank Document + XWPFDocument document = new XWPFDocument(); + + // Write the Document in file system + FileOutputStream out = new FileOutputStream(new File(filename)); + + // create paragraph + XWPFParagraph paragraph = document.createParagraph(); + + // Set alignment paragraph to RIGHT + paragraph.setAlignment(ParagraphAlignment.RIGHT); + XWPFRun run = paragraph.createRun(); + run.setText("At tutorialspoint.com, we strive hard to " + "provide quality tutorials for self-learning " + + "purpose in the domains of Academics, Information " + + "Technology, Management and Computer Programming " + "Languages."); + + // Create Another paragraph + paragraph = document.createParagraph(); + + // Set alignment paragraph to CENTER + paragraph.setAlignment(ParagraphAlignment.CENTER); + run = paragraph.createRun(); + run.setText("The endeavour started by Mohtashim, an AMU " + + "alumni, who is the founder and the managing director " + + "of Tutorials Point (I) Pvt. Ltd. He came up with the " + + "website tutorialspoint.com in year 2006 with the help" + + "of handpicked freelancers, with an array of tutorials" + " for computer programming languages. "); + document.write(out); + out.close(); + System.out.printf("create %s written successully\n", filename); + } + + /** + * 文本提取 + * + * @param filename + * @throws IOException + */ + public static void extractor(String filename) throws IOException { + XWPFDocument docx = new XWPFDocument(new FileInputStream(filename)); + // using XWPFWordExtractor Class + XWPFWordExtractor we = new XWPFWordExtractor(docx); + System.out.println(we.getText()); + } + + public static void setDocxProperties(String filename) throws IOException { + FileInputStream fis = new FileInputStream(new File(filename)); + XWPFDocument doc = new XWPFDocument(fis); + + POIXMLProperties properties = doc.getProperties(); + POIXMLProperties.CoreProperties coreProperties = properties.getCoreProperties(); + coreProperties.setCreator("星环信息科技有限公司"); + coreProperties.setLastModifiedByUser("星环信息科技有限公司"); + POIXMLProperties.ExtendedProperties extendedProperties = properties.getExtendedProperties(); + extendedProperties.getUnderlyingProperties().setCompany("星环信息科技有限公司"); + + FileOutputStream fos = new FileOutputStream(new File(filename)); + doc.write(fos); + + fos.close(); + doc.close(); + fis.close(); + } + + public static void setDocProperties(String filename) throws IOException { + System.out.println("filename = [" + filename + "]"); + FileInputStream fis = new FileInputStream(new File(filename)); + HWPFDocument doc = new HWPFDocument(fis); + + SummaryInformation summaryInformation = doc.getSummaryInformation(); + summaryInformation.setAuthor("张鹏"); + summaryInformation.setLastAuthor("张鹏"); + DocumentSummaryInformation documentSummaryInformation = doc.getDocumentSummaryInformation(); + documentSummaryInformation.setCompany("张鹏"); + documentSummaryInformation.setDocumentVersion("1"); + + FileOutputStream fos = new FileOutputStream(new File(filename)); + doc.write(fos); + + fos.close(); + doc.close(); + fis.close(); + } + +} diff --git a/codes/java-ecology/java-ecology-text/src/test/java/io/github/dunwu/javatech/office/WordUtilTest.java b/codes/java-ecology/java-ecology-text/src/test/java/io/github/dunwu/javatech/office/WordUtilTest.java new file mode 100644 index 00000000..1566e002 --- /dev/null +++ b/codes/java-ecology/java-ecology-text/src/test/java/io/github/dunwu/javatech/office/WordUtilTest.java @@ -0,0 +1,111 @@ +package io.github.dunwu.javatech.office; + +import org.junit.Test; + +import java.io.IOException; + +/** + * @author Zhang Peng + * @since 2018-11-08 + */ +public class WordUtilTest { + + @Test + public void testCreateDocx() { + try { + WordUtil.create("d://temp.docx"); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Test + public void testCreateDocxWithContent() { + StringBuilder sb = new StringBuilder(); + sb.append("At tutorialspoint.com, we strive hard to "); + sb.append("provide quality tutorials for self-learning "); + sb.append("purpose in the domains of Academics, Information "); + sb.append("Technology, Management and Computer Programming Languages."); + + try { + WordUtil.create("d://temp2.docx", sb.toString()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Test + public void testCreateDocxWithBorder() { + StringBuilder sb = new StringBuilder(); + sb.append("At tutorialspoint.com, we strive hard to "); + sb.append("provide quality tutorials for self-learning "); + sb.append("purpose in the domains of Academics, Information "); + sb.append("Technology, Management and Computer Programming Languages."); + + try { + WordUtil.createWithBorders("d://temp3.docx", sb.toString()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Test + public void testCreateDocxWithTable() { + try { + WordUtil.createWithTable("d://temp4.docx"); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Test + public void testCreateDocxWithFontStyle() { + try { + WordUtil.createWithFontStyle("d://temp5.docx"); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Test + public void testCreateDocxWithAlign() { + try { + WordUtil.createWithAlign("d://temp6.docx"); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Test + public void testExtractor() { + try { + WordUtil.extractor("d://temp6.docx"); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Test + public void test() { + try { + WordUtil.setDocxProperties("d://temp6.docx"); + } catch (IOException e) { + e.printStackTrace(); + } + } + + // @Test + // public void test2() { + // File dir = new File("D:\\Docs\\ZP\\notes\\软件工程\\软件工程文档标准模板"); + // File[] files = dir.listFiles(); + // for (File file : files) { + // if (!file.isDirectory()) { + // try { + // WordUtil.setDocProperties(file.getAbsolutePath()); + // } catch (IOException e) { + // e.printStackTrace(); + // } + // } + // } + // } +} diff --git a/codes/java-ecology/java-ecology-tomcat/README.md b/codes/java-ecology/java-ecology-tomcat/README.md new file mode 100644 index 00000000..3f574661 --- /dev/null +++ b/codes/java-ecology/java-ecology-tomcat/README.md @@ -0,0 +1,50 @@ +# javatool-server + +> 本示例代码主要展示嵌入式服务器和 web 项目的集成。 +> +> 你可以在本项目中体验嵌入式 Tomcat 和嵌入式 Jetty 的启动方式。 +> + +**版本** + +* JDK:1.8 +* Tomcat:8.5.24 + +## Tomcat + +### Windows 启动 + +执行 `io.github.dunwu.javatech.server.SimpleTomcatServer#main` 方法。 + +或执行 `io.github.dunwu.javatech.server.TomcatServer.main` 方法。 + +启动后,访问 http://localhost:8080/javatool-server/ + +### 插件启动嵌入式 Tomcat + +由于插件很久没有更新(最新版本发布时间:2013-11-11),目前只能找到 Tomcat6 、Tomcat7 插件,所以弃用。 + +### 脚本启动 + +> 本项目添加了脚本启动范例。 +> +> 脚本代码全在 [`scripts`](https://github.com/dunwu/JavaStack/tree/master/scripts) 目录下。 + +* 初始化 + +```bash +wget https://raw.githubusercontent.com/dunwu/JavaStack/master/scripts/init.sh +chmod 777 init.sh +./init.sh +``` + +* 发布 + +``` +cd /home/zp/source/JavaStack/scripts +./javatool-server-release.sh master develop +``` + +## Jetty + +待添加。。。 diff --git a/codes/java-ecology/java-ecology-tomcat/pom.xml b/codes/java-ecology/java-ecology-tomcat/pom.xml new file mode 100644 index 00000000..b70084c2 --- /dev/null +++ b/codes/java-ecology/java-ecology-tomcat/pom.xml @@ -0,0 +1,88 @@ + + + 4.0.0 + + + io.github.dunwu + dunwu-parent + 0.5.1 + + + io.github.dunwu.javatech + java-ecology-tomcat + 1.0.0 + war + Java + Tomcat 示例 + + + UTF-8 + 1.8 + ${java.version} + ${java.version} + [8.5.40,) + + + + + + ch.qos.logback + logback-classic + + + org.logback-extensions + logback-ext-spring + 0.1.2 + + + org.slf4j + jcl-over-slf4j + + + + + + javax.servlet + javax.servlet-api + provided + + + javax.servlet.jsp + jsp-api + 2.2 + provided + + + + + + org.apache.tomcat.embed + tomcat-embed-core + + + + org.apache.tomcat.embed + tomcat-embed-el + + + + org.apache.tomcat.embed + tomcat-embed-jasper + + + + + + + org.springframework + spring-context-support + + + org.springframework + spring-webmvc + + + + + diff --git a/codes/java-ecology/java-ecology-tomcat/src/main/java/io/github/dunwu/javatech/controller/HelloController.java b/codes/java-ecology/java-ecology-tomcat/src/main/java/io/github/dunwu/javatech/controller/HelloController.java new file mode 100644 index 00000000..9588d2b0 --- /dev/null +++ b/codes/java-ecology/java-ecology-tomcat/src/main/java/io/github/dunwu/javatech/controller/HelloController.java @@ -0,0 +1,56 @@ +package io.github.dunwu.javatech.controller; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.servlet.ModelAndView; + +/** + * spring mvc 的第一个程序 + * + * @author Zhang Peng + * @since 2016.07.29 + */ +@Controller +@RequestMapping(value = "/hello") +public class HelloController { + + private final Logger log = LoggerFactory.getLogger(this.getClass()); + + /** + *

    + * 在本例中,Spring将会将数据传给 hello.jsp + *

    + * 访问形式:http://localhost:8080/hello?name=张三 + */ + @RequestMapping(value = "/name", method = RequestMethod.GET) + public ModelAndView hello(@RequestParam("name") String name) { + ModelAndView mav = new ModelAndView(); + mav.addObject("message", "你好," + name); + mav.setViewName("hello"); + return mav; + } + + /** + *

    + * 测试 logback 分级日志。配置项见src/main/resouces/logback.xml + *

    + * 访问形式:http://localhost:8080/log + */ + @ResponseBody + @RequestMapping(value = "/log", method = RequestMethod.GET) + public String log() { + String msg = "print log, current level: {}"; + log.trace(msg, "trace"); + log.debug(msg, "debug"); + log.info(msg, "info"); + log.warn(msg, "warn"); + log.error(msg, "error"); + return msg; + } + +} diff --git a/codes/java-ecology/java-ecology-tomcat/src/main/java/io/github/dunwu/javatech/controller/IndexController.java b/codes/java-ecology/java-ecology-tomcat/src/main/java/io/github/dunwu/javatech/controller/IndexController.java new file mode 100644 index 00000000..65549bb7 --- /dev/null +++ b/codes/java-ecology/java-ecology-tomcat/src/main/java/io/github/dunwu/javatech/controller/IndexController.java @@ -0,0 +1,31 @@ +/** + * The Apache License 2.0 Copyright (c) 2016 Zhang Peng + */ +package io.github.dunwu.javatech.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.servlet.ModelAndView; + +/** + * @author Zhang Peng + * @since 2017/4/12. + */ +@Controller +public class IndexController { + + /** + *

    + * 返回 ModelAndView 对象到视图层。在本例中,视图解析器解析视图名为 index,会自动关联到 index.jsp。 + *

    + * 访问形式:http://localhost:8080/ + */ + @RequestMapping(value = "/", method = RequestMethod.GET) + public ModelAndView index() { + ModelAndView mav = new ModelAndView(); + mav.setViewName("index"); + return mav; + } + +} diff --git a/codes/java-ecology/java-ecology-tomcat/src/main/java/io/github/dunwu/javatech/server/SimpleTomcatServer.java b/codes/java-ecology/java-ecology-tomcat/src/main/java/io/github/dunwu/javatech/server/SimpleTomcatServer.java new file mode 100644 index 00000000..5741fc81 --- /dev/null +++ b/codes/java-ecology/java-ecology-tomcat/src/main/java/io/github/dunwu/javatech/server/SimpleTomcatServer.java @@ -0,0 +1,41 @@ +package io.github.dunwu.javatech.server; + +import org.apache.catalina.startup.Tomcat; + +import java.util.Optional; + +/** + * 简单的嵌入式 Tomcat 启动类 启动后可访问 http://localhost:8080/javatool-server/ + * + * @author Zhang Peng + */ +public class SimpleTomcatServer { + + private static final int PORT = 8080; + + private static final String CONTEXT_PATH = "/javatool-server"; + + public static void main(String[] args) throws Exception { + // 设定 profile + Optional profile = Optional.ofNullable(System.getProperty("spring.profiles.active")); + System.setProperty("spring.profiles.active", profile.orElse("develop")); + + Tomcat tomcat = new Tomcat(); + tomcat.setPort(PORT); + tomcat.getHost().setAppBase("."); + tomcat.addWebapp(CONTEXT_PATH, getAbsolutePath() + "src/main/webapp"); + tomcat.start(); + tomcat.getServer().await(); + } + + private static String getAbsolutePath() { + String path = null; + String folderPath = SimpleTomcatServer.class.getProtectionDomain().getCodeSource().getLocation().getPath() + .substring(1); + if (folderPath.indexOf("target") > 0) { + path = folderPath.substring(0, folderPath.indexOf("target")); + } + return path; + } + +} diff --git a/codes/java-ecology/java-ecology-tomcat/src/main/java/io/github/dunwu/javatech/server/TomcatServer.java b/codes/java-ecology/java-ecology-tomcat/src/main/java/io/github/dunwu/javatech/server/TomcatServer.java new file mode 100644 index 00000000..382d23de --- /dev/null +++ b/codes/java-ecology/java-ecology-tomcat/src/main/java/io/github/dunwu/javatech/server/TomcatServer.java @@ -0,0 +1,133 @@ +package io.github.dunwu.javatech.server; + +import org.apache.catalina.Server; +import org.apache.catalina.startup.Catalina; +import org.apache.catalina.startup.Tomcat; +import org.apache.tomcat.util.digester.Digester; +import org.apache.tomcat.util.scan.Constants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; + +import java.io.File; + +public class TomcatServer { + + private static final Logger log = LoggerFactory.getLogger(TomcatServer.class); + + private static final String CONNECTOR_PORT = "8080"; + + private static final String RELATIVE_DEV_DUBBO_RESOVE_FILE = + "src/main/resources/properties/dubbo-resolve.properties"; + + private static final String RELATIVE_DUBBO_RESOVE_FILE = "WEB-INF/classes/properties/dubbo-resolve.properties"; + + // 以下设置轻易不要改动 + private static final String RELATIVE_DEV_BASE_DIR = "src/main/resources/tomcat/"; + + private static final String RELATIVE_BASE_DIR = "WEB-INF/classes/tomcat/"; + + private static final String RELATIVE_DEV_DOCBASE_DIR = "src/main/webapp"; + + private static final String RELATIVE_DOCBASE_DIR = "./"; + + public static void main(String[] args) throws Exception { + // 设定Spring的profile + if (StringUtils.isEmpty(System.getProperty("spring.profiles.active"))) { + System.setProperty("spring.profiles.active", "develop"); + } + + System.setProperty("tomcat.host.appBase", getAbsolutePath()); + File checkFile = new File(System.getProperty("tomcat.host.appBase") + "/WEB-INF"); + if (!checkFile.exists()) { + System.setProperty("catalina.base", getAbsolutePath() + RELATIVE_DEV_BASE_DIR); + System.setProperty("tomcat.context.docBase", RELATIVE_DEV_DOCBASE_DIR); + System.setProperty("dubbo.resolve.file", getAbsolutePath() + RELATIVE_DEV_DUBBO_RESOVE_FILE); + } else { + System.setProperty("catalina.base", getAbsolutePath() + RELATIVE_BASE_DIR); + System.setProperty("tomcat.context.docBase", RELATIVE_DOCBASE_DIR); + if ("develop".equalsIgnoreCase(System.getProperty("spring.profiles.active")) + || "test".equalsIgnoreCase("spring.profiles.active")) { + System.setProperty("dubbo.resolve.file", getAbsolutePath() + RELATIVE_DUBBO_RESOVE_FILE); + } + } + + if (StringUtils.isEmpty(System.getProperty("tomcat.connector.port"))) { + System.setProperty("tomcat.connector.port", CONNECTOR_PORT); + } + if (StringUtils.isEmpty(System.getProperty("tomcat.server.shutdownPort"))) { + System.setProperty("tomcat.server.shutdownPort", + String.valueOf(Integer.valueOf(System.getProperty("tomcat.connector.port")) + 10000)); + } + + log.info("====================ENV setting===================="); + log.info("spring.profiles.active:" + System.getProperty("spring.profiles.active")); + log.info("dubbo.resolve.file:" + System.getProperty("dubbo.resolve.file")); + log.info("catalina.base:" + System.getProperty("catalina.base")); + log.info("tomcat.host.appBase:" + System.getProperty("tomcat.host.appBase")); + log.info("tomcat.context.docBase:" + System.getProperty("tomcat.context.docBase")); + log.info("tomcat.connector.port:" + System.getProperty("tomcat.connector.port")); + log.info("tomcat.server.shutdownPort:" + System.getProperty("tomcat.server.shutdownPort")); + + ExtendedTomcat tomcat = new ExtendedTomcat(); + tomcat.start(); + tomcat.getServer().await(); + } + + private static String getAbsolutePath() { + String path = null; + String folderPath = TomcatServer.class.getProtectionDomain().getCodeSource().getLocation().getPath(); + if (folderPath.indexOf("WEB-INF") > 0) { + path = folderPath.substring(0, folderPath.indexOf("WEB-INF")); + } else if (folderPath.indexOf("target") > 0) { + path = folderPath.substring(0, folderPath.indexOf("target")); + } + return path; + } + +} + +class ExtendedTomcat extends Tomcat { + + private static final String RELATIVE_SERVERXML_PATH = "/conf/server.xml"; + + private Logger log = LoggerFactory.getLogger(this.getClass()); + + @Override + public Server getServer() { + if (server != null) { + return server; + } + // 默认不开启JNDI. 开启时, 注意maven必须添加tomcat-dbcp依赖 + System.setProperty("catalina.useNaming", "false"); + ExtendedCatalina extendedCatalina = new ExtendedCatalina(); + + // 覆盖默认的skip和scan jar包配置 + System.setProperty(Constants.SKIP_JARS_PROPERTY, ""); + System.setProperty(Constants.SCAN_JARS_PROPERTY, ""); + + Digester digester = extendedCatalina.createStartDigester(); + digester.push(extendedCatalina); + try { + server = ((ExtendedCatalina) digester + .parse(new File(System.getProperty("catalina.base") + RELATIVE_SERVERXML_PATH))).getServer(); + // 设置catalina.base和catalna.home + this.initBaseDir(); + return server; + } catch (Exception e) { + log.error("Error while parsing server.xml", e); + throw new RuntimeException("server未创建,请检查server.xml(路径:" + System.getProperty("catalina.base") + + RELATIVE_SERVERXML_PATH + ")配置是否正确"); + } + } + + private static class ExtendedCatalina extends Catalina { + + @Override + public Digester createStartDigester() { + return super.createStartDigester(); + } + + } + +} diff --git a/codes/java-ecology/java-ecology-tomcat/src/main/resources/logback.xml b/codes/java-ecology/java-ecology-tomcat/src/main/resources/logback.xml new file mode 100644 index 00000000..9884e7be --- /dev/null +++ b/codes/java-ecology/java-ecology-tomcat/src/main/resources/logback.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n + + + + + + + + ${user.dir}/logs/${FILE_NAME}-all.%d{yyyy-MM-dd}.log + 30 + + + + + 30MB + + + + %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n + + + + + + + + + + + + + + + + + + diff --git a/codes/java-ecology/java-ecology-tomcat/src/main/resources/properties/application-develop.properties b/codes/java-ecology/java-ecology-tomcat/src/main/resources/properties/application-develop.properties new file mode 100644 index 00000000..25f09a35 --- /dev/null +++ b/codes/java-ecology/java-ecology-tomcat/src/main/resources/properties/application-develop.properties @@ -0,0 +1,11 @@ +# jdbc +jdbc.driver = +jdbc.url = +jdbc.username = +jdbc.password = +# redis +redis.name = +redis.host = +redis.port = +redis.password = +redis.database = diff --git a/codes/java-ecology/java-ecology-tomcat/src/main/resources/properties/application-test.properties b/codes/java-ecology/java-ecology-tomcat/src/main/resources/properties/application-test.properties new file mode 100644 index 00000000..25f09a35 --- /dev/null +++ b/codes/java-ecology/java-ecology-tomcat/src/main/resources/properties/application-test.properties @@ -0,0 +1,11 @@ +# jdbc +jdbc.driver = +jdbc.url = +jdbc.username = +jdbc.password = +# redis +redis.name = +redis.host = +redis.port = +redis.password = +redis.database = diff --git a/codes/java-ecology/java-ecology-tomcat/src/main/resources/spring/spring-servlet.xml b/codes/java-ecology/java-ecology-tomcat/src/main/resources/spring/spring-servlet.xml new file mode 100644 index 00000000..6c05fe85 --- /dev/null +++ b/codes/java-ecology/java-ecology-tomcat/src/main/resources/spring/spring-servlet.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + diff --git a/codes/java-ecology/java-ecology-tomcat/src/main/resources/tomcat/conf/server.xml b/codes/java-ecology/java-ecology-tomcat/src/main/resources/tomcat/conf/server.xml new file mode 100644 index 00000000..77316698 --- /dev/null +++ b/codes/java-ecology/java-ecology-tomcat/src/main/resources/tomcat/conf/server.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/codes/java-ecology/java-ecology-tomcat/src/main/resources/tomcat/conf/web.xml b/codes/java-ecology/java-ecology-tomcat/src/main/resources/tomcat/conf/web.xml new file mode 100644 index 00000000..7dc916a5 --- /dev/null +++ b/codes/java-ecology/java-ecology-tomcat/src/main/resources/tomcat/conf/web.xml @@ -0,0 +1,4703 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + default + org.apache.catalina.servlets.DefaultServlet + + debug + 0 + + + listings + false + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + jsp + org.apache.jasper.servlet.JspServlet + + fork + false + + + xpoweredBy + false + + + keepgenerated + false + + 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + default + / + + + + + jsp + *.jsp + *.jspx + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 30 + + + + + + + + + + + + + 123 + application/vnd.lotus-1-2-3 + + + 3dml + text/vnd.in3d.3dml + + + 3ds + image/x-3ds + + + 3g2 + video/3gpp2 + + + 3gp + video/3gpp + + + 7z + application/x-7z-compressed + + + aab + application/x-authorware-bin + + + aac + audio/x-aac + + + aam + application/x-authorware-map + + + aas + application/x-authorware-seg + + + abs + audio/x-mpeg + + + abw + application/x-abiword + + + ac + application/pkix-attr-cert + + + acc + application/vnd.americandynamics.acc + + + ace + application/x-ace-compressed + + + acu + application/vnd.acucobol + + + acutc + application/vnd.acucorp + + + adp + audio/adpcm + + + aep + application/vnd.audiograph + + + afm + application/x-font-type1 + + + afp + application/vnd.ibm.modcap + + + ahead + application/vnd.ahead.space + + + ai + application/postscript + + + aif + audio/x-aiff + + + aifc + audio/x-aiff + + + aiff + audio/x-aiff + + + aim + application/x-aim + + + air + application/vnd.adobe.air-application-installer-package+zip + + + ait + application/vnd.dvb.ait + + + ami + application/vnd.amiga.ami + + + anx + application/annodex + + + apk + application/vnd.android.package-archive + + + appcache + text/cache-manifest + + + application + application/x-ms-application + + + apr + application/vnd.lotus-approach + + + arc + application/x-freearc + + + art + image/x-jg + + + asc + application/pgp-signature + + + asf + video/x-ms-asf + + + asm + text/x-asm + + + aso + application/vnd.accpac.simply.aso + + + asx + video/x-ms-asf + + + atc + application/vnd.acucorp + + + atom + application/atom+xml + + + atomcat + application/atomcat+xml + + + atomsvc + application/atomsvc+xml + + + atx + application/vnd.antix.game-component + + + au + audio/basic + + + avi + video/x-msvideo + + + avx + video/x-rad-screenplay + + + aw + application/applixware + + + axa + audio/annodex + + + axv + video/annodex + + + azf + application/vnd.airzip.filesecure.azf + + + azs + application/vnd.airzip.filesecure.azs + + + azw + application/vnd.amazon.ebook + + + bat + application/x-msdownload + + + bcpio + application/x-bcpio + + + bdf + application/x-font-bdf + + + bdm + application/vnd.syncml.dm+wbxml + + + bed + application/vnd.realvnc.bed + + + bh2 + application/vnd.fujitsu.oasysprs + + + bin + application/octet-stream + + + blb + application/x-blorb + + + blorb + application/x-blorb + + + bmi + application/vnd.bmi + + + bmp + image/bmp + + + body + text/html + + + book + application/vnd.framemaker + + + box + application/vnd.previewsystems.box + + + boz + application/x-bzip2 + + + bpk + application/octet-stream + + + btif + image/prs.btif + + + bz + application/x-bzip + + + bz2 + application/x-bzip2 + + + c + text/x-c + + + c11amc + application/vnd.cluetrust.cartomobile-config + + + c11amz + application/vnd.cluetrust.cartomobile-config-pkg + + + c4d + application/vnd.clonk.c4group + + + c4f + application/vnd.clonk.c4group + + + c4g + application/vnd.clonk.c4group + + + c4p + application/vnd.clonk.c4group + + + c4u + application/vnd.clonk.c4group + + + cab + application/vnd.ms-cab-compressed + + + caf + audio/x-caf + + + cap + application/vnd.tcpdump.pcap + + + car + application/vnd.curl.car + + + cat + application/vnd.ms-pki.seccat + + + cb7 + application/x-cbr + + + cba + application/x-cbr + + + cbr + application/x-cbr + + + cbt + application/x-cbr + + + cbz + application/x-cbr + + + cc + text/x-c + + + cct + application/x-director + + + ccxml + application/ccxml+xml + + + cdbcmsg + application/vnd.contact.cmsg + + + cdf + application/x-cdf + + + cdkey + application/vnd.mediastation.cdkey + + + cdmia + application/cdmi-capability + + + cdmic + application/cdmi-container + + + cdmid + application/cdmi-domain + + + cdmio + application/cdmi-object + + + cdmiq + application/cdmi-queue + + + cdx + chemical/x-cdx + + + cdxml + application/vnd.chemdraw+xml + + + cdy + application/vnd.cinderella + + + cer + application/pkix-cert + + + cfs + application/x-cfs-compressed + + + cgm + image/cgm + + + chat + application/x-chat + + + chm + application/vnd.ms-htmlhelp + + + chrt + application/vnd.kde.kchart + + + cif + chemical/x-cif + + + cii + application/vnd.anser-web-certificate-issue-initiation + + + cil + application/vnd.ms-artgalry + + + cla + application/vnd.claymore + + + class + application/java + + + clkk + application/vnd.crick.clicker.keyboard + + + clkp + application/vnd.crick.clicker.palette + + + clkt + application/vnd.crick.clicker.template + + + clkw + application/vnd.crick.clicker.wordbank + + + clkx + application/vnd.crick.clicker + + + clp + application/x-msclip + + + cmc + application/vnd.cosmocaller + + + cmdf + chemical/x-cmdf + + + cml + chemical/x-cml + + + cmp + application/vnd.yellowriver-custom-menu + + + cmx + image/x-cmx + + + cod + application/vnd.rim.cod + + + com + application/x-msdownload + + + conf + text/plain + + + cpio + application/x-cpio + + + cpp + text/x-c + + + cpt + application/mac-compactpro + + + crd + application/x-mscardfile + + + crl + application/pkix-crl + + + crt + application/x-x509-ca-cert + + + cryptonote + application/vnd.rig.cryptonote + + + csh + application/x-csh + + + csml + chemical/x-csml + + + csp + application/vnd.commonspace + + + css + text/css + + + cst + application/x-director + + + csv + text/csv + + + cu + application/cu-seeme + + + curl + text/vnd.curl + + + cww + application/prs.cww + + + cxt + application/x-director + + + cxx + text/x-c + + + dae + model/vnd.collada+xml + + + daf + application/vnd.mobius.daf + + + dart + application/vnd.dart + + + dataless + application/vnd.fdsn.seed + + + davmount + application/davmount+xml + + + dbk + application/docbook+xml + + + dcr + application/x-director + + + dcurl + text/vnd.curl.dcurl + + + dd2 + application/vnd.oma.dd2+xml + + + ddd + application/vnd.fujixerox.ddd + + + deb + application/x-debian-package + + + def + text/plain + + + deploy + application/octet-stream + + + der + application/x-x509-ca-cert + + + dfac + application/vnd.dreamfactory + + + dgc + application/x-dgc-compressed + + + dib + image/bmp + + + dic + text/x-c + + + dir + application/x-director + + + dis + application/vnd.mobius.dis + + + dist + application/octet-stream + + + distz + application/octet-stream + + + djv + image/vnd.djvu + + + djvu + image/vnd.djvu + + + dll + application/x-msdownload + + + dmg + application/x-apple-diskimage + + + dmp + application/vnd.tcpdump.pcap + + + dms + application/octet-stream + + + dna + application/vnd.dna + + + doc + application/msword + + + docm + application/vnd.ms-word.document.macroenabled.12 + + + docx + application/vnd.openxmlformats-officedocument.wordprocessingml.document + + + dot + application/msword + + + dotm + application/vnd.ms-word.template.macroenabled.12 + + + dotx + application/vnd.openxmlformats-officedocument.wordprocessingml.template + + + dp + application/vnd.osgi.dp + + + dpg + application/vnd.dpgraph + + + dra + audio/vnd.dra + + + dsc + text/prs.lines.tag + + + dssc + application/dssc+der + + + dtb + application/x-dtbook+xml + + + dtd + application/xml-dtd + + + dts + audio/vnd.dts + + + dtshd + audio/vnd.dts.hd + + + dump + application/octet-stream + + + dv + video/x-dv + + + dvb + video/vnd.dvb.file + + + dvi + application/x-dvi + + + dwf + model/vnd.dwf + + + dwg + image/vnd.dwg + + + dxf + image/vnd.dxf + + + dxp + application/vnd.spotfire.dxp + + + dxr + application/x-director + + + ecelp4800 + audio/vnd.nuera.ecelp4800 + + + ecelp7470 + audio/vnd.nuera.ecelp7470 + + + ecelp9600 + audio/vnd.nuera.ecelp9600 + + + ecma + application/ecmascript + + + edm + application/vnd.novadigm.edm + + + edx + application/vnd.novadigm.edx + + + efif + application/vnd.picsel + + + ei6 + application/vnd.pg.osasli + + + elc + application/octet-stream + + + emf + application/x-msmetafile + + + eml + message/rfc822 + + + emma + application/emma+xml + + + emz + application/x-msmetafile + + + eol + audio/vnd.digital-winds + + + eot + application/vnd.ms-fontobject + + + eps + application/postscript + + + epub + application/epub+zip + + + es3 + application/vnd.eszigno3+xml + + + esa + application/vnd.osgi.subsystem + + + esf + application/vnd.epson.esf + + + et3 + application/vnd.eszigno3+xml + + + etx + text/x-setext + + + eva + application/x-eva + + + evy + application/x-envoy + + + exe + application/octet-stream + + + exi + application/exi + + + ext + application/vnd.novadigm.ext + + + ez + application/andrew-inset + + + ez2 + application/vnd.ezpix-album + + + ez3 + application/vnd.ezpix-package + + + f + text/x-fortran + + + f4v + video/x-f4v + + + f77 + text/x-fortran + + + f90 + text/x-fortran + + + fbs + image/vnd.fastbidsheet + + + fcdt + application/vnd.adobe.formscentral.fcdt + + + fcs + application/vnd.isac.fcs + + + fdf + application/vnd.fdf + + + fe_launch + application/vnd.denovo.fcselayout-link + + + fg5 + application/vnd.fujitsu.oasysgp + + + fgd + application/x-director + + + fh + image/x-freehand + + + fh4 + image/x-freehand + + + fh5 + image/x-freehand + + + fh7 + image/x-freehand + + + fhc + image/x-freehand + + + fig + application/x-xfig + + + flac + audio/flac + + + fli + video/x-fli + + + flo + application/vnd.micrografx.flo + + + flv + video/x-flv + + + flw + application/vnd.kde.kivio + + + flx + text/vnd.fmi.flexstor + + + fly + text/vnd.fly + + + fm + application/vnd.framemaker + + + fnc + application/vnd.frogans.fnc + + + for + text/x-fortran + + + fpx + image/vnd.fpx + + + frame + application/vnd.framemaker + + + fsc + application/vnd.fsc.weblaunch + + + fst + image/vnd.fst + + + ftc + application/vnd.fluxtime.clip + + + fti + application/vnd.anser-web-funds-transfer-initiation + + + fvt + video/vnd.fvt + + + fxp + application/vnd.adobe.fxp + + + fxpl + application/vnd.adobe.fxp + + + fzs + application/vnd.fuzzysheet + + + g2w + application/vnd.geoplan + + + g3 + image/g3fax + + + g3w + application/vnd.geospace + + + gac + application/vnd.groove-account + + + gam + application/x-tads + + + gbr + application/rpki-ghostbusters + + + gca + application/x-gca-compressed + + + gdl + model/vnd.gdl + + + geo + application/vnd.dynageo + + + gex + application/vnd.geometry-explorer + + + ggb + application/vnd.geogebra.file + + + ggt + application/vnd.geogebra.tool + + + ghf + application/vnd.groove-help + + + gif + image/gif + + + gim + application/vnd.groove-identity-message + + + gml + application/gml+xml + + + gmx + application/vnd.gmx + + + gnumeric + application/x-gnumeric + + + gph + application/vnd.flographit + + + gpx + application/gpx+xml + + + gqf + application/vnd.grafeq + + + gqs + application/vnd.grafeq + + + gram + application/srgs + + + gramps + application/x-gramps-xml + + + gre + application/vnd.geometry-explorer + + + grv + application/vnd.groove-injector + + + grxml + application/srgs+xml + + + gsf + application/x-font-ghostscript + + + gtar + application/x-gtar + + + gtm + application/vnd.groove-tool-message + + + gtw + model/vnd.gtw + + + gv + text/vnd.graphviz + + + gxf + application/gxf + + + gxt + application/vnd.geonext + + + gz + application/x-gzip + + + h + text/x-c + + + h261 + video/h261 + + + h263 + video/h263 + + + h264 + video/h264 + + + hal + application/vnd.hal+xml + + + hbci + application/vnd.hbci + + + hdf + application/x-hdf + + + hh + text/x-c + + + hlp + application/winhlp + + + hpgl + application/vnd.hp-hpgl + + + hpid + application/vnd.hp-hpid + + + hps + application/vnd.hp-hps + + + hqx + application/mac-binhex40 + + + htc + text/x-component + + + htke + application/vnd.kenameaapp + + + htm + text/html + + + html + text/html + + + hvd + application/vnd.yamaha.hv-dic + + + hvp + application/vnd.yamaha.hv-voice + + + hvs + application/vnd.yamaha.hv-script + + + i2g + application/vnd.intergeo + + + icc + application/vnd.iccprofile + + + ice + x-conference/x-cooltalk + + + icm + application/vnd.iccprofile + + + ico + image/x-icon + + + ics + text/calendar + + + ief + image/ief + + + ifb + text/calendar + + + ifm + application/vnd.shana.informed.formdata + + + iges + model/iges + + + igl + application/vnd.igloader + + + igm + application/vnd.insors.igm + + + igs + model/iges + + + igx + application/vnd.micrografx.igx + + + iif + application/vnd.shana.informed.interchange + + + imp + application/vnd.accpac.simply.imp + + + ims + application/vnd.ms-ims + + + in + text/plain + + + ink + application/inkml+xml + + + inkml + application/inkml+xml + + + install + application/x-install-instructions + + + iota + application/vnd.astraea-software.iota + + + ipfix + application/ipfix + + + ipk + application/vnd.shana.informed.package + + + irm + application/vnd.ibm.rights-management + + + irp + application/vnd.irepository.package+xml + + + iso + application/x-iso9660-image + + + itp + application/vnd.shana.informed.formtemplate + + + ivp + application/vnd.immervision-ivp + + + ivu + application/vnd.immervision-ivu + + + jad + text/vnd.sun.j2me.app-descriptor + + + jam + application/vnd.jam + + + jar + application/java-archive + + + java + text/x-java-source + + + jisp + application/vnd.jisp + + + jlt + application/vnd.hp-jlyt + + + jnlp + application/x-java-jnlp-file + + + joda + application/vnd.joost.joda-archive + + + jpe + image/jpeg + + + jpeg + image/jpeg + + + jpg + image/jpeg + + + jpgm + video/jpm + + + jpgv + video/jpeg + + + jpm + video/jpm + + + js + application/javascript + + + jsf + text/plain + + + json + application/json + + + jsonml + application/jsonml+json + + + jspf + text/plain + + + kar + audio/midi + + + karbon + application/vnd.kde.karbon + + + kfo + application/vnd.kde.kformula + + + kia + application/vnd.kidspiration + + + kml + application/vnd.google-earth.kml+xml + + + kmz + application/vnd.google-earth.kmz + + + kne + application/vnd.kinar + + + knp + application/vnd.kinar + + + kon + application/vnd.kde.kontour + + + kpr + application/vnd.kde.kpresenter + + + kpt + application/vnd.kde.kpresenter + + + kpxx + application/vnd.ds-keypoint + + + ksp + application/vnd.kde.kspread + + + ktr + application/vnd.kahootz + + + ktx + image/ktx + + + ktz + application/vnd.kahootz + + + kwd + application/vnd.kde.kword + + + kwt + application/vnd.kde.kword + + + lasxml + application/vnd.las.las+xml + + + latex + application/x-latex + + + lbd + application/vnd.llamagraphics.life-balance.desktop + + + lbe + application/vnd.llamagraphics.life-balance.exchange+xml + + + les + application/vnd.hhe.lesson-player + + + lha + application/x-lzh-compressed + + + link66 + application/vnd.route66.link66+xml + + + list + text/plain + + + list3820 + application/vnd.ibm.modcap + + + listafp + application/vnd.ibm.modcap + + + lnk + application/x-ms-shortcut + + + log + text/plain + + + lostxml + application/lost+xml + + + lrf + application/octet-stream + + + lrm + application/vnd.ms-lrm + + + ltf + application/vnd.frogans.ltf + + + lvp + audio/vnd.lucent.voice + + + lwp + application/vnd.lotus-wordpro + + + lzh + application/x-lzh-compressed + + + m13 + application/x-msmediaview + + + m14 + application/x-msmediaview + + + m1v + video/mpeg + + + m21 + application/mp21 + + + m2a + audio/mpeg + + + m2v + video/mpeg + + + m3a + audio/mpeg + + + m3u + audio/x-mpegurl + + + m3u8 + application/vnd.apple.mpegurl + + + m4a + audio/mp4 + + + m4b + audio/mp4 + + + m4r + audio/mp4 + + + m4u + video/vnd.mpegurl + + + m4v + video/mp4 + + + ma + application/mathematica + + + mac + image/x-macpaint + + + mads + application/mads+xml + + + mag + application/vnd.ecowin.chart + + + maker + application/vnd.framemaker + + + man + text/troff + + + mar + application/octet-stream + + + mathml + application/mathml+xml + + + mb + application/mathematica + + + mbk + application/vnd.mobius.mbk + + + mbox + application/mbox + + + mc1 + application/vnd.medcalcdata + + + mcd + application/vnd.mcd + + + mcurl + text/vnd.curl.mcurl + + + mdb + application/x-msaccess + + + mdi + image/vnd.ms-modi + + + me + text/troff + + + mesh + model/mesh + + + meta4 + application/metalink4+xml + + + metalink + application/metalink+xml + + + mets + application/mets+xml + + + mfm + application/vnd.mfmp + + + mft + application/rpki-manifest + + + mgp + application/vnd.osgeo.mapguide.package + + + mgz + application/vnd.proteus.magazine + + + mid + audio/midi + + + midi + audio/midi + + + mie + application/x-mie + + + mif + application/x-mif + + + mime + message/rfc822 + + + mj2 + video/mj2 + + + mjp2 + video/mj2 + + + mk3d + video/x-matroska + + + mka + audio/x-matroska + + + mks + video/x-matroska + + + mkv + video/x-matroska + + + mlp + application/vnd.dolby.mlp + + + mmd + application/vnd.chipnuts.karaoke-mmd + + + mmf + application/vnd.smaf + + + mmr + image/vnd.fujixerox.edmics-mmr + + + mng + video/x-mng + + + mny + application/x-msmoney + + + mobi + application/x-mobipocket-ebook + + + mods + application/mods+xml + + + mov + video/quicktime + + + movie + video/x-sgi-movie + + + mp1 + audio/mpeg + + + mp2 + audio/mpeg + + + mp21 + application/mp21 + + + mp2a + audio/mpeg + + + mp3 + audio/mpeg + + + mp4 + video/mp4 + + + mp4a + audio/mp4 + + + mp4s + application/mp4 + + + mp4v + video/mp4 + + + mpa + audio/mpeg + + + mpc + application/vnd.mophun.certificate + + + mpe + video/mpeg + + + mpeg + video/mpeg + + + mpega + audio/x-mpeg + + + mpg + video/mpeg + + + mpg4 + video/mp4 + + + mpga + audio/mpeg + + + mpkg + application/vnd.apple.installer+xml + + + mpm + application/vnd.blueice.multipass + + + mpn + application/vnd.mophun.application + + + mpp + application/vnd.ms-project + + + mpt + application/vnd.ms-project + + + mpv2 + video/mpeg2 + + + mpy + application/vnd.ibm.minipay + + + mqy + application/vnd.mobius.mqy + + + mrc + application/marc + + + mrcx + application/marcxml+xml + + + ms + text/troff + + + mscml + application/mediaservercontrol+xml + + + mseed + application/vnd.fdsn.mseed + + + mseq + application/vnd.mseq + + + msf + application/vnd.epson.msf + + + msh + model/mesh + + + msi + application/x-msdownload + + + msl + application/vnd.mobius.msl + + + msty + application/vnd.muvee.style + + + mts + model/vnd.mts + + + mus + application/vnd.musician + + + musicxml + application/vnd.recordare.musicxml+xml + + + mvb + application/x-msmediaview + + + mwf + application/vnd.mfer + + + mxf + application/mxf + + + mxl + application/vnd.recordare.musicxml + + + mxml + application/xv+xml + + + mxs + application/vnd.triscape.mxs + + + mxu + video/vnd.mpegurl + + + n-gage + application/vnd.nokia.n-gage.symbian.install + + + n3 + text/n3 + + + nb + application/mathematica + + + nbp + application/vnd.wolfram.player + + + nc + application/x-netcdf + + + ncx + application/x-dtbncx+xml + + + nfo + text/x-nfo + + + ngdat + application/vnd.nokia.n-gage.data + + + nitf + application/vnd.nitf + + + nlu + application/vnd.neurolanguage.nlu + + + nml + application/vnd.enliven + + + nnd + application/vnd.noblenet-directory + + + nns + application/vnd.noblenet-sealer + + + nnw + application/vnd.noblenet-web + + + npx + image/vnd.net-fpx + + + nsc + application/x-conference + + + nsf + application/vnd.lotus-notes + + + ntf + application/vnd.nitf + + + nzb + application/x-nzb + + + oa2 + application/vnd.fujitsu.oasys2 + + + oa3 + application/vnd.fujitsu.oasys3 + + + oas + application/vnd.fujitsu.oasys + + + obd + application/x-msbinder + + + obj + application/x-tgif + + + oda + application/oda + + + + odb + application/vnd.oasis.opendocument.database + + + + odc + application/vnd.oasis.opendocument.chart + + + + odf + application/vnd.oasis.opendocument.formula + + + odft + application/vnd.oasis.opendocument.formula-template + + + + odg + application/vnd.oasis.opendocument.graphics + + + + odi + application/vnd.oasis.opendocument.image + + + + odm + application/vnd.oasis.opendocument.text-master + + + + odp + application/vnd.oasis.opendocument.presentation + + + + ods + application/vnd.oasis.opendocument.spreadsheet + + + + odt + application/vnd.oasis.opendocument.text + + + oga + audio/ogg + + + ogg + audio/ogg + + + ogv + video/ogg + + + + ogx + application/ogg + + + omdoc + application/omdoc+xml + + + onepkg + application/onenote + + + onetmp + application/onenote + + + onetoc + application/onenote + + + onetoc2 + application/onenote + + + opf + application/oebps-package+xml + + + opml + text/x-opml + + + oprc + application/vnd.palm + + + org + application/vnd.lotus-organizer + + + osf + application/vnd.yamaha.openscoreformat + + + osfpvg + application/vnd.yamaha.openscoreformat.osfpvg+xml + + + otc + application/vnd.oasis.opendocument.chart-template + + + otf + font/otf + + + + otg + application/vnd.oasis.opendocument.graphics-template + + + + oth + application/vnd.oasis.opendocument.text-web + + + oti + application/vnd.oasis.opendocument.image-template + + + + otp + application/vnd.oasis.opendocument.presentation-template + + + + ots + application/vnd.oasis.opendocument.spreadsheet-template + + + + ott + application/vnd.oasis.opendocument.text-template + + + oxps + application/oxps + + + oxt + application/vnd.openofficeorg.extension + + + p + text/x-pascal + + + p10 + application/pkcs10 + + + p12 + application/x-pkcs12 + + + p7b + application/x-pkcs7-certificates + + + p7c + application/pkcs7-mime + + + p7m + application/pkcs7-mime + + + p7r + application/x-pkcs7-certreqresp + + + p7s + application/pkcs7-signature + + + p8 + application/pkcs8 + + + pas + text/x-pascal + + + paw + application/vnd.pawaafile + + + pbd + application/vnd.powerbuilder6 + + + pbm + image/x-portable-bitmap + + + pcap + application/vnd.tcpdump.pcap + + + pcf + application/x-font-pcf + + + pcl + application/vnd.hp-pcl + + + pclxl + application/vnd.hp-pclxl + + + pct + image/pict + + + pcurl + application/vnd.curl.pcurl + + + pcx + image/x-pcx + + + pdb + application/vnd.palm + + + pdf + application/pdf + + + pfa + application/x-font-type1 + + + pfb + application/x-font-type1 + + + pfm + application/x-font-type1 + + + pfr + application/font-tdpfr + + + pfx + application/x-pkcs12 + + + pgm + image/x-portable-graymap + + + pgn + application/x-chess-pgn + + + pgp + application/pgp-encrypted + + + pic + image/pict + + + pict + image/pict + + + pkg + application/octet-stream + + + pki + application/pkixcmp + + + pkipath + application/pkix-pkipath + + + plb + application/vnd.3gpp.pic-bw-large + + + plc + application/vnd.mobius.plc + + + plf + application/vnd.pocketlearn + + + pls + audio/x-scpls + + + pml + application/vnd.ctc-posml + + + png + image/png + + + pnm + image/x-portable-anymap + + + pnt + image/x-macpaint + + + portpkg + application/vnd.macports.portpkg + + + pot + application/vnd.ms-powerpoint + + + potm + application/vnd.ms-powerpoint.template.macroenabled.12 + + + potx + application/vnd.openxmlformats-officedocument.presentationml.template + + + ppam + application/vnd.ms-powerpoint.addin.macroenabled.12 + + + ppd + application/vnd.cups-ppd + + + ppm + image/x-portable-pixmap + + + pps + application/vnd.ms-powerpoint + + + ppsm + application/vnd.ms-powerpoint.slideshow.macroenabled.12 + + + ppsx + application/vnd.openxmlformats-officedocument.presentationml.slideshow + + + ppt + application/vnd.ms-powerpoint + + + pptm + application/vnd.ms-powerpoint.presentation.macroenabled.12 + + + pptx + application/vnd.openxmlformats-officedocument.presentationml.presentation + + + pqa + application/vnd.palm + + + prc + application/x-mobipocket-ebook + + + pre + application/vnd.lotus-freelance + + + prf + application/pics-rules + + + ps + application/postscript + + + psb + application/vnd.3gpp.pic-bw-small + + + psd + image/vnd.adobe.photoshop + + + psf + application/x-font-linux-psf + + + pskcxml + application/pskc+xml + + + ptid + application/vnd.pvi.ptid1 + + + pub + application/x-mspublisher + + + pvb + application/vnd.3gpp.pic-bw-var + + + pwn + application/vnd.3m.post-it-notes + + + pya + audio/vnd.ms-playready.media.pya + + + pyv + video/vnd.ms-playready.media.pyv + + + qam + application/vnd.epson.quickanime + + + qbo + application/vnd.intu.qbo + + + qfx + application/vnd.intu.qfx + + + qps + application/vnd.publishare-delta-tree + + + qt + video/quicktime + + + qti + image/x-quicktime + + + qtif + image/x-quicktime + + + qwd + application/vnd.quark.quarkxpress + + + qwt + application/vnd.quark.quarkxpress + + + qxb + application/vnd.quark.quarkxpress + + + qxd + application/vnd.quark.quarkxpress + + + qxl + application/vnd.quark.quarkxpress + + + qxt + application/vnd.quark.quarkxpress + + + ra + audio/x-pn-realaudio + + + ram + audio/x-pn-realaudio + + + rar + application/x-rar-compressed + + + ras + image/x-cmu-raster + + + rcprofile + application/vnd.ipunplugged.rcprofile + + + rdf + application/rdf+xml + + + rdz + application/vnd.data-vision.rdz + + + rep + application/vnd.businessobjects + + + res + application/x-dtbresource+xml + + + rgb + image/x-rgb + + + rif + application/reginfo+xml + + + rip + audio/vnd.rip + + + ris + application/x-research-info-systems + + + rl + application/resource-lists+xml + + + rlc + image/vnd.fujixerox.edmics-rlc + + + rld + application/resource-lists-diff+xml + + + rm + application/vnd.rn-realmedia + + + rmi + audio/midi + + + rmp + audio/x-pn-realaudio-plugin + + + rms + application/vnd.jcp.javame.midlet-rms + + + rmvb + application/vnd.rn-realmedia-vbr + + + rnc + application/relax-ng-compact-syntax + + + roa + application/rpki-roa + + + roff + text/troff + + + rp9 + application/vnd.cloanto.rp9 + + + rpss + application/vnd.nokia.radio-presets + + + rpst + application/vnd.nokia.radio-preset + + + rq + application/sparql-query + + + rs + application/rls-services+xml + + + rsd + application/rsd+xml + + + rss + application/rss+xml + + + rtf + application/rtf + + + rtx + text/richtext + + + s + text/x-asm + + + s3m + audio/s3m + + + saf + application/vnd.yamaha.smaf-audio + + + sbml + application/sbml+xml + + + sc + application/vnd.ibm.secure-container + + + scd + application/x-msschedule + + + scm + application/vnd.lotus-screencam + + + scq + application/scvp-cv-request + + + scs + application/scvp-cv-response + + + scurl + text/vnd.curl.scurl + + + sda + application/vnd.stardivision.draw + + + sdc + application/vnd.stardivision.calc + + + sdd + application/vnd.stardivision.impress + + + sdkd + application/vnd.solent.sdkm+xml + + + sdkm + application/vnd.solent.sdkm+xml + + + sdp + application/sdp + + + sdw + application/vnd.stardivision.writer + + + see + application/vnd.seemail + + + seed + application/vnd.fdsn.seed + + + sema + application/vnd.sema + + + semd + application/vnd.semd + + + semf + application/vnd.semf + + + ser + application/java-serialized-object + + + setpay + application/set-payment-initiation + + + setreg + application/set-registration-initiation + + + sfd-hdstx + application/vnd.hydrostatix.sof-data + + + sfs + application/vnd.spotfire.sfs + + + sfv + text/x-sfv + + + sgi + image/sgi + + + sgl + application/vnd.stardivision.writer-global + + + sgm + text/sgml + + + sgml + text/sgml + + + sh + application/x-sh + + + shar + application/x-shar + + + shf + application/shf+xml + + + + sid + image/x-mrsid-image + + + sig + application/pgp-signature + + + sil + audio/silk + + + silo + model/mesh + + + sis + application/vnd.symbian.install + + + sisx + application/vnd.symbian.install + + + sit + application/x-stuffit + + + sitx + application/x-stuffitx + + + skd + application/vnd.koan + + + skm + application/vnd.koan + + + skp + application/vnd.koan + + + skt + application/vnd.koan + + + sldm + application/vnd.ms-powerpoint.slide.macroenabled.12 + + + sldx + application/vnd.openxmlformats-officedocument.presentationml.slide + + + slt + application/vnd.epson.salt + + + sm + application/vnd.stepmania.stepchart + + + smf + application/vnd.stardivision.math + + + smi + application/smil+xml + + + smil + application/smil+xml + + + smv + video/x-smv + + + smzip + application/vnd.stepmania.package + + + snd + audio/basic + + + snf + application/x-font-snf + + + so + application/octet-stream + + + spc + application/x-pkcs7-certificates + + + spf + application/vnd.yamaha.smaf-phrase + + + spl + application/x-futuresplash + + + spot + text/vnd.in3d.spot + + + spp + application/scvp-vp-response + + + spq + application/scvp-vp-request + + + spx + audio/ogg + + + sql + application/x-sql + + + src + application/x-wais-source + + + srt + application/x-subrip + + + sru + application/sru+xml + + + srx + application/sparql-results+xml + + + ssdl + application/ssdl+xml + + + sse + application/vnd.kodak-descriptor + + + ssf + application/vnd.epson.ssf + + + ssml + application/ssml+xml + + + st + application/vnd.sailingtracker.track + + + stc + application/vnd.sun.xml.calc.template + + + std + application/vnd.sun.xml.draw.template + + + stf + application/vnd.wt.stf + + + sti + application/vnd.sun.xml.impress.template + + + stk + application/hyperstudio + + + stl + application/vnd.ms-pki.stl + + + str + application/vnd.pg.format + + + stw + application/vnd.sun.xml.writer.template + + + sub + text/vnd.dvb.subtitle + + + sus + application/vnd.sus-calendar + + + susp + application/vnd.sus-calendar + + + sv4cpio + application/x-sv4cpio + + + sv4crc + application/x-sv4crc + + + svc + application/vnd.dvb.service + + + svd + application/vnd.svd + + + svg + image/svg+xml + + + svgz + image/svg+xml + + + swa + application/x-director + + + swf + application/x-shockwave-flash + + + swi + application/vnd.aristanetworks.swi + + + sxc + application/vnd.sun.xml.calc + + + sxd + application/vnd.sun.xml.draw + + + sxg + application/vnd.sun.xml.writer.global + + + sxi + application/vnd.sun.xml.impress + + + sxm + application/vnd.sun.xml.math + + + sxw + application/vnd.sun.xml.writer + + + t + text/troff + + + t3 + application/x-t3vm-image + + + taglet + application/vnd.mynfc + + + tao + application/vnd.tao.intent-module-archive + + + tar + application/x-tar + + + tcap + application/vnd.3gpp2.tcap + + + tcl + application/x-tcl + + + teacher + application/vnd.smart.teacher + + + tei + application/tei+xml + + + teicorpus + application/tei+xml + + + tex + application/x-tex + + + texi + application/x-texinfo + + + texinfo + application/x-texinfo + + + text + text/plain + + + tfi + application/thraud+xml + + + tfm + application/x-tex-tfm + + + tga + image/x-tga + + + thmx + application/vnd.ms-officetheme + + + tif + image/tiff + + + tiff + image/tiff + + + tmo + application/vnd.tmobile-livetv + + + torrent + application/x-bittorrent + + + tpl + application/vnd.groove-tool-template + + + tpt + application/vnd.trid.tpt + + + tr + text/troff + + + tra + application/vnd.trueapp + + + trm + application/x-msterminal + + + tsd + application/timestamped-data + + + tsv + text/tab-separated-values + + + ttc + font/collection + + + ttf + font/ttf + + + ttl + text/turtle + + + twd + application/vnd.simtech-mindmapper + + + twds + application/vnd.simtech-mindmapper + + + txd + application/vnd.genomatix.tuxedo + + + txf + application/vnd.mobius.txf + + + txt + text/plain + + + u32 + application/x-authorware-bin + + + udeb + application/x-debian-package + + + ufd + application/vnd.ufdl + + + ufdl + application/vnd.ufdl + + + ulw + audio/basic + + + ulx + application/x-glulx + + + umj + application/vnd.umajin + + + unityweb + application/vnd.unity + + + uoml + application/vnd.uoml+xml + + + uri + text/uri-list + + + uris + text/uri-list + + + urls + text/uri-list + + + ustar + application/x-ustar + + + utz + application/vnd.uiq.theme + + + uu + text/x-uuencode + + + uva + audio/vnd.dece.audio + + + uvd + application/vnd.dece.data + + + uvf + application/vnd.dece.data + + + uvg + image/vnd.dece.graphic + + + uvh + video/vnd.dece.hd + + + uvi + image/vnd.dece.graphic + + + uvm + video/vnd.dece.mobile + + + uvp + video/vnd.dece.pd + + + uvs + video/vnd.dece.sd + + + uvt + application/vnd.dece.ttml+xml + + + uvu + video/vnd.uvvu.mp4 + + + uvv + video/vnd.dece.video + + + uvva + audio/vnd.dece.audio + + + uvvd + application/vnd.dece.data + + + uvvf + application/vnd.dece.data + + + uvvg + image/vnd.dece.graphic + + + uvvh + video/vnd.dece.hd + + + uvvi + image/vnd.dece.graphic + + + uvvm + video/vnd.dece.mobile + + + uvvp + video/vnd.dece.pd + + + uvvs + video/vnd.dece.sd + + + uvvt + application/vnd.dece.ttml+xml + + + uvvu + video/vnd.uvvu.mp4 + + + uvvv + video/vnd.dece.video + + + uvvx + application/vnd.dece.unspecified + + + uvvz + application/vnd.dece.zip + + + uvx + application/vnd.dece.unspecified + + + uvz + application/vnd.dece.zip + + + vcard + text/vcard + + + vcd + application/x-cdlink + + + vcf + text/x-vcard + + + vcg + application/vnd.groove-vcard + + + vcs + text/x-vcalendar + + + vcx + application/vnd.vcx + + + vis + application/vnd.visionary + + + viv + video/vnd.vivo + + + vob + video/x-ms-vob + + + vor + application/vnd.stardivision.writer + + + vox + application/x-authorware-bin + + + vrml + model/vrml + + + vsd + application/vnd.visio + + + vsf + application/vnd.vsf + + + vss + application/vnd.visio + + + vst + application/vnd.visio + + + vsw + application/vnd.visio + + + vtu + model/vnd.vtu + + + vxml + application/voicexml+xml + + + w3d + application/x-director + + + wad + application/x-doom + + + wav + audio/x-wav + + + wax + audio/x-ms-wax + + + + wbmp + image/vnd.wap.wbmp + + + wbs + application/vnd.criticaltools.wbs+xml + + + wbxml + application/vnd.wap.wbxml + + + wcm + application/vnd.ms-works + + + wdb + application/vnd.ms-works + + + wdp + image/vnd.ms-photo + + + weba + audio/webm + + + webm + video/webm + + + webp + image/webp + + + wg + application/vnd.pmi.widget + + + wgt + application/widget + + + wks + application/vnd.ms-works + + + wm + video/x-ms-wm + + + wma + audio/x-ms-wma + + + wmd + application/x-ms-wmd + + + wmf + application/x-msmetafile + + + + wml + text/vnd.wap.wml + + + + wmlc + application/vnd.wap.wmlc + + + + wmls + text/vnd.wap.wmlscript + + + + wmlsc + application/vnd.wap.wmlscriptc + + + wmv + video/x-ms-wmv + + + wmx + video/x-ms-wmx + + + wmz + application/x-msmetafile + + + woff + font/woff + + + woff2 + font/woff2 + + + wpd + application/vnd.wordperfect + + + wpl + application/vnd.ms-wpl + + + wps + application/vnd.ms-works + + + wqd + application/vnd.wqd + + + wri + application/x-mswrite + + + wrl + model/vrml + + + wsdl + application/wsdl+xml + + + wspolicy + application/wspolicy+xml + + + wtb + application/vnd.webturbo + + + wvx + video/x-ms-wvx + + + x32 + application/x-authorware-bin + + + x3d + model/x3d+xml + + + x3db + model/x3d+binary + + + x3dbz + model/x3d+binary + + + x3dv + model/x3d+vrml + + + x3dvz + model/x3d+vrml + + + x3dz + model/x3d+xml + + + xaml + application/xaml+xml + + + xap + application/x-silverlight-app + + + xar + application/vnd.xara + + + xbap + application/x-ms-xbap + + + xbd + application/vnd.fujixerox.docuworks.binder + + + xbm + image/x-xbitmap + + + xdf + application/xcap-diff+xml + + + xdm + application/vnd.syncml.dm+xml + + + xdp + application/vnd.adobe.xdp+xml + + + xdssc + application/dssc+xml + + + xdw + application/vnd.fujixerox.docuworks + + + xenc + application/xenc+xml + + + xer + application/patch-ops-error+xml + + + xfdf + application/vnd.adobe.xfdf + + + xfdl + application/vnd.xfdl + + + xht + application/xhtml+xml + + + xhtml + application/xhtml+xml + + + xhvml + application/xv+xml + + + xif + image/vnd.xiff + + + xla + application/vnd.ms-excel + + + xlam + application/vnd.ms-excel.addin.macroenabled.12 + + + xlc + application/vnd.ms-excel + + + xlf + application/x-xliff+xml + + + xlm + application/vnd.ms-excel + + + xls + application/vnd.ms-excel + + + xlsb + application/vnd.ms-excel.sheet.binary.macroenabled.12 + + + xlsm + application/vnd.ms-excel.sheet.macroenabled.12 + + + xlsx + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet + + + xlt + application/vnd.ms-excel + + + xltm + application/vnd.ms-excel.template.macroenabled.12 + + + xltx + application/vnd.openxmlformats-officedocument.spreadsheetml.template + + + xlw + application/vnd.ms-excel + + + xm + audio/xm + + + xml + application/xml + + + xo + application/vnd.olpc-sugar + + + xop + application/xop+xml + + + xpi + application/x-xpinstall + + + xpl + application/xproc+xml + + + xpm + image/x-xpixmap + + + xpr + application/vnd.is-xpr + + + xps + application/vnd.ms-xpsdocument + + + xpw + application/vnd.intercon.formnet + + + xpx + application/vnd.intercon.formnet + + + xsl + application/xml + + + xslt + application/xslt+xml + + + xsm + application/vnd.syncml+xml + + + xspf + application/xspf+xml + + + xul + application/vnd.mozilla.xul+xml + + + xvm + application/xv+xml + + + xvml + application/xv+xml + + + xwd + image/x-xwindowdump + + + xyz + chemical/x-xyz + + + xz + application/x-xz + + + yang + application/yang + + + yin + application/yin+xml + + + z + application/x-compress + + + Z + application/x-compress + + + z1 + application/x-zmachine + + + z2 + application/x-zmachine + + + z3 + application/x-zmachine + + + z4 + application/x-zmachine + + + z5 + application/x-zmachine + + + z6 + application/x-zmachine + + + z7 + application/x-zmachine + + + z8 + application/x-zmachine + + + zaz + application/vnd.zzazz.deck+xml + + + zip + application/zip + + + zir + application/vnd.zul + + + zirz + application/vnd.zul + + + zmm + application/vnd.handheld-entertainment+xml + + + + + + + + + + + + + + + + + + index.html + index.htm + index.jsp + + + diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/META-INF/MANIFEST.MF b/codes/java-ecology/java-ecology-tomcat/src/main/webapp/META-INF/MANIFEST.MF similarity index 100% rename from codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/META-INF/MANIFEST.MF rename to codes/java-ecology/java-ecology-tomcat/src/main/webapp/META-INF/MANIFEST.MF diff --git a/codes/java-ecology/java-ecology-tomcat/src/main/webapp/WEB-INF/web.xml b/codes/java-ecology/java-ecology-tomcat/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000..974a088b --- /dev/null +++ b/codes/java-ecology/java-ecology-tomcat/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,47 @@ + + + javatool + + + + spring-servlet + org.springframework.web.servlet.DispatcherServlet + 1 + + contextConfigLocation + classpath:spring/spring-servlet.xml + + + + spring-servlet + / + + + + + + + encodingFilter + org.springframework.web.filter.CharacterEncodingFilter + + encoding + UTF-8 + + + forceEncoding + true + + + + encodingFilter + /* + REQUEST + FORWARD + + + + + /views/jsp/index.jsp + + diff --git a/codes/java-ecology/java-ecology-tomcat/src/main/webapp/views/jsp/hello.jsp b/codes/java-ecology/java-ecology-tomcat/src/main/webapp/views/jsp/hello.jsp new file mode 100644 index 00000000..774fc949 --- /dev/null +++ b/codes/java-ecology/java-ecology-tomcat/src/main/webapp/views/jsp/hello.jsp @@ -0,0 +1,16 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> +<% + String path = request.getContextPath(); + String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; +%> + + + + + HelloController + + +

    ${message}

    +回到首页
    + + diff --git a/codes/java-ecology/java-ecology-tomcat/src/main/webapp/views/jsp/index.jsp b/codes/java-ecology/java-ecology-tomcat/src/main/webapp/views/jsp/index.jsp new file mode 100644 index 00000000..763fee86 --- /dev/null +++ b/codes/java-ecology/java-ecology-tomcat/src/main/webapp/views/jsp/index.jsp @@ -0,0 +1,28 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> +<% + String path = request.getContextPath(); + String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; +%> + + + + + javatool + + + +

    javatool

    +

    <%out.print("Server Ip:" + basePath);%>

    +
    +

    示例列表

    + +
    + + diff --git a/codes/javaweb-example/javaweb-zookeeper/pom.xml b/codes/java-ecology/java-ecology-zookeeper/pom.xml similarity index 74% rename from codes/javaweb-example/javaweb-zookeeper/pom.xml rename to codes/java-ecology/java-ecology-zookeeper/pom.xml index cb029670..4a133188 100644 --- a/codes/javaweb-example/javaweb-zookeeper/pom.xml +++ b/codes/java-ecology/java-ecology-zookeeper/pom.xml @@ -7,11 +7,11 @@ io.github.dunwu dunwu-parent - 0.5.0 + 0.5.1 - io.github.dunwu.javaweb - javaweb-zookeeper + io.github.dunwu.javatech + java-ecology-zookeeper 1.0.0 jar @@ -33,20 +33,10 @@ curator-recipes 4.2.0 - - io.github.dunwu - dunwu-common-core - 0.5.0 - io.github.dunwu dunwu-tool-core - 0.5.0 - - - com.google.guava - guava - 28.2-jre + 0.5.1 junit diff --git a/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/Callback.java b/codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/dlock/Callback.java similarity index 81% rename from codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/Callback.java rename to codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/dlock/Callback.java index e514ea55..4e4f1c79 100644 --- a/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/Callback.java +++ b/codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/dlock/Callback.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.zk.dlock; +package io.github.dunwu.javatech.zk.dlock; /** * Created by sunyujia@aliyun.com on 2016/2/23. diff --git a/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/DLockTemplate.java b/codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/dlock/DLockTemplate.java similarity index 88% rename from codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/DLockTemplate.java rename to codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/dlock/DLockTemplate.java index 673e5cb7..d15f51ad 100644 --- a/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/DLockTemplate.java +++ b/codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/dlock/DLockTemplate.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.zk.dlock; +package io.github.dunwu.javatech.zk.dlock; /** * 分布式锁模板类 Created by sunyujia@aliyun.com on 2016/2/23. diff --git a/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/DistributedLock.java b/codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/dlock/DistributedLock.java similarity index 86% rename from codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/DistributedLock.java rename to codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/dlock/DistributedLock.java index d487720d..2ac111e1 100644 --- a/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/DistributedLock.java +++ b/codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/dlock/DistributedLock.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.zk.dlock; +package io.github.dunwu.javatech.zk.dlock; import java.util.concurrent.TimeUnit; diff --git a/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/TimeoutHandler.java b/codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/dlock/TimeoutHandler.java similarity index 80% rename from codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/TimeoutHandler.java rename to codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/dlock/TimeoutHandler.java index 807bb848..89205564 100644 --- a/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/TimeoutHandler.java +++ b/codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/dlock/TimeoutHandler.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.zk.dlock; +package io.github.dunwu.javatech.zk.dlock; /** * @author Zhang Peng diff --git a/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZkDLockTemplate.java b/codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/dlock/ZkDLockTemplate.java similarity index 97% rename from codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZkDLockTemplate.java rename to codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/dlock/ZkDLockTemplate.java index 523c488e..ae18db85 100644 --- a/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZkDLockTemplate.java +++ b/codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/dlock/ZkDLockTemplate.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.zk.dlock; +package io.github.dunwu.javatech.zk.dlock; import org.apache.curator.framework.CuratorFramework; import org.slf4j.Logger; diff --git a/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZkReentrantLockCleanerTask.java b/codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/dlock/ZkReentrantLockCleanerTask.java similarity index 98% rename from codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZkReentrantLockCleanerTask.java rename to codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/dlock/ZkReentrantLockCleanerTask.java index 5d0a0ab7..17260096 100644 --- a/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZkReentrantLockCleanerTask.java +++ b/codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/dlock/ZkReentrantLockCleanerTask.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.zk.dlock; +package io.github.dunwu.javatech.zk.dlock; import org.apache.curator.RetryPolicy; import org.apache.curator.framework.CuratorFramework; diff --git a/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZookeeperReentrantDistributedLock.java b/codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/dlock/ZookeeperReentrantDistributedLock.java similarity index 98% rename from codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZookeeperReentrantDistributedLock.java rename to codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/dlock/ZookeeperReentrantDistributedLock.java index ed8045ba..79251f33 100644 --- a/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZookeeperReentrantDistributedLock.java +++ b/codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/dlock/ZookeeperReentrantDistributedLock.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.zk.dlock; +package io.github.dunwu.javatech.zk.dlock; import io.github.dunwu.tool.collection.CollectionUtil; import org.apache.curator.framework.CuratorFramework; diff --git a/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperConnection.java b/codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/example/ZooKeeperConnection.java similarity index 97% rename from codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperConnection.java rename to codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/example/ZooKeeperConnection.java index 521d464d..a82cb0ae 100644 --- a/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperConnection.java +++ b/codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/example/ZooKeeperConnection.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.zk.example; +package io.github.dunwu.javatech.zk.example; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; diff --git a/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperCreate.java b/codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/example/ZooKeeperCreate.java similarity index 97% rename from codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperCreate.java rename to codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/example/ZooKeeperCreate.java index 6ee7855c..819c6ed8 100644 --- a/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperCreate.java +++ b/codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/example/ZooKeeperCreate.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.zk.example; +package io.github.dunwu.javatech.zk.example; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; diff --git a/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperDelete.java b/codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/example/ZooKeeperDelete.java similarity index 96% rename from codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperDelete.java rename to codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/example/ZooKeeperDelete.java index 67d38178..72630cb1 100644 --- a/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperDelete.java +++ b/codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/example/ZooKeeperDelete.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.zk.example; +package io.github.dunwu.javatech.zk.example; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.ZooKeeper; diff --git a/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperExists.java b/codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/example/ZooKeeperExists.java similarity index 96% rename from codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperExists.java rename to codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/example/ZooKeeperExists.java index a903793e..846266ab 100644 --- a/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperExists.java +++ b/codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/example/ZooKeeperExists.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.zk.example; +package io.github.dunwu.javatech.zk.example; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.ZooKeeper; diff --git a/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperGetChildren.java b/codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/example/ZooKeeperGetChildren.java similarity index 97% rename from codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperGetChildren.java rename to codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/example/ZooKeeperGetChildren.java index 7b4f82e7..34372a6e 100644 --- a/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperGetChildren.java +++ b/codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/example/ZooKeeperGetChildren.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.zk.example; +package io.github.dunwu.javatech.zk.example; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.ZooKeeper; diff --git a/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperGetData.java b/codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/example/ZooKeeperGetData.java similarity index 98% rename from codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperGetData.java rename to codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/example/ZooKeeperGetData.java index bc888cf3..d151b3b5 100644 --- a/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperGetData.java +++ b/codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/example/ZooKeeperGetData.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.zk.example; +package io.github.dunwu.javatech.zk.example; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.WatchedEvent; diff --git a/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperSetData.java b/codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/example/ZooKeeperSetData.java similarity index 96% rename from codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperSetData.java rename to codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/example/ZooKeeperSetData.java index 44153cd3..e4c3ac62 100644 --- a/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperSetData.java +++ b/codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/example/ZooKeeperSetData.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.zk.example; +package io.github.dunwu.javatech.zk.example; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.ZooKeeper; diff --git a/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/package-info.java b/codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/example/package-info.java similarity index 73% rename from codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/package-info.java rename to codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/example/package-info.java index 7b356a38..21f2ef05 100644 --- a/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/package-info.java +++ b/codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/example/package-info.java @@ -4,4 +4,4 @@ * @author Zhang Peng * @since 2020-01-13 */ -package io.github.dunwu.javaweb.zk.example; +package io.github.dunwu.javatech.zk.example; diff --git a/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/sequence/DistributedSequence.java b/codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/sequence/DistributedSequence.java similarity index 76% rename from codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/sequence/DistributedSequence.java rename to codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/sequence/DistributedSequence.java index aa28f666..b7597338 100644 --- a/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/sequence/DistributedSequence.java +++ b/codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/sequence/DistributedSequence.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.zk.sequence; +package io.github.dunwu.javatech.zk.sequence; /** * Created by sunyujia@aliyun.com on 2016/2/25. diff --git a/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/sequence/ZkDistributedSequence.java b/codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/sequence/ZkDistributedSequence.java similarity index 93% rename from codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/sequence/ZkDistributedSequence.java rename to codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/sequence/ZkDistributedSequence.java index 1c8920fa..59cb174d 100644 --- a/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/sequence/ZkDistributedSequence.java +++ b/codes/java-ecology/java-ecology-zookeeper/src/main/java/io/github/dunwu/javatech/zk/sequence/ZkDistributedSequence.java @@ -1,6 +1,6 @@ -package io.github.dunwu.javaweb.zk.sequence; +package io.github.dunwu.javatech.zk.sequence; -import io.github.dunwu.javaweb.zk.dlock.ZkReentrantLockCleanerTask; +import io.github.dunwu.javatech.zk.dlock.ZkReentrantLockCleanerTask; import org.apache.curator.RetryPolicy; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; diff --git a/codes/javaweb-example/javaweb-zookeeper/src/test/java/io/github/dunwu/javaweb/zk/dlock/ZkReentrantLockTemplateTest.java b/codes/java-ecology/java-ecology-zookeeper/src/test/java/io/github/dunwu/javatech/zk/dlock/ZkReentrantLockTemplateTest.java similarity index 98% rename from codes/javaweb-example/javaweb-zookeeper/src/test/java/io/github/dunwu/javaweb/zk/dlock/ZkReentrantLockTemplateTest.java rename to codes/java-ecology/java-ecology-zookeeper/src/test/java/io/github/dunwu/javatech/zk/dlock/ZkReentrantLockTemplateTest.java index 52f5c65a..ba2bfb65 100644 --- a/codes/javaweb-example/javaweb-zookeeper/src/test/java/io/github/dunwu/javaweb/zk/dlock/ZkReentrantLockTemplateTest.java +++ b/codes/java-ecology/java-ecology-zookeeper/src/test/java/io/github/dunwu/javatech/zk/dlock/ZkReentrantLockTemplateTest.java @@ -1,4 +1,4 @@ -package io.github.dunwu.javaweb.zk.dlock; +package io.github.dunwu.javatech.zk.dlock; import org.apache.curator.RetryPolicy; import org.apache.curator.framework.CuratorFramework; diff --git a/codes/javaweb-example/javaweb-zookeeper/src/test/resources/logback.xml b/codes/java-ecology/java-ecology-zookeeper/src/test/resources/logback.xml similarity index 100% rename from codes/javaweb-example/javaweb-zookeeper/src/test/resources/logback.xml rename to codes/java-ecology/java-ecology-zookeeper/src/test/resources/logback.xml diff --git a/codes/java-ecology/pom.xml b/codes/java-ecology/pom.xml new file mode 100644 index 00000000..b0b3bc51 --- /dev/null +++ b/codes/java-ecology/pom.xml @@ -0,0 +1,24 @@ + + + 4.0.0 + + io.github.dunwu.javatech + java-ecology + 1.0.0 + pom + JAVA-ECOLOGY + JAVA-ECOLOGY 示例源码 + + + java-ecology-zookeeper + java-ecology-tomcat + java-ecology-mq + java-ecology-io + java-ecology-log + java-ecology-test + java-ecology-text + java-ecology-others + + + diff --git a/codes/javaee-tutorial/README.md b/codes/javaee-tutorial/README.md deleted file mode 100644 index 7587bd07..00000000 --- a/codes/javaee-tutorial/README.md +++ /dev/null @@ -1 +0,0 @@ -# JavaEE 示例代码 \ No newline at end of file diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/pom.xml b/codes/javaee-tutorial/javaee-tutorial-filter/pom.xml deleted file mode 100644 index 13461775..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/pom.xml +++ /dev/null @@ -1,113 +0,0 @@ - - 4.0.0 - - io.github.dunwu - javaee-tutorial-filter - 1.0.0 - war - javaee-tutorial-filter - JavaEE 学习笔记之 Filter - - - UTF-8 - 1.7 - ${java.version} - ${java.version} - - - - - - ch.qos.logback - logback-classic - - - ch.qos.logback - logback-core - - - org.slf4j - jcl-over-slf4j - - - - - - net.coobird - thumbnailator - - - - - - commons-fileupload - commons-fileupload - - - commons-io - commons-io - - - org.apache.commons - commons-lang3 - - - - - - javax.servlet - javax.servlet-api - provided - - - - - - org.eclipse.jetty - jetty-webapp - test - - - org.eclipse.jetty - jetty-server - test - - - org.eclipse.jetty - jetty-annotations - test - - - org.eclipse.jetty - apache-jsp - test - - - - - - junit - junit - test - - - org.assertj - assertj-core - test - - - - - - - - io.github.dunwu - dunwu-dependencies - 0.4.2-SNAPSHOT - pom - import - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/CacheFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/CacheFilter.java deleted file mode 100644 index 22d245cd..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/CacheFilter.java +++ /dev/null @@ -1,94 +0,0 @@ -package io.github.dunwu.javaee.filter; - -import io.github.dunwu.javaee.filter.wrapper.CacheResponseWrapper; -import java.net.URLEncoder; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * @author Zhang Peng - * @since 2017/3/27. - */ -public class CacheFilter extends MyFilter { - - private ServletContext servletContext; - - // 缓存文件夹,使用Tomcat工作目录 - private File temporalDir; - - // 缓存时间,配置在Filter初始化参数中 - private long cacheTime = Long.MAX_VALUE; - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - super.init(filterConfig); - temporalDir = (File) filterConfig.getServletContext().getAttribute("javax.servlet.context.tempdir"); - servletContext = filterConfig.getServletContext(); - cacheTime = new Long(filterConfig.getInitParameter("cacheTime")); - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { - - logger.info("{} 开始做过滤处理", this.getClass().getName()); - - HttpServletRequest httpServletRequest = (HttpServletRequest) request; - HttpServletResponse httpServletResponse = (HttpServletResponse) response; - - // 如果为 POST, 则不经过缓存 - if ("POST".equalsIgnoreCase(httpServletRequest.getMethod())) { - chain.doFilter(httpServletRequest, httpServletResponse); - return; - } - - // 请求的 URI - String uri = httpServletRequest.getRequestURI(); - if (uri == null) { - uri = ""; - } - uri = uri.replace(httpServletRequest.getContextPath() + "/", ""); - uri = uri.trim().length() == 0 ? "index.jsp" : uri; - uri = httpServletRequest.getQueryString() == null ? uri : (uri + "?" + httpServletRequest.getQueryString()); - - // 对应的缓存文件 - File cacheFile = new File(temporalDir, URLEncoder.encode(uri, "UTF-8")); - System.out.println(cacheFile); - - // 如果缓存文件不存在 或者已经超出缓存时间 则请求 Servlet - if (!cacheFile.exists() || cacheFile.length() == 0 - || cacheFile.lastModified() < System.currentTimeMillis() - cacheTime) { - - CacheResponseWrapper cacheResponse = new CacheResponseWrapper(httpServletResponse); - - chain.doFilter(httpServletRequest, cacheResponse); - - // 将内容写入缓存文件 - char[] content = cacheResponse.getCacheWriter().toCharArray(); - - temporalDir.mkdirs(); - cacheFile.createNewFile(); - - Writer writer = new OutputStreamWriter(new FileOutputStream(cacheFile), "UTF-8"); - writer.write(content); - writer.close(); - } - - // 请求的ContentType - String mimeType = servletContext.getMimeType(httpServletRequest.getRequestURI()); - httpServletResponse.setContentType(mimeType); - - // 读取缓存文件的内容,写入客户端浏览器 - Reader ins = new InputStreamReader(new FileInputStream(cacheFile), "UTF-8"); - StringBuffer buffer = new StringBuffer(); - char[] cbuf = new char[1024]; - int len; - while ((len = ins.read(cbuf)) > -1) { - buffer.append(cbuf, 0, len); - } - ins.close(); - // 输出到客户端 - httpServletResponse.getWriter().write(buffer.toString()); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/CharacterEncodingFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/CharacterEncodingFilter.java deleted file mode 100644 index 545189fc..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/CharacterEncodingFilter.java +++ /dev/null @@ -1,60 +0,0 @@ -package io.github.dunwu.javaee.filter; - -import io.github.dunwu.javaee.filter.wrapper.UploadRequestWrapper; -import java.io.IOException; -import javax.servlet.http.HttpServletRequest; -import org.apache.commons.lang3.StringUtils; - -/** - * @author Zhang Peng - * @since 2017/3/27. - */ -public class CharacterEncodingFilter extends MyFilter { - - private String characterEncoding; - - private boolean enabled; - - @Override - public void init(FilterConfig config) throws ServletException { - super.init(config); - - characterEncoding = config.getInitParameter("characterEncoding"); - enabled = "true".equalsIgnoreCase(characterEncoding.trim()) || "1".equalsIgnoreCase(characterEncoding.trim()); - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { - logger.info("{} 开始做过滤处理", this.getClass().getName()); - - if (enabled || StringUtils.isNotBlank(characterEncoding)) { - request.setCharacterEncoding(characterEncoding); - response.setCharacterEncoding(characterEncoding); - } - - logger.info("系统设置HTTP请求和应答的默认编码为 {}", characterEncoding); - chain.doFilter(request, response); - } - - public static class UploadFilter implements Filter { - - public void destroy() { - - } - - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { - - UploadRequestWrapper uploadRequest = new UploadRequestWrapper((HttpServletRequest) request); - - chain.doFilter(uploadRequest, response); - } - - public void init(FilterConfig filterConfig) throws ServletException { - - } - - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/ExceptionHandlerFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/ExceptionHandlerFilter.java deleted file mode 100644 index aba7ca1a..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/ExceptionHandlerFilter.java +++ /dev/null @@ -1,48 +0,0 @@ -package io.github.dunwu.javaee.filter; - -import io.github.dunwu.javaee.filter.exception.AccountException; -import io.github.dunwu.javaee.filter.exception.BusinessException; -import java.io.IOException; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; - -/** - * @author Zhang Peng - * @since 2017/3/27. - */ -public class ExceptionHandlerFilter extends MyFilter { - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { - - try { - chain.doFilter(request, response); - } catch (Exception e) { - logger.info("{} 捕捉到异常", this.getClass().getName()); - Throwable rootCause = e; - - while (rootCause.getCause() != null) { - rootCause = rootCause.getCause(); - } - - String message = rootCause.getMessage(); - - message = message == null ? "异常:" + rootCause.getClass().getName() : message; - - request.setAttribute("message", message); - request.setAttribute("e", e); - - if (rootCause instanceof AccountException) { - request.getRequestDispatcher("/views/jsp/accountException.jsp").forward(request, response); - } else if (rootCause instanceof BusinessException) { - request.getRequestDispatcher("/views/jsp/businessException.jsp").forward(request, response); - } else { - request.getRequestDispatcher("/views/jsp/exception.jsp").forward(request, response); - } - } - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/FilterImpl.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/FilterImpl.java deleted file mode 100644 index be6e3ea6..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/FilterImpl.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.github.dunwu.javaee.filter; - -import java.io.IOException; - -/** - * @author Zhang Peng - * @since 2017/3/27. - */ -public class FilterImpl implements Filter { - - private boolean enable; - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - // 初始化代码 - enable = "true".equals(filterConfig.getInitParameter("enable")); - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { - - System.out.println("befor doFilter(). "); - - chain.doFilter(request, response); - - System.out.println("after doFitler(). "); - } - - @Override - public void destroy() { - // 资源销毁代码 - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/GZipFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/GZipFilter.java deleted file mode 100644 index 96a39a07..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/GZipFilter.java +++ /dev/null @@ -1,44 +0,0 @@ -package io.github.dunwu.javaee.filter; - -import io.github.dunwu.javaee.filter.wrapper.GZipResponseWrapper; -import java.io.IOException; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * @author Zhang Peng - * @since 2017/3/27. - */ -public class GZipFilter extends MyFilter { - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { - - logger.info("{} 开始做过滤处理", this.getClass().getName()); - - HttpServletRequest httpServletRequest = (HttpServletRequest) request; - HttpServletResponse httpServletResponse = (HttpServletResponse) response; - - String acceptEncoding = httpServletRequest.getHeader("Accept-Encoding"); - System.out.println("Accept-Encoding: " + acceptEncoding); - - if (acceptEncoding != null && acceptEncoding.toLowerCase().indexOf("gzip") != -1) { - - // 如果客户浏览器支持 GZIP 格式, 则使用 GZIP 压缩数据 - GZipResponseWrapper gzipResponse = new GZipResponseWrapper(httpServletResponse); - chain.doFilter(httpServletRequest, gzipResponse); - - // 输出压缩数据 - gzipResponse.finishResponse(); - } else { - // 否则, 不压缩 - chain.doFilter(httpServletRequest, httpServletResponse); - } - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/ImageRedirectFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/ImageRedirectFilter.java deleted file mode 100644 index 9fca074a..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/ImageRedirectFilter.java +++ /dev/null @@ -1,44 +0,0 @@ -package io.github.dunwu.javaee.filter; - -import java.io.IOException; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * @author Zhang Peng - * @since 2017/3/27. - */ -public class ImageRedirectFilter extends MyFilter { - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { - - logger.info("{} 开始做过滤处理", this.getClass().getName()); - - HttpServletRequest httpServletRequest = (HttpServletRequest) request; - HttpServletResponse httpServletResponse = (HttpServletResponse) response; - - // 禁止缓存 - httpServletResponse.setHeader("Cache-Control", "no-store"); - httpServletResponse.setHeader("Pragrma", "no-cache"); - httpServletResponse.setDateHeader("Expires", 0); - - // 链接来源地址 - String referer = httpServletRequest.getHeader("referer"); - - if (referer == null || !referer.contains(httpServletRequest.getServerName())) { - // 如果 链接地址来自其他网站,则返回错误图片 - httpServletRequest.getRequestDispatcher("/views/images/error.gif").forward(httpServletRequest, - httpServletResponse); - } else { - // 图片正常显示 - chain.doFilter(httpServletRequest, httpServletResponse); - } - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/LogFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/LogFilter.java deleted file mode 100644 index c57eb31e..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/LogFilter.java +++ /dev/null @@ -1,36 +0,0 @@ -package io.github.dunwu.javaee.filter; - -import java.io.IOException; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * @author Zhang Peng - * @since 2017/3/27. - */ -public class LogFilter extends MyFilter { - - @Override - public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) - throws IOException, ServletException { - - HttpServletRequest request = (HttpServletRequest) req; - HttpServletResponse response = (HttpServletResponse) res; - - long startTime = System.currentTimeMillis(); - String requestURI = request.getRequestURI(); - - requestURI = request.getQueryString() == null ? requestURI : (requestURI + "?" + request.getQueryString()); - - chain.doFilter(request, response); - - long endTime = System.currentTimeMillis(); - - logger.info("{} 访问了 {},总用时 {} 毫秒", request.getRemoteAddr(), requestURI, (endTime - startTime)); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/MyFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/MyFilter.java deleted file mode 100644 index 33426b0f..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/MyFilter.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * - */ -package io.github.dunwu.javaee.filter; - -import java.io.IOException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * @author Zhang Peng - * @since 2017/3/27. - */ -public abstract class MyFilter implements Filter { - - protected final Logger logger = LoggerFactory.getLogger(this.getClass()); - - private String filterName; - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - // 获取 Filter 的 name,配置在 web.xml 中 - filterName = filterConfig.getFilterName(); - logger.info("启动 Filter: {}", filterName); - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { - } - - @Override - public void destroy() { - logger.info("关闭 Filter: {}", filterName); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/OutputReplaceFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/OutputReplaceFilter.java deleted file mode 100644 index 66a881c9..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/OutputReplaceFilter.java +++ /dev/null @@ -1,56 +0,0 @@ -package io.github.dunwu.javaee.filter; - -import io.github.dunwu.javaee.filter.wrapper.HttpCharacterResponseWrapper; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.Properties; -import javax.servlet.http.HttpServletResponse; - -/** - * @author Zhang Peng - * @since 2017/3/27. - */ -public class OutputReplaceFilter extends MyFilter { - - private Properties properties = new Properties(); - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - super.init(filterConfig); - String file = filterConfig.getInitParameter("file"); - String realPath = filterConfig.getServletContext().getRealPath(file); - try { - properties.load(new FileInputStream(realPath)); - } catch (IOException e) { - e.printStackTrace(); - } - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { - logger.info("{} 开始做过滤处理", this.getClass().getName()); - - // 自定义的 response - HttpCharacterResponseWrapper wrapper = new HttpCharacterResponseWrapper((HttpServletResponse) response); - - // 提交给 Servlet 或者下一个 Filter - chain.doFilter(request, wrapper); - - // 得到缓存在自定义 response 中的输出内容 - String output = wrapper.getCharArrayWriter().toString(); - - // 修改,替换 - for (Object obj : properties.keySet()) { - String key = (String) obj; - output = output.replace(key, properties.getProperty(key)); - } - - // 输出 - PrintWriter out = response.getWriter(); - out.write(output); - out.println(""); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/PrivilegeFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/PrivilegeFilter.java deleted file mode 100644 index 293daf5a..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/PrivilegeFilter.java +++ /dev/null @@ -1,77 +0,0 @@ -package io.github.dunwu.javaee.filter; - -import io.github.dunwu.javaee.filter.exception.AccountException; -import java.io.FileInputStream; -import java.io.IOException; -import java.util.Properties; -import javax.servlet.http.HttpServletRequest; - -/** - * @author Zhang Peng - * @since 2017/3/27. - */ -public class PrivilegeFilter extends MyFilter { - - private Properties pp = new Properties(); - - @Override - public void init(FilterConfig config) throws ServletException { - - // 从 初始化参数 中获取权 限配置文件 的位置 - String file = config.getInitParameter("file"); - String realPath = config.getServletContext().getRealPath(file); - try { - pp.load(new FileInputStream(realPath)); - } catch (Exception e) { - config.getServletContext().log("读取权限控制文件失败。", e); - } - } - - @Override - public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) - throws IOException, ServletException { - - logger.info("{} 开始做过滤处理", this.getClass().getName()); - - HttpServletRequest request = (HttpServletRequest) req; - - // 获取访问的路径,例如:admin.jsp - String requestURI = request.getRequestURI().replace(request.getContextPath() + "/", ""); - - // 获取 action 参数,例如:add - String action = req.getParameter("action"); - action = action == null ? "" : action; - - // 拼接成 URI。例如:log.do?action=list - String uri = requestURI + "?action=" + action; - - // 从 session 中获取用户权限角色。 - String role = (String) request.getSession(true).getAttribute("role"); - role = role == null ? "guest" : role; - - boolean authentificated = false; - // 开始检查该用户角色是否有权限访问 uri - for (Object obj : pp.keySet()) { - String key = ((String) obj); - // 使用正则表达式验证 需要将 ? . 替换一下,并将通配符 * 处理一下 - if (uri.matches(key.replace("?", "\\?").replace(".", "\\.").replace("*", ".*"))) { - // 如果 role 匹配 - if (role.equals(pp.get(key))) { - authentificated = true; - break; - } - } - } - if (!authentificated) { - throw new RuntimeException(new AccountException("您无权访问该页面。请以合适的身份登陆后查看。")); - } - // 继续运行 - chain.doFilter(req, res); - } - - @Override - public void destroy() { - pp = null; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/UploadFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/UploadFilter.java deleted file mode 100644 index 3ae65193..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/UploadFilter.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.github.dunwu.javaee.filter; - -import io.github.dunwu.javaee.filter.wrapper.UploadRequestWrapper; -import java.io.IOException; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; - -/** - * @author Zhang Peng - * @since 2017-04-04 - */ -public class UploadFilter extends MyFilter { - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { - UploadRequestWrapper uploadRequest = new UploadRequestWrapper((HttpServletRequest) request); - chain.doFilter(uploadRequest, response); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/WaterMarkFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/WaterMarkFilter.java deleted file mode 100644 index a5c6166a..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/WaterMarkFilter.java +++ /dev/null @@ -1,47 +0,0 @@ -package io.github.dunwu.javaee.filter; - -import io.github.dunwu.javaee.filter.wrapper.WaterMarkResponseWrapper; -import java.io.IOException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * @author Zhang Peng - * @since 2017/3/27. - */ -public class WaterMarkFilter extends MyFilter { - - // 水印图片,配置在初始化参数中 - private String waterMarkFile; - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - super.init(filterConfig); - String file = filterConfig.getInitParameter("waterMarkFile"); - waterMarkFile = filterConfig.getServletContext().getRealPath(file); - } - - @Override - public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) - throws IOException, ServletException { - logger.info("{} 开始做过滤处理", this.getClass().getName()); - - HttpServletRequest request = (HttpServletRequest) req; - HttpServletResponse response = (HttpServletResponse) res; - - String requestURI = request.getRequestURI(); - - String originImageFile = request.getServletContext().getRealPath("/") + requestURI; - - // 自定义的response - WaterMarkResponseWrapper waterMarkRes = new WaterMarkResponseWrapper(response, originImageFile, waterMarkFile); - - chain.doFilter(request, waterMarkRes); - - // 打水印,输出到客户端浏览器 - waterMarkRes.finishResponse(); - - logger.info("图片 {} 已添加水印图片 {}", originImageFile, waterMarkFile); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/XSLTFilter.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/XSLTFilter.java deleted file mode 100644 index 96bd240f..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/XSLTFilter.java +++ /dev/null @@ -1,68 +0,0 @@ -package io.github.dunwu.javaee.filter; - -import java.io.CharArrayWriter; -import java.io.IOException; -import java.io.PrintWriter; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.xml.transform.Source; -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.stream.StreamResult; -import javax.xml.transform.stream.StreamSource; - -/** - * @author Zhang Peng - * @since 2017/3/28. - */ -public class XSLTFilter extends MyFilter { - - private ServletContext servletContext; - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - super.init(filterConfig); - servletContext = filterConfig.getServletContext(); - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { - - logger.info("{} 开始做过滤处理", this.getClass().getName()); - - HttpServletRequest httpServletRequest = (HttpServletRequest) request; - HttpServletResponse httpServletResponse = (HttpServletResponse) response; - - // 格式样本文件:/book.xsl - Source styleSource = new StreamSource(servletContext.getRealPath("/views/xml/messageLog.xsl")); - - // 请求的 xml 文件 - Source xmlSource = new StreamSource(servletContext - .getRealPath(httpServletRequest.getRequestURI().replace(httpServletRequest.getContextPath() + "", ""))); - try { - - // 转换器工厂 - TransformerFactory transformerFactory = TransformerFactory.newInstance(); - - // 转换器 - Transformer transformer = transformerFactory.newTransformer(styleSource); - - // 将转换的结果保存到该对象中 - CharArrayWriter charArrayWriter = new CharArrayWriter(); - StreamResult result = new StreamResult(charArrayWriter); - - // 转换 - transformer.transform(xmlSource, result); - - // 输出转换后的结果 - httpServletResponse.setContentType("text/html"); - httpServletResponse.setContentLength(charArrayWriter.toString().length()); - PrintWriter out = httpServletResponse.getWriter(); - out.write(charArrayWriter.toString()); - } catch (Exception e) { - e.printStackTrace(); - } - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/exception/AccountException.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/exception/AccountException.java deleted file mode 100644 index 96a6d42b..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/exception/AccountException.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.github.dunwu.javaee.filter.exception; - -public class AccountException extends Exception { - - private static final long serialVersionUID = -3040955562136599570L; - - public AccountException(String msg) { - super(msg); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/exception/BusinessException.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/exception/BusinessException.java deleted file mode 100644 index c3662556..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/exception/BusinessException.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.github.dunwu.javaee.filter.exception; - -public class BusinessException extends Exception { - - private static final long serialVersionUID = -3040955562136599570L; - - public BusinessException(String msg) { - super(msg); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/test/Download.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/test/Download.java deleted file mode 100644 index 26c115bf..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/test/Download.java +++ /dev/null @@ -1,36 +0,0 @@ -package io.github.dunwu.javaee.filter.test; - -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.net.URL; - -public class Download { - - public static void main(String[] args) throws Exception { - System.out.println(getContent("http://localhost:8080/filter/book/thinkInJava.xml")); - } - - public static String getContent(String url) throws Exception { - - URL r = new URL(url); - - r.openConnection(); - - InputStream ins = r.openStream(); - - Reader reader = new InputStreamReader(ins); - - int len = 0; - char[] tmp = new char[1024]; - - StringBuffer buffer = new StringBuffer(); - - while ((len = reader.read(tmp)) != -1) { - buffer.append(tmp, 0, len); - } - - return buffer.toString(); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/test/GZipTest.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/test/GZipTest.java deleted file mode 100644 index 9b4899aa..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/test/GZipTest.java +++ /dev/null @@ -1,34 +0,0 @@ -package io.github.dunwu.javaee.filter.test; - -import java.net.URL; -import java.net.URLConnection; -import java.text.NumberFormat; - -public class GZipTest { - - public static void main(String[] args) throws Exception { - test("http://localhost:8080/filter/dojo/dojo.js"); - test("http://localhost:8080/filter/image.jsp"); - test("http://localhost:8080/filter/winter.jpg"); - } - - public static void test(String url) throws Exception { - - /** 支持 GZIP 的连接 */ - URLConnection connGzip = new URL(url).openConnection(); - connGzip.setRequestProperty("Accept-Encoding", "gzip"); - int lengthGzip = connGzip.getContentLength(); - - /** 不支持 GZIP 的连接 */ - URLConnection connCommon = new URL(url).openConnection(); - int lengthCommon = connCommon.getContentLength(); - - double rate = new Double(lengthGzip) / lengthCommon; - - System.out.println("网址: " + url); - System.out.println("压缩后: " + lengthGzip + " byte, \t压缩前: " + lengthCommon + " byte, \t比率: " - + NumberFormat.getPercentInstance().format(rate)); - System.out.println(); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/CacheResponseWrapper.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/CacheResponseWrapper.java deleted file mode 100644 index 79d8bf0c..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/CacheResponseWrapper.java +++ /dev/null @@ -1,44 +0,0 @@ -package io.github.dunwu.javaee.filter.wrapper; - -import java.io.CharArrayWriter; -import java.io.IOException; -import java.io.PrintWriter; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpServletResponseWrapper; - -/** - * @author Zhang Peng - * @since 2017/3/27. - */ -public class CacheResponseWrapper extends HttpServletResponseWrapper { - - // 缓存字符类输出 - private CharArrayWriter cacheWriter = new CharArrayWriter(); - - public CacheResponseWrapper(HttpServletResponse response) throws IOException { - super(response); - } - - @Override - public PrintWriter getWriter() throws IOException { - return new PrintWriter(cacheWriter); - } - - @Override - public void flushBuffer() throws IOException { - cacheWriter.flush(); - } - - public void finishResponse() throws IOException { - cacheWriter.close(); - } - - public CharArrayWriter getCacheWriter() { - return cacheWriter; - } - - public void setCacheWriter(CharArrayWriter cacheWriter) { - this.cacheWriter = cacheWriter; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/GZipOutputStream.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/GZipOutputStream.java deleted file mode 100644 index 48e1adc1..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/GZipOutputStream.java +++ /dev/null @@ -1,74 +0,0 @@ -package io.github.dunwu.javaee.filter.wrapper; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.zip.GZIPOutputStream; -import javax.servlet.ServletOutputStream; -import javax.servlet.WriteListener; -import javax.servlet.http.HttpServletResponse; - -/** - * @author Zhang Peng - * @since 2017/3/27. - */ -public class GZipOutputStream extends ServletOutputStream { - - private HttpServletResponse response; - - // JDK 自带的压缩数据的类 - private GZIPOutputStream gzipOutputStream; - - // 将压缩后的数据存放到 ByteArrayOutputStream 对象中 - private ByteArrayOutputStream byteArrayOutputStream; - - public GZipOutputStream(HttpServletResponse response) throws IOException { - this.response = response; - byteArrayOutputStream = new ByteArrayOutputStream(); - gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream); - } - - public void write(int b) throws IOException { - gzipOutputStream.write(b); - } - - public void close() throws IOException { - - // 压缩完毕 一定要调用该方法 - gzipOutputStream.finish(); - - // 将压缩后的数据输出到客户端 - byte[] content = byteArrayOutputStream.toByteArray(); - - // 设定压缩方式为 GZIP, 客户端浏览器会自动将数据解压 - response.addHeader("Content-Encoding", "gzip"); - response.addHeader("Content-Length", Integer.toString(content.length)); - - // 输出 - ServletOutputStream out = response.getOutputStream(); - out.write(content); - out.close(); - } - - public void flush() throws IOException { - gzipOutputStream.flush(); - } - - public void write(byte[] b, int off, int len) throws IOException { - gzipOutputStream.write(b, off, len); - } - - public void write(byte[] b) throws IOException { - gzipOutputStream.write(b); - } - - @Override - public boolean isReady() { - return false; - } - - @Override - public void setWriteListener(WriteListener writeListener) { - - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/GZipResponseWrapper.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/GZipResponseWrapper.java deleted file mode 100644 index f230b860..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/GZipResponseWrapper.java +++ /dev/null @@ -1,61 +0,0 @@ -package io.github.dunwu.javaee.filter.wrapper; - -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import javax.servlet.ServletOutputStream; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpServletResponseWrapper; - -/** - * @author Zhang Peng - * @since 2017/3/27. - */ -public class GZipResponseWrapper extends HttpServletResponseWrapper { - - // 默认的 response - private HttpServletResponse response; - - // 自定义的 outputStream, 执行close()的时候对数据压缩,并输出 - private GZipOutputStream gzipOutputStream; - - // 自定义 printWriter,将内容输出到 GZipOutputStream 中 - private PrintWriter writer; - - public GZipResponseWrapper(HttpServletResponse response) throws IOException { - super(response); - this.response = response; - } - - public ServletOutputStream getOutputStream() throws IOException { - if (gzipOutputStream == null) { - gzipOutputStream = new GZipOutputStream(response); - } - return gzipOutputStream; - } - - public PrintWriter getWriter() throws IOException { - if (writer == null) { - writer = new PrintWriter(new OutputStreamWriter(new GZipOutputStream(response), "UTF-8")); - } - return writer; - } - - // 压缩后数据长度会发生变化 因此将该方法内容置空 - public void setContentLength(int contentLength) { - } - - public void flushBuffer() throws IOException { - gzipOutputStream.flush(); - } - - public void finishResponse() throws IOException { - if (gzipOutputStream != null) { - gzipOutputStream.close(); - } - if (writer != null) { - writer.close(); - } - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/HttpCharacterResponseWrapper.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/HttpCharacterResponseWrapper.java deleted file mode 100644 index d9798ee2..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/HttpCharacterResponseWrapper.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.github.dunwu.javaee.filter.wrapper; - -import java.io.CharArrayWriter; -import java.io.IOException; -import java.io.PrintWriter; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpServletResponseWrapper; - -/** - * @author Zhang Peng - * @since 2017/3/27. - */ -public class HttpCharacterResponseWrapper extends HttpServletResponseWrapper { - - private CharArrayWriter charArrayWriter = new CharArrayWriter(); - - public HttpCharacterResponseWrapper(HttpServletResponse response) { - super(response); - } - - @Override - public PrintWriter getWriter() throws IOException { - return new PrintWriter(charArrayWriter); - } - - public CharArrayWriter getCharArrayWriter() { - return charArrayWriter; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/UploadRequestWrapper.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/UploadRequestWrapper.java deleted file mode 100644 index 612bf7c2..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/UploadRequestWrapper.java +++ /dev/null @@ -1,105 +0,0 @@ -package io.github.dunwu.javaee.filter.wrapper; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.OutputStream; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletRequestWrapper; -import org.apache.commons.fileupload.DiskFileUpload; -import org.apache.commons.fileupload.FileItem; - -/** - * @author Zhang Peng - * @since 2017/3/27. - */ -public class UploadRequestWrapper extends HttpServletRequestWrapper { - - private static final String MULTIPART_HEADER = "Content-type"; - - // 是否是上传文件 - private boolean multipart; - - // map,保存所有的域 - private Map params = new HashMap(); - - @SuppressWarnings("all") - public UploadRequestWrapper(HttpServletRequest request) { - - super(request); - - // 判断是否为上传文件 - multipart = request.getHeader(MULTIPART_HEADER) != null - && request.getHeader(MULTIPART_HEADER).startsWith("multipart/form-data"); - - if (multipart) { - - try { - // 使用apache的工具解析 - DiskFileUpload upload = new DiskFileUpload(); - upload.setHeaderEncoding("utf8"); - - // 解析,获得所有的文本域与文件域 - List fileItems = upload.parseRequest(request); - - for (Iterator it = fileItems.iterator(); it.hasNext(); ) { - - // 遍历 - FileItem item = it.next(); - if (item.isFormField()) { - - // 如果是文本域,直接放到map里 - params.put(item.getFieldName(), item.getString("utf8")); - } else { - - // 否则,为文件,先获取文件名称 - String filename = item.getName().replace("\\", "/"); - filename = filename.substring(filename.lastIndexOf("/") + 1); - - // 保存到系统临时文件夹中 - File file = new File(System.getProperty("java.io.tmpdir"), filename); - - // 保存文件内容 - OutputStream ous = new FileOutputStream(file); - ous.write(item.get()); - ous.close(); - - // 放到map中 - params.put(item.getFieldName(), file); - } - } - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - public static void main(String[] args) { - - System.out.println(System.getProperties().toString().replace(", ", "\r\n")); - } - - @Override - public Object getAttribute(String name) { - - // 如果为上传文件,则从map中取值 - if (multipart && params.containsKey(name)) { - return params.get(name); - } - return super.getAttribute(name); - } - - @Override - public String getParameter(String name) { - - // 如果为上传文件,则从map中取值 - if (multipart && params.containsKey(name)) { - return params.get(name).toString(); - } - return super.getParameter(name); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/WaterMarkOutputStream.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/WaterMarkOutputStream.java deleted file mode 100644 index b94322c4..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/WaterMarkOutputStream.java +++ /dev/null @@ -1,55 +0,0 @@ -package io.github.dunwu.javaee.filter.wrapper; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import javax.servlet.ServletOutputStream; -import javax.servlet.WriteListener; - -/** - * @author Zhang Peng - * @since 2017/3/27. - */ -public class WaterMarkOutputStream extends ServletOutputStream { - - // 缓冲图片数据 - private ByteArrayOutputStream byteArrayOutputStream; - - public WaterMarkOutputStream() throws IOException { - byteArrayOutputStream = new ByteArrayOutputStream(); - } - - @Override - public boolean isReady() { - return false; - } - - @Override - public void setWriteListener(WriteListener writeListener) { - - } - - public void write(int b) throws IOException { - byteArrayOutputStream.write(b); - } - - public void close() throws IOException { - byteArrayOutputStream.close(); - } - - public void flush() throws IOException { - byteArrayOutputStream.flush(); - } - - public void write(byte[] b, int off, int len) throws IOException { - byteArrayOutputStream.write(b, off, len); - } - - public void write(byte[] b) throws IOException { - byteArrayOutputStream.write(b); - } - - public ByteArrayOutputStream getByteArrayOutputStream() { - return byteArrayOutputStream; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/WaterMarkResponseWrapper.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/WaterMarkResponseWrapper.java deleted file mode 100644 index 5f57cca4..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/java/io/github/dunwu/javaee/filter/wrapper/WaterMarkResponseWrapper.java +++ /dev/null @@ -1,60 +0,0 @@ -package io.github.dunwu.javaee.filter.wrapper; - -import java.awt.image.BufferedImage; -import java.io.FileInputStream; -import java.io.IOException; -import javax.imageio.ImageIO; -import javax.servlet.ServletOutputStream; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpServletResponseWrapper; -import net.coobird.thumbnailator.Thumbnails; -import net.coobird.thumbnailator.geometry.Positions; - -/** - * @author Zhang Peng - * @since 2017/3/27. - */ -public class WaterMarkResponseWrapper extends HttpServletResponseWrapper { - - private String originFile; - - // 水印图片位置 - private String waterMarkFile; - - // 原response - private HttpServletResponse response; - - // 自定义servletOutputStream,用于缓冲图像数据 - private WaterMarkOutputStream waterMarkOutputStream; - - public WaterMarkResponseWrapper(HttpServletResponse response, String originFile, String waterMarkFile) - throws IOException { - super(response); - this.response = response; - this.originFile = originFile; - this.waterMarkFile = waterMarkFile; - this.waterMarkOutputStream = new WaterMarkOutputStream(); - } - - // 覆盖getOutputStream(),返回自定义的waterMarkOutputStream - public ServletOutputStream getOutputStream() throws IOException { - return waterMarkOutputStream; - } - - public void flushBuffer() throws IOException { - waterMarkOutputStream.flush(); - } - - // 将图像数据打水印,并输出到客户端浏览器 - public void finishResponse() throws IOException { - FileInputStream fileInputStream = new FileInputStream(waterMarkFile); - BufferedImage wartermarkImage = ImageIO.read(fileInputStream); - Thumbnails.Builder builder = Thumbnails.of(this.originFile); - builder.scale(1.0, 1.0); - builder.watermark(Positions.BOTTOM_RIGHT, wartermarkImage, 0.5f); - - // 打水印后的图片数据 - builder.toOutputStream(response.getOutputStream()); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/resources/logback.xml deleted file mode 100644 index 5f9e7061..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/resources/logback.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n - - - - - - - - logs/${FILE_NAME}-all.%d{yyyy-MM-dd}.log - 30 - - - - - 30MB - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n - - - - - - - - - - - - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/WEB-INF/logo.png b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/WEB-INF/logo.png deleted file mode 100644 index 39ac67c335fcd99d244aecd0114e1f7e5347124a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10276 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 - - - javaee-filter - - - - index.jsp - /views/jsp/index.jsp - - - index.jsp - / - - - - dispatcherServlet - /views/jsp/dispatcher.jsp - - - dispatcherServlet - *.do - - - - - - CacheFilter - - io.github.dunwu.javaee.filter.CacheFilter - - - cache - true - - - cacheTime - 1000000 - - - - CacheFilter - *.jsp - *.html - *.do - REQUEST - - - - characterEncodingFilter - - io.github.dunwu.javaee.filter.CharacterEncodingFilter - - - characterEncoding - UTF-8 - - - enable - true - - - - characterEncodingFilter - /* - - - - ExceptionHandlerFilter - - io.github.dunwu.javaee.filter.ExceptionHandlerFilter - - - - ExceptionHandlerFilter - /* - - - - GZipFilter - io.github.dunwu.javaee.filter.GZipFilter - - - GZipFilter - /* - - - - ImageRedirectFilter - - io.github.dunwu.javaee.filter.ImageRedirectFilter - - - - ImageRedirectFilter - /views/images/* - - - - LogFilter - io.github.dunwu.javaee.filter.LogFilter - - - LogFilter - /* - - - - OutputReplaceFilter - - io.github.dunwu.javaee.filter.OutputReplaceFilter - - - file - /WEB-INF/sensitive.properties - - - - OutputReplaceFilter - *.jsp - - - - UploadFilter - io.github.dunwu.javaee.filter.UploadFilter - - - UploadFilter - /* - - - - PrivilegeFilter - - io.github.dunwu.javaee.filter.PrivilegeFilter - - - file - /WEB-INF/privilege.properties - - - - PrivilegeFilter - *.do - - - - WaterMarkFilter - - io.github.dunwu.javaee.filter.WaterMarkFilter - - - waterMarkFile - /WEB-INF/logo.png - - - - WaterMarkFilter - *.jpg - *.png - *.bmp - - - - XSLTFilter - io.github.dunwu.javaee.filter.XSLTFilter - - - XSLTFilter - /views/xml/* - - - - - /views/jsp/index.html - /views/jsp/index.htm - /views/jsp/index.jsp - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/images/error.gif b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/images/error.gif deleted file mode 100644 index b6922ac11cf64e16a15cf2976cdaa1e40118abed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 351 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%0A=1wj1uB0CSAuTbSQcNEAsyU_I2@f&xbxP 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 diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/images/sunset.jpg b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/images/sunset.jpg deleted file mode 100644 index 860f6eec2ede6ba1755418558c4455f956950eba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 71189 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Μn$q=XvO=xQT> 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 diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/images/winter.jpg b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/images/winter.jpg deleted file mode 100644 index 6db32ca1db0a2131eb3ce9940da98970a4c9e689..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 105542 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�AJ8IP8Z%y9Oe^@u9?E(GAdhM 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 diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/js/dojo.js b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/js/dojo.js deleted file mode 100644 index d5a0fc94..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/js/dojo.js +++ /dev/null @@ -1,6812 +0,0 @@ -/* - Copyright (c) 2004-2006, The Dojo Foundation - All Rights Reserved. - - Licensed under the Academic Free License version 2.1 or above OR the - modified BSD license. For more information on Dojo licensing, see: - - http://dojotoolkit.org/community/licensing.shtml - */ - -/* - This is a compiled version of Dojo, built for deployment and not for - development. To get an editable version, please visit: - - http://dojotoolkit.org - - for documentation and information on getting the source. - */ - -if (typeof dojo == 'undefined') { - var dj_global = this; - var dj_currentContext = this; - - function dj_undef(_1, _2) { - return (typeof (_2 || dj_currentContext)[_1] == 'undefined') - } - - if (dj_undef('djConfig', this)) { - var djConfig = {} - } - if (dj_undef('dojo', this)) { - var dojo = {} - } - dojo.global = function () { - return dj_currentContext - }; - dojo.locale = djConfig.locale; - dojo.version = { - major: 0, - minor: 4, - patch: 3, - flag: '-20070622', - revision: Number('$Rev: 8617 $'.match(/[0-9]+/)[0]), - toString: function () { - with (dojo.version) { - return major + '.' + minor + '.' + patch + flag + ' (' + revision + ')' - } - } - }; - dojo.evalProp = function (_3, _4, _5) { - if ((!_4) || (!_3)) { - return undefined - } - if (!dj_undef(_3, _4)) { - return _4[_3] - } - return (_5 ? (_4[_3] = {}) : undefined) - }; - dojo.parseObjPath = function (_6, _7, _8) { - var _9 = (_7 || dojo.global()); - var _a = _6.split('.'); - var _b = _a.pop(); - for (var i = 0, l = _a.length; i < l && _9; i++) { - _9 = dojo.evalProp(_a[i], _9, _8) - } - return {obj: _9, prop: _b} - }; - dojo.evalObjPath = function (_e, _f) { - if (typeof _e != 'string') { - return dojo.global() - } - if (_e.indexOf('.') == -1) { - return dojo.evalProp(_e, dojo.global(), _f) - } - var ref = dojo.parseObjPath(_e, dojo.global(), _f); - if (ref) { - return dojo.evalProp(ref.prop, ref.obj, _f) - } - return null - }; - dojo.errorToString = function (_11) { - if (!dj_undef('message', _11)) { - return _11.message - } else { - if (!dj_undef('description', _11)) { - return _11.description - } else { - return _11 - } - } - }; - dojo.raise = function (_12, _13) { - if (_13) { - _12 = _12 + ': ' + dojo.errorToString(_13) - } else { - _12 = dojo.errorToString(_12) - } - try { - if (djConfig.isDebug) { - dojo.hostenv.println('FATAL exception raised: ' + _12) - } - } catch (e) { - } - throw _13 || Error(_12) - }; - dojo.debug = function () { - }; - dojo.debugShallow = function (obj) { - }; - dojo.profile = { - start: function () { - }, end: function () { - }, stop: function () { - }, dump: function () { - } - }; - - function dj_eval(_15) { - return dj_global.eval ? dj_global.eval(_15) : eval(_15) - } - - dojo.unimplemented = function (_16, _17) { - var _18 = '\'' + _16 + '\' not implemented'; - if (_17 != null) { - _18 += ' ' + _17 - } - dojo.raise(_18) - }; - dojo.deprecated = function (_19, _1a, _1b) { - var _1c = 'DEPRECATED: ' + _19; - if (_1a) { - _1c += ' ' + _1a - } - if (_1b) { - _1c += ' -- will be removed in version: ' + _1b - } - dojo.debug(_1c) - }; - dojo.render = (function () { - function vscaffold(_1d, _1e) { - var tmp = {capable: false, support: {builtin: false, plugin: false}, prefixes: _1d}; - for (var i = 0; i < _1e.length; i++) { - tmp[_1e[i]] = false - } - return tmp - } - - return { - name: '', - ver: dojo.version, - os: {win: false, linux: false, osx: false}, - html: vscaffold(['html'], ['ie', 'opera', 'khtml', 'safari', 'moz']), - svg: vscaffold(['svg'], ['corel', 'adobe', 'batik']), - vml: vscaffold(['vml'], ['ie']), - swf: vscaffold(['Swf', 'Flash', 'Mm'], ['mm']), - swt: vscaffold(['Swt'], ['ibm']) - } - })(); - dojo.hostenv = (function () { - var _21 = { - isDebug: false, - allowQueryConfig: false, - baseScriptUri: '', - baseRelativePath: '', - libraryScriptUri: '', - iePreventClobber: false, - ieClobberMinimal: true, - preventBackButtonFix: true, - delayMozLoadingFix: false, - searchIds: [], - parseWidgets: true - }; - if (typeof djConfig == 'undefined') { - djConfig = _21 - } else { - for (var _22 in _21) { - if (typeof djConfig[_22] == 'undefined') { - djConfig[_22] = _21[_22] - } - } - } - return { - name_: '(unset)', version_: '(unset)', getName: function () { - return this.name_ - }, getVersion: function () { - return this.version_ - }, getText: function (uri) { - dojo.unimplemented('getText', 'uri=' + uri) - } - } - })(); - dojo.hostenv.getBaseScriptUri = function () { - if (djConfig.baseScriptUri.length) { - return djConfig.baseScriptUri - } - var uri = String(djConfig.libraryScriptUri || djConfig.baseRelativePath); - if (!uri) { - dojo.raise('Nothing returned by getLibraryScriptUri(): ' + uri) - } - var _25 = uri.lastIndexOf('/'); - djConfig.baseScriptUri = djConfig.baseRelativePath; - return djConfig.baseScriptUri - }; - (function () { - var _26 = { - pkgFileName: '__package__', - loading_modules_: {}, - loaded_modules_: {}, - addedToLoadingCount: [], - removedFromLoadingCount: [], - inFlightCount: 0, - modulePrefixes_: {dojo: {name: 'dojo', value: 'src'}}, - setModulePrefix: function (_27, _28) { - this.modulePrefixes_[_27] = {name: _27, value: _28} - }, - moduleHasPrefix: function (_29) { - var mp = this.modulePrefixes_; - return Boolean(mp[_29] && mp[_29].value) - }, - getModulePrefix: function (_2b) { - if (this.moduleHasPrefix(_2b)) { - return this.modulePrefixes_[_2b].value - } - return _2b - }, - getTextStack: [], - loadUriStack: [], - loadedUris: [], - post_load_: false, - modulesLoadedListeners: [], - unloadListeners: [], - loadNotifying: false - }; - for (var _2c in _26) { - dojo.hostenv[_2c] = _26[_2c] - } - })(); - dojo.hostenv.loadPath = function (_2d, _2e, cb) { - var uri; - if (_2d.charAt(0) == '/' || _2d.match(/^\w+:/)) { - uri = _2d - } else { - uri = this.getBaseScriptUri() + _2d - } - if (djConfig.cacheBust && dojo.render.html.capable) { - uri += '?' + String(djConfig.cacheBust).replace(/\W+/g, '') - } - try { - return !_2e ? this.loadUri(uri, cb) : this.loadUriAndCheck(uri, _2e, cb) - } catch (e) { - dojo.debug(e); - return false - } - }; - dojo.hostenv.loadUri = function (uri, cb) { - if (this.loadedUris[uri]) { - return true - } - var _33 = this.getText(uri, null, true); - if (!_33) { - return false - } - this.loadedUris[uri] = true; - if (cb) { - _33 = '(' + _33 + ')' - } - var _34 = dj_eval(_33); - if (cb) { - cb(_34) - } - return true - }; - dojo.hostenv.loadUriAndCheck = function (uri, _36, cb) { - var ok = true; - try { - ok = this.loadUri(uri, cb) - } catch (e) { - dojo.debug('failed loading ', uri, ' with error: ', e) - } - return Boolean(ok && this.findModule(_36, false)) - }; - dojo.loaded = function () { - }; - dojo.unloaded = function () { - }; - dojo.hostenv.loaded = function () { - this.loadNotifying = true; - this.post_load_ = true; - var mll = this.modulesLoadedListeners; - for (var x = 0; x < mll.length; x++) { - mll[x]() - } - this.modulesLoadedListeners = []; - this.loadNotifying = false; - dojo.loaded() - }; - dojo.hostenv.unloaded = function () { - var mll = this.unloadListeners; - while (mll.length) { - (mll.pop())() - } - dojo.unloaded() - }; - dojo.addOnLoad = function (obj, _3d) { - var dh = dojo.hostenv; - if (arguments.length == 1) { - dh.modulesLoadedListeners.push(obj) - } else { - if (arguments.length > 1) { - dh.modulesLoadedListeners.push(function () { - obj[_3d]() - }) - } - } - if (dh.post_load_ && dh.inFlightCount == 0 && !dh.loadNotifying) { - dh.callLoaded() - } - }; - dojo.addOnUnload = function (obj, _40) { - var dh = dojo.hostenv; - if (arguments.length == 1) { - dh.unloadListeners.push(obj) - } else { - if (arguments.length > 1) { - dh.unloadListeners.push(function () { - obj[_40]() - }) - } - } - }; - dojo.hostenv.modulesLoaded = function () { - if (this.post_load_) { - return - } - if (this.loadUriStack.length == 0 && this.getTextStack.length == 0) { - if (this.inFlightCount > 0) { - dojo.debug('files still in flight!'); - return - } - dojo.hostenv.callLoaded() - } - }; - dojo.hostenv.callLoaded = function () { - if (typeof setTimeout == 'object' || (djConfig['useXDomain'] && dojo.render.html.opera)) { - setTimeout('dojo.hostenv.loaded();', 0) - } else { - dojo.hostenv.loaded() - } - }; - dojo.hostenv.getModuleSymbols = function (_42) { - var _43 = _42.split('.'); - for (var i = _43.length; i > 0; i--) { - var _45 = _43.slice(0, i).join('.'); - if ((i == 1) && !this.moduleHasPrefix(_45)) { - _43[0] = '../' + _43[0] - } else { - var _46 = this.getModulePrefix(_45); - if (_46 != _45) { - _43.splice(0, i, _46); - break - } - } - } - return _43 - }; - dojo.hostenv._global_omit_module_check = false; - dojo.hostenv.loadModule = function (_47, _48, _49) { - if (!_47) { - return - } - _49 = this._global_omit_module_check || _49; - var _4a = this.findModule(_47, false); - if (_4a) { - return _4a - } - if (dj_undef(_47, this.loading_modules_)) { - this.addedToLoadingCount.push(_47) - } - this.loading_modules_[_47] = 1; - var _4b = _47.replace(/\./g, '/') + '.js'; - var _4c = _47.split('.'); - var _4d = this.getModuleSymbols(_47); - var _4e = ((_4d[0].charAt(0) != '/') && !_4d[0].match(/^\w+:/)); - var _4f = _4d[_4d.length - 1]; - var ok; - if (_4f == '*') { - _47 = _4c.slice(0, -1).join('.'); - while (_4d.length) { - _4d.pop(); - _4d.push(this.pkgFileName); - _4b = _4d.join('/') + '.js'; - if (_4e && _4b.charAt(0) == '/') { - _4b = _4b.slice(1) - } - ok = this.loadPath(_4b, !_49 ? _47 : null); - if (ok) { - break - } - _4d.pop() - } - } else { - _4b = _4d.join('/') + '.js'; - _47 = _4c.join('.'); - var _51 = !_49 ? _47 : null; - ok = this.loadPath(_4b, _51); - if (!ok && !_48) { - _4d.pop(); - while (_4d.length) { - _4b = _4d.join('/') + '.js'; - ok = this.loadPath(_4b, _51); - if (ok) { - break - } - _4d.pop(); - _4b = _4d.join('/') + '/' + this.pkgFileName + '.js'; - if (_4e && _4b.charAt(0) == '/') { - _4b = _4b.slice(1) - } - ok = this.loadPath(_4b, _51); - if (ok) { - break - } - } - } - if (!ok && !_49) { - dojo.raise('Could not load \'' + _47 + '\'; last tried \'' + _4b + '\'') - } - } - if (!_49 && !this['isXDomain']) { - _4a = this.findModule(_47, false); - if (!_4a) { - dojo.raise('symbol \'' + _47 + '\' is not defined after loading \'' + _4b + '\'') - } - } - return _4a - }; - dojo.hostenv.startPackage = function (_52) { - var _53 = String(_52); - var _54 = _53; - var _55 = _52.split(/\./); - if (_55[_55.length - 1] == '*') { - _55.pop(); - _54 = _55.join('.') - } - var _56 = dojo.evalObjPath(_54, true); - this.loaded_modules_[_53] = _56; - this.loaded_modules_[_54] = _56; - return _56 - }; - dojo.hostenv.findModule = function (_57, _58) { - var lmn = String(_57); - if (this.loaded_modules_[lmn]) { - return this.loaded_modules_[lmn] - } - if (_58) { - dojo.raise('no loaded module named \'' + _57 + '\'') - } - return null - }; - dojo.kwCompoundRequire = function (_5a) { - var _5b = _5a['common'] || []; - var _5c = _5a[dojo.hostenv.name_] ? _5b.concat(_5a[dojo.hostenv.name_] || []) : _5b.concat(_5a['default'] || []); - for (var x = 0; x < _5c.length; x++) { - var _5e = _5c[x]; - if (_5e.constructor == Array) { - dojo.hostenv.loadModule.apply(dojo.hostenv, _5e) - } else { - dojo.hostenv.loadModule(_5e) - } - } - }; - dojo.require = function (_5f) { - dojo.hostenv.loadModule.apply(dojo.hostenv, arguments) - }; - dojo.requireIf = function (_60, _61) { - var _62 = arguments[0]; - if ((_62 === true) || (_62 == 'common') || (_62 && dojo.render[_62].capable)) { - var _63 = []; - for (var i = 1; i < arguments.length; i++) { - _63.push(arguments[i]) - } - dojo.require.apply(dojo, _63) - } - }; - dojo.requireAfterIf = dojo.requireIf; - dojo.provide = function (_65) { - return dojo.hostenv.startPackage.apply(dojo.hostenv, arguments) - }; - dojo.registerModulePath = function (_66, _67) { - return dojo.hostenv.setModulePrefix(_66, _67) - }; - if (djConfig['modulePaths']) { - for (var param in djConfig['modulePaths']) { - dojo.registerModulePath(param, djConfig['modulePaths'][param]) - } - } - dojo.setModulePrefix = function (_68, _69) { - dojo.deprecated('dojo.setModulePrefix("' + _68 + '", "' + _69 + '")', 'replaced by dojo.registerModulePath', '0.5'); - return dojo.registerModulePath(_68, _69) - }; - dojo.exists = function (obj, _6b) { - var p = _6b.split('.'); - for (var i = 0; i < p.length; i++) { - if (!obj[p[i]]) { - return false - } - obj = obj[p[i]] - } - return true - }; - dojo.hostenv.normalizeLocale = function (_6e) { - var _6f = _6e ? _6e.toLowerCase() : dojo.locale; - if (_6f == 'root') { - _6f = 'ROOT' - } - return _6f - }; - dojo.hostenv.searchLocalePath = function (_70, _71, _72) { - _70 = dojo.hostenv.normalizeLocale(_70); - var _73 = _70.split('-'); - var _74 = []; - for (var i = _73.length; i > 0; i--) { - _74.push(_73.slice(0, i).join('-')) - } - _74.push(false); - if (_71) { - _74.reverse() - } - for (var j = _74.length - 1; j >= 0; j--) { - var loc = _74[j] || 'ROOT'; - var _78 = _72(loc); - if (_78) { - break - } - } - }; - dojo.hostenv.localesGenerated; - dojo.hostenv.registerNlsPrefix = function () { - dojo.registerModulePath('nls', 'nls') - }; - dojo.hostenv.preloadLocalizations = function () { - if (dojo.hostenv.localesGenerated) { - dojo.hostenv.registerNlsPrefix(); - - function preload(_79) { - _79 = dojo.hostenv.normalizeLocale(_79); - dojo.hostenv.searchLocalePath(_79, true, function (loc) { - for (var i = 0; i < dojo.hostenv.localesGenerated.length; i++) { - if (dojo.hostenv.localesGenerated[i] == loc) { - dojo['require']('nls.dojo_' + loc); - return true - } - } - return false - }) - } - - preload(); - var _7c = djConfig.extraLocale || []; - for (var i = 0; i < _7c.length; i++) { - preload(_7c[i]) - } - } - dojo.hostenv.preloadLocalizations = function () { - } - }; - dojo.requireLocalization = function (_7e, _7f, _80, _81) { - dojo.hostenv.preloadLocalizations(); - var _82 = dojo.hostenv.normalizeLocale(_80); - var _83 = [_7e, 'nls', _7f].join('.'); - var _84 = ''; - if (_81) { - var _85 = _81.split(','); - for (var i = 0; i < _85.length; i++) { - if (_82.indexOf(_85[i]) == 0) { - if (_85[i].length > _84.length) { - _84 = _85[i] - } - } - } - if (!_84) { - _84 = 'ROOT' - } - } - var _87 = _81 ? _84 : _82; - var _88 = dojo.hostenv.findModule(_83); - var _89 = null; - if (_88) { - if (djConfig.localizationComplete && _88._built) { - return - } - var _8a = _87.replace('-', '_'); - var _8b = _83 + '.' + _8a; - _89 = dojo.hostenv.findModule(_8b) - } - if (!_89) { - _88 = dojo.hostenv.startPackage(_83); - var _8c = dojo.hostenv.getModuleSymbols(_7e); - var _8d = _8c.concat('nls').join('/'); - var _8e; - dojo.hostenv.searchLocalePath(_87, _81, function (loc) { - var _90 = loc.replace('-', '_'); - var _91 = _83 + '.' + _90; - var _92 = false; - if (!dojo.hostenv.findModule(_91)) { - dojo.hostenv.startPackage(_91); - var _93 = [_8d]; - if (loc != 'ROOT') { - _93.push(loc) - } - _93.push(_7f); - var _94 = _93.join('/') + '.js'; - _92 = dojo.hostenv.loadPath(_94, null, function (_95) { - var _96 = function () { - }; - _96.prototype = _8e; - _88[_90] = new _96(); - for (var j in _95) { - _88[_90][j] = _95[j] - } - }) - } else { - _92 = true - } - if (_92 && _88[_90]) { - _8e = _88[_90] - } else { - _88[_90] = _8e - } - if (_81) { - return true - } - }) - } - if (_81 && _82 != _84) { - _88[_82.replace('-', '_')] = _88[_84.replace('-', '_')] - } - }; - (function () { - var _98 = djConfig.extraLocale; - if (_98) { - if (!_98 instanceof Array) { - _98 = [_98] - } - var req = dojo.requireLocalization; - dojo.requireLocalization = function (m, b, _9c, _9d) { - req(m, b, _9c, _9d); - if (_9c) { - return - } - for (var i = 0; i < _98.length; i++) { - req(m, b, _98[i], _9d) - } - } - } - })() -} -if (typeof window != 'undefined') { - (function () { - if (djConfig.allowQueryConfig) { - var _9f = document.location.toString(); - var _a0 = _9f.split('?', 2); - if (_a0.length > 1) { - var _a1 = _a0[1]; - var _a2 = _a1.split('&'); - for (var x in _a2) { - var sp = _a2[x].split('='); - if ((sp[0].length > 9) && (sp[0].substr(0, 9) == 'djConfig.')) { - var opt = sp[0].substr(9); - try { - djConfig[opt] = eval(sp[1]) - } catch (e) { - djConfig[opt] = sp[1] - } - } - } - } - } - if (((djConfig['baseScriptUri'] == '') || (djConfig['baseRelativePath'] == '')) && (document - && document.getElementsByTagName)) { - var _a6 = document.getElementsByTagName('script'); - var _a7 = /(__package__|dojo|bootstrap1)\.js([\?\.]|$)/i; - for (var i = 0; i < _a6.length; i++) { - var src = _a6[i].getAttribute('src'); - if (!src) { - continue - } - var m = src.match(_a7); - if (m) { - var _ab = src.substring(0, m.index); - if (src.indexOf('bootstrap1') > -1) { - _ab += '../' - } - if (!this['djConfig']) { - djConfig = {} - } - if (djConfig['baseScriptUri'] == '') { - djConfig['baseScriptUri'] = _ab - } - if (djConfig['baseRelativePath'] == '') { - djConfig['baseRelativePath'] = _ab - } - break - } - } - } - var dr = dojo.render; - var drh = dojo.render.html; - var drs = dojo.render.svg; - var dua = (drh.UA = navigator.userAgent); - var dav = (drh.AV = navigator.appVersion); - var t = true; - var f = false; - drh.capable = t; - drh.support.builtin = t; - dr.ver = parseFloat(drh.AV); - dr.os.mac = dav.indexOf('Macintosh') >= 0; - dr.os.win = dav.indexOf('Windows') >= 0; - dr.os.linux = dav.indexOf('X11') >= 0; - drh.opera = dua.indexOf('Opera') >= 0; - drh.khtml = (dav.indexOf('Konqueror') >= 0) || (dav.indexOf('Safari') >= 0); - drh.safari = dav.indexOf('Safari') >= 0; - var _b3 = dua.indexOf('Gecko'); - drh.mozilla = drh.moz = (_b3 >= 0) && (!drh.khtml); - if (drh.mozilla) { - drh.geckoVersion = dua.substring(_b3 + 6, _b3 + 14) - } - drh.ie = (document.all) && (!drh.opera); - drh.ie50 = drh.ie && dav.indexOf('MSIE 5.0') >= 0; - drh.ie55 = drh.ie && dav.indexOf('MSIE 5.5') >= 0; - drh.ie60 = drh.ie && dav.indexOf('MSIE 6.0') >= 0; - drh.ie70 = drh.ie && dav.indexOf('MSIE 7.0') >= 0; - var cm = document['compatMode']; - drh.quirks = (cm == 'BackCompat') || (cm == 'QuirksMode') || drh.ie55 || drh.ie50; - dojo.locale = dojo.locale || (drh.ie ? navigator.userLanguage : navigator.language).toLowerCase(); - dr.vml.capable = drh.ie; - drs.capable = f; - drs.support.plugin = f; - drs.support.builtin = f; - var _b5 = window['document']; - var tdi = _b5['implementation']; - if (drh.ie && (window.location.protocol == 'file:')) { - djConfig.ieForceActiveXXhr = true - } - if ((tdi) && (tdi['hasFeature']) && (tdi.hasFeature('org.w3c.dom.svg', '1.0'))) { - drs.capable = t; - drs.support.builtin = t; - drs.support.plugin = f - } - if (drh.safari) { - var tmp = dua.split('AppleWebKit/')[1]; - var ver = parseFloat(tmp.split(' ')[0]); - if (ver >= 420) { - drs.capable = t; - drs.support.builtin = t; - drs.support.plugin = f - } - } else { - } - })(); - dojo.hostenv.startPackage('dojo.hostenv'); - dojo.render.name = dojo.hostenv.name_ = 'browser'; - dojo.hostenv.searchIds = []; - dojo.hostenv._XMLHTTP_PROGIDS = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0']; - dojo.hostenv.getXmlhttpObject = function () { - var _b9 = null; - var _ba = null; - if (!dojo.render.html.ie || !djConfig.ieForceActiveXXhr) { - try { - _b9 = new XMLHttpRequest() - } catch (e) { - } - } - if (!_b9) { - for (var i = 0; i < 3; ++i) { - var _bc = dojo.hostenv._XMLHTTP_PROGIDS[i]; - try { - _b9 = new ActiveXObject(_bc) - } catch (e) { - _ba = e - } - if (_b9) { - dojo.hostenv._XMLHTTP_PROGIDS = [_bc]; - break - } - } - } - if (!_b9) { - return dojo.raise('XMLHTTP not available', _ba) - } - return _b9 - }; - dojo.hostenv._blockAsync = false; - dojo.hostenv.getText = function (uri, _be, _bf) { - if (!_be) { - this._blockAsync = true - } - var _c0 = this.getXmlhttpObject(); - - function isDocumentOk(_c1) { - var _c2 = _c1['status']; - return Boolean((!_c2) || ((200 <= _c2) && (300 > _c2)) || (_c2 == 304)) - } - - if (_be) { - var _c3 = this, _c4 = null, gbl = dojo.global(); - var xhr = dojo.evalObjPath('dojo.io.XMLHTTPTransport'); - _c0.onreadystatechange = function () { - if (_c4) { - gbl.clearTimeout(_c4); - _c4 = null - } - if (_c3._blockAsync || (xhr && xhr._blockAsync)) { - _c4 = gbl.setTimeout(function () { - _c0.onreadystatechange.apply(this) - }, 10) - } else { - if (4 == _c0.readyState) { - if (isDocumentOk(_c0)) { - _be(_c0.responseText) - } - } - } - } - } - _c0.open('GET', uri, _be ? true : false); - try { - _c0.send(null); - if (_be) { - return null - } - if (!isDocumentOk(_c0)) { - var err = Error('Unable to load ' + uri + ' status:' + _c0.status); - err.status = _c0.status; - err.responseText = _c0.responseText; - throw err - } - } catch (e) { - this._blockAsync = false; - if ((_bf) && (!_be)) { - return null - } else { - throw e - } - } - this._blockAsync = false; - return _c0.responseText - }; - dojo.hostenv.defaultDebugContainerId = 'dojoDebug'; - dojo.hostenv._println_buffer = []; - dojo.hostenv._println_safe = false; - dojo.hostenv.println = function (_c8) { - if (!dojo.hostenv._println_safe) { - dojo.hostenv._println_buffer.push(_c8) - } else { - try { - var _c9 = document.getElementById(djConfig.debugContainerId ? djConfig.debugContainerId : dojo.hostenv.defaultDebugContainerId); - if (!_c9) { - _c9 = dojo.body() - } - var div = document.createElement('div'); - div.appendChild(document.createTextNode(_c8)); - _c9.appendChild(div) - } catch (e) { - try { - document.write('
    ' + _c8 + '
    ') - } catch (e2) { - window.status = _c8 - } - } - } - }; - dojo.addOnLoad(function () { - dojo.hostenv._println_safe = true; - while (dojo.hostenv._println_buffer.length > 0) { - dojo.hostenv.println(dojo.hostenv._println_buffer.shift()) - } - }); - - function dj_addNodeEvtHdlr(_cb, _cc, fp) { - var _ce = _cb['on' + _cc] || function () { - }; - _cb['on' + _cc] = function () { - fp.apply(_cb, arguments); - _ce.apply(_cb, arguments) - }; - return true - } - - dojo.hostenv._djInitFired = false; - - function dj_load_init(e) { - dojo.hostenv._djInitFired = true; - var _d0 = (e && e.type) ? e.type.toLowerCase() : 'load'; - if (arguments.callee.initialized || (_d0 != 'domcontentloaded' && _d0 != 'load')) { - return - } - arguments.callee.initialized = true; - if (typeof (_timer) != 'undefined') { - clearInterval(_timer); - delete _timer - } - var _d1 = function () { - if (dojo.render.html.ie) { - dojo.hostenv.makeWidgets() - } - }; - if (dojo.hostenv.inFlightCount == 0) { - _d1(); - dojo.hostenv.modulesLoaded() - } else { - dojo.hostenv.modulesLoadedListeners.unshift(_d1) - } - } - - if (document.addEventListener) { - if (dojo.render.html.opera || (dojo.render.html.moz && (djConfig['enableMozDomContentLoaded'] === true))) { - document.addEventListener('DOMContentLoaded', dj_load_init, null) - } - window.addEventListener('load', dj_load_init, null) - } - if (dojo.render.html.ie && dojo.render.os.win) { - document.attachEvent('onreadystatechange', function (e) { - if (document.readyState == 'complete') { - dj_load_init() - } - }) - } - if (/(WebKit|khtml)/i.test(navigator.userAgent)) { - var _timer = setInterval(function () { - if (/loaded|complete/.test(document.readyState)) { - dj_load_init() - } - }, 10) - } - if (dojo.render.html.ie) { - dj_addNodeEvtHdlr(window, 'beforeunload', function () { - dojo.hostenv._unloading = true; - window.setTimeout(function () { - dojo.hostenv._unloading = false - }, 0) - }) - } - dj_addNodeEvtHdlr(window, 'unload', function () { - dojo.hostenv.unloaded(); - if ((!dojo.render.html.ie) || (dojo.render.html.ie && dojo.hostenv._unloading)) { - dojo.hostenv.unloaded() - } - }); - dojo.hostenv.makeWidgets = function () { - var _d3 = []; - if (djConfig.searchIds && djConfig.searchIds.length > 0) { - _d3 = _d3.concat(djConfig.searchIds) - } - if (dojo.hostenv.searchIds && dojo.hostenv.searchIds.length > 0) { - _d3 = _d3.concat(dojo.hostenv.searchIds) - } - if ((djConfig.parseWidgets) || (_d3.length > 0)) { - if (dojo.evalObjPath('dojo.widget.Parse')) { - var _d4 = new dojo.xml.Parse(); - if (_d3.length > 0) { - for (var x = 0; x < _d3.length; x++) { - var _d6 = document.getElementById(_d3[x]); - if (!_d6) { - continue - } - var _d7 = _d4.parseElement(_d6, null, true); - dojo.widget.getParser().createComponents(_d7) - } - } else { - if (djConfig.parseWidgets) { - var _d7 = _d4.parseElement(dojo.body(), null, true); - dojo.widget.getParser().createComponents(_d7) - } - } - } - } - }; - dojo.addOnLoad(function () { - if (!dojo.render.html.ie) { - dojo.hostenv.makeWidgets() - } - }); - try { - if (dojo.render.html.ie) { - document.namespaces.add('v', 'urn:schemas-microsoft-com:vml'); - document.createStyleSheet().addRule('v\\:*', 'behavior:url(#default#VML)') - } - } catch (e) { - } - dojo.hostenv.writeIncludes = function () { - }; - if (!dj_undef('document', this)) { - dj_currentDocument = this.document - } - dojo.doc = function () { - return dj_currentDocument - }; - dojo.body = function () { - return dojo.doc().body || dojo.doc().getElementsByTagName('body')[0] - }; - dojo.byId = function (id, doc) { - if ((id) && ((typeof id == 'string') || (id instanceof String))) { - if (!doc) { - doc = dj_currentDocument - } - var ele = doc.getElementById(id); - if (ele && (ele.id != id) && doc.all) { - ele = null; - eles = doc.all[id]; - if (eles) { - if (eles.length) { - for (var i = 0; i < eles.length; i++) { - if (eles[i].id == id) { - ele = eles[i]; - break - } - } - } else { - ele = eles - } - } - } - return ele - } - return id - }; - dojo.setContext = function (_dc, _dd) { - dj_currentContext = _dc; - dj_currentDocument = _dd - }; - dojo._fireCallback = function (_de, _df, _e0) { - if ((_df) && ((typeof _de == 'string') || (_de instanceof String))) { - _de = _df[_de] - } - return (_df ? _de.apply(_df, _e0 || []) : _de()) - }; - dojo.withGlobal = function (_e1, _e2, _e3, _e4) { - var _e5; - var _e6 = dj_currentContext; - var _e7 = dj_currentDocument; - try { - dojo.setContext(_e1, _e1.document); - _e5 = dojo._fireCallback(_e2, _e3, _e4) - } finally { - dojo.setContext(_e6, _e7) - } - return _e5 - }; - dojo.withDoc = function (_e8, _e9, _ea, _eb) { - var _ec; - var _ed = dj_currentDocument; - try { - dj_currentDocument = _e8; - _ec = dojo._fireCallback(_e9, _ea, _eb) - } finally { - dj_currentDocument = _ed - } - return _ec - } -} -dojo.requireIf((djConfig['isDebug'] || djConfig['debugAtAllCosts']), 'dojo.debug'); -dojo.requireIf(djConfig['debugAtAllCosts'] && !window.widget && !djConfig['useXDomain'], 'dojo.browser_debug'); -dojo.requireIf(djConfig['debugAtAllCosts'] && !window.widget && djConfig['useXDomain'], 'dojo.browser_debug_xd'); -dojo.provide('dojo.string.common'); -dojo.string.trim = function (str, wh) { - if (!str.replace) { - return str - } - if (!str.length) { - return str - } - var re = (wh > 0) ? (/^\s+/) : (wh < 0) ? (/\s+$/) : (/^\s+|\s+$/g); - return str.replace(re, '') -}; -dojo.string.trimStart = function (str) { - return dojo.string.trim(str, 1) -}; -dojo.string.trimEnd = function (str) { - return dojo.string.trim(str, -1) -}; -dojo.string.repeat = function (str, _f4, _f5) { - var out = ''; - for (var i = 0; i < _f4; i++) { - out += str; - if (_f5 && i < _f4 - 1) { - out += _f5 - } - } - return out -}; -dojo.string.pad = function (str, len, c, dir) { - var out = String(str); - if (!c) { - c = '0' - } - if (!dir) { - dir = 1 - } - while (out.length < len) { - if (dir > 0) { - out = c + out - } else { - out += c - } - } - return out -}; -dojo.string.padLeft = function (str, len, c) { - return dojo.string.pad(str, len, c, 1) -}; -dojo.string.padRight = function (str, len, c) { - return dojo.string.pad(str, len, c, -1) -}; -dojo.provide('dojo.string'); -dojo.provide('dojo.lang.common'); -dojo.lang.inherits = function (_103, _104) { - if (!dojo.lang.isFunction(_104)) { - dojo.raise('dojo.inherits: superclass argument [' + _104 + '] must be a function (subclass: [' + _103 + '\']') - } - _103.prototype = new _104(); - _103.prototype.constructor = _103; - _103.superclass = _104.prototype; - _103['super'] = _104.prototype -}; -dojo.lang._mixin = function (obj, _106) { - var tobj = {}; - for (var x in _106) { - if ((typeof tobj[x] == 'undefined') || (tobj[x] != _106[x])) { - obj[x] = _106[x] - } - } - if (dojo.render.html.ie - && (typeof (_106['toString']) == 'function') - && (_106['toString'] != obj['toString']) - && (_106['toString'] != tobj['toString'])) { - obj.toString = _106.toString - } - return obj -}; -dojo.lang.mixin = function (obj, _10a) { - for (var i = 1, l = arguments.length; i < l; i++) { - dojo.lang._mixin(obj, arguments[i]) - } - return obj -}; -dojo.lang.extend = function (_10d, _10e) { - for (var i = 1, l = arguments.length; i < l; i++) { - dojo.lang._mixin(_10d.prototype, arguments[i]) - } - return _10d -}; -dojo.inherits = dojo.lang.inherits; -dojo.mixin = dojo.lang.mixin; -dojo.extend = dojo.lang.extend; -dojo.lang.find = function (_111, _112, _113, _114) { - if (!dojo.lang.isArrayLike(_111) && dojo.lang.isArrayLike(_112)) { - dojo.deprecated('dojo.lang.find(value, array)', 'use dojo.lang.find(array, value) instead', '0.5'); - var temp = _111; - _111 = _112; - _112 = temp - } - var _116 = dojo.lang.isString(_111); - if (_116) { - _111 = _111.split('') - } - if (_114) { - var step = -1; - var i = _111.length - 1; - var end = -1 - } else { - var step = 1; - var i = 0; - var end = _111.length - } - if (_113) { - while (i != end) { - if (_111[i] === _112) { - return i - } - i += step - } - } else { - while (i != end) { - if (_111[i] == _112) { - return i - } - i += step - } - } - return -1 -}; -dojo.lang.indexOf = dojo.lang.find; -dojo.lang.findLast = function (_11a, _11b, _11c) { - return dojo.lang.find(_11a, _11b, _11c, true) -}; -dojo.lang.lastIndexOf = dojo.lang.findLast; -dojo.lang.inArray = function (_11d, _11e) { - return dojo.lang.find(_11d, _11e) > -1 -}; -dojo.lang.isObject = function (it) { - if (typeof it == 'undefined') { - return false - } - return (typeof it == 'object' || it === null || dojo.lang.isArray(it) || dojo.lang.isFunction(it)) -}; -dojo.lang.isArray = function (it) { - return (it && it instanceof Array || typeof it == 'array') -}; -dojo.lang.isArrayLike = function (it) { - if ((!it) || (dojo.lang.isUndefined(it))) { - return false - } - if (dojo.lang.isString(it)) { - return false - } - if (dojo.lang.isFunction(it)) { - return false - } - if (dojo.lang.isArray(it)) { - return true - } - if ((it.tagName) && (it.tagName.toLowerCase() == 'form')) { - return false - } - if (dojo.lang.isNumber(it.length) && isFinite(it.length)) { - return true - } - return false -}; -dojo.lang.isFunction = function (it) { - return (it instanceof Function || typeof it == 'function') -}; -(function () { - if ((dojo.render.html.capable) && (dojo.render.html['safari'])) { - dojo.lang.isFunction = function (it) { - if ((typeof (it) == 'function') && (it == '[object NodeList]')) { - return false - } - return (it instanceof Function || typeof it == 'function') - } - } -})(); -dojo.lang.isString = function (it) { - return (typeof it == 'string' || it instanceof String) -}; -dojo.lang.isAlien = function (it) { - if (!it) { - return false - } - return !dojo.lang.isFunction(it) && /\{\s*\[native code\]\s*\}/.test(String(it)) -}; -dojo.lang.isBoolean = function (it) { - return (it instanceof Boolean || typeof it == 'boolean') -}; -dojo.lang.isNumber = function (it) { - return (it instanceof Number || typeof it == 'number') -}; -dojo.lang.isUndefined = function (it) { - return ((typeof (it) == 'undefined') && (it == undefined)) -}; -dojo.provide('dojo.lang.extras'); -dojo.lang.setTimeout = function (func, _12a) { - var _12b = window, _12c = 2; - if (!dojo.lang.isFunction(func)) { - _12b = func; - func = _12a; - _12a = arguments[2]; - _12c++ - } - if (dojo.lang.isString(func)) { - func = _12b[func] - } - var args = []; - for (var i = _12c; i < arguments.length; i++) { - args.push(arguments[i]) - } - return dojo.global().setTimeout(function () { - func.apply(_12b, args) - }, _12a) -}; -dojo.lang.clearTimeout = function (_12f) { - dojo.global().clearTimeout(_12f) -}; -dojo.lang.getNameInObj = function (ns, item) { - if (!ns) { - ns = dj_global - } - for (var x in ns) { - if (ns[x] === item) { - return String(x) - } - } - return null -}; -dojo.lang.shallowCopy = function (obj, deep) { - var i, ret; - if (obj === null) { - return null - } - if (dojo.lang.isObject(obj)) { - ret = new obj.constructor(); - for (i in obj) { - if (dojo.lang.isUndefined(ret[i])) { - ret[i] = deep ? dojo.lang.shallowCopy(obj[i], deep) : obj[i] - } - } - } else { - if (dojo.lang.isArray(obj)) { - ret = []; - for (i = 0; i < obj.length; i++) { - ret[i] = deep ? dojo.lang.shallowCopy(obj[i], deep) : obj[i] - } - } else { - ret = obj - } - } - return ret -}; -dojo.lang.firstValued = function () { - for (var i = 0; i < arguments.length; i++) { - if (typeof arguments[i] != 'undefined') { - return arguments[i] - } - } - return undefined -}; -dojo.lang.getObjPathValue = function (_138, _139, _13a) { - with (dojo.parseObjPath(_138, _139, _13a)) { - return dojo.evalProp(prop, obj, _13a) - } -}; -dojo.lang.setObjPathValue = function (_13b, _13c, _13d, _13e) { - dojo.deprecated('dojo.lang.setObjPathValue', 'use dojo.parseObjPath and the \'=\' operator', '0.6'); - if (arguments.length < 4) { - _13e = true - } - with (dojo.parseObjPath(_13b, _13d, _13e)) { - if (obj && (_13e || (prop in obj))) { - obj[prop] = _13c - } - } -}; -dojo.provide('dojo.io.common'); -dojo.io.transports = []; -dojo.io.hdlrFuncNames = ['load', 'error', 'timeout']; -dojo.io.Request = function (url, _140, _141, _142) { - if ((arguments.length == 1) && (arguments[0].constructor == Object)) { - this.fromKwArgs(arguments[0]) - } else { - this.url = url; - if (_140) { - this.mimetype = _140 - } - if (_141) { - this.transport = _141 - } - if (arguments.length >= 4) { - this.changeUrl = _142 - } - } -}; -dojo.lang.extend(dojo.io.Request, { - url: '', - mimetype: 'text/plain', - method: 'GET', - content: undefined, - transport: undefined, - changeUrl: undefined, - formNode: undefined, - sync: false, - bindSuccess: false, - useCache: false, - preventCache: false, - jsonFilter: function (_143) { - if ((this.mimetype == 'text/json-comment-filtered') || (this.mimetype == 'application/json-comment-filtered')) { - var _144 = _143.indexOf('/*'); - var _145 = _143.lastIndexOf('*/'); - if ((_144 == -1) || (_145 == -1)) { - dojo.debug('your JSON wasn\'t comment filtered!'); - return '' - } - return _143.substring(_144 + 2, _145) - } - dojo.debug('please consider using a mimetype of text/json-comment-filtered to avoid potential security issues with JSON endpoints'); - return _143 - }, - load: function (type, data, _148, _149) { - }, - error: function (type, _14b, _14c, _14d) { - }, - timeout: function (type, _14f, _150, _151) { - }, - handle: function (type, data, _154, _155) { - }, - timeoutSeconds: 0, - abort: function () { - }, - fromKwArgs: function (_156) { - if (_156['url']) { - _156.url = _156.url.toString() - } - if (_156['formNode']) { - _156.formNode = dojo.byId(_156.formNode) - } - if (!_156['method'] && _156['formNode'] && _156['formNode'].method) { - _156.method = _156['formNode'].method - } - if (!_156['handle'] && _156['handler']) { - _156.handle = _156.handler - } - if (!_156['load'] && _156['loaded']) { - _156.load = _156.loaded - } - if (!_156['changeUrl'] && _156['changeURL']) { - _156.changeUrl = _156.changeURL - } - _156.encoding = dojo.lang.firstValued(_156['encoding'], djConfig['bindEncoding'], ''); - _156.sendTransport = dojo.lang.firstValued(_156['sendTransport'], djConfig['ioSendTransport'], false); - var _157 = dojo.lang.isFunction; - for (var x = 0; x < dojo.io.hdlrFuncNames.length; x++) { - var fn = dojo.io.hdlrFuncNames[x]; - if (_156[fn] && _157(_156[fn])) { - continue - } - if (_156['handle'] && _157(_156['handle'])) { - _156[fn] = _156.handle - } - } - dojo.lang.mixin(this, _156) - } -}); -dojo.io.Error = function (msg, type, num) { - this.message = msg; - this.type = type || 'unknown'; - this.number = num || 0 -}; -dojo.io.transports.addTransport = function (name) { - this.push(name); - this[name] = dojo.io[name] -}; -dojo.io.bind = function (_15e) { - if (!(_15e instanceof dojo.io.Request)) { - try { - _15e = new dojo.io.Request(_15e) - } catch (e) { - dojo.debug(e) - } - } - var _15f = ''; - if (_15e['transport']) { - _15f = _15e['transport']; - if (!this[_15f]) { - dojo.io.sendBindError(_15e, 'No dojo.io.bind() transport with name \'' + _15e['transport'] + '\'.'); - return _15e - } - if (!this[_15f].canHandle(_15e)) { - dojo.io.sendBindError(_15e, 'dojo.io.bind() transport with name \'' - + _15e['transport'] - + '\' cannot handle this type of request.'); - return _15e - } - } else { - for (var x = 0; x < dojo.io.transports.length; x++) { - var tmp = dojo.io.transports[x]; - if ((this[tmp]) && (this[tmp].canHandle(_15e))) { - _15f = tmp; - break - } - } - if (_15f == '') { - dojo.io.sendBindError(_15e, 'None of the loaded transports for dojo.io.bind()' + ' can handle the request.'); - return _15e - } - } - this[_15f].bind(_15e); - _15e.bindSuccess = true; - return _15e -}; -dojo.io.sendBindError = function (_162, _163) { - if ((typeof _162.error == 'function' || typeof _162.handle == 'function') && (typeof setTimeout - == 'function' - || typeof setTimeout - == 'object')) { - var _164 = new dojo.io.Error(_163); - setTimeout(function () { - _162[(typeof _162.error == 'function') ? 'error' : 'handle']('error', _164, null, _162) - }, 50) - } else { - dojo.raise(_163) - } -}; -dojo.io.queueBind = function (_165) { - if (!(_165 instanceof dojo.io.Request)) { - try { - _165 = new dojo.io.Request(_165) - } catch (e) { - dojo.debug(e) - } - } - var _166 = _165.load; - _165.load = function () { - dojo.io._queueBindInFlight = false; - var ret = _166.apply(this, arguments); - dojo.io._dispatchNextQueueBind(); - return ret - }; - var _168 = _165.error; - _165.error = function () { - dojo.io._queueBindInFlight = false; - var ret = _168.apply(this, arguments); - dojo.io._dispatchNextQueueBind(); - return ret - }; - dojo.io._bindQueue.push(_165); - dojo.io._dispatchNextQueueBind(); - return _165 -}; -dojo.io._dispatchNextQueueBind = function () { - if (!dojo.io._queueBindInFlight) { - dojo.io._queueBindInFlight = true; - if (dojo.io._bindQueue.length > 0) { - dojo.io.bind(dojo.io._bindQueue.shift()) - } else { - dojo.io._queueBindInFlight = false - } - } -}; -dojo.io._bindQueue = []; -dojo.io._queueBindInFlight = false; -dojo.io.argsFromMap = function (map, _16b, last) { - var enc = /utf/i.test(_16b || '') ? encodeURIComponent : dojo.string.encodeAscii; - var _16e = []; - var _16f = {}; - for (var name in map) { - var _171 = function (elt) { - var val = enc(name) + '=' + enc(elt); - _16e[(last == name) ? 'push' : 'unshift'](val) - }; - if (!_16f[name]) { - var _174 = map[name]; - if (dojo.lang.isArray(_174)) { - dojo.lang.forEach(_174, _171) - } else { - _171(_174) - } - } - } - return _16e.join('&') -}; -dojo.io.setIFrameSrc = function (_175, src, _177) { - try { - var r = dojo.render.html; - if (!_177) { - if (r.safari) { - _175.location = src - } else { - frames[_175.name].location = src - } - } else { - var idoc; - if (r.ie) { - idoc = _175.contentWindow.document - } else { - if (r.safari) { - idoc = _175.document - } else { - idoc = _175.contentWindow - } - } - if (!idoc) { - _175.location = src; - - } else { - idoc.location.replace(src) - } - } - } catch (e) { - dojo.debug(e); - dojo.debug('setIFrameSrc: ' + e) - } -}; -dojo.provide('dojo.lang.array'); -dojo.lang.mixin(dojo.lang, { - has: function (obj, name) { - try { - return typeof obj[name] != 'undefined' - } catch (e) { - return false - } - }, isEmpty: function (obj) { - if (dojo.lang.isObject(obj)) { - var tmp = {}; - var _17e = 0; - for (var x in obj) { - if (obj[x] && (!tmp[x])) { - _17e++; - break - } - } - return _17e == 0 - } else { - if (dojo.lang.isArrayLike(obj) || dojo.lang.isString(obj)) { - return obj.length == 0 - } - } - }, map: function (arr, obj, _182) { - var _183 = dojo.lang.isString(arr); - if (_183) { - arr = arr.split('') - } - if (dojo.lang.isFunction(obj) && (!_182)) { - _182 = obj; - obj = dj_global - } else { - if (dojo.lang.isFunction(obj) && _182) { - var _184 = obj; - obj = _182; - _182 = _184 - } - } - if (Array.map) { - var _185 = Array.map(arr, _182, obj) - } else { - var _185 = []; - for (var i = 0; i < arr.length; ++i) { - _185.push(_182.call(obj, arr[i])) - } - } - if (_183) { - return _185.join('') - } else { - return _185 - } - }, reduce: function (arr, _188, obj, _18a) { - var _18b = _188; - if (arguments.length == 2) { - _18a = _188; - _18b = arr[0]; - arr = arr.slice(1) - } else { - if (arguments.length == 3) { - if (dojo.lang.isFunction(obj)) { - _18a = obj; - obj = null - } - } else { - if (dojo.lang.isFunction(obj)) { - var tmp = _18a; - _18a = obj; - obj = tmp - } - } - } - var ob = obj || dj_global; - dojo.lang.map(arr, function (val) { - _18b = _18a.call(ob, _18b, val) - }); - return _18b - }, forEach: function (_18f, _190, _191) { - if (dojo.lang.isString(_18f)) { - _18f = _18f.split('') - } - if (Array.forEach) { - Array.forEach(_18f, _190, _191) - } else { - if (!_191) { - _191 = dj_global - } - for (var i = 0, l = _18f.length; i < l; i++) { - _190.call(_191, _18f[i], i, _18f) - } - } - }, _everyOrSome: function (_194, arr, _196, _197) { - if (dojo.lang.isString(arr)) { - arr = arr.split('') - } - if (Array.every) { - return Array[_194 ? 'every' : 'some'](arr, _196, _197) - } else { - if (!_197) { - _197 = dj_global - } - for (var i = 0, l = arr.length; i < l; i++) { - var _19a = _196.call(_197, arr[i], i, arr); - if (_194 && !_19a) { - return false - } else { - if ((!_194) && (_19a)) { - return true - } - } - } - return Boolean(_194) - } - }, every: function (arr, _19c, _19d) { - return this._everyOrSome(true, arr, _19c, _19d) - }, some: function (arr, _19f, _1a0) { - return this._everyOrSome(false, arr, _19f, _1a0) - }, filter: function (arr, _1a2, _1a3) { - var _1a4 = dojo.lang.isString(arr); - if (_1a4) { - arr = arr.split('') - } - var _1a5; - if (Array.filter) { - _1a5 = Array.filter(arr, _1a2, _1a3) - } else { - if (!_1a3) { - if (arguments.length >= 3) { - dojo.raise('thisObject doesn\'t exist!') - } - _1a3 = dj_global - } - _1a5 = []; - for (var i = 0; i < arr.length; i++) { - if (_1a2.call(_1a3, arr[i], i, arr)) { - _1a5.push(arr[i]) - } - } - } - if (_1a4) { - return _1a5.join('') - } else { - return _1a5 - } - }, unnest: function () { - var out = []; - for (var i = 0; i < arguments.length; i++) { - if (dojo.lang.isArrayLike(arguments[i])) { - var add = dojo.lang.unnest.apply(this, arguments[i]); - out = out.concat(add) - } else { - out.push(arguments[i]) - } - } - return out - }, toArray: function (_1aa, _1ab) { - var _1ac = []; - for (var i = _1ab || 0; i < _1aa.length; i++) { - _1ac.push(_1aa[i]) - } - return _1ac - } -}); -dojo.provide('dojo.lang.func'); -dojo.lang.hitch = function (_1ae, _1af) { - var args = []; - for (var x = 2; x < arguments.length; x++) { - args.push(arguments[x]) - } - var fcn = (dojo.lang.isString(_1af) ? _1ae[_1af] : _1af) || function () { - }; - return function () { - var ta = args.concat([]); - for (var x = 0; x < arguments.length; x++) { - ta.push(arguments[x]) - } - return fcn.apply(_1ae, ta) - } -}; -dojo.lang.anonCtr = 0; -dojo.lang.anon = {}; -dojo.lang.nameAnonFunc = function (_1b5, _1b6, _1b7) { - var nso = (_1b6 || dojo.lang.anon); - if ((_1b7) || ((dj_global['djConfig']) && (djConfig['slowAnonFuncLookups'] == true))) { - for (var x in nso) { - try { - if (nso[x] === _1b5) { - return x - } - } catch (e) { - } - } - } - var ret = '__' + dojo.lang.anonCtr++; - while (typeof nso[ret] != 'undefined') { - ret = '__' + dojo.lang.anonCtr++ - } - nso[ret] = _1b5; - return ret -}; -dojo.lang.forward = function (_1bb) { - return function () { - return this[_1bb].apply(this, arguments) - } -}; -dojo.lang.curry = function (_1bc, func) { - var _1be = []; - _1bc = _1bc || dj_global; - if (dojo.lang.isString(func)) { - func = _1bc[func] - } - for (var x = 2; x < arguments.length; x++) { - _1be.push(arguments[x]) - } - var _1c0 = (func['__preJoinArity'] || func.length) - _1be.length; - - function gather(_1c1, _1c2, _1c3) { - var _1c4 = _1c3; - var _1c5 = _1c2.slice(0); - for (var x = 0; x < _1c1.length; x++) { - _1c5.push(_1c1[x]) - } - _1c3 = _1c3 - _1c1.length; - if (_1c3 <= 0) { - var res = func.apply(_1bc, _1c5); - _1c3 = _1c4; - return res - } else { - return function () { - return gather(arguments, _1c5, _1c3) - } - } - } - - return gather([], _1be, _1c0) -}; -dojo.lang.curryArguments = function (_1c8, func, args, _1cb) { - var _1cc = []; - var x = _1cb || 0; - for (x = _1cb; x < args.length; x++) { - _1cc.push(args[x]) - } - return dojo.lang.curry.apply(dojo.lang, [_1c8, func].concat(_1cc)) -}; -dojo.lang.tryThese = function () { - for (var x = 0; x < arguments.length; x++) { - try { - if (typeof arguments[x] == 'function') { - var ret = (arguments[x]()); - if (ret) { - return ret - } - } - } catch (e) { - dojo.debug(e) - } - } -}; -dojo.lang.delayThese = function (farr, cb, _1d2, _1d3) { - if (!farr.length) { - if (typeof _1d3 == 'function') { - _1d3() - } - return - } - if ((typeof _1d2 == 'undefined') && (typeof cb == 'number')) { - _1d2 = cb; - cb = function () { - } - } else { - if (!cb) { - cb = function () { - }; - if (!_1d2) { - _1d2 = 0 - } - } - } - setTimeout(function () { - (farr.shift())(); - cb(); - dojo.lang.delayThese(farr, cb, _1d2, _1d3) - }, _1d2) -}; -dojo.provide('dojo.string.extras'); -dojo.string.substituteParams = function (_1d4, hash) { - var map = (typeof hash == 'object') ? hash : dojo.lang.toArray(arguments, 1); - return _1d4.replace(/\%\{(\w+)\}/g, function (_1d7, key) { - if (typeof (map[key]) != 'undefined' && map[key] != null) { - return map[key] - } - dojo.raise('Substitution not found: ' + key) - }) -}; -dojo.string.capitalize = function (str) { - if (!dojo.lang.isString(str)) { - return '' - } - if (arguments.length == 0) { - str = this - } - var _1da = str.split(' '); - for (var i = 0; i < _1da.length; i++) { - _1da[i] = _1da[i].charAt(0).toUpperCase() + _1da[i].substring(1) - } - return _1da.join(' ') -}; -dojo.string.isBlank = function (str) { - if (!dojo.lang.isString(str)) { - return true - } - return (dojo.string.trim(str).length == 0) -}; -dojo.string.encodeAscii = function (str) { - if (!dojo.lang.isString(str)) { - return str - } - var ret = ''; - var _1df = escape(str); - var _1e0, re = /%u([0-9A-F]{4})/i; - while ((_1e0 = _1df.match(re))) { - var num = Number('0x' + _1e0[1]); - var _1e3 = escape('&#' + num + ';'); - ret += _1df.substring(0, _1e0.index) + _1e3; - _1df = _1df.substring(_1e0.index + _1e0[0].length) - } - ret += _1df.replace(/\+/g, '%2B'); - return ret -}; -dojo.string.escape = function (type, str) { - var args = dojo.lang.toArray(arguments, 1); - switch (type.toLowerCase()) { - case 'xml': - case 'html': - case 'xhtml': - return dojo.string.escapeXml.apply(this, args); - case 'sql': - return dojo.string.escapeSql.apply(this, args); - case 'regexp': - case 'regex': - return dojo.string.escapeRegExp.apply(this, args); - case 'javascript': - case 'jscript': - case 'js': - return dojo.string.escapeJavaScript.apply(this, args); - case 'ascii': - return dojo.string.encodeAscii.apply(this, args); - default: - return str - } -}; -dojo.string.escapeXml = function (str, _1e8) { - str = str.replace(/&/gm, '&').replace(//gm, '>').replace(/"/gm, '"'); - if (!_1e8) { - str = str.replace(/'/gm, ''') - } - return str -}; -dojo.string.escapeSql = function (str) { - return str.replace(/'/gm, '\'\'') -}; -dojo.string.escapeRegExp = function (str) { - return str.replace(/\\/gm, '\\\\').replace(/([\f\b\n\t\r[\^$|?*+(){}])/gm, '\\$1') -}; -dojo.string.escapeJavaScript = function (str) { - return str.replace(/(["'\f\b\n\t\r])/gm, '\\$1') -}; -dojo.string.escapeString = function (str) { - return ('"' - + str.replace(/(["\\])/g, '\\$1') - + '"').replace(/[\f]/g, '\\f').replace(/[\b]/g, '\\b').replace(/[\n]/g, '\\n').replace(/[\t]/g, '\\t').replace(/[\r]/g, '\\r') -}; -dojo.string.summary = function (str, len) { - if (!len || str.length <= len) { - return str - } - return str.substring(0, len).replace(/\.+$/, '') + '...' -}; -dojo.string.endsWith = function (str, end, _1f1) { - if (_1f1) { - str = str.toLowerCase(); - end = end.toLowerCase() - } - if ((str.length - end.length) < 0) { - return false - } - return str.lastIndexOf(end) == str.length - end.length -}; -dojo.string.endsWithAny = function (str) { - for (var i = 1; i < arguments.length; i++) { - if (dojo.string.endsWith(str, arguments[i])) { - return true - } - } - return false -}; -dojo.string.startsWith = function (str, _1f5, _1f6) { - if (_1f6) { - str = str.toLowerCase(); - _1f5 = _1f5.toLowerCase() - } - return str.indexOf(_1f5) == 0 -}; -dojo.string.startsWithAny = function (str) { - for (var i = 1; i < arguments.length; i++) { - if (dojo.string.startsWith(str, arguments[i])) { - return true - } - } - return false -}; -dojo.string.has = function (str) { - for (var i = 1; i < arguments.length; i++) { - if (str.indexOf(arguments[i]) > -1) { - return true - } - } - return false -}; -dojo.string.normalizeNewlines = function (text, _1fc) { - if (_1fc == '\n') { - text = text.replace(/\r\n/g, '\n'); - text = text.replace(/\r/g, '\n') - } else { - if (_1fc == '\r') { - text = text.replace(/\r\n/g, '\r'); - text = text.replace(/\n/g, '\r') - } else { - text = text.replace(/([^\r])\n/g, '$1\r\n').replace(/\r([^\n])/g, '\r\n$1') - } - } - return text -}; -dojo.string.splitEscaped = function (str, _1fe) { - var _1ff = []; - for (var i = 0, _201 = 0; i < str.length; i++) { - if (str.charAt(i) == '\\') { - i++; - continue - } - if (str.charAt(i) == _1fe) { - _1ff.push(str.substring(_201, i)); - _201 = i + 1 - } - } - _1ff.push(str.substr(_201)); - return _1ff -}; -dojo.provide('dojo.dom'); -dojo.dom.ELEMENT_NODE = 1; -dojo.dom.ATTRIBUTE_NODE = 2; -dojo.dom.TEXT_NODE = 3; -dojo.dom.CDATA_SECTION_NODE = 4; -dojo.dom.ENTITY_REFERENCE_NODE = 5; -dojo.dom.ENTITY_NODE = 6; -dojo.dom.PROCESSING_INSTRUCTION_NODE = 7; -dojo.dom.COMMENT_NODE = 8; -dojo.dom.DOCUMENT_NODE = 9; -dojo.dom.DOCUMENT_TYPE_NODE = 10; -dojo.dom.DOCUMENT_FRAGMENT_NODE = 11; -dojo.dom.NOTATION_NODE = 12; -dojo.dom.dojoml = 'http://www.dojotoolkit.org/2004/dojoml'; -dojo.dom.xmlns = { - svg: 'http://www.w3.org/2000/svg', - smil: 'http://www.w3.org/2001/SMIL20/', - mml: 'http://www.w3.org/1998/Math/MathML', - cml: 'http://www.xml-cml.org', - xlink: 'http://www.w3.org/1999/xlink', - xhtml: 'http://www.w3.org/1999/xhtml', - xul: 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul', - xbl: 'http://www.mozilla.org/xbl', - fo: 'http://www.w3.org/1999/XSL/Format', - xsl: 'http://www.w3.org/1999/XSL/Transform', - xslt: 'http://www.w3.org/1999/XSL/Transform', - xi: 'http://www.w3.org/2001/XInclude', - xforms: 'http://www.w3.org/2002/01/xforms', - saxon: 'http://icl.com/saxon', - xalan: 'http://xml.apache.org/xslt', - xsd: 'http://www.w3.org/2001/XMLSchema', - dt: 'http://www.w3.org/2001/XMLSchema-datatypes', - xsi: 'http://www.w3.org/2001/XMLSchema-instance', - rdf: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', - rdfs: 'http://www.w3.org/2000/01/rdf-schema#', - dc: 'http://purl.org/dc/elements/1.1/', - dcq: 'http://purl.org/dc/qualifiers/1.0', - 'soap-env': 'http://schemas.xmlsoap.org/soap/envelope/', - wsdl: 'http://schemas.xmlsoap.org/wsdl/', - AdobeExtensions: 'http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/' -}; -dojo.dom.isNode = function (wh) { - if (typeof Element == 'function') { - try { - return wh instanceof Element - } catch (e) { - } - } else { - return wh && !isNaN(wh.nodeType) - } -}; -dojo.dom.getUniqueId = function () { - var _203 = dojo.doc(); - do { - var id = 'dj_unique_' + (++arguments.callee._idIncrement) - } while (_203.getElementById(id)); - return id -}; -dojo.dom.getUniqueId._idIncrement = 0; -dojo.dom.firstElement = dojo.dom.getFirstChildElement = function (_205, _206) { - var node = _205.firstChild; - while (node && node.nodeType != dojo.dom.ELEMENT_NODE) { - node = node.nextSibling - } - if (_206 && node && node.tagName && node.tagName.toLowerCase() != _206.toLowerCase()) { - node = dojo.dom.nextElement(node, _206) - } - return node -}; -dojo.dom.lastElement = dojo.dom.getLastChildElement = function (_208, _209) { - var node = _208.lastChild; - while (node && node.nodeType != dojo.dom.ELEMENT_NODE) { - node = node.previousSibling - } - if (_209 && node && node.tagName && node.tagName.toLowerCase() != _209.toLowerCase()) { - node = dojo.dom.prevElement(node, _209) - } - return node -}; -dojo.dom.nextElement = dojo.dom.getNextSiblingElement = function (node, _20c) { - if (!node) { - return null - } - do { - node = node.nextSibling - } while (node && node.nodeType != dojo.dom.ELEMENT_NODE); - if (node && _20c && _20c.toLowerCase() != node.tagName.toLowerCase()) { - return dojo.dom.nextElement(node, _20c) - } - return node -}; -dojo.dom.prevElement = dojo.dom.getPreviousSiblingElement = function (node, _20e) { - if (!node) { - return null - } - if (_20e) { - _20e = _20e.toLowerCase() - } - do { - node = node.previousSibling - } while (node && node.nodeType != dojo.dom.ELEMENT_NODE); - if (node && _20e && _20e.toLowerCase() != node.tagName.toLowerCase()) { - return dojo.dom.prevElement(node, _20e) - } - return node -}; -dojo.dom.moveChildren = function (_20f, _210, trim) { - var _212 = 0; - if (trim) { - while (_20f.hasChildNodes() && _20f.firstChild.nodeType == dojo.dom.TEXT_NODE) { - _20f.removeChild(_20f.firstChild) - } - while (_20f.hasChildNodes() && _20f.lastChild.nodeType == dojo.dom.TEXT_NODE) { - _20f.removeChild(_20f.lastChild) - } - } - while (_20f.hasChildNodes()) { - _210.appendChild(_20f.firstChild); - _212++ - } - return _212 -}; -dojo.dom.copyChildren = function (_213, _214, trim) { - var _216 = _213.cloneNode(true); - return this.moveChildren(_216, _214, trim) -}; -dojo.dom.replaceChildren = function (node, _218) { - var _219 = []; - if (dojo.render.html.ie) { - for (var i = 0; i < node.childNodes.length; i++) { - _219.push(node.childNodes[i]) - } - } - dojo.dom.removeChildren(node); - node.appendChild(_218); - for (var i = 0; i < _219.length; i++) { - dojo.dom.destroyNode(_219[i]) - } -}; -dojo.dom.removeChildren = function (node) { - var _21c = node.childNodes.length; - while (node.hasChildNodes()) { - dojo.dom.removeNode(node.firstChild) - } - return _21c -}; -dojo.dom.replaceNode = function (node, _21e) { - return node.parentNode.replaceChild(_21e, node) -}; -dojo.dom.destroyNode = function (node) { - if (node.parentNode) { - node = dojo.dom.removeNode(node) - } - if (node.nodeType != 3) { - if (dojo.evalObjPath('dojo.event.browser.clean', false)) { - dojo.event.browser.clean(node) - } - if (dojo.render.html.ie) { - node.outerHTML = '' - } - } -}; -dojo.dom.removeNode = function (node) { - if (node && node.parentNode) { - return node.parentNode.removeChild(node) - } -}; -dojo.dom.getAncestors = function (node, _222, _223) { - var _224 = []; - var _225 = (_222 && (_222 instanceof Function || typeof _222 == 'function')); - while (node) { - if (!_225 || _222(node)) { - _224.push(node) - } - if (_223 && _224.length > 0) { - return _224[0] - } - node = node.parentNode - } - if (_223) { - return null - } - return _224 -}; -dojo.dom.getAncestorsByTag = function (node, tag, _228) { - tag = tag.toLowerCase(); - return dojo.dom.getAncestors(node, function (el) { - return ((el.tagName) && (el.tagName.toLowerCase() == tag)) - }, _228) -}; -dojo.dom.getFirstAncestorByTag = function (node, tag) { - return dojo.dom.getAncestorsByTag(node, tag, true) -}; -dojo.dom.isDescendantOf = function (node, _22d, _22e) { - if (_22e && node) { - node = node.parentNode - } - while (node) { - if (node == _22d) { - return true - } - node = node.parentNode - } - return false -}; -dojo.dom.innerXML = function (node) { - if (node.innerXML) { - return node.innerXML - } else { - if (node.xml) { - return node.xml - } else { - if (typeof XMLSerializer != 'undefined') { - return (new XMLSerializer()).serializeToString(node) - } - } - } -}; -dojo.dom.createDocument = function () { - var doc = null; - var _231 = dojo.doc(); - if (!dj_undef('ActiveXObject')) { - var _232 = ['MSXML2', 'Microsoft', 'MSXML', 'MSXML3']; - for (var i = 0; i < _232.length; i++) { - try { - doc = new ActiveXObject(_232[i] + '.XMLDOM') - } catch (e) { - } - if (doc) { - break - } - } - } else { - if ((_231.implementation) && (_231.implementation.createDocument)) { - doc = _231.implementation.createDocument('', '', null) - } - } - return doc -}; -dojo.dom.createDocumentFromText = function (str, _235) { - if (!_235) { - _235 = 'text/xml' - } - if (!dj_undef('DOMParser')) { - var _236 = new DOMParser(); - return _236.parseFromString(str, _235) - } else { - if (!dj_undef('ActiveXObject')) { - var _237 = dojo.dom.createDocument(); - if (_237) { - _237.async = false; - _237.loadXML(str); - return _237 - } else { - dojo.debug('toXml didn\'t work?') - } - } else { - var _238 = dojo.doc(); - if (_238.createElement) { - var tmp = _238.createElement('xml'); - tmp.innerHTML = str; - if (_238.implementation && _238.implementation.createDocument) { - var _23a = _238.implementation.createDocument('foo', '', null); - for (var i = 0; i < tmp.childNodes.length; i++) { - _23a.importNode(tmp.childNodes.item(i), true) - } - return _23a - } - return ((tmp.document) && (tmp.document.firstChild ? tmp.document.firstChild : tmp)) - } - } - } - return null -}; -dojo.dom.prependChild = function (node, _23d) { - if (_23d.firstChild) { - _23d.insertBefore(node, _23d.firstChild) - } else { - _23d.appendChild(node) - } - return true -}; -dojo.dom.insertBefore = function (node, ref, _240) { - if ((_240 != true) && (node === ref || node.nextSibling === ref)) { - return false - } - var _241 = ref.parentNode; - _241.insertBefore(node, ref); - return true -}; -dojo.dom.insertAfter = function (node, ref, _244) { - var pn = ref.parentNode; - if (ref == pn.lastChild) { - if ((_244 != true) && (node === ref)) { - return false - } - pn.appendChild(node) - } else { - return this.insertBefore(node, ref.nextSibling, _244) - } - return true -}; -dojo.dom.insertAtPosition = function (node, ref, _248) { - if ((!node) || (!ref) || (!_248)) { - return false - } - switch (_248.toLowerCase()) { - case 'before': - return dojo.dom.insertBefore(node, ref); - case 'after': - return dojo.dom.insertAfter(node, ref); - case 'first': - if (ref.firstChild) { - return dojo.dom.insertBefore(node, ref.firstChild) - } else { - ref.appendChild(node); - return true - } - break; - default: - ref.appendChild(node); - return true - } -}; -dojo.dom.insertAtIndex = function (node, _24a, _24b) { - var _24c = _24a.childNodes; - if (!_24c.length || _24c.length == _24b) { - _24a.appendChild(node); - return true - } - if (_24b == 0) { - return dojo.dom.prependChild(node, _24a) - } - return dojo.dom.insertAfter(node, _24c[_24b - 1]) -}; -dojo.dom.textContent = function (node, text) { - if (arguments.length > 1) { - var _24f = dojo.doc(); - dojo.dom.replaceChildren(node, _24f.createTextNode(text)); - return text - } else { - if (node.textContent != undefined) { - return node.textContent - } - var _250 = ''; - if (node == null) { - return _250 - } - for (var i = 0; i < node.childNodes.length; i++) { - switch (node.childNodes[i].nodeType) { - case 1: - case 5: - _250 += dojo.dom.textContent(node.childNodes[i]); - break; - case 3: - case 2: - case 4: - _250 += node.childNodes[i].nodeValue; - break; - default: - break - } - } - return _250 - } -}; -dojo.dom.hasParent = function (node) { - return Boolean(node && node.parentNode && dojo.dom.isNode(node.parentNode)) -}; -dojo.dom.isTag = function (node) { - if (node && node.tagName) { - for (var i = 1; i < arguments.length; i++) { - if (node.tagName == String(arguments[i])) { - return String(arguments[i]) - } - } - } - return '' -}; -dojo.dom.setAttributeNS = function (elem, _256, _257, _258) { - if (elem == null || ((elem == undefined) && (typeof elem == 'undefined'))) { - dojo.raise('No element given to dojo.dom.setAttributeNS') - } - if (!((elem.setAttributeNS == undefined) && (typeof elem.setAttributeNS == 'undefined'))) { - elem.setAttributeNS(_256, _257, _258) - } else { - var _259 = elem.ownerDocument; - var _25a = _259.createNode(2, _257, _256); - _25a.nodeValue = _258; - elem.setAttributeNode(_25a) - } -}; -dojo.provide('dojo.undo.browser'); -try { - if ((!djConfig['preventBackButtonFix']) && (!dojo.hostenv.post_load_)) { - document.write('') - } -} catch (e) { -} -if (dojo.render.html.opera) { - dojo.debug('Opera is not supported with dojo.undo.browser, so back/forward detection will not work.') -} -dojo.undo.browser = { - initialHref: (!dj_undef('window')) ? window.location.href : '', - initialHash: (!dj_undef('window')) ? window.location.hash : '', - moveForward: false, - historyStack: [], - forwardStack: [], - historyIframe: null, - bookmarkAnchor: null, - locationTimer: null, - setInitialState: function (args) { - this.initialState = this._createState(this.initialHref, args, this.initialHash) - }, - addToHistory: function (args) { - this.forwardStack = []; - var hash = null; - var url = null; - if (!this.historyIframe) { - if (djConfig['useXDomain'] && !djConfig['dojoIframeHistoryUrl']) { - dojo.debug('dojo.undo.browser: When using cross-domain Dojo builds,' - + ' please save iframe_history.html to your domain and set djConfig.dojoIframeHistoryUrl' - + ' to the path on your domain to iframe_history.html') - } - this.historyIframe = window.frames['djhistory'] - } - if (!this.bookmarkAnchor) { - this.bookmarkAnchor = document.createElement('a'); - dojo.body().appendChild(this.bookmarkAnchor); - this.bookmarkAnchor.style.display = 'none' - } - if (args['changeUrl']) { - hash = '#' + ((args['changeUrl'] !== true) ? args['changeUrl'] : (new Date()).getTime()); - if (this.historyStack.length == 0 && this.initialState.urlHash == hash) { - this.initialState = this._createState(url, args, hash); - return - } else { - if (this.historyStack.length > 0 && this.historyStack[this.historyStack.length - 1].urlHash == hash) { - this.historyStack[this.historyStack.length - 1] = this._createState(url, args, hash); - return - } - } - this.changingUrl = true; - setTimeout('window.location.href = \'' + hash + '\'; dojo.undo.browser.changingUrl = false;', 1); - this.bookmarkAnchor.href = hash; - if (dojo.render.html.ie) { - url = this._loadIframeHistory(); - var _25f = args['back'] || args['backButton'] || args['handle']; - var tcb = function (_261) { - if (window.location.hash != '') { - setTimeout('window.location.href = \'' + hash + '\';', 1) - } - _25f.apply(this, [_261]) - }; - if (args['back']) { - args.back = tcb - } else { - if (args['backButton']) { - args.backButton = tcb - } else { - if (args['handle']) { - args.handle = tcb - } - } - } - var _262 = args['forward'] || args['forwardButton'] || args['handle']; - var tfw = function (_264) { - if (window.location.hash != '') { - window.location.href = hash - } - if (_262) { - _262.apply(this, [_264]) - } - }; - if (args['forward']) { - args.forward = tfw - } else { - if (args['forwardButton']) { - args.forwardButton = tfw - } else { - if (args['handle']) { - args.handle = tfw - } - } - } - } else { - if (dojo.render.html.moz) { - if (!this.locationTimer) { - this.locationTimer = setInterval('dojo.undo.browser.checkLocation();', 200) - } - } - } - } else { - url = this._loadIframeHistory() - } - this.historyStack.push(this._createState(url, args, hash)) - }, - checkLocation: function () { - if (!this.changingUrl) { - var hsl = this.historyStack.length; - if ((window.location.hash == this.initialHash || window.location.href == this.initialHref) && (hsl == 1)) { - this.handleBackButton(); - return - } - if (this.forwardStack.length > 0) { - if (this.forwardStack[this.forwardStack.length - 1].urlHash == window.location.hash) { - this.handleForwardButton(); - return - } - } - if ((hsl >= 2) && (this.historyStack[hsl - 2])) { - if (this.historyStack[hsl - 2].urlHash == window.location.hash) { - this.handleBackButton(); - - } - } - } - }, - iframeLoaded: function (evt, _267) { - if (!dojo.render.html.opera) { - var _268 = this._getUrlQuery(_267.href); - if (_268 == null) { - if (this.historyStack.length == 1) { - this.handleBackButton() - } - return - } - if (this.moveForward) { - this.moveForward = false; - return - } - if (this.historyStack.length >= 2 && _268 == this._getUrlQuery(this.historyStack[this.historyStack.length - - 2].url)) { - this.handleBackButton() - } else { - if (this.forwardStack.length > 0 && _268 == this._getUrlQuery(this.forwardStack[this.forwardStack.length - - 1].url)) { - this.handleForwardButton() - } - } - } - }, - handleBackButton: function () { - var _269 = this.historyStack.pop(); - if (!_269) { - return - } - var last = this.historyStack[this.historyStack.length - 1]; - if (!last && this.historyStack.length == 0) { - last = this.initialState - } - if (last) { - if (last.kwArgs['back']) { - last.kwArgs['back']() - } else { - if (last.kwArgs['backButton']) { - last.kwArgs['backButton']() - } else { - if (last.kwArgs['handle']) { - last.kwArgs.handle('back') - } - } - } - } - this.forwardStack.push(_269) - }, - handleForwardButton: function () { - var last = this.forwardStack.pop(); - if (!last) { - return - } - if (last.kwArgs['forward']) { - last.kwArgs.forward() - } else { - if (last.kwArgs['forwardButton']) { - last.kwArgs.forwardButton() - } else { - if (last.kwArgs['handle']) { - last.kwArgs.handle('forward') - } - } - } - this.historyStack.push(last) - }, - _createState: function (url, args, hash) { - return {'url': url, 'kwArgs': args, 'urlHash': hash} - }, - _getUrlQuery: function (url) { - var _270 = url.split('?'); - if (_270.length < 2) { - return null - } else { - return _270[1] - } - }, - _loadIframeHistory: function () { - var url = (djConfig['dojoIframeHistoryUrl'] || dojo.hostenv.getBaseScriptUri() + 'iframe_history.html') - + '?' - + (new Date()).getTime(); - this.moveForward = true; - dojo.io.setIFrameSrc(this.historyIframe, url, false); - return url - } -}; -dojo.provide('dojo.io.BrowserIO'); -if (!dj_undef('window')) { - dojo.io.checkChildrenForFile = function (node) { - var _273 = false; - var _274 = node.getElementsByTagName('input'); - dojo.lang.forEach(_274, function (_275) { - if (_273) { - return - } - if (_275.getAttribute('type') == 'file') { - _273 = true - } - }); - return _273 - }; - dojo.io.formHasFile = function (_276) { - return dojo.io.checkChildrenForFile(_276) - }; - dojo.io.updateNode = function (node, _278) { - node = dojo.byId(node); - var args = _278; - if (dojo.lang.isString(_278)) { - args = {url: _278} - } - args.mimetype = 'text/html'; - args.load = function (t, d, e) { - while (node.firstChild) { - dojo.dom.destroyNode(node.firstChild) - } - node.innerHTML = d - }; - dojo.io.bind(args) - }; - dojo.io.formFilter = function (node) { - var type = (node.type || '').toLowerCase(); - return !node.disabled && node.name && !dojo.lang.inArray(['file', 'submit', 'image', 'reset', 'button'], type) - }; - dojo.io.encodeForm = function (_27f, _280, _281) { - if ((!_27f) || (!_27f.tagName) || (!_27f.tagName.toLowerCase() == 'form')) { - dojo.raise('Attempted to encode a non-form element.') - } - if (!_281) { - _281 = dojo.io.formFilter - } - var enc = /utf/i.test(_280 || '') ? encodeURIComponent : dojo.string.encodeAscii; - var _283 = []; - for (var i = 0; i < _27f.elements.length; i++) { - var elm = _27f.elements[i]; - if (!elm || elm.tagName.toLowerCase() == 'fieldset' || !_281(elm)) { - continue - } - var name = enc(elm.name); - var type = elm.type.toLowerCase(); - if (type == 'select-multiple') { - for (var j = 0; j < elm.options.length; j++) { - if (elm.options[j].selected) { - _283.push(name + '=' + enc(elm.options[j].value)) - } - } - } else { - if (dojo.lang.inArray(['radio', 'checkbox'], type)) { - if (elm.checked) { - _283.push(name + '=' + enc(elm.value)) - } - } else { - _283.push(name + '=' + enc(elm.value)) - } - } - } - var _289 = _27f.getElementsByTagName('input'); - for (var i = 0; i < _289.length; i++) { - var _28a = _289[i]; - if (_28a.type.toLowerCase() == 'image' && _28a.form == _27f && _281(_28a)) { - var name = enc(_28a.name); - _283.push(name + '=' + enc(_28a.value)); - _283.push(name + '.x=0'); - _283.push(name + '.y=0') - } - } - return _283.join('&') + '&' - }; - dojo.io.FormBind = function (args) { - this.bindArgs = {}; - if (args && args.formNode) { - this.init(args) - } else { - if (args) { - this.init({formNode: args}) - } - } - }; - dojo.lang.extend(dojo.io.FormBind, { - form: null, bindArgs: null, clickedButton: null, init: function (args) { - var form = dojo.byId(args.formNode); - if (!form || !form.tagName || form.tagName.toLowerCase() != 'form') { - throw new Error('FormBind: Couldn\'t apply, invalid form') - } else { - if (this.form == form) { - return - } else { - if (this.form) { - throw new Error('FormBind: Already applied to a form') - } - } - } - dojo.lang.mixin(this.bindArgs, args); - this.form = form; - this.connect(form, 'onsubmit', 'submit'); - for (var i = 0; i < form.elements.length; i++) { - var node = form.elements[i]; - if (node && node.type && dojo.lang.inArray(['submit', 'button'], node.type.toLowerCase())) { - this.connect(node, 'onclick', 'click') - } - } - var _290 = form.getElementsByTagName('input'); - for (var i = 0; i < _290.length; i++) { - var _291 = _290[i]; - if (_291.type.toLowerCase() == 'image' && _291.form == form) { - this.connect(_291, 'onclick', 'click') - } - } - }, onSubmit: function (form) { - return true - }, submit: function (e) { - e.preventDefault(); - if (this.onSubmit(this.form)) { - dojo.io.bind(dojo.lang.mixin(this.bindArgs, {formFilter: dojo.lang.hitch(this, 'formFilter')})) - } - }, click: function (e) { - var node = e.currentTarget; - if (node.disabled) { - return - } - this.clickedButton = node - }, formFilter: function (node) { - var type = (node.type || '').toLowerCase(); - var _298 = false; - if (node.disabled || !node.name) { - _298 = false - } else { - if (dojo.lang.inArray(['submit', 'button', 'image'], type)) { - if (!this.clickedButton) { - this.clickedButton = node - } - _298 = node == this.clickedButton - } else { - _298 = !dojo.lang.inArray(['file', 'submit', 'reset', 'button'], type) - } - } - return _298 - }, connect: function (_299, _29a, _29b) { - if (dojo.evalObjPath('dojo.event.connect')) { - dojo.event.connect(_299, _29a, this, _29b) - } else { - var fcn = dojo.lang.hitch(this, _29b); - _299[_29a] = function (e) { - if (!e) { - e = window.event - } - if (!e.currentTarget) { - e.currentTarget = e.srcElement - } - if (!e.preventDefault) { - e.preventDefault = function () { - window.event.returnValue = false - } - } - fcn(e) - } - } - } - }); - dojo.io.XMLHTTPTransport = new function () { - var _29e = this; - var _29f = {}; - this.useCache = false; - this.preventCache = false; - - function getCacheKey(url, _2a1, _2a2) { - return url + '|' + _2a1 + '|' + _2a2.toLowerCase() - } - - function addToCache(url, _2a4, _2a5, http) { - _29f[getCacheKey(url, _2a4, _2a5)] = http - } - - function getFromCache(url, _2a8, _2a9) { - return _29f[getCacheKey(url, _2a8, _2a9)] - } - - this.clearCache = function () { - _29f = {} - }; - - function doLoad(_2aa, http, url, _2ad, _2ae) { - if (((http.status >= 200) && (http.status < 300)) - || (http.status == 304) - || (http.status == 1223) - || (location.protocol == 'file:' && (http.status == 0 || http.status == undefined)) - || (location.protocol == 'chrome:' && (http.status == 0 || http.status == undefined))) { - var ret; - if (_2aa.method.toLowerCase() == 'head') { - var _2b0 = http.getAllResponseHeaders(); - ret = {}; - ret.toString = function () { - return _2b0 - }; - var _2b1 = _2b0.split(/[\r\n]+/g); - for (var i = 0; i < _2b1.length; i++) { - var pair = _2b1[i].match(/^([^:]+)\s*:\s*(.+)$/i); - if (pair) { - ret[pair[1]] = pair[2] - } - } - } else { - if (_2aa.mimetype == 'text/javascript') { - try { - ret = dj_eval(http.responseText) - } catch (e) { - dojo.debug(e); - dojo.debug(http.responseText); - ret = null - } - } else { - if (_2aa.mimetype.substr(0, 9) == 'text/json' || _2aa.mimetype.substr(0, 16) == 'application/json') { - try { - ret = dj_eval('(' + _2aa.jsonFilter(http.responseText) + ')') - } catch (e) { - dojo.debug(e); - dojo.debug(http.responseText); - ret = false - } - } else { - if ((_2aa.mimetype == 'application/xml') || (_2aa.mimetype == 'text/xml')) { - ret = http.responseXML; - if (!ret || typeof ret == 'string' || !http.getResponseHeader('Content-Type')) { - ret = dojo.dom.createDocumentFromText(http.responseText) - } - } else { - ret = http.responseText - } - } - } - } - if (_2ae) { - addToCache(url, _2ad, _2aa.method, http) - } - _2aa[(typeof _2aa.load == 'function') ? 'load' : 'handle']('load', ret, http, _2aa) - } else { - var _2b4 = new dojo.io.Error('XMLHttpTransport Error: ' + http.status + ' ' + http.statusText); - _2aa[(typeof _2aa.error == 'function') ? 'error' : 'handle']('error', _2b4, http, _2aa) - } - } - - function setHeaders(http, _2b6) { - if (_2b6['headers']) { - for (var _2b7 in _2b6['headers']) { - if (_2b7.toLowerCase() == 'content-type' && !_2b6['contentType']) { - _2b6['contentType'] = _2b6['headers'][_2b7] - } else { - http.setRequestHeader(_2b7, _2b6['headers'][_2b7]) - } - } - } - } - - this.inFlight = []; - this.inFlightTimer = null; - this.startWatchingInFlight = function () { - if (!this.inFlightTimer) { - this.inFlightTimer = setTimeout('dojo.io.XMLHTTPTransport.watchInFlight();', 10) - } - }; - this.watchInFlight = function () { - var now = null; - if (!dojo.hostenv._blockAsync && !_29e._blockAsync) { - for (var x = this.inFlight.length - 1; x >= 0; x--) { - try { - var tif = this.inFlight[x]; - if (!tif || tif.http._aborted || !tif.http.readyState) { - this.inFlight.splice(x, 1); - continue - } - if (4 == tif.http.readyState) { - this.inFlight.splice(x, 1); - doLoad(tif.req, tif.http, tif.url, tif.query, tif.useCache) - } else { - if (tif.startTime) { - if (!now) { - now = (new Date()).getTime() - } - if (tif.startTime + (tif.req.timeoutSeconds * 1000) < now) { - if (typeof tif.http.abort == 'function') { - tif.http.abort() - } - this.inFlight.splice(x, 1); - tif.req[(typeof tif.req.timeout - == 'function') ? 'timeout' : 'handle']('timeout', null, tif.http, tif.req) - } - } - } - } catch (e) { - try { - var _2bb = new dojo.io.Error('XMLHttpTransport.watchInFlight Error: ' + e); - tif.req[(typeof tif.req.error == 'function') ? 'error' : 'handle']('error', _2bb, tif.http, tif.req) - } catch (e2) { - dojo.debug('XMLHttpTransport error callback failed: ' + e2) - } - } - } - } - clearTimeout(this.inFlightTimer); - if (this.inFlight.length == 0) { - this.inFlightTimer = null; - return - } - this.inFlightTimer = setTimeout('dojo.io.XMLHTTPTransport.watchInFlight();', 10) - }; - var _2bc = dojo.hostenv.getXmlhttpObject() ? true : false; - this.canHandle = function (_2bd) { - var mlc = _2bd['mimetype'].toLowerCase() || ''; - return _2bc - && ((dojo.lang.inArray(['text/plain', 'text/html', 'application/xml', 'text/xml', 'text/javascript'], mlc)) - || (mlc.substr(0, 9) == 'text/json' || mlc.substr(0, 16) == 'application/json')) - && !(_2bd['formNode'] && dojo.io.formHasFile(_2bd['formNode'])) - }; - this.multipartBoundary = '45309FFF-BD65-4d50-99C9-36986896A96F'; - this.bind = function (_2bf) { - if (!_2bf['url']) { - if (!_2bf['formNode'] - && (_2bf['backButton'] || _2bf['back'] || _2bf['changeUrl'] || _2bf['watchForURL']) - && (!djConfig.preventBackButtonFix)) { - dojo.deprecated('Using dojo.io.XMLHTTPTransport.bind() to add to browser history without doing an IO request', 'Use dojo.undo.browser.addToHistory() instead.', '0.4'); - dojo.undo.browser.addToHistory(_2bf); - return true - } - } - var url = _2bf.url; - var _2c1 = ''; - if (_2bf['formNode']) { - var ta = _2bf.formNode.getAttribute('action'); - if ((ta) && (!_2bf['url'])) { - url = ta - } - var tp = _2bf.formNode.getAttribute('method'); - if ((tp) && (!_2bf['method'])) { - _2bf.method = tp - } - _2c1 += dojo.io.encodeForm(_2bf.formNode, _2bf.encoding, _2bf['formFilter']) - } - if (url.indexOf('#') > -1) { - dojo.debug('Warning: dojo.io.bind: stripping hash values from url:', url); - url = url.split('#')[0] - } - if (_2bf['file']) { - _2bf.method = 'post' - } - if (!_2bf['method']) { - _2bf.method = 'get' - } - if (_2bf.method.toLowerCase() == 'get') { - _2bf.multipart = false - } else { - if (_2bf['file']) { - _2bf.multipart = true - } else { - if (!_2bf['multipart']) { - _2bf.multipart = false - } - } - } - if (_2bf['backButton'] || _2bf['back'] || _2bf['changeUrl']) { - dojo.undo.browser.addToHistory(_2bf) - } - var _2c4 = _2bf['content'] || {}; - if (_2bf.sendTransport) { - _2c4['dojo.transport'] = 'xmlhttp' - } - do { - if (_2bf.postContent) { - _2c1 = _2bf.postContent; - break - } - if (_2c4) { - _2c1 += dojo.io.argsFromMap(_2c4, _2bf.encoding) - } - if (_2bf.method.toLowerCase() == 'get' || !_2bf.multipart) { - break - } - var t = []; - if (_2c1.length) { - var q = _2c1.split('&'); - for (var i = 0; i < q.length; ++i) { - if (q[i].length) { - var p = q[i].split('='); - t.push('--' + this.multipartBoundary, 'Content-Disposition: form-data; name="' + p[0] + '"', '', p[1]) - } - } - } - if (_2bf.file) { - if (dojo.lang.isArray(_2bf.file)) { - for (var i = 0; i < _2bf.file.length; ++i) { - var o = _2bf.file[i]; - t.push('--' + this.multipartBoundary, 'Content-Disposition: form-data; name="' - + o.name - + '"; filename="' - + ('fileName' in o ? o.fileName : o.name) - + '"', 'Content-Type: ' + ('contentType' - in o ? o.contentType : 'application/octet-stream'), '', o.content) - } - } else { - var o = _2bf.file; - t.push('--' + this.multipartBoundary, 'Content-Disposition: form-data; name="' - + o.name - + '"; filename="' - + ('fileName' in o ? o.fileName : o.name) - + '"', 'Content-Type: ' + ('contentType' - in o ? o.contentType : 'application/octet-stream'), '', o.content) - } - } - if (t.length) { - t.push('--' + this.multipartBoundary + '--', ''); - _2c1 = t.join('\r\n') - } - } while (false); - var _2ca = _2bf['sync'] ? false : true; - var _2cb = _2bf['preventCache'] || (this.preventCache == true && _2bf['preventCache'] != false); - var _2cc = _2bf['useCache'] == true || (this.useCache == true && _2bf['useCache'] != false); - if (!_2cb && _2cc) { - var _2cd = getFromCache(url, _2c1, _2bf.method); - if (_2cd) { - doLoad(_2bf, _2cd, url, _2c1, false); - return - } - } - var http = dojo.hostenv.getXmlhttpObject(_2bf); - var _2cf = false; - if (_2ca) { - var _2d0 = this.inFlight.push({ - 'req': _2bf, - 'http': http, - 'url': url, - 'query': _2c1, - 'useCache': _2cc, - 'startTime': _2bf.timeoutSeconds ? (new Date()).getTime() : 0 - }); - this.startWatchingInFlight() - } else { - _29e._blockAsync = true - } - if (_2bf.method.toLowerCase() == 'post') { - if (!_2bf.user) { - http.open('POST', url, _2ca) - } else { - http.open('POST', url, _2ca, _2bf.user, _2bf.password) - } - setHeaders(http, _2bf); - http.setRequestHeader('Content-Type', _2bf.multipart ? ('multipart/form-data; boundary=' - + this.multipartBoundary) : (_2bf.contentType || 'application/x-www-form-urlencoded')); - try { - http.send(_2c1) - } catch (e) { - if (typeof http.abort == 'function') { - http.abort() - } - doLoad(_2bf, {status: 404}, url, _2c1, _2cc) - } - } else { - var _2d1 = url; - if (_2c1 != '') { - _2d1 += (_2d1.indexOf('?') > -1 ? '&' : '?') + _2c1 - } - if (_2cb) { - _2d1 += (dojo.string.endsWithAny(_2d1, '?', '&') ? '' : (_2d1.indexOf('?') > -1 ? '&' : '?')) - + 'dojo.preventCache=' - + new Date().valueOf() - } - if (!_2bf.user) { - http.open(_2bf.method.toUpperCase(), _2d1, _2ca) - } else { - http.open(_2bf.method.toUpperCase(), _2d1, _2ca, _2bf.user, _2bf.password) - } - setHeaders(http, _2bf); - try { - http.send(null) - } catch (e) { - if (typeof http.abort == 'function') { - http.abort() - } - doLoad(_2bf, {status: 404}, url, _2c1, _2cc) - } - } - if (!_2ca) { - doLoad(_2bf, http, url, _2c1, _2cc); - _29e._blockAsync = false - } - _2bf.abort = function () { - try { - http._aborted = true - } catch (e) { - } - return http.abort() - }; - - }; - dojo.io.transports.addTransport('XMLHTTPTransport') - }; -} -dojo.provide('dojo.io.cookie'); -dojo.io.cookie.setCookie = function (name, _2d3, days, path, _2d6, _2d7) { - var _2d8 = -1; - if ((typeof days == 'number') && (days >= 0)) { - var d = new Date(); - d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000)); - _2d8 = d.toGMTString() - } - _2d3 = escape(_2d3); - document.cookie = name + '=' + _2d3 + ';' + (_2d8 != -1 ? ' expires=' + _2d8 + ';' : '') + (path ? 'path=' - + path : '') + (_2d6 ? '; domain=' + _2d6 : '') + (_2d7 ? '; secure' : '') -}; -dojo.io.cookie.set = dojo.io.cookie.setCookie; -dojo.io.cookie.getCookie = function (name) { - var idx = document.cookie.lastIndexOf(name + '='); - if (idx == -1) { - return null - } - var _2dc = document.cookie.substring(idx + name.length + 1); - var end = _2dc.indexOf(';'); - if (end == -1) { - end = _2dc.length - } - _2dc = _2dc.substring(0, end); - _2dc = unescape(_2dc); - return _2dc -}; -dojo.io.cookie.get = dojo.io.cookie.getCookie; -dojo.io.cookie.deleteCookie = function (name) { - dojo.io.cookie.setCookie(name, '-', 0) -}; -dojo.io.cookie.setObjectCookie = function (name, obj, days, path, _2e3, _2e4, _2e5) { - if (arguments.length == 5) { - _2e5 = _2e3; - _2e3 = null; - _2e4 = null - } - var _2e6 = [], _2e7, _2e8 = ''; - if (!_2e5) { - _2e7 = dojo.io.cookie.getObjectCookie(name) - } - if (days >= 0) { - if (!_2e7) { - _2e7 = {} - } - for (var prop in obj) { - if (obj[prop] == null) { - delete _2e7[prop] - } else { - if ((typeof obj[prop] == 'string') || (typeof obj[prop] == 'number')) { - _2e7[prop] = obj[prop] - } - } - } - prop = null; - for (var prop in _2e7) { - _2e6.push(escape(prop) + '=' + escape(_2e7[prop])) - } - _2e8 = _2e6.join('&') - } - dojo.io.cookie.setCookie(name, _2e8, days, path, _2e3, _2e4) -}; -dojo.io.cookie.getObjectCookie = function (name) { - var _2eb = null, _2ec = dojo.io.cookie.getCookie(name); - if (_2ec) { - _2eb = {}; - var _2ed = _2ec.split('&'); - for (var i = 0; i < _2ed.length; i++) { - var pair = _2ed[i].split('='); - var _2f0 = pair[1]; - if (isNaN(_2f0)) { - _2f0 = unescape(pair[1]) - } - _2eb[unescape(pair[0])] = _2f0 - } - } - return _2eb -}; -dojo.io.cookie.isSupported = function () { - if (typeof navigator.cookieEnabled != 'boolean') { - dojo.io.cookie.setCookie('__TestingYourBrowserForCookieSupport__', 'CookiesAllowed', 90, null); - var _2f1 = dojo.io.cookie.getCookie('__TestingYourBrowserForCookieSupport__'); - navigator.cookieEnabled = (_2f1 == 'CookiesAllowed'); - if (navigator.cookieEnabled) { - this.deleteCookie('__TestingYourBrowserForCookieSupport__') - } - } - return navigator.cookieEnabled -}; -if (!dojo.io.cookies) { - dojo.io.cookies = dojo.io.cookie -} -dojo.kwCompoundRequire({ - common: ['dojo.io.common'], - rhino: ['dojo.io.RhinoIO'], - browser: ['dojo.io.BrowserIO', 'dojo.io.cookie'], - dashboard: ['dojo.io.BrowserIO', 'dojo.io.cookie'] -}); -dojo.provide('dojo.io.*'); -dojo.provide('dojo.event.common'); -dojo.event = new function () { - this._canTimeout = dojo.lang.isFunction(dj_global['setTimeout']) || dojo.lang.isAlien(dj_global['setTimeout']); - - function interpolateArgs(args, _2f3) { - var dl = dojo.lang; - var ao = { - srcObj: dj_global, - srcFunc: null, - adviceObj: dj_global, - adviceFunc: null, - aroundObj: null, - aroundFunc: null, - adviceType: (args.length > 2) ? args[0] : 'after', - precedence: 'last', - once: false, - delay: null, - rate: 0, - adviceMsg: false, - maxCalls: -1 - }; - switch (args.length) { - case 0: - return; - case 1: - return; - case 2: - ao.srcFunc = args[0]; - ao.adviceFunc = args[1]; - break; - case 3: - if ((dl.isObject(args[0])) && (dl.isString(args[1])) && (dl.isString(args[2]))) { - ao.adviceType = 'after'; - ao.srcObj = args[0]; - ao.srcFunc = args[1]; - ao.adviceFunc = args[2] - } else { - if ((dl.isString(args[1])) && (dl.isString(args[2]))) { - ao.srcFunc = args[1]; - ao.adviceFunc = args[2] - } else { - if ((dl.isObject(args[0])) && (dl.isString(args[1])) && (dl.isFunction(args[2]))) { - ao.adviceType = 'after'; - ao.srcObj = args[0]; - ao.srcFunc = args[1]; - var _2f6 = dl.nameAnonFunc(args[2], ao.adviceObj, _2f3); - ao.adviceFunc = _2f6 - } else { - if ((dl.isFunction(args[0])) && (dl.isObject(args[1])) && (dl.isString(args[2]))) { - ao.adviceType = 'after'; - ao.srcObj = dj_global; - var _2f6 = dl.nameAnonFunc(args[0], ao.srcObj, _2f3); - ao.srcFunc = _2f6; - ao.adviceObj = args[1]; - ao.adviceFunc = args[2] - } - } - } - } - break; - case 4: - if ((dl.isObject(args[0])) && (dl.isObject(args[2]))) { - ao.adviceType = 'after'; - ao.srcObj = args[0]; - ao.srcFunc = args[1]; - ao.adviceObj = args[2]; - ao.adviceFunc = args[3] - } else { - if ((dl.isString(args[0])) && (dl.isString(args[1])) && (dl.isObject(args[2]))) { - ao.adviceType = args[0]; - ao.srcObj = dj_global; - ao.srcFunc = args[1]; - ao.adviceObj = args[2]; - ao.adviceFunc = args[3] - } else { - if ((dl.isString(args[0])) && (dl.isFunction(args[1])) && (dl.isObject(args[2]))) { - ao.adviceType = args[0]; - ao.srcObj = dj_global; - var _2f6 = dl.nameAnonFunc(args[1], dj_global, _2f3); - ao.srcFunc = _2f6; - ao.adviceObj = args[2]; - ao.adviceFunc = args[3] - } else { - if ((dl.isString(args[0])) - && (dl.isObject(args[1])) - && (dl.isString(args[2])) - && (dl.isFunction(args[3]))) { - ao.srcObj = args[1]; - ao.srcFunc = args[2]; - var _2f6 = dl.nameAnonFunc(args[3], dj_global, _2f3); - ao.adviceObj = dj_global; - ao.adviceFunc = _2f6 - } else { - if (dl.isObject(args[1])) { - ao.srcObj = args[1]; - ao.srcFunc = args[2]; - ao.adviceObj = dj_global; - ao.adviceFunc = args[3] - } else { - if (dl.isObject(args[2])) { - ao.srcObj = dj_global; - ao.srcFunc = args[1]; - ao.adviceObj = args[2]; - ao.adviceFunc = args[3] - } else { - ao.srcObj = ao.adviceObj = ao.aroundObj = dj_global; - ao.srcFunc = args[1]; - ao.adviceFunc = args[2]; - ao.aroundFunc = args[3] - } - } - } - } - } - } - break; - case 6: - ao.srcObj = args[1]; - ao.srcFunc = args[2]; - ao.adviceObj = args[3]; - ao.adviceFunc = args[4]; - ao.aroundFunc = args[5]; - ao.aroundObj = dj_global; - break; - default: - ao.srcObj = args[1]; - ao.srcFunc = args[2]; - ao.adviceObj = args[3]; - ao.adviceFunc = args[4]; - ao.aroundObj = args[5]; - ao.aroundFunc = args[6]; - ao.once = args[7]; - ao.delay = args[8]; - ao.rate = args[9]; - ao.adviceMsg = args[10]; - ao.maxCalls = (!isNaN(parseInt(args[11]))) ? args[11] : -1; - break - } - if (dl.isFunction(ao.aroundFunc)) { - var _2f6 = dl.nameAnonFunc(ao.aroundFunc, ao.aroundObj, _2f3); - ao.aroundFunc = _2f6 - } - if (dl.isFunction(ao.srcFunc)) { - ao.srcFunc = dl.getNameInObj(ao.srcObj, ao.srcFunc) - } - if (dl.isFunction(ao.adviceFunc)) { - ao.adviceFunc = dl.getNameInObj(ao.adviceObj, ao.adviceFunc) - } - if ((ao.aroundObj) && (dl.isFunction(ao.aroundFunc))) { - ao.aroundFunc = dl.getNameInObj(ao.aroundObj, ao.aroundFunc) - } - if (!ao.srcObj) { - dojo.raise('bad srcObj for srcFunc: ' + ao.srcFunc) - } - if (!ao.adviceObj) { - dojo.raise('bad adviceObj for adviceFunc: ' + ao.adviceFunc) - } - if (!ao.adviceFunc) { - dojo.debug('bad adviceFunc for srcFunc: ' + ao.srcFunc); - dojo.debugShallow(ao) - } - return ao - } - - this.connect = function () { - if (arguments.length == 1) { - var ao = arguments[0] - } else { - var ao = interpolateArgs(arguments, true) - } - if (dojo.lang.isString(ao.srcFunc) && (ao.srcFunc.toLowerCase() == 'onkey')) { - if (dojo.render.html.ie) { - ao.srcFunc = 'onkeydown'; - this.connect(ao) - } - ao.srcFunc = 'onkeypress' - } - if (dojo.lang.isArray(ao.srcObj) && ao.srcObj != '') { - var _2f8 = {}; - for (var x in ao) { - _2f8[x] = ao[x] - } - var mjps = []; - dojo.lang.forEach(ao.srcObj, function (src) { - if ((dojo.render.html.capable) && (dojo.lang.isString(src))) { - src = dojo.byId(src) - } - _2f8.srcObj = src; - mjps.push(dojo.event.connect.call(dojo.event, _2f8)) - }); - return mjps - } - var mjp = dojo.event.MethodJoinPoint.getForMethod(ao.srcObj, ao.srcFunc); - if (ao.adviceFunc) { - var mjp2 = dojo.event.MethodJoinPoint.getForMethod(ao.adviceObj, ao.adviceFunc) - } - mjp.kwAddAdvice(ao); - return mjp - }; - this.log = function (a1, a2) { - var _300; - if ((arguments.length == 1) && (typeof a1 == 'object')) { - _300 = a1 - } else { - _300 = {srcObj: a1, srcFunc: a2} - } - _300.adviceFunc = function () { - var _301 = []; - for (var x = 0; x < arguments.length; x++) { - _301.push(arguments[x]) - } - dojo.debug('(' + _300.srcObj + ').' + _300.srcFunc, ':', _301.join(', ')) - }; - this.kwConnect(_300) - }; - this.connectBefore = function () { - var args = ['before']; - for (var i = 0; i < arguments.length; i++) { - args.push(arguments[i]) - } - return this.connect.apply(this, args) - }; - this.connectAround = function () { - var args = ['around']; - for (var i = 0; i < arguments.length; i++) { - args.push(arguments[i]) - } - return this.connect.apply(this, args) - }; - this.connectOnce = function () { - var ao = interpolateArgs(arguments, true); - ao.once = true; - return this.connect(ao) - }; - this.connectRunOnce = function () { - var ao = interpolateArgs(arguments, true); - ao.maxCalls = 1; - return this.connect(ao) - }; - this._kwConnectImpl = function (_309, _30a) { - var fn = (_30a) ? 'disconnect' : 'connect'; - if (typeof _309['srcFunc'] == 'function') { - _309.srcObj = _309['srcObj'] || dj_global; - var _30c = dojo.lang.nameAnonFunc(_309.srcFunc, _309.srcObj, true); - _309.srcFunc = _30c - } - if (typeof _309['adviceFunc'] == 'function') { - _309.adviceObj = _309['adviceObj'] || dj_global; - var _30c = dojo.lang.nameAnonFunc(_309.adviceFunc, _309.adviceObj, true); - _309.adviceFunc = _30c - } - _309.srcObj = _309['srcObj'] || dj_global; - _309.adviceObj = _309['adviceObj'] || _309['targetObj'] || dj_global; - _309.adviceFunc = _309['adviceFunc'] || _309['targetFunc']; - return dojo.event[fn](_309) - }; - this.kwConnect = function (_30d) { - return this._kwConnectImpl(_30d, false) - }; - this.disconnect = function () { - if (arguments.length == 1) { - var ao = arguments[0] - } else { - var ao = interpolateArgs(arguments, true) - } - if (!ao.adviceFunc) { - return - } - if (dojo.lang.isString(ao.srcFunc) && (ao.srcFunc.toLowerCase() == 'onkey')) { - if (dojo.render.html.ie) { - ao.srcFunc = 'onkeydown'; - this.disconnect(ao) - } - ao.srcFunc = 'onkeypress' - } - if (!ao.srcObj[ao.srcFunc]) { - return null - } - var mjp = dojo.event.MethodJoinPoint.getForMethod(ao.srcObj, ao.srcFunc, true); - mjp.removeAdvice(ao.adviceObj, ao.adviceFunc, ao.adviceType, ao.once); - return mjp - }; - this.kwDisconnect = function (_310) { - return this._kwConnectImpl(_310, true) - } -}; -;dojo.event.MethodInvocation = function (_311, obj, args) { - this.jp_ = _311; - this.object = obj; - this.args = []; - for (var x = 0; x < args.length; x++) { - this.args[x] = args[x] - } - this.around_index = -1 -}; -dojo.event.MethodInvocation.prototype.proceed = function () { - this.around_index++; - if (this.around_index >= this.jp_.around.length) { - return this.jp_.object[this.jp_.methodname].apply(this.jp_.object, this.args) - } else { - var ti = this.jp_.around[this.around_index]; - var mobj = ti[0] || dj_global; - var meth = ti[1]; - return mobj[meth].call(mobj, this) - } -}; -dojo.event.MethodJoinPoint = function (obj, _319) { - this.object = obj || dj_global; - this.methodname = _319; - this.methodfunc = this.object[_319]; - this.squelch = false -}; -dojo.event.MethodJoinPoint.getForMethod = function (obj, _31b) { - if (!obj) { - obj = dj_global - } - var ofn = obj[_31b]; - if (!ofn) { - ofn = obj[_31b] = function () { - }; - if (!obj[_31b]) { - dojo.raise('Cannot set do-nothing method on that object ' + _31b) - } - } else { - if ((typeof ofn != 'function') && (!dojo.lang.isFunction(ofn)) && (!dojo.lang.isAlien(ofn))) { - return null - } - } - var _31d = _31b + '$joinpoint'; - var _31e = _31b + '$joinpoint$method'; - var _31f = obj[_31d]; - if (!_31f) { - var _320 = false; - if (dojo.event['browser']) { - if ((obj['attachEvent']) || (obj['nodeType']) || (obj['addEventListener'])) { - _320 = true; - dojo.event.browser.addClobberNodeAttrs(obj, [_31d, _31e, _31b]) - } - } - var _321 = ofn.length; - obj[_31e] = ofn; - _31f = obj[_31d] = new dojo.event.MethodJoinPoint(obj, _31e); - if (!_320) { - obj[_31b] = function () { - return _31f.run.apply(_31f, arguments) - } - } else { - obj[_31b] = function () { - var args = []; - if (!arguments.length) { - var evt = null; - try { - if (obj.ownerDocument) { - evt = obj.ownerDocument.parentWindow.event - } else { - if (obj.documentElement) { - evt = obj.documentElement.ownerDocument.parentWindow.event - } else { - if (obj.event) { - evt = obj.event - } else { - evt = window.event - } - } - } - } catch (e) { - evt = window.event - } - if (evt) { - args.push(dojo.event.browser.fixEvent(evt, this)) - } - } else { - for (var x = 0; x < arguments.length; x++) { - if ((x == 0) && (dojo.event.browser.isEvent(arguments[x]))) { - args.push(dojo.event.browser.fixEvent(arguments[x], this)) - } else { - args.push(arguments[x]) - } - } - } - return _31f.run.apply(_31f, args) - } - } - obj[_31b].__preJoinArity = _321 - } - return _31f -}; -dojo.lang.extend(dojo.event.MethodJoinPoint, { - squelch: false, unintercept: function () { - this.object[this.methodname] = this.methodfunc; - this.before = []; - this.after = []; - this.around = [] - }, disconnect: dojo.lang.forward('unintercept'), run: function () { - var obj = this.object || dj_global; - var args = arguments; - var _327 = []; - for (var x = 0; x < args.length; x++) { - _327[x] = args[x] - } - var _329 = function (marr) { - if (!marr) { - dojo.debug('Null argument to unrollAdvice()'); - return - } - var _32b = marr[0] || dj_global; - var _32c = marr[1]; - if (!_32b[_32c]) { - dojo.raise('function "' + _32c + '" does not exist on "' + _32b + '"') - } - var _32d = marr[2] || dj_global; - var _32e = marr[3]; - var msg = marr[6]; - var _330 = marr[7]; - if (_330 > -1) { - if (_330 == 0) { - return - } - marr[7]-- - } - var _331; - var to = { - args: [], jp_: this, object: obj, proceed: function () { - return _32b[_32c].apply(_32b, to.args) - } - }; - to.args = _327; - var _333 = parseInt(marr[4]); - var _334 = ((!isNaN(_333)) && (marr[4] !== null) && (typeof marr[4] != 'undefined')); - if (marr[5]) { - var rate = parseInt(marr[5]); - var cur = new Date(); - var _337 = false; - if ((marr['last']) && ((cur - marr.last) <= rate)) { - if (dojo.event._canTimeout) { - if (marr['delayTimer']) { - clearTimeout(marr.delayTimer) - } - var tod = parseInt(rate * 2); - var mcpy = dojo.lang.shallowCopy(marr); - marr.delayTimer = setTimeout(function () { - mcpy[5] = 0; - _329(mcpy) - }, tod) - } - return - } else { - marr.last = cur - } - } - if (_32e) { - _32d[_32e].call(_32d, to) - } else { - if ((_334) && ((dojo.render.html) || (dojo.render.svg))) { - dj_global['setTimeout'](function () { - if (msg) { - _32b[_32c].call(_32b, to) - } else { - _32b[_32c].apply(_32b, args) - } - }, _333) - } else { - if (msg) { - _32b[_32c].call(_32b, to) - } else { - _32b[_32c].apply(_32b, args) - } - } - } - }; - var _33a = function () { - if (this.squelch) { - try { - return _329.apply(this, arguments) - } catch (e) { - dojo.debug(e) - } - } else { - return _329.apply(this, arguments) - } - }; - if ((this['before']) && (this.before.length > 0)) { - dojo.lang.forEach(this.before.concat([]), _33a) - } - var _33b; - try { - if ((this['around']) && (this.around.length > 0)) { - var mi = new dojo.event.MethodInvocation(this, obj, args); - _33b = mi.proceed() - } else { - if (this.methodfunc) { - _33b = this.object[this.methodname].apply(this.object, args) - } - } - } catch (e) { - if (!this.squelch) { - dojo.debug(e, 'when calling', this.methodname, 'on', this.object, 'with arguments', args); - dojo.raise(e) - } - } - if ((this['after']) && (this.after.length > 0)) { - dojo.lang.forEach(this.after.concat([]), _33a) - } - return (this.methodfunc) ? _33b : null - }, getArr: function (kind) { - var type = 'after'; - if ((typeof kind == 'string') && (kind.indexOf('before') != -1)) { - type = 'before' - } else { - if (kind == 'around') { - type = 'around' - } - } - if (!this[type]) { - this[type] = [] - } - return this[type] - }, kwAddAdvice: function (args) { - this.addAdvice(args['adviceObj'], args['adviceFunc'], args['aroundObj'], args['aroundFunc'], args['adviceType'], args['precedence'], args['once'], args['delay'], args['rate'], args['adviceMsg'], args['maxCalls']) - }, addAdvice: function (_340, _341, _342, _343, _344, _345, once, _347, rate, _349, _34a) { - var arr = this.getArr(_344); - if (!arr) { - dojo.raise('bad this: ' + this) - } - var ao = [_340, _341, _342, _343, _347, rate, _349, _34a]; - if (once) { - if (this.hasAdvice(_340, _341, _344, arr) >= 0) { - return - } - } - if (_345 == 'first') { - arr.unshift(ao) - } else { - arr.push(ao) - } - }, hasAdvice: function (_34d, _34e, _34f, arr) { - if (!arr) { - arr = this.getArr(_34f) - } - var ind = -1; - for (var x = 0; x < arr.length; x++) { - var aao = (typeof _34e == 'object') ? (String(_34e)).toString() : _34e; - var a1o = (typeof arr[x][1] == 'object') ? (String(arr[x][1])).toString() : arr[x][1]; - if ((arr[x][0] == _34d) && (a1o == aao)) { - ind = x - } - } - return ind - }, removeAdvice: function (_355, _356, _357, once) { - var arr = this.getArr(_357); - var ind = this.hasAdvice(_355, _356, _357, arr); - if (ind == -1) { - return false - } - while (ind != -1) { - arr.splice(ind, 1); - if (once) { - break - } - ind = this.hasAdvice(_355, _356, _357, arr) - } - return true - } -}); -dojo.provide('dojo.event.topic'); -dojo.event.topic = new function () { - this.topics = {}; - this.getTopic = function (_35b) { - if (!this.topics[_35b]) { - this.topics[_35b] = new this.TopicImpl(_35b) - } - return this.topics[_35b] - }; - this.registerPublisher = function (_35c, obj, _35e) { - var _35c = this.getTopic(_35c); - _35c.registerPublisher(obj, _35e) - }; - this.subscribe = function (_35f, obj, _361) { - var _35f = this.getTopic(_35f); - _35f.subscribe(obj, _361) - }; - this.unsubscribe = function (_362, obj, _364) { - var _362 = this.getTopic(_362); - _362.unsubscribe(obj, _364) - }; - this.destroy = function (_365) { - this.getTopic(_365).destroy(); - delete this.topics[_365] - }; - this.publishApply = function (_366, args) { - var _366 = this.getTopic(_366); - _366.sendMessage.apply(_366, args) - }; - this.publish = function (_368, _369) { - var _368 = this.getTopic(_368); - var args = []; - for (var x = 1; x < arguments.length; x++) { - args.push(arguments[x]) - } - _368.sendMessage.apply(_368, args) - } -}; -;dojo.event.topic.TopicImpl = function (_36c) { - this.topicName = _36c; - this.subscribe = function (_36d, _36e) { - var tf = _36e || _36d; - var to = (!_36e) ? dj_global : _36d; - return dojo.event.kwConnect({srcObj: this, srcFunc: 'sendMessage', adviceObj: to, adviceFunc: tf}) - }; - this.unsubscribe = function (_371, _372) { - var tf = (!_372) ? _371 : _372; - var to = (!_372) ? null : _371; - return dojo.event.kwDisconnect({srcObj: this, srcFunc: 'sendMessage', adviceObj: to, adviceFunc: tf}) - }; - this._getJoinPoint = function () { - return dojo.event.MethodJoinPoint.getForMethod(this, 'sendMessage') - }; - this.setSquelch = function (_375) { - this._getJoinPoint().squelch = _375 - }; - this.destroy = function () { - this._getJoinPoint().disconnect() - }; - this.registerPublisher = function (_376, _377) { - dojo.event.connect(_376, _377, this, 'sendMessage') - }; - this.sendMessage = function (_378) { - } -}; -dojo.provide('dojo.event.browser'); -dojo._ie_clobber = new function () { - this.clobberNodes = []; - - function nukeProp(node, prop) { - try { - node[prop] = null - } catch (e) { - } - try { - delete node[prop] - } catch (e) { - } - try { - node.removeAttribute(prop) - } catch (e) { - } - } - - this.clobber = function (_37b) { - var na; - var tna; - if (_37b) { - tna = _37b.all || _37b.getElementsByTagName('*'); - na = [_37b]; - for (var x = 0; x < tna.length; x++) { - if (tna[x]['__doClobber__']) { - na.push(tna[x]) - } - } - } else { - try { - window.onload = null - } catch (e) { - } - na = (this.clobberNodes.length) ? this.clobberNodes : document.all - } - tna = null; - var _37f = {}; - for (var i = na.length - 1; i >= 0; i = i - 1) { - var el = na[i]; - try { - if (el && el['__clobberAttrs__']) { - for (var j = 0; j < el.__clobberAttrs__.length; j++) { - nukeProp(el, el.__clobberAttrs__[j]) - } - nukeProp(el, '__clobberAttrs__'); - nukeProp(el, '__doClobber__') - } - } catch (e) { - } - } - na = null - } -}; -; -if (dojo.render.html.ie) { - dojo.addOnUnload(function () { - dojo._ie_clobber.clobber(); - try { - if ((dojo['widget']) && (dojo.widget['manager'])) { - dojo.widget.manager.destroyAll() - } - } catch (e) { - } - if (dojo.widget) { - for (var name in dojo.widget._templateCache) { - if (dojo.widget._templateCache[name].node) { - dojo.dom.destroyNode(dojo.widget._templateCache[name].node); - dojo.widget._templateCache[name].node = null; - delete dojo.widget._templateCache[name].node - } - } - } - try { - window.onload = null - } catch (e) { - } - try { - window.onunload = null - } catch (e) { - } - dojo._ie_clobber.clobberNodes = [] - }) -} -dojo.event.browser = new function () { - var _384 = 0; - this.normalizedEventName = function (_385) { - switch (_385) { - case 'CheckboxStateChange': - case 'DOMAttrModified': - case 'DOMMenuItemActive': - case 'DOMMenuItemInactive': - case 'DOMMouseScroll': - case 'DOMNodeInserted': - case 'DOMNodeRemoved': - case 'RadioStateChange': - return _385; - break; - default: - var lcn = _385.toLowerCase(); - return (lcn.indexOf('on') == 0) ? lcn.substr(2) : lcn; - break - } - }; - this.clean = function (node) { - if (dojo.render.html.ie) { - dojo._ie_clobber.clobber(node) - } - }; - this.addClobberNode = function (node) { - if (!dojo.render.html.ie) { - return - } - if (!node['__doClobber__']) { - node.__doClobber__ = true; - dojo._ie_clobber.clobberNodes.push(node); - node.__clobberAttrs__ = [] - } - }; - this.addClobberNodeAttrs = function (node, _38a) { - if (!dojo.render.html.ie) { - return - } - this.addClobberNode(node); - for (var x = 0; x < _38a.length; x++) { - node.__clobberAttrs__.push(_38a[x]) - } - }; - this.removeListener = function (node, _38d, fp, _38f) { - if (!_38f) { - var _38f = false - } - _38d = dojo.event.browser.normalizedEventName(_38d); - if (_38d == 'key') { - if (dojo.render.html.ie) { - this.removeListener(node, 'onkeydown', fp, _38f) - } - _38d = 'keypress' - } - if (node.removeEventListener) { - node.removeEventListener(_38d, fp, _38f) - } - }; - this.addListener = function (node, _391, fp, _393, _394) { - if (!node) { - return - } - if (!_393) { - var _393 = false - } - _391 = dojo.event.browser.normalizedEventName(_391); - if (_391 == 'key') { - if (dojo.render.html.ie) { - this.addListener(node, 'onkeydown', fp, _393, _394) - } - _391 = 'keypress' - } - if (!_394) { - var _395 = function (evt) { - if (!evt) { - evt = window.event - } - var ret = fp(dojo.event.browser.fixEvent(evt, this)); - if (_393) { - dojo.event.browser.stopEvent(evt) - } - return ret - } - } else { - _395 = fp - } - if (node.addEventListener) { - node.addEventListener(_391, _395, _393); - return _395 - } else { - _391 = 'on' + _391; - if (typeof node[_391] == 'function') { - var _398 = node[_391]; - node[_391] = function (e) { - _398(e); - return _395(e) - } - } else { - node[_391] = _395 - } - if (dojo.render.html.ie) { - this.addClobberNodeAttrs(node, [_391]) - } - return _395 - } - }; - this.isEvent = function (obj) { - return (typeof obj != 'undefined') && (obj) && (typeof Event != 'undefined') && (obj.eventPhase) - }; - this.currentEvent = null; - this.callListener = function (_39b, _39c) { - if (typeof _39b != 'function') { - dojo.raise('listener not a function: ' + _39b) - } - dojo.event.browser.currentEvent.currentTarget = _39c; - return _39b.call(_39c, dojo.event.browser.currentEvent) - }; - this._stopPropagation = function () { - dojo.event.browser.currentEvent.cancelBubble = true - }; - this._preventDefault = function () { - dojo.event.browser.currentEvent.returnValue = false - }; - this.keys = { - KEY_BACKSPACE: 8, - KEY_TAB: 9, - KEY_CLEAR: 12, - KEY_ENTER: 13, - KEY_SHIFT: 16, - KEY_CTRL: 17, - KEY_ALT: 18, - KEY_PAUSE: 19, - KEY_CAPS_LOCK: 20, - KEY_ESCAPE: 27, - KEY_SPACE: 32, - KEY_PAGE_UP: 33, - KEY_PAGE_DOWN: 34, - KEY_END: 35, - KEY_HOME: 36, - KEY_LEFT_ARROW: 37, - KEY_UP_ARROW: 38, - KEY_RIGHT_ARROW: 39, - KEY_DOWN_ARROW: 40, - KEY_INSERT: 45, - KEY_DELETE: 46, - KEY_HELP: 47, - KEY_LEFT_WINDOW: 91, - KEY_RIGHT_WINDOW: 92, - KEY_SELECT: 93, - KEY_NUMPAD_0: 96, - KEY_NUMPAD_1: 97, - KEY_NUMPAD_2: 98, - KEY_NUMPAD_3: 99, - KEY_NUMPAD_4: 100, - KEY_NUMPAD_5: 101, - KEY_NUMPAD_6: 102, - KEY_NUMPAD_7: 103, - KEY_NUMPAD_8: 104, - KEY_NUMPAD_9: 105, - KEY_NUMPAD_MULTIPLY: 106, - KEY_NUMPAD_PLUS: 107, - KEY_NUMPAD_ENTER: 108, - KEY_NUMPAD_MINUS: 109, - KEY_NUMPAD_PERIOD: 110, - KEY_NUMPAD_DIVIDE: 111, - KEY_F1: 112, - KEY_F2: 113, - KEY_F3: 114, - KEY_F4: 115, - KEY_F5: 116, - KEY_F6: 117, - KEY_F7: 118, - KEY_F8: 119, - KEY_F9: 120, - KEY_F10: 121, - KEY_F11: 122, - KEY_F12: 123, - KEY_F13: 124, - KEY_F14: 125, - KEY_F15: 126, - KEY_NUM_LOCK: 144, - KEY_SCROLL_LOCK: 145 - }; - this.revKeys = []; - for (var key in this.keys) { - this.revKeys[this.keys[key]] = key - } - this.fixEvent = function (evt, _39f) { - if (!evt) { - if (window['event']) { - evt = window.event - } - } - if ((evt['type']) && (evt['type'].indexOf('key') == 0)) { - evt.keys = this.revKeys; - for (var key in this.keys) { - evt[key] = this.keys[key] - } - if (evt['type'] == 'keydown' && dojo.render.html.ie) { - switch (evt.keyCode) { - case evt.KEY_SHIFT: - case evt.KEY_CTRL: - case evt.KEY_ALT: - case evt.KEY_CAPS_LOCK: - case evt.KEY_LEFT_WINDOW: - case evt.KEY_RIGHT_WINDOW: - case evt.KEY_SELECT: - case evt.KEY_NUM_LOCK: - case evt.KEY_SCROLL_LOCK: - case evt.KEY_NUMPAD_0: - case evt.KEY_NUMPAD_1: - case evt.KEY_NUMPAD_2: - case evt.KEY_NUMPAD_3: - case evt.KEY_NUMPAD_4: - case evt.KEY_NUMPAD_5: - case evt.KEY_NUMPAD_6: - case evt.KEY_NUMPAD_7: - case evt.KEY_NUMPAD_8: - case evt.KEY_NUMPAD_9: - case evt.KEY_NUMPAD_PERIOD: - break; - case evt.KEY_NUMPAD_MULTIPLY: - case evt.KEY_NUMPAD_PLUS: - case evt.KEY_NUMPAD_ENTER: - case evt.KEY_NUMPAD_MINUS: - case evt.KEY_NUMPAD_DIVIDE: - break; - case evt.KEY_PAUSE: - case evt.KEY_TAB: - case evt.KEY_BACKSPACE: - case evt.KEY_ENTER: - case evt.KEY_ESCAPE: - case evt.KEY_PAGE_UP: - case evt.KEY_PAGE_DOWN: - case evt.KEY_END: - case evt.KEY_HOME: - case evt.KEY_LEFT_ARROW: - case evt.KEY_UP_ARROW: - case evt.KEY_RIGHT_ARROW: - case evt.KEY_DOWN_ARROW: - case evt.KEY_INSERT: - case evt.KEY_DELETE: - case evt.KEY_F1: - case evt.KEY_F2: - case evt.KEY_F3: - case evt.KEY_F4: - case evt.KEY_F5: - case evt.KEY_F6: - case evt.KEY_F7: - case evt.KEY_F8: - case evt.KEY_F9: - case evt.KEY_F10: - case evt.KEY_F11: - case evt.KEY_F12: - case evt.KEY_F12: - case evt.KEY_F13: - case evt.KEY_F14: - case evt.KEY_F15: - case evt.KEY_CLEAR: - case evt.KEY_HELP: - evt.key = evt.keyCode; - break; - default: - if (evt.ctrlKey || evt.altKey) { - var _3a1 = evt.keyCode; - if (_3a1 >= 65 && _3a1 <= 90 && evt.shiftKey == false) { - _3a1 += 32 - } - if (_3a1 >= 1 && _3a1 <= 26 && evt.ctrlKey) { - _3a1 += 96 - } - evt.key = String.fromCharCode(_3a1) - } - } - } else { - if (evt['type'] == 'keypress') { - if (dojo.render.html.opera) { - if (evt.which == 0) { - evt.key = evt.keyCode - } else { - if (evt.which > 0) { - switch (evt.which) { - case evt.KEY_SHIFT: - case evt.KEY_CTRL: - case evt.KEY_ALT: - case evt.KEY_CAPS_LOCK: - case evt.KEY_NUM_LOCK: - case evt.KEY_SCROLL_LOCK: - break; - case evt.KEY_PAUSE: - case evt.KEY_TAB: - case evt.KEY_BACKSPACE: - case evt.KEY_ENTER: - case evt.KEY_ESCAPE: - evt.key = evt.which; - break; - default: - var _3a1 = evt.which; - if ((evt.ctrlKey || evt.altKey || evt.metaKey) && (evt.which - >= 65 - && evt.which - <= 90 - && evt.shiftKey - == false)) { - _3a1 += 32 - } - evt.key = String.fromCharCode(_3a1) - } - } - } - } else { - if (dojo.render.html.ie) { - if (!evt.ctrlKey && !evt.altKey && evt.keyCode >= evt.KEY_SPACE) { - evt.key = String.fromCharCode(evt.keyCode) - } - } else { - if (dojo.render.html.safari) { - switch (evt.keyCode) { - case 25: - evt.key = evt.KEY_TAB; - evt.shift = true; - break; - case 63232: - evt.key = evt.KEY_UP_ARROW; - break; - case 63233: - evt.key = evt.KEY_DOWN_ARROW; - break; - case 63234: - evt.key = evt.KEY_LEFT_ARROW; - break; - case 63235: - evt.key = evt.KEY_RIGHT_ARROW; - break; - case 63236: - evt.key = evt.KEY_F1; - break; - case 63237: - evt.key = evt.KEY_F2; - break; - case 63238: - evt.key = evt.KEY_F3; - break; - case 63239: - evt.key = evt.KEY_F4; - break; - case 63240: - evt.key = evt.KEY_F5; - break; - case 63241: - evt.key = evt.KEY_F6; - break; - case 63242: - evt.key = evt.KEY_F7; - break; - case 63243: - evt.key = evt.KEY_F8; - break; - case 63244: - evt.key = evt.KEY_F9; - break; - case 63245: - evt.key = evt.KEY_F10; - break; - case 63246: - evt.key = evt.KEY_F11; - break; - case 63247: - evt.key = evt.KEY_F12; - break; - case 63250: - evt.key = evt.KEY_PAUSE; - break; - case 63272: - evt.key = evt.KEY_DELETE; - break; - case 63273: - evt.key = evt.KEY_HOME; - break; - case 63275: - evt.key = evt.KEY_END; - break; - case 63276: - evt.key = evt.KEY_PAGE_UP; - break; - case 63277: - evt.key = evt.KEY_PAGE_DOWN; - break; - case 63302: - evt.key = evt.KEY_INSERT; - break; - case 63248: - case 63249: - case 63289: - break; - default: - evt.key = evt.charCode >= evt.KEY_SPACE ? String.fromCharCode(evt.charCode) : evt.keyCode - } - } else { - evt.key = evt.charCode > 0 ? String.fromCharCode(evt.charCode) : evt.keyCode - } - } - } - } - } - } - if (dojo.render.html.ie) { - if (!evt.target) { - evt.target = evt.srcElement - } - if (!evt.currentTarget) { - evt.currentTarget = (_39f ? _39f : evt.srcElement) - } - if (!evt.layerX) { - evt.layerX = evt.offsetX - } - if (!evt.layerY) { - evt.layerY = evt.offsetY - } - var doc = (evt.srcElement && evt.srcElement.ownerDocument) ? evt.srcElement.ownerDocument : document; - var _3a3 = ((dojo.render.html.ie55) || (doc['compatMode'] == 'BackCompat')) ? doc.body : doc.documentElement; - if (!evt.pageX) { - evt.pageX = evt.clientX + (_3a3.scrollLeft || 0) - } - if (!evt.pageY) { - evt.pageY = evt.clientY + (_3a3.scrollTop || 0) - } - if (evt.type == 'mouseover') { - evt.relatedTarget = evt.fromElement - } - if (evt.type == 'mouseout') { - evt.relatedTarget = evt.toElement - } - this.currentEvent = evt; - evt.callListener = this.callListener; - evt.stopPropagation = this._stopPropagation; - evt.preventDefault = this._preventDefault - } - return evt - }; - this.stopEvent = function (evt) { - if (window.event) { - evt.cancelBubble = true; - evt.returnValue = false - } else { - evt.preventDefault(); - evt.stopPropagation() - } - } -}; -;dojo.kwCompoundRequire({ - common: ['dojo.event.common', 'dojo.event.topic'], browser: ['dojo.event.browser'], dashboard: ['dojo.event.browser'] -}); -dojo.provide('dojo.event.*'); -dojo.provide('dojo.gfx.color'); -dojo.gfx.color.Color = function (r, g, b, a) { - if (dojo.lang.isArray(r)) { - this.r = r[0]; - this.g = r[1]; - this.b = r[2]; - this.a = r[3] || 1 - } else { - if (dojo.lang.isString(r)) { - var rgb = dojo.gfx.color.extractRGB(r); - this.r = rgb[0]; - this.g = rgb[1]; - this.b = rgb[2]; - this.a = g || 1 - } else { - if (r instanceof dojo.gfx.color.Color) { - this.r = r.r; - this.b = r.b; - this.g = r.g; - this.a = r.a - } else { - this.r = r; - this.g = g; - this.b = b; - this.a = a - } - } - } -}; -dojo.gfx.color.Color.fromArray = function (arr) { - return new dojo.gfx.color.Color(arr[0], arr[1], arr[2], arr[3]) -}; -dojo.extend(dojo.gfx.color.Color, { - toRgb: function (_3ab) { - if (_3ab) { - return this.toRgba() - } else { - return [this.r, this.g, this.b] - } - }, toRgba: function () { - return [this.r, this.g, this.b, this.a] - }, toHex: function () { - return dojo.gfx.color.rgb2hex(this.toRgb()) - }, toCss: function () { - return 'rgb(' + this.toRgb().join() + ')' - }, toString: function () { - return this.toHex() - }, blend: function (_3ac, _3ad) { - var rgb = null; - if (dojo.lang.isArray(_3ac)) { - rgb = _3ac - } else { - if (_3ac instanceof dojo.gfx.color.Color) { - rgb = _3ac.toRgb() - } else { - rgb = new dojo.gfx.color.Color(_3ac).toRgb() - } - } - return dojo.gfx.color.blend(this.toRgb(), rgb, _3ad) - } -}); -dojo.gfx.color.named = { - white: [255, 255, 255], - black: [0, 0, 0], - red: [255, 0, 0], - green: [0, 255, 0], - lime: [0, 255, 0], - blue: [0, 0, 255], - navy: [0, 0, 128], - gray: [128, 128, 128], - silver: [192, 192, 192] -}; -dojo.gfx.color.blend = function (a, b, _3b1) { - if (typeof a == 'string') { - return dojo.gfx.color.blendHex(a, b, _3b1) - } - if (!_3b1) { - _3b1 = 0 - } - _3b1 = Math.min(Math.max(-1, _3b1), 1); - _3b1 = ((_3b1 + 1) / 2); - var c = []; - for (var x = 0; x < 3; x++) { - c[x] = parseInt(b[x] + ((a[x] - b[x]) * _3b1)) - } - return c -}; -dojo.gfx.color.blendHex = function (a, b, _3b6) { - return dojo.gfx.color.rgb2hex(dojo.gfx.color.blend(dojo.gfx.color.hex2rgb(a), dojo.gfx.color.hex2rgb(b), _3b6)) -}; -dojo.gfx.color.extractRGB = function (_3b7) { - var hex = '0123456789abcdef'; - _3b7 = _3b7.toLowerCase(); - if (_3b7.indexOf('rgb') == 0) { - var _3b9 = _3b7.match(/rgba*\((\d+), *(\d+), *(\d+)/i); - var ret = _3b9.splice(1, 3); - return ret - } else { - var _3bb = dojo.gfx.color.hex2rgb(_3b7); - if (_3bb) { - return _3bb - } else { - return dojo.gfx.color.named[_3b7] || [255, 255, 255] - } - } -}; -dojo.gfx.color.hex2rgb = function (hex) { - var _3bd = '0123456789ABCDEF'; - var rgb = new Array(3); - if (hex.indexOf('#') == 0) { - hex = hex.substring(1) - } - hex = hex.toUpperCase(); - if (hex.replace(new RegExp('[' + _3bd + ']', 'g'), '') != '') { - return null - } - if (hex.length == 3) { - rgb[0] = hex.charAt(0) + hex.charAt(0); - rgb[1] = hex.charAt(1) + hex.charAt(1); - rgb[2] = hex.charAt(2) + hex.charAt(2) - } else { - rgb[0] = hex.substring(0, 2); - rgb[1] = hex.substring(2, 4); - rgb[2] = hex.substring(4) - } - for (var i = 0; i < rgb.length; i++) { - rgb[i] = _3bd.indexOf(rgb[i].charAt(0)) * 16 + _3bd.indexOf(rgb[i].charAt(1)) - } - return rgb -}; -dojo.gfx.color.rgb2hex = function (r, g, b) { - if (dojo.lang.isArray(r)) { - g = r[1] || 0; - b = r[2] || 0; - r = r[0] || 0 - } - var ret = dojo.lang.map([r, g, b], function (x) { - x = Number(x); - var s = x.toString(16); - while (s.length < 2) { - s = '0' + s - } - return s - }); - ret.unshift('#'); - return ret.join('') -}; -dojo.provide('dojo.lfx.Animation'); -dojo.lfx.Line = function (_3c6, end) { - this.start = _3c6; - this.end = end; - if (dojo.lang.isArray(_3c6)) { - var diff = []; - dojo.lang.forEach(this.start, function (s, i) { - diff[i] = this.end[i] - s - }, this); - this.getValue = function (n) { - var res = []; - dojo.lang.forEach(this.start, function (s, i) { - res[i] = (diff[i] * n) + s - }, this); - return res - } - } else { - var diff = end - _3c6; - this.getValue = function (n) { - return (diff * n) + this.start - } - } -}; -if ((dojo.render.html.khtml) && (!dojo.render.html.safari)) { - dojo.lfx.easeDefault = function (n) { - return (parseFloat('0.5') + ((Math.sin((n + parseFloat('1.5')) * Math.PI)) / 2)) - } -} else { - dojo.lfx.easeDefault = function (n) { - return (0.5 + ((Math.sin((n + 1.5) * Math.PI)) / 2)) - } -} -dojo.lfx.easeIn = function (n) { - return Math.pow(n, 3) -}; -dojo.lfx.easeOut = function (n) { - return (1 - Math.pow(1 - n, 3)) -}; -dojo.lfx.easeInOut = function (n) { - return ((3 * Math.pow(n, 2)) - (2 * Math.pow(n, 3))) -}; -dojo.lfx.IAnimation = function () { -}; -dojo.lang.extend(dojo.lfx.IAnimation, { - curve: null, - duration: 1000, - easing: null, - repeatCount: 0, - rate: 10, - handler: null, - beforeBegin: null, - onBegin: null, - onAnimate: null, - onEnd: null, - onPlay: null, - onPause: null, - onStop: null, - play: null, - pause: null, - stop: null, - connect: function (evt, _3d6, _3d7) { - if (!_3d7) { - _3d7 = _3d6; - _3d6 = this - } - _3d7 = dojo.lang.hitch(_3d6, _3d7); - var _3d8 = this[evt] || function () { - }; - this[evt] = function () { - var ret = _3d8.apply(this, arguments); - _3d7.apply(this, arguments); - return ret - }; - return this - }, - fire: function (evt, args) { - if (this[evt]) { - this[evt].apply(this, (args || [])) - } - return this - }, - repeat: function (_3dc) { - this.repeatCount = _3dc; - return this - }, - _active: false, - _paused: false -}); -dojo.lfx.Animation = function (_3dd, _3de, _3df, _3e0, _3e1, rate) { - dojo.lfx.IAnimation.call(this); - if (dojo.lang.isNumber(_3dd) || (!_3dd && _3de.getValue)) { - rate = _3e1; - _3e1 = _3e0; - _3e0 = _3df; - _3df = _3de; - _3de = _3dd; - _3dd = null - } else { - if (_3dd.getValue || dojo.lang.isArray(_3dd)) { - rate = _3e0; - _3e1 = _3df; - _3e0 = _3de; - _3df = _3dd; - _3de = null; - _3dd = null - } - } - if (dojo.lang.isArray(_3df)) { - this.curve = new dojo.lfx.Line(_3df[0], _3df[1]) - } else { - this.curve = _3df - } - if (_3de != null && _3de > 0) { - this.duration = _3de - } - if (_3e1) { - this.repeatCount = _3e1 - } - if (rate) { - this.rate = rate - } - if (_3dd) { - dojo.lang.forEach(['handler', 'beforeBegin', 'onBegin', 'onEnd', 'onPlay', 'onStop', 'onAnimate'], function (item) { - if (_3dd[item]) { - this.connect(item, _3dd[item]) - } - }, this) - } - if (_3e0 && dojo.lang.isFunction(_3e0)) { - this.easing = _3e0 - } -}; -dojo.inherits(dojo.lfx.Animation, dojo.lfx.IAnimation); -dojo.lang.extend(dojo.lfx.Animation, { - _startTime: null, _endTime: null, _timer: null, _percent: 0, _startRepeatCount: 0, play: function (_3e4, _3e5) { - if (_3e5) { - clearTimeout(this._timer); - this._active = false; - this._paused = false; - this._percent = 0 - } else { - if (this._active && !this._paused) { - return this - } - } - this.fire('handler', ['beforeBegin']); - this.fire('beforeBegin'); - if (_3e4 > 0) { - setTimeout(dojo.lang.hitch(this, function () { - this.play(null, _3e5) - }), _3e4); - return this - } - this._startTime = new Date().valueOf(); - if (this._paused) { - this._startTime -= (this.duration * this._percent / 100) - } - this._endTime = this._startTime + this.duration; - this._active = true; - this._paused = false; - var step = this._percent / 100; - var _3e7 = this.curve.getValue(step); - if (this._percent == 0) { - if (!this._startRepeatCount) { - this._startRepeatCount = this.repeatCount - } - this.fire('handler', ['begin', _3e7]); - this.fire('onBegin', [_3e7]) - } - this.fire('handler', ['play', _3e7]); - this.fire('onPlay', [_3e7]); - this._cycle(); - return this - }, pause: function () { - clearTimeout(this._timer); - if (!this._active) { - return this - } - this._paused = true; - var _3e8 = this.curve.getValue(this._percent / 100); - this.fire('handler', ['pause', _3e8]); - this.fire('onPause', [_3e8]); - return this - }, gotoPercent: function (pct, _3ea) { - clearTimeout(this._timer); - this._active = true; - this._paused = true; - this._percent = pct; - if (_3ea) { - this.play() - } - return this - }, stop: function (_3eb) { - clearTimeout(this._timer); - var step = this._percent / 100; - if (_3eb) { - step = 1 - } - var _3ed = this.curve.getValue(step); - this.fire('handler', ['stop', _3ed]); - this.fire('onStop', [_3ed]); - this._active = false; - this._paused = false; - return this - }, status: function () { - if (this._active) { - return this._paused ? 'paused' : 'playing' - } else { - return 'stopped' - } - return this - }, _cycle: function () { - clearTimeout(this._timer); - if (this._active) { - var curr = new Date().valueOf(); - var step = (curr - this._startTime) / (this._endTime - this._startTime); - if (step >= 1) { - step = 1; - this._percent = 100 - } else { - this._percent = step * 100 - } - if ((this.easing) && (dojo.lang.isFunction(this.easing))) { - step = this.easing(step) - } - var _3f0 = this.curve.getValue(step); - this.fire('handler', ['animate', _3f0]); - this.fire('onAnimate', [_3f0]); - if (step < 1) { - this._timer = setTimeout(dojo.lang.hitch(this, '_cycle'), this.rate) - } else { - this._active = false; - this.fire('handler', ['end']); - this.fire('onEnd'); - if (this.repeatCount > 0) { - this.repeatCount--; - this.play(null, true) - } else { - if (this.repeatCount == -1) { - this.play(null, true) - } else { - if (this._startRepeatCount) { - this.repeatCount = this._startRepeatCount; - this._startRepeatCount = 0 - } - } - } - } - } - return this - } -}); -dojo.lfx.Combine = function (_3f1) { - dojo.lfx.IAnimation.call(this); - this._anims = []; - this._animsEnded = 0; - var _3f2 = arguments; - if (_3f2.length == 1 && (dojo.lang.isArray(_3f2[0]) || dojo.lang.isArrayLike(_3f2[0]))) { - _3f2 = _3f2[0] - } - dojo.lang.forEach(_3f2, function (anim) { - this._anims.push(anim); - anim.connect('onEnd', dojo.lang.hitch(this, '_onAnimsEnded')) - }, this) -}; -dojo.inherits(dojo.lfx.Combine, dojo.lfx.IAnimation); -dojo.lang.extend(dojo.lfx.Combine, { - _animsEnded: 0, play: function (_3f4, _3f5) { - if (!this._anims.length) { - return this - } - this.fire('beforeBegin'); - if (_3f4 > 0) { - setTimeout(dojo.lang.hitch(this, function () { - this.play(null, _3f5) - }), _3f4); - return this - } - if (_3f5 || this._anims[0].percent == 0) { - this.fire('onBegin') - } - this.fire('onPlay'); - this._animsCall('play', null, _3f5); - return this - }, pause: function () { - this.fire('onPause'); - this._animsCall('pause'); - return this - }, stop: function (_3f6) { - this.fire('onStop'); - this._animsCall('stop', _3f6); - return this - }, _onAnimsEnded: function () { - this._animsEnded++; - if (this._animsEnded >= this._anims.length) { - this.fire('onEnd') - } - return this - }, _animsCall: function (_3f7) { - var args = []; - if (arguments.length > 1) { - for (var i = 1; i < arguments.length; i++) { - args.push(arguments[i]) - } - } - var _3fa = this; - dojo.lang.forEach(this._anims, function (anim) { - anim[_3f7](args) - }, _3fa); - return this - } -}); -dojo.lfx.Chain = function (_3fc) { - dojo.lfx.IAnimation.call(this); - this._anims = []; - this._currAnim = -1; - var _3fd = arguments; - if (_3fd.length == 1 && (dojo.lang.isArray(_3fd[0]) || dojo.lang.isArrayLike(_3fd[0]))) { - _3fd = _3fd[0] - } - var _3fe = this; - dojo.lang.forEach(_3fd, function (anim, i, _401) { - this._anims.push(anim); - if (i < _401.length - 1) { - anim.connect('onEnd', dojo.lang.hitch(this, '_playNext')) - } else { - anim.connect('onEnd', dojo.lang.hitch(this, function () { - this.fire('onEnd') - })) - } - }, this) -}; -dojo.inherits(dojo.lfx.Chain, dojo.lfx.IAnimation); -dojo.lang.extend(dojo.lfx.Chain, { - _currAnim: -1, play: function (_402, _403) { - if (!this._anims.length) { - return this - } - if (_403 || !this._anims[this._currAnim]) { - this._currAnim = 0 - } - var _404 = this._anims[this._currAnim]; - this.fire('beforeBegin'); - if (_402 > 0) { - setTimeout(dojo.lang.hitch(this, function () { - this.play(null, _403) - }), _402); - return this - } - if (_404) { - if (this._currAnim == 0) { - this.fire('handler', ['begin', this._currAnim]); - this.fire('onBegin', [this._currAnim]) - } - this.fire('onPlay', [this._currAnim]); - _404.play(null, _403) - } - return this - }, pause: function () { - if (this._anims[this._currAnim]) { - this._anims[this._currAnim].pause(); - this.fire('onPause', [this._currAnim]) - } - return this - }, playPause: function () { - if (this._anims.length == 0) { - return this - } - if (this._currAnim == -1) { - this._currAnim = 0 - } - var _405 = this._anims[this._currAnim]; - if (_405) { - if (!_405._active || _405._paused) { - this.play() - } else { - this.pause() - } - } - return this - }, stop: function () { - var _406 = this._anims[this._currAnim]; - if (_406) { - _406.stop(); - this.fire('onStop', [this._currAnim]) - } - return _406 - }, _playNext: function () { - if (this._currAnim == -1 || this._anims.length == 0) { - return this - } - this._currAnim++; - if (this._anims[this._currAnim]) { - this._anims[this._currAnim].play(null, true) - } - return this - } -}); -dojo.lfx.combine = function (_407) { - var _408 = arguments; - if (dojo.lang.isArray(arguments[0])) { - _408 = arguments[0] - } - if (_408.length == 1) { - return _408[0] - } - return new dojo.lfx.Combine(_408) -}; -dojo.lfx.chain = function (_409) { - var _40a = arguments; - if (dojo.lang.isArray(arguments[0])) { - _40a = arguments[0] - } - if (_40a.length == 1) { - return _40a[0] - } - return new dojo.lfx.Chain(_40a) -}; -dojo.provide('dojo.html.common'); -dojo.lang.mixin(dojo.html, dojo.dom); -dojo.html.body = function () { - dojo.deprecated('dojo.html.body() moved to dojo.body()', '0.5'); - return dojo.body() -}; -dojo.html.getEventTarget = function (evt) { - if (!evt) { - evt = dojo.global().event || {} - } - var t = (evt.srcElement ? evt.srcElement : (evt.target ? evt.target : null)); - while ((t) && (t.nodeType != 1)) { - t = t.parentNode - } - return t -}; -dojo.html.getViewport = function () { - var _40d = dojo.global(); - var _40e = dojo.doc(); - var w = 0; - var h = 0; - if (dojo.render.html.mozilla) { - w = _40e.documentElement.clientWidth; - h = _40d.innerHeight - } else { - if (!dojo.render.html.opera && _40d.innerWidth) { - w = _40d.innerWidth; - h = _40d.innerHeight - } else { - if (!dojo.render.html.opera && dojo.exists(_40e, 'documentElement.clientWidth')) { - var w2 = _40e.documentElement.clientWidth; - if (!w || w2 && w2 < w) { - w = w2 - } - h = _40e.documentElement.clientHeight - } else { - if (dojo.body().clientWidth) { - w = dojo.body().clientWidth; - h = dojo.body().clientHeight - } - } - } - } - return {width: w, height: h} -}; -dojo.html.getScroll = function () { - var _412 = dojo.global(); - var _413 = dojo.doc(); - var top = _412.pageYOffset || _413.documentElement.scrollTop || dojo.body().scrollTop || 0; - var left = _412.pageXOffset || _413.documentElement.scrollLeft || dojo.body().scrollLeft || 0; - return {top: top, left: left, offset: {x: left, y: top}} -}; -dojo.html.getParentByType = function (node, type) { - var _418 = dojo.doc(); - var _419 = dojo.byId(node); - type = type.toLowerCase(); - while ((_419) && (_419.nodeName.toLowerCase() != type)) { - if (_419 == (_418['body'] || _418['documentElement'])) { - return null - } - _419 = _419.parentNode - } - return _419 -}; -dojo.html.getAttribute = function (node, attr) { - node = dojo.byId(node); - if ((!node) || (!node.getAttribute)) { - return null - } - var ta = typeof attr == 'string' ? attr : String(attr); - var v = node.getAttribute(ta.toUpperCase()); - if ((v) && (typeof v == 'string') && (v != '')) { - return v - } - if (v && v.value) { - return v.value - } - if ((node.getAttributeNode) && (node.getAttributeNode(ta))) { - return (node.getAttributeNode(ta)).value - } else { - if (node.getAttribute(ta)) { - return node.getAttribute(ta) - } else { - if (node.getAttribute(ta.toLowerCase())) { - return node.getAttribute(ta.toLowerCase()) - } - } - } - return null -}; -dojo.html.hasAttribute = function (node, attr) { - return dojo.html.getAttribute(dojo.byId(node), attr) ? true : false -}; -dojo.html.getCursorPosition = function (e) { - e = e || dojo.global().event; - var _421 = {x: 0, y: 0}; - if (e.pageX || e.pageY) { - _421.x = e.pageX; - _421.y = e.pageY - } else { - var de = dojo.doc().documentElement; - var db = dojo.body(); - _421.x = e.clientX + ((de || db)['scrollLeft']) - ((de || db)['clientLeft']); - _421.y = e.clientY + ((de || db)['scrollTop']) - ((de || db)['clientTop']) - } - return _421 -}; -dojo.html.isTag = function (node) { - node = dojo.byId(node); - if (node && node.tagName) { - for (var i = 1; i < arguments.length; i++) { - if (node.tagName.toLowerCase() == String(arguments[i]).toLowerCase()) { - return String(arguments[i]).toLowerCase() - } - } - } - return '' -}; -if (dojo.render.html.ie && !dojo.render.html.ie70) { - if (window.location.href.substr(0, 6).toLowerCase() != 'https:') { - (function () { - var _426 = dojo.doc().createElement('script'); - _426.src = 'javascript:\'dojo.html.createExternalElement=function(doc, tag){ return doc.createElement(tag); }\''; - dojo.doc().getElementsByTagName('head')[0].appendChild(_426) - })() - } -} else { - dojo.html.createExternalElement = function (doc, tag) { - return doc.createElement(tag) - } -} -dojo.html._callDeprecated = function (_429, _42a, args, _42c, _42d) { - dojo.deprecated('dojo.html.' + _429, 'replaced by dojo.html.' + _42a + '(' + (_42c ? 'node, {' - + _42c - + ': ' - + _42c - + '}' : '') + ')' + (_42d ? '.' + _42d : ''), '0.5'); - var _42e = []; - if (_42c) { - var _42f = {}; - _42f[_42c] = args[1]; - _42e.push(args[0]); - _42e.push(_42f) - } else { - _42e = args - } - var ret = dojo.html[_42a].apply(dojo.html, args); - if (_42d) { - return ret[_42d] - } else { - return ret - } -}; -dojo.html.getViewportWidth = function () { - return dojo.html._callDeprecated('getViewportWidth', 'getViewport', arguments, null, 'width') -}; -dojo.html.getViewportHeight = function () { - return dojo.html._callDeprecated('getViewportHeight', 'getViewport', arguments, null, 'height') -}; -dojo.html.getViewportSize = function () { - return dojo.html._callDeprecated('getViewportSize', 'getViewport', arguments) -}; -dojo.html.getScrollTop = function () { - return dojo.html._callDeprecated('getScrollTop', 'getScroll', arguments, null, 'top') -}; -dojo.html.getScrollLeft = function () { - return dojo.html._callDeprecated('getScrollLeft', 'getScroll', arguments, null, 'left') -}; -dojo.html.getScrollOffset = function () { - return dojo.html._callDeprecated('getScrollOffset', 'getScroll', arguments, null, 'offset') -}; -dojo.provide('dojo.uri.Uri'); -dojo.uri = new function () { - this.dojoUri = function (uri) { - return new dojo.uri.Uri(dojo.hostenv.getBaseScriptUri(), uri) - }; - this.moduleUri = function (_432, uri) { - var loc = dojo.hostenv.getModuleSymbols(_432).join('/'); - if (!loc) { - return null - } - if (loc.lastIndexOf('/') != loc.length - 1) { - loc += '/' - } - var _435 = loc.indexOf(':'); - var _436 = loc.indexOf('/'); - if (loc.charAt(0) != '/' && (_435 == -1 || _435 > _436)) { - loc = dojo.hostenv.getBaseScriptUri() + loc - } - return new dojo.uri.Uri(loc, uri) - }; - this.Uri = function () { - var uri = arguments[0]; - for (var i = 1; i < arguments.length; i++) { - if (!arguments[i]) { - continue - } - var _439 = new dojo.uri.Uri(arguments[i].toString()); - var _43a = new dojo.uri.Uri(uri.toString()); - if ((_439.path == '') && (_439.scheme == null) && (_439.authority == null) && (_439.query == null)) { - if (_439.fragment != null) { - _43a.fragment = _439.fragment - } - _439 = _43a - } else { - if (_439.scheme == null) { - _439.scheme = _43a.scheme; - if (_439.authority == null) { - _439.authority = _43a.authority; - if (_439.path.charAt(0) != '/') { - var path = _43a.path.substring(0, _43a.path.lastIndexOf('/') + 1) + _439.path; - var segs = path.split('/'); - for (var j = 0; j < segs.length; j++) { - if (segs[j] == '.') { - if (j == segs.length - 1) { - segs[j] = '' - } else { - segs.splice(j, 1); - j-- - } - } else { - if (j > 0 && !(j == 1 && segs[0] == '') && segs[j] == '..' && segs[j - 1] != '..') { - if (j == segs.length - 1) { - segs.splice(j, 1); - segs[j - 1] = '' - } else { - segs.splice(j - 1, 2); - j -= 2 - } - } - } - } - _439.path = segs.join('/') - } - } - } - } - uri = ''; - if (_439.scheme != null) { - uri += _439.scheme + ':' - } - if (_439.authority != null) { - uri += '//' + _439.authority - } - uri += _439.path; - if (_439.query != null) { - uri += '?' + _439.query - } - if (_439.fragment != null) { - uri += '#' + _439.fragment - } - } - this.uri = uri.toString(); - var _43e = '^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?$'; - var r = this.uri.match(new RegExp(_43e)); - this.scheme = r[2] || (r[1] ? '' : null); - this.authority = r[4] || (r[3] ? '' : null); - this.path = r[5]; - this.query = r[7] || (r[6] ? '' : null); - this.fragment = r[9] || (r[8] ? '' : null); - if (this.authority != null) { - _43e = '^((([^:]+:)?([^@]+))@)?([^:]*)(:([0-9]+))?$'; - r = this.authority.match(new RegExp(_43e)); - this.user = r[3] || null; - this.password = r[4] || null; - this.host = r[5]; - this.port = r[7] || null - } - this.toString = function () { - return this.uri - } - } -}; -;dojo.provide('dojo.html.style'); -dojo.html.getClass = function (node) { - node = dojo.byId(node); - if (!node) { - return '' - } - var cs = ''; - if (node.className) { - cs = node.className - } else { - if (dojo.html.hasAttribute(node, 'class')) { - cs = dojo.html.getAttribute(node, 'class') - } - } - return cs.replace(/^\s+|\s+$/g, '') -}; -dojo.html.getClasses = function (node) { - var c = dojo.html.getClass(node); - return (c == '') ? [] : c.split(/\s+/g) -}; -dojo.html.hasClass = function (node, _445) { - return (new RegExp('(^|\\s+)' + _445 + '(\\s+|$)')).test(dojo.html.getClass(node)) -}; -dojo.html.prependClass = function (node, _447) { - _447 += ' ' + dojo.html.getClass(node); - return dojo.html.setClass(node, _447) -}; -dojo.html.addClass = function (node, _449) { - if (dojo.html.hasClass(node, _449)) { - return false - } - _449 = (dojo.html.getClass(node) + ' ' + _449).replace(/^\s+|\s+$/g, ''); - return dojo.html.setClass(node, _449) -}; -dojo.html.setClass = function (node, _44b) { - node = dojo.byId(node); - var cs = String(_44b); - try { - if (typeof node.className == 'string') { - node.className = cs - } else { - if (node.setAttribute) { - node.setAttribute('class', _44b); - node.className = cs - } else { - return false - } - } - } catch (e) { - dojo.debug('dojo.html.setClass() failed', e) - } - return true -}; -dojo.html.removeClass = function (node, _44e, _44f) { - try { - if (!_44f) { - var _450 = dojo.html.getClass(node).replace(new RegExp('(^|\\s+)' + _44e + '(\\s+|$)'), '$1$2') - } else { - var _450 = dojo.html.getClass(node).replace(_44e, '') - } - dojo.html.setClass(node, _450) - } catch (e) { - dojo.debug('dojo.html.removeClass() failed', e) - } - return true -}; -dojo.html.replaceClass = function (node, _452, _453) { - dojo.html.removeClass(node, _453); - dojo.html.addClass(node, _452) -}; -dojo.html.classMatchType = {ContainsAll: 0, ContainsAny: 1, IsOnly: 2}; -dojo.html.getElementsByClass = function (_454, _455, _456, _457, _458) { - _458 = false; - var _459 = dojo.doc(); - _455 = dojo.byId(_455) || _459; - var _45a = _454.split(/\s+/g); - var _45b = []; - if (_457 != 1 && _457 != 2) { - _457 = 0 - } - var _45c = new RegExp('(\\s|^)((' + _45a.join(')|(') + '))(\\s|$)'); - var _45d = _45a.join(' ').length; - var _45e = []; - if (!_458 && _459.evaluate) { - var _45f = './/' + (_456 || '*') + '[contains('; - if (_457 != dojo.html.classMatchType.ContainsAny) { - _45f += 'concat(\' \',@class,\' \'), \' ' - + _45a.join(' \') and contains(concat(\' \',@class,\' \'), \' ') - + ' \')'; - if (_457 == 2) { - _45f += ' and string-length(@class)=' + _45d + ']' - } else { - _45f += ']' - } - } else { - _45f += 'concat(\' \',@class,\' \'), \' ' - + _45a.join(' \') or contains(concat(\' \',@class,\' \'), \' ') - + ' \')]' - } - var _460 = _459.evaluate(_45f, _455, null, XPathResult.ANY_TYPE, null); - var _461 = _460.iterateNext(); - while (_461) { - try { - _45e.push(_461); - _461 = _460.iterateNext() - } catch (e) { - break - } - } - return _45e - } else { - if (!_456) { - _456 = '*' - } - _45e = _455.getElementsByTagName(_456); - var node, i = 0; - outer: - while (node = _45e[i++]) { - var _464 = dojo.html.getClasses(node); - if (_464.length == 0) { - continue; - } - var _465 = 0; - for (var j = 0; j < _464.length; j++) { - if (_45c.test(_464[j])) { - if (_457 == dojo.html.classMatchType.ContainsAny) { - _45b.push(node); - continue outer - } else { - _465++ - } - } else { - if (_457 == dojo.html.classMatchType.IsOnly) { - continue outer - } - } - } - if (_465 == _45a.length) { - if ((_457 == dojo.html.classMatchType.IsOnly) && (_465 == _464.length)) { - _45b.push(node) - } else { - if (_457 == dojo.html.classMatchType.ContainsAll) { - _45b.push(node) - } - } - } - } - return _45b - } -}; -dojo.html.getElementsByClassName = dojo.html.getElementsByClass; -dojo.html.toCamelCase = function (_467) { - var arr = _467.split('-'), cc = arr[0]; - for (var i = 1; i < arr.length; i++) { - cc += arr[i].charAt(0).toUpperCase() + arr[i].substring(1) - } - return cc -}; -dojo.html.toSelectorCase = function (_46b) { - return _46b.replace(/([A-Z])/g, '-$1').toLowerCase() -}; -if (dojo.render.html.ie) { - dojo.html.getComputedStyle = function (node, _46d, _46e) { - node = dojo.byId(node); - if (!node || !node.currentStyle) { - return _46e - } - return node.currentStyle[dojo.html.toCamelCase(_46d)] - }; - dojo.html.getComputedStyles = function (node) { - return node.currentStyle - } -} else { - dojo.html.getComputedStyle = function (node, _471, _472) { - node = dojo.byId(node); - if (!node || !node.style) { - return _472 - } - var s = document.defaultView.getComputedStyle(node, null); - return (s && s[dojo.html.toCamelCase(_471)]) || '' - }; - dojo.html.getComputedStyles = function (node) { - return document.defaultView.getComputedStyle(node, null) - } -} -dojo.html.getStyleProperty = function (node, _476) { - node = dojo.byId(node); - return (node && node.style ? node.style[dojo.html.toCamelCase(_476)] : undefined) -}; -dojo.html.getStyle = function (node, _478) { - var _479 = dojo.html.getStyleProperty(node, _478); - return (_479 ? _479 : dojo.html.getComputedStyle(node, _478)) -}; -dojo.html.setStyle = function (node, _47b, _47c) { - node = dojo.byId(node); - if (node && node.style) { - var _47d = dojo.html.toCamelCase(_47b); - node.style[_47d] = _47c - } -}; -dojo.html.setStyleText = function (_47e, text) { - try { - _47e.style.cssText = text - } catch (e) { - _47e.setAttribute('style', text) - } -}; -dojo.html.copyStyle = function (_480, _481) { - if (!_481.style.cssText) { - _480.setAttribute('style', _481.getAttribute('style')) - } else { - _480.style.cssText = _481.style.cssText - } - dojo.html.addClass(_480, dojo.html.getClass(_481)) -}; -dojo.html.getUnitValue = function (node, _483, _484) { - var s = dojo.html.getComputedStyle(node, _483); - if ((!s) || ((s == 'auto') && (_484))) { - return {value: 0, units: 'px'} - } - var _486 = s.match(/(\-?[\d.]+)([a-z%]*)/i); - if (!_486) { - return dojo.html.getUnitValue.bad - } - return {value: Number(_486[1]), units: _486[2].toLowerCase()} -}; -dojo.html.getUnitValue.bad = {value: NaN, units: ''}; -if (dojo.render.html.ie) { - dojo.html.toPixelValue = function (_487, _488) { - if (!_488) { - return 0 - } - if (_488.slice(-2) == 'px') { - return parseFloat(_488) - } - var _489 = 0; - with (_487) { - var _48a = style.left; - var _48b = runtimeStyle.left; - runtimeStyle.left = currentStyle.left; - try { - style.left = _488 || 0; - _489 = style.pixelLeft; - style.left = _48a; - runtimeStyle.left = _48b - } catch (e) { - } - } - return _489 - } -} else { - dojo.html.toPixelValue = function (_48c, _48d) { - return (_48d && (_48d.slice(-2) == 'px') ? parseFloat(_48d) : 0) - } -} -dojo.html.getPixelValue = function (node, _48f, _490) { - return dojo.html.toPixelValue(node, dojo.html.getComputedStyle(node, _48f)) -}; -dojo.html.setPositivePixelValue = function (node, _492, _493) { - if (isNaN(_493)) { - return false - } - node.style[_492] = Math.max(0, _493) + 'px'; - return true -}; -dojo.html.styleSheet = null; -dojo.html.insertCssRule = function (_494, _495, _496) { - if (!dojo.html.styleSheet) { - if (document.createStyleSheet) { - dojo.html.styleSheet = document.createStyleSheet() - } else { - if (document.styleSheets[0]) { - dojo.html.styleSheet = document.styleSheets[0] - } else { - return null - } - } - } - if (arguments.length < 3) { - if (dojo.html.styleSheet.cssRules) { - _496 = dojo.html.styleSheet.cssRules.length - } else { - if (dojo.html.styleSheet.rules) { - _496 = dojo.html.styleSheet.rules.length - } else { - return null - } - } - } - if (dojo.html.styleSheet.insertRule) { - var rule = _494 + ' { ' + _495 + ' }'; - return dojo.html.styleSheet.insertRule(rule, _496) - } else { - if (dojo.html.styleSheet.addRule) { - return dojo.html.styleSheet.addRule(_494, _495, _496) - } else { - return null - } - } -}; -dojo.html.removeCssRule = function (_498) { - if (!dojo.html.styleSheet) { - dojo.debug('no stylesheet defined for removing rules'); - return false - } - if (dojo.render.html.ie) { - if (!_498) { - _498 = dojo.html.styleSheet.rules.length; - dojo.html.styleSheet.removeRule(_498) - } - } else { - if (document.styleSheets[0]) { - if (!_498) { - _498 = dojo.html.styleSheet.cssRules.length - } - dojo.html.styleSheet.deleteRule(_498) - } - } - return true -}; -dojo.html._insertedCssFiles = []; -dojo.html.insertCssFile = function (URI, doc, _49b, _49c) { - if (!URI) { - return - } - if (!doc) { - doc = document - } - var _49d = dojo.hostenv.getText(URI, false, _49c); - if (_49d === null) { - return - } - _49d = dojo.html.fixPathsInCssText(_49d, URI); - if (_49b) { - var idx = -1, node, ent = dojo.html._insertedCssFiles; - for (var i = 0; i < ent.length; i++) { - if ((ent[i].doc == doc) && (ent[i].cssText == _49d)) { - idx = i; - node = ent[i].nodeRef; - break - } - } - if (node) { - var _4a2 = doc.getElementsByTagName('style'); - for (var i = 0; i < _4a2.length; i++) { - if (_4a2[i] == node) { - return - } - } - dojo.html._insertedCssFiles.shift(idx, 1) - } - } - var _4a3 = dojo.html.insertCssText(_49d, doc); - dojo.html._insertedCssFiles.push({'doc': doc, 'cssText': _49d, 'nodeRef': _4a3}); - if (_4a3 && djConfig.isDebug) { - _4a3.setAttribute('dbgHref', URI) - } - return _4a3 -}; -dojo.html.insertCssText = function (_4a4, doc, URI) { - if (!_4a4) { - return - } - if (!doc) { - doc = document - } - if (URI) { - _4a4 = dojo.html.fixPathsInCssText(_4a4, URI) - } - var _4a7 = doc.createElement('style'); - _4a7.setAttribute('type', 'text/css'); - var head = doc.getElementsByTagName('head')[0]; - if (!head) { - dojo.debug('No head tag in document, aborting styles'); - return - } else { - head.appendChild(_4a7) - } - if (_4a7.styleSheet) { - var _4a9 = function () { - try { - _4a7.styleSheet.cssText = _4a4 - } catch (e) { - dojo.debug(e) - } - }; - if (_4a7.styleSheet.disabled) { - setTimeout(_4a9, 10) - } else { - _4a9() - } - } else { - var _4aa = doc.createTextNode(_4a4); - _4a7.appendChild(_4aa) - } - return _4a7 -}; -dojo.html.fixPathsInCssText = function (_4ab, URI) { - if (!_4ab || !URI) { - return - } - var _4ad, str = '', url = '', _4b0 = '[\\t\\s\\w\\(\\)\\/\\.\\\\\'"-:#=&?~]+'; - var _4b1 = new RegExp('url\\(\\s*(' + _4b0 + ')\\s*\\)'); - var _4b2 = /(file|https?|ftps?):\/\//; - regexTrim = new RegExp('^[\\s]*([\'"]?)(' + _4b0 + ')\\1[\\s]*?$'); - if (dojo.render.html.ie55 || dojo.render.html.ie60) { - var _4b3 = new RegExp('AlphaImageLoader\\((.*)src=[\'"](' + _4b0 + ')[\'"]'); - while (_4ad = _4b3.exec(_4ab)) { - url = _4ad[2].replace(regexTrim, '$2'); - if (!_4b2.exec(url)) { - url = (new dojo.uri.Uri(URI, url).toString()) - } - str += _4ab.substring(0, _4ad.index) + 'AlphaImageLoader(' + _4ad[1] + 'src=\'' + url + '\''; - _4ab = _4ab.substr(_4ad.index + _4ad[0].length) - } - _4ab = str + _4ab; - str = '' - } - while (_4ad = _4b1.exec(_4ab)) { - url = _4ad[1].replace(regexTrim, '$2'); - if (!_4b2.exec(url)) { - url = (new dojo.uri.Uri(URI, url).toString()) - } - str += _4ab.substring(0, _4ad.index) + 'url(' + url + ')'; - _4ab = _4ab.substr(_4ad.index + _4ad[0].length) - } - return str + _4ab -}; -dojo.html.setActiveStyleSheet = function (_4b4) { - var i = 0, a, els = dojo.doc().getElementsByTagName('link'); - while (a = els[i++]) { - if (a.getAttribute('rel').indexOf('style') != -1 && a.getAttribute('title')) { - a.disabled = true; - if (a.getAttribute('title') == _4b4) { - a.disabled = false - } - } - } -}; -dojo.html.getActiveStyleSheet = function () { - var i = 0, a, els = dojo.doc().getElementsByTagName('link'); - while (a = els[i++]) { - if (a.getAttribute('rel').indexOf('style') != -1 && a.getAttribute('title') && !a.disabled) { - return a.getAttribute('title') - } - } - return null -}; -dojo.html.getPreferredStyleSheet = function () { - var i = 0, a, els = dojo.doc().getElementsByTagName('link'); - while (a = els[i++]) { - if (a.getAttribute('rel').indexOf('style') - != -1 - && a.getAttribute('rel').indexOf('alt') - == -1 - && a.getAttribute('title')) { - return a.getAttribute('title') - } - } - return null -}; -dojo.html.applyBrowserClass = function (node) { - var drh = dojo.render.html; - var _4c0 = { - dj_ie: drh.ie, - dj_ie55: drh.ie55, - dj_ie6: drh.ie60, - dj_ie7: drh.ie70, - dj_iequirks: drh.ie && drh.quirks, - dj_opera: drh.opera, - dj_opera8: drh.opera && (Math.floor(dojo.render.version) == 8), - dj_opera9: drh.opera && (Math.floor(dojo.render.version) == 9), - dj_khtml: drh.khtml, - dj_safari: drh.safari, - dj_gecko: drh.mozilla - }; - for (var p in _4c0) { - if (_4c0[p]) { - dojo.html.addClass(node, p) - } - } -}; -dojo.provide('dojo.html.display'); -dojo.html._toggle = function (node, _4c3, _4c4) { - node = dojo.byId(node); - _4c4(node, !_4c3(node)); - return _4c3(node) -}; -dojo.html.show = function (node) { - node = dojo.byId(node); - if (dojo.html.getStyleProperty(node, 'display') == 'none') { - dojo.html.setStyle(node, 'display', (node.dojoDisplayCache || '')); - node.dojoDisplayCache = undefined - } -}; -dojo.html.hide = function (node) { - node = dojo.byId(node); - if (typeof node['dojoDisplayCache'] == 'undefined') { - var d = dojo.html.getStyleProperty(node, 'display'); - if (d != 'none') { - node.dojoDisplayCache = d - } - } - dojo.html.setStyle(node, 'display', 'none') -}; -dojo.html.setShowing = function (node, _4c9) { - dojo.html[(_4c9 ? 'show' : 'hide')](node) -}; -dojo.html.isShowing = function (node) { - return (dojo.html.getStyleProperty(node, 'display') != 'none') -}; -dojo.html.toggleShowing = function (node) { - return dojo.html._toggle(node, dojo.html.isShowing, dojo.html.setShowing) -}; -dojo.html.displayMap = {tr: '', td: '', th: '', img: 'inline', span: 'inline', input: 'inline', button: 'inline'}; -dojo.html.suggestDisplayByTagName = function (node) { - node = dojo.byId(node); - if (node && node.tagName) { - var tag = node.tagName.toLowerCase(); - return (tag in dojo.html.displayMap ? dojo.html.displayMap[tag] : 'block') - } -}; -dojo.html.setDisplay = function (node, _4cf) { - dojo.html.setStyle(node, 'display', ((_4cf - instanceof String - || typeof _4cf - == 'string') ? _4cf : (_4cf ? dojo.html.suggestDisplayByTagName(node) : 'none'))) -}; -dojo.html.isDisplayed = function (node) { - return (dojo.html.getComputedStyle(node, 'display') != 'none') -}; -dojo.html.toggleDisplay = function (node) { - return dojo.html._toggle(node, dojo.html.isDisplayed, dojo.html.setDisplay) -}; -dojo.html.setVisibility = function (node, _4d3) { - dojo.html.setStyle(node, 'visibility', ((_4d3 - instanceof String - || typeof _4d3 - == 'string') ? _4d3 : (_4d3 ? 'visible' : 'hidden'))) -}; -dojo.html.isVisible = function (node) { - return (dojo.html.getComputedStyle(node, 'visibility') != 'hidden') -}; -dojo.html.toggleVisibility = function (node) { - return dojo.html._toggle(node, dojo.html.isVisible, dojo.html.setVisibility) -}; -dojo.html.setOpacity = function (node, _4d7, _4d8) { - node = dojo.byId(node); - var h = dojo.render.html; - if (!_4d8) { - if (_4d7 >= 1) { - if (h.ie) { - dojo.html.clearOpacity(node); - return - } else { - _4d7 = 0.999999 - } - } else { - if (_4d7 < 0) { - _4d7 = 0 - } - } - } - if (h.ie) { - if (node.nodeName.toLowerCase() == 'tr') { - var tds = node.getElementsByTagName('td'); - for (var x = 0; x < tds.length; x++) { - tds[x].style.filter = 'Alpha(Opacity=' + _4d7 * 100 + ')' - } - } - node.style.filter = 'Alpha(Opacity=' + _4d7 * 100 + ')' - } else { - if (h.moz) { - node.style.opacity = _4d7; - node.style.MozOpacity = _4d7 - } else { - if (h.safari) { - node.style.opacity = _4d7; - node.style.KhtmlOpacity = _4d7 - } else { - node.style.opacity = _4d7 - } - } - } -}; -dojo.html.clearOpacity = function (node) { - node = dojo.byId(node); - var ns = node.style; - var h = dojo.render.html; - if (h.ie) { - try { - if (node.filters && node.filters.alpha) { - ns.filter = '' - } - } catch (e) { - } - } else { - if (h.moz) { - ns.opacity = 1; - ns.MozOpacity = 1 - } else { - if (h.safari) { - ns.opacity = 1; - ns.KhtmlOpacity = 1 - } else { - ns.opacity = 1 - } - } - } -}; -dojo.html.getOpacity = function (node) { - node = dojo.byId(node); - var h = dojo.render.html; - if (h.ie) { - var opac = (node.filters - && node.filters.alpha - && typeof node.filters.alpha.opacity - == 'number' ? node.filters.alpha.opacity : 100) / 100 - } else { - var opac = node.style.opacity || node.style.MozOpacity || node.style.KhtmlOpacity || 1 - } - return opac >= 0.999999 ? 1 : Number(opac) -}; -dojo.provide('dojo.html.color'); -dojo.html.getBackgroundColor = function (node) { - node = dojo.byId(node); - var _4e3; - do { - _4e3 = dojo.html.getStyle(node, 'background-color'); - if (_4e3.toLowerCase() == 'rgba(0, 0, 0, 0)') { - _4e3 = 'transparent' - } - if (node == document.getElementsByTagName('body')[0]) { - node = null; - break - } - node = node.parentNode - } while (node && dojo.lang.inArray(['transparent', ''], _4e3)); - if (_4e3 == 'transparent') { - _4e3 = [255, 255, 255, 0] - } else { - _4e3 = dojo.gfx.color.extractRGB(_4e3) - } - return _4e3 -}; -dojo.provide('dojo.html.layout'); -dojo.html.sumAncestorProperties = function (node, prop) { - node = dojo.byId(node); - if (!node) { - return 0 - } - var _4e6 = 0; - while (node) { - if (dojo.html.getComputedStyle(node, 'position') == 'fixed') { - return 0 - } - var val = node[prop]; - if (val) { - _4e6 += val - 0; - if (node == dojo.body()) { - break - } - } - node = node.parentNode - } - return _4e6 -}; -dojo.html.setStyleAttributes = function (node, _4e9) { - node = dojo.byId(node); - var _4ea = _4e9.replace(/(;)?\s*$/, '').split(';'); - for (var i = 0; i < _4ea.length; i++) { - var _4ec = _4ea[i].split(':'); - var name = _4ec[0].replace(/\s*$/, '').replace(/^\s*/, '').toLowerCase(); - var _4ee = _4ec[1].replace(/\s*$/, '').replace(/^\s*/, ''); - switch (name) { - case 'opacity': - dojo.html.setOpacity(node, _4ee); - break; - case 'content-height': - dojo.html.setContentBox(node, {height: _4ee}); - break; - case 'content-width': - dojo.html.setContentBox(node, {width: _4ee}); - break; - case 'outer-height': - dojo.html.setMarginBox(node, {height: _4ee}); - break; - case 'outer-width': - dojo.html.setMarginBox(node, {width: _4ee}); - break; - default: - node.style[dojo.html.toCamelCase(name)] = _4ee - } - } -}; -dojo.html.boxSizing = { - MARGIN_BOX: 'margin-box', BORDER_BOX: 'border-box', PADDING_BOX: 'padding-box', CONTENT_BOX: 'content-box' -}; -dojo.html.getAbsolutePosition = dojo.html.abs = function (node, _4f0, _4f1) { - node = dojo.byId(node, node.ownerDocument); - var ret = {x: 0, y: 0}; - var bs = dojo.html.boxSizing; - if (!_4f1) { - _4f1 = bs.CONTENT_BOX - } - var _4f4 = 2; - var _4f5; - switch (_4f1) { - case bs.MARGIN_BOX: - _4f5 = 3; - break; - case bs.BORDER_BOX: - _4f5 = 2; - break; - case bs.PADDING_BOX: - default: - _4f5 = 1; - break; - case bs.CONTENT_BOX: - _4f5 = 0; - break - } - var h = dojo.render.html; - var db = document['body'] || document['documentElement']; - if (h.ie) { - with (node.getBoundingClientRect()) { - ret.x = left - 2; - ret.y = top - 2 - } - } else { - if (document.getBoxObjectFor) { - _4f4 = 1; - try { - var bo = document.getBoxObjectFor(node); - ret.x = bo.x - dojo.html.sumAncestorProperties(node, 'scrollLeft'); - ret.y = bo.y - dojo.html.sumAncestorProperties(node, 'scrollTop') - } catch (e) { - } - } else { - if (node['offsetParent']) { - var _4f9; - if ((h.safari) && (node.style.getPropertyValue('position') == 'absolute') && (node.parentNode == db)) { - _4f9 = db - } else { - _4f9 = db.parentNode - } - if (node.parentNode != db) { - var nd = node; - if (dojo.render.html.opera) { - nd = db - } - ret.x -= dojo.html.sumAncestorProperties(nd, 'scrollLeft'); - ret.y -= dojo.html.sumAncestorProperties(nd, 'scrollTop') - } - var _4fb = node; - do { - var n = _4fb['offsetLeft']; - if (!h.opera || n > 0) { - ret.x += isNaN(n) ? 0 : n - } - var m = _4fb['offsetTop']; - ret.y += isNaN(m) ? 0 : m; - _4fb = _4fb.offsetParent - } while ((_4fb != _4f9) && (_4fb != null)) - } else { - if (node['x'] && node['y']) { - ret.x += isNaN(node.x) ? 0 : node.x; - ret.y += isNaN(node.y) ? 0 : node.y - } - } - } - } - if (_4f0) { - var _4fe = dojo.html.getScroll(); - ret.y += _4fe.top; - ret.x += _4fe.left - } - var _4ff = [dojo.html.getPaddingExtent, dojo.html.getBorderExtent, dojo.html.getMarginExtent]; - if (_4f4 > _4f5) { - for (var i = _4f5; i < _4f4; ++i) { - ret.y += _4ff[i](node, 'top'); - ret.x += _4ff[i](node, 'left') - } - } else { - if (_4f4 < _4f5) { - for (var i = _4f5; i > _4f4; --i) { - ret.y -= _4ff[i - 1](node, 'top'); - ret.x -= _4ff[i - 1](node, 'left') - } - } - } - ret.top = ret.y; - ret.left = ret.x; - return ret -}; -dojo.html.isPositionAbsolute = function (node) { - return (dojo.html.getComputedStyle(node, 'position') == 'absolute') -}; -dojo.html._sumPixelValues = function (node, _503, _504) { - var _505 = 0; - for (var x = 0; x < _503.length; x++) { - _505 += dojo.html.getPixelValue(node, _503[x], _504) - } - return _505 -}; -dojo.html.getMargin = function (node) { - return { - width: dojo.html._sumPixelValues(node, ['margin-left', 'margin-right'], (dojo.html.getComputedStyle(node, 'position') - == 'absolute')), - height: dojo.html._sumPixelValues(node, ['margin-top', 'margin-bottom'], (dojo.html.getComputedStyle(node, 'position') - == 'absolute')) - } -}; -dojo.html.getBorder = function (node) { - return { - width: dojo.html.getBorderExtent(node, 'left') + dojo.html.getBorderExtent(node, 'right'), - height: dojo.html.getBorderExtent(node, 'top') + dojo.html.getBorderExtent(node, 'bottom') - } -}; -dojo.html.getBorderExtent = function (node, side) { - return (dojo.html.getStyle(node, 'border-' + side + '-style') == 'none' ? 0 : dojo.html.getPixelValue(node, 'border-' - + side - + '-width')) -}; -dojo.html.getMarginExtent = function (node, side) { - return dojo.html._sumPixelValues(node, ['margin-' + side], dojo.html.isPositionAbsolute(node)) -}; -dojo.html.getPaddingExtent = function (node, side) { - return dojo.html._sumPixelValues(node, ['padding-' + side], true) -}; -dojo.html.getPadding = function (node) { - return { - width: dojo.html._sumPixelValues(node, ['padding-left', 'padding-right'], true), - height: dojo.html._sumPixelValues(node, ['padding-top', 'padding-bottom'], true) - } -}; -dojo.html.getPadBorder = function (node) { - var pad = dojo.html.getPadding(node); - var _512 = dojo.html.getBorder(node); - return {width: pad.width + _512.width, height: pad.height + _512.height} -}; -dojo.html.getBoxSizing = function (node) { - var h = dojo.render.html; - var bs = dojo.html.boxSizing; - if (((h.ie) || (h.opera)) && node.nodeName.toLowerCase() != 'img') { - var cm = document['compatMode']; - if ((cm == 'BackCompat') || (cm == 'QuirksMode')) { - return bs.BORDER_BOX - } else { - return bs.CONTENT_BOX - } - } else { - if (arguments.length == 0) { - node = document.documentElement - } - var _517; - if (!h.ie) { - _517 = dojo.html.getStyle(node, '-moz-box-sizing'); - if (!_517) { - _517 = dojo.html.getStyle(node, 'box-sizing') - } - } - return (_517 ? _517 : bs.CONTENT_BOX) - } -}; -dojo.html.isBorderBox = function (node) { - return (dojo.html.getBoxSizing(node) == dojo.html.boxSizing.BORDER_BOX) -}; -dojo.html.getBorderBox = function (node) { - node = dojo.byId(node); - return {width: node.offsetWidth, height: node.offsetHeight} -}; -dojo.html.getPaddingBox = function (node) { - var box = dojo.html.getBorderBox(node); - var _51c = dojo.html.getBorder(node); - return {width: box.width - _51c.width, height: box.height - _51c.height} -}; -dojo.html.getContentBox = function (node) { - node = dojo.byId(node); - var _51e = dojo.html.getPadBorder(node); - return {width: node.offsetWidth - _51e.width, height: node.offsetHeight - _51e.height} -}; -dojo.html.setContentBox = function (node, args) { - node = dojo.byId(node); - var _521 = 0; - var _522 = 0; - var isbb = dojo.html.isBorderBox(node); - var _524 = (isbb ? dojo.html.getPadBorder(node) : {width: 0, height: 0}); - var ret = {}; - if (typeof args.width != 'undefined') { - _521 = args.width + _524.width; - ret.width = dojo.html.setPositivePixelValue(node, 'width', _521) - } - if (typeof args.height != 'undefined') { - _522 = args.height + _524.height; - ret.height = dojo.html.setPositivePixelValue(node, 'height', _522) - } - return ret -}; -dojo.html.getMarginBox = function (node) { - var _527 = dojo.html.getBorderBox(node); - var _528 = dojo.html.getMargin(node); - return {width: _527.width + _528.width, height: _527.height + _528.height} -}; -dojo.html.setMarginBox = function (node, args) { - node = dojo.byId(node); - var _52b = 0; - var _52c = 0; - var isbb = dojo.html.isBorderBox(node); - var _52e = (!isbb ? dojo.html.getPadBorder(node) : {width: 0, height: 0}); - var _52f = dojo.html.getMargin(node); - var ret = {}; - if (typeof args.width != 'undefined') { - _52b = args.width - _52e.width; - _52b -= _52f.width; - ret.width = dojo.html.setPositivePixelValue(node, 'width', _52b) - } - if (typeof args.height != 'undefined') { - _52c = args.height - _52e.height; - _52c -= _52f.height; - ret.height = dojo.html.setPositivePixelValue(node, 'height', _52c) - } - return ret -}; -dojo.html.getElementBox = function (node, type) { - var bs = dojo.html.boxSizing; - switch (type) { - case bs.MARGIN_BOX: - return dojo.html.getMarginBox(node); - case bs.BORDER_BOX: - return dojo.html.getBorderBox(node); - case bs.PADDING_BOX: - return dojo.html.getPaddingBox(node); - case bs.CONTENT_BOX: - default: - return dojo.html.getContentBox(node) - } -}; -dojo.html.toCoordinateObject = dojo.html.toCoordinateArray = function (_534, _535, _536) { - if (_534 instanceof Array || typeof _534 == 'array') { - dojo.deprecated('dojo.html.toCoordinateArray', 'use dojo.html.toCoordinateObject({left: , top: , width: , height: }) instead', '0.5'); - while (_534.length < 4) { - _534.push(0) - } - while (_534.length > 4) { - _534.pop() - } - var ret = {left: _534[0], top: _534[1], width: _534[2], height: _534[3]} - } else { - if (!_534.nodeType && !(_534 instanceof String || typeof _534 == 'string') && ('width' - in _534 - || 'height' - in _534 - || 'left' - in _534 - || 'x' - in _534 - || 'top' - in _534 - || 'y' - in _534)) { - var ret = { - left: _534.left || _534.x || 0, top: _534.top || _534.y || 0, width: _534.width || 0, height: _534.height || 0 - } - } else { - var node = dojo.byId(_534); - var pos = dojo.html.abs(node, _535, _536); - var _53a = dojo.html.getMarginBox(node); - var ret = {left: pos.left, top: pos.top, width: _53a.width, height: _53a.height} - } - } - ret.x = ret.left; - ret.y = ret.top; - return ret -}; -dojo.html.setMarginBoxWidth = dojo.html.setOuterWidth = function (node, _53c) { - return dojo.html._callDeprecated('setMarginBoxWidth', 'setMarginBox', arguments, 'width') -}; -dojo.html.setMarginBoxHeight = dojo.html.setOuterHeight = function () { - return dojo.html._callDeprecated('setMarginBoxHeight', 'setMarginBox', arguments, 'height') -}; -dojo.html.getMarginBoxWidth = dojo.html.getOuterWidth = function () { - return dojo.html._callDeprecated('getMarginBoxWidth', 'getMarginBox', arguments, null, 'width') -}; -dojo.html.getMarginBoxHeight = dojo.html.getOuterHeight = function () { - return dojo.html._callDeprecated('getMarginBoxHeight', 'getMarginBox', arguments, null, 'height') -}; -dojo.html.getTotalOffset = function (node, type, _53f) { - return dojo.html._callDeprecated('getTotalOffset', 'getAbsolutePosition', arguments, null, type) -}; -dojo.html.getAbsoluteX = function (node, _541) { - return dojo.html._callDeprecated('getAbsoluteX', 'getAbsolutePosition', arguments, null, 'x') -}; -dojo.html.getAbsoluteY = function (node, _543) { - return dojo.html._callDeprecated('getAbsoluteY', 'getAbsolutePosition', arguments, null, 'y') -}; -dojo.html.totalOffsetLeft = function (node, _545) { - return dojo.html._callDeprecated('totalOffsetLeft', 'getAbsolutePosition', arguments, null, 'left') -}; -dojo.html.totalOffsetTop = function (node, _547) { - return dojo.html._callDeprecated('totalOffsetTop', 'getAbsolutePosition', arguments, null, 'top') -}; -dojo.html.getMarginWidth = function (node) { - return dojo.html._callDeprecated('getMarginWidth', 'getMargin', arguments, null, 'width') -}; -dojo.html.getMarginHeight = function (node) { - return dojo.html._callDeprecated('getMarginHeight', 'getMargin', arguments, null, 'height') -}; -dojo.html.getBorderWidth = function (node) { - return dojo.html._callDeprecated('getBorderWidth', 'getBorder', arguments, null, 'width') -}; -dojo.html.getBorderHeight = function (node) { - return dojo.html._callDeprecated('getBorderHeight', 'getBorder', arguments, null, 'height') -}; -dojo.html.getPaddingWidth = function (node) { - return dojo.html._callDeprecated('getPaddingWidth', 'getPadding', arguments, null, 'width') -}; -dojo.html.getPaddingHeight = function (node) { - return dojo.html._callDeprecated('getPaddingHeight', 'getPadding', arguments, null, 'height') -}; -dojo.html.getPadBorderWidth = function (node) { - return dojo.html._callDeprecated('getPadBorderWidth', 'getPadBorder', arguments, null, 'width') -}; -dojo.html.getPadBorderHeight = function (node) { - return dojo.html._callDeprecated('getPadBorderHeight', 'getPadBorder', arguments, null, 'height') -}; -dojo.html.getBorderBoxWidth = dojo.html.getInnerWidth = function () { - return dojo.html._callDeprecated('getBorderBoxWidth', 'getBorderBox', arguments, null, 'width') -}; -dojo.html.getBorderBoxHeight = dojo.html.getInnerHeight = function () { - return dojo.html._callDeprecated('getBorderBoxHeight', 'getBorderBox', arguments, null, 'height') -}; -dojo.html.getContentBoxWidth = dojo.html.getContentWidth = function () { - return dojo.html._callDeprecated('getContentBoxWidth', 'getContentBox', arguments, null, 'width') -}; -dojo.html.getContentBoxHeight = dojo.html.getContentHeight = function () { - return dojo.html._callDeprecated('getContentBoxHeight', 'getContentBox', arguments, null, 'height') -}; -dojo.html.setContentBoxWidth = dojo.html.setContentWidth = function (node, _551) { - return dojo.html._callDeprecated('setContentBoxWidth', 'setContentBox', arguments, 'width') -}; -dojo.html.setContentBoxHeight = dojo.html.setContentHeight = function (node, _553) { - return dojo.html._callDeprecated('setContentBoxHeight', 'setContentBox', arguments, 'height') -}; -dojo.provide('dojo.lfx.html'); -dojo.lfx.html._byId = function (_554) { - if (!_554) { - return [] - } - if (dojo.lang.isArrayLike(_554)) { - if (!_554.alreadyChecked) { - var n = []; - dojo.lang.forEach(_554, function (node) { - n.push(dojo.byId(node)) - }); - n.alreadyChecked = true; - return n - } else { - return _554 - } - } else { - var n = []; - n.push(dojo.byId(_554)); - n.alreadyChecked = true; - return n - } -}; -dojo.lfx.html.propertyAnimation = function (_557, _558, _559, _55a, _55b) { - _557 = dojo.lfx.html._byId(_557); - var _55c = {'propertyMap': _558, 'nodes': _557, 'duration': _559, 'easing': _55a || dojo.lfx.easeDefault}; - var _55d = function (args) { - if (args.nodes.length == 1) { - var pm = args.propertyMap; - if (!dojo.lang.isArray(args.propertyMap)) { - var parr = []; - for (var _561 in pm) { - pm[_561].property = _561; - parr.push(pm[_561]) - } - pm = args.propertyMap = parr - } - dojo.lang.forEach(pm, function (prop) { - if (dj_undef('start', prop)) { - if (prop.property != 'opacity') { - prop.start = parseInt(dojo.html.getComputedStyle(args.nodes[0], prop.property)) - } else { - prop.start = dojo.html.getOpacity(args.nodes[0]) - } - } - }) - } - }; - var _563 = function (_564) { - var _565 = []; - dojo.lang.forEach(_564, function (c) { - _565.push(Math.round(c)) - }); - return _565 - }; - var _567 = function (n, _569) { - n = dojo.byId(n); - if (!n || !n.style) { - return - } - for (var s in _569) { - try { - if (s == 'opacity') { - dojo.html.setOpacity(n, _569[s]) - } else { - n.style[s] = _569[s] - } - } catch (e) { - dojo.debug(e) - } - } - }; - var _56b = function (_56c) { - this._properties = _56c; - this.diffs = new Array(_56c.length); - dojo.lang.forEach(_56c, function (prop, i) { - if (dojo.lang.isFunction(prop.start)) { - prop.start = prop.start(prop, i) - } - if (dojo.lang.isFunction(prop.end)) { - prop.end = prop.end(prop, i) - } - if (dojo.lang.isArray(prop.start)) { - this.diffs[i] = null - } else { - if (prop.start instanceof dojo.gfx.color.Color) { - prop.startRgb = prop.start.toRgb(); - prop.endRgb = prop.end.toRgb() - } else { - this.diffs[i] = prop.end - prop.start - } - } - }, this); - this.getValue = function (n) { - var ret = {}; - dojo.lang.forEach(this._properties, function (prop, i) { - var _573 = null; - if (dojo.lang.isArray(prop.start)) { - } else { - if (prop.start instanceof dojo.gfx.color.Color) { - _573 = (prop.units || 'rgb') + '('; - for (var j = 0; j < prop.startRgb.length; j++) { - _573 += Math.round(((prop.endRgb[j] - prop.startRgb[j]) * n) + prop.startRgb[j]) + (j - < prop.startRgb.length - - 1 ? ',' : '') - } - _573 += ')' - } else { - _573 = ((this.diffs[i]) * n) + prop.start + (prop.property != 'opacity' ? prop.units || 'px' : '') - } - } - ret[dojo.html.toCamelCase(prop.property)] = _573 - }, this); - return ret - } - }; - var anim = new dojo.lfx.Animation({ - beforeBegin: function () { - _55d(_55c); - anim.curve = new _56b(_55c.propertyMap) - }, onAnimate: function (_576) { - dojo.lang.forEach(_55c.nodes, function (node) { - _567(node, _576) - }) - } - }, _55c.duration, null, _55c.easing); - if (_55b) { - for (var x in _55b) { - if (dojo.lang.isFunction(_55b[x])) { - anim.connect(x, anim, _55b[x]) - } - } - } - return anim -}; -dojo.lfx.html._makeFadeable = function (_579) { - var _57a = function (node) { - if (dojo.render.html.ie) { - if ((node.style.zoom.length == 0) && (dojo.html.getStyle(node, 'zoom') == 'normal')) { - node.style.zoom = '1' - } - if ((node.style.width.length == 0) && (dojo.html.getStyle(node, 'width') == 'auto')) { - node.style.width = 'auto' - } - } - }; - if (dojo.lang.isArrayLike(_579)) { - dojo.lang.forEach(_579, _57a) - } else { - _57a(_579) - } -}; -dojo.lfx.html.fade = function (_57c, _57d, _57e, _57f, _580) { - _57c = dojo.lfx.html._byId(_57c); - var _581 = {property: 'opacity'}; - if (!dj_undef('start', _57d)) { - _581.start = _57d.start - } else { - _581.start = function () { - return dojo.html.getOpacity(_57c[0]) - } - } - if (!dj_undef('end', _57d)) { - _581.end = _57d.end - } else { - dojo.raise('dojo.lfx.html.fade needs an end value') - } - var anim = dojo.lfx.propertyAnimation(_57c, [_581], _57e, _57f); - anim.connect('beforeBegin', function () { - dojo.lfx.html._makeFadeable(_57c) - }); - if (_580) { - anim.connect('onEnd', function () { - _580(_57c, anim) - }) - } - return anim -}; -dojo.lfx.html.fadeIn = function (_583, _584, _585, _586) { - return dojo.lfx.html.fade(_583, {end: 1}, _584, _585, _586) -}; -dojo.lfx.html.fadeOut = function (_587, _588, _589, _58a) { - return dojo.lfx.html.fade(_587, {end: 0}, _588, _589, _58a) -}; -dojo.lfx.html.fadeShow = function (_58b, _58c, _58d, _58e) { - _58b = dojo.lfx.html._byId(_58b); - dojo.lang.forEach(_58b, function (node) { - dojo.html.setOpacity(node, 0) - }); - var anim = dojo.lfx.html.fadeIn(_58b, _58c, _58d, _58e); - anim.connect('beforeBegin', function () { - if (dojo.lang.isArrayLike(_58b)) { - dojo.lang.forEach(_58b, dojo.html.show) - } else { - dojo.html.show(_58b) - } - }); - return anim -}; -dojo.lfx.html.fadeHide = function (_591, _592, _593, _594) { - var anim = dojo.lfx.html.fadeOut(_591, _592, _593, function () { - if (dojo.lang.isArrayLike(_591)) { - dojo.lang.forEach(_591, dojo.html.hide) - } else { - dojo.html.hide(_591) - } - if (_594) { - _594(_591, anim) - } - }); - return anim -}; -dojo.lfx.html.wipeIn = function (_596, _597, _598, _599) { - _596 = dojo.lfx.html._byId(_596); - var _59a = []; - dojo.lang.forEach(_596, function (node) { - var _59c = {}; - var _59d, _59e, _59f; - with (node.style) { - _59d = top; - _59e = left; - _59f = position; - top = '-9999px'; - left = '-9999px'; - position = 'absolute'; - display = '' - } - var _5a0 = dojo.html.getBorderBox(node).height; - with (node.style) { - top = _59d; - left = _59e; - position = _59f; - display = 'none' - } - var anim = dojo.lfx.propertyAnimation(node, { - 'height': { - start: 1, end: function () { - return _5a0 - } - } - }, _597, _598); - anim.connect('beforeBegin', function () { - _59c.overflow = node.style.overflow; - _59c.height = node.style.height; - with (node.style) { - overflow = 'hidden'; - height = '1px' - } - dojo.html.show(node) - }); - anim.connect('onEnd', function () { - with (node.style) { - overflow = _59c.overflow; - height = _59c.height - } - if (_599) { - _599(node, anim) - } - }); - _59a.push(anim) - }); - return dojo.lfx.combine(_59a) -}; -dojo.lfx.html.wipeOut = function (_5a2, _5a3, _5a4, _5a5) { - _5a2 = dojo.lfx.html._byId(_5a2); - var _5a6 = []; - dojo.lang.forEach(_5a2, function (node) { - var _5a8 = {}; - var anim = dojo.lfx.propertyAnimation(node, { - 'height': { - start: function () { - return dojo.html.getContentBox(node).height - }, end: 1 - } - }, _5a3, _5a4, { - 'beforeBegin': function () { - _5a8.overflow = node.style.overflow; - _5a8.height = node.style.height; - with (node.style) { - overflow = 'hidden' - } - dojo.html.show(node) - }, 'onEnd': function () { - dojo.html.hide(node); - with (node.style) { - overflow = _5a8.overflow; - height = _5a8.height - } - if (_5a5) { - _5a5(node, anim) - } - } - }); - _5a6.push(anim) - }); - return dojo.lfx.combine(_5a6) -}; -dojo.lfx.html.slideTo = function (_5aa, _5ab, _5ac, _5ad, _5ae) { - _5aa = dojo.lfx.html._byId(_5aa); - var _5af = []; - var _5b0 = dojo.html.getComputedStyle; - if (dojo.lang.isArray(_5ab)) { - dojo.deprecated('dojo.lfx.html.slideTo(node, array)', 'use dojo.lfx.html.slideTo(node, {top: value, left: value});', '0.5'); - _5ab = {top: _5ab[0], left: _5ab[1]} - } - dojo.lang.forEach(_5aa, function (node) { - var top = null; - var left = null; - var init = (function () { - var _5b5 = node; - return function () { - var pos = _5b0(_5b5, 'position'); - top = (pos == 'absolute' ? node.offsetTop : parseInt(_5b0(node, 'top')) || 0); - left = (pos == 'absolute' ? node.offsetLeft : parseInt(_5b0(node, 'left')) || 0); - if (!dojo.lang.inArray(['absolute', 'relative'], pos)) { - var ret = dojo.html.abs(_5b5, true); - dojo.html.setStyleAttributes(_5b5, 'position:absolute;top:' + ret.y + 'px;left:' + ret.x + 'px;'); - top = ret.y; - left = ret.x - } - } - })(); - init(); - var anim = dojo.lfx.propertyAnimation(node, { - 'top': {start: top, end: (_5ab.top || 0)}, 'left': {start: left, end: (_5ab.left || 0)} - }, _5ac, _5ad, {'beforeBegin': init}); - if (_5ae) { - anim.connect('onEnd', function () { - _5ae(_5aa, anim) - }) - } - _5af.push(anim) - }); - return dojo.lfx.combine(_5af) -}; -dojo.lfx.html.slideBy = function (_5b9, _5ba, _5bb, _5bc, _5bd) { - _5b9 = dojo.lfx.html._byId(_5b9); - var _5be = []; - var _5bf = dojo.html.getComputedStyle; - if (dojo.lang.isArray(_5ba)) { - dojo.deprecated('dojo.lfx.html.slideBy(node, array)', 'use dojo.lfx.html.slideBy(node, {top: value, left: value});', '0.5'); - _5ba = {top: _5ba[0], left: _5ba[1]} - } - dojo.lang.forEach(_5b9, function (node) { - var top = null; - var left = null; - var init = (function () { - var _5c4 = node; - return function () { - var pos = _5bf(_5c4, 'position'); - top = (pos == 'absolute' ? node.offsetTop : parseInt(_5bf(node, 'top')) || 0); - left = (pos == 'absolute' ? node.offsetLeft : parseInt(_5bf(node, 'left')) || 0); - if (!dojo.lang.inArray(['absolute', 'relative'], pos)) { - var ret = dojo.html.abs(_5c4, true); - dojo.html.setStyleAttributes(_5c4, 'position:absolute;top:' + ret.y + 'px;left:' + ret.x + 'px;'); - top = ret.y; - left = ret.x - } - } - })(); - init(); - var anim = dojo.lfx.propertyAnimation(node, { - 'top': {start: top, end: top + (_5ba.top || 0)}, 'left': {start: left, end: left + (_5ba.left || 0)} - }, _5bb, _5bc).connect('beforeBegin', init); - if (_5bd) { - anim.connect('onEnd', function () { - _5bd(_5b9, anim) - }) - } - _5be.push(anim) - }); - return dojo.lfx.combine(_5be) -}; -dojo.lfx.html.explode = function (_5c8, _5c9, _5ca, _5cb, _5cc) { - var h = dojo.html; - _5c8 = dojo.byId(_5c8); - _5c9 = dojo.byId(_5c9); - var _5ce = h.toCoordinateObject(_5c8, true); - var _5cf = document.createElement('div'); - h.copyStyle(_5cf, _5c9); - if (_5c9.explodeClassName) { - _5cf.className = _5c9.explodeClassName - } - with (_5cf.style) { - position = 'absolute'; - display = 'none'; - var _5d0 = h.getStyle(_5c8, 'background-color'); - backgroundColor = _5d0 ? _5d0.toLowerCase() : 'transparent'; - backgroundColor = (backgroundColor == 'transparent') ? 'rgb(221, 221, 221)' : backgroundColor - } - dojo.body().appendChild(_5cf); - with (_5c9.style) { - visibility = 'hidden'; - display = 'block' - } - var _5d1 = h.toCoordinateObject(_5c9, true); - with (_5c9.style) { - display = 'none'; - visibility = 'visible' - } - var _5d2 = {opacity: {start: 0.5, end: 1}}; - dojo.lang.forEach(['height', 'width', 'top', 'left'], function (type) { - _5d2[type] = {start: _5ce[type], end: _5d1[type]} - }); - var anim = new dojo.lfx.propertyAnimation(_5cf, _5d2, _5ca, _5cb, { - 'beforeBegin': function () { - h.setDisplay(_5cf, 'block') - }, 'onEnd': function () { - h.setDisplay(_5c9, 'block'); - _5cf.parentNode.removeChild(_5cf) - } - }); - if (_5cc) { - anim.connect('onEnd', function () { - _5cc(_5c9, anim) - }) - } - return anim -}; -dojo.lfx.html.implode = function (_5d5, end, _5d7, _5d8, _5d9) { - var h = dojo.html; - _5d5 = dojo.byId(_5d5); - end = dojo.byId(end); - var _5db = dojo.html.toCoordinateObject(_5d5, true); - var _5dc = dojo.html.toCoordinateObject(end, true); - var _5dd = document.createElement('div'); - dojo.html.copyStyle(_5dd, _5d5); - if (_5d5.explodeClassName) { - _5dd.className = _5d5.explodeClassName - } - dojo.html.setOpacity(_5dd, 0.3); - with (_5dd.style) { - position = 'absolute'; - display = 'none'; - backgroundColor = h.getStyle(_5d5, 'background-color').toLowerCase() - } - dojo.body().appendChild(_5dd); - var _5de = {opacity: {start: 1, end: 0.5}}; - dojo.lang.forEach(['height', 'width', 'top', 'left'], function (type) { - _5de[type] = {start: _5db[type], end: _5dc[type]} - }); - var anim = new dojo.lfx.propertyAnimation(_5dd, _5de, _5d7, _5d8, { - 'beforeBegin': function () { - dojo.html.hide(_5d5); - dojo.html.show(_5dd) - }, 'onEnd': function () { - _5dd.parentNode.removeChild(_5dd) - } - }); - if (_5d9) { - anim.connect('onEnd', function () { - _5d9(_5d5, anim) - }) - } - return anim -}; -dojo.lfx.html.highlight = function (_5e1, _5e2, _5e3, _5e4, _5e5) { - _5e1 = dojo.lfx.html._byId(_5e1); - var _5e6 = []; - dojo.lang.forEach(_5e1, function (node) { - var _5e8 = dojo.html.getBackgroundColor(node); - var bg = dojo.html.getStyle(node, 'background-color').toLowerCase(); - var _5ea = dojo.html.getStyle(node, 'background-image'); - var _5eb = (bg == 'transparent' || bg == 'rgba(0, 0, 0, 0)'); - while (_5e8.length > 3) { - _5e8.pop() - } - var rgb = new dojo.gfx.color.Color(_5e2); - var _5ed = new dojo.gfx.color.Color(_5e8); - var anim = dojo.lfx.propertyAnimation(node, {'background-color': {start: rgb, end: _5ed}}, _5e3, _5e4, { - 'beforeBegin': function () { - if (_5ea) { - node.style.backgroundImage = 'none' - } - node.style.backgroundColor = 'rgb(' + rgb.toRgb().join(',') + ')' - }, 'onEnd': function () { - if (_5ea) { - node.style.backgroundImage = _5ea - } - if (_5eb) { - node.style.backgroundColor = 'transparent' - } - if (_5e5) { - _5e5(node, anim) - } - } - }); - _5e6.push(anim) - }); - return dojo.lfx.combine(_5e6) -}; -dojo.lfx.html.unhighlight = function (_5ef, _5f0, _5f1, _5f2, _5f3) { - _5ef = dojo.lfx.html._byId(_5ef); - var _5f4 = []; - dojo.lang.forEach(_5ef, function (node) { - var _5f6 = new dojo.gfx.color.Color(dojo.html.getBackgroundColor(node)); - var rgb = new dojo.gfx.color.Color(_5f0); - var _5f8 = dojo.html.getStyle(node, 'background-image'); - var anim = dojo.lfx.propertyAnimation(node, {'background-color': {start: _5f6, end: rgb}}, _5f1, _5f2, { - 'beforeBegin': function () { - if (_5f8) { - node.style.backgroundImage = 'none' - } - node.style.backgroundColor = 'rgb(' + _5f6.toRgb().join(',') + ')' - }, 'onEnd': function () { - if (_5f3) { - _5f3(node, anim) - } - } - }); - _5f4.push(anim) - }); - return dojo.lfx.combine(_5f4) -}; -dojo.lang.mixin(dojo.lfx, dojo.lfx.html); -dojo.kwCompoundRequire({browser: ['dojo.lfx.html'], dashboard: ['dojo.lfx.html']}); -dojo.provide('dojo.lfx.*'); - diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/accountException.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/accountException.jsp deleted file mode 100644 index 308c7052..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/accountException.jsp +++ /dev/null @@ -1,44 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> - - - - - accountException - - - - -
    - ${ message } -
    - -
    - - - - - - - - - - - - - -
    Account
    Password
     
    -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/businessException.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/businessException.jsp deleted file mode 100644 index 54abdded..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/businessException.jsp +++ /dev/null @@ -1,24 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> - - - - - businessException - - - - -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/dispatcher.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/dispatcher.jsp deleted file mode 100644 index 3372bdc7..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/dispatcher.jsp +++ /dev/null @@ -1,13 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> - - - - - ${ pageContext.request.requestURI } - - - -你正在访问 ${ pageContext.request.requestURI }?${ pageContext.request.queryString }. - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/exception.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/exception.jsp deleted file mode 100644 index 7e581362..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/exception.jsp +++ /dev/null @@ -1,24 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> - - - - - exception - - - - -
    - ${ message } 返回 -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/image.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/image.jsp deleted file mode 100644 index b1608a46..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/image.jsp +++ /dev/null @@ -1,19 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> - - - - - Insert title here - - - - -刷新 -直接访问 -
    - -request.getHeader("referer"): ${ header['referer'] } - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/index.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/index.jsp deleted file mode 100644 index ffc11dfa..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/index.jsp +++ /dev/null @@ -1,52 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> -<% - String path = request.getContextPath(); - String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; -%> - - - - - javaee-filter 首页 - - - -

    javaee-filter 首页

    -

    <%out.print("Server Ip:" + basePath);%>

    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testCacheFilter.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testCacheFilter.jsp deleted file mode 100644 index 77d55892..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testCacheFilter.jsp +++ /dev/null @@ -1,71 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> - - - - - Insert title here - - - - - - - - - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testCharacterEncodingFilter.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testCharacterEncodingFilter.jsp deleted file mode 100644 index f9da90a4..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testCharacterEncodingFilter.jsp +++ /dev/null @@ -1,28 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> - - - - - Insert title here - - - -
    -您输入了:
    -${ param.text }
    -
    -

    -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testExceptionHandlerFilter.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testExceptionHandlerFilter.jsp deleted file mode 100644 index ef1f3304..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testExceptionHandlerFilter.jsp +++ /dev/null @@ -1,29 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ page import="io.github.dunwu.javaee.filter.exception.AccountException" %> -<%@ page import="io.github.dunwu.javaee.filter.exception.BusinessException" %> -<% - String action = request.getParameter("action"); - - if ("businessException".equals(action)) { - throw new BusinessException("业务操作失败. "); - } else if ("accountException".equals(action)) { - throw new AccountException("您需要登陆后再进行此项操作. "); - } else if ("exception".equals(action)) { - Integer.parseInt(""); - } - -%> - - - - - exceptionHandler - - - -test BusinessException
    -test AccountException
    -test Exception
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testGZipFilter.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testGZipFilter.jsp deleted file mode 100644 index be254535..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testGZipFilter.jsp +++ /dev/null @@ -1,66 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> - -<%@page import="java.net.URL" %> -<%@page import="java.net.URLConnection" %> -<%@page import="java.text.NumberFormat" %> - - - - Insert title here - - - -<%! - public void test(JspWriter out, String url) throws Exception { - - // 模拟支持 GZIP 的浏览器 - URLConnection connGzip = new URL(url).openConnection(); - connGzip.setRequestProperty("Accept-Encoding", "gzip"); - int lengthGzip = connGzip.getContentLength(); - - // 模拟不支持 GZIP 的浏览器 - URLConnection connCommon = new URL(url).openConnection(); - int lengthCommon = connCommon.getContentLength(); - - double rate = new Double(lengthGzip) / lengthCommon; - - out.println(""); - out.println(" "); - out.println(" "); - out.println(" "); - out.println(" "); - out.println(" "); - out.println(" "); - out.println(" "); - out.println(" "); - out.println("
    网址: " + url + "
    压缩后:" + lengthGzip + " byte压缩前:" + lengthCommon + " byte比率:" + NumberFormat.getPercentInstance().format(rate) + "
    "); - } -%> -<% - String[] urls = {"http://localhost:9899/views/js/dojo.js", "http://localhost:9899/views/images/image.jsp", - "http://localhost:9899/views/images/winter.jpg", "http://www.baidu.com", "http://www.google.cn",}; - for (String url : urls) { - test(out, url); - } -%> - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testOutputReplaceFilter.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testOutputReplaceFilter.jsp deleted file mode 100644 index 7517a03a..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testOutputReplaceFilter.jsp +++ /dev/null @@ -1,19 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> - - - - - Insert title here - - - -Chna
    -
    -色情
    -赌博
    -情色
    -
    -www.baidu.com.cn
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testUploadFilter.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testUploadFilter.jsp deleted file mode 100644 index d6b602ce..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testUploadFilter.jsp +++ /dev/null @@ -1,43 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> - -<%@page import="java.io.File" %> - - - - Insert title here - - - - -header['Content-type'] = ${ header['Content-type'] } - -
    - - <%= request.getParameter("text1") %>
    - - <% - File file1 = (File) request.getAttribute("file1"); - if (file1 != null) { - out.println("
    文件: " + file1 + ",
    大小: " + file1.length()); - } - %>

    - - <%= request.getParameter("text2") %>
    - - <% - File file2 = (File) request.getAttribute("file2"); - if (file2 != null) { - out.println("
    文件: " + file2 + ",
    大小: " + file2.length()); - } - %>

    - - - -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testWaterMarkFilter.jsp b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testWaterMarkFilter.jsp deleted file mode 100644 index 99c5585c..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/jsp/testWaterMarkFilter.jsp +++ /dev/null @@ -1,16 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> - - - - - 图片过滤器示例 - - - - -刷新 -
    -${ header } - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/book.xml b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/book.xml deleted file mode 100644 index ab4bc9fc..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/book.xml +++ /dev/null @@ -1,6 +0,0 @@ - - Bruce - Java 编程思想 - 计算机经典书籍系列丛书 - $100.00 - diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/book.xsl b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/book.xsl deleted file mode 100644 index 409e24e3..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/book.xsl +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - 图书资料: - - - - - - - - - - - - - - - - - -
    作者: - -
    书名: - -
    类别: - -
    定价: - -
    - - -
    -
    diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/demo.xml b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/demo.xml deleted file mode 100644 index 82020867..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/demo.xml +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - 你会Ajax吗 - - - - - - - - - 我们用的是dojo框架的 - - - - - - - - - response.setHeader("Content-Disposition", - "attachment;filename=" - + URLEncoder.encode(newFile.getName(), - "UTF-8")); - response.setHeader("Connection", "close"); - response.setContentLength((int)newFile.length()); - resp - - - - - - - - - - 先 out.clear() - - - - - - - - - ok - - - - - - - - - - response.setContentLength((int)newFile.length()); - response.setHeader("Content-Type", - "application/octet-stream"); - - - - - - - - - - application/vnd.ms-excel - - diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/messageLog.xsl b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/messageLog.xsl deleted file mode 100644 index 715ce717772dd3b5862e1da44648c417f17f484d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9852 zcmeHN&2C#o6do2V(wE>i1UZ#(ofNedp|lZHg`yTA5)fhoj_o)R{)uciKaff+S+d~~ zcpx5vH$W_b@0;V(Gcz9B*GU?cfGpeBbLXD_^PMv@{`;?c?twdYUDt7iJ9I5Ka6`B6 z`mX2Rcgt?w-E?dCe&FuAYwjLq4l#2BpCfmIs}`>I@Y%xeVoaxpv5oOe(%8ejkvqUx z4X-2rx{1$sK)VlqZH%tFcm1p8zH(dc2}ZVYrw%EGZr9K1g3>GrDc?1>1R1ZpDqaPo z>$?VMbfC}okg$m{%00mE9=Hzin=|<&pHtlq@eJqG-Hz9=!0b9K)PhAS?n6-RxM%p? zb01@@4!X43H|`;>N4Tzl;_t0D+V`NvG@2L1a~pC}GV=Vh^~W!l!SfNc?Sm_M{%`W9e)$_YnMd5OZyBfi=YH z?8sUqp0}Z&#Q7GYS8|$mX4dsEQpFXce;-kQf*juR5jTW1J@+0m$mS6@m}eXAuDk8t z@^_gJ$DWGhVH;0v_^jH+D4!S18R1#U)hf6#V`b(DQXF6&HCnX4@oIYywk zizkl3t2xf)+4d=)?H_si)M5b5L;DvNgP$h?3p8(k+rZPr+db`+pAV4ZN8zTlgkwNDY02Z&uV% z>CBD!GxEByEu)>+1%C0Wfw*IBeF6#E_}1!2+p^wDJ#TvMtXdt64ZIAJNit8Wm((KW z7e|?iBg}7knlj4I7!8XtXUYG;I6sv}SoK&dTkuF9wrBlqdD;!|p!}O-I>ee!p&4r# z>wGQYZq<3;)1fVBRnk~NrsXc5(>JZF2F&kx~0 zb~4g^2*OfsL(Ujs=_%2NUY5?P3o6slb{+V$2E3IhFpn(-`WNm?P_o`Gmaz6h#?UI1 zeGkv1-Ux9nwj}RRV)jH9k)%VUY67og8&rHWvjb#DwgD}OAkxQaEE(n(nAL=TEAE3a zHLG2fgz~cc0dZmX4Ktp@PKD3b4kA%})A0P;p7x{+=Z^tW5JZagUoKhw3Utd>ZEnND z$B2epo=oFZ@%G49v*)mA>SNNe8meB7qb9FWDCxSpGeg$AP0k|eze$o?K&R>cyG)zB zI+EP=^*N5|IM&!_8LosF$BH$G&|Hz@e7LPt$T`hgVoSb9Bf8v25AYb+%M9YvF{Z|J z(>lkyxy+NN^piR-E>&nzl|pUAb;|5Yy;5T3n@1$kC~pbXByWdTOG{mAUu`XQb$_+B z~YK*HDU5|x(e|erq;K?;z0ZQR&W06%~xE={@rBW;Y*0Dz5D(?gFQb8aup-4or~Huth?&_*|Ekw2C?0FV~zVb zjJARA^fk)WlKO2HT~s$xu%~DFqfETyJ@Vg;mb~}kd=H~5MKkrU#IK=f*f|RJ>i(zQ z;kcr%j&e+Qfi!|c{wc!99Lj4klFmtYp3?eayyJe{D9ZkBCxdX|3CptYGrK?-?g_PpW=aqqx`1yE9h%V@?HrF3CT zvr@&i1QuHsNx8kUGzR3ic2A*{X;z&j^ByBF4&en>X71K9PfoGooE+u8G`-3h ztSP7N6I`>tpJLyYT^#!|^A@92r55|D?=LrZ#nT1i%T9AO6VtwJdbZ`LFn7vR2qVb$ zal_FNF()JN|NKx4;ZBHQ})MQOP_7~m2v&fpD5;5X=d+9 zPp)RyRqT*>%2D7vhMu*PDD6x_8PARuCfWQ-wld|U*3LunHBNDVQcCq3b(uUNNUQR+ z7@oB0v{O8_-K-93z8!Mw;8b%#B||A{NYo1Zj-1XSVJhDuUfRT}oR&_{K2wQu@`Sh= e##b4e=$bpW#5}H{4W{)o;T-D=?Z0A-qJIDcYpG8F diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/thinkInJava.xml b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/thinkInJava.xml deleted file mode 100644 index e3cb21c7..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/thinkInJava.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - Bruce - Java 编程思想 - 计算机经典书籍系列丛书 - $100.00 - diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/xml.xsl b/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/xml.xsl deleted file mode 100644 index 3dd7c355..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/main/webapp/views/xml/xml.xsl +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/FilterDemosBootstrap.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/FilterDemosBootstrap.java deleted file mode 100644 index fe36788e..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/FilterDemosBootstrap.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.github.dunwu.javaee.server; - -import org.eclipse.jetty.server.Server; - -/** - * 快速启动 jetty 服务器,方便测试 - * - * @author Zhang Peng - */ -public class FilterDemosBootstrap { - - // private static int STARTUP_TYPE = JettyFactory.IDE_ECLIPSE; - private static int STARTUP_TYPE = JettyFactory.IDE_INTELLIJ; - - public static void main(String[] args) throws Exception { - Server server = JettyFactory.initServer(); - JettyFactory.initWebAppContext(server, STARTUP_TYPE); - - try { - JettyFactory.startServer(server); - - // 等待用户输入回车重载应用 - while (true) { - char c = (char) System.in.read(); - if (c == '\n') { - JettyFactory.reloadWebAppContext(server); - } - } - } catch (Exception e) { - e.printStackTrace(); - System.exit(-1); - } - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java b/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java deleted file mode 100644 index 26b94cdb..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java +++ /dev/null @@ -1,131 +0,0 @@ -package io.github.dunwu.javaee.server; - -import java.util.ArrayList; -import java.util.List; -import org.apache.commons.lang3.StringUtils; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.webapp.WebAppClassLoader; -import org.eclipse.jetty.webapp.WebAppContext; - -/** - * JettyFactory 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 - * - * @author Zhang Peng - */ -@SuppressWarnings("all") -public class JettyFactory { - - public static final int IDE_ECLIPSE = 0; - - public static final int IDE_INTELLIJ = 1; - - public static final String ACTIVE_PROFILE = "spring.profiles.active"; - - public static final String DEFAULT_PROFILE = "spring.profiles.default"; - - public static final String DEVELOPMENT = "development"; - - private static final int PORT = 9527; - - private static final String CONTEXT = "/"; - - private static final String RESOURCE_BASE_PATH = "src/main/webapp"; - - private static final String WEB_XML_PATH = "/WEB-INF/web.xml"; - - private static final String[] TLD_JAR_NAMES = new String[] {"sitemesh", "spring-webmvc", "tiles"} - - private static final String WINDOWS_WEBDEFAULT_PATH = "jetty/webdefault.xml"; - - public static Server initServer() { - setProfileAsSystemProperty(DEVELOPMENT); - WebAppContext webAppContext = new WebAppContext(); - Server server = new Server(PORT); - server.setHandler(webAppContext); - return server; - } - - /** - * 在Spring启动前,设置profile的环境变量。 - */ - public static void setProfileAsSystemProperty(String profile) { - System.setProperty(ACTIVE_PROFILE, profile); - } - - public static void initWebAppContext(Server server, int type) throws Exception { - System.out.println("[INFO] Application loading"); - WebAppContext webAppContext = (WebAppContext) server.getHandler(); - webAppContext.setContextPath(CONTEXT); - webAppContext.setResourceBase(getAbsolutePath() + RESOURCE_BASE_PATH); - webAppContext.setDescriptor(getAbsolutePath() + RESOURCE_BASE_PATH + WEB_XML_PATH); - - if (IDE_INTELLIJ == type) { - webAppContext.setDefaultsDescriptor(WINDOWS_WEBDEFAULT_PATH); - supportJspAndSetTldJarNames(server, TLD_JAR_NAMES); - } else { - webAppContext.setParentLoaderPriority(true); - } - - System.out.println("[INFO] Application loaded"); - } - - public static String getAbsolutePath() { - String path = null; - String folderPath = JettyFactory.class.getProtectionDomain().getCodeSource().getLocation().getPath() - .substring(1); - if (folderPath.indexOf("target") > 0) { - path = folderPath.substring(0, folderPath.indexOf("target")); - } - return path; - } - - public static void supportJspAndSetTldJarNames(Server server, String... jarNames) { - WebAppContext context = (WebAppContext) server.getHandler(); - // This webapp will use jsps and jstl. We need to enable the - // AnnotationConfiguration in - // order to correctly set up the jsp container - org.eclipse.jetty.webapp.Configuration.ClassList classlist = org.eclipse.jetty.webapp.Configuration.ClassList - .setServerDefault(server); - classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", - "org.eclipse.jetty.annotations.AnnotationConfiguration"); - // Set the ContainerIncludeJarPattern so that jetty examines these container-path - // jars for - // tlds, web-fragments etc. - // If you omit the jar that contains the jstl .tlds, the jsp engine will scan for - // them - // instead. - - List list = new ArrayList<>(); - list.add(".*/[^/]*servlet-api-[^/]*\\.jar$"); - list.add(".*/javax.servlet.jsp.jstl-.*\\.jar$"); - list.add(".*/[^/]*taglibs.*\\.jar$"); - - for (String jarName : jarNames) { - String str = ".*/" + jarName + "-[^/]*\\.jar$"; - list.add(str); - } - - context.setAttribute("org.eclipse.jetty.io.github.dunwu.javaee.server.webapp.ContainerIncludeJarPattern", - StringUtils.join(list, '|')); - } - - public static void reloadWebAppContext(Server server) throws Exception { - WebAppContext webAppContext = (WebAppContext) server.getHandler(); - System.out.println("[INFO] Application reloading"); - webAppContext.stop(); - WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext); - classLoader.addClassPath(getAbsolutePath() + "target/classes"); - classLoader.addClassPath(getAbsolutePath() + "target/test-classes"); - webAppContext.setClassLoader(classLoader); - webAppContext.start(); - System.out.println("[INFO] Application reloaded"); - } - - public static void startServer(Server server) throws Exception { - System.out.println("[HINT] Don't forget to set -XX:MaxPermSize=128m"); - server.start(); - System.out.println("Server running at http://localhost:" + PORT + CONTEXT); - System.out.println("[HINT] Hit Enter to reload the application quickly"); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-filter/src/test/resources/jetty/webdefault.xml b/codes/javaee-tutorial/javaee-tutorial-filter/src/test/resources/jetty/webdefault.xml deleted file mode 100644 index b991d44c..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-filter/src/test/resources/jetty/webdefault.xml +++ /dev/null @@ -1,534 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - Default web.xml file. - This file is applied to a Web application before it's own WEB_INF/web.xml file - - - - - - - - org.eclipse.jetty.servlet.listener.ELContextCleaner - - - - - - - - org.eclipse.jetty.servlet.listener.IntrospectorCleaner - - - - - - - - - - - - - - - - - default - org.eclipse.jetty.servlet.DefaultServlet - - aliases - false - - - acceptRanges - true - - - dirAllowed - true - - - welcomeServlets - false - - - redirectWelcome - false - - - maxCacheSize - 256000000 - - - maxCachedFileSize - 200000000 - - - maxCachedFiles - 2048 - - - gzip - false - - - etags - false - - - useFileMappedBuffer - false - - - - 0 - - - - default - / - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - jsp - org.eclipse.jetty.jsp.JettyJspServlet - - logVerbosityLevel - DEBUG - - - fork - false - - - xpoweredBy - false - - - compilerTargetVM - 1.7 - - - compilerSourceVM - 1.7 - - - 0 - - - - jsp - *.jsp - *.jspf - *.jspx - *.xsp - *.JSP - *.JSPF - *.JSPX - *.XSP - - - - - - - - 30 - - - - - - - - - - - - - - - index.html - index.htm - index.jsp - - - - - - - - ar - ISO-8859-6 - - - be - ISO-8859-5 - - - bg - ISO-8859-5 - - - ca - ISO-8859-1 - - - cs - ISO-8859-2 - - - da - ISO-8859-1 - - - de - ISO-8859-1 - - - el - ISO-8859-7 - - - en - ISO-8859-1 - - - es - ISO-8859-1 - - - et - ISO-8859-1 - - - fi - ISO-8859-1 - - - fr - ISO-8859-1 - - - hr - ISO-8859-2 - - - hu - ISO-8859-2 - - - is - ISO-8859-1 - - - it - ISO-8859-1 - - - iw - ISO-8859-8 - - - ja - Shift_JIS - - - ko - EUC-KR - - - lt - ISO-8859-2 - - - lv - ISO-8859-2 - - - mk - ISO-8859-5 - - - nl - ISO-8859-1 - - - no - ISO-8859-1 - - - pl - ISO-8859-2 - - - pt - ISO-8859-1 - - - ro - ISO-8859-2 - - - ru - ISO-8859-5 - - - sh - ISO-8859-5 - - - sk - ISO-8859-2 - - - sl - ISO-8859-2 - - - sq - ISO-8859-2 - - - sr - ISO-8859-5 - - - sv - ISO-8859-1 - - - tr - ISO-8859-9 - - - uk - ISO-8859-5 - - - zh - GB2312 - - - zh_TW - Big5 - - - - - - - - - Disable TRACE - / - TRACE - - - - - - Enable everything but TRACE - / - TRACE - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/pom.xml b/codes/javaee-tutorial/javaee-tutorial-jsp/pom.xml deleted file mode 100644 index 4f3cd880..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/pom.xml +++ /dev/null @@ -1,97 +0,0 @@ - - - 4.0.0 - - io.github.dunwu - javaee-tutorial-jsp - 1.0.0 - war - javaee-tutorial-jsp - JavaEE 学习笔记之 JSP - - - UTF-8 - 1.7 - ${java.version} - ${java.version} - - - - - - ch.qos.logback - logback-classic - - - org.slf4j - jcl-over-slf4j - - - - - commons-fileupload - commons-fileupload - - - commons-io - commons-io - - - org.apache.commons - commons-lang3 - - - - - javax.servlet - javax.servlet-api - provided - - - org.eclipse.jetty - jetty-webapp - test - - - org.eclipse.jetty - jetty-server - test - - - org.eclipse.jetty - jetty-annotations - test - - - org.eclipse.jetty - apache-jsp - test - - - - - junit - junit - test - - - org.assertj - assertj-core - test - - - - - - - io.github.dunwu - dunwu-dependencies - 0.4.2-SNAPSHOT - pom - import - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/com/sun/products/applet/demo/Graph.java b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/com/sun/products/applet/demo/Graph.java deleted file mode 100644 index a38e3e10..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/com/sun/products/applet/demo/Graph.java +++ /dev/null @@ -1,467 +0,0 @@ -package com.sun.products.applet.demo; -/* - * @(#)Graph.java 1.9 99/08/04 - * - * Copyright (c) 1997, 1998 Sun Microsystems, Inc. All Rights Reserved. - * - * Sun grants you ("Licensee") a non-exclusive, royalty free, license to use, - * modify and redistribute this software in source and binary code form, - * provided that i) this copyright notice and license appear on all copies of - * the software; and ii) Licensee does not utilize the software in a manner - * which is disparaging to Sun. - * - * This software is provided "AS IS," without a warranty of any kind. ALL - * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY - * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR - * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE - * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING - * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS - * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, - * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER - * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF - * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGES. - * - * This software is not designed or intended for use in on-line control of - * aircraft, air traffic, aircraft navigation or aircraft communications; or in - * the design, construction, operation or maintenance of any nuclear - * facility. Licensee represents and warrants that it will not use or - * redistribute the Software for such purposes. - */ - -import java.applet.Applet; -import java.util.StringTokenizer; - -class Node { - - double x; - - double y; - - double dx; - - double dy; - - boolean fixed; - - String lbl; - -} - -class Edge { - - int from; - - int to; - - double len; - -} - -class GraphPanel extends Panel implements Runnable, MouseListener, MouseMotionListener { - - /** - * - */ - private static final long serialVersionUID = -6414075534397495418L; - - final Color fixedColor = Color.red; - - final Color selectColor = Color.pink; - - final Color edgeColor = Color.black; - - final Color nodeColor = new Color(250, 220, 100); - - final Color stressColor = Color.darkGray; - - final Color arcColor1 = Color.black; - - final Color arcColor2 = Color.pink; - - final Color arcColor3 = Color.red; - - Graph graph; - - int nnodes; - - Node nodes[] = new Node[100]; - - int nedges; - - Edge edges[] = new Edge[200]; - - Thread relaxer; - - boolean stress; - - boolean random; - - Node pick; - - boolean pickfixed; - - Image offscreen; - - Dimension offscreensize; - - Graphics offgraphics; - - GraphPanel(Graph graph) { - this.graph = graph; - addMouseListener(this); - } - - void addEdge(String from, String to, int len) { - Edge e = new Edge(); - e.from = findNode(from); - e.to = findNode(to); - e.len = len; - edges[nedges++] = e; - } - - int findNode(String lbl) { - for (int i = 0; i < nnodes; i++) { - if (nodes[i].lbl.equals(lbl)) { - return i; - } - } - return addNode(lbl); - } - - int addNode(String lbl) { - Node n = new Node(); - n.x = 10 + 380 * Math.random(); - n.y = 10 + 380 * Math.random(); - n.lbl = lbl; - nodes[nnodes] = n; - return nnodes++; - } - - public void run() { - Thread me = Thread.currentThread(); - while (relaxer == me) { - relax(); - if (random && (Math.random() < 0.03)) { - Node n = nodes[(int) (Math.random() * nnodes)]; - if (!n.fixed) { - n.x += 100 * Math.random() - 50; - n.y += 100 * Math.random() - 50; - } - graph.play(graph.getCodeBase(), "audio/drip.au"); - } - try { - Thread.sleep(100); - } catch (InterruptedException e) { - break; - } - } - } - - synchronized void relax() { - for (int i = 0; i < nedges; i++) { - Edge e = edges[i]; - double vx = nodes[e.to].x - nodes[e.from].x; - double vy = nodes[e.to].y - nodes[e.from].y; - double len = Math.sqrt(vx * vx + vy * vy); - len = (len == 0) ? .0001 : len; - double f = (edges[i].len - len) / (len * 3); - double dx = f * vx; - double dy = f * vy; - - nodes[e.to].dx += dx; - nodes[e.to].dy += dy; - nodes[e.from].dx += -dx; - nodes[e.from].dy += -dy; - } - - for (int i = 0; i < nnodes; i++) { - Node n1 = nodes[i]; - double dx = 0; - double dy = 0; - - for (int j = 0; j < nnodes; j++) { - if (i == j) { - continue; - } - Node n2 = nodes[j]; - double vx = n1.x - n2.x; - double vy = n1.y - n2.y; - double len = vx * vx + vy * vy; - if (len == 0) { - dx += Math.random(); - dy += Math.random(); - } else if (len < 100 * 100) { - dx += vx / len; - dy += vy / len; - } - } - double dlen = dx * dx + dy * dy; - if (dlen > 0) { - dlen = Math.sqrt(dlen) / 2; - n1.dx += dx / dlen; - n1.dy += dy / dlen; - } - } - - Dimension d = getSize(); - for (int i = 0; i < nnodes; i++) { - Node n = nodes[i]; - if (!n.fixed) { - n.x += Math.max(-5, Math.min(5, n.dx)); - n.y += Math.max(-5, Math.min(5, n.dy)); - } - if (n.x < 0) { - n.x = 0; - } else if (n.x > d.width) { - n.x = d.width; - } - if (n.y < 0) { - n.y = 0; - } else if (n.y > d.height) { - n.y = d.height; - } - n.dx /= 2; - n.dy /= 2; - } - repaint(); - } - - public synchronized void update(Graphics g) { - Dimension d = getSize(); - if ((offscreen == null) || (d.width != offscreensize.width) || (d.height != offscreensize.height)) { - offscreen = createImage(d.width, d.height); - offscreensize = d; - if (offgraphics != null) { - offgraphics.dispose(); - } - offgraphics = offscreen.getGraphics(); - offgraphics.setFont(getFont()); - } - - offgraphics.setColor(getBackground()); - offgraphics.fillRect(0, 0, d.width, d.height); - for (int i = 0; i < nedges; i++) { - Edge e = edges[i]; - int x1 = (int) nodes[e.from].x; - int y1 = (int) nodes[e.from].y; - int x2 = (int) nodes[e.to].x; - int y2 = (int) nodes[e.to].y; - int len = (int) Math.abs(Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)) - e.len); - offgraphics.setColor((len < 10) ? arcColor1 : (len < 20 ? arcColor2 : arcColor3)); - offgraphics.drawLine(x1, y1, x2, y2); - if (stress) { - String lbl = String.valueOf(len); - offgraphics.setColor(stressColor); - offgraphics.drawString(lbl, x1 + (x2 - x1) / 2, y1 + (y2 - y1) / 2); - offgraphics.setColor(edgeColor); - } - } - - FontMetrics fm = offgraphics.getFontMetrics(); - for (int i = 0; i < nnodes; i++) { - paintNode(offgraphics, nodes[i], fm); - } - g.drawImage(offscreen, 0, 0, null); - } - - public void paintNode(Graphics g, Node n, FontMetrics fm) { - int x = (int) n.x; - int y = (int) n.y; - g.setColor((n == pick) ? selectColor : (n.fixed ? fixedColor : nodeColor)); - int w = fm.stringWidth(n.lbl) + 10; - int h = fm.getHeight() + 4; - g.fillRect(x - w / 2, y - h / 2, w, h); - g.setColor(Color.black); - g.drawRect(x - w / 2, y - h / 2, w - 1, h - 1); - g.drawString(n.lbl, x - (w - 10) / 2, (y - (h - 4) / 2) + fm.getAscent()); - } - - // 1.1 event handling - public void mouseClicked(MouseEvent e) { - } - - public void mousePressed(MouseEvent e) { - addMouseMotionListener(this); - double bestdist = Double.MAX_VALUE; - int x = e.getX(); - int y = e.getY(); - for (int i = 0; i < nnodes; i++) { - Node n = nodes[i]; - double dist = (n.x - x) * (n.x - x) + (n.y - y) * (n.y - y); - if (dist < bestdist) { - pick = n; - bestdist = dist; - } - } - pickfixed = pick.fixed; - pick.fixed = true; - pick.x = x; - pick.y = y; - repaint(); - e.consume(); - } - - public void mouseReleased(MouseEvent e) { - removeMouseMotionListener(this); - if (pick != null) { - pick.x = e.getX(); - pick.y = e.getY(); - pick.fixed = pickfixed; - pick = null; - } - repaint(); - e.consume(); - } - - public void mouseEntered(MouseEvent e) { - } - - public void mouseExited(MouseEvent e) { - } - - public void mouseDragged(MouseEvent e) { - pick.x = e.getX(); - pick.y = e.getY(); - repaint(); - e.consume(); - } - - public void mouseMoved(MouseEvent e) { - } - - public void start() { - relaxer = new Thread(this); - relaxer.start(); - } - - public void stop() { - relaxer = null; - } - -} - -public class Graph extends Applet implements ActionListener, ItemListener { - - /** - * - */ - private static final long serialVersionUID = 9208137208697128121L; - - GraphPanel panel; - - Panel controlPanel; - - Button scramble = new Button("Scramble"); - - Button shake = new Button("Shake"); - - Checkbox stress = new Checkbox("Stress"); - - Checkbox random = new Checkbox("Random"); - - public void init() { - setLayout(new BorderLayout()); - - panel = new GraphPanel(this); - add("Center", panel); - controlPanel = new Panel(); - add("South", controlPanel); - - controlPanel.add(scramble); - scramble.addActionListener(this); - controlPanel.add(shake); - shake.addActionListener(this); - controlPanel.add(stress); - stress.addItemListener(this); - controlPanel.add(random); - random.addItemListener(this); - - String edges = getParameter("edges"); - for (StringTokenizer t = new StringTokenizer(edges, ","); t.hasMoreTokens(); ) { - String str = t.nextToken(); - int i = str.indexOf('-'); - if (i > 0) { - int len = 50; - int j = str.indexOf('/'); - if (j > 0) { - len = Integer.valueOf(str.substring(j + 1)).intValue(); - str = str.substring(0, j); - } - panel.addEdge(str.substring(0, i), str.substring(i + 1), len); - } - } - Dimension d = getSize(); - String center = getParameter("center"); - if (center != null) { - Node n = panel.nodes[panel.findNode(center)]; - n.x = d.width / 2; - n.y = d.height / 2; - n.fixed = true; - } - } - - public void destroy() { - remove(panel); - remove(controlPanel); - } - - public void start() { - panel.start(); - } - - public void stop() { - panel.stop(); - } - - public void actionPerformed(ActionEvent e) { - Object src = e.getSource(); - - if (src == scramble) { - play(getCodeBase(), "audio/computer.au"); - Dimension d = getSize(); - for (int i = 0; i < panel.nnodes; i++) { - Node n = panel.nodes[i]; - if (!n.fixed) { - n.x = 10 + (d.width - 20) * Math.random(); - n.y = 10 + (d.height - 20) * Math.random(); - } - } - return; - } - - if (src == shake) { - play(getCodeBase(), "audio/gong.au"); - // Dimension d = getSize(); - for (int i = 0; i < panel.nnodes; i++) { - Node n = panel.nodes[i]; - if (!n.fixed) { - n.x += 80 * Math.random() - 40; - n.y += 80 * Math.random() - 40; - } - } - } - } - - public void itemStateChanged(ItemEvent e) { - Object src = e.getSource(); - boolean on = e.getStateChange() == ItemEvent.SELECTED; - if (src == stress) { - panel.stress = on; - } else if (src == random) { - panel.random = on; - } - } - - public String getAppletInfo() { - return "Title: GraphLayout \nAuthor: "; - } - - public String[][] getParameterInfo() { - String[][] info = {{"edges", "delimited string", - "A comma-delimited list of all the edges. It takes the form of 'C-N1,C-N2,C-N3,C-NX,N1-N2/M12,N2-N3/M23,N3-NX/M3X,...' where C is the name of center node (see 'center' parameter) and NX is a node attached to the center node. For the edges connecting nodes to eachother (and not to the center node) you may (optionally) specify a length MXY separated from the edge name by a forward slash."}, - {"center", "string", "The name of the center node."}} - return info; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Counter.java b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Counter.java deleted file mode 100644 index 2b4108d5..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Counter.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.github.dunwu.javaee.jsp.bean; - -public class Counter { - - private int count; - - public int getCount() { - return ++count; - } - - public void setCount(int count) { - this.count = count; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Message.java b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Message.java deleted file mode 100644 index a15011f0..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Message.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.github.dunwu.javaee.jsp.bean; - -public class Message { - - private String content; - - public String getContent() { - return content; - } - - public void setContent(String content) { - this.content = content; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Person.java b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Person.java deleted file mode 100644 index a699495f..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/bean/Person.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.github.dunwu.javaee.jsp.bean; - -public class Person { - - private String name; - - private int age; - - private String sex; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public int getAge() { - return age; - } - - public void setAge(int age) { - this.age = age; - } - - public String getSex() { - return sex; - } - - public void setSex(String sex) { - this.sex = sex; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/IpUtil.java b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/IpUtil.java deleted file mode 100644 index 5b3c47f9..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/IpUtil.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.github.dunwu.javaee.jsp.util; - -import io.github.dunwu.javaee.jsp.util.ip.IPSeeker; - -public class IpUtil { - - public static String getIpAddress(String ip) { - try { - return IPSeeker.getInstance().getAddress(ip); - } catch (Exception e) { - e.printStackTrace(); - } - return "δ֪����"; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/IPEntry.java b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/IPEntry.java deleted file mode 100644 index d662262e..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/IPEntry.java +++ /dev/null @@ -1,51 +0,0 @@ -package io.github.dunwu.javaee.jsp.util.ip; - -/* - * LumaQQ - Java QQ Client - * - * Copyright (C) 2004 luma - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -/** - *
    - * һ��IP��Χ��¼�������������Һ�����Ҳ������ʼIP�ͽ���IP
    - * 
    - * - * @author ����� - */ -public class IPEntry { - - public String beginIp; - - public String endIp; - - public String country; - - public String area; - - /** - * ���캯�� - */ - public IPEntry() { - beginIp = endIp = country = area = ""; - } - - public String toString() { - return this.area + " " + this.country + " IP��Χ:" + this.beginIp + "-" + this.endIp; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/IPSeeker.java b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/IPSeeker.java deleted file mode 100644 index 06fee4d4..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/IPSeeker.java +++ /dev/null @@ -1,709 +0,0 @@ -package io.github.dunwu.javaee.jsp.util.ip; - -/* - * LumaQQ - Java QQ Client - * - * Copyright (C) 2004 luma - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; -import java.util.ArrayList; -import java.util.Hashtable; -import java.util.List; - -/** - *
    - * ������ȡQQwry.dat�ļ����Ը���ip��ú���λ�ã�QQwry.dat�ĸ�ʽ��
    - * һ. �ļ�ͷ����8�ֽ�
    - * 	   1. ��һ����ʼIP�ľ���ƫ�ƣ� 4�ֽ�
    - *     2. ���һ����ʼIP�ľ���ƫ�ƣ� 4�ֽ�
    - * ��. "������ַ/����/����"��¼��
    - *     ���ֽ�ip��ַ�����ÿһ����¼�ֳ���������
    - *     1. ���Ҽ�¼
    - *     2. ������¼
    - *     ���ǵ�����¼�Dz�һ���еġ����ҹ��Ҽ�¼�͵�����¼����������ʽ
    - *     1. ��0�������ַ���
    - *     2. 4���ֽڣ�һ���ֽڿ���Ϊ0x1��0x2
    - * 		  a. Ϊ0x1ʱ����ʾ�ھ���ƫ�ƺ󻹸���һ������ļ�¼��ע���Ǿ���ƫ��֮�󣬶��������ĸ��ֽ�֮��
    - *        b. Ϊ0x2ʱ����ʾ�ھ���ƫ�ƺ�û�������¼
    - *        ����Ϊ0x1����0x2���������ֽڶ���ʵ�ʹ��������ļ��ھ���ƫ��
    - * 		  ����ǵ�����¼��0x1��0x2�ĺ��岻����������������������ֽڣ�Ҳ�϶��Ǹ���3���ֽ�ƫ�ƣ��������
    - *        ��Ϊ0��β�ַ���
    - * ��. "��ʼ��ַ/������ַƫ��"��¼��
    - *     1. ÿ����¼7�ֽڣ�������ʼ��ַ��С��������
    - *        a. ��ʼIP��ַ��4�ֽ�
    - *        b. ����ip��ַ�ľ���ƫ�ƣ�3�ֽ�
    - *
    - * ע�⣬����ļ����ip��ַ�����е�ƫ����������little-endian��ʽ����java�Dz���
    - * big-endian��ʽ�ģ�Ҫע��ת��
    - * 
    - * - * @author ����� - */ -public class IPSeeker { - - private static final File IP_FILE = new File(IPSeeker.class.getResource("").getFile(), "QQWry.dat"); - - // һЩ�̶������������¼���ȵȵ� - private static final int IP_RECORD_LENGTH = 7; - - private static final byte AREA_FOLLOWED = 0x01; - - private static final byte NO_AREA = 0x2; - - // ��һģʽʵ�� - private static IPSeeker instance = new IPSeeker(); - - // ������Ϊcache����ѯһ��ipʱ���Ȳ鿴cache���Լ��ٲ���Ҫ���ظ����� - private Hashtable ipCache; - - // ����ļ������� - private RandomAccessFile ipFile; - - // �ڴ�ӳ���ļ� - private MappedByteBuffer mbb; - - // ��ʼ�����Ŀ�ʼ�ͽ����ľ���ƫ�� - private long ipBegin, ipEnd; - - // Ϊ���Ч�ʶ����õ���ʱ���� - private IPLocation loc; - - private byte[] buf; - - private byte[] b4; - - private byte[] b3; - - /** - * ˽�й��캯�� - */ - private IPSeeker() { - ipCache = new Hashtable(); - loc = new IPLocation(); - buf = new byte[100]; - b4 = new byte[4]; - b3 = new byte[3]; - try { - ipFile = new RandomAccessFile(IP_FILE, "r"); - } catch (FileNotFoundException e) { - System.out.println(IP_FILE.getAbsolutePath()); - System.out.println(IP_FILE); - System.out.println("IP��ַ��Ϣ�ļ�û���ҵ���IP��ʾ���ܽ��޷�ʹ��"); - ipFile = null; - } - // ������ļ��ɹ�����ȡ�ļ�ͷ��Ϣ - if (ipFile != null) { - try { - ipBegin = readLong4(0); - ipEnd = readLong4(4); - if (ipBegin == -1 || ipEnd == -1) { - ipFile.close(); - ipFile = null; - } - } catch (IOException e) { - System.out.println("IP��ַ��Ϣ�ļ���ʽ�д���IP��ʾ���ܽ��޷�ʹ��"); - ipFile = null; - } - } - } - - /** - * ��offsetλ�ö�ȡ4���ֽ�Ϊһ��long����ΪjavaΪbig-endian��ʽ������û�취 ������ôһ����������ת�� - * - * @param offset - * @return ��ȡ��longֵ������-1��ʾ��ȡ�ļ�ʧ�� - */ - private long readLong4(long offset) { - long ret = 0; - try { - ipFile.seek(offset); - ret |= (ipFile.readByte() & 0xFF); - ret |= ((ipFile.readByte() << 8) & 0xFF00); - ret |= ((ipFile.readByte() << 16) & 0xFF0000); - ret |= ((ipFile.readByte() << 24) & 0xFF000000); - return ret; - } catch (IOException e) { - return -1; - } - } - - /** - * @return ��һʵ�� - */ - public static IPSeeker getInstance() { - return instance; - } - - /** - * ����һ���ص�IJ���ȫ���֣��õ�һϵ�а���s�Ӵ���IP��Χ��¼ - * - * @param s �ص��Ӵ� - * @return ����IPEntry���͵�List - */ - public List getIPEntriesDebug(String s) { - List ret = new ArrayList(); - long endOffset = ipEnd + 4; - for (long offset = ipBegin + 4; offset <= endOffset; offset += IP_RECORD_LENGTH) { - // ��ȡ����IPƫ�� - long temp = readLong3(offset); - // ���temp������-1����ȡIP�ĵص���Ϣ - if (temp != -1) { - IPLocation loc = getIPLocation(temp); - // �ж��Ƿ�����ص����������s�Ӵ�����������ˣ���������¼��List�У����û�У����� - if (loc.country.indexOf(s) != -1 || loc.area.indexOf(s) != -1) { - IPEntry entry = new IPEntry(); - entry.country = loc.country; - entry.area = loc.area; - // �õ���ʼIP - readIP(offset - 4, b4); - entry.beginIp = Utils.getIpStringFromBytes(b4); - // �õ�����IP - readIP(temp, b4); - entry.endIp = Utils.getIpStringFromBytes(b4); - // ��Ӹü�¼ - ret.add(entry); - } - } - } - return ret; - } - - /** - * ��offsetλ�ö�ȡ3���ֽ�Ϊһ��long����ΪjavaΪbig-endian��ʽ������û�취 ������ôһ����������ת�� - * - * @param offset - * @return ��ȡ��longֵ������-1��ʾ��ȡ�ļ�ʧ�� - */ - private long readLong3(long offset) { - long ret = 0; - try { - ipFile.seek(offset); - ipFile.readFully(b3); - ret |= (b3[0] & 0xFF); - ret |= ((b3[1] << 8) & 0xFF00); - ret |= ((b3[2] << 16) & 0xFF0000); - return ret; - } catch (IOException e) { - return -1; - } - } - - /** - * ����һ��ip���ҵ�����¼��ƫ�ƣ�����һ��IPLocation�ṹ - * - * @param offset - * @return - */ - private IPLocation getIPLocation(long offset) { - try { - // ����4�ֽ�ip - ipFile.seek(offset + 4); - // ��ȡ��һ���ֽ��ж��Ƿ��־�ֽ� - byte b = ipFile.readByte(); - if (b == AREA_FOLLOWED) { - // ��ȡ����ƫ�� - long countryOffset = readLong3(); - // ��ת��ƫ�ƴ� - ipFile.seek(countryOffset); - // �ټ��һ�α�־�ֽڣ���Ϊ���ʱ������ط���Ȼ�����Ǹ��ض��� - b = ipFile.readByte(); - if (b == NO_AREA) { - loc.country = readString(readLong3()); - ipFile.seek(countryOffset + 4); - } else { - loc.country = readString(countryOffset); - } - // ��ȡ������־ - loc.area = readArea(ipFile.getFilePointer()); - } else if (b == NO_AREA) { - loc.country = readString(readLong3()); - loc.area = readArea(offset + 8); - } else { - loc.country = readString(ipFile.getFilePointer() - 1); - loc.area = readArea(ipFile.getFilePointer()); - } - return loc; - } catch (IOException e) { - return null; - } - } - - /** - * ��offsetλ�ö�ȡ�ĸ��ֽڵ�ip��ַ����ip�����У���ȡ���ipΪbig-endian��ʽ������ �ļ�����little-endian��ʽ���������ת�� - * - * @param offset - * @param ip - */ - private void readIP(long offset, byte[] ip) { - try { - ipFile.seek(offset); - ipFile.readFully(ip); - byte temp = ip[0]; - ip[0] = ip[3]; - ip[3] = temp; - temp = ip[1]; - ip[1] = ip[2]; - ip[2] = temp; - } catch (IOException e) { - System.out.println(e.getMessage()); - } - } - - /** - * �ӵ�ǰλ�ö�ȡ3���ֽ�ת����long - * - * @return - */ - private long readLong3() { - long ret = 0; - try { - ipFile.readFully(b3); - ret |= (b3[0] & 0xFF); - ret |= ((b3[1] << 8) & 0xFF00); - ret |= ((b3[2] << 16) & 0xFF0000); - return ret; - } catch (IOException e) { - return -1; - } - } - - /** - * ��offsetƫ�ƴ���ȡһ����0�������ַ��� - * - * @param offset - * @return ��ȡ���ַ����������ؿ��ַ��� - */ - private String readString(long offset) { - try { - ipFile.seek(offset); - int i; - for (i = 0, buf[i] = ipFile.readByte(); buf[i] != 0; buf[++i] = ipFile.readByte()) - ; - if (i != 0) { - return Utils.getString(buf, 0, i, "GBK"); - } - } catch (IOException e) { - System.out.println(e.getMessage()); - } - return ""; - } - - /** - * ��offsetƫ�ƿ�ʼ����������ֽڣ�����һ�������� - * - * @param offset - * @return �������ַ��� - * @throws IOException - */ - private String readArea(long offset) throws IOException { - ipFile.seek(offset); - byte b = ipFile.readByte(); - if (b == 0x01 || b == 0x02) { - long areaOffset = readLong3(offset + 1); - if (areaOffset == 0) { - return "δ֪����"; - } else { - return readString(areaOffset); - } - } else { - return readString(offset); - } - } - - /** - * ����һ���ص�IJ���ȫ���֣��õ�һϵ�а���s�Ӵ���IP��Χ��¼ - * - * @param s �ص��Ӵ� - * @return ����IPEntry���͵�List - */ - public List getIPEntries(String s) { - List ret = new ArrayList(); - try { - // ӳ��IP��Ϣ�ļ����ڴ��� - if (mbb == null) { - FileChannel fc = ipFile.getChannel(); - mbb = fc.map(FileChannel.MapMode.READ_ONLY, 0, ipFile.length()); - mbb.order(ByteOrder.LITTLE_ENDIAN); - } - - int endOffset = (int) ipEnd; - for (int offset = (int) ipBegin + 4; offset <= endOffset; offset += IP_RECORD_LENGTH) { - int temp = readInt3(offset); - if (temp != -1) { - IPLocation loc = getIPLocation(temp); - // �ж��Ƿ�����ص����������s�Ӵ�����������ˣ���������¼��List�У����û�У����� - if (loc.country.indexOf(s) != -1 || loc.area.indexOf(s) != -1) { - IPEntry entry = new IPEntry(); - entry.country = loc.country; - entry.area = loc.area; - // �õ���ʼIP - readIP(offset - 4, b4); - entry.beginIp = Utils.getIpStringFromBytes(b4); - // �õ�����IP - readIP(temp, b4); - entry.endIp = Utils.getIpStringFromBytes(b4); - // ��Ӹü�¼ - ret.add(entry); - } - } - } - } catch (IOException e) { - System.out.println(e.getMessage()); - } - return ret; - } - - /** - * ���ڴ�ӳ���ļ���offsetλ�ÿ�ʼ��3���ֽڶ�ȡһ��int - * - * @param offset - * @return - */ - private int readInt3(int offset) { - mbb.position(offset); - return mbb.getInt() & 0x00FFFFFF; - } - - /** - * @param offset - * @return - */ - private IPLocation getIPLocation(int offset) { - // ����4�ֽ�ip - mbb.position(offset + 4); - // ��ȡ��һ���ֽ��ж��Ƿ��־�ֽ� - byte b = mbb.get(); - if (b == AREA_FOLLOWED) { - // ��ȡ����ƫ�� - int countryOffset = readInt3(); - // ��ת��ƫ�ƴ� - mbb.position(countryOffset); - // �ټ��һ�α�־�ֽڣ���Ϊ���ʱ������ط���Ȼ�����Ǹ��ض��� - b = mbb.get(); - if (b == NO_AREA) { - loc.country = readString(readInt3()); - mbb.position(countryOffset + 4); - } else { - loc.country = readString(countryOffset); - } - // ��ȡ������־ - loc.area = readArea(mbb.position()); - } else if (b == NO_AREA) { - loc.country = readString(readInt3()); - loc.area = readArea(offset + 8); - } else { - loc.country = readString(mbb.position() - 1); - loc.area = readArea(mbb.position()); - } - return loc; - } - - /** - * ��offsetλ�ö�ȡ�ĸ��ֽڵ�ip��ַ����ip�����У���ȡ���ipΪbig-endian��ʽ������ �ļ�����little-endian��ʽ���������ת�� - * - * @param offset - * @param ip - */ - private void readIP(int offset, byte[] ip) { - mbb.position(offset); - mbb.get(ip); - byte temp = ip[0]; - ip[0] = ip[3]; - ip[3] = temp; - temp = ip[1]; - ip[1] = ip[2]; - ip[2] = temp; - } - - /** - * ���ڴ�ӳ���ļ��ĵ�ǰλ�ÿ�ʼ��3���ֽڶ�ȡһ��int - * - * @return - */ - private int readInt3() { - return mbb.getInt() & 0x00FFFFFF; - } - - /** - * ���ڴ�ӳ���ļ���offsetλ�õõ�һ��0��β�ַ��� - * - * @param offset - * @return - */ - private String readString(int offset) { - try { - mbb.position(offset); - int i; - for (i = 0, buf[i] = mbb.get(); buf[i] != 0; buf[++i] = mbb.get()) - ; - if (i != 0) { - return Utils.getString(buf, 0, i, "GBK"); - } - } catch (IllegalArgumentException e) { - System.out.println(e.getMessage()); - } - return ""; - } - - /** - * @param offset - * @return - */ - private String readArea(int offset) { - mbb.position(offset); - byte b = mbb.get(); - if (b == 0x01 || b == 0x02) { - int areaOffset = readInt3(); - if (areaOffset == 0) { - return "δ֪����"; - } else { - return readString(areaOffset); - } - } else { - return readString(offset); - } - } - - /** - * ����IP�õ������� - * - * @param ip ip���ֽ�������ʽ - * @return �������ַ��� - */ - public String getCountry(byte[] ip) { - // ���ip��ַ�ļ��Ƿ����� - if (ipFile == null) { - return "�����IP���ݿ��ļ�"; - } - // ����ip��ת��ip�ֽ�����Ϊ�ַ�����ʽ - String ipStr = Utils.getIpStringFromBytes(ip); - // �ȼ��cache���Ƿ��Ѿ����������ip�Ľ����û���������ļ� - if (ipCache.containsKey(ipStr)) { - IPLocation loc = (IPLocation) ipCache.get(ipStr); - return loc.country; - } else { - IPLocation loc = getIPLocation(ip); - ipCache.put(ipStr, loc.getCopy()); - return loc.country; - } - } - - /** - * ����ip����ip��Ϣ�ļ����õ�IPLocation�ṹ����������ip���������Աip�еõ� - * - * @param ip Ҫ��ѯ��IP - * @return IPLocation�ṹ - */ - private IPLocation getIPLocation(byte[] ip) { - IPLocation info = null; - long offset = locateIP(ip); - if (offset != -1) { - info = getIPLocation(offset); - } - if (info == null) { - info = new IPLocation(); - info.country = "δ֪����"; - info.area = "δ֪����"; - } - return info; - } - - /** - * �������������ip�����ݣ���λ���������ip���ҵ����ļ�¼��������һ������ƫ�� ����ʹ�ö��ַ����ҡ� - * - * @param ip Ҫ��ѯ��IP - * @return ����ҵ��ˣ����ؽ���IP��ƫ�ƣ����û���ҵ�������-1 - */ - private long locateIP(byte[] ip) { - long m = 0; - int r; - // �Ƚϵ�һ��ip�� - readIP(ipBegin, b4); - r = compareIP(ip, b4); - if (r == 0) { - return ipBegin; - } else if (r < 0) { - return -1; - } - // ��ʼ�������� - for (long i = ipBegin, j = ipEnd; i < j; ) { - m = getMiddleOffset(i, j); - readIP(m, b4); - r = compareIP(ip, b4); - // log.debug(Utils.getIpStringFromBytes(b)); - if (r > 0) { - i = m; - } else if (r < 0) { - if (m == j) { - j -= IP_RECORD_LENGTH; - m = j; - } else { - j = m; - } - } else { - return readLong3(m + 4); - } - } - // ���ѭ�������ˣ���ôi��j�ض�����ȵģ������¼Ϊ����ܵļ�¼�����Dz��� - // �϶����ǣ���Ҫ���һ�£�����ǣ��ͷ��ؽ�����ַ���ľ���ƫ�� - m = readLong3(m + 4); - readIP(m, b4); - r = compareIP(ip, b4); - if (r <= 0) { - return m; - } else { - return -1; - } - } - - /** - * �����Աip��beginIp�Ƚϣ�ע�����beginIp��big-endian�� - * - * @param ip Ҫ��ѯ��IP - * @param beginIp �ͱ���ѯIP��Ƚϵ�IP - * @return ��ȷ���0��ip����beginIp�򷵻�1��С�ڷ���-1�� - */ - private int compareIP(byte[] ip, byte[] beginIp) { - for (int i = 0; i < 4; i++) { - int r = compareByte(ip[i], beginIp[i]); - if (r != 0) { - return r; - } - } - return 0; - } - - /** - * �õ�beginƫ�ƺ�endƫ���м�λ�ü�¼��ƫ�� - * - * @param begin - * @param end - * @return - */ - private long getMiddleOffset(long begin, long end) { - long records = (end - begin) / IP_RECORD_LENGTH; - records >>= 1; - if (records == 0) { - records = 1; - } - return begin + records * IP_RECORD_LENGTH; - } - - /** - * ������byte�����޷��������бȽ� - * - * @param b1 - * @param b2 - * @return ��b1����b2�򷵻�1����ȷ���0��С�ڷ���-1 - */ - private int compareByte(byte b1, byte b2) { - if ((b1 & 0xFF) > (b2 & 0xFF)) // �Ƚ��Ƿ���� - { - return 1; - } else if ((b1 ^ b2) == 0)// �ж��Ƿ���� - { - return 0; - } else { - return -1; - } - } - - /** - * ����IP�õ������� - * - * @param ip ip���ֽ�������ʽ - * @return �������ַ��� - */ - public String getArea(byte[] ip) { - // ���ip��ַ�ļ��Ƿ����� - if (ipFile == null) { - return "�����IP���ݿ��ļ�"; - } - // ����ip��ת��ip�ֽ�����Ϊ�ַ�����ʽ - String ipStr = Utils.getIpStringFromBytes(ip); - // �ȼ��cache���Ƿ��Ѿ����������ip�Ľ����û���������ļ� - if (ipCache.containsKey(ipStr)) { - IPLocation loc = (IPLocation) ipCache.get(ipStr); - return loc.area; - } else { - IPLocation loc = getIPLocation(ip); - ipCache.put(ipStr, loc.getCopy()); - return loc.area; - } - } - - public String getAddress(String ip) { - String country = getCountry(ip).equals(" CZ88.NET") ? "" : getCountry(ip); - String area = getArea(ip).equals(" CZ88.NET") ? "" : getArea(ip); - String address = country + " " + area; - return address.trim(); - } - - /** - * ����IP�õ������� - * - * @param ip IP���ַ�����ʽ - * @return �������ַ��� - */ - public String getCountry(String ip) { - return getCountry(Utils.getIpByteArrayFromString(ip)); - } - - /** - * ����IP�õ������� - * - * @param ip IP���ַ�����ʽ - * @return �������ַ��� - */ - public String getArea(String ip) { - return getArea(Utils.getIpByteArrayFromString(ip)); - } - - /** - *
    -	 * ������װip�����Ϣ��Ŀǰֻ�������ֶΣ�ip���ڵĹ��Һ͵���
    -	 * 
    - * - * @author ����� - */ - private class IPLocation { - - public String country; - - public String area; - - public IPLocation() { - country = area = ""; - } - - public IPLocation getCopy() { - IPLocation ret = new IPLocation(); - ret.country = country; - ret.area = area; - return ret; - } - - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/QQWry.dat b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/java/io/github/dunwu/javaee/jsp/util/ip/QQWry.dat deleted file mode 100644 index a77baf0a25b8f6ad8275ead56d89e1786c94ead6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 824560 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

    )H5(~YrcC`EdC3s+wlp3Q$+q#s<6ARb4a!c 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 %9i{~Q9i|dq?=Vg91_i=#3WSp#rUQQ2 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 - - - - - - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n - - - - - - - - logs/${FILE_NAME}-all.%d{yyyy-MM-dd}.log - 30 - - - - - 30MB - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n - - - - - - - - - - - - - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/META-INF/MANIFEST.MF b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/META-INF/MANIFEST.MF deleted file mode 100644 index 254272e1..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Class-Path: - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/WEB-INF/META-INF/MANIFEST.MF b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/WEB-INF/META-INF/MANIFEST.MF deleted file mode 100644 index 254272e1..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/WEB-INF/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Class-Path: - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/WEB-INF/resources/jsp/index.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/WEB-INF/resources/jsp/index.jsp deleted file mode 100644 index 473a1315..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/WEB-INF/resources/jsp/index.jsp +++ /dev/null @@ -1,16 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> - - - - - My JSP 'index.jsp' starting page - - - - - - - -

    This is my JSP page.

    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/WEB-INF/web.xml b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index a403cc53..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - HelloServlet - /examples/configuration.jsp - - message - welcome to jsp - - 1 - - - HelloServlet - /config - /config.jsp - - - - - /WEB-INF/views/jsp/index.jsp - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/break.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/break.jsp deleted file mode 100644 index 0c39bb74..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/break.jsp +++ /dev/null @@ -1,26 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=utf-8" %> - -JSP Scriptlets - - -<% - for (int i = 0; i < 5; i++) { -%> break 所在的循环, i = <%= i %>。
    -<% - if (i == 2) { - break; - } - } -%> break 循环完毕,
    -<% - for (int i = 0; i < 5; i++) { -%> return 所在的循环, i = <%= i %>。
    -<% - if (i == 2) { - return; - } - } -%> return 循环完毕,
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/for.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/for.jsp deleted file mode 100644 index 7f172314..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/for.jsp +++ /dev/null @@ -1,54 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=utf-8" %> - - - 02.JSP语法 - for示例 - - - -
    -<% - Object[][] letters = {{true, "恭喜您注册的信息已经生效", "e_inn@163.com", "forbreak@163.com", "2007-8-8"}, - {true, "JavaEE 7.0 release!", "admin@sun.com", "forbreak@163.com", "2007-6-24"}, - {false, "来信已经收到,下周来面谈", "foo@bar.com", "forbreak@163.com", "2007-5-20"}, - {false, "您有新的邮件", "blog@foo.bar.com", "forbreak@163.com", "2007-3-2"},}; - String[] colors = {"#DDDDDD", "#AAAAAA",}; -%> - - - - - - - - - <% - for (int i = 0; i < letters.length; i++) { - Object[] letter = letters[i]; - %> - - - - - - - - <% - } - %> -
     标题 发信人 收信人 时间 
    - <% - if (letter[0] == Boolean.TRUE) { - %> - - <% - } else { - out.println(" "); - } - %> - <%= letter[1] %> - <%= letter[2] %> - <%= letter[3] %> - <%= letter[4] %> -
    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/if.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/if.jsp deleted file mode 100644 index 5d5d19ff..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/if.jsp +++ /dev/null @@ -1,68 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> - - -02.JSP语法 - if...else示例 - - -<% - String param = request.getParameter("param"); - if ("1".equals(param)) { -%> -《破阵子·为孙同甫赋壮语以寄》
    -【宋】辛弃疾
    -醉里挑灯看剑,梦回吹角连营。
    -八百里分麾下炙,五十弦翻塞外声,沙场秋点兵。
    -马作的卢飞快,弓如霹雳弦惊。
    -了却君王天下事,赢得生前身后名。可怜白发生!
    -<% -} else if ("2".equals(param)) { -%> -《青玉案·元夕》
    -【宋】辛弃疾
    -东风夜放花千树,更吹落,星如雨。
    -宝马雕车香满路,凤箫声动,玉壶光转,一夜鱼龙舞。
    -蛾儿雪柳黄金缕,笑语盈盈暗香去。
    -众里寻他千百度,蓦然回首,那人却在,灯火阑珊处。
    -<% -} else if ("3".equals(param)) { -%> -《丑奴儿》
    -【宋】辛弃疾
    -少年不识愁滋味,爱上层楼,爱上层楼,为赋新词强说愁。
    -而今识得愁滋味,欲说还休,欲说还休,却道天凉好个秋。
    -<% -} else if ("4".equals(param)) { -%> -《永遇乐》
    -【宋】辛弃疾
    -千古江山,英雄无觅,孙仲谋处。
    -舞榭歌台,风流总被、雨打风吹去。
    -斜阳草树,寻常巷陌,人道寄奴曾住。
    -想当年,金戈铁马,气吞万里如虎。
    -元嘉草草,封狼居胥,赢得仓皇北顾。
    -四十三年,望中犹记,烽火扬州路。
    -可堪回首,佛狸祠下,一片神鸦社鼓。
    -凭谁问:廉颇老矣,尚能饭否?
    -<% -} else if ("5".equals(param)) { -%> -《南乡子》
    -【宋】辛弃疾
    -何处望神州,满眼风光北固楼。
    -千古兴亡多少事,悠悠,不尽长江滚滚流。
    -年少万兜鍪,坐断东南战未休。
    -天下英雄谁敌手?曹刘,生子当如孙仲谋。
    -<% -} else { -%> -请使用参数 param=1,2,3,4,5 选择要显示的诗歌
    -
    if.jsp?param=1
    -if.jsp?param=2
    -if.jsp?param=3
    -if.jsp?param=4
    -if.jsp?param=5
    -<% - } -%> - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/if2.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/if2.jsp deleted file mode 100644 index ac691dfe..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/if2.jsp +++ /dev/null @@ -1,17 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> -<%! int day = 1; %> - - - - - 02.JSP语法 - if...else示例 - - -

    if...else示例

    -<% if (day == 1 | day == 7) { %> -

    今天是周末

    -<% } else { %> -

    今天不是周末

    -<% } %> - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/scriptlet.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/scriptlet.jsp deleted file mode 100644 index 38c13ab0..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/scriptlet.jsp +++ /dev/null @@ -1,16 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=utf-8" %> - -JSP Scriptlets - - -<% - int num = 10; - int result = 1; - for (int i = 1; i <= num; i++) { - result *= i; - } - out.println("
    "); - out.println("数字 " + num + " 的阶乘为:" + result); -%> - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/switch.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/switch.jsp deleted file mode 100644 index 435c36b9..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/switch.jsp +++ /dev/null @@ -1,37 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%! int day = 3; %> - - - - - 02.JSP语法 - switch...case示例 - - -

    Sswitch...case示例

    -<% - switch (day) { - case 0: - out.println("星期天"); - break; - case 1: - out.println("星期一"); - break; - case 2: - out.println("星期二"); - break; - case 3: - out.println("星期三"); - break; - case 4: - out.println("星期四"); - break; - case 5: - out.println("星期五"); - break; - default: - out.println("星期六"); - } -%> - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/while.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/while.jsp deleted file mode 100644 index c35802eb..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/02.grammar/while.jsp +++ /dev/null @@ -1,23 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=utf-8" %> - - - JSP Scriptlets - - -<% - java.util.List list = new java.util.ArrayList(); - - list.add("青青子衿"); - list.add("悠悠我心"); - list.add("但为君故"); - list.add("沉吟至今"); - - java.util.Iterator it = list.iterator(); - - while (it.hasNext()) { -%> <%= it.next() %>
    -<% - } -%> - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/foot.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/foot.jsp deleted file mode 100644 index 62f768cd..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/foot.jsp +++ /dev/null @@ -1,13 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=utf-8" %> - - - - - - -
    - Copyright 2017 &Zhang Peng -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/head.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/head.jsp deleted file mode 100644 index e356ddf7..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/head.jsp +++ /dev/null @@ -1,20 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=utf-8" %> - - - JSP 示例 - - - - - - - - - - - - - -
    JSP 示例 -
    首页资源文档
    diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/include.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/include.jsp deleted file mode 100644 index 677945d6..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/include.jsp +++ /dev/null @@ -1,18 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=utf-8" %> - -<%@ include file="head.jsp" %> - -

    念奴娇·赤壁怀古

    -

    宋·苏轼

    -

    -

    大江东去,浪淘尽,千古风流人物。 -

    故垒西边,人道是、三国周郎赤壁。 -

    乱石穿空,惊涛拍岸,卷起千堆雪。 -

    江山如画,一时多少豪杰。 -

    遥想公瑾当年,小乔初嫁了,雄姿英发。 -

    羽扇纶巾,谈笑间、强虏灰飞烟灭。 -

    故国神游,多情应笑我,早生华发。 -

    人生如梦,一樽还酹江月。 -

    - - <%@ include file="foot.jsp" %> diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/page.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/page.jsp deleted file mode 100644 index 4eeebb40..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/03.directive/page.jsp +++ /dev/null @@ -1,17 +0,0 @@ -<%@ page language="java" %> -<%@ page contentType="text/html; charset=UTF-8" %> -<%@ page pageEncoding="UTF-8" %> -<%@ page trimDirectiveWhitespaces="false" %> -<%@ page buffer="10kb" %> -<%@ page info="false" %> - - - - 第一个Jsp程序 - - -<% - out.println("你好!"); -%> - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/date.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/date.jsp deleted file mode 100644 index 824dce57..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/date.jsp +++ /dev/null @@ -1,4 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> -

    - 今天的日期是: <%= (new java.util.Date())%> -

    diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspforward.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspforward.jsp deleted file mode 100644 index 968220e6..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspforward.jsp +++ /dev/null @@ -1,23 +0,0 @@ - - -<% - out.clear(); - if ("1".equals(request.getParameter("param"))) { -%> - - - - -<% - } -%> - - - 闀ㄤ讲鍓ラ弪镡煎禌?/title> - <link rel='stylesheet' type='text/css' href='css/style.css'> - </head> -<body> -i闀ㄥ洷娓规繛锲噭閵娿储鍕鹃柛褉锅挞梹妞ょ箰瀵剟寮?param=1 闀ㄤ礁娼″Λ鍓佹嫚閵夆敛钪妫婚ⅳ顑藉亾? -</body> -</html> - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspinclude.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspinclude.jsp deleted file mode 100644 index 765997b9..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspinclude.jsp +++ /dev/null @@ -1,14 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> -<!DOCTYPE html> -<html> -<head> - <meta charset="utf-8"> - <title>jsp:include 示例 - - - -

    jsp:include 示例

    - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspplugin.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspplugin.jsp deleted file mode 100644 index bd6f10db..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspplugin.jsp +++ /dev/null @@ -1,33 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=utf-8" %> - - - plugin - - -
    - - - - - -
    - - - - - - - 您的浏览器不支持 Java Applet - - -
    - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspuseBean.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspuseBean.jsp deleted file mode 100644 index df227703..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspuseBean.jsp +++ /dev/null @@ -1,55 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=utf-8" %> - - - Java Bean Actions - - -
    - -<%-- 声明 person 类对象 --%> - - -<%-- 设置 person 的所有属性, 属性值从 request 中自动取得,* 表示所有属性 --%> - - -
    -
    -
    - 请填写 person 信息 - - - - - - - - - - - - - - - - - - -
    姓名 - <%-- 获取 person 的 name 属性 --%> - -
    年龄 - <%-- 获取 person 的 age 属性 --%> - -
    性别 - <%-- 获取 person 的 sex 属性 --%> - -
    - -
    -
    -
    -
    - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspuseBean2.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspuseBean2.jsp deleted file mode 100644 index d8012add..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/jspuseBean2.jsp +++ /dev/null @@ -1,43 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=utf-8" %> - - - 计数器 - - -
    - -<%-- 定义一个 session 范围内的计数器,记录个人的访问信息 --%> - - -<%-- 定义一个 application 范围内的计数器,记录所有人的访问信息 --%> - - -
    -
    -
    - 计数器 - - - - - - - - - -
    您的访问次数: - <%-- 获取各人的访问次数 --%> - - 次 -
    总共的访问次数: - <%-- 获取所有人的访问次数 --%> - - 次 -
    -
    -
    -
    - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/useBean.html b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/useBean.html deleted file mode 100644 index a83a1cb0..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/04.action/useBean.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - Java Bean Actions - - -
    - -
    -
    -
    - 请填写 Person 信息 - - - - - - - - - - - - - - - - - - -
    姓名:
    年龄:
    性别: - Male - Female -
    - -
    -
    -
    -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/05.implicit_object/error.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/05.implicit_object/error.jsp deleted file mode 100644 index 5a9640f9..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/05.implicit_object/error.jsp +++ /dev/null @@ -1,11 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=utf-8" isErrorPage="true" %> - -JSP - - -<% - out.println("程序拋出了一个异常:" + exception); -%> - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/05.implicit_object/exception.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/05.implicit_object/exception.jsp deleted file mode 100644 index 3edbf87e..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/05.implicit_object/exception.jsp +++ /dev/null @@ -1,14 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=utf-8" errorPage="error.jsp" %> -<% - out.clear(); - String str = null; - // length() 会抛出 NullPointerException - int length = str.length(); -%> - -JSP - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/cookie/readCookie.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/cookie/readCookie.jsp deleted file mode 100644 index 03e068a6..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/cookie/readCookie.jsp +++ /dev/null @@ -1,31 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ page import="java.net.URLDecoder" %> - - - - - 获取 Cookie - - -<% - Cookie cookie = null; - Cookie[] cookies = null; - // 获取cookies的数据,是一个数组 - cookies = request.getCookies(); - if (cookies != null) { - out.println("

    查找 Cookie 名与值

    "); - for (int i = 0; i < cookies.length; i++) { - cookie = cookies[i]; - - out.print("参数名 : " + cookie.getName()); - out.print("
    "); - out.print("参数值: " + URLDecoder.decode(cookie.getValue(), "utf-8") + "
    "); - out.print("------------------------------------
    "); - } - } else { - out.println("

    没有发现 Cookie

    "); - } -%> - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/cookie/session.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/cookie/session.jsp deleted file mode 100644 index f8adb69e..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/cookie/session.jsp +++ /dev/null @@ -1,71 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ page import="java.util.Date" %> -<% - // 获取session创建时间 - Date createTime = new Date(session.getCreationTime()); - // 获取最后访问页面的时间 - Date lastAccessTime = new Date(session.getLastAccessedTime()); - - String note = "再次访问"; - Integer visitCount = 0; - String visitCountKey = "visitCount"; - String userIDKey = "userID"; - String userID = "ABCD"; - - // 检测网页是否由新的访问用户 - if (session.isNew()) { - note = "第一次访问"; - session.setAttribute(userIDKey, userID); - session.setAttribute(visitCountKey, visitCount); - } else { - visitCount = (Integer) session.getAttribute(visitCountKey); - if (null == visitCount) { - visitCount = 0; - } - visitCount += 1; - userID = (String) session.getAttribute(userIDKey); - if (null == userID) { - userID = "ABCD"; - } - session.setAttribute(visitCountKey, visitCount); - } -%> - - - Session 示例 - - - - -<%--提示语--%> -

    <% out.print(note); %>

    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Session 信息
    id<% out.print(session.getId()); %>
    创建时间<% out.print(createTime); %>
    最后访问时间<% out.print(lastAccessTime); %>
    用户 ID<% out.print(userID); %>
    访问次数<% out.print(visitCount); %>
    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/cookie/writeCookie.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/cookie/writeCookie.jsp deleted file mode 100644 index 330410be..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/cookie/writeCookie.jsp +++ /dev/null @@ -1,41 +0,0 @@ -<%-- -访问本页面的形式如:http://127.0.0.1:8080/runoobDemos/writeCookie.jsp?name=张三&url=www.baidu.com ---%> - -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ page import="java.net.URLEncoder" %> -<% - // 编码,解决中文乱码 - String name = URLEncoder.encode(request.getParameter("name"), "utf-8"); - - // 设置 nameCookie 和 urlCookie cookie - Cookie nameCookie = new Cookie("name", name); - Cookie urlCookie = new Cookie("url", request.getParameter("url")); - - // 设置cookie过期时间为24小时。 - nameCookie.setMaxAge(60 * 60 * 24); - urlCookie.setMaxAge(60 * 60 * 24); - - // 在响应头部添加cookie - response.addCookie(nameCookie); - response.addCookie(urlCookie); -%> - - - 设置 Cookie - - - -

    设置 Cookie

    - -
      -
    • 网站名: - <%= request.getParameter("name")%> -

    • -
    • 网址: - <%= request.getParameter("url")%> -

    • -
    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/language.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/language.jsp deleted file mode 100644 index e625db79..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/language.jsp +++ /dev/null @@ -1,23 +0,0 @@ -<%@ page import="java.util.Locale" %> -<% - //获取客户端本地化信息 - Locale locale = request.getLocale(); - String language = locale.getLanguage(); - String country = locale.getCountry(); -%> - - - Detecting Locale - - -
    -

    Detecting Locale

    -
    -

    - <% - out.println("Language : " + language + "
    "); - out.println("Country : " + country + "
    "); - %> -

    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/language2.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/language2.jsp deleted file mode 100644 index 9b93661d..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/language2.jsp +++ /dev/null @@ -1,20 +0,0 @@ -<% - // Set response content type - response.setContentType("text/html"); - // Set spanish language code. - response.setHeader("Content-Language", "es"); - String title = "En Espa?ol"; - -%> - - - <% out.print(title); %> - - -

    <% out.print(title); %>

    -
    -

    En Espa?ol

    -

    ?Hola Mundo!

    -
    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/localeCurrency.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/localeCurrency.jsp deleted file mode 100644 index b92f0c0e..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/localeCurrency.jsp +++ /dev/null @@ -1,20 +0,0 @@ -<%@ page import="java.text.NumberFormat,java.util.Locale" %> - -<% - String title = "Locale Specific Currency"; - //Get the client's Locale - Locale locale = request.getLocale(); - NumberFormat nft = NumberFormat.getCurrencyInstance(locale); - String formattedCurr = nft.format(1000000); -%> - - - <% out.print(title); %> - - -

    <% out.print(title); %>

    -
    -

    Formatted Currency: <% out.print(formattedCurr); %>

    -
    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/localeDate.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/localeDate.jsp deleted file mode 100644 index 916debc2..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/localeDate.jsp +++ /dev/null @@ -1,20 +0,0 @@ -<%@ page import="java.text.DateFormat,java.util.Date" %> -<%@ page import="java.util.Locale " %> - -<% - String title = "Locale Specific Dates"; - //Get the client's Locale - Locale locale = request.getLocale(); - String date = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.SHORT, locale).format(new Date()); -%> - - - <% out.print(title); %> - - -

    <% out.print(title); %>

    -
    -

    Local Date: <% out.print(date); %>

    -
    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/localePercent.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/localePercent.jsp deleted file mode 100644 index 926d7766..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/locale/localePercent.jsp +++ /dev/null @@ -1,20 +0,0 @@ -<%@ page import="java.text.NumberFormat,java.util.Locale" %> - -<% - String title = "Locale Specific Percentage"; - //Get the client's Locale - Locale locale = request.getLocale(); - NumberFormat nft = NumberFormat.getPercentInstance(locale); - String formattedPerc = nft.format(0.51); -%> - - - <% out.print(title); %> - - -

    <% out.print(title); %>

    -
    -

    Formatted Percentage: <% out.print(formattedPerc); %>

    -
    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/date.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/date.jsp deleted file mode 100644 index c0b67962..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/date.jsp +++ /dev/null @@ -1,24 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ page import="java.text.SimpleDateFormat,java.util.Date" %> - - - 显示当前时间与日期 - - - -

    显示当前时间与日期

    - -<% - Date date = new Date(); - out.print("

    " + date.toString() + "

    "); -%> - -

    使用SimpleDateFormat格式化日期

    -<% - Date dNow = new Date(); - SimpleDateFormat ft = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - out.print("

    " + ft.format(dNow) + "

    "); -%> - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox.html b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox.html deleted file mode 100644 index 67be6224..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - Checkbox - - - -
    - 谷歌 - 百度 - 淘宝 - -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox.jsp deleted file mode 100644 index aea6c4a8..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox.jsp +++ /dev/null @@ -1,23 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> - - - - - Checkbox - - -

    从复选框中读取数据

    -
      -
    • 谷歌是否选中: - <%= request.getParameter("google")%> -

    • -
    • 百度是否选中: - <%= request.getParameter("baidu")%> -

    • -
    • 淘宝是否选中: - <%= request.getParameter("taobao")%> -

    • -
    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox2.html b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox2.html deleted file mode 100644 index efc913fb..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox2.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - Checkbox - - - -
    - 谷歌 - 百度 - 淘宝 - -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox2.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox2.jsp deleted file mode 100644 index 3339a2e7..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/checkbox2.jsp +++ /dev/null @@ -1,29 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ page import="java.util.Enumeration" %> - - - - - Checkbox2 - - -

    读取所有表单参数

    - - - - - - <% - Enumeration paramNames = request.getParameterNames(); - - while (paramNames.hasMoreElements()) { - String paramName = (String) paramNames.nextElement(); - out.print("\n"); - String paramValue = request.getParameter(paramName); - out.println("\n"); - } - %> -
    参数名参数值
    " + paramName + " " + paramValue + "
    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formGet.html b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formGet.html deleted file mode 100644 index 4f905ebc..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formGet.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - 表单 - Get操作 - - - -
    - 站点名: -
    - 网址: - -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formGet.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formGet.jsp deleted file mode 100644 index 095eb1e0..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formGet.jsp +++ /dev/null @@ -1,20 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> - - - - - 表单 - Get操作 - - -

    使用 GET 方法读取数据

    -
      -
    • 站点名: - <%= request.getParameter("name")%> -

    • -
    • 网址: - <%= request.getParameter("url")%> -

    • -
    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formPost.html b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formPost.html deleted file mode 100644 index 3bc877ab..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formPost.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - 表单 - Post操作 - - - -
    - 站点名: -
    - 网址: - -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formPost.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formPost.jsp deleted file mode 100644 index e74174a9..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/form/formPost.jsp +++ /dev/null @@ -1,24 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> - - - - - 表单 - Post操作 - - -

    使用 POST 方法读取数据

    -
      -
    • 站点名: - <% - // 解决中文乱码的问题 - String name = new String((request.getParameter("name")).getBytes("ISO-8859-1"), "UTF-8"); - %> - <%=name%> -

    • -
    • 网址: - <%= request.getParameter("url")%> -

    • -
    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/hitCount.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/hitCount.jsp deleted file mode 100644 index 945dbb66..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/hitCount.jsp +++ /dev/null @@ -1,27 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> - - - - 访问量统计 - - -<% - Integer hitsCount = (Integer) application.getAttribute("hitCounter"); - if (hitsCount == null || hitsCount == 0) { - /* 第一次访问 */ - out.println("欢迎访问!"); - hitsCount = 1; - } else { - /* 返回访问值 */ - out.println("欢迎再次访问!"); - hitsCount += 1; - } - application.setAttribute("hitCounter", hitsCount); -%> - -

    页面访问量为: <%= hitsCount%> -

    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/mail/sendMail.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/mail/sendMail.jsp deleted file mode 100644 index 7261e11d..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/mail/sendMail.jsp +++ /dev/null @@ -1,55 +0,0 @@ -<%@ page import="java.util.Properties" %> -<% - String result; - // 收件人的电子邮件 - String to = "abcd@gmail.com"; - - // 发件人的电子邮件 - String from = "mcmohd@gmail.com"; - - // 假设你是从本地主机发送电子邮件 - String host = "localhost"; - - // 获取系统属性对象 - Properties properties = System.getProperties(); - - // 设置邮件服务器 - properties.setProperty("mail.smtp.host", host); - - // 获取默认的Session对象。 - Session mailSession = Session.getDefaultInstance(properties); - - try { - // 创建一个默认的MimeMessage对象。 - MimeMessage message = new MimeMessage(mailSession); - // 设置 From: 头部的header字段 - message.setFrom(new InternetAddress(from)); - // 设置 To: 头部的header字段 - message.addRecipient(Message.RecipientType.TO, new InternetAddress(to)); - // 设置 Subject: header字段 - message.setSubject("This is the Subject Line!"); - // 现在设置的实际消息 - message.setText("This is actual message"); - // 发送消息 - Transport.send(message); - result = "Sent message successfully...."; - } catch (MessagingException mex) { - mex.printStackTrace(); - result = "Error: unable to send message...."; - } -%> - - - Send Email using JSP - - -
    -

    Send Email using JSP

    -
    -

    - <% - out.println("Result: " + result + "\n"); - %> -

    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/redirect.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/redirect.jsp deleted file mode 100644 index c4e082b1..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/redirect.jsp +++ /dev/null @@ -1,20 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> - - - - 页面重定向 - - - -

    页面重定向

    - -<% - // 重定向到新地址 - String site = "http://www.baidu.com"; - response.setStatus(response.SC_MOVED_TEMPORARILY); - response.setHeader("Location", site); -%> - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/refresh.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/refresh.jsp deleted file mode 100644 index 85d34aa9..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/practice/refresh.jsp +++ /dev/null @@ -1,30 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ page import="java.util.Calendar,java.util.GregorianCalendar" %> - - - 自动刷新实例 - - - -

    每秒刷新时间

    -<% - // 设置每秒刷新一次 - response.setIntHeader("Refresh", 1); - // 获取当前时间 - Calendar calendar = new GregorianCalendar(); - String am_pm; - int hour = calendar.get(Calendar.HOUR); - int minute = calendar.get(Calendar.MINUTE); - int second = calendar.get(Calendar.SECOND); - if (calendar.get(Calendar.AM_PM) == 0) { - am_pm = "AM"; - } else { - am_pm = "PM"; - } - String CT = hour + ":" + minute + ":" + second + " " + am_pm; - out.println("当前时间为: " + CT + "\n"); -%> - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/01.helloWorld.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/01.helloWorld.jsp deleted file mode 100644 index bba59c7f..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/01.helloWorld.jsp +++ /dev/null @@ -1,10 +0,0 @@ - - - First Jsp Programe - - -<% - out.println("Hello World!"); -%> - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/01.helloWorld_zh.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/01.helloWorld_zh.jsp deleted file mode 100644 index b02b856b..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/01.helloWorld_zh.jsp +++ /dev/null @@ -1,13 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> - - - - 第一个Jsp程序 - - -<% - out.println("你好!"); -%> - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/02.life.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/02.life.jsp deleted file mode 100644 index 2e29bad8..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/02.life.jsp +++ /dev/null @@ -1,45 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> - - - life.jsp - - - -<%! - private int initVar = 0; - - private int serviceVar = 0; - - private int destroyVar = 0; -%> - -<%! - public void jspInit() { - initVar++; - System.out.println("jspInit(): JSP被初始化了" + initVar + "次"); - } - - public void jspDestroy() { - destroyVar++; - System.out.println("jspDestroy(): JSP被销毁了" + destroyVar + "次"); - } -%> - -<% - serviceVar++; - System.out.println("_jspService(): JSP共响应了" + serviceVar + "次请求"); - - String content1 = "初始化次数 : " + initVar; - String content2 = "响应客户请求次数 : " + serviceVar; - String content3 = "销毁次数 : " + destroyVar; -%> -

    菜鸟教程 JSP 测试实例

    -

    <%=content1 %> -

    -

    <%=content2 %> -

    -

    <%=content3 %> -

    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/03.yourIp.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/03.yourIp.jsp deleted file mode 100644 index 354bb2f9..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/03.yourIp.jsp +++ /dev/null @@ -1,15 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> - - - - - 菜鸟教程(runoob.com) - - -Hello World!
    -<% - out.println("你的 IP 地址 " + request.getRemoteAddr()); -%> - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/configuration.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/configuration.jsp deleted file mode 100644 index 68764cca..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/examples/uncheck/configuration.jsp +++ /dev/null @@ -1,14 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=utf-8" %> - - - JSP config 示例 - - -

    - <% - String message = config.getInitParameter("message"); - out.println(message); - %> -

    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/images/bg-btn-blue.gif b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/images/bg-btn-blue.gif deleted file mode 100644 index bc03f1bd5948a2057abb97707ece2434cea75203..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 827 zcmZ?wbhEHbWMnX6_|CwPHuG*k>y@ysYcAF2qxx^ePrhYUcrtm~ZLfxlcBN++M!{$Z w4C@e3{K*3LpALuw}~g^ZMxIH_EzJft91*n zl+L=CIq__H`EskwbOwb~3QWE2#9PWX?-jye?-oSAou9 z#t$Y<#sW3x`T3;v7JmLK!?p0if(xSaSQk86U?L%KZiRB;f_p4sEY9pP8f&gTYuGVW mYRU~q^Mkewf?jh=_p@s;HZ(Rhn)A1{cXVoW_oyl|SOWkQuTW|L diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/images/vertical_line.gif b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/images/vertical_line.gif deleted file mode 100644 index 65f8ee7882196570f92051f9c5fc4f48c5bfa066..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 301 zcma)1+X=!z5S-0D5-%5X=NS!&f(2NCKYj>7t;ik(!8&Y0L9hiMKX!7SehBf)Sr(R= zVRqMF{S#*0tQrszmZ9JuSU_^l1(!m|jtre9QYNWZDpg7w{ejWl+~i&DT)*$!sIKeL zL gJo)DBuOGJO@6e3GGG;Nyc5_&rX1noz+FbnY1^zZDQUCw| diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/css/style.css b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/css/style.css deleted file mode 100644 index 2ae2e7bd..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/css/style.css +++ /dev/null @@ -1,51 +0,0 @@ -body, div, td, input { - font-size: 12px; - margin: 0px; -} - -select { - height: 20px; - width: 300px; -} - -.title { - font-size: 16px; - padding: 10px; - margin: 10px; - width: 80%; -} - -.text { - height: 20px; - width: 300px; - border: 1px solid #AAAAAA; -} - -.line { - margin: 2px; -} - -.leftDiv { - width: 110px; - float: left; - height: 22px; - line-height: 22px; - font-weight: bold; -} - -.rightDiv { - height: 22px; - line-height: 22px; -} - -.button { - color: #FFFFFF; - font-weight: bold; - font-size: 11px; - text-align: center; - padding: .17em 0 .2em .17em; - border-style: solid; - border-width: 1px; - border-color: #99CCFF #115599 #115599 #99CCFF; - background: #6699CC url(../images/bg-btn-blue.gif) repeat-x; -} diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action.jsp deleted file mode 100644 index e2353464..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action.jsp +++ /dev/null @@ -1,7 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> - - - -这里是正文 - - \ No newline at end of file diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/date.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/date.jsp deleted file mode 100644 index 4537afec..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/date.jsp +++ /dev/null @@ -1,4 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> -

    - 今天的日期是: <%=(new java.util.Date()).toLocaleString()%> -

    diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/forward.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/forward.jsp deleted file mode 100644 index 40b34380..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/forward.jsp +++ /dev/null @@ -1,12 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> - - - - - jsp:forward范例 - - -

    jsp:forward范例

    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/getProperty.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/getProperty.jsp deleted file mode 100644 index 78e1c59c..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/getProperty.jsp +++ /dev/null @@ -1,19 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> - - - - jsp:setProperty和jsp:getProperty使用范例 - - - -

    jsp:setProperty和jsp:getProperty使用范例

    - - - - -

    输出信息....

    - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/include.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/include.jsp deleted file mode 100644 index cdb036a0..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/include.jsp +++ /dev/null @@ -1,12 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> - - - - - jsp:include范例 - - -

    include 动作实例

    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/useBean.html b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/useBean.html deleted file mode 100644 index e087b551..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/useBean.html +++ /dev/null @@ -1,38 +0,0 @@ - - - Java Bean Actions - - - - -
    -
    -
    -
    - 请填写 Person 信息 - - - - - - - - - - - - - - - - - -
    姓名:
    年龄:
    性别:Male Female -
    -
    -
    -
    -
    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/useBean.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/useBean.jsp deleted file mode 100644 index 7d7d1850..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/action/useBean.jsp +++ /dev/null @@ -1,53 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=utf-8" %> - - - Java Bean Actions - - - -
    - -<%-- 声明 Person 类对象 person --%> - - -<%-- 设置 person 的所有属性,所有的属性值从 request 中自动取得 --%> - - -
    -
    -
    - 请填写 Person 信息 - - - - - - - - - - - - - - - - - -
    姓名: - <%-- 获取 person 的 name 属性 --%> - -
    年龄: - <%-- 获取 person 的 age 属性 --%> - -
    性别: - <%-- 获取 person 的 sex 属性 --%> - -
    -
    -
    -
    -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/application.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/application.jsp deleted file mode 100644 index 655a26b4..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/application.jsp +++ /dev/null @@ -1,15 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=utf-8" %> -<% - out.getBufferSize(); - - out.println(request); - - -%> - -JSP Scriptlets - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/break.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/break.jsp deleted file mode 100644 index 72ab9510..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/break.jsp +++ /dev/null @@ -1,27 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=utf-8" %> - -JSP Scriptlets - - -<% - - for (int i = 0; i < 5; i++) { -%> break 所在的循环, i = <%= i %>.
    -<% - if (i == 2) { - break; - } - } -%> break 循环完毕.
    -<% - for (int i = 0; i < 5; i++) { -%> return 所在的循环, i = <%= i %>.
    -<% - if (i == 2) { - return; - } - } -%> return 循环完毕.
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/comment.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/comment.jsp deleted file mode 100644 index 457d8ad9..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/comment.jsp +++ /dev/null @@ -1,21 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<% - // 这是 Java 行注释 - String path = request.getContextPath(); - - /* - 这是 Java 多行注释 - */ -%> - - - - JSP 注释 - -<%-- - 这是 JSP 注释,可以添加多行注释 ---%> - -This is my JSP page.
    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/counter.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/counter.jsp deleted file mode 100644 index 0c7095a8..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/counter.jsp +++ /dev/null @@ -1,43 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=utf-8" %> - - - 计数器 - - -
    - -<%-- 定义一个 session 范围内的计数器 记录个人访问信息 --%> - - -<%-- 定义一个 application 范围内的计数器 记录所有人的访问信息 --%> - - -
    -
    -
    - 计数器 - - - - - - - - - -
    您的访问次数: - <%-- 获取个人的 访问次数 --%> - - 次 -
    总共的访问次数: - <%-- 获取所有人的 访问次数 --%> - - 次 -
    -
    -
    -
    - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive.jsp deleted file mode 100644 index 21f32e35..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive.jsp +++ /dev/null @@ -1,4 +0,0 @@ -<%@page contentType="image/jpeg" %> - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/include/foot.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/include/foot.jsp deleted file mode 100644 index a86f2eb7..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/include/foot.jsp +++ /dev/null @@ -1,13 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=utf-8" %> - - - - - - -
    - Copyright 2007-2010 ©Helloweenvsfei -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/include/head.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/include/head.jsp deleted file mode 100644 index 479ac0d7..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/include/head.jsp +++ /dev/null @@ -1,23 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=utf-8" %> - - - JSP指令include范例 - - - - - - - - - - - - - - - - - - -
    JSP指令include范例
    首页资源文档下载关于邮件社区
    diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/include/include.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/include/include.jsp deleted file mode 100644 index addd936c..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/include/include.jsp +++ /dev/null @@ -1,15 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=utf-8" %> - -<%@ include file="head.jsp" %> -

    -

    如今且说林黛玉自在荣府以来,贾母万般怜爱,寝食起居,一如宝玉,迎春,探春, 惜春三个亲孙女倒且靠后, - 便是宝玉和黛玉二人之亲密友爱处,亦自较别个不同,日则同行同坐,夜则同息同止,真是言和意顺,略无参商.不想如今忽然来了一个薛宝钗 ,年岁虽大不多,然品格端方,容貌丰美,人多谓黛玉所不及.而且宝钗行为豁达,随分从时, - 不比黛玉孤高自许,目无下尘,故比黛玉大得下人之心.便是那些小丫头子们, 亦多喜与宝钗去顽. 因此黛玉心中便有些悒郁不忿之意,宝钗却浑然不觉.那宝玉亦在孩提之间, - 况自天性所禀来的一片愚拙偏僻,视姊妹弟兄皆出一意,并无亲疏远近之别.其中因与黛玉同随贾母一处坐卧,故略比别个姊妹熟惯些.既熟惯,则更觉亲密 , - 既亲密,则不免一时有求全之毁,不虞之隙.这日不知为何,他二人言语有些不合起来,黛玉又气的独在房中垂泪,宝玉又自悔言语冒撞,前去俯就,那黛玉方渐渐的回转来. 因东边宁府中花园内梅花盛开,贾珍之妻尤氏乃治酒,请贾母,邢夫人,王夫人等赏花. - 是日先携了贾蓉之妻,二人来面请.贾母等于早饭后过来,就在会芳园游顽,先茶后酒,不过皆是宁荣二府女眷家宴小集,并无别样新文趣事可记. -

    - 一时宝玉倦怠,欲睡中觉,贾母命人好生哄着,歇一回再来.贾蓉之妻秦氏便忙笑回道:"我们这里有给宝叔收拾下的屋子,老祖宗放心,只管交与我就是了."又向宝玉的奶娘丫鬟等道:"嬷嬷,姐姐们,请宝叔随我这里来."贾母素知秦氏是个极妥当的人 , - 生的袅娜纤巧,行事又温柔和平,乃重孙媳中第一个得意之人,见他去安置宝玉,自是安稳的. -

    - <%@ include file="foot.jsp" %> diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/page/contentType.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/page/contentType.jsp deleted file mode 100644 index 1599e7d1..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/page/contentType.jsp +++ /dev/null @@ -1,12 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> - - - - JSP指令page-属性contentType - - - -

    你好啊

    -

    contentType="text/html; charset=UTF-8"

    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/page/errorPage.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/page/errorPage.jsp deleted file mode 100644 index a63836ae..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/page/errorPage.jsp +++ /dev/null @@ -1,13 +0,0 @@ - -<%@ page language="java" errorPage="isErrorPage.jsp" pageEncoding="UTF-8" %> - - - JSP指令page-属性errorPage - - -<% - //这行代码肯定会出错,因为除数是0,一运行就会抛出异常 - int x = 1 / 0; -%> - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/page/isErrorPage.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/page/isErrorPage.jsp deleted file mode 100644 index db80567d..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/directive/page/isErrorPage.jsp +++ /dev/null @@ -1,12 +0,0 @@ - -<%@ page language="java" pageEncoding="UTF-8" isErrorPage="true" %> - - - JSP - - -<% - out.println("程序抛出了一个异常:" + exception); -%> - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/el.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/el.jsp deleted file mode 100644 index d58a74a1..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/el.jsp +++ /dev/null @@ -1,26 +0,0 @@ -<%@ page language="java" pageEncoding="UTF-8" %> - -<%! - String ss1 = "ss String"; -%> -<% -%> - - - My JSP 'el.jsp' starting page - - - - - - - - - - -${not (1==2) } - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/error.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/error.jsp deleted file mode 100644 index ae1fe636..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/error.jsp +++ /dev/null @@ -1,11 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=utf-8" isErrorPage="true" %> - - - JSP - - -<% - out.println("程序抛出了一个异常:" + exception); -%> - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/exception.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/exception.jsp deleted file mode 100644 index 4abf68c2..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/exception.jsp +++ /dev/null @@ -1,14 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=utf-8" errorPage="/error.jsp" %> -<% - out.clear(); - String str = null; - // length() 操作会抛出 NullPointerException - int length = str.length(); -%> - -JSP - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/grammar/for.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/grammar/for.jsp deleted file mode 100644 index 860e0467..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/grammar/for.jsp +++ /dev/null @@ -1,53 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=utf-8" %> - -JSP Scriptlets - - - -
    -<% - Object[][] letters = {{true, "恭喜您注册的信息已经生效", "e_inn@163.com", "helloweenvsfei@gmail.com", "2007-8-8"}, - {true, "Java EE 5.0 release!!", "admin@sun.com", "helloweenvsfei@gmail.com", "2007-6-24"}, - {false, "来信已经收到,下周末见面商谈", "foo@bar.com", "helloweenvsfei@gmail.com", "2007-5-20"}, - {false, "您的博客有新的留言", "blog@foo.bar.com", "helloweenvsfei@gmail.com", "2007-3-2"},}; - String[] colors = {"#DDDDDD", "#AAAAAA",}; -%> - - - - - - - - - <% - for (int i = 0; i < letters.length; i++) { - Object[] letter = letters[i]; - %> - - - - - - - - <% - } - %> -
     标题 发信人 收信人 时间 
    - <% - if (letter[0] == Boolean.TRUE) { - %> - - <% - } else { - out.println(" "); - } - %> - <%= letter[1] %> - <%= letter[2] %> - <%= letter[3] %> - <%= letter[4] %> -
    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/grammar/if.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/grammar/if.jsp deleted file mode 100644 index a9f1bfb8..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/grammar/if.jsp +++ /dev/null @@ -1,48 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=utf-8" %> - -JSP Scriptlets - - -<% - String param = request.getParameter("param"); - - if ("1".equals(param)) { -%> -关雎·周南·诗经
    -关关雎鸠,在河之洲。窈窕淑女,君子好逑。
    -参差荇菜,左右流之。窈窕淑女,寤寐求之。
    -求之不得,寤寐思服。悠哉悠哉,辗转反侧。
    -参差荇菜,左右采之。窈窕淑女,琴瑟友之。
    -参差荇菜,左右芼之。窈窕淑女,钟鼓乐之。
    -<% -} else if ("2".equals(param)) { -%> -蒹葭·秦风·诗经
    -蒹葭苍苍,白露为霜。所谓伊人,在水一方。
    -溯洄从之,道阻且长。溯游从之,宛在水中央。
    -蒹葭凄凄,白露未晞。所谓伊人,在水之湄。
    -溯洄从之,道阻且跻。溯游从之,宛在水中坻。
    -蒹葭采采,白露未已。所谓伊人,在水之涘。
    -溯洄从之,道阻且右。溯游从之,宛在水中沚。
    -<% -} else if ("3".equals(param)) { -%> -子衿·国风·郑风
    -青青子衿,悠悠我心。
    -纵我不往,子宁不嗣音?
    -青青子佩,悠悠我思。
    -纵我不往,子宁不来?
    -挑兮达兮,在城阙兮。
    -一日不见,如三月兮。
    -<% -} else { -%> -请使用参数 param=1, 2, 3 选择要显示的诗歌

    -if.jsp?param=1
    -if.jsp?param=2
    -if.jsp?param=3
    -<% - } -%> - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/grammar/while.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/grammar/while.jsp deleted file mode 100644 index a8a2ce6e..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/grammar/while.jsp +++ /dev/null @@ -1,23 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=utf-8" %> - -JSP Scriptlets - - -<% - java.util.List list = new java.util.ArrayList(); - - list.add("茕茕白兔"); - list.add("东走西顾"); - list.add("衣不如新"); - list.add("人不如故"); - - java.util.Iterator it = list.iterator(); - - while (it.hasNext()) { -%> <%= it.next() %>
    -<% - } - -%> - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/greeting.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/greeting.jsp deleted file mode 100644 index 24ea2f01..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/greeting.jsp +++ /dev/null @@ -1,44 +0,0 @@ -<%@ page language="java" import="java.util.Calendar" contentType="text/html; charset=utf-8" %> -<%@ page import="java.util.Locale" %> -<% - Locale locale = request.getLocale(); - - Calendar calendar = Calendar.getInstance(locale); - - int hour = calendar.get(Calendar.HOUR_OF_DAY); - - String greeting = ""; - - if (hour <= 6) { - greeting = "凌晨好,您该睡觉了。良好的睡眠是美好一天的开始。"; - } else if (hour <= 9) { - greeting = "早上好。早餐应该注意营养。"; - } else if (hour <= 12) { - greeting = "上午好。工作时注意保护眼睛。"; - } else if (hour <= 18) { - greeting = "下午好。小心工作中打瞌睡。"; - } else if (hour <= 24) { - greeting = "晚上好。放松一下自己,好好休息。睡觉不要太晚啊~~"; - } else { - - } -%> - - - - 欢迎页面 - - - - - - - - - - - -
    <%= greeting %> -
    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/index.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/index.jsp deleted file mode 100644 index f0dded67..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/index.jsp +++ /dev/null @@ -1,25 +0,0 @@ -<%@ page language="java" pageEncoding="GB18030" %> -<% - String path = request.getContextPath(); - String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; -%> - - - - - - javaee-jsp ҳ - - - - - - - - - -This is my JSP page.
    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/life.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/life.jsp deleted file mode 100644 index b80202bf..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/life.jsp +++ /dev/null @@ -1,15 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%! - -%> - - - - - - Insert title here - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/method.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/method.jsp deleted file mode 100644 index bfc8cda9..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/method.jsp +++ /dev/null @@ -1,84 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=utf-8" %> -<%@ page import="com.helloweenvsfei.util.ip.IPSeeker" %> -<%! - // 全局变量 - private IPSeeker ipSeeker = IPSeeker.getInstance(); - - // 方法一 - public String getArea(String ip) { - return ipSeeker.getArea(ip); - } - - //方法二 - public String getCountry(String ip) { - return ipSeeker.getCountry(ip); - } - - // 方法三 正则表达式判断是否合法 IP 地址 - public boolean isValidIp(String ip) { - return ip != null && ip.trim().matches("^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$"); - } -%> - - - IP 地址查询 - - -
    -<% - String ip = request.getParameter("ip"); - String area = ""; - String country = ""; - - // 如果是合法的 IP 地址 - if (isValidIp(ip)) { - // 调用方法一 - country = getCountry(ip); - // 调用方法二 - area = getArea(ip); - } - -%> -
    -
    -
    - IP 地址查询 - - <% - if (isValidIp(ip)) { - %> - - - - - - - - - - - - - <% - } - %> - - - - - - - - -
    IP 地址:<%= ip %> -
    国家:<%= country %> -
    地区:<%= area %> -
    请输入要查询的 IP 地址:
    - -
    -
    -
    -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/plugin.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/plugin.jsp deleted file mode 100644 index bd6f10db..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/plugin.jsp +++ /dev/null @@ -1,33 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=utf-8" %> - - - plugin - - -
    - - - - - -
    - - - - - - - 您的浏览器不支持 Java Applet - - -
    - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/return.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/return.jsp deleted file mode 100644 index e064ddf6..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/return.jsp +++ /dev/null @@ -1,27 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=utf-8" %> - -JSP Scriptlets - - -<% - String param = request.getParameter("param"); -%> -昔我往矣,
    -杨柳依依。
    -今我来思,
    -雨雪霏霏。
    -<% - if ("return".equals(param)) { - return; - } -%> -青青子衿,
    -悠悠我心,
    -但为君故,
    -沉吟至今!
    - -
    -<%= request.getRequestURI() %>?param=return - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/scriptlet.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/scriptlet.jsp deleted file mode 100644 index 5197d1ce..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/scriptlet.jsp +++ /dev/null @@ -1,17 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=utf-8" %> - -JSP Scriptlets - - -<% - int num = 10; - int result = 1; - for (int i = 1; i <= num; i++) { - result *= i; - out.println("第" + i + "步运算:" + result + "
    "); - } - out.println("
    "); - out.println("数字 " + num + " 的阶乘为: " + result); -%> - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/taglib.jsp b/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/taglib.jsp deleted file mode 100644 index e0560793..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/main/webapp/views/jsp/taglib.jsp +++ /dev/null @@ -1,14 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> - -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - - - My JSP 'taglib.jsp' starting page - - - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java b/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java deleted file mode 100644 index 5fb3a29d..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java +++ /dev/null @@ -1,114 +0,0 @@ -package io.github.dunwu.javaee.server; - -import java.util.ArrayList; -import org.apache.commons.lang3.StringUtils; -import org.assertj.core.util.Lists; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.webapp.WebAppClassLoader; -import org.eclipse.jetty.webapp.WebAppContext; - -/** - * JettyFactory 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 - * - * @author Zhang Peng - */ -@SuppressWarnings("unused") -public class JettyFactory { - - public static final int IDE_ECLIPSE = 0; - - public static final int IDE_INTELLIJ = 1; - - private static final int PORT = 9798; - - private static final String CONTEXT = "/"; - - private static final String RESOURCE_BASE_PATH = "src/main/webapp"; - - private static final String WEB_XML_PATH = "/WEB-INF/web.xml"; - - private static final String[] TLD_JAR_NAMES = new String[] {"sitemesh", "spring-webmvc", "shiro-web", "tiles"} - - private static final String WINDOWS_WEBDEFAULT_PATH = "jetty/webdefault.xml"; - - public static Server initServer() { - Profiles.setProfileAsSystemProperty(Profiles.DEVELOPMENT); - WebAppContext webAppContext = new WebAppContext(); - Server server = new Server(PORT); - server.setHandler(webAppContext); - return server; - } - - public static void initWebAppContext(Server server, int type) throws Exception { - System.out.println("[INFO] Application loading"); - WebAppContext webAppContext = (WebAppContext) server.getHandler(); - webAppContext.setContextPath(CONTEXT); - webAppContext.setResourceBase(getAbsolutePath() + RESOURCE_BASE_PATH); - webAppContext.setDescriptor(getAbsolutePath() + RESOURCE_BASE_PATH + WEB_XML_PATH); - - if (IDE_INTELLIJ == type) { - webAppContext.setDefaultsDescriptor(WINDOWS_WEBDEFAULT_PATH); - supportJspAndSetTldJarNames(server, TLD_JAR_NAMES); - } else { - webAppContext.setParentLoaderPriority(true); - } - - System.out.println("[INFO] Application loaded"); - } - - public static String getAbsolutePath() { - String path = null; - String folderPath = JettyFactory.class.getProtectionDomain().getCodeSource().getLocation().getPath() - .substring(1); - if (folderPath.indexOf("target") > 0) { - path = folderPath.substring(0, folderPath.indexOf("target")); - } - return path; - } - - public static void supportJspAndSetTldJarNames(Server server, String... jarNames) { - WebAppContext context = (WebAppContext) server.getHandler(); - // This webapp will use jsps and jstl. We need to enable the - // AnnotationConfiguration in - // order to correctly set up the jsp container - org.eclipse.jetty.webapp.Configuration.ClassList classlist = org.eclipse.jetty.webapp.Configuration.ClassList - .setServerDefault(server); - classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", - "org.eclipse.jetty.annotations.AnnotationConfiguration"); - // Set the ContainerIncludeJarPattern so that jetty examines these container-path - // jars for - // tlds, web-fragments etc. - // If you omit the jar that contains the jstl .tlds, the jsp engine will scan for - // them - // instead. - ArrayList jarNameExprssions = Lists.newArrayList(".*/[^/]*servlet-api-[^/]*\\.jar$", - ".*/javax.servlet.jsp.jstl-.*\\.jar$", ".*/[^/]*taglibs.*\\.jar$"); - - for (String jarName : jarNames) { - jarNameExprssions.add(".*/" + jarName + "-[^/]*\\.jar$"); - } - - context.setAttribute("org.eclipse.jetty.io.github.dunwu.javaee.server.webapp.ContainerIncludeJarPattern", - StringUtils.join(jarNameExprssions, '|')); - } - - public static void reloadWebAppContext(Server server) throws Exception { - WebAppContext webAppContext = (WebAppContext) server.getHandler(); - System.out.println("[INFO] Application reloading"); - webAppContext.stop(); - WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext); - classLoader.addClassPath(getAbsolutePath() + "target/classes"); - classLoader.addClassPath(getAbsolutePath() + "target/test-classes"); - webAppContext.setClassLoader(classLoader); - webAppContext.start(); - System.out.println("[INFO] Application reloaded"); - } - - public static void startServer(Server server) throws Exception { - System.out.println("[HINT] Don't forget to set -XX:MaxPermSize=128m"); - server.start(); - System.out.println("Server running at http://localhost:" + PORT + CONTEXT); - System.out.println("[HINT] Hit Enter to reload the application quickly"); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/Profiles.java b/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/Profiles.java deleted file mode 100644 index dcfd3e39..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/Profiles.java +++ /dev/null @@ -1,34 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2005, 2014 springside.github.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - *******************************************************************************/ -package io.github.dunwu.javaee.server; - -/** - * Spring profile 常用方法与profile名称。 - * - * @author calvin - */ -public class Profiles { - - public static final String ACTIVE_PROFILE = "spring.profiles.active"; - - public static final String DEFAULT_PROFILE = "spring.profiles.default"; - - public static final String PRODUCTION = "production"; - - public static final String DEVELOPMENT = "development"; - - public static final String UNIT_TEST = "test"; - - public static final String FUNCTIONAL_TEST = "functional"; - - /** - * 在Spring启动前,设置profile的环境变量。 - */ - public static void setProfileAsSystemProperty(String profile) { - System.setProperty(ACTIVE_PROFILE, profile); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java b/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java deleted file mode 100644 index 049c8802..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.github.dunwu.javaee.server; - -import org.eclipse.jetty.server.Server; - -/** - * 快速启动 jetty 服务器,方便测试 - * - * @author Zhang Peng - */ -public class QuickStartServer { - - // private static int STARTUP_TYPE = JettyFactory.IDE_ECLIPSE; - private static int STARTUP_TYPE = JettyFactory.IDE_INTELLIJ; - - public static void main(String[] args) throws Exception { - Server server = JettyFactory.initServer(); - JettyFactory.initWebAppContext(server, STARTUP_TYPE); - - try { - JettyFactory.startServer(server); - - // 等待用户输入回车重载应用 - while (true) { - char c = (char) System.in.read(); - if (c == '\n') { - JettyFactory.reloadWebAppContext(server); - } - } - } catch (Exception e) { - e.printStackTrace(); - System.exit(-1); - } - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/resources/jetty/webdefault.xml b/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/resources/jetty/webdefault.xml deleted file mode 100644 index b991d44c..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/resources/jetty/webdefault.xml +++ /dev/null @@ -1,534 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - Default web.xml file. - This file is applied to a Web application before it's own WEB_INF/web.xml file - - - - - - - - org.eclipse.jetty.servlet.listener.ELContextCleaner - - - - - - - - org.eclipse.jetty.servlet.listener.IntrospectorCleaner - - - - - - - - - - - - - - - - - default - org.eclipse.jetty.servlet.DefaultServlet - - aliases - false - - - acceptRanges - true - - - dirAllowed - true - - - welcomeServlets - false - - - redirectWelcome - false - - - maxCacheSize - 256000000 - - - maxCachedFileSize - 200000000 - - - maxCachedFiles - 2048 - - - gzip - false - - - etags - false - - - useFileMappedBuffer - false - - - - 0 - - - - default - / - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - jsp - org.eclipse.jetty.jsp.JettyJspServlet - - logVerbosityLevel - DEBUG - - - fork - false - - - xpoweredBy - false - - - compilerTargetVM - 1.7 - - - compilerSourceVM - 1.7 - - - 0 - - - - jsp - *.jsp - *.jspf - *.jspx - *.xsp - *.JSP - *.JSPF - *.JSPX - *.XSP - - - - - - - - 30 - - - - - - - - - - - - - - - index.html - index.htm - index.jsp - - - - - - - - ar - ISO-8859-6 - - - be - ISO-8859-5 - - - bg - ISO-8859-5 - - - ca - ISO-8859-1 - - - cs - ISO-8859-2 - - - da - ISO-8859-1 - - - de - ISO-8859-1 - - - el - ISO-8859-7 - - - en - ISO-8859-1 - - - es - ISO-8859-1 - - - et - ISO-8859-1 - - - fi - ISO-8859-1 - - - fr - ISO-8859-1 - - - hr - ISO-8859-2 - - - hu - ISO-8859-2 - - - is - ISO-8859-1 - - - it - ISO-8859-1 - - - iw - ISO-8859-8 - - - ja - Shift_JIS - - - ko - EUC-KR - - - lt - ISO-8859-2 - - - lv - ISO-8859-2 - - - mk - ISO-8859-5 - - - nl - ISO-8859-1 - - - no - ISO-8859-1 - - - pl - ISO-8859-2 - - - pt - ISO-8859-1 - - - ro - ISO-8859-2 - - - ru - ISO-8859-5 - - - sh - ISO-8859-5 - - - sk - ISO-8859-2 - - - sl - ISO-8859-2 - - - sq - ISO-8859-2 - - - sr - ISO-8859-5 - - - sv - ISO-8859-1 - - - tr - ISO-8859-9 - - - uk - ISO-8859-5 - - - zh - GB2312 - - - zh_TW - Big5 - - - - - - - - - Disable TRACE - / - TRACE - - - - - - Enable everything but TRACE - / - TRACE - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/resources/logback.xml deleted file mode 100644 index 5b969d92..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jsp/src/test/resources/logback.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n - - - - - - - - logs/${FILE_NAME}-all.%d{yyyy-MM-dd}.log - 30 - - - - - 30MB - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n - - - - - - - - - - - - - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/pom.xml b/codes/javaee-tutorial/javaee-tutorial-jstl/pom.xml deleted file mode 100644 index cfb2c5db..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/pom.xml +++ /dev/null @@ -1,123 +0,0 @@ - - - 4.0.0 - - - io.github.dunwu - javaee-tutorial-jstl - 1.0.0 - war - - - - - javaee-tutorial-jstl - JavaEE 学习笔记之 jstl - - - - - - UTF-8 - 1.7 - ${java.version} - ${java.version} - - - - - - ch.qos.logback - logback-classic - - - org.slf4j - jcl-over-slf4j - - - - - - org.apache.commons - commons-lang3 - - - - - - javax.servlet - javax.servlet-api - provided - - - javax.servlet.jsp - javax.servlet.jsp-api - provided - - - javax.servlet - jstl - 1.2 - - - - - - org.eclipse.jetty - jetty-webapp - test - - - org.eclipse.jetty - jetty-annotations - test - - - org.eclipse.jetty - apache-jsp - test - - - org.eclipse.jetty - apache-jstl - test - - - - - - org.assertj - assertj-core - test - - - - - - - xalan - xalan - 2.7.2 - - - xerces - xercesImpl - 2.11.0 - - - - - - - - io.github.dunwu - dunwu-dependencies - 0.4.2-SNAPSHOT - pom - import - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/bean/Person.java b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/bean/Person.java deleted file mode 100644 index 36943d29..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/bean/Person.java +++ /dev/null @@ -1,107 +0,0 @@ -package io.github.dunwu.javaee.bean; - -public class Person { - - private int id; - - private String name; - - private String sex; - - private int age; - - private String telephone; - - private String birthday; - - private String mobile; - - private String address; - - private String city; - - private boolean deleted; - - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getSex() { - return sex; - } - - public void setSex(String sex) { - this.sex = sex; - } - - public int getAge() { - return age; - } - - public void setAge(int age) { - this.age = age; - } - - public boolean isDeleted() { - return deleted; - } - - public void setDeleted(boolean deleted) { - this.deleted = deleted; - } - - public String getTelephone() { - return telephone; - } - - public void setTelephone(String telephone) { - this.telephone = telephone; - } - - public String getBirthday() { - return birthday; - } - - public void setBirthday(String birthday) { - this.birthday = birthday; - } - - public String getMobile() { - return mobile; - } - - public void setMobile(String mobile) { - this.mobile = mobile; - } - - public String getAddress() { - return address; - } - - public void setAddress(String address) { - this.address = address; - } - - public String getCity() { - return city; - } - - public void setCity(String city) { - this.city = city; - } - -} - -// end diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/util/Example.java b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/util/Example.java deleted file mode 100644 index 9299f96e..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/util/Example.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.github.dunwu.javaee.util; - -import java.util.ListResourceBundle; - -public class Example extends ListResourceBundle { - - static final Object[][] contents = {{"count.one", "一"}, {"count.two", "二"}, {"count.three", "三"},} - - public Object[][] getContents() { - return contents; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/util/Example_es_ES.java b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/util/Example_es_ES.java deleted file mode 100644 index 00214af2..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/util/Example_es_ES.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.github.dunwu.javaee.util; - -import java.util.ListResourceBundle; - -public class Example_es_ES extends ListResourceBundle { - - static final Object[][] contents = {{"count.one", "one"}, {"count.two", "two"}, {"count.three", "three"},} - - public Object[][] getContents() { - return contents; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/util/Pagination.java b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/util/Pagination.java deleted file mode 100644 index e31e0526..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/java/io/github/dunwu/javaee/util/Pagination.java +++ /dev/null @@ -1,204 +0,0 @@ -package io.github.dunwu.javaee.util; - -import java.net.URLEncoder; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -public class Pagination { - - private int pageSize = 20; - - private int pageNum = 1; - - private int recordCount; - - private int pageCount; - - private int firstResult; - - private String pageUrl; - - public Pagination(HttpServletRequest request, HttpServletResponse response) { - try { - pageNum = Integer.parseInt(request.getParameter("pageNum")); - } catch (Exception e) { - } - - for (Cookie cookie : request.getCookies()) { - if ("pageSize".equals(cookie.getName())) { - try { - pageSize = Integer.parseInt(cookie.getValue()); - } catch (Exception e) { - } - } - } - - try { - pageSize = Integer.parseInt(request.getParameter("pageSize")); - } catch (Exception e) { - } - - Cookie cookie = new Cookie("pageSize", Integer.toString(pageSize)); - cookie.setMaxAge(Integer.MAX_VALUE); - - response.addCookie(cookie); - - StringBuffer queryString = new StringBuffer(); - - for (Object parameterName : request.getParameterMap().keySet()) { - String name = (String) parameterName; - - if ("pageNum".equals(name) || "pageSize".equals(name)) { - continue; - } - - for (String value : request.getParameterValues(name)) { - if (queryString.length() > 0) { - queryString.append("&"); - } - - try { - queryString.append(name + "=" + URLEncoder.encode(value, "UTF-8")); - } catch (Exception e) { - queryString.append(name + "=" + value); - } - } - } - - pageUrl = request.getRequestURI() + "?" + queryString.toString(); - } - - /** - * 生成分页信息 包括第一页,上一页,下一页,最后一页等等。 - */ - public String toString() { - calculate(); - - String url = pageUrl.contains("?") ? pageUrl : pageUrl + "?"; - - StringBuffer buffer = new StringBuffer(); - - buffer.append("每页 "); - - buffer.append(" 条记录 "); - - buffer.append(" 总记录数: " + recordCount); - - buffer.append(" 页数/总页数: " + pageNum + "/" + pageCount + " "); - - buffer.append(" "); - - buffer.append(pageCount == 0 || pageNum == 1 ? " 第一页 " : " 第一页 "); - - buffer.append("   "); - - buffer.append(pageCount == 0 || pageNum == 1 ? " 上一页 " - : " 上一页 "); - - buffer.append("   "); - - buffer.append(pageCount == 0 || pageNum == pageCount ? " 下一页 " - : " 下一页 "); - - buffer.append("   "); - - buffer.append(pageCount == 0 || pageNum == pageCount ? " 最后一页 " - : " 最后一页 "); - - buffer.append("   转到第页 "); - - buffer.append(" "); - - buffer.append(""); - - return buffer.toString(); - } - - private void calculate() { - pageCount = (recordCount + pageSize - 1) / pageSize; - - firstResult = (pageNum - 1) * pageSize; - } - - public int getPageSize() { - calculate(); - - return pageSize; - } - - public void setPageSize(int pageSize) { - calculate(); - - this.pageSize = pageSize; - } - - public int getRecordCount() { - calculate(); - - return recordCount; - } - - public void setRecordCount(int recordCount) { - calculate(); - - this.recordCount = recordCount; - } - - public int getFirstResult() { - calculate(); - - return firstResult; - } - - public void setFirstResult(int firstResult) { - calculate(); - - this.firstResult = firstResult; - } - - public String getPageUrl() { - return pageUrl + "&pageNum=" + pageNum; - } - - public void setPageUrl(String pageUrl) { - this.pageUrl = pageUrl; - } - -} - -// end diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/init.sql b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/init.sql deleted file mode 100644 index 562a8d72..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/init.sql +++ /dev/null @@ -1,45 +0,0 @@ -USE jstl; - -ALTER DATABASE jstl -CHARACTER SET utf8; - -SET NAMES gbk; - -CREATE TABLE tb_corporation ( - id INTEGER AUTO_INCREMENT, - name VARCHAR(255), - description TEXT, - PRIMARY KEY (id) -); - -INSERT INTO tb_corporation (name, description) VALUES ('MicroSoft', '微软'); -INSERT INTO tb_corporation (name, description) VALUES ('IBM', '国际商用机器'); -INSERT INTO tb_corporation (name, description) VALUES ('IBM', '国际商用机器'); -INSERT INTO tb_corporation (name, description) VALUES ('IBM', '国际商用机器'); -INSERT INTO tb_corporation (name, description) VALUES ('IBM', '国际商用机器'); -INSERT INTO tb_corporation (name, description) VALUES ('IBM', '国际商用机器'); -INSERT INTO tb_corporation (name, description) VALUES ('IBM', '国际商用机器'); -INSERT INTO tb_corporation (name, description) VALUES ('IBM', '国际商用机器'); -INSERT INTO tb_corporation (name, description) VALUES ('IBM', '国际商用机器'); -INSERT INTO tb_corporation (name, description) VALUES ('IBM', '国际商用机器'); -INSERT INTO tb_corporation (name, description) VALUES ('IBM', '国际商用机器'); -INSERT INTO tb_corporation (name, description) VALUES ('IBM', '国际商用机器'); -INSERT INTO tb_corporation (name, description) VALUES ('IBM', '国际商用机器'); -INSERT INTO tb_corporation (name, description) VALUES ('IBM', '国际商用机器'); -INSERT INTO tb_corporation (name, description) VALUES ('IBM', '国际商用机器'); -INSERT INTO tb_corporation (name, description) VALUES ('IBM', '国际商用机器'); -INSERT INTO tb_corporation (name, description) VALUES ('IBM', '国际商用机器'); -INSERT INTO tb_corporation (name, description) VALUES ('IBM', '国际商用机器'); -INSERT INTO tb_corporation (name, description) VALUES ('IBM', '国际商用机器'); -INSERT INTO tb_corporation (name, description) VALUES ('IBM', '国际商用机器'); -INSERT INTO tb_corporation (name, description) VALUES ('IBM', '国际商用机器'); -INSERT INTO tb_corporation (name, description) VALUES ('IBM', '国际商用机器'); -INSERT INTO tb_corporation (name, description) VALUES ('IBM', '国际商用机器'); -INSERT INTO tb_corporation (name, description) VALUES ('IBM', '国际商用机器'); -INSERT INTO tb_corporation (name, description) VALUES ('IBM', '国际商用机器'); -INSERT INTO tb_corporation (name, description) VALUES ('IBM', '国际商用机器'); -INSERT INTO tb_corporation (name, description) VALUES ('IBM', '国际商用机器'); -INSERT INTO tb_corporation (name, description) VALUES ('IBM', '国际商用机器'); -INSERT INTO tb_corporation (name, description) VALUES ('IBM', '国际商用机器'); -INSERT INTO tb_corporation (name, description) VALUES ('IBM', '国际商用机器'); - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/logback.xml deleted file mode 100644 index 5985be1e..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/logback.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n - - - - - - - - logs/${FILE_NAME}-all.%d{yyyy-MM-dd}.log - 30 - - - - - 30MB - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n - - - - - - - - - - - - - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/messages.properties b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/messages.properties deleted file mode 100644 index 8530dcc4..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/messages.properties +++ /dev/null @@ -1,2 +0,0 @@ -prompt.hello=Hello, "{0}". -prompt.greeting=Nice to meet you. diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/messages_zh_CN.properties b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/messages_zh_CN.properties deleted file mode 100644 index 1cec3305..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/messages_zh_CN.properties +++ /dev/null @@ -1,2 +0,0 @@ -prompt.hello=\u4f60\u597d, "{0}". -prompt.greeting=\u5f88\u9ad8\u5174\u89c1\u5230\u4f60. diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/sql/create_employees.sql b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/sql/create_employees.sql deleted file mode 100644 index 707cd5cb..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/sql/create_employees.sql +++ /dev/null @@ -1,12 +0,0 @@ -create table Employees -( - id int not null, - age int not null, - first varchar (255), - last varchar (255) -); - -INSERT INTO Employees VALUES (100, 18, 'Zara', 'Ali'); -INSERT INTO Employees VALUES (101, 25, 'Mahnaz', 'Fatma'); -INSERT INTO Employees VALUES (102, 30, 'Zaid', 'Khan'); -INSERT INTO Employees VALUES (103, 28, 'Sumit', 'Mittal'); \ No newline at end of file diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/sql/create_students.sql b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/sql/create_students.sql deleted file mode 100644 index a7a434d6..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/resources/sql/create_students.sql +++ /dev/null @@ -1,12 +0,0 @@ -create table Students -( - id int not null, - first varchar (255), - last varchar (255), - dob date -); - -INSERT INTO Students VALUES (100, 'Zara', 'Ali', '2002/05/16'); -INSERT INTO Students VALUES (101, 'Mahnaz', 'Fatma', '1978/11/28'); -INSERT INTO Students VALUES (102, 'Zaid', 'Khan', '1980/10/10'); -INSERT INTO Students VALUES (103, 'Sumit', 'Mittal', '1971/05/08'); \ No newline at end of file diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/WEB-INF/web.xml b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index a403cc53..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - HelloServlet - /examples/configuration.jsp - - message - welcome to jsp - - 1 - - - HelloServlet - /config - /config.jsp - - - - - /WEB-INF/views/jsp/index.jsp - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_catch.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_catch.jsp deleted file mode 100644 index 87fa389f..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_catch.jsp +++ /dev/null @@ -1,20 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - - - c:catch 标签实例 - - - - - <% int x = 5 / 0;%> - - - -

    异常为 : ${catchException}
    - 发生了异常: ${catchException.message}

    -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_choose.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_choose.jsp deleted file mode 100644 index 898147dc..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_choose.jsp +++ /dev/null @@ -1,23 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - - - c:choose 标签实例 - - - -

    你的工资为 :

    - - - 太惨了。 - - - 不错的薪水,还能生活。 - - - 什么都没有。 - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_forEach.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_forEach.jsp deleted file mode 100644 index 57b8421e..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_forEach.jsp +++ /dev/null @@ -1,13 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - - - c:forEach 标签实例 - - - -Item

    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_forTokens.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_forTokens.jsp deleted file mode 100644 index 046f4c49..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_forTokens.jsp +++ /dev/null @@ -1,13 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - - - c:forTokens 标签实例 - - - -

    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_if.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_if.jsp deleted file mode 100644 index 4abf6c52..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_if.jsp +++ /dev/null @@ -1,14 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - - - c:if 标签实例 - - - - -

    我的工资为:

    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_import.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_import.jsp deleted file mode 100644 index 98562585..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_import.jsp +++ /dev/null @@ -1,12 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - - - c:import 标签实例 - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_out.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_out.jsp deleted file mode 100644 index ab88b66f..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_out.jsp +++ /dev/null @@ -1,20 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - - - - c:out 标签实例 - - - - - <c:out>实例 - - -

    <c:out> 实例

    -
    -
    -使用的表达式结果为null,则输出该默认值
    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_param.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_param.jsp deleted file mode 100644 index 95438a73..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_param.jsp +++ /dev/null @@ -1,21 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - - - c:forTokens 标签实例 - - -

    <c:param> 实例

    - - - - -<%-- - - - -"> - 使用 <c:param> 为指定URL发送两个参数。--%> - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_redirect.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_redirect.jsp deleted file mode 100644 index fbfbf835..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_redirect.jsp +++ /dev/null @@ -1,11 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - - - c:redirect 标签实例 - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_remove.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_remove.jsp deleted file mode 100644 index 5d53732b..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_remove.jsp +++ /dev/null @@ -1,14 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - - - c:remove 标签实例 - - - -

    salary 变量值:

    - -

    删除 salary 变量后的值:

    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_set.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_set.jsp deleted file mode 100644 index e78c3bca..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_set.jsp +++ /dev/null @@ -1,12 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - - - c:set 标签实例 - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_url.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_url.jsp deleted file mode 100644 index d0e892cf..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/core/c_url.jsp +++ /dev/null @@ -1,14 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - - - c:url 标签实例 - - -

    <c:url>实例 Demo

    -"> - 这个链接通过 <c:url> 标签生成。 - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_bundle.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_bundle.jsp deleted file mode 100644 index b6e3211d..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_bundle.jsp +++ /dev/null @@ -1,18 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> - - - fmt:bundle 标签 - - - - -
    -
    -
    -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_formatDate.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_formatDate.jsp deleted file mode 100644 index 958f062c..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_formatDate.jsp +++ /dev/null @@ -1,33 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> - - - - fmt:dateNumber 标签 - - -

    日期格式化:

    - - -

    日期格式化 (1):

    -

    日期格式化 (2):

    -

    日期格式化 (3):

    -

    日期格式化 (4):

    -

    日期格式化 (5):

    -

    日期格式化 (6):

    -

    日期格式化 (7):

    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_formatNumber.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_formatNumber.jsp deleted file mode 100644 index 529449bb..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_formatNumber.jsp +++ /dev/null @@ -1,33 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> - - - - fmt:formatNumber 标签 - - -

    数字格式化:

    - -

    格式化数字 (1):

    -

    格式化数字 (2):

    -

    格式化数字 (3):

    -

    格式化数字 (4):

    -

    格式化数字 (5):

    -

    格式化数字 (6):

    -

    格式化数字 (7):

    -

    格式化数字 (8):

    -

    美元 : - -

    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_message.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_message.jsp deleted file mode 100644 index cac1b759..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_message.jsp +++ /dev/null @@ -1,26 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> - - - fmt:message 标签 - - - - -
    -
    -
    -
    - - - - -
    -
    -
    -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_parseDate.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_parseDate.jsp deleted file mode 100644 index a6d5241c..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_parseDate.jsp +++ /dev/null @@ -1,19 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> - - - - fmt:parseDate 标签 - - -

    日期解析:

    - - - -

    解析后的日期为:

    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_parseNumber.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_parseNumber.jsp deleted file mode 100644 index 4918be0d..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_parseNumber.jsp +++ /dev/null @@ -1,20 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> - - - fmt:parseNumber 标签 - - -

    数字解析:

    - - - -

    数字解析 (1) :

    - -

    数字解析 (2) :

    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_requestEncoding.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_requestEncoding.jsp deleted file mode 100644 index e81f2b3f..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_requestEncoding.jsp +++ /dev/null @@ -1,20 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> - - - fmt:message 标签 - - - - - - - -
    -
    -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_setLocale.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_setLocale.jsp deleted file mode 100644 index 6941dd40..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_setLocale.jsp +++ /dev/null @@ -1,26 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> - - - fmt:setLocale 标签 - - - - -
    -
    -
    -
    - - - - -
    -
    -
    -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_setTimeZone.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_setTimeZone.jsp deleted file mode 100644 index e05a7959..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_setTimeZone.jsp +++ /dev/null @@ -1,18 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> - - - fmt:setTimeZone 标签 - - - -

    当前时区时间:

    -

    修改为 GMT-8 时区:

    - -

    Date in Changed Zone:

    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_timeZone.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_timeZone.jsp deleted file mode 100644 index adb6f13b..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/fmt/fmt_timeZone.jsp +++ /dev/null @@ -1,42 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> - - - fmt:timeZone 标签 - - - - - - - - - - - - - - - -
    -

    - - Formatting: - - - -

    -
    - - - - - -
    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_contains.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_contains.jsp deleted file mode 100644 index 9ec50047..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_contains.jsp +++ /dev/null @@ -1,24 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - - - fn:contains 示例 - - - - - - -

    找到 china -

    - - - -

    找到 CHINA -

    - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_containsIgnoreCase.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_containsIgnoreCase.jsp deleted file mode 100644 index 512104b7..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_containsIgnoreCase.jsp +++ /dev/null @@ -1,24 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - - - 使用 JSTL 函数 - - - - - - -

    找到 china -

    - - - -

    找到 CHINA -

    - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_endsWith.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_endsWith.jsp deleted file mode 100644 index 8ec96d0e..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_endsWith.jsp +++ /dev/null @@ -1,24 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - - - 使用 JSTL 函数 - - - - - - -

    字符串以 123 结尾 -

    - - - -

    字符串以 china 结尾 -

    - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_escapeXml.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_escapeXml.jsp deleted file mode 100644 index d9509b06..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_escapeXml.jsp +++ /dev/null @@ -1,23 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - - - Using JSTL Functions - - - - - - -

    使用 escapeXml() 函数:

    -

    string (1) : ${fn:escapeXml(string1)}

    -

    string (2) : ${fn:escapeXml(string2)}

    - -

    不使用 escapeXml() 函数:

    -

    string (1) : ${string1}

    -

    string (2) : ${string2}

    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_indexOf.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_indexOf.jsp deleted file mode 100644 index d4896f7a..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_indexOf.jsp +++ /dev/null @@ -1,18 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - - - Using JSTL Functions - - - - - - -

    Index (1) : ${fn:indexOf(string1, "first")}

    -

    Index (2) : ${fn:indexOf(string2, "second")}

    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_join.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_join.jsp deleted file mode 100644 index aba2d96e..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_join.jsp +++ /dev/null @@ -1,18 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - - - 使用 JSTL 函数 - - - - - - - -

    字符串为 : ${string3}

    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_length.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_length.jsp deleted file mode 100644 index 0361946a..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_length.jsp +++ /dev/null @@ -1,19 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - - - 使用 JSTL 函数 - - - - - - -

    字符串长度 (1) : ${fn:length(string1)}

    -

    字符串长度 (2) : ${fn:length(string2)}

    - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_replace.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_replace.jsp deleted file mode 100644 index e5931747..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_replace.jsp +++ /dev/null @@ -1,19 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - - - 使用 JSTL 函数 - - - - - - -

    替换后的字符串 : ${string2}

    - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_split.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_split.jsp deleted file mode 100644 index 5ea82273..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_split.jsp +++ /dev/null @@ -1,23 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - - - 使用 JSTL 函数 - - - - - - - -

    string3 字符串 : ${string3}

    - - - - -

    string5 字符串: ${string5}

    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_startsWith.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_startsWith.jsp deleted file mode 100644 index 9879a840..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_startsWith.jsp +++ /dev/null @@ -1,21 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - - - 使用 JSTL 函数 - - - - - -

    字符串以 Google 开头

    -
    -
    - -

    字符串以 China 开头

    -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substring.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substring.jsp deleted file mode 100644 index 55f74fc4..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substring.jsp +++ /dev/null @@ -1,17 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - - - 使用 JSTL 函数 - - - - - - -

    生成的子字符串为 : ${string2}

    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substringAfter.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substringAfter.jsp deleted file mode 100644 index 0ec7b3f4..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substringAfter.jsp +++ /dev/null @@ -1,17 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - - - 使用 JSTL 函数 - - - - - - -

    生成的子字符串 : ${string2}

    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substringBefore.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substringBefore.jsp deleted file mode 100644 index c6967814..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_substringBefore.jsp +++ /dev/null @@ -1,17 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - - - 使用 JSTL 函数 - - - - - - -

    生成的子字符串 : ${string2}

    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_toLowerCase.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_toLowerCase.jsp deleted file mode 100644 index 804d856d..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_toLowerCase.jsp +++ /dev/null @@ -1,17 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - - - 使用 JSTL 函数 - - - - - - -

    字符串为 : ${string2}

    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_toUpperCase.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_toUpperCase.jsp deleted file mode 100644 index 3ea5988a..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_toUpperCase.jsp +++ /dev/null @@ -1,17 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - - - 使用 JSTL 函数 - - - - - - -

    字符串为 : ${string2}

    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_trim.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_trim.jsp deleted file mode 100644 index 1ac0e961..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/function/fn_trim.jsp +++ /dev/null @@ -1,19 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - - - 使用 JSTL 函数 - - - - -

    string1 长度 : ${fn:length(string1)}

    - - -

    string2 长度 : ${fn:length(string2)}

    -

    字符串为 : ${string2}

    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_dateParam.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_dateParam.jsp deleted file mode 100644 index a24b0770..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_dateParam.jsp +++ /dev/null @@ -1,52 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ page import="java.util.Date" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %> - -<%--执行本例之前,需要先运行 sql/create_students.sql--%> - - - - sql:dataParam 示例 - - - - - -<% - Date DoB = new Date("2001/12/16"); - int studentId = 100; -%> - - - UPDATE Students SET dob = ? WHERE Id = ? - - - - - - SELECT * from Students; - - - - - - - - - - - - - - - - - -
    Emp IDFirst NameLast NameDoB
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_param.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_param.jsp deleted file mode 100644 index 4c53b6d6..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_param.jsp +++ /dev/null @@ -1,47 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %> - -<%--运行本例之前,先执行sql/create_employees.sql--%> - - - - sql:param 示例 - - - - - - - - - DELETE FROM Employees WHERE Id = ? - - - - - SELECT * from Employees; - - - - - - - - - - - - - - - - - -
    Emp IDFirst NameLast NameAge
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_query.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_query.jsp deleted file mode 100644 index 41d515fe..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_query.jsp +++ /dev/null @@ -1,40 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %> - -<%--运行本例之前,先执行sql/create_employees.sql--%> - - - - sql:query 示例 - - - - - - - SELECT * from test.Employees; - - - - - - - - - - - - - - - - - -
    Emp IDFirst NameLast NameAge
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_setDataSource.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_setDataSource.jsp deleted file mode 100644 index 73986187..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_setDataSource.jsp +++ /dev/null @@ -1,18 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %> - - - sql:setDataSource 示例 - - - - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_transaction.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_transaction.jsp deleted file mode 100644 index f792744a..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_transaction.jsp +++ /dev/null @@ -1,60 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ page import="java.util.Date" %> - -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %> - -<%--执行本例之前,需要先运行 sql/create_students.sql--%> - - - - sql:transaction 示例 - - - - - -<% - Date DoB = new Date("2001/12/16"); - int studentId = 100; -%> - - - - UPDATE Students SET last = 'Ali' WHERE Id = 102 - - - UPDATE Students SET last = 'Shah' WHERE Id = 103 - - - INSERT INTO Students - VALUES (104,'Nuha', 'Ali', '2010/05/26'); - - - - - SELECT * from Students; - - - - - - - - - - - - - - - - - -
    Emp IDFirst NameLast NameDoB
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_update.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_update.jsp deleted file mode 100644 index 651ae8d4..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/sql/sql_update.jsp +++ /dev/null @@ -1,44 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %> - -<%--运行本例之前,先执行sql/create_employees.sql--%> - - - - sql:update 示例 - - - - - - - INSERT INTO Employees VALUES (104, 2, 'Nuha', 'Ali'); - - - - SELECT * from Employees; - - - - - - - - - - - - - - - - - -
    Emp IDFirst NameLast NameAge
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/bundle.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/bundle.jsp deleted file mode 100644 index 77cd39f6..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/bundle.jsp +++ /dev/null @@ -1,41 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> - - - - - Insert title here - - - - - - - - - -
    - - - -
    - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/catch.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/catch.jsp deleted file mode 100644 index 4a502a96..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/catch.jsp +++ /dev/null @@ -1,26 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - - - - - Insert title here - - - - - - - - - - 程序抛出了异常 ${ e.class.name },原因: ${ e.message } - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/choose.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/choose.jsp deleted file mode 100644 index 69d457a7..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/choose.jsp +++ /dev/null @@ -1,21 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - - - - - Insert title here - - - - - - when 标签的输出 - - - otherwise 标签的输出 - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/contains.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/contains.jsp deleted file mode 100644 index 98e1b491..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/contains.jsp +++ /dev/null @@ -1,35 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - - - - - Insert title here - - - - -header['User-Agent'] = "${ header['User-Agent'] }";

    - -您使用 -IE 浏览器 -Firefox 浏览器 -Maxth 浏览器 -MyIE2 浏览器 -Opera 浏览器 -腾讯 Traveler 浏览器 -世界之窗 浏览器 -Kubuntu 浏览器 -, -Windows 操作系统 -Windows 操作系统 -Linux 操作系统 -Linux 操作系统 -Sun 操作系统 -Mac 操作系统 -。 - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/containsIgnoreCase.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/containsIgnoreCase.jsp deleted file mode 100644 index f63fc4c2..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/containsIgnoreCase.jsp +++ /dev/null @@ -1,33 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - - - - - Insert title here - - - - -header['User-Agent'] = "${ header['User-Agent'] }";

    - -您使用 -IE 浏览器 -Firefox 浏览器 -Maxth 浏览器 -MyIE2 浏览器 -Opera 浏览器 -腾讯 Traveler 浏览器 -世界之窗 浏览器 -Kubuntu 浏览器 -, -Windows 操作系统 -Linux 操作系统 -Sun 操作系统 -Mac 操作系统 -。 - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/dateParam.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/dateParam.jsp deleted file mode 100644 index 71223df1..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/dateParam.jsp +++ /dev/null @@ -1,80 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - - - - - Insert title here - - - - - - - - - - create table if not exists tb_person - ( id integer auto_increment, - name varchar(255), - birthday timestamp null, - primary key (id) - ) - - - - insert into tb_person ( name, birthday ) values ( ?, ? ) - - - - - - select * from tb_person where birthday > ( ? - 1 ) - - - - - - - - - - - - - - - - - - -
    ${ columnName }
    ${ row[columnName] }
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/endsWith.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/endsWith.jsp deleted file mode 100644 index 588a50b6..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/endsWith.jsp +++ /dev/null @@ -1,70 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - -<%@page import="java.io.File" %> - - - - Insert title here - - - - -<% - request.setAttribute("files", new File("c:\\").listFiles()); -%> - - - - - - - - - - - - - - -
    File NameType
    ${ file.name } - - - 文件夹 - - JPG 图片 - EXE 应用程序 - GIF 图片 - TXT 文本文件 - WORD 文件 - Excel 文件 - LOG 日志文件 - SQL 数据库脚本文件 - - - -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/escapeXml.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/escapeXml.jsp deleted file mode 100644 index fb35be08..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/escapeXml.jsp +++ /dev/null @@ -1,21 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - - - - - Insert title here - - - - - - -
    -${ fn:escapeXml(source) } -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/fmt.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/fmt.jsp deleted file mode 100644 index c237093e..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/fmt.jsp +++ /dev/null @@ -1,14 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> - - - - - Insert title here - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/fn.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/fn.jsp deleted file mode 100644 index cfa2276c..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/fn.jsp +++ /dev/null @@ -1,14 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - - - - - Insert title here - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forEach.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forEach.jsp deleted file mode 100644 index f8eeeb8f..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forEach.jsp +++ /dev/null @@ -1,76 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - - - - - Insert title here - - - - - -
    ${ num }
    -
    - -
    -
    -
    - - - - - - - - - - - - - -
    Header NameHeader Value
    ${ headerName }${ header[headerName] }
    - -
    -
    -
    - - - - - - - - - - - - - -
    Header NameHeader Value
    ${ item.key }${ item.value }
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forEachWithList.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forEachWithList.jsp deleted file mode 100644 index e4e54bce..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forEachWithList.jsp +++ /dev/null @@ -1,196 +0,0 @@ -<%@ page language="java" import="com.helloweenvsfei.jstl.bean.*, java.util.ArrayList" - contentType="text/html; charset=UTF-8" %> -<%@ page import="java.util.List" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<% - List personList = new ArrayList(); - - int i = 1; - - Person person = new Person(); - person.setId(i++); - person.setName("张三"); - person.setAge(20); - person.setSex("男"); - person.setAddress("北京市海淀区上地软件园"); - person.setBirthday("2008-08-08"); - person.setMobile("13820080808"); - person.setTelephone("69653234"); - person.setCity("北京"); - - personList.add(person); - - Person person2 = new Person(); - person2.setId(i++); - person2.setName("李四"); - person2.setAge(20); - person2.setSex("男"); - person2.setAddress("北京市东皇城根锡拉胡同"); - person2.setBirthday("2008-01-01"); - person2.setMobile("13820080808"); - person2.setTelephone("20054879"); - person2.setCity("北京"); - - personList.add(person2); - - Person person3 = new Person(); - person3.setId(i++); - person3.setName("王五"); - person3.setAge(20); - person3.setSex("男"); - person3.setAddress("北京市东皇城根锡拉胡同"); - person3.setBirthday("2008-01-01"); - person3.setMobile("13820080808"); - person3.setTelephone("20054879"); - person3.setCity("北京"); - - personList.add(person3); - - Person person4 = new Person(); - person4.setId(i++); - person4.setName("王二麻子"); - person4.setAge(20); - person4.setSex("男"); - person4.setAddress("北京市东皇城根锡拉胡同"); - person4.setBirthday("2008-01-01"); - person4.setMobile("13820080808"); - person4.setTelephone("20054879"); - person4.setCity("北京"); - - personList.add(person4); - - Person person5 = new Person(); - person5.setId(i++); - person5.setName("王二麻子"); - person5.setAge(20); - person5.setSex("男"); - person5.setAddress("北京市东皇城根锡拉胡同"); - person5.setBirthday("2008-01-01"); - person5.setMobile("13820080808"); - person5.setTelephone("20054879"); - person5.setCity("北京"); - - personList.add(person5); - - Person person6 = new Person(); - person6.setId(i++); - person6.setName("王二麻子"); - person6.setAge(20); - person6.setSex("男"); - person6.setAddress("北京市东皇城根锡拉胡同"); - person6.setBirthday("2008-01-01"); - person6.setMobile("13820080808"); - person6.setTelephone("20054879"); - person6.setCity("北京"); - - personList.add(person6); - - Person person7 = new Person(); - person7.setId(i++); - person7.setName("王二麻子"); - person7.setAge(20); - person7.setSex("男"); - person7.setAddress("北京市东皇城根锡拉胡同"); - person7.setBirthday("2008-01-01"); - person7.setMobile("13820080808"); - person7.setTelephone("20054879"); - person7.setCity("北京"); - - personList.add(person7); - - request.setAttribute("personList", personList); - -%> - - - - - Insert title here - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    编号姓名年龄性别城市地址生日手机电话
    ${ person.id }${ person.name }${ person.age }${ person.sex }${ person.city }${ person.address }${ person.birthday }${ person.mobile }${ person.telephone }
    - -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    编号姓名年龄性别城市地址生日手机电话
    ${ varStatus.current.id }${ varStatus.current.name }${ varStatus.current.age }${ varStatus.current.sex }${ varStatus.current.city }${ varStatus.current.address }${ varStatus.current.birthday }${ varStatus.current.mobile }${ varStatus.current.telephone }
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forTokens.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forTokens.jsp deleted file mode 100644 index f0c9e9cd..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/forTokens.jsp +++ /dev/null @@ -1,39 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - - - - - Insert title here - - - - - - - - - - - - - - - -
    varStatus.indexname
    ${ varStatus.index }${ item }
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/formatDate.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/formatDate.jsp deleted file mode 100644 index bf224c8d..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/formatDate.jsp +++ /dev/null @@ -1,77 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> - -<%@page import="java.lang.reflect.Field" %> -<%@page import="java.util.ArrayList" %> -<%@page import="java.util.List" %> -<%@page import="java.util.Locale" %> - - - - Insert title here - - - - -<% - Field[] field = Locale.class.getFields(); - - List localeList = new ArrayList(); - - for (int i = 0; i < field.length; i++) { - if (field[i].getType().equals(Locale.class)) { - localeList.add((Locale) field[i].get(null)); - } - } - - request.setAttribute("localeList", localeList); -%> - - - - - - - - - - - - - - - - - - - - - - -
    LocaleDate and TimeNumbercurrency
    ${ locale }
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/formatNumber.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/formatNumber.jsp deleted file mode 100644 index 55306342..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/formatNumber.jsp +++ /dev/null @@ -1,89 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> - -<%@page import="java.lang.reflect.Field" %> -<%@page import="java.util.ArrayList" %> -<%@page import="java.util.List" %> -<%@page import="java.util.Locale" %> - - - - Insert title here - - - - -<% - Field[] field = Locale.class.getFields(); - - List localeList = new ArrayList(); - - for (int i = 0; i < field.length; i++) { - if (field[i].getType().equals(Locale.class)) { - localeList.add((Locale) field[i].get(null)); - } - } - - request.setAttribute("localeList", localeList); - - double[] numbers = {0, 10000, 55.0, -123.2568}; - request.setAttribute("numbers", numbers); -%> - - - -当前格式: - -  - - - ${ locale }  - - - - - - - - - - - - - - - - - - - -
    数字原值数字格式货币格式百分数格式
    ${ number }
    - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/if.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/if.jsp deleted file mode 100644 index 7e8910e1..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/if.jsp +++ /dev/null @@ -1,56 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - - - - - - - - - -
    - - - 添加操作 - - - - - - - - - -
    帐号
    真实姓名
    -
    - - 修改操作 - - - - - - - - - -
    帐号
    真实姓名
    -
    - - - - - -
    - - -
    - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/import.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/import.jsp deleted file mode 100644 index d1c07771..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/import.jsp +++ /dev/null @@ -1,25 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - - - - - Insert title here - - - - - - - -Baidu 的源代码为: - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/index.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/index.jsp deleted file mode 100644 index 938d29b0..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/index.jsp +++ /dev/null @@ -1,16 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - - - - - Insert title here - - - -index.jsp - -Test - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/indexOf.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/indexOf.jsp deleted file mode 100644 index 7d8293ab..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/indexOf.jsp +++ /dev/null @@ -1,16 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - - - - - Insert title here - - - -fn:indexOf('filename.txt', '.') = ${ fn:indexOf('filename.txt', '.') } - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/join.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/join.jsp deleted file mode 100644 index 29971147..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/join.jsp +++ /dev/null @@ -1,20 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - - - - - Insert title here - - - -<% - request.setAttribute("array", new String[] {"John", "Tom", "Tommi", "Kurt",}); -%> - -${ fn:join(array, '; ') } - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/length.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/length.jsp deleted file mode 100644 index 1972e925..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/length.jsp +++ /dev/null @@ -1,18 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - - - - - Insert title here - - - -"${ pageContext.request.requestURL }" 的长度:${ fn:length(pageContext.request.requestURI) }
    -Cookie[] 的长度:${ fn:length(pageContext.request.cookies) }
    -Map header 的长度: ${ fn:length(header) }
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/out.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/out.jsp deleted file mode 100644 index a72e6d0c..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/out.jsp +++ /dev/null @@ -1,14 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - - - - - Insert title here - - - -action 参数为: - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/param.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/param.jsp deleted file mode 100644 index e378bd06..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/param.jsp +++ /dev/null @@ -1,17 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=GBK" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - - - - - Insert title here - - - - - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parse.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parse.jsp deleted file mode 100644 index 301b736e..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parse.jsp +++ /dev/null @@ -1,51 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/xml" prefix="x" %> - - - - - Insert title here - - - - - - -
    - -
    - - - -新浪 RSS
    -版本:
    -标题:
    -来源:
    -版权:
    -出版时间:
    -链接地址:
    - - - - Helloween - 20 - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parseDate.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parseDate.jsp deleted file mode 100644 index 56aba204..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parseDate.jsp +++ /dev/null @@ -1,16 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> - - - - - Insert title here - - - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parseNumber.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parseNumber.jsp deleted file mode 100644 index 8c6bc209..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/parseNumber.jsp +++ /dev/null @@ -1,16 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> - - - - - Insert title here - - - -
    -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/query.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/query.jsp deleted file mode 100644 index a1428b47..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/query.jsp +++ /dev/null @@ -1,57 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - - - - - Insert title here - - - - - - - - - - - - - - - - - - - - - - - - -
    IDNameDescription
    ${ row['id'] }${ row['name'] }${ row['description'] }
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/queryPagination.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/queryPagination.jsp deleted file mode 100644 index 64cde5e5..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/queryPagination.jsp +++ /dev/null @@ -1,79 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - -<%@page import="com.helloweenvsfei.util.Pagination" %> - - - - Insert title here - - - - -<% - request.setAttribute("pagination", new Pagination(request, response)); -%> - - - - - SELECT count(*) count FROM help_topic - - - - - - - - SELECT * FROM help_topic - - - - - - - - - - - - - - - - - -
    Help_IDNameDescription
    ${ row['help_topic_id'] }${ row['name'] }${ row['description'] }
    -
    -${ pagination } - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/queryReflect.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/queryReflect.jsp deleted file mode 100644 index 43e26dbe..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/queryReflect.jsp +++ /dev/null @@ -1,75 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - - - - - Insert title here - - - - -
    -
    - -
    - - - - - - - ${ param.sql } - - - - - - - - - - - - - - - - - -
    ${ columnName }
    ${ row[columnName] }
    - -
    - - -
    Exception: ${ e.message }
    -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/redirect.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/redirect.jsp deleted file mode 100644 index 8ac64b11..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/redirect.jsp +++ /dev/null @@ -1,14 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - - - - - Insert title here - - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/remove.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/remove.jsp deleted file mode 100644 index 74a5e512..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/remove.jsp +++ /dev/null @@ -1,21 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - -<%@page import="java.util.HashMap" %> - - - - Insert title here - - - -<% - request.setAttribute("somemap", new HashMap()); -%> - - - -${ somemap == null ? 'somemap 已经被删除' : 'somemap 没有被删除' } - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/requestEncoding.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/requestEncoding.jsp deleted file mode 100644 index 8c62bad5..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/requestEncoding.jsp +++ /dev/null @@ -1,26 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> - - - - - Insert title here - - - - - - -
    - 关键字: -

    - -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/set.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/set.jsp deleted file mode 100644 index cfe7d922..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/set.jsp +++ /dev/null @@ -1,34 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - - - - - Insert title here - - - - - -本网站总访问人次:${ totalCount }
    -其中您的访问次数:${ count }
    - - -by body - -
    -
    -
    -
    -<% - request.setAttribute("person", new com.helloweenvsfei.jstl.bean.Person()); - request.setAttribute("map", new java.util.HashMap()); -%> - -${ person.name } - - -${ map.name } - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setBundle.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setBundle.jsp deleted file mode 100644 index a702dd29..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setBundle.jsp +++ /dev/null @@ -1,23 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> - - - - - Insert title here - - - - - - - - - Helloween -
    - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setDataSource.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setDataSource.jsp deleted file mode 100644 index bf4fc563..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setDataSource.jsp +++ /dev/null @@ -1,27 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - - - - - Insert title here - - - - - - - -数据源:${ dataSource.class.name } - -${ serverDataSource } - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setLocale.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setLocale.jsp deleted file mode 100644 index b077fe91..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setLocale.jsp +++ /dev/null @@ -1,68 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> - -<%@page import="java.util.Locale" %> - - - - Insert title here - - - - -<% - request.setAttribute("localeList", Locale.getAvailableLocales()); -%> - - - - - - - - - - - - - - - - - - - - - - - - -
    LocaleLanguageDate and TimeNumbercurrency
    ${ locale.displayName }${ locale.displayLanguage }
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setTimeZone.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setTimeZone.jsp deleted file mode 100644 index 2e3f0dde..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/setTimeZone.jsp +++ /dev/null @@ -1,43 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> - - - - - Insert title here - - - - - - - - -现在时刻:北京时间 -
    - - -
    - GMT${ i>=12 ? '+' : '' }${ i-12 } : - - - -
    -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/sina.xml b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/sina.xml deleted file mode 100644 index 772fca53..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/sina.xml +++ /dev/null @@ -1,300 +0,0 @@ - - - - - - - <![CDATA[新闻中心-新闻要闻]]> - - - - <![CDATA[新闻中心]]> - - http://news.sina.com.cn - http://www.sinaimg.cn/dy/sina_news626.gif - - - - - http://news.sina.com.cn/iframe/o/allnews/input/index.htm - zh-cn - WWW.SINA.COM.CN - 5 - - - - Fri, 30 Nov 2007 05:25:01 GMT - - - - - - <![CDATA[视频-联盟杯拜仁紫百合惊险战平 克洛斯笑纳空门]]> - - http://video.sina.com.cn/sports/g/bn/2007-11-30/13236492.shtml - WWW.SINA.COM.CN - http://video.sina.com.cn/sports/g/bn/2007-11-30/13236492.shtml - - - - Fri, 30 Nov 2007 05:23:53 GMT - - - - - - - - <![CDATA[18律师声援遭诱捕浏阳商人]]> - - http://finance.sina.com.cn/column/complain/20071130/13234236791.shtml - WWW.SINA.COM.CN - http://finance.sina.com.cn/column/complain/20071130/13234236791.shtml - - - - Fri, 30 Nov 2007 05:23:08 GMT - - - - - - - - <![CDATA[视频-马竞必杀定位球降服弱旅 幸运之神眷顾纽伦堡]]> - - http://video.sina.com.cn/sports/g/bn/2007-11-30/13206491.shtml - WWW.SINA.COM.CN - http://video.sina.com.cn/sports/g/bn/2007-11-30/13206491.shtml - - - - Fri, 30 Nov 2007 05:20:23 GMT - - - - - - - - <![CDATA[[泰国橡胶]USS3橡胶现货价格下跌,因基本面疲弱]]> - - http://finance.sina.com.cn/money/future/20071130/13204236790.shtml - WWW.SINA.COM.CN - http://finance.sina.com.cn/money/future/20071130/13204236790.shtml - - - - Fri, 30 Nov 2007 05:20:17 GMT - - - - - - - - <![CDATA[[马来棕榈油]2007年11月30日午马来西亚棕榈油现货行情]]> - - http://finance.sina.com.cn/money/future/20071130/13204236789.shtml - WWW.SINA.COM.CN - http://finance.sina.com.cn/money/future/20071130/13204236789.shtml - - - - Fri, 30 Nov 2007 05:20:13 GMT - - - - - - - - <![CDATA[马来西亚BMD毛棕榈油期货午盘下滑,市场等待出口预估数据]]> - - http://finance.sina.com.cn/money/future/20071130/13204236788.shtml - WWW.SINA.COM.CN - http://finance.sina.com.cn/money/future/20071130/13204236788.shtml - - - - Fri, 30 Nov 2007 05:20:04 GMT - - - - - - - - <![CDATA[[机构看盘]集成利期货:郑糖探低回升中幅收跌,短线压力依旧较大]]> - - http://finance.sina.com.cn/money/future/20071130/13194236787.shtml - WWW.SINA.COM.CN - http://finance.sina.com.cn/money/future/20071130/13194236787.shtml - - - - Fri, 30 Nov 2007 05:19:56 GMT - - - - - - - - <![CDATA[[现货行情]11月30日广西糖网食糖批发市场收市行情]]> - - http://finance.sina.com.cn/money/future/20071130/13194236786.shtml - WWW.SINA.COM.CN - http://finance.sina.com.cn/money/future/20071130/13194236786.shtml - - - - Fri, 30 Nov 2007 05:19:52 GMT - - - - - - - - <![CDATA[[现货行情]11月30日昆明商品中心批发市场收市行情]]> - - http://finance.sina.com.cn/money/future/20071130/13194236785.shtml - WWW.SINA.COM.CN - http://finance.sina.com.cn/money/future/20071130/13194236785.shtml - - - - Fri, 30 Nov 2007 05:19:48 GMT - - - - - - - - <![CDATA[长沙百亿企业添新]]> - - http://finance.sina.com.cn/china/dfjj/20071130/13194236784.shtml - WWW.SINA.COM.CN - http://finance.sina.com.cn/china/dfjj/20071130/13194236784.shtml - - - - Fri, 30 Nov 2007 05:19:41 GMT - - - - - - - - <![CDATA[新代码启用 垃圾短信难隐形]]> - - http://tech.sina.com.cn/t/2007-11-30/13191884840.shtml - WWW.SINA.COM.CN - http://tech.sina.com.cn/t/2007-11-30/13191884840.shtml - - - - Fri, 30 Nov 2007 05:19:36 GMT - - - - - - - - <![CDATA[至酷腕表型MP3 松下MP130V仅400元甩卖]]> - - http://tech.sina.com.cn/digi/mp3/2007-11-30/13191884839.shtml - WWW.SINA.COM.CN - http://tech.sina.com.cn/digi/mp3/2007-11-30/13191884839.shtml - - - - Fri, 30 Nov 2007 05:19:12 GMT - - - - - - - - <![CDATA[稳定成品油市场供应加强价格监管]]> - - http://finance.sina.com.cn/g/20071130/13184236783.shtml - WWW.SINA.COM.CN - http://finance.sina.com.cn/g/20071130/13184236783.shtml - - - - Fri, 30 Nov 2007 05:18:42 GMT - - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/split.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/split.jsp deleted file mode 100644 index a418e32d..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/split.jsp +++ /dev/null @@ -1,21 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - - - - - Insert title here - - -${ header['accept'] } - - -header['accept']:

    - - ${ name }
    -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/sql.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/sql.jsp deleted file mode 100644 index cad2d4b0..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/sql.jsp +++ /dev/null @@ -1,16 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - - - - - Insert title here - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/substring.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/substring.jsp deleted file mode 100644 index d1e5af91..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/substring.jsp +++ /dev/null @@ -1,21 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - - - - - Insert title here - - - -liujhua@cn.ibm.com -${ fn:substring(email, 0, fn:indexOf(email, '@') ) } - -${ fn:substringBefore(email, '@') } - -${ fn:substringAfter(email, '@') } - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/timeZone.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/timeZone.jsp deleted file mode 100644 index e3521846..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/timeZone.jsp +++ /dev/null @@ -1,81 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> - - - -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> - - - - - Insert title here - - - - -<% - Map hashMap = new HashMap(); - - for (String ID : TimeZone.getAvailableIDs()) { - hashMap.put(ID, TimeZone.getTimeZone(ID)); - } - - request.setAttribute("timeZoneIds", TimeZone.getAvailableIDs()); - request.setAttribute("timeZone", hashMap); -%> - - - - - -现在时刻:<%= TimeZone.getDefault().getDisplayName() %> -
    - - - - - - - - - - - - - - - - - -
    时区ID时区现在时间时差
    ${ ID }${ timeZone[ID].displayName } - - - - ${ timeZone[ID].rawOffset / 60 / 60 / 1000 }
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/transaction.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/transaction.jsp deleted file mode 100644 index 388df0d8..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/transaction.jsp +++ /dev/null @@ -1,91 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - - - - - Insert title here - - - - - - - - - - - - - insert into tb_corporation ( name, description ) values ('事务测试', '事务测试') - - - 已插入一条。
    -
    - - - - insert into tb_corporation ( id, name, description ) values (1, '事务测试', '事务测试') - - -
    - -
    - - -
    操作异常,原因:${ e.message }。事务已经回滚。
    -
    - - - select * from tb_corporation - - - - - - - - - - - - - - - - - -
    ${ columnName }
    ${ row[columnName] }
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/update.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/update.jsp deleted file mode 100644 index 3e03c34e..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/update.jsp +++ /dev/null @@ -1,65 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - - - - - Insert title here - - - - - - - - drop table if exists tb_corporation - - -DROP TABLE, 影响到的数据条数:${ result }
    - - - create table tb_corporation ( - id integer auto_increment, - name varchar(255), - description text, - primary key(id) - ) - - -CREATE TABLE, 影响到的数据条数:${ result }
    - - - insert into tb_corporation ( name, description ) values ('MicroSoft', '微软') - - -INSERT, 影响到的数据条数:${ result }
    - - - insert into tb_corporation ( name, description ) values ('IBM', '国际商用机器') - - -INSERT, 影响到的数据条数:${ result }
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/url.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/url.jsp deleted file mode 100644 index 099f71ff..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/url.jsp +++ /dev/null @@ -1,15 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> - - - - - Insert title here - - - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_choose.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_choose.jsp deleted file mode 100644 index f2169bc8..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_choose.jsp +++ /dev/null @@ -1,43 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/xml" prefix="x" %> - - - - - Insert title here - - - - - - -
    - - - - - - 使用了 JNDI 属性。 - - - 没有使用 JNDI 属性。 - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_forEach.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_forEach.jsp deleted file mode 100644 index fdb7ef5d..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_forEach.jsp +++ /dev/null @@ -1,44 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/xml" prefix="x" %> - - - - - Insert title here - - - - - - -
    - -
    - - - - - ${ status.count }. - " target="_blank">  -
    -

    - -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_if.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_if.jsp deleted file mode 100644 index 96521079..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_if.jsp +++ /dev/null @@ -1,42 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/xml" prefix="x" %> - - - - - Insert title here - - - - - - -
    - - - - - 属性 Driver Class Name 存在: .
    -
    - - - 属性 Driver Class Name 不存在。 - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_out.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_out.jsp deleted file mode 100644 index 6ba9e20a..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_out.jsp +++ /dev/null @@ -1,25 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/xml" prefix="x" %> - - - - - Insert title here - - - - - - Helloween - 20 - - - -content: ${ content }
    - -输出属性 description:
    -输出元素 name:
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_set.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_set.jsp deleted file mode 100644 index 5deff358..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_set.jsp +++ /dev/null @@ -1,38 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/xml" prefix="x" %> - - - - - Insert title here - - - - - - -
    - - - - - -Driver Class Name: - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_transform.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_transform.jsp deleted file mode 100644 index ff902f9a..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/x_transform.jsp +++ /dev/null @@ -1,107 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/xml" prefix="x" %> - - - - - Insert title here - - - - - - - - - - 1 - 张三 - zhangsan@host.com - Software Engine - - - 2 - 李四 - lisi@somehost.com - Sales - - - 3 - 王五 - wangwu@someweb.com - Manager - - - - - - - - - - - - - - - - - - - - - - - - -
    编号姓名电子邮件描述
    -
    -
    - -
    - -
    - -
    - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/xml.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/xml.jsp deleted file mode 100644 index db0f4a94..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/uncheck/xml.jsp +++ /dev/null @@ -1,25 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/xml" prefix="x" %> - - - - - Insert title here - - - - - - Helloween - 20 - - - -Description:
    -Name:
    -age:
    - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_choose.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_choose.jsp deleted file mode 100644 index d4261640..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_choose.jsp +++ /dev/null @@ -1,42 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -<%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %> - - - - x:choose 标签 - - -

    Books Info:

    - - - - - Padam History - ZARA - 100 - - - Great Mistry - NUHA - 2000 - - - - - - - - Book is written by ZARA - - - Book is written by NUHA - - - Unknown author. - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_forEach.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_forEach.jsp deleted file mode 100644 index 81dc6762..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_forEach.jsp +++ /dev/null @@ -1,36 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -<%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %> - - - - x:forEach 标签 - - -

    Books Info:

    - - - - - Padam History - ZARA - 100 - - - Great Mistry - NUHA - 2000 - - - - - -
      - -
    • Book Name:
    • -
      -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_if.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_if.jsp deleted file mode 100644 index 6222d2f8..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_if.jsp +++ /dev/null @@ -1,40 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -<%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %> - - - - x:if 标签 - - -

    Books Info:

    - - - - - Padam History - ZARA - 100 - - - Great Mistry - NUHA - 2000 - - - - - - - -Document has at least one - element. - -
    - - Book prices are very high - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_out.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_out.jsp deleted file mode 100644 index 11924dbd..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_out.jsp +++ /dev/null @@ -1,35 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -<%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %> - - - - x:out 标签 - - -

    Books Info:

    - - - - - Padam History - ZARA - 100 - - - Great Mistry - NUHA - 2000 - - - - - -The title of the first book is: - -
    -The price of the second book: - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_param.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_param.jsp deleted file mode 100644 index e7701603..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_param.jsp +++ /dev/null @@ -1,33 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -<%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %> - - - - x:param 标签 - - -

    Books Info:

    - - - - Padam History - ZARA - 100 - - - Great Mistry - NUHA - 2000 - - - - - - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_parse.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_parse.jsp deleted file mode 100644 index 22aca6a5..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_parse.jsp +++ /dev/null @@ -1,22 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -<%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %> - - - - x:parse 标签 - - -

    Books Info:

    - - - -The title of the first book is: - -
    -The price of the second book: - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_set.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_set.jsp deleted file mode 100644 index 2c0617df..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_set.jsp +++ /dev/null @@ -1,31 +0,0 @@ -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -<%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %> - - - - x:set 标签 - - -

    Books Info:

    - - - - - Padam History - ZARA - 100 - - - Great Mistry - NUHA - 2000 - - - - - - -The price of the second book: - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_transform.jsp b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_transform.jsp deleted file mode 100644 index c8bc4075..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/examples/xml/x_transform.jsp +++ /dev/null @@ -1,31 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -<%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %> - - - - x:transform 标签 - - -

    Books Info:

    - - - - Padam History - ZARA - 100 - - - Great Mistry - NUHA - 2000 - - - - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/xml/books.xml b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/xml/books.xml deleted file mode 100644 index 1ff9cc88..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/xml/books.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - Padam History - ZARA - 100 - - - Great Mistry - NUHA - 2000 - - diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/xml/style.xsl b/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/xml/style.xsl deleted file mode 100644 index 728b223b..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/main/webapp/xml/style.xsl +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - - - -
    -
    diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java b/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java deleted file mode 100644 index 5fb3a29d..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java +++ /dev/null @@ -1,114 +0,0 @@ -package io.github.dunwu.javaee.server; - -import java.util.ArrayList; -import org.apache.commons.lang3.StringUtils; -import org.assertj.core.util.Lists; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.webapp.WebAppClassLoader; -import org.eclipse.jetty.webapp.WebAppContext; - -/** - * JettyFactory 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 - * - * @author Zhang Peng - */ -@SuppressWarnings("unused") -public class JettyFactory { - - public static final int IDE_ECLIPSE = 0; - - public static final int IDE_INTELLIJ = 1; - - private static final int PORT = 9798; - - private static final String CONTEXT = "/"; - - private static final String RESOURCE_BASE_PATH = "src/main/webapp"; - - private static final String WEB_XML_PATH = "/WEB-INF/web.xml"; - - private static final String[] TLD_JAR_NAMES = new String[] {"sitemesh", "spring-webmvc", "shiro-web", "tiles"} - - private static final String WINDOWS_WEBDEFAULT_PATH = "jetty/webdefault.xml"; - - public static Server initServer() { - Profiles.setProfileAsSystemProperty(Profiles.DEVELOPMENT); - WebAppContext webAppContext = new WebAppContext(); - Server server = new Server(PORT); - server.setHandler(webAppContext); - return server; - } - - public static void initWebAppContext(Server server, int type) throws Exception { - System.out.println("[INFO] Application loading"); - WebAppContext webAppContext = (WebAppContext) server.getHandler(); - webAppContext.setContextPath(CONTEXT); - webAppContext.setResourceBase(getAbsolutePath() + RESOURCE_BASE_PATH); - webAppContext.setDescriptor(getAbsolutePath() + RESOURCE_BASE_PATH + WEB_XML_PATH); - - if (IDE_INTELLIJ == type) { - webAppContext.setDefaultsDescriptor(WINDOWS_WEBDEFAULT_PATH); - supportJspAndSetTldJarNames(server, TLD_JAR_NAMES); - } else { - webAppContext.setParentLoaderPriority(true); - } - - System.out.println("[INFO] Application loaded"); - } - - public static String getAbsolutePath() { - String path = null; - String folderPath = JettyFactory.class.getProtectionDomain().getCodeSource().getLocation().getPath() - .substring(1); - if (folderPath.indexOf("target") > 0) { - path = folderPath.substring(0, folderPath.indexOf("target")); - } - return path; - } - - public static void supportJspAndSetTldJarNames(Server server, String... jarNames) { - WebAppContext context = (WebAppContext) server.getHandler(); - // This webapp will use jsps and jstl. We need to enable the - // AnnotationConfiguration in - // order to correctly set up the jsp container - org.eclipse.jetty.webapp.Configuration.ClassList classlist = org.eclipse.jetty.webapp.Configuration.ClassList - .setServerDefault(server); - classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", - "org.eclipse.jetty.annotations.AnnotationConfiguration"); - // Set the ContainerIncludeJarPattern so that jetty examines these container-path - // jars for - // tlds, web-fragments etc. - // If you omit the jar that contains the jstl .tlds, the jsp engine will scan for - // them - // instead. - ArrayList jarNameExprssions = Lists.newArrayList(".*/[^/]*servlet-api-[^/]*\\.jar$", - ".*/javax.servlet.jsp.jstl-.*\\.jar$", ".*/[^/]*taglibs.*\\.jar$"); - - for (String jarName : jarNames) { - jarNameExprssions.add(".*/" + jarName + "-[^/]*\\.jar$"); - } - - context.setAttribute("org.eclipse.jetty.io.github.dunwu.javaee.server.webapp.ContainerIncludeJarPattern", - StringUtils.join(jarNameExprssions, '|')); - } - - public static void reloadWebAppContext(Server server) throws Exception { - WebAppContext webAppContext = (WebAppContext) server.getHandler(); - System.out.println("[INFO] Application reloading"); - webAppContext.stop(); - WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext); - classLoader.addClassPath(getAbsolutePath() + "target/classes"); - classLoader.addClassPath(getAbsolutePath() + "target/test-classes"); - webAppContext.setClassLoader(classLoader); - webAppContext.start(); - System.out.println("[INFO] Application reloaded"); - } - - public static void startServer(Server server) throws Exception { - System.out.println("[HINT] Don't forget to set -XX:MaxPermSize=128m"); - server.start(); - System.out.println("Server running at http://localhost:" + PORT + CONTEXT); - System.out.println("[HINT] Hit Enter to reload the application quickly"); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/Profiles.java b/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/Profiles.java deleted file mode 100644 index dcfd3e39..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/Profiles.java +++ /dev/null @@ -1,34 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2005, 2014 springside.github.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - *******************************************************************************/ -package io.github.dunwu.javaee.server; - -/** - * Spring profile 常用方法与profile名称。 - * - * @author calvin - */ -public class Profiles { - - public static final String ACTIVE_PROFILE = "spring.profiles.active"; - - public static final String DEFAULT_PROFILE = "spring.profiles.default"; - - public static final String PRODUCTION = "production"; - - public static final String DEVELOPMENT = "development"; - - public static final String UNIT_TEST = "test"; - - public static final String FUNCTIONAL_TEST = "functional"; - - /** - * 在Spring启动前,设置profile的环境变量。 - */ - public static void setProfileAsSystemProperty(String profile) { - System.setProperty(ACTIVE_PROFILE, profile); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java b/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java deleted file mode 100644 index 049c8802..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.github.dunwu.javaee.server; - -import org.eclipse.jetty.server.Server; - -/** - * 快速启动 jetty 服务器,方便测试 - * - * @author Zhang Peng - */ -public class QuickStartServer { - - // private static int STARTUP_TYPE = JettyFactory.IDE_ECLIPSE; - private static int STARTUP_TYPE = JettyFactory.IDE_INTELLIJ; - - public static void main(String[] args) throws Exception { - Server server = JettyFactory.initServer(); - JettyFactory.initWebAppContext(server, STARTUP_TYPE); - - try { - JettyFactory.startServer(server); - - // 等待用户输入回车重载应用 - while (true) { - char c = (char) System.in.read(); - if (c == '\n') { - JettyFactory.reloadWebAppContext(server); - } - } - } catch (Exception e) { - e.printStackTrace(); - System.exit(-1); - } - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/resources/jetty/webdefault.xml b/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/resources/jetty/webdefault.xml deleted file mode 100644 index b991d44c..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-jstl/src/test/resources/jetty/webdefault.xml +++ /dev/null @@ -1,534 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - Default web.xml file. - This file is applied to a Web application before it's own WEB_INF/web.xml file - - - - - - - - org.eclipse.jetty.servlet.listener.ELContextCleaner - - - - - - - - org.eclipse.jetty.servlet.listener.IntrospectorCleaner - - - - - - - - - - - - - - - - - default - org.eclipse.jetty.servlet.DefaultServlet - - aliases - false - - - acceptRanges - true - - - dirAllowed - true - - - welcomeServlets - false - - - redirectWelcome - false - - - maxCacheSize - 256000000 - - - maxCachedFileSize - 200000000 - - - maxCachedFiles - 2048 - - - gzip - false - - - etags - false - - - useFileMappedBuffer - false - - - - 0 - - - - default - / - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - jsp - org.eclipse.jetty.jsp.JettyJspServlet - - logVerbosityLevel - DEBUG - - - fork - false - - - xpoweredBy - false - - - compilerTargetVM - 1.7 - - - compilerSourceVM - 1.7 - - - 0 - - - - jsp - *.jsp - *.jspf - *.jspx - *.xsp - *.JSP - *.JSPF - *.JSPX - *.XSP - - - - - - - - 30 - - - - - - - - - - - - - - - index.html - index.htm - index.jsp - - - - - - - - ar - ISO-8859-6 - - - be - ISO-8859-5 - - - bg - ISO-8859-5 - - - ca - ISO-8859-1 - - - cs - ISO-8859-2 - - - da - ISO-8859-1 - - - de - ISO-8859-1 - - - el - ISO-8859-7 - - - en - ISO-8859-1 - - - es - ISO-8859-1 - - - et - ISO-8859-1 - - - fi - ISO-8859-1 - - - fr - ISO-8859-1 - - - hr - ISO-8859-2 - - - hu - ISO-8859-2 - - - is - ISO-8859-1 - - - it - ISO-8859-1 - - - iw - ISO-8859-8 - - - ja - Shift_JIS - - - ko - EUC-KR - - - lt - ISO-8859-2 - - - lv - ISO-8859-2 - - - mk - ISO-8859-5 - - - nl - ISO-8859-1 - - - no - ISO-8859-1 - - - pl - ISO-8859-2 - - - pt - ISO-8859-1 - - - ro - ISO-8859-2 - - - ru - ISO-8859-5 - - - sh - ISO-8859-5 - - - sk - ISO-8859-2 - - - sl - ISO-8859-2 - - - sq - ISO-8859-2 - - - sr - ISO-8859-5 - - - sv - ISO-8859-1 - - - tr - ISO-8859-9 - - - uk - ISO-8859-5 - - - zh - GB2312 - - - zh_TW - Big5 - - - - - - - - - Disable TRACE - / - TRACE - - - - - - Enable everything but TRACE - / - TRACE - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/pom.xml b/codes/javaee-tutorial/javaee-tutorial-listener/pom.xml deleted file mode 100644 index f67b65d1..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-listener/pom.xml +++ /dev/null @@ -1,102 +0,0 @@ - - 4.0.0 - - io.github.dunwu - javaee-tutorial-listener - 1.0.0 - war - javaee-tutorial-listener - JavaEE 学习笔记之 listener - - - UTF-8 - 1.7 - ${java.version} - ${java.version} - - - - - - ch.qos.logback - logback-classic - - - org.slf4j - jcl-over-slf4j - - - - - - commons-fileupload - commons-fileupload - - - commons-io - commons-io - - - org.apache.commons - commons-lang3 - - - - - - javax.servlet - javax.servlet-api - provided - - - javax.servlet.jsp - javax.servlet.jsp-api - provided - - - - - - org.eclipse.jetty - jetty-webapp - test - - - org.eclipse.jetty - jetty-annotations - test - - - org.eclipse.jetty - apache-jsp - test - - - org.eclipse.jetty - apache-jstl - test - - - - - - junit - junit - test - - - - - - - - io.github.dunwu - dunwu-dependencies - 0.4.2-SNAPSHOT - pom - import - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/LoginSessionListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/LoginSessionListener.java deleted file mode 100644 index 05906ade..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/LoginSessionListener.java +++ /dev/null @@ -1,89 +0,0 @@ -package io.github.dunwu.javaee.listener; - -import io.github.dunwu.javaee.listener.bean.PersonInfo; -import java.util.HashMap; -import java.util.Map; -import javax.servlet.http.HttpSession; -import javax.servlet.http.HttpSessionAttributeListener; -import javax.servlet.http.HttpSessionBindingEvent; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * 单点登录处理类 - * - * @author Zhang Peng - * @since 2017-04-04 - */ -public class LoginSessionListener implements HttpSessionAttributeListener { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - Map map = new HashMap(); - - @Override - public void attributeAdded(HttpSessionBindingEvent event) { - String name = event.getName(); - - // 登录 - if (name.equals("personInfo")) { - - PersonInfo personInfo = (PersonInfo) event.getValue(); - - if (map.get(personInfo.getAccount()) != null) { - - // map 中有记录,表明该帐号在其他机器上登录过,将以前的登录失效 - HttpSession session = map.get(personInfo.getAccount()); - PersonInfo oldPersonInfo = (PersonInfo) session.getAttribute("personInfo"); - - logger.debug("帐号" + oldPersonInfo.getAccount() + "在" + oldPersonInfo.getIp() + "已经登录,该登录将被迫下线。"); - - session.removeAttribute("personInfo"); - session.setAttribute("msg", "您的帐号已经在其他机器上登录,您被迫下线。"); - } - - // 将session以用户名为索引,放入map中 - map.put(personInfo.getAccount(), event.getSession()); - logger.debug("帐号" + personInfo.getAccount() + "在" + personInfo.getIp() + "登录。"); - } - } - - @Override - public void attributeRemoved(HttpSessionBindingEvent event) { - String name = event.getName(); - - // 注销 - if (name.equals("personInfo")) { - // 将该session从map中移除 - PersonInfo personInfo = (PersonInfo) event.getValue(); - map.remove(personInfo.getAccount()); - logger.debug("帐号" + personInfo.getAccount() + "注销。"); - } - } - - @Override - public void attributeReplaced(HttpSessionBindingEvent event) { - String name = event.getName(); - - // 没有注销的情况下,用另一个帐号登录 - if (name.equals("personInfo")) { - - // 移除旧的的登录信息 - PersonInfo oldPersonInfo = (PersonInfo) event.getValue(); - map.remove(oldPersonInfo.getAccount()); - - // 新的登录信息 - PersonInfo personInfo = (PersonInfo) event.getSession().getAttribute("personInfo"); - - // 也要检查新登录的帐号是否在别的机器上登录过 - if (map.get(personInfo.getAccount()) != null) { - // map 中有记录,表明该帐号在其他机器上登录过,将以前的登录失效 - HttpSession session = map.get(personInfo.getAccount()); - session.removeAttribute("personInfo"); - session.setAttribute("msg", "您的帐号已经在其他机器上登录,您被迫下线。"); - } - map.put("personInfo", event.getSession()); - } - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionActivationListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionActivationListener.java deleted file mode 100644 index 647a568e..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionActivationListener.java +++ /dev/null @@ -1,46 +0,0 @@ -package io.github.dunwu.javaee.listener; - -import java.io.Serializable; -import javax.servlet.http.HttpSessionActivationListener; -import javax.servlet.http.HttpSessionEvent; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * HttpSessionActivationListener 接口的 JavaBean 对象可以感知自己被序列化或反序列化的事件。 - * - * @author Zhang Peng - * @since 2017-04-04 - */ -public class MyHttpSessionActivationListener implements HttpSessionActivationListener, Serializable { - - private static final long serialVersionUID = 1L; - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private String name; - - public MyHttpSessionActivationListener(String name) { - this.name = name; - } - - @Override - public void sessionWillPassivate(HttpSessionEvent se) { - - logger.debug(name + "和session一起被序列化到硬盘了,session的id是:" + se.getSession().getId()); - } - - @Override - public void sessionDidActivate(HttpSessionEvent se) { - logger.debug(name + "和session一起从硬盘反序列化回到内存了,session的id是:" + se.getSession().getId()); - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionAttributeListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionAttributeListener.java deleted file mode 100644 index cc55a3e1..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionAttributeListener.java +++ /dev/null @@ -1,44 +0,0 @@ -package io.github.dunwu.javaee.listener; - -import javax.servlet.http.HttpSession; -import javax.servlet.http.HttpSessionAttributeListener; -import javax.servlet.http.HttpSessionBindingEvent; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * HttpSessionAttributeListener 用于监听 HttpSession 中的属性变化 - * - * @author Zhang Peng - * @since 2017-04-04 - */ -public class MyHttpSessionAttributeListener implements HttpSessionAttributeListener { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - // 添加属性 - @Override - public void attributeAdded(HttpSessionBindingEvent se) { - HttpSession session = se.getSession(); - String name = se.getName(); - logger.info("新建session属性:" + name + ", 值为:" + se.getValue()); - } - - // 删除属性 - @Override - public void attributeRemoved(HttpSessionBindingEvent se) { - HttpSession session = se.getSession(); - String name = se.getName(); - logger.info("删除session属性:" + name + ", 值为:" + se.getValue()); - } - - // 修改属性 - @Override - public void attributeReplaced(HttpSessionBindingEvent se) { - HttpSession session = se.getSession(); - String name = se.getName(); - Object oldValue = se.getValue(); - logger.info("修改session属性:" + name + ", 原值:" + oldValue + ", 新值:" + session.getAttribute(name)); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionBindingListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionBindingListener.java deleted file mode 100644 index 783712c8..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionBindingListener.java +++ /dev/null @@ -1,28 +0,0 @@ -package io.github.dunwu.javaee.listener; - -import javax.servlet.http.HttpSessionBindingEvent; -import javax.servlet.http.HttpSessionBindingListener; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * HttpSessionBindingListener 接口的 JavaBean 对象可以感知自己被绑定或解绑定到 Session 中的事件。 - * - * @author Zhang Peng - * @since 2017-04-04 - */ -public class MyHttpSessionBindingListener implements HttpSessionBindingListener { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - @Override - public void valueBound(HttpSessionBindingEvent event) { - logger.debug("HttpSessionBinding valueBound"); - } - - @Override - public void valueUnbound(HttpSessionBindingEvent event) { - logger.debug("HttpSessionBinding valueUnbound"); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionListener.java deleted file mode 100644 index 0fa16180..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyHttpSessionListener.java +++ /dev/null @@ -1,48 +0,0 @@ -package io.github.dunwu.javaee.listener; - -import io.github.dunwu.javaee.listener.util.ApplicationConstants; -import java.util.Date; -import javax.servlet.http.HttpSession; -import javax.servlet.http.HttpSessionEvent; -import javax.servlet.http.HttpSessionListener; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * HttpSessionListener 接口用于监听 HttpSession 对象的创建和销毁。 - * - * @author Zhang Peng - * @since 2017-04-04 - */ -public class MyHttpSessionListener implements HttpSessionListener { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - @Override - public void sessionCreated(HttpSessionEvent se) { - HttpSession session = se.getSession(); - - // 将 session 放入 map - ApplicationConstants.SESSION_MAP.put(session.getId(), session); - // 总访问人数++ - ApplicationConstants.TOTAL_HISTORY_COUNT++; - - // 如果当前在线人数超过历史记录,则更新最大在线人数,并记录时间 - if (ApplicationConstants.SESSION_MAP.size() > ApplicationConstants.MAX_ONLINE_COUNT) { - ApplicationConstants.MAX_ONLINE_COUNT = ApplicationConstants.SESSION_MAP.size(); - ApplicationConstants.MAX_ONLINE_COUNT_DATE = new Date(); - } - - logger.debug("创建了一个session: {}", session); - } - - @Override - public void sessionDestroyed(HttpSessionEvent se) { - HttpSession session = se.getSession(); - // 将session从map中移除 - ApplicationConstants.SESSION_MAP.remove(session.getId()); - - logger.debug("销毁了一个session: {}", session); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextAttributeListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextAttributeListener.java deleted file mode 100644 index e1d2723e..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextAttributeListener.java +++ /dev/null @@ -1,33 +0,0 @@ -package io.github.dunwu.javaee.listener; - -import javax.servlet.ServletContextAttributeEvent; -import javax.servlet.ServletContextAttributeListener; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * ServletContextAttributeListener 用于监听 ServletContext 中的属性变化 - * - * @author Zhang Peng - * @since 2017-04-04 - */ -public class MyServletContextAttributeListener implements ServletContextAttributeListener { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - @Override - public void attributeAdded(ServletContextAttributeEvent scab) { - logger.debug("ServletContext域对象中添加了属性:{},属性值是:{}", scab.getName(), scab.getValue()); - } - - @Override - public void attributeRemoved(ServletContextAttributeEvent scab) { - logger.debug("ServletContext域对象中删除了属性:{},属性值是:{}", scab.getName(), scab.getValue()); - } - - @Override - public void attributeReplaced(ServletContextAttributeEvent scab) { - logger.debug("ServletContext域对象中替换了属性:{},原值是:{}, 现值是:{}", scab.getName(), scab.getSource(), scab.getValue()); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextListener.java deleted file mode 100644 index 2855c52e..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletContextListener.java +++ /dev/null @@ -1,38 +0,0 @@ -package io.github.dunwu.javaee.listener; - -import io.github.dunwu.javaee.listener.util.ApplicationConstants; -import java.util.Date; -import javax.servlet.ServletContext; -import javax.servlet.ServletContextEvent; -import javax.servlet.ServletContextListener; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * ServletContextListener 接口用于监听 ServletContext 对象的创建和销毁事件。 - * - * @author Zhang Peng - * @since 2017-04-04 - */ -public class MyServletContextListener implements ServletContextListener { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - @Override - public void contextInitialized(ServletContextEvent event) { - // 启动时,记录服务器启动时间 - ApplicationConstants.START_DATE = new Date(); - ServletContext servletContext = event.getServletContext(); - logger.info("即将启动 {}", servletContext.getContextPath()); - } - - @Override - public void contextDestroyed(ServletContextEvent event) { - // 关闭时,将结果清除。也可以将结果保存到硬盘上。 - ApplicationConstants.START_DATE = null; - ApplicationConstants.MAX_ONLINE_COUNT_DATE = null; - ServletContext servletContext = event.getServletContext(); - logger.info("即将关闭 {}", servletContext.getContextPath()); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestAttributeListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestAttributeListener.java deleted file mode 100644 index 1a6fad17..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestAttributeListener.java +++ /dev/null @@ -1,33 +0,0 @@ -package io.github.dunwu.javaee.listener; - -import javax.servlet.ServletRequestAttributeEvent; -import javax.servlet.ServletRequestAttributeListener; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * ServletContextAttributeListener 用于监听 ServletRequest 中的属性变化 - * - * @author Zhang Peng - * @since 2017-04-04 - */ -public class MyServletRequestAttributeListener implements ServletRequestAttributeListener { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - @Override - public void attributeAdded(ServletRequestAttributeEvent srae) { - logger.debug("ServletRequest域对象中添加了属性:{},属性值是:{}", srae.getName(), srae.getValue()); - } - - @Override - public void attributeRemoved(ServletRequestAttributeEvent srae) { - logger.debug("ServletRequest域对象中删除了属性:{},属性值是:{}", srae.getName(), srae.getValue()); - } - - @Override - public void attributeReplaced(ServletRequestAttributeEvent srae) { - logger.debug("ServletRequest域对象中替换了属性:{},原值是:{}, 现值是:{}", srae.getName(), srae.getSource(), srae.getValue()); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestListener.java deleted file mode 100644 index 55803334..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/MyServletRequestListener.java +++ /dev/null @@ -1,61 +0,0 @@ -package io.github.dunwu.javaee.listener; - -import javax.servlet.ServletRequestEvent; -import javax.servlet.ServletRequestListener; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * ServletRequestListener 接口用于监听 ServletRequest 对象的创建和销毁。 - * - * @author Zhang Peng - * @since 2017-04-04 - */ -public class MyServletRequestListener implements ServletRequestListener { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - @Override - public void requestInitialized(ServletRequestEvent event) { - - HttpServletRequest request = (HttpServletRequest) event.getServletRequest(); - - HttpSession session = request.getSession(true); - - // 记录IP地址 - session.setAttribute("ip", request.getRemoteAddr()); - - // 记录访问次数,只记录访问 .html, .do, .jsp, .action 的累计次数 - String uri = request.getRequestURI(); - uri = request.getQueryString() == null ? uri : (uri + "?" + request.getQueryString()); - request.setAttribute("dateCreated", System.currentTimeMillis()); - String[] suffix = {".html", ".do", ".jsp", ".action"} - for (int i = 0; i < suffix.length; i++) { - if (uri.endsWith(suffix[i])) { - break; - } - if (i == suffix.length - 1) { - return; - } - } - - Integer activeTimes = (Integer) session.getAttribute("activeTimes"); - - if (activeTimes == null) { - activeTimes = 0; - } - - session.setAttribute("activeTimes", activeTimes + 1); - logger.debug("IP: {} 请求 {}", request.getRemoteAddr(), uri); - } - - @Override - public void requestDestroyed(ServletRequestEvent event) { - HttpServletRequest request = (HttpServletRequest) event.getServletRequest(); - long time = System.currentTimeMillis() - (Long) request.getAttribute("dateCreated"); - logger.debug("{} 请求处理结束, 用时 {} 毫秒", request.getRemoteAddr(), time); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/PersonInfoListener.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/PersonInfoListener.java deleted file mode 100644 index 20a43920..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/PersonInfoListener.java +++ /dev/null @@ -1,72 +0,0 @@ -package io.github.dunwu.javaee.listener; - -import java.io.Serializable; -import java.util.Date; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * @author Zhang Peng - * @since 2017-04-04 - */ -public class PersonInfoListener implements HttpSessionActivationListener, HttpSessionBindingListener, Serializable { - - private static final long serialVersionUID = -4780592776386225973L; - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private String name; - - private Date dateCreated; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Date getDateCreated() { - return dateCreated; - } - - public void setDateCreated(Date dateCreated) { - this.dateCreated = dateCreated; - } - - // 从硬盘加载后 - public void sessionDidActivate(HttpSessionEvent se) { - HttpSession session = se.getSession(); - logger.info(this + "已经成功从硬盘中加载。sessionId: " + session.getId()); - } - - // 即将被钝化到硬盘时 - public void sessionWillPassivate(HttpSessionEvent se) { - HttpSession session = se.getSession(); - logger.info(this + "即将保存到硬盘。sessionId: " + session.getId()); - } - - // 被放进session前 - public void valueBound(HttpSessionBindingEvent event) { - HttpSession session = event.getSession(); - String name = event.getName(); - logger.info(this + "被绑定到session \"" + session.getId() + "\"的" + name + "属性上"); - - // 记录放到session中的时间 - this.setDateCreated(new Date()); - } - - // 从session中移除后 - public void valueUnbound(HttpSessionBindingEvent event) { - HttpSession session = event.getSession(); - String name = event.getName(); - logger.info(this + "被从session \"" + session.getId() + "\"的" + name + "属性上移除"); - } - - @Override - public String toString() { - return "PersonInfoListener(" + name + ")"; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/bean/PersonInfo.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/bean/PersonInfo.java deleted file mode 100644 index 360c969e..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/bean/PersonInfo.java +++ /dev/null @@ -1,51 +0,0 @@ -package io.github.dunwu.javaee.listener.bean; - -import java.io.Serializable; -import java.util.Date; - -public class PersonInfo implements Serializable { - - private static final long serialVersionUID = 4063725584941336123L; - - // 帐号 - private String account; - - // 登录IP地址 - private String ip; - - // 登录时间 - private Date loginDate; - - public String getIp() { - return ip; - } - - public void setIp(String ip) { - this.ip = ip; - } - - public Date getLoginDate() { - return loginDate; - } - - public void setLoginDate(Date loginDate) { - this.loginDate = loginDate; - } - - @Override - public boolean equals(Object obj) { - if (obj == null || !(obj instanceof PersonInfo)) { - return false; - } - return account.equalsIgnoreCase(((PersonInfo) obj).getAccount()); - } - - public String getAccount() { - return account; - } - - public void setAccount(String account) { - this.account = account; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/util/ApplicationConstants.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/util/ApplicationConstants.java deleted file mode 100644 index f91d75c3..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/java/io/github/dunwu/javaee/listener/util/ApplicationConstants.java +++ /dev/null @@ -1,28 +0,0 @@ -package io.github.dunwu.javaee.listener.util; - -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import javax.servlet.http.HttpSession; - -public class ApplicationConstants { - - // 所有的 Session - public static Map SESSION_MAP = new HashMap(); - - // 当前登录的用户总数 - public static int CURRENT_LOGIN_COUNT = 0; - - // 历史访客总数 - public static int TOTAL_HISTORY_COUNT = 0; - - // 服务器启动时间 - public static Date START_DATE = new Date(); - - // 最高在线时间 - public static Date MAX_ONLINE_COUNT_DATE = new Date(); - - // 最高在线人数 - public static int MAX_ONLINE_COUNT = 0; - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/resources/logback.xml deleted file mode 100644 index c05e3f34..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/resources/logback.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n - - - - - - - - logs/${FILE_NAME}-all.%d{yyyy-MM-dd}.log - 30 - - - - - 30MB - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n - - - - - - - - - - - - - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/META-INF/MANIFEST.MF b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/META-INF/MANIFEST.MF deleted file mode 100644 index 254272e1..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Class-Path: - diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/META-INF/context.xml b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/META-INF/context.xml deleted file mode 100644 index 9d357dc7..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/META-INF/context.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/WEB-INF/resources/jsp/index.jsp b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/WEB-INF/resources/jsp/index.jsp deleted file mode 100644 index 473a1315..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/WEB-INF/resources/jsp/index.jsp +++ /dev/null @@ -1,16 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> - - - - - My JSP 'index.jsp' starting page - - - - - - - -

    This is my JSP page.

    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/WEB-INF/web.xml b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index b9947a30..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - contextPath - value. sdf - - - - - io.github.dunwu.javaee.listener.MyHttpSessionListener - - - io.github.dunwu.javaee.listener.MyServletContextListener - - - io.github.dunwu.javaee.listener.MyServletRequestListener - - - - - io.github.dunwu.javaee.listener.MyHttpSessionAttributeListener - - - io.github.dunwu.javaee.listener.MyServletContextAttributeListener - - - io.github.dunwu.javaee.listener.MyServletRequestAttributeListener - - - - - io.github.dunwu.javaee.listener.MyHttpSessionBindingListener - - - io.github.dunwu.javaee.listener.MyHttpSessionActivationListener - - - - - io.github.dunwu.javaee.listener.LoginSessionListener - - - - - - io.github.dunwu.javaee.listener.PersonInfoListener - - - - - - index.jsp - - diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/a.gif b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/a.gif deleted file mode 100644 index 6f1cba44b44ed0d36a6f9b217b04d38a26ef3a67..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 120 zcmV-;0EhoaNk%w1VHN;B0FeLyaE_*WmaBS}tZsdfsd

    - -

    sadfsdf

    - -
      -
    1. sadf
    2. -
    3. asdfsadf
    4. -
    5. sdf
    6. -
    7. sdfsadfasdf
    8. -
    - - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/active.jsp b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/active.jsp deleted file mode 100644 index a2bc7909..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/active.jsp +++ /dev/null @@ -1,24 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> - -<%@ page import="io.github.dunwu.javaee.listener.bean.PersonInfo" %> - - - - Insert title here - - - -<% - PersonInfo personInfo = (PersonInfo) session.getAttribute("personInfo"); - if (personInfo == null) { - personInfo = new PersonInfo(); - personInfo.setAccount("Zhang Peng"); - session.setAttribute("personInfo", personInfo); - out.println("PersonInfo 对象不存在。已经成功新建。sessionId: " + session.getId()); - } else { - out.println("PersonInfo 对象存在。无需新建。sessionId: " + session.getId()); - } -%> - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/b.gif b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/b.gif deleted file mode 100644 index 2996ad0ee771a97b8875c3dbee39ff8f60e0f11b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 128 zcmZ?wbhEHb6l1Vsn8?7;yL89Y)qAF{-rKu$r{Yf*1`yBz5g>U6robuvD^I`WkDamQ zR`=$6Hoy4W9(hb-x<2*AsckDbkAL#ff7iR+l;g`kejX;aBa1$E_-IW(v+Ofxuh;S` ftAaX2qqg5!_nn)syZ7d%{2ss6*WaWvGFSru(Cs)h diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/index.jsp b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/index.jsp deleted file mode 100644 index 7f55cd70..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/index.jsp +++ /dev/null @@ -1,22 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<% - String path = request.getContextPath(); - String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; -%> - - - - - - javaee-listener 首页 - - - -This is my JSP page.
    -Listener
    -active
    -登录
    -在线用户统计
    -Listener
    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/listener.jsp b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/listener.jsp deleted file mode 100644 index c8326ee4..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/listener.jsp +++ /dev/null @@ -1,91 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> - - - -<% - String action = request.getParameter("action"); - String name = request.getParameter("name"); - String value = request.getParameter("value"); - - session.getId(); - - if ("addRequestAttribute".equals(action)) { - request.setAttribute(name, value); - } else if ("removeRequestAttribute".equals(action)) { - request.removeAttribute(name); - } else if ("addSessionAttribute".equals(action)) { - session.setAttribute(name, value); - } else if ("removeSessionAttribute".equals(action)) { - session.removeAttribute(name); - } else if ("logout".equals(action)) { - session.invalidate(); - out.println("返回"); - return; - } -%> - - - - - - -
    -
    -
    -
    删除 Session
    -
    - -
    -
    -
    - -服务器启动时间:<%=DateFormat.getDateTimeInstance().format(ApplicationConstants.START_DATE)%>, -累计共接待过 <%= ApplicationConstants.TOTAL_HISTORY_COUNT %> 访客。
    -同时在线人数最多为 <%= ApplicationConstants.MAX_ONLINE_COUNT %> 人, -发生在 <%=DateFormat.getDateTimeInstance().format(ApplicationConstants.MAX_ONLINE_COUNT_DATE)%>。
    - -目前在线总数:<%= ApplicationConstants.SESSION_MAP.size() %>,登录用户:<%=ApplicationConstants.CURRENT_LOGIN_COUNT%>。
    - - - - - - - - - - - <% - for (String id : ApplicationConstants.SESSION_MAP.keySet()) { - HttpSession sess = ApplicationConstants.SESSION_MAP.get(id); - %> - - - - - - - - - - <% - } - %> -
    jsessionidaccountcreationTimelastAccessedTimenewactiveTimesip
    <%=id%> - <%=sess.getAttribute("account")%> - <%=DateFormat.getDateTimeInstance().format(new Date(sess.getCreationTime()))%> - <%=DateFormat.getDateTimeInstance().format(new Date(sess.getLastAccessedTime()))%> - <%=sess.isNew()%> - <%=sess.getAttribute("activeTimes")%> - <%=sess.getAttribute("ip")%> -
    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/online.jsp b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/online.jsp deleted file mode 100644 index e9219e1c..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/online.jsp +++ /dev/null @@ -1,55 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> - - - - - - -服务器启动时间:<%=DateFormat.getDateTimeInstance().format(ApplicationConstants.START_DATE)%>, -累计共接待过 <%= ApplicationConstants.TOTAL_HISTORY_COUNT %> 访客。
    -同时在线人数最多为 <%= ApplicationConstants.MAX_ONLINE_COUNT %> 人, -发生在 <%=DateFormat.getDateTimeInstance().format(ApplicationConstants.MAX_ONLINE_COUNT_DATE)%>。
    - -目前在线总数:<%= ApplicationConstants.SESSION_MAP.size() %>,登录用户:<%=ApplicationConstants.CURRENT_LOGIN_COUNT%>。
    - - - - - - - - - - - <% - for (String id : ApplicationConstants.SESSION_MAP.keySet()) { - HttpSession sess = ApplicationConstants.SESSION_MAP.get(id); - PersonInfo personInfo = (PersonInfo) sess.getAttribute("personInfo"); - %> - > - - - - - - - - - <% - } - %> -
    jsessionidaccountcreationTimelastAccessedTimenewactiveTimesip
    <%=id%> - <%=personInfo == null ? " " : personInfo.getAccount()%> - <%=DateFormat.getDateTimeInstance().format(sess.getCreationTime())%> - <%=DateFormat.getDateTimeInstance().format(new Date(sess.getLastAccessedTime()))%> - <%=sess.isNew()%> - <%=sess.getAttribute("activeTimes")%> - <%=sess.getAttribute("ip") %> -
    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/testLoginSessionListener.jsp b/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/testLoginSessionListener.jsp deleted file mode 100644 index 7ef99320..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/main/webapp/testLoginSessionListener.jsp +++ /dev/null @@ -1,70 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> - -<% - String action = request.getParameter("action"); - String account = request.getParameter("account"); - - if ("login".equals(action) && account.trim().length() > 0) { - - // 登录,将personInfo放入session - PersonInfo personInfo = new PersonInfo(); - personInfo.setAccount(account.trim().toLowerCase()); - personInfo.setIp(request.getRemoteAddr()); - personInfo.setLoginDate(new java.util.Date()); - - session.setAttribute("personInfo", personInfo); - - response.sendRedirect(response.encodeRedirectURL(request.getRequestURI())); - return; - } else if ("logout".equals(action)) { - - // 注销,将personInfo从session中移除 - session.removeAttribute("personInfo"); - - response.sendRedirect(response.encodeRedirectURL(request.getRequestURI())); - return; - } -%> - - - - - Insert title here - - - - - - - - - 欢迎您,${ personInfo.account }。
    - 您的登录IP为${ personInfo.ip },
    - 登录时间为。 - 退出 - - - -
    - - - - ${ msg } - -
    - 帐号: - - -
    -
    - -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java deleted file mode 100644 index 26b94cdb..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java +++ /dev/null @@ -1,131 +0,0 @@ -package io.github.dunwu.javaee.server; - -import java.util.ArrayList; -import java.util.List; -import org.apache.commons.lang3.StringUtils; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.webapp.WebAppClassLoader; -import org.eclipse.jetty.webapp.WebAppContext; - -/** - * JettyFactory 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 - * - * @author Zhang Peng - */ -@SuppressWarnings("all") -public class JettyFactory { - - public static final int IDE_ECLIPSE = 0; - - public static final int IDE_INTELLIJ = 1; - - public static final String ACTIVE_PROFILE = "spring.profiles.active"; - - public static final String DEFAULT_PROFILE = "spring.profiles.default"; - - public static final String DEVELOPMENT = "development"; - - private static final int PORT = 9527; - - private static final String CONTEXT = "/"; - - private static final String RESOURCE_BASE_PATH = "src/main/webapp"; - - private static final String WEB_XML_PATH = "/WEB-INF/web.xml"; - - private static final String[] TLD_JAR_NAMES = new String[] {"sitemesh", "spring-webmvc", "tiles"} - - private static final String WINDOWS_WEBDEFAULT_PATH = "jetty/webdefault.xml"; - - public static Server initServer() { - setProfileAsSystemProperty(DEVELOPMENT); - WebAppContext webAppContext = new WebAppContext(); - Server server = new Server(PORT); - server.setHandler(webAppContext); - return server; - } - - /** - * 在Spring启动前,设置profile的环境变量。 - */ - public static void setProfileAsSystemProperty(String profile) { - System.setProperty(ACTIVE_PROFILE, profile); - } - - public static void initWebAppContext(Server server, int type) throws Exception { - System.out.println("[INFO] Application loading"); - WebAppContext webAppContext = (WebAppContext) server.getHandler(); - webAppContext.setContextPath(CONTEXT); - webAppContext.setResourceBase(getAbsolutePath() + RESOURCE_BASE_PATH); - webAppContext.setDescriptor(getAbsolutePath() + RESOURCE_BASE_PATH + WEB_XML_PATH); - - if (IDE_INTELLIJ == type) { - webAppContext.setDefaultsDescriptor(WINDOWS_WEBDEFAULT_PATH); - supportJspAndSetTldJarNames(server, TLD_JAR_NAMES); - } else { - webAppContext.setParentLoaderPriority(true); - } - - System.out.println("[INFO] Application loaded"); - } - - public static String getAbsolutePath() { - String path = null; - String folderPath = JettyFactory.class.getProtectionDomain().getCodeSource().getLocation().getPath() - .substring(1); - if (folderPath.indexOf("target") > 0) { - path = folderPath.substring(0, folderPath.indexOf("target")); - } - return path; - } - - public static void supportJspAndSetTldJarNames(Server server, String... jarNames) { - WebAppContext context = (WebAppContext) server.getHandler(); - // This webapp will use jsps and jstl. We need to enable the - // AnnotationConfiguration in - // order to correctly set up the jsp container - org.eclipse.jetty.webapp.Configuration.ClassList classlist = org.eclipse.jetty.webapp.Configuration.ClassList - .setServerDefault(server); - classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", - "org.eclipse.jetty.annotations.AnnotationConfiguration"); - // Set the ContainerIncludeJarPattern so that jetty examines these container-path - // jars for - // tlds, web-fragments etc. - // If you omit the jar that contains the jstl .tlds, the jsp engine will scan for - // them - // instead. - - List list = new ArrayList<>(); - list.add(".*/[^/]*servlet-api-[^/]*\\.jar$"); - list.add(".*/javax.servlet.jsp.jstl-.*\\.jar$"); - list.add(".*/[^/]*taglibs.*\\.jar$"); - - for (String jarName : jarNames) { - String str = ".*/" + jarName + "-[^/]*\\.jar$"; - list.add(str); - } - - context.setAttribute("org.eclipse.jetty.io.github.dunwu.javaee.server.webapp.ContainerIncludeJarPattern", - StringUtils.join(list, '|')); - } - - public static void reloadWebAppContext(Server server) throws Exception { - WebAppContext webAppContext = (WebAppContext) server.getHandler(); - System.out.println("[INFO] Application reloading"); - webAppContext.stop(); - WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext); - classLoader.addClassPath(getAbsolutePath() + "target/classes"); - classLoader.addClassPath(getAbsolutePath() + "target/test-classes"); - webAppContext.setClassLoader(classLoader); - webAppContext.start(); - System.out.println("[INFO] Application reloaded"); - } - - public static void startServer(Server server) throws Exception { - System.out.println("[HINT] Don't forget to set -XX:MaxPermSize=128m"); - server.start(); - System.out.println("Server running at http://localhost:" + PORT + CONTEXT); - System.out.println("[HINT] Hit Enter to reload the application quickly"); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/ListenerDemosBootstrap.java b/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/ListenerDemosBootstrap.java deleted file mode 100644 index 3ec9a1a0..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/test/java/io/github/dunwu/javaee/server/ListenerDemosBootstrap.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.github.dunwu.javaee.server; - -import org.eclipse.jetty.server.Server; - -/** - * 快速启动 jetty 服务器,方便测试 - * - * @author Zhang Peng - */ -public class ListenerDemosBootstrap { - - // private static int STARTUP_TYPE = JettyFactory.IDE_ECLIPSE; - private static int STARTUP_TYPE = JettyFactory.IDE_INTELLIJ; - - public static void main(String[] args) throws Exception { - Server server = JettyFactory.initServer(); - JettyFactory.initWebAppContext(server, STARTUP_TYPE); - - try { - JettyFactory.startServer(server); - - // 等待用户输入回车重载应用 - while (true) { - char c = (char) System.in.read(); - if (c == '\n') { - JettyFactory.reloadWebAppContext(server); - } - } - } catch (Exception e) { - e.printStackTrace(); - System.exit(-1); - } - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-listener/src/test/resources/jetty/webdefault.xml b/codes/javaee-tutorial/javaee-tutorial-listener/src/test/resources/jetty/webdefault.xml deleted file mode 100644 index b991d44c..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-listener/src/test/resources/jetty/webdefault.xml +++ /dev/null @@ -1,534 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - Default web.xml file. - This file is applied to a Web application before it's own WEB_INF/web.xml file - - - - - - - - org.eclipse.jetty.servlet.listener.ELContextCleaner - - - - - - - - org.eclipse.jetty.servlet.listener.IntrospectorCleaner - - - - - - - - - - - - - - - - - default - org.eclipse.jetty.servlet.DefaultServlet - - aliases - false - - - acceptRanges - true - - - dirAllowed - true - - - welcomeServlets - false - - - redirectWelcome - false - - - maxCacheSize - 256000000 - - - maxCachedFileSize - 200000000 - - - maxCachedFiles - 2048 - - - gzip - false - - - etags - false - - - useFileMappedBuffer - false - - - - 0 - - - - default - / - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - jsp - org.eclipse.jetty.jsp.JettyJspServlet - - logVerbosityLevel - DEBUG - - - fork - false - - - xpoweredBy - false - - - compilerTargetVM - 1.7 - - - compilerSourceVM - 1.7 - - - 0 - - - - jsp - *.jsp - *.jspf - *.jspx - *.xsp - *.JSP - *.JSPF - *.JSPX - *.XSP - - - - - - - - 30 - - - - - - - - - - - - - - - index.html - index.htm - index.jsp - - - - - - - - ar - ISO-8859-6 - - - be - ISO-8859-5 - - - bg - ISO-8859-5 - - - ca - ISO-8859-1 - - - cs - ISO-8859-2 - - - da - ISO-8859-1 - - - de - ISO-8859-1 - - - el - ISO-8859-7 - - - en - ISO-8859-1 - - - es - ISO-8859-1 - - - et - ISO-8859-1 - - - fi - ISO-8859-1 - - - fr - ISO-8859-1 - - - hr - ISO-8859-2 - - - hu - ISO-8859-2 - - - is - ISO-8859-1 - - - it - ISO-8859-1 - - - iw - ISO-8859-8 - - - ja - Shift_JIS - - - ko - EUC-KR - - - lt - ISO-8859-2 - - - lv - ISO-8859-2 - - - mk - ISO-8859-5 - - - nl - ISO-8859-1 - - - no - ISO-8859-1 - - - pl - ISO-8859-2 - - - pt - ISO-8859-1 - - - ro - ISO-8859-2 - - - ru - ISO-8859-5 - - - sh - ISO-8859-5 - - - sk - ISO-8859-2 - - - sl - ISO-8859-2 - - - sq - ISO-8859-2 - - - sr - ISO-8859-5 - - - sv - ISO-8859-1 - - - tr - ISO-8859-9 - - - uk - ISO-8859-5 - - - zh - GB2312 - - - zh_TW - Big5 - - - - - - - - - Disable TRACE - / - TRACE - - - - - - Enable everything but TRACE - / - TRACE - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/pom.xml b/codes/javaee-tutorial/javaee-tutorial-oss/pom.xml deleted file mode 100644 index db27089f..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/pom.xml +++ /dev/null @@ -1,154 +0,0 @@ - - - 4.0.0 - - io.github.dunwu - javaee-tutorial-oss - 1.0.0 - jar - javaee-tutorial-oss - JavaEE 学习笔记之 OSS(Open Source Software) - - - UTF-8 - 1.7 - ${java.version} - ${java.version} - - - - - - ch.qos.logback - logback-classic - - - org.slf4j - jcl-over-slf4j - - - - - - commons-codec - commons-codec - - - org.bouncycastle - bcprov-jdk15on - 1.54 - - - - - - - org.jsoup - jsoup - 1.9.2 - - - - - - com.google.zxing - core - 3.3.0 - - - com.google.zxing - javase - 3.3.0 - - - net.coobird - thumbnailator - - - net.sf.jmimemagic - jmimemagic - 0.1.4 - - - - - - org.apache.activemq - activemq-all - 5.14.1 - - - - - - com.alibaba - fastjson - - - com.fasterxml.jackson.core - jackson-core - - - com.fasterxml.jackson.core - jackson-databind - - - - - - javax.mail - javax.mail-api - - - javax.validation - validation-api - - - - - - org.apache.velocity - velocity - 1.7 - - - - - - junit - junit - test - - - org.assertj - assertj-core - test - - - - - - org.apache.commons - commons-lang3 - - - org.apache.commons - commons-collections4 - - - - - - - - io.github.dunwu - dunwu-dependencies - 0.4.2-SNAPSHOT - pom - import - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/DsaCoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/DsaCoder.java deleted file mode 100644 index e58fc382..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/DsaCoder.java +++ /dev/null @@ -1,79 +0,0 @@ -package io.github.dunwu.javaee.oss.encode.digest; - -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; -import org.apache.commons.codec.binary.Base64; - -/** - * 数字签名算法(Digital Signature Algorithm, DSA)工具类。 DSA是一种数字签名算法。 DSA仅支持SHA系列算法,而JDK仅支持SHA1withDSA。 - * - * @author Zhang Peng - * @since 2016年7月21日 - */ -public class DsaCoder { - - public static final String KEY_ALGORITHM = "DSA"; - - public static final String SIGN_ALGORITHM = "SHA1withDSA"; - - /** - * DSA密钥长度默认1024位。 密钥长度必须是64的整数倍,范围在512~1024之间 - */ - private static final int KEY_SIZE = 1024; - - private KeyPair keyPair; - - public DsaCoder() throws Exception { - keyPair = initKey(); - } - - private KeyPair initKey() throws Exception { - // 初始化密钥对生成器 - KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM); - // 实例化密钥对生成器 - keyPairGen.initialize(KEY_SIZE); - // 实例化密钥对 - return keyPairGen.genKeyPair(); - } - - public static void main(String[] args) throws Exception { - String msg = "Hello World"; - DsaCoder dsa = new DsaCoder(); - byte[] sign = dsa.signature(msg.getBytes(), dsa.getPrivateKey()); - boolean flag = dsa.verify(msg.getBytes(), dsa.getPublicKey(), sign); - String result = flag ? "数字签名匹配" : "数字签名不匹配"; - System.out.println("数字签名:" + Base64.encodeBase64URLSafeString(sign)); - System.out.println("验证结果:" + result); - } - - public byte[] signature(byte[] data, byte[] privateKey) throws Exception { - PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey); - KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); - PrivateKey key = keyFactory.generatePrivate(keySpec); - - Signature signature = Signature.getInstance(SIGN_ALGORITHM); - signature.initSign(key); - signature.update(data); - return signature.sign(); - } - - public byte[] getPrivateKey() { - return keyPair.getPrivate().getEncoded(); - } - - public boolean verify(byte[] data, byte[] publicKey, byte[] sign) throws Exception { - X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey); - KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); - PublicKey key = keyFactory.generatePublic(keySpec); - - Signature signature = Signature.getInstance(SIGN_ALGORITHM); - signature.initVerify(key); - signature.update(data); - return signature.verify(sign); - } - - public byte[] getPublicKey() { - return keyPair.getPublic().getEncoded(); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/HmacCoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/HmacCoder.java deleted file mode 100644 index c0878d00..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/HmacCoder.java +++ /dev/null @@ -1,38 +0,0 @@ -package io.github.dunwu.javaee.oss.encode.digest; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; -import org.apache.commons.codec.binary.Base64; - -/** - * 消息认证码算法(Message Autherntication Code, MAC)是基于密码的消息摘要算法。 它兼容了MD/SHA的特性,并以此为基础加入了密钥。 - * - * @author Zhang Peng - * @since 2016年7月21日 - */ -public class HmacCoder { - - public static void main(String[] args) throws Exception { - String msg = "Hello World!"; - byte[] secretKey = "Secret_Key".getBytes("UTF8"); - byte[] digest = HmacCoder.encode(msg.getBytes(), secretKey, HmacTypeEn.HmacSHA256); - System.out.println("原文: " + msg); - System.out.println("摘要: " + Base64.encodeBase64URLSafeString(digest)); - } - - public static byte[] encode(byte[] plaintext, byte[] secretKey, HmacTypeEn type) throws Exception { - SecretKeySpec keySpec = new SecretKeySpec(secretKey, type.name()); - Mac mac = Mac.getInstance(keySpec.getAlgorithm()); - mac.init(keySpec); - return mac.doFinal(plaintext); - } - - /** - * JDK支持HmacMD5, HmacSHA1, HmacSHA256, HmacSHA384, HmacSHA512 - */ - public enum HmacTypeEn { - - HmacMD5, HmacSHA1, HmacSHA256, HmacSHA384, HmacSHA512; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/MdCoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/MdCoder.java deleted file mode 100644 index b466a25e..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/MdCoder.java +++ /dev/null @@ -1,46 +0,0 @@ -package io.github.dunwu.javaee.oss.encode.digest; - -import java.security.MessageDigest; -import org.apache.commons.codec.binary.Base64; - -/** - * 消息摘要算法(Message Digest, MD)是消息摘要算法。 - * - * @author Zhang Peng - * @since 2016年7月21日 - */ -public class MdCoder { - - public static void main(String[] args) throws Exception { - String msg = "Hello World!"; - byte[] encodeWithBase64 = MdCoder.encodeWithBase64(msg.getBytes(), MdTypeEn.MD5); - - String result = String.format("%s摘要:%s", MdTypeEn.MD5.name(), new String(encodeWithBase64)); - System.out.println("原文: " + msg); - System.out.println(result); - } - - public static byte[] encodeWithBase64(byte[] input, MdTypeEn type) throws Exception { - return Base64.encodeBase64URLSafe(encode(input, type)); - } - - public static byte[] encode(byte[] input, MdTypeEn type) throws Exception { - // 根据类型,初始化消息摘要对象 - MessageDigest md5Digest = MessageDigest.getInstance(type.name()); - - // 更新要计算的内容 - md5Digest.update(input); - - // 完成哈希计算,返回摘要 - return md5Digest.digest(); - } - - /** - * JDK支持MD2和MD5两种MD算法 - */ - public enum MdTypeEn { - - MD2, MD5 - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/ShaCoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/ShaCoder.java deleted file mode 100644 index f22efaf3..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/digest/ShaCoder.java +++ /dev/null @@ -1,56 +0,0 @@ -package io.github.dunwu.javaee.oss.encode.digest; - -import java.security.MessageDigest; -import org.apache.commons.codec.binary.Base64; - -/** - * 安全散列算法(Secure Hash Algorithm, SHA)是消息摘要算法 - * - * @author Zhang Peng - * @since 2016年7月21日 - */ -public class ShaCoder { - - public static void main(String[] args) throws Exception { - String msg = "Hello World!"; - byte[] encodeWithBase64 = ShaCoder.encodeWithBase64(msg.getBytes(), ShaTypeEn.SHA384); - - String result = String.format("%s摘要:%s", ShaTypeEn.SHA384.getName(), new String(encodeWithBase64)); - System.out.println("原文: " + msg); - System.out.println(result); - } - - public static byte[] encodeWithBase64(byte[] input, ShaTypeEn type) throws Exception { - return Base64.encodeBase64URLSafe(encode(input, type)); - } - - public static byte[] encode(byte[] input, ShaTypeEn type) throws Exception { - // 根据类型,初始化消息摘要对象 - MessageDigest md5Digest = MessageDigest.getInstance(type.getName()); - - // 更新要计算的内容 - md5Digest.update(input); - - // 完成哈希计算,返回摘要 - return md5Digest.digest(); - } - - /** - * JDK支持SHA1、SHA256、SHA384和SHA512几种SHA算法 - */ - public enum ShaTypeEn { - - SHA1("SHA1"), SHA256("SHA-256"), SHA384("SHA-384"), SHA512("SHA-512"); - - private String name; - - ShaTypeEn(String name) { - this.name = name; - } - - public String getName() { - return this.name; - } - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/AESCoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/AESCoder.java deleted file mode 100644 index 5323b053..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/AESCoder.java +++ /dev/null @@ -1,131 +0,0 @@ -package io.github.dunwu.javaee.oss.encode.encrypt; - -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import javax.crypto.BadPaddingException; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.spec.IvParameterSpec; -import org.bouncycastle.util.encoders.Base64; - -/** - * AES安全编码:对称加密算法。DES的替代方案。 - * - * @author Zhang Peng - * @since 2016年7月14日 - */ -public class AESCoder { - - public static final String KEY_ALGORITHM_AES = "AES"; - - public static final String CIPHER_AES_DEFAULT = "AES"; - - public static final String CIPHER_AES_ECB_PKCS5PADDING = "AES/ECB/PKCS5Padding"; // 算法/模式/补码方式 - - public static final String CIPHER_AES_CBC_PKCS5PADDING = "AES/CBC/PKCS5Padding"; - - public static final String CIPHER_AES_CBC_NOPADDING = "AES/CBC/NoPadding"; - - private static final String SEED = "%%%today is nice***"; // 用于生成随机数的种子 - - private Key key; - - private Cipher cipher; - - private String transformation; - - public AESCoder() throws NoSuchAlgorithmException, NoSuchPaddingException { - this.key = initKey(); - this.cipher = Cipher.getInstance(CIPHER_AES_DEFAULT); - this.transformation = CIPHER_AES_DEFAULT; - } - - /** - * 根据随机数种子生成一个密钥 - * - * @return Key - * @throws NoSuchAlgorithmException - * @author Zhang Peng - * @since 2016年7月14日 - */ - private Key initKey() throws NoSuchAlgorithmException { - // 根据种子生成一个安全的随机数 - SecureRandom secureRandom = null; - secureRandom = new SecureRandom(SEED.getBytes()); - - KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHM_AES); - keyGen.init(secureRandom); - return keyGen.generateKey(); - } - - public AESCoder(String transformation) throws NoSuchAlgorithmException, NoSuchPaddingException { - this.key = initKey(); - this.cipher = Cipher.getInstance(transformation); - this.transformation = transformation; - } - - public static void main(String[] args) throws Exception { - AESCoder aes = new AESCoder(CIPHER_AES_CBC_PKCS5PADDING); - - String msg = "Hello World!"; - System.out.println("[AES加密、解密]"); - System.out.println("message: " + msg); - byte[] encoded = aes.encrypt(msg.getBytes("UTF8")); - String encodedBase64 = Base64.toBase64String(encoded); - System.out.println("encoded: " + encodedBase64); - - byte[] decodedBase64 = Base64.decode(encodedBase64); - byte[] decoded = aes.decrypt(decodedBase64); - System.out.println("decoded: " + new String(decoded)); - } - - /** - * 加密 - * - * @param input 明文 - * @return byte[] 密文 - * @throws InvalidKeyException - * @throws IllegalBlockSizeException - * @throws BadPaddingException - * @throws InvalidAlgorithmParameterException - * @author Zhang Peng - * @since 2016年7月20日 - */ - public byte[] encrypt(byte[] input) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, - InvalidAlgorithmParameterException { - if (transformation.equals(CIPHER_AES_CBC_PKCS5PADDING) || transformation.equals(CIPHER_AES_CBC_NOPADDING)) { - cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(getIV())); - } else { - cipher.init(Cipher.ENCRYPT_MODE, key); - } - return cipher.doFinal(input); - } - - /** - * 解密 - * - * @param input 密文 - * @return byte[] 明文 - * @throws InvalidKeyException - * @throws IllegalBlockSizeException - * @throws BadPaddingException - * @throws InvalidAlgorithmParameterException - * @author Zhang Peng - * @since 2016年7月20日 - */ - public byte[] decrypt(byte[] input) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, - InvalidAlgorithmParameterException { - if (transformation.equals(CIPHER_AES_CBC_PKCS5PADDING) || transformation.equals(CIPHER_AES_CBC_NOPADDING)) { - cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(getIV())); - } else { - cipher.init(Cipher.DECRYPT_MODE, key); - } - return cipher.doFinal(input); - } - - private byte[] getIV() { - String iv = "0123456789ABCDEF"; // IV length: must be 16 bytes long - return iv.getBytes(); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/Base64Demo.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/Base64Demo.java deleted file mode 100644 index 7ebb28a4..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/Base64Demo.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.dunwu.javaee.oss.encode.encrypt; - -import java.io.UnsupportedEncodingException; -import org.apache.commons.codec.binary.Base64; - -/** - * Base64编码、解码范例 - * - * @author Zhang Peng - * @since 2016年7月21日 - */ -public class Base64Demo { - - public static void main(String[] args) throws UnsupportedEncodingException { - String url = - "https://www.baidu.com/s?wd=Base64&rsv_spt=1&rsv_iqid=0xa9188d560005131f&issp=1&f=3&rsv_bp=0&rsv_idx=2&ie=utf-8&tn=baiduhome_pg&rsv_enter=1&rsv_sug3=1&rsv_sug1=1&rsv_sug7=001&rsv_sug2=1&rsp=0&rsv_sug9=es_2_1&rsv_sug4=2153&rsv_sug=9"; - // byte[] encoded = Base64.encodeBase64(url.getBytes("UTF8")); // 标准的Base64编码 - byte[] encoded = Base64.encodeBase64URLSafe(url.getBytes("UTF8")); // URL安全的Base64编码 - byte[] decoded = Base64.decodeBase64(encoded); - System.out.println("url:" + url); - System.out.println("encoded:" + new String(encoded)); - System.out.println("decoded:" + new String(decoded)); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/DESCoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/DESCoder.java deleted file mode 100644 index 5f5761f6..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/DESCoder.java +++ /dev/null @@ -1,133 +0,0 @@ -package io.github.dunwu.javaee.oss.encode.encrypt; - -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import javax.crypto.BadPaddingException; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.spec.IvParameterSpec; -import org.bouncycastle.util.encoders.Base64; - -/** - * DES安全编码:是经典的对称加密算法。密钥仅56位,且迭代次数偏少。已被视为并不安全的加密算法。 - * - * @author Zhang Peng - * @since 2016年7月14日 - */ -public class DESCoder { - - public static final String KEY_ALGORITHM_DES = "DES"; - - public static final String CIPHER_DES_DEFAULT = "DES"; - - public static final String CIPHER_DES_ECB_PKCS5PADDING = "DES/ECB/PKCS5Padding"; // 算法/模式/补码方式 - - public static final String CIPHER_DES_CBC_PKCS5PADDING = "DES/CBC/PKCS5Padding"; - - public static final String CIPHER_DES_CBC_NOPADDING = "DES/CBC/NoPadding"; - - private static final String SEED = "%%%today is nice***"; // 用于生成随机数的种子 - - private Key key; - - private Cipher cipher; - - private String transformation; - - public DESCoder() throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException { - this.key = initKey(); - this.cipher = Cipher.getInstance(CIPHER_DES_DEFAULT); - this.transformation = CIPHER_DES_DEFAULT; - } - - /** - * 根据随机数种子生成一个密钥 - * - * @return Key - * @throws NoSuchAlgorithmException - * @throws NoSuchProviderException - * @author Zhang Peng - * @since 2016年7月14日 - */ - private Key initKey() throws NoSuchAlgorithmException, NoSuchProviderException { - // 根据种子生成一个安全的随机数 - SecureRandom secureRandom = null; - secureRandom = new SecureRandom(SEED.getBytes()); - - KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHM_DES); - keyGen.init(secureRandom); - return keyGen.generateKey(); - } - - public DESCoder(String transformation) - throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException { - this.key = initKey(); - this.cipher = Cipher.getInstance(transformation); - this.transformation = transformation; - } - - public static void main(String[] args) throws Exception { - DESCoder aes = new DESCoder(CIPHER_DES_CBC_PKCS5PADDING); - - String msg = "Hello World!"; - System.out.println("原文: " + msg); - byte[] encoded = aes.encrypt(msg.getBytes("UTF8")); - String encodedBase64 = Base64.toBase64String(encoded); - System.out.println("密文: " + encodedBase64); - - byte[] decodedBase64 = Base64.decode(encodedBase64); - byte[] decoded = aes.decrypt(decodedBase64); - System.out.println("明文: " + new String(decoded)); - } - - /** - * 加密 - * - * @param input 明文 - * @return byte[] 密文 - * @throws InvalidKeyException - * @throws IllegalBlockSizeException - * @throws BadPaddingException - * @throws InvalidAlgorithmParameterException - * @author Zhang Peng - * @since 2016年7月20日 - */ - public byte[] encrypt(byte[] input) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, - InvalidAlgorithmParameterException { - if (transformation.equals(CIPHER_DES_CBC_PKCS5PADDING) || transformation.equals(CIPHER_DES_CBC_NOPADDING)) { - cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(getIV())); - } else { - cipher.init(Cipher.ENCRYPT_MODE, key); - } - return cipher.doFinal(input); - } - - /** - * 解密 - * - * @param input 密文 - * @return byte[] 明文 - * @throws InvalidKeyException - * @throws IllegalBlockSizeException - * @throws BadPaddingException - * @throws InvalidAlgorithmParameterException - * @author Zhang Peng - * @since 2016年7月20日 - */ - public byte[] decrypt(byte[] input) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, - InvalidAlgorithmParameterException { - if (transformation.equals(CIPHER_DES_CBC_PKCS5PADDING) || transformation.equals(CIPHER_DES_CBC_NOPADDING)) { - cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(getIV())); - } else { - cipher.init(Cipher.DECRYPT_MODE, key); - } - return cipher.doFinal(input); - } - - private byte[] getIV() { - String iv = "01234567"; // IV length: must be 8 bytes long - return iv.getBytes(); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/DESedeCoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/DESedeCoder.java deleted file mode 100644 index 1af976f8..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/DESedeCoder.java +++ /dev/null @@ -1,73 +0,0 @@ -package io.github.dunwu.javaee.oss.encode.encrypt; - -import java.security.Key; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.Security; -import javax.crypto.Cipher; -import javax.crypto.KeyGenerator; -import org.apache.commons.codec.binary.Base64; -import org.bouncycastle.jce.provider.BouncyCastleProvider; - -/** - * DESede安全编码,DES的升级版,支持更长的密钥,基本算法不变。 - * - * @author Zhang Peng - * @since 2016年7月20日 - */ -public class DESedeCoder { - - /** - * 加密算法 - */ - public static final String KEY_ALGORITHM = "DESede"; - - /** - * 算法名称/加密模式/填充方式 - */ - public static final String CIPHER_ALGORITHM = "DESede/ECB/PKCS5Padding"; - - /** - * 密钥 - */ - private Key key; - - public DESedeCoder() throws NoSuchAlgorithmException, NoSuchProviderException { - this.key = initKey(); - } - - private Key initKey() throws NoSuchAlgorithmException, NoSuchProviderException { - // 标准的密钥生成 - // KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHM); - // keyGen.init(112); - - // 标准的密钥生成不支持128位。如果要使用,需引入Bouncy Castle的加密算法,方法如下 - Security.addProvider(new BouncyCastleProvider()); - KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHM, "BC"); - keyGen.init(128); - return keyGen.generateKey(); - } - - public static void main(String[] args) throws Exception { - DESedeCoder desedeCoder = new DESedeCoder(); - String message = "Hello World!"; - byte[] ciphertext = desedeCoder.encrypt(message.getBytes()); - System.out.println(Base64.encodeBase64String(ciphertext)); - - byte[] plaintext = desedeCoder.decrypt(ciphertext); - System.out.println(new String(plaintext)); - } - - public byte[] encrypt(byte[] plaintext) throws Exception { - Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); - cipher.init(Cipher.ENCRYPT_MODE, key); - return cipher.doFinal(plaintext); - } - - public byte[] decrypt(byte[] ciphertext) throws Exception { - Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); - cipher.init(Cipher.DECRYPT_MODE, key); - return cipher.doFinal(ciphertext); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/PBECoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/PBECoder.java deleted file mode 100644 index c4f22552..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/PBECoder.java +++ /dev/null @@ -1,91 +0,0 @@ -package io.github.dunwu.javaee.oss.encode.encrypt; - -import java.security.Key; -import java.security.SecureRandom; -import javax.crypto.Cipher; -import javax.crypto.SecretKey; -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.PBEKeySpec; -import javax.crypto.spec.PBEParameterSpec; -import org.apache.commons.codec.binary.Base64; - -/** - * 基于口令加密(Password Based Encryption, PBE),是一种对称加密算法。 其特点是:口令由用户自己掌管,采用随机数(这里叫做盐)杂凑多重加密等方法保证数据的安全性。 - * PBE没有密钥概念,密钥在其他对称加密算法中是经过计算得出的,PBE则使用口令替代了密钥。 - * - * @author Zhang Peng - * @since 2016年7月20日 - */ -public class PBECoder { - - public static final String KEY_ALGORITHM = "PBEWITHMD5andDES"; - - public static final int ITERATION_COUNT = 100; - - private Key key; - - private byte[] salt; - - public PBECoder(String password) throws Exception { - this.salt = initSalt(); - this.key = initKey(password); - } - - private byte[] initSalt() throws Exception { - SecureRandom secureRandom = new SecureRandom(); - return secureRandom.generateSeed(8); // 盐长度必须为8字节 - } - - private Key initKey(String password) throws Exception { - PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray()); - SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(KEY_ALGORITHM); - return keyFactory.generateSecret(keySpec); - } - - public static void main(String[] args) throws Exception { - PBECoder encode = new PBECoder("123456"); - String message = "Hello World!"; - byte[] ciphertext = encode.encrypt(message.getBytes()); - byte[] plaintext = encode.decrypt(ciphertext); - - System.out.println("原文:" + message); - System.out.println("密文:" + Base64.encodeBase64String(ciphertext)); - System.out.println("明文:" + new String(plaintext)); - } - - public byte[] encrypt(byte[] plaintext) throws Exception { - PBEParameterSpec paramSpec = new PBEParameterSpec(salt, ITERATION_COUNT); - Cipher cipher = Cipher.getInstance(KEY_ALGORITHM); - cipher.init(Cipher.ENCRYPT_MODE, key, paramSpec); - return cipher.doFinal(plaintext); - } - - public byte[] decrypt(byte[] ciphertext) throws Exception { - PBEParameterSpec paramSpec = new PBEParameterSpec(salt, ITERATION_COUNT); - Cipher cipher = Cipher.getInstance(KEY_ALGORITHM); - cipher.init(Cipher.DECRYPT_MODE, key, paramSpec); - return cipher.doFinal(ciphertext); - } - - public static void test1() throws Exception { - - // 产生盐 - SecureRandom secureRandom = new SecureRandom(); - byte[] salt = secureRandom.generateSeed(8); // 盐长度必须为8字节 - - // 产生Key - String password = "123456"; - PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray()); - SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(KEY_ALGORITHM); - SecretKey secretKey = keyFactory.generateSecret(keySpec); - - PBEParameterSpec paramSpec = new PBEParameterSpec(salt, ITERATION_COUNT); - Cipher cipher = Cipher.getInstance(KEY_ALGORITHM); - cipher.init(Cipher.ENCRYPT_MODE, secretKey, paramSpec); - - byte[] plaintext = "Hello World".getBytes(); - byte[] ciphertext = cipher.doFinal(plaintext); - new String(ciphertext); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/RSACoder.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/RSACoder.java deleted file mode 100644 index 79881b66..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/encrypt/RSACoder.java +++ /dev/null @@ -1,138 +0,0 @@ -package io.github.dunwu.javaee.oss.encode.encrypt; - -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; -import javax.crypto.Cipher; -import org.apache.commons.codec.binary.Base64; - -/** - * RSA安全编码:非对称加密算法。它既可以用来加密、解密,也可以用来做数字签名 - * - * @author Zhang Peng - * @since 2016年7月20日 - */ -public class RSACoder { - - public final static String KEY_ALGORITHM = "RSA"; - - public final static String SIGN_ALGORITHM = "MD5WithRSA"; - - private KeyPair keyPair; - - public RSACoder() throws Exception { - this.keyPair = initKeyPair(); - } - - private KeyPair initKeyPair() throws Exception { - // KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象 - KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM); - // 初始化密钥对生成器,密钥大小为1024位 - keyPairGen.initialize(1024); - // 生成一个密钥对 - return keyPairGen.genKeyPair(); - } - - public static void main(String[] args) throws Exception { - String msg = "Hello World!"; - RSACoder coder = new RSACoder(); - // 私钥加密,公钥解密 - byte[] ciphertext = coder.encryptByPrivateKey(msg.getBytes("UTF8"), coder.keyPair.getPrivate().getEncoded()); - byte[] plaintext = coder.decryptByPublicKey(ciphertext, coder.keyPair.getPublic().getEncoded()); - - // 公钥加密,私钥解密 - byte[] ciphertext2 = coder.encryptByPublicKey(msg.getBytes(), coder.keyPair.getPublic().getEncoded()); - byte[] plaintext2 = coder.decryptByPrivateKey(ciphertext2, coder.keyPair.getPrivate().getEncoded()); - - byte[] sign = coder.signature(msg.getBytes(), coder.getPrivateKey(), RsaSignTypeEn.SHA1WithRSA); - boolean flag = coder.verify(msg.getBytes(), coder.getPublicKey(), sign, RsaSignTypeEn.SHA1WithRSA); - String result = flag ? "数字签名匹配" : "数字签名不匹配"; - - System.out.println("原文:" + msg); - System.out.println("公钥:" + Base64.encodeBase64URLSafeString(coder.keyPair.getPublic().getEncoded())); - System.out.println("私钥:" + Base64.encodeBase64URLSafeString(coder.keyPair.getPrivate().getEncoded())); - - System.out.println("============== 私钥加密,公钥解密 =============="); - System.out.println("密文:" + Base64.encodeBase64URLSafeString(ciphertext)); - System.out.println("明文:" + new String(plaintext)); - - System.out.println("============== 公钥加密,私钥解密 =============="); - System.out.println("密文:" + Base64.encodeBase64URLSafeString(ciphertext2)); - System.out.println("明文:" + new String(plaintext2)); - - System.out.println("============== 数字签名 =============="); - System.out.println("数字签名:" + Base64.encodeBase64URLSafeString(sign)); - System.out.println("验证结果:" + result); - } - - public byte[] encryptByPrivateKey(byte[] plaintext, byte[] key) throws Exception { - PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(key); - KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); - PrivateKey privateKey = keyFactory.generatePrivate(keySpec); - Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); - cipher.init(Cipher.ENCRYPT_MODE, privateKey); - return cipher.doFinal(plaintext); - } - - public byte[] decryptByPublicKey(byte[] ciphertext, byte[] key) throws Exception { - X509EncodedKeySpec keySpec = new X509EncodedKeySpec(key); - KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); - PublicKey publicKey = keyFactory.generatePublic(keySpec); - Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); - cipher.init(Cipher.DECRYPT_MODE, publicKey); - return cipher.doFinal(ciphertext); - } - - public byte[] encryptByPublicKey(byte[] plaintext, byte[] key) throws Exception { - X509EncodedKeySpec keySpec = new X509EncodedKeySpec(key); - KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); - PublicKey publicKey = keyFactory.generatePublic(keySpec); - Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); - cipher.init(Cipher.ENCRYPT_MODE, publicKey); - return cipher.doFinal(plaintext); - } - - public byte[] decryptByPrivateKey(byte[] ciphertext, byte[] key) throws Exception { - PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(key); - KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); - PrivateKey privateKey = keyFactory.generatePrivate(keySpec); - Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); - cipher.init(Cipher.DECRYPT_MODE, privateKey); - return cipher.doFinal(ciphertext); - } - - public byte[] signature(byte[] data, byte[] privateKey, RsaSignTypeEn type) throws Exception { - PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey); - KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); - PrivateKey key = keyFactory.generatePrivate(keySpec); - - Signature signature = Signature.getInstance(type.name()); - signature.initSign(key); - signature.update(data); - return signature.sign(); - } - - public byte[] getPrivateKey() { - return keyPair.getPrivate().getEncoded(); - } - - public boolean verify(byte[] data, byte[] publicKey, byte[] sign, RsaSignTypeEn type) throws Exception { - X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey); - KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); - PublicKey key = keyFactory.generatePublic(keySpec); - - Signature signature = Signature.getInstance(type.name()); - signature.initVerify(key); - signature.update(data); - return signature.verify(sign); - } - - public byte[] getPublicKey() { - return keyPair.getPublic().getEncoded(); - } - - public enum RsaSignTypeEn { - - MD2WithRSA, MD5WithRSA, SHA1WithRSA - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/DownloadPolicy.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/DownloadPolicy.java deleted file mode 100644 index d5720446..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/DownloadPolicy.java +++ /dev/null @@ -1,111 +0,0 @@ -package io.github.dunwu.javaee.oss.encode.sample; - -import java.io.Serializable; -import org.apache.commons.lang3.StringUtils; - -/** - * Created by Zhang Peng on 2016/7/22. - */ -public class DownloadPolicy implements Serializable { - - /** - * - */ - private static final long serialVersionUID = 1159078308838844309L; - - /** - * 操作类型 - */ - private final String operate = UploadConstant.TOKEN_DOWNLOAD; - - /** - * 文件ID - */ - private Long fileId; - - /** - * 下载文件所属空间 - */ - private String namespace; - - /** - * 令牌有效的截止时间。用Unix时间表示。单位秒 - */ - private Long deadline; - - /** - * 允许下载文件类型 - */ - private String fType = UploadConstant.SUPPORT_FILE_TYPE; - - /** - * 判断数据是否有效 - * - * @param policy - * @return boolean - * @author Zhang Peng - * @since 2016年7月22日 - */ - public static boolean isValid(DownloadPolicy policy) { - // 检查必要项是否为空 - if (StringUtils.isBlank(policy.namespace) || null == policy.deadline) { - return false; - } - - // 令牌截止时间不能是已过期时间 - long life = policy.deadline - System.currentTimeMillis() / 1000; - if (life <= 0) { - return false; - } - - // 检查文件类型 - if (StringUtils.isBlank(policy.fType)) { - return false; - } - String[] requestTypes = policy.fType.split("\\|"); - for (String item : requestTypes) { - if (!UploadConstant.SUPPORT_FILE_TYPE_SET.contains(item)) { - return false; - } - } - - return true; - } - - public Long getFileId() { - return fileId; - } - - public void setFileId(Long fileId) { - this.fileId = fileId; - } - - public String getNamespace() { - return namespace; - } - - public void setNamespace(String namespace) { - this.namespace = namespace; - } - - public Long getDeadline() { - return deadline; - } - - public void setDeadline(Long e) { - this.deadline = e; - } - - public String getfType() { - return fType; - } - - public void setfType(String fType) { - this.fType = fType; - } - - public String getOperate() { - return operate; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/ModifyPolicy.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/ModifyPolicy.java deleted file mode 100644 index 0ed0a444..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/ModifyPolicy.java +++ /dev/null @@ -1,95 +0,0 @@ -package io.github.dunwu.javaee.oss.encode.sample; - -import java.io.Serializable; -import org.apache.commons.lang3.StringUtils; - -/** - * Created by Zhang Peng on 2016/7/22. - */ -public class ModifyPolicy implements Serializable { - - private static final long serialVersionUID = -547821705196510884L; - - /** - * 操作类型 - */ - private final String operate = UploadConstant.TOKEN_MODIFY; - - /** - * 下载文件所属空间 - */ - private String namespace; - - /** - * 令牌有效的截止时间。用Unix时间表示。单位秒 - */ - private Long deadline; - - /** - * 允许下载文件类型 - */ - private String fType = UploadConstant.SUPPORT_FILE_TYPE; - - /** - * 判断数据是否有效 - * - * @param policy - * @return boolean - * @author Zhang Peng - * @since 2016年7月22日 - */ - public static boolean isValid(ModifyPolicy policy) { - // 检查必要项是否为空 - if (StringUtils.isBlank(policy.namespace) || null == policy.deadline) { - return false; - } - - // 令牌截止时间不能是已过期时间 - long life = policy.deadline - System.currentTimeMillis() / 1000; - if (life <= 0) { - return false; - } - - // 检查文件类型 - if (StringUtils.isBlank(policy.fType)) { - return false; - } - String[] requestTypes = policy.fType.split("\\|"); - for (String item : requestTypes) { - if (!UploadConstant.SUPPORT_FILE_TYPE_SET.contains(item)) { - return false; - } - } - - return true; - } - - public String getNamespace() { - return namespace; - } - - public void setNamespace(String namespace) { - this.namespace = namespace; - } - - public Long getDeadline() { - return deadline; - } - - public void setDeadline(Long deadline) { - this.deadline = deadline; - } - - public String getfType() { - return fType; - } - - public void setfType(String fType) { - this.fType = fType; - } - - public String getOperate() { - return operate; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/TokenUtil.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/TokenUtil.java deleted file mode 100644 index 35b5f1ab..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/TokenUtil.java +++ /dev/null @@ -1,158 +0,0 @@ -package io.github.dunwu.javaee.oss.encode.sample; - -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONObject; -import io.github.dunwu.javaee.oss.encode.digest.HmacCoder; -import org.apache.commons.codec.binary.Base64; - -/** - * 令牌工具类 token = accessKey + digest + policy digest = HmacSHA256(policy, secretKey) - */ -public final class TokenUtil { - - private static final String SEPARATOR = "|||"; - - private static final String ACCESS_KEY = "ACCESS_KEY"; - - private static final String SECRET_KEY = "SECRET_KEY"; - - public static Object testGetPolicy(String token, String tokenType) throws Exception { - String[] params = token.split(SEPARATOR); - // byte[] accessKey = Base64.decodeBase64(params[0]); - String digestBase64 = params[1]; - String policyBase64 = params[2]; - - if (TokenTypeEN.UPLOAD.name().equals(tokenType)) { - UploadPolicy policy = (UploadPolicy) getPolicy(policyBase64, digestBase64, SECRET_KEY.getBytes(), - tokenType); - return policy; - } else if (TokenTypeEN.DOWNLOAD.name().equals(tokenType)) { - DownloadPolicy policy = (DownloadPolicy) getPolicy(policyBase64, digestBase64, SECRET_KEY.getBytes(), - tokenType); - return policy; - } else if (TokenTypeEN.MODIFY.name().equals(tokenType)) { - ModifyPolicy policy = (ModifyPolicy) getPolicy(policyBase64, digestBase64, SECRET_KEY.getBytes(), - tokenType); - return policy; - } else { - return null; - } - } - - public static Object getPolicy(String policyBase64, String digestBase64, byte[] secretKey, String tokenType) - throws Exception { - String policy = new String(Base64.decodeBase64(policyBase64)); - - // 根据secretKey和policy验证摘要是否被篡改 - byte[] checkDigest = HmacCoder.encode(policy.getBytes(), secretKey, HmacCoder.HmacTypeEn.HmacSHA512); - String checkDigestBase64 = Base64.encodeBase64URLSafeString(checkDigest); - if (!checkDigestBase64.equals(digestBase64)) { - throw new SecurityException("The policy is not match to the digest."); - } - - JSONObject json = JSONObject.parseObject(policy); - if (TokenTypeEN.UPLOAD.name().equalsIgnoreCase(tokenType)) { - return JSONObject.toJavaObject(json, UploadPolicy.class); - } else if (TokenTypeEN.DOWNLOAD.name().equalsIgnoreCase(tokenType)) { - return JSONObject.toJavaObject(json, DownloadPolicy.class); - } else if (TokenTypeEN.MODIFY.name().equalsIgnoreCase(tokenType)) { - return JSONObject.toJavaObject(json, ModifyPolicy.class); - } - - return null; - } - - public static void main(String[] args) throws Exception { - testGetToken(TokenTypeEN.DOWNLOAD.name()); - } - - public static String testGetToken(String tokenType) throws Exception { - String policy = null; - if (TokenTypeEN.UPLOAD.name().equals(tokenType)) { - policy = initUploadPolicy(); - } else if (TokenTypeEN.DOWNLOAD.name().equals(tokenType)) { - policy = initDownladPolicy(); - } else if (TokenTypeEN.MODIFY.name().equals(tokenType)) { - policy = initModifyPolicy(); - } - String policyBase64 = Base64.encodeBase64URLSafeString(policy.getBytes()); - String accessKeyBase64 = Base64.encodeBase64URLSafeString(ACCESS_KEY.getBytes()); - - System.out.println(String.format("============== %s ==============", tokenType)); - System.out.println("policy:" + policy); - System.out.println("policyBase64:" + policyBase64); - System.out.println("accessKeyBase64:" + accessKeyBase64); - - String token = getToken(policy.getBytes(), ACCESS_KEY.getBytes(), SECRET_KEY.getBytes(), tokenType); - System.out.println("Token:" + token); - return token; - } - - private static String initUploadPolicy() { - long deadline = System.currentTimeMillis() / 1000 + 3600 * 7; - UploadPolicy policy = new UploadPolicy(); - policy.setNamespace("namespace"); - policy.setDeadline(deadline); - policy.setfType("pdf"); - return JSON.toJSONString(policy); - } - - private static String initDownladPolicy() { - long deadline = System.currentTimeMillis() / 1000 + 3600 * 7; - DownloadPolicy policy = new DownloadPolicy(); - policy.setFileId(5748527L); - policy.setNamespace("namespace"); - policy.setDeadline(deadline); - policy.setfType("pdf"); - return JSON.toJSONString(policy); - } - - private static String initModifyPolicy() { - long deadline = System.currentTimeMillis() / 1000 + 3600 * 7; - ModifyPolicy policy = new ModifyPolicy(); - policy.setNamespace("namespace"); - policy.setDeadline(deadline); - policy.setfType("png"); - return JSON.toJSONString(policy); - } - - public static String getToken(byte[] policy, byte[] accessKey, byte[] secretKey, String tokenType) - throws Exception { - JSONObject policyJson = JSONObject.parseObject(new String(policy)); - - // 检查令牌是否符合系统规格 - if (TokenTypeEN.UPLOAD.name().equalsIgnoreCase(tokenType)) { - UploadPolicy uploadPolicy = JSONObject.toJavaObject(policyJson, UploadPolicy.class); - if (!UploadPolicy.isValid(uploadPolicy)) { - throw new Exception("The policy is not conform to the specifications of the system."); - } - } else if (TokenTypeEN.DOWNLOAD.name().equalsIgnoreCase(tokenType)) { - DownloadPolicy downloadPolicy = JSONObject.toJavaObject(policyJson, DownloadPolicy.class); - if (!DownloadPolicy.isValid(downloadPolicy)) { - throw new Exception("The policy is not conform to the specifications of the system."); - } - } else if (TokenTypeEN.MODIFY.name().equalsIgnoreCase(tokenType)) { - ModifyPolicy modifyPolicy = JSONObject.toJavaObject(policyJson, ModifyPolicy.class); - if (!ModifyPolicy.isValid(modifyPolicy)) { - throw new Exception("The policy is not conform to the specifications of the system."); - } - } else { - throw new Exception("Required token is not supported."); - } - - // 根据secretKey和policy生成消息摘要(使用基于口令编码的HmacSHA256算法) - byte[] digest = HmacCoder.encode(policy, secretKey, HmacCoder.HmacTypeEn.HmacSHA512); - - // Token = AccessKey::Digest::Policy。数据拼接之前都要做URL安全的Base64编码 - String token = Base64.encodeBase64URLSafeString(accessKey) + SEPARATOR - + Base64.encodeBase64URLSafeString(digest) + SEPARATOR + Base64.encodeBase64URLSafeString(policy); - - return token; - } - - public enum TokenTypeEN { - - UPLOAD, DOWNLOAD, MODIFY - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/UploadConstant.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/UploadConstant.java deleted file mode 100644 index 8c6affbf..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/UploadConstant.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.github.dunwu.javaee.oss.encode.sample; - -import java.util.HashSet; -import java.util.Set; -import org.apache.commons.collections.CollectionUtils; - -/** - * Created by Zhang Peng on 2016/7/26. - */ -public class UploadConstant { - - public static final long FSIZE_MIN = 1024L; // 1KB - - public static final long FSIZE_MAX = 5 * 1024 * 1024L; // 5MB - - public static final long FSIZE_MIN_DEFAULT = 1024L; // 1KB - - public static final long FSIZE_MAX_DEFAULT = 2 * 1024 * 1024L; // 2MB - - public static final String TOKEN_UPLOAD = "UPLOAD"; - - public static final String TOKEN_DOWNLOAD = "DOWNLOAD"; - - public static final String TOKEN_MODIFY = "MODIFY"; - - public static final String SUPPORT_FILE_TYPE = "pdf|doc|docx|png|jpg|jpeg|gif"; - - public static final Set SUPPORT_FILE_TYPE_SET; - - static { - SUPPORT_FILE_TYPE_SET = new HashSet(); - String[] supportedTypes = UploadConstant.SUPPORT_FILE_TYPE.split("\\|"); - CollectionUtils.addAll(SUPPORT_FILE_TYPE_SET, supportedTypes); - } -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/UploadPolicy.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/UploadPolicy.java deleted file mode 100644 index 2afad08a..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/encode/sample/UploadPolicy.java +++ /dev/null @@ -1,153 +0,0 @@ -package io.github.dunwu.javaee.oss.encode.sample; - -import java.io.Serializable; -import org.apache.commons.lang3.StringUtils; - -/** - * Created by Zhang Peng on 2016/7/22. - */ -public class UploadPolicy implements Serializable { - - private static final long serialVersionUID = 8289239747395166646L; - - /** - * 操作类型 - */ - private final String operate = UploadConstant.TOKEN_UPLOAD; - - /** - * 上传文件所属空间 - */ - private String namespace; - - /** - * 令牌有效的截止时间。用Unix时间表示。单位秒 - */ - private Long deadline; - - /** - * 文件上传后保留时间。单位天。默认值-1,表示永久保留 - */ - private Integer deleteAfterDays = -1; - - /** - * 上传文件大小上限。单位Byte。 - */ - private Long fsizeMin = UploadConstant.FSIZE_MIN_DEFAULT; - - /** - * 上传文件大小上限。单位Byte。 - */ - private Long fsizeMax = UploadConstant.FSIZE_MAX_DEFAULT; - - /** - * 允许上传文件类型 - */ - private String fType = UploadConstant.SUPPORT_FILE_TYPE; - - /** - * 是否认证令牌。0表示认证,1表示不认证。默认为0 - */ - private Integer verifyToken = 0; - - /** - * 判断数据是否有效 - * - * @param policy - * @return boolean - * @author Zhang Peng - * @since 2016年7月22日 - */ - public static boolean isValid(UploadPolicy policy) { - // 检查必要项是否为空 - if (StringUtils.isBlank(policy.namespace) || null == policy.deadline) { - return false; - } - - // 令牌截止时间不能是已过期时间 - long life = policy.deadline - System.currentTimeMillis() / 1000; - if (life <= 0) { - return false; - } - - // 判断文件大小的上限、下限是否符合系统规格 - if (policy.fsizeMin > policy.fsizeMax || policy.fsizeMin < UploadConstant.FSIZE_MIN - || policy.fsizeMax > UploadConstant.FSIZE_MAX) { - return false; - } - - // 检查文件类型 - if (StringUtils.isBlank(policy.fType)) { - return false; - } - String[] requestTypes = policy.fType.split("\\|"); - for (String item : requestTypes) { - if (!UploadConstant.SUPPORT_FILE_TYPE_SET.contains(item)) { - return false; - } - } - - return true; - } - - public String getNamespace() { - return namespace; - } - - public void setNamespace(String namespace) { - this.namespace = namespace; - } - - public Long getDeadline() { - return deadline; - } - - public void setDeadline(Long deadline) { - this.deadline = deadline; - } - - public Integer getDeleteAfterDays() { - return deleteAfterDays; - } - - public void setDeleteAfterDays(Integer deleteAfterDays) { - this.deleteAfterDays = deleteAfterDays; - } - - public Long getFsizeMin() { - return fsizeMin; - } - - public void setFsizeMin(Long fsizeMin) { - this.fsizeMin = fsizeMin; - } - - public Long getFsizeMax() { - return fsizeMax; - } - - public void setFsizeMax(Long fsizeMax) { - this.fsizeMax = fsizeMax; - } - - public String getfType() { - return fType; - } - - public void setfType(String fType) { - this.fType = fType; - } - - public Integer getVerifyToken() { - return verifyToken; - } - - public void setVerifyToken(Integer verifyToken) { - this.verifyToken = verifyToken; - } - - public String getOperate() { - return operate; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/html/CnblogParser.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/html/CnblogParser.java deleted file mode 100644 index c8027e39..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/html/CnblogParser.java +++ /dev/null @@ -1,52 +0,0 @@ -/** - * The Apache License 2.0 Copyright (c) 2016 Victor Zhang - */ -package io.github.dunwu.javaee.oss.html; - -import java.io.IOException; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.select.Elements; - -/** - * 博客园博文抓取工具 - * - * @author Victor Zhang - * @since 2016/11/8. - */ -public class CnblogParser { - - private static final String BLOG_URL = "http://www.cnblogs.com/jingmoxukong/"; - - public static void main(String[] args) throws Exception { - int total = 0; - for (int page = 0; page <= 16; page++) { - total += printAllTitleInPage(BLOG_URL, page); - } - System.out.println("总文章数:" + total); - } - - /** - * 获取指定页HTML 文档指定的body - * - * @throws IOException - */ - private static int printAllTitleInPage(String blogUrl, int page) throws IOException { - int count = 0; - Document doc = Jsoup.connect(blogUrl + "default.html?page=" + page).get(); - Elements postTitles = doc.body().getElementsByClass("postTitle"); - for (Element postTitle : postTitles) { - Elements links = postTitle.getElementsByTag("a"); - for (Element link : links) { - if (link.hasText()) { - System.out.println(link.text()); - System.out.println(link.attr("href")); - count++; - } - } - } - return count; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/html/XiamiParser.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/html/XiamiParser.java deleted file mode 100644 index e5708593..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/html/XiamiParser.java +++ /dev/null @@ -1,109 +0,0 @@ -/** - * The Apache License 2.0 Copyright (c) 2016 Victor Zhang - */ -package io.github.dunwu.javaee.oss.html; - -import java.io.IOException; -import java.util.HashSet; -import java.util.Set; -import org.apache.commons.collections.CollectionUtils; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.select.Elements; - -/** - * 获取虾米网我的音乐中所有曲目 - * - * @author Victor Zhang - * @since 2016/11/8. - */ -public class XiamiParser { - - private static final String BLOG_URL = "http://www.xiami.com/space/lib-song/u/5524914/page"; - - public static void main(String[] args) throws Exception { - XiamiParser parser = new XiamiParser(); - Set allSongInfos = new HashSet<>(); - for (int page = 0; page <= 65; page++) { - Set curPageSongs = parser.getSongInfoSet(BLOG_URL, page); - CollectionUtils.addAll(allSongInfos, curPageSongs.iterator()); - } - System.out.println("总歌曲数目:" + allSongInfos.size()); - parser.printAllSongInfo(allSongInfos); - } - - /** - * 获取指定页HTML 文档指定的body - * - * @throws IOException - */ - private Set getSongInfoSet(String blogUrl, int page) throws IOException { - Set songList = new HashSet(); - Document doc = Jsoup.connect(blogUrl + "/" + page).get(); - Elements postTitles = doc.body().getElementsByClass("track_list"); - for (Element postTitle : postTitles) { - Elements songs = postTitle.getElementsByTag("tr"); - for (Element song : songs) { - Elements name = song.getElementsByClass("song_name"); - for (Element link : name) { - SongInfo songinfo = new SongInfo(); - songinfo.setName(link.child(0).text()); - Elements artistName = link.getElementsByClass("artist_name"); - songinfo.setArtist(artistName.get(0).text()); - songList.add(songinfo); - } - } - } - return songList; - } - - private void printAllSongInfo(Set songs) { - for (SongInfo song : songs) { - System.out.println(song.getName() + "\t" + song.getArtist()); - } - } - - public class SongInfo { - - private String name; - - private String artist; - - @Override - public boolean equals(Object obj) { - if (obj.getClass() != SongInfo.class) { - return false; - } - SongInfo external = (SongInfo) obj; - if (external.getName().equals(this.getName()) && external.getArtist().equals(this.getArtist())) { - return true; - } else { - return false; - } - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getArtist() { - return artist; - } - - public void setArtist(String artist) { - this.artist = artist; - } - - @Override - public int hashCode() { - return 1; - } - - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/ImageUtil.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/ImageUtil.java deleted file mode 100644 index d0edc676..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/ImageUtil.java +++ /dev/null @@ -1,143 +0,0 @@ -/** - * The Apache License 2.0 Copyright (c) 2016 Victor Zhang - */ -package io.github.dunwu.javaee.oss.image; - -import io.github.dunwu.javaee.oss.image.dto.ImageParamDTO; -import java.awt.image.BufferedImage; -import javax.imageio.ImageIO; -import net.coobird.thumbnailator.Thumbnails; -import net.coobird.thumbnailator.geometry.Positions; -import net.sf.jmimemagic.*; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * 图片工具类 - * - * @author Victor Zhang - * @since 2017/1/16. - */ -public class ImageUtil { - - private static final Logger logger = LoggerFactory.getLogger(ImageUtil.class); - - public static void toFile(String oldFile, String newFile, ImageParamDTO params) throws IOException { - if (StringUtils.isBlank(oldFile) || StringUtils.isBlank(newFile)) { - logger.error("原文件名或目标文件名为空"); - return; - } - Thumbnails.Builder builder = Thumbnails.of(oldFile); - fillBuilderWithParams(builder, params); - if (null == builder) { - return; - } - builder.toFile(newFile); - } - - private static void fillBuilderWithParams(Thumbnails.Builder builder, ImageParamDTO params) throws IOException { - if (null == params) { - throw new IOException("图片格式化参数为空"); - } - - // 按照一定规则改变原图尺寸 - if (null != params.getWidth() && null != params.getHeight()) { - builder.size(params.getWidth(), params.getHeight()); - } else if (null != params.getXscale() && null != params.getYscale()) { - builder.scale(params.getXscale(), params.getYscale()); - } else if (null != params.getScale()) { - builder.scale(params.getScale(), params.getScale()); - } else { - builder.scale(1.0); // 如果没有设置尺寸参数,默认大小为原图大小 - } - - // 设置图片旋转角度 - if (null != params.getRotate()) { - builder.rotate(params.getRotate()); - } - - // 设置图片压缩质量 - if (null != params.getQuality()) { - builder.outputQuality(params.getQuality()); - } - - // 设置图片格式 - if (StringUtils.isNotBlank(params.getFormat())) { - builder.outputFormat(params.getFormat()); - } - - // 设置水印 - ImageParamDTO.WaterMark waterMark = params.getWaterMark(); - if (null != waterMark) { - Positions pos = ImageParamDTO.getPostionsByCode(waterMark.getPosition()); - if (null == pos) { - throw new IOException("请检查水印图片的位置类型,有效范围在[1,9]"); - } - BufferedImage bufferedImage = ImageIO.read(new FileInputStream(waterMark.getImage())); - builder.watermark(pos, bufferedImage, waterMark.getOpacity()); - } - } - - public static BufferedImage toBufferedImage(String oldFile, ImageParamDTO params) throws IOException { - if (StringUtils.isBlank(oldFile)) { - logger.error("原文件名或目标文件名为空"); - return null; - } - Thumbnails.Builder builder = Thumbnails.of(oldFile); - fillBuilderWithParams(builder, params); - if (null == builder) { - return null; - } - return builder.asBufferedImage(); - } - - public static OutputStream toOutputStream(InputStream input, OutputStream output, ImageParamDTO params) - throws IOException { - Thumbnails.Builder builder = Thumbnails.of(input); - if (null == builder) { - return null; - } - - try { - fillBuilderWithParams(builder, params); - builder.toOutputStream(output); - } catch (IOException e) { - logger.error("图片处理失败\n" + e.getMessage()); - throw e; - } - - return output; - } - - /** - * 获取文件的 ContentType - * - * @param content - * @return - * @throws MagicParseException - * @throws MagicException - * @throws MagicMatchNotFoundException - */ - public static String getContentType(byte[] content) - throws MagicParseException, MagicException, MagicMatchNotFoundException { - MagicMatch match = Magic.getMagicMatch(content); - return match.getMimeType(); - } - - public static final InputStream bytes2InputStream(byte[] buf) { - return new ByteArrayInputStream(buf); - } - - public static final byte[] inputStream2bytes(InputStream inStream) throws IOException { - ByteArrayOutputStream swapStream = new ByteArrayOutputStream(); - byte[] buff = new byte[100]; - int rc = 0; - while ((rc = inStream.read(buff, 0, 100)) > 0) { - swapStream.write(buff, 0, rc); - } - byte[] in2b = swapStream.toByteArray(); - return in2b; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/QRCodeUtil.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/QRCodeUtil.java deleted file mode 100644 index 376407f4..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/QRCodeUtil.java +++ /dev/null @@ -1,60 +0,0 @@ -package io.github.dunwu.javaee.oss.image; - -import com.google.zxing.*; -import com.google.zxing.client.j2se.BufferedImageLuminanceSource; -import com.google.zxing.client.j2se.MatrixToImageWriter; -import com.google.zxing.common.BitMatrix; -import com.google.zxing.common.HybridBinarizer; -import io.github.dunwu.javaee.oss.image.dto.BarcodeParamDTO; -import java.awt.image.BufferedImage; -import java.io.FileInputStream; -import java.io.IOException; -import java.nio.file.FileSystems; -import java.nio.file.Path; -import javax.imageio.ImageIO; - -/** - * 二维码工具类 - * - * @author Victor Zhang - * @since 2017/1/16. - */ -public class QRCodeUtil { - - /** - * 创建一个qrcode图片 - * - * @param content 加密信息,建议使用json格式 - * @param paramDTO qrcode 参数 - * @throws WriterException - * @throws IOException - */ - public static void encode(String content, BarcodeParamDTO paramDTO) throws WriterException, IOException { - // 生成矩阵 - BitMatrix bitMatrix = new MultiFormatWriter().encode(content, paramDTO.getBarcodeFormat(), paramDTO.getWidth(), - paramDTO.getHeight(), paramDTO.getEncodeHints()); - Path path = FileSystems.getDefault().getPath(paramDTO.getFilepath()); - MatrixToImageWriter.writeToPath(bitMatrix, paramDTO.getImageFormat(), path);// 输出图像 - } - - /** - * 解析 qrcode 图片 - * - * @param paramDTO qrcode 参数 - * @return - */ - public static String decode(BarcodeParamDTO paramDTO) { - try { - BufferedImage bufferedImage = ImageIO.read(new FileInputStream(paramDTO.getFilepath())); - LuminanceSource source = new BufferedImageLuminanceSource(bufferedImage); - Binarizer binarizer = new HybridBinarizer(source); - BinaryBitmap bitmap = new BinaryBitmap(binarizer); - Result result = new MultiFormatReader().decode(bitmap, paramDTO.getDecodeHints()); - return result.getText(); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/dto/BarcodeParamDTO.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/dto/BarcodeParamDTO.java deleted file mode 100644 index 49c97e41..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/dto/BarcodeParamDTO.java +++ /dev/null @@ -1,87 +0,0 @@ -/** - * The Apache License 2.0 Copyright (c) 2016 Victor Zhang - */ -package io.github.dunwu.javaee.oss.image.dto; - -import com.google.zxing.BarcodeFormat; -import com.google.zxing.DecodeHintType; -import com.google.zxing.EncodeHintType; -import java.util.Map; - -/** - * @author Victor Zhang - * @since 2017/1/17. - */ -public class BarcodeParamDTO { - - private Integer width; // 图像宽度 - - private Integer height; // 图像高度 - - private String filepath; // 图片路径 - - private String imageFormat; // 图片文件格式 - - private BarcodeFormat barcodeFormat; // 二维码形式 - - private Map encodeHints; // 二维码的编码参数 - - private Map decodeHints; // 二维码的解码参数 - - public Integer getWidth() { - return width; - } - - public void setWidth(Integer width) { - this.width = width; - } - - public Integer getHeight() { - return height; - } - - public void setHeight(Integer height) { - this.height = height; - } - - public String getFilepath() { - return filepath; - } - - public void setFilepath(String filepath) { - this.filepath = filepath; - } - - public String getImageFormat() { - return imageFormat; - } - - public void setImageFormat(String imageFormat) { - this.imageFormat = imageFormat; - } - - public BarcodeFormat getBarcodeFormat() { - return barcodeFormat; - } - - public void setBarcodeFormat(BarcodeFormat barcodeFormat) { - this.barcodeFormat = barcodeFormat; - } - - public Map getEncodeHints() { - return encodeHints; - } - - public void setEncodeHints(Map encodeHints) { - this.encodeHints = encodeHints; - } - - public Map getDecodeHints() { - return decodeHints; - } - - public void setDecodeHints(Map decodeHints) { - this.decodeHints = decodeHints; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/dto/ImageParamDTO.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/dto/ImageParamDTO.java deleted file mode 100644 index 19146965..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/image/dto/ImageParamDTO.java +++ /dev/null @@ -1,181 +0,0 @@ -/** - * The Apache License 2.0 Copyright (c) 2016 Victor Zhang - */ -package io.github.dunwu.javaee.oss.image.dto; - -import java.io.Serializable; -import net.coobird.thumbnailator.geometry.Positions; - -/** - * @author Victor Zhang - * @since 2017/1/16. - */ -public class ImageParamDTO implements Serializable { - - public static String[] IMAGE_TYPES = {"png", "jpg", "jpeg", "bmp", "gif"} - - private Integer width; // 宽度 - - private Integer height; // 高度 - - private Double xscale; // 宽度比例 - - private Double yscale; // 高度比例 - - private Double scale; // 总比例,相当于将xscale和yscale都设为同比例 - - private Double rotate; // 旋转角度,范围为[0.0, 360.0] - - private Double quality; // 压缩质量,范围为[0.0, 1.0] - - private String format; // 图片格式,支持jpg,jpeg,png,bmp,gif - - private WaterMark waterMark; // 水印信息 - - /** - * 将位置类型码转换为 thumbnailator 可以识别的位置类型 - * - * @param code - * @return - */ - public static Positions getPostionsByCode(Integer code) { - switch (code) { - case 1: - return Positions.TOP_LEFT; - case 2: - return Positions.TOP_CENTER; - case 3: - return Positions.TOP_RIGHT; - case 4: - return Positions.CENTER_LEFT; - case 5: - return Positions.CENTER; - case 6: - return Positions.CENTER_RIGHT; - case 7: - return Positions.BOTTOM_LEFT; - case 8: - return Positions.BOTTOM_CENTER; - case 9: - return Positions.BOTTOM_RIGHT; - default: - return null; - } - } - - public Integer getWidth() { - return width; - } - - public void setWidth(Integer width) { - this.width = width; - } - - public Integer getHeight() { - return height; - } - - public void setHeight(Integer height) { - this.height = height; - } - - public Double getXscale() { - return xscale; - } - - public void setXscale(Double xscale) { - this.xscale = xscale; - } - - public Double getYscale() { - return yscale; - } - - public void setYscale(Double yscale) { - this.yscale = yscale; - } - - public Double getScale() { - return scale; - } - - public void setScale(Double scale) { - this.scale = scale; - } - - public Double getRotate() { - return rotate; - } - - public void setRotate(Double rotate) { - this.rotate = rotate; - } - - public Double getQuality() { - return quality; - } - - public void setQuality(Double quality) { - this.quality = quality; - } - - public String getFormat() { - return format; - } - - public void setFormat(String format) { - this.format = format; - } - - public WaterMark getWaterMark() { - return waterMark; - } - - public void setWaterMark(WaterMark waterMark) { - this.waterMark = waterMark; - } - - public static class WaterMark { - - private Integer position; - - private String image; - - private Float opacity; - - public WaterMark() { - } - - public WaterMark(Integer position, String image, Float opacity) { - this.position = position; - this.image = image; - this.opacity = opacity; - } - - public Integer getPosition() { - return position; - } - - public void setPosition(Integer position) { - this.position = position; - } - - public String getImage() { - return image; - } - - public void setImage(String image) { - this.image = image; - } - - public Float getOpacity() { - return opacity; - } - - public void setOpacity(Float opacity) { - this.opacity = opacity; - } - - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSHelloWorld.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSHelloWorld.java deleted file mode 100644 index 7808119c..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSHelloWorld.java +++ /dev/null @@ -1,140 +0,0 @@ -/** - * The Apache License 2.0 Copyright (c) 2016 Victor Zhang - */ -package io.github.dunwu.javaee.oss.jms; - -import javax.jms.*; -import org.apache.activemq.ActiveMQConnectionFactory; - -/** - * @author Victor Zhang - * @since 2016/11/29. - */ -public class JMSHelloWorld { - - public static void main(String[] args) throws Exception { - thread(new HelloWorldProducer(), false); - thread(new HelloWorldProducer(), false); - thread(new HelloWorldConsumer(), false); - Thread.sleep(1000); - thread(new HelloWorldConsumer(), false); - thread(new HelloWorldProducer(), false); - thread(new HelloWorldConsumer(), false); - thread(new HelloWorldProducer(), false); - Thread.sleep(1000); - thread(new HelloWorldConsumer(), false); - thread(new HelloWorldProducer(), false); - thread(new HelloWorldConsumer(), false); - thread(new HelloWorldConsumer(), false); - thread(new HelloWorldProducer(), false); - thread(new HelloWorldProducer(), false); - Thread.sleep(1000); - thread(new HelloWorldProducer(), false); - thread(new HelloWorldConsumer(), false); - thread(new HelloWorldConsumer(), false); - thread(new HelloWorldProducer(), false); - thread(new HelloWorldConsumer(), false); - thread(new HelloWorldProducer(), false); - thread(new HelloWorldConsumer(), false); - thread(new HelloWorldProducer(), false); - thread(new HelloWorldConsumer(), false); - thread(new HelloWorldConsumer(), false); - thread(new HelloWorldProducer(), false); - } - - public static void thread(Runnable runnable, boolean daemon) { - Thread brokerThread = new Thread(runnable); - brokerThread.setDaemon(daemon); - brokerThread.start(); - } - - public static class HelloWorldProducer implements Runnable { - - public void run() { - try { - // Create a ConnectionFactory - ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("vm://localhost"); - - // Create a Connection - Connection connection = connectionFactory.createConnection(); - connection.start(); - - // Create a Session - Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); - - // Create the destination (Topic or Queue) - Destination destination = session.createQueue("TEST.FOO"); - - // Create a MessageProducer from the Session to the Topic or Queue - MessageProducer producer = session.createProducer(destination); - producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT); - - // Create a messages - String text = "Hello world! From: " + Thread.currentThread().getName() + " : " + this.hashCode(); - TextMessage message = session.createTextMessage(text); - - // Tell the producer to send the message - System.out.println("Sent message: " + message.hashCode() + " : " + Thread.currentThread().getName()); - producer.send(message); - - // Clean up - session.close(); - connection.close(); - } catch (Exception e) { - System.out.println("Caught: " + e); - e.printStackTrace(); - } - } - - } - - public static class HelloWorldConsumer implements Runnable, ExceptionListener { - - public void run() { - try { - - // Create a ConnectionFactory - ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("vm://localhost"); - - // Create a Connection - Connection connection = connectionFactory.createConnection(); - connection.start(); - - connection.setExceptionListener(this); - - // Create a Session - Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); - - // Create the destination (Topic or Queue) - Destination destination = session.createQueue("TEST.FOO"); - - // Create a MessageConsumer from the Session to the Topic or Queue - MessageConsumer consumer = session.createConsumer(destination); - - // Wait for a message - Message message = consumer.receive(1000); - - if (message instanceof TextMessage) { - TextMessage textMessage = (TextMessage) message; - String text = textMessage.getText(); - System.out.println("Received: " + text); - } else { - System.out.println("Received: " + message); - } - - consumer.close(); - session.close(); - connection.close(); - } catch (Exception e) { - System.out.println("Caught: " + e); - e.printStackTrace(); - } - } - - public synchronized void onException(JMSException ex) { - System.out.println("JMS Exception occured. Shutting down client."); - } - - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSReceiver.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSReceiver.java deleted file mode 100644 index 604ccbb8..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSReceiver.java +++ /dev/null @@ -1,62 +0,0 @@ -/** - * The Apache License 2.0 Copyright (c) 2016 Victor Zhang - */ -package io.github.dunwu.javaee.oss.jms; - -import javax.jms.*; -import org.apache.activemq.ActiveMQConnection; -import org.apache.activemq.ActiveMQConnectionFactory; - -/** - * 消息的消费者 - * - * @author Victor Zhang - * @since 2016/11/28. - */ -public class JMSReceiver { - - public static void main(String[] args) { - // ConnectionFactory :连接工厂,JMS 用它创建连接 - ConnectionFactory connectionFactory; - // Connection :JMS 客户端到JMS Provider 的连接 - Connection connection = null; - // Session: 一个发送或接收消息的线程 - Session session; - // Destination :消息的目的地;消息发送给谁. - Destination destination; - // 消费者,消息接收者 - MessageConsumer consumer; - connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_USER, - ActiveMQConnection.DEFAULT_PASSWORD, ActiveMQConnection.DEFAULT_BROKER_URL); - try { - // 构造从工厂得到连接对象 - connection = connectionFactory.createConnection(); - // 启动 - connection.start(); - // 获取操作连接 - session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE); - // 获取session注意参数值xingbo.xu-queue是一个服务器的queue,须在在ActiveMq的console配置 - destination = session.createQueue("FirstQueue"); - consumer = session.createConsumer(destination); - while (true) { - // 设置接收者接收消息的时间,为了便于测试,这里谁定为100s - TextMessage message = (TextMessage) consumer.receive(100000); - if (null != message) { - System.out.println("收到消息" + message.getText()); - } else { - break; - } - } - } catch (Exception e) { - e.printStackTrace(); - } finally { - try { - if (null != connection) { - connection.close(); - } - } catch (Throwable ignore) { - } - } - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSSender.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSSender.java deleted file mode 100644 index 0f11282d..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/jms/JMSSender.java +++ /dev/null @@ -1,77 +0,0 @@ -package io.github.dunwu.javaee.oss.jms; - -import javax.jms.*; -import org.apache.activemq.ActiveMQConnection; -import org.apache.activemq.ActiveMQConnectionFactory; - -/** - * 消息的生产者 - * - * @author Victor Zhang - * @since 2016/11/28. - */ -public class JMSSender { - - private static final int SEND_NUMBER = 4; - - public static void main(String[] args) { - // ConnectionFactory :连接工厂,JMS 用它创建连接 - ConnectionFactory connectionFactory; - // Connection :JMS 客户端到JMS Provider 的连接 - Connection connection = null; - // Session: 一个发送或接收消息的线程 - Session session; - // Destination :消息的目的地 - Destination destination; - // MessageProducer:消息发送者 - MessageProducer producer; - // TextMessage message; - // 构造ConnectionFactory实例对象,此处采用ActiveMq的实现jar - connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_USER, - ActiveMQConnection.DEFAULT_PASSWORD, ActiveMQConnection.DEFAULT_BROKER_URL); - try { - // 构造从工厂得到连接对象 - connection = connectionFactory.createConnection(); - // 启动 - connection.start(); - // 获取操作连接 - session = connection.createSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE); - // 获取session注意参数值xingbo.xu-queue是一个服务器的queue,须在在ActiveMq的console配置 - destination = session.createQueue("FirstQueue"); - // 得到消息生成者【发送者】 - producer = session.createProducer(destination); - // 设置不持久化,此处学习,实际根据项目决定 - producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT); - // 构造消息,此处写死,项目就是参数,或者方法获取 - sendMessage(session, producer); - session.commit(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - try { - if (null != connection) { - connection.close(); - } - } catch (Throwable ignore) { - } - } - } - - /** - * 发送消息 - * - * @param session - * @param messageProducer 消息生产者 - * @throws Exception - */ - public static void sendMessage(Session session, MessageProducer messageProducer) throws Exception { - for (int i = 0; i < SEND_NUMBER; i++) { - // 创建一条文本消息 - TextMessage message = session.createTextMessage("ActiveMQ 发送消息" + i); - System.out.println("发送消息:Activemq 发送消息" + i); - // 通过消息生产者发出消息 - messageProducer.send(message); - } - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/logging/JclDemo.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/logging/JclDemo.java deleted file mode 100644 index fa75b912..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/logging/JclDemo.java +++ /dev/null @@ -1,29 +0,0 @@ -/** - * The Apache License 2.0 Copyright (c) 2016 Victor Zhang - */ -package io.github.dunwu.javaee.oss.logging; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * 测试 common-logging + log4j 输出日志 - * - * @author Victor Zhang - * @since 2016/11/18. - */ -public class JclDemo { - - private static final Log log = LogFactory.getLog(JclDemo.class); - - public static void main(String[] args) { - String msg = "print logging, current level: "; - log.trace(msg + "trace"); - log.debug(msg + "debug"); - log.info(msg + "info"); - log.warn(msg + "warn"); - log.error(msg + "error"); - log.fatal(msg + "fatal"); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/logging/Slf4jDemo.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/logging/Slf4jDemo.java deleted file mode 100644 index 6f9ce216..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/logging/Slf4jDemo.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * The Apache License 2.0 Copyright (c) 2016 Victor Zhang - */ -package io.github.dunwu.javaee.oss.logging; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * 测试 slf4j + logback 输出日志 - * - * @author Victor Zhang - * @since 2016/11/18. - */ -public class Slf4jDemo { - - private static final Logger log = LoggerFactory.getLogger(Slf4jDemo.class); - - public static void main(String[] args) { - String msg = "print log, current level: {}"; - log.trace(msg, "trace"); - log.debug(msg, "debug"); - log.info(msg, "info"); - log.warn(msg, "warn"); - log.error(msg, "error"); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/ForwardMail.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/ForwardMail.java deleted file mode 100644 index b4a013be..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/ForwardMail.java +++ /dev/null @@ -1,110 +0,0 @@ -package io.github.dunwu.javaee.oss.mail; /** - * The Apache License 2.0 Copyright (c) 2016 Zhang Peng - */ - -import java.util.Date; -import java.util.Properties; -import javax.mail.*; -import javax.mail.internet.InternetAddress; -import javax.mail.internet.MimeBodyPart; -import javax.mail.internet.MimeMessage; -import javax.mail.internet.MimeMultipart; - -/** - * @author Zhang Peng - * @since 2017/4/5. - */ -public class ForwardMail { - - private static final String MAIL_SERVER_SMTP = "smtp.163.com"; - - private static final String MAIL_SERVER_POP3 = "pop3.163.com"; - - private static final String USER = "xxxxxx"; - - private static final String PASSWORD = "******"; - - private static final String MAIL_FROM = "xxxxxx@163.com"; - - private static final String MAIL_TO = "xxxxxx@163.com"; - - private static final String MAIL_CC = "xxxxxx@163.com"; - - private static final String MAIL_BCC = "xxxxxx@163.com"; - - public static void main(String[] args) throws Exception { - Properties prop = new Properties(); - prop.put("mail.store.protocol", "pop3"); - prop.put("mail.pop3.host", MAIL_SERVER_POP3); - prop.put("mail.pop3.starttls.enable", "true"); - prop.put("mail.smtp.auth", "true"); - prop.put("mail.smtp.host", MAIL_SERVER_SMTP); - - // 1、创建session - Session session = Session.getDefaultInstance(prop); - - // 2、读取邮件夹 - Store store = session.getStore("pop3"); - store.connect(MAIL_SERVER_POP3, USER, PASSWORD); - Folder folder = store.getFolder("inbox"); - folder.open(Folder.READ_ONLY); - - // 获取邮件夹中第1封邮件信息 - Message[] messages = folder.getMessages(); - if (messages.length <= 0) { - return; - } - Message message = messages[0]; - - // 打印邮件关键信息 - String from = InternetAddress.toString(message.getFrom()); - if (from != null) { - System.out.println("From: " + from); - } - - String replyTo = InternetAddress.toString(message.getReplyTo()); - if (replyTo != null) { - System.out.println("Reply-to: " + replyTo); - } - - String to = InternetAddress.toString(message.getRecipients(Message.RecipientType.TO)); - if (to != null) { - System.out.println("To: " + to); - } - - String subject = message.getSubject(); - if (subject != null) { - System.out.println("Subject: " + subject); - } - - Date sent = message.getSentDate(); - if (sent != null) { - System.out.println("Sent: " + sent); - } - - // 设置转发邮件信息头 - Message forward = new MimeMessage(session); - forward.setFrom(new InternetAddress(MAIL_FROM)); - forward.setRecipient(Message.RecipientType.TO, new InternetAddress(MAIL_TO)); - forward.setSubject("Fwd: " + message.getSubject()); - - // 设置转发邮件内容 - MimeBodyPart bodyPart = new MimeBodyPart(); - bodyPart.setContent(message, "message/rfc822"); - - Multipart multipart = new MimeMultipart(); - multipart.addBodyPart(bodyPart); - forward.setContent(multipart); - forward.saveChanges(); - - Transport ts = session.getTransport("smtp"); - ts.connect(USER, PASSWORD); - ts.sendMessage(forward, forward.getAllRecipients()); - - folder.close(false); - store.close(); - ts.close(); - System.out.println("message forwarded successfully...."); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailConfigDTO.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailConfigDTO.java deleted file mode 100644 index 6f001ef6..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailConfigDTO.java +++ /dev/null @@ -1,72 +0,0 @@ -/** - * The Apache License 2.0 Copyright (c) 2016 Victor Zhang - */ -package io.github.dunwu.javaee.oss.mail; - -/** - * @author Victor Zhang - * @since 2016/12/23. - */ -public class MailConfigDTO { - - private String smtpHost; - - private String pop3Host; - - private String mailDomain; - - private String mailAccount; - - private String mailPassword; - - private String mailFromHost; - - public String getSmtpHost() { - return smtpHost; - } - - public void setSmtpHost(String smtpHost) { - this.smtpHost = smtpHost; - } - - public String getPop3Host() { - return pop3Host; - } - - public void setPop3Host(String pop3Host) { - this.pop3Host = pop3Host; - } - - public String getMailDomain() { - return mailDomain; - } - - public void setMailDomain(String mailDomain) { - this.mailDomain = mailDomain; - } - - public String getMailAccount() { - return mailAccount; - } - - public void setMailAccount(String mailAccount) { - this.mailAccount = mailAccount; - } - - public String getMailPassword() { - return mailPassword; - } - - public void setMailPassword(String mailPassword) { - this.mailPassword = mailPassword; - } - - public String getMailFromHost() { - return mailFromHost; - } - - public void setMailFromHost(String mailFromHost) { - this.mailFromHost = mailFromHost; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailDTO.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailDTO.java deleted file mode 100644 index a032cb45..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailDTO.java +++ /dev/null @@ -1,104 +0,0 @@ -/** - * The Apache License 2.0 Copyright (c) 2016 Victor Zhang - */ -package io.github.dunwu.javaee.oss.mail; - -import javax.mail.internet.MimeMultipart; - -/** - * @author Victor Zhang - * @since 2016/12/22. - */ -public class MailDTO { - - private String from; - - private String to; // 邮件的收件人 - - private String cc; // 邮件的抄送人 - - private String bcc; // 邮件的密送人 - - private String subject; // 邮件主题 - - private String type; // text或html两种类型 - - private String text; // 邮件文本内容 - - private String charset; // 邮件编码类型(如UTF-8、GBK等) - - private MimeMultipart content; - - public String getFrom() { - return from; - } - - public void setFrom(String from) { - this.from = from; - } - - public String getTo() { - return to; - } - - public void setTo(String to) { - this.to = to; - } - - public String getCc() { - return cc; - } - - public void setCc(String cc) { - this.cc = cc; - } - - public String getBcc() { - return bcc; - } - - public void setBcc(String bcc) { - this.bcc = bcc; - } - - public String getSubject() { - return subject; - } - - public void setSubject(String subject) { - this.subject = subject; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public String getText() { - return text; - } - - public void setText(String text) { - this.text = text; - } - - public String getCharset() { - return charset; - } - - public void setCharset(String charset) { - this.charset = charset; - } - - public MimeMultipart getContent() { - return content; - } - - public void setContent(MimeMultipart content) { - this.content = content; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailUtil.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailUtil.java deleted file mode 100644 index 90882856..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/MailUtil.java +++ /dev/null @@ -1,204 +0,0 @@ -/** - * The Apache License 2.0 Copyright (c) 2016 Victor Zhang - */ -package io.github.dunwu.javaee.oss.mail; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; -import javax.mail.MessagingException; -import javax.mail.internet.InternetAddress; -import javax.mail.internet.MimeMessage; -import javax.mail.internet.MimeUtility; -import org.apache.commons.lang3.StringUtils; - -/** - * @author Victor Zhang - * @since 2016/12/22. - */ -public class MailUtil { - - private static final String TYPE_TEXT = "text"; - - private static final String TYPE_HTML = "html"; - - private MailConfigDTO configDTO; - - /** - * 以默认配置初始化邮件收发工具 - */ - public MailUtil() { - this.configDTO = initEmailConfig(); - } - - private MailConfigDTO initEmailConfig() { - MailConfigDTO configDTO = new MailConfigDTO(); - Properties p = new Properties(); - try { - p.load(MailUtil.class.getResourceAsStream("/mail/mail.properties")); - } catch (IOException e) { - e.printStackTrace(); - } - - configDTO.setSmtpHost(p.getProperty("smtp.host")); - configDTO.setPop3Host(p.getProperty("pop3.host")); - configDTO.setMailDomain(p.getProperty("mail.host")); - configDTO.setMailAccount(p.getProperty("mail.account")); - configDTO.setMailPassword(p.getProperty("mail.password")); - configDTO.setMailFromHost(configDTO.getMailAccount() + configDTO.getMailDomain()); - return configDTO; - } - - /** - * 以自定义配置初始化邮件收发工具 - */ - public MailUtil(MailConfigDTO configDTO) { - this.configDTO = configDTO; - } - - /** - * 发送邮件 - * - * @param info - * @throws MessagingException - */ - public void sendEmail(MailDTO info) throws MessagingException { - Properties props = new Properties(); - props.setProperty("mail.debug", "true"); - props.setProperty("mail.transport.protocol", "smtp"); - props.setProperty("mail.host", configDTO.getSmtpHost()); - props.setProperty("mail.smtp.auth", "true"); - - // 1、创建session - Session session = Session.getInstance(props); - - // 2、通过session得到transport对象 - Transport ts = session.getTransport(); - - // 3、连上邮件服务器 - ts.connect(configDTO.getSmtpHost(), configDTO.getMailAccount(), configDTO.getMailPassword()); - - // 4、创建邮件 - MimeMessage message = new MimeMessage(session); - if (!fillEmail(message, info)) { - return; - } - - // 5、发送邮件 - ts.sendMessage(message, message.getAllRecipients()); - ts.close(); - } - - private boolean fillEmail(MimeMessage message, MailDTO info) throws MessagingException { - return fillEmailHeader(message, info) && fillEmailBody(message, info); - } - - /** - * 填充邮件头 - */ - private boolean fillEmailHeader(MimeMessage message, MailDTO info) throws MessagingException { - // 邮件的发件人 - if (StringUtils.isNotBlank(configDTO.getMailFromHost())) { - message.setFrom(new InternetAddress(configDTO.getMailFromHost())); - } else { - System.out.println("发件人不能为空"); - return false; - } - - // 邮件的收件人 - if (StringUtils.isNotBlank(info.getTo())) { - message.setRecipient(Message.RecipientType.TO, new InternetAddress(info.getTo())); - } else { - System.out.println("收件人不能为空"); - return false; - } - - // 邮件的抄送人 - if (StringUtils.isNotBlank(info.getCc())) { - message.setRecipient(Message.RecipientType.CC, new InternetAddress(info.getCc())); - } - - // 邮件的密送人 - if (StringUtils.isNotBlank(info.getBcc())) { - message.setRecipient(Message.RecipientType.BCC, new InternetAddress(info.getBcc())); - } - - // 邮件的标题 - if (StringUtils.isNotBlank(info.getCharset())) { - message.setSubject(info.getSubject(), info.getCharset()); - } else { - message.setSubject(info.getSubject()); - } - - return true; - } - - /** - * 填充邮件内容 - */ - private boolean fillEmailBody(MimeMessage message, MailDTO info) throws MessagingException { - if (StringUtils.isBlank(info.getType()) || StringUtils.isBlank(info.getText())) { - return false; - } - - if (StringUtils.equals(info.getType(), TYPE_TEXT)) { - if (StringUtils.isNotBlank(info.getCharset())) { - message.setText(info.getText(), info.getCharset()); - } else { - message.setText(info.getText()); - } - } else if (StringUtils.equals(info.getType(), TYPE_HTML)) { - String type = "text/html"; - if (StringUtils.isNotBlank(info.getCharset())) { - type += ";charset=" + info.getCharset(); - } - message.setContent(info.getText(), type); - } - - return true; - } - - public List receiveEmail() throws MessagingException, IOException { - // 创建一个有具体连接信息的Properties对象 - Properties prop = new Properties(); - prop.setProperty("mail.debug", "false"); - prop.setProperty("mail.store.protocol", "pop3"); - prop.setProperty("mail.pop3.host", configDTO.getPop3Host()); - - // 1、创建session - Session session = Session.getInstance(prop); - - // 2、通过session得到Store对象 - Store store = session.getStore(); - - // 3、连上邮件服务器 - store.connect(configDTO.getPop3Host(), configDTO.getMailAccount(), configDTO.getMailPassword()); - - // 4、获得邮箱内的邮件夹 - Folder folder = store.getFolder("inbox"); - folder.open(Folder.READ_ONLY); - - // 获得邮件夹Folder内的所有邮件Message对象 - Message[] messages = folder.getMessages(); - - List results = new ArrayList(); - for (int i = 0; i < messages.length; i++) { - MailDTO dto = new MailDTO(); - dto.setFrom(MimeUtility.decodeText(messages[i].getFrom()[0].toString())); - dto.setSubject(messages[i].getSubject()); - dto.setText(messages[i].getContent().toString()); - results.add(dto); - System.out.println("第 " + (i + 1) + "封邮件的主题:" + dto.getSubject()); - System.out.println("第 " + (i + 1) + "封邮件的发件人地址:" + dto.getFrom()); - // System.out.println("第 " + (i + 1) + "封邮件的内容:\n" + - // messages[i].getContent().toString()); - } - - // 5、关闭 - folder.close(false); - store.close(); - return results; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendAttachmentMail.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendAttachmentMail.java deleted file mode 100644 index 8581319b..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendAttachmentMail.java +++ /dev/null @@ -1,84 +0,0 @@ -package io.github.dunwu.javaee.oss.mail; /** - * The Apache License 2.0 Copyright (c) 2016 Zhang Peng - */ - -import java.util.Properties; -import javax.mail.Message; -import javax.mail.Session; -import javax.mail.Transport; -import javax.mail.internet.InternetAddress; -import javax.mail.internet.MimeBodyPart; -import javax.mail.internet.MimeMessage; -import javax.mail.internet.MimeMultipart; - -/** - * @author Zhang Peng - * @since 2017/4/5. - */ -public class SendAttachmentMail { - - private static final String MAIL_SERVER_HOST = "smtp.163.com"; - - private static final String USER = "xxxxxx"; - - private static final String PASSWORD = "******"; - - private static final String MAIL_FROM = "xxxxxx@163.com"; - - private static final String MAIL_TO = "xxxxxx@163.com"; - - private static final String MAIL_CC = "xxxxxx@163.com"; - - private static final String MAIL_BCC = "xxxxxx@163.com"; - - public static void main(String[] args) throws Exception { - Properties prop = new Properties(); - prop.setProperty("mail.debug", "true"); - prop.setProperty("mail.host", MAIL_SERVER_HOST); - prop.setProperty("mail.transport.protocol", "smtp"); - prop.setProperty("mail.smtp.auth", "true"); - - // 1、创建session - Session session = Session.getInstance(prop); - - // 2、通过session得到transport对象 - Transport ts = session.getTransport(); - - // 3、连上邮件服务器 - ts.connect(MAIL_SERVER_HOST, USER, PASSWORD); - - // 4、创建邮件 - MimeMessage message = new MimeMessage(session); - - // 邮件消息头 - message.setFrom(new InternetAddress(MAIL_FROM)); // 邮件的发件人 - message.setRecipient(Message.RecipientType.TO, new InternetAddress(MAIL_TO)); // 邮件的收件人 - message.setRecipient(Message.RecipientType.CC, new InternetAddress(MAIL_CC)); // 邮件的抄送人 - message.setRecipient(Message.RecipientType.BCC, new InternetAddress(MAIL_BCC)); // 邮件的密送人 - message.setSubject("测试带附件邮件"); // 邮件的标题 - - MimeBodyPart text = new MimeBodyPart(); - text.setContent("邮件中有两个附件。", "text/html;charset=UTF-8"); - - // 描述数据关系 - MimeMultipart mm = new MimeMultipart(); - mm.setSubType("related"); - mm.addBodyPart(text); - String[] files = {"D:\\00_Temp\\temp\\1.jpg", "D:\\00_Temp\\temp\\2.png"} - - // 添加邮件附件 - for (String filename : files) { - MimeBodyPart attachPart = new MimeBodyPart(); - attachPart.attachFile(filename); - mm.addBodyPart(attachPart); - } - - message.setContent(mm); - message.saveChanges(); - - // 5、发送邮件 - ts.sendMessage(message, message.getAllRecipients()); - ts.close(); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendTemplateMail.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendTemplateMail.java deleted file mode 100644 index 7e89e9ac..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendTemplateMail.java +++ /dev/null @@ -1,81 +0,0 @@ -/** - * The Apache License 2.0 Copyright (c) 2016 Victor Zhang - */ -package io.github.dunwu.javaee.oss.mail; - -import io.github.dunwu.javaee.oss.template.VelocityUtil; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import javax.mail.MessagingException; -import org.apache.velocity.VelocityContext; - -/** - * @author Victor Zhang - * @see org.zp.javaee.tools.mail.MailUtil 注意:如果想要成功发送邮件,需要修改JavaParty项目 src\javaee\tools\src\main\resources\mail\mail.properties - * 中的 参数,请根据实际邮箱来配置。 - * @since 2016/12/23. - */ -public class SendTemplateMail { - - private static final String DEFAULT_TO = "xxxxxx@163.com"; - - public static void main(String[] args) throws MessagingException { - VelocityContext context = new VelocityContext(); - context.put("name", "Victor Zhang"); - context.put("hint", "欢迎使用Velocity邮件模板:"); - - // 直接传入一个对象 - context.put("date", new Date()); - - // 传入一个Vector - Hyperlink item1 = new Hyperlink("百度首页", "https://www.baidu.com"); - Hyperlink item2 = new Hyperlink("网易首页", "http://www.163.com/"); - List list = new ArrayList<>(); - list.add(item1); - list.add(item2); - context.put("links", list); - context.put("logo", - "http://images.cnblogs.com/cnblogs_com/jingmoxukong/709053/o_%e6%94%bb%e5%9f%8e%e7%8b%ae2.png"); - - MailDTO info = new MailDTO(); - info.setTo(DEFAULT_TO); // 收件人邮箱 - info.setSubject("测试html邮件"); // 邮件主题 - info.setType("html"); - info.setCharset("utf-8"); - - info.setText(VelocityUtil.getMergeOutput(context, "template/mail.vm")); - MailUtil mailUtil = new MailUtil(); - mailUtil.sendEmail(info); - } - - public static class Hyperlink { - - private String link; - - private String name; - - Hyperlink(String name, String link) { - this.name = name; - this.link = link; - } - - public String getLink() { - return link; - } - - public void setLink(String link) { - this.link = link; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendTextMail.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendTextMail.java deleted file mode 100644 index fe4833a4..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/SendTextMail.java +++ /dev/null @@ -1,67 +0,0 @@ -package io.github.dunwu.javaee.oss.mail; /** - * The Apache License 2.0 Copyright (c) 2016 Zhang Peng - */ - -import java.util.Properties; -import javax.mail.Message; -import javax.mail.Session; -import javax.mail.Transport; -import javax.mail.internet.InternetAddress; -import javax.mail.internet.MimeMessage; - -/** - * @author Zhang Peng - * @since 2017/4/5. - */ -public class SendTextMail { - - private static final String MAIL_SERVER_HOST = "smtp.163.com"; - - private static final String USER = "xxxxxx"; - - private static final String PASSWORD = "******"; - - private static final String MAIL_FROM = "xxxxxx@163.com"; - - private static final String MAIL_TO = "xxxxxx@163.com"; - - private static final String MAIL_CC = "xxxxxx@163.com"; - - private static final String MAIL_BCC = "xxxxxx@163.com"; - - public static void main(String[] args) throws Exception { - Properties prop = new Properties(); - prop.setProperty("mail.debug", "true"); - prop.setProperty("mail.host", MAIL_SERVER_HOST); - prop.setProperty("mail.transport.protocol", "smtp"); - prop.setProperty("mail.smtp.auth", "true"); - - // 1、创建session - Session session = Session.getInstance(prop); - Transport ts = null; - - // 2、通过session得到transport对象 - ts = session.getTransport(); - - // 3、连上邮件服务器 - ts.connect(MAIL_SERVER_HOST, USER, PASSWORD); - - // 4、创建邮件 - MimeMessage message = new MimeMessage(session); - - // 邮件消息头 - message.setFrom(new InternetAddress(MAIL_FROM)); // 邮件的发件人 - message.setRecipient(Message.RecipientType.TO, new InternetAddress(MAIL_TO)); // 邮件的收件人 - message.setRecipient(Message.RecipientType.CC, new InternetAddress(MAIL_CC)); // 邮件的抄送人 - message.setRecipient(Message.RecipientType.BCC, new InternetAddress(MAIL_BCC)); // 邮件的密送人 - message.setSubject("测试文本邮件"); // 邮件的标题 - - // 邮件消息体 - message.setText("天下无双。"); - - // 5、发送邮件 - ts.sendMessage(message, message.getAllRecipients()); - ts.close(); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/StoreMail.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/StoreMail.java deleted file mode 100644 index 32df1236..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/StoreMail.java +++ /dev/null @@ -1,66 +0,0 @@ -package io.github.dunwu.javaee.oss.mail; /** - * The Apache License 2.0 Copyright (c) 2016 Zhang Peng - */ - -import java.util.Properties; -import javax.mail.Folder; -import javax.mail.Message; -import javax.mail.Session; -import javax.mail.Store; - -/** - * @author Zhang Peng - * @since 2017/4/5. - */ -public class StoreMail { - - private static final String MAIL_SERVER_HOST = "pop3.163.com"; - - private static final String USER = "xxxxxx"; - - private static final String PASSWORD = "******"; - - private static final String MAIL_FROM = "xxxxxx@163.com"; - - private static final String MAIL_TO = "xxxxxx@163.com"; - - private static final String MAIL_CC = "xxxxxx@163.com"; - - private static final String MAIL_BCC = "xxxxxx@163.com"; - - public static void main(String[] args) throws Exception { - - // 创建一个有具体连接信息的Properties对象 - Properties prop = new Properties(); - prop.setProperty("mail.debug", "true"); - prop.setProperty("mail.store.protocol", "pop3"); - prop.setProperty("mail.pop3.host", MAIL_SERVER_HOST); - - // 1、创建session - Session session = Session.getInstance(prop); - - // 2、通过session得到Store对象 - Store store = session.getStore(); - - // 3、连上邮件服务器 - store.connect(MAIL_SERVER_HOST, USER, PASSWORD); - - // 4、获得邮箱内的邮件夹 - Folder folder = store.getFolder("inbox"); - folder.open(Folder.READ_ONLY); - - // 获得邮件夹Folder内的所有邮件Message对象 - Message[] messages = folder.getMessages(); - for (int i = 0; i < messages.length; i++) { - String subject = messages[i].getSubject(); - String from = (messages[i].getFrom()[0]).toString(); - System.out.println("第 " + (i + 1) + "封邮件的主题:" + subject); - System.out.println("第 " + (i + 1) + "封邮件的发件人地址:" + from); - } - - // 5、关闭 - folder.close(false); - store.close(); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/sendHtmlMail.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/sendHtmlMail.java deleted file mode 100644 index bdceb7a1..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/mail/sendHtmlMail.java +++ /dev/null @@ -1,84 +0,0 @@ -package io.github.dunwu.javaee.oss.mail; /** - * The Apache License 2.0 Copyright (c) 2016 Zhang Peng - */ - -import java.util.Properties; -import javax.activation.DataHandler; -import javax.activation.FileDataSource; -import javax.mail.Message; -import javax.mail.Session; -import javax.mail.Transport; -import javax.mail.internet.InternetAddress; -import javax.mail.internet.MimeBodyPart; -import javax.mail.internet.MimeMessage; -import javax.mail.internet.MimeMultipart; - -/** - * @author Zhang Peng - * @since 2017/4/5. - */ -public class sendHtmlMail { - - private static final String MAIL_SERVER_HOST = "smtp.163.com"; - - private static final String USER = "xxxxxx"; - - private static final String PASSWORD = "******"; - - private static final String MAIL_FROM = "xxxxxx@163.com"; - - private static final String MAIL_TO = "xxxxxx@163.com"; - - private static final String MAIL_CC = "xxxxxx@163.com"; - - private static final String MAIL_BCC = "xxxxxx@163.com"; - - public static void main(String[] args) throws Exception { - Properties prop = new Properties(); - prop.setProperty("mail.debug", "true"); - prop.setProperty("mail.host", MAIL_SERVER_HOST); - prop.setProperty("mail.transport.protocol", "smtp"); - prop.setProperty("mail.smtp.auth", "true"); - - // 1、创建session - Session session = Session.getInstance(prop); - Transport ts = null; - - // 2、通过session得到transport对象 - ts = session.getTransport(); - - // 3、连上邮件服务器 - ts.connect(MAIL_SERVER_HOST, USER, PASSWORD); - - // 4、创建邮件 - MimeMessage message = new MimeMessage(session); - - // 邮件消息头 - message.setFrom(new InternetAddress(MAIL_FROM)); // 邮件的发件人 - message.setRecipient(Message.RecipientType.TO, new InternetAddress(MAIL_TO)); // 邮件的收件人 - message.setRecipient(Message.RecipientType.CC, new InternetAddress(MAIL_CC)); // 邮件的抄送人 - message.setRecipient(Message.RecipientType.BCC, new InternetAddress(MAIL_BCC)); // 邮件的密送人 - message.setSubject("测试HTML邮件"); // 邮件的标题 - - String htmlContent = "

    Hello

    " + "

    显示图片1.jpg

    "; - MimeBodyPart text = new MimeBodyPart(); - text.setContent(htmlContent, "text/html;charset=UTF-8"); - MimeBodyPart image = new MimeBodyPart(); - DataHandler dh = new DataHandler(new FileDataSource("D:\\05_Datas\\图库\\吉他少年背影.png")); - image.setDataHandler(dh); - image.setContentID("abc.jpg"); - - // 描述数据关系 - MimeMultipart mm = new MimeMultipart(); - mm.addBodyPart(text); - mm.addBodyPart(image); - mm.setSubType("related"); - message.setContent(mm); - message.saveChanges(); - - // 5、发送邮件 - ts.sendMessage(message, message.getAllRecipients()); - ts.close(); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/LoadVelocityDemo.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/LoadVelocityDemo.java deleted file mode 100644 index 4d21f306..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/LoadVelocityDemo.java +++ /dev/null @@ -1,94 +0,0 @@ -/** - * The Apache License 2.0 Copyright (c) 2016 Victor Zhang - */ -package io.github.dunwu.javaee.oss.template; - -import java.io.IOException; -import java.io.StringWriter; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Properties; -import org.apache.velocity.Template; -import org.apache.velocity.VelocityContext; -import org.apache.velocity.app.VelocityEngine; - -/** - * @author Victor Zhang - * @since 2016/12/22. - */ -public class LoadVelocityDemo { - - public static void main(String[] args) throws IOException { - loadByClasspath(); - loadByFilepath(); - loadByConfig(); - } - - /** - * 加载classpath目录下的vm文件 - */ - public static void loadByClasspath() { - System.out.println("========== loadByClasspath =========="); - - Properties p = new Properties(); - p.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); - VelocityEngine ve = new VelocityEngine(); - ve.init(p); - Template t = ve.getTemplate("template/hello.vm"); - - System.out.println(fillTemplate(t)); - } - - /** - * 根据绝对路径加载,vm文件置于硬盘某分区中 - */ - public static void loadByFilepath() { - System.out.println("========== loadByFilepath =========="); - - Properties p = new Properties(); - p.put(VelocityEngine.FILE_RESOURCE_LOADER_PATH, - "D:\\01_Workspace\\Project\\zp\\javaparty\\src\\toolbox\\template\\src\\main\\resources"); - VelocityEngine ve = new VelocityEngine(); - ve.init(p); - Template t = ve.getTemplate("hello.vm"); - - System.out.println(fillTemplate(t)); - } - - /** - * 根据资源路径加载 - */ - public static void loadByConfig() throws IOException { - System.out.println("========== loadByConfig =========="); - - Properties p = new Properties(); - p.load(LoadVelocityDemo.class.getResourceAsStream("/template/velocity.properties")); - VelocityEngine ve = new VelocityEngine(); - ve.init(p); - Template t = ve.getTemplate("template/hello.vm"); - - System.out.println(fillTemplate(t)); - } - - /** - * 使用文本文件,使用文本文件,如:velocity.properties - */ - private static String fillTemplate(Template t) { - // 初始化VelocityContext - VelocityContext ctx = new VelocityContext(); - ctx.put("name", "victor"); - ctx.put("date", (new Date()).toString()); - List temp = new ArrayList(); - temp.add("1"); - temp.add("2"); - ctx.put("list", temp); - - // 初始化Writer - StringWriter sw = new StringWriter(); - - t.merge(ctx, sw); - return sw.toString(); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityHelloWorld.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityHelloWorld.java deleted file mode 100644 index c5d9b3ee..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityHelloWorld.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.github.dunwu.javaee.oss.template; /** - * The Apache License 2.0 Copyright (c) 2016 Victor Zhang - */ - -import java.io.StringWriter; -import org.apache.velocity.Template; -import org.apache.velocity.VelocityContext; -import org.apache.velocity.app.VelocityEngine; - -/** - * Velocity 的 HelloWorld 示例 - * - * @author Victor Zhang - * @since 2016/12/22. - */ -public class VelocityHelloWorld { - - public static void main(String args[]) { - /* 1.初始化 Velocity */ - VelocityEngine velocityEngine = new VelocityEngine(); - velocityEngine.setProperty(VelocityEngine.RESOURCE_LOADER, "file"); - velocityEngine.setProperty(VelocityEngine.FILE_RESOURCE_LOADER_PATH, - "D:/01_Workspace/Project/zp/javaparty/src/toolbox/template/src/main/resources"); - velocityEngine.init(); - - /* 2.创建一个上下文对象 */ - VelocityContext context = new VelocityContext(); - - /* 3.添加你的数据对象到上下文 */ - context.put("name", "Victor Zhang"); - context.put("project", "Velocity"); - - /* 4.选择一个模板 */ - Template template = velocityEngine.getTemplate("template/hello.vm"); - - /* 5.将你的数据与模板合并,产生输出内容 */ - StringWriter sw = new StringWriter(); - template.merge(context, sw); - System.out.println("final output:\n" + sw); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityHelloWorld2.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityHelloWorld2.java deleted file mode 100644 index 19398dfb..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityHelloWorld2.java +++ /dev/null @@ -1,47 +0,0 @@ -package io.github.dunwu.javaee.oss.template; /** - * The Apache License 2.0 Copyright (c) 2016 Victor Zhang - */ - -import java.io.IOException; -import java.io.StringWriter; -import java.util.Properties; -import org.apache.velocity.Template; -import org.apache.velocity.VelocityContext; -import org.apache.velocity.app.VelocityEngine; - -/** - * Velocity 的 HelloWorld 示例 - * - * @author Victor Zhang - * @since 2016/12/22. - */ -public class VelocityHelloWorld2 { - - public static void main(String args[]) { - /* 1.初始化 Velocity */ - Properties p = new Properties(); - try { - p.load(VelocityUtil.class.getResourceAsStream("/template/velocity.properties")); - } catch (IOException e) { - e.printStackTrace(); - } - VelocityEngine velocityEngine = new VelocityEngine(); - velocityEngine.init(p); - - /* 2.创建一个上下文对象 */ - VelocityContext context = new VelocityContext(); - - /* 3.添加你的数据对象到上下文 */ - context.put("name", "Victor Zhang"); - context.put("project", "Velocity"); - - /* 4.选择一个模板 */ - Template template = velocityEngine.getTemplate("template/hello.vm"); - - /* 5.将你的数据与模板合并,产生输出内容 */ - StringWriter sw = new StringWriter(); - template.merge(context, sw); - System.out.println("final output:\n" + sw); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityUtil.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityUtil.java deleted file mode 100644 index 77945c52..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/java/io/github/dunwu/javaee/oss/template/VelocityUtil.java +++ /dev/null @@ -1,46 +0,0 @@ -/** - * The Apache License 2.0 Copyright (c) 2016 Victor Zhang - */ -package io.github.dunwu.javaee.oss.template; - -import java.io.IOException; -import java.io.StringWriter; -import java.util.Properties; -import org.apache.velocity.Template; -import org.apache.velocity.VelocityContext; -import org.apache.velocity.app.VelocityEngine; - -/** - * @author Victor Zhang - * @since 2016/12/23. - */ -public class VelocityUtil { - - private static VelocityEngine velocityEngine; - - static { - Properties props = new Properties(); - try { - props.load(VelocityUtil.class.getResourceAsStream("/template/velocity.properties")); - } catch (IOException e) { - e.printStackTrace(); - } - velocityEngine = new VelocityEngine(); - velocityEngine.init(props); - } - - public static String getMergeOutput(VelocityContext context, String templateName) { - Template template = velocityEngine.getTemplate(templateName); - - StringWriter sw = new StringWriter(); - template.merge(context, sw); - String output = sw.toString(); - try { - sw.close(); - } catch (IOException e) { - e.printStackTrace(); - } - return output; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/html/example.html b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/html/example.html deleted file mode 100644 index 32c7cf6a..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/html/example.html +++ /dev/null @@ -1,8 +0,0 @@ - - - 测试html页面 - - -

    牛刀小试

    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/images/lion.jpg b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/images/lion.jpg deleted file mode 100644 index d10bd212f4d4b70edfc239b0a2e78a7815914798..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16791 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 diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/images/lion2.jpg b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/images/lion2.jpg deleted file mode 100644 index 6794cb3483aa2cc8fc06aca4459dfa49aa516569..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31726 zcmb5V1yo$i)-KvuAi>?;f`{PJKyY{0;O_2{1cEit1PJcdSa5fDcXxO9$NvAb_dDmk zd;dG`>M?4Jso?0My$L@VW>P0l>k)!otG9y)|%f zaPQ!e5a8b~R7Ax0Na&~-80e_z=$JS}xR_W3*y!kZ&gfYa0L^0V)_O7zPR*0F4d>gAVoD1t9*5OgN~&=>7YG zgMxhrj{pPx{_VCZ8UP9g1{xLy77hU(_8kKJ+vCu09rSnP@EELLFqI4uC>#T^KF4I1 zV^az%8wItW;ZU(TfwRVG*hMO0vnwao&+#}oMOBQybx@0|y14%6e4CZO|e_!*B z258uKFz|0T1<~J@d|Lwn4jK*?8XD$}EU33GIvhDG#uueCOhd=Ox7EhqQG9M+$2!NR zWD{1#p<*{u2{LvPQJr{Q03gA<&5jO(4iE$!RY3uS&|v>=wAH0#u#qOz1vn?FK`x`e z0@{X5!18UzTUn3o_t)LE7KHA2Jls;65082VFV#7D5zi<|@?Uw@{pIy815YBp5~0oP z8)1#heYgs@wkR3}=IxLwF|yJ0xGyE&FDaHH9R~pd$nb>FaNYLs74Pw$p7o-@=ZTL9 zVS$Ct1CS(Xckjj8%6OZYGTETqD3Tv$UvwWBV`eD2Kb2FR)lY~S*r$bk^`wdTlRn)y z7r5ts&lINpBR`HxZs~yp8;$Cm(YmPwjvb5LQCWozjF&txp8CRb*-tA_sHFT5iX(A2 zD&Q3hiyXur*?_Ake4mS6S@^_1Q_=Eet6I%Y(>=%(gIavsc%pBueB3p6 zWefN56%djh$^e&m*rbJ|qcflCo|Hr{y(vs*dhRqQnlg&F%~xkk_*|6P!TL^_Rd;7u zV+_*u(Li-eyrJ*Mc*8l}<8XJgiQX$CDb*!Qz!1FLrasa-wB}DtS@^KiRN#HwdiyL973I9si7k%3XEI7q~Wbkik zgt{Dlq=tgrjrZyicS;Up%SJU?;RDdLKQ0l|(dP3keInJ3ox{c*SVvc1Hm5m<(KuY8 zQ^V&-?OQBvS|6J$kTUUrr|bgii+Ak;<$2Vo%{Yj-ObpoSPM9q}c~Vzy_jB7nxyNI% zS5>ihB!#7MQxM~|2lxq?Po2~!LUK|Ul}qk-MY4j^xG~0aqipOW%3$UmOq>J{%17?t z!s;T<$}{RE-#{W%Ha&zBfU(dsl$8A%Pg(in1BgE^woCX4@76i8dyd| z5fhi(5#8uwoK&!7aJcn%sJ9yudSeqiFKziA;x^fn=}e~j(HGP!^R@5d^h?E@uS|k4I$BdOf)SpHElHg|vsa_CO-Sal(FcIf7`Bj)z_)S4!AY^Y}tGZ>MC zC&`cOs1l^nRU4_sheQ<$rcO)PE&fqBapOr(s3wP0^AOLM7^@_0C|S|^9o~&`*`OJh~0&^g*qUr zUKZw&U#`Z9%*JN4%#WLP>0r@n4dkJU8Ml7sbDEfjz3`=u1R%;Rw z!`w>|r(nFP1H?wY&8^l?omM|L$SYID^)jBmibIY~7;hu0dLUvPlFIp8TmDN@;i_GI znc%3VT`Rlg9sBLhcTty)Q~~(Gu|*+US6APcTzjpcr~|iW20~kDIJ?JU=WvhPv5&mSM6AizFN8W zR$v&r&Li!H#H}CR8EWvZRIqTLGO(59I?z-Q5DAhHwxWR{ovSM_`TaJZT}6kwmA4Nm zt?}^MV1(v9=piLjeYN%78_IQ1;8hP6Nxh1v&PoBMsTM?jiKr3u8@eR9+zJa3#paH4 zs*iu@B7bGj2aq8^L-(B0GD7pOHJzU6U>!G>iv0A!t}}Fu$f*0`@csMS26`+pSTbGp zV#OXfyIlgck2Kr=)h?ZN?%9wQNV$KNb zj${d+@&NEQk8`=w6jp%%4_yy-&z$3aLq@`SXmm9M=k{M<9{DUua0iL!R8?0v0EX5@ zW{$>`qe?_VYd*TVzG~x&cK>Pj(C}7TXBOOr_ZuR=6k2a)WrY~VCJa2E6B163aPI-7 z+F&zeN2oW_F*p?Pi(1RxA0;t?c5|y)@4#j7%U~nk-BMnZ#m9quw61v>H#ROWw|EzB zYlwjR)~Yg15}sw@yn+IzQ84F4c_bmbzo4DGb6npb+m2K7p{oicj7D_I8lx{{MI&?% zEm|C73m0o@Qr4*_8h$@DKvO`xmYNzZC9>2SM)$<%-7c}3cO14|u!Zl(zok&+0~WUO z2SX^8hCqj$e`!&uv%751!MRx<;eL{mF%zF>64VzY|MmAZClq}?E#TB;L;F5MY3ZvK zu_)Tb!qjV##4`r=0xv5@ok1Ii2XNH4glsD%S7A;WsX}^+QGRn0V~6HNO1-QYQ+~&m z&y{3(XlM^fo!HCesy#PidU7FO7=DusgG6*EK4~lD_eFej3^B6H(+k;|zR>rF)AJ)* z__b&zufSILoZ@jU25E`Ps{W33Bc~zX(~XD+>!87o1cSqg()RGp_yO4IwF9i+AnI^?h+$ep!Hw_N-EYSH>UK>u-x->M#d zuZpCz&|L^92~S*nBsM}lolqjv)hzQ&H>&`jVC|dZZ!Ce&1E_5T&C{07?-48@$>50V zf5ZhI9vQ6P=}%C_(&)r4T`^N_ffdp;K9#EJ6pm+6P)BEVMF%{9xcbg|*z0>M=(Erg z!7lMTm8i)m4##so>N&BiG>O?#fR$kY)K+yREmODrvR9$1Dn1+z4CRmy$PbYz8i(v} z>P?04l|N*1(*450SNx*(_wd`BAx9^tv8i;j%tnxchbw>$X!Y^fzmOtZD9w=VV+ZCy zu5RkAs^!FS)y{-CT!V5P3#rAw8|XIp$5bV%q8m%U9B#*~js=K6<4{slw&+fV@D0-s zd^}JAdL$192#m$K9Sq2!c8BFvm>cogKA&{C!h9v#m?BHhI=CQN+uABBS8wMP;GR!9uO$9iyN9bB|8AEGEXDA<>s+s> z8+yCh^O~2R52-pgj%_LJX}hUmG|!JAZGoj&$w;yTW+guE8s%1=aJe_*#qCl3GmvtM zCO>XaX~E`Nu12XBII>T^ZA(qauO-t2;`I{WYaJ)hA~Gm36&M7~dL zfB(pWTGtMNG51klCKW&R%*wu;=Fh(o;uZ*rF3EjC7p>9hYoWUlyJRO;(o>A@4;|%; z$O@UCpXB0dp*Y~JN1egS5o+HfkO?69W*YSn#65feiNbm6rzaEQ)=P0%(`^Fne61Y2 z8M0onQS5a~D*h|rX^OV1a&^a8qj$+4$;e`x!Y<|AjQZXxbHWByoZSIgtXya~B|t7#Di>np(+bWl(O`;I!B!P*zMw2(snMpb zk8`)e(_bm}IYxP8i)+Qf)?%3?xXq32meXR`Pb`|89Sbhlz=;W=yeRuA-u3xC$}50G zm$cfh_iWqxT2y}erXKQS7S}*u=3c+6P-g<;CZg5sIq4!MV0#rk5hjY;6 z|ETc_Skin2z~;UJZj!fO0at<%GOvIiJ{og`ErmaKih`3mXh1nSIh4mtP|{yjPadqa zTS(R&6Y|Pg(#x&YiWkL~X7_y+B>56;w=;a4)D89v^`3bMS_G}ZzmF`h%N28o#3r1O zS~l(!V)s{T=RVBFt~bo=n;sc>lN9&t>y=D4&c)|UHTV@*eeue0POsE9w{R5Dq~+08 zHpDJJB)B!-vt>iejg`pv6D=Tb!#P29s4^g&t8vnjtKlh=skZy!z1f=J8W|C?6i+id z_K~6;o3RFp5x^rl1QSz__udq)*?A|7Vo#Ik%WYF{zgJIB{rnGdlemkf`jZ{OpcQK+ zc9GHI%Z}*J@i1n)|ctP1H?YYEb@vBsy^4EM~R&^BFH ze_XRS80)I4F1vWAQD)Ue>j7K)qfYjE6BGr?Z8OD958OIAGD zwK~~?DUeuF{!f0f<$;S-0fsnR6ssa(*7RiyMXFQQa^}kPWoiD%dvJU1piT}Ls z9ohm*?KsM+*qwFN+v@|Rrn~kXmblC&1`LukF&0_x?;YQHD_kAMc<}K z+{SHF#owzaD-Z&SUjaY0gXLJVsUEOfeg!%;88dw+DKSq5-L>~02Yj|$%LzdZrMH?m z(IXr^N?}%u5JUL zO(*3ex(jhK3oW(Ew4GOdBI&hK#kfRIGZ$R3n+_PL(3U8&A;$;6z#|oiV1)ISNM+N&Gu+ zW-gl5JU5+LDjXz!+{8!0ODRopySnP63-~Eep@PQ~h)QE0k5VN=8ZF4=X4w$+QzmCJ z;7Yq~GLiSMyd*b!=Wrt8Tru)TDax$Uj)OR#nM6;h&fc*uZdH$p$;3Tjbac|z%OIk5 zLuV}Zk(C=eR%)HdG1V;;SVJAq29`Yu--7ZiUP2&l^qJqXj9+roUuUVn@B+W&{?+xSqNXWOAW~y!^f%@2 zS3sye+lEvE-S1giV1CegvFzNo80)~xhjZSsF&piXdCwoI?<(%`DXp^}s9NoJcDNivLX-S$#X1+}v+QRJs|9;4NPJ5AVK#)L);)YB zo;;75a4$`PDwvR{hjD{S3eIqK4ZW2;_MONncj@@XxFK6|WWx*R5H6M>3Ln1l<;j6a zd5^ha$1Xpmo}y8;q2Le+$E*`)l*G!pHHZ1x4}We05AdW&IScnbS@}c6{56h34?LMn zeOhIdnyw!)Reg{*#B^BQHkgQUylOzJ8%|Neo*#Pe;_)&3nbTeY&#hdaoEc0WwrkDw z`xms{sibWPhOpch8mk=L`ULjqC}1&10!0`Ko7|pI3~J|8{RwlNUICnmd`>sC zSJm&6sSO03ahuM{!e{h7jlzZ@7 zd}*6#whz44AzOpTaHhcI4??OU}{Fg23}^WShHN9N-=HOdq) z41V3tt1+nI3>Mmi`;29^_1!1N$ib{7arR^OGW=eTUf&YF2APX*dE*R(q{MKF7wIO5 zQQs+bMM|`L$J1#6H^mfafqGSqpOtoUig=7Z8QAYRbgIq zw$EH#Ak~=T$V)^f=`?F3}NysZeXJrR#49#)9^6PEd8o=w` z&*=Tx%SE=yLl`8X(5`7X_x%?JW5me5&HXN$c3xs-&4Ae639`eXOt3(>yo%aXd-Z$$l>H$HXPU|83bYV9!=IaPQfQw0EN{HQ z{G-VsuUPf7=g9)EC9LDfdZGo9Ji+W{k*_$f@iOho0cAXrUVP0^L7HHch%^sX=W=&i z%4MQ8zDZ1t9aCOvV|@SONC*HO@E`>+w7 za<3=3Y;E3U^_^fh6I<6aC-Z}!5ls|wan`Ru4u;XaI(K4+_44)$G6QBYnoTa3z-%g; z98!z74ZGhutnURw5A4c)(fGoM%j@O2H|Wnh5X>=t5qS%5V>~RJi6aQ~Xc|T?_Q+;z z;-8$V(Sa-9j@A8mpN&1STO>SQR$|J)((EGQ6&5e)0-@J^1&=y$uYI4CuU(RBFD!hx*uKIfgXDz z#R^QjV&*I zh}PAvz|vU_ry_r9AQftwxhLzl#NHz~uWo{Yn{p1d+R*To2&;N`qHoSq3ynXgl~+!* zLA%z~L$_fJwk^(x#u40sL*k=uT_!WzQAc&C~v|FSpfNokSA@hk_qW2+4kO4_b^ z!D;tKg%4_QLIOl7Aal8&CjN<|(RLMz5@kB?#B*ifP=$dO()w#6c;_9F#x#pJlRm&Mvy&4T&$ z{Kq3-&7qF(yO;EzZmI~BDeUs}BO`7B0A)R_vvE1{$&rVeDrXvXOm8(iYC#O%r1Y5b zr9#0FzbGnBa8Vd9`^FZ$f*8(|{? zh$p8_0O>Rz6R!BpPJx##x#RXh)1&Z~seWHf)uL;vJbWl~MqK`_5ao+egZ|J?Yxe_g10C!T2<3;uYKry0?vZ7Q`d! z-yQ2u#LGF>yz7TW)%4Kn*5iMaY%~vE{AiC=vVcf=oy;!jPhM+#e{u&lnDcDc7o_o% z0OL!iMifwdre;7~(+!8Q^2N1E$~pyKGc)tUl9CV>iIl|0X39(E0PsS%#FVR)aU(jA z1SVtcRLTTg~COkQC_E~#__arx7f&!w4;6$G_w{_@_ zT{lWSvMYSp{G)QwYFUO~Wx^QMvd6dQYVI_x+pQW4PnF4fsk^dj*!N+8`+$B0^7EKC zb}lt@(;tfoY<8S`l#t{WQ8y-iXF@soJLM^>_tke>kBDi0-IHHOv_3+}*ONRS$@LVQiDjK!7bllx zIU(`V?(1(jTO)nEWRxr@(urT>r#t8UL9>!tku$axy_RhFyu$Y-?pfD|n!zF_H4a=t zHB~3&`Z%!IupAkGBYR-X0E)j2OXe3A8A21eednMwfyjgIX`G&C`-*h9-d8|E(OcX; zukZ@+^~NKWyHQ&!^0FLgjT^~+d4O@xK!AqP@Ro2CV8(Yiv5r8tNod%-(vcHXzF!h3 zw^yFI7BGjQ{Qa@zEI}o_7z=Z}ok`4Rq_9D~&x!#;a$+vU5K2p1hfg`Rb*rn-pj-Xf zrrJ-H_lYy=>U?=s`EayMUo;HD*e;CKSUZsvvfXy~29=~!Z?iYPH<_Zql(Vn^e1+Po z!etxl^NppC;EMD#jW#^jomZcE8ERT>&pt-X>upU`P+-A&Bp*`B>zb%6UbRkQj7rQl z85f`($j>)N14_SoKo#}4REh&K9)^{sEsKpFYf&%?x3Wk)JV|BYvO?T#0c z_atRb%c>1;=4@UL@ke0v|3(GBC5@eW2}%UVTll0@d*YQ2tXMzwPCtlx^PS+|_20Pu z8hO^owLLFOIBF4N?;lq)aIU|mY=ERyn)SARFpjskXnbD_O%)XE$&6w+*J12n;U;J~ z)Z5)0RPO~b#IW5fN{H8&*~zQ2^r|^JnL5Po!+lfoZ{SPqAH}`8(9UK5$hHTl)~9xt zRpgQeQ+@n6j~~;Tsb-{1VpI`~J`r#D7w_E4bOZU+GD6btY?E&tZ`gz*DASI0pzsop zS#-DZ`t*V6nASMv1bxDaSIRaP!>bSA%yt;xHU7NOD2rj+;G2H_JR*Y^Zj@~ux;(H2 z`O6Kv1e-|^=ESr|STH+>wpkEMT?sj$V!4Lb<#$7lWqT9$AK6kxObtgF{`lUP@5nzx zEvG{Y9g|vu1tN=*tB;t@!sL_WWBOx#W*rUF6?X5RaLJt^)j>fD+7^239h|oZKJhG0 z9L$+$Xy6z$(shu8hyL;$z!vt&d~=bcYMr7JA2Afc^Ux?vQxT~tHF{qcqFK)1_v4D(y+vBdB&v3*{Enx)oc$E2hfkE6^ zkxeDWLTvz)uwaP>35KOwHLis&E1SHKhOVK1*M)z3tp6HAW;=E?23LmsS^$GTJuCxt zh*hNUhwRA9NKp$EvNgV(Z5`X3{8#`otqu~PdZ)hSn4#t5LnS`D0V$8EUc|& zO!Q3b4jEi4u;`kAQoWc4e3&L=3!ajub;`_vKdl8a(F+P8$&d4oA#4_nOky(+JHv&g zbMe(}yY0j}2zTZ_wlRUxP-=A}RB;NDSr3Hy27Gl^cH=6=whQi4c@wI6XKy-i_L$GX zbTRC~s;$k$G%a5G0HkuQM-G*Ehr{(GQ`nI_W;ij>x(xR6J>Ad%@>iLs?rhPLF8_KiLoUMz$YJZVQJIXRE3d1`w7z30`{)6f3hm2dx(oIlM;EVlwt3jO9d!Z& z|8Z%a2jMsT!4^&YYbL~uHY**XL&TSa+=SyzZ*06SL@GT>S9^wa@C~srlFi)qju4UMSJqsay+zvrU4FY*?l- zT~x%m@TID{a~ba8G1^(p>+UUB1eh>Hs1W&@&XW!dKWUs#Z#qlIhmzl?CP&sr_I~A~ z$MkD|kATRqJrg`=R@}IhXM^=Cy1`RlLA=UjbJhlG)$|aFB}p{0Fe%MRGNxMp7;USB zk4ZhhVU?qMgjqU#YiqK6@lN&lRug^>zKjfbyH{SA2xFWR>15X2T0Prz*}m?410jZN zh0Jh~-Kd%Ni&9;d=9!uzi&6F?r_qXUF2O^y-IgS2#pC~edDkqf$H-!iS@0ZMO?jH~ zA){7}J82MGwx<7ER1=H>8+;q-8y4y0(t#hoEgqP$zi09vv2-fMz+G zfMp5svo4NBmB6lx3Wdp^qydZ5*^LPbp&YCPoHJ|(cJjN!d|n_k&Jt>o47c!n{vDUF z?ZN+@-}Ddb@rY~WD;NXcBYeOGyypY(PtDsKF*~ymxR!6IXYjJft77IA&{&^Tm)p_j zz+22os0Z<0(b8V&q(Q(q>z|lZSEmeeannh%s+FcEu4Ern)OP#sfI8isE{Scn_g02^43?8~+ zXh!71tyYhX>5kU7*sPy~Cyt0|j28kb@I`e9ime!4x>OCht2K^oI3hIAhj5j?&>Asq zDw1&sR;$;z1+O*lv?LjzZ0nEPXKV|c#VbmDy0(T{0%~V%5jp1D4p(UvS^zA39A8|% zP4BwfFPX%g>2i`QSE}+X#@M9XrneO>K8i5jmu1;06E zIe9VJ6e;`y=AMR;gSD4EIM+0UDr(Q1roE!){M3*zmO}5^Bvf5TTs22AP>oh*;iQ+m zTwaE-O#yYVqRy(f+XKMky=F=H?N5g>?VdHk7 zpdg%XSmUp}U@x$D>`whaL%-FQmZ?rz%-G~)Me5HU8vW+njcH(C?y-)` z$TX5}a^I0B(OYTOa)h0dk(3^n_e;pRx0uz%I+mg?9IZ~lcCx+*+@x5)u!fk(g5_#dfZsrDDBW};gLjoj8ZnbWC4Sl_09jS$4unC-ZP1<=?Q7(4dyKFVum^zrvagM+L z0yxf#3&KDktNP`BEY=1o)L0>^=XP(Ew*74z+iCUQlnE-OqFKkJN9sA6C>dSX<_-bv zV=>iz{PeMW|JfQZ-5#;Sj|>&Q?JIY*KZSb@+@AxKX5<%^Ir4LfPfW8Ja>}G z!@RWBD6Kg@2C}V}!1SxDc!5Phd)sDgEcD>r3&)qwe1at{ZM$`VTQyg_ZIfBs8;wkcu(H@D~U(j!Ws)~&2Hk&7_Tv^#C*VX;x}@@r%xF?G4EMCtkS`n%s#^3hTpYOqF`UDt`}$+1tzPRAhb zp+kD3oZNUw<}nCQ(BKHkHhq{|c4$qhdiH~u7usKanTnvFSnQ!f`O*^FrYjQmIa4t+|>w!mhC2Y+bx z52KE&%b$sZB*e5tuyt&%MnMM21c3@1{D0zkHHQ z@lON^wlrF7OHo=+iisLvM)_Fv7U8O@DZmKcG)*&4EWu8(b<$GHb+Fn7+$Tmcv|XRw zkM^584{Z?=qtz_XeH0P;Bs$Ydj98vPT1)K^cj+>X>URk7uyC7Fa#1}tR~yXDvcra< zg(+3u!bd{?xn^Q~C4NwS@)ll&#_W~9sc*$!@T2dURwr02Xhv@8CiaUw6MPrUe|`0j zyT9vca5b3tI&V#J@7KTM9EUimQT7bf`ziP-N!esy9&?VQT1Y9Pko0~hgd#ONu%=fYy2A?hbprQ*B zRb6plwyL(t-6a90mS}fC^pvvA&C;e5SzL-Ov99w=X)Fja{3VeuPvLEU`q+9g{Dr?n zIb#z|52F}NXzyw`h=eC-r;6m~*DmeW`eNiIw#7?}%;_-j1}(7xf+X~2(1hmjBJ4sl ze_a%xXz;0VDb$Lc?msk&T6nDRXx=kl%TbaAZsm zYUBfvVromKB)wPfYDjELGJXoYH*I^z8Nv_$vZ z$NO>}^^-7*ytSt1KEbEi&o1jw#9k42^E6!W@M|@esivx|Ug%n@l=f>iv!78dtqd6D zOp@cT++D*pBXu(BQ{Hk&uK-H#FZzsS(VWCzEHKNzy#m5vYVI%Ya@{>tL#Zh>yi7+O zyEkI4T$#_$`6PZZV2bu!*L<7cok>bdSDUl28=vHFLCq0;9})BI+Z4=_`x{Ej?GEy4 zSa!JypW(7vxXa3~K{!?&a_pzzf6pVK5=!urUR=bgXiLf4#MMwV_GWkp%*K06W z(pFS@Qc=Q~MeQEx0;TstG*_s;U-lvlfsn6&>Xn+MW8iYhd1F*NPX*ZmXE*f$X-ns0 zAq0d}Q;jQPIv2>!He;@b;f13m@+*M#!|3}^>f*>|_p^rQNJeS7FpV+|$lS`x=uD>B z+RX24~D_zzVn*l$+2jkBw3mN~GXC02KCArb@Vd2p$T;G~Sg#w1I%evp{Pyhv?z=hz;v1da zE1F@Z4lUUU8Fms{wtlL{xUO0rs$F6g*Pk`+G~AH|bS~zor`llkKN8lNU^j^Xn3S2v zi056yR(7hV$8m=#L_+H#Dwpa6d_5h-TcU&pk!U|-_zmgJ@_Mjz+J#4kE`n23?s-J*xyPg{Ts^H(kN=1>$)^J?~hH?ZqSDG>cpt z(n52ia-g7oo;&I5;c~Ev8))z={&8n!M(MrR5IGyNI2b%rFnRoSw^<@*$8eHwuwaS* z#VRK=oaoosXPkS$On@Mq1b-@3sn@Aa456ELGt{MQpV}WWW#NAr>%YhU z=^|-Fu&2wf%R_RvcbKcDc(QMj34cDe>CEZeFi@zzp%j-K5b#-J>(VX?iK;Jym^L*$ zc~uAhTV);73!*WhZPC`rm5BETor%iUsWdgXz_atAj>#oMber0DJdx9x0YbvoA8Hr1 zEE+0}zqsW=m`Sq;Hd_~jTiZx2(^kra!-?!KG*4Q5Q77{Fm@El{aMhh9CIA7uEcP;o z`*(&`6E2tU6beqth1Taf@lhQGTh|$Nx2)9$voNMKc>A1}Y)xcs9RF}c5S3W$0rqf5 zN$HypuBX*sXubNzBi>N9c*MRd7F*VkX?XN&C_#=*zT&Y7Q;Wp!P*4$v7RegU6TeXK zAH*|q?D%_G;m`HBI@4L?Xkl*+%EdrT6P(wV2W|)>?+%lIM{h9izxEErf5W}1q*c}` zESVV8VYd?|f-NT-r;o|gGhz)up@l_{jcjkDbe&T2;hGc$OK;1Z(3^GD5S1921P`vg z0?h@qu$hxc!v6s+ zD6YR7*;0G_Sgeu8Px7-Nw;5-G7#lp&LP*$N0dmXXdrtoEM<1bVU|s>qH+N;~2I$m9 zdb7r>?{p?;J%2$ykL97^9d%;V`HibvW(J<+pz(`Z@NzbH`5GX-0%kaL*Q~9ITv_Us zBB}QqUjguYJMje%`bpK(<^mi<%I2etmU^KB7#7el5x{c0;N#vy-d9EK8dl)&}CEw!l5?aAvk zFKE(XurlD_pSp8SBH1{Rf}erKj7W`A^l1tG5~)1+fiX(F9+U4GWS12tE=bitI5=+| zJZlS+cY@6|HfP7BMahz9-1&sJiL!-4X|$og;UL+=HR3qDdG|}ri6h1=vc<>VmbZd& zk8TxFO%F1+RFuy6Z^sp8;Lq@tR0~KVa=o!*XMgzHV0zL3Q)FQx}Pv)r{7<`!*HpuP5tatkx|ZrZIN>OZvj6ClIP~ zSA-q#McAVHzn3IYy#kyG{O5s+=jji2M+PFXb@GC9h~{sl7%$TYgB#S%)(s{5&a3Za zl=D5~=s%BU zpSl(lsdLzPA+fBzUnq=vR?)C<5*G>}6BNA5*B@N1GDlE*Zx4==Kf?IJo!FeyZEzS-=!PU^2eA>L3Wm>4fz`gxCV5pHSDs=FzO`= z3+;z`0zW+A? zP~nbV#foQ@s#CjgHA(SR55Je-SA!UJR-T1aK^wR)-Am3j zPz$^vUW|5hJjNOoE>eUfTni`B4e0MjiloUi3pL9e zBqdF|EG1Le;JRxn+)LE^uLisG&YHzk7)?GpbYD>?jCBmb@9if4|_qS6e-qYe!T>U_G0ejgBzbh z1L|+xfy|@uxDN2Yoss|A|Nm73qwcM?^a`ML5WgCGk)mHlW&nET*nfHjAUHML`2ibt zAQnH1wv9#5d^7Yb)wSvT01r)B5(2WE$AZMsbSQrH;FJNQp|Ie|6b1;33p^|iho3K1NLiE z+wDA^E46YI6E4Fuo2D;=6T9S17h38*6wxegyCi6I!Nl-c#-k$Dg&f>H@FwEVwB-Nb ztI4v8wN}0P4-YSCPi!qRk-qe!m`H8CX%xwD862vEmZ3 zc*%{8rspAFXZqTy12i%LeTQCB@?U>gt1XQI0m0Mkc`Z8Ft^S7^5KYNT*gA%{dhtb1 z-MI@5h7Zv3XyDfZKX!?T$ zGv8YsaNJV5Go;;rlf^1_;CCGYQcMVIRDfD|;oeW*k8kph=$*>Ia6HYfjDfF&x#37G zVIauOL%y_tHLIqdd zg%O8!J6UYCE*?4GIiDie(bdGE8~uA!4l7WR)1qOSbRfs&6gKbXeIH@iUxh6T3HYgW9^?BVoC`*m=aO2tkm_6qc(GIQA(>`W9;0m zGysoTR+d}ZO6Q{4T;Cjaw&-cYfM8%QFanY%%r3?>pIgA>FBMtNZHXUt^MDV+=5tjJk~$m z-yIlO=v2!n0GU&>O=0ceb4vUDpC;I!aOLRz65y@5n(dc|Bk9G9rw^Wo6xvZF-q&$j7dP?5pFX8O zT0gQ`bVY0RK$;bv%X1b`-qK@MKHK%~^evsfGZFB#ygpc8<%>qFy$}CvoIni9fBbc% zP(S^(^?bHk>RMJ8_MI5YS?}|h-?rwl>XjG}3++unahTT#7mXxQxlr@0mx|^mG z(T$l7+|j7Yy8e5!a-sPLfA*{})wf#G?b#9|o8fdeHj|uJw_Nl;?AU37_8tCB%1AM# zh(7UZrQj1nIj3y~jPa#2)ecZFBouesgjVH$bhKYkKLz$!n+2Q|>={#MZ892O42gFS zSy$jqF8;OG^4j!v$F9_$qr4&y0>=*Kb)&RdbW*~F3sSLyRy^C zil1ESbFI1L#js!?s$WlcdUfsgA>lD+V3hzft^Js2)mrFU)s+-kRU(C%^G_W0nl~BS5vE011iPgHY(3w z?lvt2#!xTgJj-TUB>%UK|8G&_^ZQt2E7{^k!4Y?nEVr+d`cOuKjLaAtAA8+Laww5V z3-F37mykb5T*P$M_Z`L0k(`4xf>hVLff8GE%-1K*MJLy&r3IdNR88qs#{Tj~B`;*D z#EQ|i;?(sg<%i#dO9D;w^!6K<`hWIy><&7S@b>TLIg@Nsejy8S)fovblgW&nM(jVx zj0Xw#M#RT_oA%)3;7^y{*w@0g{xvR&R7v*^cU zg=YLgB8vdco;|U>+L29z|97vNO~haw$}AUK%29Fzf z88BF>Q_mmbDt2RqxQl#}WR}KjE=zxq>->8co*kjb*kXsj>-tDN(v4U_j%1NmqU6}5 zc3xv_F24}0p`}@n`qI;__HON;yeyQlfAz%5QQVBz+NF$+{DWsb@xX~hRrQ>J2gA(! zRM^OvhOA{Isz@rUYWEb89u#=15=12qJt|CnMoJb_6ZGwIt^svah|n_&#Ca|SHPi5} z3%elHH^D*gImOotg4u>TuIt$xHKii zxHh+Kdl)utX^Ryou0=|bKyioS?(Qw_7F=6sancg3xLX3DSb(6#U4y&3yY`=b&b|Ad zd*AnKKIHeTHRqaZ${1sn6CcofHO)V%&@f?)-gXlg09xSUCSXjTJqx{~);HGcB&;~) z;xU4l4ZA1%SUNixZ;W~z3gNqV{`zMTXdmoxBz`-53p`x$hMx%Q8ZkkO1NJRFRgYIT z-QL8ax1Po+g|KSOmN)j)+{D*KNUbuh*e2wu1={c0`kWX@@SBl0!iCrKNzd zEFx1%=OL7@92ul;vKV&F0Ec*Ekh^da0>6h4P1DIH` z(_(D9Kw7hziT)>C`#tp&*X}57zYf)?`?bBk$o&UuP_adZ^v2m0l{MnU%6$i3DM+cNJ!a|$;kVZV%h5S5zK zx6a0cU2M?B+`(|esE6jn_@{Tj#@~)#)dz~da3M$)GB}}7;+koQ6C5wiDj6r%o3<}| zjeLFn3n|m1L{HXp3uA&t2(Kq<5p!zYh7HnB_|)CPH^M4vWs1T}5wU_&+?g|+z_GABc> zUX6@`-JfATDn@emEb6TKj!tUoN?YYaLUP|9YF_8#p@H(~>yFl|!2DfsKcgT^-tBb+ z^&ZCi|L!XMg&uRUw>7HLQ**)FRlA&wTVt_N45@&JR7y1IU^J3!HAKtRsg>BV%qCNm z7WF&W37wry4U8v+kn!uU9rC#zmWPx7o{Y5S)z<2In_IH+8!QqRA_U}rLKAmL;ufyQ zg`keBXFuO#W?^2ynt(1$2Dm1F^NWw}Z*Bz2quJ5f^{4i4N8PL9=Da-P`yT>$tZaiVr zaVp4XH&-=@xN>D%X~(bcO=U|Z(=UZ$f5$GJTc?(dN-8$cNNB>(&{4lae&VdI;Q%4h$ z@ZcwF4J8~dhPx!ZOT|UKz2#=Y*Ow_zb0La%CcKsjm1APMm_lT-l*62lnL$yR@D%1v z4=1Bs*DdWu{W0Y#>F?S#{S>V#GTf>`?~`CWVhm45d-$1N-kV@NF@KB45#xvl%J6EB)#h}U4O0=^xQ9ERq8 z274^qv7&)&D_yi+$BX;;p_y99SLjQNthMdilkhJU$U^yARlY6ygz^+xs9sw=tnoH2 z>U(Hvyg{Y#_t(4^fxjys-QaVUAws$~0XC$feqo_^GJ4|VVR1SMZgifo=110fGC778 zo*~nrz}WJf^Ne_z=`_pn^Wnl=M^JLyxZrx-8bdPSIv*)@R8(c9VXu&a=dKd*tJWT) z$~MpcQzzJRI0cH|NQGbSoh!2^O&RpkSBx=#z`Q^BeqbWrPPjj?o$TiqMAB2zu_j|K z{3YFA*!BPje?wj#8uH2iKCps}8MdN3!je@!DAluS8F5 zoeBgz&beiN;c9oQ>Whtn9wX~mQVyV=#It^B)mrYrxO3msQm@>!*`BYmLh#j;JC51! z9H-W-H=#9b$#t}Ks3={LObPmTJ8C)FK7Zd#5=yFe37rk!27Qhjw>1%GZLT^<7-Aac zz8A9s^%?jv}XN6w?C9EqZ&)7DVgp=uPp6{fo)+^0+qRArh{9 zDSbWbN;3geMCu?i(6_K5@!jzwBcl6`|U_|G(cD?K{zM`yXtq0V-*s|q<3EgiIKu&)va&fRewQu_-wA3`y1C$CroNuATb~fcS>!*z z-mjnNsd$b;ls);UYN6&m_oz7^Dl97uE5uwz2-g_3H`gQTD#6>Y~? zx`S4?8qUy(_7=Ai9gUF+J-=<>A*e=YR0}x|I}^%1112E#2F{X6z-sJ)Tz>SLUg+FKBp8XWI1U+6mbps40T5uvPe)ZA0=io^7L%S$edMsc@9T#LmrOHE`8X-Pvh z1*k5PntqLf`>@efQx_K|59p^Wx6*5eK#~ z-f*k1IOEty{QKef3c+K+eYYFV4CruFR^IPyypSD`4+ISry6`eT;7Yv1%f}?~|EP{w zI>586lWnDMn2zBPuccUA4f;)9)@f8D|LC35@~y?XB>hm!3`V)%_Lt$ned}cqwB=O| zi`Hg6fPuK;Z%`k`5f+0dxV5`eW>3;SYo|Hkd*|j8ld=U09b$KrS~>5YC3@lY&~f#N zZWFxi-DL3t)0SYkoVbbXuJ>c^lYT$o&{+`=Zh57Hli<_SPOa77Z^vZ4z$Bnje}94C z9Ew-P(03boi579(3%~vGX(ZM2Eccp_77Yyphw7Qo(_ZLA zzbA4BAD^H{#~c;0@V!$iH{0+g7W$nt;5RWQt+7T7*v!aKzDu2o45b4c#)U!l+_A|k zYUi?B<;^0u6LZHFExkejoIaVuze_%eFgj@{(A*~{DeU3O1%l?Y$K02BJb zy%5LCuMG;H-dD$j{Ot&sx25oq&6FRnN0!d5bApOdh>v)28a2519XTA7M}c-QtV~@; zALN`Aj~@nOD1;4%HgVtGyyo@sUvVE)TFhKrG>ScHI|S^+dJ@`MB%hj`G#|)R7N7P_ zd5ENR3t;xU%|A6W3msNEqjTbp3LJOadTPPY>u!p5x#}nqWAjFP)7cb-cSmIs{}(Pm zMg`IejCjKfhgR6`QPxI31b9^M&txCom^71iT>wEsclwY-W zx0Kx_<=%^I3%T`67T|?~i zxvzJO3Y^^$YE;B^O|Ll^P#E=1@_gj+MW1mWiW_#X@AuTobudI$Y4#4so&Q21Au%@P zBG!NlBiIS{CXU#Cc2sPzxCLXKG$YH3kgS&yE}o^JIp<st>c*UN=ra&ZULzxi9-) zR=~V@>GcJxxVkg-Hb8OK)bR8B*`I0*We+P;o6pa66Z8jF-l`ZCJsofOlA{wYp^;jU z92IyCMkq!4t%XY@AR;Sw2L*oj%_e2-CG2C=GcDPYO~=nIQV~`DmWZ|?ICnpuZ@lo% zQKM(EQ77B}1gK-rl;<^2R$C+%C_B%K zox(oBB~nH|`~MlvJb!8F4E(kG)p$}wZ;dfrPI*rh!EfeCaS2*d_Wf!@;RB=5_HOMx zLvw}|zqeyC8tXspN<)riR+MU6aJwJ!B*5<$Ax*JL<-2pPWi*H9Dl`)%6zNG}=K^

    HeTO7B)^p4~Swn3t@%e)K8n;)zuH3=r`T@%)5Yun%SV5 zGApZ8ggsfRP{>qz{22pY$a=+gnXp->sGwBwZ!4W|2EPM+HLWyAq~5*gcWh>)mk>J( z9KR?2I#@L@m*RfK8=BIACY-n)0R>}W-_TZQ|0^>?a|A9}!Hd~aNjJI`#)a1BI>D8Y zPH&6)6a(qi_QA~$^x|~fOQJ*@Z-p6Dwu@BYo_lLY?uF17Aep&XFB7B{0vKyJtWcA1 zd)a^EJl~4mVDy2Y8u!S0P@KlqFA-kni4)}Uea8E`Tr8P2gHeU!V$NeV^YM~jHFEXY zk4aa|oxKHK?Z(ETLiY8HEYgMWfRpcR5W_~HLhu8t`x?Y2O+!b9W2{i)G8i%{dSrtX(f_Je-Efu;nidbNs4Q0*L61T4Rb$SdhGkRxdkzW@AjKEUM5Zt1&YL}~+@ zQ$Og?AIA=|8oq0M?ZOiTT~&MK`3FrD0A||W!|1|3VwhANpTa!nhPP7%RfYBKG`BJwTj~3 zZyE8sJKrbsB;acn^(C7M_kNlZ4fKZ~J!40*QK_7TkB`%P8*ky^o3s0>f1p+?SDYxdPZ|9CNhQPp z6uFdaDoN9ssl%A8WRjNn-IsHSe zmr1R=|LQ*&qjNYnz%TSr^;W}a2ZHD z>1TNIF`vY7JFxB1OGLBD!X)KqapOrvg~t+M^|KtD z`$Q-}vuO=J1Cu9&O4x-DEEbZ+*Vu=rbdsd;B%oEXBQ@_j)ULU)M z5er}7Rs$v_mM<{r&6_Ty2$=L2imGREbc6#G)>C3)3+i#*1ZnR-&>!p8$q2K&)otRc zyh*GpkjjoEiq058R2r8!PfqWZm>jm)yw6y3-5o!^f6i(Mk3Fy4=kPW-lGC332_J7M zK5ib>U2*`B%g$sKnL*d`VwUep+0lpQdhoN9p(4++oOKdCRD|_T+*`=Ata)PjZ-chB zlTxbGcMe2dD(2{qix18R3cqA3TS&OFGEsZuV7g@HBdeC}{(E1p#t^|APg&|UW*;s*-cO~BuPu3KyJ2D4zBz>*dd-nJ z__C8e&t(3w+Z(%t6@RDk7sB-5a_$==j&?eDg0Kfzg=v1cO!!Mw=myZ|D5MyXLGSH* z!pq{Y7NXnLa9>B@#0bNscm+H*#C28z23?KhEgG+GIV<#D=;s>IVnon}NMODZmf&@P z68_@tctJ6BnG`eX2pE^*F-kTteF7o+M-z<=UR9@q>6O&5h-r=o=91%do~XQdwV4-h zrMWF(>=Bk)zL-0#hztCgwvv9cQOC4d_NJRPx0l9aZ+H;M!`QZCIRW4AKQ_>`tt%T7 zyadP;v)u?{7nA`kg7kZD5*xD!7ET0@X7-^f9)=H}_kppzG5F|iUQ|N`1Y0F~zAGBQ`q;Xx9{urx3 zmIe*K*zX4t=5Ff6+Z{|Ul$?h}s3kuK zrU|eaNfHP;Hqk{V$_q$$S`X)r?TfmsLiGwYk6|k4#ha_TiFv^4gMiPb+qo`xr7&UT z-!m|#k-yy1#c`_3@~yOuu;v?I&I%Q2DZw)yEQ2d~D{%}CFnAxim~zIwU_O?1n+`>3 z3wGxsupdFIXYI^MsTt(!l@LhED(?%CzRk^uB&phoFZusbb>q)Q4a)@mnwnwPh_v0? z$x=}tp0t=XT-m93hc|>^QH-T`ZQj9JYiIG;Fs@Lb|AKzJUSCc&AHpTKp9FIt$pvm% zOlRq$VEq*%D>|{3><&{q!*z3}Lyfq#Cp4&GpGj#+e&p08xdVO0(P&4vldx<%;{mk+ zj<*dgB$N0zh5leC<(ZHj(cz-~&N~i2Ev?CI6a8V_BNwK&_;dT^?go0ioxj8^ZN!ef z1m`VqXC4FWzK;)+=R~B#9i8`C5KmX7UGG zL9V0U;TC?zlBw@leJ{$M*6|sy`w- zeD4R&ZJo3K@2cfrpG(i}S6Bi{YC} zCczVe6xV9>L2@+58QrcHRzz$$?5ULAPNk(zo>rhc`8&_Y`7jMd$Y8zeu|v4!KFVo za+PsO>0;O9b^Q62A!6dxL;k5v_3^N@|;f!Sv;i)J>5tsda74es=IgF6Y3z z#=cVBqTFyRUN1RcS0fp%|JNtou%W0gV2mB~ttD8{b6m>HlzQX|Q#HDA}IY%U6beMERRg-9D>neQjL?`4!K`=$8=Ub~-t(yB?i^ zAC|HH7}FAV+q#Gv4afXKo3YRT5iQWjYq9wTQ5(fjck zF8`xaV1WV58)KoAzD0?Ksg=dJ+mm=GTg$73*+e?wUoDAJSZ(BRA!g)ER zXib=|X0 z1N0X0bHkrPlb}(l&y;9(nTZcf=0?s7I!X`2@)%X_x8+aQO4 zEaR0*lvxf?c>~sym&F1Fy);}%sKl5JD?uKeecB}^==#pc^)@dK z{9g1cabou04q+Ww{NEe%ZIV|sgW%4BbvkCrO;)3!pl znWQ(4$kZtP`#NzAjtdg$t${5ECQrvEEU|#yu%lHleb~Ns6?$wGXxxX6)-Xv0pumcVWCz?xE3~{DXGHQ-77Z?}Uzbmr$7F$&r?1epyosDuQlw1NPeuP|vaA|W?S}c;sc?d`U-vfkHsvD(c*ZLX|HxUlg|V>EgnRSU zQ=s_`fXbcC(11NHnb-KH2bx#pIASpGe)=BArl^Jk+oAJqK7ny5mv|EBjPONwycNZd z!#`-wFAnzOv{qvbm?y_vT*R8+IZ7D-2=d+zrX`1qS6mRTrSkP-mxB3YYW7`eze^Ia z#R>7V(tvXUDP!XrR0f}%c0b`#;q1_O-?Z+9X8F+fF}fhGY!%L#@<3^S_`+RSWr>+1 z!BmvbfZi*K&Rst^;#9obsy^b=aK870o-b6&xDS4Dwy~|S7GU!d(dROs&GLesnZ-mE zrP7(1^YMCg<#k$^u43yYWi{lA*ATA7Gmw8;NjmU-o9{w%5}MTWvXhn3M#LHSCFO*3 z_T5siNx3H)h)J;DDWL2Z%CV#N5iQ~fil)A^G3_aLrakLXKwrH^{G(l1gdJfIuT zNHbb?XqMq0qwmtqH`t1|c+M~%BX55@Dt7o8$SX%qFHZduEdsDKX=peW`o$#+4Viz8 zxXZH<9w@6bhdXn+$ITc$d-H-7^z&%cs#3DV%Bp7ZI<$D9pt@*t)mog{xF1NxW_gHy zxEA_ZXwB&h-6g4c`D)Ug>TjDdCai^1IBDfUHv4Ruv(UEQ1POJvcVyKW5Vz*wD$=8cr%H~ha zHN_M2O3qcX7pBX?^0&}pe@P0PuR^PcTpimQ?ytWV8&d^T#v3zt*1xK}FXX%s@CjZc3%@v33=;|r7xh;rx= z{LL#lE`E$}c-_&Il>N$;NtK_J-SSAhN#twr)B5jadrJ%3;otrWu5}59q!Dbndh9QY>vsNuouO&H^G)J0Byaf_|(Cc^Gi zX}65s%>&tfk;lZ?PQwWY#(-d1_@RXMQd+0I_4C1^rcrwIRykhtg0+6YU58kSfV{F% zqKV0!Iobeox~Y9bQ(q+(8Hc)vLIjot`=>!8NDbJTZ3$&dWJMCDs=ZwZ}T zdAF63d24Qnn!RH3c<;c%@7?(eefbVQYe^G4|5z=Xbw}TIima$M(!_dLp{nL2vt{J` zXkGP9xgIZ7$&(B~=lAxvl&}ZqPJ0E_k~HNw=fDM~^`iApy+L!n??QwTyF~64pXBs! z-Jkj99f~A%#L>rSI4ZZZzWo^B+;i791|))7af?mT7OLY<8N$@<)|kZegF|LQL@pCL zwXbT#Q{%F;ihUK^a^Svzk%F$$4@0uSwn6J*q6LU;O8Ce(CQ5=ch;Aolf#pkENxfk zm_)9IOWL$mcf>G!c$HDPcIEgCjdTOD$5@(ZMt8?#BbfHSZJNwF$(R*2NBK}OvGA^V z_gyT&VS6LQ9Xl5ILHNtKx!4~x#-qqjhuLwYQ2&AfI~0S@tIUF3r>p0K-FG`fza~+> zl-06SE@yJ{6|!Os7lMq#-*#dKSHZU+6Sa0C`xz=#1iRhB^{Hgh>!*@YZWl_mT1@pwT4Y;18+mY{^@%0V!vcBh z_lCH+Fm=@%y;LY!Yt&v?(5kX{QU=h4tN8{RBU-RUllPXpZl)Xdg53n?nK%P}spe)x zv~$72+_0yoEcH}_Q;M0E`JOToM}J=JE<)D|>`0+IB3>0g zMS)3$z^GN4=OEdp7}2&NzX3w73$0^SSepFgE8Trl-;M&o5c}xBHn39QY~*}mf_n^U z&msC!LRL4Du+}WR6l+W_jYMV(xtLR~_;or+RZ=c_>QE<*F|m(L;vz(BsYWGEJ38ky zyT^)6CJL;k-OIyJs80P$_1ct%?Dy%uwqWskWr#T#A{4evDdK7Oh$ShX7@YzJ^ywj^ zd@dhH=XipNIOX8A^CWcmL+q%Dgs9?@awR8wG31+wJGNy5Wed?3gD71=?W^cG$E*4y zb~lE2uwE`BxCbwRp{8-(&WZ1a6|zV{!RH{S*H9%_e%!lX%lvLhhmJF=0;;3kXFG9t zxX0#LOOahCHeOs-X~eD?Tca++suUn^Dwrzk_e1Dv`sxWQn`8(dC*v1qWUucX(R2{q z4t0eJWHWQIqf0bMUt2MWJ+ktp?LnqLwV-Pv1)3e@l}qF!aX9A{%EUXVJ{UR`4_!`0rSP%1cW}M zVnuJPSJhZsJgF3b7`HR(z?L4nf^tM9(bI%{D|d1rKH!3y3G$ z*LO6_CaQ2KoK-Qs$p(0nC+i{&TzJ&ri`!}d6NbBe*lFAsW@G3oDp21)*M~2z}~1-&56SyzRuZpNX)bprMUXs2Gv=T{d$^dQw`x6|-IP;6)Xm-;<$k!|-bCOFMqT|(Pp#hyN&p?@ zvZ&`@fQyHp@wRAqkDdx|`Ef?3o^3Rqq*}Li`d3S_Epy;mPkQ_CZWiSO*%F2aGov&f z8ooR(tkq3hRM7YMPx{0fz26R2`s=>`f&;hQA}f~0h)IoUER{)QerDc(rFBXuFfpl; z`GT?PENSQLJ0Zo-^ni>xQC#i{K}Z`X&6vVyZyHs(o>_~=fu3?@;hb3EHgG7j9bu$X@YH74qN+K6Dig?Fy%4h3 z5vzT`VU>cMgqTzL)KxonZQZ@4nH1$b*~j&MeUYx;xG1_o*e$WFeE%7v(e4f0a zqP+)TE;=wY{jFqCIR;M+OKGh548K*SDicbDefcDzUY?@E7d48<6lo)@ETQMn6D@&>kVYWOe@N@NY+;Jp&WGJd+Sam zS%#zjzYq-1OT1b4hM7 z6KV7z2T!PIPmT*~8{hE*m%WIBlEx=)?NXf)qY7Gfptn*~Y$&E;tSx%B???1dq!(+n zljVM*wL|H~R>DZ72R0DJlX#Qk*^N|Gb1*l88C(0G2CVWFYobAG z+rOY~`QLjt4^JYzCjuYW zUp?DeZiBOIc#%m@ayrr9T%NI}ml$Zm%nIt6L=M^OQre6rLsI<@BcuEa(;0fT9N5`Y ze+0$ni5ibyuEE>}=+a(=6WYsYpwfJwv-Wn82-$wg1~==4ZF8^aM55Uk9rYmwn;o79 zYZ@k$7{?Bx9Fmiic!#I@(7C{C=?R!MPyTFWZ#bK#_bwaUu5R)a7Flj7n+vmvDEVm1 zI4-X`*saMwJhMrZ_!Mu~@}@aqLh*JNS@@l!#DnqT>ZLu84W?a9V*l+BBx*~|$ko?N zTB9y00@Epd(}wEw*@u_2@vlLU3gn>L#*Bv0jT(JcQp%`oJj?x5y|W2T_$shku77Yy zb^2^LiQjm)ZQZZC`BDhpH}T!Y6J~N;B{SS9s&tO%>LreaD~rfEz%o+rCUWdZy6ODMm7cp zbuD+UOMvq; zs#``+QZWFbxjxI8y>-U?#!8N!jtZT4emLu{lPZ~QgXy<1k4Xw!TsF!J?ls&@3y@92 z8Yts4MrD3E3|wgyx6u1r0Jsy^poa0$x{pj~j1QIVj+r7q7{LNWC#b}k>`AA$g8FZa zCC=$MIz6)R3}w~ui0_K#p&ep+vX3vFmpF6=le|$tRXZ4iu0;Mrp^pW0_bn(jGk?&j zBvtO&ulF0-bs3*&)r!H0sojIivpT?N3*ZR?<-UI>;`4<=X zVGk~LB@VvkW#QPF9H9;0rQ)_rZQX>T7#|HcetYn4<%2wK;<=8(g!mS+QEE%+@TRd! zf~{WRxo{PFkW$1Q+9?``h-A%0RVl1L#C1!H#fRXTV@~`lJgY)~{gHdu6)Fnay1hXP z9zKT*296R~5m0S(cN&wsX*6A&;)ZZNaASPMCwCxHNi$8wL?FjNLv55p<>icuOo5AI z%j}@hCl8L;ManW^(5KKJ{;}Oj{K}GF?5LBBGm6|CB=l>P2e~CxHR(Ze4Gf$G-P9j~ zm#gU`E&cUVA=3+BX$#7S9&76USydbr*Wy)^`>L4sbp!; zRcqj8-GBk5FR#%tJsayBVOCB*LdzWc>#NZh?TWwq)a1aqml>6=*7h+g=QYT)_gA=^ zfO|@mX7YHM-fZ+Gs~f|knlHRe3jvu@(3YiQWAmjhfPlRA`!+8iJ`|&{aSf%!P1M~i zT6V6RGFig*Xqu$-J z!(~<<*~Q05?Vblnwg0rKw+Pk$gZ3HQ#ENIIB({=U&dwbo%l7vxA)W^~4y^C8p^+f$ j%(s0b2NAerWEZBjLtPa6#=yG{3%3b*u^&$`|IGY94%Y$R diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/log4j.xml b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/log4j.xml deleted file mode 100644 index 6e67642f..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/log4j.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/logback.xml deleted file mode 100644 index f4b2c110..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/logback.xml +++ /dev/null @@ -1,188 +0,0 @@ - - - - - - - - - - - %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n - - - - - - - - ${user.dir}/logs/${DIR_NAME}/all.%d{yyyy-MM-dd}.log - 30 - - - - - 30MB - - - - %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n - - - - - - - ${user.dir}/logs/${DIR_NAME}/error.%d{yyyy-MM-dd}.log - 30 - - - - - 10MB - - - - ERROR - ACCEPT - DENY - - - - %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n - - - - - - - ${user.dir}/logs/${DIR_NAME}/warn.%d{yyyy-MM-dd}.log - 30 - - - - - 10MB - - - - WARN - ACCEPT - DENY - - - - %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n - - - - - - - ${user.dir}/logs/${DIR_NAME}/info.%d{yyyy-MM-dd}.log - 30 - - - - - 10MB - - - - INFO - ACCEPT - DENY - - - - %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n - - - - - - - ${user.dir}/logs/${DIR_NAME}/debug.%d{yyyy-MM-dd}.log - 30 - - - - - 10MB - - - - DEBUG - ACCEPT - DENY - - - - %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n - - - - - - - ${user.dir}/logs/${DIR_NAME}/trace.%d{yyyy-MM-dd}.log - 30 - - - - - 10MB - - - - TRACE - ACCEPT - DENY - - - - %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n - - - - - - - ${user.dir}/logs/${DIR_NAME}/springframework.%d{yyyy-MM-dd}.log - - 30 - - - - - 10MB - - - - %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/mail/mail.properties b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/mail/mail.properties deleted file mode 100644 index 2c6aebd2..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/mail/mail.properties +++ /dev/null @@ -1,5 +0,0 @@ -smtp.host=smtp.163.com -pop3.host=pop3.163.com -mail.host=@163.com -mail.account=xxxxxx -mail.password=xxxxxx diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/footer.vm b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/footer.vm deleted file mode 100644 index 967693d7..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/footer.vm +++ /dev/null @@ -1,16 +0,0 @@ -#set ($company = "Apache") -#set ($year = "2016") - - - - - - - - -
    - Copyright $company $year. 保留所有权利。 -
    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/header.vm b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/header.vm deleted file mode 100644 index 877d1e5d..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/header.vm +++ /dev/null @@ -1,6 +0,0 @@ - - - Velocity 邮件模板 - - diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/hello.vm b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/hello.vm deleted file mode 100644 index b43003ab..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/hello.vm +++ /dev/null @@ -1,3 +0,0 @@ -Hello World! The first velocity demo. -Name is $name. -Project is $project \ No newline at end of file diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/mail.vm b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/mail.vm deleted file mode 100644 index 9f439023..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/mail.vm +++ /dev/null @@ -1,140 +0,0 @@ - - - - - - - - - - - - - ## 页首 - #parse("template/header.vm") - - - - - - ## 页脚 - #parse("template/footer.vm") - -
    - - - - ## 收件人名 - - - - - ## 提示 - - - - - ## 邮件正文 - - - - - ## 日期 - - - - - ## 系统提示 - - - - - -
    - - 亲爱的 $name: -
    -
    - -

    $hint

    -
    -
    -

    将超链接、超链接名存入对象,将多个这样的对象放入列表:

    - #foreach( $item in $links ) - $item.name
    - #end -

    传图片url,显示logo

    - logo -
    -
    - $date -
    -
    -
    - 提示:此邮件由系统自动发送,请勿直接回复 -
    -
    -
    - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/velocity.properties b/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/velocity.properties deleted file mode 100644 index c299635c..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/main/resources/template/velocity.properties +++ /dev/null @@ -1,4 +0,0 @@ -resource.loader=file -file.resource.loader.path=D:/01_Workspace/Project/zp/java/javaee-tutorial/codes/oss/src/main/resources -input.encoding=utf-8 -output.encoding=utf-8 diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/html/JsoupTest.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/html/JsoupTest.java deleted file mode 100644 index 23503c78..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/html/JsoupTest.java +++ /dev/null @@ -1,105 +0,0 @@ -/** - * The Apache License 2.0 Copyright (c) 2016 Victor Zhang - */ -package io.github.dunwu.javaee.oss.html; - -import java.io.File; -import java.io.IOException; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.select.Elements; -import org.junit.Before; -import org.junit.Test; - -/** - * @author Victor Zhang - * @since 2016/11/24. - */ -public class JsoupTest { - - final String filePath = System.getProperty("user.dir") + "\\src\\test\\resources\\html\\jsoup-cookbook.html"; - - private Document docFromStr; - - private Document docFromUrl; - - private Document docFromFile; - - /** - * Jsoup 有三种方式加载文档(Document): HTML字符串、URL地址、html文件 - * - * @throws IOException - */ - @Before - public void before() throws IOException { - // 从一个html字符串加载Document对象 - final String html = "First parse" - + "

    Parsed HTML into a doc.

    "; - docFromStr = Jsoup.parse(html); - - // 从一个URL加载Document对象 - docFromUrl = Jsoup.connect("https://www.baidu.com/").get(); - - // 从一个文件加载Document对象 - File input = new File(filePath); - docFromFile = Jsoup.parse(input, "UTF-8"); - - String htmlFragment = "

    Lorem ipsum.

    "; - Document doc = Jsoup.parse(htmlFragment); - } - - /** - * 获取html的title、head、body - */ - @Test - public void testGetHeadAndBody() { - System.out.println("title内容:\n" + docFromStr.title()); - System.out.println("head内容:\n" + docFromStr.head()); - System.out.println("body内容:\n" + docFromStr.body()); - } - - /** - * 使用DOM方法来遍历一个文档 - */ - @Test - public void test01() { - // 遍历一个Document对象中所有的链接 - Element content = docFromFile.body(); - Elements links = content.getElementsByTag("a"); - for (Element link : links) { - System.out.println("linkHref: " + link.attr("href")); - System.out.println("linkText: " + link.text()); - } - } - - /** - * 使用选择器语法来查找元素 - */ - @Test - public void testSelect() { - // 带有href属性的a元素 - Elements hrefs = docFromUrl.select("a[href]"); - System.out.println("[hrefs]\n" + hrefs.toString()); - // 扩展名为.png的图片 - Elements pngs = docFromUrl.select("img[src$=.png]"); - System.out.println("[pngs]\n" + pngs.toString()); - // class等于masthead的div标签 - Element head_wrappers = docFromUrl.select("div.head_wrapper").first(); - System.out.println("[head_wrapper:]\n" + head_wrappers.toString()); - // 在h3元素之后的a元素 - Elements resultLinks = docFromUrl.select("div.head_wrapper > a"); - System.out.println("[resultLinks]\n" + resultLinks.toString()); - } - - @Test - public void test() { - // 从元素集合抽取属性、文本和html内容 - Element link = docFromUrl.select("a").first();// 查找第一个a元素 - System.out.println("outerHtml: " + link.outerHtml()); - System.out.println("html: " + link.html()); // 取得链接内的html内容 - System.out.println("href: " + link.attr("href")); // 取得字符串中的文本 - System.out.println("text: " + link.text()); // 取得链接地址中的文本 - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/ImageUtilTest.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/ImageUtilTest.java deleted file mode 100644 index b2e99641..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/ImageUtilTest.java +++ /dev/null @@ -1,67 +0,0 @@ -/** - * The Apache License 2.0 Copyright (c) 2016 Victor Zhang - */ -package io.github.dunwu.javaee.oss.image; - -import io.github.dunwu.javaee.oss.image.dto.ImageParamDTO; -import java.awt.image.BufferedImage; -import net.sf.jmimemagic.MagicException; -import net.sf.jmimemagic.MagicMatchNotFoundException; -import net.sf.jmimemagic.MagicParseException; -import org.junit.Assert; -import org.junit.Test; - -/** - * @author Victor Zhang - * @since 2017/1/16. - */ -public class ImageUtilTest { - - @Test - public void testToFile() throws IOException { - final String oldFile = System.getProperty("user.dir") + "/src/test/resources/images/lion2.jpg"; - final String newFile = System.getProperty("user.dir") + "/src/test/resources/images/lion2_watermark"; - final String warterFile = System.getProperty("user.dir") + "/src/test/resources/images/wartermark.png"; - - ImageParamDTO params = new ImageParamDTO(); - params.setFormat("png"); - params.setWaterMark(new ImageParamDTO.WaterMark(9, warterFile, 0.6f)); - ImageUtil.toFile(oldFile, newFile, params); - } - - @Test - public void testToBufferedImage() throws IOException { - final String oldFile = System.getProperty("user.dir") + "/src/test/resources/images/lion2.jpg"; - - ImageParamDTO params = new ImageParamDTO(); - params.setWidth(64); - params.setHeight(64); - - BufferedImage bufferedImage = ImageUtil.toBufferedImage(oldFile, params); - Assert.assertNotNull(bufferedImage); - } - - @Test - public void testGetContentType() - throws MagicParseException, MagicException, MagicMatchNotFoundException, IOException { - final String oldFile = System.getProperty("user.dir") + "/src/test/resources/images/lion2.jpg"; - byte[] bytes = toBytes(new File(oldFile)); - String type = ImageUtil.getContentType(bytes); - Assert.assertEquals("image/jpeg", type); - } - - private static byte[] toBytes(File file) throws IOException { - InputStream input = new FileInputStream(file); - ByteArrayOutputStream output = new ByteArrayOutputStream(); - byte[] buffer = new byte[1024]; - int length = 0; - while ((length = input.read(buffer)) != -1) { - output.write(buffer, 0, length); - } - byte[] data = output.toByteArray(); - output.close(); - input.close(); - return data; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/QRCodeUtilTest.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/QRCodeUtilTest.java deleted file mode 100644 index 7de69c35..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/QRCodeUtilTest.java +++ /dev/null @@ -1,99 +0,0 @@ -/** - * The Apache License 2.0 Copyright (c) 2016 Victor Zhang - */ -package io.github.dunwu.javaee.oss.image; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.zxing.BarcodeFormat; -import com.google.zxing.DecodeHintType; -import com.google.zxing.EncodeHintType; -import com.google.zxing.WriterException; -import io.github.dunwu.javaee.oss.image.dto.BarcodeParamDTO; -import java.io.File; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import org.junit.Assert; -import org.junit.Before; -import org.junit.FixMethodOrder; -import org.junit.Test; -import org.junit.runners.MethodSorters; - -/** - * 测试qrcode工具类 - * - * @author Victor Zhang - * @since 2017/1/16. - */ -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -public class QRCodeUtilTest { - - private static final String qrcodeFile = "d:\\qrcode.png"; - - private String jsonContent = null; - - private BarcodeParamDTO paramDTO = null; - - @Before - public void before() throws JsonProcessingException { - jsonContent = initTestJson(); - paramDTO = initBarcodeParam(); - } - - private String initTestJson() throws JsonProcessingException { - Map userData = new HashMap(); - Map fullname = new HashMap(); - fullname.put("first", "Peng"); - fullname.put("last", "Zhang"); - userData.put("name", fullname); - userData.put("gender", "MALE"); - userData.put("email", "aaa@163.com"); - - ObjectMapper mapper = new ObjectMapper(); // can reuse, share globally - return mapper.writeValueAsString(userData); - } - - private BarcodeParamDTO initBarcodeParam() { - BarcodeParamDTO paramDTO = new BarcodeParamDTO(); - paramDTO.setWidth(200); - paramDTO.setHeight(200); - paramDTO.setFilepath(qrcodeFile); - paramDTO.setImageFormat("png"); - paramDTO.setBarcodeFormat(BarcodeFormat.QR_CODE); - - // 编码参数 - Map encodeHints = new HashMap(); - encodeHints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); - paramDTO.setEncodeHints(encodeHints); - - // 解码参数 - HashMap decodeHints = new HashMap(); - decodeHints.put(DecodeHintType.CHARACTER_SET, "UTF-8"); - paramDTO.setDecodeHints(decodeHints); - - return paramDTO; - } - - /** - * 测试创建qrcode图片 - */ - @Test - public void test01() throws IOException, WriterException { - QRCodeUtil.encode(jsonContent, paramDTO); - File f = new File(qrcodeFile); - Assert.assertTrue(f.exists()); - } - - /** - * 测试解析qrcode图片 - */ - @Test - public void test02() { - String expect = - "{\"gender\":\"MALE\",\"name\":{\"last\":\"Zhang\",\"first\":\"Peng\"},\"email\":\"aaa@163.com\"}"; - String content = QRCodeUtil.decode(paramDTO); - Assert.assertEquals(expect, content); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/ThumbnailatorTest.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/ThumbnailatorTest.java deleted file mode 100644 index 74442d51..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/image/ThumbnailatorTest.java +++ /dev/null @@ -1,143 +0,0 @@ -/** - * The Apache License 2.0 Copyright (c) 2016 Victor Zhang - */ -package io.github.dunwu.javaee.oss.image; - -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.net.URL; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import javax.imageio.ImageIO; -import javax.imageio.stream.FileImageOutputStream; -import net.coobird.thumbnailator.Thumbnails; -import net.coobird.thumbnailator.geometry.Positions; -import net.coobird.thumbnailator.name.Rename; -import org.junit.Assert; -import org.junit.Test; - -/** - * @author Victor Zhang - * @since 2017/1/17. - */ -public class ThumbnailatorTest { - - /** - * 测试输入单个对象 执行后会在D:\下生成几张不同尺寸的图片 - */ - @Test - public void testInput01() throws IOException { - final String oldFile = System.getProperty("user.dir") + "/src/test/resources/images/lion.jpg"; - - // 输入形式:文件名 - Thumbnails.of(oldFile).size(16, 16).toFile("d:\\test_input_16_16.png"); - Assert.assertTrue(new File("d:\\test_input_16_16.png").exists()); - - // 输入形式:File 对象 - Thumbnails.of(new File(oldFile)).size(32, 32).toFile(new File("d:\\test_input_32_32.png")); - Assert.assertTrue(new File("d:\\test_input_32_32.png").exists()); - - // 输入形式:URL 对象 - URL url = new URL( - "https://raw.githubusercontent.com/dunwu/JavaParty/master/toolbox/image/src/test/resources/images/lion.jpg"); - Thumbnails.of(url).size(64, 64).toFile(new File("d:\\test_input_64_64.png")); - Assert.assertTrue(new File("d:\\test_input_64_64.png").exists()); - - // 输入形式:BufferedImage 对象 - BufferedImage originalImage = ImageIO.read(new File(oldFile)); - Thumbnails.of(originalImage).size(80, 80).toFile(new File("d:\\test_input_80_80.png")); - Assert.assertTrue(new File("d:\\test_input_80_80.png").exists()); - - // 输入形式:InputStream 对象 - InputStream fis = new FileInputStream(oldFile); - Thumbnails.of(fis).size(96, 96).toFile(new File("d:\\test_input_96_96.png")); - Assert.assertTrue(new File("d:\\test_input_96_96.png").exists()); - } - - /** - * 测试输入多个对象 执行后会在D:\下生成几个含有图片的文件夹 注:如果输入是多个对象,则输出也必须选用输出多个对象的方式 - * - * @throws IOException - */ - @Test - public void testInput02() throws IOException { - final String oldFile = System.getProperty("user.dir") + "/src/test/resources/images/lion.jpg"; - final String oldFile2 = System.getProperty("user.dir") + "/src/test/resources/images/lion2.jpg"; - - createFolderIfNotExist("D:\\fromFilenames"); - Set filenames = new HashSet(); - filenames.add(oldFile); - filenames.add(oldFile2); - Thumbnails.fromFilenames(filenames).size(50, 50).toFiles(new File("D:\\fromFilenames"), - Rename.PREFIX_DOT_THUMBNAIL); - - createFolderIfNotExist("D:\\fromFiles"); - List files = new ArrayList(); - files.add(new File(oldFile)); - files.add(new File(oldFile2)); - Thumbnails.fromFiles(files).size(50, 50).toFiles(new File("D:\\fromFiles"), Rename.PREFIX_HYPHEN_THUMBNAIL); - } - - private void createFolderIfNotExist(String folderPath) throws IOException { - File f = new File(folderPath); - if (!(f.exists() && f.isDirectory())) { - f.mkdirs(); - } - } - - @Test - public void testOutput() throws IOException { - final String oldFile = System.getProperty("user.dir") + "/src/test/resources/images/lion2.jpg"; - File file = new File(oldFile); - BufferedImage bufferedImage = ImageIO.read(file); - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - // 输入形式:文件名 - Thumbnails.of(bufferedImage).scale(1.0).outputFormat("png").toOutputStream(outputStream); - byte[] bytes = outputStream.toByteArray(); - FileImageOutputStream imageOutput = new FileImageOutputStream(new File("d:\\lion_output.png")); - imageOutput.write(bytes, 0, bytes.length); - imageOutput.close(); - } - - @Test - public void testResize() throws IOException { - final String oldFile = System.getProperty("user.dir") + "/src/test/resources/images/lion2.jpg"; - Thumbnails.of(oldFile).size(16, 16).toFile("d:\\lion_16_16.png"); - - Thumbnails.of(oldFile).scale(2.0).toFile("d:\\lion_scale_2.0.png"); - - Thumbnails.of(oldFile).scale(1.0, 0.5).toFile("d:\\lion_scale_1.0_0.5.png"); - } - - @Test - public void testRotate() throws IOException { - final String oldFile = System.getProperty("user.dir") + "/src/test/resources/images/lion2.jpg"; - Thumbnails.of(oldFile).scale(0.8).rotate(90).toFile("d:\\lion2_rotate_90.png"); - - Thumbnails.of(oldFile).scale(0.8).rotate(180).toFile("d:\\lion2_rotate_180.png"); - } - - @Test - public void testWatermark() throws IOException { - final String oldFile = System.getProperty("user.dir") + "/src/test/resources/images/lion2.jpg"; - final String wartermarkFile = System.getProperty("user.dir") + "/src/test/resources/images/wartermark.png"; - BufferedImage watermarkImage = ImageIO.read(new File(wartermarkFile)); - Thumbnails.of(oldFile).scale(0.8).watermark(Positions.BOTTOM_LEFT, watermarkImage, 0.5f) - .toFile("d:\\lion2_watermark.png"); - } - - @Test - public void testBatchChange() throws IOException { - final String oldFile = System.getProperty("user.dir") + "/src/test/resources/images/lion.jpg"; - final String oldFile2 = System.getProperty("user.dir") + "/src/test/resources/images/lion2.jpg"; - final String wartermarkFile = System.getProperty("user.dir") + "/src/test/resources/images/wartermark.png"; - BufferedImage watermarkImage = ImageIO.read(new File(wartermarkFile)); - createFolderIfNotExist("D:\\watermark"); - - Thumbnails.of(oldFile, oldFile2).scale(0.8).watermark(Positions.BOTTOM_LEFT, watermarkImage, 0.5f) - .toFiles(new File("D:\\watermark"), Rename.PREFIX_DOT_THUMBNAIL); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/template/VelocityUtilTest.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/template/VelocityUtilTest.java deleted file mode 100644 index 8bae0bc6..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/template/VelocityUtilTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * The Apache License 2.0 Copyright (c) 2016 Victor Zhang - */ -package io.github.dunwu.javaee.oss.template; - -import org.apache.velocity.VelocityContext; -import org.junit.Test; - -/** - * @author Victor Zhang - * @since 2016/12/23. - */ -public class VelocityUtilTest { - - @Test - public void test() { - VelocityContext context = new VelocityContext(); - context.put("name", "Victor Zhang"); - context.put("project", "Velocity"); - System.out.println(VelocityUtil.getMergeOutput(context, "template/hello.vm")); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/test/JUnitExecTest.java b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/test/JUnitExecTest.java deleted file mode 100644 index dac6690b..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/java/io/github/dunwu/javaee/oss/test/JUnitExecTest.java +++ /dev/null @@ -1,89 +0,0 @@ -package io.github.dunwu.javaee.oss.test; /** - * The Apache License 2.0 Copyright (c) 2016 Victor Zhang - */ - -import static org.junit.Assert.fail; - -/** - * JUnit4开始支持注解,本例展示一个单元测试执行过程中,各个注解的调用顺序 - * - * @author Victor Zhang - * @since 2016/11/18. - */ -public class JUnitExecTest { - - private static JUnitExecTest instance = new JUnitExecTest(); - - @BeforeClass - public static void beforeClass1() { - System.out.println("@beforeClass1"); - } - - @BeforeClass - public static void beforeClass2() { - System.out.println("@beforeClass2"); - } - - @AfterClass - public static void afterClass1() { - System.out.println("@afterClass1"); - } - - @AfterClass - public static void afterClass2() { - System.out.println("@afterClass2"); - } - - @Before - public void before1() throws Exception { - System.out.println("@before1"); - } - - @Before - public void before2() throws Exception { - System.out.println("@before2"); - } - - @Test - public void testAdd() { - System.out.println(1); - } - - @Test - public void testSubstract() { - System.out.println(2); - } - - @Ignore("Multiply() Not yet implemented") - @Test - public void testMultiply() { - System.out.println(3); - fail("Not yet implemented"); - } - - @Test - public void testDivide() { - System.out.println(4); - } - - @Test(timeout = 1000) - public void testSquareRoot() { - System.out.println(5); - } - - @Test - public void divideByZero() { - System.out.println(6); - } - - @After - public void after1() { - System.out.println("@after1"); - } - - @After - public void after2() { - System.out.println("@after2"); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/html/jsoup-cookbook.html b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/html/jsoup-cookbook.html deleted file mode 100644 index e1beb7a3..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/html/jsoup-cookbook.html +++ /dev/null @@ -1,107 +0,0 @@ - - - - - - Cookbook: jsoup Java HTML parser - - - - - - - - - - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/images/lion.jpg b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/images/lion.jpg deleted file mode 100644 index d10bd212f4d4b70edfc239b0a2e78a7815914798..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16791 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 diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/images/lion2.jpg b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/images/lion2.jpg deleted file mode 100644 index 6794cb3483aa2cc8fc06aca4459dfa49aa516569..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31726 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 diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/images/lion2_watermark.png b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/images/lion2_watermark.png deleted file mode 100644 index 2de3bb8a3b9f5cab8fe7e52027ca2ca6e577c4c5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 139930 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

    |5vb=AZ+pN(v< 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 VX1BS3zV`h8t%E+*)-1>;{tuX(i9P@T diff --git a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/logback.xml deleted file mode 100644 index 1598ec4f..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-oss/src/test/resources/logback.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n - - - - - - - - - ${user.dir}/logs/${DIR_NAME}/all.%d{yyyy-MM-dd}.log - 30 - - - - - 30MB - - - - %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n - - - - - - ${user.dir}/logs/${DIR_NAME}/spring.%d{yyyy-MM-dd}.log - - 30 - - - - - 10MB - - - - %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n - - - - - - - - - - - - - - - - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/pom.xml b/codes/javaee-tutorial/javaee-tutorial-servlet/pom.xml deleted file mode 100644 index 9a107e69..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/pom.xml +++ /dev/null @@ -1,89 +0,0 @@ - - - 4.0.0 - - io.github.dunwu - javaee-tutorial-servlet - 1.0.0 - war - javaee-tutorial-servlet - JavaEE 学习笔记之 servlet - - - UTF-8 - 1.7 - ${java.version} - ${java.version} - 0.4.0 - - - - - ch.qos.logback - logback-classic - - - - - javax.servlet - javax.servlet-api - - - org.eclipse.jetty - jetty-webapp - test - - - org.eclipse.jetty - jetty-annotations - test - - - org.eclipse.jetty - apache-jsp - test - - - org.eclipse.jetty - apache-jstl - test - - - - - junit - junit - test - - - org.assertj - assertj-core - test - - - - - org.apache.commons - commons-lang3 - - - commons-fileupload - commons-fileupload - - - - - - - io.github.dunwu - dunwu-dependencies - 0.4.2-SNAPSHOT - pom - import - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/AnnotationServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/AnnotationServlet.java deleted file mode 100644 index 9156ddb6..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/AnnotationServlet.java +++ /dev/null @@ -1,85 +0,0 @@ -package io.github.dunwu.javaee.servlet; - -import java.io.IOException; -import java.io.PrintWriter; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -public class AnnotationServlet extends HttpServlet { - - /** - * - */ - private static final long serialVersionUID = 7516608289980410869L; - - /** - * Constructor of the object. - */ - public AnnotationServlet() { - this.log("AnnotationServlet()"); - } - - public void log(String str) { - System.out.println(str); - } - - /** - * Destruction of the servlet.
    - */ - public void destroy() { - this.log("destroy()"); - } - - /** - * The doGet method of the servlet.
    - *

    - * This method is called when a form has its tag value method equals to get. - * - * @param request the request send by the client to the server - * @param response the response send by the server to the client - * @throws ServletException if an error occurred - * @throws IOException if an error occurred - */ - public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - - this.log("doGet()"); - - response.setContentType("text/html"); - PrintWriter out = response.getWriter(); - out.println(""); - out.println(""); - out.println(" A Servlet"); - out.println(" "); - out.print(" This is "); - out.print(this.getClass()); - out.println(", using the GET method"); - out.println(" "); - out.println(""); - out.flush(); - out.close(); - } - - /** - * Initialization of the servlet.
    - * - * @throws ServletException if an error occure - */ - public void init() throws ServletException { - this.log("init()"); - } - - @PostConstruct - public void postConstruct() { - this.log("postConstruct()"); - } - - public @PreDestroy - void preDestroy() { - this.log("preDestroy()"); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ContextParamServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ContextParamServlet.java deleted file mode 100644 index 0c4c2732..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ContextParamServlet.java +++ /dev/null @@ -1,64 +0,0 @@ -package io.github.dunwu.javaee.servlet; - -import java.io.IOException; -import java.io.PrintWriter; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * 读取web.xml中的 配置在中,只能让对应的servlet使用; 配置在全局中,可以让所有的servlet使用。 - */ -public class ContextParamServlet extends HttpServlet { - - private static final long serialVersionUID = 3194071196406358461L; - - private final Logger logger = LoggerFactory.getLogger(this.getClass()); - - public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - - logger.info("ContextParamServlet 读取 web.xml 中的"); - - response.setCharacterEncoding("UTF-8"); - response.setContentType("text/html"); - PrintWriter out = response.getWriter(); - out.println(""); - out.println(""); - out.println(" 读取文档参数"); - out.println(" "); - out.println(" "); - out.println("


    "); - out.println("
    所有的文档参数
    "); - - ServletContext servletContext = this.getServletConfig().getServletContext(); - String uploadFolder = servletContext.getInitParameter("upload folder"); - String allowedFileType = servletContext.getInitParameter("allowed file type"); - - out.println("
    "); - out.println("
    上传文件夹
    "); - out.println("
    " + uploadFolder + "
    "); - out.println("
    "); - - out.println("
    "); - out.println("
    实际磁盘路径
    "); - out.println("
    " + servletContext.getRealPath(uploadFolder) + "
    "); - out.println("
    "); - - out.println("
    "); - out.println("
    允许上传的类型
    "); - out.println("
    " + allowedFileType + "
    "); - out.println("
    "); - - out.println("
    "); - - out.println(" "); - out.println(""); - out.flush(); - out.close(); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/FirstServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/FirstServlet.java deleted file mode 100644 index 36b6dde7..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/FirstServlet.java +++ /dev/null @@ -1,92 +0,0 @@ -package io.github.dunwu.javaee.servlet; - -import java.io.IOException; -import java.io.PrintWriter; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * 第一个Servlet - */ -public class FirstServlet extends HttpServlet { - - private static final long serialVersionUID = 2386052823761867369L; - - /** - * 以 GET 方式访问页面时执行该函数。 执行 doGet 前会先执行 getLastModified,如果浏览器发现 getLastModified 返回的数值 - * 与上次访问时返回的数值相同,则认为该文档没有更新,浏览器采用缓存而不执行 doGet。 如果 getLastModified 返回 -1,则认为是时刻更新的,总是执行该函数。 - */ - @Override - public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - - // 调用 HttpServlet 自带的日志函数输出信息到控制台 - this.log("执行 doGet 方法... "); - - // 处理 doGet - this.execute(request, response); - } - - private void execute(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - - response.setCharacterEncoding("UTF-8"); - request.setCharacterEncoding("UTF-8"); - - // 访问该 Servlet 的 URI - String requestURI = request.getRequestURI(); - // 访问该 Servlet 的方式,是 GET 还是 POST - String method = request.getMethod(); - // 客户端提交的参数 param 值 - String param = request.getParameter("param"); - - response.setContentType("text/html"); - PrintWriter out = response.getWriter(); - out.println(""); - out.println(""); - out.println(" A Servlet"); - out.println(" "); - out.println(" 以 " + method + " 方式访问该页面。取到的 param 参数为:" + param + "
    "); - - out.println("
    "); - out.println("
    "); - - // 由客户端浏览器读取该文档的更新时间 - out.println(" "); - out.println(" "); - - out.println(" "); - out.println(""); - out.flush(); - out.close(); - } - - /** - * 以 POST 方式访问页面时执行该函数。 - */ - @Override - public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - - // 调用 HttpServlet 自带的日志函数输出信息到控制台 - this.log("执行 doPost 方法... "); - - // 处理 doPost - this.execute(request, response); - } - - /** - * 返回该 Servlet 生成的文档的更新时间。对 Get 方式访问有效。 返回的时间为相对于 1970年1月1日08:00:00 的毫秒数。 如果为 -1 则认为是实时更新。默认为 -1。 - */ - @Override - public long getLastModified(HttpServletRequest request) { - - // 调用 HttpServlet 自带的日志函数输出信息到控制台 - this.log("执行 getLastModified 方法... "); - - return 0; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ForwardServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ForwardServlet.java deleted file mode 100644 index bb47a2ac..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ForwardServlet.java +++ /dev/null @@ -1,47 +0,0 @@ -package io.github.dunwu.javaee.servlet; - -import java.io.IOException; -import java.util.Date; -import javax.servlet.RequestDispatcher; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -public class ForwardServlet extends HttpServlet { - - private static final long serialVersionUID = -291840563095094360L; - - public void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - this.doGet(request, response); - } - - public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - - String destination = request.getParameter("destination"); - - // 跳转到 /WEB-INF/web.xml。通过地址栏输入网址是不能访问到该文件的,但是 forward 可以 - if ("file".equals(destination)) { - RequestDispatcher dispatcher = request.getRequestDispatcher("/WEB-INF/web.xml"); - dispatcher.forward(request, response); - } - // 跳转到 /forward.jsp - else if ("jsp".equals(destination)) { - // 通过 setAttribute 方法传递一个 Date 对象给 JSP 页面 - Date date = new Date(); - request.setAttribute("date", date); - RequestDispatcher dispatcher = request.getRequestDispatcher("/forward.jsp"); - dispatcher.forward(request, response); - } - // 跳转到另一个 Servlet - else if ("servlet".equals(destination)) { - RequestDispatcher dispatcher = request.getRequestDispatcher("/servlet/LifeCycleServlet"); - dispatcher.forward(request, response); - } else { - response.setCharacterEncoding("UTF-8"); - response.setContentType("text/html; charset=UTF-8"); - response.getWriter().println("缺少参数。用法:" + request.getRequestURL() + "?destination=jsp 或者 file 或者 servlet "); - } - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/HelloServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/HelloServlet.java deleted file mode 100644 index 97d42916..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/HelloServlet.java +++ /dev/null @@ -1,108 +0,0 @@ -/** - * The Apache License 2.0 Copyright (c) 2016 Zhang Peng - */ -package io.github.dunwu.javaee.servlet; - -import java.io.IOException; -import java.io.PrintWriter; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * @author Zhang Peng - * @since 2016/12/23. - */ -public class HelloServlet extends HttpServlet { - - private static final long serialVersionUID = 1L; - - /** - * Constructor of the object. - */ - public HelloServlet() { - super(); - } - - /** - * Destruction of the servlet.
    - */ - public void destroy() { - super.destroy(); // Just puts "destroy" string in log - // Put your code here - } - - /** - * The doPost method of the servlet.
    - *

    - * This method is called when a form has its tag value method equals to post. - * - * @param request the request send by the client to the server - * @param response the response send by the server to the client - * @throws ServletException if an error occurred - * @throws IOException if an error occurred - */ - public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - - this.doGet(request, response); - } - - /** - * The doGet method of the servlet.
    - *

    - * This method is called when a form has its tag value method equals to get. - * - * @param request the request send by the client to the server - * @param response the response send by the server to the client - * @throws ServletException if an error occurred - * @throws IOException if an error occurred - */ - public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - - // 设置 request,response 编码方式 - response.setCharacterEncoding("UTF-8"); - request.setCharacterEncoding("UTF-8"); - - // 设置文档类型 - response.setContentType("text/html"); - PrintWriter out = response.getWriter(); - - // 输出到客户端浏览器 - out.println(""); - out.println(""); - out.println(""); - out.println("A Servlet"); - out.println(" "); - - String requestURI = request.getRequestURI(); - out.println("

    "); - out.println("请输入您的名字:"); - out.println(""); - out.println("
    "); - out.println(""); - - // 取浏览器提交的 name 参数 - String name = request.getParameter("name"); - - // 如果 name 不为空且长度大于 0 - if (name != null && name.trim().length() > 0) { - out.println("您好, " + name + ". 欢迎来到 Java Web 世界. "); - } - - out.println(" "); - out.println(""); - out.flush(); - out.close(); - } - - /** - * Initialization of the servlet.
    - * - * @throws ServletException if an error occure - */ - public void init() throws ServletException { - // Put your code here - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ImageServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ImageServlet.java deleted file mode 100644 index 32c8e135..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ImageServlet.java +++ /dev/null @@ -1,91 +0,0 @@ -package io.github.dunwu.javaee.servlet; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -public class ImageServlet extends HttpServlet { - - private static final long serialVersionUID = -5446593186536558309L; - - public ImageServlet() { - System.out.println("正在加载 " + this.getClass().getName() + " ... "); - } - - public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - - this.doGet(request, response); - } - - public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - - String referer = request.getHeader("referer"); - - // 如果直接输入的网址,或者是从别的网站打开的,显示错误信息。 - if (referer == null || !referer.toLowerCase().startsWith("http://" + request.getServerName().toLowerCase())) { - // 打开图片 error.gif - request.getRequestDispatcher("/error.gif").forward(request, response); - return; - } - - String requestURI = request.getRequestURI(); - String fileName = requestURI.substring(requestURI.lastIndexOf("/") + 1); - - // 请求的文件位置 - File file = new File(this.getServletContext().getRealPath("upload"), fileName); - this.log("请求文件 " + file.getAbsolutePath()); - - // 如果文件不存在,显示错误信息 - if (!file.exists()) { - response.getWriter().println("File " + requestURI + " doesn't exist. "); - return; - } - - // 设置打开方式为 inline,浏览器中打开 - response.setHeader("Content-Disposition", "inline;filename=" + file.getName()); - response.setHeader("Connection", "close"); - - if (fileName.toLowerCase().endsWith(".jpg")) - // .jpg 图片格式 - { - response.setHeader("Content-Type", "image/jpeg"); - } else if (fileName.toLowerCase().endsWith(".gif")) - // .gif 图片格式 - { - response.setHeader("Content-Type", "image/gif"); - } else if (fileName.toLowerCase().endsWith(".doc")) - // word 格式 - { - response.setHeader("Content-Type", "application/msword"); - } else - // 其他格式 - { - response.setHeader("Content-Type", "application/octet-stream"); - } - - // 通过 ins 读取文件 - InputStream ins = new FileInputStream(file); - // 通过 ous 发送给客户端 - OutputStream ous = response.getOutputStream(); - - try { - // 缓存 - byte[] buffer = new byte[1024]; - int len = 0; - - // 读取文件内容并将它发送给客户端浏览器 - while ((len = ins.read(buffer)) > -1) { - ous.write(buffer, 0, len); - } - } finally { - if (ous != null) { - ous.close(); - } - if (ins != null) { - ins.close(); - } - } - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/InitParamServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/InitParamServlet.java deleted file mode 100644 index e05c2a44..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/InitParamServlet.java +++ /dev/null @@ -1,81 +0,0 @@ -package io.github.dunwu.javaee.servlet; - -import java.io.IOException; -import java.io.PrintWriter; -import java.util.Enumeration; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * 初始化参数示例 读取web.xml中的 配置在中,只能让对应的servlet使用; 配置在全局中,可以让所有的servlet使用。 - */ -public class InitParamServlet extends HttpServlet { - - private static final long serialVersionUID = 7298032096933866458L; - - private final Logger logger = LoggerFactory.getLogger(this.getClass()); - - public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - // 提交的 username 参数 - String username = request.getParameter("username"); - // 提交的 password 参数 - String password = request.getParameter("password"); - // 取所有的初始化参数名称 - Enumeration params = this.getInitParameterNames(); - while (params.hasMoreElements()) { - String usernameParam = (String) params.nextElement(); - // 取参数值 - String passnameParam = this.getInitParameter(usernameParam); - // 如果 username 匹配且 password 匹配. username 大小写不敏感,password大小写敏感 - if (usernameParam.equalsIgnoreCase(username) && passnameParam.equals(password)) { - // 显示文件。/WEB-INF 下的文件不能通过浏览器访问到,因此是安全的 - request.getRequestDispatcher("/WEB-INF/notice.html").forward(request, response); - return; - } - } - // username,password 不匹配,显示登录页面 - logger.warn("用户名={}、密码={}不匹配。", username, password); - this.doGet(request, response); - } - - public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - - logger.info("InitParamServlet 初始化用户名、密码参数"); - request.setCharacterEncoding("UTF-8"); - response.setCharacterEncoding("UTF-8"); - response.setContentType("text/html"); - - PrintWriter out = response.getWriter(); - out.println(""); - out.println(""); - out.println(" 请登录查看 Notice 文件"); - out.println(""); - out.println(" "); - out.println("
    "); - out.println("帐号:
    "); - out.println("密码:

    "); - out.println(""); - out.println("
    "); - - if (true) { - out.println("






    用户名、密码为:
    "); - Enumeration params = this.getInitParameterNames(); - while (params.hasMoreElements()) { - String username = (String) params.nextElement(); - String password = this.getInitParameter(username); - out.println("[" + username + ", " + password + "], "); - logger.info("用户名={}、密码={}", username, password); - } - } - - out.println(" "); - out.println(""); - out.flush(); - out.close(); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/InjectionServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/InjectionServlet.java deleted file mode 100644 index 66eca83b..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/InjectionServlet.java +++ /dev/null @@ -1,54 +0,0 @@ -package io.github.dunwu.javaee.servlet; - -import java.io.IOException; -import java.io.PrintWriter; -import javax.annotation.Resource; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -public class InjectionServlet extends HttpServlet { - - private static final long serialVersionUID = -8526907492073769090L; - - // 注入的 字符串 - private @Resource(name = "hello") - String hello; - - // 注入的 整数 - private @Resource(name = "i") - int i; - - // 注入更常见的写法 - @Resource(name = "persons") - private String persons; - - public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - - response.setCharacterEncoding("UTF-8"); - request.setCharacterEncoding("UTF-8"); - - response.setContentType("text/html"); - PrintWriter out = response.getWriter(); - out.println(""); - out.println(""); - out.println(" 资源注入"); - out.println(""); - - out.println("注入的字符串
      - " + hello + "
    "); - out.println("注入的整数
      - " + i + "
    "); - out.println("注入的字符串数组
    "); - - for (String person : persons.split(",")) { - out.println("  - " + person + "
    "); - } - - out.println(" "); - out.println(" "); - out.println(""); - out.flush(); - out.close(); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/LifeCycleServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/LifeCycleServlet.java deleted file mode 100644 index f1c82384..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/LifeCycleServlet.java +++ /dev/null @@ -1,138 +0,0 @@ -package io.github.dunwu.javaee.servlet; - -import java.io.IOException; -import java.io.PrintWriter; -import javax.servlet.ServletConfig; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -public class LifeCycleServlet extends HttpServlet { - - private static final long serialVersionUID = -7197419401412129310L; - - private static double startPoint = 0; - - @Override - public void init() throws ServletException { - this.log("执行 init() 方法 ... "); - ServletConfig conf = this.getServletConfig(); - startPoint = Double.parseDouble(conf.getInitParameter("startPoint")); - } - - @Override - protected void service(HttpServletRequest arg0, HttpServletResponse arg1) throws ServletException, IOException { - this.log("执行 service() 方法 ... "); - super.service(arg0, arg1); - } - - @Override - public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - this.log("执行 doPost() 方法 ... "); - response.setCharacterEncoding("UTF-8"); - response.setContentType("text/html"); - PrintWriter out = response.getWriter(); - out.println(""); - out.println("个人所得税计算"); - out.println(""); - out.println(""); - - out.println("

    个税计算器
    "); - - try { - // 从参数中获取的工资数目 - double income = new Double(request.getParameter("income")); - // 应纳税部分 - double charge = income - startPoint; - // 缴税 - double tax = 0; - - if (charge <= 0) { - tax = 0; - } - if (charge > 0 && charge <= 500) { - tax = charge * 0.05; - } - if (charge > 500 && charge <= 2000) { - tax = charge * 0.1 - 25; - } - if (charge > 2000 && charge <= 5000) { - tax = charge * 0.15 - 125; - } - if (charge > 5000 && charge <= 20000) { - tax = charge * 0.2 - 375; - } - if (charge > 20000 && charge <= 40000) { - tax = charge * 0.25 - 1375; - } - if (charge > 40000 && charge <= 60000) { - tax = charge * 0.30 - 3375; - } - if (charge > 60000 && charge <= 80000) { - tax = charge * 0.35 - 6375; - } - if (charge > 80000 && charge <= 100000) { - tax = charge * 0.4 - 10375; - } - if (charge > 100000) { - tax = charge * 0.45 - 15375; - } - - out.println("
    "); - out.println("
    您的工资为
    " + income + " 元
    "); - out.println("
    "); - - out.println("
    "); - out.println("
    您应纳税
    " + tax + " 元
    "); - out.println("

    "); - - out.println(""); - } catch (Exception e) { - out.println("请输入数值类型数据。"); - } - out.println(""); - out.println(""); - out.flush(); - out.close(); - } - - @Override - public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - this.log("执行 doGet() 方法 ... "); - response.setCharacterEncoding("UTF-8"); - response.setContentType("text/html"); - PrintWriter out = response.getWriter(); - out.println(""); - out.println(""); - out.println("个人所得税计算"); - - out.println("

    个税计算器
    "); - out.println("
    "); - - out.println("
    "); - out.println( - "
    您的工资为
    单位:元
    "); - out.println("

    "); - - out.println("
    "); - out.println( - "
    "); - out.println("
    "); - - out.println("
    "); - - out.println(""); - out.println(""); - out.println(""); - out.flush(); - out.close(); - } - - @Override - public void destroy() { - this.log("执行 destroy() 方法 ... "); - startPoint = 0; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/PostServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/PostServlet.java deleted file mode 100644 index 33bb6a0c..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/PostServlet.java +++ /dev/null @@ -1,153 +0,0 @@ -package io.github.dunwu.javaee.servlet; - -import java.io.IOException; -import java.io.PrintWriter; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Date; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -public class PostServlet extends HttpServlet { - - private static final long serialVersionUID = 2112378505872783022L; - - public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - response.setCharacterEncoding("UTF-8"); - response.getWriter().println("请使用 POST 方式提交数据。"); - } - - public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - - response.setCharacterEncoding("UTF-8"); - request.setCharacterEncoding("UTF-8"); - - // 从 文本框 text 中取姓名 - String name = request.getParameter("name"); - // 从 密码域 password 中取密码 - String password = request.getParameter("password"); - // 从 单选框 checkbox 中取性别 - String sex = request.getParameter("sex"); - - int age = 0; - try { - // 取 年龄. 需要把 字符串 转换为 int. - // 如果格式不对会抛出 NumberFormattingException - age = Integer.parseInt(request.getParameter("age")); - } catch (Exception e) { - } - - Date birthday = null; - try { - // 取 生日. 需要把 字符串 转化为 Date. - // 如果格式不对会抛出 ParseException - DateFormat format = new SimpleDateFormat("yyyy-MM-dd"); - birthday = format.parse(request.getParameter("birthday")); - } catch (Exception e) { - } - - // 从 多选框 checkbox 中取多个值 - String[] interesting = request.getParameterValues("interesting"); - // 从 下拉框 select 中取值 - String area = request.getParameter("area"); - // 从 文本域 textarea 中取值 - String description = request.getParameter("description"); - - // 取 提交按钮 的键值 - String btn = request.getParameter("btn"); - - response.setContentType("text/html"); - PrintWriter out = response.getWriter(); - - out.println(""); - out.println(""); - out.println("感谢您提交信息"); - out.println(""); - out.println(""); - - out.println("

    "); - out.println("
    填写用户信息
    "); - - out.println("
    "); - out.println("
    您的姓名:
    "); - out.println("
    " + name + "
    "); - out.println("
    "); - - out.println("
    "); - out.println("
    您的密码:
    "); - out.println("
    " + password + "
    "); - out.println("
    "); - - out.println("
    "); - out.println("
    您的性别:
    "); - out.println("
    " + sex + "
    "); - out.println("
    "); - - out.println("
    "); - out.println("
    您的年龄:
    "); - out.println("
    " + age + "
    "); - out.println("
    "); - - out.println("
    "); - out.println("
    您的生日:
    "); - out.println("
    "); - out.println(new SimpleDateFormat("yyyy年MM月dd日").format(birthday)); - out.println("
    "); - out.println("
    "); - - out.println("
    "); - out.println("
    您的兴趣:
    "); - out.println("
    "); - - if (interesting != null) { - for (String str : interesting) { - out.println(str + ", "); - } - } - - out.println("
    "); - out.println("
    "); - - out.println("
    "); - out.println("
    自我描述:
    "); - out.println("
    " + description + "
    "); - out.println("
    "); - - out.println("
    "); - out.println("
    按钮键值:
    "); - out.println("
    " + btn + "
    "); - out.println("
    "); - - out.println("
    "); - out.println("
    "); - out.println("
    "); - out.println( - "

    "); - out.println("
    "); - out.println("
    "); - - out.println(""); - out.println(""); - out.println(""); - out.flush(); - out.close(); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/RedirectServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/RedirectServlet.java deleted file mode 100644 index f751bb3c..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/RedirectServlet.java +++ /dev/null @@ -1,78 +0,0 @@ -package io.github.dunwu.javaee.servlet; - -import java.io.IOException; -import java.io.PrintWriter; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -public class RedirectServlet extends HttpServlet { - - private static final long serialVersionUID = 4442189888545647793L; - - Map map = new HashMap(); - - @Override - public void init() throws ServletException { - map.put("/download/setup.exe", 0); - map.put("/download/application.zip", 0); - map.put("/download/01.mp3", 0); - } - - public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - - String filename = request.getParameter("filename"); - - if (filename != null) { - // 取下载次数 - int hit = map.get(filename); - // 下载次数 + 1 后保存 - map.put(filename, ++hit); - // 重定向到文件 - response.sendRedirect(request.getContextPath() + filename); - } else { - response.setCharacterEncoding("UTF-8"); - response.setContentType("text/html"); - PrintWriter out = response.getWriter(); - out.println(""); - out.println(""); - out.println(" 文件下载"); - out.println(" "); - out.println("
    "); - - out.println("
    文件下载"); - out.println(""); - out.println(" "); - out.println(" "); - out.println(" "); - out.println(" "); - out.println(" "); - - for (Entry entry : map.entrySet()) { - out.println(""); - out.println(" "); - out.println(" "); - out.println(" "); - out.println(""); - } - - out.println("
    文件名下载次数下载
    " + entry.getKey() + "" + entry.getValue() + "下载
    "); - out.println(" "); - out.println(" "); - out.println(""); - out.flush(); - out.close(); - } - } - - @Override - public void destroy() { - map = null; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/RequestServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/RequestServlet.java deleted file mode 100644 index 177e97af..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/RequestServlet.java +++ /dev/null @@ -1,187 +0,0 @@ -package io.github.dunwu.javaee.servlet; - -import java.io.IOException; -import java.io.PrintWriter; -import java.security.Principal; -import java.util.Locale; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * 获取HttpServletRequest信息示例 - */ -public class RequestServlet extends HttpServlet { - - private static final long serialVersionUID = -7936817351382556277L; - - private final Logger logger = LoggerFactory.getLogger(this.getClass()); - - @Override - public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - logger.info("访问 doGet"); - - request.setCharacterEncoding("UTF-8"); - response.setCharacterEncoding("UTF-8"); - - response.setContentType("text/html"); - - String authType = request.getAuthType(); - String localAddr = request.getLocalAddr(); - Locale locale = request.getLocale(); - String localName = request.getLocalName(); - String contextPath = request.getContextPath(); - int localPort = request.getLocalPort(); - String method = request.getMethod(); - String pathInfo = request.getPathInfo(); - String pathTranslated = request.getPathTranslated(); - String protocol = request.getProtocol(); - String queryString = request.getQueryString(); - String remoteAddr = request.getRemoteAddr(); - int port = request.getRemotePort(); - String remoteUser = request.getRemoteUser(); - String requestedSessionId = request.getRequestedSessionId(); - String requestURI = request.getRequestURI(); - StringBuffer requestURL = request.getRequestURL(); - String scheme = request.getScheme(); - String serverName = request.getServerName(); - int serverPort = request.getServerPort(); - String servletPath = request.getServletPath(); - Principal userPrincipal = request.getUserPrincipal(); - - String accept = request.getHeader("accept"); - String referer = request.getHeader("referer"); - String userAgent = request.getHeader("user-agent"); - - String serverInfo = this.getServletContext().getServerInfo(); - - PrintWriter out = response.getWriter(); - out.println(""); - out.println(""); - - // 这里之间的信息在浏览器中显示为标题 - out.println(" Request Servlet"); - out.println(" "); - out.println(" "); - - out.println("您的IP为 " + remoteAddr + ";您使用 " + getOS(userAgent) + " 操作系统," - + getNavigator(userAgent) + " 。您使用 " + getLocale(locale) + "。
    "); - out.println("服务器IP为 " + localAddr + localAddr + ";服务器使用 " + serverPort + " 端口,您的浏览器使用了 " - + port + " 端口访问本网页。
    "); - out.println("服务器软件为:" + serverInfo + "。服务器名称为 " + localName + "。
    "); - out.println("您的浏览器接受 " + getAccept(accept) + "。
    "); - out.println("您从 " + referer + " 访问到该页面。
    "); - out.println("使用的协议为 " + protocol + "。URL协议头 " + scheme + ",服务器名称 " + serverName - + ",您访问的URI为 " + requestURI + "。
    "); - out.println("该 Servlet 路径为 " + servletPath + ",该 Servlet 类名为 " + this.getClass().getName() - + "。
    "); - out.println("本应用程序在硬盘的根目录为 " + this.getServletContext().getRealPath("") + ",网络相对路径为 " - + contextPath + "。
    "); - - out.println("
    "); - - out.println("

    点击刷新本页面 "); - - out.println(" "); - out.println(""); - out.flush(); - out.close(); - } - - /** - * @param userAgent - * @return 客户端操作系统 - */ - private String getOS(String userAgent) { - if (userAgent.indexOf("Windows NT 5.1") > 0) { - return "Windows XP"; - } - if (userAgent.indexOf("Windows 98") > 0) { - return "Windows 98"; - } - if (userAgent.indexOf("Windows NT 5.0") > 0) { - return "Windows 2000"; - } - if (userAgent.indexOf("Linux") > 0) { - return "Linux"; - } - if (userAgent.indexOf("Unix") > 0) { - return "Unix"; - } - return "未知"; - } - - /** - * @param userAgent - * @return 客户端浏览器信息 - */ - private String getNavigator(String userAgent) { - if (userAgent.indexOf("TencentTraveler") > 0) { - return "腾讯浏览器"; - } - if (userAgent.indexOf("Maxthon") > 0) { - return "Maxthon浏览器"; - } - if (userAgent.indexOf("MyIE2") > 0) { - return "MyIE2浏览器"; - } - if (userAgent.indexOf("Firefox") > 0) { - return "Firefox浏览器"; - } - if (userAgent.indexOf("MSIE") > 0) { - return "IE 浏览器"; - } - return "未知浏览器"; - } - - /** - * @param locale - * @return 语言环境名称 - */ - private String getLocale(Locale locale) { - if (Locale.SIMPLIFIED_CHINESE.equals(locale)) { - return "简体中文"; - } - if (Locale.TRADITIONAL_CHINESE.equals(locale)) { - return "繁体中文"; - } - if (Locale.ENGLISH.equals(locale)) { - return "英文"; - } - if (Locale.JAPANESE.equals(locale)) { - return "日文"; - } - return "未知语言环境"; - } - - /** - * @param accept - * @return 客户端浏览器接受的文件类型 - */ - private String getAccept(String accept) { - StringBuffer buffer = new StringBuffer(); - if (accept.contains("image/gif")) { - buffer.append("GIF 文件, "); - } - if (accept.contains("image/x-xbitmap")) { - buffer.append("BMP 文件, "); - } - if (accept.contains("image/jpeg")) { - buffer.append("JPG 文件, "); - } - if (accept.contains("application/vnd.ms-excel")) { - buffer.append("Excel 文件, "); - } - if (accept.contains("application/vnd.ms-powerpoint")) { - buffer.append("PPT 文件, "); - } - if (accept.contains("application/msword")) { - buffer.append("Word 文件, "); - } - return buffer.toString().replaceAll(", $", ""); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ThreadSafetyServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ThreadSafetyServlet.java deleted file mode 100644 index 3dd596f0..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/ThreadSafetyServlet.java +++ /dev/null @@ -1,34 +0,0 @@ -package io.github.dunwu.javaee.servlet; - -import java.io.IOException; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -public class ThreadSafetyServlet extends HttpServlet { - - private static final long serialVersionUID = 2957055449370562943L; - - private String name; - - public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - - name = request.getParameter("name"); - - try { - Thread.sleep(5000); - } catch (InterruptedException e) { - } - - response.getWriter().println("您好, " + name + ". 您使用了 GET 方式提交数据"); - } - - public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - - name = request.getParameter("name"); - - response.getWriter().println("您好, " + name + ". 您使用了 POST 方式提交数据"); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/UploadServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/UploadServlet.java deleted file mode 100644 index 0fc1b733..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/UploadServlet.java +++ /dev/null @@ -1,171 +0,0 @@ -package io.github.dunwu.javaee.servlet; - -import java.net.URLEncoder; -import java.util.List; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.apache.commons.fileupload.DiskFileUpload; -import org.apache.commons.fileupload.FileItem; -import org.apache.commons.fileupload.FileUploadException; - -public class UploadServlet extends HttpServlet { - - private static final long serialVersionUID = 7523024737218332088L; - - public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - response.setCharacterEncoding("UTF-8"); - response.getWriter().println("请以 POST 方式上传文件"); - } - - @SuppressWarnings("unchecked") - public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - - File file1 = null, file2 = null; - String description1 = null, description2 = null; - - response.setCharacterEncoding("UTF-8"); - response.setContentType("text/html"); - PrintWriter out = response.getWriter(); - out.println(""); - out.println(""); - out.println(" A Servlet"); - out.println(" "); - out.println(" "); - - out.println("

    "); - out.println("
    上传文件
    "); - - out.println("
    "); - out.println("
    上传日志:
    "); - out.println("
    "); - - // 使用 DiskFileUpload 对象解析 request - DiskFileUpload diskFileUpload = new DiskFileUpload(); - try { - // 将 解析的结果 放置在 List 中 - List list = diskFileUpload.parseRequest(request); - out.println("遍历所有的 FileItem ...
    "); - // 遍历 list 中所有的 FileItem - for (FileItem fileItem : list) { - if (fileItem.isFormField()) { - // 如果是 文本域 - if ("description1".equals(fileItem.getFieldName())) { - // 如果该 FileItem 名称为 description1 - out.println("遍历到 description1 ...
    "); - description1 = new String(fileItem.getString().getBytes(), "UTF-8"); - } - if ("description2".equals(fileItem.getFieldName())) { - // 如果该 FileItem 名称为 description2 - out.println("遍历到 description2 ...
    "); - description2 = new String(fileItem.getString().getBytes(), "UTF-8"); - } - } else { - // 否则,为文件域 - if ("file1".equals(fileItem.getFieldName())) { - // 客户端文件路径构建的 File 对象 - File remoteFile = new File(new String(fileItem.getName().getBytes(), "UTF-8")); - out.println("遍历到 file1 ...
    "); - out.println("客户端文件位置: " + remoteFile.getAbsolutePath() + "
    "); - // 服务器端文件,放在 upload 文件夹下 - file1 = new File(this.getServletContext().getRealPath("attachment"), remoteFile.getName()); - file1.getParentFile().mkdirs(); - file1.createNewFile(); - - // 写文件,将 FileItem 的文件内容写到文件中 - InputStream ins = fileItem.getInputStream(); - OutputStream ous = new FileOutputStream(file1); - - try { - byte[] buffer = new byte[1024]; - int len = 0; - while ((len = ins.read(buffer)) > -1) { - ous.write(buffer, 0, len); - } - out.println("已保存文件" + file1.getAbsolutePath() + "
    "); - } finally { - ous.close(); - ins.close(); - } - } - if ("file2".equals(fileItem.getFieldName())) { - // 客户端文件路径构建的 File 对象 - File remoteFile = new File(new String(fileItem.getName().getBytes(), "UTF-8")); - out.println("遍历到 file2 ...
    "); - out.println("客户端文件位置: " + remoteFile.getAbsolutePath() + "
    "); - // 服务器端文件,放在 upload 文件夹下 - file2 = new File(this.getServletContext().getRealPath("attachment"), remoteFile.getName()); - file2.getParentFile().mkdirs(); - file2.createNewFile(); - - // 写文件,将 FileItem 的文件内容写到文件中 - InputStream ins = fileItem.getInputStream(); - OutputStream ous = new FileOutputStream(file2); - - try { - byte[] buffer = new byte[1024]; - int len = 0; - while ((len = ins.read(buffer)) > -1) { - ous.write(buffer, 0, len); - } - out.println("已保存文件" + file2.getAbsolutePath() + "
    "); - } finally { - ous.close(); - ins.close(); - } - } - } - } - out.println("Request 解析完毕"); - } catch (FileUploadException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - out.println("
    "); - out.println("
    "); - - if (file1 != null) { - out.println("
    "); - out.println("
    file1:
    "); - out.println("
    "); - out.println(" " + file1.getName() + ""); - out.println("
    "); - out.println("
    "); - } - - if (file2 != null) { - out.println("
    "); - out.println("
    file2:
    "); - out.println("
    "); - out.println(" " + file2.getName() + ""); - out.println("
    "); - out.println("
    "); - } - - out.println("
    "); - out.println("
    description1:
    "); - out.println("
    "); - out.println(description1); - out.println("
    "); - out.println("
    "); - - out.println("
    "); - out.println("
    description2:
    "); - out.println("
    "); - out.println(description2); - out.println("
    "); - out.println("
    "); - - out.println("
    "); - - out.println(" "); - out.println(""); - out.flush(); - out.close(); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/ProgressUploadServlet.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/ProgressUploadServlet.java deleted file mode 100644 index cb62876c..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/ProgressUploadServlet.java +++ /dev/null @@ -1,117 +0,0 @@ -package io.github.dunwu.javaee.servlet.upload; - -import java.util.Iterator; -import java.util.List; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.apache.commons.fileupload.FileItem; -import org.apache.commons.fileupload.disk.DiskFileItemFactory; -import org.apache.commons.fileupload.servlet.ServletFileUpload; - -public class ProgressUploadServlet extends HttpServlet { - - private static final long serialVersionUID = -4935921396709035718L; - - public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - - // 上传状态 - UploadStatus status = new UploadStatus(); - - // 监听器 - UploadListener listener = new UploadListener(status); - - // 把 UploadStatus 放到 session 里 - request.getSession(true).setAttribute("uploadStatus", status); - - // Apache 上传工具 - ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory()); - - // 设置 listener - upload.setProgressListener(listener); - - try { - List itemList = upload.parseRequest(request); - - for (Iterator it = itemList.iterator(); it.hasNext(); ) { - FileItem item = (FileItem) it.next(); - if (item.isFormField()) { - System.out.println("FormField: " + item.getFieldName() + " = " + item.getString()); - } else { - System.out.println("File: " + item.getName()); - - // 统一 Linux 与 windows 的路径分隔符 - String fileName = item.getName().replace("/", "\\"); - fileName = fileName.substring(fileName.lastIndexOf("\\")); - - File saved = new File("C:\\upload_test", fileName); - saved.getParentFile().mkdirs(); - - InputStream ins = item.getInputStream(); - OutputStream ous = new FileOutputStream(saved); - - byte[] tmp = new byte[1024]; - int len = -1; - - while ((len = ins.read(tmp)) != -1) { - ous.write(tmp, 0, len); - } - - ous.close(); - ins.close(); - - response.getWriter().println("已保存文件:" + saved); - } - } - } catch (Exception e) { - e.printStackTrace(); - response.getWriter().println("上传发生错误:" + e.getMessage()); - } - } - - public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - - response.setHeader("Cache-Control", "no-store"); - response.setHeader("Pragrma", "no-cache"); - response.setDateHeader("Expires", 0); - - UploadStatus status = (UploadStatus) request.getSession(true).getAttribute("uploadStatus"); - - if (status == null) { - response.getWriter().println("没有上传信息"); - return; - } - - long startTime = status.getStartTime(); - long currentTime = System.currentTimeMillis(); - - // 已传输的时间 单位:s - long time = (currentTime - startTime) / 1000 + 1; - - // 传输速度 单位:byte/s - double velocity = ((double) status.getBytesRead()) / (double) time; - - // 估计总时间 单位:s - double totalTime = status.getContentLength() / velocity; - - // 估计剩余时间 单位:s - double timeLeft = totalTime - time; - - // 已完成的百分比 - int percent = (int) (100 * (double) status.getBytesRead() / (double) status.getContentLength()); - - // 已完成数 单位:M - double length = ((double) status.getBytesRead()) / 1024 / 1024; - - // 总长度 单位:M - double totalLength = ((double) status.getContentLength()) / 1024 / 1024; - - // 格式:百分比||已完成数(M)||文件总长度(M)||传输速率(K)||已用时间(s)||估计总时间(s)||估计剩余时间(s)||正在上传第几个文件 - String value = percent + "||" + length + "||" + totalLength + "||" + velocity + "||" + time + "||" + totalTime - + "||" + timeLeft + "||" + status.getItems(); - - response.getWriter().println(value); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/UploadListener.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/UploadListener.java deleted file mode 100644 index 3812ae18..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/UploadListener.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.github.dunwu.javaee.servlet.upload; - -import org.apache.commons.fileupload.ProgressListener; - -public class UploadListener implements ProgressListener { - - private UploadStatus status; - - public UploadListener(UploadStatus status) { - this.status = status; - } - - public void update(long bytesRead, long contentLength, int items) { - status.setBytesRead(bytesRead); - status.setContentLength(contentLength); - status.setItems(items); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/UploadStatus.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/UploadStatus.java deleted file mode 100644 index 0483f070..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/java/io/github/dunwu/javaee/servlet/upload/UploadStatus.java +++ /dev/null @@ -1,45 +0,0 @@ -package io.github.dunwu.javaee.servlet.upload; - -public class UploadStatus { - - private long bytesRead; - - private long contentLength; - - private int items; - - private long startTime = System.currentTimeMillis(); - - public long getBytesRead() { - return bytesRead; - } - - public void setBytesRead(long bytesRead) { - this.bytesRead = bytesRead; - } - - public long getContentLength() { - return contentLength; - } - - public void setContentLength(long contentLength) { - this.contentLength = contentLength; - } - - public int getItems() { - return items; - } - - public void setItems(int items) { - this.items = items; - } - - public long getStartTime() { - return startTime; - } - - public void setStartTime(long startTime) { - this.startTime = startTime; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/resources/logback.xml deleted file mode 100644 index 8b8545db..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/resources/logback.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n - - - - - - - - ${user.dir}/logs/${DIR_NAME}/all.%d{yyyy-MM-dd}.log - 30 - - - - - 30MB - - - - %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n - - - - - - - - - - - - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/META-INF/MANIFEST.MF b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/META-INF/MANIFEST.MF deleted file mode 100644 index 254272e1..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Class-Path: - diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/WEB-INF/notice.html b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/WEB-INF/notice.html deleted file mode 100644 index aebd0084..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/WEB-INF/notice.html +++ /dev/null @@ -1,116 +0,0 @@ - - - - - Eclipse.org Software User Agreement - - - -

    Eclipse Foundation Software User Agreement

    -

    March 17, 2005

    - -

    Usage Of Content

    - -

    THE ECLIPSE FOUNDATION MAKES AVAILABLE SOFTWARE, DOCUMENTATION, INFORMATION AND/OR OTHER MATERIALS FOR OPEN SOURCE - PROJECTS - (COLLECTIVELY "CONTENT"). USE OF THE CONTENT IS GOVERNED BY THE TERMS AND CONDITIONS OF THIS AGREEMENT - AND/OR THE TERMS AND - CONDITIONS OF LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW. BY USING THE CONTENT, YOU AGREE THAT YOUR - USE - OF THE CONTENT IS GOVERNED BY THIS AGREEMENT AND/OR THE TERMS AND CONDITIONS OF ANY APPLICABLE LICENSE AGREEMENTS OR - NOTICES INDICATED OR REFERENCED BELOW. IF YOU DO NOT AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT AND THE TERMS - AND - CONDITIONS OF ANY APPLICABLE LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW, THEN YOU MAY NOT USE THE - CONTENT.

    - -

    Applicable Licenses

    - -

    Unless otherwise indicated, all Content made available by the Eclipse Foundation is provided to you under the terms - and conditions of the Eclipse Public License Version 1.0 - ("EPL"). A copy of the EPL is provided with this Content and is also available at http://www.eclipse.org/legal/epl-v10.html. - For purposes of the EPL, "Program" will mean the Content.

    - -

    Content includes, but is not limited to, source code, object code, documentation and other files maintained in the - Eclipse.org CVS repository ("Repository") in CVS - modules ("Modules") and made available as downloadable archives ("Downloads").

    - -
      -
    • Content may be structured and packaged into modules to facilitate delivering, extending, and upgrading the - Content. Typical modules may include plug-ins ("Plug-ins"), plug-in fragments ("Fragments"), and - features ("Features"). -
    • -
    • Each Plug-in or Fragment may be packaged as a sub-directory or JAR (Java™ ARchive) in a directory named - "plugins". -
    • -
    • A Feature is a bundle of one or more Plug-ins and/or Fragments and associated material. Each Feature may be - packaged as a sub-directory in a directory named "features". Within a Feature, files named "feature.xml" - may contain a list of the names and version numbers of the Plug-ins - and/or Fragments associated with that Feature. -
    • -
    • Features may also include other Features ("Included Features"). Within a Feature, files named "feature.xml" - may contain a list of the names and version numbers of Included Features. -
    • -
    - -

    The terms and conditions governing Plug-ins and Fragments should be contained in files named "about.html" ("Abouts"). - The terms and conditions governing Features and - Included Features should be contained in files named "license.html" ("Feature Licenses"). Abouts - and Feature Licenses may be located in any directory of a Download or Module - including, but not limited to the following locations:

    - -
      -
    • The top-level (root) directory
    • -
    • Plug-in and Fragment directories
    • -
    • Inside Plug-ins and Fragments packaged as JARs
    • -
    • Sub-directories of the directory named "src" of certain Plug-ins
    • -
    • Feature directories
    • -
    - -

    Note: if a Feature made available by the Eclipse Foundation is installed using the Eclipse Update Manager, you must - agree to a license ("Feature Update License") during the - installation process. If the Feature contains Included Features, the Feature Update License should either provide you - with the terms and conditions governing the Included Features or - inform you where you can locate them. Feature Update Licenses may be found in the "license" property of - files named "feature.properties" found within a Feature. - Such Abouts, Feature Licenses, and Feature Update Licenses contain the terms and conditions (or references to such - terms and conditions) that govern your use of the associated Content in - that directory.

    - -

    THE ABOUTS, FEATURE LICENSES, AND FEATURE UPDATE LICENSES MAY REFER TO THE EPL OR OTHER LICENSE AGREEMENTS, NOTICES - OR TERMS AND CONDITIONS. SOME OF THESE - OTHER LICENSE AGREEMENTS MAY INCLUDE (BUT ARE NOT LIMITED TO):

    - - - -

    IT IS YOUR OBLIGATION TO READ AND ACCEPT ALL SUCH TERMS AND CONDITIONS PRIOR TO USE OF THE CONTENT. If no About, - Feature License, or Feature Update License is provided, please - contact the Eclipse Foundation to determine what terms and conditions govern that particular Content.

    - -

    Cryptography

    - -

    Content may contain encryption software. The country in which you are currently may have restrictions on the import, - possession, and use, and/or re-export to - another country, of encryption software. BEFORE using any encryption software, please check the country's laws, - regulations and policies concerning the import, - possession, or use, and re-export of encryption software, to see if this is permitted.

    - -Java and all Java-based trademarks are trademarks of Sun Microsystems, Inc. in the United States, other - countries, or both. - - diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/WEB-INF/web.xml b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 38144543..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,216 +0,0 @@ - - - - - FirstServlet - - io.github.dunwu.javaee.servlet.FirstServlet - - - message - welcome to FirstServlet - - - encoding - utf-8 - - 1 - - - FirstServlet - /servlet/FirstServlet - /servlet/FirstServlet.asp - /servlet/FirstServlet.jsp - /servlet/FirstServlet.php - /servlet/FirstServlet.aspx - - - - RequestServlet - - io.github.dunwu.javaee.servlet.RequestServlet - - - - RequestServlet - /servlet/RequestServlet - - - - InitParamServlet - - io.github.dunwu.javaee.servlet.InitParamServlet - - - root - root - - - admin - admin - - - - InitParamServlet - /servlet/InitParamServlet - - - - - upload folder - attachment - - - allowed file type - .gif,.jpg,.bmp - - - ContextParamServlet - - io.github.dunwu.javaee.servlet.ContextParamServlet - - - - ContextParamServlet - /servlet/ContextParamServlet - - - - InjectionServlet - - io.github.dunwu.javaee.servlet.InjectionServlet - - - - InjectionServlet - /servlet/InjectionServlet - - - - PostServlet - - io.github.dunwu.javaee.servlet.PostServlet - - - - PostServlet - /servlet/PostServlet - - - - ImageServlet - - io.github.dunwu.javaee.servlet.ImageServlet - - 1 - - - UploadServlet - - io.github.dunwu.javaee.servlet.UploadServlet - - - - LifeCycleServlet - - io.github.dunwu.javaee.servlet.LifeCycleServlet - - - startPoint - 2000 - - - - AnnotationServlet - - io.github.dunwu.javaee.servlet.AnnotationServlet - - - - ForwardServlet - - io.github.dunwu.javaee.servlet.ForwardServlet - - - - RedirectServlet - - io.github.dunwu.javaee.servlet.RedirectServlet - - - - ProgressUploadServlet - - io.github.dunwu.javaee.servlet.upload.ProgressUploadServlet - - - - ThreadSafetyServlet - - io.github.dunwu.javaee.servlet.ThreadSafetyServlet - - - - - ImageServlet - /upload/* - - - UploadServlet - /servlet/UploadServlet - - - LifeCycleServlet - /servlet/LifeCycleServlet - - - AnnotationServlet - /servlet/AnnotationServlet - - - ForwardServlet - /servlet/ForwardServlet - - - RedirectServlet - /servlet/RedirectServlet - - - ProgressUploadServlet - /servlet/ProgressUploadServlet - - - ThreadSafetyServlet - /servlet/ThreadSafetyServlet - - - - index.jsp - - - - hello - java.lang.String - - Hello, Welcome to the JavaEE Resource Injection. - - - - - i - java.lang.Integer - 30 - - - - persons - java.lang.String - - Helloween, Cobain, Roses, Axl, - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/index.jsp b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/index.jsp deleted file mode 100644 index b4a2d228..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/index.jsp +++ /dev/null @@ -1,33 +0,0 @@ -<%@ page contentType="text/html;charset=UTF-8" language="java" %> - - - javaee-web - - -

    首页

    -

    欢迎访问

    -

    测试链接

    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/views/jsp/postPersonalInformation.html b/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/views/jsp/postPersonalInformation.html deleted file mode 100644 index 771902df..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/main/webapp/views/jsp/postPersonalInformation.html +++ /dev/null @@ -1,152 +0,0 @@ - - - - 提交用户信息 - - - - -
    -
    -
    -
    - 填写用户信息 -
    -
    -
    请填写您的姓名:
    -
    - -
    -
    -
    -
    请填写您的密码:
    -
    - -
    -
    -
    -
    请再次输入密码:
    -
    - -
    -
    -
    -
    请选择性别:
    -
    - - - - -
    -
    -
    -
    请输入年龄:
    -
    - -
    -
    -
    -
    请输入生日:
    -
    - -
    格式:"yyyy-mm-dd" -
    -
    -
    -
    请选择您的爱好
    -
    - - - - - - -
    -
    -
    -
    请选择省市:
    -
    - -
    -
    -
    -
    自我描述:
    -
    - -
    -
    -
    -
    -
    -

    -
    -
    -
    -
    -
    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java deleted file mode 100644 index f622dc2a..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java +++ /dev/null @@ -1,114 +0,0 @@ -package io.github.dunwu.javaee.server; - -import java.util.ArrayList; -import org.apache.commons.lang3.StringUtils; -import org.assertj.core.util.Lists; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.webapp.WebAppClassLoader; -import org.eclipse.jetty.webapp.WebAppContext; - -/** - * JettyFactory 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 - * - * @author Zhang Peng - */ -@SuppressWarnings("unused") -public class JettyFactory { - - public static final int IDE_ECLIPSE = 0; - - public static final int IDE_INTELLIJ = 1; - - private static final int PORT = 8080; - - private static final String CONTEXT = "/"; - - private static final String RESOURCE_BASE_PATH = "src/main/webapp"; - - private static final String WEB_XML_PATH = "/WEB-INF/web.xml"; - - private static final String[] TLD_JAR_NAMES = new String[] {"sitemesh", "spring-webmvc", "shiro-web", "tiles"} - - private static final String WINDOWS_WEBDEFAULT_PATH = "jetty/webdefault.xml"; - - public static Server initServer() { - Profiles.setProfileAsSystemProperty(Profiles.DEVELOPMENT); - WebAppContext webAppContext = new WebAppContext(); - Server server = new Server(PORT); - server.setHandler(webAppContext); - return server; - } - - public static void initWebAppContext(Server server, int type) throws Exception { - System.out.println("[INFO] Application loading"); - WebAppContext webAppContext = (WebAppContext) server.getHandler(); - webAppContext.setContextPath(CONTEXT); - webAppContext.setResourceBase(getAbsolutePath() + RESOURCE_BASE_PATH); - webAppContext.setDescriptor(getAbsolutePath() + RESOURCE_BASE_PATH + WEB_XML_PATH); - - if (IDE_INTELLIJ == type) { - webAppContext.setDefaultsDescriptor(WINDOWS_WEBDEFAULT_PATH); - supportJspAndSetTldJarNames(server, TLD_JAR_NAMES); - } else { - webAppContext.setParentLoaderPriority(true); - } - - System.out.println("[INFO] Application loaded"); - } - - public static String getAbsolutePath() { - String path = null; - String folderPath = JettyFactory.class.getProtectionDomain().getCodeSource().getLocation().getPath() - .substring(1); - if (folderPath.indexOf("target") > 0) { - path = folderPath.substring(0, folderPath.indexOf("target")); - } - return path; - } - - public static void supportJspAndSetTldJarNames(Server server, String... jarNames) { - WebAppContext context = (WebAppContext) server.getHandler(); - // This webapp will use jsps and jstl. We need to enable the - // AnnotationConfiguration in - // order to correctly set up the jsp container - org.eclipse.jetty.webapp.Configuration.ClassList classlist = org.eclipse.jetty.webapp.Configuration.ClassList - .setServerDefault(server); - classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", - "org.eclipse.jetty.annotations.AnnotationConfiguration"); - // Set the ContainerIncludeJarPattern so that jetty examines these container-path - // jars for - // tlds, web-fragments etc. - // If you omit the jar that contains the jstl .tlds, the jsp engine will scan for - // them - // instead. - ArrayList jarNameExprssions = Lists.newArrayList(".*/[^/]*servlet-api-[^/]*\\.jar$", - ".*/javax.servlet.jsp.jstl-.*\\.jar$", ".*/[^/]*taglibs.*\\.jar$"); - - for (String jarName : jarNames) { - jarNameExprssions.add(".*/" + jarName + "-[^/]*\\.jar$"); - } - - context.setAttribute("org.eclipse.jetty.io.github.dunwu.javaee.server.webapp.ContainerIncludeJarPattern", - StringUtils.join(jarNameExprssions, '|')); - } - - public static void reloadWebAppContext(Server server) throws Exception { - WebAppContext webAppContext = (WebAppContext) server.getHandler(); - System.out.println("[INFO] Application reloading"); - webAppContext.stop(); - WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext); - classLoader.addClassPath(getAbsolutePath() + "target/classes"); - classLoader.addClassPath(getAbsolutePath() + "target/test-classes"); - webAppContext.setClassLoader(classLoader); - webAppContext.start(); - System.out.println("[INFO] Application reloaded"); - } - - public static void startServer(Server server) throws Exception { - System.out.println("[HINT] Don't forget to set -XX:MaxPermSize=128m"); - server.start(); - System.out.println("Server running at http://localhost:" + PORT + CONTEXT); - System.out.println("[HINT] Hit Enter to reload the application quickly"); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/Profiles.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/Profiles.java deleted file mode 100644 index dcfd3e39..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/Profiles.java +++ /dev/null @@ -1,34 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2005, 2014 springside.github.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - *******************************************************************************/ -package io.github.dunwu.javaee.server; - -/** - * Spring profile 常用方法与profile名称。 - * - * @author calvin - */ -public class Profiles { - - public static final String ACTIVE_PROFILE = "spring.profiles.active"; - - public static final String DEFAULT_PROFILE = "spring.profiles.default"; - - public static final String PRODUCTION = "production"; - - public static final String DEVELOPMENT = "development"; - - public static final String UNIT_TEST = "test"; - - public static final String FUNCTIONAL_TEST = "functional"; - - /** - * 在Spring启动前,设置profile的环境变量。 - */ - public static void setProfileAsSystemProperty(String profile) { - System.setProperty(ACTIVE_PROFILE, profile); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java b/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java deleted file mode 100644 index 049c8802..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.github.dunwu.javaee.server; - -import org.eclipse.jetty.server.Server; - -/** - * 快速启动 jetty 服务器,方便测试 - * - * @author Zhang Peng - */ -public class QuickStartServer { - - // private static int STARTUP_TYPE = JettyFactory.IDE_ECLIPSE; - private static int STARTUP_TYPE = JettyFactory.IDE_INTELLIJ; - - public static void main(String[] args) throws Exception { - Server server = JettyFactory.initServer(); - JettyFactory.initWebAppContext(server, STARTUP_TYPE); - - try { - JettyFactory.startServer(server); - - // 等待用户输入回车重载应用 - while (true) { - char c = (char) System.in.read(); - if (c == '\n') { - JettyFactory.reloadWebAppContext(server); - } - } - } catch (Exception e) { - e.printStackTrace(); - System.exit(-1); - } - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/resources/jetty/webdefault.xml b/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/resources/jetty/webdefault.xml deleted file mode 100644 index b991d44c..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/resources/jetty/webdefault.xml +++ /dev/null @@ -1,534 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - Default web.xml file. - This file is applied to a Web application before it's own WEB_INF/web.xml file - - - - - - - - org.eclipse.jetty.servlet.listener.ELContextCleaner - - - - - - - - org.eclipse.jetty.servlet.listener.IntrospectorCleaner - - - - - - - - - - - - - - - - - default - org.eclipse.jetty.servlet.DefaultServlet - - aliases - false - - - acceptRanges - true - - - dirAllowed - true - - - welcomeServlets - false - - - redirectWelcome - false - - - maxCacheSize - 256000000 - - - maxCachedFileSize - 200000000 - - - maxCachedFiles - 2048 - - - gzip - false - - - etags - false - - - useFileMappedBuffer - false - - - - 0 - - - - default - / - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - jsp - org.eclipse.jetty.jsp.JettyJspServlet - - logVerbosityLevel - DEBUG - - - fork - false - - - xpoweredBy - false - - - compilerTargetVM - 1.7 - - - compilerSourceVM - 1.7 - - - 0 - - - - jsp - *.jsp - *.jspf - *.jspx - *.xsp - *.JSP - *.JSPF - *.JSPX - *.XSP - - - - - - - - 30 - - - - - - - - - - - - - - - index.html - index.htm - index.jsp - - - - - - - - ar - ISO-8859-6 - - - be - ISO-8859-5 - - - bg - ISO-8859-5 - - - ca - ISO-8859-1 - - - cs - ISO-8859-2 - - - da - ISO-8859-1 - - - de - ISO-8859-1 - - - el - ISO-8859-7 - - - en - ISO-8859-1 - - - es - ISO-8859-1 - - - et - ISO-8859-1 - - - fi - ISO-8859-1 - - - fr - ISO-8859-1 - - - hr - ISO-8859-2 - - - hu - ISO-8859-2 - - - is - ISO-8859-1 - - - it - ISO-8859-1 - - - iw - ISO-8859-8 - - - ja - Shift_JIS - - - ko - EUC-KR - - - lt - ISO-8859-2 - - - lv - ISO-8859-2 - - - mk - ISO-8859-5 - - - nl - ISO-8859-1 - - - no - ISO-8859-1 - - - pl - ISO-8859-2 - - - pt - ISO-8859-1 - - - ro - ISO-8859-2 - - - ru - ISO-8859-5 - - - sh - ISO-8859-5 - - - sk - ISO-8859-2 - - - sl - ISO-8859-2 - - - sq - ISO-8859-2 - - - sr - ISO-8859-5 - - - sv - ISO-8859-1 - - - tr - ISO-8859-9 - - - uk - ISO-8859-5 - - - zh - GB2312 - - - zh_TW - Big5 - - - - - - - - - Disable TRACE - / - TRACE - - - - - - Enable everything but TRACE - / - TRACE - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/resources/logback.xml deleted file mode 100644 index 5b969d92..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-servlet/src/test/resources/logback.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n - - - - - - - - logs/${FILE_NAME}-all.%d{yyyy-MM-dd}.log - 30 - - - - - 30MB - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n - - - - - - - - - - - - - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-session/pom.xml b/codes/javaee-tutorial/javaee-tutorial-session/pom.xml deleted file mode 100644 index ed43f5eb..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-session/pom.xml +++ /dev/null @@ -1,93 +0,0 @@ - - 4.0.0 - - io.github.dunwu - javaee-tutorial-session - 1.0.0 - war - javaee-tutorial-session - JavaEE 学习笔记之 session - - - UTF-8 - 1.7 - ${java.version} - ${java.version} - - - - - - ch.qos.logback - logback-classic - - - org.slf4j - jcl-over-slf4j - - - - - - org.apache.commons - commons-lang3 - - - - - - javax.servlet - javax.servlet-api - - - - - - org.eclipse.jetty - jetty-webapp - test - - - org.eclipse.jetty - jetty-annotations - test - - - org.eclipse.jetty - apache-jsp - test - - - org.eclipse.jetty - apache-jstl - test - - - - - - junit - junit - test - - - org.assertj - assertj-core - test - - - - - - - - io.github.dunwu - dunwu-dependencies - 0.4.2-SNAPSHOT - pom - import - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/AddCookies.java b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/AddCookies.java deleted file mode 100644 index d30df96a..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/AddCookies.java +++ /dev/null @@ -1,68 +0,0 @@ -/** - * The Apache License 2.0 Copyright (c) 2017 Zhang Peng - */ -package io.github.dunwu.javaee.cookie; - -import java.io.IOException; -import java.io.PrintWriter; -import java.net.URLEncoder; -import javax.servlet.ServletException; -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * @author Zhang Peng - * @since 2017/3/26. - */ -@WebServlet("/servlet/AddCookies") -public class AddCookies extends HttpServlet { - - private static final long serialVersionUID = 1L; - - /** - * @see HttpServlet#HttpServlet() - */ - public AddCookies() { - super(); - } - - /** - * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) - */ - protected void doPost(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - doGet(request, response); - } - - /** - * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) - */ - public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - // 为名字和姓氏创建 Cookie - Cookie name = new Cookie("name", URLEncoder.encode(request.getParameter("name"), "UTF-8")); // 中文转码 - Cookie url = new Cookie("url", request.getParameter("url")); - - // 为两个 Cookie 设置过期日期为 24 小时后 - name.setMaxAge(60 * 60 * 24); - url.setMaxAge(60 * 60 * 24); - - // 在响应头中添加两个 Cookie - response.addCookie(name); - response.addCookie(url); - - // 设置响应内容类型 - response.setContentType("text/html;charset=UTF-8"); - - PrintWriter out = response.getWriter(); - String title = "设置 Cookie 实例"; - String docType = "\n"; - out.println(docType + "\n" + "" + title + "\n" - + "\n" + "

    " + title + "

    \n" + "
      \n" - + "
    • 站点名::" + request.getParameter("name") + "\n
    • " + "
    • 站点 URL::" - + request.getParameter("url") + "\n
    • " + "
    \n" + ""); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/DeleteCookies.java b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/DeleteCookies.java deleted file mode 100644 index b3cd3193..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/DeleteCookies.java +++ /dev/null @@ -1,75 +0,0 @@ -/** - * The Apache License 2.0 Copyright (c) 2017 Zhang Peng - */ -package io.github.dunwu.javaee.cookie; - -import java.io.IOException; -import java.io.PrintWriter; -import javax.servlet.ServletException; -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * @author Zhang Peng - * @since 2017/3/26. - */ -@WebServlet("/servlet/DeleteCookies") -public class DeleteCookies extends HttpServlet { - - private static final long serialVersionUID = 1L; - - /** - * @see HttpServlet#HttpServlet() - */ - public DeleteCookies() { - super(); - } - - /** - * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) - */ - protected void doPost(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - doGet(request, response); - } - - /** - * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) - */ - public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - Cookie cookie = null; - Cookie[] cookies = null; - // 获取与该域相关的 Cookie 的数组 - cookies = request.getCookies(); - - // 设置响应内容类型 - response.setContentType("text/html;charset=UTF-8"); - - PrintWriter out = response.getWriter(); - String title = "删除 Cookie 实例"; - String docType = "\n"; - out.println( - docType + "\n" + "" + title + "\n" + "\n"); - if (cookies != null) { - out.println("

    Cookie 名称和值

    "); - for (int i = 0; i < cookies.length; i++) { - cookie = cookies[i]; - if ((cookie.getName()).compareTo("url") == 0) { - cookie.setMaxAge(0); - response.addCookie(cookie); - out.print("已删除的 cookie:" + cookie.getName() + "
    "); - } - out.print("名称:" + cookie.getName() + ","); - out.print("值:" + cookie.getValue() + "
    "); - } - } else { - out.println("

    No Cookie founds

    "); - } - out.println(""); - out.println(""); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/ReadCookies.java b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/ReadCookies.java deleted file mode 100644 index add3ecab..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/ReadCookies.java +++ /dev/null @@ -1,76 +0,0 @@ -/** - * The Apache License 2.0 Copyright (c) 2017 Zhang Peng - */ -package io.github.dunwu.javaee.cookie; - -import java.io.IOException; -import java.io.PrintWriter; -import java.net.URLDecoder; -import javax.servlet.ServletException; -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * @author Zhang Peng - * @since 2017/3/26. - */ -@WebServlet("/servlet/ReadCookies") -public class ReadCookies extends HttpServlet { - - private static final long serialVersionUID = 1L; - - /** - * @see HttpServlet#HttpServlet() - */ - public ReadCookies() { - super(); - } - - /** - * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) - */ - protected void doPost(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - doGet(request, response); - } - - /** - * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) - */ - public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - Cookie cookie = null; - Cookie[] cookies = null; - // 获取与该域相关的 Cookie 的数组 - cookies = request.getCookies(); - - // 设置响应内容类型 - response.setContentType("text/html;charset=UTF-8"); - - PrintWriter out = response.getWriter(); - String title = "Delete Cookie Example"; - String docType = "\n"; - out.println( - docType + "\n" + "" + title + "\n" + "\n"); - if (cookies != null) { - out.println("

    Cookie 名称和值

    "); - for (int i = 0; i < cookies.length; i++) { - cookie = cookies[i]; - if ((cookie.getName()).compareTo("name") == 0) { - cookie.setMaxAge(0); - response.addCookie(cookie); - out.print("已删除的 cookie:" + cookie.getName() + "
    "); - } - out.print("名称:" + cookie.getName() + ","); - out.print("值:" + URLDecoder.decode(cookie.getValue(), "utf-8") + "
    "); - } - } else { - out.println("

    No Cookie founds

    "); - } - out.println(""); - out.println(""); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Person.java b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Person.java deleted file mode 100644 index 6e4d9271..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Person.java +++ /dev/null @@ -1,65 +0,0 @@ -/** - * The Apache License 2.0 Copyright (c) 2017 Zhang Peng - */ -package io.github.dunwu.javaee.cookie.bean; - -import java.io.Serializable; -import java.util.Date; - -/** - * @author Zhang Peng - * @since 2017/3/26. - */ -public class Person implements Serializable { - - private static final long serialVersionUID = -827111150707830908L; - - private String name; - - private String password; - - private int age; - - private Date birthday; - - public Person(String name, String password, int age, Date birthday) { - // super(); - this.name = name; - this.password = password; - this.age = age; - this.birthday = birthday; - } - - public int getAge() { - return age; - } - - public void setAge(int age) { - this.age = age; - } - - public Date getBirthday() { - return birthday; - } - - public void setBirthday(Date birthday) { - this.birthday = birthday; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Topic.java b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Topic.java deleted file mode 100644 index 4e263d60..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/cookie/bean/Topic.java +++ /dev/null @@ -1,42 +0,0 @@ -/** - * The Apache License 2.0 Copyright (c) 2017 Zhang Peng - */ -package io.github.dunwu.javaee.cookie.bean; - -/** - * @author Zhang Peng - * @since 2017/3/26. - */ -public class Topic { - - private int id; - - private String title; - - private String content; - - public String getContent() { - return content; - } - - public void setContent(String content) { - this.content = content; - } - - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/session/SessionTrackServlet.java b/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/session/SessionTrackServlet.java deleted file mode 100644 index a495546d..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/java/io/github/dunwu/javaee/session/SessionTrackServlet.java +++ /dev/null @@ -1,69 +0,0 @@ -/** - * The Apache License 2.0 Copyright (c) 2017 Zhang Peng - */ -package io.github.dunwu.javaee.session; - -import java.io.IOException; -import java.io.PrintWriter; -import java.text.SimpleDateFormat; -import java.util.Date; -import javax.servlet.ServletException; -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; - -/** - * @author Zhang Peng - * @since 2017/3/26. - */ -@WebServlet("/servlet/SessionTrackServlet") -public class SessionTrackServlet extends HttpServlet { - - private static final long serialVersionUID = 1L; - - public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - // 如果不存在 session 会话,则创建一个 session 对象 - HttpSession session = request.getSession(true); - // 获取 session 创建时间 - Date createTime = new Date(session.getCreationTime()); - // 获取该网页的最后一次访问时间 - Date lastAccessTime = new Date(session.getLastAccessedTime()); - - // 设置日期输出的格式 - SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - - String title = "Servlet Session 实例"; - Integer visitCount = new Integer(0); - String visitCountKey = new String("visitCount"); - String userIDKey = new String("userID"); - String userID = new String("admin"); - - // 检查网页上是否有新的访问者 - if (session.isNew()) { - session.setAttribute(userIDKey, userID); - } else { - visitCount = (Integer) session.getAttribute(visitCountKey); - visitCount = visitCount + 1; - userID = (String) session.getAttribute(userIDKey); - } - session.setAttribute(visitCountKey, visitCount); - - // 设置响应内容类型 - response.setContentType("text/html;charset=UTF-8"); - PrintWriter out = response.getWriter(); - - String docType = "\n"; - out.println(docType + "\n" + "" + title + "\n" - + "\n" + "

    " + title + "

    \n" - + "

    Session 信息

    \n" + "\n" - + "\n" + " \n" + "\n" + " \n" - + " \n" + "\n" + " \n" + " \n" + "\n" + " \n" + " \n" + "\n" + " \n" + " \n" + "\n" + " \n" + " \n" - + "
    Session 信息
    id" + session.getId() + "
    创建时间" - + df.format(createTime) + "
    最后访问时间" - + df.format(lastAccessTime) + "
    用户 ID" + userID - + "
    访问统计:" + visitCount + "
    \n" + ""); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-session/src/main/resources/logback.xml deleted file mode 100644 index 0e8f7363..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/resources/logback.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n - - - - - - - - logs/${FILE_NAME}-all.%d{yyyy-MM-dd}.log - 30 - - - - - 30MB - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n - - - - - - - - - - - - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/META-INF/MANIFEST.MF b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/META-INF/MANIFEST.MF deleted file mode 100644 index 254272e1..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Class-Path: - diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/META-INF/context.xml b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/META-INF/context.xml deleted file mode 100644 index 8f05edc7..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/META-INF/context.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/WEB-INF/web.xml b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 83c873a5..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - - AddCookies - io.github.dunwu.javaee.servlet.cookie.AddCookies - - - AddCookies - /servlet/AddCookies - - - - ReadCookies - io.github.dunwu.javaee.servlet.cookie.ReadCookies - - - ReadCookies - /servlet/ReadCookies - - - - DeleteCookies - io.github.dunwu.javaee.servlet.cookie.DeleteCookies - - - DeleteCookies - /servlet/DeleteCookies - - - - SessionTrackServlet - io.github.dunwu.javaee.session.SessionTrackServlet - - - SessionTrackServlet - /servlet/SessionTrackServlet - - - - 20 - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/encodeURL.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/encodeURL.jsp deleted file mode 100644 index c2aeaad0..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/encodeURL.jsp +++ /dev/null @@ -1,15 +0,0 @@ -<%@ page language="java" pageEncoding="UTF-8" %> - - - - Cookie Encoding - - -">Homepage - -<%= response.encodeURL("index.jsp") %>?c=1&wd=Java - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/index.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/index.jsp deleted file mode 100644 index 79af5bcc..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/index.jsp +++ /dev/null @@ -1,26 +0,0 @@ -<%@ page language="java" pageEncoding="UTF-8" %> -<% - String path = request.getContextPath(); - String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; -%> - - - - - - - My JSP 'index.jsp' starting page - - - - - - - - - -This is my JSP page.
    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/css/style.css b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/css/style.css deleted file mode 100644 index 2ae2e7bd..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/css/style.css +++ /dev/null @@ -1,51 +0,0 @@ -body, div, td, input { - font-size: 12px; - margin: 0px; -} - -select { - height: 20px; - width: 300px; -} - -.title { - font-size: 16px; - padding: 10px; - margin: 10px; - width: 80%; -} - -.text { - height: 20px; - width: 300px; - border: 1px solid #AAAAAA; -} - -.line { - margin: 2px; -} - -.leftDiv { - width: 110px; - float: left; - height: 22px; - line-height: 22px; - font-weight: bold; -} - -.rightDiv { - height: 22px; - line-height: 22px; -} - -.button { - color: #FFFFFF; - font-weight: bold; - font-size: 11px; - text-align: center; - padding: .17em 0 .2em .17em; - border-style: solid; - border-width: 1px; - border-color: #99CCFF #115599 #115599 #99CCFF; - background: #6699CC url(../images/bg-btn-blue.gif) repeat-x; -} diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/images/bg-btn-blue.gif b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/images/bg-btn-blue.gif deleted file mode 100644 index bc03f1bd5948a2057abb97707ece2434cea75203..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 827 zcmZ?wbhEHbWMnX6_|CwPHuG*k>y@ysYcAF2qxx^ePrhYUcrtm~ZLfxlcBN++M!{$Z w4C@e3{K*3LpALuw>Qm;Jxj`0rlh|A$@w9(4VG)c@~M-~Y$c|2>)h z|LMH{&*uGmI`7}JdH)-y6 z{|6)f9gO&YF!JBQ$p1hsr_2AHuK0YU;@`#gf0sHQAME~rd;Y5vlm0(gT^%S~7c9~m zA=VrwRu>}L5+U9lE7=tz*%d3{jASsC)D=O>?>lXPrW!qJ%tM`tBm zT9STYaoU;rDW~Qop8$%_PCPOz;quaqOG`2?EzP*NIQ{&hv~!En&MrtjJul_t+~ni4 zla9|xIyyV?#G?Fj3sTQ7OglO=@!Y!Bt9$0%J+t%w|Njis1ByRc7#SGS7<52Jg5rdM zeQiTpQ*&x?aB6dBSE!U!aMy%XA0f$*jtM}2o0?1b&S*;U6}7Y!4^LSTAz)!;t0b5> zBR)Yy(aze8KQexTAFGzJgT09sn_pMFyRe#(le3W;Q`FvOe+Fe0Lsu6ARaM65=J)_+ zP4yQqG&NW}uEuca>gmYayUA6z3obJRnSdDW7c4h+@+$P5Uu diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/images/errorstate.gif b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/images/errorstate.gif deleted file mode 100644 index a3b621b5709ff889e5b435d5d6d6a9f8220d1340..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 609 zcmZ?wbhEHb6k!lyc*el+DJK4Na@yyV^snt5AN<2U1cbkfi2W2D|1mn@Q+m$(yyDln zg&&H`zgE?Mt!a4c9{ABW?4w`UU9XUP-of|%!@uU1e#tG}Eu?TrUi+M;#W8KOch(*k zEnM$9_bzWQ2fcl$iU#tpaXI{C{7sI_cb^-HMg|3wRd!Ob?fWt z7#Zp4>UZlJnb{c{+L;;YcIp{fo0?cy7#mm`>9y+{Ihflz*>2hFU^JywNyN&=#?i^f z$&8qW_nJ%28zd{SOo`oJfr zkJ9qjcFBtIJMsT_Y_eH#!LenbxVDIj!hsEmOstYcqCpcI9hsRWcuf)}IJzo{s;lWp d7$h{Z@Muc3UMM)o(k>t*esYdyahd{yH2_yuu)_cV diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/images/mail.gif b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/images/mail.gif deleted file mode 100644 index 0f2b0d7618913ad725dfc296b4af442d8518b88c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 226 zcmZ?wbhEHb6krfwIKsg2|NsAAzy5vt^n3G(XKN2XS-SV(>}~g^ZMxIH_EzJft91*n zl+L=CIq__H`EskwbOwb~3QWE2#9PWX?-jye?-oSAou9 z#t$Y<#sW3x`T3;v7JmLK!?p0if(xSaSQk86U?L%KZiRB;f_p4sEY9pP8f&gTYuGVW mYRU~q^Mkewf?jh=_p@s;HZ(Rhn)A1{cXVoW_oyl|SOWkQuTW|L diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/images/vertical_line.gif b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/images/vertical_line.gif deleted file mode 100644 index 65f8ee7882196570f92051f9c5fc4f48c5bfa066..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 301 zcma)1+X=!z5S-0D5-%5X=NS!&f(2NCKYj>7t;ik(!8&Y0L9hiMKX!7SehBf)Sr(R= zVRqMF{S#*0tQrszmZ9JuSU_^l1(!m|jtre9QYNWZDpg7w{ejWl+~i&DT)*$!sIKeL zL gJo)DBuOGJO@6e3GGG;Nyc5_&rX1noz+FbnY1^zZDQUCw| diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/addCookies.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/addCookies.jsp deleted file mode 100644 index 58b5ff9e..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/addCookies.jsp +++ /dev/null @@ -1,16 +0,0 @@ -<%@ page language="java" pageEncoding="UTF-8" %> - - - - - 添加Cookie - - -
    - 站点名 : -
    - 站点 URL:
    - -
    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/base64.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/base64.jsp deleted file mode 100644 index 49b525ac..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/base64.jsp +++ /dev/null @@ -1,35 +0,0 @@ -<%@ page language="java" pageEncoding="UTF-8" %> - - - -<% - File file = new File(this.getServletContext().getRealPath("../../images/cookie.gif")); - - // 二进制数组 - byte[] binary = new byte[(int) file.length()]; - - // 从图片文件读取二进制数据. - InputStream ins = this.getServletContext().getResourceAsStream(file.getName()); - ins.read(binary); - ins.close(); - - // BASE64 编码 - String content = BASE64Encoder.class.newInstance().encode(binary); - - // 包含二进制数据的 Cookie - Cookie cookie = new Cookie("file", content); - - // 将 Cookie 发送到客户端 - response.addCookie(cookie); -%> - - - - Cookie Encoding - - -从 Cookie 中获取到的二进制图片:
    - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/base64_decode.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/base64_decode.jsp deleted file mode 100644 index e317b628..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/base64_decode.jsp +++ /dev/null @@ -1,32 +0,0 @@ -<%@ page language="java" pageEncoding="UTF-8" %> - - -<% - // 清除输出 - out.clear(); - - for (Cookie cookie : request.getCookies()) { - - if (cookie.getName().equals("file")) { - - // 从 Cookie 中取二进制数据 - byte[] binary = BASE64Decoder.class.newInstance().decodeBuffer(cookie.getValue().replace(" ", "")); - - // 设置内容类型为 gif 图片 - response.setHeader("Content-Type", "image/gif"); - response.setHeader("Content-Disposition", "inline;filename=cookie.gif"); - response.setHeader("Connection", "close"); - - // 设置长度 - response.setContentLength(binary.length); - - // 输出到客户端 - response.getOutputStream().write(binary); - response.getOutputStream().flush(); - response.getOutputStream().close(); - - return; - } - } - -%> diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/cookie.gif b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/cookie.gif deleted file mode 100644 index 6c6bd580872e46427d52e49b46612c19dacce7dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 592 zcmZ?wbhEHb6krfwc*el+?{@LO+a>>Qm;Jxj`0rlh|A$@w9(4VG)c@~M-~Y$c|2>)h z|LMH{&*uGmI`7}JdH)-y6 z{|6)f9gO&YF!JBQ$p1hsr_2AHuK0YU;@`#gf0sHQAME~rd;Y5vlm0(gT^%S~7c9~m zA=VrwRu>}L5+U9lE7=tz*%d3{jASsC)D=O>?>lXPrW!qJ%tM`tBm zT9STYaoU;rDW~Qop8$%_PCPOz;quaqOG`2?EzP*NIQ{&hv~!En&MrtjJul_t+~ni4 zla9|xIyyV?#G?Fj3sTQ7OglO=@!Y!Bt9$0%J+t%w|Njis1ByRc7#SGS7<52Jg5rdM zeQiTpQ*&x?aB6dBSE!U!aMy%XA0f$*jtM}2o0?1b&S*;U6}7Y!4^LSTAz)!;t0b5> zBR)Yy(aze8KQexTAFGzJgT09sn_pMFyRe#(le3W;Q`FvOe+Fe0Lsu6ARaM65=J)_+ zP4yQqG&NW}uEuca>gmYayUA6z3obJRnSdDW7c4h+@+$P5Uu diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/cookie.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/cookie.jsp deleted file mode 100644 index e6d40bbd..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/cookie.jsp +++ /dev/null @@ -1,81 +0,0 @@ -<%@ page language="java" pageEncoding="UTF-8" errorPage="login.jsp" %> -<% - request.setCharacterEncoding("UTF-8"); - - String username = ""; - int visitTimes = 0; - - // 所有的 cookie - Cookie[] cookies = request.getCookies(); - - // 遍历所有的 Cookie 寻找 用户帐号信息与登录次数信息 - for (int i = 0; cookies != null && i < cookies.length; i++) { - Cookie cookie = cookies[i]; - if ("username".equals(cookie.getName())) { - username = cookie.getValue(); - } else if ("visitTimes".equals(cookie.getName())) { - visitTimes = Integer.parseInt(cookie.getValue()); - cookie.setValue("" + ++visitTimes); - } - } - - // 如果没有找到 Cookie 中保存的用户名,则转到登录界面 - if (username == null || username.trim().equals("")) { - throw new Exception("您还没有登录。请先登录"); - } - - // 修改 Cookie,更新用户的访问次数 - Cookie visitTimesCookie = new Cookie("visitTimes", Integer.toString(++visitTimes)); - response.addCookie(visitTimesCookie); - -%> - - - - - Cookie - - - - - - - - -
    -
    - 登录信息 -
    - - - - - - - - - - - - - -
    - 您的帐号: - - <%= username %> -
    - 登录次数: - - <%= visitTimes %> -
    - - -
    -
    -
    -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/cookieAttribute.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/cookieAttribute.jsp deleted file mode 100644 index 2fda1553..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/cookieAttribute.jsp +++ /dev/null @@ -1,30 +0,0 @@ -<%@ page language="java" pageEncoding="UTF-8" errorPage="login.jsp" %> -<% - - Cookie[] cc = request.getCookies(); - - out.println(cc); - - Cookie cookie = new Cookie("password1", "babyface009988"); - - cookie.setPath(request.getContextPath()); - - response.addCookie(cookie); - -%> - - - - - Cookie - - - - - - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/cookieDomain.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/cookieDomain.jsp deleted file mode 100644 index 4bfa7b8b..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/cookieDomain.jsp +++ /dev/null @@ -1,27 +0,0 @@ -<%@ page language="java" pageEncoding="UTF-8" errorPage="login.jsp" %> -<% - - Cookie cookie = new Cookie("time", "20080808"); - cookie.setDomain(".h_google.com"); - cookie.setPath("/"); - cookie.setMaxAge(Integer.MAX_VALUE); - - response.addCookie(cookie); - -%> - - - - - Cookie - - - - - - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/encoding.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/encoding.jsp deleted file mode 100644 index 02391beb..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/encoding.jsp +++ /dev/null @@ -1,33 +0,0 @@ -<%@ page language="java" pageEncoding="UTF-8" %> - - -<% - // 使用中文的 Cookie. name 与 value 都使用 UTF-8 编码. - Cookie cookie = new Cookie(URLEncoder.encode("姓名", "UTF-8"), URLEncoder.encode("张三", "UTF-8")); - - // 发送到客户端 - response.addCookie(cookie); -%> - - - - Cookie Encoding - - -<% - Cookie[] cookies = request.getCookies(); - if (null != cookies) { - for (int i = 0; i < cookies.length; i++) { - - String cookieName = URLDecoder.decode(cookies[i].getName(), "UTF-8"); - String cookieValue = URLDecoder.decode(cookies[i].getValue(), "UTF-8"); - - out.println(cookieName + "="); - out.println(cookieValue + ";
    "); - } - } else { - out.println("Cookie 已经写入客户端. 请刷新页面. "); - } -%> - - diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/history.js b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/history.js deleted file mode 100644 index 52dee341..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/history.js +++ /dev/null @@ -1,27 +0,0 @@ -function getCookie(name) { - var str = document.cookie; - if (!str || str.indexOf(name + '=') < 0) return; - var cookies = str.split('; '); - for (var i = 0; i < cookies.length; i++) { - var cookie = cookies[i]; - if (cookie.indexOf(name + '=') == 0) { - var value = cookie.substring(name.length + 1) - } - } -} - -function setCookie(name, value) { - var expires = (arguments.length > 2) ? arguments[2] : null; - var path = (arguments.length > 3) ? arguments[3] : null; - var domain = (arguments.length > 4) ? arguments[4] : null; - var secure = (arguments.length > 5) ? arguments[5] : false; - document.cookie = name - + '=' - + encodeURI(value) - + ((expires == null) ? '' : ('; expires=' + expires.toGMTString())) - + ((path == null) ? '' : ('; path=' + path)) - + ((domain == null) ? '' : ('; domain=' + domain)) - + ((secure == true) ? '; secure' : '') -} - -var ss = getCookie('history'); diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/history.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/history.jsp deleted file mode 100644 index 55a2886a..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/history.jsp +++ /dev/null @@ -1,64 +0,0 @@ -<%@ page language="java" pageEncoding="UTF-8" isErrorPage="false" %> -<%! - -%> -<% - - -%> - - - - . - - - -
    -
    - 当前有效的 Cookie - -
    -
    - 历史记录 -
    - - - - - - - - - - - - - - - - - -
    - 帐号: - - -
    - 密码: - - -
    - 有效期: - - 关闭浏览器即失效
    - 30天内有效
    - 永久有效
    -
    - - -
    -
    -
    -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/javascript.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/javascript.jsp deleted file mode 100644 index 1060d5d5..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/javascript.jsp +++ /dev/null @@ -1,106 +0,0 @@ -<%@ page language="java" pageEncoding="UTF-8" %> - - - - 欢迎您 - - - - -
    -
    - 当前有效的 Cookie -
    - -
    -
    - - 欢迎您 - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - 读取 Cookie: - - -
    -   - -
    - 设置 Cookie: - -
    - Name 属性: - - -
    - Value 属性: - - -
    - - -
    -
    -
    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/login.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/login.jsp deleted file mode 100644 index 09f2a0a3..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/login.jsp +++ /dev/null @@ -1,68 +0,0 @@ -<%@ page language="java" pageEncoding="UTF-8" isErrorPage="true" %> -<% - request.setCharacterEncoding("UTF-8"); - response.setCharacterEncoding("UTF-8"); - - if ("POST".equals(request.getMethod())) { - - Cookie usernameCookie = new Cookie("username", request.getParameter("username")); - Cookie visittimesCookie = new Cookie("visitTimes", "0"); - - response.addCookie(usernameCookie); - response.addCookie(visittimesCookie); - - response.sendRedirect(request.getContextPath() + "/cookie.jsp"); - - return; - } -%> - - - - 请先登录 - - - -
    -
    - 登录 -
    - - - - - - - - - - - - - - - - - -
    - - - <%= exception.getMessage() %> -
    - 帐号: - - -
    - 密码: - - -
    - - -
    -
    -
    -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/loginCookie.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/loginCookie.jsp deleted file mode 100644 index 00ce6801..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/loginCookie.jsp +++ /dev/null @@ -1,165 +0,0 @@ -<%@ page language="java" pageEncoding="UTF-8" isErrorPage="false" %> - -<%! - // 密钥 - private static final String KEY = ":cookie@helloweenvsfei.com"; - - // MD5 加密算法 - public final static String calcMD5(String ss) { - - String s = ss == null ? "" : ss; - - char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - try { - byte[] strTemp = s.getBytes(); - MessageDigest mdTemp = MessageDigest.getInstance("MD5"); - mdTemp.update(strTemp); - byte[] md = mdTemp.digest(); - int j = md.length; - char[] str = new char[j * 2]; - int k = 0; - for (int i = 0; i < j; i++) { - byte byte0 = md[i]; - str[k++] = hexDigits[byte0 >>> 4 & 0xf]; - str[k++] = hexDigits[byte0 & 0xf]; - } - return new String(str); - } catch (Exception e) { - return null; - } - } - -%> -<% - request.setCharacterEncoding("UTF-8"); - response.setCharacterEncoding("UTF-8"); - - String action = request.getParameter("action"); - - if ("login".equals(action)) { - - String account = request.getParameter("account"); - String password = request.getParameter("password"); - int timeout = new Integer(request.getParameter("timeout")); - - // 把帐号连同密钥使用MD5后加密后保存 - String ssid = calcMD5(account + KEY); - - // 把帐号保存到Cookie中 并控制有效期 - Cookie accountCookie = new Cookie("account", account); - accountCookie.setMaxAge(timeout); - - // 把加密结果保存到Cookie中 并控制有效期 - Cookie ssidCookie = new Cookie("ssid", ssid); - ssidCookie.setMaxAge(timeout); - - response.addCookie(accountCookie); - response.addCookie(ssidCookie); - - // 重新请求本页面 - response.sendRedirect(request.getRequestURI() + "?" + System.currentTimeMillis()); - return; - } else if ("logout".equals(action)) { - - // 删除Cookie中的帐号 - Cookie accountCookie = new Cookie("account", ""); - accountCookie.setMaxAge(0); - - // 删除Cookie中的加密结果 - Cookie ssidCookie = new Cookie("ssid", ""); - ssidCookie.setMaxAge(0); - - response.addCookie(accountCookie); - response.addCookie(ssidCookie); - - // 重新请求本页面 - response.sendRedirect(request.getRequestURI() + "?" + System.currentTimeMillis()); - return; - } - - boolean loggin = false; - - String account = null; - String ssid = null; - - // 获取Cookie中的account与ssid - if (request.getCookies() != null) { - for (Cookie cookie : request.getCookies()) { - if (cookie.getName().equals("account")) { - account = cookie.getValue(); - } - if (cookie.getName().equals("ssid")) { - ssid = cookie.getValue(); - } - } - } - - if (account != null && ssid != null) { - // 如果加密规则正确, 则视为已经登录 - loggin = ssid.equals(calcMD5(account + KEY)); - } - -%> - - - - <%= loggin ? "欢迎您回来" : "请先登录" %> - - - - -
    -
    - 当前有效的 Cookie - -
    -
    - <%= loggin ? "欢迎您回来" : "请先登录" %> - - <% if (loggin) { %> - 欢迎您, ${ cookie.account.value }.    - 注销 - <% } else { %> -
    - - - - - - - - - - - - - - - - - -
    - 帐号: - - -
    - 密码: - - -
    - 有效期: - - 关闭浏览器即失效
    - 30天内有效
    - 永久有效
    -
    - - -
    -
    - <% } %> -
    -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/maxAge.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/maxAge.jsp deleted file mode 100644 index afc2ff95..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/maxAge.jsp +++ /dev/null @@ -1,26 +0,0 @@ -<%@ page language="java" pageEncoding="UTF-8" %> -<% - Cookie cookie = new Cookie("username", "helloweenvsfei"); - cookie.setMaxAge(0); - - // 必须执行这一句 - response.addCookie(cookie); - -%> - - - - - Cookie maxAge - - - - - - - - - -This is my JSP page.
    - - diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/setCookie.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/setCookie.jsp deleted file mode 100644 index bf6c1234..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/cookie/setCookie.jsp +++ /dev/null @@ -1,109 +0,0 @@ -<%@ page language="java" pageEncoding="UTF-8" %> - -<%! - boolean isNull(String str) { - return str == null || str.trim().length() == 0; - } -%> -<% - request.setCharacterEncoding("UTF-8"); - - if ("POST".equals(request.getMethod())) { - - String name = request.getParameter("name"); - String value = request.getParameter("value"); - String maxAge = request.getParameter("maxAge"); - String domain = request.getParameter("domain"); - String path = request.getParameter("path"); - String comment = request.getParameter("comment"); - String secure = request.getParameter("secure"); - - if (!isNull(name)) { - - Cookie cookie = new Cookie(URLEncoder.encode(name, "UTF-8"), URLEncoder.encode(value, "UTF-8")); - - if (!isNull(maxAge)) { - cookie.setMaxAge(Integer.parseInt(maxAge)); - } - if (!isNull(domain)) { - cookie.setDomain(domain); - } - if (!isNull(path)) { - cookie.setPath(path); - } - if (!isNull(comment)) { - cookie.setComment(comment); - } - if (!isNull(secure)) { - cookie.setSecure("true".equalsIgnoreCase(secure)); - } - - response.addCookie(cookie); - } - } -%> - - - - Cookie - - - - - - - - -
    -
    - 当前有效的 Cookie - -
    -
    - 设置新 Cookie -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    name:
    value:
    maxAge:
    domain:
    path:
    comment:
    secure:
    - - -
    -
    -
    -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/session/session.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/session/session.jsp deleted file mode 100644 index 1a547152..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/session/session.jsp +++ /dev/null @@ -1,94 +0,0 @@ -<%@ page language="java" pageEncoding="UTF-8" %> - - - -<%@ page import="java.util.Date" %> -<%! - DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); -%> -<% - response.setCharacterEncoding("UTF-8"); - - Person[] persons = {new Person("Liu Jinghua", "password1", 34, dateFormat.parse("1982-01-01")), - new Person("Hello Kitty", "hellokitty", 23, dateFormat.parse("1984-02-25")), - new Person("Garfield", "garfield_pass", 23, dateFormat.parse("1994-09-12")),}; - - String message = ""; - - if (request.getMethod().equals("POST")) { - - for (int i = 0; i < persons.length; i++) { - // 如果 用户名正确 且 密码正确 - if (persons[i].getName().equalsIgnoreCase(request.getParameter("username")) && persons[i].getPassword() - .equals( - request.getParameter( - "password"))) { - - // 登录成功, 设置将用户的信息以及登录时间保存到 Session - session.setAttribute("person", persons[i]); - session.setAttribute("loginTime", new Date()); - - response.sendRedirect(request.getContextPath() + "welcome.jsp"); - return; - } - } - - // 登录失败 - message = "用户名密码不匹配,登录失败。"; - } - -%> - - - - 请先登录 - - - -
    -
    - 登录 -
    - - <% if (!message.equals("")) { %> - - - - - <% } %> - - - - - - - - - - - - -
    - - - <%= message %> -
    - 帐号: - - -
    - 密码: - - -
    - - -
    -
    -
    -
    - -Hello Kitty, hellokitty - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/session/vote.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/session/vote.jsp deleted file mode 100644 index 17d7c865..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/session/vote.jsp +++ /dev/null @@ -1,52 +0,0 @@ -<%@ page language="java" pageEncoding="UTF-8" %> - - - - 欢迎您 - - - -
    -
    - 投票 - - - - - - - - - - - - - - - - - - - - - -
    - 您的姓名: - -
    - -
    - 您的年龄: - -
    - 您的生日: - -
    - - -
    -
    -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/session/welcome.jsp b/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/session/welcome.jsp deleted file mode 100644 index 5cb7b51b..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/main/webapp/views/jsp/session/welcome.jsp +++ /dev/null @@ -1,63 +0,0 @@ -<%@ page language="java" pageEncoding="UTF-8" %> -<% - if (session.getAttribute("person") == null) { - response.sendRedirect("session.jsp"); - return; - } -%> - - - - 欢迎您, ${ person.name } - - - -
    -
    - 欢迎您${ person.name } - - - - - - - - - - - - - - - - - - - - - -
    - 您的姓名: - - ${ person.name } -
    - 登录时间: - - ${ loginTime } -
    - 您的年龄: - - ${ person.age } -
    - 您的生日: - - ${ person.birthday } -
    - - -
    -
    -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java b/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java deleted file mode 100644 index 6ac1a20f..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java +++ /dev/null @@ -1,114 +0,0 @@ -package io.github.dunwu.javaee.server; - -import java.util.ArrayList; -import org.apache.commons.lang3.StringUtils; -import org.assertj.core.util.Lists; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.webapp.WebAppClassLoader; -import org.eclipse.jetty.webapp.WebAppContext; - -/** - * JettyFactory 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 - * - * @author Zhang Peng - */ -@SuppressWarnings("unused") -public class JettyFactory { - - public static final int IDE_ECLIPSE = 0; - - public static final int IDE_INTELLIJ = 1; - - private static final int PORT = 9899; - - private static final String CONTEXT = "/"; - - private static final String RESOURCE_BASE_PATH = "src/main/webapp"; - - private static final String WEB_XML_PATH = "/WEB-INF/web.xml"; - - private static final String[] TLD_JAR_NAMES = new String[] {"sitemesh", "spring-webmvc", "shiro-web", "tiles"} - - private static final String WINDOWS_WEBDEFAULT_PATH = "jetty/webdefault.xml"; - - public static Server initServer() { - Profiles.setProfileAsSystemProperty(Profiles.DEVELOPMENT); - WebAppContext webAppContext = new WebAppContext(); - Server server = new Server(PORT); - server.setHandler(webAppContext); - return server; - } - - public static void initWebAppContext(Server server, int type) throws Exception { - System.out.println("[INFO] Application loading"); - WebAppContext webAppContext = (WebAppContext) server.getHandler(); - webAppContext.setContextPath(CONTEXT); - webAppContext.setResourceBase(getAbsolutePath() + RESOURCE_BASE_PATH); - webAppContext.setDescriptor(getAbsolutePath() + RESOURCE_BASE_PATH + WEB_XML_PATH); - - if (IDE_INTELLIJ == type) { - webAppContext.setDefaultsDescriptor(WINDOWS_WEBDEFAULT_PATH); - supportJspAndSetTldJarNames(server, TLD_JAR_NAMES); - } else { - webAppContext.setParentLoaderPriority(true); - } - - System.out.println("[INFO] Application loaded"); - } - - public static String getAbsolutePath() { - String path = null; - String folderPath = JettyFactory.class.getProtectionDomain().getCodeSource().getLocation().getPath() - .substring(1); - if (folderPath.indexOf("target") > 0) { - path = folderPath.substring(0, folderPath.indexOf("target")); - } - return path; - } - - public static void supportJspAndSetTldJarNames(Server server, String... jarNames) { - WebAppContext context = (WebAppContext) server.getHandler(); - // This webapp will use jsps and jstl. We need to enable the - // AnnotationConfiguration in - // order to correctly set up the jsp container - org.eclipse.jetty.webapp.Configuration.ClassList classlist = org.eclipse.jetty.webapp.Configuration.ClassList - .setServerDefault(server); - classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", - "org.eclipse.jetty.annotations.AnnotationConfiguration"); - // Set the ContainerIncludeJarPattern so that jetty examines these container-path - // jars for - // tlds, web-fragments etc. - // If you omit the jar that contains the jstl .tlds, the jsp engine will scan for - // them - // instead. - ArrayList jarNameExprssions = Lists.newArrayList(".*/[^/]*servlet-api-[^/]*\\.jar$", - ".*/javax.servlet.jsp.jstl-.*\\.jar$", ".*/[^/]*taglibs.*\\.jar$"); - - for (String jarName : jarNames) { - jarNameExprssions.add(".*/" + jarName + "-[^/]*\\.jar$"); - } - - context.setAttribute("org.eclipse.jetty.io.github.dunwu.javaee.server.webapp.ContainerIncludeJarPattern", - StringUtils.join(jarNameExprssions, '|')); - } - - public static void reloadWebAppContext(Server server) throws Exception { - WebAppContext webAppContext = (WebAppContext) server.getHandler(); - System.out.println("[INFO] Application reloading"); - webAppContext.stop(); - WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext); - classLoader.addClassPath(getAbsolutePath() + "target/classes"); - classLoader.addClassPath(getAbsolutePath() + "target/test-classes"); - webAppContext.setClassLoader(classLoader); - webAppContext.start(); - System.out.println("[INFO] Application reloaded"); - } - - public static void startServer(Server server) throws Exception { - System.out.println("[HINT] Don't forget to set -XX:MaxPermSize=128m"); - server.start(); - System.out.println("Server running at http://localhost:" + PORT + CONTEXT); - System.out.println("[HINT] Hit Enter to reload the application quickly"); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/Profiles.java b/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/Profiles.java deleted file mode 100644 index dcfd3e39..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/Profiles.java +++ /dev/null @@ -1,34 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2005, 2014 springside.github.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - *******************************************************************************/ -package io.github.dunwu.javaee.server; - -/** - * Spring profile 常用方法与profile名称。 - * - * @author calvin - */ -public class Profiles { - - public static final String ACTIVE_PROFILE = "spring.profiles.active"; - - public static final String DEFAULT_PROFILE = "spring.profiles.default"; - - public static final String PRODUCTION = "production"; - - public static final String DEVELOPMENT = "development"; - - public static final String UNIT_TEST = "test"; - - public static final String FUNCTIONAL_TEST = "functional"; - - /** - * 在Spring启动前,设置profile的环境变量。 - */ - public static void setProfileAsSystemProperty(String profile) { - System.setProperty(ACTIVE_PROFILE, profile); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java b/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java deleted file mode 100644 index 049c8802..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.github.dunwu.javaee.server; - -import org.eclipse.jetty.server.Server; - -/** - * 快速启动 jetty 服务器,方便测试 - * - * @author Zhang Peng - */ -public class QuickStartServer { - - // private static int STARTUP_TYPE = JettyFactory.IDE_ECLIPSE; - private static int STARTUP_TYPE = JettyFactory.IDE_INTELLIJ; - - public static void main(String[] args) throws Exception { - Server server = JettyFactory.initServer(); - JettyFactory.initWebAppContext(server, STARTUP_TYPE); - - try { - JettyFactory.startServer(server); - - // 等待用户输入回车重载应用 - while (true) { - char c = (char) System.in.read(); - if (c == '\n') { - JettyFactory.reloadWebAppContext(server); - } - } - } catch (Exception e) { - e.printStackTrace(); - System.exit(-1); - } - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-session/src/test/resources/jetty/webdefault.xml b/codes/javaee-tutorial/javaee-tutorial-session/src/test/resources/jetty/webdefault.xml deleted file mode 100644 index b991d44c..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-session/src/test/resources/jetty/webdefault.xml +++ /dev/null @@ -1,534 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - Default web.xml file. - This file is applied to a Web application before it's own WEB_INF/web.xml file - - - - - - - - org.eclipse.jetty.servlet.listener.ELContextCleaner - - - - - - - - org.eclipse.jetty.servlet.listener.IntrospectorCleaner - - - - - - - - - - - - - - - - - default - org.eclipse.jetty.servlet.DefaultServlet - - aliases - false - - - acceptRanges - true - - - dirAllowed - true - - - welcomeServlets - false - - - redirectWelcome - false - - - maxCacheSize - 256000000 - - - maxCachedFileSize - 200000000 - - - maxCachedFiles - 2048 - - - gzip - false - - - etags - false - - - useFileMappedBuffer - false - - - - 0 - - - - default - / - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - jsp - org.eclipse.jetty.jsp.JettyJspServlet - - logVerbosityLevel - DEBUG - - - fork - false - - - xpoweredBy - false - - - compilerTargetVM - 1.7 - - - compilerSourceVM - 1.7 - - - 0 - - - - jsp - *.jsp - *.jspf - *.jspx - *.xsp - *.JSP - *.JSPF - *.JSPX - *.XSP - - - - - - - - 30 - - - - - - - - - - - - - - - index.html - index.htm - index.jsp - - - - - - - - ar - ISO-8859-6 - - - be - ISO-8859-5 - - - bg - ISO-8859-5 - - - ca - ISO-8859-1 - - - cs - ISO-8859-2 - - - da - ISO-8859-1 - - - de - ISO-8859-1 - - - el - ISO-8859-7 - - - en - ISO-8859-1 - - - es - ISO-8859-1 - - - et - ISO-8859-1 - - - fi - ISO-8859-1 - - - fr - ISO-8859-1 - - - hr - ISO-8859-2 - - - hu - ISO-8859-2 - - - is - ISO-8859-1 - - - it - ISO-8859-1 - - - iw - ISO-8859-8 - - - ja - Shift_JIS - - - ko - EUC-KR - - - lt - ISO-8859-2 - - - lv - ISO-8859-2 - - - mk - ISO-8859-5 - - - nl - ISO-8859-1 - - - no - ISO-8859-1 - - - pl - ISO-8859-2 - - - pt - ISO-8859-1 - - - ro - ISO-8859-2 - - - ru - ISO-8859-5 - - - sh - ISO-8859-5 - - - sk - ISO-8859-2 - - - sl - ISO-8859-2 - - - sq - ISO-8859-2 - - - sr - ISO-8859-5 - - - sv - ISO-8859-1 - - - tr - ISO-8859-9 - - - uk - ISO-8859-5 - - - zh - GB2312 - - - zh_TW - Big5 - - - - - - - - - Disable TRACE - / - TRACE - - - - - - Enable everything but TRACE - / - TRACE - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/pom.xml b/codes/javaee-tutorial/javaee-tutorial-taglib/pom.xml deleted file mode 100644 index ecc38dd2..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/pom.xml +++ /dev/null @@ -1,123 +0,0 @@ - - - 4.0.0 - - - io.github.dunwu - javaee-tutorial-taglib - 1.0.0 - war - - - - - javaee-tutorial-taglib - JavaEE 学习笔记之 taglib - - - - - - UTF-8 - 1.7 - ${java.version} - ${java.version} - - - - - - ch.qos.logback - logback-classic - - - org.slf4j - jcl-over-slf4j - - - - - - org.apache.commons - commons-lang3 - - - - - - javax.servlet - javax.servlet-api - provided - - - javax.servlet.jsp - javax.servlet.jsp-api - provided - - - - - - org.eclipse.jetty - jetty-webapp - test - - - org.eclipse.jetty - jetty-annotations - test - - - org.eclipse.jetty - apache-jsp - test - - - org.eclipse.jetty - apache-jstl - test - - - - - - junit - junit - test - - - org.assertj - assertj-core - test - - - - - - - xalan - xalan - 2.7.2 - - - xerces - xercesImpl - 2.11.0 - - - - - - - - io.github.dunwu - dunwu-dependencies - 0.4.2-SNAPSHOT - pom - import - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/Copyright.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/Copyright.java deleted file mode 100644 index a1208fb2..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/Copyright.java +++ /dev/null @@ -1,62 +0,0 @@ -/** - * The Apache License 2.0 Copyright (c) 2017 Zhang Peng - */ -package io.github.dunwu.javaee.taglib; - -import java.io.IOException; -import java.util.ResourceBundle; -import javax.servlet.jsp.JspException; -import javax.servlet.jsp.JspWriter; -import javax.servlet.jsp.PageContext; -import javax.servlet.jsp.tagext.Tag; - -/** - * @author Zhang Peng - * @since 2017/4/3. - */ -public class Copyright implements Tag { - - private Tag parent; - - private PageContext pageContext; - - @Override - public int doEndTag() throws JspException { - JspWriter out = pageContext.getOut(); - - try { - out.println("
    "); - out.println(ResourceBundle.getBundle("copyright").getString("copyright")); - out.println("
    "); - } catch (IOException e) { - throw new JspException(e); - } - - return EVAL_PAGE; - } - - @Override - public int doStartTag() throws JspException { - return SKIP_BODY; - } - - @Override - public Tag getParent() { - return this.parent; - } - - @Override - public void setParent(Tag parent) { - this.parent = parent; - } - - @Override - public void release() { - } - - @Override - public void setPageContext(PageContext pageContext) { - this.pageContext = pageContext; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag.java deleted file mode 100644 index 348c1d2e..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag.java +++ /dev/null @@ -1,24 +0,0 @@ -/** - * The Apache License 2.0 Copyright (c) 2017 Zhang Peng - */ -package io.github.dunwu.javaee.taglib; - -/** - * @author Zhang Peng - * @since 2017/4/3. - */ - -import java.io.IOException; -import javax.servlet.jsp.JspException; -import javax.servlet.jsp.JspWriter; -import javax.servlet.jsp.tagext.SimpleTagSupport; - -public class HelloTag extends SimpleTagSupport { - - @Override - public void doTag() throws JspException, IOException { - JspWriter out = getJspContext().getOut(); - out.println("Hello Custom Tag!"); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag2.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag2.java deleted file mode 100644 index 604014b7..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag2.java +++ /dev/null @@ -1,24 +0,0 @@ -/** - * The Apache License 2.0 Copyright (c) 2017 Zhang Peng - */ -package io.github.dunwu.javaee.taglib; - -import java.io.IOException; -import java.io.StringWriter; -import javax.servlet.jsp.JspException; -import javax.servlet.jsp.tagext.SimpleTagSupport; - -/** - * @author Zhang Peng - * @since 2017/4/3. - */ -public class HelloTag2 extends SimpleTagSupport { - - StringWriter sw = new StringWriter(); - - public void doTag() throws JspException, IOException { - getJspBody().invoke(sw); - getJspContext().getOut().println(sw.toString()); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag3.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag3.java deleted file mode 100644 index 5aeb9a3a..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/HelloTag3.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * The Apache License 2.0 Copyright (c) 2017 Zhang Peng - */ -package io.github.dunwu.javaee.taglib; - -import java.io.IOException; -import java.io.StringWriter; -import javax.servlet.jsp.JspException; -import javax.servlet.jsp.JspWriter; -import javax.servlet.jsp.tagext.SimpleTagSupport; - -/** - * @author Zhang Peng - * @since 2017/4/3. - */ -public class HelloTag3 extends SimpleTagSupport { - - StringWriter sw = new StringWriter(); - - private String message; - - public void setMessage(String msg) { - this.message = msg; - } - - public void doTag() throws JspException, IOException { - if (message != null) { - /* 从属性中使用消息 */ - JspWriter out = getJspContext().getOut(); - out.println(message); - } else { - /* 从内容体中使用消息 */ - getJspBody().invoke(sw); - getJspContext().getOut().println(sw.toString()); - } - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/bean/Person.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/bean/Person.java deleted file mode 100644 index 07868678..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/bean/Person.java +++ /dev/null @@ -1,105 +0,0 @@ -package io.github.dunwu.javaee.taglib.bean; - -public class Person { - - private int id; - - private String name; - - private String sex; - - private int age; - - private String telephone; - - private String birthday; - - private String mobile; - - private String address; - - private String city; - - private boolean deleted; - - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getSex() { - return sex; - } - - public void setSex(String sex) { - this.sex = sex; - } - - public int getAge() { - return age; - } - - public void setAge(int age) { - this.age = age; - } - - public boolean isDeleted() { - return deleted; - } - - public void setDeleted(boolean deleted) { - this.deleted = deleted; - } - - public String getTelephone() { - return telephone; - } - - public void setTelephone(String telephone) { - this.telephone = telephone; - } - - public String getBirthday() { - return birthday; - } - - public void setBirthday(String birthday) { - this.birthday = birthday; - } - - public String getMobile() { - return mobile; - } - - public void setMobile(String mobile) { - this.mobile = mobile; - } - - public String getAddress() { - return address; - } - - public void setAddress(String address) { - this.address = address; - } - - public String getCity() { - return city; - } - - public void setCity(String city) { - this.city = city; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/function/Function.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/function/Function.java deleted file mode 100644 index 9858e9c6..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/function/Function.java +++ /dev/null @@ -1,62 +0,0 @@ -package io.github.dunwu.javaee.taglib.function; - -import java.util.Collection; - -public class Function { - - /** - * 返回字节长度 - * - * @param obj - * @return - */ - @SuppressWarnings("unchecked") - public static int length(Object obj) { - - if (obj == null) { - return 0; - } - - if (obj instanceof StringBuffer) { - return length(((StringBuffer) obj).toString()); - } - - if (obj instanceof String) { - return ((String) obj).getBytes().length; - } - - if (obj instanceof Collection) { - return ((Collection) obj).size(); - } - - return 0; - } - - public static String substring(String str, int byteLength) { - - if (str == null) { - return ""; - } - - StringBuffer buffer = new StringBuffer(); - - for (int i = 0; i < str.length(); i++) { - char ch = str.charAt(i); - if (length(buffer.toString() + ch) > byteLength) { - break; - } else { - buffer.append(ch); - } - } - - return buffer.toString(); - } - - public static void main(String[] args) { - - System.out.println(length("中文测试")); - - System.out.println(substring("中文测试", 5)); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/AddTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/AddTag.java deleted file mode 100644 index a8f23eac..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/AddTag.java +++ /dev/null @@ -1,32 +0,0 @@ -package io.github.dunwu.javaee.taglib.tags; - -import javax.servlet.jsp.JspException; -import javax.servlet.jsp.tagext.TagSupport; - -public class AddTag extends TagSupport { - - private static final long serialVersionUID = -579746915908972833L; - - private int num1; - - private int num2; - - public void setNum1(int num1) { - this.num1 = num1; - } - - public void setNum2(int num2) { - this.num2 = num2; - } - - @Override - public int doEndTag() throws JspException { - try { - this.pageContext.getOut().println(num1 + " + " + num2 + " = " + (num1 + num2)); - } catch (Exception e) { - e.printStackTrace(); - } - return EVAL_PAGE; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/Copyright.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/Copyright.java deleted file mode 100644 index 649a5aa4..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/Copyright.java +++ /dev/null @@ -1,57 +0,0 @@ -package io.github.dunwu.javaee.taglib.tags; - -import java.io.IOException; -import java.util.ResourceBundle; -import javax.servlet.jsp.JspException; -import javax.servlet.jsp.JspWriter; -import javax.servlet.jsp.PageContext; -import javax.servlet.jsp.tagext.Tag; - -public class Copyright implements Tag { - - private Tag parent; - - private PageContext pageContext; - - @Override - public int doEndTag() throws JspException { - JspWriter out = pageContext.getOut(); - - try { - out.println("
    "); - out.println(ResourceBundle.getBundle("copyright").getString("copyright")); - out.println("
    "); - } catch (IOException e) { - throw new JspException(e); - } - - return EVAL_PAGE; - } - - @Override - public int doStartTag() throws JspException { - return SKIP_BODY; - } - - @Override - public Tag getParent() { - return this.parent; - } - - @Override - public void setParent(Tag parent) { - this.parent = parent; - } - - @Override - public void release() { - } - - @Override - public void setPageContext(PageContext pageContext) { - this.pageContext = pageContext; - } - -} - -// end diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/Copyright2.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/Copyright2.java deleted file mode 100644 index 93e6c32f..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/Copyright2.java +++ /dev/null @@ -1,33 +0,0 @@ -package io.github.dunwu.javaee.taglib.tags; - -import java.io.IOException; -import java.util.ResourceBundle; -import javax.servlet.jsp.JspException; -import javax.servlet.jsp.JspWriter; -import javax.servlet.jsp.tagext.TagSupport; - -public class Copyright2 extends TagSupport { - - private static final long serialVersionUID = -2936770589554413334L; - - @Override - public int doEndTag() throws JspException { - JspWriter out = pageContext.getOut(); - - try { - out.println("
    "); - out.println(ResourceBundle.getBundle("copyright").getString("copyright")); - out.println("
    "); - } catch (IOException e) { - throw new JspException(e); - } - - return EVAL_PAGE; - } - - @Override - public int doStartTag() throws JspException { - return super.doStartTag(); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/DynamicAttributeTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/DynamicAttributeTag.java deleted file mode 100644 index 69edd78d..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/DynamicAttributeTag.java +++ /dev/null @@ -1,58 +0,0 @@ -package io.github.dunwu.javaee.taglib.tags; - -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; -import javax.servlet.jsp.JspException; -import javax.servlet.jsp.JspWriter; -import javax.servlet.jsp.tagext.DynamicAttributes; -import javax.servlet.jsp.tagext.TagSupport; - -public class DynamicAttributeTag extends TagSupport implements DynamicAttributes { - - private static final long serialVersionUID = -1477571708507488373L; - - private Map map = new HashMap(); - - @Override - public int doEndTag() throws JspException { - - JspWriter out = pageContext.getOut(); - - double min = 0, max = 0; - - for (Double d : map.values()) { - min = Math.min(d, min); - max = Math.max(d, max); - } - - StringBuffer buffer = new StringBuffer(); - - buffer.append(""); - - for (Entry entry : map.entrySet()) { - buffer.append(""); - buffer.append(""); - buffer.append(""); - buffer.append(""); - } - - buffer.append("
    " + entry.getKey() + " " + entry.getValue() + "
    "); - - try { - out.write(buffer.toString()); - } catch (Exception e) { - } - - return super.doEndTag(); - } - - @Override - public void setDynamicAttribute(String uri, String key, Object value) throws JspException { - - map.put(key, Double.parseDouble((String) value)); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/HelloTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/HelloTag.java deleted file mode 100644 index 68fe5624..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/HelloTag.java +++ /dev/null @@ -1,32 +0,0 @@ -package io.github.dunwu.javaee.taglib.tags; - -import javax.servlet.jsp.JspException; -import javax.servlet.jsp.tagext.TagSupport; - -public class HelloTag extends TagSupport { - - private static final long serialVersionUID = -8828591126748246256L; - - private String name; - - @Override - public int doEndTag() throws JspException { - - try { - this.pageContext.getOut().println("Hello, " + name); - } catch (Exception e) { - throw new JspException(e); - } - - return EVAL_PAGE; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/IteratorTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/IteratorTag.java deleted file mode 100644 index 22eaad19..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/IteratorTag.java +++ /dev/null @@ -1,31 +0,0 @@ -package io.github.dunwu.javaee.taglib.tags; - -import java.util.Collection; -import javax.servlet.jsp.JspException; -import javax.servlet.jsp.tagext.TagSupport; - -public class IteratorTag extends TagSupport { - - private static final long serialVersionUID = -8828591126748246256L; - - private Collection connection; - - @Override - public int doEndTag() throws JspException { - - try { - for (Object obj : connection) { - this.pageContext.getOut().println(obj + ",
    "); - } - } catch (Exception e) { - throw new JspException(e); - } - - return EVAL_PAGE; - } - - public void setConnection(Collection connection) { - this.connection = connection; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/LoopTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/LoopTag.java deleted file mode 100644 index d89122ff..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/LoopTag.java +++ /dev/null @@ -1,40 +0,0 @@ -package io.github.dunwu.javaee.taglib.tags; - -import javax.servlet.jsp.JspException; -import javax.servlet.jsp.tagext.BodyTagSupport; - -public class LoopTag extends BodyTagSupport { - - private static final long serialVersionUID = 5882067091737658241L; - - private int times; - - @Override - public int doStartTag() throws JspException { - times = 5; - return super.doStartTag(); - } - - @Override - public int doAfterBody() throws JspException { - - if (times-- > 0) { - - /** 只要 times > 0 就继续循环,同时 times 自减 */ - try { - this.getPreviousOut().println(this.getBodyContent().getString()); - } catch (Exception e) { - } - - return EVAL_BODY_AGAIN; - } else { - - /** 结束运行,同时复原 times */ - - times = 5; - - return SKIP_BODY; - } - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/ToLowerCaseTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/ToLowerCaseTag.java deleted file mode 100644 index 60e372b3..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/ToLowerCaseTag.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.github.dunwu.javaee.taglib.tags; - -import javax.servlet.jsp.JspException; -import javax.servlet.jsp.tagext.BodyTagSupport; - -public class ToLowerCaseTag extends BodyTagSupport { - - private static final long serialVersionUID = -2529343271020971948L; - - @Override - public int doEndTag() throws JspException { - - String content = this.getBodyContent().getString(); - - try { - this.pageContext.getOut().print(content.toLowerCase()); - } catch (Exception e) { - } - - return EVAL_PAGE; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/table/Column.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/table/Column.java deleted file mode 100644 index 016c7746..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/table/Column.java +++ /dev/null @@ -1,64 +0,0 @@ -package io.github.dunwu.javaee.taglib.tags.table; - -import java.util.HashMap; -import java.util.Map; -import javax.servlet.jsp.JspException; -import javax.servlet.jsp.tagext.TagSupport; - -public class Column extends TagSupport { - - private static final long serialVersionUID = 5119493903438602864L; - - private String property; - - private String label; - - private String type; - - public int doStartTag() throws JspException { - if (!(this.getParent() instanceof Table)) { - throw new JspException("Column must be inside Table. "); - } - - Map column = new HashMap(); - - column.put("label", label); - column.put("property", property); - column.put("type", type); - - Table table = (Table) this.getParent(); - - table.getColumns().add(column); - - return SKIP_BODY; - } - - public int doEndTag() throws JspException { - return EVAL_PAGE; - } - - public String getProperty() { - return property; - } - - public void setProperty(String property) { - this.property = property; - } - - public String getLabel() { - return label; - } - - public void setLabel(String label) { - this.label = label; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/table/Table.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/table/Table.java deleted file mode 100644 index 5b13dd31..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags/table/Table.java +++ /dev/null @@ -1,203 +0,0 @@ -package io.github.dunwu.javaee.taglib.tags.table; - -import java.io.IOException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.jsp.JspException; -import javax.servlet.jsp.JspWriter; -import javax.servlet.jsp.tagext.BodyContent; -import javax.servlet.jsp.tagext.BodyTagSupport; - -public class Table extends BodyTagSupport { - - private static final long serialVersionUID = 3358444196409845360L; - - /** - * 存储列信息 - */ - private List> columns = new ArrayList>(); - - /** - * 存储数据,可能为 集合类型的或者数组类型的 - */ - private Object items; - - /** - * 取排序数据的 URL - */ - private String url; - - @Override - public int doStartTag() throws JspException { - columns.clear(); - - return super.doStartTag(); - } - - @Override - @SuppressWarnings("unchecked") - public int doAfterBody() throws JspException { - try { - BodyContent bc = getBodyContent(); - JspWriter out = bc.getEnclosingWriter(); - - HttpServletRequest request = (HttpServletRequest) pageContext.getRequest(); - - /** 按哪一列排序 */ - String orderName = request.getParameter("orderName"); - /** 按升序还是降序排序 */ - String orderType = request.getParameter("orderType"); - - orderType = "desc".equals(orderType) ? "desc" : "asc"; - - out.println(""); - out.println(" "); - out.println(" "); - - for (int i = 0; i < columns.size(); i++) { - /** 获取列信息 */ - Map column = columns.get(i); - - /** 列头 */ - String label = column.get("label"); - /** 该列对应的 Java Bean 的属性 */ - String property = column.get("property"); - - label = label == null ? property : label; - - out.println(""); - out.println(""); - } - - out.println(" "); - - if (items != null) { - - /** 遍历所有的数据 */ - for (Object obj : (Iterable) items) { - - out.println(" "); - - for (int i = 0; i < columns.size(); i++) { - - Map column = columns.get(i); - - String property = column.get("property"); - - String getterStyle = toGetterStyle(property); - - try { - String getter = "get" + getterStyle; - String is = "is" + getterStyle; - - Method method = null; - - try { - /** 获取 getXxx() 形式的方法 */ - method = obj.getClass().getMethod(getter); - } catch (Exception e) { - } - - if (method == null) { - /** 如果没有,获取 isXxx() 形式的方法 */ - method = obj.getClass().getMethod(is); - } - - method.setAccessible(true); - - /** 获取属性值 */ - Object value = method.invoke(obj); - out.println(""); - } catch (Exception e) { - throw new JspException(e); - } - } - out.println(" "); - } - } - - out.println("
    "); - out.println(""); - - out.println(""); - - out.println(label); - - if (property.equals(orderName)) { - - out.println(""); - } - - out.println(""); - - out.println("
    " + value + "
    "); - - out.println(""); - } catch (IOException ioe) { - throw new JspException("Error: " + ioe.getMessage()); - } - - return SKIP_BODY; - } - - /** - * 首字母大写 - * - * @param column - * @return - */ - public String toGetterStyle(String column) { - if (column.length() == 1) { - return column.toUpperCase(); - } - - char ch = column.charAt(0); - - return Character.toUpperCase(ch) + column.substring(1); - } - - public Object getItems() { - return items; - } - - public void setItems(Object items) { - this.items = items; - } - - public List> getColumns() { - return columns; - } - - public void setColumns(List> columns) { - this.columns = columns; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/MultiAttributeTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/MultiAttributeTag.java deleted file mode 100644 index 610efed7..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/MultiAttributeTag.java +++ /dev/null @@ -1,44 +0,0 @@ -package io.github.dunwu.javaee.taglib.tags2; - -import java.io.IOException; -import java.io.StringWriter; -import javax.servlet.jsp.JspException; -import javax.servlet.jsp.tagext.JspFragment; -import javax.servlet.jsp.tagext.SimpleTagSupport; - -public class MultiAttributeTag extends SimpleTagSupport { - - private JspFragment body1; - - private JspFragment body2; - - public void setBody1(JspFragment body1) { - this.body1 = body1; - } - - public void setBody2(JspFragment body2) { - this.body2 = body2; - } - - @Override - public void doTag() throws JspException, IOException { - - StringWriter writer1 = new StringWriter(); - StringWriter writer2 = new StringWriter(); - - for (int i = 0; i < 5; i++) { - // body1 调用 5 次 - body1.invoke(writer1); - } - - for (int i = 0; i < 3; i++) { - // body2 调用 3 次 - body2.invoke(writer2); - } - - this.getJspContext().getOut().print("3 次调用 body2 后的结果:" + writer2.getBuffer().toString() + "

    "); - - this.getJspContext().getOut().print("5 次调用 body1 后的结果:" + writer1.getBuffer().toString() + "

    "); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/MultiTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/MultiTag.java deleted file mode 100644 index 6ec8ca32..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/MultiTag.java +++ /dev/null @@ -1,34 +0,0 @@ -package io.github.dunwu.javaee.taglib.tags2; - -import java.io.IOException; -import javax.servlet.jsp.JspException; -import javax.servlet.jsp.tagext.SimpleTagSupport; - -public class MultiTag extends SimpleTagSupport { - - private int num1; - - private int num2; - - @Override - public void doTag() throws JspException, IOException { - this.getJspContext().getOut().write("" + num1 + " * " + num2 + " = " + (num1 * num2)); - } - - public int getNum1() { - return num1; - } - - public void setNum1(int num1) { - this.num1 = num1; - } - - public int getNum2() { - return num2; - } - - public void setNum2(int num2) { - this.num2 = num2; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/RepeatTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/RepeatTag.java deleted file mode 100644 index ecd9dd61..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/RepeatTag.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.github.dunwu.javaee.taglib.tags2; - -import javax.servlet.jsp.tagext.SimpleTagSupport; - -public class RepeatTag extends SimpleTagSupport { - - private int times; - - public int getTimes() { - return times; - } - - public void setTimes(int times) { - this.times = times; - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/ToUpperCaseTag.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/ToUpperCaseTag.java deleted file mode 100644 index e48cb349..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/tags2/ToUpperCaseTag.java +++ /dev/null @@ -1,28 +0,0 @@ -package io.github.dunwu.javaee.taglib.tags2; - -import java.io.IOException; -import java.io.StringWriter; -import javax.servlet.jsp.JspException; -import javax.servlet.jsp.tagext.JspFragment; -import javax.servlet.jsp.tagext.SimpleTagSupport; - -public class ToUpperCaseTag extends SimpleTagSupport { - - @Override - public void doTag() throws JspException, IOException { - - // 将 标签体内容读入该 writer - StringWriter writer = new StringWriter(); - - // 标签体 为 JspFragment 的形式 - JspFragment jspFragment = this.getJspBody(); - - // 通过 invoke 输出到指定的 writer 中。 - // 如果参数为 null,将输出到默认的 writer 中,即 getJspContext().getOut() 输出到HTML中 - jspFragment.invoke(writer); - - String content = writer.getBuffer().toString(); - this.getJspContext().getOut().print(content.toUpperCase()); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/Messages.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/Messages.java deleted file mode 100644 index 624e2da6..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/Messages.java +++ /dev/null @@ -1,34 +0,0 @@ -package io.github.dunwu.javaee.taglib.test; - -import java.text.MessageFormat; -import java.util.MissingResourceException; -import java.util.ResourceBundle; - -public class Messages { - - private static final String BUNDLE_NAME = "com.helloweenvsfei.test.messages"; //$NON-NLS-1$ - - private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle(BUNDLE_NAME); - - private Messages() { - } - - public static String getString(String key) { - try { - return RESOURCE_BUNDLE.getString(key); - } catch (MissingResourceException e) { - return '!' + key + '!'; - } - } - - public static String getString(String key, Object... params) { - try { - String value = RESOURCE_BUNDLE.getString(key); - - return MessageFormat.format(value, params); - } catch (MissingResourceException e) { - return '!' + key + '!'; - } - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/TestMessage.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/TestMessage.java deleted file mode 100644 index e97e5ed9..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/TestMessage.java +++ /dev/null @@ -1,9 +0,0 @@ -package io.github.dunwu.javaee.taglib.test; - -public class TestMessage { - - public static void main(String[] args) { - System.out.println(Messages.getString("TestMessage.0", "A", "B")); //$NON-NLS-1$ - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/messages.properties b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/messages.properties deleted file mode 100644 index c99d9bfc..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/java/io/github/dunwu/javaee/taglib/test/messages.properties +++ /dev/null @@ -1 +0,0 @@ -TestMessage.0=test {0}, {1} diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/resources/copyright.properties b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/resources/copyright.properties deleted file mode 100644 index 2a835e8b..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/resources/copyright.properties +++ /dev/null @@ -1 +0,0 @@ -copyright=©2016-2017, Zhang Peng diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/resources/logback.xml deleted file mode 100644 index 112831e0..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/resources/logback.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n - - - - - - - - logs/${FILE_NAME}-all.%d{yyyy-MM-dd}.log - 30 - - - - - 30MB - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c.%M - %m%n - - - - - - - - - - - - - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/function.tld b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/function.tld deleted file mode 100644 index 4bf0a5e4..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/function.tld +++ /dev/null @@ -1,44 +0,0 @@ - - - - - custom functions library - custom functions - 1.1 - function - http://www.victorzhang.com/function - - - - - length - - io.github.dunwu.javaee.taglib.function.Function - - - int length(java.lang.Object) - - - ${fn:length(string)} - - - - - - - substring - - io.github.dunwu.javaee.taglib.function.Function - - - java.lang.String substring(java.lang.String, int) - - - ${fn:length(string, 3)} - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello.tld b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello.tld deleted file mode 100644 index 2dca9237..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello.tld +++ /dev/null @@ -1,10 +0,0 @@ - - 1.0 - 2.0 - Example TLD - - Hello - io.github.dunwu.javaee.taglib.HelloTag - empty - - diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello2.tld b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello2.tld deleted file mode 100644 index 44728ef9..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello2.tld +++ /dev/null @@ -1,10 +0,0 @@ - - 1.0 - 2.0 - Example TLD with Body - - Hello - io.github.dunwu.javaee.taglib.HelloTag2 - scriptless - - diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello3.tld b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello3.tld deleted file mode 100644 index 6d995a74..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/hello3.tld +++ /dev/null @@ -1,13 +0,0 @@ - - 1.0 - 2.0 - Example TLD with Body - - Hello - io.github.dunwu.javaee.taglib.HelloTag3 - scriptless - - message - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/taglib.tld b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/taglib.tld deleted file mode 100644 index 0874c62f..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/tld/taglib.tld +++ /dev/null @@ -1,149 +0,0 @@ - - - - - - - 1.0 - 1.1 - taglib - http://www.victorzhang.com/tags - A simple tab library for the examples - - - copyright - io.github.dunwu.javaee.taglib.Copyright - JSP - Copyright tag. - - - - hello - io.github.dunwu.javaee.taglib.tags.HelloTag - empty - Hello tag with parameters. - - name - true - true - - - - - add - io.github.dunwu.javaee.taglib.tags.AddTag - empty - Add tag with parameters. - - num1 - true - true - - - num2 - true - true - - - - - toLowerCase - io.github.dunwu.javaee.taglib.tags.ToLowerCaseTag - JSP - Tag with body. - - - - loop - io.github.dunwu.javaee.taglib.tags.LoopTag - JSP - Tag with body. - - - - dynamicAttribute - io.github.dunwu.javaee.taglib.tags.DynamicAttributeTag - empty - true - Tag with dynamic attribute. - - - - table - io.github.dunwu.javaee.taglib.tags.table.Table - JSP - Table tag. - - items - true - true - - - url - false - true - - - - - column - io.github.dunwu.javaee.taglib.tags.table.Column - empty - Column tag. - - property - true - true - - - label - false - true - - - - - multi - io.github.dunwu.javaee.taglib.tags2.MultiTag - empty - multi tag with parameters. - - num1 - true - true - - - num2 - true - true - - - - - toUpperCase - io.github.dunwu.javaee.taglib.tags2.ToUpperCaseTag - tagdependent - body tag - - - - multiAttribute - io.github.dunwu.javaee.taglib.tags2.MultiAttributeTag - tagdependent - multi attribute tag with parameters. - - body1 - false - true - - - body2 - false - true - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/web.xml b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index a403cc53..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - HelloServlet - /examples/configuration.jsp - - message - welcome to jsp - - 1 - - - HelloServlet - /config - /config.jsp - - - - - /WEB-INF/views/jsp/index.jsp - - diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/dynamic.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/dynamic.jsp deleted file mode 100644 index abb85b60..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/dynamic.jsp +++ /dev/null @@ -1,18 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://www.victorzhang.com/tags" prefix="taglib" %> - - - - Insert title here - - - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/function.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/function.jsp deleted file mode 100644 index 67b405b6..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/function.jsp +++ /dev/null @@ -1,46 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://www.victorzhang.com/function" prefix="fn" %> - - - - Insert title here - - - - -<% - request.setAttribute("string", "字符串测试"); -%> - - - - - - - - - - - - - - -
    字符串变量${ string }
    字符串长度(按字节计)${ fn:length(string) }
    截取 7 个字节${ fn:substring(string, 7) }
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/hello.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/hello.jsp deleted file mode 100644 index 2aa13cd9..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/hello.jsp +++ /dev/null @@ -1,15 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://www.victorzhang.com/tags" prefix="taglib" %> - - - - Insert title here - - - -
    -
    -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/I.png b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/I.png deleted file mode 100644 index e8512fb911f40ec90e7cbf057ace91404573df37..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 148 zcmeAS@N?(olHy`uVBq!ia0vp^!ayv*$P6UUaa~gcQtTz3zOL*Scz79AwQm3V5DFCH zEbxddW?AMyVi9{#UaQ0r%7Fss&l*vy)E6e!K$>FVdQ&MBb@0CA5htN;K2 diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/L.png b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/L.png deleted file mode 100644 index eb334edaeac52e2f473ffd92a49b025fb6148ec3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 153 zcmeAS@N?(olHy`uVBq!ia0vp^!ayv*$P6UUaa~gcQtTz3zOL*Scz79Am98pQqyvRG z3p^r=85p=efH0%e8j~47LAC&&5ZCE_)&Kwh7Y~}?0Tf{>3Gxg6&+v4+fftY`;pyTS tQgJK!NBsYWhyUvpj=8p5Ib^(%U@*zon(m*SoDEdN;OXk;vd$@?2>{+EE2IDb diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/Lminus.png b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/Lminus.png deleted file mode 100644 index f7c43c0aa3bebb499e86eb744b1e47b9a9445ba7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 219 zcmeAS@N?(olHy`uVBq!ia0vp^!ayv*!VDzYUPT51DfSXiUsv`EJiLsmQh${X`~(Vd z7I;J!Gca&{0AWU_H6}BFf(8LTA+A9B|NsBf`>KHqVn8Lk#c`lIrjj7P;QtIyw;Ol? zc?O;?jv*Ddk_A|pTm=*lC~zFVdQ&MBb@0OtiQr2qf` diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/Lplus.png b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/Lplus.png deleted file mode 100644 index 848ec2fc3bbaab6345864c303684ff8a86559cfb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 224 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 diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/Tminus.png b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/Tminus.png deleted file mode 100644 index 2260e4248cef23f97d59b9c3defa571564debca3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 207 zcmeAS@N?(olHy`uVBq!ia0vp^!ayv*!VDzYUPT51DfSXiUsv`EJiLr*Oh=QrbAUpe z1s;*b3=CW!K$y{KjmZq4ph197i0l9V|EKp=19?OO=>#nkpi;(?AirP+hi5m^fE-m% z7srr_TgeFwOpI(f46|1#2yiGY`Di?KiU>FVdQ I&MBb@06e2A@c;k- diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/Tplus.png b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/Tplus.png deleted file mode 100644 index 2c8d8f4fd38259b2ef70fc63fad505fb0a0f55a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 222 zcmeAS@N?(olHy`uVBq!ia0vp^!ayv*!VDzYUPT51DfSXiUsv`EJiLtRLI=-jc>;wv z3p^r=85p=efH0%e8j~47L4yFF5LY1m|NsB#ebqn)F`$y&;y6$pQ%R6t@PCG<+YP*c zJY!E6$B>F!$pS1)t^$e&6gZd#)XV}^6#`leL>0XheB5$;*x1}$U0qr4a0#d>%86hy4NT6+w`tp00i_>zopr0L}p{>Hq)$ diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/asc.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/asc.gif deleted file mode 100644 index ca472c094c121031c4ee67778702c77cbe3c8d02..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 118 zcmZ?wbhEHbV|(KLGNbUC`+o%) FtO23?AZ7pn diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/bg-btn-blue.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/bg-btn-blue.gif deleted file mode 100644 index bc03f1bd5948a2057abb97707ece2434cea75203..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 827 zcmZ?wbhEHbWMnX6_|CwPHuG*k>y@ysYcAF2qxx^ePrhYUcrtm~ZLfxlcBN++M!{$Z w4C@e3{K*3LpALuw!M=S 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 Dp1HQ5 diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/btn-go-dark.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/btn-go-dark.gif deleted file mode 100644 index 206207a790322f0ca6a0a200a2594fb7b13d2317..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 276 zcmZ?wbhEHb6lV}&IKsfdVBEo-wdw!=|4B=qvw6;E^IpgvxP&Wt9ee09HowJeev7!$ zHgG1a<&0Uym9ddMXbDHe3XX`CZ2pVc1DCS-FZ%KOFIU=npfR|B;!hT^ln#gl*~!2< zQ9+?EC9}_Y)w&#QK1ZGNCV#J4)IG1+^L?lM1~m~0)=i5GzZF^ZF79I1GP06g-zv6h zE5i=%53e2CCY@$th^nnHymvvFnah8Fe6P(GLI2l(dG!SZ1O>e^1=^Ja1@t-k`&B2Z zOwecJXA_=2n{CeA8PoMSg}9b-a&j%_5?QrknLg{rO`ErD(P!DQbJyihO9GFSru DNlRJ} diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/delete.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/delete.gif deleted file mode 100644 index c610608f3d6a0e2e73aba4d38ad8e52be2c3222e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 74 zcmZ?wbhEHb6krfwXkcLY4@Cd}EB<6*WME)q&|v@qkPHKpVo(3d)eLVYRA*kR$T?hf ZmakFq7>oSQz(tH*+1|NNeUw=ltN{z17VZE5 diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/delimiter.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/delimiter.gif deleted file mode 100644 index 5bfd67a2d6f72ac3a55cbfcea5866e841d22f5d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43 mcmZ?wbhEHbWMp7uXkdT>#h)yUAf^t80Ld^gF}W}@SOWlZ0R#L1 diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/desc.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/desc.gif deleted file mode 100644 index dfab21c17f49a20c788ce0c8dc2a960d04ffb827..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 118 zcmZ?wbhEHb+d54hU0L>&W8h&TvTl!gj2 GSOWn1BO#Xn diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/edit.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/edit.gif deleted file mode 100644 index 255832653fde9e9748855347fe7bce013aa0a6c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 162 zcmZ?wbhEHb6krfw*v!Ci|Ni{?`uO?t>-X=UfB*je|NsB*2eRhRZ)j)$vKcUd;!hSv z1_n+B9gqyj3&>#;PFW3S?Ey~Z^d=Ib=Q)_ty((#Hu?(NPPIEQ wFt9a7JasYQt8+ZHp~C0XEcX|=2_;e6_wsD`%rk?zV@6`Fg@V`I3Kj-y0F-Mvy#N3J diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/element.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/element.gif deleted file mode 100644 index b07b87b10022e1359621cdb280df664fe5eb006b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 330 zcmV-Q0k!@|Nk%w1VGsZi0M!5hT6d9MdX!>*m}rBXU3!yZf0$^5n|QY5ki+S1iJx$c zqN>sFyxQ`mdK;vA8>oIAt$`r3gd)0$CAx|w!H+4xk15BLEX|oO&6qIMoifv%GTWmz z|LDBgpf&!yNdCc7{>5MZ%y0kFfBx8z|KFnjtaW$toq@^^Fde|q(bhX4Qn0000000000 z00000A^8LW0027xEC2ui01yBW000JOz@2bNEE_7#bZLLP`!EA0Qzj cBP1mpCXh=zg@+Lm6c!g5kAteKtV2NnJ7=Pig#Z8m diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/file.png b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/file.png deleted file mode 100644 index a20c6fa0c88a959109264446a69020a0892ca454..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 251 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPF$k8C-m2iN8 Zo#CR6tYFg7L+v1&Jzf1=);T3K0RT{JOkDr~ diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/foldericon.png b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/foldericon.png deleted file mode 100644 index 2684748b0af9804a3a6d74ee82099f660976c1e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 229 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPF@MtwUbVx|CCuD_3M1(}kL@rTAt`?@NJD7g1T-cVt+{lozLxII0fq`Ms9dUP2 S4@VK8*$kepelF{r5}E+oVobaM diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/ibm-tab-background.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/ibm-tab-background.gif deleted file mode 100644 index d36dc07306126c5361d4aaa52303fbadaea76c46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2538 zcmd6l_g52!0)=^230j~QsaUj@jwgte@{|e>t)-R`LV{F#7?CPg1Y`uN77% zq9|b#Ru*IsAVHR42-%<@U|3-$1PCDsnP8s%FWx=(_wPIBUSB^S_kZ8W{>Z^(#l!@L z#63V$vMXrUvoJSuYHz{oq7d~_h`QT(4KaBQvH94zd~7@t7hiyjM`9CDco-53E5Ifc z;1W={gCkZ%W0)o(n>FJ^jY6`SJ~lkFXo z;~kp=Ovv+&$^P-$Ul(tspAWm^5emC-?Jw}%$KZR9e~C=Llw5Q!6m~hSkMi=71*pe`Po5S%dtQt#DJ?6nKz&k;sj022Z@}X41Y#q;l1!mC(^^{F=nV26 zi_Wg@o|nwtzW#w%gZ<`g4wuIl43E5iGdeb6IWhT8I5j;pJ13f-cUoFr5ldF3YwH`b z&9zSzO4atK2emrAVQ2Tf(d458)@B$?Z`NBY$1s+Quo-^v8&CzXx%O55p`$U=tY2$P z_SyZCcg&Ag&n|MfLJVWm8n|eeo16;2mPqbC_q)<*b_;H}+B56G7a&(WzYbXB_&cYS za0TaG2dV_M5ntm%;s-sMZH*I@@cz6n{OL{aTB1jZzjNuNDMq4kkyGiYjeXdUmA@jrVgTvz1YIp zOtTYs(ZdyjNR)NZ$6-F(Iw*QR=SZYBJo8wRWFBsp>#%_Mwm5p>(KkZeLY@;{Qv2Yu z3cE$5>v;5Hf%^)65p`Z2()ZZY^yE^Z*WuesPk<)~OU9=_wPdL%(5Lj9{l5j>UVa`D zNmwopOIlq)Ve9xvHYPbCg*Y=3-oEM7B{A|`2OA80l-a~S|< ztti_DrkzLFleG&-H<`Q0+sj$E^eh;rTP}$r>sBg~$+|@h+*vQFN5k~1c$}qv?~zuS zevLZhY*=rdfEhO0#bkr5OCvLEG6COr$$qg;*x6#)Q+5dH1NgBn+A0bW;QFs^*eo9p#gjs0{kQghY!FoOw1iiG!i~)i zNL3h%Fy;*oKfT-Y&l1HmJh;Izn9T|5P&}t18ywM|T&Pe{+|FxoPG)nj=@n?EIo1X4 z$NNM0=D8~ndm7E=MS3VpN03-o+`l28hbYS?d06upCLflnEEk#M+=u)GcgmC%GXt3( z6Kp|Jhq6+R#GM!W4W|kZ0VF)!1r7UBf>2a#VuAMrfJPqLsxW2{{6!nbkKz~iP%!6BafhnGrGNmA0*#hc{RVL7 z69SSsqg8qp&eMVz2nUT}gep7`LcEOTjA1>t34uR4T94wCA>1az_BCG9a3*^6&nYPuP2m9l$v#^( z6#GqS2*6G zZle~E;;#D(g+evGolk-#bEjtYY6jCn79Zt5y}1;{~0M% z(=o{>Co#A)vQ$l{$bynG zyfm?-W&psV9iN}g%mSznZFnNnbK2g6D5^2r9z1{GsQm`!i|*qesI}uwLw(Gx?;QDgMIDz>$A^8LW000L7 zEC2ui0B!&>000C3c)HyFFv>}*y*TU5yZ>M)j$~<`XsWJk>%MR-&vb3yc&_i00Q|sU zP(TzKje$lWa10I{gF$o1t6HzvY>PP^7Q0=+VF?C28RcIx#VrfS>0=TeZp7Exyneqi zO6WfgK0*#%3{q1xei2_lN{eWT3vEYymL-r6fMo`SQ%eVfk}{bNn_!)GpGl%kiiC`< zU;(L-LWn=OxuuuAzL&fVr-q(DL<+74!7a-Q!=tu8$I6w|LBH79dd;D-kFe1Ipkv_H zj@szy>Qx2p1tGGllyTRMLgVIm=0;)=?;rEz(L*vOptOGG&LEqDP(eEb|JIdocOas~ z5fm+UFnHuG4aYKPo=qz#PF0hJ4K!|uck)@6C=2Wb!H29^$CMN=>fFh*r_Y~2g9`nb G0028EJEUX) diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/left-nav-corner.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/left-nav-corner.gif deleted file mode 100644 index ae395ea6b4106e8de138797e5574f1a47de63740..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 909 zcmZ?wbhEHb+`=HraFv1Kz=gX8p)ZEhA)}A58!67ClF5cJQJF%~iW5NW^nZ1+5r%#zWXW@i7oRfQdmMrXD z*5AK!Mc=YTlP68xyl(llZL610n!alNl6i*@igE5(d}#61CDP|FT$Jv=aOLXdtKyf= z-@1MG;{6BLZ$G(n^TwrXuU_1_`|jDB7w_Ibz5MX&_Yaqp{`~#-pMjZ6#$&^RgUuYm zS}`XI651FTjXdW35J)=OE~4&sW=G&6*Df*TJQ2yD#qJX%{L}Vi2rW4=RbRXB%a0

    pU=)sPft%*Prg?p(VWh}VAswjE9IPdepzsTn68xR%PVW6*W1NbnY_F@ zzj!s<-0E%F*Or(5{-?Xk?9GF%nX{ky&e{Iy9)n8F48Og*zP`S(Is5*(y}Q4^e{i^4 zzTfr`BQqNpheSfd-(TOD9_<$oX!y&(_=0=Ma(f5+zrQ}LygrZd(^>KP)$EVTA2573 z+;Ok!onVAR?MJ~1`FeK06$_gAlWwe%Yj|~Zxm+{DJ$1#p7l{@NTNN*Dzh5Q%?f8Q_ z-EY?)wtO`$IKU|L<;CNEhoTJ*6I{eHQzkget$Z@c$L!^jiJo>ZQzr4fsc2x2NL%@I zT1?r?r_&SKG@s2#nU?u%W=57}<5cz#2S!%W9Y2@NEU6robuvD^I`WkDamQ zR`=$6Hoy4W9(hb-x<2*AsckDbkAL#ff7iR+l;g`kejX;aBa1$E_-IW(v+Ofxuh;S` ftAaX2qqg5!_nn)syZ7d%{2ss6*WaWvGFSru(Cs)h diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/line01.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/line01.gif deleted file mode 100644 index 6b9a6e3f23c267ae32f48eb614ec4a22d76a5e4c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46 ucmZ?wbhEHbWMbfCXkcJy+4fz)bBf|m76uT|0TCb>1}0XXp5>XW4AubUya<*6 diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/masthead-links-gradient.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/masthead-links-gradient.gif deleted file mode 100644 index b20772a45e1cb1b43d8d4ec5be025aef824a2bfe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 264 zcmZ?wbhEHbhPf+)dM#?^?X&$lASU_MW@5=j`o$=kIPicKN`CdxtMS zICSa$v8#`cUU_)%()|M$@9jN%^ThSX$FBiJ9vr@Omw^z_0Vx34#lYIDpwO3+FmJ_z zb#ET*Gf+Hll62o<$$Oip{||H)c%(@5pXew$nPaAUVTFiis>!+=Ym!dC)!P@(e&b_> z%Bi$3iD&bqxmcTR0)h#SwcJg=KXKO%>2IQz4kMwizmA)sgkydJf69$ySTGE zMP=ruZsNvn6iqc(brn~3rI;0SQ5SJx7m8IjXLS~5b|xS>nUgw+6FZ>*HlTrpWlU4> z#8WddV>1d`RH~+8%BB=5&=fTh6E>j`g;`c(F=k_eMywjCkr=TNSfLtV5Dk@(g&v53 z4QOCtnZi)ic%sUx6#hVqN<@WKDC|N7nk>q!OmGiTm{k%bR)Rg`0h!oF9Pkeja0t}^ zgJ`J4D0F}r*nkEWMRL(bjmIJsLBbzsQ85b>a)n)}KvRf?kRy^t0MLbT1P!-{5_v!- zwh;$3!7v;`HNYSmn~((^AO<#|DNYpAXrsm>Tns_NA81iA#m5o3ruU9!dcue~y-Lic zK}YIocCnhq5l2WJ22v_IZj$N<;1n941FnSd;3=pw8BdbonWe$Wg}$-*a+3L%$(DBS z*{QDC*}HG6@lff=v7gN+Hk*$BYAJlJuHC55uQxQVz|$+elKp4&?orkA@XAN9_zctYh&QqE?}6&0S#;nP3xw;};M9fbz` z5tT%{dg$Fm?fL$V;=pHHJ$EN}uW?H(k%F@{jxKs+!i@#`DWtUmiBkp1QI9 zzRO-TcNVXdI-a)Hyq$09|Io2K_wvWV!pNU*n{`Rk{<9$k8C-m2iN8 Zo#CR6tYFg7L+v1&Jzf1=);T3K0RT{JOkDr~ diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/next.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/next.gif deleted file mode 100644 index d0d7d2fe75187b37b0896a1d54b490b1eb919926..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 302 zcmZ?wbhEHb6krfwxXQpVbN8#d6%W^*cwe*fVdLt@IbD|UIluejXQ`x+OXiPP^ow_ICz z@Xds+&zaX?!G#6_sf#S4}$w{Cry7S=69fc$^B(V-n6cNX+|z+ldx z1Cj^%iGj`g!0ZAK9jX2k!c3W4b|&~da&|CwZQVF?jq1)mGp&GSBBG2uEJs@oT`6$l o4tsuR%NnibE9zaGCINEcoC55o;pX~0tnKD1d_5B;Ix<)T0E1PHrT_o{ diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/openfoldericon.png b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/openfoldericon.png deleted file mode 100644 index 15fcd567111e13cfd9e6f1c0cd8103ca0e2d76ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 232 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPFKheKdVb6i#4OEw{M26cw_Gj{yv63W{1!+$*s*N3f2 zemsk&HQ&u~&f!%4#A(caCa+!4-!jYT%bH^M9p4*PZTkKrpyj95hk2~iCLLVQKL3cY Ud_U9dR-h#ep00i_>zopr03|O{b^rhX diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/pagetools-gradient.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/pagetools-gradient.gif deleted file mode 100644 index 7beb3cb9ee8abc13178dc6c0d40774bbdfe21eeb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 986 zcmV<0110=NNk%w1VLSjb0M!5h?(XjJ@bCHh`u+a?`S|+&{Qmm+`ThL;$;rvf%gdIQ zmc6~brlzLy^76{c%EZLPv$M1J_xJ4V?E3op^YioY@bLEb_V@Po`S|$v_xSep_44!c z^YZid_V@ex`}p|y_4fAu{{8y<`|$7Z`}+F$_xSt!`uOm&_WS$$=;-L}?d|pT_5S|;>gww8@$vlp z{LIYEA^8LV00000EC2ui06YLP000L6z@2bNEEW+Keb?5Icjv3y@A$la&+q&HfPsQHJ~xJig@lHSjE#$Rj**9viHANO z6C@KiCLT8)A~z&A6Cx8PpPC|`ogyT!o1G@A9wxS-6Q!WDH>;z)9-6r;xf2F40uwP4 z$pXpD%Eivc#nrjT#Rk^P#@f;o7%M6tDHs?t7z7vs<>f0YGvx;G?#5? z1_Y=mr$SsleC5o=Sdn4{h7>O{WOz~HLxvJ3PJDP_@x_Z18BTmG0mEWRix(?$3@H)h z2^b__L^x3*!bF)QE0T=BGeV31o<1Ykh=2m9QKLhH9$m`xC(sTTqDHV_p#xM4HlSXB zilPHmsuvd;_8um%t=G{}&#_;Fy!lr7+m?4ogD9Uv^|1wliw>C>oFt6t5zb!rM6Hf-Pkdjo|A zv~9ztu)D$T-@IeCZZ@o4l1I^Vv8*1z{89*)@b96IOeG1 zjyB>@qmMk`;3JSg200{+L<%`$kxB;1qmV;FDdm(@R%zvxSZ1l^mRxo@r2=4vDdw1D zmTBghXpYIinryb|=9_TFDd(JSLSW~ec;>0+o_zM{XPyfLD(Iku7Ha6Bh$c$t1B^E6 z=%bKED(R$@K8nDlm}aW!rkr-_>8F}HAnK^3mTKy$sHUoFsR^{|>Z`EED(kGY)(Yzn zxaO+suDtf@>#x8DE9|hu7HjOW$R?}ovdlK??6c5DEA6z@R%`9G*k-Hkw%m5>?YH2D IOKcDTJCkH9)&Kwi diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/pagetools_gradient_a.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/pagetools_gradient_a.gif deleted file mode 100644 index 50b38ed018817d5a059de3581c27f8728ca0478c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 946 zcmeHG%PRzN9G`NTa#2!yKpPiHQpzI^4q|s*#MKtLEICLHZe9oF;-HoTq1DoQuE8F( zNw%1ol_W`ab8y+Feaz1Iy=Q)z{Tp0;ZlBlp@TqUVx38mftXpsiND%V*ysj4rr8egT zQ*Loeh;7-jEUT&-3+Fvb8v&TYz=UeFn(7-7b!lXW^66HH3b3k8P*2#{kVL@5CP65?Eg+~x>D##knk z$>nkY0M5ClX~kmEvaDn>Dg5Jqfj=zJRO*&~K@c~DDzVygzI(SURyH<0?(W^MRMh*n zJkh#d(bYPcdW?o1*4*vQF7L&@yxuSz8JRE^)s2>MWy>ptpEkuAEhS3_&pSQK#%#~! z;Y-M~-so3;0!s&jA>X#|D)M$TG#^MSS4Zz>qxY*d1J}nNQs6b>SFcY#W7FTt_CV}3 XeKTijX*G8Cb-PejUOUz17P@``3z7zz diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/password.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/password.gif deleted file mode 100644 index 8e3d48a637e96d36565e4964b8f7f93227fdb48d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 239 zcmZ?wbhEHb6krfwIKsg2`_JFMpHIKI*z)yG&!1=WHub1)pQy8Yn&JLgrU&O)%&gH} z+HSh6(_&q}&AtWRC)dWmI9+n@Q2xy$?G@3&%j;xTHp;JUQ@(g`_W%F?8L$DxpDc_F z3=#}FAW4v&46Hc{YJDl0Nz)g`J+Dx85KwvZQ)r6Uh2@SC%JLUn8Ux&pCcm7TVKsYe zpp)BDHz8G*28Rg-Dt9;*l{Czd6>hgI(vW0oKA8Nm!@X^>Q_B4SlW&5os$z^xOkBcT ZZGvK|q74(OMO{RuPMfMbQ%RA*8UVO3S=j&p diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/prev.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/prev.gif deleted file mode 100644 index e7d51fd7ec024ab197d8739f44dda383a9174710..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 303 zcmZ?wbhEHb6krfwxXQpVbN9=Qr`}gBdsMja-ppMuv*$jjTK2GL;eBDt?RkyowTjNC z&A6RD>rVIPr=?3CS1o%wVcT=*)RU1D?urK;NSSuGZT-{q$v6J|{a3T{Vc~)Y@l)?C zJMt#G`9kj8yXM&^^XA|6YQHIDyrp^VW2>4gqV9WV?S0j|=~>d$TRC$dB~O1ib;paT zJ73tAUl4KFS-#{!cF)zdC*MiN9BW+t*f9P`=IlEjt=Inl|IdIMDE?$&WMD93&;cm{ z`H6we$6yROOZY`&nfMko{pnW#iT=*c<>fzYiess4se;mK}YJ9Ue^ zNtZ$|ffAdMNkpr-)~>oS?D#S>yY9@+&aXqe1s$a5fiFA{&kJ9^&-1{4nsi(;7K`=R zwtc%1heDw*p65OP>*=yAKib&X@I22GmSu^_$;skBF^EJW&!(oPhIL&}yRMs9TwJ{F z`+idM{LIuIr8(hT#BV&+}@gX<``00Py}#1`2Q!=m%O-sZ@)mX*b5l#`Z4^ z4t9mZVF0q(tUot5S29i0kR<70p-^}s0e6OmE2$hN1-kCvyNmA!l*>oFTS>FGf5^~+dj|@m^h*)!kl4bFBdFf@Pa`XiF z3>Z@Vn3Osiz6!L0q_@W7;|F`@Wl55}NHqGYFK%5WIIYlBZMqrRD=WrhPUIb0@km<2$+@eN~wC4*J^bFaC|qpTHb)U z!AVCL6c`u`e;7qDU~w>BqhmASABJm&PCyA2ga`nJ0gQ`r;aoeI2b}C3XbxkXXcU?= z-HE~UW;5N~m~*&Z9^N7TVGHK_3xb5PDe=N+)xscwJXfJgU#nZE3y;g+uK(cUlq_8) zx(P;ic4oRTXS3O}GerVX=6@cB20*ogXK*Ai4#24xLB$NG0Uf=n`LKc0P87>ofM$oR zHO^9puDuRM6Bv97K?Q!G4$5R`vxiSvu2ISr$%!(J+NiI6px2CK#J{=fNkf?yn=F=Pll5_}-^-pGmj(?aNxob*6uv_{C;HPdi;IvfnU~ zezeRhTOMfpRP->T=!8w+aH8*o?_w%?gq4c!j55V6Rd7I{RN+tnM#6C0=bFQ!ixX5f ziNoV3ZK*xk`s&*EwyYO(=}QEjUptJb;OL_TtrFhl&R)t)Zb7N7>-5Go3SF?Dp=~65 zbeJkpotk2V5yAJfgn`Od1B`YUrZBRt6b1tx<3(wLheFq+XEbn(uqa%BM5VELk&b*$ zloZk!TDf2YfbHXDjpAyE`=Q(|m9=pZ&9!>jz@r6@zPJYJNb!e;Hq_I@ zRx6*u+g-PHIC!1tlrYdoh2#Ic-UAJiEq47B)IXl&g-oAsXhW&5R-_w~&+gp9;XK2i7X02I&p<}0$AX># zSq4jbG%}yfiA?&(GKd_53(PyyUGNKs)T#WF=ANN7seWLNUOCWsZh^dnR4x;(@riZ) zJY)STVs~nE)2^)Us;k<~O9c#7l=F|?#k<^@&40{4@nznPfLe9viEv?|OL=Aftf}E0 zQf^$!=8H27Z8XS3vs$74_pa}@1wp;lWZ=tOAKB>LJP<+L#>7Glx;`FXN~3dl2CI=Hk+_E*&bgF2sd9PW`r2djwl_&zWOkk#-0S?imoqNRj0=E{ z9GMdF3sbVDUTg_u(3D(+D^2@amX!j5ri3teh=ZmCRYo{W{)Kr(1tc15Y?1ue>1ubY zxBL(o+!}LaV)Ma+-)!pVhTZ;URHd$=DEjaf2l3plzPtJB_WYc;;&F+5duuQCahCk| zRN4d)^C%@Ka6lhj-1OO`^zI_nDUZ|&*>c9kTPvoYg@zJ+I^yS+WFoO{ICLEr=r&p} zI!ixEV8*5flL`7s!lbaFR%Ns#$dQn}Ux(gR-bOBn84(hAp$mGF=0~A*U-Z|E-T+ljue_7{>xtp$SJo0$c(Z|!5 z?OV6+`s!Vm*6g~ta{IYW2d=N%b7{lg%j@@CTD$A~hP{^>rfiHQ$u=Fb4zPWpipG1uV`4SU9xbx6`yOmc#1)5q;QvN3~#!xxNl3Gu@+alEAKQJ zJsS>Z zbJrWP&e|=K0kJ&lQff@G;U=wyQL$-qmW-*N-CKj+I3zq{=3`0~DoF_JI5C4cO)kS> iM}tqVlabCA1K}p`Mk50Q1w#`KR~BKF;7L3z4Aua9Tqc>({TZtgK8=Pxtin1PU@>0L7my zj0_B%3_2hgkQodtJ_aW}SFcr@;eCfEY&nSJI{rI!5RRgR5Qr{ diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/template-gradient.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/template-gradient.gif deleted file mode 100644 index 1bd540ead6b30538c920d4d805a7fddb1fb5495a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 659 zcmZ?wbhEHbG-F6%xXQpFBO{}rpdc?Vuc4tKCnqN>D{E|QtgWrBudlDGt7~XzXliPz zqN1Xxs3(+1BxM}m2ty@`k?A*0`&)$9e_cI+jeB|h{ z<0np@WIS{B-1!R^FI~ROd+qv-o40P?xqDamfv}{o?1RS-A3uHc@b#eg)?) z8P7>7Et55ZSH+y%wA6b#d(x>no|~7Qoo!L5cH`vc<>xodcWCAk?OL$n;$n}hKGkcgtg<&?AZAD_ylF|c|JQgJv}|cIQ!n2otvMZU*Oy= z=eujm%gZZ*SI3>*we|J&4aukH`R?BK_V$kA*Z0ou-v0jnfo5)bzdbuXK0Yy7JO13B You8jySnNIDZ||Px# diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/top-content-shadow.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/top-content-shadow.gif deleted file mode 100644 index 34035a8a14113c16fe601d031e08762a916d5c45..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 325 zcmV-L0lNN2Nk%w1VYC1g0J8u9|NsB~{{H>_{rmg-_4W1a?Cjv+;Ly;}yu7@rsi~Ef zm4$_c000000000000000EC2ui0JH!U000E~aLGxly*TU5yZ>M)W=d(EXsWJk>%MR- z&veZuc&_h!@BhG{a7Zi~kH{Zl$!t2G(5Q4uty-_xtad9sc)#GVcuX#v&*-#z&2GEP zhPZrAuiNkVynfH``~QG}cm;%ohKGoWii?bmj*pO$l9QB+1echZnwy-Ro}ZwhqNAjx zrl+0*s;jK6uCK7Mva__cwzs&sy0HSizQ4f1!o$SH#>dFX%FE2n!~xLJ($mz{*4NnC z+S}aS-rwNY0ORE4=I7|?>g(+7?(gvN^7Hid_V@Vt`uqI-{{H|23LHqVpuvL(6DnND Xu%W|;5F<*QNU@^Dix@NJivR#SVQ0X7 diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/top-left.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/top-left.gif deleted file mode 100644 index 00b8ab415bae3b73edf82449ef3c22e87b504831..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 917 zcmaJ=&r8&C5FMd|;KfT+>Lt8-sfkF0D8kZ+prj%qXocor_7kO$f+9+i4!fbqkOdLC z#1c~2Ars6t%4q6fhhniXq90IEskmv_^yd2qG`C@9-n{qb^V!n4d0lO4IaN@R$Ro^M z-Nl{VDKaxRbrUysqbRDms;juND@CuEi@JylyHJd>Ijgfcvoit7$(+V0!2|1F<}!5Qs`wh7GpLhaKxyQ8i^4bVJlbz45Yyl zv{1uhU;`Rh7^V;uIYLxfl|moZA`?+z6$-gvfg+1CD-+a%6nd3JiIpG^et;&XVF&bs z1Qdcbz(5)-(Fz_|3~WFHi^91mBS)|Zg^|#QwaA!-3BE!uSfD7xLhunz!vOF?JB)@} zSP4Hs6VtE*oFEtq!5Uy7jY-e~4=e^Ypeari-6$hR5H1EIp$}`3F~#EuUz2-BF*#w_ zoLnV(6QjfR#JdI3Ii!29XCmM1aJb4=YT80JZQqI#l^q#3xB@MOm7f)VI?&#=lI^DDDOz+;ay8nD%s`L86t{X?v18qIGF5bL%_3qfUp?r4a z+2Es>xzXp3CkllRUyAb!Gjm0}Y#{$XR?aUeA)2L{Xp28Vr6;JQw5lOJwf5xOiA!Zq z)2Xq}nZofy4R_w>&rQF}|Pt)Xq=a=x6J2 z-=6KCt$lrCOXafeYC2k9+g#T>)I2hLS!nj|u5H?&DBQ&Y|jmY*$aN-4uuHq1JMYLeOh9rnCv_4hc0vMdKm!ZI z=%&~cp=M&nW)y3YshWx@n^LeqQPf0C*o1-F>0hnV#G$+3f2GvX|Mz> z)UX)XfCd(ZDFj815LH&C(1*3iL{wOXLM~XK$fC^31oa?=UL{dtCCGyxpowYN0sSBW zgX_a4yQo5iCMsB=lh|GG<|duaFBCCP zSBc)l=x{ypE=ChO;s}YtKuSo*O%fgfoIv9_;7TwLny_kW>TiDX&)4zXmv29IwD-04 z4W7F*(w`Z9{OaAh#*_FG`hdnoj`tCiw z^616I2hWFJj*WfzG?AbD`FjE{8_55Ul6+ACQH~ZwTl@){)<8qW(@PiDE^c^Br5i`u zvYF<>!GW&VSLY75WJm6w*s!ytqV;2DZ_)ShdF9(?R&K8D?C*Vc?DN{jrX%e=D^I7g zqr=75X0JNFbkXIiRPKG*_T??ha;K`(ncZ)?SG3OmmA!SL@NV6V!}VKl@7nZa-<-;u Lmuk`l1yueIn##a8 diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/trans.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/images/trans.gif deleted file mode 100644 index 6964168b947afc2cf76780a85f43d4f77c257b77..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43 qcmZ?wbhEHbWMp7uXkcLY4+e@qSs2-Y0y+#p0Fq%~V)9{Rum%7ZDGCMv diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/index.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/index.jsp deleted file mode 100644 index b3346ace..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/index.jsp +++ /dev/null @@ -1,83 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=gb2312" %> -<%@ taglib uri="http://www.victorzhang.com/tags" prefix="taglib" %> - - - - Insert title here - - - - - - - - - - - - - - - - - - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/loop.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/loop.jsp deleted file mode 100644 index 540e2cb2..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/loop.jsp +++ /dev/null @@ -1,15 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://www.victorzhang.com/tags" prefix="taglib" %> - - - - Insert title here - - - -

    -Loop. 
    -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/multi.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/multi.jsp deleted file mode 100644 index c960cbd1..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/multi.jsp +++ /dev/null @@ -1,13 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://www.victorzhang.com/tags" prefix="taglib" %> - - - - Insert title here - - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/multiAttribute.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/multiAttribute.jsp deleted file mode 100644 index f245d55d..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/multiAttribute.jsp +++ /dev/null @@ -1,21 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://www.victorzhang.com/tags" prefix="taglib" %> - - - - Insert title here - - - - - - 标签体一, - 标签体二, - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/style/style.css b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/style/style.css deleted file mode 100644 index 94ac1b2f..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/style/style.css +++ /dev/null @@ -1,66 +0,0 @@ -body, td, div, input, textarea, span { - font-size: 12px; - font-family: Arial; -} - -.list_table { - border-collapse: collapse; -} - -.list_table .tr_title { - -} - -.list_table .tr_title td { - border: 1px solid #DDDDDD; - background: #EEEEEE; - background: url("../images/ibm-tab-background.gif") no-repeat 0px 0px; - border-top: 1px solid #7E9AB0; - color: #FFFFFF; - text-align: center; - padding-top: 4px; - padding-bottom: 4px; - white-space: nowrap; - font-weight: bold; -} - -.list_table .tr_title td span { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.list_table .tr_title img { - padding-top: 1px; - padding-bottom: 1px; -} - -.list_table .tr_data { - padding: 2px; - text-align: center; -} - -.list_table .tr_data td { - border: 1px solid #DDDDDD; - padding: 2px; -} - -.list_table .tr_data td span { - width: 100%; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.button { - color: #FFFFFF; - font-weight: bold; - font-size: 11px; - font-family: verdana; - text-align: center; - padding: .17em 0 .2em .17em; - border-style: solid; - border-width: 1px; - border-color: #99CCFF #115599 #115599 #99CCFF; - background: #6699CC url(../images/bg-btn-blue.gif) repeat-x; -} diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/table.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/table.jsp deleted file mode 100644 index ea9c96ef..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/table.jsp +++ /dev/null @@ -1,184 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=gb2312" %> -<%@ taglib uri="http://www.victorzhang.com/tags" prefix="taglib" %> -<%@page import="io.github.dunwu.javaee.taglib.bean.Person" %> -<%@page import="java.util.ArrayList" %> -<%@page import="java.util.List" %> -<% - List personList = new ArrayList(); - - int i = 1; - - Person person = new Person(); - person.setId(i++); - person.setName(""); - person.setAge(20); - person.setSex(""); - person.setAddress("кϵ԰"); - person.setBirthday("2008-08-08"); - person.setMobile("13820080808"); - person.setTelephone("69653234"); - person.setCity(""); - - personList.add(person); - - Person person2 = new Person(); - person2.setId(i++); - person2.setName(""); - person2.setAge(20); - person2.setSex(""); - person2.setAddress("жʳǸͬ"); - person2.setBirthday("2008-01-01"); - person2.setMobile("13820080808"); - person2.setTelephone("20054879"); - person2.setCity(""); - - personList.add(person2); - - Person person3 = new Person(); - person3.setId(i++); - person3.setName(""); - person3.setAge(20); - person3.setSex(""); - person3.setAddress("жʳǸͬ"); - person3.setBirthday("2008-01-01"); - person3.setMobile("13820080808"); - person3.setTelephone("20054879"); - person3.setCity(""); - - personList.add(person3); - - Person person4 = new Person(); - person4.setId(i++); - person4.setName(""); - person4.setAge(20); - person4.setSex("Ů"); - person4.setAddress("жʳǸͬ"); - person4.setBirthday("2008-01-01"); - person4.setMobile("13820080808"); - person4.setTelephone("20054879"); - person4.setCity(""); - - personList.add(person4); - - Person person5 = new Person(); - person5.setId(i++); - person5.setName(""); - person5.setAge(20); - person5.setSex(""); - person5.setAddress("жʳǸͬ"); - person5.setBirthday("2008-01-01"); - person5.setMobile("13820080808"); - person5.setTelephone("20054879"); - person5.setCity(""); - - personList.add(person5); - - Person person6 = new Person(); - person6.setId(i++); - person6.setName(""); - person6.setAge(20); - person6.setSex("Ů"); - person6.setAddress("жʳǸͬ"); - person6.setBirthday("2008-01-01"); - person6.setMobile("13820080808"); - person6.setTelephone("20054879"); - person6.setCity(""); - - personList.add(person6); - - Person person7 = new Person(); - person7.setId(i++); - person7.setName(""); - person7.setAge(20); - person7.setSex(""); - person7.setAddress("кּ36A"); - person7.setBirthday("2008-01-01"); - person7.setMobile("13820080808"); - person7.setTelephone("20054879"); - person7.setCity(""); - - personList.add(person7); - - request.setAttribute("personList", personList); -%> - - - - Insert title here - - - - - - - - - - - - - - - - - - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/taglib/copyright.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/taglib/copyright.jsp deleted file mode 100644 index c8dc90db..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/taglib/copyright.jsp +++ /dev/null @@ -1,13 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://www.victorzhang.com/tags" prefix="taglib" %> - - - - Insert title here - - - -Hello, taglib - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/taglib/helloTag.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/taglib/helloTag.jsp deleted file mode 100644 index 209768fb..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/taglib/helloTag.jsp +++ /dev/null @@ -1,9 +0,0 @@ -<%@ taglib prefix="ex" uri="/WEB-INF/tld/hello.tld" %> - - - A sample custom tag - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/taglib/helloTag2.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/taglib/helloTag2.jsp deleted file mode 100644 index ab30e991..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/taglib/helloTag2.jsp +++ /dev/null @@ -1,11 +0,0 @@ -<%@ taglib prefix="ex" uri="/WEB-INF/tld/hello2.tld" %> - - - A sample custom tag - - - - This is message body - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/taglib/helloTag3.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/taglib/helloTag3.jsp deleted file mode 100644 index e36e2737..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/taglib/helloTag3.jsp +++ /dev/null @@ -1,9 +0,0 @@ -<%@ taglib prefix="ex" uri="/WEB-INF/tld/hello3.tld" %> - - - A sample custom tag - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/test.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/test.jsp deleted file mode 100644 index 8191a043..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/test.jsp +++ /dev/null @@ -1,38 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@page import="io.github.dunwu.javaee.taglib.bean.Person" %> -<%@page import="java.util.ArrayList" %> -<%@page import="java.util.List" %> -<% - if (true) { - List personList = new ArrayList(); - - Person person = new Person(); - person.setId(1); - person.setName("张三"); - person.setAge(20); - person.setSex("男"); - person.setAddress("北京市海淀区上地软件园"); - person.setBirthday("2008-08-08"); - person.setMobile("13820080808"); - person.setTelephone("69653234"); - person.setCity("北京"); - - personList.add(person); - Person person2 = new Person(); - person2.setId(2); - person2.setName("李四"); - person2.setAge(20); - person2.setSex("女"); - person2.setAddress("北京市东皇城根锡拉胡同"); - person2.setBirthday("2008-01-01"); - person2.setMobile("13820080808"); - person2.setTelephone("20054879"); - person2.setCity("北京"); - - personList.add(person2); - - request.setAttribute("personList", personList); - - request.getRequestDispatcher("/index.jsp").forward(request, response); - } -%> diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/toLowerCase.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/toLowerCase.jsp deleted file mode 100644 index 2e7b1f49..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/toLowerCase.jsp +++ /dev/null @@ -1,13 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://www.victorzhang.com/tags" prefix="taglib" %> - - - - Insert title here - - - -Hello, To Lower Case Tag with Body. - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/toUpperCase.jsp b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/toUpperCase.jsp deleted file mode 100644 index c761cf88..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/toUpperCase.jsp +++ /dev/null @@ -1,15 +0,0 @@ -<%@ page language="java" contentType="text/html; charset=UTF-8" %> -<%@ taglib uri="http://www.victorzhang.com/tags" prefix="taglib" %> - - - - Insert title here - - - - - This is a to upper case tag. - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/vote.gif b/codes/javaee-tutorial/javaee-tutorial-taglib/src/main/webapp/vote.gif deleted file mode 100644 index 26a5a3e52a9fbede373ff93f07998c08243b766d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65 zcmZ?wbhEHbWMtrESj52a`QpdFzkh$f_w{D_ja#|5p3QptV$rh;&KDGavM_*v4u}A$ QWngBj2o^|sqQYPe0NYO)l>h($ diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java deleted file mode 100644 index 5fb3a29d..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/JettyFactory.java +++ /dev/null @@ -1,114 +0,0 @@ -package io.github.dunwu.javaee.server; - -import java.util.ArrayList; -import org.apache.commons.lang3.StringUtils; -import org.assertj.core.util.Lists; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.webapp.WebAppClassLoader; -import org.eclipse.jetty.webapp.WebAppContext; - -/** - * JettyFactory 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 - * - * @author Zhang Peng - */ -@SuppressWarnings("unused") -public class JettyFactory { - - public static final int IDE_ECLIPSE = 0; - - public static final int IDE_INTELLIJ = 1; - - private static final int PORT = 9798; - - private static final String CONTEXT = "/"; - - private static final String RESOURCE_BASE_PATH = "src/main/webapp"; - - private static final String WEB_XML_PATH = "/WEB-INF/web.xml"; - - private static final String[] TLD_JAR_NAMES = new String[] {"sitemesh", "spring-webmvc", "shiro-web", "tiles"} - - private static final String WINDOWS_WEBDEFAULT_PATH = "jetty/webdefault.xml"; - - public static Server initServer() { - Profiles.setProfileAsSystemProperty(Profiles.DEVELOPMENT); - WebAppContext webAppContext = new WebAppContext(); - Server server = new Server(PORT); - server.setHandler(webAppContext); - return server; - } - - public static void initWebAppContext(Server server, int type) throws Exception { - System.out.println("[INFO] Application loading"); - WebAppContext webAppContext = (WebAppContext) server.getHandler(); - webAppContext.setContextPath(CONTEXT); - webAppContext.setResourceBase(getAbsolutePath() + RESOURCE_BASE_PATH); - webAppContext.setDescriptor(getAbsolutePath() + RESOURCE_BASE_PATH + WEB_XML_PATH); - - if (IDE_INTELLIJ == type) { - webAppContext.setDefaultsDescriptor(WINDOWS_WEBDEFAULT_PATH); - supportJspAndSetTldJarNames(server, TLD_JAR_NAMES); - } else { - webAppContext.setParentLoaderPriority(true); - } - - System.out.println("[INFO] Application loaded"); - } - - public static String getAbsolutePath() { - String path = null; - String folderPath = JettyFactory.class.getProtectionDomain().getCodeSource().getLocation().getPath() - .substring(1); - if (folderPath.indexOf("target") > 0) { - path = folderPath.substring(0, folderPath.indexOf("target")); - } - return path; - } - - public static void supportJspAndSetTldJarNames(Server server, String... jarNames) { - WebAppContext context = (WebAppContext) server.getHandler(); - // This webapp will use jsps and jstl. We need to enable the - // AnnotationConfiguration in - // order to correctly set up the jsp container - org.eclipse.jetty.webapp.Configuration.ClassList classlist = org.eclipse.jetty.webapp.Configuration.ClassList - .setServerDefault(server); - classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", - "org.eclipse.jetty.annotations.AnnotationConfiguration"); - // Set the ContainerIncludeJarPattern so that jetty examines these container-path - // jars for - // tlds, web-fragments etc. - // If you omit the jar that contains the jstl .tlds, the jsp engine will scan for - // them - // instead. - ArrayList jarNameExprssions = Lists.newArrayList(".*/[^/]*servlet-api-[^/]*\\.jar$", - ".*/javax.servlet.jsp.jstl-.*\\.jar$", ".*/[^/]*taglibs.*\\.jar$"); - - for (String jarName : jarNames) { - jarNameExprssions.add(".*/" + jarName + "-[^/]*\\.jar$"); - } - - context.setAttribute("org.eclipse.jetty.io.github.dunwu.javaee.server.webapp.ContainerIncludeJarPattern", - StringUtils.join(jarNameExprssions, '|')); - } - - public static void reloadWebAppContext(Server server) throws Exception { - WebAppContext webAppContext = (WebAppContext) server.getHandler(); - System.out.println("[INFO] Application reloading"); - webAppContext.stop(); - WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext); - classLoader.addClassPath(getAbsolutePath() + "target/classes"); - classLoader.addClassPath(getAbsolutePath() + "target/test-classes"); - webAppContext.setClassLoader(classLoader); - webAppContext.start(); - System.out.println("[INFO] Application reloaded"); - } - - public static void startServer(Server server) throws Exception { - System.out.println("[HINT] Don't forget to set -XX:MaxPermSize=128m"); - server.start(); - System.out.println("Server running at http://localhost:" + PORT + CONTEXT); - System.out.println("[HINT] Hit Enter to reload the application quickly"); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/Profiles.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/Profiles.java deleted file mode 100644 index dcfd3e39..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/Profiles.java +++ /dev/null @@ -1,34 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2005, 2014 springside.github.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - *******************************************************************************/ -package io.github.dunwu.javaee.server; - -/** - * Spring profile 常用方法与profile名称。 - * - * @author calvin - */ -public class Profiles { - - public static final String ACTIVE_PROFILE = "spring.profiles.active"; - - public static final String DEFAULT_PROFILE = "spring.profiles.default"; - - public static final String PRODUCTION = "production"; - - public static final String DEVELOPMENT = "development"; - - public static final String UNIT_TEST = "test"; - - public static final String FUNCTIONAL_TEST = "functional"; - - /** - * 在Spring启动前,设置profile的环境变量。 - */ - public static void setProfileAsSystemProperty(String profile) { - System.setProperty(ACTIVE_PROFILE, profile); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java b/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java deleted file mode 100644 index 049c8802..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/java/io/github/dunwu/javaee/server/QuickStartServer.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.github.dunwu.javaee.server; - -import org.eclipse.jetty.server.Server; - -/** - * 快速启动 jetty 服务器,方便测试 - * - * @author Zhang Peng - */ -public class QuickStartServer { - - // private static int STARTUP_TYPE = JettyFactory.IDE_ECLIPSE; - private static int STARTUP_TYPE = JettyFactory.IDE_INTELLIJ; - - public static void main(String[] args) throws Exception { - Server server = JettyFactory.initServer(); - JettyFactory.initWebAppContext(server, STARTUP_TYPE); - - try { - JettyFactory.startServer(server); - - // 等待用户输入回车重载应用 - while (true) { - char c = (char) System.in.read(); - if (c == '\n') { - JettyFactory.reloadWebAppContext(server); - } - } - } catch (Exception e) { - e.printStackTrace(); - System.exit(-1); - } - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/resources/jetty/webdefault.xml b/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/resources/jetty/webdefault.xml deleted file mode 100644 index b991d44c..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-taglib/src/test/resources/jetty/webdefault.xml +++ /dev/null @@ -1,534 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - Default web.xml file. - This file is applied to a Web application before it's own WEB_INF/web.xml file - - - - - - - - org.eclipse.jetty.servlet.listener.ELContextCleaner - - - - - - - - org.eclipse.jetty.servlet.listener.IntrospectorCleaner - - - - - - - - - - - - - - - - - default - org.eclipse.jetty.servlet.DefaultServlet - - aliases - false - - - acceptRanges - true - - - dirAllowed - true - - - welcomeServlets - false - - - redirectWelcome - false - - - maxCacheSize - 256000000 - - - maxCachedFileSize - 200000000 - - - maxCachedFiles - 2048 - - - gzip - false - - - etags - false - - - useFileMappedBuffer - false - - - - 0 - - - - default - / - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - jsp - org.eclipse.jetty.jsp.JettyJspServlet - - logVerbosityLevel - DEBUG - - - fork - false - - - xpoweredBy - false - - - compilerTargetVM - 1.7 - - - compilerSourceVM - 1.7 - - - 0 - - - - jsp - *.jsp - *.jspf - *.jspx - *.xsp - *.JSP - *.JSPF - *.JSPX - *.XSP - - - - - - - - 30 - - - - - - - - - - - - - - - index.html - index.htm - index.jsp - - - - - - - - ar - ISO-8859-6 - - - be - ISO-8859-5 - - - bg - ISO-8859-5 - - - ca - ISO-8859-1 - - - cs - ISO-8859-2 - - - da - ISO-8859-1 - - - de - ISO-8859-1 - - - el - ISO-8859-7 - - - en - ISO-8859-1 - - - es - ISO-8859-1 - - - et - ISO-8859-1 - - - fi - ISO-8859-1 - - - fr - ISO-8859-1 - - - hr - ISO-8859-2 - - - hu - ISO-8859-2 - - - is - ISO-8859-1 - - - it - ISO-8859-1 - - - iw - ISO-8859-8 - - - ja - Shift_JIS - - - ko - EUC-KR - - - lt - ISO-8859-2 - - - lv - ISO-8859-2 - - - mk - ISO-8859-5 - - - nl - ISO-8859-1 - - - no - ISO-8859-1 - - - pl - ISO-8859-2 - - - pt - ISO-8859-1 - - - ro - ISO-8859-2 - - - ru - ISO-8859-5 - - - sh - ISO-8859-5 - - - sk - ISO-8859-2 - - - sl - ISO-8859-2 - - - sq - ISO-8859-2 - - - sr - ISO-8859-5 - - - sv - ISO-8859-1 - - - tr - ISO-8859-9 - - - uk - ISO-8859-5 - - - zh - GB2312 - - - zh_TW - Big5 - - - - - - - - - Disable TRACE - / - TRACE - - - - - - Enable everything but TRACE - / - TRACE - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-websocket/pom.xml b/codes/javaee-tutorial/javaee-tutorial-websocket/pom.xml deleted file mode 100644 index 37bdec26..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-websocket/pom.xml +++ /dev/null @@ -1,125 +0,0 @@ - - - 4.0.0 - - io.github.dunwu - javaee-tutorial-websocket - 1.0.0 - war - javaee-tutorial-websocket - JavaEE 学习笔记之 WebSocket - - - UTF-8 - 1.7 - ${java.version} - ${java.version} - - - - - - ch.qos.logback - logback-classic - - - org.slf4j - jcl-over-slf4j - - - - - - javax.servlet - javax.servlet-api - provided - - - javax.servlet.jsp - javax.servlet.jsp-api - provided - - - javax.websocket - javax.websocket-api - provided - - - - - - org.eclipse.jetty - jetty-webapp - - - org.eclipse.jetty - jetty-annotations - - - org.eclipse.jetty - apache-jsp - - - org.eclipse.jetty - apache-jstl - - - org.eclipse.jetty.websocket - javax-websocket-server-impl - - - - - - org.apache.commons - commons-lang3 - - - com.google.guava - guava - - - - - - - - org.apache.tomcat.maven - tomcat7-maven-plugin - 2.2 - - 8089 - /${artifactId} - UTF-8 - - - - org.eclipse.jetty - jetty-maven-plugin - 9.4.18.v20190429 - - - 8089 - - - /${artifactId} - - - - - - - - - - io.github.dunwu - dunwu-dependencies - 0.4.2-SNAPSHOT - pom - import - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/servlet/SocketServlet.java b/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/servlet/SocketServlet.java deleted file mode 100644 index c2730f56..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/servlet/SocketServlet.java +++ /dev/null @@ -1,58 +0,0 @@ -package io.github.dunwu.javaee.servlet; - -import io.github.dunwu.javaee.websocket.WebSocketServer; -import java.io.IOException; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * SocketServlet信息示例 - */ -public class SocketServlet extends HttpServlet { - - private static final long serialVersionUID = -7936817351382556277L; - - private final Logger logger = LoggerFactory.getLogger(this.getClass()); - - /** - * Constructor of the object. - */ - public SocketServlet() { - super(); - } - - public void destroy() { - super.destroy(); // Just puts "destroy" string in log - // Put your code here - } - - public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - this.doGet(request, response); - } - - public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - - // 设置 request,response 编码方式 - response.setCharacterEncoding("UTF-8"); - request.setCharacterEncoding("UTF-8"); - - // 取浏览器提交的 name 参数 - String id = request.getParameter("id"); - String text = request.getParameter("text"); - - if (id != null && id.length() > 0) { - WebSocketServer.send(id, text); - } else { - WebSocketServer.sendAll(text); - } - } - - public void init() throws ServletException { - // Put your code here - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/websocket/WebSocketServer.java b/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/websocket/WebSocketServer.java deleted file mode 100644 index 3b65bd22..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/websocket/WebSocketServer.java +++ /dev/null @@ -1,116 +0,0 @@ -package io.github.dunwu.javaee.websocket; - -import java.io.IOException; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import javax.websocket.server.PathParam; -import javax.websocket.server.ServerEndpoint; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Websocket 消息处理中心 - * - * @author Zhang Peng - * @see https://github.com/jetty-project/embedded-jetty-websocket-examples - */ -@ServerEndpoint(value = "/auth/user/{id}", configurator = WebSocketServerConfigurator.class) -public class WebSocketServer { - - private static final Logger logger = LoggerFactory.getLogger(WebSocketServer.class); - - static Map> userSessionMap = new ConcurrentHashMap<>(); - - // private static Map userSessionMap = new ConcurrentHashMap>(); - - /** - * 发送广播消息 - * - * @param message - */ - public static void sendAll(String message) { - logger.info("SendAll: {}", message); - - for (Set groups : userSessionMap.values()) { - for (Session session : groups) { - try { - session.getBasicRemote().sendObject(message); - } catch (IOException e) { - logger.error(e.toString()); - } catch (EncodeException e) { - logger.error(e.toString()); - } - } - } - } - - public static void send(String userId, String message) { - for (String id : userSessionMap.keySet()) { - if (id.equals(userId)) { - for (Session session : userSessionMap.get(userId)) { - try { - session.getBasicRemote().sendObject(message); - logger.info("SendAll: {}", message); - } catch (Exception e) { - logger.error(e.toString()); - } - } - } - } - } - - @OnMessage - public void onMessage(String message, Session session) throws IOException, InterruptedException { - logger.debug("收到一条客户端消息。session:{}, msg:{}", session.getId(), message); - } - - @OnOpen - public void onOpen(Session session, EndpointConfig config, @PathParam("id") String id) { - logger.info("Session {} connected.", session.getId()); - - // 如果是新 Session,记录进 Map - boolean isNewUser = true; - Iterator i = userSessionMap.entrySet().iterator(); - while (i.hasNext()) { - Map.Entry entry = (Map.Entry) i.next(); - String key = (String) entry.getKey(); - if (key.equals(id)) { - userSessionMap.get(key).add(session); - isNewUser = false; - break; - } - } - if (isNewUser) { - Set sessions = new HashSet<>(); - sessions.add(session); - userSessionMap.put(id, sessions); - } - logger.info("当前在线用户数: {}", userSessionMap.values().size()); - } - - @OnClose - public void onClose(Session session, CloseReason closeReason) { - logger.info("Session {} disconnected. Because of {}", session.getId(), closeReason); - for (Set item : userSessionMap.values()) { - if (item.contains(session)) { - // 删除连接 session - item.remove(session); - // 如果 userId 对应的 session 数为 0 ,删除该 userId 对应的记录 - if (0 == item.size()) { - userSessionMap.values().remove(item); - } - break; - } - } - logger.info("当前在线用户数: {}", userSessionMap.values().size()); - } - - @OnError - public void onError(Throwable error) { - logger.error("Websocket error: {}", error.getMessage()); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/websocket/WebSocketServerConfigurator.java b/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/websocket/WebSocketServerConfigurator.java deleted file mode 100644 index 4b515645..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/java/io/github/dunwu/javaee/websocket/WebSocketServerConfigurator.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.github.dunwu.javaee.websocket; - -import javax.servlet.http.HttpSession; -import javax.websocket.HandshakeResponse; -import javax.websocket.server.HandshakeRequest; -import javax.websocket.server.ServerEndpointConfig; - -/** - * @author Zhang Peng - */ -public class WebSocketServerConfigurator extends ServerEndpointConfig.Configurator { - - @Override - public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) { - HttpSession httpSession = (HttpSession) request.getHttpSession(); - sec.getUserProperties().put(HttpSession.class.getName(), httpSession); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/resources/logback.xml deleted file mode 100644 index ca349629..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/resources/logback.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n - - - - - - ${user.dir}/logs/${DIR_NAME}/all.%d{yyyy-MM-dd}.log - 30 - - - - - 30MB - - - - %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n - - - - - - - - - - - - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/webapp/WEB-INF/web.xml b/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 942745f7..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - SocketServlet - - io.github.dunwu.javaee.servlet.SocketServlet - - - - SocketServlet - /sock/* - - - - index.jsp - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/webapp/websocketA.html b/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/webapp/websocketA.html deleted file mode 100644 index 6b15a582..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/webapp/websocketA.html +++ /dev/null @@ -1,60 +0,0 @@ - - - - - webscocket test - - - - - - - - -
    -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/webapp/websocketB.html b/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/webapp/websocketB.html deleted file mode 100644 index b9b7b081..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-websocket/src/main/webapp/websocketB.html +++ /dev/null @@ -1,60 +0,0 @@ - - - - - webscocket test - - - - - - - - -
    -
    - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-websocket/src/test/java/io/github/dunwu/javaee/server/DevServer.java b/codes/javaee-tutorial/javaee-tutorial-websocket/src/test/java/io/github/dunwu/javaee/server/DevServer.java deleted file mode 100644 index 72849425..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-websocket/src/test/java/io/github/dunwu/javaee/server/DevServer.java +++ /dev/null @@ -1,136 +0,0 @@ -package io.github.dunwu.javaee.server; - -import com.google.common.collect.Lists; -import io.github.dunwu.javaee.websocket.WebSocketServer; -import java.util.ArrayList; -import org.apache.commons.lang3.StringUtils; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.webapp.WebAppClassLoader; -import org.eclipse.jetty.webapp.WebAppContext; -import org.eclipse.jetty.websocket.jsr356.server.ServerContainer; -import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer; - -/** - * DevServer 可以工作在 Eclipse 和 Intellij 中,用来启动 jetty 服务。 Intellij 并不支持jetty,所以要想类似eclipse一样的使用jetty,需要配置webdefault.xml。 - * - * @author Zhang Peng - */ -@SuppressWarnings("unused") -public class DevServer { - - public static final int IDE_ECLIPSE = 0; - - public static final int IDE_INTELLIJ = 1; - - public static final String PRODUCTION = "production"; - - public static final String DEVELOPMENT = "development"; - - public static final String UNIT_TEST = "test"; - - public static final String FUNCTIONAL_TEST = "functional"; - - private static final int PORT = 8089; - - private static final String CONTEXT = "/"; - - private static final String RESOURCE_BASE_PATH = "src/main/webapp"; - - private static final String WEB_XML_PATH = "/WEB-INF/web.xml"; - - private static final String[] TLD_JAR_NAMES = new String[] {"sitemesh", "spring-webmvc", "shiro-web", "tiles"} - - private static final String WINDOWS_WEBDEFAULT_PATH = "jetty/webdefault.xml"; - - public static void main(String[] args) throws Exception { - - // 初始化 WebAppContext - System.out.println("[INFO] Application loading"); - WebAppContext webAppContext = new WebAppContext(); - webAppContext.setContextPath(CONTEXT); - webAppContext.setResourceBase(getAbsolutePath() + RESOURCE_BASE_PATH); - webAppContext.setDescriptor(getAbsolutePath() + RESOURCE_BASE_PATH + WEB_XML_PATH); - webAppContext.setDefaultsDescriptor(WINDOWS_WEBDEFAULT_PATH); - - // 初始化 server - Server server = new Server(PORT); - server.setHandler(webAppContext); - supportJspAndSetTldJarNames(server, TLD_JAR_NAMES); - - System.out.println("[INFO] Application loaded"); - - try { - // Initialize javax.websocket layer - ServerContainer wscontainer = WebSocketServerContainerInitializer.configureContext(webAppContext); - - // Add WebSocket endpoint to javax.websocket layer - wscontainer.addEndpoint(WebSocketServer.class); - - System.out.println("[HINT] Don't forget to set -XX:MaxPermSize=128m"); - System.out.println("Server running at http://localhost:" + PORT + CONTEXT); - System.out.println("[HINT] Hit Enter to reload the application quickly"); - - server.start(); - // server.join(); - - // 等待用户输入回车重载应用 - while (true) { - char c = (char) System.in.read(); - if (c == '\n') { - DevServer.reloadWebAppContext(server); - } - } - } catch (Exception e) { - e.printStackTrace(); - System.exit(-1); - } - } - - private static String getAbsolutePath() { - String path = null; - String folderPath = DevServer.class.getProtectionDomain().getCodeSource().getLocation().getPath().substring(1); - if (folderPath.indexOf("target") > 0) { - path = folderPath.substring(0, folderPath.indexOf("target")); - } - return path; - } - - private static void supportJspAndSetTldJarNames(Server server, String... jarNames) { - WebAppContext context = (WebAppContext) server.getHandler(); - // This webapp will use jsps and jstl. We need to enable the - // AnnotationConfiguration in - // order to correctly set up the jsp container - org.eclipse.jetty.webapp.Configuration.ClassList classlist = org.eclipse.jetty.webapp.Configuration.ClassList - .setServerDefault(server); - classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", - "org.eclipse.jetty.annotations.AnnotationConfiguration"); - // Set the ContainerIncludeJarPattern so that jetty examines these container-path - // jars for - // tlds, web-fragments etc. - // If you omit the jar that contains the jstl .tlds, the jsp engine will scan for - // them - // instead. - ArrayList jarNameExprssions = Lists.newArrayList(".*/[^/]*servlet-api-[^/]*\\.jar$", - ".*/javax.servlet.jsp.jstl-.*\\.jar$", ".*/[^/]*taglibs.*\\.jar$"); - - for (String jarName : jarNames) { - jarNameExprssions.add(".*/" + jarName + "-[^/]*\\.jar$"); - } - - context.setAttribute("org.eclipse.jetty.io.github.dunwu.javaee.server.webapp.ContainerIncludeJarPattern", - StringUtils.join(jarNameExprssions, '|')); - } - - private static void reloadWebAppContext(Server server) throws Exception { - WebAppContext webAppContext = (WebAppContext) server.getHandler(); - System.out.println("[INFO] Application reloading"); - webAppContext.stop(); - WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext); - classLoader.addClassPath(getAbsolutePath() + "target/classes"); - classLoader.addClassPath(getAbsolutePath() + "target/test-classes"); - webAppContext.setClassLoader(classLoader); - webAppContext.start(); - System.out.println("[INFO] Application reloaded"); - } - -} diff --git a/codes/javaee-tutorial/javaee-tutorial-websocket/src/test/resources/jetty/webdefault.xml b/codes/javaee-tutorial/javaee-tutorial-websocket/src/test/resources/jetty/webdefault.xml deleted file mode 100644 index b991d44c..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-websocket/src/test/resources/jetty/webdefault.xml +++ /dev/null @@ -1,534 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - Default web.xml file. - This file is applied to a Web application before it's own WEB_INF/web.xml file - - - - - - - - org.eclipse.jetty.servlet.listener.ELContextCleaner - - - - - - - - org.eclipse.jetty.servlet.listener.IntrospectorCleaner - - - - - - - - - - - - - - - - - default - org.eclipse.jetty.servlet.DefaultServlet - - aliases - false - - - acceptRanges - true - - - dirAllowed - true - - - welcomeServlets - false - - - redirectWelcome - false - - - maxCacheSize - 256000000 - - - maxCachedFileSize - 200000000 - - - maxCachedFiles - 2048 - - - gzip - false - - - etags - false - - - useFileMappedBuffer - false - - - - 0 - - - - default - / - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - jsp - org.eclipse.jetty.jsp.JettyJspServlet - - logVerbosityLevel - DEBUG - - - fork - false - - - xpoweredBy - false - - - compilerTargetVM - 1.7 - - - compilerSourceVM - 1.7 - - - 0 - - - - jsp - *.jsp - *.jspf - *.jspx - *.xsp - *.JSP - *.JSPF - *.JSPX - *.XSP - - - - - - - - 30 - - - - - - - - - - - - - - - index.html - index.htm - index.jsp - - - - - - - - ar - ISO-8859-6 - - - be - ISO-8859-5 - - - bg - ISO-8859-5 - - - ca - ISO-8859-1 - - - cs - ISO-8859-2 - - - da - ISO-8859-1 - - - de - ISO-8859-1 - - - el - ISO-8859-7 - - - en - ISO-8859-1 - - - es - ISO-8859-1 - - - et - ISO-8859-1 - - - fi - ISO-8859-1 - - - fr - ISO-8859-1 - - - hr - ISO-8859-2 - - - hu - ISO-8859-2 - - - is - ISO-8859-1 - - - it - ISO-8859-1 - - - iw - ISO-8859-8 - - - ja - Shift_JIS - - - ko - EUC-KR - - - lt - ISO-8859-2 - - - lv - ISO-8859-2 - - - mk - ISO-8859-5 - - - nl - ISO-8859-1 - - - no - ISO-8859-1 - - - pl - ISO-8859-2 - - - pt - ISO-8859-1 - - - ro - ISO-8859-2 - - - ru - ISO-8859-5 - - - sh - ISO-8859-5 - - - sk - ISO-8859-2 - - - sl - ISO-8859-2 - - - sq - ISO-8859-2 - - - sr - ISO-8859-5 - - - sv - ISO-8859-1 - - - tr - ISO-8859-9 - - - uk - ISO-8859-5 - - - zh - GB2312 - - - zh_TW - Big5 - - - - - - - - - Disable TRACE - / - TRACE - - - - - - Enable everything but TRACE - / - TRACE - - - - - diff --git a/codes/javaee-tutorial/javaee-tutorial-websocket/src/test/resources/logback.xml b/codes/javaee-tutorial/javaee-tutorial-websocket/src/test/resources/logback.xml deleted file mode 100644 index 220d0752..00000000 --- a/codes/javaee-tutorial/javaee-tutorial-websocket/src/test/resources/logback.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n - - - - - - ${user.dir}/logs/${DIR_NAME}/test.%d{yyyy-MM-dd}.log - 30 - - - - - 30MB - - - - %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n - - - - - - - - - - - - - - - - diff --git a/codes/javaee-tutorial/pom.xml b/codes/javaee-tutorial/pom.xml deleted file mode 100644 index f58b67dc..00000000 --- a/codes/javaee-tutorial/pom.xml +++ /dev/null @@ -1,121 +0,0 @@ - - - 4.0.0 - - io.github.dunwu - javaee-tutorial - 1.0.0 - pom - - - javaee-tutorial-servlet - javaee-tutorial-jsp - javaee-tutorial-session - javaee-tutorial-filter - javaee-tutorial-listener - javaee-tutorial-jstl - javaee-tutorial-taglib - javaee-tutorial-oss - javaee-tutorial-websocket - - - - UTF-8 - 1.6 - ${java.version} - ${java.version} - - - 9.4.12.v20180830 - 4.1.4.RELEASE - 1.4.0 - 4.12 - - 2.9.5 - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.12 - - - org.apache.tomcat.maven - tomcat7-maven-plugin - 2.2 - - - org.eclipse.jetty - jetty-maven-plugin - ${jetty.version} - - - org.codehaus.mojo - aspectj-maven-plugin - - 1.2 - - - - org.aspectj - aspectjrt - 1.8.1 - - - org.aspectj - aspectjtools - 1.8.1 - - - - - - compile - test-compile - - - - - true - ${java.version} - ${java.version} - - - - - org.mybatis.generator - mybatis-generator-maven-plugin - 1.3.5 - - - org.mybatis.generator - mybatis-generator-core - 1.3.5 - - - - - - - - 2016-2019 - - - Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0.txt - repo - A business-friendly OSS license - - - - - Zhang Peng - forbreak@163.com - +8 - - - diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/ConsistentHashLoadBalance.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/ConsistentHashLoadBalance.java deleted file mode 100644 index 8cdfc12c..00000000 --- a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/ConsistentHashLoadBalance.java +++ /dev/null @@ -1,65 +0,0 @@ -package io.github.dunwu.javaweb; - -import io.github.dunwu.javaweb.support.HashStrategy; -import io.github.dunwu.javaweb.support.MurmurHashStrategy; - -import java.util.*; - -public class ConsistentHashLoadBalance implements LoadBalance { - - private HashStrategy hashStrategy = new MurmurHashStrategy(); - - private final static int VIRTUAL_NODE_SIZE = 1000; - - private final static String VIRTUAL_NODE_SUFFIX = "&&"; - - private Set nodes = new LinkedHashSet<>(); - - private TreeMap hashRing = new TreeMap<>(); - - @Override - public void buildInList(final Collection collection) { - this.nodes = new LinkedHashSet<>(collection); - this.hashRing = buildConsistentHashRing(this.nodes); - } - - @Override - public void addNode(V node) { - this.nodes.add(node); - this.hashRing = buildConsistentHashRing(this.nodes); - } - - @Override - public void removeNode(V node) { - this.nodes.removeIf(v -> v.equals(node)); - this.hashRing = buildConsistentHashRing(this.nodes); - } - - @Override - public V next() { - return next(UUID.randomUUID().toString()); - } - - public V next(String key) { - int hashCode = hashStrategy.hashCode(key); - // 向右找到第一个 key - Map.Entry entry = hashRing.ceilingEntry(hashCode); - if (entry == null) { - // 想象成一个环,超过尾部则取第一个 key - entry = hashRing.firstEntry(); - } - return entry.getValue(); - } - - private TreeMap buildConsistentHashRing(Set nodes) { - TreeMap hashRing = new TreeMap<>(); - for (V node : nodes) { - for (int i = 0; i < VIRTUAL_NODE_SIZE; i++) { - // 新增虚拟节点的方式如果有影响,也可以抽象出一个由物理节点扩展虚拟节点的类 - hashRing.put(hashStrategy.hashCode(node + VIRTUAL_NODE_SUFFIX + i), node); - } - } - return hashRing; - } - -} diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/LoadBalance.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/LoadBalance.java deleted file mode 100644 index 772b0fe2..00000000 --- a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/LoadBalance.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.github.dunwu.javaweb; - -import java.util.Collection; - -/** - * 负载均衡策略接口 - * - * @author Zhang Peng - * @since 2020-01-21 - */ -public interface LoadBalance { - - void buildInList(Collection collection); - - void addNode(V node); - - void removeNode(V node); - - V next(); - -} diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/Node.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/Node.java deleted file mode 100644 index 41a1f630..00000000 --- a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/Node.java +++ /dev/null @@ -1,77 +0,0 @@ -package io.github.dunwu.javaweb; - -import java.util.Objects; - -/** - * 负载均衡节点 - * - * @author Zhang Peng - * @since 2020-01-23 - */ -public class Node implements Comparable { - - public static final Integer DEFAULT_WEIGHT = 1; - - protected String url; - - protected Integer weight; - - public Node(String url) { - this.url = url; - this.weight = DEFAULT_WEIGHT; - } - - public Node(String url, Integer weight) { - this.url = url; - this.weight = weight; - } - - @Override - public int compareTo(Node o) { - return url.compareTo(o.url); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof Node)) { - return false; - } - Node node = (Node) o; - return url.equals(node.url); - } - - @Override - public int hashCode() { - return Objects.hash(url); - } - - @Override - public String toString() { - return "Node{" + - "url='" + url + '\'' + - ", weight=" + weight + - '}'; - } - - public String getUrl() { - return url; - } - - public Node setUrl(String url) { - this.url = url; - return this; - } - - public Integer getWeight() { - return weight; - } - - public Node setWeight(Integer weight) { - this.weight = weight; - return this; - } - -} diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/RandomLoadBalance.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/RandomLoadBalance.java deleted file mode 100644 index 3577ff2f..00000000 --- a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/RandomLoadBalance.java +++ /dev/null @@ -1,85 +0,0 @@ -package io.github.dunwu.javaweb; - -import io.github.dunwu.tool.collection.CollectionUtil; - -import java.util.*; -import java.util.concurrent.ThreadLocalRandom; - -/** - * (加权)随机负载均衡策略 - * - * @author Zhang Peng - * @see Zhang Peng - * @since 2020-01-20 - */ -public class RandomLoadBalance implements LoadBalance { - - private boolean weightMode; - - private final Random random = ThreadLocalRandom.current(); - - private Set nodes = Collections.emptyNavigableSet(); - - public RandomLoadBalance() { - this.weightMode = false; - } - - public RandomLoadBalance(boolean weightMode) { - this.weightMode = weightMode; - } - - @Override - public void buildInList(final Collection collection) { - this.nodes = new LinkedHashSet<>(collection); - } - - @Override - public void addNode(V node) { - this.nodes.add(node); - } - - @Override - public void removeNode(V node) { - this.nodes.remove(node); - } - - @Override - public V next() { - if (weightMode) { - return getNextInWeightMode(); - } else { - return getNextInNormalMode(); - } - } - - private V getNextInWeightMode() { - if (CollectionUtil.isEmpty(nodes)) { - return null; - } - - List list = new ArrayList<>(); - for (V node : nodes) { - for (int i = 0; i < node.getWeight(); i++) { - list.add(node); - } - } - - int totalWeight = nodes.stream().mapToInt(Node::getWeight).sum(); - int number = random.nextInt(totalWeight); - return list.get(number); - } - - private V getNextInNormalMode() { - if (CollectionUtil.isEmpty(nodes)) { - return null; - } - - int number = random.nextInt(nodes.size()); - Iterator iterator = nodes.iterator(); - while (number-- > 0) { - iterator.next(); - } - return iterator.next(); - } - -} diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/RoundRobinLoadBalance.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/RoundRobinLoadBalance.java deleted file mode 100644 index fdb0f2ef..00000000 --- a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/RoundRobinLoadBalance.java +++ /dev/null @@ -1,87 +0,0 @@ -package io.github.dunwu.javaweb; - -import io.github.dunwu.tool.collection.CollectionUtil; - -import java.util.*; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * (加权)轮询负载均衡策略 - * - * @author Zhang Peng - * @since 2020-01-20 - */ -public class RoundRobinLoadBalance implements LoadBalance { - - private boolean weightMode; - - private AtomicInteger offset = new AtomicInteger(0); - - private Set nodes = Collections.emptyNavigableSet(); - - public RoundRobinLoadBalance() { - this.weightMode = false; - } - - public RoundRobinLoadBalance(boolean weightMode) { - this.weightMode = weightMode; - } - - @Override - public void buildInList(final Collection collection) { - this.offset = new AtomicInteger(0); - this.nodes = new LinkedHashSet<>(collection); - } - - @Override - public void addNode(V node) { - this.nodes.add(node); - } - - @Override - public void removeNode(V node) { - this.nodes.remove(node); - } - - @Override - public V next() { - if (weightMode) { - return getNextInWeightMode(); - } else { - return getNextInNormalMode(); - } - } - - private V getNextInWeightMode() { - if (CollectionUtil.isEmpty(nodes)) { - return null; - } - - int totalWeight = nodes.stream().mapToInt(Node::getWeight).sum(); - int number = offset.getAndIncrement() % totalWeight; - - for (V node : nodes) { - if (node.getWeight() > number) { - return node; - } - number -= node.getWeight(); - } - return null; - } - - private V getNextInNormalMode() { - if (CollectionUtil.isEmpty(this.nodes)) { - return null; - } - - int size = this.nodes.size(); - offset.compareAndSet(size, 0); - int number = offset.getAndIncrement(); - Iterator iterator = nodes.iterator(); - while (number-- > 0) { - iterator.next(); - } - return iterator.next(); - } - -} diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/package-info.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/package-info.java deleted file mode 100644 index b01bb1ce..00000000 --- a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/package-info.java +++ /dev/null @@ -1,7 +0,0 @@ -/** - * 负载均衡算法实现 - * - * @author Zhang Peng - * @since 2020-01-22 - */ -package io.github.dunwu.javaweb; diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/CRCHashStrategy.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/CRCHashStrategy.java deleted file mode 100644 index 4e4dc412..00000000 --- a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/CRCHashStrategy.java +++ /dev/null @@ -1,69 +0,0 @@ -package io.github.dunwu.javaweb.support; - -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; - -public class CRCHashStrategy implements HashStrategy { - - private static final int LOOKUP_TABLE[] = { 0x0000, 0x1021, 0x2042, 0x3063, - 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, - 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210, 0x3273, 0x2252, - 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A, - 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401, - 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, - 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, - 0x76D7, 0x66F6, 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, - 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, - 0x0840, 0x1861, 0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, - 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, - 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, - 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, - 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, - 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, - 0x3E13, 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, - 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, - 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, - 0x5004, 0x4025, 0x7046, 0x6067, 0x83B9, 0x9398, 0xA3FB, 0xB3DA, - 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290, 0x22F3, 0x32D2, - 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, 0x95A8, 0x8589, - 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0, 0x0481, - 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8, - 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, - 0x6657, 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, - 0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, - 0x18C0, 0x08E1, 0x3882, 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, - 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, - 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, - 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, 0x5C64, 0x4C45, - 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, - 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, - 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0, }; - - /** - * Create a CRC16 checksum from the bytes. implementation is from mp911de/lettuce, modified with some more - * optimizations - * - * @param bytes - * @return CRC16 as integer value - */ - public static int getCRC16(byte[] bytes) { - int crc = 0x0000; - - for (byte b : bytes) { - crc = ((crc << 8) ^ LOOKUP_TABLE[((crc >>> 8) ^ (b & 0xFF)) & 0xFF]); - } - return crc & 0xFFFF; - } - - public static int getCRC16(String key) { - return getCRC16(key.getBytes(StandardCharsets.UTF_8)); - } - - @Override - public int hashCode(String key) { - // optimization with modulo operator with power of 2 - // equivalent to getCRC16(key) % 16384 - return getCRC16(key) & (16384 - 1); - } - -} diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/FnvHashStrategy.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/FnvHashStrategy.java deleted file mode 100644 index 541332ba..00000000 --- a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/FnvHashStrategy.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.github.dunwu.javaweb.support; - -public class FnvHashStrategy implements HashStrategy { - - private static final long FNV_32_INIT = 2166136261L; - - private static final int FNV_32_PRIME = 16777619; - - @Override - public int hashCode(String key) { - final int p = FNV_32_PRIME; - int hash = (int) FNV_32_INIT; - for (int i = 0; i < key.length(); i++) - hash = (hash ^ key.charAt(i)) * p; - hash += hash << 13; - hash ^= hash >> 7; - hash += hash << 3; - hash ^= hash >> 17; - hash += hash << 5; - hash = Math.abs(hash); - return hash; - } - -} diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/HashStrategy.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/HashStrategy.java deleted file mode 100644 index 689f0d74..00000000 --- a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/HashStrategy.java +++ /dev/null @@ -1,7 +0,0 @@ -package io.github.dunwu.javaweb.support; - -public interface HashStrategy { - - int hashCode(String key); - -} diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/JdkHashCodeStrategy.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/JdkHashCodeStrategy.java deleted file mode 100644 index b08328cd..00000000 --- a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/JdkHashCodeStrategy.java +++ /dev/null @@ -1,10 +0,0 @@ -package io.github.dunwu.javaweb.support; - -public class JdkHashCodeStrategy implements HashStrategy { - - @Override - public int hashCode(String key) { - return key.hashCode(); - } - -} diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/KetamaHashStrategy.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/KetamaHashStrategy.java deleted file mode 100644 index c8dd7079..00000000 --- a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/KetamaHashStrategy.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.github.dunwu.javaweb.support; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -public class KetamaHashStrategy implements HashStrategy { - - private static MessageDigest md5Digest; - - static { - try { - md5Digest = MessageDigest.getInstance("MD5"); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException("MD5 not supported", e); - } - } - - @Override - public int hashCode(String key) { - byte[] bKey = computeMd5(key); - long rv = ((long) (bKey[3] & 0xFF) << 24) - | ((long) (bKey[2] & 0xFF) << 16) - | ((long) (bKey[1] & 0xFF) << 8) - | (bKey[0] & 0xFF); - return (int) (rv & 0xffffffffL); - } - - /** - * Get the md5 of the given key. - */ - public static byte[] computeMd5(String k) { - MessageDigest md5; - try { - md5 = (MessageDigest) md5Digest.clone(); - } catch (CloneNotSupportedException e) { - throw new RuntimeException("clone of MD5 not supported", e); - } - md5.update(k.getBytes()); - return md5.digest(); - } - -} diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/MurmurHashStrategy.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/MurmurHashStrategy.java deleted file mode 100644 index 35d4a968..00000000 --- a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/MurmurHashStrategy.java +++ /dev/null @@ -1,51 +0,0 @@ -package io.github.dunwu.javaweb.support; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; - -public class MurmurHashStrategy implements HashStrategy { - - @Override - public int hashCode(String key) { - - ByteBuffer buf = ByteBuffer.wrap(key.getBytes()); - int seed = 0x1234ABCD; - - ByteOrder byteOrder = buf.order(); - buf.order(ByteOrder.LITTLE_ENDIAN); - - long m = 0xc6a4a7935bd1e995L; - int r = 47; - - long h = seed ^ (buf.remaining() * m); - - long k; - while (buf.remaining() >= 8) { - k = buf.getLong(); - - k *= m; - k ^= k >>> r; - k *= m; - - h ^= k; - h *= m; - } - - if (buf.remaining() > 0) { - ByteBuffer finish = ByteBuffer.allocate(8).order( - ByteOrder.LITTLE_ENDIAN); - // for big-endian version, do this first: - // finish.position(8-buf.remaining()); - finish.put(buf).rewind(); - h ^= finish.getLong(); - h *= m; - } - h ^= h >>> r; - h *= m; - h ^= h >>> r; - - buf.order(byteOrder); - return (int) (h & 0xffffffffL); - } - -} diff --git a/codes/javaweb-example/javaweb-load-balance/src/test/java/io/github/dunwu/javaweb/LoadBalanceTests.java b/codes/javaweb-example/javaweb-load-balance/src/test/java/io/github/dunwu/javaweb/LoadBalanceTests.java deleted file mode 100644 index 8c2a2054..00000000 --- a/codes/javaweb-example/javaweb-load-balance/src/test/java/io/github/dunwu/javaweb/LoadBalanceTests.java +++ /dev/null @@ -1,164 +0,0 @@ -package io.github.dunwu.javaweb; - -import org.junit.Test; - -import java.util.*; - -/** - * 负载均衡测试 - * - * @author Zhang Peng - * @since 2020-01-20 - */ -public class LoadBalanceTests { - - /** - * 生成 100 个样本节点,权重值为 10 以内的随机数 - */ - private List initNodes() { - Random random = new Random(); - List nodes = new ArrayList<>(); - for (int i = 1; i <= 100; i++) { - Node node = new Node("192.168.0." + i, random.nextInt(10)); - nodes.add(node); - } - return nodes; - } - - /** - * 统计负载均衡命中次数,样本数为 10000 次访问 - */ - private Map staticLoadBalance(LoadBalance algorithm) { - Map staticMap = new TreeMap<>(); - - for (int i = 0; i < 10000; i++) { - Node node = algorithm.next(); - // System.out.printf(">>>> url = %s\n", node.url); - if (staticMap.containsKey(node)) { - Long value = staticMap.get(node); - staticMap.put(node, ++value); - } else { - staticMap.put(node, 1L); - } - } - - System.out.println("======================= 统计数据 ======================="); - staticMap.forEach((key, value) -> { - System.out.printf("key = %s, value = %s\n", key, value); - }); - System.out.printf("方差:%s, ", StatisticsUtil.variance(staticMap.values().toArray(new Long[0]))); - System.out.printf("标准差:%s\n", StatisticsUtil.standardDeviation(staticMap.values().toArray(new Long[] {}))); - return staticMap; - } - - @Test - public void randomLoadBalanceDistribution() { - List nodes = initNodes(); - - LoadBalance loadBalance = new RandomLoadBalance<>(); - loadBalance.buildInList(nodes); - System.out.println("======================= 随机负载均衡 ======================="); - staticLoadBalance(loadBalance); - - LoadBalance loadBalance2 = new RandomLoadBalance<>(true); - loadBalance2.buildInList(nodes); - System.out.println("======================= 加权随机负载均衡 ======================="); - staticLoadBalance(loadBalance2); - } - - @Test - public void randomLoadBalanceUpdateNodes() { - List oldNodes = initNodes(); - List newNodes = oldNodes.subList(0, 80); - - LoadBalance oldLoadBalance = new RandomLoadBalance<>(); - oldLoadBalance.buildInList(oldNodes); - LoadBalance newLoadBalance = new RandomLoadBalance<>(); - newLoadBalance.buildInList(newNodes); - - double count = 0.0d; - int size = newNodes.size(); - for (int i = 0; i < newNodes.size(); i++) { - Node oldNode = oldLoadBalance.next(); - Node newNode = newLoadBalance.next(); - if (oldNode.equals(newNode)) count++; - } - System.out.println(count / size); - } - - @Test - public void roundRobinLoadBalanceDistribution() { - List nodes = initNodes(); - - LoadBalance loadBalance = new RoundRobinLoadBalance<>(); - loadBalance.buildInList(nodes); - System.out.println("======================= 轮询负载均衡 ======================="); - staticLoadBalance(loadBalance); - - LoadBalance loadBalance2 = new RoundRobinLoadBalance<>(true); - loadBalance2.buildInList(nodes); - System.out.println("======================= 加权轮询负载均衡 ======================="); - staticLoadBalance(loadBalance2); - } - - @Test - public void roundRobinLoadBalanceUpdateNodes() { - List oldNodes = initNodes(); - List newNodes = oldNodes.subList(0, 80); - - LoadBalance oldLoadBalance = new RoundRobinLoadBalance<>(); - oldLoadBalance.buildInList(oldNodes); - LoadBalance newLoadBalance = new RoundRobinLoadBalance<>(); - newLoadBalance.buildInList(newNodes); - - double count = 0.0d; - int size = newNodes.size(); - for (int i = 0; i < newNodes.size(); i++) { - Node oldNode = oldLoadBalance.next(); - Node newNode = newLoadBalance.next(); - if (oldNode.equals(newNode)) count++; - } - System.out.println(count / size); - } - - @Test - public void consistentHashLoadBalanceDistribution() { - LoadBalance loadBalance = new ConsistentHashLoadBalance<>(); - loadBalance.buildInList(initNodes()); - System.out.println("======================= 一致性 Hash 负载均衡 ======================="); - staticLoadBalance(loadBalance); - } - - /** - * 测试节点新增删除后的变化程度 - */ - @Test - public void testNodeAddAndRemove() { - // 构造 10000 随机请求 - List keys = new ArrayList<>(); - for (int i = 0; i < 10000; i++) { - keys.add(UUID.randomUUID().toString()); - } - - List nodes = new ArrayList<>(); - for (int i = 1; i <= 100; i++) { - Node node = new Node("192.168.0." + i); - nodes.add(node); - } - - List newNodes = nodes.subList(0, 80); - ConsistentHashLoadBalance oldLoadBalance = new ConsistentHashLoadBalance<>(); - oldLoadBalance.buildInList(nodes); - ConsistentHashLoadBalance newLoadBalance = new ConsistentHashLoadBalance<>(); - newLoadBalance.buildInList(newNodes); - - int count = 0; - for (String key : keys) { - Node oldNode = oldLoadBalance.next(key); - Node newNode = newLoadBalance.next(key); - if (oldNode.equals(newNode)) count++; - } - System.out.println(count / 10000D); - } - -} diff --git a/codes/javaweb-example/javaweb-load-balance/src/test/java/io/github/dunwu/javaweb/StatisticsUtil.java b/codes/javaweb-example/javaweb-load-balance/src/test/java/io/github/dunwu/javaweb/StatisticsUtil.java deleted file mode 100644 index ebc8765f..00000000 --- a/codes/javaweb-example/javaweb-load-balance/src/test/java/io/github/dunwu/javaweb/StatisticsUtil.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.github.dunwu.javaweb; - -public class StatisticsUtil { - - // 方差 s^2=[(x1-x)^2 +...(xn-x)^2]/n - public static double variance(Long[] array) { - int m = array.length; - double sum = 0; - for (Long item : array) {// 求和 - sum += item; - } - double avg = sum / m;// 求平均值 - double value = 0; - for (Long item : array) {// 求方差 - value += (item - avg) * (item - avg); - } - return value / m; - } - - // 标准差σ=sqrt(s^2) - public static double standardDeviation(Long[] array) { - int m = array.length; - double sum = 0; - for (Long item : array) {// 求和 - sum += item; - } - double avg = sum / m;// 求平均值 - double value = 0; - for (Long item : array) {// 求方差 - value += (item - avg) * (item - avg); - } - return Math.sqrt(value / m); - } - -} diff --git a/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/pom.xml b/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/pom.xml deleted file mode 100644 index 6c95de36..00000000 --- a/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/pom.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - 4.0.0 - - io.github.dunwu.javaweb - javaweb-mq-kafka - 1.0.0 - jar - - - UTF-8 - 1.8 - ${java.version} - ${java.version} - - - - - org.apache.kafka - kafka-clients - 1.1.0 - - - org.apache.kafka - kafka-streams - 1.1.0 - - - ch.qos.logback - logback-classic - 1.1.2 - - - diff --git a/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/pom.xml b/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/pom.xml deleted file mode 100644 index e7493e6f..00000000 --- a/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/pom.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - 4.0.0 - - io.github.dunwu.javaweb - javaweb-mq-rocketmq - 1.0.0 - jar - - - UTF-8 - 1.8 - ${java.version} - ${java.version} - - - - - org.apache.rocketmq - rocketmq-client - 4.2.0 - - - ch.qos.logback - logback-classic - 1.1.2 - - - - diff --git a/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedProducer.java b/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedProducer.java deleted file mode 100644 index a781943e..00000000 --- a/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedProducer.java +++ /dev/null @@ -1,46 +0,0 @@ -package io.github.dunwu.javaweb.rocketmq; - -import java.util.List; -import org.apache.rocketmq.client.producer.DefaultMQProducer; -import org.apache.rocketmq.client.producer.MQProducer; -import org.apache.rocketmq.client.producer.MessageQueueSelector; -import org.apache.rocketmq.client.producer.SendResult; -import org.apache.rocketmq.common.message.Message; -import org.apache.rocketmq.common.message.MessageQueue; -import org.apache.rocketmq.remoting.common.RemotingHelper; - -/** - * 发送全局和分区排序的消息。 - * - * @author Zhang Peng - */ -public class OrderedProducer { - - public static void main(String[] args) throws Exception { - // Instantiate with a producer group name. - MQProducer producer = new DefaultMQProducer("example_group_name"); - ((DefaultMQProducer) producer).setNamesrvAddr(RocketConfig.HOST); - // Launch the instance. - producer.start(); - String[] tags = new String[] {"TagA", "TagB", "TagC", "TagD", "TagE"} - for (int i = 0; i < 100; i++) { - int orderId = i % 10; - // Create a message instance, specifying topic, tag and message body. - Message msg = new Message("TopicTestjjj", tags[i % tags.length], "KEY" + i, - ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET)); - SendResult sendResult = producer.send(msg, new MessageQueueSelector() { - @Override - public MessageQueue select(List mqs, Message msg, Object arg) { - Integer id = (Integer) arg; - int index = id % mqs.size(); - return mqs.get(index); - } - }, orderId); - - System.out.printf("%s%n", sendResult); - } - // server shutdown - producer.shutdown(); - } - -} diff --git a/codes/javaweb-example/javaweb-mq/pom.xml b/codes/javaweb-example/javaweb-mq/pom.xml deleted file mode 100644 index 30c2bdb4..00000000 --- a/codes/javaweb-example/javaweb-mq/pom.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - 4.0.0 - - io.github.dunwu.javaweb - javaweb-mq - 1.0.0 - pom - - - javaweb-mq-kafka - javaweb-mq-kafka-spring-boot - javaweb-mq-rocketmq - - diff --git a/codes/javaweb-example/pom.xml b/codes/javaweb-example/pom.xml deleted file mode 100644 index 17c8ed1c..00000000 --- a/codes/javaweb-example/pom.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - 4.0.0 - - io.github.dunwu.javaweb - javaweb-example - 1.0.0 - pom - JAVAWEB-EXAMPLE - JavaWeb 示例源码 - - - javaweb-mq - javaweb-zookeeper - javaweb-load-balance - - - diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index efa1f886..9ae7601e 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -28,69 +28,51 @@ module.exports = { lastUpdated: "上次更新", nav: [ { - text: "Java生态", - link: "/ecology/", - items: [ - { - text: "核心框架", - link: "/ecology/framework/", - }, - { - text: "缓存", - link: "/ecology/cache/", - }, - { - text: "消息队列", - link: "/ecology/mq/", - }, - { - text: "搜索引擎", - link: "/ecology/search/", - }, - { - text: "存储", - link: "/ecology/storage/", - }, - { - text: "微服务", - link: "/ecology/microservices/", - }, - { - text: "安全", - link: "/ecology/security/", - }, - { - text: "测试", - link: "/ecology/test/", - }, - { - text: "服务器", - link: "/ecology/server/", - }, - ] + text: "框架", + link: "/framework/", }, { - text: "Java工具", - link: "/tool/", - items: [ - { - text: "构建", - link: "/tool/build/", - }, - { - text: "IDE", - link: "/tool/ide/", - }, - ] + text: "缓存", + link: "/cache/", + }, + { + text: "消息队列", + link: "/mq/", + }, + { + text: "搜索引擎", + link: "/search/", + }, + { + text: "存储", + link: "/storage/", }, { - text: "JavaEE", - link: "/javaee/" + text: "微服务", + link: "/soa/", + }, + { + text: "安全", + link: "/security/", + }, + { + text: "测试", + link: "/test/", + }, + { + text: "服务器", + link: "/server/", }, { text: "Java系列", ariaLabel: "Java", items: [ + { + text: "Java 教程 📚", + link: "https://dunwu.github.io/java-tutorial/", + target: "_blank", + rel: "" + }, { text: "JavaCore 教程 📚", link: "https://dunwu.github.io/javacore/", diff --git a/docs/README.md b/docs/README.md index 4c2d412f..f5f8a0f2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -20,98 +20,84 @@ footer: MIT Licensed | Copyright © 2018-present Dunwu > ☕ **javatech** 汇总了 Java 开发中常见的主流技术的应用、特性、原理。 > -> - 🔁 项目同步维护:[Github](https://github.com/dunwu/javatech/) | [Gitee](https://gitee.com/turnon/javaweb/) -> - 📖 电子书阅读:[Github Pages](https://dunwu.github.io/javatech/) | [Gitee Pages](http://turnon.gitee.io/javaweb/) +> - 🔁 项目同步维护:[Github](https://github.com/dunwu/javatech/) | [Gitee](https://gitee.com/turnon/javatech/) +> - 📖 电子书阅读:[Github Pages](https://dunwu.github.io/javatech/) | [Gitee Pages](http://turnon.gitee.io/javatech/) +> +> 说明:下面的内容清单中,凡是有 📚 标记的技术,都已整理成详细的教程。 ## Java 生态 -### 框架 +## 框架 - [Spring](https://dunwu.github.io/spring-tutorial/) 📚 - [Spring Boot](https://dunwu.github.io/spring-boot-tutorial/) 📚 -- [Mybatis](ecology/framework/mybatis.md) -- [ShardingSphere](ecology/storage/shardingsphere.md) - -### 消息队列 +- [Mybatis](framework/mybatis.md) +- [ShardingSphere](storage/shardingsphere.md) -- [消息队列面经](ecology/mq/MqInterview.md) -- [Kafka 应用指南基础篇](ecology/mq/kafka-basic.md) -- [Kafka 应用指南进阶篇](ecology/mq/kafka-advance.md) -- [Kafka 运维指南](ecology/mq/kafka-ops.md) -- [RocketMQ 基础篇](ecology/mq/RocketmqBasics.md) -- [RocketMQ 进阶篇](ecology/mq/RocketmqAdvanced.md) -- [ActiveMQ 实战篇](ecology/mq/ActiveMQ.md) +## 消息队列 -### 缓存 +> 消息队列(Message Queue,简称 MQ)技术是分布式应用间交换信息的一种技术。 +> +> 消息队列主要解决应用耦合,异步消息,流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构。是大型分布式系统不可缺少的中间件。 +> +> 如果想深入学习各种消息队列产品,建议先了解一下 [消息队列基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/distributed/mq-theory.md) ,有助于理解消息队列特性的实现和设计思路。 -- [缓存面经](ecology/cache/CacheInterview.md) -- [Redis](ecology/cache/Redis.md) -- [Ehcache](ecology/cache/Ehcache.md) -- [Caffeine](ecology/cache/Caffeine.md) +- [消息队列面经](mq/mq-interview.md) +- [Kafka](mq/kafka) +- [RocketMQ](mq/rocketmq.md) +- [ActiveMQ](mq/activemq.md) -### 安全 +## 缓存 -> Java 领域比较流行的安全框架就是 shiro 和 spring-security。 -> -> shiro 更为简单、轻便,容易理解,能满足大多数基本安全场景下的需要。 +> 缓存可以说是优化系统性能的第一手段,在各种技术中都会有缓存的应用。 > -> spring-security 功能更丰富,也比 shiro 更复杂。值得一提的是由于 spring-security 是 spring 团队开发,所以集成 spring 和 spring-boot 框架更容易。 +> 如果想深入学习缓存,建议先了解一下 [缓存基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/distributed/cache-theory.md),有助于理解缓存的特性、原理,使用缓存常见的问题及解决方案。 -- [shiro](ecology/security/shiro.md) -- [spring-security](ecology/security/spring-security.md) +- [缓存面经](cache/CacheInterview.md) +- [Redis](cache/Redis.md) +- [Ehcache](cache/Ehcache.md) +- [Caffeine](cache/Caffeine.md) -### 微服务 +## 微服务 -- [Dubbo](ecology/microservices/dubbo.md) +- [Dubbo](soa/dubbo.md) - Spring Cloud -- [ZooKeeper 应用指南](ecology/microservices/zookeeper.md) -- [ZooKeeper 运维指南](ecology/microservices/zookeeper-ops.md) +- [ZooKeeper](soa/zookeeper) +- Eureka +- Consul +- Nacos +- Zuul +- Gateway -### 测试 +## 搜索引擎 -- [Junit](ecology/test/junit.md) -- [Mockito](ecology/test/mockito.md) -- [JMH](ecology/test/jmh.md) +- [ElasticSearch](search/elasticsearch) +- Solr +- Lucene -### 服务器 +## 安全 -> Tomcat 和 Jetty 都是 Java 比较流行的轻量级服务器。 +> Java 领域比较流行的安全框架就是 shiro 和 spring-security。 > -> Nginx 是目前最流行的反向代理服务器,也常用于负载均衡。 - -- [Tomcat](ecology/server/tomcat.md) -- [Jetty](ecology/server/jetty.md) -- [Nginx](https://github.com/dunwu/nginx-tutorial) 📚 - -## Java 工具 - -### 构建 - -> [构建工具](tool/build) - -- [Maven 教程 📚](tool/build/maven/README.md) -- [Maven 快速指南](tool/build/maven/maven-quickstart.md) -- [Maven 教程之 pom.xml 详解](tool/build/maven/maven-pom.md) -- [Maven 教程之 settings.xml 详解](tool/build/maven/maven-settings.md) -- [Maven 实战问题和最佳实践](tool/build/maven/maven-action.md) -- [Maven 教程之发布 jar 到私服或中央仓库](tool/build/maven/maven-deploy.md) -- [Maven 插件之代码检查](tool/build/maven/maven-checkstyle-plugin.md) -- [Ant 简易教程](tool/build/ant.md) +> shiro 更为简单、轻便,容易理解,能满足大多数基本安全场景下的需要。 +> +> spring-security 功能更丰富,也比 shiro 更复杂。值得一提的是由于 spring-security 是 spring 团队开发,所以集成 spring 和 spring-boot 框架更容易。 -### IDE +- [Shiro](security/shiro.md) +- [Spring Security](security/spring-security.md) -> [IDE](tool/ide) +## 测试 -- [Intellij Idea](tool/ide/intellij-idea.md) -- [Eclipse](tool/ide/eclipse.md) -- [vscode](tool/ide/vscode.md) +- [Junit](test/junit.md) +- [Mockito](test/mockito.md) +- [JMH](test/jmh.md) -## JavaEE +## 服务器 -> [☕ JavaEE](javaee/README.md) 技术是 Java Web 的基石 +> Tomcat 和 Jetty 都是 Java 比较流行的轻量级服务器。 +> +> Nginx 是目前最流行的反向代理服务器,也常用于负载均衡。 -- [JavaEE 面经](javaee/javaee-interview.md) -- [Servlet](javaee/javaee-servlet.md) -- [Jsp](javaee/javaee-jsp.md) -- [Filter 和 Listener](javaee/javaee-filter-listener.md) -- [Cookie 和 Session](javaee/javaee-cookie-sesion.md) +- [Tomcat](server/tomcat.md) +- [Jetty](server/jetty.md) +- [Nginx](https://github.com/dunwu/nginx-tutorial) 📚 diff --git a/docs/ecology/cache/CacheInterview.md b/docs/cache/CacheInterview.md similarity index 100% rename from docs/ecology/cache/CacheInterview.md rename to docs/cache/CacheInterview.md diff --git a/docs/ecology/cache/Caffeine.md b/docs/cache/Caffeine.md similarity index 100% rename from docs/ecology/cache/Caffeine.md rename to docs/cache/Caffeine.md diff --git a/docs/ecology/cache/Ehcache.md b/docs/cache/Ehcache.md similarity index 100% rename from docs/ecology/cache/Ehcache.md rename to docs/cache/Ehcache.md diff --git a/docs/ecology/cache/README.md b/docs/cache/README.md similarity index 100% rename from docs/ecology/cache/README.md rename to docs/cache/README.md diff --git a/docs/ecology/cache/Redis.md b/docs/cache/Redis.md similarity index 100% rename from docs/ecology/cache/Redis.md rename to docs/cache/Redis.md diff --git a/docs/ecology/README.md b/docs/ecology/README.md deleted file mode 100644 index 896cb376..00000000 --- a/docs/ecology/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# 主流技术 - -> 主流技术汇集 JavaWeb 开发常用的各种主流技术。 - -- **Web 框架** - - [Spring 教程 📚](https://dunwu.github.io/spring-tutorial/) - - [Spring Boot 教程 📚](https://dunwu.github.io/spring-boot-tutorial/) -- **服务器** - - [Tomcat 应用指南](technology/server/Tomcat.md) - - [Jetty 应用指南](technology/server/Jetty.md) - - [Nginx 简易教程 📚](https://github.com/dunwu/nginx-tutorial) -- **消息队列** - - [消息队列面经](technology/mq/MqInterview.md) - - [Kafka 应用指南基础篇](technology/mq/kafka-basic.md) - - [Kafka 应用指南进阶篇](technology/mq/kafka-advance.md) - - [Kafka 运维指南](technology/mq/kafka-ops.md) - - [RocketMQ 基础篇](technology/mq/RocketmqBasics.md) - - [RocketMQ 进阶篇](technology/mq/RocketmqAdvanced.md) - - [ActiveMQ 实战篇](technology/mq/ActiveMQ.md) -- **缓存** - - [缓存面经](technology/cache/CacheInterview.md) - - [Redis](technology/cache/Redis.md) - - [Ehcache](technology/cache/Ehcache.md) - - [Caffeine](technology/cache/Caffeine.md) -- **RPC** - - [Dubbo 应用指南](technology/rpc/Dubbo.md) - - Spring Cloud -- **安全** - - [Shiro 应用指南](technology/security/Shiro.md) - - [Spring Security 应用指南](technology/security/SpringSecurity.md) -- **数据** - - [Mybatis 应用指南](technology/data/Mybatis.md) - - [ShardingSphere 应用指南](technology/data/ShardingSphere.md) -- **协调/监控/诊断/测试** - - **协调** - - [ZooKeeper 应用指南](technology/monitor/zookeeper.md) - - [ZooKeeper 运维指南](technology/monitor/zookeeper-ops.md) - - **诊断** - - [Arthas 应用指南](technology/monitor/arthas.md) - - **测试** - - [Jmeter 应用指南](technology/monitor/jmeter.md) diff --git a/docs/ecology/microservices/README.md b/docs/ecology/microservices/README.md deleted file mode 100644 index 69439d9e..00000000 --- a/docs/ecology/microservices/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Java 和 RPC - -- [Dubbo](dubbo.md) -- Spring Cloud diff --git a/docs/ecology/mq/README.md b/docs/ecology/mq/README.md deleted file mode 100644 index 106224c3..00000000 --- a/docs/ecology/mq/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Java 和消息队列 - -- [消息队列面经](MqInterview.md) -- [Kafka 应用指南基础篇](kafka-basic.md) -- [Kafka 应用指南进阶篇](kafka-advance.md) -- [Kafka 运维指南](kafka-ops.md) -- [RocketMQ 基础篇](RocketmqBasics.md) -- [RocketMQ 进阶篇](RocketmqAdvanced.md) -- [ActiveMQ 实战篇](ActiveMQ.md) diff --git a/docs/ecology/mq/RocketmqAdvanced.md b/docs/ecology/mq/RocketmqAdvanced.md deleted file mode 100644 index ece19698..00000000 --- a/docs/ecology/mq/RocketmqAdvanced.md +++ /dev/null @@ -1,192 +0,0 @@ -# RocketMQ 进阶篇 - -## 架构 - -
    - -
    更详细信息可以参考官方文档:[here](http://rocketmq.apache.org/rocketmq/four-methods-to-feed-name-server-address-list/) - -### Broker - -Broker 通过提供轻量级的 TOPIC 和 QUEUE 机制来处理消息存储。它们支持 Push 和 Pull 模型,包含容错机制(2 个副本或 3 个副本),并提供强大的峰值填充和以原始时间顺序累积数千亿条消息的能力。此外,Brokers 还提供灾难恢复,丰富的指标统计和警报机制。 - -Broker 有几个重要的子模块: - -- Remoting Module - 即代理的条目,处理来自客户端的请求。 -- Client Manager - 管理客户(生产者/消费者)并维护消费者的主题订阅。 -- Store 服务 - 提供简单的 API 来存储或查询物理磁盘中的消息。 -- HA 服务 - 提供主代理和从代理之间的数据同步功能。 -- Index 服务 - 按指定密钥构建消息索引,并提供快速消息查询。 - -
    - -
    - -### Producer - -Producers 支持分布式部署。Distributed Producers 通过多种负载均衡模式向 Broker 集群发送消息。发送过程支持快速故障并具有低延迟。 - -### Consumer - -Consumer 也支持 Push 和 Pull 模型中的分布式部署。它还支持群集消费和消息广播。它提供实时消息订阅机制,可以满足大多数消费者的需求。 RocketMQ 的网站为感兴趣的用户提供了一个简单的快速入门指南。 - -## 关键特性以及其实现原理 - -分布式消息系统作为实现分布式系统可扩展、可伸缩性的关键组件,需要具有高吞吐量、高可用等特点。而谈到消息系统的设计,就回避不了两个问题: - -1. 消息的顺序问题 -2. 消息的重复问题 - -### 顺序消息 - -#### 第一种模型 - -假如生产者产生了 2 条消息:M1、M2,要保证这两条消息的顺序,应该怎样做?你脑中想到的可能是这样: - -
    - -
    - -假定 M1 发送到 S1,M2 发送到 S2,如果要保证 M1 先于 M2 被消费,那么需要 M1 到达消费端被消费后,通知 S2,然后 S2 再将 M2 发送到消费端。 - -这个模型存在的问题是,如果 M1 和 M2 分别发送到两台 Server 上,就不能保证 M1 先达到 MQ 集群,也不能保证 M1 被先消费。换个角度看,如果 M2 先于 M1 达到 MQ 集群,甚至 M2 被消费后,M1 才达到消费端,这时消息也就乱序了,说明以上模型是不能保证消息的顺序的。 - -
    - -
    - -#### 第二种模型 - -如何才能在 MQ 集群保证消息的顺序?一种简单的方式就是将 M1、M2 发送到同一个 Server 上: - -这样可以保证 M1 先于 M2 到达 MQServer(生产者等待 M1 发送成功后再发送 M2),根据先达到先被消费的原则,M1 会先于 M2 被消费,这样就保证了消息的顺序。 - -这个模型也仅仅是理论上可以保证消息的顺序,在实际场景中可能会遇到下面的问题: - -
    - -
    - -只要将消息从一台服务器发往另一台服务器,就会存在网络延迟问题。如上图所示,如果发送 M1 耗时大于发送 M2 的耗时,那么 M2 就仍将被先消费,仍然不能保证消息的顺序。即使 M1 和 M2 同时到达消费端,由于不清楚消费端 1 和消费端 2 的负载情况,仍然有可能出现 M2 先于 M1 被消费的情况。 - -如何解决这个问题?将 M1 和 M2 发往同一个消费者,且发送 M1 后,需要消费端响应成功后才能发送 M2。 - -这可能产生另外的问题:如果 M1 被发送到消费端后,消费端 1 没有响应,那是继续发送 M2 呢,还是重新发送 M1?一般为了保证消息一定被消费,肯定会选择重发 M1 到另外一个消费端 2,就如下图所示。 - -
    - -
    - -这样的模型就严格保证消息的顺序,细心的你仍然会发现问题,消费端 1 没有响应 Server 时有两种情况,一种是 M1 确实没有到达(数据在网络传送中丢失),另外一种消费端已经消费 M1 且已经发送响应消息,只是 MQ Server 端没有收到。如果是第二种情况,重发 M1,就会造成 M1 被重复消费。也就引入了我们要说的第二个问题,消息重复问题,这个后文会详细讲解。 - -回过头来看消息顺序问题,严格的顺序消息非常容易理解,也可以通过文中所描述的方式来简单处理。总结起来,要实现严格的顺序消息,简单且可行的办法就是: - -**保证生产者 - MQServer - 消费者是一对一对一的关系。** - -这样的设计虽然简单易行,但也会存在一些很严重的问题,比如: - -1. 并行度就会成为消息系统的瓶颈(吞吐量不够) -2. 更多的异常处理,比如:只要消费端出现问题,就会导致整个处理流程阻塞,我们不得不花费更多的精力来解决阻塞的问题。 - -RocketMQ 的解决方案:通过合理的设计或者将问题分解来规避。如果硬要把时间花在解决问题本身,实际上不仅效率低下,而且也是一种浪费。从这个角度来看消息的顺序问题,我们可以得出两个结论: - -1. 不关注乱序的应用实际大量存在 -2. 队列无序并不意味着消息无序 - -最后我们从源码角度分析 RocketMQ 怎么实现发送顺序消息。 - -RocketMQ 通过轮询所有队列的方式来确定消息被发送到哪一个队列(负载均衡策略)。比如下面的示例中,订单号相同的消息会被先后发送到同一个队列中: - -```java -// RocketMQ 通过 MessageQueueSelector 中实现的算法来确定消息发送到哪一个队列上 -// RocketMQ 默认提供了两种 MessageQueueSelector 实现:随机/Hash -// 当然你可以根据业务实现自己的 MessageQueueSelector 来决定消息按照何种策略发送到消息队列中 -SendResult sendResult = producer.send(msg, new MessageQueueSelector() { - @Override - public MessageQueue select(List mqs, Message msg, Object arg) { - Integer id = (Integer) arg; - int index = id % mqs.size(); - return mqs.get(index); - } -}, orderId); -``` - -在获取到路由信息以后,会根据 MessageQueueSelector 实现的算法来选择一个队列,同一个 OrderId 获取到的肯定是同一个队列。 - -```java -private SendResult send() { - // 获取topic路由信息 - TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic()); - if (topicPublishInfo != null && topicPublishInfo.ok()) { - MessageQueue mq = null; - // 根据我们的算法,选择一个发送队列 - // 这里的arg = orderId - mq = selector.select(topicPublishInfo.getMessageQueueList(), msg, arg); - if (mq != null) { - return this.sendKernelImpl(msg, mq, communicationMode, sendCallback, timeout); - } - } -} -``` - -### 消息重复 - -造成消息重复的根本原因是:网络不可达。只要通过网络交换数据,就无法避免这个问题。所以解决这个问题的办法就是绕过这个问题。那么问题就变成了:如果消费端收到两条一样的消息,应该怎样处理? - -1. 消费端处理消息的业务逻辑保持幂等性。 -2. 保证每条消息都有唯一编号且保证消息处理成功与去重表的日志同时出现。 - -第 1 条很好理解,只要保持幂等性,不管来多少条重复消息,最后处理的结果都一样。 - -第 2 条原理就是利用一张日志表来记录已经处理成功的消息的 ID,如果新到的消息 ID 已经在日志表中,那么就不再处理这条消息。 - -第 1 条解决方案,很明显应该在消费端实现,不属于消息系统要实现的功能。 - -第 2 条可以消息系统实现,也可以业务端实现。正常情况下出现重复消息的概率其实很小,如果由消息系统来实现的话,肯定会对消息系统的吞吐量和高可用有影响,所以最好还是由业务端自己处理消息重复的问题,这也是 RocketMQ 不解决消息重复的问题的原因。 - -**RocketMQ 不保证消息不重复,如果你的业务需要保证严格的不重复消息,需要你自己在业务端去重。** - -### 事务消息 - -RocketMQ 除了支持普通消息,顺序消息,另外还支持事务消息。 - -假设这样的场景: - -![img](https://upload-images.jianshu.io/upload_images/3101171-253d8bd65736694f.png) - -图中执行本地事务(Bob 账户扣款)和发送异步消息应该保证同时成功或者同时失败,也就是扣款成功了,发送消息一定要成功,如果扣款失败了,就不能再发送消息。那问题是:我们是先扣款还是先发送消息呢? - -![img](http://upload-images.jianshu.io/upload_images/3101171-088dc074c4ecd192) - -RocketMQ 分布式事务步骤: - -发送 Prepared 消息 2222222222222222222,并拿到接受消息的地址。 -执行本地事务 -通过第 1 步骤拿到的地址去访问消息,并修改消息状态。 - -## 资料 - -- [RocketMQ 官方文档](http://rocketmq.apache.org/docs/quick-start/) -- [分布式开放消息系统(RocketMQ)的原理与实践](https://www.jianshu.com/p/453c6e7ff81c) diff --git a/docs/ecology/framework/README.md b/docs/framework/README.md similarity index 100% rename from docs/ecology/framework/README.md rename to docs/framework/README.md diff --git a/docs/ecology/framework/mybatis.md b/docs/framework/mybatis.md similarity index 100% rename from docs/ecology/framework/mybatis.md rename to docs/framework/mybatis.md diff --git a/docs/ecology/io/dozer.md b/docs/io/dozer.md similarity index 100% rename from docs/ecology/io/dozer.md rename to docs/io/dozer.md diff --git a/docs/ecology/io/javalib-binary.md b/docs/io/javalib-binary.md similarity index 100% rename from docs/ecology/io/javalib-binary.md rename to docs/io/javalib-binary.md diff --git a/docs/ecology/io/javalib-json.md b/docs/io/javalib-json.md similarity index 100% rename from docs/ecology/io/javalib-json.md rename to docs/io/javalib-json.md diff --git a/docs/ecology/io/javalib-log.md b/docs/io/javalib-log.md similarity index 100% rename from docs/ecology/io/javalib-log.md rename to docs/io/javalib-log.md diff --git a/docs/javaee/README.md b/docs/javaee/README.md deleted file mode 100644 index be0701d0..00000000 --- a/docs/javaee/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# ☕ JavaEE - -## 知识点 - -- [JavaEE 面经](javaee-interview.md) -- [JavaEE 之 Servlet 指南](javaee-servlet.md) -- [JavaEE 之 Jsp 指南](javaee-jsp.md) -- [JavaEE 之 Filter 和 Listener](javaee-filter-listener.md) -- [JavaEE 之 Cookie 和 Session](javaee-cookie-sesion.md) - -## 学习资料 - -- 书 - - [Java Web 整合开发王者归来](https://book.douban.com/subject/4189495/) - - [Head First Servlets & JSP](https://book.douban.com/subject/1942934/) -- 教程 - - [博客园孤傲苍狼 JavaWeb 学习总结](https://www.cnblogs.com/xdp-gacl/tag/JavaWeb%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93/) - - [Servlet 教程](https://www.runoob.com/servlet/servlet-tutorial.html) - - [JSP 教程](https://www.runoob.com/jsp/jsp-tutorial.html) diff --git a/docs/javaee/javaee-cookie-sesion.md b/docs/javaee/javaee-cookie-sesion.md deleted file mode 100644 index 829d8ecf..00000000 --- a/docs/javaee/javaee-cookie-sesion.md +++ /dev/null @@ -1,574 +0,0 @@ -# JavaEE 之 Cookie 和 Session - -## Cookie - -由于 Http 是一种无状态的协议,服务器单从网络连接上无从知道客户身份。 - -会话跟踪是 Web 程序中常用的技术,用来跟踪用户的整个会话。常用会话跟踪技术是 Cookie 与 Session。 - -### Cookie 是什么 - -Cookie 实际上是存储在客户端上的文本信息,并保留了各种跟踪的信息。 - -**Cookie 工作步骤:** - -1. 客户端请求服务器,如果服务器需要记录该用户的状态,就是用 response 向客户端浏览器颁发一个 Cookie。 -2. 客户端浏览器会把 Cookie 保存下来。 -3. 当浏览器再请求该网站时,浏览器把该请求的网址连同 Cookie 一同提交给服务器。服务器检查该 Cookie,以此来辨认用户状态。 - -**_注:Cookie 功能需要浏览器的支持,如果浏览器不支持 Cookie 或者 Cookie 禁用了,Cookie 功能就会失效。_** - -Java 中把 Cookie 封装成了`javax.servlet.http.Cookie`类。 - -### Cookie 剖析 - -Cookies 通常设置在 HTTP 头信息中(虽然 JavaScript 也可以直接在浏览器上设置一个 Cookie)。 - -设置 Cookie 的 Servlet 会发送如下的头信息: - -```http -HTTP/1.1 200 OK -Date: Fri, 04 Feb 2000 21:03:38 GMT -Server: Apache/1.3.9 (UNIX) PHP/4.0b3 -Set-Cookie: name=xyz; expires=Friday, 04-Feb-07 22:03:38 GMT; - path=/; domain=w3cschool.cc -Connection: close -Content-Type: text/html -``` - -正如您所看到的,`Set-Cookie` 头包含了一个名称值对、一个 GMT 日期、一个路径和一个域。名称和值会被 URL 编码。expires 字段是一个指令,告诉浏览器在给定的时间和日期之后"忘记"该 Cookie。 - -如果浏览器被配置为存储 Cookies,它将会保留此信息直到到期日期。如果用户的浏览器指向任何匹配该 Cookie 的路径和域的页面,它会重新发送 Cookie 到服务器。浏览器的头信息可能如下所示: - -```http -GET / HTTP/1.0 -Connection: Keep-Alive -User-Agent: Mozilla/4.6 (X11; I; Linux 2.2.6-15apmac ppc) -Host: zink.demon.co.uk:1126 -Accept: image/gif, */* -Accept-Encoding: gzip -Accept-Language: en -Accept-Charset: iso-8859-1,*,utf-8 -Cookie: name=xyz -``` - -### Cookie 类中的方法 - -| 方法 | 功能 | -| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | -| public void setDomain(String pattern) | 该方法设置 cookie 适用的域。 | -| public String getDomain() | 该方法获取 cookie 适用的域。 | -| public void setMaxAge(int expiry) | 该方法设置 cookie 过期的时间(以秒为单位)。如果不这样设置,cookie 只会在当前 session 会话中持续有效。 | -| public int getMaxAge() | 该方法返回 cookie 的最大生存周期(以秒为单位),默认情况下,-1 表示 cookie 将持续下去,直到浏览器关闭。 | -| public String getName() | 该方法返回 cookie 的名称。名称在创建后不能改变。 | -| public void setValue(String newValue) | 该方法设置与 cookie 关联的值。 | -| public String getValue() | 该方法获取与 cookie 关联的值。 | -| public void setPath(String uri) | 该方法设置 cookie 适用的路径。如果您不指定路径,与当前页面相同目录下的(包括子目录下的)所有 URL 都会返回 cookie。 | -| public String getPath() | 该方法获取 cookie 适用的路径。 | -| public void setSecure(boolean flag) | 该方法设置布尔值,向浏览器指示,只会在 HTTPS 和 SSL 等安全协议中传输此类 Cookie。 | -| public void setComment(String purpose) | 该方法规定了描述 cookie 目的的注释。该注释在浏览器向用户呈现 cookie 时非常有用。 | -| public String getComment() | 该方法返回了描述 cookie 目的的注释,如果 cookie 没有注释则返回 null。 | - -### Cookie 的有效期 - -`Cookie`的`maxAge`决定着 Cookie 的有效期,单位为秒。 - -如果 maxAge 为 0,则表示删除该 Cookie; - -如果为负数,表示该 Cookie 仅在本浏览器中以及本窗口打开的子窗口内有效,关闭窗口后该 Cookie 即失效。 - -Cookie 中提供`getMaxAge()`**和**`setMaxAge(int expiry)`方法来读写`maxAge`属性。 - -### Cookie 的域名 - -Cookie 是不可以跨域名的。域名 www.google.com 颁发的 Cookie 不会被提交到域名 www.baidu.com 去。这是由 Cookie 的隐私安全机制决定的。隐私安全机制能够禁止网站非法获取其他网站的 Cookie。 - -正常情况下,同一个一级域名的两个二级域名之间也不能互相使用 Cookie。如果想让某域名下的子域名也可以使用该 Cookie,需要设置 Cookie 的 domain 参数。 - -Java 中使用`setDomain(Stringdomain)`和`getDomain()`方法来设置、获取 domain。 - -### Cookie 的路径 - -Path 属性决定允许访问 Cookie 的路径。 - -Java 中使用`setPath(Stringuri)`和`getPath()`方法来设置、获取 path。 - -### Cookie 的安全属性 - -HTTP 协议不仅是无状态的,而且是不安全的。 - -使用 HTTP 协议的数据不经过任何加密就直接在网络上传播,有被截获的可能。如果不希望 Cookie 在 HTTP 等非安全协议中传输,可以设置 Cookie 的 secure 属性为 true。浏览器只会在 HTTPS 和 SSL 等安全协议中传输此类 Cookie。 - -Java 中使用`setSecure(booleanflag)`和`getSecure ()`方法来设置、获取 Secure。 - -### Cookie 实例 - -#### 添加 Cookie - -通过 Servlet 添加 Cookies 包括三个步骤: - -1. 创建一个 Cookie 对象:您可以调用带有 cookie 名称和 cookie 值的 Cookie 构造函数,cookie 名称和 cookie 值都是字符串。 - -2. 设置最大生存周期:您可以使用 `setMaxAge` 方法来指定 cookie 能够保持有效的时间(以秒为单位)。 - -3. 发送 Cookie 到 HTTP 响应头:您可以使用 `response.addCookie` 来添加 HTTP 响应头中的 Cookies。 - -AddCookies.java - -```java -import java.io.IOException; -import java.io.PrintWriter; -import java.net.URLEncoder; - -import javax.servlet.ServletException; -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -@WebServlet("/servlet/AddCookies") -public class AddCookies extends HttpServlet { - private static final long serialVersionUID = 1L; - - /** - * @see HttpServlet#HttpServlet() - */ - public AddCookies() { - super(); - } - - /** - * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) - */ - public void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - // 为名字和姓氏创建 Cookie - Cookie name = new Cookie("name", URLEncoder.encode(request.getParameter("name"), "UTF-8")); // 中文转码 - Cookie url = new Cookie("url", request.getParameter("url")); - - // 为两个 Cookie 设置过期日期为 24 小时后 - name.setMaxAge(60 * 60 * 24); - url.setMaxAge(60 * 60 * 24); - - // 在响应头中添加两个 Cookie - response.addCookie(name); - response.addCookie(url); - - // 设置响应内容类型 - response.setContentType("text/html;charset=UTF-8"); - - PrintWriter out = response.getWriter(); - String title = "设置 Cookie 实例"; - String docType = "\n"; - out.println(docType + "\n" + "" + title + "\n" - + "\n" + "

    " + title - + "

    \n" + "
      \n" + "
    • 站点名::" + request.getParameter("name") - + "\n
    • " + "
    • 站点 URL::" + request.getParameter("url") - + "\n
    • " + "
    \n" + ""); - } - - /** - * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) - */ - protected void doPost(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - doGet(request, response); - } - -} -``` - -addCookies.jsp - -```java -<%@ page language="java" pageEncoding="UTF-8" %> - - - - - 添加Cookie - - -
    - 站点名 : -
    - 站点 URL:
    - -
    - - -``` - -#### 显示 Cookie - -要读取 Cookies,您需要通过调用 `HttpServletRequest` 的 `getCookies()` 方法创建一个 `javax.servlet.http.Cookie` 对象的数组。然后循环遍历数组,并使用 `getName()` 和 `getValue()` 方法来访问每个 cookie 和关联的值。 - -ReadCookies.java - -```java -import java.io.IOException; -import java.io.PrintWriter; -import java.net.URLDecoder; - -import javax.servlet.ServletException; -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -@WebServlet("/servlet/ReadCookies") -public class ReadCookies extends HttpServlet { - private static final long serialVersionUID = 1L; - - /** - * @see HttpServlet#HttpServlet() - */ - public ReadCookies() { - super(); - } - - /** - * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) - */ - public void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - Cookie cookie = null; - Cookie[] cookies = null; - // 获取与该域相关的 Cookie 的数组 - cookies = request.getCookies(); - - // 设置响应内容类型 - response.setContentType("text/html;charset=UTF-8"); - - PrintWriter out = response.getWriter(); - String title = "Delete Cookie Example"; - String docType = "\n"; - out.println(docType + "\n" + "" + title + "\n" - + "\n"); - if (cookies != null) { - out.println("

    Cookie 名称和值

    "); - for (int i = 0; i < cookies.length; i++) { - cookie = cookies[i]; - if ((cookie.getName()).compareTo("name") == 0) { - cookie.setMaxAge(0); - response.addCookie(cookie); - out.print("已删除的 cookie:" + cookie.getName() + "
    "); - } - out.print("名称:" + cookie.getName() + ","); - out.print("值:" + URLDecoder.decode(cookie.getValue(), "utf-8") + "
    "); - } - } else { - out.println("

    No Cookie founds

    "); - } - out.println(""); - out.println(""); - } - - /** - * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) - */ - protected void doPost(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - doGet(request, response); - } - -} -``` - -#### 删除 Cookie - -Java 中并没有提供直接删除 Cookie 的方法,如果想要删除一个 Cookie,直接将这个 Cookie 的有效期设为 0 就可以了。步骤如下: - -1. 读取一个现有的 cookie,并把它存储在 Cookie 对象中。 - -2. 使用 `setMaxAge()` 方法设置 cookie 的年龄为零,来删除现有的 cookie。 - -3. 把这个 cookie 添加到响应头。 - -DeleteCookies.java - -```java -import java.io.IOException; -import java.io.PrintWriter; - -import javax.servlet.ServletException; -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -@WebServlet("/servlet/DeleteCookies") -public class DeleteCookies extends HttpServlet { - private static final long serialVersionUID = 1L; - - /** - * @see HttpServlet#HttpServlet() - */ - public DeleteCookies() { - super(); - } - - /** - * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) - */ - public void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - Cookie cookie = null; - Cookie[] cookies = null; - // 获取与该域相关的 Cookie 的数组 - cookies = request.getCookies(); - - // 设置响应内容类型 - response.setContentType("text/html;charset=UTF-8"); - - PrintWriter out = response.getWriter(); - String title = "删除 Cookie 实例"; - String docType = "\n"; - out.println(docType + "\n" + "" + title + "\n" - + "\n"); - if (cookies != null) { - out.println("

    Cookie 名称和值

    "); - for (int i = 0; i < cookies.length; i++) { - cookie = cookies[i]; - if ((cookie.getName()).compareTo("url") == 0) { - cookie.setMaxAge(0); - response.addCookie(cookie); - out.print("已删除的 cookie:" + cookie.getName() + "
    "); - } - out.print("名称:" + cookie.getName() + ","); - out.print("值:" + cookie.getValue() + "
    "); - } - } else { - out.println("

    No Cookie founds

    "); - } - out.println(""); - out.println(""); - } - - /** - * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) - */ - protected void doPost(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - doGet(request, response); - } - -} -``` - -## Session - -### Session 是什么 - -不同于 Cookie 保存在客户端浏览器中,Session 保存在服务器上。 - -如果说 Cookie 机制是通过检查客户身上的“通行证”来确定客户身份的话,那么 Session 机制就是通过检查服务器上的“客户明细表”来确认客户身份。 - -Session 对应的类为 `javax.servlet.http.HttpSession` 类。Session 对象是在客户第一次请求服务器时创建的。 - -### Session 类中的方法 - -`javax.servlet.http.HttpSession` 类中的方法: - -| **方法** | **功能** | -| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | -| public Object getAttribute(String name) | 该方法返回在该 session 会话中具有指定名称的对象,如果没有指定名称的对象,则返回 null。 | -| public Enumeration getAttributeNames() | 该方法返回 String 对象的枚举,String 对象包含所有绑定到该 session 会话的对象的名称。 | -| public long getCreationTime() | 该方法返回该 session 会话被创建的时间,自格林尼治标准时间 1970 年 1 月 1 日午夜算起,以毫秒为单位。 | -| public String getId() | 该方法返回一个包含分配给该 session 会话的唯一标识符的字符串。 | -| public long getLastAccessedTime() | 该方法返回客户端最后一次发送与该 session 会话相关的请求的时间自格林尼治标准时间 1970 年 1 月 1 日午夜算起,以毫秒为单位。 | -| public int getMaxInactiveInterval() | 该方法返回 Servlet 容器在客户端访问时保持 session 会话打开的最大时间间隔,以秒为单位。 | -| public void invalidate() | 该方法指示该 session 会话无效,并解除绑定到它上面的任何对象。 | -| public boolean isNew() | 如果客户端还不知道该 session 会话,或者如果客户选择不参入该 session 会话,则该方法返回 true。 | -| public void removeAttribute(String name) | 该方法将从该 session 会话移除指定名称的对象。 | -| public void setAttribute(String name, Object value) | 该方法使用指定的名称绑定一个对象到该 session 会话。 | -| public void setMaxInactiveInterval(int interval) | 该方法在 Servlet 容器指示该 session 会话无效之前,指定客户端请求之间的时间,以秒为单位。 | - -### Session 的有效期 - -由于会有越来越多的用户访问服务器,因此 Session 也会越来越多。为防止内存溢出,服务器会把长时间没有活跃的 Session 从内存中删除。 - -Session 的超时时间为`maxInactiveInterval`属性,可以通过`getMaxInactiveInterval()`、`setMaxInactiveInterval(longinterval)`来读写这个属性。 - -Tomcat 中 Session 的默认超时时间为 20 分钟。可以修改 web.xml 改变 Session 的默认超时时间。 - -例: - -```xml - - 60 - -``` - -### Session 对浏览器的要求 - -HTTP 协议是无状态的,Session 不能依据 HTTP 连接来判断是否为同一客户。因此服务器向客户端浏览器发送一个名为 JESSIONID 的 Cookie,他的值为该 Session 的 id(也就是 HttpSession.getId()的返回值)。Session 依据该 Cookie 来识别是否为同一用户。 - -该 Cookie 为服务器自动生成的,它的`maxAge`属性一般为-1,表示仅当前浏览器内有效,并且各浏览器窗口间不共享,关闭浏览器就会失效。 - -### URL 地址重写 - -URL 地址重写的原理是将该用户 Session 的 id 信息重写到 URL 地址中。服务器能够解析重写后的 URL 获取 Session 的 id。这样即使客户端不支持 Cookie,也可以使用 Session 来记录用户状态。 - -`HttpServletResponse`类提供了`encodeURL(Stringurl)`实现 URL 地址重写。 - -### Session 中禁用 Cookie - -在`META-INF/context.xml`中编辑如下: - -```xml - - -``` - -部署后,TOMCAT 便不会自动生成名 JESSIONID 的 Cookie,Session 也不会以 Cookie 为识别标志,而仅仅以重写后的 URL 地址为识别标志了。 - -### Session 实例 - -#### Session 跟踪 - -SessionTrackServlet.java - -```java -import java.io.IOException; -import java.io.PrintWriter; -import java.text.SimpleDateFormat; -import java.util.Date; - -import javax.servlet.ServletException; -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; - -@WebServlet("/servlet/SessionTrackServlet") -public class SessionTrackServlet extends HttpServlet { - private static final long serialVersionUID = 1L; - - public void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - // 如果不存在 session 会话,则创建一个 session 对象 - HttpSession session = request.getSession(true); - // 获取 session 创建时间 - Date createTime = new Date(session.getCreationTime()); - // 获取该网页的最后一次访问时间 - Date lastAccessTime = new Date(session.getLastAccessedTime()); - - // 设置日期输出的格式 - SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - - String title = "Servlet Session 实例"; - Integer visitCount = new Integer(0); - String visitCountKey = new String("visitCount"); - String userIDKey = new String("userID"); - String userID = new String("admin"); - - // 检查网页上是否有新的访问者 - if (session.isNew()) { - session.setAttribute(userIDKey, userID); - } else { - visitCount = (Integer) session.getAttribute(visitCountKey); - visitCount = visitCount + 1; - userID = (String) session.getAttribute(userIDKey); - } - session.setAttribute(visitCountKey, visitCount); - - // 设置响应内容类型 - response.setContentType("text/html;charset=UTF-8"); - PrintWriter out = response.getWriter(); - - String docType = "\n"; - out.println(docType + "\n" + "" + title + "\n" - + "\n" + "

    " + title - + "

    \n" + "

    Session 信息

    \n" - + "\n" + "\n" - + " \n" + "\n" + " \n" - + " \n" + "\n" - + " \n" + " \n" - + "\n" + " \n" + " \n" + "\n" + " \n" + " \n" + "\n" + " \n" + " \n" + "
    Session 信息
    id" + session.getId() + "
    创建时间" + df.format(createTime) + "
    最后访问时间" + df.format(lastAccessTime) - + "
    用户 ID" + userID - + "
    访问统计:" + visitCount - + "
    \n" + ""); - } -} -``` - -web.xml - -```xml - - SessionTrackServlet - SessionTrackServlet - - - SessionTrackServlet - /servlet/SessionTrackServlet - -``` - -#### 删除 Session 会话数据 - -当您完成了一个用户的 session 会话数据,您有以下几种选择: - -**移除一个特定的属性:**您可以调用 `removeAttribute(String name)` 方法来删除与特定的键相关联的值。 - -**删除整个 session 会话:**您可以调用 `invalidate()` 方法来丢弃整个 session 会话。 - -**设置 session 会话过期时间:**您可以调用 `setMaxInactiveInterval(int interval)` 方法来单独设置 session 会话超时。 - -**注销用户:**如果使用的是支持 servlet 2.4 的服务器,您可以调用 `logout` 来注销 Web 服务器的客户端,并把属于所有用户的所有 session 会话设置为无效。 - -**web.xml 配置:**如果您使用的是 Tomcat,除了上述方法,您还可以在 web.xml 文件中配置 session 会话超时,如下所示: - -```xml - - 15 - -``` - -上面实例中的超时时间是以分钟为单位,将覆盖 Tomcat 中默认的 30 分钟超时时间。 - -在一个 Servlet 中的 `getMaxInactiveInterval()` 方法会返回 session 会话的超时时间,以秒为单位。所以,如果在 web.xml 中配置 session 会话超时时间为 15 分钟,那么`getMaxInactiveInterval()` 会返回 900。 - -## Cookie vs Session - -### 存取方式 - -Cookie 只能保存`ASCII`字符串,如果需要存取 Unicode 字符或二进制数据,需要进行`UTF-8`、`GBK`或`BASE64`等方式的编码。 - -Session 可以存取任何类型的数据,甚至是任何 Java 类。可以将 Session 看成是一个 Java 容器类。 - -### 隐私安全 - -Cookie 存于客户端浏览器,一些客户端的程序可能会窥探、复制或修改 Cookie 内容。 - -Session 存于服务器,对客户端是透明的,不存在敏感信息泄露的危险。 - -### 有效期 - -使用 Cookie 可以保证长时间登录有效,只要设置 Cookie 的`maxAge`属性为一个很大的数字。 - -而 Session 虽然理论上也可以通过设置很大的数值来保持长时间登录有效,但是,由于 Session 依赖于名为`JESSIONID`的 Cookie,而 Cookie `JESSIONID`的`maxAge`默认为-1,只要关闭了浏览器该 Session 就会失效,因此,Session 不能实现信息永久有效的效果。使用 URL 地址重写也不能实现。 - -### 服务器的开销 - -由于 Session 是保存在服务器的,每个用户都会产生一个 Session,如果并发访问的用户非常多,会产生很多的 Session,消耗大量的内存。 - -而 Cookie 由于保存在客户端浏览器上,所以不占用服务器资源。 - -### 浏览器的支持 - -Cookie 需要浏览器支持才能使用。 - -如果浏览器不支持 Cookie,需要使用 Session 以及 URL 地址重写。 - -需要注意的事所有的用到 Session 程序的 URL 都要使用`response.encodeURL(StringURL)` 或`response.encodeRediretURL(String URL)`进行 URL 地址重写,否则导致 Session 会话跟踪失效。 - -### 跨域名 - -* Cookie 支持跨域名。 -* Session 不支持跨域名。 diff --git a/docs/javaee/javaee-filter-listener.md b/docs/javaee/javaee-filter-listener.md deleted file mode 100644 index 9d6cf977..00000000 --- a/docs/javaee/javaee-filter-listener.md +++ /dev/null @@ -1,190 +0,0 @@ -# JavaEE 之 Filter 和 Listener - -## Filter - -过滤器(Filter)用于在 Servlet 之外对 request 或 response 进行修改。Filter 提供了过滤链(Filter Chain)的概念,一个过滤链包括多个 Filter。客户端请求 request 在抵达 Servlet 之前会经过过滤链的所有 Filter,服务器响应 response 从 Servlet 抵达客户端浏览器之前也会经过过滤链的所有 FIlter。 - -![img](http://dunwu.test.upcdn.net/snap/1559054413341.png) - -### 过滤器方法 - -Filter 接口有三个方法: - -- init -- destroy -- doFilter - -`init` 和 `destroy` 方法只会被调用一次;doFilter 每次有客户端请求都会被调用一次。 - -```java -public interface Filter { - - /** - * web 程序启动时调用此方法, 用于初始化该 Filter - * @param config - * 可以从该参数中获取初始化参数以及ServletContext信息等 - * @throws ServletException - */ - public void init(FilterConfig config) throws ServletException; - - /** - * 客户请求服务器时会经过 - * - * @param request - * 客户请求 - * @param response - * 服务器响应 - * @param chain - * 过滤链, 通过 chain.doFilter(request, response) 将请求传给下个 Filter 或 - * Servlet - * @throws ServletException - * @throws IOException - */ - public void doFilter(ServletRequest request, ServletResponse response, - FilterChain chain) throws ServletException, IOException; - - /** - * web 程序关闭时调用此方法, 用于销毁一些资源 - */ - public void destroy(); - -} -``` - -### 过滤器配置 - -`Filter` 需要配置在 `web.xml` 中才能生效。一个 `Filter` 需要配置 `` 与 `` 标签。 - -- `` 配置 Filter 名称,实现类以及初始化参数。 -- `` 配置什么规则下使用该 Filter。 -- `` 的 filterName 与 `` 的 filterName 必须匹配。 -- `` 配置 URL 的规则,可以配置多个,可以使用通配符(`*`)。 -- `` 配置到达 Servlet 的方式,有 4 种取值:REQUEST、FORWARD、INCLUDE、ERROR。可以同时配置多个 ``。如果没有配置任何 ``,默认为 REQUEST。 - - REQUEST - 表示仅当直接请求 Servlet 时才生效。 - - FORWARD - 表示仅当某 Servlet 通过 FORWARD 到该 Servlet 时才生效。 - - INCLUDE - JSP 中可以通过 `` 请求某 Servlet。仅在这种情况表有效。 - - ERROR - JSP 中可以通过 `<%@ page errorPage="error.jsp" %>` 指定错误处理页面。仅在这种情况表有效。 - -## Listener - -> 监听器(`Listener`)用于监听 web 应用程序中的`ServletContext`, `HttpSession`和 `ServletRequest`等域对象的创建与销毁事件,以及监听这些域对象中的属性发生修改的事件。 - -使用 Listener 不需要关注该类事件时怎样触发或者怎么调用相应的 Listener,只要记住该类事件触发时一定会调用相应的 Listener,遵循 Servlet 规范的服务器会自动完成相应工作。 - -### 监听器的分类 - -在 Servlet 规范中定义了多种类型的监听器,它们用于监听的事件源分别为`ServletContext`,`HttpSession`和`ServletRequest`这三个域对象 -Servlet 规范针对这三个对象上的操作,又把多种类型的监听器划分为三种类型: - -1. 监听域对象自身的创建和销毁的事件监听器。 -2. 监听域对象中的属性的增加和删除的事件监听器。 -3. 监听绑定到 HttpSession 域中的某个对象的状态的事件监听器。 - -### 监听对象的创建和销毁 - -#### HttpSessionListener - -**`HttpSessionListener` 接口用于监听 `HttpSession` 对象的创建和销毁。** - -- 创建一个 `Session` 时,激发 `sessionCreated (HttpSessionEvent se)` 方法 -- 销毁一个 `Session` 时,激发 `sessionDestroyed (HttpSessionEvent se)` 方法。 - -#### ServletContextListener - -**`ServletContextListener` 接口用于监听 `ServletContext` 对象的创建和销毁事件。** - -实现了 `ServletContextListener` 接口的类都可以对 `ServletContext` 对象的创建和销毁进行监听。 - -- 当 `ServletContext` 对象被创建时,激发 `contextInitialized (ServletContextEvent sce)` 方法。 -- 当 `ServletContext` 对象被销毁时,激发 `contextDestroyed(ServletContextEvent sce)` 方法。 - -`ServletContext` 域对象创建和销毁时机: - -- 创建:服务器启动针对每一个 Web 应用创建 `ServletContext` -- 销毁:服务器关闭前先关闭代表每一个 web 应用的 `ServletContext` - -#### ServletRequestListener - -**`ServletRequestListener` 接口用于监听 `ServletRequest` 对象的创建和销毁。** - -- `Request` 对象被创建时,监听器的 `requestInitialized(ServletRequestEvent sre)` 方法将会被调用 -- `Request` 对象被销毁时,监听器的 `requestDestroyed(ServletRequestEvent sre)` 方法将会被调用 - -`ServletRequest` 域对象创建和销毁时机: - -- 创建:用户每一次访问都会创建 request 对象 -- 销毁:当前访问结束,request 对象就会销毁 - -### 监听对象的属性变化 - -域对象中属性的变更的事件监听器就是用来监听 `ServletContext`、`HttpSession`、`HttpServletRequest` 这三个对象中的属性变更信息事件的监听器。 -这三个监听器接口分别是 `ServletContextAttributeListener`、`HttpSessionAttributeListener` `和 ServletRequestAttributeListener`,这三个接口中都定义了三个方法来处理被监听对象中的属性的增加,删除和替换的事件,同一个事件在这三个接口中对应的方法名称完全相同,只是接受的参数类型不同。 - -#### attributeAdded 方法 - -当向被监听对象中增加一个属性时,web 容器就调用事件监听器的 `attributeAdded` 方法进行响应,这个方法接收一个事件类型的参数,监听器可以通过这个参数来获得正在增加属性的域对象和被保存到域中的属性对象 -各个域属性监听器中的完整语法定义为: - -```java -public void attributeAdded(ServletContextAttributeEvent scae) -public void attributeReplaced(HttpSessionBindingEvent hsbe) -public void attributeRmoved(ServletRequestAttributeEvent srae) -``` - -#### attributeRemoved 方法 - -当删除被监听对象中的一个属性时,web 容器调用事件监听器的 `attributeRemoved` 方法进行响应 -各个域属性监听器中的完整语法定义为: - -```java -public void attributeRemoved(ServletContextAttributeEvent scae) -public void attributeRemoved(HttpSessionBindingEvent hsbe) -public void attributeRemoved(ServletRequestAttributeEvent srae) -``` - -#### attributeReplaced 方法 - -当监听器的域对象中的某个属性被替换时,web 容器调用事件监听器的 `attributeReplaced` 方法进行响应 -各个域属性监听器中的完整语法定义为: - -```java -public void attributeReplaced(ServletContextAttributeEvent scae) -public void attributeReplaced(HttpSessionBindingEvent hsbe) -public void attributeReplaced(ServletRequestAttributeEvent srae) -``` - -### 监听 Session 内的对象 - -保存在 Session 域中的对象可以有多种状态: - -- 绑定(`session.setAttribute("bean",Object)`)到 `Session` 中; -- 从 `Session` 域中解除绑定(`session.removeAttribute("bean")`); -- 随 `Session` 对象持久化到一个存储设备中; -- 随 `Session` 对象从一个存储设备中恢复。 - -Servlet 规范中定义了两个特殊的监听器接口 `HttpSessionBindingListener` 和`HttpSessionActivationListener` 来帮助 JavaBean 对象了解自己在 Session 域中的这些状态。 - -实现这两个接口的类不需要 `web.xml` 文件中进行注册。 - -#### HttpSessionBindingListener - -`HttpSessionBindingListener` 接口的 JavaBean 对象可以感知自己被绑定或解绑定到 `Session` 中的事件。 - -- 当对象被绑定到 `HttpSession` 对象中时,web 服务器调用该对象的 `valueBound(HttpSessionBindingEvent event)` 方法。 -- 当对象从 `HttpSession` 对象中解除绑定时,web 服务器调用该对象的 `valueUnbound(HttpSessionBindingEvent event)` 方法。 - -#### HttpSessionActivationListener - -实现了 `HttpSessionActivationListener` 接口的 JavaBean 对象可以感知自己被活化(反序列化)和钝化(序列化)的事件。 - -- 当绑定到 `HttpSession` 对象中的 JavaBean 对象将要随 `HttpSession` 对象被序列化之前,web 服务器调用该 JavaBean 对象的 `sessionWillPassivate(HttpSessionEvent event)` 方法。这样 JavaBean 对象就可以知道自己将要和 `HttpSession` 对象一起被序列化到硬盘中. -- 当绑定到 `HttpSession` 对象中的 JavaBean 对象将要随 `HttpSession` 对象被反序列化之后,web 服务器调用该 JavaBean 对象的 `sessionDidActive(HttpSessionEvent event)` 方法。这样 JavaBean 对象就可以知道自己将要和 `HttpSession` 对象一起被反序列化回到内存中 - -## 示例代码 - -- `Filter` 的示例源码:[源码](https://github.com/dunwu/javatech/tree/master/codes/javaee-tutorial/javaee-tutorial-filter) -- `Listener` 的示例源码:[源码](https://github.com/dunwu/javatech/tree/master/codes/javaee-tutorial/javaee-tutorial-listener) - -## 参考资料 - -- Java Web 整合开发 diff --git a/docs/javaee/javaee-interview.md b/docs/javaee/javaee-interview.md deleted file mode 100644 index 0f1be619..00000000 --- a/docs/javaee/javaee-interview.md +++ /dev/null @@ -1,254 +0,0 @@ -# JavaEE 面经 - -## 1. Servlet - -### 1.1. 什么是 Servlet - -Servlet(Server Applet),即小服务程序或服务连接器。Servlet 是 Java 编写的服务器端程序,具有独立于平台和协议的特性,主要功能在于交互式地浏览和生成数据,生成动态 Web 内容。 - -- 狭义的 Servlet 是指 Java 实现的一个接口。 -- 广义的 Servlet 是指任何实现了这个 Servlet 接口的类。 - -Servlet 运行于支持 Java 的应用服务器中。从原理上讲,Servlet 可以响应任何类型的请求,但绝大多数情况下 Servlet 只用来扩展基于 HTTP 协议的 Web 服务器。 - -### 1.2. Servlet 和 CGI 的区别 - -Servlet 技术出现之前,Web 主要使用 CGI 技术。它们的区别如下: - -- Servlet 是基于 Java 编写的,处于服务器进程中,他能够通过多线程方式运行 service() 方法,一个实例可以服务于多个请求,而且一般不会销毁; -- CGI(Common Gateway Interface),即通用网关接口。它会为每个请求产生新的进程,服务完成后销毁,所以效率上低于 Servlet。 - -### 1.3. Servlet 版本以及主要特性 - -| 版本 | 日期 | JAVA EE/JDK 版本 | 特性 | -| ----------- | ------------- | ------------------ | --------------------------------------------------------------------- | -| Servlet 4.0 | 2017 年 10 月 | JavaEE 8 | HTTP2 | -| Servlet 3.1 | 2013 年 5 月 | JavaEE 7 | 非阻塞 I/O,HTTP 协议升级机制 | -| Servlet 3.0 | 2009 年 12 月 | JavaEE 6, JavaSE 6 | 可插拔性,易于开发,异步 Servlet,安全性,文件上传 | -| Servlet 2.5 | 2005 年 10 月 | JavaEE 5, JavaSE 5 | 依赖 JavaSE 5,支持注解 | -| Servlet 2.4 | 2003 年 11 月 | J2EE 1.4, J2SE 1.3 | web.xml 使用 XML Schema | -| Servlet 2.3 | 2001 年 8 月 | J2EE 1.3, J2SE 1.2 | Filter | -| Servlet 2.2 | 1999 年 8 月 | J2EE 1.2, J2SE 1.2 | 成为 J2EE 标准 | -| Servlet 2.1 | 1998 年 11 月 | 未指定 | First official specification, added RequestDispatcher, ServletContext | -| Servlet 2.0 | | JDK 1.1 | Part of Java Servlet Development Kit 2.0 | -| Servlet 1.0 | 1997 年 6 月 | | | - -### 1.4. Servlet 和 JSP 的区别 - -1. Servlet 是一个运行在服务器上的 Java 类,依靠服务器支持向浏览器传输数据。 -2. **JSP 本质上就是 Servlet**,每次运行的时候 JSP 都会被编译成 .java 文件,然后再被编译成 .class 文件。 -3. 有了 JSP,Servlet 不再负责动态生成页面,转而去负责控制程序逻辑的作用,控制 JSP 与 JavaBean 之间的流转。 -4. JSP 侧重于视图,而 Servlet 侧重于控制逻辑,在 MVC 架构模式中,JSP 适合充当视图 View,Servlet 适合充当控制器 Controller。 - -### 1.5. 简述 Servlet 生命周期 - -![img](http://www.runoob.com/wp-content/uploads/2014/07/Servlet-LifeCycle.jpg) - -Servlet 生命周期如下: - -1. **加载** - 第一个到达服务器的 HTTP 请求被委派到 Servlet 容器。容器通过类加载器使用 Servlet 类对应的文件加载 servlet; -2. **初始化** - Servlet 通过调用 **init ()** 方法进行初始化。 -3. **服务** - Servlet 调用 **service()** 方法来处理客户端的请求。 -4. **销毁** - Servlet 通过调用 **destroy()** 方法终止(结束)。 -5. **卸载** - Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。 - -### 1.6. 如何现实 servlet 的单线程模式 - -```java -<%@ page isThreadSafe="false" %> -``` - -### 1.7. Servlet 中如何获取用户提交的查询参数或者表单数据 - -- HttpServletRequest 的 getParameter() 方法。 -- HttpServletRequest 的 getParameterValues() 方法。 -- HttpServletRequest 的 getParameterMap() 方法。 - -### 1.8. request 的主要方法 - -- setAttribute(String name,Object):设置名字为 name 的 request 的参数值 -- getAttribute(String name):返回由 name 指定的属性值 -- getAttributeNames():返回 request 对象所有属性的名字集合,结果是一个枚举的实例 -- getCookies():返回客户端的所有 Cookie 对象,结果是一个 Cookie 数组 -- getCharacterEncoding():返回请求中的字符编码方式 -- getContentLength():返回请求的 Body 的长度 -- getHeader(String name):获得 HTTP 协议定义的文件头信息 -- getHeaders(String name):返回指定名字的 request Header 的所有值,结果是一个枚举的实例 -- getHeaderNames():返回所以 request Header 的名字,结果是一个枚举的实例 -- getInputStream():返回请求的输入流,用于获得请求中的数据 getMethod():获得客户端向服务器端传送数据的方法 -- getParameter(String name):获得客户端传送给服务器端的有 name 指定的参数值 -- getParameterNames():获得客户端传送给服务器端的所有参数的名字,结果是一个枚举的实例 -- getParameterValues(String name):获得有 name 指定的参数的所有值 -- getProtocol():获取客户端向服务器端传送数据所依据的协议名称 -- getQueryString():获得查询字符串 -- getRequestURI():获取发出请求字符串的客户端地址 -- getRemoteAddr():获取客户端的 IP 地址 -- getRemoteHost():获取客户端的名字 -- getSession([Boolean create]):返回和请求相关 -- Session getServerName():获取服务器的名字 -- getServletPath():获取客户端所请求的脚本文件的路径 -- getServerPort():获取服务器的端口号 -- removeAttribute(String name):删除请求中的一个属性 - -## 2. JSP - -### 2.1. JSP 的内置对象 - -1. **request**:包含**客户端请求的信息**; -2. **response**:包含**服务器传回客户端的响应信息**; -3. **session**:主要用来**区分每个用户信息和会话状态**; -4. **pageContext**:管理**页面属性**; -5. **application**:服务器启动时创建,服务器关闭时停止,**保存所有应用系统中的共有数据**,一个共享的内置对象(即一个容器中的多个用户共享一个 application 对象); -6. **out**:向客户端**输出数据**; -7. **config**:代码片段配置对象,用于**初始化 Servlet 的配置参数**; -8. **page**:指**网页本身**; -9. **exception**:处理 JSP 文件执行时发生的错误和异常,只要在**错误页面**里才能使用。 - -### 2.2. JSP 的作用域 - -1. **page**:一个页面; -2. **request**:一次请求; -3. **session**:一次会话; -4. **application**:服务器从启动到停止。 - -### 2.3. JSP 中 7 个动作指令和作用 - -1. **jsp:forward** - 执行页面转向,把请求转发到下一个页面; -2. **jsp:param** - 用于传递参数,必须与其他支持参数的标签一起使用; -3. **jsp:include** - 用于**动态引入一个 JSP 页面**; -4. **jsp:plugin** - 用于**下载 JavaBean 或 Applet 到客户端执行**; -5. **jsp:useBean** - 寻求或者实例化一个 JavaBean; -6. **jsp:setProperty** - 设置 JavaBean 的属性值; -7. **jsp:getProperty** - 获取 JavaBean 的属性值。 - -### 2.4. JSP 中动态 INCLUDE 和静态 INCLUDE 有什么区别 - -- **静态 INCLUDE**:用 include 伪码实现,**不会检查所含文件的变化**,适用于包含**静态页面<%@ include file="页面名称.html" %>**。**先合并再编译**。 -- **动态 INCLUDE**:用 jsp:include 动作实现 **** 它总是**会检查文件中的变化**,适用于包含**动态页面**,并且可以**带参数**。**先编译再合并**。 - -## 3. 原理 - -### 3.1. 请求转发(forward)和重定向(redirect)的区别 - -- 效率上 - - 转发(forward) > 重定向(redirect) -- 显示上 - - 重定向(redirect):显示新的 URL - - 转发(forward):地址栏不变 -- 数据上 - - 转发(forward):可以共享 request 里面的数据 - - 重定向(redirect):不能 -- 请求次数 - - 重定向(redirect)是两次 - - 转发(forward)是一次 - -### 3.2. get 请求和 post 请求的区别 - -![img](https://upload-images.jianshu.io/upload_images/7779232-5be5ae990207f9d2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/814/format/webp) - -- GET: - - 从服务器上获取数据,一般不能使用在写操作接口 - - 由 URL 所限制,GET 方式传输的数据大小有所限制,传送的数据量不超过 2KB - - 请求的数据会附加在 URL 之后,以?分隔 URL 和传输数据,多个参数用&连接 - - 安全性差 -- POST: - - 向服务器提交数据,一般处理写业务 - - POST 方式传送的数据量比较大,一般被默认为没有限制 - - 安全性高 - - 请的求的数据内容放置在 HTML HEADER 中 - -### 3.3. 用户在浏览器中输入 URL 之后,发什么了什么?写出请求和响应的流程 - -1. 域名解析 -2. TCP 三次握手 -3. 浏览器向服务器发送 http 请求 -4. 浏览器发送请求头信息 -5. 服务器处理请求 -6. 服务器做出应答 -7. 服务器发送应答头信息 -8. 服务器发送数据 -9. TCP 连接关闭 - -### 3.4. 什么是 Web Service? - -1. WebService 就是一个应用程序,它向外界暴露出一个能够通过 Web 进行调用的 API。 -2. 它是基于 HTTP 协议传输数据,这使得运行在不同机上的不同应用程序,无须借助附加的、专门的第三方 软件或硬件,就可以相互交换数据或集成。 - -### 3.5. 会话跟踪技术有哪些? - -由于 HTTP 协议本身是无状态的,服务器为了区分不同的用户,就需要对用户会话进行跟踪,简单的说就是为用户进行登记,为用户分配唯一的 ID,下一次用户在请求中包含此 ID,服务器根据此判断到底是哪一个用户。 - -- URL 重写:在 URL 中添加会话信息作为请求的参数,或者将唯一的会话 ID 添加到 URL 结尾,以表示一个会话。设置表单隐藏域:将和会话跟踪相关的字段添加到隐藏域中,这些信息不会在浏览器显示,但是提交表单时会提交给服务器。 -- cookie:cookie 有两种: - - 一种是基于窗口的,浏览器关闭后,cookie 就没有了; - - 另一种是将信息存储在一个临时文件中,并设置其有效路径和最大存活时间。当用户通过浏览器和服务器建立一次会话后,会话 ID 就会随相应信息储存在基于窗口的 cookie 中,那就意味着只要浏览器没有关闭,会话没有超时,下一次请求时这个会话 ID 又会提交给服务器,让服务器识别用户身份。 - - 在使用 cookie 时要注意几点: - - 首先不要在 cookie 中存放敏 感信息; - - 其次 cookie 存储的数据量有限(4k),不能将过多的内容存储 cookie 中; - - 再者浏览器通常只允许一个站点最多存放 20 个 cookie。 - - 当然,和用户会话相关的其他信息(除了会话 ID)也可以存在 cookie 方便进行会话 跟踪; -- HttpSession:在所有会话跟踪技术中,HttpSession 对象是最强大也是功能最多的。当一个用户第一次访问某个网站时会自动创建 HttpSession,每个用户可以访问他自己的 HttpSession。可以通过 HttpServletRequest 对象的 getSession 方法获得 HttpSession,通过 HttpSession 的 setAttribute 方法可以将一个值放在 HttpSession 中,通过调用 HttpSession 对象的 getAttribute 方法,同时传入属性名就可以获取保存在 HttpSession 中的对象。 - - 与上面三种方式不同的是,HttpSession 放在服务器的内存中,因此不要将过大的对象放在里面,即使目前的 Servlet 容器可以在内存将满时将 HttpSession 中的对象移到其他存储设备中,但是这样势必影响性能。 - - 添加到 HttpSession 中 的值可以是任意 Java 对象,这个对象最好实现了 Serializable 接口,这样 Servlet 容器在必要的时候可以将其序列 化到文件中,否则在序列化时就会出现异常。 - -### 3.6. 响应结果状态码有哪些,并给出中文含义? - -- `1**`:信息性状态码 -- `2**`:成功状态码 - - 200:请求正常成功 - - 204:指示请求成功但没有返回新信息 - - 206:指示服务器已完成对资源的部分 GET 请求 -- `3**`:重定向状态码 - - 301:永久性重定向 - - 302:临时性重定向 - - 304:服务器端允许请求访问资源,但未满足条件 -- `4**`:客户端错误状态码 - - 400:请求报文中存在语法错误 - - 401:发送的请求需要有通过 HTTP 认证的认证信息 - - 403:对请求资源的访问被服务器拒绝了 - - 404:服务器上无法找到请求的资源 -- `5**`:服务器错误状态码 - - 500:服务器端在执行请求时发生了错误 - - 503:服务器暂时处于超负载或正在进行停机维护,现在无法处理请求 - -### 3.7. XML 文档定义有几种形式?它们之间有何本质区别?解析 XML 文档有哪几种方式? - -(1)XML 文档有两种约束方式: - -1. DTD 约束 -2. Schema 约束 - -(2)XML 文档区别: -1 DTD 不符合 XML 的语法结构,schema 符合 XML 的语法结构; -2 DTD 的约束扩展性比较差,XML 文档只能引入一个 DTD 的文件。schema 可以引入多个文件; -3 DTD 不支持名称空间(理解包结构),schema 支持名称空间; -4 DTD 支持数据比较少,schema 支持更多的数据类型; - -(3)解析方式主要有三种: - -- DOM 解析: - - (a)加载整个 xml 的文档到内存中,形成树状结构,生成对象; - - (b)容易产生内存溢出; - - (c)可以做增删改 -- SAX 解析 - - (a)边读边解析; - - (b)不可以做增删改 -- DOM4J 解析(hibernate 底层采用) - - (a)可让 SAX 解析也产生树状结构。 - - (b)主要 api 开发步骤: - - 1)SAXReader.read(xxx.xml)代表解析 xml 的文档,返回对象是 Document; - - 2)Document.getRootElement(),返回的是文档的根节点,是 Element 对象; - - 3)Element: - - .element(...)-- 获得指定名称第一个子元素。可以不指定名称; - - .elements(...)-- 获得指定名称的所有子元素。可以不指定名称; - - .getText()-- 获得当前元素的文本内容; - - .elementText(...)-- 获得指定名称子元素的文本值 - - .addElement()-- 添加子节点 - - .setText()-- 设置子标签内容 - - 4)XMLWriter.write("..")-- 写出 - - 5)XMLWriter.close()-- 关闭输出流 - -## 4. 参考资料 - -- https://blog.csdn.net/YM_IlY/article/details/81266959 -- https://www.jianshu.com/p/f073dde56262 diff --git a/docs/javaee/javaee-jsp.md b/docs/javaee/javaee-jsp.md deleted file mode 100644 index 8e426d1f..00000000 --- a/docs/javaee/javaee-jsp.md +++ /dev/null @@ -1,1716 +0,0 @@ -# JSP 指南 - -## 简介 - -### 什么是 Java Server Pages - -`JSP`全称`Java Server Pages`,是一种动态网页开发技术。 - -它使用 JSP 标签在 HTML 网页中插入 Java 代码。标签通常以`<%`开头以`%>`结束。 - -JSP 是一种 Java servlet,主要用于实现 Java web 应用程序的用户界面部分。网页开发者们通过结合 HTML 代码、XHTML 代码、XML 元素以及嵌入 JSP 操作和命令来编写 JSP。 - -JSP 通过网页表单获取用户输入数据、访问数据库及其他数据源,然后动态地创建网页。 - -JSP 标签有多种功能,比如访问数据库、记录用户选择信息、访问 JavaBeans 组件等,还可以在不同的网页中传递控制信息和共享信息。 - -### 为什么使用 JSP - -JSP 也是一种 Servlet,因此 JSP 能够完成 Servlet 能完成的任何工作。 - -JSP 程序与 CGI 程序有着相似的功能,但和 CGI 程序相比,JSP 程序有如下优势: - -- 性能更加优越,因为 JSP 可以直接在 HTML 网页中动态嵌入元素而不需要单独引用 CGI 文件。 -- 服务器调用的是已经编译好的 JSP 文件,而不像 CGI/Perl 那样必须先载入解释器和目标脚本。 -- JSP 基于 Java Servlets API,因此,JSP 拥有各种强大的企业级 Java API,包括 JDBC,JNDI,EJB,JAXP 等等。 -- JSP 页面可以与处理业务逻辑的 servlets 一起使用,这种模式被 Java servlet 模板引擎所支持。 - -最后,JSP 是 Java EE 不可或缺的一部分,是一个完整的企业级应用平台。这意味着 JSP 可以用最简单的方式来实现最复杂的应用。 - -### JSP 的优势 - -以下列出了使用 JSP 带来的其他好处: - -- 与 ASP 相比:JSP 有两大优势。首先,动态部分用 Java 编写,而不是 VB 或其他 MS 专用语言,所以更加强大与易用。第二点就是 JSP 易于移植到非 MS 平台上。 -- 与纯 Servlets 相比:JSP 可以很方便的编写或者修改 HTML 网页而不用去面对大量的 println 语句。 -- 与 SSI 相比:SSI 无法使用表单数据、无法进行数据库链接。 -- 与 JavaScript 相比:虽然 JavaScript 可以在客户端动态生成 HTML,但是很难与服务器交互,因此不能提供复杂的服务,比如访问数据库和图像处理等等。 -- 与静态 HTML 相比:静态 HTML 不包含动态信息。 - -## JSP 工作原理 - -**JSP 是一种 Servlet**,但工作方式和 Servlet 有所差别。 - -Servlet 是先将源代码编译为 class 文件后部署到服务器下的,**先编译后部署**。 - -Jsp 是先将源代码部署到服务器再编译,**先部署后编译**。 - -Jsp 会在客户端第一次请求 Jsp 文件时被编译为 HttpJspPage 类(Servlet 的一个子类)。该类会被服务器临时存放在服务器工作目录里。所以,第一次请求 Jsp 后,访问速度会变快就是这个道理。 - -### JSP 工作流程 - -网络服务器需要一个 JSP 引擎,也就是一个容器来处理 JSP 页面。容器负责截获对 JSP 页面的请求。本教程使用内嵌 JSP 容器的 Apache 来支持 JSP 开发。 - -JSP 容器与 Web 服务器协同合作,为 JSP 的正常运行提供必要的运行环境和其他服务,并且能够正确识别专属于 JSP 网页的特殊元素。 - -下图显示了 JSP 容器和 JSP 文件在 Web 应用中所处的位置。 - -![img](http://www.runoob.com/wp-content/uploads/2014/01/jsp-arch.jpg) - -#### 工作步骤 - -以下步骤表明了 Web 服务器是如何使用 JSP 来创建网页的: - -- 就像其他普通的网页一样,您的浏览器发送一个 HTTP 请求给服务器。 -- Web 服务器识别出这是一个对 JSP 网页的请求,并且将该请求传递给 JSP 引擎。通过使用 URL 或者.jsp 文件来完成。 -- JSP 引擎从磁盘中载入 JSP 文件,然后将它们转化为 servlet。这种转化只是简单地将所有模板文本改用 println()语句,并且将所有的 JSP 元素转化成 Java 代码。 -- JSP 引擎将 servlet 编译成可执行类,并且将原始请求传递给 servlet 引擎。 -- Web 服务器的某组件将会调用 servlet 引擎,然后载入并执行 servlet 类。在执行过程中,servlet 产生 HTML 格式的输出并将其内嵌于 HTTP response 中上交给 Web 服务器。 -- Web 服务器以静态 HTML 网页的形式将 HTTP response 返回到您的浏览器中。 -- 最终,Web 浏览器处理 HTTP response 中动态产生的 HTML 网页,就好像在处理静态网页一样。 - -以上提及到的步骤可以用下图来表示: - -一般情况下,JSP 引擎会检查 JSP 文件对应的 servlet 是否已经存在,并且检查 JSP 文件的修改日期是否早于 servlet。如果 JSP 文件的修改日期早于对应的 servlet,那么容器就可以确定 JSP 文件没有被修改过并且 servlet 有效。这使得整个流程与其他脚本语言(比如 PHP)相比要高效快捷一些。 - -### JSP 生命周期 - -理解 JSP 底层功能的关键就是去理解它们所遵守的生命周期。 - -JSP 生命周期就是从创建到销毁的整个过程,类似于 servlet 生命周期,区别在于 JSP 生命周期还包括将 JSP 文件编译成 servlet。 - -以下是 JSP 生命周期中所走过的几个阶段: - -- **编译阶段:**servlet 容器编译 servlet 源文件,生成 servlet 类 -- **初始化阶段:**加载与 JSP 对应的 servlet 类,创建其实例,并调用它的初始化方法 -- **执行阶段:**调用与 JSP 对应的 servlet 实例的服务方法 -- **销毁阶段:**调用与 JSP 对应的 servlet 实例的销毁方法,然后销毁 servlet 实例 - -很明显,JSP 生命周期的四个主要阶段和 servlet 生命周期非常相似,下面给出图示: - -![img](http://www.runoob.com/wp-content/uploads/2014/01/jsp_life_cycle.jpg) - -#### JSP 编译 - -当浏览器请求 JSP 页面时,JSP 引擎会首先去检查是否需要编译这个文件。如果这个文件没有被编译过,或者在上次编译后被更改过,则编译这个 JSP 文件。 - -编译的过程包括三个步骤: - -- 解析 JSP 文件。 -- 将 JSP 文件转为 servlet。 -- 编译 servlet。 - -#### JSP 初始化 - -容器载入 JSP 文件后,它会在为请求提供任何服务前调用 jspInit()方法。如果您需要执行自定义的 JSP 初始化任务,复写 jspInit()方法就行了,就像下面这样: - -```java -public void jspInit(){ - // 初始化代码 -} -``` - -一般来讲程序只初始化一次,servlet 也是如此。通常情况下您可以在 jspInit()方法中初始化数据库连接、打开文件和创建查询表。 - -#### JSP 执行 - -这一阶段描述了 JSP 生命周期中一切与请求相关的交互行为,直到被销毁。 - -当 JSP 网页完成初始化后,JSP 引擎将会调用 `_jspService()` 方法。 - -`_jspService()` 方法需要一个 HttpServletRequest 对象和一个 HttpServletResponse 对象作为它的参数,就像下面这样: - -```java -void _jspService(HttpServletRequest request, - HttpServletResponse response) { - // 服务端处理代码 -} -``` - -`_jspService()` 方法在每个 request 中被调用一次并且负责产生与之相对应的 response,并且它还负责产生所有 7 个 HTTP 方法的回应,比如 GET、POST、DELETE 等等。 - -#### JSP 清理 - -JSP 生命周期的销毁阶段描述了当一个 JSP 网页从容器中被移除时所发生的一切。 - -jspDestroy()方法在 JSP 中等价于 servlet 中的销毁方法。当您需要执行任何清理工作时复写 jspDestroy()方法,比如释放数据库连接或者关闭文件夹等等。 - -jspDestroy()方法的格式如下: - -```java -public void jspDestroy() { - // 清理代码 -} -``` - -## 语法 - -### 脚本 - -脚本程序可以包含任意量的 Java 语句、变量、方法或表达式,只要它们在脚本语言中是有效的。 - -脚本程序的语法格式: - -``` -<% 代码片段 %> -``` - -或者,您也可以编写与其等价的 XML 语句,就像下面这样: - -``` - - 代码片段 - -``` - -任何文本、HTML 标签、JSP 元素必须写在脚本程序的外面。 - -下面给出一个示例,同时也是本教程的第一个 JSP 示例: - -``` - - - Hello World - - - Hello World!
    - <% out.println("Your IP address is " + request.getRemoteAddr()); %> - - -``` - -**注意:**请确保 Apache Tomcat 已经安装在 C:\apache-tomcat-7.0.2 目录下并且运行环境已经正确设置。 - -将以上代码保存在 hello.jsp 中,然后将它放置在 C:\apache-tomcat-7.0.2\webapps\ROOT 目录下,打开浏览器并在地址栏中输入 `http://localhost:8080/hello.jsp` 。运行后得到以下结果: - -![img](http://www.runoob.com/wp-content/uploads/2014/01/jsp_hello_world.jpg) - -#### 中文编码问题 - -如果我们要在页面正常显示中文,我们需要在 JSP 文件头部添加以下代码:`<>` - -``` -<%@ page language="java" contentType="text/html; charset=UTF-8" -pageEncoding="UTF-8"%> -``` - -接下来我们将以上程序修改为: - -``` -<%@ page language="java" contentType="text/html; charset=UTF-8" -pageEncoding="UTF-8"%> - - - - - 菜鸟教程(runoob.com) - - - Hello World!
    - <% out.println("你的 IP 地址 " + request.getRemoteAddr()); %> - - -``` - -这样中文就可以正常显示了。 - -### JSP 声明 - -一个声明语句可以声明一个或多个变量、方法,供后面的 Java 代码使用。在 JSP 文件中,您必须先声明这些变量和方法然后才能使用它们。 - -JSP 声明的语法格式: - -``` -<%! declaration; [ declaration; ]+ ... %> -``` - -或者,您也可以编写与其等价的 XML 语句,就像下面这样: - -``` - - 代码片段 - -``` - -程序示例: - -``` -<%! int i = 0; %> <%! int a, b, c; %> <%! Circle a = new Circle(2.0); %> -``` - -### JSP 表达式 - -一个 JSP 表达式中包含的脚本语言表达式,先被转化成 String,然后插入到表达式出现的地方。 - -由于表达式的值会被转化成 String,所以您可以在一个文本行中使用表达式而不用去管它是否是 HTML 标签。 - -表达式元素中可以包含任何符合 Java 语言规范的表达式,但是不能使用分号来结束表达式。 - -JSP 表达式的语法格式: - -``` -<%= 表达式 %> -``` - -同样,您也可以编写与之等价的 XML 语句: - -``` - - 表达式 - -``` - -程序示例: - -``` -<%@ page language="java" contentType="text/html; charset=UTF-8" -pageEncoding="UTF-8"%> - - - - - 菜鸟教程(runoob.com) - - -

    - 今天的日期是: <%= (new java.util.Date()).toLocaleString()%> -

    - - -``` - -运行后得到以下结果: - -``` -今天的日期是: 2016-6-25 13:40:07 -``` - ---- - -### JSP 注释 - -JSP 注释主要有两个作用:为代码作注释以及将某段代码注释掉。 - -JSP 注释的语法格式: - -``` -<%@ page language="java" contentType="text/html; charset=UTF-8" -pageEncoding="UTF-8"%> - - - - - JSP注释示例 - - - <%-- 该部分注释在网页中不会被显示--%> -

    - 今天的日期是: <%= (new java.util.Date()).toLocaleString()%> -

    - - -``` - -运行后得到以下结果: - -``` -今天的日期是: 2016-6-25 13:41:26 -``` - -不同情况下使用注释的语法规则: - -| **语法** | 描述 | -| ---------------- | ----------------------------------------------------- | -| `<%-- 注释 --%>` | JSP 注释,注释内容不会被发送至浏览器甚至不会被编译 | -| `` | HTML 注释,通过浏览器查看网页源代码时可以看见注释内容 | -| `<%` | 代表静态 <%常量 | -| `%>` | 代表静态 %> 常量 | -| `'` | 在属性中使用的单引号 | -| `"` | 在属性中使用的双引号 | - -### 控制语句 - -JSP 提供对 Java 语言的全面支持。您可以在 JSP 程序中使用 Java API 甚至建立 Java 代码块,包括判断语句和循环语句等等。 - -#### if…else 语句 - -`If…else`块,请看下面这个例子: - -``` -<%@ page language="java" contentType="text/html; charset=UTF-8" -pageEncoding="UTF-8"%> <%! int day = 1; %> - - - - - 02.JSP语法 - if...else示例 - - -

    IF...ELSE 实例

    - <% if (day == 1 | day == 7) { %> -

    今天是周末

    - <% } else { %> -

    今天不是周末

    - <% } %> - - -``` - -运行后得到以下结果: - -``` -IF...ELSE 实例 -今天不是周末 -``` - -#### switch…case 语句 - -现在来看看 switch…case 块,与 if…else 块有很大的不同,它使用 out.println(),并且整个都装在脚本程序的标签中,就像下面这样: - -``` -<%@ page language="java" contentType="text/html; charset=UTF-8" -pageEncoding="UTF-8"%> <%! int day = 3; %> - - - - - 02.JSP语法 - switch...case示例 - - -

    Sswitch...case示例

    - <% switch(day) { case 0: out.println("星期天"); break; case 1: - out.println("星期一"); break; case 2: out.println("星期二"); break; case 3: - out.println("星期三"); break; case 4: out.println("星期四"); break; case 5: - out.println("星期五"); break; default: out.println("星期六"); } %> - - -``` - -浏览器访问,运行后得出以下结果: - -``` -SWITCH...CASE 实例 - -星期三 -``` - -#### 循环语句 - -在 JSP 程序中可以使用 Java 的三个基本循环类型:for,while,和 do…while。 - -让我们来看看 for 循环的例子,以下输出的不同字体大小的"菜鸟教程": - -``` -<%@ page language="java" contentType="text/html; charset=UTF-8" -pageEncoding="UTF-8"%> <%! int fontSize; %> - - - - - 菜鸟教程(runoob.com) - - -

    For 循环实例

    - <%for ( fontSize = 1; fontSize <= 3; fontSize++){ %> - - 菜鸟教程
    - <%}%> - - -``` - -运行后得到以下结果: - -![img](http://www.runoob.com/wp-content/uploads/2014/01/7B4B85CF-FE4B-43CB-AAFF-F8594AD4342C.jpg) - -将上例改用 while 循环来写: - -``` -<%@ page language="java" contentType="text/html; charset=UTF-8" -pageEncoding="UTF-8"%> <%! int fontSize; %> - - - - - 菜鸟教程(runoob.com) - - -

    While 循环实例

    - <%while ( fontSize <= 3){ %> - - 菜鸟教程
    - <%fontSize++;%> <%}%> - - -``` - -浏览器访问,输出结果为(fontSize 初始化为 0,所以多输出了一行): - -![img](http://www.runoob.com/wp-content/uploads/2014/01/4F744CC9-E484-45BA-AF18-27AFCF4AD45C.jpg) - -JSP 运算符 - -JSP 支持所有 Java 逻辑和算术运算符。 - -下表罗列出了 JSP 常见运算符,优先级从高到底: - -| **类别** | **操作符** | **结合性** | -| --------- | ----------------------------------- | ---------- | -| 后缀 | `() [] .` (点运算符) | 左到右 | -| 一元 | `++ - - ! ~` | 右到左 | -| 可乘性 | `* / %` | 左到右 | -| 可加性 | `+ -` | 左到右 | -| 移位 | `>> >>> <<` | 左到右 | -| 关系 | `> >= < <=` | 左到右 | -| 相等/不等 | `== !=` | 左到右 | -| 位与 | `&` | 左到右 | -| 位异或 | `^` | 左到右 | -| 位或 | `|` | 左到右 | -| 逻辑与 | `&&` | 左到右 | -| 逻辑或 | `||` | 左到右 | -| 条件判断 | `?:` | 右到左 | -| 赋值 | `= += -= *= /= %= >>= <<= &= ^= |=` | 右到左 | -| 逗号 | `,` | 左到右 | - -### JSP 字面量 - -JSP 语言定义了以下几个字面量: - -- 布尔值(boolean):true 和 false; -- 整型(int):与 Java 中的一样; -- 浮点型(float):与 Java 中的一样; -- 字符串(string):以单引号或双引号开始和结束; -- Null:null。 - -## 指令 - -JSP 指令用来设置整个 JSP 页面相关的属性,如网页的编码方式和脚本语言。 - -JSP 指令以开`<%@`开始,以`%>`结束。 - -JSP 指令语法格式如下: - -``` -<%@ directive attribute="value" %> -``` - -指令可以有很多个属性,它们以键值对的形式存在,并用逗号隔开。 - -JSP 中的三种指令标签: - -| **指令** | **描述** | -| -------------------- | -------------------------------------------------------- | -| `<%@ page ... %>` | 定义网页依赖属性,比如脚本语言、error 页面、缓存需求等等 | -| `<%@ include ... %>` | 包含其他文件 | -| `<%@ taglib ... %>` | 引入标签库的定义,可以是自定义标签 | - -### Page 指令 - -Page 指令为容器提供当前页面的使用说明。一个 JSP 页面可以包含多个`page`指令。 - -Page 指令的语法格式: - -``` -<%@ page attribute="value" %> -``` - -等价的 XML 格式: - -``` - -``` - -例: - -``` -<%@ page language="java" contentType="text/html; charset=UTF-8" -pageEncoding="UTF-8" %> -``` - -#### 属性 - -下表列出与 Page 指令相关的属性: - -| **属性** | **描述** | -| ------------------ | ----------------------------------------------------- | -| buffer | 指定 out 对象使用缓冲区的大小 | -| autoFlush | 控制 out 对象的 缓存区 | -| contentType | 指定当前 JSP 页面的 MIME 类型和字符编码 | -| errorPage | 指定当 JSP 页面发生异常时需要转向的错误处理页面 | -| isErrorPage | 指定当前页面是否可以作为另一个 JSP 页面的错误处理页面 | -| extends | 指定 servlet 从哪一个类继承 | -| import | 导入要使用的 Java 类 | -| info | 定义 JSP 页面的描述信息 | -| isThreadSafe | 指定对 JSP 页面的访问是否为线程安全 | -| language | 定义 JSP 页面所用的脚本语言,默认是 Java | -| session | 指定 JSP 页面是否使用 session | -| isELIgnored | 指定是否执行 EL 表达式 | -| isScriptingEnabled | 确定脚本元素能否被使用 | - -### Include 指令 - -JSP 可以通过`include`指令来包含其他文件。 - -被包含的文件可以是 JSP 文件、HTML 文件或文本文件。包含的文件就好像是该 JSP 文件的一部分,会被同时编译执行。 - -Include 指令的语法格式如下: - -``` -<%@ include file="文件相对 url 地址" %> -``` - -**include** 指令中的文件名实际上是一个相对的 URL 地址。 - -如果您没有给文件关联一个路径,JSP 编译器默认在当前路径下寻找。 - -等价的 XML 语法: - -``` - -``` - -### Taglib 指令 - -JSP 允许用户自定义标签,一个自定义标签库就是自定义标签的集合。 - -`taglib`指令引入一个自定义标签集合的定义,包括库路径、自定义标签。 - -`taglib`指令的语法: - -``` -<%@ taglib uri="uri" prefix="prefixOfTag" %> -``` - -uri 属性确定标签库的位置,prefix 属性指定标签库的前缀。 - -等价的 XML 语法: - -``` - -``` - -## JSP 动作元素 - -JSP 动作元素是一组 JSP 内置的标签,只需要书写很少的标记代码就能使用 JSP 提供的丰富功能。JSP 动作元素是对常用的 JSP 功能的抽象与封装,包括两种,自定义 JSP 动作元素与标准 JSP 动作元素。 - -与 JSP 指令元素不同的是,JSP 动作元素在请求处理阶段起作用。JSP 动作元素是用 XML 语法写成的。 - -利用 JSP 动作可以动态地插入文件、重用 JavaBean 组件、把用户重定向到另外的页面、为 Java 插件生成 HTML 代码。 - -动作元素只有一种语法,它符合 XML 标准: - -``` - -``` - -动作元素基本上都是预定义的函数,JSP 规范定义了一系列的标准动作,它用 JSP 作为前缀,可用的标准动作元素如下: - -| 语法 | 描述 | -| --------------- | ----------------------------------------------------- | -| jsp:include | 在页面被请求的时候引入一个文件。 | -| jsp:useBean | 寻找或者实例化一个 JavaBean。 | -| jsp:setProperty | 设置 JavaBean 的属性。 | -| jsp:getProperty | 输出某个 JavaBean 的属性。 | -| jsp:forward | 把请求转到一个新的页面。 | -| jsp:plugin | 根据浏览器类型为 Java 插件生成 OBJECT 或 EMBED 标记。 | -| jsp:element | 定义动态 XML 元素 | -| jsp:attribute | 设置动态定义的 XML 元素属性。 | -| jsp:body | 设置动态定义的 XML 元素内容。 | -| jsp:text | 在 JSP 页面和文档中使用写入文本的模板 | - -### 常见的属性 - -所有的动作要素都有两个属性:id 属性和 scope 属性。 - -- **id 属性:**id 属性是动作元素的唯一标识,可以在 JSP 页面中引用。动作元素创建的 id 值可以通过 PageContext 来调用。 -- **scope 属性:**该属性用于识别动作元素的生命周期。 id 属性和 scope 属性有直接关系,scope 属性定义了相关联 id 对象的寿命。 scope 属性有四个可能的值: (a) page, (b)request, (c)session, 和 (d) application。 - -### `` - -`` 用来包含静态和动态的文件。该动作把指定文件插入正在生成的页面。 - -如果被包含的文件为 JSP 程序,则会先执行 JSP 程序,再将执行结果包含进来。 - -语法格式如下: - -``` - -``` - -前面已经介绍过 include 指令,它是在 JSP 文件被转换成 Servlet 的时候引入文件,而这里的 jsp:include 动作不同,插入文件的时间是在页面被请求的时候。 - -以下是 include 动作相关的属性列表。 - -| 属性 | 描述 | -| ----- | ------------------------------------------ | -| page | 包含在页面中的相对 URL 地址。 | -| flush | 布尔属性,定义在包含资源前是否刷新缓存区。 | - -示例: - -以下我们定义了两个文件 **date.jsp** 和 **main.jsp**,代码如下所示: - -date.jsp 文件代码: - -``` -<%@ page language="java" contentType="text/html; charset=UTF-8" -pageEncoding="UTF-8"%> -

    - 今天的日期是: <%= (new java.util.Date())%> -

    -``` - -main.jsp 文件代码: - -``` -<%@ page language="java" contentType="text/html; charset=UTF-8" -pageEncoding="UTF-8"%> - - - - - 菜鸟教程(runoob.com) - - -

    include 动作实例

    - - - -``` - -现在将以上两个文件放在服务器的根目录下,访问 main.jsp 文件。显示结果如下: - -``` -include 动作实例 - -今天的日期是: 2016-6-25 14:08:17 -``` - -### `` - -**jsp:useBean** 动作用来加载一个将在 JSP 页面中使用的 JavaBean。 - -这个功能非常有用,因为它使得我们可以发挥 Java 组件复用的优势。 - -jsp:useBean 动作最简单的语法为: - -``` - -``` - -在类载入后,我们既可以通过 jsp:setProperty 和 jsp:getProperty 动作来修改和检索 bean 的属性。 - -以下是 useBean 动作相关的属性列表。 - -| 属性 | 描述 | -| -------- | ------------------------------------------------------------- | -| class | 指定 Bean 的完整包名。 | -| type | 指定将引用该对象变量的类型。 | -| beanName | 通过 java.beans.Beans 的 instantiate() 方法指定 Bean 的名字。 | - -在给出具体实例前,让我们先来看下 jsp:setProperty 和 jsp:getProperty 动作元素: - -### `` - -jsp:setProperty 用来设置已经实例化的 Bean 对象的属性,有两种用法。首先,你可以在 jsp:useBean 元素的外面(后面)使用 jsp:setProperty,如下所示: - -``` - -... - -``` - -此时,不管 jsp:useBean 是找到了一个现有的 Bean,还是新创建了一个 Bean 实例,jsp:setProperty 都会执行。第二种用法是把 jsp:setProperty 放入 jsp:useBean 元素的内部,如下所示: - -``` - -... - - -``` - -此时,jsp:setProperty 只有在新建 Bean 实例时才会执行,如果是使用现有实例则不执行 jsp:setProperty。 - -jsp:setProperty 动作有下面四个属性,如下表: - -| 属性 | 描述 | -| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| name | name 属性是必需的。它表示要设置属性的是哪个 Bean。 | -| property | property 属性是必需的。它表示要设置哪个属性。有一个特殊用法:如果 property 的值是"\*",表示所有名字和 Bean 属性名字匹配的请求参数都将被传递给相应的属性 set 方法。 | -| value | value 属性是可选的。该属性用来指定 Bean 属性的值。字符串数据会在目标类中通过标准的 valueOf 方法自动转换成数字、boolean、Boolean、 byte、Byte、char、Character。例如,boolean 和 Boolean 类型的属性值(比如"true")通过 Boolean.valueOf 转换,int 和 Integer 类型的属性值(比如"42")通过 Integer.valueOf 转换。    value 和 param 不能同时使用,但可以使用其中任意一个。 | -| param | param 是可选的。它指定用哪个请求参数作为 Bean 属性的值。如果当前请求没有参数,则什么事情也不做,系统不会把 null 传递给 Bean 属性的 set 方法。因此,你可以让 Bean 自己提供默认属性值,只有当请求参数明确指定了新值时才修改默认属性值。 | - -### `` - -jsp:getProperty 动作提取指定 Bean 属性的值,转换成字符串,然后输出。语法格式如下: - -``` - -... - -``` - -下表是与 getProperty 相关联的属性: - -| 属性 | 描述 | -| -------- | ----------------------------------------- | -| name | 要检索的 Bean 属性名称。Bean 必须已定义。 | -| property | 表示要提取 Bean 属性的值 | - -实例 - -以下实例我们使用了 Bean: - -```java -package com.runoob.main; - -public class TestBean { - private String message = "菜鸟教程"; - - public String getMessage() { - return(message); - } - public void setMessage(String message) { - this.message = message; - } -} -``` - -编译以上实例文件 TestBean.java : - -``` -$ javac TestBean.java -``` - -编译完成后会在当前目录下生成一个 **TestBean.class** 文件, 将该文件拷贝至当前 JSP 项目的 **WebContent/WEB-INF/classes/com/runoob/main** 下( com/runoob/main 包路径,没有需要手动创建)。 - -下面是一个 Eclipse 中目录结构图: - -![img](http://www.runoob.com/wp-content/uploads/2014/01/6AC33FBA-0B76-4BFD-A690-E856E9E01900.jpg) - -下面是一个很简单的例子,它的功能是装载一个 Bean,然后设置/读取它的 message 属性。 - -现在让我们在 main.jsp 文件中调用该 Bean: - -``` -<%@ page language="java" contentType="text/html; charset=UTF-8" -pageEncoding="UTF-8"%> - - - - - 菜鸟教程(runoob.com) - - -

    Jsp 使用 JavaBean 实例

    - - - - -

    输出信息....

    - - - - -``` - -浏览器访问,执行以上文件,输出如下所示: - -![img](http://www.runoob.com/wp-content/uploads/2014/01/D7AD87A8-3392-4D4E-8731-18806B0644CD.jpg) - -### `` - -jsp:forward 动作把请求转到另外的页面。jsp:forward 标记只有一个属性 page。语法格式如下所示: - -``` - -``` - -以下是 forward 相关联的属性: - -| 属性 | 描述 | -| ---- | ----------------------------------------------------------------------------------------------------------------------------- | -| page | page 属性包含的是一个相对 URL。page 的值既可以直接给出,也可以在请求的时候动态计算,可以是一个 JSP 页面或者一个 Java Servlet. | - -实例 - -以下实例我们使用了两个文件,分别是: date.jsp 和 main.jsp。 - -date.jsp 文件代码如下: - -``` -<%@ page language="java" contentType="text/html; charset=UTF-8" -pageEncoding="UTF-8"%> -

    - 今天的日期是: <%= (new java.util.Date()).toLocaleString()%> -

    -``` - -main.jsp 文件代码: - -``` -<%@ page language="java" contentType="text/html; charset=UTF-8" -pageEncoding="UTF-8"%> - - - - - 菜鸟教程(runoob.com) - - -

    forward 动作实例

    - - - -``` - -现在将以上两个文件放在服务器的根目录下,访问 main.jsp 文件。显示结果如下: - -``` -今天的日期是: 2016-6-25 14:37:25 -``` - -### `` - -jsp:plugin 动作用来根据浏览器的类型,插入通过 Java 插件 运行 Java Applet 所必需的 OBJECT 或 EMBED 元素。 - -如果需要的插件不存在,它会下载插件,然后执行 Java 组件。 Java 组件可以是一个 applet 或一个 JavaBean。 - -plugin 动作有多个对应 HTML 元素的属性用于格式化 Java 组件。param 元素可用于向 Applet 或 Bean 传递参数。 - -以下是使用 plugin 动作元素的典型实例: - -``` - - - - - - Unable to initialize Java Plugin - - - -``` - -如果你有兴趣可以尝试使用 applet 来测试 `jsp:plugin` 动作元素,`` 元素是一个新元素,在组件出现故障的错误是发送给用户错误信息。 - -### `` 、 ``、`` - -`` 、 ``、`` 动作元素动态定义 XML 元素。动态是非常重要的,这就意味着 XML 元素在编译时是动态生成的而非静态。 - -以下实例动态定义了 XML 元素: - -``` -<%@ page language="java" contentType="text/html; charset=UTF-8" -pageEncoding="UTF-8"%> - - - - - 菜鸟教程(runoob.com) - - - - - 属性值 - - - XML 元素的主体 - - - - -``` - -浏览器访问以下页面,输出结果如下所示: - -![img](http://www.runoob.com/wp-content/uploads/2014/01/7D8C47F0-0DDE-4F1D-8BE1-B2C9C955683E.jpg) - -### `` - -动作元素允许在 JSP 页面和文档中使用写入文本的模板,语法格式如下: - -``` -模板数据 -``` - -以上文本模板不能包含其他元素,只能只能包含文本和 EL 表达式(注:EL 表达式将在后续章节中介绍)。请注意,在 XML 文件中,您不能使用表达式如 ${whatever > 0},因为>符号是非法的。 你可以使用 ${whatever gt 0}表达式或者嵌入在一个 CDATA 部分的值。 - -``` -]]> -``` - -如果你需要在 XHTML 中声明 DOCTYPE,必须使用到 `` 动作元素,实例如下: - -``` -]]> - -jsp:text action - - - - Welcome to JSP Programming - - - - -``` - -你可以对以上实例尝试使用及不使用该动作元素执行结果的区别。 - -## JSP 隐式对象 - -JSP 隐式对象是 JSP 容器为每个页面提供的 Java 对象,开发者可以直接使用它们而不用显式声明。JSP 隐式对象也被称为预定义变量。 - -JSP 所支持的九大隐式对象: - -| **对象** | **描述** | -| ----------- | ------------------------------------------------------------------ | -| request | **HttpServletRequest**类的实例 | -| response | **HttpServletResponse**类的实例 | -| out | **PrintWriter**类的实例,用于把结果输出至网页上 | -| session | **HttpSession**类的实例 | -| application | **ServletContext**类的实例,与应用上下文有关 | -| config | **ServletConfig**类的实例 | -| pageContext | **PageContext**类的实例,提供对 JSP 页面所有对象以及命名空间的访问 | -| page | 类似于 Java 类中的 this 关键字 | -| Exception | **Exception**类的对象,代表发生错误的 JSP 页面中对应的异常对象 | - -### request 对象 - -`request`对象是`javax.servlet.http.HttpServletRequest` 类的实例。 - -每当客户端请求一个 JSP 页面时,JSP 引擎就会制造一个新的`request`对象来代表这个请求。 - -`request`对象提供了一系列方法来获取 HTTP 头信息,cookies,HTTP 方法等等。 - -### response 对象 - -`response`对象是`javax.servlet.http.HttpServletResponse`类的实例。 - -当服务器创建`request`对象时会同时创建用于响应这个客户端的`response`对象。 - -`response`对象也定义了处理 HTTP 头模块的接口。通过这个对象,开发者们可以添加新的 cookies,时间戳,HTTP 状态码等等。 - -### out 对象 - -`out`对象是`javax.servlet.jsp.JspWriter`类的实例,用来在`response`对象中写入内容。 - -最初的`JspWriter`类对象根据页面是否有缓存来进行不同的实例化操作。可以在`page`指令中使用`buffered='false'`属性来轻松关闭缓存。 - -`JspWriter`类包含了大部分`java.io.PrintWriter`类中的方法。不过,`JspWriter`新增了一些专为处理缓存而设计的方法。还有就是,`JspWriter`类会抛出`IOExceptions`异常,而`PrintWriter`不会。 - -下表列出了我们将会用来输出`boolean`,`char`,`int`,`double`,`String`,`object`等类型数据的重要方法: - -| **方法** | **描述** | -| ---------------------------- | -------------------------- | -| **out.print(dataType dt)** | 输出 Type 类型的值 | -| **out.println(dataType dt)** | 输出 Type 类型的值然后换行 | -| **out.flush()** | 刷新输出流 | - -### session 对象 - -`session`对象是`javax.servlet.http.HttpSession`类的实例。和 Java Servlets 中的`session`对象有一样的行为。 - -`session`对象用来跟踪在各个客户端请求间的会话。 - -### application 对象 - -`application`对象直接包装了 servlet 的`ServletContext`类的对象,是`javax.servlet.ServletContext`类的实例。 - -这个对象在 JSP 页面的整个生命周期中都代表着这个 JSP 页面。这个对象在 JSP 页面初始化时被创建,随着`jspDestroy()`方法的调用而被移除。 - -通过向`application`中添加属性,则所有组成您 web 应用的 JSP 文件都能访问到这些属性。 - -### config 对象 - -`config`对象是`javax.servlet.ServletConfig`类的实例,直接包装了 servlet 的`ServletConfig`类的对象。 - -这个对象允许开发者访问 Servlet 或者 JSP 引擎的初始化参数,比如文件路径等。 - -以下是 config 对象的使用方法,不是很重要,所以不常用: - -``` -config.getServletName(); -``` - -它返回包含在``元素中的 servlet 名字,注意,``元素在`WEB-INF\web.xml`文件中定义。 - -### pageContext 对象 - -`pageContext`对象是`javax.servlet.jsp.PageContext`类的实例,用来代表整个 JSP 页面。 - -这个对象主要用来访问页面信息,同时过滤掉大部分实现细节。 - -这个对象存储了`request`对象和`response`对象的引用。`application`对象,`config`对象,`session`对象,`out`对象可以通过访问这个对象的属性来导出。 - -`pageContext`对象也包含了传给 JSP 页面的指令信息,包括缓存信息,ErrorPage URL,页面 scope 等。 - -`PageContext`类定义了一些字段,包括 PAGE_SCOPE,REQUEST_SCOPE,SESSION_SCOPE, APPLICATION_SCOPE。它也提供了 40 余种方法,有一半继承自`javax.servlet.jsp.JspContext` 类。 - -其中一个重要的方法就是`removeArribute()`,它可接受一个或两个参数。比如,pageContext.removeArribute("attrName")移除四个 scope 中相关属性,但是下面这种方法只移除特定 scope 中的相关属性: - -``` -pageContext.removeAttribute("attrName", PAGE_SCOPE); -``` - -### page 对象 - -这个对象就是页面实例的引用。它可以被看做是整个 JSP 页面的代表。 - -`page`对象就是`this`对象的同义词。 - -### exception 对象 - -`exception`对象包装了从先前页面中抛出的异常信息。它通常被用来产生对出错条件的适当响应。 - -## EL 表达式 - -EL 表达式是用`${}`括起来的脚本,用来更方便地读取对象。EL 表达式写在 JSP 的 HTML 代码中,而不能写在`<%`与`%>`引起的 JSP 脚本中。 - -JSP 表达式语言(EL)使得访问存储在 JavaBean 中的数据变得非常简单。JSP EL 既可以用来创建算术表达式也可以用来创建逻辑表达式。在 JSP EL 表达式内可以使用整型数,浮点数,字符串,常量 true、false,还有 null。 - -### 一个简单的语法 - -典型的,当您需要在 JSP 标签中指定一个属性值时,只需要简单地使用字符串即可: - -``` - -``` - -JSP EL 允许您指定一个表达式来表示属性值。一个简单的表达式语法如下: - -``` -${expr} -``` - -其中,expr 指的是表达式。在 JSP EL 中通用的操作符是"."和"[]"。这两个操作符允许您通过内嵌的 JSP 对象访问各种各样的 JavaBean 属性。 - -举例来说,上面的 `` 标签可以使用表达式语言改写成如下形式: - -``` - -``` - -当 JSP 编译器在属性中见到"\${}"格式后,它会产生代码来计算这个表达式,并且产生一个替代品来代替表达式的值。 - -您也可以在标签的模板文本中使用表达式语言。比如 `` 标签简单地将其主体中的文本插入到 JSP 输出中: - -``` - -

    Hello JSP!

    -
    -``` - -现在,在标签主体中使用表达式,就像这样: - -``` - - Box Perimeter is: ${2*box.width + 2*box.height} - -``` - -在 EL 表达式中可以使用圆括号来组织子表达式。比如 `${(1 + 2) _ 3}` 等于 9,但是 `${1 + (2 _ 3)}` 等于 7。 - -想要停用对 EL 表达式的评估的话,需要使用 page 指令将 isELIgnored 属性值设为 true: - -``` -<%@ page isELIgnored ="true|false" %> -``` - -这样,EL 表达式就会被忽略。若设为 false,则容器将会计算 EL 表达式。 - -### EL 中的基础操作符 - -EL 表达式支持大部分 Java 所提供的算术和逻辑操作符: - -| **操作符** | **描述** | -| ---------- | ---------------------------------- | -| . | 访问一个 Bean 属性或者一个映射条目 | -| [] | 访问一个数组或者链表的元素 | -| ( ) | 组织一个子表达式以改变优先级 | -| + | 加 | -| - | 减或负 | -| \* | 乘 | -| / or div | 除 | -| % or mod | 取模 | -| == or eq | 测试是否相等 | -| != or ne | 测试是否不等 | -| < or lt | 测试是否小于 | -| > or gt | 测试是否大于 | -| <= or le | 测试是否小于等于 | -| >= or ge | 测试是否大于等于 | -| && or and | 测试逻辑与 | -| \|\| or or | 测试逻辑或 | -| ! or not | 测试取反 | -| empty | 测试是否空值 | - -### JSP EL 中的函数 - -JSP EL 允许您在表达式中使用函数。这些函数必须被定义在自定义标签库中。函数的使用语法如下: - -``` -${ns:func(param1, param2, ...)} -``` - -ns 指的是命名空间(namespace),func 指的是函数的名称,param1 指的是第一个参数,param2 指的是第二个参数,以此类推。比如,有函数 fn:length,在 JSTL 库中定义,可以像下面这样来获取一个字符串的长度: - -``` -${fn:length("Get my length")} -``` - -要使用任何标签库中的函数,您需要将这些库安装在服务器中,然后使用 `` 标签在 JSP 文件中包含这些库。 - -### JSP EL 隐含对象 - -JSP EL 支持下表列出的隐含对象: - -| **隐含对象** | **描述** | -| ---------------- | ------------------------------ | -| pageScope | page 作用域 | -| requestScope | request 作用域 | -| sessionScope | session 作用域 | -| applicationScope | application 作用域 | -| param | Request 对象的参数,字符串 | -| paramValues | Request 对象的参数,字符串集合 | -| header | HTTP 信息头,字符串 | -| headerValues | HTTP 信息头,字符串集合 | -| initParam | 上下文初始化参数 | -| cookie | Cookie 值 | -| pageContext | 当前页面的 pageContext | - -您可以在表达式中使用这些对象,就像使用变量一样。接下来会给出几个例子来更好的理解这个概念。 - -### pageContext 对象 - -pageContext 对象是 JSP 中 pageContext 对象的引用。通过 pageContext 对象,您可以访问 request 对象。比如,访问 request 对象传入的查询字符串,就像这样: - -``` -${pageContext.request.queryString} -``` - -### Scope 对象 - -pageScope,requestScope,sessionScope,applicationScope 变量用来访问存储在各个作用域层次的变量。 - -举例来说,如果您需要显式访问在 applicationScope 层的 box 变量,可以这样来访问:applicationScope.box。 - -### param 和 paramValues 对象 - -param 和 paramValues 对象用来访问参数值,通过使用 request.getParameter 方法和 request.getParameterValues 方法。 - -举例来说,访问一个名为 order 的参数,可以这样使用表达式:`${param.order}`,或者`${param["order"]}`。 - -接下来的例子表明了如何访问 request 中的 username 参数: - -``` -<%@ page import="java.io.*,java.util.*" %> <% String title = "Accessing Request -Param"; %> - - - <% out.print(title); %> - - -
    -

    <% out.print(title); %>

    -
    -
    -

    ${param["username"]}

    -
    - - -``` - -param 对象返回单一的字符串,而 paramValues 对象则返回一个字符串数组。 - -### header 和 headerValues 对象 - -header 和 headerValues 对象用来访问信息头,通过使用 request.getHeader 方法和 request.getHeaders 方法。 - -举例来说,要访问一个名为 user-agent 的信息头,可以这样使用表达式:`${header.user-agent}`,或者 `${header["user-agent"]}`。 - -接下来的例子表明了如何访问 user-agent 信息头: - -``` -<%@ page import="java.io.*,java.util.*" %> <% String title = "User Agent -Example"; %> - - - <% out.print(title); %> - - -
    -

    <% out.print(title); %>

    -
    -
    -

    ${header["user-agent"]}

    -
    - - -``` - -运行结果如下: - -![img](http://www.runoob.com/wp-content/uploads/2014/01/jsp-expression-language.jpg) - -header 对象返回单一值,而 headerValues 则返回一个字符串数组。 - -## JSTL - -JSP 标准标签库(JSTL)是一个 JSP 标签集合,它封装了 JSP 应用的通用核心功能。 - -JSTL 支持通用的、结构化的任务,比如迭代,条件判断,XML 文档操作,国际化标签,SQL 标签。 除了这些,它还提供了一个框架来使用集成 JSTL 的自定义标签。 - -根据 JSTL 标签所提供的功能,可以将其分为 5 个类别。 - -- **核心标签** -- **格式化标签** -- **SQL 标签** -- **XML 标签** -- **JSTL 函数** - -### JSTL 库安装 - -Apache Tomcat 安装 JSTL 库步骤如下: - -从 Apache 的标准标签库中下载的二进包(jakarta-taglibs-standard-current.zip)。 - -- 官方下载地址: -- 本站下载地址:[jakarta-taglibs-standard-1.1.2.zip](http://static.runoob.com/download/jakarta-taglibs-standard-1.1.2.tar.gz) - -下载 **jakarta-taglibs-standard-1.1.2.zip** 包并解压,将 **jakarta-taglibs-standard-1.1.2/lib/** 下的两个 jar 文件:**standard.jar** 和 **jstl.jar** 文件拷贝到 **/WEB-INF/lib/** 下。 - -将 tld 下的需要引入的 tld 文件复制到 WEB-INF 目录下。 - -接下来我们在 web.xml 文件中添加以下配置: - -``` - - - - - http://java.sun.com/jsp/jstl/fmt - /WEB-INF/fmt.tld - - - http://java.sun.com/jsp/jstl/fmt-rt - /WEB-INF/fmt-rt.tld - - - http://java.sun.com/jsp/jstl/core - /WEB-INF/c.tld - - - http://java.sun.com/jsp/jstl/core-rt - /WEB-INF/c-rt.tld - - - http://java.sun.com/jsp/jstl/sql - /WEB-INF/sql.tld - - - http://java.sun.com/jsp/jstl/sql-rt - /WEB-INF/sql-rt.tld - - - http://java.sun.com/jsp/jstl/x - /WEB-INF/x.tld - - - http://java.sun.com/jsp/jstl/x-rt - /WEB-INF/x-rt.tld - - - -``` - -使用任何库,你必须在每个 JSP 文件中的头部包含 **** 标签。 - -### 核心标签 - -核心标签是最常用的 JSTL 标签。引用核心标签库的语法如下: - -``` -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -``` - -| 标签 | 描述 | -| :---------------------------------------------------------------------- | :-------------------------------------------------------------------------- | -| [``](http://www.runoob.com/jsp/jstl-core-out-tag.html) | 用于在 JSP 中显示数据,就像<%= ... > | -| [``](http://www.runoob.com/jsp/jstl-core-set-tag.html) | 用于保存数据 | -| [``](http://www.runoob.com/jsp/jstl-core-remove-tag.html) | 用于删除数据 | -| [``](http://www.runoob.com/jsp/jstl-core-catch-tag.html) | 用来处理产生错误的异常状况,并且将错误信息储存起来 | -| [``](http://www.runoob.com/jsp/jstl-core-if-tag.html) | 与我们在一般程序中用的 if 一样 | -| [``](http://www.runoob.com/jsp/jstl-core-choose-tag.html) | 本身只当做的父标签 | -| [``](http://www.runoob.com/jsp/jstl-core-choose-tag.html) | 的子标签,用来判断条件是否成立 | -| [``](http://www.runoob.com/jsp/jstl-core-choose-tag.html) | 的子标签,接在标签后,当标签判断为 false 时被执行 | -| [``](http://www.runoob.com/jsp/jstl-core-import-tag.html) | 检索一个绝对或相对 URL,然后将其内容暴露给页面 | -| [``](http://www.runoob.com/jsp/jstl-core-foreach-tag.html) | 基础迭代标签,接受多种集合类型 | -| [``](http://www.runoob.com/jsp/jstl-core-foreach-tag.html) | 根据指定的分隔符来分隔内容并迭代输出 | -| [``](http://www.runoob.com/jsp/jstl-core-param-tag.html) | 用来给包含或重定向的页面传递参数 | -| [``](http://www.runoob.com/jsp/jstl-core-redirect-tag.html) | 重定向至一个新的 URL. | -| [``](http://www.runoob.com/jsp/jstl-core-url-tag.html) | 使用可选的查询参数来创造一个 URL | - -### 格式化标签 - -JSTL 格式化标签用来格式化并输出文本、日期、时间、数字。引用格式化标签库的语法如下: - -``` -<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> -``` - -| 标签 | 描述 | -| :---------------------------------------------------------------------------------------- | :--------------------------------------- | -| [``](http://www.runoob.com/jsp/jstl-format-formatnumber-tag.html) | 使用指定的格式或精度格式化数字 | -| [``](http://www.runoob.com/jsp/jstl-format-parsenumber-tag.html) | 解析一个代表着数字,货币或百分比的字符串 | -| [``](http://www.runoob.com/jsp/jstl-format-formatdate-tag.html) | 使用指定的风格或模式格式化日期和时间 | -| [``](http://www.runoob.com/jsp/jstl-format-parsedate-tag.html) | 解析一个代表着日期或时间的字符串 | -| [``](http://www.runoob.com/jsp/jstl-format-bundle-tag.html) | 绑定资源 | -| [``](http://www.runoob.com/jsp/jstl-format-setlocale-tag.html) | 指定地区 | -| [``](http://www.runoob.com/jsp/jstl-format-setbundle-tag.html) | 绑定资源 | -| [``](http://www.runoob.com/jsp/jstl-format-timezone-tag.html) | 指定时区 | -| [``](http://www.runoob.com/jsp/jstl-format-settimezone-tag.html) | 指定时区 | -| [``](http://www.runoob.com/jsp/jstl-format-message-tag.html) | 显示资源配置文件信息 | -| [``](http://www.runoob.com/jsp/jstl-format-requestencoding-tag.html) | 设置 request 的字符编码 | - -### SQL 标签 - -JSTL SQL 标签库提供了与关系型数据库(Oracle,MySQL,SQL Server 等等)进行交互的标签。引用 SQL 标签库的语法如下: - -``` -<%@ taglib prefix="sql" uri="http://java.sun.com/jsp/jstl/sql" %> -``` - -| 标签 | 描述 | -| :--------------------------------------------------------------------------------- | :--------------------------------------------------------------------------- | -| [``](http://www.runoob.com/jsp/jstl-sql-setdatasource-tag.html) | 指定数据源 | -| [``](http://www.runoob.com/jsp/jstl-sql-query-tag.html) | 运行 SQL 查询语句 | -| [``](http://www.runoob.com/jsp/jstl-sql-update-tag.html) | 运行 SQL 更新语句 | -| [``](http://www.runoob.com/jsp/jstl-sql-param-tag.html) | 将 SQL 语句中的参数设为指定值 | -| [``](http://www.runoob.com/jsp/jstl-sql-dateparam-tag.html) | 将 SQL 语句中的日期参数设为指定的 java.util.Date 对象值 | -| [``](http://www.runoob.com/jsp/jstl-sql-transaction-tag.html) | 在共享数据库连接中提供嵌套的数据库行为元素,将所有语句以一个事务的形式来运行 | - -### XML 标签 - -JSTL XML 标签库提供了创建和操作 XML 文档的标签。引用 XML 标签库的语法如下: - -``` -<%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %> -``` - -在使用 xml 标签前,你必须将 XML 和 XPath 的相关包拷贝至你的 `\lib` 下: - -- XercesImpl.jar - - 下载地址: - -- xalan.jar - - 下载地址: - -| 标签 | 描述 | -| :----------------------------------------------------------------------- | :---------------------------------------------------------- | -| [``](http://www.runoob.com/jsp/jstl-xml-out-tag.html) | 与<%= ... >,类似,不过只用于 XPath 表达式 | -| [``](http://www.runoob.com/jsp/jstl-xml-parse-tag.html) | 解析 XML 数据 | -| [``](http://www.runoob.com/jsp/jstl-xml-set-tag.html) | 设置 XPath 表达式 | -| [``](http://www.runoob.com/jsp/jstl-xml-if-tag.html) | 判断 XPath 表达式,若为真,则执行本体中的内容,否则跳过本体 | -| [``](http://www.runoob.com/jsp/jstl-xml-foreach-tag.html) | 迭代 XML 文档中的节点 | -| [``](http://www.runoob.com/jsp/jstl-xml-choose-tag.html) | 的父标签 | -| [``](http://www.runoob.com/jsp/jstl-xml-choose-tag.html) | 的子标签,用来进行条件判断 | -| [``](http://www.runoob.com/jsp/jstl-xml-choose-tag.html) | 的子标签,当判断为 false 时被执行 | -| [``](http://www.runoob.com/jsp/jstl-xml-transform-tag.html) | 将 XSL 转换应用在 XML 文档中 | -| [``](http://www.runoob.com/jsp/jstl-xml-param-tag.html) | 与共同使用,用于设置 XSL 样式表 | - -### JSTL 函数 - -JSTL 包含一系列标准函数,大部分是通用的字符串处理函数。引用 JSTL 函数库的语法如下: - -``` -<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> -``` - -| 函数 | 描述 | -| :----------------------------------------------------------------------------------------- | :------------------------------------------------------- | -| [fn:contains()](http://www.runoob.com/jsp/jstl-function-contains.html) | 测试输入的字符串是否包含指定的子串 | -| [fn:containsIgnoreCase()](http://www.runoob.com/jsp/jstl-function-containsignoreCase.html) | 测试输入的字符串是否包含指定的子串,大小写不敏感 | -| [fn:endsWith()](http://www.runoob.com/jsp/jstl-function-endswith.html) | 测试输入的字符串是否以指定的后缀结尾 | -| [fn:escapeXml()](http://www.runoob.com/jsp/jstl-function-escapexml.html) | 跳过可以作为 XML 标记的字符 | -| [fn:indexOf()](http://www.runoob.com/jsp/jstl-function-indexof.html) | 返回指定字符串在输入字符串中出现的位置 | -| [fn:join()](http://www.runoob.com/jsp/jstl-function-join.html) | 将数组中的元素合成一个字符串然后输出 | -| [fn:length()](http://www.runoob.com/jsp/jstl-function-length.html) | 返回字符串长度 | -| [fn:replace()](http://www.runoob.com/jsp/jstl-function-replace.html) | 将输入字符串中指定的位置替换为指定的字符串然后返回 | -| [fn:split()](http://www.runoob.com/jsp/jstl-function-split.html) | 将字符串用指定的分隔符分隔然后组成一个子字符串数组并返回 | -| [fn:startsWith()](http://www.runoob.com/jsp/jstl-function-startswith.html) | 测试输入字符串是否以指定的前缀开始 | -| [fn:substring()](http://www.runoob.com/jsp/jstl-function-substring.html) | 返回字符串的子集 | -| [fn:substringAfter()](http://www.runoob.com/jsp/jstl-function-substringafter.html) | 返回字符串在指定子串之后的子集 | -| [fn:substringBefore()](http://www.runoob.com/jsp/jstl-function-substringbefore.html) | 返回字符串在指定子串之前的子集 | -| [fn:toLowerCase()](http://www.runoob.com/jsp/jstl-function-tolowercase.html) | 将字符串中的字符转为小写 | -| [fn:toUpperCase()](http://www.runoob.com/jsp/jstl-function-touppercase.html) | 将字符串中的字符转为大写 | -| [fn:trim()](http://www.runoob.com/jsp/jstl-function-trim.html) | 移除首尾的空白符 | - -## Taglib - -### JSP 自定义标签 - -自定义标签是用户定义的 JSP 语言元素。当 JSP 页面包含一个自定义标签时将被转化为 servlet,标签转化为对被 称为 tag handler 的对象的操作,即当 servlet 执行时 Web container 调用那些操作。 - -JSP 标签扩展可以让你创建新的标签并且可以直接插入到一个 JSP 页面。 JSP 2.0 规范中引入 Simple Tag Handlers 来编写这些自定义标记。 - -你可以继承 SimpleTagSupport 类并重写的 doTag()方法来开发一个最简单的自定义标签。 - -### 创建"Hello"标签 - -接下来,我们想创建一个自定义标签叫作,标签格式为: - -``` - -``` - -要创建自定义的 JSP 标签,你首先必须创建处理标签的 Java 类。所以,让我们创建一个 HelloTag 类,如下所示: - -``` -package com.runoob; import javax.servlet.jsp.tagext.*; import -javax.servlet.jsp.*; import java.io.*; public class HelloTag extends -SimpleTagSupport { public void doTag() throws JspException, IOException { -JspWriter out = getJspContext().getOut(); out.println("Hello Custom Tag!"); } } -``` - -以下代码重写了 doTag()方法,方法中使用了 getJspContext()方法来获取当前的 JspContext 对象,并将"Hello Custom Tag!"传递给 JspWriter 对象。 - -编译以上类,并将其复制到环境变量 CLASSPATH 目录中。最后创建如下标签库:`webapps\ROOT\WEB-INF\custom.tld`。 - -``` - - 1.0 - 2.0 - Example TLD - - Hello - com.runoob.HelloTag - empty - - -``` - -接下来,我们就可以在 JSP 文件中使用 Hello 标签: - -``` -<%@ taglib prefix="ex" uri="WEB-INF/custom.tld"%> - - - A sample custom tag - - - - - -``` - -以上程序输出结果为: - -``` -Hello Custom Tag! -``` - -### 访问标签体 - -你可以像标准标签库一样在标签中包含消息内容。如我们要在我们自定义的 Hello 中包含内容,格式如下: - -``` - - This is message body - -``` - -我们可以修改标签处理类文件,代码如下: - -```java -package com.runoob; - -import javax.servlet.jsp.tagext.*; -import javax.servlet.jsp.*; -import java.io.*; - -public class HelloTag extends SimpleTagSupport { - - StringWriter sw = new StringWriter(); - public void doTag() - throws JspException, IOException - { - getJspBody().invoke(sw); - getJspContext().getOut().println(sw.toString()); - } - -} -``` - -接下来我们需要修改 TLD 文件,如下所示: - -``` - - 1.0 - 2.0 - Example TLD with Body - - Hello - com.runoob.HelloTag - scriptless - - -``` - -现在我们可以在 JSP 使用修改后的标签,如下所示: - -``` -<%@ taglib prefix="ex" uri="WEB-INF/custom.tld"%> - - - A sample custom tag - - - - This is message body - - - -``` - -以上程序输出结果如下所示: - -``` -This is message body -``` - -### 自定义标签属性 - -你可以在自定义标准中设置各种属性,要接收属性,值自定义标签类必须实现 setter 方法, JavaBean 中的 setter 方法如下所示: - -```java -package com.runoob; - -import javax.servlet.jsp.tagext.*; -import javax.servlet.jsp.*; -import java.io.*; - -public class HelloTag extends SimpleTagSupport { - - private String message; - - public void setMessage(String msg) { - this.message = msg; - } - - StringWriter sw = new StringWriter(); - - public void doTag() - throws JspException, IOException - { - if (message != null) { - /* 从属性中使用消息 */ - JspWriter out = getJspContext().getOut(); - out.println( message ); - } - else { - /* 从内容体中使用消息 */ - getJspBody().invoke(sw); - getJspContext().getOut().println(sw.toString()); - } - } - -} -``` - -属性的名称是"message",所以 setter 方法是的 setMessage()。现在让我们在 TLD 文件中使用的元素添加此属性: - -``` - - 1.0 - 2.0 - Example TLD with Body - - Hello - com.runoob.HelloTag - scriptless - - message - - - -``` - -现在我们就可以在 JSP 文件中使用 message 属性了,如下所示: - -``` -<%@ taglib prefix="ex" uri="WEB-INF/custom.tld"%> - - - A sample custom tag - - - - - -``` - -以上实例数据输出结果为: - -``` -This is custom tag -``` - -你还可以包含以下属性: - -| 属性 | 描述 | -| :---------- | :------------------------------------------------------- | -| name | 定义属性的名称。每个标签的是属性名称必须是唯一的。 | -| required | 指定属性是否是必须的或者可选的,如果设置为 false 为可选。 | -| rtexprvalue | 声明在运行表达式时,标签属性是否有效。 | -| type | 定义该属性的 Java 类类型 。默认指定为 **String** | -| description | 描述信息 | -| fragment | 如果声明了该属性,属性值将被视为一个 **JspFragment**。 | - -以下是指定相关的属性实例: - -``` -..... - - attribute_name - false - java.util.Date - false - -..... -``` - -如果你使用了两个属性,修改 TLD 文件,如下所示: - -``` -..... - - attribute_name1 - false - java.util.Boolean - false - - - attribute_name2 - true - java.util.Date - -..... -``` diff --git a/docs/javaee/javaee-servlet.md b/docs/javaee/javaee-servlet.md deleted file mode 100644 index 5ba4cf8f..00000000 --- a/docs/javaee/javaee-servlet.md +++ /dev/null @@ -1,296 +0,0 @@ -# Servlet 指南 - - - -- [JavaWeb 简介](#javaweb-简介) - - [Web 应用程序](#web-应用程序) - - [常见 Web 服务器](#常见-web-服务器) -- [Servlet 简介](#servlet-简介) - - [什么是 Servlet](#什么是-servlet) - - [Servlet 和 CGI 的区别](#servlet-和-cgi-的区别) - - [Servlet 版本以及主要特性](#servlet-版本以及主要特性) - - [Servlet 任务](#servlet-任务) - - [Servlet 生命周期](#servlet-生命周期) -- [Servlet API](#servlet-api) - - [Servlet 包](#servlet-包) - - [Servlet 接口](#servlet-接口) -- [Servlet 和 HTTP 状态码](#servlet-和-http-状态码) - - [HTTP 状态码](#http-状态码) - - [设置 HTTP 状态码的方法](#设置-http-状态码的方法) - - [HTTP 状态码实例](#http-状态码实例) - - - -## JavaWeb 简介 - -### Web 应用程序 - -Web,在英语中 web 即表示网页的意思,它用于表示 Internet 主机上供外界访问的资源。 - -Web 应用程序是一种可以通过 Web 访问的应用程序,程序的最大好处是用户很容易访问应用程序,用户只需要有浏览器即可,不需要再安装其他软件。 - -Internet 上供外界访问的 Web 资源分为: - -- 静态 web 资源:指 web 页面中供人们浏览的数据始终是不变。常见静态资源文件:html、css、各种图片类型(jpg、png) -- 动态 web 资源:指 web 页面中供人们浏览的数据是由程序产生的,不同时间点访问 web 页面看到的内容各不相同。常见动态资源技术:JSP/Servlet、ASP、PHP - -### 常见 Web 服务器 - -- [Tomcat](http://tomcat.apache.org/) -- [Jetty](http://www.eclipse.org/jetty/) -- [Resin](https://caucho.com/) -- [Apache](http://httpd.apache.org/) -- [Nginx](http://nginx.org/en/) -- [WebSphere](https://www.ibm.com/cloud/websphere-application-platform) -- [WebLogic](https://www.oracle.com/middleware/technologies/weblogic.html) -- JBoss - -## Servlet 简介 - -### 什么是 Servlet - -Servlet(Server Applet),即小服务程序或服务连接器。Servlet 是 Java 编写的服务器端程序,具有独立于平台和协议的特性,主要功能在于交互式地浏览和生成数据,生成动态 Web 内容。 - -- 狭义的 Servlet 是指 Java 实现的一个接口。 -- 广义的 Servlet 是指任何实现了这个 Servlet 接口的类。 - -Servlet 运行于支持 Java 的应用服务器中。从原理上讲,Servlet 可以响应任何类型的请求,但绝大多数情况下 Servlet 只用来扩展基于 HTTP 协议的 Web 服务器。 - -### Servlet 和 CGI 的区别 - -Servlet 技术出现之前,Web 主要使用 CGI 技术。它们的区别如下: - -- Servlet 是基于 Java 编写的,处于服务器进程中,他能够通过多线程方式运行 service() 方法,一个实例可以服务于多个请求,而且一般不会销毁; -- CGI(Common Gateway Interface),即通用网关接口。它会为每个请求产生新的进程,服务完成后销毁,所以效率上低于 Servlet。 - -### Servlet 版本以及主要特性 - -| 版本 | 日期 | JAVA EE/JDK 版本 | 特性 | -| ----------- | ------------- | ------------------ | --------------------------------------------------------------------- | -| Servlet 4.0 | 2017 年 10 月 | JavaEE 8 | HTTP2 | -| Servlet 3.1 | 2013 年 5 月 | JavaEE 7 | 非阻塞 I/O,HTTP 协议升级机制 | -| Servlet 3.0 | 2009 年 12 月 | JavaEE 6, JavaSE 6 | 可插拔性,易于开发,异步 Servlet,安全性,文件上传 | -| Servlet 2.5 | 2005 年 10 月 | JavaEE 5, JavaSE 5 | 依赖 JavaSE 5,支持注解 | -| Servlet 2.4 | 2003 年 11 月 | J2EE 1.4, J2SE 1.3 | web.xml 使用 XML Schema | -| Servlet 2.3 | 2001 年 8 月 | J2EE 1.3, J2SE 1.2 | Filter | -| Servlet 2.2 | 1999 年 8 月 | J2EE 1.2, J2SE 1.2 | 成为 J2EE 标准 | -| Servlet 2.1 | 1998 年 11 月 | 未指定 | First official specification, added RequestDispatcher, ServletContext | -| Servlet 2.0 | | JDK 1.1 | Part of Java Servlet Development Kit 2.0 | -| Servlet 1.0 | 1997 年 6 月 | | | - -### Servlet 任务 - -Servlet 执行以下主要任务: - -- 读取客户端(浏览器)发送的显式的数据。这包括网页上的 HTML 表单,或者也可以是来自 applet 或自定义的 HTTP 客户端程序的表单。 -- 读取客户端(浏览器)发送的隐式的 HTTP 请求数据。这包括 cookies、媒体类型和浏览器能理解的压缩格式等等。 -- 处理数据并生成结果。这个过程可能需要访问数据库,执行 RMI 或 CORBA 调用,调用 Web 服务,或者直接计算得出对应的响应。 -- 发送显式的数据(即文档)到客户端(浏览器)。该文档的格式可以是多种多样的,包括文本文件(HTML 或 XML)、二进制文件(GIF 图像)、Excel 等。 -- 发送隐式的 HTTP 响应到客户端(浏览器)。这包括告诉浏览器或其他客户端被返回的文档类型(例如 HTML),设置 cookies 和缓存参数,以及其他类似的任务。 - -### Servlet 生命周期 - -![img](http://www.runoob.com/wp-content/uploads/2014/07/Servlet-LifeCycle.jpg) - -Servlet 生命周期如下: - -1. **加载** - 第一个到达服务器的 HTTP 请求被委派到 Servlet 容器。容器通过类加载器使用 Servlet 类对应的文件加载 servlet; -2. **初始化** - Servlet 通过调用 **init ()** 方法进行初始化。 -3. **服务** - Servlet 调用 **service()** 方法来处理客户端的请求。 -4. **销毁** - Servlet 通过调用 **destroy()** 方法终止(结束)。 -5. **卸载** - Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。 - -## Servlet API - -### Servlet 包 - -Java Servlet 是运行在带有支持 Java Servlet 规范的解释器的 web 服务器上的 Java 类。 - -Servlet 可以使用 **javax.servlet** 和 **javax.servlet.http** 包创建,它是 Java 企业版的标准组成部分,Java 企业版是支持大型开发项目的 Java 类库的扩展版本。 - -Java Servlet 就像任何其他的 Java 类一样已经被创建和编译。在您安装 Servlet 包并把它们添加到您的计算机上的 Classpath 类路径中之后,您就可以通过 JDK 的 Java 编译器或任何其他编译器来编译 Servlet。 - -### Servlet 接口 - -#### init() 方法 - -init 方法被设计成只调用一次。它在第一次创建 Servlet 时被调用,在后续每次用户请求时不再调用。因此,它是用于一次性初始化,就像 Applet 的 init 方法一样。 - -Servlet 创建于用户第一次调用对应于该 Servlet 的 URL 时,但是您也可以指定 Servlet 在服务器第一次启动时被加载。 - -当用户调用一个 Servlet 时,就会创建一个 Servlet 实例,每一个用户请求都会产生一个新的线程,适当的时候移交给 doGet 或 doPost 方法。init() 方法简单地创建或加载一些数据,这些数据将被用于 Servlet 的整个生命周期。 - -init 方法的定义如下: - -```java -public void init() throws ServletException { - // 初始化代码... -} -``` - -#### service() 方法 - -service() 方法是执行实际任务的主要方法。Servlet 容器(即 Web 服务器)调用 service() 方法来处理来自客户端(浏览器)的请求,并把格式化的响应写回给客户端。 - -每次服务器接收到一个 Servlet 请求时,服务器会产生一个新的线程并调用服务。service() 方法检查 HTTP 请求类型(GET、POST、PUT、DELETE 等),并在适当的时候调用 doGet、doPost、doPut,doDelete 等方法。 - -下面是该方法的特征: - -```java -public void service(ServletRequest request, - ServletResponse response) - throws ServletException, IOException{ -} -``` - -service() 方法由容器调用,service 方法在适当的时候调用 doGet、doPost、doPut、doDelete 等方法。所以,您不用对 service() 方法做任何动作,您只需要根据来自客户端的请求类型来重写 doGet() 或 doPost() 即可。 - -doGet() 和 doPost() 方法是每次服务请求中最常用的方法。下面是这两种方法的特征。 - -#### doGet() 方法 - -GET 请求来自于一个 URL 的正常请求,或者来自于一个未指定 METHOD 的 HTML 表单,它由 doGet() 方法处理。 - -```java -public void doGet(HttpServletRequest request, - HttpServletResponse response) - throws ServletException, IOException { - // Servlet 代码 -} -``` - -#### doPost() 方法 - -POST 请求来自于一个特别指定了 METHOD 为 POST 的 HTML 表单,它由 doPost() 方法处理。 - -```java -public void doPost(HttpServletRequest request, - HttpServletResponse response) - throws ServletException, IOException { - // Servlet 代码 -} -``` - -#### destroy() 方法 - -destroy() 方法只会被调用一次,在 Servlet 生命周期结束时被调用。destroy() 方法可以让您的 Servlet 关闭数据库连接、停止后台线程、把 Cookie 列表或点击计数器写入到磁盘,并执行其他类似的清理活动。 - -在调用 destroy() 方法之后,servlet 对象被标记为垃圾回收。destroy 方法定义如下所示: - -```java - public void destroy() { - // 终止化代码... - } -``` - -## Servlet 和 HTTP 状态码 - -title: JavaEE Servlet HTTP 状态码 -date: 2017-11-08 -categories: - -- javaee - tags: -- javaee -- servlet -- http - -### HTTP 状态码 - -HTTP 请求和 HTTP 响应消息的格式是类似的,结构如下: - -- 初始状态行 + 回车换行符(回车+换行) -- 零个或多个标题行+回车换行符 -- 一个空白行,即回车换行符 -- 一个可选的消息主体,比如文件、查询数据或查询输出 - -例如,服务器的响应头如下所示: - -```http -HTTP/1.1 200 OK -Content-Type: text/html -Header2: ... -... -HeaderN: ... - (Blank Line) - - -... - -... - - -``` - -状态行包括 HTTP 版本(在本例中为 HTTP/1.1)、一个状态码(在本例中为 200)和一个对应于状态码的短消息(在本例中为 OK)。 - -以下是可能从 Web 服务器返回的 HTTP 状态码和相关的信息列表: - -- `1**`:信息性状态码 -- `2**`:成功状态码 - - 200:请求正常成功 - - 204:指示请求成功但没有返回新信息 - - 206:指示服务器已完成对资源的部分 GET 请求 -- `3**`:重定向状态码 - - 301:永久性重定向 - - 302:临时性重定向 - - 304:服务器端允许请求访问资源,但未满足条件 -- `4**`:客户端错误状态码 - - 400:请求报文中存在语法错误 - - 401:发送的请求需要有通过 HTTP 认证的认证信息 - - 403:对请求资源的访问被服务器拒绝了 - - 404:服务器上无法找到请求的资源 -- `5**`:服务器错误状态码 - - 500:服务器端在执行请求时发生了错误 - - 503:服务器暂时处于超负载或正在进行停机维护,现在无法处理请求 - -### 设置 HTTP 状态码的方法 - -下面的方法可用于在 Servlet 程序中设置 HTTP 状态码。这些方法通过 `HttpServletResponse` 对象可用。 - -| 序号 | 方法 & 描述 | -| ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| 1 | **public void setStatus ( int statusCode )**该方法设置一个任意的状态码。setStatus 方法接受一个 int(状态码)作为参数。如果您的反应包含了一个特殊的状态码和文档,请确保在使用 _PrintWriter_ 实际返回任何内容之前调用 setStatus。 | -| 2 | **public void sendRedirect(String url)**该方法生成一个 302 响应,连同一个带有新文档 URL 的 _Location_ 头。 | -| 3 | **public void sendError(int code, String message)**该方法发送一个状态码(通常为 404),连同一个在 HTML 文档内部自动格式化并发送到客户端的短消息。 | - -### HTTP 状态码实例 - -下面的例子把 407 错误代码发送到客户端浏览器,浏览器会显示 "Need authentication!!!" 消息。 - -```java -// 导入必需的 java 库 -import java.io.*; -import javax.servlet.*; -import javax.servlet.http.*; -import java.util.*; - -// 扩展 HttpServlet 类 -public class showError extends HttpServlet { - - // 处理 GET 方法请求的方法 - public void doGet(HttpServletRequest request, - HttpServletResponse response) - throws ServletException, IOException - { - // 设置错误代码和原因 - response.sendError(407, "Need authentication!!!" ); - } - // 处理 POST 方法请求的方法 - public void doPost(HttpServletRequest request, - HttpServletResponse response) - throws ServletException, IOException { - doGet(request, response); - } -} -``` - -现在,调用上面的 Servlet 将显示以下结果: - -```http -HTTP Status 407 - Need authentication!!! -type Status report -message Need authentication!!! -description The client must first authenticate itself with the proxy (Need authentication!!!). -Apache Tomcat/5.5.29 -``` diff --git a/docs/mq/README.md b/docs/mq/README.md new file mode 100644 index 00000000..fe5d50b8 --- /dev/null +++ b/docs/mq/README.md @@ -0,0 +1,15 @@ +# Java 和消息队列 + +> 消息队列(Message Queue,简称 MQ)技术是分布式应用间交换信息的一种技术。 +> +> 消息队列主要解决应用耦合,异步消息,流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构。是大型分布式系统不可缺少的中间件。 +> +> 如果想深入学习各种消息队列产品,建议先了解一下 [消息队列基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/distributed/mq-theory.md) ,有助于理解消息队列特性的实现和设计思路。 + +- [消息队列面经](mq-interview.md) +- [Kafka](kafka) + - [Kafka 应用指南基础篇](kafka/kafka-basic.md) + - [Kafka 应用指南进阶篇](kafka/kafka-advance.md) + - [Kafka 运维指南](kafka/kafka-ops.md) +- [RocketMQ](rocketmq.md) +- [ActiveMQ](activemq.md) diff --git a/docs/ecology/mq/ActiveMQ.md b/docs/mq/activemq.md similarity index 96% rename from docs/ecology/mq/ActiveMQ.md rename to docs/mq/activemq.md index 2ee8e043..d6a3c659 100644 --- a/docs/ecology/mq/ActiveMQ.md +++ b/docs/mq/activemq.md @@ -1,21 +1,10 @@ # ActiveMQ - - -- [1. JMS 基本概念](#1-jms-基本概念) - - [1.1. 消息模型](#11-消息模型) - - [1.2. JMS 编程模型](#12-jms-编程模型) -- [2. 安装](#2-安装) -- [3. 项目中的应用](#3-项目中的应用) -- [4. 资源](#4-资源) - - - -## 1. JMS 基本概念 +## JMS 基本概念 `JMS` 即 **Java 消息服务(Java Message Service)API**,是一个 Java 平台中关于面向消息中间件的 API。它用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。Java 消息服务是一个与具体平台无关的 API,绝大多数 MOM 提供商都对 JMS 提供支持。 -### 1.1. 消息模型 +### 消息模型 JMS 有两种消息模型: @@ -50,7 +39,7 @@ JMS 有两种消息模型: 如果你希望发送的消息可以不被做任何处理、或者被一个消息者处理、或者可以被多个消费者处理的话,那么可以采用 Pub/Sub 模型。 -### 1.2. JMS 编程模型 +### JMS 编程模型 ![img](http://dunwu.test.upcdn.net/cs/java/javalib/jms/jms-publishSubscribe.gif!zp) @@ -97,7 +86,7 @@ JMS 有两种消息模型: | MessageProducer | QueueSender | TopicPublisher | | MessageSender | QueueReceiver, QueueBrowser | TopicSubscriber | -## 2. 安装 +## 安装 **安装条件** @@ -144,7 +133,7 @@ netstat -an|find “61616” 5. 添加一个队列(queue) -## 3. 项目中的应用 +## 项目中的应用 **引入依赖** @@ -296,7 +285,7 @@ Receiver 的输出内容 收到消息ActiveMQ 发送消息3 ``` -## 4. 资源 +## 资源 - [ActiveMQ 官网](http://activemq.apache.org/) - [oracle 官方的 jms 介绍](https://docs.oracle.com/cd/E19575-01/819-3669/6n5sg7cgq/index.html) diff --git a/docs/mq/kafka/README.md b/docs/mq/kafka/README.md new file mode 100644 index 00000000..8732d0d6 --- /dev/null +++ b/docs/mq/kafka/README.md @@ -0,0 +1,5 @@ +# Kafka 教程 + +- [Kafka 应用指南基础篇](kafka-basic.md) +- [Kafka 应用指南进阶篇](kafka-advance.md) +- [Kafka 运维指南](kafka-ops.md) diff --git a/docs/ecology/mq/kafka-advance.md b/docs/mq/kafka/kafka-advance.md similarity index 100% rename from docs/ecology/mq/kafka-advance.md rename to docs/mq/kafka/kafka-advance.md diff --git a/docs/ecology/mq/kafka-basic.md b/docs/mq/kafka/kafka-basic.md similarity index 100% rename from docs/ecology/mq/kafka-basic.md rename to docs/mq/kafka/kafka-basic.md diff --git a/docs/ecology/mq/kafka-ops.md b/docs/mq/kafka/kafka-ops.md similarity index 100% rename from docs/ecology/mq/kafka-ops.md rename to docs/mq/kafka/kafka-ops.md diff --git a/docs/ecology/mq/MqInterview.md b/docs/mq/mq-interview.md similarity index 100% rename from docs/ecology/mq/MqInterview.md rename to docs/mq/mq-interview.md diff --git a/docs/ecology/mq/RocketmqBasics.md b/docs/mq/rocketmq.md similarity index 52% rename from docs/ecology/mq/RocketmqBasics.md rename to docs/mq/rocketmq.md index 369310ac..64a2d311 100644 --- a/docs/ecology/mq/RocketmqBasics.md +++ b/docs/mq/rocketmq.md @@ -1,15 +1,11 @@ -# RocketMQ 基础篇 +# RocketMQ -## 概述 - -### 简介 +## 简介 RocketMQ 是一款开源的分布式消息队列,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务。 RocketMQ 被阿里巴巴捐赠给 Apache,成为 Apache 的孵化项目。 -### 核心概念 -
    @@ -297,6 +293,193 @@ brokerIP1 = 10.10.30.63 nohup sh bin/mqbroker -n localhost:9876 -c conf/broker.conf & ``` -## 资料 +## 架构 + +
    + +
    更详细信息可以参考官方文档:[here](http://rocketmq.apache.org/rocketmq/four-methods-to-feed-name-server-address-list/) + +### Broker + +Broker 通过提供轻量级的 TOPIC 和 QUEUE 机制来处理消息存储。它们支持 Push 和 Pull 模型,包含容错机制(2 个副本或 3 个副本),并提供强大的峰值填充和以原始时间顺序累积数千亿条消息的能力。此外,Brokers 还提供灾难恢复,丰富的指标统计和警报机制。 + +Broker 有几个重要的子模块: + +- Remoting Module - 即代理的条目,处理来自客户端的请求。 +- Client Manager - 管理客户(生产者/消费者)并维护消费者的主题订阅。 +- Store 服务 - 提供简单的 API 来存储或查询物理磁盘中的消息。 +- HA 服务 - 提供主代理和从代理之间的数据同步功能。 +- Index 服务 - 按指定密钥构建消息索引,并提供快速消息查询。 + +
    + +
    + +### Producer + +Producers 支持分布式部署。Distributed Producers 通过多种负载均衡模式向 Broker 集群发送消息。发送过程支持快速故障并具有低延迟。 + +### Consumer + +Consumer 也支持 Push 和 Pull 模型中的分布式部署。它还支持群集消费和消息广播。它提供实时消息订阅机制,可以满足大多数消费者的需求。 RocketMQ 的网站为感兴趣的用户提供了一个简单的快速入门指南。 + +## 原理 + +分布式消息系统作为实现分布式系统可扩展、可伸缩性的关键组件,需要具有高吞吐量、高可用等特点。而谈到消息系统的设计,就回避不了两个问题: + +1. 消息的顺序问题 +2. 消息的重复问题 + +### 顺序消息 + +#### 第一种模型 + +假如生产者产生了 2 条消息:M1、M2,要保证这两条消息的顺序,应该怎样做?你脑中想到的可能是这样: + +
    + +
    + +假定 M1 发送到 S1,M2 发送到 S2,如果要保证 M1 先于 M2 被消费,那么需要 M1 到达消费端被消费后,通知 S2,然后 S2 再将 M2 发送到消费端。 + +这个模型存在的问题是,如果 M1 和 M2 分别发送到两台 Server 上,就不能保证 M1 先达到 MQ 集群,也不能保证 M1 被先消费。换个角度看,如果 M2 先于 M1 达到 MQ 集群,甚至 M2 被消费后,M1 才达到消费端,这时消息也就乱序了,说明以上模型是不能保证消息的顺序的。 + +
    + +
    + +#### 第二种模型 + +如何才能在 MQ 集群保证消息的顺序?一种简单的方式就是将 M1、M2 发送到同一个 Server 上: + +这样可以保证 M1 先于 M2 到达 MQServer(生产者等待 M1 发送成功后再发送 M2),根据先达到先被消费的原则,M1 会先于 M2 被消费,这样就保证了消息的顺序。 + +这个模型也仅仅是理论上可以保证消息的顺序,在实际场景中可能会遇到下面的问题: + +
    + +
    + +只要将消息从一台服务器发往另一台服务器,就会存在网络延迟问题。如上图所示,如果发送 M1 耗时大于发送 M2 的耗时,那么 M2 就仍将被先消费,仍然不能保证消息的顺序。即使 M1 和 M2 同时到达消费端,由于不清楚消费端 1 和消费端 2 的负载情况,仍然有可能出现 M2 先于 M1 被消费的情况。 + +如何解决这个问题?将 M1 和 M2 发往同一个消费者,且发送 M1 后,需要消费端响应成功后才能发送 M2。 + +这可能产生另外的问题:如果 M1 被发送到消费端后,消费端 1 没有响应,那是继续发送 M2 呢,还是重新发送 M1?一般为了保证消息一定被消费,肯定会选择重发 M1 到另外一个消费端 2,就如下图所示。 + +
    + +
    + +这样的模型就严格保证消息的顺序,细心的你仍然会发现问题,消费端 1 没有响应 Server 时有两种情况,一种是 M1 确实没有到达(数据在网络传送中丢失),另外一种消费端已经消费 M1 且已经发送响应消息,只是 MQ Server 端没有收到。如果是第二种情况,重发 M1,就会造成 M1 被重复消费。也就引入了我们要说的第二个问题,消息重复问题,这个后文会详细讲解。 + +回过头来看消息顺序问题,严格的顺序消息非常容易理解,也可以通过文中所描述的方式来简单处理。总结起来,要实现严格的顺序消息,简单且可行的办法就是: + +**保证生产者 - MQServer - 消费者是一对一对一的关系。** + +这样的设计虽然简单易行,但也会存在一些很严重的问题,比如: + +1. 并行度就会成为消息系统的瓶颈(吞吐量不够) +2. 更多的异常处理,比如:只要消费端出现问题,就会导致整个处理流程阻塞,我们不得不花费更多的精力来解决阻塞的问题。 + +RocketMQ 的解决方案:通过合理的设计或者将问题分解来规避。如果硬要把时间花在解决问题本身,实际上不仅效率低下,而且也是一种浪费。从这个角度来看消息的顺序问题,我们可以得出两个结论: + +1. 不关注乱序的应用实际大量存在 +2. 队列无序并不意味着消息无序 + +最后我们从源码角度分析 RocketMQ 怎么实现发送顺序消息。 + +RocketMQ 通过轮询所有队列的方式来确定消息被发送到哪一个队列(负载均衡策略)。比如下面的示例中,订单号相同的消息会被先后发送到同一个队列中: + +```java +// RocketMQ 通过 MessageQueueSelector 中实现的算法来确定消息发送到哪一个队列上 +// RocketMQ 默认提供了两种 MessageQueueSelector 实现:随机/Hash +// 当然你可以根据业务实现自己的 MessageQueueSelector 来决定消息按照何种策略发送到消息队列中 +SendResult sendResult = producer.send(msg, new MessageQueueSelector() { + @Override + public MessageQueue select(List mqs, Message msg, Object arg) { + Integer id = (Integer) arg; + int index = id % mqs.size(); + return mqs.get(index); + } +}, orderId); +``` + +在获取到路由信息以后,会根据 MessageQueueSelector 实现的算法来选择一个队列,同一个 OrderId 获取到的肯定是同一个队列。 + +```java +private SendResult send() { + // 获取topic路由信息 + TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic()); + if (topicPublishInfo != null && topicPublishInfo.ok()) { + MessageQueue mq = null; + // 根据我们的算法,选择一个发送队列 + // 这里的arg = orderId + mq = selector.select(topicPublishInfo.getMessageQueueList(), msg, arg); + if (mq != null) { + return this.sendKernelImpl(msg, mq, communicationMode, sendCallback, timeout); + } + } +} +``` + +### 消息重复 + +造成消息重复的根本原因是:网络不可达。只要通过网络交换数据,就无法避免这个问题。所以解决这个问题的办法就是绕过这个问题。那么问题就变成了:如果消费端收到两条一样的消息,应该怎样处理? + +1. 消费端处理消息的业务逻辑保持幂等性。 +2. 保证每条消息都有唯一编号且保证消息处理成功与去重表的日志同时出现。 + +第 1 条很好理解,只要保持幂等性,不管来多少条重复消息,最后处理的结果都一样。 + +第 2 条原理就是利用一张日志表来记录已经处理成功的消息的 ID,如果新到的消息 ID 已经在日志表中,那么就不再处理这条消息。 + +第 1 条解决方案,很明显应该在消费端实现,不属于消息系统要实现的功能。 + +第 2 条可以消息系统实现,也可以业务端实现。正常情况下出现重复消息的概率其实很小,如果由消息系统来实现的话,肯定会对消息系统的吞吐量和高可用有影响,所以最好还是由业务端自己处理消息重复的问题,这也是 RocketMQ 不解决消息重复的问题的原因。 + +**RocketMQ 不保证消息不重复,如果你的业务需要保证严格的不重复消息,需要你自己在业务端去重。** + +### 事务消息 + +RocketMQ 除了支持普通消息,顺序消息,另外还支持事务消息。 + +假设这样的场景: + +![img](https://upload-images.jianshu.io/upload_images/3101171-253d8bd65736694f.png) + +图中执行本地事务(Bob 账户扣款)和发送异步消息应该保证同时成功或者同时失败,也就是扣款成功了,发送消息一定要成功,如果扣款失败了,就不能再发送消息。那问题是:我们是先扣款还是先发送消息呢? + +![img](http://upload-images.jianshu.io/upload_images/3101171-088dc074c4ecd192) + +RocketMQ 分布式事务步骤: + +发送 Prepared 消息 2222222222222222222,并拿到接受消息的地址。 +执行本地事务 +通过第 1 步骤拿到的地址去访问消息,并修改消息状态。 + +## 参考资料 - [RocketMQ 官方文档](http://rocketmq.apache.org/docs/quick-start/) +- [分布式开放消息系统(RocketMQ)的原理与实践](https://www.jianshu.com/p/453c6e7ff81c) diff --git a/docs/ecology/other/README.md b/docs/other/README.md similarity index 100% rename from docs/ecology/other/README.md rename to docs/other/README.md diff --git a/docs/ecology/other/freemark.md b/docs/other/freemark.md similarity index 100% rename from docs/ecology/other/freemark.md rename to docs/other/freemark.md diff --git a/docs/ecology/other/javalib-util.md b/docs/other/javalib-util.md similarity index 100% rename from docs/ecology/other/javalib-util.md rename to docs/other/javalib-util.md diff --git a/docs/ecology/other/javamail.md b/docs/other/javamail.md similarity index 100% rename from docs/ecology/other/javamail.md rename to docs/other/javamail.md diff --git a/docs/ecology/other/jsoup.md b/docs/other/jsoup.md similarity index 100% rename from docs/ecology/other/jsoup.md rename to docs/other/jsoup.md diff --git a/docs/other/lombok.md b/docs/other/lombok.md new file mode 100644 index 00000000..3b9709fa --- /dev/null +++ b/docs/other/lombok.md @@ -0,0 +1,373 @@ +# Lombok 应用指南 + + + +- [1. Lombok 简介](#1-lombok-简介) +- [2. Lombok 安装](#2-lombok-安装) +- [3. Lombok 使用](#3-lombok-使用) + - [3.1. @Getter and @Setter](#31-getter-and-setter) + - [3.2. @NonNull](#32-nonnull) + - [3.3. @ToString](#33-tostring) + - [3.4. @EqualsAndHashCode](#34-equalsandhashcode) + - [3.5. @Data](#35-data) + - [3.6. @Cleanup](#36-cleanup) + - [3.7. @Synchronized](#37-synchronized) + - [3.8. @SneakyThrows](#38-sneakythrows) + - [3.9. 示例源码](#39-示例源码) +- [4. 参考资料](#4-参考资料) + + + +## 1. Lombok 简介 + +Lombok 是一种 Java 实用工具,可用来帮助开发人员消除 Java 的冗长,尤其是对于简单的 Java 对象(POJO)。它通过注释实现这一目的。通过在开发环境中实现 Lombok,开发人员可以节省构建诸如 `hashCode()` 和 `equals()` 、`getter / setter` 这样的方法以及以往用来分类各种 accessor 和 mutator 的大量时间。 + +## 2. Lombok 安装 + +使 IntelliJ IDEA 支持 Lombok 方式如下: + +- **Intellij 设置支持注解处理** + - 点击 File > Settings > Build > Annotation Processors + - 勾选 Enable annotation processing +- **安装插件** + - 点击 Settings > Plugins > Browse repositories + - 查找 Lombok Plugin 并进行安装 + - 重启 IntelliJ IDEA +- **将 lombok 添加到 pom 文件** + +```xml + + org.projectlombok + lombok + 1.16.8 + +``` + +## 3. Lombok 使用 + +Lombok 提供注解 API 来修饰指定的类: + +### 3.1. @Getter and @Setter + +[@Getter and @Setter](http://jnb.ociweb.com/jnb/jnbJan2010.html#gettersetter) Lombok 代码: + +```java +@Getter @Setter private boolean employed = true; +@Setter(AccessLevel.PROTECTED) private String name; +``` + +等价于 Java 源码: + +```java +private boolean employed = true; +private String name; + +public boolean isEmployed() { + return employed; +} + +public void setEmployed(final boolean employed) { + this.employed = employed; +} + +protected void setName(final String name) { + this.name = name; +} +``` + +### 3.2. @NonNull + +[@NonNull](http://jnb.ociweb.com/jnb/jnbJan2010.html#nonnull) Lombok 代码: + +```java +@Getter @Setter @NonNull +private List members; +``` + +等价于 Java 源码: + +```java +@NonNull +private List members; + +public Family(@NonNull final List members) { + if (members == null) throw new java.lang.NullPointerException("members"); + this.members = members; +} + +@NonNull +public List getMembers() { + return members; +} + +public void setMembers(@NonNull final List members) { + if (members == null) throw new java.lang.NullPointerException("members"); + this.members = members; +} +``` + +### 3.3. @ToString + +[@ToString](http://jnb.ociweb.com/jnb/jnbJan2010.html#tostring) Lombok 代码: + +```java +@ToString(callSuper=true,exclude="someExcludedField") +public class Foo extends Bar { + private boolean someBoolean = true; + private String someStringField; + private float someExcludedField; +} +``` + +等价于 Java 源码: + +```java +public class Foo extends Bar { + private boolean someBoolean = true; + private String someStringField; + private float someExcludedField; + + @java.lang.Override + public java.lang.String toString() { + return "Foo(super=" + super.toString() + + ", someBoolean=" + someBoolean + + ", someStringField=" + someStringField + ")"; + } +} +``` + +### 3.4. @EqualsAndHashCode + +[@EqualsAndHashCode](http://jnb.ociweb.com/jnb/jnbJan2010.html#equals) Lombok 代码: + +```java +@EqualsAndHashCode(callSuper=true,exclude={"address","city","state","zip"}) +public class Person extends SentientBeing { + enum Gender { Male, Female } + + @NonNull private String name; + @NonNull private Gender gender; + + private String ssn; + private String address; + private String city; + private String state; + private String zip; +} +``` + +等价于 Java 源码: + +```java +public class Person extends SentientBeing { + + enum Gender { + /*public static final*/ Male /* = new Gender() */, + /*public static final*/ Female /* = new Gender() */; + } + @NonNull + private String name; + @NonNull + private Gender gender; + private String ssn; + private String address; + private String city; + private String state; + private String zip; + + @java.lang.Override + public boolean equals(final java.lang.Object o) { + if (o == this) return true; + if (o == null) return false; + if (o.getClass() != this.getClass()) return false; + if (!super.equals(o)) return false; + final Person other = (Person)o; + if (this.name == null ? other.name != null : !this.name.equals(other.name)) return false; + if (this.gender == null ? other.gender != null : !this.gender.equals(other.gender)) return false; + if (this.ssn == null ? other.ssn != null : !this.ssn.equals(other.ssn)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = result * PRIME + super.hashCode(); + result = result * PRIME + (this.name == null ? 0 : this.name.hashCode()); + result = result * PRIME + (this.gender == null ? 0 : this.gender.hashCode()); + result = result * PRIME + (this.ssn == null ? 0 : this.ssn.hashCode()); + return result; + } +} +``` + +### 3.5. @Data + +[@Data](http://jnb.ociweb.com/jnb/jnbJan2010.html#data) Lombok 代码: + +```java +@Data(staticConstructor="of") +public class Company { + private final Person founder; + private String name; + private List employees; +} +``` + +等价于 Java 源码: + +```java +public class Company { + private final Person founder; + private String name; + private List employees; + + private Company(final Person founder) { + this.founder = founder; + } + + public static Company of(final Person founder) { + return new Company(founder); + } + + public Person getFounder() { + return founder; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public List getEmployees() { + return employees; + } + + public void setEmployees(final List employees) { + this.employees = employees; + } + + @java.lang.Override + public boolean equals(final java.lang.Object o) { + if (o == this) return true; + if (o == null) return false; + if (o.getClass() != this.getClass()) return false; + final Company other = (Company)o; + if (this.founder == null ? other.founder != null : !this.founder.equals(other.founder)) return false; + if (this.name == null ? other.name != null : !this.name.equals(other.name)) return false; + if (this.employees == null ? other.employees != null : !this.employees.equals(other.employees)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = result * PRIME + (this.founder == null ? 0 : this.founder.hashCode()); + result = result * PRIME + (this.name == null ? 0 : this.name.hashCode()); + result = result * PRIME + (this.employees == null ? 0 : this.employees.hashCode()); + return result; + } + + @java.lang.Override + public java.lang.String toString() { + return "Company(founder=" + founder + ", name=" + name + ", employees=" + employees + ")"; + } +} +``` + +### 3.6. @Cleanup + +[@Cleanup](http://jnb.ociweb.com/jnb/jnbJan2010.html#cleanup) Lombok 代码: + +```java +public void testCleanUp() { + try { + @Cleanup ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(new byte[] {'Y','e','s'}); + System.out.println(baos.toString()); + } catch (IOException e) { + e.printStackTrace(); + } +} +``` + +等价于 Java 源码: + +```java +public void testCleanUp() { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + baos.write(new byte[]{'Y', 'e', 's'}); + System.out.println(baos.toString()); + } finally { + baos.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } +} +``` + +### 3.7. @Synchronized + +[@Synchronized](http://jnb.ociweb.com/jnb/jnbJan2010.html#synchronized) Lombok 代码: + +```java +private DateFormat format = new SimpleDateFormat("MM-dd-YYYY"); + +@Synchronized +public String synchronizedFormat(Date date) { + return format.format(date); +} +``` + +等价于 Java 源码: + +```java +private final java.lang.Object $lock = new java.lang.Object[0]; +private DateFormat format = new SimpleDateFormat("MM-dd-YYYY"); + +public String synchronizedFormat(Date date) { + synchronized ($lock) { + return format.format(date); + } +} +``` + +### 3.8. @SneakyThrows + +[@SneakyThrows](http://jnb.ociweb.com/jnb/jnbJan2010.html#sneaky) Lombok 代码: + +```java +@SneakyThrows +public void testSneakyThrows() { + throw new IllegalAccessException(); +} +``` + +等价于 Java 源码: + +```java +public void testSneakyThrows() { + try { + throw new IllegalAccessException(); + } catch (java.lang.Throwable $ex) { + throw lombok.Lombok.sneakyThrow($ex); + } +} +``` + +### 3.9. 示例源码 + +> 示例源码:[javalib-bean](https://github.com/dunwu/java-tutorial/tree/master/javalib-bean) + +## 4. 参考资料 + +- [Lombok 官网](https://projectlombok.org/) +- [Lombok Github](https://github.com/rzwitserloot/lombok) +- [IntelliJ IDEA - Lombok Plugin](http://plugins.jetbrains.com/plugin/6317-lombok-plugin) diff --git a/docs/ecology/other/reflections.md b/docs/other/reflections.md similarity index 100% rename from docs/ecology/other/reflections.md rename to docs/other/reflections.md diff --git a/docs/ecology/other/thumbnailator.md b/docs/other/thumbnailator.md similarity index 100% rename from docs/ecology/other/thumbnailator.md rename to docs/other/thumbnailator.md diff --git a/docs/ecology/other/zxing.md b/docs/other/zxing.md similarity index 100% rename from docs/ecology/other/zxing.md rename to docs/other/zxing.md diff --git a/docs/ecology/search/README.md b/docs/search/README.md similarity index 86% rename from docs/ecology/search/README.md rename to docs/search/README.md index 1cbcdf08..69c02c12 100644 --- a/docs/ecology/search/README.md +++ b/docs/search/README.md @@ -1,13 +1,18 @@ # Java 和搜索引擎 +## ElasticSearch + +> [ElasticSearch](https://www.elastic.co/products/elasticsearch) 是一个基于 [Lucene](http://lucene.apache.org/core/documentation.html) 构建的开源,分布式,RESTful 搜索引擎。 + +- [ElasticSearch 入门指南](elasticsearch/elasticsearch.md) +- [ElasticSearch 运维](elasticsearch/elasticsearch-ops.md) + ## Elastic 技术栈 -> **Elastic 技术栈通常被用来作为日志中心。** +> **Elastic 技术栈通常被用来作为日志采集、检索、可视化解决方案。** > > ELK 是 elastic 公司旗下三款产品 [ElasticSearch](https://www.elastic.co/products/elasticsearch) 、[Logstash](https://www.elastic.co/products/logstash) 、[Kibana](https://www.elastic.co/products/kibana) 的首字母组合。 > -> [ElasticSearch](https://www.elastic.co/products/elasticsearch) 是一个基于 [Lucene](http://lucene.apache.org/core/documentation.html) 构建的开源,分布式,RESTful 搜索引擎。 -> > [Logstash](https://www.elastic.co/products/logstash) 传输和处理你的日志、事务或其他数据。 > > [Kibana](https://www.elastic.co/products/kibana) 将 Elasticsearch 的数据分析并渲染为可视化的报表。 @@ -15,16 +20,14 @@ > Elastic 技术栈,在 ELK 的基础上扩展了一些新的产品,如:[Beats](https://www.elastic.co/products/beats) 、[X-Pack](https://www.elastic.co/products/x-pack) 。 - [Elastic 技术栈快速入门](elastic-quickstart.md) -- [ElasticSearch](elastic-elasticsearch.md) -- [Beats](elastic-beats.md) -- [Kibana](elastic-kibana.md) -- [Logstash](elastic-logstash.md) -- [ElasticSearch 运维](elastic-elasticsearch-ops.md) +- [Beats 入门指南](elastic-beats.md) - [Beats 运维](elastic-beats-ops.md) +- [Kibana 入门指南](elastic-kibana.md) - [Kibana 运维](elastic-kibana-ops.md) +- [Logstash 入门指南](elastic-logstash.md) - [Logstash 运维](elastic-logstash-ops.md) -### 学习资源 +## 学习资源 - **官方资源** - [Elasticsearch 官网](https://www.elastic.co/cn/products/elasticsearch) diff --git a/docs/ecology/search/elastic-beats-ops.md b/docs/search/elastic-beats-ops.md similarity index 100% rename from docs/ecology/search/elastic-beats-ops.md rename to docs/search/elastic-beats-ops.md diff --git a/docs/ecology/search/elastic-beats.md b/docs/search/elastic-beats.md similarity index 100% rename from docs/ecology/search/elastic-beats.md rename to docs/search/elastic-beats.md diff --git a/docs/ecology/search/elastic-kibana-ops.md b/docs/search/elastic-kibana-ops.md similarity index 100% rename from docs/ecology/search/elastic-kibana-ops.md rename to docs/search/elastic-kibana-ops.md diff --git a/docs/ecology/search/elastic-kibana.md b/docs/search/elastic-kibana.md similarity index 100% rename from docs/ecology/search/elastic-kibana.md rename to docs/search/elastic-kibana.md diff --git a/docs/ecology/search/elastic-logstash-ops.md b/docs/search/elastic-logstash-ops.md similarity index 100% rename from docs/ecology/search/elastic-logstash-ops.md rename to docs/search/elastic-logstash-ops.md diff --git a/docs/ecology/search/elastic-logstash.md b/docs/search/elastic-logstash.md similarity index 100% rename from docs/ecology/search/elastic-logstash.md rename to docs/search/elastic-logstash.md diff --git a/docs/ecology/search/elastic-quickstart.md b/docs/search/elastic-quickstart.md similarity index 99% rename from docs/ecology/search/elastic-quickstart.md rename to docs/search/elastic-quickstart.md index ca228ed2..aa01a1d7 100644 --- a/docs/ecology/search/elastic-quickstart.md +++ b/docs/search/elastic-quickstart.md @@ -250,7 +250,7 @@ Filebeat 将每个事件的传递状态存储在注册表文件中。所以它 ## 5. 运维 -- [ElasticSearch 运维](elastic-elasticsearch-ops.md) +- [ElasticSearch 运维](elasticsearch/elasticsearch-ops.md) - [Logstash 运维](elastic-logstash-ops.md) - [Kibana 运维](elastic-kibana-ops.md) - [Beats 运维](elastic-beats-ops.md) diff --git a/docs/search/elasticsearch/README.md b/docs/search/elasticsearch/README.md new file mode 100644 index 00000000..62f8a90b --- /dev/null +++ b/docs/search/elasticsearch/README.md @@ -0,0 +1,15 @@ +# ElasticSearch + +> [ElasticSearch](https://www.elastic.co/products/elasticsearch) 是一个基于 [Lucene](http://lucene.apache.org/core/documentation.html) 构建的开源,分布式,RESTful 搜索引擎。 + +- [ElasticSearch 入门指南](elasticsearch.md) +- [ElasticSearch 运维](elasticsearch-ops.md) + +## 学习资源 + +- **官方资源** + - [Elasticsearch 官网](https://www.elastic.co/cn/products/elasticsearch) + - [Elasticsearch Github](https://github.com/elastic/elasticsearch) + - [Elasticsearch 官方文档](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html) +- **教程** + - [Elasticsearch 权威指南(中文版)](https://es.xiaoleilu.com/index.html) diff --git a/docs/ecology/search/elastic-elasticsearch-ops.md b/docs/search/elasticsearch/elasticsearch-ops.md similarity index 100% rename from docs/ecology/search/elastic-elasticsearch-ops.md rename to docs/search/elasticsearch/elasticsearch-ops.md diff --git a/docs/ecology/search/elastic-elasticsearch.md b/docs/search/elasticsearch/elasticsearch.md similarity index 99% rename from docs/ecology/search/elastic-elasticsearch.md rename to docs/search/elasticsearch/elasticsearch.md index a0c648ce..c8dc39be 100644 --- a/docs/ecology/search/elastic-elasticsearch.md +++ b/docs/search/elasticsearch/elasticsearch.md @@ -521,7 +521,7 @@ buffer 每 refresh 一次,就会产生一个 `segment file`,所以默认情 ## Elastic 技术栈系列 -- [ElasticSearch 运维](elastic-elasticsearch-ops.md) - ElasticSearch 安装、配置、命令详解。 +- [ElasticSearch 运维](elasticsearch-ops.md) - ElasticSearch 安装、配置、命令详解。 ## 参考资料 diff --git a/docs/ecology/security/README.md b/docs/security/README.md similarity index 100% rename from docs/ecology/security/README.md rename to docs/security/README.md diff --git a/docs/ecology/security/shiro.md b/docs/security/shiro.md similarity index 100% rename from docs/ecology/security/shiro.md rename to docs/security/shiro.md diff --git a/docs/ecology/security/spring-security.md b/docs/security/spring-security.md similarity index 100% rename from docs/ecology/security/spring-security.md rename to docs/security/spring-security.md diff --git a/docs/ecology/server/README.md b/docs/server/README.md similarity index 100% rename from docs/ecology/server/README.md rename to docs/server/README.md diff --git a/docs/ecology/server/jetty.md b/docs/server/jetty.md similarity index 100% rename from docs/ecology/server/jetty.md rename to docs/server/jetty.md diff --git a/docs/ecology/server/tomcat.md b/docs/server/tomcat.md similarity index 100% rename from docs/ecology/server/tomcat.md rename to docs/server/tomcat.md diff --git a/docs/soa/README.md b/docs/soa/README.md new file mode 100644 index 00000000..232be5fa --- /dev/null +++ b/docs/soa/README.md @@ -0,0 +1,13 @@ +# Java 和 RPC + +## RPC + +- [Dubbo](dubbo.md) +- Spring Cloud + +## 注册中心 + +### [ZooKeeper](zookeeper) + +- [ZooKeeper 应用指南](zookeeper/zookeeper.md) +- [ZooKeeper 运维指南](zookeeper/zookeeper-ops.md) \ No newline at end of file diff --git a/docs/ecology/microservices/dubbo.md b/docs/soa/dubbo.md similarity index 100% rename from docs/ecology/microservices/dubbo.md rename to docs/soa/dubbo.md diff --git a/docs/soa/zookeeper/README.md b/docs/soa/zookeeper/README.md new file mode 100644 index 00000000..7c5d7a1a --- /dev/null +++ b/docs/soa/zookeeper/README.md @@ -0,0 +1,4 @@ +# ZooKeeper + +- [ZooKeeper 应用指南](zookeeper.md) +- [ZooKeeper 运维指南](zookeeper-ops.md) diff --git a/docs/ecology/microservices/zookeeper-ops.md b/docs/soa/zookeeper/zookeeper-ops.md similarity index 100% rename from docs/ecology/microservices/zookeeper-ops.md rename to docs/soa/zookeeper/zookeeper-ops.md diff --git a/docs/ecology/microservices/zookeeper.md b/docs/soa/zookeeper/zookeeper.md similarity index 100% rename from docs/ecology/microservices/zookeeper.md rename to docs/soa/zookeeper/zookeeper.md diff --git a/docs/ecology/storage/README.md b/docs/storage/README.md similarity index 100% rename from docs/ecology/storage/README.md rename to docs/storage/README.md diff --git a/docs/ecology/storage/shardingsphere.md b/docs/storage/shardingsphere.md similarity index 100% rename from docs/ecology/storage/shardingsphere.md rename to docs/storage/shardingsphere.md diff --git a/docs/ecology/test/README.md b/docs/test/README.md similarity index 100% rename from docs/ecology/test/README.md rename to docs/test/README.md diff --git a/docs/ecology/test/jmh.md b/docs/test/jmh.md similarity index 97% rename from docs/ecology/test/jmh.md rename to docs/test/jmh.md index 36b23beb..3dfd60a8 100644 --- a/docs/ecology/test/jmh.md +++ b/docs/test/jmh.md @@ -144,7 +144,7 @@ $ java -jar target/benchmarks.jar # Timeout: 10 min per iteration # Threads: 8 threads, will synchronize iterations # Benchmark mode: Throughput, ops/time -# Benchmark: io.github.dunwu.javalib.jmh.StringBuilderBenchmark.testStringAdd +# Benchmark: io.github.dunwu.javatech.jmh.StringBuilderBenchmark.testStringAdd # Run progress: 0.00% complete, ETA 00:05:20 # Fork: 1 of 2 @@ -179,7 +179,7 @@ Iteration 9: 22493.844 ops/ms Iteration 10: 22568.078 ops/ms -Result "io.github.dunwu.javalib.jmh.StringBuilderBenchmark.testStringAdd": +Result "io.github.dunwu.javatech.jmh.StringBuilderBenchmark.testStringAdd": 21996.435 ±(99.9%) 412.955 ops/ms [Average] (min, avg, max) = (20863.282, 21996.435, 22568.078), stdev = 475.560 CI (99.9%): [21583.480, 22409.390] (assumes normal distribution) @@ -194,7 +194,7 @@ Result "io.github.dunwu.javalib.jmh.StringBuilderBenchmark.testStringAdd": # Timeout: 10 min per iteration # Threads: 8 threads, will synchronize iterations # Benchmark mode: Throughput, ops/time -# Benchmark: io.github.dunwu.javalib.jmh.StringBuilderBenchmark.testStringBuilderAdd +# Benchmark: io.github.dunwu.javatech.jmh.StringBuilderBenchmark.testStringBuilderAdd # Run progress: 50.00% complete, ETA 00:02:41 # Fork: 1 of 2 @@ -229,7 +229,7 @@ Iteration 9: 87994.726 ops/ms Iteration 10: 87784.453 ops/ms -Result "io.github.dunwu.javalib.jmh.StringBuilderBenchmark.testStringBuilderAdd": +Result "io.github.dunwu.javatech.jmh.StringBuilderBenchmark.testStringBuilderAdd": 84487.902 ±(99.9%) 4355.525 ops/ms [Average] (min, avg, max) = (72316.121, 84487.902, 89388.624), stdev = 5015.829 CI (99.9%): [80132.377, 88843.427] (assumes normal distribution) diff --git a/docs/ecology/test/junit.md b/docs/test/junit.md similarity index 100% rename from docs/ecology/test/junit.md rename to docs/test/junit.md diff --git a/docs/ecology/test/mockito.md b/docs/test/mockito.md similarity index 100% rename from docs/ecology/test/mockito.md rename to docs/test/mockito.md diff --git a/docs/tool/README.md b/docs/tool/README.md deleted file mode 100644 index e52d37bf..00000000 --- a/docs/tool/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Java 工具 - -> 本部分内容主要是 Java 开发领域使用的一些 Java 工具,如构建工具、IDE、服务器、日志中心等等。 - -## 构建 - -> [构建工具](build/README.md) - -- [Maven 快速指南](build/maven/maven-quickstart.md) -- [Maven 教程之 pom.xml 详解](build/maven/maven-pom.md) -- [Maven 教程之 settings.xml 详解](build/maven/maven-settings.md) -- [Maven 实战问题和最佳实践](build/maven/maven-action.md) -- [Maven 教程之发布 jar 到私服或中央仓库](build/maven/maven-deploy.md) -- [Ant](build/ant.md) - -## IDE - -> [Java IDE](ide/README.md) - -- [IDEA](ide/intellij-idea.md) -- [Eclipse](ide/eclipse.md) diff --git a/docs/tool/ide/README.md b/docs/tool/ide/README.md deleted file mode 100644 index bf58c9a8..00000000 --- a/docs/tool/ide/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# Java IDE - - -> 自动有了 **IDE**,写代码从此就告别了刀耕火种的蛮荒时代。 -> -> - [Eclipse](eclipse.md) 是久负盛名的开源 Java IDE,我的学生时代一直使用它写 Java。 -> - 曾经抗拒从转 [Intellij Idea](intellij-idea.md) ,但后来发现真香,不得不说,确实是目前最优秀的 Java IDE。 -> - 你可以在 [vscode](vscode.md) 中写各种语言,只要安装相应插件即可。如果你的项目中使用了很多种编程语言,又懒得在多个 IDE 之间切换,那么就用 vscode 来一网打尽吧。 - -- [Intellij IDEA 应用指南](intellij-idea.md) -- [Eclipse 应用指南](eclipse.md) -- [Vscode 应用指南](vscode.md) diff --git a/docs/tool/ide/eclipse.md b/docs/tool/ide/eclipse.md deleted file mode 100644 index 15a6ca98..00000000 --- a/docs/tool/ide/eclipse.md +++ /dev/null @@ -1,233 +0,0 @@ -# Eclipse 应用指南 - -## 代码智能提示 - -### Java 智能提示 - -Window -> Preferences -> Java -> Editor -> Content Assist -> Auto Activation - -![img](http://upload-images.jianshu.io/upload_images/3101171-1a0d2206870c6542.jpg) - -delay 是自动弹出提示框的延时时间,我们可以修改成 100 毫秒;triggers 这里默认是".",只要加上"abcdefghijklmnopqrstuvwxyz"或者"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",嘿嘿!这下就能做到和 VS 一样的输入每个字母都能提示啦: - -![img](http://upload-images.jianshu.io/upload_images/3101171-5eb9ad6afbe05289.jpg) - -其它类型的文件比如 HTML、JavaScript、JSP 如果也能提供提示那不是更爽了?有了第二点设置的基础,其实这些设置都是一样的。 - -### JavaScript 智能提示 - -Window -> Preferences -> JavaScript-> Editor -> Content Assist -> Auto-Activation - -![img](http://upload-images.jianshu.io/upload_images/3101171-31bca3ed1b0d0050.jpg) - -### HTML 智能提示 - -Window -> Preferences -> Web -> HTML Files -> Editor -> Content Assist -> Auto-Activation - -![img](http://upload-images.jianshu.io/upload_images/3101171-7c1ce7a35793f234.jpg) - -保存后,我们再来输入看看,感觉真是不错呀: - -![img](http://images2015.cnblogs.com/blog/318837/201605/318837-20160519135330638-166066199.jpg) - -## 插件安装 - -很多教科书上说到 Eclipse 的插件安装都是通过 Help -> Install New SoftWare 这种自动检索的方式,操作起来固然是方便,不过当我们不需要某种插件时不太容易找到要删除哪些内容,而且以后 Eclipse 版本升级的时候,通过这种方式安装过的插件都得再重新装一次。另外一种通过 Link 链接方式,就可以解决这些问题。 - -我们以 Eclipse 的中文汉化包插件为例,先到官方提供的汉化包地址下载一个:[http://www.eclipse.org/babel/downloads.php](http://www.eclipse.org/babel/downloads.php),注意选好自己的 Eclipse 版本: - -![img](http://upload-images.jianshu.io/upload_images/3101171-d8a662eaba3550e3.jpg) - -我的版本是 Kepler,然后进入下载页面,单击红框框中的链接,即可下载汉化包了: - -![img](http://upload-images.jianshu.io/upload_images/3101171-3544d5393f4e298f.jpg) - -下载完解压缩后,会有个包含 features 和 plugin 目录的 eclipse 文件夹,把这个 eclipse 放在我们的 Eclipse 安装根目录,也就是和 eclipse.exe 同一级目录下。然后仍然在这一级目录下,新建一个 links 文件夹,并在该文件夹内,建一个 language.link 的文本文件。该文本文件的名字是可以任取的,后缀名是.link,而不是.txt 哟。好了,最后一步,编辑该文件,在里面写入刚才放入的语言包的地址,并用“\\”表示路径,一定要有 path= 这个前缀。 - -![img](http://upload-images.jianshu.io/upload_images/3101171-18f74bf3080d2c1b.jpg) - -保存文件后,重新打开 Eclipse,熟悉的中文界面终于看到了。虽然汉化不完全,不过也够用了不是么。如果仍然出现的是英文,说明汉化失败,重新检查下 language.link 文件中配置的信息是否和汉化包的目录一致。  其它的插件安装方法也是如此,当不需要某个插件时,只需删除存放插件的目录和 links 目录下相应的 link 文件,或者改变下 link 文件里面的路径变成无效路径即可;对 Eclipse 做高版本升级时,也只需把老版存放插件的目录和 links 目录复制过去就行了。 - -## 基本设置 - -在 Preference 的搜索项中搜索 Text Editors。 -可以参考我的设置: -Show line numbers -Show print margin -Insert spaces for tabs - -![img](http://upload-images.jianshu.io/upload_images/3101171-91aa025fbe7592f8.png) -设置代码的字体类型和大小: - -Window -> Preferences -> General -> Appearance -> Content Assist -> Colors and Fornts,只需修改 Basic 里面的 Text Font 就可以了。 - -推荐 Courier New。 - -![img](http://upload-images.jianshu.io/upload_images/3101171-49b9126aa0dd58c0.jpg) - -![img](http://upload-images.jianshu.io/upload_images/3101171-b7d6d71b2c211321.jpg) - -## 设置文本文件及 JSP 文件编码 - -Window -> Preferences -> General -> Workspace -> Text file encoding -> Other: - -![img](http://upload-images.jianshu.io/upload_images/3101171-b7aa010673565c88.jpg) - -![img](http://upload-images.jianshu.io/upload_images/3101171-b83fa3476fddde46.jpg) - -## 设置 JDK 本地 JavaDOC API 路径及源码路径 - -![img](http://upload-images.jianshu.io/upload_images/3101171-45b5dee3d3ce917a.jpg) - -![img](http://upload-images.jianshu.io/upload_images/3101171-f960daf4839662e3.jpg) - -还都生成的是无意义的变量名,这样可能会对含有相同类型的变量参数的调用顺序造成干扰; - -这种问题,我们把 JDK 或者相应 Jar 包的源码导入进去就能避免了: - -Window -> Preferences -> Java -> Installed JREs -> Edit: - -![img](http://upload-images.jianshu.io/upload_images/3101171-a08c9166dbbf4361.jpg) - -选中设置好的 JRE 目录,编辑,然后全选 JRE system libraries 下的所有 Jar 包,点击右边的 Source Attachment; - -![img](http://upload-images.jianshu.io/upload_images/3101171-4e6c78afa8e3e50b.jpg) - -External location 下,选中 JDK 安装目录下的 src.zip 文件,一路 OK 下来。 - -![img](http://upload-images.jianshu.io/upload_images/3101171-e82d03ce88f64312.jpg) - -设置完,我们再来看看,幸福来的好突然有木有! - -![img](http://upload-images.jianshu.io/upload_images/3101171-400b3952aa60cb8e.jpg) - -## 设置 Servlet 源码或其它 Jar 包源码 - -![img](http://upload-images.jianshu.io/upload_images/3101171-ec1980f58297e42c.jpg) - -上一步已经设置过了 JDK 的源码或 JavaDoc 路径,为啥现在又出来了呢?其实这个不难理解,因为我们使用到的类的源码并不在 JDK 的源码包中。 - -仔细看,我们会发现这些 Jar 包其实都在 Tomcat 根目录下的 lib 文件夹中,但是翻遍了 Tomcat 目录也没有相应的 jar 或 zip 文件呀。既然本地没有,那就去官网上找找: - -http://tomcat.apache.org/download-70.cgi这里有Tomcat的安装包和源码包; - -![img](http://upload-images.jianshu.io/upload_images/3101171-2962b9cd48422373.jpg) - -可以自定义一个专门用于存放 JavaSource 和 JavaDoc 的文件夹,把下载文件放到该目录下, - -然后再切换到 Eclipse 下,选中没有代码提示的类或者函数, 按下 F3,点击 Change Attached Source: - -![img](http://upload-images.jianshu.io/upload_images/3101171-e8c7cc17364206cf.jpg) - -选择我们刚才下载好的 tomcat 源码文件,一路 OK。 - -![img](http://upload-images.jianshu.io/upload_images/3101171-138cb66d553d9306.jpg) - -然后再回过头看看我们的代码提示,友好多了: - -![img](http://upload-images.jianshu.io/upload_images/3101171-b27652e5ff1d6eab.jpg) - -其它 Jar 包源码的设置方式也一样。 - -## 反编译插件 JD-Eclipse - -无论是开发还是调试,反编译必不可少,每次都用 jd-gui 打开去看,多麻烦,干脆配置下 JD 插件,自动关联.class: - -先从 http://jd.benow.ca/ 上下载离线安装包 jdeclipse_update_site.zip,解压缩后把 features、plugins 这 2 个文件夹复制到 新建文件夹 jdeclipse,然后把 jdeclipse 文件夹整个复制到 Eclipse 根目录的 dropins 文件夹下,重启 Eclipse 即可。这种方式是不是比建 link 文件更方便了? - -![img](http://upload-images.jianshu.io/upload_images/3101171-403597b1b46607ef.jpg) - -打开 Eclipse,Window -> Preferences -> General - > Editors ,把 .class 文件设置关联成 jd 插件的 editor - -![img](http://upload-images.jianshu.io/upload_images/3101171-d95625fcf362526c.jpg) - -## Validate 优化 - -我们在 eclipse 里经常看到这个进程,validating... 逐个的检查每一个文件。那么如何关闭一些 validate 操作呢? - -![img](http://upload-images.jianshu.io/upload_images/3101171-8323d6f595f96fdd.png) - -打开 eclipse,点击【window】菜单,选择【preferences】选项。 - -![img](http://upload-images.jianshu.io/upload_images/3101171-88bd81ece1b3f29f.png) - -在左侧点击【validation】选项,在右侧可以看到 eclipse 进行的自动检查都有哪些内容。 - -![img](http://upload-images.jianshu.io/upload_images/3101171-0c3cf67c6e954dd6.png) - -将 Manual(手动)保持不动,将 build 里面只留下 classpath dependency Validator,其他的全部去掉。 - -![img](http://upload-images.jianshu.io/upload_images/3101171-e1a3051fde4828d8.png) - -最后点击【OK】按钮,保存设置。 - -![img](http://upload-images.jianshu.io/upload_images/3101171-7c6803eed8cf618a.png) - -以后如果需要对文件进行校验检查的时候,在文件上点击右键,点击【Validate】进行检查。 - -## 常用快捷键 - -| 快捷键 | 描述 | -| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Ctrl+1 | 快速修复(最经典的快捷键,就不用多说了,可以解决很多问题,比如 import 类、try catch 包围等) | -| Ctrl+Shift+F | 格式化当前代码 | -| Ctrl+Shift+M | 添加类的 import 导入 | -| Ctrl+Shift+O | 组织类的 import 导入(既有 Ctrl+Shift+M 的作用,又可以帮你去除没用的导入,很有用) | -| Ctrl+Y | 重做(与撤销 Ctrl+Z 相反) | -| Alt+/ | 内容辅助(帮你省了多少次键盘敲打,太常用了) | -| Ctrl+D | 删除当前行或者多行 | -| Alt+↓ | 当前行和下面一行交互位置(特别实用,可以省去先剪切,再粘贴了) | -| Alt+↑ | 当前行和上面一行交互位置(同上) | -| Ctrl+Alt+↓ | 复制当前行到下一行(复制增加) | -| Ctrl+Alt+↑ | 复制当前行到上一行(复制增加) | -| Shift+Enter | 在当前行的下一行插入空行(这时鼠标可以在当前行的任一位置,不一定是最后) | -| Ctrl+/ | 注释当前行,再按则取消注释 | -| Alt+Shift+↑ | 选择封装元素 | -| Alt+Shift+← | 选择上一个元素 | -| Alt+Shift+→ | 选择下一个元素 | -| Shift+← | 从光标处开始往左选择字符 | -| Shift+→ | 从光标处开始往右选择字符 | -| Ctrl+Shift+← | 选中光标左边的单词 | -| Ctrl+Shift+→ | 选中光标又边的单词 | -| Ctrl+← | 光标移到左边单词的开头,相当于 vim 的 b | -| Ctrl+→ | 光标移到右边单词的末尾,相当于 vim 的 e | -| Ctrl+K | 参照选中的 Word 快速定位到下一个(如果没有选中 word,则搜索上一次使用搜索的 word) | -| Ctrl+Shift+K | 参照选中的 Word 快速定位到上一个 | -| Ctrl+J | 正向增量查找(按下 Ctrl+J 后,你所输入的每个字母编辑器都提供快速匹配定位到某个单词,如果没有,则在状态栏中显示没有找到了,查一个单词时,特别实用,要退出这个模式,按 escape 建) | -| Ctrl+Shift+J | 反向增量查找(和上条相同,只不过是从后往前查) | -| Ctrl+Shift+U | 列出所有包含字符串的行 | -| Ctrl+H | 打开搜索对话框 | -| Ctrl+G | 工作区中的声明 | -| Ctrl+Shift+G | 工作区中的引用 | -| Ctrl+Shift+T | 搜索类(包括工程和关联的第三 jar 包) | -| Ctrl+Shift+R | 搜索工程中的文件 | -| Ctrl+E | 快速显示当前 Editer 的下拉列表(如果当前页面没有显示的用黑体表示) | -| F4 | 打开类型层次结构 | -| F3 | 跳转到声明处 | -| Alt+← | 前一个编辑的页面 | -| Alt+→ | 下一个编辑的页面(当然是针对上面那条来说了) | -| Ctrl+PageUp/PageDown | 在编辑器中,切换已经打开的文件 | -| F5 | 单步跳入 | -| F6 | 单步跳过 | -| F7 | 单步返回 | -| F8 | 继续 | -| Ctrl+Shift+D | 显示变量的值 | -| Ctrl+Shift+B | 在当前行设置或者去掉断点 | -| Ctrl+R | 运行至行(超好用,可以节省好多的断点) | -| Alt+Shift+R | 重命名方法名、属性或者变量名 (是我自己最爱用的一个了,尤其是变量和类的 Rename,比手工方法能节省很多劳动力) | -| Alt+Shift+M | 把一段函数内的代码抽取成方法 (这是重构里面最常用的方法之一了,尤其是对一大堆泥团代码有用) | -| Alt+Shift+C | 修改函数结构(比较实用,有 N 个函数调用了这个方法,修改一次搞定) | -| Alt+Shift+L | 抽取本地变量( 可以直接把一些魔法数字和字符串抽取成一个变量,尤其是多处调用的时候) | -| Alt+Shift+F | 把 Class 中的 local 变量变为 field 变量 (比较实用的功能) | -| Alt+Shift+I | 合并变量(可能这样说有点不妥 Inline) | -| Alt+Shift+V | 移动函数和变量(不怎么常用) | -| Alt+Shift+Z | 重构的后悔药(Undo) | -| Alt+Enter | 显示当前选择资源的属性,windows 下的查看文件的属性就是这个快捷键,通常用来查看文件在 windows 中的实际路径 | -| Ctrl+↑ | 文本编辑器 上滚行 | -| Ctrl+↓ | 文本编辑器 下滚行 | -| Ctrl+M | 最大化当前的 Edit 或 View (再按则反之) | -| Ctrl+O | 快速显示 OutLine | -| Ctrl+T | 快速显示当前类的继承结构 | -| Ctrl+W | 关闭当前 Editer | -| Ctrl+L | 文本编辑器 转至行 | -| F2 | 显示工具提示描述 | diff --git a/docs/tool/ide/intellij-idea.md b/docs/tool/ide/intellij-idea.md deleted file mode 100644 index e1bac2d4..00000000 --- a/docs/tool/ide/intellij-idea.md +++ /dev/null @@ -1,269 +0,0 @@ -# Intellij IDEA 应用指南 - -## 快捷键 - -### 核心快捷键 - -IntelliJ IDEA 作为一个以快捷键为中心的 IDE,为大多数操作建议了键盘快捷键。在这个主题中,您可以找到最不可缺少的列表,使 IntelliJ IDEA 轻松实现第一步。 - -核心快捷键表: - -| 操作 | 快捷键 | -| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------- | -| [根据名称查找操作](https://www.jetbrains.com/help/idea/navigating-to-action.html) | `Ctrl+Shift+A` | -| 显示可用 [意图操作](https://www.jetbrains.com/help/idea/intention-actions.html) 列表 | `Alt+Enter` | -| 切换视图 ([Project](https://www.jetbrains.com/help/idea/project-tool-window.html),[Structure](https://www.jetbrains.com/help/idea/structure-tool-window-file-structure-popup.html), etc.). | `Alt+F1` | -| [切换](https://www.jetbrains.com/help/idea/navigating-between-files-and-tool-windows.html)工具窗口和在编辑器中打开的文件 | `Ctrl+Tab` | -| 显示 [导航栏](https://www.jetbrains.com/help/idea/navigation-bar.html). | `Alt+Home` | -| [插入代码模板](https://www.jetbrains.com/help/idea/generating-code.html). | `Ctrl+J` | -| [在周围插入代码模板](https://www.jetbrains.com/help/idea/creating-code-constructs-using-surround-templates.html). | `Ctrl+Alt+J` | -| [Edit an item from the Project or another tree view](https://www.jetbrains.com/help/idea/opening-and-reopening-files-in-the-editor.html). | `F4` | -| [注释](https://www.jetbrains.com/help/idea/commenting-and-uncommenting-blocks-of-code.html) | `Ctrl+/` `Ctrl+Shift+/` | -| [根据名称查找类或文件](https://www.jetbrains.com/help/idea/navigating-to-class-file-or-symbol-by-name.html). | `Ctrl+N` `Ctrl+Shift+N` | -| [拷贝当前行或指定的行](https://www.jetbrains.com/help/idea/adding-deleting-and-moving-code-elements.html#duplicate). | `Ctrl+D` | -| [增加或减少选中的表达式](https://www.jetbrains.com/help/idea/selecting-text-in-the-editor.html). | `Ctrl+W` and `Ctrl+Shift+W` | -| [在当前文件查找或替换](https://www.jetbrains.com/help/idea/finding-and-replacing-text-in-file.html). | `Ctrl+F` `Ctrl+R` | -| [在项目中或指定的目录中查找或替换](https://www.jetbrains.com/help/idea/finding-and-replacing-text-in-project.html) | `Ctrl+Shift+F` `Ctrl+Shift+R` | -| [全局搜索](https://www.jetbrains.com/help/idea/searching-everywhere.htmls) | 双击 `Shift` | -| [快速查看选中对象的引用](https://www.jetbrains.com/help/idea/highlighting-usages.html). | `Ctrl+Shift+F7` | -| [展开或折叠编辑器中的代码块](https://www.jetbrains.com/help/idea/code-folding.html). | `Ctrl+NumPad Plus` `Ctrl+NumPad -` | -| [调用代码完成](https://www.jetbrains.com/help/idea/auto-completing-code.html#basic_completion). | `Ctrl+Space` | -| [智能声明完成](https://www.jetbrains.com/help/idea/auto-completing-code.html#statements_completion). | `Ctrl+Shift+Enter` | -| [智能补全代码](https://www.jetbrains.com/help/idea/auto-completing-code.html#smart_completion) | `Ctrl+Shift+Space` | -| 显示可用的[重构](https://www.jetbrains.com/help/idea/refactoring-source-code.html)方法列表 | `Ctrl+Shift+Alt+T` | - -### 快捷键分类 - -#### Tradition - -| 快捷键 | 介绍 | -| ---------------- | ------------------------------- | -| Ctrl + Z | 撤销 | -| Ctrl + Shift + Z | 取消撤销 | -| Ctrl + X | 剪切 | -| Ctrl + C | 复制 | -| Ctrl + S | 保存 | -| Tab | 缩进 | -| Shift + Tab | 取消缩进 | -| Shift + Home/End | 选中光标到当前行头位置/行尾位置 | -| Ctrl + Home/End | 跳到文件头/文件尾 | - -#### Editing - -| 快捷键 | 介绍 | -| ----------------------- | -------------------------------------------------------------------------------------------------------- | -| Ctrl + Space | 基础代码补全,默认在 Windows 系统上被输入法占用,需要进行修改,建议修改为 Ctrl + 逗号`(必备)` | -| Ctrl + Alt + Space | 类名自动完成 | -| Ctrl + Shift + Enter | 自动结束代码,行末自动添加分号`(必备)` | -| Ctrl + P | 方法参数提示显示 | -| Ctrl + Q | 光标所在的变量/类名/方法名等上面(也可以在提示补充的时候按),显示文档内容 | -| Shift + F1 | 如果有外部文档可以连接外部文档 | -| Ctrl + F1 | 在光标所在的错误代码处显示错误信息`(必备)` | -| Alt + Insert | 代码自动生成,如生成对象的 set/get 方法,构造函数,toString() 等`(必备)` | -| Ctrl + O | 选择可重写的方法 | -| Ctrl + I | 选择可继承的方法 | -| Ctrl + Alt + T | 对选中的代码弹出环绕选项弹出层`(必备)` | -| Ctrl + / | 注释光标所在行代码,会根据当前不同文件类型使用不同的注释符号`(必备)` | -| Ctrl + Shift + / | 代码块注释`(必备)` | -| Ctrl + W | 递进式选择代码块。可选中光标所在的单词或段落,连续按会在原有选中的基础上再扩展选中范围`(必备)` | -| Ctrl + Shift + W | 递进式取消选择代码块。可选中光标所在的单词或段落,连续按会在原有选中的基础上再扩展取消选中范围`(必备)` | -| Alt + Q | 弹出一个提示,显示当前类的声明/上下文信息 | -| Alt + Enter | IntelliJ IDEA 根据光标所在问题,提供快速修复选择,光标放在的位置不同提示的结果也不同`(必备)` | -| Ctrl + Alt + L | 格式化代码,可以对当前文件和整个包目录使用`(必备)` | -| Ctrl + Alt + O | 优化导入的类,可以对当前文件和整个包目录使用`(必备)` | -| Ctrl + Alt + I | 光标所在行 或 选中部分进行自动代码缩进,有点类似格式化 | -| Ctrl + Shift + C | 复制当前文件磁盘路径到剪贴板`(必备)` | -| Ctrl + Shift + V | 弹出缓存的最近拷贝的内容管理器弹出层 | -| Ctrl + Alt + Shift + C | 复制参考信息 | -| Ctrl + Alt + Shift + V | 无格式黏贴`(必备)` | -| Ctrl + D | 复制光标所在行 或 复制选择内容,并把复制内容插入光标位置下面`(必备)` | -| Ctrl + Y | 删除光标所在行 或 删除选中的行`(必备)` | -| Ctrl + Shift + J | 自动将下一行合并到当前行末尾`(必备)` | -| Shift + Enter | 开始新一行。光标所在行下空出一行,光标定位到新行位置`(必备)` | -| Ctrl + Shift + U | 对选中的代码进行大/小写轮流转换`(必备)` | -| Ctrl + Shift + ]/[ | 选中从光标所在位置到它的底部/顶部的中括号位置`(必备)` | -| Ctrl + Delete | 删除光标后面的单词或是中文句`(必备)` | -| Ctrl + BackSpace | 删除光标前面的单词或是中文句`(必备)` | -| Ctrl + +/- | 展开/折叠代码块 | -| Ctrl + Shift + +/- | 展开/折叠所有代码`(必备)` | -| Ctrl + F4 | 关闭当前编辑文件 | -| Ctrl + Shift + Up/Down | 光标放在方法名上,将方法移动到上一个/下一个方法前面,调整方法排序`(必备)` | -| Alt + Shift + Up/Down | 移动光标所在行向上移动/向下移动`(必备)` | -| Ctrl + Shift + 左键单击 | 把光标放在某个类变量上,按此快捷键可以直接定位到该类中`(必备)` | -| Alt + Shift + 左键双击 | 选择被双击的单词/中文句,按住不放,可以同时选择其他单词/中文句`(必备)` | -| Ctrl + Shift + T | 对当前类生成单元测试类,如果已经存在的单元测试类则可以进行选择`(必备)` | - -#### Search/Replace - -| 快捷键 | 介绍 | -| ---------------- | -------------------------------------------------------------------- | -| Double Shift | 弹出 Search Everywhere 弹出层 | -| F3 | 在查找模式下,定位到下一个匹配处 | -| Shift + F3 | 在查找模式下,查找匹配上一个 | -| Ctrl + F | 在当前文件进行文本查找`(必备)` | -| Ctrl + R | 在当前文件进行文本替换`(必备)` | -| Ctrl + Shift + F | 根据输入内容查找整个项目 或 指定目录内文件`(必备)` | -| Ctrl + Shift + R | 根据输入内容替换对应内容,范围为整个项目 或 指定目录内文件`(必备)` | - -#### Usage Search - -| 快捷键 | 介绍 | -| ----------------- | -------------------------------------------------------------------- | -| Alt + F7 | 查找光标所在的方法/变量/类被调用的地方 | -| Ctrl + Alt + F7 | 显示使用的地方。寻找被该类或是变量被调用的地方,用弹出框的方式找出来 | -| Ctrl + Shift + F7 | 高亮显示所有该选中文本,按 Esc 高亮消失`(必备)` | - -#### Compile and Run - -| 快捷键 | 介绍 | -| ----------------- | ------------------------ | -| Ctrl + F9 | 执行 Make Project 操作 | -| Ctrl + Shift + F9 | 编译选中的文件/包/Module | -| Shift + F9 | Debug | -| Shift + F10 | Run | -| Alt + Shift + F9 | 弹出 Debug 的可选择菜单 | -| Alt + Shift + F10 | 弹出 Run 的可选择菜单 | - -#### Debugging - -| 快捷键 | 介绍 | -| ----------------- | ------------------------------------------------------------------------------------------------------------------------- | -| F7 | 在 Debug 模式下,进入下一步,如果当前行断点是一个方法,则进入当前方法体内,如果该方法体还有方法,则不会进入该内嵌的方法中 | -| F8 | 在 Debug 模式下,进入下一步,如果当前行断点是一个方法,则不进入当前方法体内 | -| Shift + F7 | 在 Debug 模式下,智能步入。断点所在行上有多个方法调用,会弹出进入哪个方法 | -| Shift + F8 | 在 Debug 模式下,跳出,表现出来的效果跟 F9 一样 | -| Alt + F8 | 在 Debug 模式下,选中对象,弹出可输入计算表达式调试框,查看该输入内容的调试结果 | -| Alt + F9 | 在 Debug 模式下,执行到光标处 | -| F9 | 在 Debug 模式下,恢复程序运行,但是如果该断点下面代码还有断点则停在下一个断点上 | -| Ctrl + F8 | 在 Debug 模式下,设置光标当前行为断点,如果当前已经是断点则去掉断点 | -| Ctrl + Shift + F8 | 在 Debug 模式下,指定断点进入条件 | - -#### Navigation - -| 快捷键 | 介绍 | -| ------------------------ | ----------------------------------------------------------------------------------------------------------------------- | -| Ctrl + N | 跳转到类`(必备)` | -| Ctrl + Shift + N | 跳转到文件`(必备)` | -| Ctrl + Alt + Shift + N | 跳转到符号`(必备)` | -| Alt + Left/Right | 切换当前已打开的窗口中的子视图,比如 Debug 窗口中有 Output、Debugger 等子视图,用此快捷键就可以在子视图中切换`(必备)` | -| F12 | 回到前一个工具窗口`(必备)` | -| ESC | 从工具窗口进入代码文件窗口`(必备)` | -| Shift + ESC | 隐藏当前 或 最后一个激活的工具窗口 | -| Ctrl + G | 跳转到当前文件的指定行处 | -| Ctrl + E | 显示最近打开的文件记录列表`(必备)` | -| Ctrl + Shift + E | 显示最近编辑的文件记录列表`(必备)` | -| Ctrl + Alt + Left/Right | 跳转到上一个/下一个操作的地方`(必备)` | -| Ctrl + Shift + Backspace | 退回到上次修改的地方`(必备)` | -| Alt + F1 | 显示当前文件选择目标弹出层,弹出层中有很多目标可以进行选择`(必备)` | -| Ctrl + B/Ctrl + 左键单击 | 跳转到声明处 | -| Ctrl + Alt + B | 在某个调用的方法名上使用会跳到具体的实现处,可以跳过接口 | -| Ctrl + Shift + B | 跳转到类型声明处`(必备)` | -| Ctrl + Shift + I | 快速查看光标所在的方法 或 类的定义 | -| Ctrl + U | 前往当前光标所在的方法的父类的方法/接口定义`(必备)` | -| Alt + Up/Down | 跳转到当前文件的前一个/后一个方法`(必备)` | -| Ctrl + ]/[ | 跳转到当前所在代码的花括号结束位置/开始位置 | -| Ctrl + F12 | 弹出当前文件结构层,可以在弹出的层上直接输入,进行筛选 | -| Ctrl + H | 显示当前类的层次结构 | -| Ctrl + Shift + H | 显示方法层次结构 | -| Ctrl + Alt + H | 调用层次 | -| F2/Shift + F2 | 跳转到下一个/上一个高亮错误 或 警告位置`(必备)` | -| F4 | 编辑源`(必备)` | -| Alt + Home | 定位/显示到当前文件的 Navigation Bar | -| F11 | 添加书签`(必备)` | -| Ctrl + F11 | 选中文件/文件夹,使用助记符设定/取消书签`(必备)` | -| Shift + F11 | 弹出书签显示层`(必备)` | -| Alt + 1,2,3...9 | 显示对应数值的选项卡,其中 1 是 Project 用得最多`(必备)` | -| Ctrl + 1,2,3...9 | 定位到对应数值的书签位置`(必备)` | - -#### Refactoring - -| 快捷键 | 介绍 | -| ---------------------- | ------------------------------ | -| Shift + F6 | 对文件/文件夹 重命名`(必备)` | -| Ctrl + Alt + Shift + T | 打开重构菜单`(必备)` | - -#### VCS/Local History - -| 快捷键 | 介绍 | -| --------------- | -------------------------------------------------- | -| Ctrl + K | 版本控制提交项目,需要此项目有加入到版本控制才可用 | -| Ctrl + T | 版本控制更新项目,需要此项目有加入到版本控制才可用 | -| `Alt + |` | 显示版本控制常用操作菜单弹出层`(必备)` | -| Alt + Shift + C | 查看最近操作项目的变化情况列表 | -| Alt + Shift + N | 选择/添加 task`(必备)` | - -#### Live Templates - -| 快捷键 | 介绍 | -| -------------- | -------------------------------------------- | -| Ctrl + J | 插入自定义动态代码模板`(必备)` | -| Ctrl + Alt + J | 弹出模板选择窗口,将选定的代码加入动态模板中 | - -#### General - -| 快捷键 | 介绍 | -| ---------------------- | --------------------------------------------------------------------- | -| Ctrl + Tab | 编辑窗口切换,如果在切换的过程又加按上 delete,则是关闭对应选中的窗口 | -| Ctrl + Alt + Y | 同步、刷新 | -| Ctrl + Alt + S | 打开 IntelliJ IDEA 系统设置`(必备)` | -| Ctrl + Alt + Shift + S | 打开当前项目设置`(必备)` | -| Ctrl + Shift + A | 查找动作/设置`(必备)` | -| Ctrl + Shift + F12 | 编辑器最大化`(必备)` | -| Alt + Shift + F | 显示添加到收藏夹弹出层/添加到收藏夹 | -| Alt + Shift + I | 查看项目当前文件 | - -### Intellij IDEA 官方快捷键表 - -![img](http://upload-images.jianshu.io/upload_images/3101171-6a44121ae280a10e.png) - -## 插件 - -推荐几个比较好用的插件 - -- [Key promoter](https://plugins.jetbrains.com/plugin/4455?pr=idea) [快捷键提示](https://plugins.jetbrains.com/plugin/4455?pr=idea) -- [CamelCase](https://plugins.jetbrains.com/plugin/7160?pr=idea) 驼峰式命名和下划线命名交替变化 -- [CheckStyle-IDEA](https://plugins.jetbrains.com/plugin/1065?pr=idea) 代码规范检查 -- [FindBugs-IDEA](https://plugins.jetbrains.com/plugin/3847?pr=idea)潜在 Bug 检查 -- [MetricsReloaded](https://plugins.jetbrains.com/plugin/93?pr=idea) 代码复杂度检查 -- [Statistic](https://plugins.jetbrains.com/plugin/4509?pr=idea) 代码统计 -- [JRebel Plugin](https://plugins.jetbrains.com/plugin/?id=4441) 热部署 -- [GsonFormat](https://plugins.jetbrains.com/plugin/7654?pr=idea) 把 JSON 字符串直接实例化成类 -- [Eclipse Code Formatter](https://plugins.jetbrains.com/plugin/6546-eclipse-code-formatter) 如果你以前用的是 IDE,并有自己的一套代码风格配置,可以通过此插件导入到 IDEA -- [Alibaba Java Coding Guidelines](https://plugins.jetbrains.com/plugin/10046-alibaba-java-coding-guidelines) 阿里 Java 开发规范的静态检查工具 -- [IDE Features Trainer](https://plugins.jetbrains.com/plugin/8554-ide-features-trainer) 官方的新手训练插件 -- [Markdown Navigator](https://plugins.jetbrains.com/plugin/7896-markdown-navigator) Markdown 插件,适用于喜欢用 markdown 写文档的人 - -## 个性化 - -### 颜色主题 - -[intellij-colors-solarized](https://github.com/jkaving/intellij-colors-solarized) 个人觉得这种色彩搭配十分优雅 - -[下载地址](https://github.com/altercation/solarized) - -## 破解 - -Intellij 是一个收费的 IDE,坦白说有点小贵,买不起。 - -所以,很惭愧,只好用下破解方法了。网上有很多使用注册码的网文,但是注册码不稳定,随时可能被封。还是自行搭建一个注册服务器比较稳定。我使用了 [ilanyu](http://blog.lanyus.com/) 博文 [IntelliJ IDEA License Server 本地搭建教程](http://blog.lanyus.com/archives/174.html) 的方法,亲测十分有效。 - -我的备用地址:[百度云盘](https://yun.baidu.com/disk/home?#list/vmode=list&path=%2F%E8%BD%AF%E4%BB%B6%2F%E5%BC%80%E5%8F%91%E8%BD%AF%E4%BB%B6%2FIDE) - -下载并解压文中的压缩包到本地,选择适合操作系统的版本运行。 - -如果是在 Linux 上运行,推荐创建一个脚本,代码如下: - -```bash -# 使用 nohup 创建守护进程,运行 IntelliJIDEALicenseServer_linux_amd64 -# 如果运行在其他 Linux 发行版本,替换执行的脚本即可 -nohup sh IntelliJIDEALicenseServer_linux_amd64 2>&1 -``` - -这样做是因为:大部分人使用 linux 是使用仿真器连接虚拟机,如果断开连接,进程也会被 kill,每次启动这个注册服务器很麻烦不是吗?而启动了守护进程,则不会出现这种情况,只有你主动 kill 进程才能将其干掉。 - -Windows 版本是 exe 程序,将其设为开机自动启动即可,别告诉我你不知道怎么设置开机自动启动。 - -## 参考资料 - -- [IntelliJ-IDEA-Tutorial](https://github.com/judasn/IntelliJ-IDEA-Tutorial) -- [极客学院 - Intellij IDEA 使用教程](http://wiki.jikexueyuan.com/project/intellij-idea-tutorial/) diff --git a/docs/tool/ide/vscode.md b/docs/tool/ide/vscode.md deleted file mode 100644 index 3e0b7ac0..00000000 --- a/docs/tool/ide/vscode.md +++ /dev/null @@ -1,47 +0,0 @@ -# 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 - -## 更多内容 - -- 官方 - - https://github.com/Microsoft/vscode - - https://github.com/Microsoft/vscode-docs - - https://github.com/Microsoft/vscode-tips-and-tricks -- 更多资源 - - https://github.com/viatsko/awesome-vscode diff --git a/docs/tool/jmeter.md b/docs/tool/jmeter.md deleted file mode 100644 index d5d7630d..00000000 --- a/docs/tool/jmeter.md +++ /dev/null @@ -1,204 +0,0 @@ -# JMeter 应用指南 - -> [Jmeter](https://github.com/apache/jmeter) 是一款基于 Java 开发的功能和性能测试软件。 -> -> 🎁 本文编辑时的最新版本为:5.1.1 - -## 1. 简介 - -[Jmeter](https://github.com/apache/jmeter) 是一款使用 Java 开发的功能和性能测试软件。 - -### 1.1. 特性 - -Jmeter 能够加载和性能测试许多不同的应用程序/服务器/协议类型: - -- 网络 - HTTP,HTTPS(Java,NodeJS,PHP,ASP.NET 等) -- SOAP / REST Web 服务 -- FTP 文件 -- 通过 JDBC 的数据库 -- LDAP -- 通过 JMS 的面向消息的中间件(MOM) -- 邮件-SMTP(S),POP3(S)和 IMAP(S) -- 本机命令或 Shell 脚本 -- TCP 协议 -- Java 对象 - -### 1.2. 工作流 - -Jmeter 的工作原理是仿真用户向服务器发送请求,并收集服务器应答信息并计算统计信息。 - -Jmeter 的工作流如下图所示: - -![](http://dunwu.test.upcdn.net/cs/java/javaweb/technology/test/jmeter-workflow.png!zp) - -### 1.3. 主要元素 - -Jmeter 的主要元素如下: - -- **`测试计划(Test Plan)`** - 可以将测试计划视为 JMeter 的测试脚本 。测试计划由测试元素组成,例如线程组,逻辑控制器,样本生成控制器,监听器,定时器,断言和配置元素。 -- **`线程组(Thread Group)`** - 线程组的作用是:模拟大量用户负载的运行场景。 - - 设置线程数 - - 设置加速期 - - 设置执行测试的次数 -- **`控制器(Controllers)`** - 可以分为两大类: - - **`采样器(Sampler)`** - 采样器的作用是模拟用户对目标服务器发送请求。 采样器是必须将组件添加到测试计划中的,因为它只能让 JMeter 知道需要将哪种类型的请求发送到服务器。 请求可以是 HTTP,HTTP(s),FTP,TCP,SMTP,SOAP 等。 - - **`逻辑控制器`** - 逻辑控制器的作用是:控制多个请求发送的循环次数及顺序等。 -- **`监听器(Listeners)`** - 监听器的作用是:收集测试结果信息。如查看结果树、汇总报告等。 -- **`计时器(Timers)`** - 计时器的作用是:控制多个请求发送的时间频次。 -- **`配置元素(Configuration Elements)`** - 配置元素的工作与采样器的工作类似。但是,它不发送请求,而是提供预备的数据等,如 CSV、函数助手。 -- **`预处理器元素(Pre-Processor Elements)`** - 预处理器元素在采样器发出请求之前执行,如果预处理器附加到采样器元素,那么它将在该采样器元素运行之前执行。预处理器元素用于在运行之前准备环境及参数。 -- **`后处理器元素(Post-Processor Elements)`** - 后处理器元素是在发送采样器请求之后执行的元素,常用于处理响应数据。 - -![](http://dunwu.test.upcdn.net/cs/java/javaweb/technology/test/jmeter-elements.png!zp) - -> 📌 提示: -> -> Jmeter 元素的数量关系大致如下: -> -> 1. 脚本中最多只能有一个测试计划。 -> 2. 测试计划中至少要有一个线程组。 -> 3. 线程组中至少要有一个取样器。 -> 4. 线程组中至少要有一个监听器。 - -## 2. 安装 - -### 2.1. 环境要求 - -- 必要的。Jmeter 基于 JDK8 开发,所以必须运行在 JDK8 环境。 - - - JDK8 - -- 可选的。有些 jar 包不是 Jmeter 提供的,如果需要相应的功能,需要自行下载并置于 `lib` 目录。 - - JDBC - - JMS - - [Bouncy Castle](http://www.bouncycastle.org/test_releases.html) - -### 2.2. 下载 - -进入 [**Jmeter 官网下载地址**](https://jmeter.apache.org/download_jmeter.cgi) 选择需要版本进行下载。 - -### 2.3. 启动 - -解压 Jmeter 压缩包,进入 bin 目录 - -Unix 类系统运行 `jmeter` ;Windows 系统运行 `jmeter.bat` - -![image-20191024104517721](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024104517721.png!zp) - -## 3. 使用 - -### 3.1. 创建测试计划 - -> 🔔 注意: -> -> - 在运行整个测试计划之前,应保存测试计划。 -> -> - JMeter 的测试计划以 `.jmx` 扩展文件的形式保存。 - -#### 3.1.1. 创建线程组 - -- 在“测试计划”上右键 【添加】=>【线程(用户)】=>【线程组】。 - -- 设置线程数和循环次数 - -![image-20191024105545736](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024105545736.png!zp) - -#### 3.1.2. 配置原件 - -- 在新建的线程组上右键 【添加】=>【配置元件】=>【HTTP 请求默认值】。 - -- 填写协议、服务器名称或 IP、端口号 - -![image-20191024110016264](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024110016264.png!zp) - -#### 3.1.3. 构造 HTTP 请求 - -- 在“线程组”上右键 【添加-】=>【取样器】=>【HTTP 请求】。 - -- 填写协议、服务器名称或 IP、端口号(如果配置了 HTTP 请求默认值可以忽略) -- 填写方法、路径 -- 填写参数、消息体数据、文件上传 - -![image-20191024110953063](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024110953063.png!zp) - -#### 3.1.4. 添加 HTTP 请求头 - -- 在“线程组”上右键 【添加】=>【配置元件】=>【HTTP 信息头管理器】 -- 由于我的测试例中传输的数据为 json 形式,所以设置键值对 `Content-Type`:`application/json` - -![image-20191024111825226](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024111825226.png!zp) - -#### 3.1.5. 添加断言 - -- 在“线程组”上右键 【添加】=>【断言】=>【 响应断言 】 -- 在我的案例中,以 HTTP 应答状态码为 200 来判断请求是否成功 - -![image-20191024112335130](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024112335130.png!zp) - -#### 3.1.6. 添加察看结果树 - -- 在“线程组”上右键 【添加】=>【监听器】=>【察看结果树】 -- 直接点击运行,就可以查看测试结果 - -![image-20191024113849270](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024113849270.png!zp) - -#### 3.1.7. 添加汇总报告 - -- 在“线程组”上右键 【添加】=>【监听器】=>【汇总报告】 -- 直接点击运行,就可以查看测试结果 - -![image-20191024114016424](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024114016424.png!zp) - -#### 3.1.8. 保存测试计划 - -执行测试计划前,GUI 会提示先保存配置为 `jmx` 文件。 - -### 3.2. 执行测试计划 - -官方建议不要直接使用 GUI 来执行测试计划,这种模式指适用于创建测试计划和 debug。 - -执行测试计划应该使用命令行模式,语法形式如下: - -```bash -jmeter -n -t [jmx file] -l [results file] -e -o [Path to web report folder] -``` - -执行测试计划后,在 `-e -o` 参数后指定的 web 报告目录下,可以找到测试报告内容。在浏览器中打开 `index.html` 文件,可以看到如下报告: - -![image-20191024120233058](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024120233058.png!zp) - -## 4. 问题 - -### 4.1. 如何读取本地 txt/csv 文件作为请求参数 - -参考:[Jmeter 读取本地 txt/csv 文件作为请求参数,实现接口自动化](https://www.jianshu.com/p/3b2d3b643415) - -(1)依次点击【添加】=>【配置元件】=>【CSV 数据文件设置】 - -配置如下所示: - -![image-20191127175820747](http://dunwu.test.upcdn.net/snap/image-20191127175820747.png) - -重要配置说明(其他配置根据实际情况填): - -- 文件名:输入需要导入的数据文件位置。 -- 文件编码:设为 UTF-8,避免乱码。 -- 变量名称:使用 `,` 分隔输入变量列表。如截图中设置了两个变量 `a` 和 `b` - -(2)在 HTTP 请求的消息体数据中配置参数 - -``` -[{"a":"${a}","b":"${b}"}] -``` - -### 4.2. 如何有序发送数据 - -依次点击【添加】=>【逻辑控制器】=>【事务控制器】 - -## 5. 参考资料 - -- [Jmeter 官网](https://jmeter.apache.org/) -- [Jmeter Github](https://github.com/apache/jmeter) -- [Jmeter 性能测试入门](https://www.cnblogs.com/TankXiao/p/4045439.html) -- [易百教程 - Jmeter 教程](https://www.yiibai.com/jmeter) -- [Jmeter 读取本地 txt/csv 文件作为请求参数,实现接口自动化](https://www.jianshu.com/p/3b2d3b643415) diff --git a/docs/tool/monitor/arthas.md b/docs/tool/monitor/arthas.md deleted file mode 100644 index a205242b..00000000 --- a/docs/tool/monitor/arthas.md +++ /dev/null @@ -1,412 +0,0 @@ -# Arthas 应用指南 - -> `Arthas` 是 Alibaba 开源的 Java 诊断工具 。 - -## 简介 - -`Arthas`可以解决的问题: - -1. 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception? -2. 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了? -3. 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗? -4. 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现! -5. 是否有一个全局视角来查看系统的运行状况? -6. 有什么办法可以监控到 JVM 的实时运行状态? - -`Arthas`支持 JDK 6+,支持 Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 `Tab` 自动补全功能,进一步方便进行问题的定位和诊断。 - -## 安装 - -### 使用`arthas-boot`(推荐) - -下载`arthas-boot.jar`,然后用`java -jar`的方式启动: - -``` -wget https://alibaba.github.io/arthas/arthas-boot.jar -java -jar arthas-boot.jar -``` - -打印帮助信息: - -``` -java -jar arthas-boot.jar -h -``` - -- 如果下载速度比较慢,可以使用 aliyun 的镜像: - - ``` - java -jar arthas-boot.jar --repo-mirror aliyun --use-http - ``` - -- 如果从 github 下载有问题,可以使用 gitee 镜像 - - ``` - wget https://arthas.gitee.io/arthas-boot.jar - ``` - -### 使用`as.sh` - -Arthas 支持在 Linux/Unix/Mac 等平台上一键安装,请复制以下内容,并粘贴到命令行中,敲 `回车` 执行即可: - -``` -curl -L https://alibaba.github.io/arthas/install.sh | sh -``` - -上述命令会下载启动脚本文件 `as.sh` 到当前目录,你可以放在任何地方或将其加入到 `$PATH` 中。 - -直接在 shell 下面执行`./as.sh`,就会进入交互界面。 - -也可以执行`./as.sh -h`来获取更多参数信息。 - -- 如果从 github 下载有问题,可以使用 gitee 镜像 - - ``` - curl -L https://arthas.gitee.io/install.sh | sh - ``` - -### 全量安装 - -最新版本,点击下载:[下载地址](http://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=com.taobao.arthas&a=arthas-packaging&e=zip&c=bin&v=LATEST) - -解压后,在文件夹里有`arthas-boot.jar`,直接用`java -jar`的方式启动: - -``` -java -jar arthas-boot.jar -``` - -打印帮助信息: - -``` -java -jar arthas-boot.jar -h -``` - -## 基础使用 - -### 启动 Demo - -```bash -wget https://alibaba.github.io/arthas/arthas-demo.jar -java -jar arthas-demo.jar -``` - -`arthas-demo`是一个简单的程序,每隔一秒生成一个随机数,再执行质因式分解,并打印出分解结果。 - -`arthas-demo`源代码:[查看](https://github.com/alibaba/arthas/blob/master/demo/src/main/java/demo/MathGame.java) - -### 启动 arthas - -在命令行下面执行(使用和目标进程一致的用户启动,否则可能 attach 失败): - -``` -wget https://alibaba.github.io/arthas/arthas-boot.jar -java -jar arthas-boot.jar -``` - -- 执行该程序的用户需要和目标进程具有相同的权限。比如以`admin`用户来执行:`sudo su admin && java -jar arthas-boot.jar` 或 `sudo -u admin -EH java -jar arthas-boot.jar`。 -- 如果 attach 不上目标进程,可以查看`~/logs/arthas/` 目录下的日志。 -- 如果下载速度比较慢,可以使用 aliyun 的镜像:`java -jar arthas-boot.jar --repo-mirror aliyun --use-http` -- `java -jar arthas-boot.jar -h` 打印更多参数信息。 - -选择应用 java 进程: - -```bash -$ $ java -jar arthas-boot.jar -* [1]: 35542 - [2]: 71560 arthas-demo.jar -``` - -Demo 进程是第 2 个,则输入 2,再输入`回车/enter`。Arthas 会 attach 到目标进程上,并输出日志: - -```bash -[INFO] Try to attach process 71560 -[INFO] Attach process 71560 success. -[INFO] arthas-client connect 127.0.0.1 3658 - ,---. ,------. ,--------.,--. ,--. ,---. ,---. - / O \ | .--. ''--. .--'| '--' | / O \ ' .-' -| .-. || '--'.' | | | .--. || .-. |`. `-. -| | | || |\ \ | | | | | || | | |.-' | -`--' `--'`--' '--' `--' `--' `--'`--' `--'`-----' - -wiki: https://alibaba.github.io/arthas -version: 3.0.5.20181127201536 -pid: 71560 -time: 2018-11-28 19:16:24 - -$ -``` - -### 查看 dashboard - -输入[dashboard](https://alibaba.github.io/arthas/dashboard.html),按`回车/enter`,会展示当前进程的信息,按`ctrl+c`可以中断执行。 - -```bash -$ dashboard -ID NAME GROUP PRIORI STATE %CPU TIME INTERRU DAEMON -17 pool-2-thread-1 system 5 WAITIN 67 0:0 false false -27 Timer-for-arthas-dashb system 10 RUNNAB 32 0:0 false true -11 AsyncAppender-Worker-a system 9 WAITIN 0 0:0 false true -9 Attach Listener system 9 RUNNAB 0 0:0 false true -3 Finalizer system 8 WAITIN 0 0:0 false true -2 Reference Handler system 10 WAITIN 0 0:0 false true -4 Signal Dispatcher system 9 RUNNAB 0 0:0 false true -26 as-command-execute-dae system 10 TIMED_ 0 0:0 false true -13 job-timeout system 9 TIMED_ 0 0:0 false true -1 main main 5 TIMED_ 0 0:0 false false -14 nioEventLoopGroup-2-1 system 10 RUNNAB 0 0:0 false false -18 nioEventLoopGroup-2-2 system 10 RUNNAB 0 0:0 false false -23 nioEventLoopGroup-2-3 system 10 RUNNAB 0 0:0 false false -15 nioEventLoopGroup-3-1 system 10 RUNNAB 0 0:0 false false -Memory used total max usage GC -heap 32M 155M 1820M 1.77% gc.ps_scavenge.count 4 -ps_eden_space 14M 65M 672M 2.21% gc.ps_scavenge.time(m 166 -ps_survivor_space 4M 5M 5M s) -ps_old_gen 12M 85M 1365M 0.91% gc.ps_marksweep.count 0 -nonheap 20M 23M -1 gc.ps_marksweep.time( 0 -code_cache 3M 5M 240M 1.32% ms) -Runtime -os.name Mac OS X -os.version 10.13.4 -java.version 1.8.0_162 -java.home /Library/Java/JavaVir - tualMachines/jdk1.8.0 - _162.jdk/Contents/Hom - e/jre -``` - -### 通过 thread 命令来获取到`arthas-demo`进程的 Main Class - -`thread 1`会打印线程 ID 1 的栈,通常是 main 函数的线程。 - -```bash -$ thread 1 | grep 'main(' - at demo.MathGame.main(MathGame.java:17) -``` - -### 通过 jad 来反编译 Main Class - -```java -$ jad demo.MathGame - -ClassLoader: -+-sun.misc.Launcher$AppClassLoader@3d4eac69 - +-sun.misc.Launcher$ExtClassLoader@66350f69 - -Location: -/tmp/arthas-demo.jar - -/* - * Decompiled with CFR 0_132. - */ -package demo; - -import java.io.PrintStream; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Random; -import java.util.concurrent.TimeUnit; - -public class MathGame { - private static Random random = new Random(); - private int illegalArgumentCount = 0; - - public static void main(String[] args) throws InterruptedException { - MathGame game = new MathGame(); - do { - game.run(); - TimeUnit.SECONDS.sleep(1L); - } while (true); - } - - public void run() throws InterruptedException { - try { - int number = random.nextInt(); - List primeFactors = this.primeFactors(number); - MathGame.print(number, primeFactors); - } - catch (Exception e) { - System.out.println(String.format("illegalArgumentCount:%3d, ", this.illegalArgumentCount) + e.getMessage()); - } - } - - public static void print(int number, List primeFactors) { - StringBuffer sb = new StringBuffer("" + number + "="); - Iterator iterator = primeFactors.iterator(); - while (iterator.hasNext()) { - int factor = iterator.next(); - sb.append(factor).append('*'); - } - if (sb.charAt(sb.length() - 1) == '*') { - sb.deleteCharAt(sb.length() - 1); - } - System.out.println(sb); - } - - public List primeFactors(int number) { - if (number < 2) { - ++this.illegalArgumentCount; - throw new IllegalArgumentException("number is: " + number + ", need >= 2"); - } - ArrayList result = new ArrayList(); - int i = 2; - while (i <= number) { - if (number % i == 0) { - result.add(i); - number /= i; - i = 2; - continue; - } - ++i; - } - return result; - } -} - -Affect(row-cnt:1) cost in 970 ms. -``` - -### watch - -通过[watch](https://alibaba.github.io/arthas/watch.html)命令来查看`demo.MathGame#primeFactors`函数的返回值: - -```bash -$ watch demo.MathGame primeFactors returnObj -Press Ctrl+C to abort. -Affect(class-cnt:1 , method-cnt:1) cost in 107 ms. -ts=2018-11-28 19:22:30; [cost=1.715367ms] result=null -ts=2018-11-28 19:22:31; [cost=0.185203ms] result=null -ts=2018-11-28 19:22:32; [cost=19.012416ms] result=@ArrayList[ - @Integer[5], - @Integer[47], - @Integer[2675531], -] -ts=2018-11-28 19:22:33; [cost=0.311395ms] result=@ArrayList[ - @Integer[2], - @Integer[5], - @Integer[317], - @Integer[503], - @Integer[887], -] -ts=2018-11-28 19:22:34; [cost=10.136007ms] result=@ArrayList[ - @Integer[2], - @Integer[2], - @Integer[3], - @Integer[3], - @Integer[31], - @Integer[717593], -] -ts=2018-11-28 19:22:35; [cost=29.969732ms] result=@ArrayList[ - @Integer[5], - @Integer[29], - @Integer[7651739], -] -``` - -更多的功能可以查看[进阶使用](https://alibaba.github.io/arthas/advanced-use.html)。 - -### 退出 arthas - -如果只是退出当前的连接,可以用`quit`或者`exit`命令。Attach 到目标进程上的 arthas 还会继续运行,端口会保持开放,下次连接时可以直接连接上。 - -如果想完全退出 arthas,可以执行`shutdown`命令。 - -## 进阶使用 - -### 基础命令 - -- help——查看命令帮助信息 -- [cat](https://alibaba.github.io/arthas/cat.html)——打印文件内容,和linux里的cat命令类似 -- [pwd](https://alibaba.github.io/arthas/pwd.html)——返回当前的工作目录,和linux命令类似 -- cls——清空当前屏幕区域 -- session——查看当前会话的信息 -- [reset](https://alibaba.github.io/arthas/reset.html)——重置增强类,将被 Arthas 增强过的类全部还原,Arthas 服务端关闭时会重置所有增强过的类 -- version——输出当前目标 Java 进程所加载的 Arthas 版本号 -- history——打印命令历史 -- quit——退出当前 Arthas 客户端,其他 Arthas 客户端不受影响 -- shutdown——关闭 Arthas 服务端,所有 Arthas 客户端全部退出 -- [keymap](https://alibaba.github.io/arthas/keymap.html)——Arthas快捷键列表及自定义快捷键 - -### jvm相关 - -- [dashboard](https://alibaba.github.io/arthas/dashboard.html)——当前系统的实时数据面板 -- [thread](https://alibaba.github.io/arthas/thread.html)——查看当前 JVM 的线程堆栈信息 -- [jvm](https://alibaba.github.io/arthas/jvm.html)——查看当前 JVM 的信息 -- [sysprop](https://alibaba.github.io/arthas/sysprop.html)——查看和修改JVM的系统属性 -- [sysenv](https://alibaba.github.io/arthas/sysenv.html)——查看JVM的环境变量 -- [vmoption](https://alibaba.github.io/arthas/vmoption.html)——查看和修改JVM里诊断相关的option -- [logger](https://alibaba.github.io/arthas/logger.html)——查看和修改logger -- [getstatic](https://alibaba.github.io/arthas/getstatic.html)——查看类的静态属性 -- [ognl](https://alibaba.github.io/arthas/ognl.html)——执行ognl表达式 -- [mbean](https://alibaba.github.io/arthas/mbean.html)——查看 Mbean 的信息 -- [heapdump](https://alibaba.github.io/arthas/heapdump.html)——dump java heap, 类似jmap命令的heap dump功能 - -### class/classloader相关 - -- [sc](https://alibaba.github.io/arthas/sc.html)——查看JVM已加载的类信息 -- [sm](https://alibaba.github.io/arthas/sm.html)——查看已加载类的方法信息 -- [jad](https://alibaba.github.io/arthas/jad.html)——反编译指定已加载类的源码 -- [mc](https://alibaba.github.io/arthas/mc.html)——内存编绎器,内存编绎`.java`文件为`.class`文件 -- [redefine](https://alibaba.github.io/arthas/redefine.html)——加载外部的`.class`文件,redefine到JVM里 -- [dump](https://alibaba.github.io/arthas/dump.html)——dump 已加载类的 byte code 到特定目录 -- [classloader](https://alibaba.github.io/arthas/classloader.html)——查看classloader的继承树,urls,类加载信息,使用classloader去getResource - -### monitor/watch/trace相关 - -> 请注意,这些命令,都通过字节码增强技术来实现的,会在指定类的方法中插入一些切面来实现数据统计和观测,因此在线上、预发使用时,请尽量明确需要观测的类、方法以及条件,诊断结束要执行 `shutdown` 或将增强过的类执行 `reset` 命令。 - -- [monitor](https://alibaba.github.io/arthas/monitor.html)——方法执行监控 -- [watch](https://alibaba.github.io/arthas/watch.html)——方法执行数据观测 -- [trace](https://alibaba.github.io/arthas/trace.html)——方法内部调用路径,并输出方法路径上的每个节点上耗时 -- [stack](https://alibaba.github.io/arthas/stack.html)——输出当前方法被调用的调用路径 -- [tt](https://alibaba.github.io/arthas/tt.html)——方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测 - -### options - -- [options](https://alibaba.github.io/arthas/options.html)——查看或设置Arthas全局开关 - -### 管道 - -Arthas支持使用管道对上述命令的结果进行进一步的处理,如`sm java.lang.String * | grep 'index'` - -- grep——搜索满足条件的结果 -- plaintext——将命令的结果去除ANSI颜色 -- wc——按行统计输出结果 - -### 后台异步任务 - -当线上出现偶发的问题,比如需要watch某个条件,而这个条件一天可能才会出现一次时,异步后台任务就派上用场了,详情请参考[这里](https://alibaba.github.io/arthas/async.html) - -- 使用 > 将结果重写向到日志文件,使用 & 指定命令是后台运行,session断开不影响任务执行(生命周期默认为1天) -- jobs——列出所有job -- kill——强制终止任务 -- fg——将暂停的任务拉到前台执行 -- bg——将暂停的任务放到后台执行 - -### Web Console - -通过websocket连接Arthas。 - -- [Web Console](https://alibaba.github.io/arthas/web-console.html) - -### 用户数据回报 - -在`3.1.4`版本后,增加了用户数据回报功能,方便统一做安全或者历史数据统计。 - -在启动时,指定`stat-url`,就会回报执行的每一行命令,比如: `./as.sh --stat-url 'http://192.168.10.11:8080/api/stat'` - -在tunnel server里有一个示例的回报代码,用户可以自己在服务器上实现。 - -[StatController.java](https://github.com/alibaba/arthas/blob/master/tunnel-server/src/main/java/com/alibaba/arthas/tunnel/server/app/web/StatController.java) - -### 其他特性 - -- [异步命令支持](https://alibaba.github.io/arthas/async.html) -- [执行结果存日志](https://alibaba.github.io/arthas/save-log.html) -- [批处理的支持](https://alibaba.github.io/arthas/batch-support.html) -- [ognl表达式的用法说明](https://github.com/alibaba/arthas/issues/11) - -## 参考资料 - -- [Arthas Github](https://github.com/alibaba/arthas) -- [Arthas 用户文档](https://alibaba.github.io/arthas/index.html) -- [arthas源码分析](https://www.jianshu.com/p/4e34d0ab47d1) diff --git a/docs/tool/monitor/skywalking.md b/docs/tool/monitor/skywalking.md deleted file mode 100644 index 7d21f45f..00000000 --- a/docs/tool/monitor/skywalking.md +++ /dev/null @@ -1,27 +0,0 @@ -# SkyWalking 应用指南 - -> SkyWalking 是分布式系统的应用程序性能监视工具,专为微服务、云原生架构和基于容器(Docker、K8s、Mesos)架构而设计。 -> -> SkyWalking 是观察性分析平台和应用性能管理系统。它提供分布式追踪、服务网格遥测分析、度量聚合和可视化一体化解决方案。 - -![img](https://skywalking.apache.org/assets/frame.jpeg) - -## 安装 - -### 环境要求 - -- JDK8 -- Elasticsearch 6.x -- 8080,10800,11800,12800 端口不被占用 - -### 下载 - -[官方下载地址](http://skywalking.apache.org/downloads/) - -### 启动 - -## 参考资料 - -- [SkyWalking 官网](https://skywalking.apache.org/) -- [SkyWalking Github](https://github.com/apache/skywalking) -- [SkyWalking 分布式追踪系统](https://www.jianshu.com/p/2fd56627a3cf) diff --git a/pom.xml b/pom.xml index ed49002b..618b968c 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - io.github.dunwu.javaweb + io.github.dunwu.javatech javatech 1.0.0 pom @@ -11,6 +11,6 @@ Java 技术示例源码 - codes/javaweb-example + codes/java-ecology From d0025d914f80ba8c0adeecea5104484b2be720e4 Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Tue, 11 Feb 2020 20:51:16 +0800 Subject: [PATCH 57/75] update docs --- .travis.yml | 24 ++ README.md | 5 +- docs/.vuepress/config.js | 4 +- docs/README.md | 18 +- docs/cache/README.md | 2 +- docs/cache/Redis.md | 625 --------------------------------------- docs/storage/README.md | 11 +- docs/test/README.md | 1 + docs/test/jmeter.md | 204 +++++++++++++ scripts/deploy.sh | 44 +++ 10 files changed, 294 insertions(+), 644 deletions(-) create mode 100644 .travis.yml delete mode 100644 docs/cache/Redis.md create mode 100644 docs/test/jmeter.md create mode 100644 scripts/deploy.sh diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..7f7498fb --- /dev/null +++ b/.travis.yml @@ -0,0 +1,24 @@ +# 持续集成 CI +# @see https://docs.travis-ci.com/user/tutorial/ + +language: node_js + +sudo: required + +node_js: stable + +branches: + only: + - master + +before_install: + - export TZ=Asia/Shanghai + +script: bash ./scripts/deploy.sh + +notifications: + email: + recipients: + - forbreak@163.com + on_success: change + on_failure: always diff --git a/README.md b/README.md index cde4e90f..04a54860 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ license

    -

    JavaTech

    +

    JAVATECH

    > ☕ **javatech** 汇总了 Java 开发中常见的主流技术的应用、特性、原理。 > @@ -46,7 +46,7 @@ > 如果想深入学习缓存,建议先了解一下 [缓存基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/distributed/cache-theory.md),有助于理解缓存的特性、原理,使用缓存常见的问题及解决方案。 - [缓存面经](docs/cache/CacheInterview.md) -- [Redis](docs/cache/Redis.md) +- [Redis](https://github.com/dunwu/db-tutorial/tree/master/docs/nosql/redis) 📚 - [Ehcache](docs/cache/Ehcache.md) - [Caffeine](docs/cache/Caffeine.md) @@ -83,6 +83,7 @@ - [Junit](docs/test/junit.md) - [Mockito](docs/test/mockito.md) - [JMH](docs/test/jmh.md) +- [Jmeter](docs/test/jmeter.md) ## 服务器 diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 9ae7601e..800607a3 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -15,9 +15,11 @@ module.exports = { } }, themeConfig: { - logo: "https://raw.githubusercontent.com/dunwu/images/master/common/dunwu-logo-200.png", + logo: "images/dunwu-logo-100.png", repo: "dunwu/javatech", repoLabel: "Github", + docsDir: 'docs', + docsBranch: 'master', editLinks: true, smoothScroll: true, locales: { diff --git a/docs/README.md b/docs/README.md index f5f8a0f2..41eec51e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,20 +1,13 @@ --- home: true -heroImage: http://dunwu.test.upcdn.net/common/logo/java-logo.png -heroText: JavaTech +heroImage: /images/dunwu-logo-200.png +heroText: JAVATECH tagline: ☕ javatech 汇总了 Java 开发中常见的主流技术的应用、特性、原理。 actionLink: / -features: - - title: Java 生态 - details: Java 开发中常见应用技术,如:框架、缓存、消息队列、存储、安全、微服务、测试、服务器等。 - - title: Java 工具 - details: Java 开发中常用工具,如:构建、IDE、监控等。 - - title: JavaEE - details: Java Web 开发的基础技术:Servlet、Jsp、Filter、Listener 等。 -footer: MIT Licensed | Copyright © 2018-present Dunwu +footer: CC-BY-SA-4.0 Licensed | Copyright © 2018-Now Dunwu --- -# JavaTech +# JAVATECH ![license](https://badgen.net/github/license/dunwu/javatech) @@ -54,7 +47,7 @@ footer: MIT Licensed | Copyright © 2018-present Dunwu > 如果想深入学习缓存,建议先了解一下 [缓存基本原理](https://github.com/dunwu/blog/blob/master/source/_posts/distributed/cache-theory.md),有助于理解缓存的特性、原理,使用缓存常见的问题及解决方案。 - [缓存面经](cache/CacheInterview.md) -- [Redis](cache/Redis.md) +- [Redis](https://github.com/dunwu/db-tutorial/tree/master/docs/nosql/redis) 📚 - [Ehcache](cache/Ehcache.md) - [Caffeine](cache/Caffeine.md) @@ -91,6 +84,7 @@ footer: MIT Licensed | Copyright © 2018-present Dunwu - [Junit](test/junit.md) - [Mockito](test/mockito.md) - [JMH](test/jmh.md) +- [Jmeter](test/jmeter.md) ## 服务器 diff --git a/docs/cache/README.md b/docs/cache/README.md index f8cbfc22..1d4526aa 100644 --- a/docs/cache/README.md +++ b/docs/cache/README.md @@ -1,6 +1,6 @@ # Java 和缓存 - [缓存面经](CacheInterview.md) -- [Redis](Redis.md) +- [Redis](https://github.com/dunwu/db-tutorial/tree/master/docs/nosql/redis) 📚 - [Ehcache](Ehcache.md) - [Caffeine](Caffeine.md) diff --git a/docs/cache/Redis.md b/docs/cache/Redis.md deleted file mode 100644 index 9cc8744e..00000000 --- a/docs/cache/Redis.md +++ /dev/null @@ -1,625 +0,0 @@ ---- -title: Redis -date: 2018-06-09 -categories: -- database -tags: -- database -- key-value -- cache ---- - -# Redis - - - -- [1. 概述](#1-概述) - - [1.1. Redis 简介](#11-redis-简介) - - [1.2. Redis 的优势](#12-redis-的优势) - - [1.3. Redis 与 Memcached](#13-redis-与-memcached) -- [2. 数据类型](#2-数据类型) - - [2.1. STRING](#21-string) - - [2.2. LIST](#22-list) - - [2.3. SET](#23-set) - - [2.4. HASH](#24-hash) - - [2.5. ZSET](#25-zset) -- [3. 使用场景](#3-使用场景) -- [4. Redis 管道](#4-redis-管道) -- [5. 键的过期时间](#5-键的过期时间) -- [6. 数据淘汰策略](#6-数据淘汰策略) -- [7. 持久化](#7-持久化) - - [7.1. 快照持久化](#71-快照持久化) - - [7.2. AOF 持久化](#72-aof-持久化) -- [8. 发布与订阅](#8-发布与订阅) -- [9. 事务](#9-事务) - - [9.1. EXEC](#91-exec) - - [9.2. MULTI](#92-multi) - - [9.3. DISCARD](#93-discard) - - [9.4. WATCH](#94-watch) -- [10. 事件](#10-事件) - - [10.1. 文件事件](#101-文件事件) - - [10.2. 时间事件](#102-时间事件) - - [10.3. 事件的调度与执行](#103-事件的调度与执行) -- [11. 集群](#11-集群) - - [11.1. 复制](#111-复制) - - [11.2. 哨兵](#112-哨兵) - - [11.3. 分片](#113-分片) -- [12. Redis Client](#12-redis-client) -- [13. 资料](#13-资料) - - - -## 1. 概述 - -### 1.1. Redis 简介 - -Redis 是速度非常快的非关系型(NoSQL)内存键值数据库,可以存储键和五种不同类型的值之间的映射。 - -键的类型只能为字符串,值支持的五种类型数据类型为:字符串、列表、集合、有序集合、散列表。 - -Redis 支持很多特性,例如将内存中的数据持久化到硬盘中,使用复制来扩展读性能,使用分片来扩展写性能。 - -### 1.2. Redis 的优势 - -- 性能极高 – Redis 能读的速度是 110000 次/s,写的速度是 81000 次/s。 -- 丰富的数据类型 - 支持字符串、列表、集合、有序集合、散列表。 -- 原子 - Redis 的所有操作都是原子性的。单个操作是原子性的。多个操作也支持事务,即原子性,通过 MULTI 和 EXEC 指令包起来。 -- 持久化 - Redis 支持数据的持久化。可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。 -- 备份 - Redis 支持数据的备份,即 master-slave 模式的数据备份。 -- 丰富的特性 - Redis 还支持发布订阅, 通知, key 过期等等特性。 - -### 1.3. Redis 与 Memcached - -Redis 与 Memcached 因为都可以用于缓存,所以常常被拿来做比较,二者主要有以下区别: - -**数据类型** - -- Memcached 仅支持字符串类型; -- 而 Redis 支持五种不同种类的数据类型,使得它可以更灵活地解决问题。 - -**数据持久化** - -- Memcached 不支持持久化; -- Redis 支持两种持久化策略:RDB 快照和 AOF 日志。 - -**分布式** - -- Memcached 不支持分布式,只能通过在客户端使用像一致性哈希这样的分布式算法来实现分布式存储,这种方式在存储和查询时都需要先在客户端计算一次数据所在的节点。 -- Redis Cluster 实现了分布式的支持。 - -**内存管理机制** - -- Memcached 将内存分割成特定长度的块来存储数据,以完全解决内存碎片的问题,但是这种方式会使得内存的利用率不高,例如块的大小为 128 bytes,只存储 100 bytes 的数据,那么剩下的 28 bytes 就浪费掉了。 -- 在 Redis 中,并不是所有数据都一直存储在内存中,可以将一些很久没用的 value 交换到磁盘。而 Memcached 的数据则会一直在内存中。 - -## 2. 数据类型 - -| 数据类型 | 可以存储的值 | 操作 | -| -------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------- | -| STRING | 字符串、整数或者浮点数 | 对整个字符串或者字符串的其中一部分执行操作
    对整数和浮点数执行自增或者自减操作 | -| LIST | 列表 | 从两端压入或者弹出元素
    读取单个或者多个元素
    进行修剪,只保留一个范围内的元素 | -| SET | 无序集合 | 添加、获取、移除单个元素
    检查一个元素是否存在于集合中
    计算交集、并集、差集
    从集合里面随机获取元素 | -| HASH | 包含键值对的无序散列表 | 添加、获取、移除单个键值对
    获取所有键值对
    检查某个键是否存在 | -| ZSET | 有序集合 | 添加、获取、删除元素
    根据分值范围或者成员来获取元素
    计算一个键的排名 | - -> [What Redis data structures look like](https://redislabs.com/ebook/part-1-getting-started/chapter-1-getting-to-know-redis/1-2-what-redis-data-structures-look-like/) - -### 2.1. STRING - -
    - -
    - -命令: - -| 命令 | 行为 | -| ---- | -------------------------------------------------- | -| GET | 获取存储在给定键中的值 | -| SET | 设置存储在给定键中的值 | -| DEL | 删除存储在给定键中的值(这个命令可以用于所有类型) | - -示例: - -```py -127.0.0.1:6379> set name jack -OK -127.0.0.1:6379> get name -"jack" -127.0.0.1:6379> del name -(integer) 1 -127.0.0.1:6379> get name -(nil) -``` - -### 2.2. LIST - -
    - -
    - -命令: - -| 命令 | 行为 | -| ------ | -------------------------------------------------- | -| RPUSH | 获取存储在给定键中的值 | -| LRANGE | 设置存储在给定键中的值 | -| LINDEX | 删除存储在给定键中的值(这个命令可以用于所有类型) | -| LPOP | 删除存储在给定键中的值(这个命令可以用于所有类型) | - -示例: - -```py -127.0.0.1:6379> rpush list item1 -(integer) 1 -127.0.0.1:6379> rpush list item2 -(integer) 2 -127.0.0.1:6379> rpush list item3 -(integer) 3 -127.0.0.1:6379> lrange list 0 -1 -1) "item1" -2) "item2" -3) "item3" -127.0.0.1:6379> lindex list 1 -"item2" -127.0.0.1:6379> lpop list -"item1" -127.0.0.1:6379> lrange list 0 -1 -1) "item2" -2) "item3" -``` - -### 2.3. SET - -
    - -
    - -命令: - -| 命令 | 行为 | -| --------- | -------------------------------- | -| SADD | 添加一个或多个元素到集合里 | -| SMEMBERS | 获取集合里面的所有元素 | -| SISMEMBER | 确定一个给定的值是一个集合的成员 | -| SREM | 从集合里删除一个或多个元素 | - -示例: - -```py -127.0.0.1:6379> sadd set item1 -(integer) 1 -127.0.0.1:6379> sadd set item2 -(integer) 1 -127.0.0.1:6379> sadd set item3 -(integer) 1 -127.0.0.1:6379> sadd set item3 -(integer) 0 - -127.0.0.1:6379> smembers set -1) "item3" -2) "item2" -3) "item1" - -127.0.0.1:6379> sismember set item2 -(integer) 1 -127.0.0.1:6379> sismember set item6 -(integer) 0 - -127.0.0.1:6379> srem set item2 -(integer) 1 -127.0.0.1:6379> srem set item2 -(integer) 0 - -127.0.0.1:6379> smembers set -1) "item3" -2) "item1" -``` - -### 2.4. HASH - -
    - -
    - -命令: - -| 命令 | 行为 | -| ------- | -------------------------- | -| HSET | 设置 hash 里面一个字段的值 | -| HGET | 获取 hash 中域的值 | -| HGETALL | 从 hash 中读取全部的域和值 | -| HDEL | 删除一个或多个域 | - -示例: - -```py -127.0.0.1:6379> hset myhash key1 value1 -(integer) 1 -127.0.0.1:6379> hset myhash key2 value2 -(integer) 1 -127.0.0.1:6379> hset myhash key3 value3 -(integer) 1 -127.0.0.1:6379> hset myhash key3 value2 -(integer) 0 - -127.0.0.1:6379> hgetall myhash -1) "key1" -2) "value1" -3) "key2" -4) "value2" -5) "key3" -6) "value2" - -127.0.0.1:6379> hdel myhash key2 -(integer) 1 -127.0.0.1:6379> hdel myhash key2 -(integer) 0 - -127.0.0.1:6379> hget myhash key2 -(nil) - -127.0.0.1:6379> hgetall myhash -1) "key1" -2) "value1" -3) "key3" -4) "value2" -127.0.0.1:6379> -``` - -### 2.5. ZSET - -
    - -
    - -命令: - -| 命令 | 行为 | -| ------------- | ------------------------------------------------------------- | -| ZADD | 添加到有序 set 的一个或多个成员,或更新的分数,如果它已经存在 | -| ZRANGE | 根据指定的 index 返回,返回 sorted set 的成员列表 | -| ZRANGEBYSCORE | 返回有序集合中指定分数区间内的成员,分数由低到高排序。 | -| ZREM | 从排序的集合中删除一个或多个成员 | - -示例: - -```py -127.0.0.1:6379> zadd zset 1 redis -(integer) 1 -127.0.0.1:6379> zadd zset 2 mongodb -(integer) 1 -127.0.0.1:6379> zadd zset 3 mysql -(integer) 1 -127.0.0.1:6379> zadd zset 3 mysql -(integer) 0 -127.0.0.1:6379> zadd zset 4 mysql -(integer) 0 - -127.0.0.1:6379> zrange zset 0 -1 withscores -1) "redis" -2) "1" -3) "mongodb" -4) "2" -5) "mysql" -6) "4" - -127.0.0.1:6379> zrangebyscore zset 0 2 withscores -1) "redis" -2) "1" -3) "mongodb" -4) "2" - -127.0.0.1:6379> zrem zset mysql -(integer) 1 -127.0.0.1:6379> zrange zset 0 -1 withscores -1) "redis" -2) "1" -3) "mongodb" -4) "2" -``` - -## 3. 使用场景 - -- **缓存** - 将热点数据放到内存中,设置内存的最大使用量以及过期淘汰策略来保证缓存的命中率。 -- **计数器** - Redis 这种内存数据库能支持计数器频繁的读写操作。 -- **应用限流** - 限制一个网站访问流量。 -- **消息队列** - 使用 List 数据类型,它是双向链表。 -- **查找表** - 使用 HASH 数据类型。 -- **交集运算** - 使用 SET 类型,例如求两个用户的共同好友。 -- **排行榜** - 使用 ZSET 数据类型。 -- **分布式 Session** - 多个应用服务器的 Session 都存储到 Redis 中来保证 Session 的一致性。 -- **分布式锁** - 除了可以使用 SETNX 实现分布式锁之外,还可以使用官方提供的 RedLock 分布式锁实现。 - -## 4. Redis 管道 - -Redis 是一种基于 C/S 模型以及请求/响应协议的 TCP 服务。 - -Redis 支持管道技术。管道技术允许请求以异步方式发送,即旧请求的应答还未返回的情况下,允许发送新请求。这种方式可以大大提高传输效率。 - -使用管道发送命令时,服务器将被迫回复一个队列答复,占用很多内存。所以,如果你需要发送大量的命令,最好是把他们按照合理数量分批次的处理。 - -## 5. 键的过期时间 - -Redis 可以为每个键设置过期时间,当键过期时,会自动删除该键。 - -对于散列表这种容器,只能为整个键设置过期时间(整个散列表),而不能为键里面的单个元素设置过期时间。 - -可以使用 `EXPIRE` 或 `EXPIREAT` 来为 key 设置过期时间。 - -> 🔔 注意:当 `EXPIRE` 的时间如果设置的是负数,`EXPIREAT` 设置的时间戳是过期时间,将直接删除 key。 - -示例: - -```py -redis> SET mykey "Hello" -"OK" -redis> EXPIRE mykey 10 -(integer) 1 -redis> TTL mykey -(integer) 10 -redis> SET mykey "Hello World" -"OK" -redis> TTL mykey -(integer) -1 -redis> -``` - -## 6. 数据淘汰策略 - -可以设置内存最大使用量,当内存使用量超过时施行淘汰策略,具体有 6 种淘汰策略。 - -| 策略 | 描述 | -| --------------- | ---------------------------------------------------- | -| volatile-lru | 从已设置过期时间的数据集中挑选最近最少使用的数据淘汰 | -| volatile-ttl | 从已设置过期时间的数据集中挑选将要过期的数据淘汰 | -| volatile-random | 从已设置过期时间的数据集中任意选择数据淘汰 | -| allkeys-lru | 从所有数据集中挑选最近最少使用的数据淘汰 | -| allkeys-random | 从所有数据集中任意选择数据进行淘汰 | -| noeviction | 禁止驱逐数据 | - -如果使用 Redis 来缓存数据时,要保证所有数据都是热点数据,可以将内存最大使用量设置为热点数据占用的内存量,然后启用 allkeys-lru 淘汰策略,将最近最少使用的数据淘汰。 - -作为内存数据库,出于对性能和内存消耗的考虑,Redis 的淘汰算法(LRU、TTL)实际实现上并非针对所有 key,而是抽样一小部分 key 从中选出被淘汰 key,抽样数量可通过 maxmemory-samples 配置。 - -## 7. 持久化 - -Redis 是内存型数据库,为了保证数据在断电后不会丢失,需要将内存中的数据持久化到硬盘上。 - -### 7.1. 快照持久化 - -将某个时间点的所有数据都存放到硬盘上。 - -可以将快照复制到其它服务器从而创建具有相同数据的服务器副本。 - -如果系统发生故障,将会丢失最后一次创建快照之后的数据。 - -如果数据量很大,保存快照的时间会很长。 - -### 7.2. AOF 持久化 - -将写命令添加到 AOF 文件(Append Only File)的末尾。 - -对硬盘的文件进行写入时,写入的内容首先会被存储到缓冲区,然后由操作系统决定什么时候将该内容同步到硬盘,用户可以调用 file.flush() 方法请求操作系统尽快将缓冲区存储的数据同步到硬盘。可以看出写入文件的数据不会立即同步到硬盘上,在将写命令添加到 AOF 文件时,要根据需求来保证何时同步到硬盘上。 - -有以下同步选项: - -| 选项 | 同步频率 | -| :------: | :----------------------: | -| always | 每个写命令都同步 | -| everysec | 每秒同步一次 | -| no | 让操作系统来决定何时同步 | - -- always 选项会严重减低服务器的性能; -- everysec 选项比较合适,可以保证系统奔溃时只会丢失一秒左右的数据,并且 Redis 每秒执行一次同步对服务器性能几乎没有任何影响; -- no 选项并不能给服务器性能带来多大的提升,而且也会增加系统奔溃时数据丢失的数量。 - -随着服务器写请求的增多,AOF 文件会越来越大。Redis 提供了一种将 AOF 重写的特性,能够去除 AOF 文件中的冗余写命令。 - -## 8. 发布与订阅 - -订阅者订阅了频道之后,发布者向频道发送字符串消息会被所有订阅者接收到。 - -某个客户端使用 SUBSCRIBE 订阅一个频道,其它客户端可以使用 PUBLISH 向这个频道发送消息。 - -发布与订阅模式和观察者模式有以下不同: - -- 观察者模式中,观察者和主题都知道对方的存在;而在发布与订阅模式中,发布者与订阅者不知道对方的存在,它们之间通过频道进行通信。 -- 观察者模式是同步的,当事件触发时,主题会去调用观察者的方法;而发布与订阅模式是异步的; - -## 9. 事务 - -MULTI 、 EXEC 、 DISCARD 和 WATCH 是 Redis 事务相关的命令。 - -事务可以一次执行多个命令, 并且有以下两个重要的保证: - -- 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。 -- 事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。 - -### 9.1. EXEC - -EXEC 命令负责触发并执行事务中的所有命令: - -- 如果客户端在使用 MULTI 开启了一个事务之后,却因为断线而没有成功执行 EXEC ,那么事务中的所有命令都不会被执行。 -- 另一方面,如果客户端成功在开启事务之后执行 EXEC ,那么事务中的所有命令都会被执行。 - -### 9.2. MULTI - -MULTI 命令用于开启一个事务,它总是返回 OK 。 MULTI 执行之后, 客户端可以继续向服务器发送任意多条命令, 这些命令不会立即被执行, 而是被放到一个队列中, 当 EXEC 命令被调用时, 所有队列中的命令才会被执行。 - -### 9.3. DISCARD - -当执行 DISCARD 命令时, 事务会被放弃, 事务队列会被清空, 并且客户端会从事务状态中退出。 - -示例: - -```py -> SET foo 1 -OK -> MULTI -OK -> INCR foo -QUEUED -> DISCARD -OK -> GET foo -"1" -``` - -### 9.4. WATCH - -WATCH 命令可以为 Redis 事务提供 check-and-set (CAS)行为。 - -被 WATCH 的键会被监视,并会发觉这些键是否被改动过了。 如果有至少一个被监视的键在 EXEC 执行之前被修改了, 那么整个事务都会被取消, EXEC 返回 nil-reply 来表示事务已经失败。 - -``` -WATCH mykey -val = GET mykey -val = val + 1 -MULTI -SET mykey $val -EXEC -``` - -使用上面的代码, 如果在 WATCH 执行之后, EXEC 执行之前, 有其他客户端修改了 mykey 的值, 那么当前客户端的事务就会失败。 程序需要做的, 就是不断重试这个操作, 直到没有发生碰撞为止。 - -这种形式的锁被称作乐观锁, 它是一种非常强大的锁机制。 并且因为大多数情况下, 不同的客户端会访问不同的键, 碰撞的情况一般都很少, 所以通常并不需要进行重试。 - -WATCH 使得 EXEC 命令需要有条件地执行:事务只能在所有被监视键都没有被修改的前提下执行,如果这个前提不能满足的话,事务就不会被执行。 - -WATCH 命令可以被调用多次。对键的监视从 WATCH 执行之后开始生效,直到调用 EXEC 为止。 - -用户还可以在单个 WATCH 命令中监视任意多个键,例如: - -```py -redis> WATCH key1 key2 key3 -OK -``` - -当 EXEC 被调用时, 不管事务是否成功执行, 对所有键的监视都会被取消。 - -另外, 当客户端断开连接时, 该客户端对键的监视也会被取消。 - -使用无参数的 UNWATCH 命令可以手动取消对所有键的监视。 对于一些需要改动多个键的事务, 有时候程序需要同时对多个键进行加锁, 然后检查这些键的当前值是否符合程序的要求。 当值达不到要求时, 就可以使用 UNWATCH 命令来取消目前对键的监视, 中途放弃这个事务, 并等待事务的下次尝试。 - -## 10. 事件 - -Redis 服务器是一个事件驱动程序。 - -### 10.1. 文件事件 - -服务器通过套接字与客户端或者其它服务器进行通信,文件事件就是对套接字操作的抽象。 - -Redis 基于 Reactor 模式开发了自己的网络时间处理器,使用 I/O 多路复用程序来同时监听多个套接字,并将到达的时间传送给文件事件分派器,分派器会根据套接字产生的事件类型调用响应的时间处理器。 - -### 10.2. 时间事件 - -服务器有一些操作需要在给定的时间点执行,时间事件是对这类定时操作的抽象。 - -时间事件又分为: - -- 定时事件:是让一段程序在指定的时间之内执行一次; -- 周期性事件:是让一段程序每隔指定时间就执行一次。 - -Redis 将所有时间事件都放在一个无序链表中,通过遍历整个链表查找出已到达的时间事件,并调用响应的事件处理器。 - -### 10.3. 事件的调度与执行 - -服务器需要不断监听文件事件的套接字才能得到待处理的文件事件,但是不能监听太久,否则时间事件无法在规定的时间内执行,因此监听时间应该根据距离现在最近的时间事件来决定。 - -事件调度与执行由 aeProcessEvents 函数负责,伪代码如下: - -```python -def aeProcessEvents(): - - ## 获取到达时间离当前时间最接近的时间事件 - time_event = aeSearchNearestTimer() - - ## 计算最接近的时间事件距离到达还有多少毫秒 - remaind_ms = time_event.when - unix_ts_now() - - ## 如果事件已到达,那么 remaind_ms 的值可能为负数,将它设为 0 - if remaind_ms < 0: - remaind_ms = 0 - - ## 根据 remaind_ms 的值,创建 timeval - timeval = create_timeval_with_ms(remaind_ms) - - ## 阻塞并等待文件事件产生,最大阻塞时间由传入的 timeval 决定 - aeApiPoll(timeval) - - ## 处理所有已产生的文件事件 - procesFileEvents() - - ## 处理所有已到达的时间事件 - processTimeEvents() -``` - -将 aeProcessEvents 函数置于一个循环里面,加上初始化和清理函数,就构成了 Redis 服务器的主函数,伪代码如下: - -```python -def main(): - - ## 初始化服务器 - init_server() - - ## 一直处理事件,直到服务器关闭为止 - while server_is_not_shutdown(): - aeProcessEvents() - - ## 服务器关闭,执行清理操作 - clean_server() -``` - -从事件处理的角度来看,服务器运行流程如下: - -## 11. 集群 - -### 11.1. 复制 - -通过使用 slaveof host port 命令来让一个服务器成为另一个服务器的从服务器。 - -一个从服务器只能有一个主服务器,并且不支持主主复制。 - -#### 12.1. 连接过程 - -1. 主服务器创建快照文件,发送给从服务器,并在发送期间使用缓冲区记录执行的写命令。快照文件发送完毕之后,开始向从服务器发送存储在缓冲区中的写命令; - -2. 从服务器丢弃所有旧数据,载入主服务器发来的快照文件,之后从服务器开始接受主服务器发来的写命令; - -3. 主服务器每执行一次写命令,就向从服务器发送相同的写命令。 - -#### 12.2. 主从链 - -随着负载不断上升,主服务器可能无法很快地更新所有从服务器,或者重新连接和重新同步从服务器将导致系统超载。为了解决这个问题,可以创建一个中间层来分担主服务器的复制工作。中间层的服务器是最上层服务器的从服务器,又是最下层服务器的主服务器。 - -### 11.2. 哨兵 - -Sentinel(哨兵)可以监听主服务器,并在主服务器进入下线状态时,自动从从服务器中选举出新的主服务器。 - -### 11.3. 分片 - -分片是将数据划分为多个部分的方法,可以将数据存储到多台机器里面,也可以从多台机器里面获取数据,这种方法在解决某些问题时可以获得线性级别的性能提升。 - -假设有 4 个 Reids 实例 R0,R1,R2,R3,还有很多表示用户的键 user:1,user:2,... 等等,有不同的方式来选择一个指定的键存储在哪个实例中。最简单的方式是范围分片,例如用户 id 从 0\~1000 的存储到实例 R0 中,用户 id 从 1001\~2000 的存储到实例 R1 中,等等。但是这样需要维护一张映射范围表,维护操作代价很高。还有一种方式是哈希分片,使用 CRC32 哈希函数将键转换为一个数字,再对实例数量求模就能知道应该存储的实例。 - -主要有三种分片方式: - -- 客户端分片:客户端使用一致性哈希等算法决定键应当分布到哪个节点。 -- 代理分片:将客户端请求发送到代理上,由代理转发请求到正确的节点上。 -- 服务器分片:Redis Cluster。 - -## 12. Redis Client - -Redis 社区中有多种编程语言的客户端,可以在这里查找合适的客户端:https://redis.io/clients - -redis 官方推荐的 Java Redis Client: - -- [jedis](https://github.com/xetorthio/jedis) -- [redisson](https://github.com/redisson/redisson) -- [lettuce](https://github.com/lettuce-io/lettuce-core) - -## 13. 资料 - -- [Redis 官网](https://redis.io/) -- [awesome-redis](https://github.com/JamzyWang/awesome-redis) -- [Redis 实战](https://item.jd.com/11791607.html) diff --git a/docs/storage/README.md b/docs/storage/README.md index 1d6ef2d2..65d63d85 100644 --- a/docs/storage/README.md +++ b/docs/storage/README.md @@ -2,11 +2,16 @@ ## 数据库存储 -### 分库分表中间件 +> 数据库教程已整理在: +> +> - 📚 [db-tutorial](https://dunwu.github.io/db-tutorial/) -- [shardingsphere](shardingsphere.md) +### 数据库中间件 -## 分布式文件存储 +- 分库分表 + - [shardingsphere](shardingsphere.md) + +## 文件存储 - [ ] HDFS - [ ] FastDFS diff --git a/docs/test/README.md b/docs/test/README.md index 7b075d01..5231265b 100644 --- a/docs/test/README.md +++ b/docs/test/README.md @@ -3,3 +3,4 @@ - [Junit](junit.md) - [Mockito](mockito.md) - [JMH](jmh.md) +- [Jmeter](jmeter.md) diff --git a/docs/test/jmeter.md b/docs/test/jmeter.md new file mode 100644 index 00000000..d5d7630d --- /dev/null +++ b/docs/test/jmeter.md @@ -0,0 +1,204 @@ +# JMeter 应用指南 + +> [Jmeter](https://github.com/apache/jmeter) 是一款基于 Java 开发的功能和性能测试软件。 +> +> 🎁 本文编辑时的最新版本为:5.1.1 + +## 1. 简介 + +[Jmeter](https://github.com/apache/jmeter) 是一款使用 Java 开发的功能和性能测试软件。 + +### 1.1. 特性 + +Jmeter 能够加载和性能测试许多不同的应用程序/服务器/协议类型: + +- 网络 - HTTP,HTTPS(Java,NodeJS,PHP,ASP.NET 等) +- SOAP / REST Web 服务 +- FTP 文件 +- 通过 JDBC 的数据库 +- LDAP +- 通过 JMS 的面向消息的中间件(MOM) +- 邮件-SMTP(S),POP3(S)和 IMAP(S) +- 本机命令或 Shell 脚本 +- TCP 协议 +- Java 对象 + +### 1.2. 工作流 + +Jmeter 的工作原理是仿真用户向服务器发送请求,并收集服务器应答信息并计算统计信息。 + +Jmeter 的工作流如下图所示: + +![](http://dunwu.test.upcdn.net/cs/java/javaweb/technology/test/jmeter-workflow.png!zp) + +### 1.3. 主要元素 + +Jmeter 的主要元素如下: + +- **`测试计划(Test Plan)`** - 可以将测试计划视为 JMeter 的测试脚本 。测试计划由测试元素组成,例如线程组,逻辑控制器,样本生成控制器,监听器,定时器,断言和配置元素。 +- **`线程组(Thread Group)`** - 线程组的作用是:模拟大量用户负载的运行场景。 + - 设置线程数 + - 设置加速期 + - 设置执行测试的次数 +- **`控制器(Controllers)`** - 可以分为两大类: + - **`采样器(Sampler)`** - 采样器的作用是模拟用户对目标服务器发送请求。 采样器是必须将组件添加到测试计划中的,因为它只能让 JMeter 知道需要将哪种类型的请求发送到服务器。 请求可以是 HTTP,HTTP(s),FTP,TCP,SMTP,SOAP 等。 + - **`逻辑控制器`** - 逻辑控制器的作用是:控制多个请求发送的循环次数及顺序等。 +- **`监听器(Listeners)`** - 监听器的作用是:收集测试结果信息。如查看结果树、汇总报告等。 +- **`计时器(Timers)`** - 计时器的作用是:控制多个请求发送的时间频次。 +- **`配置元素(Configuration Elements)`** - 配置元素的工作与采样器的工作类似。但是,它不发送请求,而是提供预备的数据等,如 CSV、函数助手。 +- **`预处理器元素(Pre-Processor Elements)`** - 预处理器元素在采样器发出请求之前执行,如果预处理器附加到采样器元素,那么它将在该采样器元素运行之前执行。预处理器元素用于在运行之前准备环境及参数。 +- **`后处理器元素(Post-Processor Elements)`** - 后处理器元素是在发送采样器请求之后执行的元素,常用于处理响应数据。 + +![](http://dunwu.test.upcdn.net/cs/java/javaweb/technology/test/jmeter-elements.png!zp) + +> 📌 提示: +> +> Jmeter 元素的数量关系大致如下: +> +> 1. 脚本中最多只能有一个测试计划。 +> 2. 测试计划中至少要有一个线程组。 +> 3. 线程组中至少要有一个取样器。 +> 4. 线程组中至少要有一个监听器。 + +## 2. 安装 + +### 2.1. 环境要求 + +- 必要的。Jmeter 基于 JDK8 开发,所以必须运行在 JDK8 环境。 + + - JDK8 + +- 可选的。有些 jar 包不是 Jmeter 提供的,如果需要相应的功能,需要自行下载并置于 `lib` 目录。 + - JDBC + - JMS + - [Bouncy Castle](http://www.bouncycastle.org/test_releases.html) + +### 2.2. 下载 + +进入 [**Jmeter 官网下载地址**](https://jmeter.apache.org/download_jmeter.cgi) 选择需要版本进行下载。 + +### 2.3. 启动 + +解压 Jmeter 压缩包,进入 bin 目录 + +Unix 类系统运行 `jmeter` ;Windows 系统运行 `jmeter.bat` + +![image-20191024104517721](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024104517721.png!zp) + +## 3. 使用 + +### 3.1. 创建测试计划 + +> 🔔 注意: +> +> - 在运行整个测试计划之前,应保存测试计划。 +> +> - JMeter 的测试计划以 `.jmx` 扩展文件的形式保存。 + +#### 3.1.1. 创建线程组 + +- 在“测试计划”上右键 【添加】=>【线程(用户)】=>【线程组】。 + +- 设置线程数和循环次数 + +![image-20191024105545736](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024105545736.png!zp) + +#### 3.1.2. 配置原件 + +- 在新建的线程组上右键 【添加】=>【配置元件】=>【HTTP 请求默认值】。 + +- 填写协议、服务器名称或 IP、端口号 + +![image-20191024110016264](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024110016264.png!zp) + +#### 3.1.3. 构造 HTTP 请求 + +- 在“线程组”上右键 【添加-】=>【取样器】=>【HTTP 请求】。 + +- 填写协议、服务器名称或 IP、端口号(如果配置了 HTTP 请求默认值可以忽略) +- 填写方法、路径 +- 填写参数、消息体数据、文件上传 + +![image-20191024110953063](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024110953063.png!zp) + +#### 3.1.4. 添加 HTTP 请求头 + +- 在“线程组”上右键 【添加】=>【配置元件】=>【HTTP 信息头管理器】 +- 由于我的测试例中传输的数据为 json 形式,所以设置键值对 `Content-Type`:`application/json` + +![image-20191024111825226](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024111825226.png!zp) + +#### 3.1.5. 添加断言 + +- 在“线程组”上右键 【添加】=>【断言】=>【 响应断言 】 +- 在我的案例中,以 HTTP 应答状态码为 200 来判断请求是否成功 + +![image-20191024112335130](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024112335130.png!zp) + +#### 3.1.6. 添加察看结果树 + +- 在“线程组”上右键 【添加】=>【监听器】=>【察看结果树】 +- 直接点击运行,就可以查看测试结果 + +![image-20191024113849270](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024113849270.png!zp) + +#### 3.1.7. 添加汇总报告 + +- 在“线程组”上右键 【添加】=>【监听器】=>【汇总报告】 +- 直接点击运行,就可以查看测试结果 + +![image-20191024114016424](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024114016424.png!zp) + +#### 3.1.8. 保存测试计划 + +执行测试计划前,GUI 会提示先保存配置为 `jmx` 文件。 + +### 3.2. 执行测试计划 + +官方建议不要直接使用 GUI 来执行测试计划,这种模式指适用于创建测试计划和 debug。 + +执行测试计划应该使用命令行模式,语法形式如下: + +```bash +jmeter -n -t [jmx file] -l [results file] -e -o [Path to web report folder] +``` + +执行测试计划后,在 `-e -o` 参数后指定的 web 报告目录下,可以找到测试报告内容。在浏览器中打开 `index.html` 文件,可以看到如下报告: + +![image-20191024120233058](http://dunwu.test.upcdn.net/snap/jmeter/image-20191024120233058.png!zp) + +## 4. 问题 + +### 4.1. 如何读取本地 txt/csv 文件作为请求参数 + +参考:[Jmeter 读取本地 txt/csv 文件作为请求参数,实现接口自动化](https://www.jianshu.com/p/3b2d3b643415) + +(1)依次点击【添加】=>【配置元件】=>【CSV 数据文件设置】 + +配置如下所示: + +![image-20191127175820747](http://dunwu.test.upcdn.net/snap/image-20191127175820747.png) + +重要配置说明(其他配置根据实际情况填): + +- 文件名:输入需要导入的数据文件位置。 +- 文件编码:设为 UTF-8,避免乱码。 +- 变量名称:使用 `,` 分隔输入变量列表。如截图中设置了两个变量 `a` 和 `b` + +(2)在 HTTP 请求的消息体数据中配置参数 + +``` +[{"a":"${a}","b":"${b}"}] +``` + +### 4.2. 如何有序发送数据 + +依次点击【添加】=>【逻辑控制器】=>【事务控制器】 + +## 5. 参考资料 + +- [Jmeter 官网](https://jmeter.apache.org/) +- [Jmeter Github](https://github.com/apache/jmeter) +- [Jmeter 性能测试入门](https://www.cnblogs.com/TankXiao/p/4045439.html) +- [易百教程 - Jmeter 教程](https://www.yiibai.com/jmeter) +- [Jmeter 读取本地 txt/csv 文件作为请求参数,实现接口自动化](https://www.jianshu.com/p/3b2d3b643415) diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100644 index 00000000..62f72c96 --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env sh + +# ------------------------------------------------------------------------------ +# gh-pages 部署脚本 +# @author Zhang Peng +# @since 2020/2/10 +# ------------------------------------------------------------------------------ + +# 装载其它库 +ROOT_DIR=$(cd `dirname $0`/..; pwd) + +# 确保脚本抛出遇到的错误 +set -e + +cd ${ROOT_DIR}/docs + +# 生成静态文件 +npm install +npm run build + +# 进入生成的文件夹 +cd dist + +# 如果是发布到自定义域名 +# echo 'www.example.com' > CNAME + +git init +git checkout -b gh-pages && git add . +git commit -m 'deploy' + +# 如果发布到 https://.github.io/ +if [[ ${GITHUB_TOKEN} && ${GITEE_TOKEN} ]]; then + echo "使用 token 公钥部署 gh-pages" + # ${GITHUB_TOKEN} 是 Github 私人令牌;${GITEE_TOKEN} 是 Gitee 私人令牌 + # ${GITHUB_TOKEN} 和 ${GITEE_TOKEN} 都是环境变量;travis-ci 构建时会传入变量 + git push --force --quiet "https://dunwu:${GITHUB_TOKEN}@github.com/dunwu/javatech.git" gh-pages + git push --force --quiet "https://turnon:${GITEE_TOKEN}@gitee.com/turnon/javatech.git" gh-pages +else + echo "使用 ssh 公钥部署 gh-pages" + git push -f git@github.com:dunwu/javatech.git gh-pages + git push -f git@gitee.com:turnon/javatech.git gh-pages +fi + +cd ${ROOT_DIR} From 29e22b29696f42938a43cab326890bfa34aca106 Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Fri, 14 Feb 2020 18:45:52 +0800 Subject: [PATCH 58/75] update docs --- README.md | 13 +- docs/README.md | 12 +- docs/{other => lib}/README.md | 10 +- docs/{io => lib/bean}/dozer.md | 0 docs/{other => lib/bean}/lombok.md | 0 docs/{io => lib}/javalib-log.md | 0 docs/{other => lib}/javalib-util.md | 0 docs/{other => lib}/javamail.md | 0 docs/{other => lib}/jsoup.md | 0 docs/{other => lib}/reflections.md | 0 docs/lib/serialized/README.md | 23 + docs/{io => lib/serialized}/javalib-binary.md | 0 docs/{io => lib/serialized}/javalib-json.md | 0 docs/lib/template/README.md | 43 + docs/{other => lib/template}/freemark.md | 157 +- docs/lib/template/thymeleaf.md | 4912 +++++++++++++++++ docs/lib/template/velocity.md | 316 ++ docs/{other => lib}/thumbnailator.md | 0 docs/{other => lib}/zxing.md | 0 19 files changed, 5355 insertions(+), 131 deletions(-) rename docs/{other => lib}/README.md (60%) rename docs/{io => lib/bean}/dozer.md (100%) rename docs/{other => lib/bean}/lombok.md (100%) rename docs/{io => lib}/javalib-log.md (100%) rename docs/{other => lib}/javalib-util.md (100%) rename docs/{other => lib}/javamail.md (100%) rename docs/{other => lib}/jsoup.md (100%) rename docs/{other => lib}/reflections.md (100%) create mode 100644 docs/lib/serialized/README.md rename docs/{io => lib/serialized}/javalib-binary.md (100%) rename docs/{io => lib/serialized}/javalib-json.md (100%) create mode 100644 docs/lib/template/README.md rename docs/{other => lib/template}/freemark.md (50%) create mode 100644 docs/lib/template/thymeleaf.md create mode 100644 docs/lib/template/velocity.md rename docs/{other => lib}/thumbnailator.md (100%) rename docs/{other => lib}/zxing.md (100%) diff --git a/README.md b/README.md index 04a54860..2262a7d5 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,6 @@

    JAVATECH

    - > ☕ **javatech** 汇总了 Java 开发中常见的主流技术的应用、特性、原理。 > > - 🔁 项目同步维护:[Github](https://github.com/dunwu/javatech/) | [Gitee](https://gitee.com/turnon/javatech/) @@ -17,8 +16,6 @@ > > 说明:下面的内容清单中,凡是有 📚 标记的技术,都已整理成详细的教程。 -## Java 生态 - ## 框架 - [Spring](https://dunwu.github.io/spring-tutorial/) 📚 @@ -94,3 +91,13 @@ - [Tomcat](docs/server/tomcat.md) - [Jetty](docs/server/jetty.md) - [Nginx](https://github.com/dunwu/nginx-tutorial) 📚 + +## LIB + +- [日志](docs/lib/javalib-log.md) - log4j2、logback、log4j、Slf4j +- [序列化](docs/lib/serialized/) + - [JSON](docs/lib/serialized/javalib-json.md) - Fastjson、Jackson、Gson + - [二进制](docs/lib/serialized/javalib-binary.md) - Protobuf、Thrift、Hessian、Kryo、FST +- [模板引擎](docs/lib/template) - [Freemark](docs/lib/template/freemark.md)、[Velocity](docs/lib/template/velocity.md)、[Thymeleaf](docs/lib/template/thymeleaf.md) +- JavaBean - [Lombok](docs/lib/bean/lombok.md)、[Dozer](docs/lib/bean/dozer.md) +- 工具包 - Apache Common、Guava、Hutool diff --git a/docs/README.md b/docs/README.md index 41eec51e..3d67b3e3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -18,8 +18,6 @@ footer: CC-BY-SA-4.0 Licensed | Copyright © 2018-Now Dunwu > > 说明:下面的内容清单中,凡是有 📚 标记的技术,都已整理成详细的教程。 -## Java 生态 - ## 框架 - [Spring](https://dunwu.github.io/spring-tutorial/) 📚 @@ -95,3 +93,13 @@ footer: CC-BY-SA-4.0 Licensed | Copyright © 2018-Now Dunwu - [Tomcat](server/tomcat.md) - [Jetty](server/jetty.md) - [Nginx](https://github.com/dunwu/nginx-tutorial) 📚 + +## LIB + +- [日志](lib/javalib-log.md) - log4j2、logback、log4j、Slf4j +- [序列化](lib/serialized/) + - [JSON](lib/serialized/javalib-json.md) - Fastjson、Jackson、Gson + - [二进制](lib/serialized/javalib-binary.md) - Protobuf、Thrift、Hessian、Kryo、FST +- [模板引擎](lib/template) - [Freemark](lib/template/freemark.md)、[Velocity](lib/template/velocity.md)、[Thymeleaf](lib/template/thymeleaf.md) +- JavaBean - [Lombok](lib/bean/lombok.md)、[Dozer](lib/bean/dozer.md) +- 工具包 - Apache Common、Guava、Hutool diff --git a/docs/other/README.md b/docs/lib/README.md similarity index 60% rename from docs/other/README.md rename to docs/lib/README.md index 00f7bce5..c41c643c 100644 --- a/docs/other/README.md +++ b/docs/lib/README.md @@ -1,14 +1,14 @@ # Java 库 -- [Dozer 应用指南](../io/dozer.md) -- [Freemark 应用指南](freemark.md) -- [Java 与 JSON](../io/javalib-json.md) -- [细说 Java 主流日志工具库](../io/javalib-log.md) +- [Dozer 应用指南](bean/dozer.md) +- [Freemark 应用指南](template/freemark.md) +- [Java 与 JSON](serialized/javalib-json.md) +- [细说 Java 主流日志工具库](javalib-log.md) - [细说 Java 主流工具包](javalib-util.md) - [JavaMail 应用指南](javamail.md) - [Jsoup 应用指南](jsoup.md) - [JUnit5 应用指南](../test/junit.md) -- [Lombok 应用指南](lombok.md) +- [Lombok 应用指南](bean/lombok.md) - [Mockito 应用指南](../test/mockito.md) - [Reflections 应用指南](reflections.md) - [Thumbnailator 应用指南](thumbnailator.md) diff --git a/docs/io/dozer.md b/docs/lib/bean/dozer.md similarity index 100% rename from docs/io/dozer.md rename to docs/lib/bean/dozer.md diff --git a/docs/other/lombok.md b/docs/lib/bean/lombok.md similarity index 100% rename from docs/other/lombok.md rename to docs/lib/bean/lombok.md diff --git a/docs/io/javalib-log.md b/docs/lib/javalib-log.md similarity index 100% rename from docs/io/javalib-log.md rename to docs/lib/javalib-log.md diff --git a/docs/other/javalib-util.md b/docs/lib/javalib-util.md similarity index 100% rename from docs/other/javalib-util.md rename to docs/lib/javalib-util.md diff --git a/docs/other/javamail.md b/docs/lib/javamail.md similarity index 100% rename from docs/other/javamail.md rename to docs/lib/javamail.md diff --git a/docs/other/jsoup.md b/docs/lib/jsoup.md similarity index 100% rename from docs/other/jsoup.md rename to docs/lib/jsoup.md diff --git a/docs/other/reflections.md b/docs/lib/reflections.md similarity index 100% rename from docs/other/reflections.md rename to docs/lib/reflections.md diff --git a/docs/lib/serialized/README.md b/docs/lib/serialized/README.md new file mode 100644 index 00000000..536e1c45 --- /dev/null +++ b/docs/lib/serialized/README.md @@ -0,0 +1,23 @@ +# Java 序列化库 + +Java 官方的序列化存在许多问题,因此,很多人更愿意使用优秀的第三方序列化工具来替代 Java 自身的序列化机制。 如果想详细了解 Java 自身序列化方式,可以参考:[深入理解 Java 序列化](https://github.com/dunwu/javacore/blob/master/docs/io/java-serialization.md) + +序列化库技术选型: + +- [thrift](https://github.com/apache/thrift)、[protobuf](https://github.com/protocolbuffers/protobuf) - 适用于对性能敏感,对开发体验要求不高的内部系统。 +- [hessian](http://hessian.caucho.com/doc/hessian-overview.xtp) - 适用于对开发体验敏感,性能有要求的内外部系统。 +- [jackson](https://github.com/FasterXML/jackson)、[gson](https://github.com/google/gson)、[fastjson](https://github.com/alibaba/fastjson) - 适用于对序列化后的数据要求有良好的可读性(转为 json 、xml 形式)。 + +## 内容 + + - [JSON](docs/lib/serialized/javalib-json.md) - Fastjson、Jackson、Gson + - [二进制](javatech/docs/lib/serialized/javalib-binary.md) - Protobuf、Thrift、Hessian、Kryo、FST + +## 资料 + +- [Thrift Github](https://github.com/apache/thrift) +- [Protobuf Github](https://github.com/protocolbuffers/protobuf) +- [Hessian 官网](http://hessian.caucho.com/doc/hessian-overview.xtp) +- [Fastjson Github](https://github.com/alibaba/fastjson) +- [Jackson Github](https://github.com/FasterXML/jackson) +- [Gson Github](https://github.com/google/gson) \ No newline at end of file diff --git a/docs/io/javalib-binary.md b/docs/lib/serialized/javalib-binary.md similarity index 100% rename from docs/io/javalib-binary.md rename to docs/lib/serialized/javalib-binary.md diff --git a/docs/io/javalib-json.md b/docs/lib/serialized/javalib-json.md similarity index 100% rename from docs/io/javalib-json.md rename to docs/lib/serialized/javalib-json.md diff --git a/docs/lib/template/README.md b/docs/lib/template/README.md new file mode 100644 index 00000000..4cad92b7 --- /dev/null +++ b/docs/lib/template/README.md @@ -0,0 +1,43 @@ +# 模板引擎 + +模板引擎不属于特定技术领域,它是跨领域跨平台的概念。 模板引擎的作用就是分离业务数据和最终呈现内容,它可以生成特定格式的文档(模板) 。 + +模板引擎简单来说,就是:***`模板 + 数据模型 = 输出`*** + +较早,也比较经典的模板引擎是 JavaEE 的标准技术 JSP。 + +但 JSP 存在以下缺点,导致逐渐被淘汰: + +- **性能差** + - JSP 本质上是 Servlet,第一次请求 JSP 页面,必须要在 web 服务器中编译成 servlet,所以第一次响应较慢。 + - 每次请求 JSP 都是访问servlet再用输出流输出的html页面。 + - JSP中的内容很多,页面响应会很慢,因为是同步加载。 +- **无法前后端分离** + - 动态资源和静态资源全部耦合在一起,无法做到前后端分离。一旦服务器出现状况,前后台一起玩完。 + - 而且 Java 工程师既当爹又当妈,又要维护 Java 代码,又要维护 JSP 代码,痛苦。 + - 前端工程师如果不理解 JSP 语法,面对各种 JSP 标签、表达式、指令,会一脸懵逼,痛苦。 +- **不是所有服务器都支持** - JSP 必须要在支持 JSP 技术的 web 服务器里运行(如 Tomcat)。但有些服务器则不支持 JSP ,如 Nginx。 + +在 Java 领域,目前最常见的模板引擎就是: + +- Freemark +- Thymeleaf +- Velocity + +## 内容 + +- [Freemark](freemark.md) +- [Thymeleaf](thymeleaf.md) +- [Velocity](velocity.md) + +## 资源 + +- **Freemark** + - [Freemark Github](https://github.com/apache/freemarker/) + - [Freemark 中文教程](http://freemarker.foofun.cn/) + - [在线 Freemark 工具](https://try.freemarker.apache.org/) +- **Velocity** + - [Velocity Github](https://github.com/apache/velocity-engine/) + - [Velocity 官网](https://velocity.apache.org/) + - [Velocity 中文文档](https://wizardforcel.gitbooks.io/velocity-doc/content/) + - [velocity-spring-boot-project](https://github.com/alibaba/velocity-spring-boot-project) diff --git a/docs/other/freemark.md b/docs/lib/template/freemark.md similarity index 50% rename from docs/other/freemark.md rename to docs/lib/template/freemark.md index aa631293..86a8f63c 100644 --- a/docs/other/freemark.md +++ b/docs/lib/template/freemark.md @@ -1,148 +1,61 @@ -# Freemark 应用指南 +# Freemark Cheat Sheet -> FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML 网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个 Java 类库,是一款程序员可以嵌入他们所开发产品的组件。 +> FreeMarker 是一款模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML 网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个 Java 类库,是一款程序员可以嵌入他们所开发产品的组件。 - +## Freemark 简介 -- [简介](#简介) - - [什么是 Freemark?](#什么是-freemark) -- [入门](#入门) - - [创建 Configuration 实例](#创建-configuration-实例) - - [创建数据模型](#创建数据模型) - - [获取模板](#获取模板) - - [合并模板和数据模型](#合并模板和数据模型) - - [完整示例](#完整示例) -- [基础](#基础) - - [数值](#数值) - - [类型](#类型) - - [模板](#模板) -- [参考资料](#参考资料) - - - -## 简介 - -### 什么是 Freemark? - -FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML 网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个 Java 类库,是一款程序员可以嵌入他们所开发产品的组件。 - -简言之:模板 + 数据 = 输出 +Freemark 模板编写为 FreeMarker Template Language (FTL)。它是简单的,专用的语言, _不是_ 像 PHP 那样成熟的编程语言。在模板中,你可以专注于如何展现数据, 而在模板之外可以专注于要展示什么数据。 ![img](http://freemarker.foofun.cn/figures/overview.png) -## 入门 +这种方式通常被称为 [MVC (模型 视图 控制器) 模式](http://freemarker.foofun.cn/gloss.html#gloss.MVC),对于动态网页来说,是一种特别流行的模式。 它帮助从开发人员(Java 程序员)中分离出网页设计师(HTML 设计师)。设计师无需面对模板中的复杂逻辑, 在没有程序员来修改或重新编译代码时,也可以修改页面的样式。 -### 创建 Configuration 实例 +Freemark 模板一句话概括就是:***`模板 + 数据模型 = 输出`*** -首先,你应该创建一个 `freemarker.template.Configuration` 实例, 然后调整它的设置。Configuration 实例是存储 FreeMarker 应用级设置的核心部分。同时,它也处理创建和缓存预解析模板(比如 Template 对象)的工作。 +## 数据类型 -```java -// Create your Configuration instance, and specify if up to what FreeMarker -// version (here 2.3.22) do you want to apply the fixes that are not 100% -// backward-compatible. See the Configuration JavaDoc for details. -Configuration cfg = new Configuration(Configuration.VERSION_2_3_22); +Freemark 支持的类型有: -// Specify the source where the template files come from. Here I set a -// plain directory for it, but non-file-system sources are possible too: -cfg.setDirectoryForTemplateLoading(new File("/where/you/store/templates")); +### 标量 -// Set the preferred charset template files are stored in. UTF-8 is -// a good choice in most applications: -cfg.setDefaultEncoding("UTF-8"); +字符串 -// Sets how errors will appear. -// During web page *development* TemplateExceptionHandler.HTML_DEBUG_HANDLER is better. -cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); ``` - -> 注:不需要重复创建 Configuration 实例;它的代价很高,尤其是会丢失缓存。Configuration 实例就是应用级别的单例。 - -### 创建数据模型 - -在简单的示例中你可以使用 `java.lang` 和 `java.util` 包中的类, 还有用户自定义的 Java Bean 来构建数据对象: - -- 使用 `java.lang.String` 来构建字符串。 -- 使用 `java.lang.Number` 来派生数字类型。 -- 使用 `java.lang.Boolean` 来构建布尔值。 -- 使用 `java.util.List` 或 Java 数组来构建序列。 -- 使用 `java.util.Map` 来构建哈希表。 -- 使用自定义的 bean 类来构建哈希表,bean 中的项和 bean 的属性对应。比如, `product` 的 `price` 属性 (`getProperty()`)可以通过 `product.price` 获取。(bean 的 action 也可以通过这种方式拿到; 要了解更多可以参看 [这里](http://freemarker.foofun.cn/pgui_misc_beanwrapper.html)) - -示例: - -```java -// Create the root hash -Map root = new HashMap<>(); -// Put string ``user'' into the root -root.put("user", "Big Joe"); -// Create the hash for ``latestProduct'' -Map latest = new HashMap<>(); -// and put it into the root -root.put("latestProduct", latest); -// put ``url'' and ``name'' into latest -latest.put("url", "products/greenmouse.html"); -latest.put("name", "green mouse"); +${"Hello ${user}"} +${"I can escape with \\ ${user}"} +${r"Now I can read dollar signs $"} ``` -### 获取模板 +输出: -模板代表了 `freemarker.template.Template` 实例。典型的做法是从 `Configuration` 实例中获取一个 `Template` 实例。无论什么时候你需要一个模板实例, 都可以使用它的 `getTemplate` 方法来获取。在 [之前](http://freemarker.foofun.cn/pgui_quickstart_createconfiguration.html) 设置的目录中的 `test.ftl` 文件中存储 [示例模板](http://freemarker.foofun.cn/dgui_quickstart_basics.html#example.first),那么就可以这样来做: - -```java -Template temp = cfg.getTemplate("test.ftl"); ``` - -当调用这个方法的时候,将会创建一个 `test.ftl` 的 `Template` 实例,通过读取 `*/where/you/store/templates/*test.ftl` 文件,之后解析(编译)它。`Template` 实例以解析后的形式存储模板, 而不是以源文件的文本形式。 - -`Configuration` 缓存 `Template` 实例,当再次获得 `test.ftl` 的时候,它可能再读取和解析模板文件了, 而只是返回第一次的 `Template`实例。 - -### 合并模板和数据模型 - -我们已经知道,数据模型+模板=输出,我们有了一个数据模型 (`root`) 和一个模板 (`temp`), 为了得到输出就需要合并它们。这是由模板的 `process` 方法完成的。它用数据模型 root 和 `Writer` 对象作为参数,然后向 `Writer` 对象写入产生的内容。 为简单起见,这里我们只做标准的输出: - +Hello deister +I can escape with \ deister +Now I can read dollar signs $ ``` -Writer out = new OutputStreamWriter(System.out); -temp.process(root, out); -``` - -这会向你的终端输出你在模板开发指南部分的 [第一个示例](http://freemarker.foofun.cn/dgui_quickstart_basics.html#example.first) 中看到的内容。 -Java I/O 相关注意事项:基于 `out` 对象,必须保证 `out.close()` 最后被调用。当 `out` 对象被打开并将模板的输出写入文件时,这是很电影的做法。其它时候, 比如典型的 Web 应用程序,那就 _不能_ 关闭 `out` 对象。FreeMarker 会在模板执行成功后 (也可以在 `Configuration` 中禁用) 调用 `out.flush()`,所以不必为此担心。 +数字 -请注意,一旦获得了 `Template` 实例, 就能将它和不同的数据模型进行不限次数 (`Template`实例是无状态的)的合并。此外, 当 `Template`实例创建之后 `test.ftl` 文件才能访问,而不是在调用处理方法时。 +布尔值 -### 完整示例 +日期/时间 (日期,时间或日期时间) -[**源码**](https://github.com/dunwu/spring-boot-tutorial/tree/master/codes/template/sbe-tmpl-freemark) +### 容器 -## 基础 +- 哈希表 +- 序列 +- 集合 -### 数值 +### 子程序 -注意观察每个数据模型的例子你也许能发现:被"(root)"所标识的内容就是哈希表类型的值。 当编写如 `user` 这样的代码时,那就意味着要把"user"变量存储在哈希表的根上。 就像编写 `root.user`一样,这里但并没有名"root"为的变量, 那么这就起不到任何作用了。 +- [方法和函数](http://freemarker.foofun.cn/dgui_datamodel_types.html#dgui_datamodel_method) +- [用户自定义指令](http://freemarker.foofun.cn/dgui_datamodel_types.html#dgui_datamodel_userdefdir) -某些人也许会被这种数据模型的例子所困惑,也就是说,根哈希表包含更多的哈希表或序列 (`lotteryNumbers` and `cargo`)。其它就没有更特殊的内容了。 哈希表包含其他变量,那些变量包含其它值,这些数值可以是字符串,数字等变量, 当然也可以是哈希表或序列变量。最初我们解释过的,就像字符串和数字, 序列或哈希表也是一种值的表示形式。 +### 其它 -### 类型 - -Freemark 支持的类型有: +- [结点](http://freemarker.foofun.cn/dgui_datamodel_types.html#dgui_datamodel_node) -- 标量: - - 字符串 - - 数字 - - 布尔值 - - 日期/时间 (日期,时间或日期时间) -- 容器: - - 哈希表 - - 序列 - - 集合 -- 子程序: - - [方法和函数](http://freemarker.foofun.cn/dgui_datamodel_types.html#dgui_datamodel_method) - - [用户自定义指令](http://freemarker.foofun.cn/dgui_datamodel_types.html#dgui_datamodel_userdefdir) -- 其它/很少使用: - - [结点](http://freemarker.foofun.cn/dgui_datamodel_types.html#dgui_datamodel_node) - -### 模板 +## 模板 #### 总体结构 @@ -153,7 +66,7 @@ Freemark 支持的类型有: - **FTL 标签**:FTL 标签和 HTML 标签很相似,但是它们却是给 FreeMarker 的指示, 而且不会打印在输出内容中。 - **注释**:注释和 HTML 的注释也很相似,但它们是由 `<#--` 和 `-->`来分隔的。注释会被 FreeMarker 直接忽略, 更不会在输出内容中显示。 -![img](http://dunwu.test.upcdn.net/snap/ftl-template.png!zp) +![img](http://dunwu.test.upcdn.net/snap/ftl-template.png) > 🔔 注意: > @@ -168,8 +81,8 @@ Freemark 支持的类型有: FTL 标签分为两种: -- 开始标签: `<#*directivename* *parameters*>` -- 结束标签: `` +- 开始标签: `<#directivename parameters>` +- 结束标签: `` 除了标签以 `#` 开头外,其他都和 HTML,XML 的语法很相似。 如果标签没有嵌套内容(在开始标签和结束标签之间的内容),那么可以只使用开始标签。 例如 `<#if *something*>*...*`, 而 FreeMarker 知道 `<#include *something*>` 中的 `include` 指令没有可嵌套的内容。 @@ -237,4 +150,6 @@ FTL 标签分为两种: ## 参考资料 -- http://freemarker.foofun.cn/ +- [Freemark Github](https://github.com/apache/freemarker) +- [Freemark 中文教程](http://freemarker.foofun.cn/) +- [在线 Freemark 工具](https://try.freemarker.apache.org/) diff --git a/docs/lib/template/thymeleaf.md b/docs/lib/template/thymeleaf.md new file mode 100644 index 00000000..ccb9c6d2 --- /dev/null +++ b/docs/lib/template/thymeleaf.md @@ -0,0 +1,4912 @@ +# Thymeleaf Cheat Sheet + +## 简介 + +### 什么是 Thymeleaf + +Thymeleaf 是一个用于 web 和独立环境的现代 server-side Java 模板引擎,能够处理 HTML,XML,JavaScript,CSS 甚至纯文本。 + +Thymeleaf 的主要目标是提供一种优雅的 highly-maintainable 方式的 creating 模板。为了实现这一目标,它以自然模板的概念为基础,将其逻辑注入模板 files,其方式不会影响模板被用作设计原型。这改善了设计沟通,缩小了设计和开发团队之间的差距。 + +Thymeleaf 也从一开始就设计了 Web 标准 - 特别是**HTML5** - 允许您创建完全验证的模板,如果这是您的需要。 + +### Thymeleaf process 可以使用哪种模板 + +Out-of-the-box,Thymeleaf 允许您处理六种模板,每种模板称为**模板模式**: + +- HTML +- XML +- 文本 +- JAVASCRIPT +- CSS +- 生 + +有两种标记模板模式(`HTML`和`XML`),三种文本模板模式(`TEXT`,`JAVASCRIPT`和`CSS`)和 no-op 模板模式(`RAW`)。 + +**HTML**模板模式将允许任何类型的 HTML 输入,包括 HTML5,HTML 4 和 XHTML。不会执行验证或 well-formedness 检查,并且模板 code/structure 将在输出中尽可能地受到尊重。 + +**XML**模板模式将允许 XML 输入。在这种情况下,code 应该是 well-formed - 没有未关闭的标签,没有不带引号的属性等 - 如果找到 well-formedness 违规,解析器将抛出 exceptions。请注意,不会执行任何验证(针对 DTD 或 XML Schema)。 + +**TEXT**模板模式允许对 non-markup 性质的模板使用特殊语法。此类模板的示例可能是文本电子邮件或模板文档。请注意,HTML 或 XML 模板也可以作为`TEXT`处理,在这种情况下,它们不会被解析为标记,并且每个标记,DOCTYPE,comment 等都将被视为纯文本。 + +**JAVASCRIPT**模板模式将允许在 Thymeleaf application 中处理 JavaScript files。这意味着能够在 JavaScript files 中使用 model 数据,就像在 HTML files 中一样,但是使用 JavaScript-specific 集成,例如专门的转义或自然脚本。 `JAVASCRIPT`模板模式被视为文本模式,因此使用与`TEXT`模板模式相同的特殊语法。 + +**CSS**模板模式将允许处理 Thymeleaf application 中涉及的 CSS files。与`JAVASCRIPT`模式类似,`CSS`模板模式也是文本模式,并使用`TEXT`模板模式中的特殊处理语法。 + +**RAW**模板模式根本不会处理模板。它用于插入未经处理的资源(files,URL 响应,etc.)到正在处理的模板中。例如,HTML 格式的外部不受控制的资源可以包含在 application 模板中,安全地知道任何 Thymeleaf code 这些资源可能会包含将不会被执行。 + +### 方言:标准方言 + +Thymeleaf 是一个极其可扩展的模板引擎(实际上它可以称为模板引擎 framework),它允许您定义和自定义模板处理细节级别的方式。 + +将一些逻辑应用于标记 artifact(标记,某些文本,comment 或仅仅是占位符,如果模板不是标记)的 object 称为处理器,这些处理器的集合 - 加上可能还有一些额外的 artifacts - 是什么**方言**通常由...组成。开箱即用,Thymeleaf 的核心 library 提供了一种名为**标准方言**的方言,对大多数用户来说应该足够了。 + +> 请注意,方言实际上可以没有处理器,并且完全由其他类型的 artifacts 组成,但处理器绝对是最常见的用例。 + +本教程涵盖标准方言。您将在以下页面中了解的每个属性和语法 feature 都由此方言定义,即使未明确提及。 + +当然,如果用户希望在利用 library 的高级 features 的同时定义自己的处理逻辑,则可以创建自己的方言(甚至扩展标准方言)。 Thymeleaf 也可以配置为在 time 使用多种方言。 + +> 官方的 thymeleaf-spring3 和 thymeleaf-spring4 integration 软件包都定义了一种名为“SpringStandard Dialect”的方言,它与标准方言大致相同,但是在 Spring Framework 中更好地利用了一些 features(例如,使用 Spring)表达式语言或 SpringEL 而不是 OGNL)。所以如果你是一个 Spring MVC 用户,你不会浪费你的 time,因为你在这里学到的几乎所有内容都将在你的 Spring 应用程序中使用。 + +标准方言的大多数处理器都是属性处理器。这使得浏览器甚至可以在处理之前正确显示 HTML 模板 files,因为它们只会忽略其他属性。例如,虽然使用标记 libraries 的 JSP 可能包含 code 的片段,但不能由浏览器直接显示,如: + +```xml + +``` + +...... Thymeleaf Standard Dialect 将允许我们实现相同的功能: + +```xml + +``` + +这不仅可以被浏览器正确显示,而且还允许我们(可选地)在其中指定 value 属性(在这种情况下为“James Carrot”),当在浏览器中静态打开原型时将显示该属性,并且这将由在处理模板期间`${user.name}`的 evaluation 产生的 value 代替。 + +这有助于您的设计人员和开发人员处理相同的模板文件,并减少将静态原型转换为工作模板文件所需的工作量。这样做的能力是一种称为自然模板的特征。 + +## Good Thymes 虚拟杂货店 + +本指南的本章和后续章节中显示的示例的 source code 可以在[Good Thymes Virtual Grocery GitHub repository](https://github.com/thymeleaf/thymeleafexamples-gtvg)中找到。 + +### 杂货店的网站 + +为了更好地解释使用 Thymeleaf 处理模板所涉及的概念,本教程将使用 demo application,您可以从项目的 web 站点下载该应用程序。 + +这个 application 是假想的虚拟杂货的 web 网站,它将为我们提供许多场景来展示 Thymeleaf 的许多 features。 + +首先,我们需要一个简单的 model 实体用于我们的 application:`Products`,它们通过 creating `Orders`卖给`Customers`。我们还将管理`Comments`关于那些`Products`: + +![Example application model](https://www.docs4dev.com/images/thymeleaf/3.0/gtvg-model.jpg) +Example application model +我们的 application 也有一个非常简单的服务层,由`Service` objects 组成,包含如下方法: + +```java +public class ProductService { + + ... + + public List findAll() { + return ProductRepository.getInstance().findAll(); + } + + public Product findById(Integer id) { + return ProductRepository.getInstance().findById(id); + } + +} +``` + +在 web 层,我们的 application 将有一个过滤器,它将执行委托给 Thymeleaf-enabled 命令,具体取决于请求 URL: + +```java +private boolean process(HttpServletRequest request, HttpServletResponse response) + throws ServletException { + + try { + + // This prevents triggering engine executions for resource URLs + if (request.getRequestURI().startsWith("/css") || + request.getRequestURI().startsWith("/images") || + request.getRequestURI().startsWith("/favicon")) { + return false; + } + + + /* + * Query controller/URL mapping and obtain the controller + * that will process the request. If no controller is available, + * return false and let other filters/servlets process the request. + */ + IGTVGController controller = this.application.resolveControllerForRequest(request); + if (controller == null) { + return false; + } + + /* + * Obtain the TemplateEngine instance. + */ + ITemplateEngine templateEngine = this.application.getTemplateEngine(); + + /* + * Write the response headers + */ + response.setContentType("text/html;charset=UTF-8"); + response.setHeader("Pragma", "no-cache"); + response.setHeader("Cache-Control", "no-cache"); + response.setDateHeader("Expires", 0); + + /* + * Execute the controller and process view template, + * writing the results to the response writer. + */ + controller.process( + request, response, this.servletContext, templateEngine); + + return true; + + } catch (Exception e) { + try { + response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } catch (final IOException ignored) { + // Just ignore this + } + throw new ServletException(e); + } + +} +``` + +这是我们的`IGTVGController`界面: + +```java +public interface IGTVGController { + + public void process( + HttpServletRequest request, HttpServletResponse response, + ServletContext servletContext, ITemplateEngine templateEngine); + +} +``` + +我们现在要做的就是创建`IGTVGController`接口的 implementations,使用`ITemplateEngine` object 从服务和处理模板中检索数据。 + +最后,它看起来像这样: + +![Example application 主页](https://www.docs4dev.com/images/thymeleaf/3.0/gtvg-view.jpg) +Example application 主页 +但首先让我们看看该模板引擎是如何初始化的。 + +### 创建和配置模板引擎 + +我们的过滤器中的 process(...)方法包含此 line: + +```java +ITemplateEngine templateEngine = this.application.getTemplateEngine(); +``` + +这意味着 GTVGApplication class 负责创建和配置 Thymeleaf application 中最重要的 objects 之一:`TemplateEngine`实例(`ITemplateEngine`接口的 implementation)。 + +我们的`org.thymeleaf.TemplateEngine` object 初始化如下: + +```java +public class GTVGApplication { + + + ... + private final TemplateEngine templateEngine; + ... + + + public GTVGApplication(final ServletContext servletContext) { + + super(); + + ServletContextTemplateResolver templateResolver = + new ServletContextTemplateResolver(servletContext); + + // HTML is the default mode, but we set it anyway for better understanding of code + templateResolver.setTemplateMode(TemplateMode.HTML); + // This will convert "home" to "/WEB-INF/templates/home.html" + templateResolver.setPrefix("/WEB-INF/templates/"); + templateResolver.setSuffix(".html"); + // Template cache TTL=1h. If not set, entries would be cached until expelled + templateResolver.setCacheTTLMs(Long.valueOf(3600000L)); + + // Cache is set to true by default. Set to false if you want templates to + // be automatically updated when modified. + templateResolver.setCacheable(true); + + this.templateEngine = new TemplateEngine(); + this.templateEngine.setTemplateResolver(templateResolver); + + ... + + } + +} +``` + +有很多方法可以配置`TemplateEngine` object,但是现在这几个 code 的 lines 将教会我们足够的所需步骤。 + +#### 模板解析器 + +让我们从模板解析器开始: + +```java +ServletContextTemplateResolver templateResolver = + new ServletContextTemplateResolver(servletContext); +``` + +模板解析器是 objects,它们实现了名为`org.thymeleaf.templateresolver.ITemplateResolver`的 Thymeleaf API 的接口: + +```java +public interface ITemplateResolver { + + ... + + /* + * Templates are resolved by their name (or content) and also (optionally) their + * owner template in case we are trying to resolve a fragment for another template. + * Will return null if template cannot be handled by this template resolver. + */ + public TemplateResolution resolveTemplate( + final IEngineConfiguration configuration, + final String ownerTemplate, final String template, + final Map templateResolutionAttributes); +} +``` + +这些 objects 负责确定如何访问我们的模板,在这个 GTVG application 中,`org.thymeleaf.templateresolver.ServletContextTemplateResolver`意味着我们将从 Servlet Context 中检索我们的模板 files 作为资源:每个 Java web application 中都存在 application-wide `javax.servlet.ServletContext` object ,并从 web application 根解析资源。 + +但这并不是我们可以说的关于模板解析器的全部内容,因为我们可以在其上设置一些 configuration 参数。一,模板模式: + +```java +templateResolver.setTemplateMode(TemplateMode.HTML); +``` + +HTML 是`ServletContextTemplateResolver`的默认模板模式,但最好还是建立它,以便我们的 code 文档清楚地显示正在发生的事情。 + +```java +templateResolver.setPrefix("/WEB-INF/templates/"); +templateResolver.setSuffix(".html"); +``` + +前缀和后缀修改我们将传递给引擎的模板名称,以获取要使用的实际资源名称。 + +使用此 configuration,模板 name“product/list”将对应于: + +```java +servletContext.getResourceAsStream("/WEB-INF/templates/product/list.html") +``` + +(可选)通过 cacheTTLMs property 在模板解析器中配置解析模板可以在缓存中存在的 time 的数量: + +```java +templateResolver.setCacheTTLMs(3600000L); +``` + +如果达到最大高速缓存大小并且它是当前高速缓存的最旧条目,则在达到该 TTL 之前,模板仍然可以从高速缓存中排除。 + +> 用户可以通过实现`ICacheManager`接口或通过修改`StandardCacheManager` object 来管理默认缓存来定义缓存行为和大小。 + +关于模板解析器还有很多东西需要学习,但是现在让我们来看看 Template Engine object 的创建。 + +#### 模板引擎 + +模板引擎 objects 是`org.thymeleaf.ITemplateEngine`接口的 implementations。其中一个 implementation 由 Thymeleaf 核心提供:`org.thymeleaf.TemplateEngine`,我们在这里创建一个实例: + +```java +templateEngine = new TemplateEngine(); +templateEngine.setTemplateResolver(templateResolver); +``` + +相当简单,不是吗?我们所需要的只是创建一个实例并将模板解析器设置为它。 + +模板解析器是`TemplateEngine`需要的唯一必需参数,尽管稍后将介绍许多其他参数(消息解析器,缓存大小等)。现在,这就是我们所需要的。 + +我们的模板引擎现已准备就绪,我们可以使用 Thymeleaf 开始创建我们的页面。 + +## 使用文本 + +### A multi-language 欢迎 + +我们的第一个任务是为我们的杂货网站创建一个主页。 + +这个页面的第一个 version 非常简单:只是标题和欢迎信息。这是我们的`/WEB-INF/templates/home.html`文件: + +```xml + + + + + + Good Thymes Virtual Grocery + + + + + + +

    Welcome to our grocery store!

    + + + + +``` + +你会注意到的第一件事是这个文件是 HTML5,可以被任何浏览器正确显示,因为它不包含任何 non-HTML 标签(浏览器会忽略他们不理解的所有属性,如`th:text`)。 + +但您可能还注意到此模板实际上不是有效的 HTML5 文档,因为 HTML5 规范不允许我们在`th:*`表单中使用这些 non-standard 属性。实际上,我们甚至在``标签中添加`xmlns:th`属性,绝对是 non-HTML5-ish: + +```xml + +``` + +...在模板处理中根本没有任何影响,但作为一个咒语,阻止我们的 IDE 抱怨缺少所有这些`th:*`属性的命名空间定义。 + +那么如果我们想制作这个模板**HTML5-valid**呢?简单:切换到 Thymeleaf 的数据属性语法,使用`data-`前缀作为属性名称和连字符(`-`)分隔符而不是 semi-colons(`:`): + +```xml + + + + + + Good Thymes Virtual Grocery + + + + + + +

    Welcome to our grocery store!

    + + + + +``` + +HTML5 规范允许自定义`data-`前缀属性,因此,使用上面的 code,我们的模板将是一个有效的 HTML5 文档。 + +> 两种表示法都是完全等效且可互换的,但为了简化和 code samples 的紧凑性,本教程将使用名称空间表示法(`th:*`)。此外,`th:*`表示法更通用,并且在每个 Thymeleaf 模板模式(`XML`,`TEXT` ...)中都允许使用`th:*`表示法,而`data-`表示法仅允许在`HTML`模式中使用。 + +#### 使用 th:text 和外化文本 + +外化文本是从模板 files 中提取模板 code 的片段,以便它们可以保存在单独的 files(通常为`.properties` files)中,并且可以使用其他语言编写的等效文本(称为国际化的过程或简称 i18n)轻松替换它们。外化的文本片段通常称为“消息”。 + +消息始终具有标识它们的 key,并且 Thymeleaf 允许您指定文本应与具有`#{...}`语法的特定消息对应: + +```xml +

    Welcome to our grocery store!

    +``` + +我们在这里看到的实际上是 Thymeleaf 标准方言的两个不同的特点: + +- `th:text`属性,它评估其 value 表达式并将结果设置为 host 标记的主体,有效地替换了“欢迎来到我们的杂货店 store!”我们在 code 中看到的文字。 +- 标准表达式语法中指定的`#{home.welcome}`表达式,指示`th:text`属性要使用的文本应该是`home.welcome` key 对应于我们正在处理模板的 locale 的消息。 + +现在,这个外化文本在哪里? + +Thymeleaf 中外化文本的位置是完全可配置的,它取决于所使用的特定`org.thymeleaf.messageresolver.IMessageResolver` implementation。通常,将使用基于`.properties` files 的 implementation,但是如果我们想要的话,我们可以创建自己的 implementations,例如,从数据库获取消息。 + +但是,我们在初始化期间没有为模板引擎指定消息解析器,这意味着我们的 application 正在使用由`org.thymeleaf.messageresolver.StandardMessageResolver`实现的标准消息解析器。 + +标准消息解析程序希望在 properties files 中找到`/WEB-INF/templates/home.html`的消息,并在同一文件夹中使用与模板相同的 name,如: + +- `/WEB-INF/templates/home_en.properties`用于英文文本。 +- `/WEB-INF/templates/home_es.properties`用于西班牙语文本。 +- `/WEB-INF/templates/home_pt_BR.properties`代表葡萄牙语(巴西)语言文本。 +- `/WEB-INF/templates/home.properties`表示默认文本(如果 locale 不匹配)。 + +我们来看看我们的`home_es.properties`文件: + +```java +home.welcome=¡Bienvenido a nuestra tienda de comestibles! +``` + +这就是我们将 Thymeleaf process 作为模板所需的全部内容。让我们创建我们的 Home 控制器。 + +#### 上下文 + +在 process to process 我们的模板中,我们将创建一个实现我们之前看到的`IGTVGController`接口的`HomeController` class: + +```java +public class HomeController implements IGTVGController { + + public void process( + final HttpServletRequest request, final HttpServletResponse response, + final ServletContext servletContext, final ITemplateEngine templateEngine) + throws Exception { + + WebContext ctx = + new WebContext(request, response, servletContext, request.getLocale()); + + templateEngine.process("home", ctx, response.getWriter()); + + } + +} +``` + +我们看到的第一件事是创建 context。 Thymeleaf context 是一个实现`org.thymeleaf.context.IContext`接口的 object。上下文应包含在变量 map 中执行模板引擎所需的所有数据,并且还引用必须用于外部化消息的 locale。 + +```java +public interface IContext { + + public Locale getLocale(); + public boolean containsVariable(final String name); + public Set getVariableNames(); + public Object getVariable(final String name); + +} +``` + +这个接口有一个专门的扩展,`org.thymeleaf.context.IWebContext`,意味着在 ServletAPI-based web applications(如 SpringMVC)中使用。 + +```java +public interface IWebContext extends IContext { + + public HttpServletRequest getRequest(); + public HttpServletResponse getResponse(); + public HttpSession getSession(); + public ServletContext getServletContext(); + +} +``` + +Thymeleaf 核心 library 提供了每个接口的 implementation: + +- `org.thymeleaf.context.Context`实现`IContext` +- `org.thymeleaf.context.WebContext`实现`IWebContext` + +正如您在控制器 code 中看到的那样,`WebContext`是我们使用的那个。实际上我们必须这样做,因为使用`ServletContextTemplateResolver`要求我们使用 context 实现`IWebContext`。 + +```java +WebContext ctx = new WebContext(request, response, servletContext, request.getLocale()); +``` + +这四个构造函数 arguments 中只有三个是必需的,因为如果指定了 none,将使用系统的默认 locale(尽管你不应该在真正的 applications 中发生这种情况)。 + +我们可以使用一些专门的表达式来获取请求参数以及模板中`WebContext`的 request,session 和 application 属性。例如: + +- `${x}`将\_return 存储在 Thymeleaf context 中的变量`x`或作为请求属性。 +- `${param.x}`将\_return 一个名为`x`的请求参数(可能是多值的)。 +- `${session.x}`将 return 一个名为`x`的 session 属性。 +- `${application.x}`将 return \_selet context 属性,名为`x`。 + +#### 执行模板引擎 + +在我们的 context object 准备就绪之后,现在我们可以告诉模板引擎使用 context 处理模板(通过它的 name),并传递响应 writer,以便可以将响应写入它: + +```java +templateEngine.process("home", ctx, response.getWriter()); +``` + +让我们看一下使用西班牙语 locale 的结果: + +```xml + + + + + + Good Thymes Virtual Grocery + + + + + + +

    ¡Bienvenido a nuestra tienda de comestibles!

    + + + + +``` + +### 有关文本和变量的更多信息 + +#### 未转义的文字 + +我们主页上最简单的 version 现在似乎已经准备就绪了,但是我们还没有想到......如果我们有这样的消息怎么办? + +```xml +home.welcome=Welcome to our fantastic grocery store! +``` + +如果我们像以前一样执行此模板,我们将获得: + +```xml +

    Welcome to our <b>fantastic</b> grocery store!

    +``` + +这不完全符合我们的预期,因为我们的``标签已被转义,因此它将显示在浏览器中。 + +这是`th:text`属性的默认行为。如果我们希望 Thymeleaf 尊重我们的 HTML 标签而不是逃避它们,我们将不得不使用不同的属性:`th:utext`(对于“非转义文本”): + +```xml +

    Welcome to our grocery store!

    +``` + +这将输出我们的消息,就像我们想要的那样: + +```xml +

    Welcome to our fantastic grocery store!

    +``` + +#### 使用和显示变量 + +现在让我们在主页上添加更多内容。对于 example,我们可能希望在欢迎消息下方显示 date,如下所示: + +```java +Welcome to our fantastic grocery store! + +Today is: 12 july 2010 +``` + +首先,我们必须修改控制器,以便将 date 添加为 context 变量: + +```java +public void process( + final HttpServletRequest request, final HttpServletResponse response, + final ServletContext servletContext, final ITemplateEngine templateEngine) + throws Exception { + + SimpleDateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy"); + Calendar cal = Calendar.getInstance(); + + WebContext ctx = + new WebContext(request, response, servletContext, request.getLocale()); + ctx.setVariable("today", dateFormat.format(cal.getTime())); + + templateEngine.process("home", ctx, response.getWriter()); + +} +``` + +我们在 context 中添加了一个名为`today`的`String`变量,现在我们可以在模板中显示它: + +```xml + + +

    Welcome to our grocery store!

    + +

    Today is: 13 February 2011

    + + +``` + +正如您所看到的,我们仍然使用\_j 属性 job(这是正确的,因为我们想要替换标签的主体),但是这个 time 的语法有点不同而不是`#{...}`表达式 value,我们是使用`${...}`一个。这是一个**变量表达式**,它包含一个名为 OGNL(Object-Graph Navigation Language)的语言表达式,它将在我们之前讨论的 context 变量 map 上执行。 + +`${today}`表达式只是意味着“获取今天调用的变量”,但这些表达式可能更复杂(如`${user.name}`表示“获取名为 user 的变量,并调用其`getName()`方法”)。 + +属性值有很多可能性:消息,变量表达式......还有很多。下一章将向我们展示所有这些可能性。 + +## 标准表达式语法 + +我们将在开发杂货虚拟商店时采取一个小小的 break 来了解 Thymeleaf 标准方言中最重要的部分之一:Thymeleaf 标准表达式语法。 + +我们已经看到在这种语法中表达的两种类型的有效属性值:消息和变量表达式: + +```xml +

    Welcome to our grocery store!

    + +

    Today is: 13 february 2011

    +``` + +但是有更多类型的表达式,以及更多有趣的细节来了解我们已经知道的那些。首先,让我们看一下标准表达式 features 的快速摘要: + +- 简单表达: +- 变量表达式:`${...}` +- 选择变量表达式:`*{...}` +- 消息表达式:`#{...}` +- 链接网址表达式:`@{...}` +- 片段表达式:`~{...}` +- Literals +- 文字 literals:`'one text'`,`'Another one!'`,... +- 数字 literals:`0`,`34`,`3.0`,`12.3`,... +- Boolean literals:`true`,`false` +- 空文字:`null` +- 文字代币:`one`,`sometext`,`main`,...... +- 文字操作: +- String 连接:`+` +- 字面替换:`|The name is ${name}|` +- 算术运算: +- 二进制 operators:`+`,`-`,`*`,`/`,`%` +- 减号(一元运算符):`-` +- Boolean 操作: +- 二进制操作符:`and`,`or` +- Boolean negation(一元 operator):`!`,`not` +- 比较和平等: +- 比较器:`>`,`<`,`>=`,`<=`(`gt`,`lt`,`ge`,`le`) +- Equality operators:`==`,`!=`(`eq`,`ne`) +- 有条件的 operators: +- If-then:`(if) ? (then)` +- If-then-else:`(if) ? (then) : (else)` +- 默认值:`(value) ?: (defaultvalue)` +- 特殊代币: +- No-Operation:`_` + +所有这些 features 都可以组合和嵌套: + +```java +'User is of type ' + (${user.isAdmin()} ? 'Administrator' : (${user.type} ?: 'Unknown')) +``` + +### 消息 + +正如我们已经知道的那样,`#{...}`消息表达式允许我们链接: + +```xml +

    Welcome to our grocery store!

    +``` + +......对此: + +```java +home.welcome=¡Bienvenido a nuestra tienda de comestibles! +``` + +但是有一个我们仍然没有想到的方面:如果消息文本不是完全静态会发生什么?如果,例如,我们的 application 知道谁是在任何 moment 访问该网站的用户,我们想通过 name 来问候他们呢? + +```xml +

    ¡Bienvenido a nuestra tienda de comestibles, John Apricot!

    +``` + +这意味着我们需要在消息中添加一个参数。像这样: + +```java +home.welcome=¡Bienvenido a nuestra tienda de comestibles, {0}! +``` + +参数是根据[java.text.MessageFormat](https://docs.oracle.com/javase/10/docs/api/java/text/MessageFormat.html)标准语法指定的,这意味着您可以格式化包中 classes 的 API docs 中指定的 numbers 和 date。 + +在 order 中为我们的参数指定一个 value,并给定一个名为`user`的 HTTP session 属性,我们可以: + +```xml +

    + Welcome to our grocery store, Sebastian Pepper! +

    +``` + +> 请注意,此处使用`th:utext`表示格式化消息不会被转义。此 example 假定`user.name`已经转义。 + +可以指定几个参数,以逗号分隔。 + +消息 key 本身可以来自变量: + +```xml +

    + Welcome to our grocery store, Sebastian Pepper! +

    +``` + +### 变量 + +我们已经提到`${...}`表达式实际上是在 context 中包含的变量的 map 上执行的 OGNL(Object-Graph Navigation Language)表达式。 + +> 有关 OGNL 语法和 features 的详细信息,您应该阅读[OGNL 语言指南](http://commons.apache.org/ognl/) + +在 Spring MVC-enabled applications 中,OGNL 将被替换为**SpringEL**,但其语法与 OGNL 的语法非常相似(实际上,对于大多数 common 案例来说完全相同)。 + +从 OGNL 的语法,我们知道表达式: + +```xml +

    Today is: 13 february 2011.

    +``` + +......实际上相当于这个: + +```java +ctx.getVariable("today"); +``` + +但是 OGNL 允许我们创建更强大的表达式,这就是: + +```xml +

    + Welcome to our grocery store, Sebastian Pepper! +

    +``` + +...通过执行以下命令获取用户 name: + +```java +((User) ctx.getVariable("session").get("user")).getName(); +``` + +但是 getter 方法导航只是 OGNL 的 features 之一。让我们看看更多: + +```java +/* + * Access to properties using the point (.). Equivalent to calling property getters. + */ +${person.father.name} + +/* + * Access to properties can also be made by using brackets ([]) and writing + * the name of the property as a variable or between single quotes. + */ +${person['father']['name']} + +/* + * If the object is a map, both dot and bracket syntax will be equivalent to + * executing a call on its get(...) method. + */ +${countriesByCode.ES} +${personsByName['Stephen Zucchini'].age} + +/* + * Indexed access to arrays or collections is also performed with brackets, + * writing the index without quotes. + */ +${personsArray[0].name} + +/* + * Methods can be called, even with arguments. + */ +${person.createCompleteName()} +${person.createCompleteNameWithSeparator('-')} +``` + +#### Expression Basic Objects + +在 context 变量上评估 OGNL 表达式时,某些 object 可用于表达式以获得更高的灵活性。从`#`符号开始,将引用这些 objects(按照 OGNL 标准): + +- `#ctx`:context object。 +- `#vars:` context 变量。 +- `#locale`:context locale。 +- `#request` :(仅在 Web 上下文中)`HttpServletRequest` object。 +- `#response` :(仅在 Web 上下文中)`HttpServletResponse` object。 +- `#session` :(仅在 Web 上下文中)`HttpSession` object。 +- `#servletContext` :(仅在 Web 上下文中)`ServletContext` object。 + +所以我们可以这样做: + +```xml +Established locale country: US. +``` + +您可以在[附录 A.](https://www.docs4dev.com/docs/zh/thymeleaf/3.0/reference/using_thymeleaf.html#appendix-a-expression-basic-objects)中读取这些 objects 的完整 reference。 + +#### 表达式实用程序 Objects + +除了这些基本的 objects 之外,Thymeleaf 将为我们提供一组实用工具 objects,它们将帮助我们在表达式中执行 common 任务。 + +- `#execInfo`:有关正在处理的模板的信息。 +- `#messages`:在变量表达式中获取外部化消息的方法,与使用#{}语法获取的方法相同。 +- `#uris`:转义 URLs/URIs 部分的方法 +- `#conversions`:执行已配置的转换服务的方法(如果有)。 +- `#dates`:`java.util.Date` objects 的方法:格式化,组件提取等。 +- `#calendars`:类似于`#dates`,但是对于`java.util.Calendar` objects。 +- `#numbers`:格式化数字 objects 的方法。 +- `#strings`:`String` objects 的方法:contains,startsWith,prepending/appending 等。 +- `#objects`:一般的 objects 方法。 +- `#bools`:boolean evaluation 的方法。 +- `#arrays`:数组的方法。 +- `#lists`:lists 的方法。 +- `#sets`:sets 的方法。 +- `#maps`:maps 的方法。 +- `#aggregates`:用于在数组或集合上创建聚合的方法。 +- `#ids`:处理可能重复的 id 属性的方法(对于 example,作为迭代的结果)。 + +您可以检查[附录 B.](https://www.docs4dev.com/docs/zh/thymeleaf/3.0/reference/using_thymeleaf.html#appendix-b-expression-utility-objects)中每个实用程序 objects 提供的功能。 + +#### 在我们的主页中重新格式化日期 + +现在我们知道这些实用程序 objects,我们可以使用它们来改变我们在主页中显示 date 的方式。而不是在我们的`HomeController`中这样做: + +```java +SimpleDateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy"); +Calendar cal = Calendar.getInstance(); + +WebContext ctx = new WebContext(request, servletContext, request.getLocale()); +ctx.setVariable("today", dateFormat.format(cal.getTime())); + +templateEngine.process("home", ctx, response.getWriter()); +``` + +......我们可以做到这一点: + +```java +WebContext ctx = + new WebContext(request, response, servletContext, request.getLocale()); +ctx.setVariable("today", Calendar.getInstance()); + +templateEngine.process("home", ctx, response.getWriter()); +``` + +...然后在视图层本身中执行 date 格式: + +```xml +

    + Today is: 13 May 2011 +

    +``` + +### 关于选择的表达式(星号语法) + +变量表达式不仅可以写为`${...}`,还可以写为`*{...}`。 + +但是有一个重要的区别:星号语法评估所选 objects 上的表达式而不是整个 context 上的表达式。也就是说,由于没有选择的 object,因此美元和星号语法完全相同。 + +什么是选定的 object?使用`th:object`属性的表达式的结果。让我们在用户 profile(`userprofile.html`)页面中使用一个: + +```xml +
    +

    Name: Sebastian.

    +

    Surname: Pepper.

    +

    Nationality: Saturn.

    +
    +``` + +这完全等同于: + +```xml +
    +

    Name: Sebastian.

    +

    Surname: Pepper.

    +

    Nationality: Saturn.

    +
    +``` + +当然,美元和星号语法可以混合使用: + +```xml +
    +

    Name: Sebastian.

    +

    Surname: Pepper.

    +

    Nationality: Saturn.

    +
    +``` + +当 object 选择到位时,所选的 object 也可用作美元表达式作为`#object`表达式变量: + +```xml +
    +

    Name: Sebastian.

    +

    Surname: Pepper.

    +

    Nationality: Saturn.

    +
    +``` + +如上所述,如果没有执行 object 选择,则美元和星号语法是等效的。 + +```xml +
    +

    Name: Sebastian.

    +

    Surname: Pepper.

    +

    Nationality: Saturn.

    +
    +``` + +### 链接网址 + +由于它们的重要性,URL 在 web application 模板中是 first-class 公民,而 Thymeleaf 标准方言有一个特殊的语法,`@`语法:`@{...}` + +有不同类型的网址: + +- 绝对网址:`http://www.thymeleaf.org` +- 相对 URL,可以是: +- Page-relative:`user/login.html` +- Context-relative:`/itemdetails?id=3`(服务器中的 context name 将自动添加) +- Server-relative:`~/billing/processInvoice`(允许在同一服务器中的另一个 context(= application)中调用 URL。 +- Protocol-relative 网址:`//code.jquery.com/jquery-2.0.3.min.js` + +这些表达式的实际处理及其转换为将要输出的 URL 是通过`org.thymeleaf.linkbuilder.ILinkBuilder`接口的 implementations 来完成的,这些接口被注册到正在使用的`ITemplateEngine` object 中。 + +默认情况下,此接口的单个 implementation 已注册 class `org.thymeleaf.linkbuilder.StandardLinkBuilder`,这对于基于 Servlet API 的脱机(non-web)和 web 方案都足够了。其他方案(如 gb 框架的 integration)可能需要链接构建器接口的特定 implementation。 + +让我们使用这种新语法。符合`th:href`属性: + +```xml + +view + + +view + + +view +``` + +有些事情需要注意: + +- `th:href`是修饰符属性:处理后,它将计算要使用的链接 URL,并将 value 设置为``标记的`href`属性。 +- 我们被允许对 URL 参数使用表达式(如`orderId=${o.id}`中所示)。所需的 URL-parameter-encoding 操作也将自动执行。 +- 如果需要多个参数,这些参数将用逗号分隔:`@{/order/process(execId=${execId},execType='FAST')}` +- URL paths 中也允许使用变量模板:`@{/order/{orderId}/details(orderId=${orderId})}` +- 以`/`开头的相对 URL(例如:`/order/details`)将自动以 application context name 为前缀。 +- 如果 cookies 未启用或尚未知道,则可能会在相对 URL 中添加`";jsessionid=..."`后缀,以便保留 session。这称为 URL 重写,Thymeleaf 允许您使用 Servlet API 中的`response.encodeURL(...)`机制为每个 URL 插入自己的重写过滤器。 +- `th:href`属性允许我们(可选)在我们的模板中具有工作静态`href`属性,以便我们的模板链接在直接打开以进行原型设计时仍可由浏览器导航。 + +与消息语法(`#{...}`)的情况一样,URL 基数也可以是评估另一个表达式的结果: + +```xml +view +view +``` + +#### 我们主页的菜单 + +既然我们知道如何创建链接 URL,那么在我们的主页中为网站中的其他一些页面添加一个小菜单呢? + +```xml +

    Please select an option

    +
      +
    1. Product List
    2. +
    3. Order List
    4. +
    5. Subscribe to our Newsletter
    6. +
    7. See User Profile
    8. +
    +``` + +#### 服务器根目录相对 URL + +可以使用其他语法在 order 中创建 server-root-relative(而不是 context-root-relative)URL,以链接到同一服务器中的不同上下文。这些网址将被指定为`@{~/path/to/something}` + +### 片段 + +片段表达式是表示标记片段并在模板周围移动它们的简单方法。这允许我们复制它们,将它们作为 arguments 传递给其他模板,依此类推。 + +最常见的用法是使用`th:insert`或`th:replace`进行片段插入(在后面的部分中将详细介绍): + +```xml +
    ...
    +``` + +但它们可以在任何地方使用,就像任何其他变量一样: + +```xml +
    +

    +

    +``` + +本教程后面有一整节专门介绍模板布局,包括对片段表达式的更深入解释。 + +### Literals + +#### 文字 literals + +文本 literals 只是在单引号之间指定的字符 strings。它们可以包含任何字符,但您应该使用`\'`来转义其中的任何单引号。 + +```xml +

    + Now you are looking at a template file. +

    +``` + +#### 数字 literals + +数字 literals 就是:numbers。 + +```xml +

    The year is 1492.

    +

    In two years, it will be 1494.

    +``` + +#### Boolean literals + +boolean literals 是`true`和`false`。例如: + +```xml +
    ... +``` + +在这个例子中,`== false`被写在大括号之外,因此 Thymeleaf 会处理它。如果它写在大括号内,那将是 OGNL/SpringEL 引擎的责任: + +```xml +
    ... +``` + +#### null 文字 + +也可以使用`null` literal: + +```xml +
    ... +``` + +#### 文字代币 + +实际上,数字,boolean 和 null literals 是文字标记的特例。 + +这些令牌允许在标准表达式中进行一些简化。它们与 text literals(`'...'`)完全相同,但它们只允许字母(`A-Z`和`a-z`), numbers(`0-9`),括号(`[`和`]`),点(`.`),连字符(`-`)和下划线(`_`)。所以没有空格,没有逗号等。 + +好的部分?令牌不需要任何围绕它们的引号。所以我们可以这样做: + +```xml +
    ...
    +``` + +代替: + +```xml +
    ...
    +``` + +### 附加文本 + +文本,无论是\_literal 还是评估变量或消息表达式的结果,都可以使用`+` operator 轻松追加: + +```xml + +``` + +### 字面替换 + +文字替换允许轻松格式化包含变量值的 strings,而无需使用`'...' + '...'`附加 literals。 + +这些替换必须用竖线(`|`)包围,如: + +```xml + +``` + +这相当于: + +```xml + +``` + +文字替换可以与其他类型的表达相结合: + +```xml + +``` + +> 在`|...|`字面替换中只允许 variable/message 个表达式(`${...}`,`*{...}`,`#{...}`)。没有其他 literals(`'...'`),boolean/numeric 令牌,条件表达式等。 + +### 算术运算 + +一些算术运算也可用:`+`,`-`,`*`,`/`和`%`。 + +```xml +
    +``` + +请注意,这些 operators 也可以在 OGNL 变量表达式本身中应用(在这种情况下,将由 OGNL 而不是 Thymeleaf 标准表达式引擎执行): + +```xml +
    +``` + +请注意,其中一些 operators 存在文本别名:`div`(`/`),`mod`(`%`)。 + +### 比较和平等 + +表达式中的值可以与`>`,`<`,`>=`和`<=`符号进行比较,`==`和`!=` operators 可以用于检查是否相等(或缺少它)。请注意,XML 确定不应在属性值中使用`<`和`>`符号,因此应将它们替换为`<`和`>`。 + +```xml +
    + +``` + +一个更简单的替代方法可能是使用为某些这些 operators 存在的文本别名:`gt`(`>`),`lt`(`<`),`ge`(`>=`),`le`(`<=`),`not`(`!`)。也`eq`(`==`),`neq`/`ne`(`!=`)。 + +### 条件表达式 + +条件表达式仅用于评估两个表达式中的一个,具体取决于评估条件的结果(它本身是另一个表达式)。 + +让我们看一下 example 片段(引入另一个属性修饰符,`th:class`): + +```xml + + ... + +``` + +条件表达式(`condition`,`then`和`else`)的所有三个部分本身都是表达式,这意味着它们可以是变量(`${...}`,`*{...}`),消息(`#{...}`),URL(`@{...}`)或 literals(`'...'`)。 + +条件表达式也可以使用括号嵌套: + +```xml + + ... + +``` + +其他表达式也可以省略,在这种情况下,如果条件为 false,则返回 null value: + +```xml + + ... + +``` + +### 默认表达式(Elvis operator) + +默认表达式是一种特殊的条件 value,没有 then 部分。它等同于某些语言(例如 Groovy)中的 Elvis operator,允许您指定两个表达式:如果它没有计算为 null,则使用第一个表达式,但如果它没有,则使用第二个表达式。 + +让我们在用户 profile 页面中看到它的运行情况: + +```xml +
    + ... +

    Age: 27.

    +
    +``` + +如您所见,operator 是`?:`,只有在评估`*{age}`的结果为 null 时,我们才在此处使用它来为 name(在本例中为文字 value)指定默认 value。因此,这相当于: + +```xml +

    Age: 27.

    +``` + +与条件值一样,它们可以包含括号之间的嵌套表达式: + +```xml +

    + Name: + Sebastian +

    +``` + +### No-Operation 标记 + +No-Operation 标记由下划线符号(`_`)表示。 + +此标记后面的 idea 是指定表达式的所需结果是什么都不做,i.e。就像可处理的属性(e.g. `th:text`)根本不存在一样。 + +除了其他可能性之外,这允许开发人员使用原型文本作为默认值。对于 example,而不是: + +```xml +... +``` + +...我们可以直接使用“没有用户身份验证”作为原型文本,从设计的角度来看,code 既简洁又通用: + +```xml +no user authenticated +``` + +### 数据转换/格式化 + +Thymeleaf 为变量(`${...}`)和选择(`*{...}`)表达式定义 double-brace 语法,允许我们通过配置的转换服务应用数据转换。 + +它基本上是这样的: + +```xml +... +``` + +注意到那里的 double 括号?:`${{...}}`。这指示 Thymeleaf 将`user.lastAccessDate`表达式的结果传递给转换服务,并要求它在写入结果之前执行**格式化操作**(转换为`String`)。 + +假设`user.lastAccessDate`的类型为`java.util.Calendar`,如果已经注册了转换服务(`IStandardConversionService`的实现)并且包含`Calendar -> String`的有效转换,则将应用它。 + +`IStandardConversionService`(`StandardConversionService` class)的默认 implementation 只是在转换为`String`的任何 object 上执行`.toString()`。有关如何注册自定义转换服务 implementation 的更多信息,请查看[有关 Configuration 的更多信息](https://www.docs4dev.com/docs/zh/thymeleaf/3.0/reference/using_thymeleaf.html#more-on-configuration)部分。 + +> 官方的 thymeleaf-spring3 和 thymeleaf-spring4 integration 包透明地将 Thymeleaf 的转换服务机制与 Spring 自己的转换服务基础结构相集成,以便 Spring configuration 中声明的转换服务和格式化程序将自动可用于`${{...}}`和`*{{...}}`表达式。 + +### 预处理 + +除了用于表达式处理的所有这些 features 之外,Thymeleaf 还具有预处理表达式的 feature。 + +预处理是在正常表达式之前完成的表达式的执行,它允许修改最终将被执行的表达式。 + +预处理表达式与普通表达式完全相同,但显示为 double 下划线符号(如`__${expression}__`)。 + +让我们假设我们有一个 i18n `Messages_fr.properties`条目,其中包含一个调用 language-specific 静态方法的 OGNL 表达式,如: + +```java +article.text=@myapp.translator.Translator@translateToFrench({0}) +``` + +......和`Messages_es.properties equivalent`: + +```java +article.text=@myapp.translator.Translator@translateToSpanish({0}) +``` + +我们可以创建一个标记片段,根据 locale 计算一个表达式或另一个表达式。为此,我们将首先选择表达式(通过预处理),然后让 Thymeleaf 执行它: + +```xml +

    Some text here...

    +``` + +请注意,法语 locale 的预处理 step 将创建以下等效项: + +```xml +

    Some text here...

    +``` + +可以使用`\_\_`在属性中转义预处理 String `__`。 + +## 设置属性值 + +本章将解释我们在标记中设置(或修改)属性值的方式。 + +### 设置任何属性的 value + +假设我们的网站发布了一个时事通讯,我们希望我们的用户能够订阅它,所以我们创建一个带有表单的`/WEB-INF/templates/subscribe.html`模板: + +```xml +
    +
    + + +
    +
    +``` + +与 Thymeleaf 一样,此模板更像是静态原型,而不是 web application 的模板。首先,我们表单中的`action`属性静态链接到模板文件本身,因此没有地方可以进行有用的 URL 重写。其次,提交按钮中的`value`属性使其显示英文文本,但我们希望它能够国际化。 + +然后输入`th:attr`属性,以及更改其设置的标记属性的 value 的能力: + +```xml +
    +
    + + +
    +
    +``` + +这个概念非常简单:`th:attr`只需要一个为属性赋予 value 的表达式。创建了相应的控制器和消息 files 后,处理该文件的结果将是: + +```xml +
    +
    + + +
    +
    +``` + +除了新的属性值之外,您还可以看到 applicacion context name 已自动添加到`/gtvg/subscribe`中的 URL 基础作为前缀,如上一章所述。 + +但是如果我们想在 time 设置多个属性呢? XML 规则不允许您在标记中设置两次属性,因此`th:attr`将采用 comma-separated 分配列表,例如: + +```xml + +``` + +给定所需的消息 files,这将输出: + +```xml +Logo de Good Thymes +``` + +### 将 value 设置为特定属性 + +到现在为止,您可能会想到以下内容: + +```xml + +``` + +......是一个非常丑陋的标记。在属性的 value 中指定赋值可能非常实用,但如果你必须在 time 中完成,那么它不是创建模板的最优雅方式。 + +Thymeleaf 同意你的意见,这就是为什么在模板中几乎不使用`th:attr`的原因。通常,您将使用其任务设置特定标记属性的其他`th:*`属性(而不仅仅是`th:attr`之类的任何属性)。 + +对于 example,要设置`value`属性,请使用`th:value`: + +```xml + +``` + +这看起来好多了!让我们尝试对`form`标签中的`action`属性执行相同的操作: + +```xml +
    +``` + +你还记得我们之前在`home.html`中放过的那些吗?它们正是同样的属性: + +```xml +
  • Product List
  • +``` + +有很多这样的属性,每个属性都针对特定的 HTML5 属性: + +| | | | +| :---------------------- | :-------------------- | :------------------ | +| `th:abbr` | `th:accept` | `th:accept-charset` | +| `th:accesskey` | `th:action` | `th:align` | +| `th:alt` | `th:archive` | `th:audio` | +| `th:autocomplete` | `th:axis` | `th:background` | +| `th:bgcolor` | `th:border` | `th:cellpadding` | +| `th:cellspacing` | `th:challenge` | `th:charset` | +| `th:cite` | `th:class` | `th:classid` | +| `th:codebase` | `th:codetype` | `th:cols` | +| `th:colspan` | `th:compact` | `th:content` | +| `th:contenteditable` | `th:contextmenu` | `th:data` | +| `th:datetime` | `th:dir` | `th:draggable` | +| `th:dropzone` | `th:enctype` | `th:for` | +| `th:form` | `th:formaction` | `th:formenctype` | +| `th:formmethod` | `th:formtarget` | `th:fragment` | +| `th:frame` | `th:frameborder` | `th:headers` | +| `th:height` | `th:high` | `th:href` | +| `th:hreflang` | `th:hspace` | `th:http-equiv` | +| `th:icon` | `th:id` | `th:inline` | +| `th:keytype` | `th:kind` | `th:label` | +| `th:lang` | `th:list` | `th:longdesc` | +| `th:low` | `th:manifest` | `th:marginheight` | +| `th:marginwidth` | `th:max` | `th:maxlength` | +| `th:media` | `th:method` | `th:min` | +| `th:name` | `th:onabort` | `th:onafterprint` | +| `th:onbeforeprint` | `th:onbeforeunload` | `th:onblur` | +| `th:oncanplay` | `th:oncanplaythrough` | `th:onchange` | +| `th:onclick` | `th:oncontextmenu` | `th:ondblclick` | +| `th:ondrag` | `th:ondragend` | `th:ondragenter` | +| `th:ondragleave` | `th:ondragover` | `th:ondragstart` | +| `th:ondrop` | `th:ondurationchange` | `th:onemptied` | +| `th:onended` | `th:onerror` | `th:onfocus` | +| `th:onformchange` | `th:onforminput` | `th:onhashchange` | +| `th:oninput` | `th:oninvalid` | `th:onkeydown` | +| `th:onkeypress` | `th:onkeyup` | `th:onload` | +| `th:onloadeddata` | `th:onloadedmetadata` | `th:onloadstart` | +| `th:onmessage` | `th:onmousedown` | `th:onmousemove` | +| `th:onmouseout` | `th:onmouseover` | `th:onmouseup` | +| `th:onmousewheel` | `th:onoffline` | `th:ononline` | +| `th:onpause` | `th:onplay` | `th:onplaying` | +| `th:onpopstate` | `th:onprogress` | `th:onratechange` | +| `th:onreadystatechange` | `th:onredo` | `th:onreset` | +| `th:onresize` | `th:onscroll` | `th:onseeked` | +| `th:onseeking` | `th:onselect` | `th:onshow` | +| `th:onstalled` | `th:onstorage` | `th:onsubmit` | +| `th:onsuspend` | `th:ontimeupdate` | `th:onundo` | +| `th:onunload` | `th:onvolumechange` | `th:onwaiting` | +| `th:optimum` | `th:pattern` | `th:placeholder` | +| `th:poster` | `th:preload` | `th:radiogroup` | +| `th:rel` | `th:rev` | `th:rows` | +| `th:rowspan` | `th:rules` | `th:sandbox` | +| `th:scheme` | `th:scope` | `th:scrolling` | +| `th:size` | `th:sizes` | `th:span` | +| `th:spellcheck` | `th:src` | `th:srclang` | +| `th:standby` | `th:start` | `th:step` | +| `th:style` | `th:summary` | `th:tabindex` | +| `th:target` | `th:title` | `th:type` | +| `th:usemap` | `th:value` | `th:valuetype` | +| `th:vspace` | `th:width` | `th:wrap` | +| `th:xmlbase` | `th:xmllang` | `th:xmlspace` | + +### 在 time 设置多个 value + +有两个相当特殊的属性叫做`th:alt-title`和`th:lang-xmllang`,它们可以用于在同一个 time 中将两个属性设置为同一个 value。特别: + +- `th:alt-title`将设置`alt`和`title`。 +- `th:lang-xmllang`将设置`lang`和`xml:lang`。 + +对于我们的 GTVG 主页,这将允许我们替换: + +```xml + +``` + +......或者这个,相当于: + +```xml + +``` + +…有了这个: + +```xml + +``` + +### 追加和预先 + +Thymeleaf 还提供`th:attrappend`和`th:attrprepend`属性,它们将 evaluation 的结果附加(后缀)或前置(前缀)到现有属性值。 + +对于 example,您可能希望 storeclass 的 name 被添加(未设置,只是添加)到 context 变量中的一个按钮,因为要使用的特定 CSS class 将取决于用户所做的事情。之前: + +```xml + +``` + +如果您使用`cssStyle`变量设置为`"warning"`来处理此模板,您将获得: + +```xml + +``` + +标准方言中还有两个特定的附加属性:`th:classappend`和`th:styleappend`属性,用于向元素添加 CSS class 或样式片段而不覆盖现有元素: + +```xml + +``` + +(不要担心`th:each`属性.这是一个迭代属性,我们将讨论它 later.) + +### Fixed-value boolean 属性 + +HTML 具有 boolean 属性的概念,没有 value 的属性和 1 的 precence 意味着 value 是“true”。在 XHTML 中,这些属性只占用 1 value,这本身也是如此。 + +对于 example,`checked`: + +```xml + + +``` + +标准方言包含允许您通过评估条件来设置这些属性的属性,因此如果计算为 true,则属性将设置为其固定 value,如果计算为 false,则不会设置该属性: + +```xml + +``` + +标准方言中存在以下 fixed-value boolean 属性: + +| | | | +| :------------------ | :------------- | :-------------- | +| `th:async` | `th:autofocus` | `th:autoplay` | +| `th:checked` | `th:controls` | `th:declare` | +| `th:default` | `th:defer` | `th:disabled` | +| `th:formnovalidate` | `th:hidden` | `th:ismap` | +| `th:loop` | `th:multiple` | `th:novalidate` | +| `th:nowrap` | `th:open` | `th:pubdate` | +| `th:readonly` | `th:required` | `th:reversed` | +| `th:scoped` | `th:seamless` | `th:selected` | + +### 设置任何属性的 value(默认属性处理器) + +Thymeleaf 提供了一个默认属性处理器,允许我们设置任何属性的 value,即使在标准方言中没有为它定义特定的`th:*`处理器。 + +所以类似于: + +```xml +... +``` + +将导致: + +```xml +... +``` + +### 支持 HTML5-friendly 属性和元素名称 + +也可以使用完全不同的语法以更加 HTML5-friendly 的方式将处理器应用于模板。 + +```xml + + + + + +
    ......
    +``` + +`data-{prefix}-{name}`语法是在 HTML5 中编写自定义属性的标准方法,无需开发人员使用任何名称空间的名称,如`th:*`。 Thymeleaf 使所有方言(不仅是标准方言)自动使用此语法。 + +还有一种语法来指定自定义标记:`{prefix}-{name}`,它遵循 W3C 自定义元素规范(较大的 W3C Web 组件规范的一部分)。例如,这可用于`th:block`元素(或`th-block`),这将在后面的部分中解释。 + +**重要说明:**此语法是命名空间`th:*`的一个补充,它不会替换它。完全没有意图在将来弃用命名空间语法。 + +## 迭代 + +到目前为止,我们已经创建了一个主页,一个用户 profile 页面以及一个允许用户订阅我们的新闻通讯的页面......但是我们的产品呢?为此,我们需要一种方法来迭代集合中的项目以构建我们的产品页面。 + +### 迭代基础知识 + +要在`/WEB-INF/templates/product/list.html`页面中显示产品,我们将使用 table。我们的每个产品都会连续显示(一个元素),因此对于我们的模板,我们需要创建一个模板行 - 一个可以说明我们希望如何显示每个产品的模板行 - 然后指示 Thymeleaf 重复它,每个产品一次。 + +标准方言为我们提供了一个属性:`th:each`。 + +#### 使用 th:each + +对于我们的产品列表页面,我们需要一个控制器方法,从服务层检索产品列表并将其添加到模板 context: + +```java +public void process( + final HttpServletRequest request, final HttpServletResponse response, + final ServletContext servletContext, final ITemplateEngine templateEngine) + throws Exception { + + ProductService productService = new ProductService(); + List allProducts = productService.findAll(); + + WebContext ctx = new WebContext(request, response, servletContext, request.getLocale()); + ctx.setVariable("prods", allProducts); + + templateEngine.process("product/list", ctx, response.getWriter()); + +} +``` + +然后我们将在模板中使用`th:each`迭代产品列表: + +```xml + + + + + + Good Thymes Virtual Grocery + + + + + + +

    Product list

    + + + + + + + + + + + + +
    NAMEPRICEIN STOCK
    Onions2.41yes
    + +

    + Return to home +

    + + + + +``` + +您在上面看到的`prod : ${prods}`属性 value 意味着“对于评估`${prods}`的结果中的每个元素,使用名为 prod 的变量中的当前元素重复此模板片段”。让我们给出一个 name 每个我们看到的东西: + +- 我们将`${prods}`称为迭代表达式或迭代变量。 +- 我们将`prod`称为迭代变量或简称为变量。 + +请注意,`prod` iter 变量的作用域为元素,这意味着它可用于内部标记,如`。 + +#### 可重复的值 + +`java.util.List` class 不是可用于 Thymeleaf 迭代的唯一值。有一组非常完整的 objects 被认为是`th:each`属性可迭代的: + +- 任何 object 实现`java.util.Iterable` +- 任何 object 实现`java.util.Enumeration`。 +- 任何实现`java.util.Iterator`的 object,其值将在迭代器返回时使用,而不需要缓存 memory 中的所有值。 +- 任何 object 实现`java.util.Map`。迭代 maps 时,iter 变量将是 class `java.util.Map.Entry`。 +- 任何 array。 +- 任何其他 object 都将被视为包含 object 本身的 single-valued 列表。 + +### 保持迭代状态 + +当使用`th:each`时,Thymeleaf 提供了一种用于跟踪迭代状态的机制:状态变量。 + +状态变量在`th:each`属性中定义,并包含以下数据: + +- 当前迭代索引,从 0 开始。这是`index` property。 +- 当前迭代索引,从 1 开始。这是`count` property。 +- 迭代变量中元素的总量。这是`size` property。 +- 每次迭代的 iter 变量。这是`current` property。 +- 当前迭代是偶数还是奇数。这些是`even/odd` boolean properties。 +- 当前迭代是否是第一个。这是`first` boolean property。 +- 当前迭代是否是最后一次。这是`last` boolean property。 + +让我们看看我们如何将它与前面的例子一起使用: + +```xml + + + + + + + + + + + +
    NAMEPRICEIN STOCK
    Onions2.41yes
    +``` + +状态变量(此 example 中的`iterStat`)在`th:each`属性中定义,方法是在 iter 变量本身之后写入 name,用逗号分隔。就像 iter 变量一样,status 变量的范围也是由持有`th:each`属性的标记定义的 code 的片段。 + +我们来看看处理模板的结果: + +```xml + + + + + + Good Thymes Virtual Grocery + + + + + + +

    Product list

    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NAMEPRICEIN STOCK
    Fresh Sweet Basil4.99yes
    Italian Tomato1.25no
    Yellow Bell Pepper2.50yes
    Old Cheddar18.75yes
    + +

    + Return to home +

    + + + + +``` + +请注意,我们的迭代状态变量已经完美地工作,只将`odd` CSS class 设置为奇数行。 + +如果您没有显式设置状态变量,Thymeleaf 将始终通过将`Stat`后缀为迭代变量的 name 来为您创建一个: + +```xml + + + + + + + + + + + +
    NAMEPRICEIN STOCK
    Onions2.41yes
    +``` + +### 通过延迟检索数据进行优化 + +有时我们可能希望优化数据集合的检索(来自数据库的 e.g. ),这样只有在真正使用它们时才会检索这些集合。 + +> 实际上,这可以应用于任何数据,但考虑到 in-memory 集合可能具有的大小,检索要迭代的集合是此方案最常见的情况。 + +为了支持这一点,Thymeleaf 提供了一种延迟加载 context 变量的机制。实现`ILazyContextVariable`接口的 Context 变量 - 最有可能通过扩展其`LazyContextVariable` default implementation - 将在正在执行的 moment 中解析。例如: + +```java +context.setVariable( + "users", + new LazyContextVariable>() { + @Override + protected List loadValue() { + return databaseRepository.findAllUsers(); + } + }); +``` + +这个变量可以在不知道其惰性的情况下使用,在 code 中如下: + +```xml +
      +
    • user name
    • +
    +``` + +但是在同一 time,如果`condition`在 code 中评估为`false`,则永远不会被初始化(它的`loadValue()`方法永远不会被调用),例如: + +```xml +
      +
    • user name
    • +
    +``` + +## 有条件的 Evaluation + +### 简单条件:“if”和“除非” + +有时,如果满足某个条件,您将需要模板的片段才会出现在结果中。 + +例如,假设我们希望在我们的产品 table 中显示一个列,其中包含每个产品存在的 comments 数量,如果有任何 comments,则指向该产品的 comment 详细信息页面的链接。 + +在 order 中,我们将使用`th:if`属性: + +```xml + + + + + + + + + + + + + +
    NAMEPRICEIN STOCKCOMMENTS
    Onions2.41yes + 2 comment/s + view +
    +``` + +这里有很多东西要看,所以让我们关注重要的 line: + +```xml +view +``` + +这将创建指向 comments 页面(带有 URL `/product/comments`)的链接,其中`prodId`参数设置为产品的`id`,但前提是该产品具有任何 comments。 + +让我们看一下结果标记: + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NAMEPRICEIN STOCKCOMMENTS
    Fresh Sweet Basil4.99yes + 0 comment/s +
    Italian Tomato1.25no + 2 comment/s + view +
    Yellow Bell Pepper2.50yes + 0 comment/s +
    Old Cheddar18.75yes + 1 comment/s + view +
    +``` + +完善!这正是我们想要的。 + +请注意,`th:if`属性不仅会评估 boolean 条件。它的功能稍微超出了它,它会根据这些规则将指定的表达式评估为`true`: + +- 如果 value 不为 null: +- 如果 value 是 boolean 并且是`true`。 +- 如果 value 是一个数字并且是 non-zero +- 如果 value 是一个字符并且是 non-zero +- 如果 value 是 String 并且不是“false”,“off”或“no” +- 如果 value 不是 boolean,数字,字符或 String。 +- (如果 value 为 null,则 th:if 将计算为 false)。 + +此外,`th:if`有一个逆属性`th:unless`,我们可以在之前的 example 中使用它,而不是在 OGNL 表达式中使用`not`: + +```xml +view +``` + +### 切换语句 + +还有一种方法可以使用 Java 中的等效开关结构有条件地显示内容:`th:switch`/`th:case`属性集。 + +```xml +
    +

    User is an administrator

    +

    User is a manager

    +
    +``` + +请注意,只要将一个`th:case`属性计算为`true`,同一个 switch context 中的每个其他`th:case`属性都将被计算为`false`。 + +默认选项指定为`th:case="*"`: + +```xml +
    +

    User is an administrator

    +

    User is a manager

    +

    User is some other thing

    +
    +``` + +## 模板布局 + +### 包括模板片段 + +#### 定义和引用片段 + +在我们的模板中,我们通常希望包含来自其他模板的部分,例如页脚,页眉,菜单...... + +为了做到这一点,Thymeleaf 需要我们定义这些部分,“片段”,以便包含,这可以使用`th:fragment`属性来完成。 + +假设我们要为所有杂货页面添加标准版权页脚,因此我们创建一个包含此 code 的`/WEB-INF/templates/footer.html`文件: + +```xml + + + + + + +
    + © 2011 The Good Thymes Virtual Grocery +
    + + + + +``` + +上面的 code 定义了一个名为`copy`的片段,我们可以使用`th:insert`或`th:replace`属性中的一个轻松地在我们的主页中包含它(以及`th:include`,但是自 Thymeleaf 3.0 以来不再推荐使用它): + +```xml + + + ... + +
    + + +``` + +请注意,`th:insert`需要一个片段表达式(`~{...}`),它是一个导致片段的表达式。在上面的 example 中,这是一个 non-complex 片段表达式,(`~{`,`}`)封闭是完全可选的,所以上面的 code 等同于: + +```xml + + + ... + +
    + + +``` + +#### 片段规范语法 + +片段表达式的语法非常简单。有三种不同的格式: + +- `"~{templatename::selector}"`包括在名为`templatename`的模板上应用指定标记选择器而产生的片段。请注意`selector`可以只是片段 name,因此您可以像上面的`~{footer :: copy}`一样指定像`~{templatename::fragmentname}`这样简单的东西。 + +> 标记选择器语法由底层 AttoParser 解析 library 定义,类似于 XPath 表达式或 CSS selectors。有关详细信息,请参阅[附录 C.](https://www.docs4dev.com/docs/zh/thymeleaf/3.0/reference/using_thymeleaf.html#appendix-c-markup-selector-syntax)。 + +- `"~{templatename}"`包含名为`templatename`的完整模板。 + +> 请注意,您在`th:insert`/`th:replace`标记中使用的模板 name 必须由模板引擎当前使用的模板解析器解析。 + +- `~{::selector}"`或`"~{this::selector}"`从同一模板插入一个与`selector`匹配的片段。如果在表达式出现的模板上找不到,则模板 calls(插入)的堆栈将遍历最初处理的模板(根),直到`selector`匹配某个 level。 + +上面例子中的`templatename`和`selector`都可以是 fully-featured 表达式(偶数条件!),如: + +```xml +
    +``` + +再次注意`th:insert`/`th:replace`中周围的`~{...}`包络是如何可选的。 + +片段可以包含任何`th:*`属性。一旦将片段包含在目标模板(具有`th:insert`/`th:replace`属性的模板)中,就会评估这些属性,并且它们将能够引用此目标模板中定义的任何 context 变量。 + +> 这种片段方法的一大优点是,您可以在浏览器完美显示的页面中编写片段,具有完整且有效的标记结构,同时仍保留使 Thymeleaf 将其包含在其他模板中的能力。 + +#### 在没有 th:fragment 的情况下引用片段 + +由于 Markup Selector 的强大功能,我们可以包含不使用任何`th:fragment`属性的片段。它甚至可以是来自不同的 application 的标记 code,完全不了解 Thymeleaf: + +```xml +... +
    + © 2011 The Good Thymes Virtual Grocery +
    +... +``` + +我们可以使用上面的片段简单地通过其`id`属性引用它,方式与 CSS 选择器类似: + +```xml + + + ... + +
    + + +``` + +#### th:insert 和 th:replace(和 th:include)之间的区别 + +`th:insert`和`th:replace`之间有什么区别(和`th:include`,自 3.0 以来不推荐)? + +- `th:insert`是最简单的:它只是将指定的片段作为其 host 标记的主体插入。 +- `th:replace`实际上用指定的片段替换了它的 host 标记。 +- `th:include`类似于`th:insert`,但它不是插入片段,而是仅插入此片段的内容。 + +所以像这样的 HTML 片段: + +```xml +
    + © 2011 The Good Thymes Virtual Grocery +
    +``` + +...在 host ``标签中包含三次,如下所示: + +```xml + + + ... + +
    + +
    + +
    + + +``` + +......将导致: + +```xml + + + ... + +
    +
    + © 2011 The Good Thymes Virtual Grocery +
    +
    + +
    + © 2011 The Good Thymes Virtual Grocery +
    + +
    + © 2011 The Good Thymes Virtual Grocery +
    + + +``` + +### 可参数化的片段签名 + +在 order 中为模板片段创建更多 function-like 机制,使用`th:fragment`定义的片段可以指定一组参数: + +```xml +
    +

    ...

    +
    +``` + +这需要使用这两种语法之一来从`th:insert`或`th:replace`调用片段: + +```xml +
    ...
    +
    ...
    +``` + +请注意,order 在最后一个选项中并不重要: + +```xml +
    ...
    +``` + +#### 片段局部变量没有片段 arguments + +即使片段没有像这样的 arguments 定义: + +```xml +
    + ... +
    +``` + +我们可以使用上面指定的第二种语法来调用它们(只有第二种语法): + +```xml +
    +``` + +这相当于`th:replace`和`th:with`的组合: + +```xml +
    +``` + +**注意**片段的局部变量规范 - 无论是否有参数签名 - 都不会导致 context 在执行之前被清空。片段仍然可以像访问目前那样访问调用模板中使用的每个 context 变量。 + +#### th:assert 表示 in-template 断言 + +`th:assert`属性可以指定应该被计算的 comma-separated 表达式列表,并为每个 evaluation 生成 true,否则会引发 exception。 + +```xml +
    ...
    +``` + +这对于验证片段签名的参数非常方便: + +```xml +
    ...
    +``` + +### 灵活的布局:仅仅是片段插入 + +由于片段表达式,我们可以为不是 text,numbers,bean objects 的片段指定参数,而是指定标记片段。 + +这允许我们以一种方式创建我们的片段,使得它们可以通过来自调用模板的标记来丰富,从而产生非常灵活的**模板布局机制**。 + +注意在下面的片段中使用`title`和`links`变量: + +```xml + + + The awesome application + + + + + + + + + + +``` + +我们现在可以将这个片段称为: + +```xml +... + + + Awesome - Main + + + + + +... +``` + +...结果将使用调用模板中的实际`和`标记作为`title`和`links`变量的值,从而导致我们的片段在插入期间自定义: + +```xml +... + + + Awesome - Main + + + + + + + + + + +... +``` + +#### 使用空片段 + +特殊片段表达式,即空片段(`~{}`),可用于指定无标记。使用前面的 example: + +```xml + + + Awesome - Main + + +... +``` + +注意片段(`links`)的第二个参数如何设置为空片段,因此没有为``块写入任何内容: + +```xml +... + + + Awesome - Main + + + + + + + +... +``` + +#### 使用 no-operation 标记 + +如果我们只想让我们的片段使用其当前标记作为默认 value,no-op 也可以用作片段的参数。再次,使用`common_header` example: + +```xml +... + + + Awesome - Main + + + + + +... +``` + +看看`title`参数(`common_header`片段的第一个参数)是如何设置为 no-op(`_`)的,这导致片段的这一部分根本不被执行(`title` = no-operation): + +```xml +The awesome application +``` + +结果是: + +```xml +... + + + The awesome application + + + + + + + + + + +... +``` + +#### 高级条件插入片段 + +emtpy 片段和 no-operation 令牌的可用性允许我们以非常简单和优雅的方式执行片段的条件插入。 + +对于 example,我们可以在 order 中执行此操作,只有在用户是管理员时 insert 我们的`common :: adminhead`片段,如果不是,则 insert 什么(emtpy 片段): + +```xml +... +
    ...
    +... +``` + +此外,我们可以使用 order 中的 no-operation 标记仅在满足指定条件时插入片段,但如果不满足条件则保留标记而不进行修改: + +```xml +... +
    + Welcome [[${user.name}]], click here for help-desk support. +
    +... +``` + +另外,如果我们已经配置了模板解析器以检查模板资源是否存在 - 通过它们的`checkExistence` flag - 我们可以使用片段本身的存在作为默认操作中的条件: + +```xml +... + + +
    + Welcome [[${user.name}]], click here for help-desk support. +
    +... +``` + +### 删除模板片段 + +回到 example application,让我们重新审视产品列表模板的最后一个 version: + +```xml + + + + + + + + + + + + + +
    NAMEPRICEIN STOCKCOMMENTS
    Onions2.41yes + 2 comment/s + view +
    +``` + +这个 code 作为一个模板很好,但作为一个静态页面(当浏览器直接打开而没有 Thymeleaf 处理它时)它不会成为一个好的原型。 + +为什么?因为虽然浏览器可以完全显示,但 table 只有一行,而且这行包含 mock 数据。作为原型,它看起来不够逼真......我们应该有多个产品,我们需要更多的行。 + +所以让我们添加一些: + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + +
    NAMEPRICEIN STOCKCOMMENTS
    Onions2.41yes + 2 comment/s + view +
    Blue Lettuce9.55no + 0 comment/s +
    Mild Cinnamon1.99yes + 3 comment/s + view +
    +``` + +好的,现在我们有三个,对原型来说肯定更好。但是......当我们用 Thymeleaf 处理它时会发生什么?: + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NAMEPRICEIN STOCKCOMMENTS
    Fresh Sweet Basil4.99yes + 0 comment/s +
    Italian Tomato1.25no + 2 comment/s + view +
    Yellow Bell Pepper2.50yes + 0 comment/s +
    Old Cheddar18.75yes + 1 comment/s + view +
    Blue Lettuce9.55no + 0 comment/s +
    Mild Cinnamon1.99yes + 3 comment/s + view +
    +``` + +最后两行是 mock 行!嗯,当然它们是:迭代只适用于第一行,所以没有理由为什么 Thymeleaf 应该删除其他两个。 + +我们需要一种在模板处理过程中删除这两行的方法。让我们在第二个和第三个标签上使用`th:remove`属性: + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + +
    NAMEPRICEIN STOCKCOMMENTS
    Onions2.41yes + 2 comment/s + view +
    Blue Lettuce9.55no + 0 comment/s +
    Mild Cinnamon1.99yes + 3 comment/s + view +
    +``` + +处理完毕后,所有内容都会再次显示: + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NAMEPRICEIN STOCKCOMMENTS
    Fresh Sweet Basil4.99yes + 0 comment/s +
    Italian Tomato1.25no + 2 comment/s + view +
    Yellow Bell Pepper2.50yes + 0 comment/s +
    Old Cheddar18.75yes + 1 comment/s + view +
    +``` + +那个属性中的`all` value 是什么意思? `th:remove`可以以五种不同的方式运行,具体取决于其 value: + +- `all`:删除包含标记及其所有 children。 +- `body`:不要删除包含标记,但删除所有 children。 +- `tag`:删除包含标记,但不删除其 children。 +- `all-but-first`:删除包含第一个标记的所有 children。 +- `none`:什么都不做。此 value 对动态 evaluation 很有用。 + +`all-but-first` value 有什么用?在原型设计时,它会让我们保存一些`th:remove="all"`: + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NAMEPRICEIN STOCKCOMMENTS
    Onions2.41yes + 2 comment/s + view +
    Blue Lettuce9.55no + 0 comment/s +
    Mild Cinnamon1.99yes + 3 comment/s + view +
    +``` + +`th:remove`属性可以采用任何 Thymeleaf 标准表达式,因为它返回一个允许的 String 值(`all`,`tag`,`body`,`all-but-first`或`none`)。 + +这意味着删除可能是有条件的,例如: + +```xml +Link text not to be removed +``` + +另请注意,`th:remove`认为`null`是`none`的同义词,因此以下工作方式与上面的 example 相同: + +```xml +Link text not to be removed +``` + +在这种情况下,如果`${condition}`是 false,将返回`null`,因此不会执行删除操作。 + +### 布局继承 + +为了能够将单个文件作为布局,可以使用片段。使用`th:fragment`和`th:replace`的`title`和`content`的简单布局的示例: + +```xml + + + + Layout Title + + +

    Layout H1

    +
    +

    Layout content

    +
    +
    + Layout footer +
    + + +``` + +此 example 声明了一个名为**layout**的片段,其标题和内容作为参数。两者都将在页面上替换,并在下面的 example 中通过提供的片段表达式继承它。 + +```xml + + + + Page Title + + +
    +

    Page content

    +
    Included on page
    +
    + + +``` + +在此文件中,`html`标记将替换为布局,但在布局中`title`和`content`将分别替换为`title`和`section`块。 + +如果需要,布局可以由多个片段组成页眉和页脚。 + +## 局部变量 + +Thymeleaf calls 局部变量是为模板的特定片段定义的变量,并且仅可用于该片段内的 evaluation。 + +我们已经看到的示例是产品列表页面中的`prod` iter 变量: + +```xml + + ... + +``` + +该`prod`变量仅在标记的范围内可用。特别: + +- 它将可用于在该标记中执行的任何其他`th:*`属性,其优先级低于`th:each`(这意味着它们将在`th:each`之后执行)。 +- 它将可用于标记的任何 child 元素,例如任何`元素。 + +Thymeleaf 为您提供了一种使用`th:with`属性声明局部变量而无需迭代的方法,其语法类似于属性 value 赋值: + +```xml +
    +

    + The name of the first person is Julius Caesar. +

    +
    +``` + +处理`th:with`时,该`firstPer`变量被创建为局部变量并添加到来自 context 的变量 map 中,因此它可用于 evaluation 以及 context 中声明的任何其他变量,但仅限于包含的范围内``标签。 + +您可以使用通常的多重赋值语法在同一 time 定义多个变量: + +```xml +
    +

    + The name of the first person is Julius Caesar. +

    +

    + But the name of the second person is + Marcus Antonius. +

    +
    +``` + +`th:with`属性允许重用在同一属性中定义的变量: + +```xml +
    ...
    +``` + +让我们在 Grocery 的主页上使用它!还记得我们为输出格式化的 date 而编写的 code 吗? + +```xml +

    + Today is: + 13 february 2011 +

    +``` + +那么,如果我们希望`"dd MMMM yyyy"`实际上依赖于 locale 呢?对于 example,我们可能希望将以下消息添加到`home_en.properties`: + +```java +date.format=MMMM dd'','' yyyy +``` + +......和我们`home_es.properties`的等价物: + +```java +date.format=dd ''de'' MMMM'','' yyyy +``` + +现在,让我们使用`th:with`将本地化 date 格式转换为变量,然后在`th:text`表达式中使用它: + +```xml +

    + Today is: 13 February 2011 +

    +``` + +那简洁干净。事实上,鉴于`th:with`的`precedence`比`th:text`高,我们可以在`span`标签中解决这个问题: + +```xml +

    + Today is: + 13 February 2011 +

    +``` + +你可能会想:优先顺序?我们还没有谈过这个!好吧,不要担心,因为这正是下一章的内容。 + +## 属性优先 + +在同一个标签中写入多个`th:*`属性会发生什么?例如: + +```xml +
      +
    • Item description here...
    • +
    +``` + +我们希望在`th:text`之前执行`th:each`属性,以便我们得到我们想要的结果,但是考虑到 HTML/XML 标准没有给写入标签中的属性的 order 赋予任何意义,优先级必须在 order 中的属性本身中建立机制,以确保它将按预期工作。 + +因此,所有 Thymeleaf 属性都定义了一个数字优先级,它建立了在标记中执行它们的 order。这个 order 是: + +| 订购 | 特征 | 属性 | +| :--- | :------------------ | :----------------------------------------- | +| 1 | 片段包含 | `th:insert` `th:replace` | +| 2 | 片段迭代 | `th:each` | +| 3 | 有条件的 evaluation | `th:if` `th:unless` `th:switch` `th:case` | +| 4 | 局部变量定义 | `th:object` `th:with` | +| 5 | 一般属性修改 | `th:attr` `th:attrprepend` `th:attrappend` | +| 6 | 具体属性修改 | `th:value` `th:href` `th:src` `...` | +| 7 | 文字(标签正文修改) | `th:text` `th:utext` | +| 8 | 片段规范 | `th:fragment` | +| 9 | 片段删除 | `th:remove` | + +这个优先级机制意味着如果属性位置被反转,上面的迭代片段将给出完全相同的结果(虽然它的可读性稍差): + +```xml +
      +
    • Item description here...
    • +
    +``` + +## Comments 和 Blocks + +### 标准 HTML/XML comments + +标准 HTML/XML comments ``可以在 Thymeleaf 模板中的任何位置使用。这些 comments 中的任何内容都不会由 Thymeleaf 处理,并将逐字复制到结果中: + +```xml + +
    + ... +
    +``` + +### Thymeleaf parser-level comment 阻止 + +Parser-level comment 块是 code,当 Thymeleaf 解析时,它将被简单地从模板中删除。它们看起来像这样: + +```java + +``` + +Thymeleaf 将删除`和`\*/-->`之间的所有内容,因此当模板静态打开时,这些 comment 块也可用于显示 code,知道在 Thymeleaf 处理它时它将被删除: + +```xml + +
    + you can see me only before Thymeleaf processes me! +
    + +``` + +对于具有大量的表进行原型设计,这可能非常方便,例如: + +```xml + + + ... + + + + ... + + + ... + + +
    +``` + +### Thymeleaf prototype-only comment 阻止 + +当模板静态打开(i.e.作为原型)时,Thymeleaf 允许定义标记为 comments 的特殊 comment 块,但在执行模板时 Thymeleaf 认为是正常标记。 + +```xml +hello! + +goodbye! +``` + +Thymeleaf 的解析系统将简单地删除`和`/\*/-->`标记,但不删除其内容,因此将取消注释。因此,在执行模板时,Thymeleaf 实际上会看到: + +```xml +hello! + +
    + ... +
    + +goodbye! +``` + +与 parser-level comment 块一样,此 feature 为 dialect-independent。 + +### 合成 th:block 标签 + +Thymeleaf 唯一包含在标准方言中的元素处理器(不是属性)是`th:block`。 + +`th:block`是一个纯粹的属性容器,允许模板开发人员指定他们想要的任何属性。 Thymeleaf 将执行这些属性,然后简单地使块,但不是它的内容,消失。 + +所以它可能是有用的,例如,当 creating 每个元素需要多个的迭代表时: + +```xml + + + + + + + + + + +
    ......
    ...
    +``` + +当与 prototype-only comment 块结合使用时尤其有用: + +```xml + + + + + + + + + + +
    ......
    ...
    +``` + +注意这个解决方案如何允许模板是有效的 HTML(不需要在`中添加禁止的`块),并且在浏览器中静态打开时仍然可以正常工作! + +## 内联 + +### 表达内联 + +虽然标准方言允许我们使用标记属性来完成几乎所有操作,但在某些情况下我们可能更喜欢将表达式直接编写到 HTML 文本中。例如,我们更喜欢写这个: + +```xml +

    Hello, [[${session.user.name}]]!

    +``` + +......而不是这个: + +```xml +

    Hello, Sebastian!

    +``` + +`[[...]]`或`[(...)]`之间的表达式被认为是 Thymeleaf 中的**内联表达式**,在其中我们可以使用任何在`th:text`或`th:utext`属性中也有效的表达式。 + +请注意,虽然`[[...]]`对应于`th:text`(i.e.结果将是 HTML-escaped),但`[(...)]`对应于`th:utext`并且不会执行任何 HTML-escaping。所以对于一个变量如`msg = 'This is great!'`,给定这个片段: + +```xml +

    The message is "[(${msg})]"

    +``` + +结果将使那些``标签未转义,因此: + +```xml +

    The message is "This is great!"

    +``` + +而如果像以下一样逃脱: + +```xml +

    The message is "[[${msg}]]"

    +``` + +结果将是 HTML-escaped: + +```xml +

    The message is "This is <b>great!</b>"

    +``` + +请注意,**文本内联在我们的标记中的每个标记的主体中默认为 active** - 而不是标记本身 - 因此我们无需执行任何操作。 + +#### 内联 vs 自然模板 + +如果你来自其他模板引擎,其中这种输出文本的方式是常态,你可能会问:为什么我们从一开始就不这样做?它比所有`th:text`属性更少 code! + +好吧,在那里要小心,因为尽管你可能会发现内联非常有趣,但是你应该永远记住,当你静态打开它们时,内联表达式将逐字显示在 HTML files 中,因此你可能无法将它们用作设计原型了! + +浏览器静态显示 code 片段而不使用内联的区别... + +```java +Hello, Sebastian! +``` + +......并使用它...... + +```java +Hello, [[${session.user.name}]]! +``` + +......在设计实用性方面非常清楚。 + +#### 禁用内联 + +但是可以禁用此机制,因为实际上可能存在我们想要输出`[[...]]`或`[(...)]` sequences 而不将其内容作为表达式处理的情况。为此,我们将使用`th:inline="none"`: + +```xml +

    A double array looks like this: [[1, 2, 3], [4, 5]]!

    +``` + +这将导致: + +```xml +

    A double array looks like this: [[1, 2, 3], [4, 5]]!

    +``` + +### 文字内联 + +文本内联与我们刚刚看到的表达内联功能非常相似,但它实际上增加了更多功能。必须使用`th:inline="text"`显式启用它。 + +文本内联不仅允许我们使用我们刚刚看到的相同内联表达式,而且实际上处理标签主体就好像它们是在`TEXT`模板模式下处理的模板一样,这允许我们执行 text-based 模板逻辑(不仅仅是输出表达式)。 + +我们将在下一章中看到有关文本模板模式的更多信息。 + +### JavaScript 内联 + +JavaScript 内联允许在`HTML`模板模式下处理的模板中更好地整合 JavaScript ``块。 + +与文本内联一样,这实际上相当于处理脚本内容,就好像它们是`JAVASCRIPT`模板模式中的模板一样,因此文本模板模式的所有功能(见下一章)都将在眼前。但是,在本节中,我们将重点介绍如何使用它将 Thymeleaf 表达式的输出添加到 JavaScript 块中。 + +必须使用`th:inline="javascript"`显式启用此模式: + +```xml + +``` + +这将导致: + +```xml + +``` + +上面的 code 中需要注意的两件重要事项: + +首先,JavaScript 内联不仅会输出所需的文本,还会使用引号和 JavaScript-escape 其内容将其括起来,以便表达式结果输出为**well-formed JavaScript 文字**。 + +其次,发生这种情况是因为我们输出`${session.user.name}`表达式为**转义**,i.e。使用 double-bracket 表达式:`[[${session.user.name}]]`。如果相反,我们使用非转义,如: + +```xml + +``` + +结果如下: + +```xml + +``` + +...这是一个格式错误的 JavaScript code。但是,如果我们通过附加内联表达式来构建脚本的一部分,那么输出未转义的内容可能就是我们所需要的,因此最好有这个工具。 + +#### JavaScript 自然模板 + +所提到的 JavaScript 内联机制的智能远不止仅仅应用 JavaScript-specific 转义并将表达式结果输出为有效\_lite。 + +对于 example,我们可以在 JavaScript comments 中包装我们的(转义的)内联表达式,如: + +```xml + +``` + +并且 Thymeleaf 将忽略我们在 comment 之后和分号之前(在这种情况下为`'Gertrud Kiwifruit'`)编写的所有内容,因此执行此操作的结果将与我们不使用包装 comments 时完全相同: + +```xml + +``` + +但要仔细看看原始模板 code: + +```xml + +``` + +请注意这是**有效的 JavaScript** code。当您以静态方式打开模板文件时(无需在服务器上执行),它将完美执行。 + +所以我们这里有一个方法来做**JavaScript 自然模板**! + +#### 高级内联 evaluation 和 JavaScript 序列化 + +关于 JavaScript 内联的一个重要注意事项是,这个表达式 evaluation 是智能的,不仅限于 Strings。 Thymeleaf 将使用以下类型的 objects 正确编写 JavaScript 语法: + +- Strings +- Numbers +- 布尔 +- 数组 +- 集合 +- Maps +- Beans(带有 getter 和 setter 方法的 objects) + +例如,如果我们有以下 code: + +```xml + +``` + +`${session.user}`表达式将评估为`User` object,而 Thymeleaf 将正确地将其转换为 Javascript 语法: + +```xml + +``` + +完成此 JavaScript 序列化的方法是通过`org.thymeleaf.standard.serializer.IStandardJavaScriptSerializer`接口的 implementation 实现,该接口可以在模板引擎上使用的`StandardDialect`实例上进行配置。 + +此 JS 序列化机制的默认 implementation 将在 classpath 中查找[Jackson library](https://github.com/FasterXML/jackson),如果存在,将使用它。如果没有,它将应用 built-in 序列化机制,涵盖大多数场景的需要并产生类似的结果(但不太灵活)。 + +### CSS 内联 + +Thymeleaf 还允许在 CSS ``标签中使用内联,例如: + +```xml + +``` + +对于 example,假设我们将两个变量设置为两个不同的`String`值: + +```java +classname = 'main elems' +align = 'center' +``` + +我们可以像以下一样使用它们: + +```xml + +``` + +结果将是: + +```xml + +``` + +请注意 CSS 内联如何具有一些智能,就像 JavaScript 一样。具体来说,通过像`[[${classname}]]`这样的转义表达式输出的表达式将作为**CSS 标识符**进行转义。这就是为什么我们的`classname = 'main elems'`在上面的 code 片段中变成`main\ elems`的原因。 + +#### 高级 features:CSS 自然模板等 + +与之前针对 JavaScript 解释的内容相同,CSS 内联还允许我们的``标记静态和动态地工作,i.e。作为**CSS 自然模板**通过在 comments 中包装内联表达式。看到: + +```xml + +``` + +## 文本模板模式 + +### 文本语法 + +三个 Thymeleaf 模板模式被认为是**文本**:`TEXT`,`JAVASCRIPT`和`CSS`。这使它们与标记模板模式区分开来:`HTML`和`XML`。 + +文本模板模式和标记模式之间的 key 区别在于,在文本模板中没有标签可以以属性的形式插入逻辑,因此我们必须依赖其他机制。 + +这些机制的第一个也是最基本的是**内联**,我们已经在前一章中详细介绍过了。内联语法是在文本模板模式下输出表达式结果的最简单方法,因此这是文本电子邮件的完美有效模板。 + +```java +Dear [(${name})], + + Please find attached the results of the report you requested + with name "[(${report.name})]". + + Sincerely, + The Reporter. +``` + +即使没有标签,上面的 example 也是一个完整有效的 Thymeleaf 模板,可以在`TEXT`模板模式下执行。 + +但是为了包含比仅仅输出表达式更复杂的逻辑,我们需要一个新的 non-tag-based 语法: + +```java +[## th:each="item : ${items}"] + - [(${item})] +[/] +``` + +这实际上是更详细的浓缩 version: + +```java +[#th:block th:each="item : ${items}"] + - [#th:block th:utext="${item}" /] +[/th:block] +``` + +请注意这个新语法是如何基于声明为`[#element ...]`而不是``的元素(i.e.可处理标记)。元素像`[#element ...]`一样打开并像`[/element]`一样关闭,并且可以通过使用`/`以几乎等同于 XML 标记的方式最小化 open 元素来声明独立标记:`[#element ... /]`。 + +标准方言只包含其中一个元素的处理器:already-known `th:block`,虽然我们可以在我们的方言中扩展它,并以通常的方式创建新元素。此外,允许将`th:block`元素(`[#th:block ...] ... [/th:block]`)缩写为空 string(`[## ...] ... [/]`),因此上面的块实际上等效于: + +```java +[## th:each="item : ${items}"] + - [## th:utext="${item}" /] +[/] +``` + +并且假定`[## th:utext="${item}" /]`等同于内联非转义表达式,我们可以在 order 中使用它来减少 code。因此,我们最终得到了上面看到的 code 的第一个片段: + +```java +[## th:each="item : ${items}"] + - [(${item})] +[/] +``` + +请注意,文本语法需要完整的元素平衡(没有未关闭的标记)和引用的属性 - 它比 HTML-style 更多 XML-style。 + +让我们看一个更完整的`TEXT`模板示例,一个纯文本电子邮件模板: + +```java +Dear [(${customer.name})], + +This is the list of our products: + +[## th:each="prod : ${products}"] + - [(${prod.name})]. Price: [(${prod.price})] EUR/kg +[/] + +Thanks, + The Thymeleaf Shop +``` + +执行后,结果可能是这样的: + +```java +Dear Mary Ann Blueberry, + +This is the list of our products: + + - Apricots. Price: 1.12 EUR/kg + - Bananas. Price: 1.78 EUR/kg + - Apples. Price: 0.85 EUR/kg + - Watermelon. Price: 1.91 EUR/kg + +Thanks, + The Thymeleaf Shop +``` + +另一个例子是`JAVASCRIPT`模板模式,一个`greeter.js`文件,我们 process 作为文本模板,我们从 HTML 页面调用结果。请注意,这不是 HTML 模板中的``块,而是将`.js`文件作为模板自行处理: + +```java +var greeter = function() { + + var username = [[${session.user.name}]]; + + [## th:each="salut : ${salutations}"] + alert([[${salut}]] + " " + username); + [/] + +}; +``` + +执行后,结果可能是这样的: + +```java +var greeter = function() { + + var username = "Bertrand \"Crunchy\" Pear"; + + alert("Hello" + " " + username); + alert("Ol\u00E1" + " " + username); + alert("Hola" + " " + username); + +}; +``` + +#### 转义元素属性 + +在 order 中,为了避免与可能在其他模式中处理的模板部分(e.g. `text` -mode 内联`HTML`模板)进行交互,Thymeleaf 3.0 允许对其文本语法中元素的属性进行转义。所以: + +- `TEXT`模板模式中的属性将为 HTML-unescaped。 +- `JAVASCRIPT`模板模式中的属性将为 JavaScript-unescaped。 +- `CSS`模板模式中的属性将为 CSS-unescaped。 + +所以这在`TEXT` -mode 模板中完全没问题(注意`>`): + +```java +[## th:if="${120<user.age}"] + Congratulations! + [/] +``` + +当然`<`在真正的文本模板中没有任何意义,但如果我们正在处理一个包含上面的 code 的`th:inline="text"`块的 HTML 模板,那么它是一个很好的 idea,我们想确保我们的浏览器不会使用`静态打开文件作为原型时打开标记的 name。 + +### 可扩展性 + +这种语法的一个优点是它和标记一样可扩展。开发人员仍然可以使用自定义元素和属性定义自己的方言,为它们应用前缀(可选),然后在文本模板模式中使用它们: + +```java +[#myorg:dosomething myorg:importantattr="211"]some text[/myorg:dosomething] +``` + +### Textual prototype-only comment blocks:添加 code + +`JAVASCRIPT`和`CSS`模板模式(不适用于`TEXT`)允许在特殊的 comment 语法`/*[+...+]*/`之间包含 code,以便 Thymeleaf 在处理模板时自动取消注释这样的 code: + +```java +var x = 23; + +/*[+ + +var msg = "This is a working application"; + ++]*/ + +var f = function() { + ... +``` + +将被执行为: + +```java +var x = 23; + +var msg = "This is a working application"; + +var f = function() { +... +``` + +您可以在这些 comments 中包含表达式,它们将被评估: + +```java +var x = 23; + +/*[+ + +var msg = "Hello, " + [[${session.user.name}]]; + ++]*/ + +var f = function() { +... +``` + +### Textual parser-level comment blocks:删除 code + +以类似于 prototype-only comment 块的方式,所有三种文本模板模式(`TEXT`,`JAVASCRIPT`和`CSS`)都可以指示 Thymeleaf 删除特殊`/*[- */`和`/* -]*/`标记之间的 code,如下所示: + +```java +var x = 23; + +/*[- */ + +var msg = "This is shown only when executed statically!"; + +/* -]*/ + +var f = function() { +... +``` + +或者,在`TEXT`模式下: + +```java +... +/*[- Note the user is obtained from the session, which must exist -]*/ +Welcome [(${session.user.name})]! +... +``` + +### 自然 JavaScript 和 CSS 模板 + +如上一章所示,JavaScript 和 CSS 内联提供了在 JavaScript/CSS comments 中包含内联表达式的可能性,如: + +```java +... +var username = /*[[${session.user.name}]]*/ "Sebastian Lychee"; +... +``` + +...这是有效的 JavaScript,一旦执行可能看起来像: + +```java +... +var username = "John Apricot"; +... +``` + +在 comments 中包含内联表达式的相同技巧实际上可以用于整个文本模式语法: + +```java +/*[## th:if="${user.admin}"]*/ + alert('Welcome admin'); + /*[/]*/ +``` + +当模板静态打开时,将显示上述 code 中的警报 - 因为它是 100%有效的 JavaScript - 以及如果用户是管理员,则模板为 run 时也是如此。它相当于: + +```java +[## th:if="${user.admin}"] + alert('Welcome admin'); + [/] +``` + +...实际上是在模板解析期间转换初始 version 的 code。 + +但请注意,comments 中的包装元素不会像内联输出表达式那样清除它们所在的 lines(向右找到`;`)。该行为仅为内联输出表达式保留。 + +因此,Thymeleaf 3.0 允许以自然模板**的形式开发**复杂的 JavaScript 脚本和 CSS 样式表,既可用作原型,也可用作工作模板。 + +## 我们杂货店的更多页面 + +现在我们对使用 Thymeleaf 了解很多,我们可以在我们的网站上添加一些新的页面来进行 order management。 + +请注意,我们将重点关注 HTML code,但如果您想查看相应的控制器,可以查看捆绑的 source code。 + +### 订单列表 + +让我们从创建 order 列表页面`/WEB-INF/templates/order/list.html`开始: + +```xml + + + + + + + Good Thymes Virtual Grocery + + + + + + +

    Order list

    + + + + + + + + + + + + + + +
    DATECUSTOMERTOTAL
    13 jan 2011Frederic Tomato23.32 + view +
    + +

    + Return to home +

    + + + + +``` + +这里没有什么可以让我们感到惊讶,除了这一点 OGNL 魔法: + +```xml +23.32 +``` + +这样做,对于 order 中的每个 order line(`OrderLine` object),将其`purchasePrice`和`amount`properties(通过调用相应的`getPurchasePrice()`和`getAmount()`方法)相乘,并将结果返回到 numbers 列表中,稍后由`#aggregates.sum(...)` function 汇总。 order 获取 order 总价。 + +你必须喜欢 OGNL 的力量。 + +### 订单详情 + +现在,对于 order 详细信息页面,我们将在其中大量使用星号语法: + +```xml + + + + + + Good Thymes Virtual Grocery + + + + + + +

    Order details

    + +
    +

    Code: 99

    +

    + Date: + 13 jan 2011 +

    +
    + +

    Customer

    + +
    +

    Name: Frederic Tomato

    +

    + Since: + 1 jan 2011 +

    +
    + +

    Products

    + + + + + + + + + + + + +
    PRODUCTAMOUNTPURCHASE PRICE
    Strawberries323.32
    + +
    + TOTAL: + 35.23 +
    + +

    + Return to order list +

    + + + + +``` + +除了这个嵌套的 object 选择之外,这里没什么新东西: + +```xml + + + ... + +
    +

    Name: Frederic Tomato

    + ... +
    + + ... + +``` + +...这使`*{name}`相当于: + +```xml +

    Name: Frederic Tomato

    +``` + +## 更多关于 Configuration + +### 模板解析器 + +对于 Good Thymes Virtual Grocery,我们选择了一个名为`ServletContextTemplateResolver`的`ITemplateResolver` implementation,它允许我们从 Servlet Context 获取模板作为资源。 + +除了让我们能够通过实现`ITemplateResolver,` Thymeleaf 创建我们自己的模板解析器,包括四个开箱即用的\_implement: + +- `org.thymeleaf.templateresolver.ClassLoaderTemplateResolver`,将模板解析为类加载器资源,如: + +```java +return Thread.currentThread().getContextClassLoader().getResourceAsStream(template); +``` + +- `org.thymeleaf.templateresolver.FileTemplateResolver`,它将模板解析为文件系统中的 files,如: + +```java +return new FileInputStream(new File(template)); +``` + +- `org.thymeleaf.templateresolver.UrlTemplateResolver`,将模板解析为 URL(甚至是 non-local 个),如: + +```java +return (new URL(template)).openStream(); +``` + +- `org.thymeleaf.templateresolver.StringTemplateResolver`,它直接解析模板,因为`String`被指定为`template`(或模板 name,在这种情况下显然远远超过仅仅 name): + +```java +return new StringReader(templateName); +``` + +`ITemplateResolver`的所有 pre-bundled implementation 都允许使用相同的 configuration 参数集,其中包括: + +- 前缀和后缀(如前所见): + +```java +templateResolver.setPrefix("/WEB-INF/templates/"); +templateResolver.setSuffix(".html"); +``` + +- 允许使用与文件名不直接对应的模板名称的模板别名。如果同时存在 suffix/prefix 和别名,则将在 prefix/suffix 之前应用别名: + +```java +templateResolver.addTemplateAlias("adminHome","profiles/admin/home"); +templateResolver.setTemplateAliases(aliasesMap); +``` + +- 读取模板时要应用的编码: + +```java +templateResolver.setEncoding("UTF-8"); +``` + +- 要使用的模板模式: + +```java +// Default is HTML +templateResolver.setTemplateMode("XML"); +``` + +- 模板缓存的默认模式,以及用于定义特定模板是否可缓存的模式: + +```java +// Default is true +templateResolver.setCacheable(false); +templateResolver.getCacheablePatternSpec().addPattern("/users/*"); +``` + +- 解析模板缓存条目的 TTL(以毫秒为单位)源自此模板解析程序。如果未设置,从缓存中删除条目的唯一方法是超过缓存最大大小(将删除最旧的条目)。 + +```java +// Default is no TTL (only cache size exceeded would remove entries) +templateResolver.setCacheTTLMs(60000L); +``` + +> Thymeleaf Spring integration 包提供`SpringResourceTemplateResolver` implementation,它使用所有 Spring 基础结构来访问和读取 applications 中的资源,这是 Spring-enabled applications 中推荐的 implementation。 + +#### 链接模板解析器 + +此外,模板引擎可以指定多个模板解析器,在这种情况下,可以在它们之间建立 order 以进行模板解析,这样,如果第一个无法解析模板,则会询问第二个,依此类推: + +```java +ClassLoaderTemplateResolver classLoaderTemplateResolver = new ClassLoaderTemplateResolver(); +classLoaderTemplateResolver.setOrder(Integer.valueOf(1)); + +ServletContextTemplateResolver servletContextTemplateResolver = + new ServletContextTemplateResolver(servletContext); +servletContextTemplateResolver.setOrder(Integer.valueOf(2)); + +templateEngine.addTemplateResolver(classLoaderTemplateResolver); +templateEngine.addTemplateResolver(servletContextTemplateResolver); +``` + +当应用多个模板解析器时,建议为每个模板解析器指定模式,以便 Thymeleaf 可以快速丢弃那些不打算解析模板的模板解析器,从而增强 performance。这样做不是必要条件,而是建议: + +```java +ClassLoaderTemplateResolver classLoaderTemplateResolver = new ClassLoaderTemplateResolver(); +classLoaderTemplateResolver.setOrder(Integer.valueOf(1)); +// This classloader will not be even asked for any templates not matching these patterns +classLoaderTemplateResolver.getResolvablePatternSpec().addPattern("/layout/*.html"); +classLoaderTemplateResolver.getResolvablePatternSpec().addPattern("/menu/*.html"); + +ServletContextTemplateResolver servletContextTemplateResolver = + new ServletContextTemplateResolver(servletContext); +servletContextTemplateResolver.setOrder(Integer.valueOf(2)); +``` + +如果未指定这些可解析的模式,我们将依赖于我们正在使用的每个`ITemplateResolver` \_implement 的特定功能。请注意,并非所有 implementations 都可以在解析之前确定模板的存在,因此可以始终将模板视为可解析和 break 解析链(不允许其他解析器检查相同的模板),但随后无法阅读真实资源。 + +核心 Thymeleaf 中包含的所有`ITemplateResolver` \_implement 包括一种机制,它允许我们在解析可解析之前让解析器真正检查资源是否存在。它是`checkExistence` flag,其工作方式如下: + +```java +ClassLoaderTemplateResolver classLoaderTemplateResolver = new ClassLoaderTemplateResolver(); +classLoaderTemplateResolver.setOrder(Integer.valueOf(1)); +classLoaderTempalteResolver.setCheckExistence(true); +``` + +此`checkExistence` flag 强制解析器在解析阶段执行资源存在的实际检查(如果存在检查返回 false,则调用链中的后续解析器)。虽然这在每种情况下听起来都不错,但在大多数情况下,这意味着 Double 访问资源本身(一次用于检查存在,另一次用于读取它),并且在某些情况下可能是 performance 问题,e.g. remote URL-based 模板资源 - 一个潜在的 performance 问题,无论如何都可以通过使用模板缓存大大减轻(在这种情况下,模板只会在访问它们的第一个 time 时解析)。 + +### 消息解析器 + +我们没有为 Grocery application 明确指定 Message Resolver implementation,正如前面所解释的那样,这意味着所使用的 implementation 是一个`org.thymeleaf.messageresolver.StandardMessageResolver` object。 + +`StandardMessageResolver`是`IMessageResolver`接口的标准 implementation,但是如果我们想要的话,我们可以自己创建,以适应我们的 application 的特定需求。 + +> Thymeleaf Spring integration 包默认提供`IMessageResolver` implementation,它使用标准 Spring 方式检索外化消息,使用 Spring Application Context 声明的`MessageSource` beans。 + +#### 标准消息解析器 + +那么`StandardMessageResolver`如何查找特定模板中请求的消息? + +如果模板 name 是`home`并且它位于`/WEB-INF/templates/home.html`,并且请求的 locale 是`gl_ES`,那么此解析器将在以下 files 中查找消息,在此 order 中: + +- `/WEB-INF/templates/home_gl_ES.properties` +- `/WEB-INF/templates/home_gl.properties` +- `/WEB-INF/templates/home.properties` + +有关完整消息解析机制如何工作的更多详细信息,请参阅`StandardMessageResolver` class 的 JavaDoc 文档。 + +#### 配置消息解析器 + +如果我们想要向模板引擎添加消息解析器(或更多),该怎么办?简单: + +```java +// For setting only one +templateEngine.setMessageResolver(messageResolver); + +// For setting more than one +templateEngine.addMessageResolver(messageResolver); +``` + +为什么我们想拥有多个消息解析器?出于与模板解析器相同的原因:订购消息解析器,如果第一个消息解析器无法解析特定消息,则会询问第二个,然后是第三个,等等。 + +### 转换服务 + +允许我们通过 double-brace 语法(`${{...}}`)执行数据转换和格式化操作的转换服务实际上是标准方言的 feature,而不是 Thymeleaf 模板引擎本身。 + +因此,配置它的方法是将`IStandardConversionService`接口的自定义 implementation 直接设置到正在配置到模板引擎中的`StandardDialect`实例中。喜欢: + +```java +IStandardConversionService customConversionService = ... + +StandardDialect dialect = new StandardDialect(); +dialect.setConversionService(customConversionService); + +templateEngine.setDialect(dialect); +``` + +> 请注意,thymeleaf-spring3 和 thymeleaf-spring4 包中包含`SpringStandardDialect`,并且此方言已经 pre-configured,实现了`IStandardConversionService`,它将 Spring 自己的转换服务基础结构集成到 Thymeleaf 中。 + +### Logging + +Thymeleaf 非常关注 logging,并且总是试图通过其 logging 界面提供最大量的有用信息。 + +使用的 logging library 是`slf4j,`,它实际上充当了我们可能想要在 application(example,`log4j`)中使用的 logging implementation 的 bridge。 + +Thymeleaf classes 将 log `TRACE`,`DEBUG`和`INFO` -level 信息,取决于我们想要的 level 细节,除了一般 logging 之外,它将使用与 TemplateEngine class 相关联的三个特殊 logger,我们可以为不同的目的单独配置: + +- `org.thymeleaf.TemplateEngine.CONFIG`将在初始化期间输出 library 的详细 configuration。 +- `org.thymeleaf.TemplateEngine.TIMER`将输出有关 time 每个模板所用 time 的数量的信息(对基准测试很有用!) +- `org.thymeleaf.TemplateEngine.cache`是一组 loggers 的前缀,用于输出有关缓存的特定信息。虽然缓存 loggers 的名称可由用户配置,因此可能会更改,但默认情况下它们是: +- `org.thymeleaf.TemplateEngine.cache.TEMPLATE_CACHE` +- `org.thymeleaf.TemplateEngine.cache.EXPRESSION_CACHE` + +使用`log4j`的 Thymeleaf 的 logging 基础结构的 example configuration 可以是: + +```java +log4j.logger.org.thymeleaf=DEBUG +log4j.logger.org.thymeleaf.TemplateEngine.CONFIG=TRACE +log4j.logger.org.thymeleaf.TemplateEngine.TIMER=TRACE +log4j.logger.org.thymeleaf.TemplateEngine.cache.TEMPLATE_CACHE=TRACE +``` + +## 模板缓存 + +Thymeleaf 的工作得益于一组解析器 - 用于标记和文本 - 将模板解析为 events 的 sequences(开放标记,文本,关闭标记,comment,etc.)和一系列处理器 - 每种类型的行为都需要应用一个 - 修改 order 中解析的 event 序列模板,通过将原始模板与我们的数据相结合来创建我们期望的结果。 + +它还包括 - 默认情况下 - 缓存 stores 解析模板;在处理模板 files 之前读取和解析的 events 序列。这在使用 web application 时特别有用,并基于以下概念构建: + +- Input/Output 几乎总是任何 application 中最慢的部分。相比之下,In-memory 处理非常快。 +- 克隆现有的 in-memory event 序列总是比读取模板文件,解析它并为其创建新的 event 序列要快得多。 +- Web applications 通常只有几十个模板。 +- 模板 files 是 small-to-medium 大小,并且在 application 为 running 时不会修改它们。 + +这一切都导致 idea 缓存 web application 中最常用的模板是可行的而不浪费大量的 memory,并且它将节省大量的 time,这些 time 将花费在一小组 files 上的 input/output 操作上,事实上,永远不会改变。 + +我们如何控制这个缓存?首先,我们之前已经了解到,我们可以在模板解析器中启用或禁用它,甚至只对特定模板执行操作: + +```java +// Default is true +templateResolver.setCacheable(false); +templateResolver.getCacheablePatternSpec().addPattern("/users/*"); +``` + +此外,我们可以通过建立我们自己的 Cache Manager object 来修改其 configuration,它可以是默认`StandardCacheManager` implementation 的实例: + +```java +// Default is 200 +StandardCacheManager cacheManager = new StandardCacheManager(); +cacheManager.setTemplateCacheMaxSize(100); +... +templateEngine.setCacheManager(cacheManager); +``` + +有关配置缓存的更多信息,请参阅`org.thymeleaf.cache.StandardCacheManager`的 javadoc API。 + +可以从模板缓存中手动删除条目: + +```java +// Clear the cache completely +templateEngine.clearTemplateCache(); + +// Clear a specific template from the cache +templateEngine.clearTemplateCacheFor("/users/userList"); +``` + +## 解耦模板逻辑 + +### 解耦逻辑:概念 + +到目前为止,我们已经为我们的 Grocery Store 工作,模板以通常的方式完成,逻辑以属性的形式插入到我们的模板中。 + +但是 Thymeleaf 还允许我们将模板标记与其逻辑完全分离,允许在`HTML`和`XML`模板模式中创建**完全 logic-less 标记模板**。 + +主要的 idea 是模板逻辑将在一个单独的逻辑文件中定义(更确切地说是一个逻辑资源,因为它不需要是一个文件)。默认情况下,该逻辑资源将是与模板文件位于同一位置(e.g. 文件夹)的附加文件,具有相同的 name 但扩展名为`.th.xml`: + +```java +/templates ++->/home.html ++->/home.th.xml +``` + +所以`home.html`文件可以完全 logic-less。它可能看起来像这样: + +```xml + + + + + + + + + + + + +
    Jeremy GrapefruitNormal User
    Alice WatermelonAdministrator
    + + +``` + +那里绝对没有 Thymeleaf code。这是一个模板文件,没有 Thymeleaf 或模板知识的设计师可以创建,编辑 and/or 理解。或者由某些外部系统提供的 HTML 片段,根本没有 Thymeleaf 挂钩。 + +现在让我们通过 creating 我们的附加`home.th.xml`文件将`home.html`模板转换为 Thymeleaf 模板: + +```xml + + + + + + + + + +``` + +在这里我们可以在`thlogic`块中看到很多`标签。这些`标签通过其`sel`属性选择的原始模板的节点上执行属性注入,这些属性包含 Thymeleaf 标记选择器(实际上是 AttoParser 标记选择器)。 + +另请注意,可以嵌套``标记,以便附加其 selectors。对于 example,上面的`sel="/tr[0]"`将被处理为`sel="#usersTable/tr[0]"`。并且用户 name `的选择器将被处理为`sel="#usersTable/tr[0]//td.username"`。 + +所以一旦合并,上面看到的两个 files 将与: + +```xml + + + + + + + + + + + + +
    Jeremy GrapefruitNormal User
    Alice WatermelonAdministrator
    + + +``` + +这看起来更熟悉,并且确实比创建两个单独的 files 更简洁。但是,解耦模板的优势在于我们可以为我们的模板提供完全独立于 Thymeleaf 的独立性,因此从设计角度来看,它具有更好的可维护性。 + +当然,仍然需要设计师或开发人员之间的一些 contracts - e.g. 用户``需要`id="usersTable"` - 这一事实,但在许多情况下,pure-HTML 模板将是设计和开发团队之间更好的通信工件。 + +### 配置解耦模板 + +#### 启用解耦模板 + +默认情况下,每个模板都不会出现解耦逻辑。相反,配置的模板解析器(`ITemplateResolver`的实现)将需要使用解耦逻辑专门标记它们解析的模板。 + +除了`StringTemplateResolver`(不允许解耦逻辑)之外,`ITemplateResolver`的所有其他 out-of-the-box \_\_mplement 将提供一个名为`useDecoupledLogic`的 flag,它将标记由该解析器解析的所有模板,因为它可能使其全部或部分逻辑存在于单独的资源中: + +```java +final ServletContextTemplateResolver templateResolver = + new ServletContextTemplateResolver(servletContext); +... +templateResolver.setUseDecoupledLogic(true); +``` + +#### 混合耦合和解耦逻辑 + +启用时,解耦模板逻辑不是必需的。启用后,这意味着引擎将查找包含解耦逻辑的资源,解析并将其与原始模板(如果存在)合并。如果解耦的逻辑资源不存在,则不会引发错误。 + +此外,在同一模板中,我们可以混合耦合和解耦逻辑,例如通过在原始模板文件中添加一些 Thymeleaf 属性,但将其他属性留给单独的解耦逻辑文件。最常见的情况是使用 new(in v3.0)`th:ref`属性。 + +### th:ref 属性 + +`th:ref`只是一个标记属性。它从处理的角度来看没有做任何事情,只是在处理模板时就消失了,但它的用处在于它充当了标记参考 i.e。它可以通过 name 从标记选择器中解析,就像标记 name 或片段(`th:fragment`)一样。 + +所以,如果我们有一个选择器,如: + +```xml + +``` + +这将 match: + +- 任何``标签。 +- 任何带有`th:fragment="whatever"`属性的标签。 +- 任何带有`th:ref="whatever"`属性的标签。 + +`th:ref`对例如使用 pure-HTML `id`属性有什么优势?仅仅是因为我们可能不希望在标签中添加如此多的`id`和`class`属性来充当逻辑锚,这可能最终会污染我们的输出。 + +从同样的意义上说,`th:ref`的缺点是什么?好吧,显然我们要在模板中添加一些 Thymeleaf 逻辑(“逻辑”)。 + +请注意`th:ref`属性**的这种适用性不仅适用于解耦逻辑模板 files**:它在其他类型的场景中也是如此,例如片段表达式(`~{...}`)。 + +### 分离模板的性能影响 + +影响非常小。当一个已解析的模板被标记为使用解耦逻辑并且它没有被缓存时,模板逻辑资源将首先被解析,解析并处理成一系列指令 in-memory:基本上是要注入每个标记选择器的属性列表。 + +但这是唯一需要的额外 step,因为在此之后,真正的模板将被解析,并且在解析时,这些属性将由解析器本身注入 on-the-fly,这要归功于 AttoParser 中节点选择的高级功能。因此,解析后的节点将从解析器中出来,就好像它们将注入的属性写入原始模板文件中一样。 + +这个的最大优点是什么?将模板配置为高速缓存时,它将被缓存,其中包含已注入的属性。因此,一旦缓存模板使用解耦模板进行缓存,其开销绝对为零。 + +### 解耦逻辑的分辨率 + +Thymeleaf 解析对应于每个模板的解耦逻辑资源的方式可由用户配置。它由一个扩展点`org.thymeleaf.templateparser.markup.decoupled.IDecoupledTemplateLogicResolver`确定,为其提供了默认的 implementation:`StandardDecoupledTemplateLogicResolver`。 + +这个标准 Implementation 做了什么? + +- 首先,它将`prefix`和`suffix`应用于模板资源的基本 name(通过其`ITemplateResource#getBaseName()`方法获得)。前缀和后缀都可以配置,默认情况下,前缀为空,后缀为`.th.xml`。 +- 其次,它要求模板资源通过其`ITemplateResource#relative(String relativeLocation)`方法解析具有计算 name 的相对资源。 + +要使用的`IDecoupledTemplateLogicResolver`的具体\_impleration 可以在`TemplateEngine`轻松配置: + +```java +final StandardDecoupledTemplateLogicResolver decoupledresolver = + new StandardDecoupledTemplateLogicResolver(); +decoupledResolver.setPrefix("../viewlogic/"); +... +templateEngine.setDecoupledTemplateLogicResolver(decoupledResolver); +``` + +## 附录 A:Expression Basic Objects + +一些 objects 和变量 maps 始终可以调用。我们来看看他们: + +#### 基础 objects + +- **#ctx**:context object。 `org.thymeleaf.context.IContext`或`org.thymeleaf.context.IWebContext`的 implementation 实现取决于我们的环境(独立或 web)。 + +注意`#vars`和`#root`是同一 object 的同义词,但建议使用`#ctx`。 + +```java +/* + * ====================================================================== + * See javadoc API for class org.thymeleaf.context.IContext + * ====================================================================== + */ + +${#ctx.locale} +${#ctx.variableNames} + +/* + * ====================================================================== + * See javadoc API for class org.thymeleaf.context.IWebContext + * ====================================================================== + */ + +${#ctx.request} +${#ctx.response} +${#ctx.session} +${#ctx.servletContext} +``` + +- **## locale**:直接访问与当前请求关联的`java.util.Locale`。 + +```java +${#locale} +``` + +#### Web context 命名空间用于 request/session 属性等。 + +在 web 环境中使用 Thymeleaf 时,我们可以使用一系列快捷方式来访问请求参数,session 属性和 application 属性: + +> 请注意,这些不是 context objects,但 maps 作为变量添加到 context,因此我们在没有`#`的情况下访问它们。在某种程度上,它们充当命名空间。 + +- **param**:用于检索请求参数。 `${param.foo}`是带有`foo`请求参数值的`String[]`,因此`${param.foo[0]}`通常用于获取第一个 value。 + +```java +/* + * ============================================================================ + * See javadoc API for class org.thymeleaf.context.WebRequestParamsVariablesMap + * ============================================================================ + */ + +${param.foo} // Retrieves a String[] with the values of request parameter 'foo' +${param.size()} +${param.isEmpty()} +${param.containsKey('foo')} +... +``` + +- **session**:用于检索 session 属性。 + +```java +/* + * ====================================================================== + * See javadoc API for class org.thymeleaf.context.WebSessionVariablesMap + * ====================================================================== + */ + +${session.foo} // Retrieves the session atttribute 'foo' +${session.size()} +${session.isEmpty()} +${session.containsKey('foo')} +... +``` + +- **application**:用于检索 application/servlet context 属性。 + +```java +/* + * ============================================================================= + * See javadoc API for class org.thymeleaf.context.WebServletContextVariablesMap + * ============================================================================= + */ + +${application.foo} // Retrieves the ServletContext atttribute 'foo' +${application.size()} +${application.isEmpty()} +${application.containsKey('foo')} +... +``` + +注意**不需要为访问请求属性**(而不是请求参数)指定名称空间,因为所有请求属性都会自动添加到 context 中作为 context 根目录中的变量: + +```java +${myRequestAttribute} +``` + +#### Web context objects + +在 web 环境中,还可以直接访问以下 objects(注意这些是 objects,而不是 maps/namespaces): + +- **#request**:直接访问与当前请求关联的`javax.servlet.http.HttpServletRequest` object。 + +```java +${#request.getAttribute('foo')} +${#request.getParameter('foo')} +${#request.getContextPath()} +${#request.getRequestName()} +... +``` + +- **## session**:直接访问与当前请求关联的`javax.servlet.http.HttpSession` object。 + +```java +${#session.getAttribute('foo')} +${#session.id} +${#session.lastAccessedTime} +... +``` + +- **#servletContext**:直接访问与当前请求关联的`javax.servlet.ServletContext` object。 + +```java +${#servletContext.getAttribute('foo')} +${#servletContext.contextPath} +... +``` + +## 附录 B:表达式实用程序 Objects + +#### 执行信息 + +- **#execInfo**:expression object 提供有关在 Thymeleaf 标准表达式中处理的模板的有用信息。 + +```java +/* + * ====================================================================== + * See javadoc API for class org.thymeleaf.expression.ExecutionInfo + * ====================================================================== + */ + +/* + * Return the name and mode of the 'leaf' template. This means the template + * from where the events being processed were parsed. So if this piece of + * code is not in the root template "A" but on a fragment being inserted + * into "A" from another template called "B", this will return "B" as a + * name, and B's mode as template mode. + */ +${#execInfo.templateName} +${#execInfo.templateMode} + +/* + * Return the name and mode of the 'root' template. This means the template + * that the template engine was originally asked to process. So if this + * piece of code is not in the root template "A" but on a fragment being + * inserted into "A" from another template called "B", this will still + * return "A" and A's template mode. + */ +${#execInfo.processedTemplateName} +${#execInfo.processedTemplateMode} + +/* + * Return the stacks (actually, List or List) of + * templates being processed. The first element will be the + * 'processedTemplate' (the root one), the last one will be the 'leaf' + * template, and in the middle all the fragments inserted in nested + * manner to reach the leaf from the root will appear. + */ +${#execInfo.templateNames} +${#execInfo.templateModes} + +/* + * Return the stack of templates being processed similarly (and in the + * same order) to 'templateNames' and 'templateModes', but returning + * a List with the full template metadata. + */ +${#execInfo.templateStack} +``` + +#### 消息 + +- **#messages**:用于在变量表达式中获取外部化消息的实用程序方法,与使用`#{...}`语法获取它们的方式相同。 + +```java +/* + * ====================================================================== + * See javadoc API for class org.thymeleaf.expression.Messages + * ====================================================================== + */ + +/* + * Obtain externalized messages. Can receive a single key, a key plus arguments, + * or an array/list/set of keys (in which case it will return an array/list/set of + * externalized messages). + * If a message is not found, a default message (like '??msgKey??') is returned. + */ +${#messages.msg('msgKey')} +${#messages.msg('msgKey', param1)} +${#messages.msg('msgKey', param1, param2)} +${#messages.msg('msgKey', param1, param2, param3)} +${#messages.msgWithParams('msgKey', new Object[] {param1, param2, param3, param4})} +${#messages.arrayMsg(messageKeyArray)} +${#messages.listMsg(messageKeyList)} +${#messages.setMsg(messageKeySet)} + +/* + * Obtain externalized messages or null. Null is returned instead of a default + * message if a message for the specified key is not found. + */ +${#messages.msgOrNull('msgKey')} +${#messages.msgOrNull('msgKey', param1)} +${#messages.msgOrNull('msgKey', param1, param2)} +${#messages.msgOrNull('msgKey', param1, param2, param3)} +${#messages.msgOrNullWithParams('msgKey', new Object[] {param1, param2, param3, param4})} +${#messages.arrayMsgOrNull(messageKeyArray)} +${#messages.listMsgOrNull(messageKeyList)} +${#messages.setMsgOrNull(messageKeySet)} +``` + +#### URIs/URLs + +- **#uris**:实用程序 object 用于在 Thymeleaf 标准表达式中执行 URI/URL 操作(尤其是 escaping/unescaping)。 + +```java +/* + * ====================================================================== + * See javadoc API for class org.thymeleaf.expression.Uris + * ====================================================================== + */ + +/* + * Escape/Unescape as a URI/URL path + */ +${#uris.escapePath(uri)} +${#uris.escapePath(uri, encoding)} +${#uris.unescapePath(uri)} +${#uris.unescapePath(uri, encoding)} + +/* + * Escape/Unescape as a URI/URL path segment (between '/' symbols) + */ +${#uris.escapePathSegment(uri)} +${#uris.escapePathSegment(uri, encoding)} +${#uris.unescapePathSegment(uri)} +${#uris.unescapePathSegment(uri, encoding)} + +/* + * Escape/Unescape as a Fragment Identifier (#frag) + */ +${#uris.escapeFragmentId(uri)} +${#uris.escapeFragmentId(uri, encoding)} +${#uris.unescapeFragmentId(uri)} +${#uris.unescapeFragmentId(uri, encoding)} + +/* + * Escape/Unescape as a Query Parameter (?var=value) + */ +${#uris.escapeQueryParam(uri)} +${#uris.escapeQueryParam(uri, encoding)} +${#uris.unescapeQueryParam(uri)} +${#uris.unescapeQueryParam(uri, encoding)} +``` + +#### 转换 + +- **#reversions**:实用程序 object,允许在模板的任何位置执行转换服务: + +```java +/* + * ====================================================================== + * See javadoc API for class org.thymeleaf.expression.Conversions + * ====================================================================== + */ + +/* + * Execute the desired conversion of the 'object' value into the + * specified class. + */ +${#conversions.convert(object, 'java.util.TimeZone')} +${#conversions.convert(object, targetClass)} +``` + +#### 日期 + +- **#dates**:`java.util.Date` objects 的实用方法: + +```java +/* + * ====================================================================== + * See javadoc API for class org.thymeleaf.expression.Dates + * ====================================================================== + */ + +/* + * Format date with the standard locale format + * Also works with arrays, lists or sets + */ +${#dates.format(date)} +${#dates.arrayFormat(datesArray)} +${#dates.listFormat(datesList)} +${#dates.setFormat(datesSet)} + +/* + * Format date with the ISO8601 format + * Also works with arrays, lists or sets + */ +${#dates.formatISO(date)} +${#dates.arrayFormatISO(datesArray)} +${#dates.listFormatISO(datesList)} +${#dates.setFormatISO(datesSet)} + +/* + * Format date with the specified pattern + * Also works with arrays, lists or sets + */ +${#dates.format(date, 'dd/MMM/yyyy HH:mm')} +${#dates.arrayFormat(datesArray, 'dd/MMM/yyyy HH:mm')} +${#dates.listFormat(datesList, 'dd/MMM/yyyy HH:mm')} +${#dates.setFormat(datesSet, 'dd/MMM/yyyy HH:mm')} + +/* + * Obtain date properties + * Also works with arrays, lists or sets + */ +${#dates.day(date)} // also arrayDay(...), listDay(...), etc. +${#dates.month(date)} // also arrayMonth(...), listMonth(...), etc. +${#dates.monthName(date)} // also arrayMonthName(...), listMonthName(...), etc. +${#dates.monthNameShort(date)} // also arrayMonthNameShort(...), listMonthNameShort(...), etc. +${#dates.year(date)} // also arrayYear(...), listYear(...), etc. +${#dates.dayOfWeek(date)} // also arrayDayOfWeek(...), listDayOfWeek(...), etc. +${#dates.dayOfWeekName(date)} // also arrayDayOfWeekName(...), listDayOfWeekName(...), etc. +${#dates.dayOfWeekNameShort(date)} // also arrayDayOfWeekNameShort(...), listDayOfWeekNameShort(...), etc. +${#dates.hour(date)} // also arrayHour(...), listHour(...), etc. +${#dates.minute(date)} // also arrayMinute(...), listMinute(...), etc. +${#dates.second(date)} // also arraySecond(...), listSecond(...), etc. +${#dates.millisecond(date)} // also arrayMillisecond(...), listMillisecond(...), etc. + +/* + * Create date (java.util.Date) objects from its components + */ +${#dates.create(year,month,day)} +${#dates.create(year,month,day,hour,minute)} +${#dates.create(year,month,day,hour,minute,second)} +${#dates.create(year,month,day,hour,minute,second,millisecond)} + +/* + * Create a date (java.util.Date) object for the current date and time + */ +${#dates.createNow()} + +${#dates.createNowForTimeZone()} + +/* + * Create a date (java.util.Date) object for the current date (time set to 00:00) + */ +${#dates.createToday()} + +${#dates.createTodayForTimeZone()} +``` + +#### 日历 + +- **#calendars**:类似于`#dates`,但是对于`java.util.Calendar` objects: + +```java +/* + * ====================================================================== + * See javadoc API for class org.thymeleaf.expression.Calendars + * ====================================================================== + */ + +/* + * Format calendar with the standard locale format + * Also works with arrays, lists or sets + */ +${#calendars.format(cal)} +${#calendars.arrayFormat(calArray)} +${#calendars.listFormat(calList)} +${#calendars.setFormat(calSet)} + +/* + * Format calendar with the ISO8601 format + * Also works with arrays, lists or sets + */ +${#calendars.formatISO(cal)} +${#calendars.arrayFormatISO(calArray)} +${#calendars.listFormatISO(calList)} +${#calendars.setFormatISO(calSet)} + +/* + * Format calendar with the specified pattern + * Also works with arrays, lists or sets + */ +${#calendars.format(cal, 'dd/MMM/yyyy HH:mm')} +${#calendars.arrayFormat(calArray, 'dd/MMM/yyyy HH:mm')} +${#calendars.listFormat(calList, 'dd/MMM/yyyy HH:mm')} +${#calendars.setFormat(calSet, 'dd/MMM/yyyy HH:mm')} + +/* + * Obtain calendar properties + * Also works with arrays, lists or sets + */ +${#calendars.day(date)} // also arrayDay(...), listDay(...), etc. +${#calendars.month(date)} // also arrayMonth(...), listMonth(...), etc. +${#calendars.monthName(date)} // also arrayMonthName(...), listMonthName(...), etc. +${#calendars.monthNameShort(date)} // also arrayMonthNameShort(...), listMonthNameShort(...), etc. +${#calendars.year(date)} // also arrayYear(...), listYear(...), etc. +${#calendars.dayOfWeek(date)} // also arrayDayOfWeek(...), listDayOfWeek(...), etc. +${#calendars.dayOfWeekName(date)} // also arrayDayOfWeekName(...), listDayOfWeekName(...), etc. +${#calendars.dayOfWeekNameShort(date)} // also arrayDayOfWeekNameShort(...), listDayOfWeekNameShort(...), etc. +${#calendars.hour(date)} // also arrayHour(...), listHour(...), etc. +${#calendars.minute(date)} // also arrayMinute(...), listMinute(...), etc. +${#calendars.second(date)} // also arraySecond(...), listSecond(...), etc. +${#calendars.millisecond(date)} // also arrayMillisecond(...), listMillisecond(...), etc. + +/* + * Create calendar (java.util.Calendar) objects from its components + */ +${#calendars.create(year,month,day)} +${#calendars.create(year,month,day,hour,minute)} +${#calendars.create(year,month,day,hour,minute,second)} +${#calendars.create(year,month,day,hour,minute,second,millisecond)} + +${#calendars.createForTimeZone(year,month,day,timeZone)} +${#calendars.createForTimeZone(year,month,day,hour,minute,timeZone)} +${#calendars.createForTimeZone(year,month,day,hour,minute,second,timeZone)} +${#calendars.createForTimeZone(year,month,day,hour,minute,second,millisecond,timeZone)} + +/* + * Create a calendar (java.util.Calendar) object for the current date and time + */ +${#calendars.createNow()} + +${#calendars.createNowForTimeZone()} + +/* + * Create a calendar (java.util.Calendar) object for the current date (time set to 00:00) + */ +${#calendars.createToday()} + +${#calendars.createTodayForTimeZone()} +``` + +#### Numbers + +- **## numbers**:数字 objects 的实用程序方法: + +```java +/* + * ====================================================================== + * See javadoc API for class org.thymeleaf.expression.Numbers + * ====================================================================== + */ + +/* + * ========================== + * Formatting integer numbers + * ========================== + */ + +/* + * Set minimum integer digits. + * Also works with arrays, lists or sets + */ +${#numbers.formatInteger(num,3)} +${#numbers.arrayFormatInteger(numArray,3)} +${#numbers.listFormatInteger(numList,3)} +${#numbers.setFormatInteger(numSet,3)} + +/* + * Set minimum integer digits and thousands separator: + * 'POINT', 'COMMA', 'WHITESPACE', 'NONE' or 'DEFAULT' (by locale). + * Also works with arrays, lists or sets + */ +${#numbers.formatInteger(num,3,'POINT')} +${#numbers.arrayFormatInteger(numArray,3,'POINT')} +${#numbers.listFormatInteger(numList,3,'POINT')} +${#numbers.setFormatInteger(numSet,3,'POINT')} + +/* + * ========================== + * Formatting decimal numbers + * ========================== + */ + +/* + * Set minimum integer digits and (exact) decimal digits. + * Also works with arrays, lists or sets + */ +${#numbers.formatDecimal(num,3,2)} +${#numbers.arrayFormatDecimal(numArray,3,2)} +${#numbers.listFormatDecimal(numList,3,2)} +${#numbers.setFormatDecimal(numSet,3,2)} + +/* + * Set minimum integer digits and (exact) decimal digits, and also decimal separator. + * Also works with arrays, lists or sets + */ +${#numbers.formatDecimal(num,3,2,'COMMA')} +${#numbers.arrayFormatDecimal(numArray,3,2,'COMMA')} +${#numbers.listFormatDecimal(numList,3,2,'COMMA')} +${#numbers.setFormatDecimal(numSet,3,2,'COMMA')} + +/* + * Set minimum integer digits and (exact) decimal digits, and also thousands and + * decimal separator. + * Also works with arrays, lists or sets + */ +${#numbers.formatDecimal(num,3,'POINT',2,'COMMA')} +${#numbers.arrayFormatDecimal(numArray,3,'POINT',2,'COMMA')} +${#numbers.listFormatDecimal(numList,3,'POINT',2,'COMMA')} +${#numbers.setFormatDecimal(numSet,3,'POINT',2,'COMMA')} + +/* + * ===================== + * Formatting currencies + * ===================== + */ + +${#numbers.formatCurrency(num)} +${#numbers.arrayFormatCurrency(numArray)} +${#numbers.listFormatCurrency(numList)} +${#numbers.setFormatCurrency(numSet)} + +/* + * ====================== + * Formatting percentages + * ====================== + */ + +${#numbers.formatPercent(num)} +${#numbers.arrayFormatPercent(numArray)} +${#numbers.listFormatPercent(numList)} +${#numbers.setFormatPercent(numSet)} + +/* + * Set minimum integer digits and (exact) decimal digits. + */ +${#numbers.formatPercent(num, 3, 2)} +${#numbers.arrayFormatPercent(numArray, 3, 2)} +${#numbers.listFormatPercent(numList, 3, 2)} +${#numbers.setFormatPercent(numSet, 3, 2)} + +/* + * =============== + * Utility methods + * =============== + */ + +/* + * Create a sequence (array) of integer numbers going + * from x to y + */ +${#numbers.sequence(from,to)} +${#numbers.sequence(from,to,step)} +``` + +#### Strings + +- **## strings**:`String` objects 的实用程序方法: + +```java +/* + * ====================================================================== + * See javadoc API for class org.thymeleaf.expression.Strings + * ====================================================================== + */ + +/* + * Null-safe toString() + */ +${#strings.toString(obj)} // also array*, list* and set* + +/* + * Check whether a String is empty (or null). Performs a trim() operation before check + * Also works with arrays, lists or sets + */ +${#strings.isEmpty(name)} +${#strings.arrayIsEmpty(nameArr)} +${#strings.listIsEmpty(nameList)} +${#strings.setIsEmpty(nameSet)} + +/* + * Perform an 'isEmpty()' check on a string and return it if false, defaulting to + * another specified string if true. + * Also works with arrays, lists or sets + */ +${#strings.defaultString(text,default)} +${#strings.arrayDefaultString(textArr,default)} +${#strings.listDefaultString(textList,default)} +${#strings.setDefaultString(textSet,default)} + +/* + * Check whether a fragment is contained in a String + * Also works with arrays, lists or sets + */ +${#strings.contains(name,'ez')} // also array*, list* and set* +${#strings.containsIgnoreCase(name,'ez')} // also array*, list* and set* + +/* + * Check whether a String starts or ends with a fragment + * Also works with arrays, lists or sets + */ +${#strings.startsWith(name,'Don')} // also array*, list* and set* +${#strings.endsWith(name,endingFragment)} // also array*, list* and set* + +/* + * Substring-related operations + * Also works with arrays, lists or sets + */ +${#strings.indexOf(name,frag)} // also array*, list* and set* +${#strings.substring(name,3,5)} // also array*, list* and set* +${#strings.substringAfter(name,prefix)} // also array*, list* and set* +${#strings.substringBefore(name,suffix)} // also array*, list* and set* +${#strings.replace(name,'las','ler')} // also array*, list* and set* + +/* + * Append and prepend + * Also works with arrays, lists or sets + */ +${#strings.prepend(str,prefix)} // also array*, list* and set* +${#strings.append(str,suffix)} // also array*, list* and set* + +/* + * Change case + * Also works with arrays, lists or sets + */ +${#strings.toUpperCase(name)} // also array*, list* and set* +${#strings.toLowerCase(name)} // also array*, list* and set* + +/* + * Split and join + */ +${#strings.arrayJoin(namesArray,',')} +${#strings.listJoin(namesList,',')} +${#strings.setJoin(namesSet,',')} +${#strings.arraySplit(namesStr,',')} // returns String[] +${#strings.listSplit(namesStr,',')} // returns List +${#strings.setSplit(namesStr,',')} // returns Set + +/* + * Trim + * Also works with arrays, lists or sets + */ +${#strings.trim(str)} // also array*, list* and set* + +/* + * Compute length + * Also works with arrays, lists or sets + */ +${#strings.length(str)} // also array*, list* and set* + +/* + * Abbreviate text making it have a maximum size of n. If text is bigger, it + * will be clipped and finished in "..." + * Also works with arrays, lists or sets + */ +${#strings.abbreviate(str,10)} // also array*, list* and set* + +/* + * Convert the first character to upper-case (and vice-versa) + */ +${#strings.capitalize(str)} // also array*, list* and set* +${#strings.unCapitalize(str)} // also array*, list* and set* + +/* + * Convert the first character of every word to upper-case + */ +${#strings.capitalizeWords(str)} // also array*, list* and set* +${#strings.capitalizeWords(str,delimiters)} // also array*, list* and set* + +/* + * Escape the string + */ +${#strings.escapeXml(str)} // also array*, list* and set* +${#strings.escapeJava(str)} // also array*, list* and set* +${#strings.escapeJavaScript(str)} // also array*, list* and set* +${#strings.unescapeJava(str)} // also array*, list* and set* +${#strings.unescapeJavaScript(str)} // also array*, list* and set* + +/* + * Null-safe comparison and concatenation + */ +${#strings.equals(first, second)} +${#strings.equalsIgnoreCase(first, second)} +${#strings.concat(values...)} +${#strings.concatReplaceNulls(nullValue, values...)} + +/* + * Random + */ +${#strings.randomAlphanumeric(count)} +``` + +#### Objects + +- **## objects**:一般用于 objects 的实用方法 + +```java +/* + * ====================================================================== + * See javadoc API for class org.thymeleaf.expression.Objects + * ====================================================================== + */ + +/* + * Return obj if it is not null, and default otherwise + * Also works with arrays, lists or sets + */ +${#objects.nullSafe(obj,default)} +${#objects.arrayNullSafe(objArray,default)} +${#objects.listNullSafe(objList,default)} +${#objects.setNullSafe(objSet,default)} +``` + +#### 布尔 + +- **#bools**:boolean evaluation 的实用程序方法 + +```java +/* + * ====================================================================== + * See javadoc API for class org.thymeleaf.expression.Bools + * ====================================================================== + */ + +/* + * Evaluate a condition in the same way that it would be evaluated in a th:if tag + * (see conditional evaluation chapter afterwards). + * Also works with arrays, lists or sets + */ +${#bools.isTrue(obj)} +${#bools.arrayIsTrue(objArray)} +${#bools.listIsTrue(objList)} +${#bools.setIsTrue(objSet)} + +/* + * Evaluate with negation + * Also works with arrays, lists or sets + */ +${#bools.isFalse(cond)} +${#bools.arrayIsFalse(condArray)} +${#bools.listIsFalse(condList)} +${#bools.setIsFalse(condSet)} + +/* + * Evaluate and apply AND operator + * Receive an array, a list or a set as parameter + */ +${#bools.arrayAnd(condArray)} +${#bools.listAnd(condList)} +${#bools.setAnd(condSet)} + +/* + * Evaluate and apply OR operator + * Receive an array, a list or a set as parameter + */ +${#bools.arrayOr(condArray)} +${#bools.listOr(condList)} +${#bools.setOr(condSet)} +``` + +#### 数组 + +- **#arrays**:数组的实用方法 + +```java +/* + * ====================================================================== + * See javadoc API for class org.thymeleaf.expression.Arrays + * ====================================================================== + */ + +/* + * Converts to array, trying to infer array component class. + * Note that if resulting array is empty, or if the elements + * of the target object are not all of the same class, + * this method will return Object[]. + */ +${#arrays.toArray(object)} + +/* + * Convert to arrays of the specified component class. + */ +${#arrays.toStringArray(object)} +${#arrays.toIntegerArray(object)} +${#arrays.toLongArray(object)} +${#arrays.toDoubleArray(object)} +${#arrays.toFloatArray(object)} +${#arrays.toBooleanArray(object)} + +/* + * Compute length + */ +${#arrays.length(array)} + +/* + * Check whether array is empty + */ +${#arrays.isEmpty(array)} + +/* + * Check if element or elements are contained in array + */ +${#arrays.contains(array, element)} +${#arrays.containsAll(array, elements)} +``` + +#### Lists + +- **#列表**:lists 的实用程序方法 + +```java +/* + * ====================================================================== + * See javadoc API for class org.thymeleaf.expression.Lists + * ====================================================================== + */ + +/* + * Converts to list + */ +${#lists.toList(object)} + +/* + * Compute size + */ +${#lists.size(list)} + +/* + * Check whether list is empty + */ +${#lists.isEmpty(list)} + +/* + * Check if element or elements are contained in list + */ +${#lists.contains(list, element)} +${#lists.containsAll(list, elements)} + +/* + * Sort a copy of the given list. The members of the list must implement + * comparable or you must define a comparator. + */ +${#lists.sort(list)} +${#lists.sort(list, comparator)} +``` + +#### Sets + +- **## sets**:sets 的实用程序方法 + +```java +/* + * ====================================================================== + * See javadoc API for class org.thymeleaf.expression.Sets + * ====================================================================== + */ + +/* + * Converts to set + */ +${#sets.toSet(object)} + +/* + * Compute size + */ +${#sets.size(set)} + +/* + * Check whether set is empty + */ +${#sets.isEmpty(set)} + +/* + * Check if element or elements are contained in set + */ +${#sets.contains(set, element)} +${#sets.containsAll(set, elements)} +``` + +#### Maps + +- **## maps**:maps 的实用程序方法 + +```java +/* + * ====================================================================== + * See javadoc API for class org.thymeleaf.expression.Maps + * ====================================================================== + */ + +/* + * Compute size + */ +${#maps.size(map)} + +/* + * Check whether map is empty + */ +${#maps.isEmpty(map)} + +/* + * Check if key/s or value/s are contained in maps + */ +${#maps.containsKey(map, key)} +${#maps.containsAllKeys(map, keys)} +${#maps.containsValue(map, value)} +${#maps.containsAllValues(map, value)} +``` + +#### 骨料 + +- **#aggregates**:用于在数组或集合上创建聚合的实用方法 + +```java +/* + * ====================================================================== + * See javadoc API for class org.thymeleaf.expression.Aggregates + * ====================================================================== + */ + +/* + * Compute sum. Returns null if array or collection is empty + */ +${#aggregates.sum(array)} +${#aggregates.sum(collection)} + +/* + * Compute average. Returns null if array or collection is empty + */ +${#aggregates.avg(array)} +${#aggregates.avg(collection)} +``` + +#### 标识 + +- **#ids**:用于处理可能重复的`id`属性的实用方法(对于 example,作为迭代的结果)。 + +```xml +/* + * ====================================================================== + * See javadoc API for class org.thymeleaf.expression.Ids + * ====================================================================== + */ + +/* + * Normally used in th:id attributes, for appending a counter to the id attribute value + * so that it remains unique even when involved in an iteration process. + */ +${#ids.seq('someId')} + +/* + * Normally used in th:for attributes in

    +// * To hash Node objects to a hash ring with a certain amount of virtual node. Method routeNode will return a Node +// * instance which the object key should be allocated to according to consistent hash algorithm +// */ +// public class ConsistentHashRouter { +// +// private final static int VIRTUAL_NODE_SIZE = 10; +// +// private final SortedMap> hashRing = new TreeMap<>(); +// +// private final HashStrategy hashStrategy; +// +// public ConsistentHashRouter(Collection physicalNodes, int count) { +// this(physicalNodes, count, new MD5Hash()); +// } +// +// /** +// * @param nodes collections of physical nodes +// * @param count amounts of virtual nodes +// * @param hashStrategy hash Function to hash Node instances +// */ +// public ConsistentHashRouter(Collection nodes, int count, HashStrategy hashStrategy) { +// if (hashStrategy == null) { +// throw new NullPointerException("Hash Function is null"); +// } +// this.hashStrategy = hashStrategy; +// if (nodes != null) { +// for (T node : nodes) { +// addNode(node, count); +// } +// } +// } +// +// /** +// * add physic node to the hash ring with some virtual nodes +// * +// * @param node physical node needs added to hash ring +// * @param count the number of virtual node of the physical node. Value should be greater than or equals to 0 +// */ +// public void addNode(T node, int count) { +// if (count < 0) throw new IllegalArgumentException("illegal virtual node counts :" + count); +// int existingReplicas = getExistingReplicas(node); +// for (int i = 0; i < count; i++) { +// VirtualNode nodes = new VirtualNode<>(node, i + existingReplicas); +// hashRing.put(hashStrategy.hash(nodes.getKey()), nodes); +// } +// } +// +// /** +// * remove the physical node from the hash ring +// * +// * @param node +// */ +// public void removeNode(T node) { +// Iterator it = hashRing.keySet().iterator(); +// while (it.hasNext()) { +// Long key = it.next(); +// VirtualNode virtualNode = hashRing.get(key); +// if (virtualNode.isVirtualNodeOf(node)) { +// it.remove(); +// } +// } +// } +// +// /** +// * with a specified key, route the nearest Node instance in the current hash ring +// * +// * @param objectKey the object key to find a nearest Node +// * @return +// */ +// public T next(String objectKey) { +// if (hashRing.isEmpty()) return null; +// long hashcode = hashStrategy.hash(objectKey); +// SortedMap> tailMap = hashRing.tailMap(hashcode); +// Long nodeHashVal = !tailMap.isEmpty() ? tailMap.firstKey() : hashRing.firstKey(); +// return hashRing.get(nodeHashVal).getPhysicalNode(); +// } +// +// public int getExistingReplicas(T pNode) { +// int replicas = 0; +// for (VirtualNode node : hashRing.values()) { +// if (node.isVirtualNodeOf(pNode)) { +// replicas++; +// } +// } +// return replicas; +// } +// +// //default hash function +// static class MD5Hash implements HashStrategy { +// +// MessageDigest instance; +// +// public MD5Hash() { +// try { +// instance = MessageDigest.getInstance("MD5"); +// } catch (NoSuchAlgorithmException e) { +// e.printStackTrace(); +// } +// } +// +// @Override +// public long hash(String key) { +// instance.reset(); +// instance.update(key.getBytes()); +// byte[] digest = instance.digest(); +// +// long h = 0; +// for (int i = 0; i < 4; i++) { +// h <<= 8; +// h |= ((int) digest[i]) & 0xFF; +// } +// return h; +// } +// +// } +// +// static class VirtualNode implements Node { +// +// final T physicalNode; +// +// final int replicaIndex; +// +// public VirtualNode(T physicalNode, int replicaIndex) { +// this.replicaIndex = replicaIndex; +// this.physicalNode = physicalNode; +// } +// +// @Override +// public String getKey() { +// return physicalNode.getKey() + "-" + replicaIndex; +// } +// +// public boolean isVirtualNodeOf(T pNode) { +// return physicalNode.getKey().equals(pNode.getKey()); +// } +// +// public T getPhysicalNode() { +// return physicalNode; +// } +// +// } +// +// } diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/LoadBalance.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/LoadBalance.java new file mode 100644 index 00000000..4f57f247 --- /dev/null +++ b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/LoadBalance.java @@ -0,0 +1,17 @@ +package io.github.dunwu.javaweb; + +import java.util.Collection; + +/** + * 负载均衡策略接口 + * + * @author Zhang Peng + * @since 2020-01-21 + */ +public interface LoadBalance { + + void buildInList(Collection collection); + + V next(); + +} diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/RandomLoadBalance.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/RandomLoadBalance.java new file mode 100644 index 00000000..579b76b7 --- /dev/null +++ b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/RandomLoadBalance.java @@ -0,0 +1,85 @@ +package io.github.dunwu.javaweb; + +import io.github.dunwu.tool.collection.CollectionUtil; +import io.github.dunwu.tool.map.MapUtil; + +import java.util.*; +import java.util.concurrent.ThreadLocalRandom; + +/** + * (加权)随机负载均衡策略 + * + * @author Zhang Peng + * @see Zhang Peng + * @since 2020-01-20 + */ +public class RandomLoadBalance implements WeightLoadBalance { + + public static final Integer DEFAULT_WEIGHT = 1; + + private boolean weightMode; + + private final Random random = ThreadLocalRandom.current(); + + private List nodeList = Collections.emptyList(); + + private final Map keyWeightMap = new HashMap<>(); + + public RandomLoadBalance() { + this.weightMode = false; + } + + public RandomLoadBalance(boolean weightMode) { + this.weightMode = weightMode; + } + + @Override + public void buildInList(final Collection collection) { + this.nodeList = new ArrayList<>(collection); + collection.forEach(item -> { + keyWeightMap.put(item, DEFAULT_WEIGHT); + }); + } + + @Override + public void buildInMap(final Map map) { + this.nodeList = new ArrayList<>(map.keySet()); + this.keyWeightMap.putAll(map); + } + + @Override + public V next() { + if (weightMode) { + return getNextInWeightMode(); + } else { + return getNextInNormalMode(); + } + } + + private V getNextInWeightMode() { + if (MapUtil.isEmpty(keyWeightMap)) { + return null; + } + + List list = new ArrayList<>(); + for (Map.Entry item : keyWeightMap.entrySet()) { + for (int i = 0; i < item.getValue(); i++) { + list.add(item.getKey()); + } + } + + int totalWeight = keyWeightMap.values().stream().mapToInt(a -> a).sum(); + int number = random.nextInt(totalWeight); + return list.get(number); + } + + private V getNextInNormalMode() { + if (CollectionUtil.isEmpty(nodeList)) { + return null; + } + + int offset = random.nextInt(nodeList.size()); + return nodeList.get(offset); + } + +} diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/RoundRobinLoadBalance.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/RoundRobinLoadBalance.java new file mode 100644 index 00000000..741756cf --- /dev/null +++ b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/RoundRobinLoadBalance.java @@ -0,0 +1,85 @@ +package io.github.dunwu.javaweb; + +import io.github.dunwu.tool.collection.CollectionUtil; +import io.github.dunwu.tool.map.MapUtil; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * (加权)轮询负载均衡策略 + * + * @author Zhang Peng + * @since 2020-01-20 + */ +public class RoundRobinLoadBalance implements WeightLoadBalance { + + private boolean weightMode; + + private AtomicInteger offset = new AtomicInteger(0); + + private List nodeList = Collections.emptyList(); + + private Map nodeMap = new LinkedHashMap<>(); + + public RoundRobinLoadBalance() { + this.weightMode = false; + } + + public RoundRobinLoadBalance(boolean weightMode) { + this.weightMode = weightMode; + } + + @Override + public void buildInList(final Collection collection) { + this.offset = new AtomicInteger(0); + this.nodeList = new ArrayList<>(collection); + this.nodeMap = new LinkedHashMap<>(collection.size()); + collection.forEach(item -> { + nodeMap.putIfAbsent(item, 1); + }); + } + + @Override + public void buildInMap(final Map map) { + this.nodeList = new ArrayList<>(map.keySet()); + this.nodeMap = new LinkedHashMap<>(map); + } + + @Override + public V next() { + if (weightMode) { + return getNextInWeightMode(); + } else { + return getNextInNormalMode(); + } + } + + private V getNextInWeightMode() { + if (MapUtil.isEmpty(nodeMap)) { + return null; + } + + int totalWeight = nodeMap.values().stream().mapToInt(a -> a).sum(); + int number = offset.getAndIncrement() % totalWeight; + + for (Map.Entry item : nodeMap.entrySet()) { + if (item.getValue() > number) { + return item.getKey(); + } + number -= item.getValue(); + } + return null; + } + + private V getNextInNormalMode() { + if (CollectionUtil.isEmpty(this.nodeList)) { + return null; + } + + int size = this.nodeList.size(); + offset.compareAndSet(size, 0); + return nodeList.get(offset.getAndIncrement()); + } + +} diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/StatisticsUtil.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/StatisticsUtil.java new file mode 100644 index 00000000..ebc8765f --- /dev/null +++ b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/StatisticsUtil.java @@ -0,0 +1,35 @@ +package io.github.dunwu.javaweb; + +public class StatisticsUtil { + + // 方差 s^2=[(x1-x)^2 +...(xn-x)^2]/n + public static double variance(Long[] array) { + int m = array.length; + double sum = 0; + for (Long item : array) {// 求和 + sum += item; + } + double avg = sum / m;// 求平均值 + double value = 0; + for (Long item : array) {// 求方差 + value += (item - avg) * (item - avg); + } + return value / m; + } + + // 标准差σ=sqrt(s^2) + public static double standardDeviation(Long[] array) { + int m = array.length; + double sum = 0; + for (Long item : array) {// 求和 + sum += item; + } + double avg = sum / m;// 求平均值 + double value = 0; + for (Long item : array) {// 求方差 + value += (item - avg) * (item - avg); + } + return Math.sqrt(value / m); + } + +} diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/WeightLoadBalance.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/WeightLoadBalance.java new file mode 100644 index 00000000..d36cd3ad --- /dev/null +++ b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/WeightLoadBalance.java @@ -0,0 +1,15 @@ +package io.github.dunwu.javaweb; + +import java.util.Map; + +/** + * 加权负载均衡策略接口 + * + * @author Zhang Peng + * @since 2020-01-20 + */ +public interface WeightLoadBalance extends LoadBalance { + + void buildInMap(Map map); + +} diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/package-info.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/package-info.java new file mode 100644 index 00000000..b01bb1ce --- /dev/null +++ b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/package-info.java @@ -0,0 +1,7 @@ +/** + * 负载均衡算法实现 + * + * @author Zhang Peng + * @since 2020-01-22 + */ +package io.github.dunwu.javaweb; diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/CRCHashStrategy.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/CRCHashStrategy.java new file mode 100644 index 00000000..5fbd64c6 --- /dev/null +++ b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/CRCHashStrategy.java @@ -0,0 +1,69 @@ +package io.github.dunwu.javaweb.support; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +public class CRCHashStrategy implements HashStrategy { + + private static final int LOOKUP_TABLE[] = { 0x0000, 0x1021, 0x2042, 0x3063, + 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, + 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210, 0x3273, 0x2252, + 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A, + 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401, + 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, + 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, + 0x76D7, 0x66F6, 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, + 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, + 0x0840, 0x1861, 0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, + 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, + 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, + 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, + 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, + 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, + 0x3E13, 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, + 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, + 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, + 0x5004, 0x4025, 0x7046, 0x6067, 0x83B9, 0x9398, 0xA3FB, 0xB3DA, + 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290, 0x22F3, 0x32D2, + 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, 0x95A8, 0x8589, + 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0, 0x0481, + 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8, + 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, + 0x6657, 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, + 0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, + 0x18C0, 0x08E1, 0x3882, 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, + 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, + 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, + 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, 0x5C64, 0x4C45, + 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, + 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, + 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0, }; + + /** + * Create a CRC16 checksum from the bytes. implementation is from mp911de/lettuce, modified with some more + * optimizations + * + * @param bytes + * @return CRC16 as integer value + */ + public static int getCRC16(byte[] bytes) { + int crc = 0x0000; + + for (byte b : bytes) { + crc = ((crc << 8) ^ LOOKUP_TABLE[((crc >>> 8) ^ (b & 0xFF)) & 0xFF]); + } + return crc & 0xFFFF; + } + + public static int getCRC16(String key) { + return getCRC16(key.getBytes(StandardCharsets.UTF_8)); + } + + @Override + public int hashCode(String origin) { + // optimization with modulo operator with power of 2 + // equivalent to getCRC16(key) % 16384 + return getCRC16(origin) & (16384 - 1); + } + +} diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/FnvHashStrategy.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/FnvHashStrategy.java new file mode 100644 index 00000000..7b20a23d --- /dev/null +++ b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/FnvHashStrategy.java @@ -0,0 +1,24 @@ +package io.github.dunwu.javaweb.support; + +public class FnvHashStrategy implements HashStrategy { + + private static final long FNV_32_INIT = 2166136261L; + + private static final int FNV_32_PRIME = 16777619; + + @Override + public int hashCode(String origin) { + final int p = FNV_32_PRIME; + int hash = (int) FNV_32_INIT; + for (int i = 0; i < origin.length(); i++) + hash = (hash ^ origin.charAt(i)) * p; + hash += hash << 13; + hash ^= hash >> 7; + hash += hash << 3; + hash ^= hash >> 17; + hash += hash << 5; + hash = Math.abs(hash); + return hash; + } + +} diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/HashStrategy.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/HashStrategy.java new file mode 100644 index 00000000..898f354e --- /dev/null +++ b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/HashStrategy.java @@ -0,0 +1,7 @@ +package io.github.dunwu.javaweb.support; + +public interface HashStrategy { + + int hashCode(String origin); + +} diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/JdkHashCodeStrategy.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/JdkHashCodeStrategy.java new file mode 100644 index 00000000..fa6f0a45 --- /dev/null +++ b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/JdkHashCodeStrategy.java @@ -0,0 +1,10 @@ +package io.github.dunwu.javaweb.support; + +public class JdkHashCodeStrategy implements HashStrategy { + + @Override + public int hashCode(String origin) { + return origin.hashCode(); + } + +} diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/KetamaHashStrategy.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/KetamaHashStrategy.java new file mode 100644 index 00000000..4c551e0f --- /dev/null +++ b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/KetamaHashStrategy.java @@ -0,0 +1,42 @@ +package io.github.dunwu.javaweb.support; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class KetamaHashStrategy implements HashStrategy { + + private static MessageDigest md5Digest; + + static { + try { + md5Digest = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("MD5 not supported", e); + } + } + + @Override + public int hashCode(String origin) { + byte[] bKey = computeMd5(origin); + long rv = ((long) (bKey[3] & 0xFF) << 24) + | ((long) (bKey[2] & 0xFF) << 16) + | ((long) (bKey[1] & 0xFF) << 8) + | (bKey[0] & 0xFF); + return (int) (rv & 0xffffffffL); + } + + /** + * Get the md5 of the given key. + */ + public static byte[] computeMd5(String k) { + MessageDigest md5; + try { + md5 = (MessageDigest) md5Digest.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException("clone of MD5 not supported", e); + } + md5.update(k.getBytes()); + return md5.digest(); + } + +} diff --git a/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/MurmurHashStrategy.java b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/MurmurHashStrategy.java new file mode 100644 index 00000000..44376f8e --- /dev/null +++ b/codes/javaweb-example/javaweb-load-balance/src/main/java/io/github/dunwu/javaweb/support/MurmurHashStrategy.java @@ -0,0 +1,51 @@ +package io.github.dunwu.javaweb.support; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class MurmurHashStrategy implements HashStrategy { + + @Override + public int hashCode(String origin) { + + ByteBuffer buf = ByteBuffer.wrap(origin.getBytes()); + int seed = 0x1234ABCD; + + ByteOrder byteOrder = buf.order(); + buf.order(ByteOrder.LITTLE_ENDIAN); + + long m = 0xc6a4a7935bd1e995L; + int r = 47; + + long h = seed ^ (buf.remaining() * m); + + long k; + while (buf.remaining() >= 8) { + k = buf.getLong(); + + k *= m; + k ^= k >>> r; + k *= m; + + h ^= k; + h *= m; + } + + if (buf.remaining() > 0) { + ByteBuffer finish = ByteBuffer.allocate(8).order( + ByteOrder.LITTLE_ENDIAN); + // for big-endian version, do this first: + // finish.position(8-buf.remaining()); + finish.put(buf).rewind(); + h ^= finish.getLong(); + h *= m; + } + h ^= h >>> r; + h *= m; + h ^= h >>> r; + + buf.order(byteOrder); + return (int) (h & 0xffffffffL); + } + +} diff --git a/codes/javaweb-example/javaweb-load-balance/src/test/java/io/github/dunwu/javaweb/LoadBalanceTests.java b/codes/javaweb-example/javaweb-load-balance/src/test/java/io/github/dunwu/javaweb/LoadBalanceTests.java new file mode 100644 index 00000000..9d9aa35a --- /dev/null +++ b/codes/javaweb-example/javaweb-load-balance/src/test/java/io/github/dunwu/javaweb/LoadBalanceTests.java @@ -0,0 +1,133 @@ +package io.github.dunwu.javaweb; + +import org.junit.Test; + +import java.util.*; + +/** + * 负载均衡测试 + * + * @author Zhang Peng + * @since 2020-01-20 + */ +public class LoadBalanceTests { + + private Map initSample() { + Map map = new LinkedHashMap<>(); + map.put("192.168.1.1", 1); + map.put("192.168.1.2", 1); + map.put("192.168.1.3", 2); + map.put("192.168.1.4", 3); + map.put("192.168.1.5", 3); + return map; + } + + /** + * 统计负载均衡命中次数,样本数为 100000 次访问 + * + * @param algorithm + */ + private void countLoadBalance(LoadBalance algorithm) { + Map countMap = new TreeMap<>(); + + for (int i = 0; i < 100000; i++) { + String node = algorithm.next(); + if (countMap.containsKey(node)) { + Integer value = countMap.get(node); + countMap.put(node, ++value); + } else { + countMap.put(node, 1); + } + } + countMap.forEach((key, value) -> { + System.out.printf("key = %s, value = %s\n", key, value); + }); + } + + @Test + public void randomLoadBalanceTest() { + RandomLoadBalance algorithm = new RandomLoadBalance<>(); + algorithm.buildInList(initSample().keySet()); + countLoadBalance(algorithm); + + RandomLoadBalance algorithm2 = new RandomLoadBalance<>(true); + algorithm2.buildInMap(initSample()); + countLoadBalance(algorithm2); + } + + @Test + public void roundRobinLoadBalanceTest() { + Map map = initSample(); + + LoadBalance algorithm = new RoundRobinLoadBalance<>(); + algorithm.buildInList(map.keySet()); + countLoadBalance(algorithm); + + WeightLoadBalance algorithm2 = new RoundRobinLoadBalance<>(true); + algorithm2.buildInMap(map); + countLoadBalance(algorithm2); + } + + @Test + public void testDistribution() { + + List nodes = new ArrayList<>(100); + for (int i = 1; i <= 100; i++) { + String item = "192.168.0." + "i"; + nodes.add(item); + } + + ConsistentHashLoadBalancer loadBalancer = new ConsistentHashLoadBalancer(); + // 构造 10000 随机请求 + List invocations = new ArrayList<>(); + for (int i = 0; i < 10000; i++) { + invocations.add(new ConsistentHashLoadBalancer.Invocation(UUID.randomUUID().toString())); + } + // 统计分布 + Map map = new LinkedHashMap<>(); + for (String node : nodes) { + map.put(node, 0L); + } + for (ConsistentHashLoadBalancer.Invocation invocation : invocations) { + String selectNode = loadBalancer.select(invocation); + System.out.printf("server = %s, key = %s\n", selectNode, invocation.getHashKey()); + Long count = map.get(selectNode); + count++; + map.put(selectNode, count); + } + System.out.println(StatisticsUtil.variance(map.values().toArray(new Long[0]))); + System.out.println(StatisticsUtil.standardDeviation(map.values().toArray(new Long[] {}))); + } + + /** + * 测试节点新增删除后的变化程度 + */ + @Test + public void testNodeAddAndRemove() { + // 构造 10000 随机请求 + List invocations = new ArrayList<>(); + for (int i = 0; i < 10000; i++) { + invocations.add(new ConsistentHashLoadBalancer.Invocation(UUID.randomUUID().toString())); + } + + List nodes = new ArrayList<>(100); + for (int i = 1; i <= 100; i++) { + String item = "192.168.0." + "i"; + nodes.add(item); + } + List newNodes = nodes.subList(0, 80); + ConsistentHashLoadBalancer oldLoadBalance = new ConsistentHashLoadBalancer<>(); + oldLoadBalance.buildInList(nodes); + ConsistentHashLoadBalancer newLoadBalance = new ConsistentHashLoadBalancer<>(); + newLoadBalance.buildInList(newNodes); + + int count = 0; + for (ConsistentHashLoadBalancer.Invocation invocation : invocations) { + String oldNode = oldLoadBalance.select(invocation); + String newNode = newLoadBalance.select(invocation); + if (oldNode.equals(newNode)) count++; + } + System.out.println(count / 10000D); + } + +} diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/pom.xml b/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/pom.xml similarity index 100% rename from codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/pom.xml rename to codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/pom.xml diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaConsumer.java b/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaConsumer.java similarity index 100% rename from codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaConsumer.java rename to codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaConsumer.java diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaProducer.java b/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaProducer.java similarity index 100% rename from codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaProducer.java rename to codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaProducer.java diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaProducerController.java b/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaProducerController.java similarity index 100% rename from codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaProducerController.java rename to codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/KafkaProducerController.java diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/MsgKafkaApplication.java b/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/MsgKafkaApplication.java similarity index 100% rename from codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/MsgKafkaApplication.java rename to codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/MsgKafkaApplication.java diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/config/KafkaConsumerConfig.java b/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/config/KafkaConsumerConfig.java similarity index 100% rename from codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/config/KafkaConsumerConfig.java rename to codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/config/KafkaConsumerConfig.java diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/config/KafkaProducerConfig.java b/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/config/KafkaProducerConfig.java similarity index 100% rename from codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/config/KafkaProducerConfig.java rename to codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/java/io/github/dunwu/javaweb/config/KafkaProducerConfig.java diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/resources/application.properties b/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/resources/application.properties similarity index 100% rename from codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/resources/application.properties rename to codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/resources/application.properties diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/resources/logback.xml b/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/resources/logback.xml similarity index 100% rename from codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/resources/logback.xml rename to codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/main/resources/logback.xml diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/test/java/io/github/dunwu/javaweb/KafkaProducerTest.java b/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/test/java/io/github/dunwu/javaweb/KafkaProducerTest.java similarity index 100% rename from codes/javaweb/javaweb-mq/javaweb-mq-kafka-spring-boot/src/test/java/io/github/dunwu/javaweb/KafkaProducerTest.java rename to codes/javaweb-example/javaweb-mq/javaweb-mq-kafka-spring-boot/src/test/java/io/github/dunwu/javaweb/KafkaProducerTest.java diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/pom.xml b/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/pom.xml similarity index 100% rename from codes/javaweb/javaweb-mq/javaweb-mq-kafka/pom.xml rename to codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/pom.xml diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerAOC.java b/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerAOC.java similarity index 100% rename from codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerAOC.java rename to codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerAOC.java diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManual.java b/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManual.java similarity index 100% rename from codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManual.java rename to codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManual.java diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManualPartition.java b/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManualPartition.java similarity index 100% rename from codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManualPartition.java rename to codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ConsumerManualPartition.java diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerDemo.java b/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerDemo.java similarity index 100% rename from codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerDemo.java rename to codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerDemo.java diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerInTransaction.java b/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerInTransaction.java similarity index 100% rename from codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerInTransaction.java rename to codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/ProducerInTransaction.java diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/StreamDemo.java b/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/StreamDemo.java similarity index 100% rename from codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/StreamDemo.java rename to codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/java/io/github/dunwu/javaweb/kafka/StreamDemo.java diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/resources/logback.xml b/codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/resources/logback.xml similarity index 100% rename from codes/javaweb/javaweb-mq/javaweb-mq-kafka/src/main/resources/logback.xml rename to codes/javaweb-example/javaweb-mq/javaweb-mq-kafka/src/main/resources/logback.xml diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/pom.xml b/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/pom.xml similarity index 100% rename from codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/pom.xml rename to codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/pom.xml diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/AsyncProducer.java b/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/AsyncProducer.java similarity index 100% rename from codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/AsyncProducer.java rename to codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/AsyncProducer.java diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BatchProducer.java b/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BatchProducer.java similarity index 100% rename from codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BatchProducer.java rename to codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BatchProducer.java diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastConsumer.java b/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastConsumer.java similarity index 100% rename from codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastConsumer.java rename to codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastConsumer.java diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastProducer.java b/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastProducer.java similarity index 100% rename from codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastProducer.java rename to codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/BroadcastProducer.java diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OnewayProducer.java b/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OnewayProducer.java similarity index 100% rename from codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OnewayProducer.java rename to codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OnewayProducer.java diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedConsumer.java b/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedConsumer.java similarity index 100% rename from codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedConsumer.java rename to codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedConsumer.java diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedProducer.java b/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedProducer.java similarity index 100% rename from codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedProducer.java rename to codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/OrderedProducer.java diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/RocketConfig.java b/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/RocketConfig.java similarity index 100% rename from codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/RocketConfig.java rename to codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/RocketConfig.java diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageConsumer.java b/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageConsumer.java similarity index 100% rename from codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageConsumer.java rename to codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageConsumer.java diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageProducer.java b/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageProducer.java similarity index 100% rename from codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageProducer.java rename to codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/ScheduledMessageProducer.java diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/SyncProducer.java b/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/SyncProducer.java similarity index 100% rename from codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/SyncProducer.java rename to codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/java/io/github/dunwu/javaweb/rocketmq/SyncProducer.java diff --git a/codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/resources/logback.xml b/codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/resources/logback.xml similarity index 100% rename from codes/javaweb/javaweb-mq/javaweb-mq-rocketmq/src/main/resources/logback.xml rename to codes/javaweb-example/javaweb-mq/javaweb-mq-rocketmq/src/main/resources/logback.xml diff --git a/codes/javaweb/javaweb-mq/pom.xml b/codes/javaweb-example/javaweb-mq/pom.xml similarity index 100% rename from codes/javaweb/javaweb-mq/pom.xml rename to codes/javaweb-example/javaweb-mq/pom.xml diff --git a/codes/javaweb/javaweb-zookeeper/pom.xml b/codes/javaweb-example/javaweb-zookeeper/pom.xml similarity index 100% rename from codes/javaweb/javaweb-zookeeper/pom.xml rename to codes/javaweb-example/javaweb-zookeeper/pom.xml diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/Callback.java b/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/Callback.java similarity index 100% rename from codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/Callback.java rename to codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/Callback.java diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/DLockTemplate.java b/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/DLockTemplate.java similarity index 100% rename from codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/DLockTemplate.java rename to codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/DLockTemplate.java diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/DistributedLock.java b/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/DistributedLock.java similarity index 100% rename from codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/DistributedLock.java rename to codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/DistributedLock.java diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/TimeoutHandler.java b/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/TimeoutHandler.java similarity index 100% rename from codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/TimeoutHandler.java rename to codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/TimeoutHandler.java diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZkDLockTemplate.java b/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZkDLockTemplate.java similarity index 100% rename from codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZkDLockTemplate.java rename to codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZkDLockTemplate.java diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZkReentrantLockCleanerTask.java b/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZkReentrantLockCleanerTask.java similarity index 100% rename from codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZkReentrantLockCleanerTask.java rename to codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZkReentrantLockCleanerTask.java diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZookeeperReentrantDistributedLock.java b/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZookeeperReentrantDistributedLock.java similarity index 100% rename from codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZookeeperReentrantDistributedLock.java rename to codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/dlock/ZookeeperReentrantDistributedLock.java diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperConnection.java b/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperConnection.java similarity index 100% rename from codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperConnection.java rename to codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperConnection.java diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperCreate.java b/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperCreate.java similarity index 100% rename from codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperCreate.java rename to codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperCreate.java diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperDelete.java b/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperDelete.java similarity index 100% rename from codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperDelete.java rename to codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperDelete.java diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperExists.java b/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperExists.java similarity index 100% rename from codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperExists.java rename to codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperExists.java diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperGetChildren.java b/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperGetChildren.java similarity index 100% rename from codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperGetChildren.java rename to codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperGetChildren.java diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperGetData.java b/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperGetData.java similarity index 100% rename from codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperGetData.java rename to codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperGetData.java diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperSetData.java b/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperSetData.java similarity index 100% rename from codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperSetData.java rename to codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/ZooKeeperSetData.java diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/package-info.java b/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/package-info.java similarity index 100% rename from codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/package-info.java rename to codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/example/package-info.java diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/sequence/DistributedSequence.java b/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/sequence/DistributedSequence.java similarity index 100% rename from codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/sequence/DistributedSequence.java rename to codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/sequence/DistributedSequence.java diff --git a/codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/sequence/ZkDistributedSequence.java b/codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/sequence/ZkDistributedSequence.java similarity index 100% rename from codes/javaweb/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/sequence/ZkDistributedSequence.java rename to codes/javaweb-example/javaweb-zookeeper/src/main/java/io/github/dunwu/javaweb/zk/sequence/ZkDistributedSequence.java diff --git a/codes/javaweb/javaweb-zookeeper/src/test/java/io/github/dunwu/javaweb/zk/dlock/ZkReentrantLockTemplateTest.java b/codes/javaweb-example/javaweb-zookeeper/src/test/java/io/github/dunwu/javaweb/zk/dlock/ZkReentrantLockTemplateTest.java similarity index 100% rename from codes/javaweb/javaweb-zookeeper/src/test/java/io/github/dunwu/javaweb/zk/dlock/ZkReentrantLockTemplateTest.java rename to codes/javaweb-example/javaweb-zookeeper/src/test/java/io/github/dunwu/javaweb/zk/dlock/ZkReentrantLockTemplateTest.java diff --git a/codes/javaweb/javaweb-zookeeper/src/test/resources/logback.xml b/codes/javaweb-example/javaweb-zookeeper/src/test/resources/logback.xml similarity index 100% rename from codes/javaweb/javaweb-zookeeper/src/test/resources/logback.xml rename to codes/javaweb-example/javaweb-zookeeper/src/test/resources/logback.xml diff --git a/codes/javaweb-example/pom.xml b/codes/javaweb-example/pom.xml new file mode 100644 index 00000000..17c8ed1c --- /dev/null +++ b/codes/javaweb-example/pom.xml @@ -0,0 +1,19 @@ + + + 4.0.0 + + io.github.dunwu.javaweb + javaweb-example + 1.0.0 + pom + JAVAWEB-EXAMPLE + JavaWeb 示例源码 + + + javaweb-mq + javaweb-zookeeper + javaweb-load-balance + + + diff --git a/docs/README.md b/docs/README.md index a044a4a6..5b6039cb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -113,17 +113,17 @@ ### RPC -- [Dubbo 应用指南](technology/rpc/Dubbo.md) +- [Dubbo 应用指南](technology/distributed/dubbo.md) - Spring Cloud ### 安全 -- [Shiro 应用指南](technology/security/Shiro.md) -- [Spring Security 应用指南](technology/security/SpringSecurity.md) +- [Shiro 应用指南](technology/security/shiro.md) +- [Spring Security 应用指南](technology/security/spring-security.md) ### 数据 -- [Mybatis 应用指南](technology/data/Mybatis.md) +- [Mybatis 应用指南](technology/data/mybatis.md) - [ShardingSphere 应用指南](technology/data/ShardingSphere.md) ### 分布式 diff --git a/docs/sidebar.md b/docs/sidebar.md index 9de0bdc8..6a50ff01 100644 --- a/docs/sidebar.md +++ b/docs/sidebar.md @@ -47,13 +47,13 @@ - [Ehcache](technology/cache/Ehcache.md) - [Caffeine](technology/cache/Caffeine.md) - RPC - - [Dubbo 应用指南](technology/rpc/Dubbo.md) + - [Dubbo 应用指南](technology/distributed/dubbo.md) - Spring Cloud - 安全 - - [Shiro 应用指南](technology/security/Shiro.md) - - [Spring Security 应用指南](technology/security/SpringSecurity.md) + - [Shiro 应用指南](technology/security/shiro.md) + - [Spring Security 应用指南](technology/security/spring-security.md) - 数据 - - [Mybatis 应用指南](technology/data/Mybatis.md) + - [Mybatis 应用指南](technology/data/mybatis.md) - [ShardingSphere 应用指南](technology/data/ShardingSphere.md) - 分布式 - [ZooKeeper 应用指南](technology/distributed/zookeeper.md) diff --git a/docs/technology/data/Mybatis.md b/docs/technology/data/mybatis.md similarity index 92% rename from docs/technology/data/Mybatis.md rename to docs/technology/data/mybatis.md index 0dacf460..04000996 100644 --- a/docs/technology/data/Mybatis.md +++ b/docs/technology/data/mybatis.md @@ -5,28 +5,28 @@ tags: ['java', 'javaweb', 'orm'] date: 2019-03-06 --- -# MyBatis 指南 +# MyBatis 应用指南 > MyBatis 的前身就是 iBatis ,是一个作用在数据持久层的对象关系映射(Object Relational Mapping,简称 ORM)框架。 - [简介](#简介) - - [什么是 MyBatis?](#什么是-mybatis) - - [MyBatis vs. Hibernate](#mybatis-vs-hibernate) + - [什么是 MyBatis?](#什么是-mybatis) + - [MyBatis vs. Hibernate](#mybatis-vs-hibernate) - [入门](#入门) - [核心 API](#核心-api) - - [SqlSessionFactoryBuilder](#sqlsessionfactorybuilder) - - [SqlSessionFactory](#sqlsessionfactory) - - [SqlSession](#sqlsession) - - [映射器](#映射器) + - [SqlSessionFactoryBuilder](#sqlsessionfactorybuilder) + - [SqlSessionFactory](#sqlsessionfactory) + - [SqlSession](#sqlsession) + - [映射器](#映射器) - [原理](#原理) - - [MyBatis 的架构](#mybatis-的架构) - - [接口层](#接口层) - - [数据处理层](#数据处理层) - - [框架支撑层](#框架支撑层) - - [引导层](#引导层) - - [主要组件](#主要组件) + - [MyBatis 的架构](#mybatis-的架构) + - [接口层](#接口层) + - [数据处理层](#数据处理层) + - [框架支撑层](#框架支撑层) + - [引导层](#引导层) + - [主要组件](#主要组件) - [配置](#配置) - [SQL XML 映射文件](#sql-xml-映射文件) - [Mybatis 扩展](#mybatis-扩展) @@ -34,9 +34,9 @@ date: 2019-03-06 -## 简介 +## Mybatis 简介 -### 什么是 MyBatis? +### 什么是 MyBatis MyBatis 是一款持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。 @@ -56,11 +56,11 @@ MyBatis 和 Hibernate 都是 Java 世界中比较流行的 ORM 框架。我们 - Hibernate 数据库移植性很好,MyBatis 的数据库移植性不好,不同的数据库需要写不同 SQL。 - Hibernate 有更好的二级缓存机制,可以使用第三方缓存。MyBatis 本身提供的缓存机制不佳。 -## 入门 +## 快速入门 > [Mybatis 官方文档之入门](http://www.mybatis.org/mybatis-3/zh/getting-started.html) 已经写得很简洁易懂,不再赘述。 -## 核心 API +## Mybatis 核心 API > 核心 API 请参考:「 [Mybatis 官方文档之 Java API](http://www.mybatis.org/mybatis-3/zh/java-api.html) 」 @@ -123,7 +123,7 @@ try { - **映射器职责** -映射器是一些由你创建的、绑定你映射的语句的接口。 +映射器是一些由用户创建的、绑定 SQL 语句的接口。 各个 insert、update、delete 和 select 方法都很强大,但也有些繁琐。更通用的方式是使用映射器类来执行映射语句。一个映射器类就是一个仅需声明与 SqlSession 方法相匹配的方法的接口类。 @@ -176,7 +176,7 @@ Mybatis 支持诸如 @Insert、@Update、@Delete、@Select、@Result 等注解 > 详细内容请参考:[Mybatis 官方文档之 sqlSessions](http://www.mybatis.org/mybatis-3/zh/java-api.html#sqlSessions),其中列举了 Mybatis 支持的注解清单,以及基本用法。 -## 原理 +## Mybatis 原理 ### MyBatis 的架构 @@ -188,8 +188,8 @@ Mybatis 支持诸如 @Insert、@Update、@Delete、@Select、@Result 等注解 MyBatis 和数据库的交互有两种方式: -1. 使用传统的 MyBatis 提供的 API; -2. 使用 Mapper 接口 +- 使用传统的 MyBatis 提供的 API; +- 使用 Mapper 接口 #### 使用传统的 MyBatis 提供的 API @@ -303,7 +303,7 @@ MyBatis 将数据缓存设计成两级结构,分为一级缓存、二级缓存