(译)数组、切片和字符串 - “append” 原理

原文地址: https://go.dev/blog/slices 作者: Rob Pike 日期: 2013 年 9 月 26 日 介绍 过程编程语言最常见的特征之一是数组的概念。数组看起来很简单,但在将它们添加到语言时必须回答许多问题,例如: 固定尺寸还是可变尺寸? 大小是类型的一部分吗? 多维数组是什么样的? 空数组有意义吗? 这些问题的答案会影响数组是否只是语言的一个特性还是其设计的核心部分。 在 Go 的早期开发中,在设计感觉正确之前,花了大约一年的时间来确定这些问题的答案。关键步骤是引入 slices,它建立在固定大小的 数组 之上,以提供灵活、可扩展的数据结构。然而,直到今天,刚接触 Go 的程序员经常对切片的工作方式感到困惑,也许是因为其他语言的经验影响了他们的思维。 在这篇文章中,我们将尝试消除混淆。我们将通过构建片段来解释 append 内置函数是如何工作的,以及为什么它会以这种方式工作。 数组 数组是 Go 中的一个重要构建块,但就像建筑物的基础一样,它们通常隐藏在更可见的组件之下。在我们继续讨论更有趣、更强大、更突出的切片概念之前,我们必须简单地讨论一下它们。 数组在 Go 程序中并不常见,因为数组的大小是其类型的一部分,这限制了它的表达能力。 以下代码: var buffer [256]byte 声明了一个数组变量 buffer ,[256]byte 表示它持有的数据类型为 byte,长度为 256。如果想声明 512 个字节的数组可以这样: [512]byte。 与数组关联的数据就是 数组中的元素。上边声明的数组缓冲区在内存中看起来像这样: buffer: byte byte byte ... 256 times ... byte byte byte 也就是说,该变量只保存 256 个字节的数据,仅此而已。我们可以使用熟悉的索引语法 buffer[0]、buffer[1] 到 buffer[255] 来访问它的元素。(索引范围 0 到 255 涵盖 256 个元素)尝试使用超出此范围的索引值访问 buffer 会使程序崩溃。 ...

2023-02-09 · 8 min · 1504 words · Hank

(译)进入Go模糊测试的世界

原文地址: https://go.dev/doc/tutorial/fuzz 本教程介绍了 Go 中模糊测试的基础知识。模糊测试会针对您的测试准备一些随机数据然后运行测试时使用它们,以尝试找出漏洞或导致崩溃的输入。可以通过模糊测试发现的一些漏洞示例包括 SQL 注入、缓冲区溢出、拒绝服务和跨站点脚本攻击(XSS)。 在本教程中,您将为一个简单的函数编写一个模糊测试,运行 go 命令,并调试和修复代码中的问题。 有关本教程中术语的帮助,请参阅 "词汇表"。 您将逐步完成以下部分: 为您的代码创建一个文件夹 添加代码进行测试 添加单元测试 添加模糊测试 修复无效字符串错误 修复双反错误 结论 注意 更多 Go 教程,请参阅 教程。 Go fuzzing 当前支持 Go Fuzzing 文档 中列出的内置类型的子集,并支持将来添加的更多内置类型。 1. 先决条件 Go 1.18 或更高版本的安装。 有关安装说明,请参阅 安装 Go。 用于编辑代码的工具。 您拥有的任何文本编辑器都可以正常工作。 一个命令终端。 Go 在 Linux 和 Mac 上的任何终端以及 Windows 中的 PowerShell 或 cmd 上都能很好地工作。 支持模糊测试的环境。 目前仅在 AMD64 和 ARM64 架构上使用覆盖检测进行模糊测试。 2. 为您的代码创建一个文件夹 首先,为您要编写的代码创建一个文件夹。 1、 打开命令提示符并切换到您的主目录。 在 Linux 或 Mac 上: $ cd ...

2022-12-19 · 9 min · 1738 words · Hank

(译)初始Go模糊测试

原文地址: https://go.dev/security/fuzz/ 从 Go 1.18 开始,Go 在其标准工具链中支持模糊测试。Native Go 模糊测试受 OSS-Fuzz 支持。 Go模糊测试详细教程见:进入Go模糊测试的世界 一文。 1. 概述 Fuzzing 是一种自动化测试,它不断地操纵程序的输入以查找错误。Go fuzzing 使用覆盖率指导来智能地不断重复执行模糊测试的代码,以发现并向用户报告问题。由于它可以覆盖人类经常错过的边缘情况,因此模糊测试对于发现安全漏洞特别有价值。 下面是一个 模糊测试 的例子,突出了它的主要组成部分。 上图显示整体模糊测试的示例代码,其中包含一个模糊目标( fuzz target )。 在模糊目标之前调用 f.Add 添加种子语料库,模糊目标的参数高亮显示为fuzzing参数。 2. 编写模糊测试 2.1. 要求 以下是模糊测试必须遵循的规则。 模糊测试必须是一个形如 FuzzXxx 的函数,以 Fuzz 作为前缀,它只接受一个 *testing.F 参数并且没有返回值。 模糊测试必须在 *_test.go 文件中才能运行。 调用 (testing.F).Fuzz 方法时的匿名函数参数称之为 模糊目标,形如 func(t *testing.T, xxx), 它必须是一个函数,接受一个 *testing.T 作为第一个参数,其他后续参数称为模糊参数,且该函数没有返回值。 每个模糊测试必须只有一个模糊目标。 所有 种子语料库 条目必须具有与 模糊参数 相同的类型,并且顺序相同。这适用于调用 (*testing.F).Add 添加的种子语料库和 testdata/fuzz 目录中已有的语料库文件。 模糊测试参数只能是以下类型: string, []byte int, int8, int16, int32/rune, int64 uint, uint8/byte, uint16, uint32, uint64 float32, float64 bool ...

2022-10-26 · 2 min · 426 words · Hank

GoLang教程——常量

golang 中常量设计摒弃了 C 语言中常量被诟病的问题,比如类型安全问题、定义和使用复杂、编译器替换字面值等,而是进行了简化。在 go 中,常量一旦声明则在编译期其值就已知且不可更改。 1. 常量的定义 常量的定义很简单,使用 const 关键字声明即可: // 单行声明常量 const c1 = 1 const c2 = -100 const c3 = "hello" 与变量一样,同样可以组合以简化声明: const ( c4 = 3.14 c5 = 1.2 + 12i // 复数 ) 但是需要注意,组合声明时后续的常量如果没有赋值则会重复最近一个非空表达式的值,例如: const ( m = 1 // 1 n // 1 k // 1 l = m + 1 // 2 i // 2 ) fmt.Println(m, n, k, l, i) ...

2022-09-10 · 3 min · 585 words · Hank

GoLang教程——变量

本质上,变量是一个持有一类值的数据存储空间的命名,我们声明变量其实就是在申请存储空间,之后引用变量其实就是在使用这块存储空间中的值。GoLang是一门强类型语言,每一个声明的变量都对应着一种类型,但是声明变量的方式却有多种。 1. 变量的定义 Go语言规范中对变量的定义如下: 变量是保存着值的一块存储空间,存储什么样的值由变量的类型决定。 变量的概念在各个语言中都有,本质上变量就是为一块存储空间取一个名字,以便开发者后续引用这个名字来使用存储空间中存储的值。至于存储什么样的值,这必须有变量的类型来决定,所以Go、Java这种强类型语言在变量声明时必须指定变量类型。 2. 变量的声明 上一篇中提到:变量声明过后,必须被使用,否则编译错误。Go中变量的声明有两种方式: 使用 var 声明一个变量,适用于大多数情况,可以不用赋初始值时 使用 := 声明变量并初始化它,适用于需要为变量直接赋初始值时,比如直接声明变量接收方法的返回值 第一种,使用 var 关键字声明变量: var v0 rune // 声明字符类型的变量 v0 var v1 int // 声明整型变量 v1 var v2 string // 声明字符串类型的变量 v2 v0 = 'A' // 给变量赋初始值 这种方式可以先声明变量然后赋值,也可以直接在声明时赋值,此时可以省略类型,编译器会自动推导出类型: var i1 = 1 fmt.Printf("i1 = %d\n", i1) 在声明多个变量时,可以使用如下简写的语法: var ( v3 [2]int // 数组 v4 []int // 数组切片 v5 struct{ i int } // 结构体 v6 *int // 指针 v7 map[string]int // map ) ...

2022-08-02 · 2 min · 310 words · Hank

GoLang教程——代码风格

Go作为编程语言中的后起之秀,在语言规范上吸收了诸多语言的优点,并形成了自身独特的语言风格。本文探讨一下Go语言的代码风格。 1. main方法 main 方法同样是go程序的入门函数,但是其定义非常简单,比如上一篇中的: func main() { fmt.Println("Hello, world!") } 直接用 func 关键字定义一个名为 main 的方法即可,没有任何参数。 那么,如何获取传递给 main 的参数呢,可以使用 os.Args: func main() { fmt.Println("hello go!") for i, arg := range os.Args { fmt.Println(i, "=", arg) } } os.Args 获取命令行传递的参数,第一个始终为执行程序的名称,一个示例执行结果如下: $ go run main_func.go a b hello go! 0 = /var/folders/8b/y3pklwbs1wj_cq7hm_yjwgpr0000gn/T/go-build3458283994/b001/exe/main_func 1 = a 2 = b 当然,通常情况不会使用 os.Args 来解析命令行参数,而是使用 go 标准库的 flag 包。 2. go的关键字 go的内置关键字有25个: ...

2022-07-30 · 3 min · 526 words · Hank

GoLang教程——初识Go语言

Go 语言是最近几年发展最快、最火热的编程语言之一,由 google 公司出品,其学习成本低、天生支持高并发、语法简洁高效、强大的标准库以及日益丰富的生态特细等特性使得其非常适合开发后端服务程序,并逐渐挑战着 Java 在服务端编程语言中的领导地位。 1. Go语言的诞生 Go 语言的前身被认为是一种名为 Limbo 的编程语言,它是由 Unix 之父、C语言之父 肯·汤普逊(Ken Thompson) 和 丹尼斯·里奇(Dennis Ritchie) 这两位计算机灵魂人物领衔并在一个名为 Plan 9 的的操作系统研究项目中发明的。 Go语言起源于2007年,当时还是身在 Google 的 Plan 9 项目原班人马在业余时间编写完成的,后来 Google 意识到了 Go 的巨大潜力并全力支持这个项目。Go 在2009年11月正式对外开放,并在2012年3月28日发布了第一个正式版本。 Go 语言的主要开发者有 肯·汤普逊(Ken Thompson)、罗布·派克(Rob Pike)、罗伯特·格里泽默(Robert Griesemer)等,每一位都是赫赫有名的大师级人物: 肯·汤普逊:与丹尼斯·里奇在贝尔实验室发明了Unix操作系统,以及用于该系统的 C编程语言 Figure 1. Ken Thompson (left) and Dennis Ritchie (right) 罗布·派克:Unix小组成员,参与了Unix后续的Plan 9和Inferno操作系统,同时也是Limbo语言和Go语言共同的发明者 罗伯特·格里泽默:协助制作Java的HotSpot编译器和Chrome浏览器的JavaScript引擎V8,Go语言的共同创造者 2. Go的特点 Go 始于 2007 年 9 月,当时 Robert Griesemer、Ken Thompson 和我开始讨论一种新语言,以解决我们和 Google 的同事在日常工作中面临的工程挑战。 当我们在 2009 年 11 月首次向公众发布 Go 时,我们不知道该语言是否会被广泛采用,或者它是否会影响未来的语言。回顾 2020 年,Go 在两个方面都取得了成功:它在 Google 内外广泛使用,其网络并发和软件工程方法对其他语言及其工具产生了显着影响。 ...

2022-07-21 · 3 min · 433 words · Hank

(译)Go并发模式——Context

原文地址: https://go.dev/blog/context 作者:Sameer Ajmani 时间:29 July 2014 1. 简介 [1] 在 Go 服务器中,每个传入的请求都在其自己的 goroutine 中处理。请求处理程序通常会启动额外的 goroutine 来访问数据库和 RPC 服务等后端。处理请求的一组 goroutine 通常需要访问特定于请求的值,例如最终用户的身份、授权令牌和请求的截止日期。当请求被取消或超时时,所有处理该请求的 goroutines 都应该快速退出,以便系统可以回收它们正在使用的任何资源。 在 Google,我们开发了一个 context 包,可以轻松地将请求范围的值、取消信号和截止日期等跨 API 边界传递给正在处理请求的所有 goroutine。 该软件包 作为context公开可用 。本文介绍了如何使用该包并提供了一个完整的工作示例。 2. Context context 包的核心是 Context 类型: type Context interface { (1) Done() <-chan struct{} (2) Err() error (3) Deadline() (deadline time.Time, ok bool) (4) Value(key interface{}) interface{} (5) } 1 Context 携带截止日期、取消信号和请求范围的跨越 API 边界的值,多个 goroutine 同时使用它的方法是安全的。 2 Done 返回一个在此 Context 取消或超时时的通道(chan) 3 Err 错误信息说明 context 为什么被取消, 在 Done 返回的 chan 被关闭之后获取 4 Deadline 返回 Context 被取消的时间 5 Value 返回参数 key 关联的值,没有则返回 nil ...

2022-07-19 · 5 min · 887 words · Hank

以太坊入门之交易

"上一篇" 介绍了MetaMask钱包的安装和使用,并成功获取到部分测试以太币(ETH)并向另外一个账户发起了一笔转账,这笔转账称之为"交易",我们将在本篇来介绍它。 1. 以太坊的账户 首先,在学习交易之前,我们需要了解以太坊的账户体系。 以太坊的账户分为两种:外部账户和合约账户。 外部账户:外部账户一般由钱包创建,具有私钥,通过私钥控制和区块链网络和智能合约的访问; 合约账户:以太坊中智能合约部署后对应着一个合约账户,合约账户拥有智能合约代码,并受其逻辑控制。 简单而言,外部账户就是区块链的用户通过钱包创建的账户,而合约账户由合约代码编写者所有,对应着一个智能合约,由 EVM 执行。 合约账户和外部账户地址没有明显的区别,都是长度 20字节、160 位、转为十六进制(0x开头)后为 40 个字符的一个串,形如 0x829BD824B016326A401D083B33D092293333A830 两者的区别: 启动交易者(首先发起交易的一方)必须是拥有私钥的外部账户,也就是说只有用户可以启动交易;但是智能合约可以通过调用别的智能合约来对交易做出响应,比如,用户A通过智能合约A发起了一笔交易,虽然智能合约A调用智能合约B也可以发起了一笔交易,但是这整个过程中交易的启动者还是用户A的外部账户 外部账户和合约账户都可以发送和接收区块链货币(如以太币),但是交易目标如果是合约账户,则会使得合约账户拥有的智能合约代码在 EVM 中执行,外部账户则不会 合约账户没有私钥,智能合约编写者通过代码逻辑来保证安全性,外部账户拥有私钥,用户需要通过妥善的保管私钥来保证账户的安全性 2. 以太币单位 交易与货币息息相关,以太坊发行的货币为以太币(Ether, ETH,符号 Ξ),其基本单位为 wei,ETH与wei的关系如下: 1 ETH = 10^18 wei 此外,还有比较常用的 gwei (gigawei): 1 ETH = 10^9 gwei 1 gwei = 10^9 wei Table 1. 以太币的面额和单位 值(以wei为单位) 指数 通用名称 标准名称 1 1 wei wei 1,000 103 babbage kilowei or femtoether 1,000,000 106 lovelace megawei or picoether 1,000,000,000 109 shannon gigawei or nanoether 1,000,000,000,000 1012 szabo microether or micro 1,000,000,000,000,000 1015 finney milliether or milli 1,000,000,000,000,000,000 1018 ether ether 1,000,000,000,000,000,000,000 1021 grand kiloether 1,000,000,000,000,000,000,000,000 1024 megaether ...

2022-07-16 · 2 min · 373 words · Hank

以太坊入门之MetaMask钱包的安装和使用

区块链中,与用户直接相关的一个重要组件是钱包。用户通过钱包来访问链上的资金、查看账户地址、转账交易等操作。可以说,没有钱包,用户就不能正常访问链上资金,可见其重要程度。而诸多钱包中,MetaMask是一款可以直接在浏览器中使用的、实现了钱包标准 BIP-39 的钱包,也是大多数使用者选择的入门级钱包。 1. 区块链钱包简介 与普通的实物钱包不同,区块链钱包的核心目的是用来保存用户的公钥和私钥,从而保证访问区块链中账户资金的权限,即:证明钱是你的,而不是存储资金。 Figure 1. 区块链钱包(图片来源网络) 钱包的一个重要的误区是:钱包存储资金。其实,钱包并不会直接存储区块链上的资金。可以将其看做一个银行账户,当你需要取钱时,只需告诉银行要取多少钱,然后银行就可以检查账户的余额是否充足并进行取款,账户只是一个逻辑划分,钱始终是存在银行。同理,区块链钱包也只是在区块链上开了一个账户,钱始终在区块链网络中。 开通银行账户,我们必须设置交易密码,已验证你有访问账户的权限;同样的,区块链钱包中,私钥就相当于账户密码,每产生一笔交易时,都需要通过钱包的私钥来进行签名,已验证你拥有操作账户资金的权限。 但是,与银行账户不同,银行是中心化的资金机构,除了验证账户密码,还有其他手段比如身份证来验证取款人身份,忘记密码还可以重置。但在区去中心化的区块链中,私钥是你唯一的凭证,一旦私钥泄露,那么任何人都可以访问你的账户。 因此,钱包可以看成是一个私钥圈,保存了多组公钥私钥对。现在的钱包大多采用随机串生成助记词,再加密生成种子密钥,最后再以种子密钥为根生成密钥树的方式来生成密钥。这里仅说明钱包的概念,关于钱包的底层原理,后续在详细讨论。 Figure 2. 钱包中的密钥树(图片来源网络) 2. 什么是MetaMask MetaMask 是一款简单易用的区块链钱包,除了 App 版本的钱包,它还提供了基于浏览器的插件,包括 Chrome、Firefox等,而且它内置了 Web3,可以直接与以太坊区块链交互并开发 DApp 网页应用,这也是大多开发者选择它的原因。 3. 安装MetaMask 安装 MetaMask 很简单,以 Chrome 为例,直接从 Google 商店安装即可,前提是需要自带梯子。步骤如下: 1、进入google应用商店,直达地址在 这里,也可以自己搜索MetaMask,第一个小狐狸头像的就是 2、点击 "添加至Chrome",然后弹出框点击"添加至扩展"即可,然后会下载 Chrome 插件,耐心等待 3、下载完成后,Chrome会自动安装,完成后会进入扩展插件地址 点击开始使用,进入钱包创建页面 4、创建钱包 如果您以前有钱包,并记得助记词,那么可以直接通过助记词导入钱包,没有则点击"创建钱包"按钮新建,下一步点击"我同意",进入密码设置页面: 设置并牢记自己的密码,以后登录钱包是需要用到,然后点击"创建"按钮后,会出现一个介绍的视频页面,直接点击"下一步" 5、备份助记词 这一步很重要,助记词作为恢复秘钥的唯一手段,需要十分安全地做备份,一般建议手抄写在纸上并保存,其他电子存储方式均存在泄漏风险 什么是助记词? 按照密钥生成策略分,钱包分为两类:非确定性钱包和确定性钱包,前者的每一个私有都是由不同的随机数生成的,私钥间没有任何关联,备份和恢复非常麻烦;而确定性钱包则是通过一个随机串作为种子密钥来生成各种私钥,只要备份种子密钥就可以恢复出所有的私钥。 但是,由于种子密钥长度长而且毫无规律,非常难以记录和输入,因此人们为这个种子密钥按照一定的算法来为其匹配单词表中的一组顺序固定、数量固定(与种子密钥长度有关)的单词,这样便于备份和恢复,这有点类似密码本,这些单词就成为助记词(mnemonic)。 因此,助记词可以看做种子密钥的别名,其安全性与之等同,必须安全保存。 ...

2022-06-11 · 1 min · 123 words · Hank

什么是以太坊

上一篇 简单介绍了什么是区块链,也介绍了以太坊的产生和基本概念,其中提到: 以太坊 是一个去中心化的、开源的、图灵完备的、有智能合约功能的区块链开放平台。作为开放平台和”世界超级计算机”,以太坊以智能合约为核心为去中心化应用(DApp)建设提供了整套解决方案。这一篇我们将详细介绍什么是以太坊。 1. 为什么要学习以太坊 以太坊是目前最大的区块链开发平台,也是“世界计算机”。以太坊是区块链开发工作的必经之路,原因如下: 以太坊是目前最大的区块链开发平台,拥有庞大的用户群和社区 以太坊发展早,技术相对成熟,资料、文档众多 以太坊生态体系完备,除了智能合约、DApp、DAO、DeFi,还提供了 ENS、Swarm等DApp构建所需技术体系 以太坊减缓了区块链陡峭的学习曲线,以应用入手,使得区块链的开发上手快速、简单 以太坊与 JavaScript 结合紧密,开发者学习难度小 总之,以太坊好比微信开放平台,DApp开发好比基于微信公众号、小程序的开发,我们不用太关注底层的东西,而是使用以太坊和微信已经提供好的功能来开发自己的DApp和微信公众号,只是以太坊是基于去中心化的区块链,而微信则是中心化应用。 2. 以太坊的诞生 上一篇 介绍了什么是区块链,其中提到, Vitalik Buterin (V神)在2013年发表了《以太坊:一个下一代智能合约和去中心化应用平台》,提出了以太坊的基本构想。 V神是比特币的狂热粉丝,他不断思考着如何扩展比特币和其智能合约协议 Mastercoin,并提出了一种可替代 Mastercoin 的专用合约语言的更灵活和脚本的智能合约机制,这只是一个他的初步构想,尽管比特币团队非常感兴趣,但是这种想法过于超前,而且与比特币的开发计划存在冲突,所以并没有得到支持。 V神 2013年12月,V神发表了 以太坊白皮书,勾勒出以太坊背后的思想:一个图灵完备的通用目的的区块链。以太坊白皮书发布后,不断有区块链爱好者向V神提交并反馈自己的想法,帮助其不断完善这个想法。其中,Gavin Wood(Mastering Ethereum的作者之一)在C++语言上对以太坊给与了极大地帮助,后来成为了以太坊的联合创始人和CTO,并编写了 以太坊黄皮书。 Gavin Wood 白皮书与黄皮书 白皮书由V神编写,旨在提出改进比特币背后区块链技术的一种构想,使其成为一种通用的、适用于多个方面而不仅仅是虚拟货币的区块链技术,这仅仅是一种理论,在以太坊的成长过程中得以逐步实现; 黄皮书是由以太坊CTO Gavin Wood 博士编写,旨在定义以太坊的技术规范,详细描述了以太坊背后的技术实现细节,其中包含了大量的数学公式,比如,黄皮书定义了产生每一笔交易包含的通用字段、如何验证交易,以太坊虚拟机规范(EVM)等等。黄皮书着重于以太坊技术规范的制定,包含大量数学公式,专业性很强,比较难懂,这里[1]有一个面向普通大众的浅黄版本。 Figure 1. 白皮书 vs 黄皮书 V神与Gavin Wood一起,不断完善以太坊的协议层和技术体系,逐步实现“可编程的、安全的、通用的区块链”这一目标。2015年7月30日,第一个以太坊区块被成功挖出,以太坊作为“世界计算机”正式运行并对外提供服务。 3. 以太坊发展的四大阶段 以太坊的发展并不是一帆风顺的,以太坊的发展整体上分为四个阶段 [2],代号分别为:Frontier(边疆)、Homestead(家园)、Metropolis(都会) 和 Serenity(宁静),每一个阶段都以"硬分叉"的方式发布,因此新版本不能与老版本兼容。并且,每一个阶段都在不断完善其新功能和安全性,比如2016年以太坊遭受DAO攻击,而分叉了 "以太坊经典" 和 "以太坊" 这样的相互竞争、并行的以太坊版本。 阶段和硬分叉按照区块高度编号并标注日期,包括: Frontier: 区块高度为0,以太坊的初始阶段,持续时间为2015年7月30日至2016年3月。 Ice Age: 冰河期,区块高度为 200,000,引入指数难度增加的硬分叉,以便在准备就绪时将激励机制过度到Pos(权益证明)。 Homestead:家园,区块高度 1,150,000, 以太坊的第二阶段,发布于2016年3月。 DAO: 区块高度 1192000,以太坊遭受严重的攻击而不得不硬分叉,从而产生了 以太坊经典(Ethereum Classic, ETC) 和 以太坊(ETH) 两个相互竞争的系统 [3]。 Tangerine Whistle:橘子口哨,区块高度 2,463,000,改变某些IO密集操作的燃气计算方法和清除拒绝服务攻击(利用这些操作的低燃气成本)累积状态的硬分叉。 Spurious Dragon:伪龙,区块高度 2,675,000,2016 年 11 月 22 日发生的另一次计划外的硬分叉,包括四项提案,用以解决一些攻击问题: EIP 155: 简单重放攻击保护 EIP 160: 提升EXP操作码的费用 EIP 161: 状态树清理 (不变量保持替代) EIP 170: 调整智能合约的最大字节数限制 ...

2022-05-09 · 2 min · 238 words · Hank

什么是区块链

区块链是这几年非常火热的一个话题,越来越多的人加入到”区块“大军之中。他们其中一些人,积极投入到挖矿、炒币行列之中,甚至发行自己的数字货币来创收,这部分人社称之为”币圈“;另一部分人,深入探究区块链底层技术,期望不断改进并将区块链技术运用到更多领域中,这部分人社称之为”链圈“。越来越多的人积极群涌入区块链,游弋于“币圈"和"链圈"之间,忙的不亦乐乎。 本文将简单介绍区块链的一些入门知识,并介绍区块链开发所需要具备的基础知识,希望对准备进入区块链的开发者们会有一些帮助。 1. 什么是区块链 区块链(Blockchain)源于 比特币,它其实是对比特币的底层技术的一种扩展和创新。2008年,一位网名“中本聪”的日本大佬发布了一篇名为 《比特币:一种点对点的电子现金系统 的论文(比特币 白皮书),文中总结了之前的数字货币发明,提出了一种完全去中心化的数字货币系统,即比特币。文中并没有提及“区块链”一词,而是使用了“区块”(Block)和“链”(Chain)来描述比特币底层技术,后来人们将区块、链合并在一起,正式提出“区块链”(Blockchian)这一概念。 区块链是一系列技术的组合,包括P2P动态组网、密码学、基于密码学的共享账本、共识机制、智能合约等。简单而言,区块链可以看做一个全球互联的超级账本,链中的每一台计算机通过P2P组网连接并保留了账本的副本,这样,链中的计算机都有一份账本。每一笔交易都需要记录到账本中,当一笔交易产生后,产生这笔交易的计算机将交易信息广播出去,其他链上的计算机就会收到交易信息,它们会进行一系列复杂计算,最终由率先计算完成的计算机将这笔交易信息记录到账本上。具体的计算机制其实是由不同的区块链设计好的,与该区块链的结构相关。比如按照比特币的设计,计算机需要将10分钟内的全部交易打包,再加上一个随机数,然后穷举随机数计算一个256位的哈希值,使得其符合一定的条件,这样就可以获得交易记账权,一旦计算完成并记账,该计算机要快速将计算的区块广播出去,由其他计算机来验证计算的正确性,防止他人篡改。 每产生一笔交易并不是立即就要被区块的各个节点记账,而是按设计要求被打包到一起,比特币设计为每10分钟将所有新交易打包(而以太坊时间更短,大约9秒左右),形成了一个区块(Block),然后将区块广播出去,区块链中的计算机都需要验证区块的正确性,并将其写入自己的账本中,完成记账。区块链则是由一个个相互连接的区块组成的,每一个区块都记录了上一个区块的 HASH 值,第一个区块没有上一个区块的 HASH 值,称之为”创世区块“。计算哈希值竞争记账权的计算机称为“矿工”,计算的过程称为”挖矿“,挖矿成功则获得交易记账权,记录打包好的交易信息并写入最新的区块,称为”出块“,最新出块的矿工可以获得一定的手续费收入。 区块链中的计算机都有账本,那么如果确定各个账本数据的一致性呢?区块链采用”共识机制“来保证大部分诚实计算机的账本一致性,这样就实现了完全去中心化。常用的共识机制有Pow(工作量证明机制,例如挖矿机制)、PoS(权益证明)、DPos(股权授权证明)和分布式一致性算法(如拜占庭容错算法、Paxos、Raft)。 与传统的中心化应用相比,区块链具有如下特点: 去中心化:区块链本质是基于P2P对等网络的分布式系统,链上的每一个节点都存有相同的数据,通过共识机制来决定交易数据的同步,不需要中心化服务器。但是目前,还不能做到完全的去中心化,某些场景还是具备中心化的特征。 高度开放:公有链上的区块链系统通常都是开源的,任何人都可以查询区块链上的数据,智能合约代码高度公开、透明,数据存储与链上所有节点中,不用担心中心化应用的资金安全等一系列问题。 高度可用:区块链应用由区块链上的每一个节点共同维护,任何一个或几个节点宕机不影响整个应用的可用性。 高度安全:区块链技术采用一系列加密措施(如非对称加密、哈希算法、椭圆曲线算法等)来对交易进行加密和签名,保证交易不能被伪造、篡改,然后借助分布式系统中各个节点的共识算法形成强大算力来抵御破坏者的攻击,从而保证链上数据的安全性。 匿名性:区块链上的用户信息都是匿名的,仅对外暴露钱包公钥和地址,就可以完成交易,完全不需要用户的真实身份信息。 2. 什么是以太坊 比特币作为一种虚拟数字货币,为金融领域开创了一种全新的去中心化货币系统。比特币的狂热粉们一直在探索,比特币技术能否使用到其他非金融领域呢? 以太坊 的出现,为区块链技术迎来了崭新的时代。 2013年,比特币的忠实拥护者 Vitalik Buterin (人称V神)提出了一种构想:区块链不应该仅用于金融领域,而是应该开放出来形成一个开放平台,供人们开发更多的去中心化应用,为此他提出了以太坊,并发表了《以太坊:一个下一代智能合约和去中心化应用平台》,也就是后来的 《以太坊白皮书》。 以太坊 是一个去中心化的、开源的、图灵完备的、有智能合约功能的区块链开放平台。 首先,以太坊是一条发行有自己货币(ether,以太币)的区块链;其次,以太坊具备开发去中心化应用功能,它具有能够在以太坊虚拟机(EVM)上运行的 智能合约 功能,开发者可以编写智能合约来自主控制去中心化应用;第三,以太坊优化了PoW(工作量证明机制),并提供了PoS权益证明共识机制,大大加快了区块链交易确认速度(比特币确定一笔交易需要10分钟,而以太坊减少到9秒左右),为去中心化应用开发提供了必要条件。 总之,以太坊是一个区块链开放平台,通过它可以开发自己的区块链 去中心化(Decentralization)应用(Dapps)。 以太坊被称为"第二代的区块链平台", 这个网站 用车站的例子动态、形象地展示了以太坊和比特币之间的一些差别,非常生动有趣。 3. 区块链中的一些基本概念 了解了区块链和以太坊,现在我们来看看区块链中的一些概念。 区块链:Blockchain,共享的分布式账本,交易附加到区块上存储并记录。 区块:Block,多个交易被打包为区块并存储,区块之间相互连接,形成一个链状结构,每一个区块都有一个哈希值加以区分,区块在链中的长度称为区块高度。 钱包:Wallet,存储用户虚拟货币的地方,对外暴露公共地址和公钥,内部包含有绝对私密的私钥信息。 以太坊:Ethereum,一个基于区块链的去中心化运行智能合约的平台,旨在解决与审查,欺诈和第三方干扰相关的问题。 主链:Mainnet,正式运行的区块链的主网络,一个区块链项目经过前期的技术开发后,最终都会发布到区块链主网上线,比如 以太坊主网。 侧链:Sidechains,侧链实质上不是特指某个区块链,而是指遵守侧链协议的所有区块链,该名词是相对于主链而言的。侧链协议是指可以让主币安全地从区块链主链转移到其他区块链,又可以从其他区块链安全地返回主链的一种协议。侧链与主链共存,并可以扩展主链的功能、提高交易速度等,比如ETM就是以太坊ETH的侧链。 测试链:Testnet,与主链功能相同的测试网络,以测试为目的搭建,可以免费从水龙头获得测试币在测试链上进行开发测试,如 币安测试链。 交易:由原始帐户签署的提交到以太坊区块链的数据,并以特定地址为目标。交易包含元数据,例如交易的燃气限额(Gas limit)。 区块链浏览器:查询区块链中数据的工具网站,可以详细查询区块链上的详细信息,如 以太坊浏览器、 币安链浏览器等等。 Dapp:去中心化应用,基于区块链技术开发的应用程序,数据存储于区块链中。 DAO:去中心化自治组织,一种将组织的管理和运营规则以智能合约的形式编码在区块链上,从而在没有集中控制或第三方干预的情况下自主运行的组织形式。DAO 有望成为应对不确定、多样、复杂环境的一种新型有效组织。 以太币:Ether,以太坊生态系统中使用的本地货币,在执行智能合约时承担gas费用,它的符号是 Ξ. 以太坊虚拟机:EVM,Ethereum Virtual Machine, 基于栈的、执行智能合约字节码的虚拟机。 智能合约:Smart Contract,在以太坊的虚拟机中执行的程序,有开发者编写以控制链上业务逻辑。 水龙头:Faucet,一个网站,用来向测试用户提供一定数量的测试币,供测试使用,测试币除了测试并没有真正的价值。 Solidity:过程式(命令式)编程语言,语法类似于 Javascript, C++ 或 Java,以太坊智能合约最流行和最常使用的语言。由以太坊架构师 Gavin Wood 发明。 图灵完备:Turing Complete,在计算理论中,如果数据操纵规则(如计算机的指令集,程序设计语言或细胞自动机)可用于模拟任何图灵机,则它被称为图灵完备或计算上通用的。这个概念是以英国数学家和计算机科学家阿兰图灵命名的。 ...

2022-04-17 · 1 min · 213 words · Hank

Typora For Mac、Windows破解版和免费版下载

Typora是一个所见即所得(WYSIWYG)的markdown编辑器,由于其快捷、方便,并且配备强大的快捷键来快速编写markdown文档,一经发布就深受大批用户(尤其是程序猿)的喜爱,也是迄今为止我用过的最好用的markdown编辑器了! Typora有如下特性: 轻量级,性能非常高,启动时间在秒级 所见即所得,也是其最大的特性,它只需要一个界面就可以完美展示渲染后的markdown格式,而不需要像大多数markdown编辑器需要两个界面,一个显示源码,一个显示渲染结果 支持表格、UML图、数学公式,编写方便 支持大量展示主题,可以下载使用他人发布的主体,也支持自定义主题 支持markdown自动目录 支持图片拖拽调整位置,支持调整图片显示大小,支持搭建自定义图床 支持导出PDF格式文件 Typora在1.0之前的版本都是免费使用的,从1.0开始,官方宣布Typora收费,价格14.99$,人民币89元,价格不算太贵,有条件请支持正版! 本站从网络上搜索好了一些平台的破解版本和最后的免费版本,请根据需要自行下载,下载地址如下: Typora for Mac 1.2.3破解版下载:链接: https://pan.baidu.com/s/1g-xT_AaagWBvzvKVBDGkmg 密码: grvk Typora for Windows 1.2.4破解版下载:链接: https://pan.baidu.com/s/1-tfx_Bz0ScExaqrixlFVzg 密码: 5it7 Typora for Mac 0.11.18免费版下载:链接: https://pan.baidu.com/s/1Xlt6fhEF_TLJpXBryPRI6w 密码: mrei Typora for Windows 0.11.18免费版下载:链接: https://pan.baidu.com/s/1DRorRI4gX9b_sgk0WdhxOA 提取码: ttsx Typora for Linux 0.11.18免费版下载:链接: https://pan.baidu.com/s/1O1aA0p_DTzzWkMCAD0g5cQ 提取码: hm84 下载声明 本站所有下载资源均来自互联网,由站长搜集整理,版本归原作者所有,如有侵权请联系站长删除。 本站所有下载资源仅供个人学习和研究使用,请勿用于商业用途,请在下载后24小时内删除。

2022-04-14 · 1 min · 49 words · Hank

读《Java并发》— Java内存模型

"上一章" 介绍了线程安全性,这一章称为"对象的共享",书中重点介绍了如何共享和发布对象,这两章是并发编程中非常基础却很重要的部分。在本章,首先介绍了什么是可见性问题,然后介绍了Java内存模型,讨论什么是内存可见性以及java保证内存可见性的方式,在此基础上介绍如何设计线程安全的对象,如何使用线程封闭技术和设计不可变对象来避免同步,最后再重点探讨如何安全地发布对象。由于内容较多,我将这一章分拆为几篇来阐述自己对本章的理解,这是第一篇。 Java的并发机制基于共享内存,要理解对象间的共享关系,则离不开对象间的内存关系,这涉及到本章要介绍的一个重要概念:Java内存模型,又称 JMM。 1. 内存可见性 上边提到,Java的并发机制是采用的是 共享内存模型,因此,在并发环境中保证对象间的内存可见性是并发编程解决的主要问题。 什么是内存可见性?可见性是一个复杂的问题,它表示程序中的变量在写入值后是否能够立即读取到。在单线程环境中,由于写入变量和读取变量都是在单线程中进行的,因此能够保证总能读取到修改后的值。但在多线程环境下,却无法保证,可能一个线程修改变量的值,而另外的线程并不能正确读取被修改的变量的值,除非我们使用变量同步等机制来保证可见性。 为什么多线程环境下变量就不能保证可见性了呢?稍后介绍JMM时再来讨论,先看一个示例。 内存可见性(Memory Visibility):某些线程修改了变量的值,正在读该变量的线程能够立即读取到修改后的值。 @NotThreadSafe public class UnsafeSequence { private int count; public void increment() { count++; } public int getCount() { return count; } } 上边的示例,count变量被多个线程共享,因此不能保证 getCount() 总能读取到 increment() 增加后的值。那是不是在 increment() 方法上使用 synchronized 进行同步就能保证可见性了呢?答案是不行。虽然使用同步能够保证只有一个线程修改count的值,但是其他多个线程仍然可能读到 失效的值,因此必须在 getCount() 上也使用同步,见 "这里"。 再看一个示例,如下边的代码: @NotThreadSafe public class NoVisibility { private static boolean ready; private static int anInt; private static class VariableReader implements Runnable { @Override public void run() { while (!ready) { Thread.yield(); } System.out.println(anInt); } } public static void main(String[] args) { new Thread(new VariableReader(), "read").start(); anInt = 47; (1) ready = true; (2) } } ...

2022-03-28 · 3 min · 578 words · Hank

读《Java并发》— 线程安全性

上一章 介绍过,并发编程会带来诸多挑战,最基本的就是 线程安全性,但这又是一个非常复杂的主题。这一章重点介绍了什么是线程安全性、原子性、竞态条件等概念,以及在Java中如何通过加锁来确保线程安全性。 1. 什么是线程安全性 1.1. 正确性 要理解什么是线程安全性,必须先明白什么是正确性,正确性是线程安全性的核心概念。 正确性的含义是,某个类的行为与其规范完全一致。 — Java并发编程实战 这个定义从静态视角出发强调了类的规范重要性。通常,我们不会定义类的详细规范,但是我们应该为类和方法提供文档注释,来说明类是否是线程安全的,以及对于线程安全性如何保证。尤其在方法上,应该明确规定该方法是否已经保证了线程安全,调用者是否应该在同步机制内调用该方法等等。 下边是 ArrayList 的类文档注释的节选,它告诉调用者关于线程安全的内容: Note that this implementation is not synchronized. If multiple threads access an ArrayList instance concurrently, and at least one of the threads modifies the list structurally, it must be synchronized externally. (A structural modification is any operation that adds or deletes one or more elements, or explicitly resizes the backing array; merely setting the value of an element is not a structural modification.) This is typically accomplished by synchronizing on some object that naturally encapsulates the list. ...

2022-01-25 · 6 min · 1177 words · Hank

读《Java并发》— 并发简史

这一章主要讲述线程的发展历史,以及并发编程带来的优势和挑战。 1. 线程的发展 1946年第一台计算机诞生,一直到20世纪50年代中期,这时候的计算机没有操作系统的概念,采用手工操作的方式工作,每次只能有一个人使用计算机。此时的手工操作方式,用户独占全机,昂贵的计算机资源得不到充分利用。 后来,随着计算机的发展,出现了批处理系统、多道程序系统,它们都提升了计算机的资源利用率。1961年,分时系统(Time Sharing System)出现,此时的一台计算机可以供多个用户终端同时连接并使用,就好像自己在独占计算机一样。分时系统将CPU的运行时间分成很短的时间片,按时间片分配给不同的连接终端使用,这充分利用了计算机资源,看起来就好像多个用户在同时使用计算机一样。 再后来,通用操作系统出现(General Operating System),它隐藏了硬件调用接口,为应用程序员提供调用硬件资源的更好、更简单、更清晰的系统调用接口,程序员就可以直接面向操作系统来编写程序,而不用关注硬件。 计算机的发展 [1],都在试图解决三个问题: 资源利用率:尽可能的利用昂贵的计算机资源,如CPU、内存,而不是使其大部分时间处于等待状态 公平性:不同的用户对计算机都有同等使用权,而不是排队等待 便利性:可以同时执行多个程序,每个程序执行一个任务并在必要时相互通信 这三个问题促使进程和线程的出现。计算机可以同时运行多个程序,每个程序一个进程;而同一个进程中同样存在多个线程(轻量级进程),它们共享进程范围内的资源,如内存句柄和文件句柄,每个线程都有自己的程序计数器、局部变量等,而且同一个进程的多个线程都可以被调度到CPU上运行。但是,线程之间存在着共享资源(进程的内存),如果没有协同机制,那么多个线程在同时处理共享内存时会存在安全问题,比如一个线程读某个变量而一个线程同时在写这个变量,如果没有线程同步机制,则该变量的值变得不可预测。 举个例子,假设进程为多条高速公路,它们之间彼此独立,而线程就好比高速公路的多个车道,它们共享这条高速路的资源,如指示牌、测速设备等,线程中执行的任务就是一辆小汽车,它们相安无事的飞驰在自己的车道上,一旦它们开始抢夺道路资源,不遵守交通规则,超速、违规变道,那么后果可想而知。 2. 多线程的优势 Java是门支持并发编程的语言,充分利用其提供的并发特性来编写高并发程序,是信息化时代发展所需。多线程程序的优势可以从几个方面来说明: 多线程程序能够极大的发挥处理器的强大能力。现代的计算机,CPU核心越来越多、处理频率越来越快、制造工艺越来越精,计算机的价格也越来越亲民,个人普通电脑都普遍是四核心、八核心甚至配置更高。在单核心CPU中,可能多线程程序不但没能提升处理性能,反而由于线程上下文切换的开销导致程序性能降低;但是,在多核心时代,多线程能够发挥处理器的强大能力,每个核心都能同时处理一个任务而不会造成上下文切换,如果还停留在单线程程序,这无疑是极大的浪费计算机资源,如果有100个CPU,那么单线程程序会造成99%的CPU都处于空闲状态。 建模更简单。首先要明确,编写多线程程序与单线程程序相比,肯定是复杂且容易出错的,那么为什么说建模更简单呢?这需要从管理线程中执行的任务角度去分析。在单线程中执行多个任务,与多个线程每个线程仅执行一个任务相比,哪个更容易管理呢?很明显是后者。虽然多线程中的任务彼此可能存在通信,但是从模型上看,一个线程只管理一个任务,职责更单一。 异步事件简化处理。这一点可以理解为编程模型的简化。单线程和多线程程序,在IO模型的选择上存在很大的不同。单线程程序,要提高并发请求数,底层需要使用异步IO、非阻塞IO或者IO多路复用模型 [2],但他们都是由操作系统底层支持,编程难度大;而多线程程序,由于应用层面本身采用了多线程模型,底层的IO则可以选择同步IO(异步的事情多线程做了),这就降低了开发难度。比如单线程模型中,Nginx采用多进程单线程和IO多路复用模型的架构,具备极高的并发处理能力;Redis采用单线程和IO多路复用模型,同样具备很高的处理效率。 响应更灵敏的用户界面。Java的GUI框架AWT和Swing采用事件分发线程来替代传统的主事件循环,并可将将耗时的处理任务放到单独的线程中异步执行,从而提高用户界面的响应速度。 3. 多线程的挑战 多线程是一把双刃剑,带来诸多优势时也让开发者面临更大的挑战。 1、开发难度问题 由于多线程程序运行的不确定性,给开发难度带来很大的挑战。相对于单线程程序,多线程编程更难以掌握,不仅需要充分理解编程语言级的多线程支持,还需要掌握操作系统层级的多线程模型。尽管如此,多线程程序仍然难以调试和跟踪,尤其是产生 "活跃性" 问题时往往无法重现。 2、安全性问题 什么是安全性?安全性指的是程序永远不会返回错误的结果。看下边的示例: public class UnsafeSequence { private int count; public int increment() { return count++; } public int getCount() { return count; } } 多个线程调用 increment 方法,由于 count++ 包含了 读取、修改、写入三步内存操作,这三步不是一个原子操作,存在多个线程重复写同一值的情况,如下图示意: Figure 1. 线程竞争导致错误的结果 由于线程在同时操作相同的count域,一个线程可能会覆盖另一个线程写入的值(线程竞争),最终count得到错误的值6,而不是预期的7. 线程安全本身是一个非常复杂的问题,由于线程之间存在资源共享,如果多个线程交替执行又没有有效的同步机制,执行结果将难以预料。尽管Java提供了足够的同步机制来保证线程安全性,但是这就要求开发者必须完全理解多线程程序的执行原理和Java的同步机制,这给程序开发带来很大的难度。 3、活跃性问题: ...

2022-01-22 · 1 min · 125 words · Hank

多线程活跃性——哲学家就餐问题及死锁

死锁是多线程编程中最常见的一种"活跃性问题",除了死锁还包括"饥饿"和"活锁",这些活跃性问题给并发编程带来极大的挑战。比如出现死锁时,定位和分析问题相对困难,一旦出现死锁,通常只能重启应用程序。本文通过死锁最经典的"哲学家就餐问题"来介绍死锁的产生原因和解决办法。 1. 死锁 死锁指的是多个线程相互等待彼此而进入永久暂停状态。比如,线程 T1 持有锁 L1 去申请锁 L2,但是线程 T2 持有锁 L2 申请锁 L1,此时它们都在等待对象释放锁,从而进入永久阻塞状态。这就好比两个小朋友,他们各有一个玩具,但都不愿意分享给对方,却希望获得对方的玩具,最终互不相让,只能彼此干瞪眼了。 写一个死锁的程序很简单,比如下边的代码: public class DeadLockTest { private static final Object lock1 = new Object(); private static final Object lock2 = new Object(); public static void main(String[] args) { new Thread(new MyThread1(lock1, lock2)).start(); new Thread(new MyThread2(lock1, lock2)).start(); } } class MyThread1 implements Runnable { private final Object lock1; private final Object lock2; public MyThread1(Object lock1, Object lock2) { this.lock1 = lock1; this.lock2 = lock2; } @Override public void run() { while (true) { synchronized (lock1) { (1) System.out.println("using lock1"); synchronized (lock2) { System.out.println("using lock2"); } } } } } class MyThread2 implements Runnable { private final Object lock1; private final Object lock2; public MyThread2(Object lock1, Object lock2) { this.lock1 = lock1; this.lock2 = lock2; } @Override public void run() { while (true) { synchronized (lock2) { (2) System.out.println("using lock2"); synchronized (lock1) { System.out.println("using lock1"); } } } } } ...

2022-01-20 · 6 min · 1208 words · Hank

读《Java并发》— 简介

这是个人的《Java并发编程实战》阅读笔记整理系列的第一篇文章,这个系列包括对书中并发编程内容的整理、总结以及个人的一些感悟和实践心得。 1. 简介 《Java并发编程实战》是 Brian Goetz 等 6 位 Java 大师合著的介绍 Java 并发编程的经典著作,这部名著由浅入深的介绍了 Java 并发编程的诸多知识,是一本完美的Java并发参考手册, 豆瓣评分 9.0,可见其受欢迎程度。 1.1. 内容概要 《Java并发编程实战》从并发性和线程安全性的基本概念出发,介绍了如何使用类库提供的基本并发构建块,用于避免并发危险、构造线程安全的类及验证线程安全的规则,如何将小的线程安全类组合成更大的线程安全类,如何利用线程来提高并发应用程序的吞吐量,如何识别可并行执行的任务,如何提高单线程子系统的响应性,如何确保并发程序执行预期任务,如何提高并发代码的性能和可伸缩性等内容,最后介绍了一些高级主题,如显式锁、原子变量、非阻塞算法以及如何开发自定义的同步工具类。 这本书介绍的是 Java 中 "并发编程" 这样的高级主题,不太适合初学者阅读。书中诸多的内容都较难理解,比如线程的 "竞态条件"、"同步机制"、"活跃性" 等等,如果对并发知之甚少,不熟悉操作系统层面的相关内容,阅读起来比较吃力。 1.2. 作者简介 本书作者都是Java Community Process JSR 166专家组(并发工具)的主要成员,并在其他很多JCP专家组里任职。Brian Goetz有20多年的软件咨询行业经验,并著有至少75篇关于Java开发的文章。Tim Peierls是“现代多处理器”的典范,他在BoxPop.biz、唱片艺术和戏剧表演方面也颇有研究。Joseph Bowbeer是一个Java ME专家,他对并发编程的兴趣始于Apollo计算机时代。David Holmes是《The Java Programming Language》一书的合著者,任职于Sun公司。Joshua Bloch是Google公司的首席Java架构师,《Effective Java》一书的作者,并参与著作了《Java Puzzlers》。Doug Lea是《Concurrent Programming》一书的作者,纽约州立大学 Oswego分校的计算机科学教授。 也许你对这几位大师不怎么熟悉,但是你一定读过它们的著作,尤其是 Joshua Bloch 的《Effective Java》,这本书也是 Java 开发者必读的书籍,目前已经出到了第三版。 如果经常编写并发程序,那么对于 Doug Lea 一定不会陌生,因为 Java 5 开始的很多并发工具(它们都在 java.util.concurrent 包中,简称 juc 包)都出自他的手笔,如 AbstractQueuedSynchronizer、AtomicXxx 原子类、BlockingQueue、ConcurrentMap、ExecutorService 等等,几乎整个并发工具包的类都是他实现的。 1.3. 阅读建议 前边说过,尽管《Java并发编程实战》是经典著作,但是由于其内容的特殊性,存在一定的阅读门槛。如果英文过关,建议直接阅读英文版,对于大多数人只能阅读翻译的中文版本了,但中文版存在一些问题,诸多概念介绍的很官方,内容不够充实、通俗易懂,理解起来费力,也存在一些翻译不通顺的问题。 ...

2022-01-20 · 1 min · 125 words · Hank

Java设计模式(24)-职责链模式

1. 引言 您有没有遇到各种需要走流程的事情?比如,请假申请,假设公司规定,3天以内的请假申请组长可以直接审批,而4到7天的请假申请必须要让部门经理来审批了,超过7天的请假申请只能由公司总经理来审批。类似的场景还有很多,尤其在工作流中,比如物资审批、报账审批、资金审批等等…… 这里场景中,请求要发送给多个处理者,处理者要能够向后继续转发请求。存在着两种情况: 一方面,每一个处理者有自身能够处理的权限范围,超过权限范围的请求自身不处理,而是将请求转交给上级来处理,如果处理了则终止; 另一方面,每一个处理者可以部分处理请求,处理后可以再将请求发送给上级,也可以决定是否终止。 这样,可能多个处理者处理同一个请求,也有可能只有一个处理者能够处理请求,如下图所示: 多个处理者连成了一条链,请求沿着这条链可以向后传递,这里就会用到今天要讲的职责链模式。我么以请假流程为例,看看使用职责链模式前后的变化,感受职责链模式带来的好处。 2. 请假流程初步 根据前边的场景描述:3天以内的请假申请组长可以直接审批,而4到7天的请假申请必须要让部门经理来审批了,超过7天的请假申请只能由公司总经理来审批。具备面向对象思维的我们不难写出如下的代码: 1、定义请假请求 请假请求类 class LeaveRequest { private final int days; (1) private final String name; (2) public LeaveRequest(int days, String name) { this.days = days; this.name = name; } // …… 省略getter和toString } 1 请假的天数,它来决定谁可以审批这个请假请求 2 请假人的名称 2、既然请假要求领导审批,我们来抽象一个领导的接口 抽象的领导接口,默认实现了请假审批逻辑 interface Leader { default void handle(LeaveRequest request) { (1) Random random = new Random(47); if (canHandle(request)) { (2) if (random.nextBoolean()) { System.out.println(this.getClass() + " [通过] 了请假请求: " + request); } else { System.out.println(this.getClass() + " [拒绝] 了请假请求: " + request); } return; } throw new RuntimeException(this.getClass() + " 不能处理请假请求: " + request); } boolean canHandle(LeaveRequest request); (3) } ...

2022-01-17 · 3 min · 613 words · Hank

Java设计模式(23)-备忘录模式

中国有句古话:"千金难买后悔药"。生活中很多时候,我们做过了的事情,虽然后悔,却无济于事。但是,在软件世界,我们却可以自己制作"后悔药"。比如,以前玩"仙剑",每每遇到Boss战,那必须要存档的,就算没打过也可以恢复存档再来一次,否则,玩过的人都知道,哭去吧……这里的游戏存档,就会用到今天说的——备忘录模式。 其实还有很多这种例子,比如Word等办公软件的撤销操作,撤销后需要恢复到前一状态;又比如,虚拟机或者今天的云服务器都支持创建快照,如果系统出问题了,可以直接恢复到快照版本…… 1. 概念 备忘录,顾名思义用来做备份的,后续可能按照备份数据进行恢复。DP 对备忘录模式的定义如下: 定义 备忘录模式(Memento Pattern):在 不破坏封装性 的前提下,捕获一个对象的内部状态,将在 该对象之外 保存这个状态。这样以后就可将该对象恢复到原先保存的状态。 这个定义比较好理解,其基本思想就是要将对象的内部状态保存下来,便于之后恢复,但是保存的逻辑不由对象本身负责,而是单独抽出取来,减轻对象的职责,同时也便于扩展。这个思想与前边的 迭代器模式] 非常类似。 需要保存内部状态的这个对象,我们称为 原发器 (Originator),抽取出来的保存原发器状态的对象我们称为 备忘录 (Memento),我们来看看它们之间的关系。 2. 结构 备忘录模式的结构如下: 可以看到,备忘录模式中有三种角色: Originator: 即原发器,持有内部状态,提供创建备忘录的方法,以保存某个时刻内部的全部或部分状态,同时还提供还原的方法,从而支持后续可以还原到保存的状态 Memento: 备忘录对象,它存储 Originator 的状态,但要求它能够防止除了 Originator 之外的对象访问备忘录 Caretaker: 管理者,它负责管理备忘录,包括存储、删除操作,但是要求它本身不能访问和修改备忘录 备忘录的宽窄接口 备忘录对象它要求能够防止除了 Originator 之外的对象访问备忘录,而管理者又不能访问和修改备忘录,因此,要求备忘录能够具有 宽窄两种接口,Originator 能够通过 宽接口 创建、访问、修改备忘录对象,而管理者只能使用 窄接口 存储、删除和允许他人查询备忘录,本身不能修改和访问备忘录。 备忘录模式具有如下的优缺点: 抽取了备忘录对象,用来保存状态,减轻了原发器的职责 备忘录和原发器都可以再次抽象出单独的接口,便于扩展 备忘录会创建多个状态的副本,可能造成很大的开销 管理者管理多个备忘录,但它并不知道备忘录的内部状态,一个小的管理者可能存储和删除很大的备忘录,带来大的开销 备忘录模式适用于以下场景: 对象需要保存自身某一时刻的状态,以便后续进行恢复 对象保存自身状态时不暴露其实现细节,需要保持其封装性不被破坏 3. 实现 备忘录模式的实现大概有三种方式,每一种都有其适用场景。 3.1. 标准实现 标准实现方式,它抽取备忘录为单独的对象,由管理者来存储。我们以仙剑游戏为例,看看示例代码实现,如下: 1、定义备忘录对象 public class Memento { private State state; public Memento(State state) { (1) this.state = state; } public State getState() { return state; } public void setState(State state) { this.state = state; } } ...

2022-01-12 · 3 min · 614 words · Hank

Java设计模式(22)-迭代器模式

生活中,存在这样的场景:需要逐个遍历一堆对象,然后判断对象是否符合要求,做出相应的处理。例如,乘火车时,检票员站在门口挨个检票,没有买票的人不能乘车。类似的场景还有很多,比如乘地铁、乘公交、从书架上找书、食堂排队打饭等等…… 我们把需要逐个遍历的这一堆对象称为 对象集合,把挨个遍历的过程称为 迭代。迭代时,如果能将迭代过程从对象集合中抽取出来单独实现,让对象集合只负责管理自身状态,而不用负担迭代的任务,这就减轻了对象集合的职责,这就是今天要说的——迭代器模式。 也许你会想,对象集合不就是 List 吗,直接使用 "增强for循环" 遍历不就完成迭代过程了吗?说的没错!迭代器模式在很多面向对象的语言中都已经内置了,比如 Iterator,所以我们大多数情况下不会再自己实现它,但是学习模式本身的原理还是非常有必要。 1. 概念 DP对迭代器模式的定义如下:迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。 这个定义还是比较抽象,简单而言:迭代器模式,提倡将聚合对象(就是前边提的 "对象集合")的迭代逻辑抽取成一个 迭代器,聚合对象本身只需要提供一个返回迭代器的方法即可,而不需要关注迭代逻辑。客户端拿到对象集合的迭代器,就可以遍历了。 这种由客户端来决定如何遍历的方式,被称为 外部(主动)迭代器;还有一种迭代方式,客户端调用一个方法告诉聚合对象它的每一个元素该如何处理,然后由聚合对象内部负责迭代,这被称为 内部(被动)迭代器。Java 两种都支持,比如外部用增强for循环遍历 List,就是外部迭代器,而调用 List 的 forEach(Consumer<? super T> action) 方法,或者调用 Iterator 的 forEachRemaining(Consumer<? super E> action) 方法就属于内部迭代器。 聚合对象中的元素并不一定是有序存储的,比如 Set。此外,迭代器除了从头开始遍历聚合对象,还可以实现从尾部开始遍历,这取决于具体实现。比如,Java 提供了 ListIterator 支持双向迭代,它继承自 Iterator。 2. 结构 迭代器模式的结构如下图所示: 这里我使用的Java的泛型方式来定义的类,标准的迭代器模式显然没有泛型。从图可知,迭代器模式一共分为四个角色: Aggregate<T>: 抽象聚合对象,定义了通用的接口方法,通常是一个对象集合,支持存储多个对象,并支持创建一个迭代器。如 Java的 List、Set、Coleection 等接口 Iterator<T>: 抽象迭代器,定义了迭代的接口方法,如 next 迭代下一个对象,hasNext() 返回是否还有下一个对象等等 ConcreteIterator<T>: 具体迭代器实现,一个 Aggregate 可能存在多个迭代器实现 ConcreteAggregate<T>: 具体聚合对象,如 Java中的 ArrayList,HashSet 等等 ...

2022-01-11 · 3 min · 571 words · Hank

Java设计模式(21)-中介者模式

如果您有租房的经历,那么您对中介并不陌生。租房时,我们先去房屋租赁中介登记,告诉它您的租房需求,然后中介会按照您的要求为您筛选适合的房子。房子确定后,您会与中介签订租赁合同,缴纳费用,然后拿到钥匙……整个过程中,所有事项都由中介一手包办,入住以后房屋有任何问题,您都直接去找中介,您甚至可能并不知道房东是谁。这就是我们今天要说的模式——中介者模式。 中介者模式来源于生活中的各个中介机构,不如前边提到的房产中介、贷款中介、票务中介等等。 1. 概念 DP 对中介者模式的定义是这样的:用一个 中介对象 来封装一系列的对象交互,各个对象 不显式地相互引用,从而使其 松散耦合,而且可以独立的改变它们之间的交互。这完全符合 迪米特法则。 简而言之,中介者模式将多个相互引用的对象解耦,使得它们不直接交互,而是"有事找中介"。 除了生活中的中介机构外,中介者模式在软件中应用也很多,比如,服务网关,各个服务不直接通信,而是都通过网关来互相调用;又比如,MVC 模式中,service 层就是使用典型的中介者模式,因为 service 层依赖了多个 dao 层的类,而 dao 层的类之间并不直接交互…… 2. 结构 中介者模式的类图如下: 存在4个角色: Mediator: 抽象中介者,定义了通用接口让同事类之间进行通信 ConcreteMediator: 具体中介者,实现 Mediator 的具体业务来支持同事类之间的通信 Colleague: 抽象同事类,定义同事类通用接口,内部聚合 Mediator ,通过它与其他同事类交互 ConcreteColleague: 具体同事类,调用 Mediator 实现与其他具体同事类的通信 在中介者模式中,同事类之间互不认识,但是他们都认识中介者,就好比租房时您不认识房东,但是房东和您都认识中介,通过中介来完成通信,因此,中介者是作为一个中心化的角色存在。 优点: 同事类之间完全结构,符合 迪米特法则] 同事类可以扩展和复用,并且减少了它们的子类数量 简化了对象模型,由多对多关系变成了一对多关系 中介者决定同事类之间如何交互,而抽象中介者对这种交互进行了抽象,便于扩展 缺点: 同事类之间的交互完全由中介者来控制,中介者复杂度增加 中心化的中介者出现问题,则同事类之间无法交互,即存在单点问题 3. 适用场景 中介者模式适用于以下场景: 多个对象间存在复杂的依赖关系,结构混乱且难以理解 一个对象引用其他多个对象,并直接与它们通信,导致难以复用该对象 一个对象被多个对象引用,难以扩展,又不想为其生成子类 4. 与其他模式的区别 与观察者模式 上一篇 讲了观察者模式,与中介者模式非常类似。 结构类似,都定义了类似的4个角色,两个抽象角色和两个具体角色 两者实现的功能不同,观察者模式适用于一个对象改变而其他对象也需要做出改变的场景,而中介者模式重点在于解耦对象间多对多的复杂通信关系 中介者模式可以与观察者模式组合使用,如:中介者也作为通知发送者 与外观模式 中介者模式与 外观模式 也类似: ...

2022-01-08 · 2 min · 303 words · Hank

Java设计模式(20)-观察者模式

很快又到年底了,一年一度的春节除了可以享受愉快地享受假期之外,每年的"春节联欢晚会"也是让人倍感期待。然而,大多数人都没能坐在电视机跟前等着它的开始,有的人在厨房忙着准备丰富的食物,有的在一起打麻将、玩牌,还有的可能在玩电脑游戏……大家都想看晚会,于是派出一个小朋友,告诉他:"如果晚会开始了,你要立刻来通知我们哦!"虽然,大家就可以继续做自己的事情,小朋友就坐在电视跟前,当晚会开始的时候,他就立即跑去挨个通知大家…… 观察者模式示意(图片来源网络) 模式源于生活,上边的这个场景,就是一个典型的观察者模式的例子。 1. 概念 先来看看观察者模式的定义。 DP 对观察者模式的定义 观察者模式(Observer),又叫发布-订阅(publish-subscribe)模式,定义了对象间的一种一对多的依赖关系,当一个对象发生改变时,所有依赖它的对象都得到通知并被自动更新。 生活中,有很多这样的一个对象依赖其他多个对象(一对多关系)的例子,该对象状态发生变化了,其他关联的对象都需要知道并且做出行动。如上边的小孩儿看到晚会开始了,要立即通知其他人,其他人可以选择去看春晚,也可以选择继续做自己的事情。又比如,路口红绿灯变绿时,意思是告诉大家可以通行了;微信群有人发消息了,群成员都可以看到这条消息,等等…… 在软件领域,观察者模式的运用非常多,如:消息队列(MQ) 可以视为观察者模式实现;又比如,响应式编程 的核心就是观察者模式。 2. 结构 观察者模式类图如下: 可以看到,观察者模式有4个角色: Subject: 抽象主题对象,又叫做被观察者(Observable),或者目标对象,定义了添加、删除和通知观察者的方法,知道注册的观察者列表 ConcreteSubject: 具体主题对象,实现 Subject,有自身的状态,状态变化后向各个 Observer 发出通知 Observer: 抽象观察者,定义观察者的通用接口,包含一个用于更新的 update 方法 ConcreteObserver: 具体观察者,实现 Observer,依赖 ConcreteSubject ,当 ConcreteSubject 变化时得到通知,自身也可以更改 ConcreteSubject 的状态,此时可以通知其他观察者更新 上边的几个角色,将抽象和实现相分离,主题对象和观察者都可以各自进行扩展。另外,并不总是主题对象调用 notify 方法去通知观察者们,观察者也可以调用它从而通知其他观察者。 优点: 观察者模式降低了对象间的耦合性,提高了复用性,符合依赖倒置原则 主题对象广播通知给所有观察者,并不关心具体的观察者是谁,易于添加和删除观察者,而具体的更新与否以及如何更新由观察者自身实现 缺点: 观察者自身并不知道其他观察者的存在,它对更改主题状态的代价一无所知,因此需要定义和维护依赖准则,否则可能引起错误更新 主题和观察者之间仍然存在耦合性(没有完全解耦),存在相互依赖关系甚至可能造成循环依赖 3. 适用场景 观察者模式的使用场景: 对象间存在一对多的依赖关系,双方都需要独立的扩展和复用 一个对象的改变会同时改变其他依赖对象 一个对象必须通知其他对象,但是并不知道其他对象具体是谁 4. 示例 接下来看看观察者模式的示例代码(注意,下边的代码没有考虑并发安全性)。 1、定义观察者 public interface Observer { void update(); (1) } ...

2022-01-06 · 3 min · 489 words · Hank

2021过去了,我都干了些啥?

2021年又过去了,不得不让人感叹岁月如梭、光阴似箭!正值元旦假期,刚好有时间梳理一下这过去一年的种种,思来想去,还是不过"平凡"二字罢了。 1. 去年计划完成情况 每年的年底,我都会做一下年终总结,也定制一下新的一年的计划。现在回过头去看,发现去年的计划完成度也只有40%,很多事情还是没有能够完成。一方面,计划可能定得过高,完成起来有一定的难度;另一方面,自身的自制力还有待提高,很多事情没有能够坚持去完成;再者,诸如读书这样的事情,如果阅读了但是没有去理解、梳理和总结,还是很难形成系统的经久不忘的知识。 先从工作和生活上来聊聊计划之内的事情,然后,作为一个技术人,着重从技术和个人能力修炼上自我剖析一下去年一年所完成的技能。 1.1. 生活篇 从2021年开始,我计划每年带父母、老婆和孩子们出去旅游至少一次,2021年完成了这一目标,虽然走的不远,但是回想起来还是满满的幸福感。 作为技术人,平时难免加班加点的工作,陪伴家人的时间很少。所以,一家人出去旅游一下也是一种不错的选择。我们在端午节花了几天时间去了峨眉山、乐山大佛,本想着错开人流高峰时间,结果没想到去乐山大佛的时候还是遇到了人流洪峰,整整排队3个小时才看到了大佛😅。两天下来,从攀登峨眉山的兴奋,到看到金顶的激动,到漫长排队看大佛的百无聊赖,到看到大佛的肃然起敬,再到后来的精疲力尽,真的是波澜起伏、人间百味!玩了两天下来,我们都已经疲惫不堪,不过远离尘世的喧嚣,静下心来仔细品味一下生活,享受人生中的酸甜苦辣,何尝不是一件幸福的事情呢? Figure 1. 峨眉山金顶(图片来源网络) 生活习惯上,仍然没有太大的变化。虽然计划着要改变自己晚睡的习惯,然后还是没有做到。明明知道熬夜带来的危害,但还是深夜十二点还在电脑上敲敲打打的写着代码……今年,希望自己能够克服这个毛病,养成早睡早起的习惯,做到严格自律,以免将来后悔。正如《自律的人生更自由》里说的:与其躺到病床上后悔,不如现在就做出改变。 1.2. 工作篇 年初时,老总制定了明确的目标,每个阶段完成的任务都列的清清楚楚,可是完成情况却很糟糕,主要是酒店行业的产品在疫情影响下推动起来相当困难。比如,我们推出的一套酒店服务人员综合素质能力培养的产品,本来预计下半年应该会有比较上规模的用户使用,可惜在疫情影响下,酒店经营者自身尚且难保,更别说在员工的软硬技能上再下功夫了。这一点在2020年全年已经体现的非常突出了,可以说新冠疫情对酒店行业的冲击是常人难以想象的,虽然2021年酒店业有一些复苏,但是同2020年比还是增长不大,而且实现营收增长的多为企业级连锁酒店。 在公司待了5年了,目前而言公司的业务线还没有真正地走上正轨,前期的产品铺垫几乎没有收到任何效果,尤其是在这几年疫情的影响下,每迈出一步都显得那么沉重。今年,我们着重再打造酒店人员实习平台,解决职业院校学生实习管理难、数字化程度低、信息化建设落后等问题,尽管现在产品已经足够完备,但是缺少验证她的用户群,22年的工作重心必定在产品的推广和优化上。希望新的一年,公司能够顺利推动业务,打磨出更高效、更易用的产品。 1.3. 技术篇 学习的技术,最好的入口是官方文档,大多的技术官网都会有n分钟guide,可以教你一步步由浅入深入的学习。当然,前提是英语必须要好,否则阅读起来非常吃力,而且会影响我们的阅读的积极性。当然,也可以借助 Chrome 的翻译工具来阅读,不过这种机器翻译准确性很难保证,所以我经常是先阅读英文,不懂的地方或者不重要的地方在通过翻译工具去阅读。技术的学习离不开实践,如果只阅读不实践,最后读过的东西也成了过眼云烟。 去年,我学习了这些技术,很多技术都是经常使用,但是没有系统地梳理它们,所以花了一些时间专门去进行系统性的学习。现在看来,在技术上花费的时间还是太少,今年需要付出更多的时间来继续钻研技术。 Spring Cloud 去年,我将 Spring Cloud 从原来的 Edgware 版本升级到了 Hoxton 版本,将 Spring Cloud 的很多组件都换成了 Spring Cloud Alibaba 的,比如注册中心从 Eureka 换成了 Nacos。升级过程中,还是遇到了很多问题,最后都一一解决了。一个最大的问题是网关,原来的是 Zuul 1.x 版本,后来换成了 Spring Cloud Gateway,但是后者是基于 WebFlux 的,这是一个实现了响应式标准 Reactive Streams 的异步响应式编程框架,底层默认使用 reactor 框架,这对于我们团队而言是一个全新的技术,我们不得不下功夫去学习。 WebFlux 与响应式编程 响应式编程是一门较新的技术,目前的响应式框架,除了较早的 RxJava 和 Vert.x 之外,还包括专用于开发云原生应用的 Quarkus,以及 Spring 家族的 WebFlux。 在项目中,使用 Spring Cloud Gateway,必须先去学习 WebFlux 和响应式编程,目前来说,对其掌握还不够系统和全面,所以今年的目标是要系统的去学习 WebFlux 和响应式编程。 ...

2022-01-03 · 1 min · 173 words · Hank

什么是时序攻击Timing Attack

最近在维护一个老项目的时候,看到一段密码匹配的代码,感觉很奇怪,于是遍寻资料,最终还是很有收获。 1. 密码匹配 事情是这样的,一个项目遇到输入的密码总是提示密码错误的问题,于是debug跟踪了一下代码,发现该项目使用了 Spring Security 3.2.2 的 PasswordEncoder 来做密码MD5加密和密码匹配验证,PasswordEncoder 只是一个接口,其实现使用的是 StandardPasswordEncoder 类,它的密码匹配代码引起了我的注意: public boolean matches(CharSequence rawPassword, String encodedPassword) { (1) byte[] digested = decode(encodedPassword); byte[] salt = subArray(digested, 0, saltGenerator.getKeyLength()); return matches(digested, digest(rawPassword, salt)); } private boolean matches(byte[] expected, byte[] actual) { (2) if (expected.length != actual.length) { return false; } int result = 0; for (int i = 0; i < expected.length; i++) { result |= expected[i] ^ actual[i]; (3) } return result == 0; } ...

2021-12-30 · 2 min · 293 words · Hank

Java反射之AnnotatedType接口

上一篇Java反射之获取注解的AnnotatedElement接口介绍了 AnnotatedElement 接口,它是用反射获取注解的顶级接口。其实现类或子接口包括 Class、TypeVariable、Parameter、Package、AccessibleObject、GenericDeclaration 等,这些在前边的文章中已经介绍过。另外,它还有一个重要的接口 AnnotatedType,本篇将介绍它。 1. 再说注解 JDK1.5推出了注解功能,给Java语言带来非常强大的诸多特性。Java注解有几种保留策略:源码、运行时、类,可以在创建注解时通过 @Retention 元注解来指定,它需要引用 RetentionPolicy 中定义的策略: SOURCE: 注解只在源码层面保留,编译后被丢弃 CLASS: 默认的保留策略,注解将由编译器记录在类文件中,但在运行时不需要由 JVM 保留 RUNTIME: 注解不仅保留在类文件,在运行时 JVM 也会保留它,因此可以通过反射获取注解 注解的作用大概可以分为以下几种: 提供编译期特性:Java提供的一些注解,可以进行编译期特性支持,比如 @SuppressWarnings, @Deprecated 和 @Override 等可以提供编译期检查,@FunctionalInterface 标注函数式接口等 生成帮助文档:Java的 @Documented 注解可以用来标记生成 javadoc 时保留类或接口上的注解 运行时获取注解实现特定逻辑:这个是使用最多的场景,使用注解,并通过反射获取注解,来实现自身业务逻辑,大多数框架都使用注解来实现高级特性,比如 Spring 的 @Bean、 @Service 等注解 一句话,有了注解,我们可以方便的按需实现自身业务逻辑。在JDK1.8之前呢,注解所能表示标注的位置有限,JDK1.8又新增了两种标注位置,支持将注解标注在任何类型上。 2. 再说注解标注的位置 还记得 上一篇 介绍注解标注的位置信息吗?文中提到,通过 ElementType 枚举来定义注解可标注的位置信息,其他包括下边两项: ElementType.TYPE_PARAMETER: 标注在类型参数上,比如泛型参数T、E上 ElementType.TYPE_USE: 使用类型,注解可以标注在任何类型变量上 它们都是在JDK1.8时新增的,TYPE_PARAMETER 主要目的在于支持将注解标注在泛型类型的参数上,而 TYPE_USE 范围更广,可以将注解标注在任何Java类型上。也就是说,JDK1.8之前,注解只能标注在特定的位置(类、方法、包、参数、域、构造器、本地变量、注解),不能标注在泛型参数上,JDK1.8之后,可以标注在任何类型上。 使用TYPE_USE就可以省略其他 ElementType 元素吗? 既然 TYPE_USE 可以表示标注在所有类型上,那么是不是就不需要其他 ElementType 元素了呢?肯定不是!TYPE_USE 有其特殊的目的,它表示 “类型使用” 类型,其实就是指任何Java类型,但是它只能用 AnnotatedType 来描述并获取注解信息(见这里)。 ...

2021-11-25 · 5 min · 1042 words · Hank

Java反射之获取注解的AnnotatedElement接口

在Java反射的体系结构一文中说过,AnnotatedElement 接口定义了通过反射获取注解信息的方法,它是一个顶层接口,能够被标记注解的反射类都实现了该接口从而得到反射获取注解的能力,比如 Class、Field、Constructor、Method、Parameter 都实现了该接口。本文将详细介绍 AnnotatedElement。 1. 再说注解 注解是JDK1.5推出的新特性,用于标注Java中的特定元素,以便通过反射来获取它们,并实现不同的功能。本文不会讨论注解的基本知识,有兴趣的可以自行查阅相关资料。但是注解有一些特性需要细说一番。 1.1. 注解标注的位置 Java中,注解可以标注在多个位置上,常见的包括类、接口上、方法(构造函数)、方法参数(构造函数参数)、成员域上以及标注在注解上(注解的注解),甚至可以将注解标注在 package-info.java 文件中定义的包上,这些位置是在定义注解时通过元注解 @Target 来指定的,比如向下边这样: @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PACKAGE, ElementType.TYPE, ElementType.FIELD}) public @interface CustomAnno { String value() default ""; } 可以看到,通过 ElementType 来指定注解可以标注的位置,它定义了以下几种位置: ElementType.TYPE: 标注在类型上,包括接口、类、枚举等 ElementType.FIELD: 标注在类、接口的成员域上,包括枚举的元素 ElementType.CONSTRUCTOR: 标注在构造器上 ElementType.ANNOTATION_TYPE: 标注在其他注解上 ElementType.METHOD: 标注在方法上 ElementType.PARAMETER: 标注在方法参数上 ElementType.LOCAL_VARIABLE: 标注本地变量上,即构造器、静态代码块、方法等的内部变量 ElementType.PACKAGE: 标注在 packag-info.java 文件中定义的包上 ElementType.TYPE_PARAMETER: 标注在类型参数上,比如泛型参数T、E上 ElementType.TYPE_USE: 使用类型,注解可以标注在任何类型变量上 上边的最后两个是在JDK1.8新增的,用于在注解标注在泛型上的情况,涉及到 AnnotatedType 接口,我们将在下一篇专门行文来介绍它。 注解用于包上的例子: package-info.java @CustomAnno("package") package com.belonk.lang.reflect; 其他标注位置比较常见,就不在赘述了。 1.2. 注解的继承 Java中定义注解时,可以指定其是否可以被继承。注解继承指的是父类标注的注解可以作用于子类,而不是说一个注解继承另一个注解。比如,自定义如下注解: @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited (1) @interface MyAnno { String value() default ""; } ...

2021-11-21 · 3 min · 569 words · Hank

Java反射之获取泛型类定义的具体类型

通过上一篇Java反射之表示所有类型的Type接口一文,我们知道 Type 接口及其五大组件是用来描述Java的各种类型,主要包括原始类型和泛型类型。很多时候,我们需要获取到泛型类上定义的具体类型,从而完成一些业务逻辑。比如,最常见的情景就是JSON的反序列化,需要将JSON字符串反序列化为泛型的具体类型,比如反序列化为 List<User>,这就要求每一个 List 中的元素都是 User 对象。那么,如何获取到泛型类上定义的具体类型呢?这就是本文要阐述的内容。 1. 通过Class能获取泛型类型吗? 在/2021/11/09/reflect-class.html中说过,Class 类上有一个 getTypeParameters() 方法用来获取类上的类型变量。通过它能不能获取到泛型类型呢?看下边的示例: class Parent<T> { (1) } 1 定义一个泛型类 这里定义一个泛型类,现在尝试通过它的实例对象来获取泛型类型,代码如下: public void printParent() { Parent<String> parent = new Parent<>(); (1) TypeVariable<? extends Class<? extends Parent>>[] parentTypeParameters = parent.getClass().getTypeParameters(); (2) System.out.println(Arrays.toString(parentTypeParameters)); } 1 创建 Parent 类的实例 2 获取类型变量 上边的示例并不能拿到实例上的 String 类型,而只能拿到 Parent 类上定义的泛型类型 T,因此,输出结果为: [T] 很显然,这个结果不是我们想要的,我们想要获取到 String 这个类。在 Java反射之创建泛型数组 一文中说过,泛型是编译期特性,在运行时泛型会被擦除。因此,我们想通过`Class` 在运行时用反射直接去获取泛型类型,看起来是不行的。 ...

2021-11-15 · 3 min · 495 words · Hank

Java反射之表示所有类型的Type接口

前边说过,JDK1.5推出了反射功能,并介绍了反射的类设计结构,其中提到了 Type 接口,它是JDK1.5新增的接口,表示Java中的所有类型。本文介绍这个接口及其五大组件。 Figure 1. Type及其组件 1. Type的五大组件概览 Type 接口表示Java中的类型,这些类型包括原始类型、参数化类型、数组类型、类型变量和基本类型,前边介绍的 Class 就是其组件之一。Type 及其组件用于通过反射来描述Java各种类型,比如描述泛型的定义,获取泛型参数个数、类型、泛型通配符的上下边界等等。一个典型的应用场景是获取真实的泛型类型,我们将在后续文章单独介绍。 Type 用其五大组件来描述上边提到这些类型,类图如下: Figure 2. Type类图 ParameterizedType: 表示参数化类型,即整个泛型的定义,如 List<String> 可以用 ParameterizedType 来描述; TypeVariable: 表示类型变量,即泛型申明中的具体类型,比如: List<E> 用参数化类型来描述,而其中的 E 就用 TypeVariable 来描述; WildcardType: 表示泛型通配符类型,即泛型申明中带 ? 通配符,比如:List<? extends Number> 中 ? extends Number 就用 WildcardType 来描述; GenericArrayType: 表示泛型数组,比如申明一个泛型数组的域 T[] array,它就用 GenericArrayType 来描述; Class: 表示运行时的类或接口,在前边Java反射的体系结构一文中已经介绍过了,不再赘述。 一句话总结,Type 的 Class 组件用来描述Java类或接口等原始类型和基本类型,其他几个组件描述泛型类型,包括泛型的变量、通配符以及泛型数组。 现在,来看看这几个组件的用法。 2. 参数化类型ParameterizedType 什么是参数化类型?其实就是指泛型,具体而言,JDK5推出泛型之后,原来的具体的类型可以申明为泛型,变得"可被参数化"了,比如:List 是一个具体类型,但是可以申明为 List<String>、List<Integer> ,就仿佛可以申明不同的参数,类似方法可以申明不同的参数一样(其实泛型是编译期特性,它两实际上是同一类型,都是 List,这在 Java反射之创建泛型数组 已经说过了)。 所以,ParameterizedType 用来描述泛型类型,比如:List<T>、List<String>、User<T extends Number>、List<? super User> 都可以用它来描述。ParameterizedType 的定义如下: ...

2021-11-14 · 4 min · 804 words · Hank

Java反射之创建泛型数组

前边介绍了Java泛型类设计体系中的一些常用类,本篇将着重介绍 Array 类和如何创建泛型数组。 1. Java能创建泛型数组吗? 1.1. 泛型类型擦除 首先,我们知道,泛型是JDK1.5推出的编译期特性,什么是编译期?即是说在编译的时候有效,编译代码时编译器能够对泛型进行检查和校验。但是如果在运行时,泛型其实已经无效了,泛型类型被擦除了。比如下面的代码: 代码清单1 List<String> stringList = new ArrayList<>(); List<Integer> integerList = new ArrayList<>(); System.out.println(stringList.getClass() == integerList.getClass()); 结果输出为 true。stringList 只能添加 String 类型的数据,而 integerList 也只能添加 Integer,不能加入别的类型的数据。为什么?因为它们申明了泛型类型,编译器就会对类型兼容性进行检查,类型不符则会抛出异常。但是,他们的 Class 确是相同的,即是说,在运行时它们是相同的类型,都是 ArrayList,而泛型类型被擦除了。 1.2. 尝试创建泛型数组 那么,我们申明泛型数组会怎么样呢?看下边的代码: 代码清单2 class GenericArray1<T> { private final T[] array; (1) private int index; public GenericArray1(int size) { this.array = (T[]) new Object[size]; (2) } public void add(T item) { (3) array[index++] = item; } public T[] array() { return array; } } ...

2021-11-10 · 3 min · 469 words · Hank

Java反射的体系结构

反射是JDK5推出的非常重要的特性,通过反射,开发者可以在运行时动态的创建类实例,获取、更改、调用类的相关信息、方法等。反射也是各大框架使用的主要技术,如知名的Spring framework框架。 本文将介绍反射的类设计体系,并配备一些简单示例来介绍反射常用API的使用,本文示例都是采用JDK1.8编写。 1. 反射类设计 Java专门为反射设计了名为java.lang.reflect包,官方API文档在这里: 反射官方文档,该包提供了一系列类共同组成反射体系。 1.1. 反射类概览 既然反射是在运行时动态描述类信息的,那么描述类信息到底是些什么东西?类信息除了类、接口、数组、枚举等常见类型的基本信息外,还包括类型中的域、方法、构造器等以及它们的修饰符(public、static、volatile、private、final、native、synchronized 等),另外,还包括方法参数和注解。反射类如下表所示: Table 1. 反射中的类 类 描述 Class 位于java.lang包,描述类、接口的运行时状态,枚举、数组也是一种类,而注解是一种特殊的接口。每一个类型都有一个Class对象与之对应 Field 描述类型中的域信息 Constructor 描述类型中的构造函数信息 Method 类型中的方法信息描述 Parameter JDK8新增,提供有关方法参数的信息,包括其名称和修饰符。它还提供了一种获取参数属性的替代方法。 Modifier 类、域、方法、构造函数等的访问修饰信息工具类 AnnotatedElement 反射获取注解的顶层接口 Type Java中的所有类型的顶层接口 Array 用于动态创建和处理数组 Proxy 动态代理的支持类 这里暂时介绍基础的Class、Field、Constructor、Method、Parameter、Modifier,其他几个类会在后续文章中单独介绍。现在,我们来看看类图的设计。 1.2. 反射类图 整个反射的类设计见下图: Figure 1. 反射类图 图中只列出了比较基础且重要的类,从图中可以看到,反射的类结构并不算复杂,顶层主要包括几个重要接口和类: AnnotatedElement: 定义了反射获取java注解的方法,如 getAnnotations() 等,实现该接口的类意味着可以获取注解信息; Member: 定义获取类成员(域、构造函数、方法)相关的标识符信息的接口,比如获取名称、标识符等; Type: 所有类型的顶层接口,用来描述java中的类型,比如泛型类型、类字节码等,包含五大组件,其中包括最重要和常见的 Class 对象,后续文章将单独介绍这个接口和其组件; GenericDeclaration: 表示泛型申明的接口,继承自 AnnotatedElement,内部定义了 getTypeParameters() 方法,用于获取类型变量(TypeVariable,该接口为 Type 接口的一个子组件); AccessibleObject: 表示可访问对象,Field、Method 和 Constructor 对象的基类,用来检查和修改它们的访问权限:公共、包、私有访问,比如:调用其 setAccessible(boolean) 方法修改域的访问权限,然后才能修改域的值; Executable: 表示可执行的对象,其实就是方法和构造函数的基类; ...

2021-11-09 · 5 min · 869 words · Hank

MindManager windows 2020中文版本下载,带正版激活码

MindManger是Mindjet公司开发一款思维导图软件,目前已经更新到了2021版本。与其他几大思维导图软件相比,个人最钟爱MindManger,因为他功能更强大、界面更友好,支持强大的备注体系,插图片、查表格都可以,可以直接用来做知识体系整理,非常方便。 这里介绍的是2020版本,提供激活码,能够正常激活使用。 官网地址: https://www.mindjet.com/ 下载地址 由于版权限制,请关注公众号并在后台回复6001获取下载地址! 如果链接失效,请在公众号后台留言,我会第一时间补上。 软件截图: 扫码关注公众号,查看更多推荐资源! 下载声明 本站所有下载资源均来自互联网,由站长搜集整理,版本归原作者所有,如有侵权请联系站长删除。 本站所有下载资源仅供个人学习和研究使用,请勿用于商业用途,请在下载后24小时内删除。

2021-07-14 · 1 min · 12 words · Hank

Java设计模式(19)-状态模式

(图片来源网络) 生活中,存在很多与"状态"相关的案例,比如:昨天晚上你熬夜看球赛,上午状态很差,迷迷糊糊的,熬到中午睡了一觉,下午又变得精神百倍了。在不同的时间,所处的状态可能不一样,而且还会按照一定条件流转,比如从犯困的状态变成了精神百倍的状态。这种在不同时间、不同条件下状态产生变化的对象,我们称之为"有状态"对象,状态作为其属性会产生变化。 在软件开发过程中,这种状态转换的场景非常多。比如,系统订单随着时间的推移,状态会产生转换,可能从下单后的待支付转换到支付后的待发货,也可能在发货后从待收货变成已收货,等等。 1. 活动状态案例 假设有一个活动需求,管理员可以在系统中添加活动,让用户来参与,同时可以对活动进行管理,比如禁用启用活动、终止活动等。假设活动的状态有:正常、已开始、已结束、已禁用、已终止等,它们之间的流转过程如下图所示: Figure 1. 活动的状态变化 那么,如何实现活动的状态变化呢?传统的方式是将活动的状态定义为枚举类,然后在代码中进行if..else..或者switch的条件判断,通过编码切换到其他的状态,示例代码如下: 活动枚举类 enum ActivityStateEnum { NORMAL, STARTED, FINISHED, DISABLED, TERMINATED (1) } 1 通过枚举来定义活动的不同状态 通过条件判断来实现状态转换 class NormalActivityStateChange { public ActivityStateEnum change(ActivityStateEnum state) { ActivityStateEnum retState = null; switch (state) { case NORMAL: // 省略具体业务逻辑... retState = ActivityStateEnum.STARTED; (1) break; case STARTED: // 省略具体业务逻辑... retState = ActivityStateEnum.FINISHED; (2) break; case DISABLED: // 省略具体业务逻辑... retState = ActivityStateEnum.NORMAL; (3) case TERMINATED: // 省略具体业务逻辑... case FINISHED: // 省略具体业务逻辑... default: // 其他为最终状态,什么都不做 } return retState; } } ...

2021-06-22 · 3 min · 506 words · Hank

Expert One-on-One J2EE Development without EJB中文版PDF下载

你的J2EE项目是否耗费了你太多的时间?它们是否难以调试?它们是否效率不彰?也许你还在使用传统的J2EE方案,然而这种主案太过复杂,而且并非真正面向对象。这里的很多问题都与EJB有关:EJB是一种复杂的技术,但它没有兑现自己曾经的承诺。 1. 站长评价 这是一本很古老的书,同时也是一本介绍Spring框架设计理念、出现缘由的书,也是一本介绍软件设计的书! 在Java开发领域,Spring框架应该说是久负盛名,而且是Java Web开发必备武器。但是,随着Spring的发展壮大, Spring涵盖的东西也越来越多:Spring Boot、Spring Cloud等等以Spring框架(Spring Framework)为基础的Spring生态系统。 所以,对于初学Spring的人而言,可能不知道从何入手。所以,推荐先阅读这本书,本书的作者也是Spring框架的发起者。他在本书中详细介绍了Spring框架的出现原因、Spring框架如何解决一系列企业级问题,如控制反转(IoC)、事务控制、远程调用等等。同时,他还详细阐述了Spring框架架构设计理念和技术抉择,对比当时流行的企业级框架EJB的种种缺陷,一一介绍了Spring框架如何采用轻量级的方式来解决企业级J2EE问题。总之,是一本深入了解Spring框架的经典著作。不论你是初学Spring,还是对Spring已经了如指掌,都可以从本书中学到很多东西。 本书的前5章都在讲述EJB相关的内容,如果不想了解EJB可以直接从第六章开始阅读。 丰富指数:☆☆☆☆ 难度指数:☆☆☆☆ 推荐指数:☆☆☆☆ 迫不及待想要阅读它了?点此立即下载本书的PDF版本(仅供可以学习研究使用,切勿用于商业用途)! 2. 内容简介 在这本实战手册中,你将看到另一种截然不同的方案:没有EJB,却可以创建质量更高的应用程序,所需的时间和成本则更低。你将学会如何充分利用各种实用的技巧和工具,包括时下流行的Spring框架和Hibernate两个开源工具。你将看到如何高效地解决企业级应用的核心问题,例如事务管理、持久化、远程调用和web设计。你将了解这种新的方案给可测试性、性能和可伸缩性带来怎样的影响,并亲身体验轻量级架构如何大幅降低项目开发所需的时间和工作量。 自从servlet、EJB、JSP等J2EE技术发布之初,本书作者Rod Johnson就一直在使用这些技术,他对于这些技术的优劣利弊了如指掌。现在,通过这本书,你将可以面对面地分享他的专家经验。 你将从本书学到…… 如何针对自己的应用程序找到 简单、 易维护的架构;在不使用EJB的情况下有效地管理事务;如何利用AOP和loC解决企业级软件开发中的常见问题;web层设计,以web层在设计良好的J2EE应用中的地位;J2EE应用中的数据访问技术,包括BC、Hibernate和O;如何利用开源产品提升生产率、减少编码量;如何从设计层面上改善性能和可伸缩性。 “传统的J2EE设计思路尤其是EJB日益让架构师和开发者们灰心丧气,我这本书正是为这些人而写的。本书将告诉读者,如何从现在开始用更清晰、更高效的方案去替代EJB,并开始迈向web应用的新时代。” 3. 作者简介 Rod Johnson,Spring框架的创始人,同时也是SpringSource的联合创始人。Spring是面向切面编程(AOP)和控制反转(IoC)的容器框架。Rod的畅销书Expert One-on-One J2EE Design and Development(2002年出版)是迄今为止J2EE领域最具影响力的书之一。 4. 目录 第1章 为什么要“j2ee without ejb” 聚光灯下的ejb j2ee还剩什么? 站在十字路口的j2ee 前行的路 主旋律 轻量级框架和容器 我们还应该使用ejb吗? 小结 第2章 目标 生产率 问题 传统j2ee方案解决生产率问题的办法 提升生产率更好的办法 oo 业务需求的重要性 经验过程的重要性 小结 第3章 各种架构 架构性构件 .业务服务层 向外部暴露业务对象 数据访问层,或eis层 j2ee架构 两种ejb架构 两种非ejb架构 j2ee架构实例 “经典的”j2ee远程ejb架构 本地ejb架构 特制的非ejb架构 “轻量级容器架构”:示例应用系统 确定是否采用应用服务器 小结 ...

2021-06-15 · 3 min · 450 words · Hank

Java设计模式(18)-命令模式

(图片来源于网络) 试想一个去餐厅吃饭的场景,假设餐厅还没有智能化的系统,流程基本上是这样:客人坐下后,服务员会拿出菜单让客人点菜,每点一个菜品就在小本上记录下来,等客人点餐完毕后,服务员将所记录的客人已点菜品清单交给厨房,由厨师来负责烹饪。等待客人用菜完成后,服务员可以拿出记录的点菜清单,然后交给餐厅前台收银员收款结账。这整个过程中,有这么几个角色:客人、服务员、厨师、收银员,现在我们用代码来描述这个场景,怎么来设计呢? 1. 传统的点菜系统设计 传统的方式来设计,我们可能会很容易想到如下的类图关系: Figure 1. 传统方式设计的点菜系统类图 客人直接依赖服务员,使用其点菜服务来点菜,而服务员直接依赖厨师类,通知其完成做菜功能。客人用餐完成后,可以去前台结账。服务员所记录的点菜清单很好的为厨师和收银人员提供了依据:厨师可以借助点菜清单知道自己需要做什么菜,收银人员可以借助点菜清单来计算收款数目。 整个设计初看没有问题,各个功能都实现并且运行的很好。但是,如果某些客人点完菜过后发现,自己点的菜品太多了,可能吃不完而造成粮食的浪费。我们需要系统支持客人取消点菜的功能,一旦客人发现菜品过剩,就可以申请取消,以发扬勤俭节约的美德。 好吧,我们只需要在服务员类上添加"取消点菜"的功能,当服务员收到取消点菜的请求时,他可以在点菜清单上将取消的菜品划掉,然后再交给厨师一个新的点菜清单。此时的类图看起来像这样: Figure 2. 添加了取消点菜功能的类图设计 虽然功能实现了,但是客人、服务员和厨师三者的关系是紧耦合的,随着客人需求的更改,服务员和厨师都需要更改。如果厨师有多个人,每个人负责不同的菜品类别,如炒菜厨师只负责炒菜,而蒸菜、汤品、面食都由不同的厨师负责。那么,客人所点的菜品可能会由多个厨师来完成制作,此时服务员可能会手忙脚乱,因为服务员与厨师紧耦合,客人新增点菜、取消点菜等等请求都需要服务员去与厨师交涉,一旦弄错了对应关系就会造成严重的后果。 其实,服务员与厨师的关系可能是通知和被通知的关系,如果能将这种关系抽象出来,那么服务员和厨师的关系不就解耦了吗?可以将服务员通知厨师看成是给厨师下达一个命令,这个命令有多种支持的操作,如:执行命令、撤销命令、重做命令等等,执行命令时,可以记录日志,以便查阅,就如同服务员手上的"点菜清单",随时都能查看客户点了什么菜、取消了什么菜。 服务员发出命令,我们称之他为请求者,而厨师我们称之为接收者,这种将命令请求者和命令接收者之间添加一层命令抽象的设计模式就是本文要讨论的命令模式。 2. 什么是命令模式 命令模式:将一个请求封装为一个对象,从而可以用不同的请求对客户进行参数化,并且可以对请求排队或记录日志,还支持可以撤销的操作。 Figure 3. 命令模式类图 从图上可以看出,命令模式有几个角色: 请求者(Invoker): 请求的发起者,内部聚合了很多命令对象,可以调用这些命令对象执行相关请求,但是不直接依赖接收者(Receiver); 抽象命令(Abstract Command): 定义了命令通用方法的接口或者抽象类(通常包含execute方法表示执行命令),如执行、撤销、重做等; 具体命令(Concrete Command): 实现或者继承抽象命令,但是自己不直接实现业务逻辑,而是内部聚合多个Receiver,调用其相关方法来完成命令的具体业务实现; 接收者(Receiver): 真正实现了命令的相关业务功能,被具体命令对象依赖; 命令模式常见的场景 遥控器,可以控制家用电器的开关、模式切换、频道切换等,可以使用命令模式来设计,遥控器作为请求者(Invoker),电器作为接收者(Receiver); 前文所述的到餐厅点菜,就可以使用命令模式设计,服务员作为请求者(Invoker),而厨师作为命令接收者(Receiver),抽象命令定义点菜、取消点菜等方法; GUI软件系统界面功能的设计,界面包含多个功能按钮,点击后分别实现不同的功能,此时可以使用命令模式,按钮作为请求者(Invoker),系统后端实现按钮点击后真正功能的类作为接收者(Receiver),而抽象命令可以定义按钮的执行方法,如下图所示: Figure 4. GUI对象将命令委派给命令对象 命令模式的优点 完全符合单一职责原则,每一个具体命令(Concrete Command)负责单独的命令逻辑; 完全符合开闭原则,可以在不修改已有客户端代码的情况下在程序中扩展新的命令; 通过引入中间层(命令层)降低了系统的耦合度,便于命令的扩展; 可以实现命令的撤销和恢复功能;可以实现命令的排队和延迟执行; 可以实现将多个命令组合成复杂的命令。 命令模式的缺点 增加了命令层,系统复杂度增加,带来系统理解上的困难; 具体命令类的数量增加导致系统的类数量增加,系统的开发和维护成本增高。 3. 命令模式代码 接下来看看命名模式的基本代码实现: 抽象命名接口(Abstract Command): interface Command { void execute(); } ...

2021-06-14 · 2 min · 356 words · Hank

Java设计模式(17)-策略模式

策略(Strategy),表示实现某种目标的方案集合。这里的方案,在计算机软件中,就是算法。比如,要实现商场打折的目标,那么可能的方案有多种:商品折扣、商品满减、商品积分等等,这些在软件系统里边都代表了不同的算法实现。再举一个简单的例子:假如从公司回家,你可能有几种方式,如乘坐地铁、乘坐公交,又或者是打网约车、自己开车,等等,这些方案都是为了达到回家这一目标,但是他们都是可以相互替换的,你可以今天坐地铁,明天坐公交,后天懒了不想走路,那么直接打网约车也可以。这样的系统怎么来设计呢? 1. 不使用策略模式 如果我们要实现前边"回家方式"的例子,最普通的方式就是通过条件判断来实现。代码如下: class BackhomeService { public static final int RAILWAY = 1; public static final int BUS = 2; public static final int ONLINE_CAR = 3; public static final int DRIVE = 4; void backHome(int type) { if (type == RAILWAY) { System.out.println("乘坐地铁回家"); } else if (type == BUS) { System.out.println("乘坐公交回家"); } else if (type == ONLINE_CAR) { System.out.println("乘坐网约车回家"); } else if (type == DRIVE) { System.out.println("开车回家"); } } } ...

2021-06-07 · 3 min · 444 words · Hank

Java设计模式(16)-模板方法模式

软件设计过程中,有时候我们发现某一业务场景的基本流程是固定的,但是可能不同业务的某些步骤存在一些差异,但是整体上存在很大的共性。比如,现在全国正大力推动新冠疫苗的接种工作,不论是现在的新冠疫苗,还是以前接种的乙肝、流感等等疫苗,接种的流程其实都差不多,比如可能都需要经过预约、前往、现场审核、接种和最后的观察等步骤。现在我们来设计疫苗接种的类图,怎么设计呢?我们可以找出流程中的共性,设计出几个基本的流程骨架,然后将不同的步骤抽象化,由具体子类去实现,这就是"模板方法模式"。 前边的系列文章,我们已经学习了创建型设计模式和结构型设计模式,从本篇开始开始学习行为型设计模式。模板方法模式属于行为型设计模式的一种,比较简单,我们直接切入正题。 1. 模板方法模式 模板方法模式(Template Method Pattern),也称模板模式,其思想是:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。 模板方法模式的类图如下: 从图上可以看到,模板方法模式分为2个部分: 抽象模板类(Abstract Template):给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。 模板方法:模板方法定义了算法的骨架,内部会顺序调用其他基本方法,实现某一算法流程。通常,模板方法会定义为final的以禁止子类重写; 基本方法:基本方法定义了算法流程中的某一个步骤,它可以定义为抽象方法,具体实现交给子类;也可以定义为普通方法,即提供了默认实现,子类可以选择是否重写。还有一种比较特殊的方法,称为钩子方法,这种方法默认是空方法,子类可以选择是否重写。 具体实现类(Concrete Class):实现抽象模板类定义的抽象方法,并依据情况覆盖抽象模板类提供的基本方法,完成自身特定的业务逻辑处理。 优点 模板方法模式的优点: 可扩展性: 模板方法模式封装了程序不变的部分,将可变的部分延迟到子类实现,可以很容的扩展多个具体子类实现 代码重用: 公共代码抽象到父类中,便于代码的复用 缺点 基于继承体系实现,因此具有继承体系的缺点,如在父类添加抽象方法,所有子类都需要实现 存在多个子类,导致类的数量过多,系统变的庞大 2. 示例 模板方法模式较为简单,我们来看一个demo。 案例:以本文开头的疫苗接种为例,疫苗接种需要经过预约、前往、现场审核、接种和观察等步骤,使用模板方法模式设计程序。 程序代码如下: 1、首先,抽象模板类: 代码清单:抽象模板类 abstract class AbstractVaccinate { protected final String name; public AbstractVaccinate(String name) { this.name = name; } // 疫苗接种 public final void vaccinate() { (1) order(); tripMode(); check(); vaccinating(); if (mustObservate()) (5) observate(); afterVaccinated(); } // 预约 public abstract void order(); (2) // 去往接种目的地方式 public void tripMode() { (3) System.out.println(this.name + " 接种前自行前往接种地..."); } // 审核 public abstract void check(); (2) // 接种 public void vaccinating() { (3) System.out.println(this.name + " 接种中..."); } // 观察 public void observate() { (3) System.out.println(this.name + " 接种后观察30分钟..."); } // 钩子方法,是否必须观察 public boolean mustObservate() { (4) return false; } // 接种完成后 public void afterVaccinated() { (3) System.out.println(this.name + " 接种成功!"); } } ...

2021-05-27 · 2 min · 287 words · Hank

HTTP权威指南中文高清版PDF下载

HTTP是Web的基础,这里所说的Web,不仅只是通常意义上的Browser/Server端的开发,而是Web Service。大多数人对HTTP的了解也就是这种程度了(没有读此书之前的我也是这样)。此书的意义在于,它让你知道,HTTP不仅只有一个简单的GET,HTTP可以做为一种通用的分布式编程的协议。现如今Web上大行其道的所谓Restful Web Service,其基础就是HTTP,而提出Rest这个概念的Roy Fielding,也是HTTP协议(RFC2616)的主要作者之一。 1. 站长评价 Web开发离不开Http,这本《HTTP权威指南》确实是介绍HTTP协议最权威的一本书籍,虽然古老(2012年出版)但是内容丰富,从Http0.9到未来的HTTP-NG,从HTTP基础到HTTP通信安全,可以说涵盖了HTTP协议的方方面面。不论是刚入行的新手,又或是有多年经验的程序大师,反复研读这本巨著肯定会受益匪浅! 丰富指数:☆☆☆☆ 难度指数:☆☆☆ 推荐指数:☆☆☆☆ 迫不及待了?点此立即下载! 2. 内容简介 HTTP(HyperText Transfer Protocol,超文本传输协议)是Web客户端与服务器交互文档和信息时所使用的协议,是每个成功Web事务的幕后推手。众所周知,我们每天访问公司内部网络、搜索绝版书籍、研究统计信息时所使用的浏览器的核心就是HTTP。但HTTP的应用远不仅仅是浏览Web内容。由于HTTP既简单又普及,很多其他网络应用程序也选择了它,尤其是采用SOAP和XML-RPC这样的Web服务。 本书详细解释了HTTP协议,包括它是如何工作的,如何用它来开发基于Web的应用程序。但本书并不只介绍了HTTP,还探讨了HTTP有效工作所依赖的所有其他核心因特网技术。尽管HTTP是本书的中心内容,但本书的本质是理解Web的工作原理,以及如何将这些知识应用到Web编程和管理之中去,主要涵盖HTTP的技术运作方式、产生动机、性能和目标,以及一些相关技术问题。 本书是HTTP协议及相关Web技术方面的著作,主要内容包括: HTTP方法、首部以及状态码 优化代理和缓存的方法 设计Web机器人和爬虫的策略 Cookies、认证以及安全HTTP 国际化及内容协商 重定向及负载平衡策略 本书由具有多年实践经验的专家编写,通过简洁语言和大量翔实的细节图解帮助读者形象地理解Web幕后所发生的事情,详细说明了Web上每条请求的实际运行情况。要想高效地进行Web开发,所有Web程序员、管理员和应用程序开发者都应该熟悉HTTP。很多书籍只介绍了Web的使用方式,而本书则深入说明了Web的工作原理。 3. 作者简介 David Gourley,Endeca的首席技术官(Chief TechnologyOfficer),负责Endeca产品的研究及开发。Endeca开发的因特网及内部网络信息访问解决方案为企业级数据的导航及研究提供了一些新的方式。在到Endeca工作之前,David是Inktomi基础工程组的一员,他在那儿帮助开发了Inktomi的因特网搜索数据库,是Inktomi的Web缓存产品的主要开发者。David在加州大学伯克利分校获得了计算机科学的学士学位,还拥有Web技术方面的几项专利。 BrianTotty,出任了Inktomi公司(这是1996年他参与建立的一家公司)研发部副总裁,在公司中他负责Web缓存、流媒体及因特网搜索技术的研发工作。他曾是SiliconGraphics公司的一名科学家,他在那儿为高性能网络和超级计算机系统设计软件并对其进行优化。在那之前,他是苹果计算机公司高级技术组的一名工程师。Brian在伊利诺伊大学Urbana-Champaign分校获得了计算机科学的博士学位,在MIT获得了计算机科学及电子工程的学士学位,在那里他获得了计算机系统研究的Organick奖。他还为加州大学扩展系统开发并讲授了一些屡获殊荣的因特网技术方面的课程。 MarjorieSayer在Inktomi公司负责编写Web缓存方面的软件。在加州大学伯克利分校获得了数学硕士和博士学位之后,一直致力于数学课程的改革。从1990年开始致力于能量资源管理、并行系统软件、电话和网络方面的写作。 Sailu Reddy目前在Inktomi公司负责嵌入式的性能增强型HTTP代理的开发。Sailu从事复杂软件系统的开发已经有12年了,从1995年开始深入Web架构的研发工作。他是Netscape Web服务器、Web代理产品,以及后面几代产品的核心工程师。他具备HTTP应用程序、数据压缩技术、数据库引擎以及合作管理等方面的技术经验。Sailu在亚里桑那大学获得了信息系统的硕士学位并握有Web技术方面的多项专利。 AnshuAggarwal是Inktomi公司的工程总监。他领导着Inktomi公司Web缓存产品的协议处理工程组,从1997年就开始参与Inktomi的Web技术设计工作。Anshu在科罗拉多大学Boulder分校获得了计算机科学的硕士和博士学位,从事分布式多处理器的内存一致性技术研究。他还拥有电子工程的硕士和学士学位。Anshu撰写了多篇技术论文,还拥有两项专利。 4. 目录 第一部分 HTTP:Web的基础 第1章 HTTP概述 1.1 HTTP——因特网的多媒体信使 1.2 Web客户端和服务器 1.3 资源 1.3.1 媒体类型 1.3.2 URI 1.3.3 URL 1.3.4 URN 1.4 事务 1.4.1 方法 1.4.2 状态码 1.4.3 Web页面中可以包含多个对象 1.5 报文 1.6 连接 1.6.1 TCP/IP 1.6.2 连接、IP地址及端口号 1.6.3 使用Telnet实例 1.7 协议版本 1.8 Web的结构组件 1.8.1 代理 1.8.2 缓存 1.8.3 网关 1.8.4 隧道 1.8.5 Agent代理 1.9 起始部分的结束语 1.10 更多信息 1.10.1 HTTP协议信息 1.10.2 历史透视 1.10.3 其他万维网信息 第2章 URL与资源 2.1 浏览因特网资源 2.2 URL的语法 2.2.1 方案——使用什么协议 2.2.2 主机与端口 2.2.3 用户名和密码 2.2.4 路径 2.2.5 参数 2.2.6 查询字符串 2.2.7 片段 2.3 URL快捷方式 2.3.1 相对URL 2.3.2 自动扩展URL 2.4 各种令人头疼的字符 2.4.1 URL字符集 2.4.2 编码机制 2.4.3 字符限制 2.4.4 另外一点说明 2.5 方案的世界 2.6 未来展望 2.7 更多信息 第3章 HTTP报文 3.1 报文流 3.1.1 报文流入源端服务器 3.1.2 报文向下游流动 3.2 报文的组成部分 3.2.1 报文的语法 3.2.2 起始行 3.2.3 首部 3.2.4 实体的主体部分 3.2.5 版本0.9的报文 3.3 方法 3.3.1 安全方法 3.3.2 GET 3.3.3 HEAD 3.3.4 PUT 3.3.5 POST 3.3.6 TRACE 3.3.7 OPTIONS 3.3.8 DELETE 3.3.9 扩展方法 3.4 状态码 3.4.1 100~199——信息性状态码 3.4.2 200~299——成功状态码 3.4.3 300~399——重定向状态码 3.4.4 400~499——客户端错误状态码 3.4.5 500~599——服务器错误状态码 3.5 首部 3.5.1 通用首部 3.5.2 请求首部 3.5.3 响应首部 3.5.4 实体首部 3.6 更多信息 第4章 连接管理 4.1 TCP连接 4.1.1 TCP的可靠数据管道 4.1.2 TCP流是分段的、由IP分组传送 4.1.3 保持TCP连接的正确运行 4.1.4 用TCP套接字编程 4.2 对TCP性能的考虑 4.2.1 HTTP事务的时延 4.2.2 性能聚焦区域 4.2.3 TCP连接的握手时延 4.2.4 延迟确认 4.2.5 TCP慢启动 4.2.6 Nagle算法与TCP_NODELAY 4.2.7 TIME_WAIT累积与端口耗尽 4.3 HTTP连接的处理 4.3.1 常被误解的Connection首部 4.3.2 串行事务处理时延 4.4 并行连接 4.4.1 并行连接可能会提高页面的加载速度 4.4.2 并行连接不一定更快 4.4.3 并行连接可能让人“感觉”更快一些 4.5 持久连接 4.5.1 持久以及并行连接 4.5.2 HTTP/1.0+ keep-alive连接 4.5.3 Keep-Alive操作 4.5.4 Keep-Alive选项 4.5.5 Keep-Alive连接的限制和规则 4.5.6 Keep-Alive和哑代理 4.5.7 插入Proxy-Connection 4.5.8 HTTP/1.1持久连接 4.5.9 持久连接的限制和规则 4.6 管道化连接 4.7 关闭连接的奥秘 4.7.1 “任意”解除连接 4.7.2 Content-Length及截尾操作 4.7.3 连接关闭容限、重试以及幂等性 4.7.4 正常关闭连接 4.8 更多信息 4.8.1 HTTP连接 4.8.2 HTTP性能问题 4.8.3 TCP/IP 第二部分 HTTP结构 第5章 Web服务器 5.1 各种形状和尺寸的Web服务器 5.1.1 Web服务器的实现 5.1.2 通用软件Web服务器 5.1.3 Web服务器设备 5.1.4 嵌入式Web服务器 5.2 最小的Perl Web服务器 5.3 实际的Web服务器会做些什么 5.4 第一步——接受客户端连接 5.4.1 处理新连接 5.4.2 客户端主机名识别 5.4.3 通过ident确定客户端用户 5.5 第二步——接收请求报文 5.5.1 报文的内部表示法 5.5.2 连接的输入/输出处理结构 5.6 第三步——处理请求 5.7 第四步——对资源的映射及访问 5.7.1 docroot 5.7.2 目录列表 5.7.3 动态内容资源的映射 5.7.4 服务器端包含项 5.7.5 访问控制 5.8 第五步——构建响应 5.8.1 响应实体 5.8.2 MIME类型 5.8.3 重定向 5.9 第六步——发送响应 5.10 第七步——记录日志 5.11 更多信息 第6章 代理 6.1 Web的中间实体 6.1.1 私有和共享代理 6.1.2 代理与网关的对比 6.2 为什么使用代理 6.3 代理会去往何处 6.3.1 代理服务器的部署 6.3.2 代理的层次结构 6.3.3 代理是如何获取流量的 6.4 客户端的代理设置 6.4.1 客户端的代理配置:手工配置 6.4.2 客户端代理配置:PAC文件 6.4.3 客户端代理配置:WPAD 6.5 与代理请求有关的一些棘手问题 6.5.1 代理URI与服务器URI的不同 6.5.2 与虚拟主机一样的问题 6.5.3 拦截代理会收到部分URI 6.5.4 代理既可以处理代理请求,也可以处理服务器请求 6.5.5 转发过程中对URI的修改 6.5.6 URI的客户端自动扩展和主机名解析 6.5.7 没有代理时URI的解析 6.5.8 有显式代理时URI的解析 6.5.9 有拦截代理时URI的解析 6.6 追踪报文 6.6.1 Via首部 6.6.2 TRACE方法 6.7 代理认证 6.8 代理的互操作性 6.8.1 处理代理不支持的首部和方法 6.8.2 OPTIONS:发现对可选特性的支持 6.8.3 Allow首部 6.9 更多信息 第7章 缓存 7.1 冗余的数据传输 7.2 带宽瓶颈 7.3 瞬间拥塞 7.4 距离时延 7.5 命中和未命中的 7.5.1 再验证 7.5.2 命中率 7.5.3 字节命中率 7.5.4 区分命中和未命中的情况 7.6 缓存的拓扑结构 7.6.1 私有缓存 7.6.2 公有代理缓存 7.6.3 代理缓存的层次结构 7.6.4 网状缓存、内容路由以及对等缓存 7.7 缓存的处理步骤 7.7.1 第一步——接收 7.7.2 第二步——解析 7.7.3 第三步——查找 7.7.4 第四步——新鲜度检测 7.7.5 第五步——创建响应 7.7.6 第六步——发送 7.7.7 第七步——日志 7.7.8 缓存处理流程图 7.8 保持副本的新鲜 7.8.1 文档过期 7.8.2 过期日期和使用期 7.8.3 服务器再验证 7.8.4 用条件方法进行再验证 7.8.5 If-Modified-Since:Date再验证 7.8.6 If-None-Match:实体标签再验证 7.8.7 强弱验证器 7.8.8 什么时候应该使用实体标签和最近修改日期 7.9 控制缓存的能力 7.9.1 no-Store与no-Cache响应首部 7.9.2 max-age响应首部 7.9.3 Expires响应首部 7.9.4 must-revalidate响应首部 7.9.5 试探性过期 7.9.6 客户端的新鲜度限制 7.9.7 注意事项 7.10 设置缓存控制 7.10.1 控制Apache的HTTP首部 7.10.2 通过HTTP-EQUIV控制HTML缓存 7.11 详细算法 7.11.1 使用期和新鲜生存期 7.11.2 使用期的计算 7.11.3 完整的使用期计算算法 7.11.4 新鲜生存期计算 7.11.5 完整的服务器——新鲜度算法 7.12 缓存和广告 7.12.1 发布广告者的两难处境 7.12.2 发布者的响应 7.12.3 日志迁移 7.12.4 命中计数和使用限制 7.13 更多信息 第8章 集成点:网关、隧道及中继 8.1 网关 8.2 协议网关 8.2.1 HTTP/*:服务器端Web网关 8.2.2 HTTP/HTTPS:服务器端安全网关 8.2.3 HTTPS/HTTP客户端安全加速器网关 8.3 资源网关 8.3.1 CGI 8.3.2 服务器扩展API 8.4 应用程序接口和Web服务 8.5 隧道 8.5.1 用CONNECT建立HTTP隧道 8.5.2 数据隧道、定时及连接管理 8.5.3 SSL隧道 8.5.4 SSL隧道与HTTP/HTTPS网关的对比 8.5.5 隧道认证 8.5.6 隧道的安全性考虑 8.6 中继 8.7 更多信息 第9章 Web机器人 9.1 爬虫及爬行方式 9.1.1 从哪儿开始:根集 9.1.2 链接的提取以及相对链接的标准化 9.1.3 避免环路的出现 9.1.4 循环与复制 9.1.5 面包屑留下的痕迹 9.1.6 别名与机器人环路 9.1.7 规范化URL 9.1.8 文件系统连接环路 9.1.9 动态虚拟Web空间 9.1.10 避免循环和重复 9.2 机器人的HTTP 9.2.1 识别请求首部 9.2.2 虚拟主机 9.2.3 条件请求 9.2.4 对响应的处理 9.2.5 User-Agent导向 9.3 行为不当的机器人 9.4 拒绝机器人访问 9.4.1 拒绝机器人访问标准 9.4.2 Web站点和robots.txt文件 9.4.3 robots.txt文件的格式 9.4.4 其他有关robots.txt的知识 9.4.5 缓存和robots.txt的过期 9.4.6 拒绝机器人访问的Perl代码 9.4.7 HTML的robot-control元标签 9.5 机器人的规范 9.6 搜索引擎 9.6.1 大格局 9.6.2 现代搜索引擎结构 9.6.3 全文索引 9.6.4 发布查询请求 9.6.5 对结果进行排序,并提供查询结果 9.6.6 欺诈 9.7 更多信息 第10章 HTTP-NG 10.1 HTTP发展中存在的问题 10.2 HTTP-NG的活动 10.3 模块化及功能增强 10.4 分布式对象 10.5 第一层——报文传输 10.6 第二层——远程调用 10.7 第三层——Web应用 10.8 WebMUX 10.9 二进制连接协议 10.10 当前的状态 10.11 更多信息 第三部分 识别、认证与安全 第11章 客户端识别与cookie机制 11.1 个性化接触 11.2 HTTP首部 11.3 客户端IP地址 11.4 用户登录 11.5 胖URL 11.6 cookie 11.6.1 cookie的类型 11.6.2 cookie是如何工作的 11.6.3 cookie罐:客户端的状态 11.6.4 不同站点使用不同的cookie 11.6.5 cookie成分 11.6.6 cookies版本0(Netscape) 11.6.7 cookies版本1(RFC 2965) 11.6.8 cookie与会话跟踪 11.6.9 cookie与缓存 11.6.10 cookie、安全性和隐私 11.7 更多信息 第12章 基本认证机制 12.1 认证 12.1.1 HTTP的质询/响应认证框架 12.1.2 认证协议与首部 12.1.3 安全域 12.2 基本认证 12.2.1 基本认证实例 12.2.2 Base-64用户名/密码编码 12.2.3 代理认证 12.3 基本认证的安全缺陷 12.4 更多信息 第13章 摘要认证 13.1 摘要认证的改进 13.1.1 用摘要保护密码 13.1.2 单向摘要 13.1.3 用随机数防止重放攻击 13.1.4 摘要认证的握手机制 13.2 摘要的计算 13.2.1 摘要算法的输入数据 13.2.2 算法H(d)和KD(s,d) 13.2.3 与安全性相关的数据(A1) 13.2.4 与报文有关的数据(A2) 13.2.5 摘要算法总述 13.2.6 摘要认证会话 13.2.7 预授权 13.2.8 随机数的选择 13.2.9 对称认证 13.3 增强保护质量 13.3.1 报文完整性保护 13.3.2 摘要认证首部 13.4 应该考虑的实际问题 13.4.1 多重质询 13.4.2 差错处理 13.4.3 保护空间 13.4.4 重写URI 13.4.5 缓存 13.5 安全性考虑 13.5.1 首部篡改 13.5.2 重放攻击 13.5.3 多重认证机制 13.5.4 词典攻击 13.5.5 恶意代理攻击和中间人攻击 13.5.6 选择明文攻击 13.5.7 存储密码 13.6 更多信息 第14章 安全HTTP 14.1 保护HTTP 的安全 14.2 数字加密 14.2.1 密码编制的机制与技巧 14.2.2 密码 14.2.3 密码机 14.2.4 使用了密钥的密码 14.2.5 数字密码 14.3 对称密钥加密技术 14.3.1 密钥长度与枚举攻击 14.3.2 建立共享密钥 14.4 公开密钥加密技术 14.4.1 RSA 14.4.2 混合加密系统和会话密钥 14.5 数字签名 14.6 数字证书 14.6.1 证书的主要内容 14.6.2 X.509 v3证书 14.6.3 用证书对服务器进行认证 14.7 HTTPS——细节介绍 14.7.1 HTTPS概述 14.7.2 HTTPS方案 14.7.3 建立安全传输 14.7.4 SSL握手 14.7.5 服务器证书 14.7.6 站点证书的有效性 14.7.7 虚拟主机与证书 14.8 HTTPS客户端实例 14.8.1 OpenSSL 14.8.2 简单的HTTPS客户端 14.8.3 执行OpenSSL客户端 14.9 通过代理以隧道形式传输安全流量 14.10 更多信息 14.10.1 HTTP安全性 14.10.2 SSL与TLS 14.10.3 公开密钥基础设施 14.10.4 数字密码 第四部分 实体、编码和国际化 第15章 实体和编码 15.1 报文是箱子,实体是货物 15.2 Content-Length: 实体的大小 15.2.1 检测截尾 15.2.2 错误的Content-Length 15.2.3 Content-Length与持久连接 15.2.4 内容编码 15.2.5 确定实体主体长度的规则 15.3 实体摘要 15.4 媒体类型和字符集 15.4.1 文本的字符编码 15.4.2 多部分媒体类型 15.4.3 多部分表格提交 15.4.4 多部分范围响应 15.5 内容编码 15.5.1 内容编码过程 15.5.2 内容编码类型 15.5.3 Accept-Encoding首部 15.6 传输编码和分块编码 15.6.1 可靠传输 15.6.2 Transfer-Encoding首部 15.6.3 分块编码 15.6.4 内容编码与传输编码的结合 15.6.5 传输编码的规则 15.7 随时间变化的实例 15.8 验证码和新鲜度 15.8.1 新鲜度 15.8.2 有条件的请求与验证码 15.9 范围请求 15.10 差异编码 15.11 更多信息 第16章 国际化 16.1 HTTP对国际性内容的支持 16.2 字符集与HTTP 16.2.1 字符集是把字符转换为二进制码的编码 16.2.2 字符集和编码如何工作 16.2.3 字符集不对,字符就不对 16.2.4 标准化的MIME charset值 16.2.5 Content-Type首部和Charset首部以及META标志 16.2.6 Accept-Charset首部 16.3 多语言字符编码入门 16.3.1 字符集术语 16.3.2 字符集的命名很糟糕 16.3.3 字符 16.3.4 字形、连笔以及表示形式 16.3.5 编码后的字符集 16.3.6 字符编码方案 16.4 语言标记与HTTP 16.4.1 Content-Language首部 16.4.2 Accept-Language首部 16.4.3 语言标记的类型 16.4.4 子标记 16.4.5 大小写 16.4.6 IANA语言标记注册 16.4.7 第一个子标记——名字空间 16.4.8 第二个子标记——名字空间 16.4.9 其余子标记——名字空间 16.4.10 配置和语言有关的首选项 16.4.11 语言标记参考表 16.5 国际化的URI 16.5.1 全球性的可转抄能力与有意义的字符的较量 16.5.2 URI字符集合 16.5.3 转义和反转义 16.5.4 转义国际化字符 16.5.5 URI中的模态切换 16.6 其他需要考虑的地方 16.6.1 首部和不合规范的数据 16.6.2 日期 16.6.3 域名 16.7 更多信息 16.7.1 附录 16.7.2 互联网的国际化 16.7.3 国际标准 第17章 内容协商与转码 17.1 内容协商技术 17.2 客户端驱动的协商 17.3 服务器驱动的协商 17.3.1 内容协商首部集 17.3.2 内容协商首部中的质量值 17.3.3 随其他首部集而变化 17.3.4 Apache中的内容协商 17.3.5 服务器端扩展 17.4 透明协商 17.4.1 进行缓存与备用候选 17.4.2 Vary首部 17.5 转码 17.5.1 格式转换 17.5.2 信息综合 17.5.3 内容注入 17.5.4 转码与静态预生成的对比 17.6 下一步计划 17.7 更多信息 第五部分 内容发布与分发 第18章 Web主机托管 18.1 主机托管服务 18.2 虚拟主机托管 18.2.1 虚拟服务器请求缺乏主机信息 18.2.2 设法让虚拟主机托管正常工作 18.2.3 HTTP/1.1的Host首部 18.3 使网站更可靠 18.3.1 镜像的服务器集群 18.3.2 内容分发网络 18.3.3 CDN中的反向代理缓存 18.3.4 CDN中的代理缓存 18.4 让网站更快 18.5 更多信息 第19章 发布系统 19.1 FrontPage为支持发布而做的服务器扩展 19.1.1 FrontPage服务器扩展 19.1.2 FrontPage术语表 19.1.3 FrontPage的RPC协议 19.1.4 FrontPage的安全模型 19.2 WebDAV与协作写作 19.2.1 WebDAV的方法 19.2.2 WebDAV与XML 19.2.3 WebDAV首部集 19.2.4 WebDAV的锁定与防止覆写 19.2.5 LOCK方法 19.2.6 UNLOCK方法 19.2.7 属性和元数据 19.2.8 PROPFIND方法 19.2.9 PROPPATCH方法 19.2.10 集合与名字空间管理 19.2.11 MKCOL方法 19.2.12 DELETE方法 19.2.13 COPY与MOVE方法 19.2.14 增强的HTTP/1.1方法 19.2.15 WebDAV中的版本管理 19.2.16 WebDAV的未来发展 19.3 更多信息 第20章 重定向与负载均衡 20.1 为什么要重定向 20.2 重定向到何地 20.3 重定向协议概览 20.4 通用的重定向方法 20.4.1 HTTP重定向 20.4.2 DNS重定向 20.4.3 任播寻址 20.4.4 IP MAC转发 20.4.5 IP地址转发 20.4.6 网元控制协议 20.5 代理的重定向方法 20.5.1 显式浏览器配置 20.5.2 代理自动配置 20.5.3 Web代理自动发现协议 20.6 缓存重定向方法 20.7 因特网缓存协议 20.8 缓存阵列路由协议 20.9 超文本缓存协议 20.9.1 HTCP认证 20.9.2 设置缓存策略 20.10 更多信息 第21章 日志记录与使用情况跟踪 21.1 记录内容 21.2 日志格式 21.2.1 常见日志格式 21.2.2 组合日志格式 21.2.3 网景扩展日志格式 21.2.4 网景扩展2日志格式 21.2.5 Squid代理日志格式 21.3 命中率测量 21.3.1 概述 21.3.2 Meter首部 21.4 关于隐私的考虑 21.5 更多信息 第六部分 附录 附录A URI方案 附录B HTTP状态码 附录C HTTP首部参考 附录D MIME类型 附录E Base-64编码 附录F 摘要认证 附录G 语言标记 附录H MIME字符集注册表 索引 ...

2021-04-12 · 6 min · 1228 words · Hank

IntelliJ IDEA 2020.3.x激活教程

IntelliJ IDEA作为Java界IDE神一般的存在,深受广大java开发者的喜欢。目前官方最新版本为2020.3,本文介绍如何对其进行激活。 实际上,IDEA 2020.3版本的激活方式跟之前的2019版本相比并没有太大区别,基本思路都是一样的,2019版本的IDEA激活方法可以看这几篇文章: IntelliJ IDEA 2019.3.3激活 IntelliJ IDEA 2019.2.x激活 1. 详细激活步骤 具体的激活步骤如下: 1、首先,从官方网站下载IDEA的2020.3版本安装包,下载地址为: https://www.jetbrains.com/idea/download 2、然后,安装IDEA,没什么好说的,一步一步安装就对了 PS: 什么?你居然不会安装?我觉得你还是趁早换个职业吧😅 3、安装完成过后,启动IDEA,先选择试用软件,如下图所示: 选择“Evaluate for free”过后,确定即可。 4、下载IDEA激活包,下载地址在文章最后。下载过后解压,得到lib目录下的BetterIntelliJ-1.16.jar文件,就是激活包,将其拷贝到任意位置(建议英文路径中)。比如我的路径为: D:\install\idea\BetterIntelliJ-1.16.jar 还有一个重要的文件:激活补丁key.txt,里边存储了激活码,后边激活会用到。 5、然后打开软件,点击菜单栏的“help”菜单,点击"Edit Custom VM Options …​"菜单,来自定义VM参数,如下图所示: 在打开的窗口中,增加如下参数: -javaagent:D:\install\idea\BetterIntelliJ-1.16.jar 如下图所示: 6、重启IDEA,然后点击help菜单的Register…​项,填入第4布中激活补丁key.txt文件中的激活码,激活即可,如图所示: 2. 激活成功 ok,您的IDEA已经成功激活到2100年,赶紧操练起来吧! 3. 说明 为了使激活长期有效,注意不要更改激活的jar包文件所在的目录路径和文件名,另外请关闭IDEA的自动更新! IDEA激活包下载地址: 链接: https://pan.baidu.com/s/1LVsi5Dvj2o9L8IuTfekEAQ 提取码:8k1y ...

2021-03-27 · 1 min · 45 words · Hank

Java设计模式(15)-代理模式

虽然马上就是2021年了,但是大家回想起去年(2020年初)的时候,是不是高兴不起来呢?人类还没有彻底战胜新冠病毒之前,我们还是要遵从国家号召:多居家、少聚集、勤洗手、戴口罩!疫情期间,我们足不出户免不了要点外卖。如果没有外卖配送服务,那么你可能得自己去餐厅取餐了。还好,我们可以享受外卖配送服务,只需要点好餐,然后外卖小哥回到餐厅取餐然后直接送到我们手上,这样我们就不需要跟餐厅直接接触了,也减少了感染的风险。其实,外卖小哥就是一个代理,他作为一个中间角色(其实就是中介),帮助用户去餐厅取餐然后送餐给用户,这样用户就不需要直接面对餐厅。 1. 什么是代理 什么是代理?代理就是中介,它提供与服务提供方相同或者相似的功能,客户不再直接面对服务提供方,而是先找代理,再由代理去与服务提供方交互完成功能。现实生活中,有很多代理的例子,比如前边提到的外卖订餐,外卖小哥就是一个代理角色;又如,购买火车票,火车票代售点就是一个代理角色,用户直接可以去代售点购买车票;再如,各种中介所,如婚姻中介所、房产中介等等,都是代理。 在软件中,代理主要指的就是本文要探讨的代理模式,或者模式中的代理类,我们 稍后 再细说。 在网络中,代理服务器(Proxy Server),专门用来代理网络用户去取得网络信息。代理服务器又有正向代理和反向代理的区分。 1.1. 正向代理和反向代理 正向代理和反向代理,其实是按照代理服务所在位置、客户端对代理服务器是否有感知来考虑的。 1、正向代理 正向代理,其服务器位于客户端,客户端需要访问目标服务器,但是不会直接访问,而是先访问代理服务器,然后再由代理服务器转发请求到目标服务器。也就是说,正向代理其代理的是客户端的请求,客户端明确知道代理服务器的地址,然后在连接到代理服务器使其转发请求到目标服务器。其原理如下: Figure 1. 正向代理示意图 如图所示,客户端链接到代理服务器(Proxy),客户端和代理服务器位于同一个网络中。 正向代理的主要用途是,客户端由于某些原因不能直接访问目标服务器,但是代理服务器可以,因此通过代理服务器作为跳板从而达到访问目标服务器的目的。例如,我们需要访问Google网站,但是国内无法访问,我们需要使用一些代理服务或软件(它们可以访问Google),这些服务和软件就是正向代理服务器。各大操作系统都有代理服务设置,例如macOS的: Figure 2. MacOS中网络代理设置 2、反向代理 反向代理,其实是相当于正向代理而言的。在反向代理中,代理服务器与目标服务器位于同一网络中,代理服务器代理并隐藏目标服务器,客户端无需知道代理服务器的存在。原理如下: Figure 3. 反向代理示意图 代理服务器就像服务端的一道防火墙,既可以过滤掉非法的请求,又可以隐藏目标服务其的真实地址等信息,对外提供统一的访问入口,提到服务端的安全性。另外,代理服务器能够实现网络负载均衡,将请求分发到不同的目标服务器上,以缓解请求压力,提高访问速度和性能。 反向代理在网络应用中使用非常普遍,常见的软件如Nginx、Apache等都可以作为反向代理服务器。 1.2. 为什么要使用代理 前边说过了正向代理和反向代理的作用,总结一下,代理有如下的作用: 访问客户端不能直接访问的服务器,比如Google、Facebook等 提高网络访问速度,例如访问某些网站速度很慢,但是代理服务器访问之很快,这样客户端通过访问代理服务器可以提高网络速度,比如常见的网络加速器 实现安全保护,比如反向代理服务器可以设置规则过滤非法请求,也可以隐藏服务器IP地址和端口以保护后端服务器,还可以做权限认证等 实施负载均衡,反向代理服务器可以按照既定策略将不同的请求分发到不同的后端服务器上,缓解其访问压力,如常见的轮询、按IP Hash、按请求URL hash、随机选择、最小访问、按地域选择等负载均衡策略 实现动静分离,反向代理服务器支持直接访问静态资源,不需要后端服务器支持,这样可以加速静态页面的访问,如Nginx对静态资源的访问支持非常好,性能强大 了解了什么是代理,接下来我们看看在计算机软件中,代理模式的概念。 2. 代理模式简介 技术来源于生活,代理模式是一种生活中代理运用的总结。DP对代理模式(Proxy Pattern)的定义如下:给对象提供一个代理以控制对该对象的访问。 当客户端不能直接依赖目标对象,又或是想要增强目标对象的功能,可以为其创建一个代理对象(中介),然后客户端访问代理对象而不直接依赖目标对象,再由中介对象来访问目标对象。这种设计模式就是代理模式,其结构如下图所示: 可以看到,代理模式有几种角色: 抽象主题角色(Subject: 抽象类或接口,用来定义业务功能的公共方法 真实主题角色(RealSubject): 实现了抽象主题(Subject)接口,完成具体业务功能实现。它就是目标对象,客户端需要依赖它提供的功能 代理对象(Proxy): RealSubject的代理实现,保证与其具有相同的功能,因此也会实现Subject接口。代理对象内部会聚合RealSubject,具体功能会调用RealSubject来完成,同时内部也可以增强RealSubject的功能 从结构上看,代理模式与 适配器模式 类似,两者都存在一个中间对象,其都会聚合具体对象并实现一个抽象接口,但是它们的目的并不相同:代理模式更强调对象的访问控制和功能增强,而适配器模式更强调功能的转换。 代理模式的主要优点: 代理模式在客户端与目标对象之间增加了中间对象,可以控制目标对象访问 代理对象可以扩展目标对象的功能 其主要缺点: 代理模式会造成系统设计中类的数量增加 在客户端和目标对象之间增加一个代理对象,中转过程会影响请求速度 增加了系统的复杂度 3. Java中代理实现的三种方式 代理可以分为静态代理和动态代理,动态代理技术有效的减少了上边的缺点。 ...

2020-12-31 · 3 min · 588 words · Hank

Java设计模式(14)-享元模式

享元模式,"享"即共享,"元"即元素,软件中则为对象,享元就是共享对象之意。这种设计模式用来共享对象而不是大量创建对象,以节约系统资源。现实中,很多东西都可以使用享元模式来解决,比如围棋、五子棋,棋子的颜色就黑白两种,只是他们在棋盘的位置不同;又如,展示类网站,多个用户公用一套系统,只是内容和展示形式存在差异;再如,教室的课桌和凳子…​上述示例都有一个共同点:相关的东西存在很大的相似,但是也不完全相同,此时如果要开发软件,那么都可以用享元模式来设计。 1. 从围棋游戏开始 假设要你开发一款围棋游戏,我们知道,围棋棋子有黑色和白色两种,棋子在棋盘上的位置随着下棋的进行而不同,那么你怎么设计类关系? 常规的思路是使用工厂模式,棋手每落下一枚棋子,那么就创建一个棋子实例,并设置它放置的位置: Figure 1. 使用工厂模式 但是,围棋有 19 × 19 = 361 个交叉点,假设每个点上全部都有棋子,那么足足需要创建361个对象!这无疑是对系统资源的浪费。 那么,有好的解决办法吗?答案是使用享元模式来设计。 2. 什么是享元模式 享元模式(flyweight),也叫蝇量模式,它强调对象的细粒度控制和共享,主张运用共享技术有效地支持对象的复用。"享"即共享,"元"即元素,软件中则为对象,说白了就是最大程度的让对象可以共享并复用。享元模式的类结构如图所示: Figure 2. 享元模式类结构 如图所示,可以看出,享元模式有如下几种角色: 抽象享元角色(Flyweight):具体享元对象的超类或接口,可以接受作用于外部状态(见后文); 具体享元角色(ConcreteFlyweight):继承或实现抽象享元角色,为内部状态增加存储空间; 非共享享元角色(UnsharedConcreteFlyweight):同样继承或实现抽象享元角色,但是这些类并不需要共享出来; 享元工厂角色:管理Flyweight对象,确保合理的共享他们。它往往提供获取Flyweight对象的方法,当对象存在时直接返回,否则创建一个。 提示 有的书上也将UnsharedConcreteFlyweight对象单独提出来,将其看做外部状态的抽象,它并不实现Flyweight,而是作为Flyweight接口方法的参数进行传递,以说明它不共享。 享元模式的结构也好理解:抽象一个享元接口,提供通用的方法,然具体享元对象实现该接口,但是并不是所有逇具体享元对象都需要共享,因此按需共享并拆分,最后由享元工厂统一管理他们。上边还提到两个概念:外部状态和内部状态,它们是什么? 2.1. 外部状态和内部状态 在享元模式中,对象状态按是否共享分为两种:共享和不共享,随着环境变化而改变的、不可以共享的状态称为外部状态;相反,不会随着环境变化而改变的状态称为内部状态。 使用享元模式,重点是要分析出哪些对象是外部状态,哪些是内部状态,并将外部状态单独抽象出来,作为一个变化的部分。 在前边围棋的例子中,棋子的颜色只有黑、白两种,它们不会随着下棋的进行而增加或者减少,因此棋子的颜色是内部状态。而棋子在棋盘上放置的位置,随着下棋的进行,棋子的位置都会不同,因此,棋子的位置是外部状态。 又比如,多个用户共用一个网站,网站可以作为Flyweight对象而共享,而网站的代码、模板、数据库都是不会变化(增加或减少)的,它们可作为内部状态来共享。但是,不同的用户账号是不同的,每个用户都有自己额账号,因此,账号可以作为外部状态。此时就可以将账号单独提取出来作为一个变化的实体: Figure 3. 共享网站的设计类图 3. 围棋游戏改进 我们再来看看如何使用享元模式来解决围棋中重复创建对象、浪费资源的问题。 首先,前边已经分析了,围棋棋子的颜色是内部状态,而其在棋盘的位置是外部状态。那么,我们可以设计如下的类图: Figure 4. 围棋使用享元模式设计的类图 内部对象定义为Color类,外部对象抽象为Position类,Piece为抽象的Flyweight对象,WeiqiPiece作为具体的享元对象实现了Piece接口。最后,这些对象通过WeiqiPieceFactory统一管理。示例代码如下: 1、定义外部状态类: // 外部状态: 棋子位置 class Position { private final int x; private final int y; public Position(int x, int y) { this.x = x; this.y = y; } public String position() { (1) return "(" + x + "," + y + ")"; } } ...

2020-12-21 · 2 min · 326 words · Hank

Java设计模式(13)-外观模式

在现实生活中有很多这样的例子,比如,炒股,散户股民大多对股票金融知识缺乏,所以他们想要炒股赚钱往往看运气,但是散户想要保证低风险的投资和拿到稳定回报,怎么办呢?大多数人会选择购买基金,相对于股票,散户们不需要专业知识,只需要将自己交给基金会,由专业人士替他们购买股票,赚到钱后再按资金比例分给他们,这样就大大降低了投资风险。 Figure 1. 股票与基金示意图 基金作为一个中间对象,它解决了股民专业知识匮乏却也能投资股票赚钱的需求。其实,这就是外观模式。 1. 外观模式简介 外观模式(Facade Pattern),也叫门面模式,是一种经常被使用的结构性设计模式。DP对其定义如下:通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问。 还记得 迪米特法则] 吗? 迪米特法则(Law of Demeter,LoD),又叫作最少知识原则(Least Knowledge Principle,LKP),描述了如何设计对象之间的依赖关系,它的定义如下:一个对象对自己所依赖的对象知道的越少越好。更进一步的意思,即:只与你的直接朋友交谈,不跟“陌生人”说话(Talk only to your immediate friends and not to strangers)。 外观模式严格体现了迪米特法则,它降低了系统之间的耦合性和复杂度,提高了系统的可维护性。 外观模式有三种角色: 外观(Facade)角色:整合多个子系统,为其提供统一的接口实以现业务需求; 子系统(SubSystem)角色: 实现某方面的业务功能,供外观角色调用,但是并不知道外观角色的存在; 客户端(Client)角色:调用外观角色访问各个子系统实现不同的业务功能。 其类结构如下图所示: Figure 2. 外观模式类图 我们在软件开发中,经常会有意无意的使用外观模式。比如,MVC模式业务开发中,Service层会屏蔽Dao层的实现细节,对外只暴露一个方法,该方法内部可能会需要使用多个Dao来完成数据库操作。又比如现在流行的微服务架构,API网关就承担着外观角色,客户端直接访问网关,而不会直接去请求各个具体的服务。 Figure 3. API网关示意图 再举一个现在比较专注的例子:智能家居。当你装上了智能家居系统,那么你就可以一键控制家用电器的开关,是不是很舒服?比如,你早上起床,控制器会一键为你打开电动窗帘、播放闹钟、关闭空调等等,再也不用一个一个去操控了。我们看看怎么使用外观模式解决这个问题。 2. 应用案例:智能家居 假设我家安装了一些智能设备:智能空调、智能音响、自动窗帘、智能电灯等,现在我可不想一个个去控制它们。是时候请出超级管家了,它来负责一键操作这些设备。设计类图如下: Figure 4. 智能家居简易设计类图 智能管家作为指挥中心,它具有问好、叫醒起床、上班前告别、下班回家欢迎等功能,当然,假设我们可以通过语音和App操控它。这些功能,智能管家自己不会实现,而是通过调用下边的各个智能设备共同完成。我们看看代码如何实现。 1、智能设备,多个子系统,提供了自身的功能方法。 // 窗帘 class SmartCurtain { public void open() { System.out.println("打开窗帘"); } public void close() { System.out.println("关闭窗帘"); } } // 灯光 class SmartLight { public void open() { System.out.println("打开灯光"); } public void close() { System.out.println("关闭灯光"); } } // 空调 class AirConditioner { public void open() { System.out.println("打开空调"); } public void close() { System.out.println("关闭空调"); } } // 智能音响 class SmartSoundBox { public void play() { System.out.println("播放音乐"); } public void stop() { System.out.println("停止播放音乐"); } } ...

2020-12-08 · 2 min · 271 words · Hank

Java设计模式(12)-组合模式

1. 整体与部分的关系 很多场景下,需要考虑整体和部分之间的关系,在对其进行管理功能设计时,需要考虑到灵活性和扩展性,而继承关系往往不能很好地解决这些问题。一个最典型的例子就是:组织机构。比如,就公司而言,总公司可能下设有分公司,分公司下又有办事处,而办事处、分公司、总公司可能都会存在一些职能部门。很明显,组织机构是一个整体和部分的关系,并且它是一颗树状结构。 Figure 1. 复杂的组织机构树状结构 设计这样的结构,你是否会使用继承关系?如让分公司继承总公司、办事处继承分公司,这样往往是从机构的大小维度来划分的,子类除了具备父类的功能外,还能够独立扩展功能。但是,在组织机构树中,其实每个节点(机构)所承担只能都是差不多的,换言之,总公司、分公司、办事处三者其实并没有父子关系,在管理职能上他们的功能是相同的,比如都能添加、删除、查询子的机构节点。所以采用继承关系来设计其实是不合适的。 还有很多整体-部分关系的例子,比如学校、学院和系的关系,电脑组装商家可以出售整机也可以出售配件,文字处理软件中单个文字和整段甚至整篇文字的处理方式差不多,又如Java AWT、Swing中的容器组件和简单控件的关系。其实,这一类问题,最终解决的都是整体和部分被一致对待的问题,组合模式很好的解决了这个问题。 2. 组合模式简介 在Design Patterns一书中对组合模式的定义如下: 组合模式(Composite Pattern),将对象组合成树形结构以表示"部分-整体"的层次结构,使得用户对单个对象和组合对象的使用具有一致性。 组合模式是一种结构型设计模式,其"对象组合成树形结构"定义代表了对整体部分关系的抽象,将树形结构抽象为三个部分:根节点、树枝节点和叶子节点,其中,根节点、树枝节点可以添加子节点,而叶子节点不能添加子节点。因此,组合模式有两种方式:透明方式和安全方式。 在说明这两种方式之前,先看看组合模式的角色: 抽象构件(Component):对象组合的高度抽象构件,声明了对象操作的公共接口。透明方式和安全方式对节点管理方法的声明存在差异:透明方式会声明管理节点的接口,如添加、删除,但是安全方式不会声明管理接口,而是将其移交到树枝节点声明和实现,具体后边再说; 树枝构件(Composite):树形结构的分支节点,实现抽象构件,同时承担管理子节点职能,如实现添加、删除节点的方法; 树叶构件(Leaf):树形结构的叶子几点,没有子节点,所以不承担管理职能,它实现添加、删除等管理职能方法是没有意义的; 透明方式和安全方式主要区别在于:客户端是否需要区分树枝构件(Composite)和树叶构件(Leaf),透明方式则不区分,而安全方式则需要区分。 2.1. 透明方式 透明方式,不区分树叶构件和树枝构件,两者都实现构建构件的api,因此,树形结构管理职能的API(添加、修改、删除等)声明在抽象构件中,客户端使用时不需要区分树枝和树叶,因为他们具有相同的方法。类图如下: Figure 2. 透明方式类图 这种方式的好处在于,客户端不需要判断树叶构件和树枝构件,因此对客户端是统一的或者说透明的,方便管理;但是前边我们说过,树叶构件是没有子节点的,它不承担管理职能,实现管理职能的方法没有意思,因此我们只能进行方法空实现或者抛出异常,这一点不太友好。 2.2. 安全方式 相对于透明方式,安全方式显得比较保守。它只在抽象构件中声明公共方法,管理职能的方法放到树枝构件中进行声明和实现,这样树叶构件就不会存在管理职能的方法了。类图如下: Figure 3. 安全方式类图 这种方式的优点在于,树叶构件不需要对管理职能方法做无意义的空实现或抛异常,代码更严谨,但是其缺点也很明显:客户端需要明确知道调用的是树叶构件还是树枝构件,相对而言比较麻烦。 3. 应用示例 看一个例子。大学中分为多个学院,每个学院下又存在多个系(专业),我们看看如果运用组合模式来实现。 分析:学校、学院、系属于整体和部分关系,我们将其看做同一个整体并进行抽象,得到一个组织机构接口,让他们分别实现该接口。同时,学校、学院下都包含子节点,因此他们可以看做树枝构件,内部持有一个List来保存子节点数据,而系下不再有子节点,它属于树叶构件。 Figure 4. 学校学院系类图 类图中,Organization为抽象构件,声明了add、remove两个管理职能方法,而业务处理方法只有一个print方法,用来打印自身和其下的所有子节点。 从类图可以看出,我们使用的是组合模式的透明方式。 具体代码如下: 1、抽象构件: interface Organization { void add(Organization org); void remove(Organization org); void print(); } 2、树枝构件: ...

2020-12-07 · 2 min · 240 words · Hank

Java设计模式(11)-装饰模式

我同事大头,喜欢吃面。这天大头去一家面馆吃面,由于大头饭量大,面没吃完觉得不够,有点了两个卤蛋,一会儿再叫了一份青菜,最后时刻又加了一碗豆浆。面我们称为主食,必须要点,其他称为小吃,可以随意组合点单也可不点,假如面的价格是8元,卤蛋2元一个,青菜5元一份,豆浆3元一碗,现在要计算总共价格,如何设计?要求具备良好的扩展性和维护性。 1. 大头吃面第一版 既然面馆提供这么多好吃的东西,最容易的想到的就是在类中增加方法,初版设计类图如下: Figure 1. 大头吃面第一版设计类图 这种方式,将点主食、小吃分别加到管理类中,然后计算总价,实现代码如下: class NoodleRestaurant { private int totalPrice; void orderNoodles(int count) { System.out.println("点了" + count + "份面"); totalPrice += 8 * count; } void addEggs(int count) { System.out.println("点了" + count + "份鸡蛋"); totalPrice += 2 * count; } void addVegetables(int count) { System.out.println("点了" + count + "份青菜"); totalPrice += 5 * count; } void addSoySauce(int count) { System.out.println("点了" + count + "份豆浆"); totalPrice += 3 * count; } public int getTotalPrice() { return totalPrice; } } ...

2020-11-05 · 4 min · 744 words · Hank

Java设计模式(10)-桥接模式

有这样一个需求:手机都有发短信、打电话等功能,而且手机有各种各样的样式,比如翻盖手机、直板手机、滑盖手机、折叠手机等等,同时,手机还有各种各样的品牌,苹果、洛基亚、华为、小米、VIVO等等,我们应该如何来设计类之间的关系,并保持良好的可扩展性呢? 1. 糟糕的继承设计方式 通常,我们会使用简单的继承的方式来设计类,因为继承的方式我们最容易想到,继承设计的类图如下: Figure 1. 继承的方式设计类图 上边的类设计,从手机样式的维度出发,将手机划分为翻盖手机、直板手机,他们继承自"手机"父类,下边的各个品牌在分别按照手机样式分类,继承自样式父类。这样的设计,表面上能够解决问题,但是带来的非常大的缺点: 类非常多,每一种手机样式下,所有的品牌都需要建立这种样式的类,我们将这种现象称为类爆炸; 难以扩展和维护,增加一种手机样式,所有的品牌都需要在这种样式下添加新的类,如果增加一种品牌,那么原有的所有样式下都需要增加新品牌的类,这违反了单一职责原则 [1] 和开闭原则 [1] 前边 面向对象设计模式遵循的原则 一文时我们说过,继承最主要的缺点在于其破坏了类的封装性,父类的修改也会导致子类的变化,子类和父类的依赖关系非常紧密。因此,在考虑使用继承关系时,首先要明确类之间是否是明确的is a的关系,而不是like a,并遵循里式替换原则,另外,合成复用原则也指出:设计类时优先考虑使用合聚合和组用而不是继承。 桥接模式能够很好的解决像这样存在多个维度变化对象(比如这里的手机样式和品牌)的设计问题。 2. 桥接模式介绍 2.1. 桥接模式简介 桥接模式(Bridge Pattern)的定义如下:将抽象部分与它的实现部分分离,使他们都可以独立变化。 桥接模式包括如下的几种角色: 抽象化(Abstraction)角色:抽象类,并包含一个对实现化对象的引用 扩展抽象化(Refined Abstraction)角色:抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法 实现化(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用 具体实现化(Concrete Implementor)角色:实现化角色接口的具体实现 几种觉色之间的关系如下图所示: Figure 2. 桥接模式的结构类图 抽象化角色、实现化角色将桥接模式结构分为两个部分:抽象部分由抽象化角色作为父类,实现部分由实现化角色为父类。然后抽象化角色依赖了实现化角色,中间的依赖线类似一座桥梁,很形象的说明了桥接模式的命名由来。从上边的类图可以看出,桥接模式是合成复用原则的体现,通过抽象化角色聚合实现化角色,将双方联系起来,这样既便于扩展,又能减少类的数量。 桥接模式的优点: 多个变化维度拆分为抽象和实现两个部分,都可以独立变化,程序易扩展 遵循开闭原则,添加新类时,仅需要修改客户端调用代码,而其他代码不需要变化,对扩展开放对修改关闭 遵循合成复用原则 注意,桥接模式定义中说的抽象与实现分离,并不是说将抽象类和其派生子类分离,这样做并无意义。这只是告诉我们在设计类时,首先需要明确可能变化的维度,将多个变化的维度都单独抽取出来,使得他们都可以独立的扩展。 比如前边的设计样式和手机品牌,这两个维度都会变化,可以将其抽象出来,手机的基本功能如打电话、发短信可以放到品牌中来实现,它就是作为实现部分,而手机样式作为抽象部分实现手机样式的不同功能。具体类图如下; Figure 3. 桥接模式解决手机设计问题 现在,我们编码来实现上述手机问题。 2.2. 桥接模式示例代码 1、首先,抽象手机品牌接口PhoneBrand,它作为实现化角色(Implementor): 实现化角色 // 手机品牌接口 interface PhoneBrand { // 获取品牌名称 String getName(); // 打电话 default String call() { return "用" + this.getName() + "手机打电话"; } } ...

2020-11-04 · 2 min · 323 words · Hank

Java设计模式(9)-适配器模式

模式来源于生活,适配器模式最能直观的体现这一点。适配器,就是将多个原本不能共同工作的产品经过一定的处理,使得他们能够彼此协同工作的一种装置。现实生活中,有很多适配器的例子,如常见的电源适配器,可以将220V的交流电压转换为手机、电脑等电器所需的低电压;又比如,苹果手机的type-C耳机插孔,不能使用3.5mm的耳机,怎么办呢?可以增加一个耳机转换器,它就是一个适配器,能够将3.5mm耳机成功用于Type-C接口的iPhone上;还有各种转换器,如HDMI转VGA、Type-C转USB等等,都是适配器。 1. 适配器模式简介 适配器模式(Adapter pattern)的定义如下:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。 对于这个定义,需要明确几点: 首先,有一个已经存在的类A,但是不满足我们的需求,我们需要的是类B; 其次,A、B完成的功能相似,或者说,类B的功能类A其实都已经实现了,我们不想再实现一次; 第三,A、B不能共同完成工作,我们需要经过适配将A、B变得可以协同工作。 所以,什么时候可以考虑使用适配器模式? 当两个类所做的事情相同或相似,但是他们不能协同工作,此时可以通过适配器模式,但是有一个前提,就是两个类都不可能修改,如果他们频繁变动,那么首先要考虑的是重构代码来使他们统一。比如,遗留的系统、老的系统组件,他们中已经实现的功能,几乎不会修改,可以使用适配器模式。 适配器模式的主要优点如下: 客户端通过适配器可以透明地调用目标接口,更简单、直接; 复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类; 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。 适配器模式有三个主要角色: 目标(Target)接口:当前业务系统所期待的类,可以是抽象类或接口 被适配者(Adaptee):即现有系统或组件中已经存在的类,实现了我们所需的功能 适配器(Adapter):一个转换器,将被适配者转换为目标,让客户可以按目标接口透明的使用被适配者的功能 在java语言中,不能多继承,所以适配器模式中,我们一般会将目标申明为接口,然后通过继承或者依赖被适配者的方式来实现适配器。 适配器模式有三种形式:类适配器、对象适配器、接口适配器。 2. 类适配器模式 顾名思义,类适配器适配的是类。要想使用类的功能,java中我们只能用继承的方式,但是这违背了合成复用原则原则。尽量不要使用继承,而是使用合成的方式。 因此,类适配器最典型的缺点是,如果要替换适配器,将会变得很复杂,这种方式应该尽量不用。 这种形式的类结构如下图所示: Figure 1. 类适配器模式 Aapater类继承被适配者Adaptee,并实现目标接口Target,因此客户端可以透明的使用Target接口的api,而Adapter内部则使用Adaptee来实现Target定义的相关功能。 举个例子:用手机充电器给手机充电,假设手机需要使用5V的直流电才可以充电,但是家用电都是220V交流电,我们看看如何使用适配器模式解决这个问题。 1、首先,我们有220v的交流电,这就是被适配者Adaptee: 被适配者 class AlternatingCurrent { int outputVoltage() { (1) return 220; } } 1 被适配者拥有一个输出220V的交流电方法。 2、然后,我们新定义一个输出5V直流电的接口,这就是目标接口: 目标接口 interface DirectCurrent { int outputVoltage(); } 3、最后,定义适配器,进行适配: 适配器 class PhoneCharger extends AlternatingCurrent implements DirectCurrent { @Override public int outputVoltage() { int voltage = super.outputVoltage(); // 电压转换处理 return voltage / 44; (1) } } ...

2020-08-24 · 2 min · 299 words · Hank

Java设计模式(8)-建造者模式

建造者模式(Builder Pattern),旨在解决复杂对象的创建过程,将对象与其创建过程分离,通过相同的创建过程创建多种不同的对象。 生活中有很多复杂对象构建的例子。例如,汽车生产线的组装,都要经过车架、车身、变速箱、发动机、车轮等零部件的组装过程,如果将这些组装过程单独抽取出来,将组装结果——汽车独立出来,那么这个组装过程就可以重用,比如组装不同颜色、不同品牌的各种汽车。再比如,建房子,需要经过打地基、搭框架、砌墙、封顶等一系列复杂过程,如果对这个过程进行抽象,将房子和其建造过程分离,则更灵活,如通过这个建造过程可以建造高层建筑、低层建筑、普通民房等房屋。 1. 什么是建造者模式 建造者模式(Builder Pattern),又叫生成器模式,将复杂对象的创建过程和对象本身进行抽象和分离,使得创建过程可以重用并创建多个不同表现的对象。 优点: 各个具体的建造者相互独立,有利于系统的扩展; 客户端不必知道产品内部组成的细节,便于控制细节风险。 缺点: 产品的创建过程相同才能重用,有一定的局限性; 产品的内部创建过程变化。 2. 建造者模式结构 建造者模式有4个角色: 产品角色(Product):复杂对象,复杂的创建过程中包含多个创建步骤,由具体建造者来实现各个创建步骤的业务逻辑; 抽象建造者(Abstract Builder):抽象产品角色的多个创建步骤,可以是接口和抽象类,通常会定义一个方法来创建复杂产品,一般命名为build; 具体建造者(Concrete Builder):实现抽象建造者中定义的创建步骤逻辑,完成复杂产品的各个部件的具体创建方法; 指挥者(Director):调用抽象建造者对象中的部件构造与组装方法,然后调用build方法完成复杂对象的创建; 他们之间的关系如下图所示: Figure 1. 建造者模式类结构图 Director内部组合了抽象的Builder,用来构建产品;Client依赖Director创建产品,但是需要告诉它使用什么具体的Builder来创建,也就是会依赖具体的构建器. 抽象的Builder有两种形式:抽象类和接口。图中抽象的Builder为抽象类,其内部组合依赖了Product,并在build方法直接返回它;如果抽象Builder为接口,那么内部不会依赖Product,类结构上也会有一些变化,如下图所示: Figure 2. Builder为接口时的类结构图 最显著的区别是,具体构建器需要实现build方法,返回具产品信息。标准设计模式提供的是一种思路,在具体实现的时候有很多形式,但是其核心思想是不变的。 3. 示例 下面我们看一个简单的示例:我们建设生产一部普通汽车,都需要经过组装底盘、组装变速箱、发动机、车架等步骤,如果我们把汽车和它的生产过程分离开,使用建造者模式来实现,该怎么做呢? 1、首先,汽车就是我们需要建造的产品,其定义如下: 产品 class Car { // 发动机 private String motor; // 变速箱 private String gearbox; // 底盘 private String chassis; // 车架 private String frame; } 为了简单,这里省略了getter和setter。这里的产品已经是具体的产品了,实际上,这个产品一般是抽象的产品,下边可能会有多个具体的产品信息。 2、然后,抽象汽车生产过程,定义抽象的建造者: 抽象建造者 abstract class CarBuilder { protected Car car = new Car(); // 构建发动机 abstract void buildMotor(); // 构建变速箱 abstract void buildGearbox(); // 构建底盘 abstract void buildChassis(); // 构建车架 abstract void buildFrame(); public Car build() { return car; } } ...

2020-08-23 · 2 min · 423 words · Hank

Spring扩展原理

Spring的扩展点很多,本文讨论Spring的 BeanPostProcessor、BeanFactoryPostProcessor、ApplicationListener 和 SmartInitializingSingleton 这四个扩展的基础类或接口。 1. Bean后置处理器 bean后置处理器用于bean创建对象初始化前后进行拦截工作,因此它是spring容器扩展必不可少的组件。 1.1. BeanPostProcessor 允许自定义修改新bean实例的工厂钩子,例如,检查标记接口或用代理包装bean。 通常,通过标记接口等填充bean的后处理器将在初始化前实现后处理,而使用代理包装bean的后处理器通常在初始化后实现后处理。 注册表 ApplicationContext 可以在其bean定义中自动检测 BeanPostProcessor bean,并将这些后处理器应用于随后创建的任何bean。一个普通的bean factory允许对后处理器进行编程注册,将它们应用于通过bean工厂创建的所有bean。 顺序 在 ApplicationContext 中自动检测到的 BeanPostProcessor bean将根据 org.springframework.core.PriorityOrdered 和 org.springframework.core.ordered 语义进行排序。相反,以编程方式向 BeanFactory 注册的 BeanPostProcessor bean将按注册顺序应用;通过实现 PriorityOrdered 或 Ordered 接口表示的任何排序语义对于以编程方式注册的后处理器都将被忽略。此外,BeanPostProcessor bean不考虑 @Order 注解。 public interface BeanPostProcessor { /** * 在传入的bean属性设置之后、初始化之前调用。在任何bean初始化回调(如initializengbean的 * afterPropertiesSet或自定义init方法)之前,将此BeanPostProcessor应用于给定的新bean实例。 * bean已经填充了属性值。返回的bean实例可以是原始实例的包装器。 * 默认实现按原样返回给定的bean。 */ @Nullable default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } /** * 在传入的bean初始化完成之后调用 * 在任何bean初始化回调(如initializengbean的afterPropertiesSet或自定义init方法)之后, * 将此BeanPostProcessor应用于给定的新bean实例。bean已经填充了属性值。 * 返回的bean实例可以是原始实例的包装器。 * 对于FactoryBean,将为FactoryBean实例和由FactoryBean创建的对象(从Spring 2.0开始)调用此回调。 * 后处理器可以通过FactoryBean检查的相应bean实例来决定是应用于FactoryBean还是创建的对象, * 或者两者都应用。此回调也将在InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation * 方法触发短路后调用,与所有其他BeanPostProcessor回调不同。 默认实现按原样返回给定的bean。 */ @Nullable default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } } ...

2020-08-12 · 4 min · 804 words · Hank

Java设计模式(7)-原型模式

某些情况下,我们需要重复的创建多个对象,但是这些对象仅仅只有某几个属性不一致,大部分的信息都是相同的,如果使用传统的构造函数来创建对象,我们需要不断的实例化对象,并且反复调用属性的set方法来设置值,而这些值大多都是相同的。有没有一种模式,能够快速而高效的创建这些差别不大的对象呢?这就需要使用到原型模式。 1. 什么是原型模式 原型模式(Prototype Pattern),它的基本思想是:创建一个对象实例作为原型,然后不断的复制(或者叫克隆)这个原型对象来创建该对象的新实例,而不是反复的使用构造函数来实例化对象。 原型模式创建对象,调用者无需关心对象创建细节,只需要调用复制方法,即可得到与原型对象属性相同的新实例,方便而且高效。 举一个最常见的例子,猴王孙悟空本领大,拔下猴毛一吹,就可以得到很多个与自己一模一样的猴子猴孙。这里就可以使用到原型模式,来复制孙悟空。另外,再举个生活中的例子,刚毕业找工作的同学们,都需要填写病打印纸质的简历,但是这些简历信息只有你想要投递的公司信息不一样,其他的信息如个人基本信息、教育经历、工作经验等都是相同的,我们就可以使用原型模式复制简历,然后修改公司信息即可,而无需重复创建多个简历,在一遍遍填写。 2. 原型模式结构 原型模式的结构如下图所示: Figure 1. 原型模式结构图 ` 结构分为三个部分: Prototype: 原型抽象接口,提供复制(clone)方法,以便实现类实现该方法来复制自己 ConcretePrototype: 具体原型对象,实现 Prototype 接口的复制方法来复制自己,从而创建新实例。 Client: 负责调用原型对象的复制方法获得原型对象新实例,并按需修改新实例 3. Java中的Cloneable接口 Java语言提供了一个 Cloneable 接口,这是一个标记型接口,用来表示实现了该接口的对象可以进行克隆,其定义如下: public interface Cloneable { } 真正的实现克隆的逻辑其实是在 Object 类上: public class Object { protected native Object clone() throws CloneNotSupportedException; // …… } 因此,Java实现原型模式比较方便,只需要实现 Cloneable 接口即可,克隆时调用对象自己的 clone 方法即可。但是,clone 方法是一个native实现,其实它仅仅实现了浅拷贝,稍后再细说。 4. 基础示例 接下来,我们编码实现前边所举的猴王孙悟空分身的例子,首先看看常规方式是如何实现的。 4.1. 常规的实现方式 先来编写一个 MonkeyKing 类,它有名称、居住地、技能强度、寿命等属性: class MonkeyKing { // 高强度 static int HIGH_SKILL_STRENGTH = 10; // 普通强度 static int NORMAL_SKILL_STRENGTH = 5; // 姓名 private String name; // 地址 private String address; // 能力强度 private int skillStrength; // 寿命 private int lifetime; // 省略getter、setter } ...

2020-07-15 · 4 min · 653 words · Hank

Java设计模式(6)-抽象工厂模式

如果工厂模式中需要生产多种类型的产品,那么工厂方法模式就适合了,需要用到抽象工厂模式。 1. 简介 抽象工厂模式,定义抽象工厂,并将产品生产延迟到具体工厂实现中,而它生产的产品都是同种类型的。比如,电视机工厂生产的都是电视机,电视机是一个抽象产品,而它的具体实现有黑白电视、彩色电视、液晶电视等等,但是都属于同一种产品类型-电视机。 如果现在电视机工厂不仅生产电视,还能生产空调等其他电器了,那么,我们称电视机、空调等不同类型的产品为产品簇,代表不同类型的多种产品。 Figure 1. 产品簇示意图 现在,工厂方法模式不适用了,我们需要使用抽象工厂模式。 抽象工厂模式(Abstract Factory Pattern)的定义如下:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类,访问类无须指定具体的类就可以得到同组的不同类型的产品。 抽象工厂模式升级了工厂方法模式,将原本仅能创建同种类型的产品升级为可以创建多种类型的产品. 优点 可以使用工厂模式生产多个产品簇,新增同类型产品时只需要新增一个具体工厂实现,不需要修改客户端代码,遵循开闭原则。 缺点 当产品簇中需要新增产品时,会修改原产品簇的所有工厂类,不符合开闭原则。 因此,抽象工厂模式在使用时应该酌情考虑其倾斜性,新增同类产品时,遵循开闭原则;但是,产品簇添加新的产品,所有的工厂类都需要添加生产新产品的方法。 2. 适用场景 抽象工厂模式通常适用于以下场景: 当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、电风扇、洗衣机、空调等 系统中有多个产品族,但每次只使用其中的某一族产品,比如有人只喜欢穿某一个品牌的衣服和鞋 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构 3. 结构 抽象工厂模式的结构与工厂方法模式一样,同样具有抽象工厂、抽象产品、具体工厂、具体产品四大角色: 抽象工厂: 定义产品创建的接口,具有多个产品创建方法,这些方法负责创建不同的产品; 具体工厂: 实现抽象工厂的产品创建方法,用于具体实现如何创建这些产品簇中不同的产品; 抽象产品: 定义产品的规范,如产品规格、属性、功能等;就有多个抽象产品,组成产品簇; 具体产品: 抽象产品的具体实现,具体产品与具体工厂是多对一的关系,即一个具体工厂可以生产多个具体产品。 Figure 2. 抽象工厂模式结构 可以看到,AbstractFactory 是顶层抽象工厂接口,定义了创建不同产品簇的方法。不同的产品簇抽象出不同的产品接口,如图中的 Product1、Product2。 如果要添加一个产品的具体实现,那么客户端和工厂不需要改动,只需要新增一个具体工厂即可,那么结构变成了这样: Figure 3. 新增具体产品时的类结构变化 图中紫色部分是需要新加的部分,原工厂和客户端不需要做改动,遵循开闭原则。 但是,如果现在要增加新的产品线,即是说产品簇中要新加一个新产品,那么原工厂代码需要添加生产这个新产品的方法,而且客户能也需要为调用这个新方法而做出修改,此时的类结构如下图: Figure 4. 产品簇中添加新产品时的类结构变化 图中紫色部分为新加的产品,而原来的工厂添加新的方法(红色部分)来生产这个新的 Product3 产品,客户端也需要修改代码来调用工厂的新方法。 4. 与工厂方法模式的比较 抽象工厂模式与工厂方法模式在结构上其实是相同的,但是他们创建的产品类型不同: 工厂方法模式仅能创建某一类产品,而抽象工厂模式可以创建多种类型的产品; 工厂方法模式如果能够支持创建多种类型的产品,那么它会演变为抽象工厂模式;同理,如果抽象工厂模式仅创建同一类型的产品,它就变成了工厂方法模式 抽象工厂模式具有工厂方法模式的优点和缺点 5. 示例 接下来看一个实例。现在有一个电器工厂,可以生产电视机、空调,假设电视机有黑白、彩色两种,空调有挂式和柜式空调两种。工厂既能生产电视机,也能生产空调。我们用抽象工厂来设计,创建两个具体的工厂,工厂A来生产黑白电视机和挂式空调,而工厂B来生产彩电和柜式空调。类的结构如下: Figure 5. 电器工厂设计类图...

2020-06-09 · 3 min · 612 words · Hank