change line sep
This commit is contained in:
parent
720d24566b
commit
4e38adb32d
@ -1,18 +1,18 @@
|
||||
### 版本情况
|
||||
|
||||
JDK版本: openjdk_8_201
|
||||
hutool版本: 5.X.X(请确保最新尝试是否还有问题)
|
||||
|
||||
### 问题描述(包括截图)
|
||||
|
||||
1. 复现代码
|
||||
|
||||
```java
|
||||
Console.log("报错了");
|
||||
```
|
||||
|
||||
2. 堆栈信息
|
||||
|
||||
3. 测试涉及到的文件(注意脱密)
|
||||
|
||||
### 版本情况
|
||||
|
||||
JDK版本: openjdk_8_201
|
||||
hutool版本: 5.X.X(请确保最新尝试是否还有问题)
|
||||
|
||||
### 问题描述(包括截图)
|
||||
|
||||
1. 复现代码
|
||||
|
||||
```java
|
||||
Console.log("报错了");
|
||||
```
|
||||
|
||||
2. 堆栈信息
|
||||
|
||||
3. 测试涉及到的文件(注意脱密)
|
||||
|
||||
比如报错的Excel文件,有问题的图片等。
|
@ -1,10 +1,10 @@
|
||||
#### 说明
|
||||
|
||||
1. 请确认你提交的PR是到'v5-dev'分支,否则我会手动修改代码并关闭PR。
|
||||
2. 请确认没有更改代码风格(如tab缩进)
|
||||
3. 新特性添加请确认注释完备,如有必要,请在src/test/java下添加Junit测试用例
|
||||
|
||||
### 修改描述(包括说明bug修复或者添加新特性)
|
||||
|
||||
1. [bug修复] balabala……
|
||||
#### 说明
|
||||
|
||||
1. 请确认你提交的PR是到'v5-dev'分支,否则我会手动修改代码并关闭PR。
|
||||
2. 请确认没有更改代码风格(如tab缩进)
|
||||
3. 新特性添加请确认注释完备,如有必要,请在src/test/java下添加Junit测试用例
|
||||
|
||||
### 修改描述(包括说明bug修复或者添加新特性)
|
||||
|
||||
1. [bug修复] balabala……
|
||||
2. [新特性] balabala……
|
6
.github/FUNDING.yml
vendored
6
.github/FUNDING.yml
vendored
@ -1,4 +1,4 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [looly]
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [looly]
|
||||
custom: https://gitee.com/loolly/hutool
|
34
.github/ISSUE_TEMPLATE.md
vendored
34
.github/ISSUE_TEMPLATE.md
vendored
@ -1,18 +1,18 @@
|
||||
### 版本情况
|
||||
|
||||
JDK版本: openjdk_8_201
|
||||
hutool版本: 5.X.X(请确保最新尝试是否还有问题)
|
||||
|
||||
### 问题描述(包括截图)
|
||||
|
||||
1. 复现代码
|
||||
|
||||
```java
|
||||
Console.log("报错了");
|
||||
```
|
||||
|
||||
2. 堆栈信息
|
||||
|
||||
3. 测试涉及到的文件(注意脱密)
|
||||
|
||||
### 版本情况
|
||||
|
||||
JDK版本: openjdk_8_201
|
||||
hutool版本: 5.X.X(请确保最新尝试是否还有问题)
|
||||
|
||||
### 问题描述(包括截图)
|
||||
|
||||
1. 复现代码
|
||||
|
||||
```java
|
||||
Console.log("报错了");
|
||||
```
|
||||
|
||||
2. 堆栈信息
|
||||
|
||||
3. 测试涉及到的文件(注意脱密)
|
||||
|
||||
比如报错的Excel文件,有问题的图片等。
|
18
.github/PULL_REQUEST_TEMPLATE.md
vendored
18
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,10 +1,10 @@
|
||||
#### 说明
|
||||
|
||||
1. 请确认你提交的PR是到'v5-dev'分支,否则我会手动修改代码并关闭PR。
|
||||
2. 请确认没有更改代码风格(如tab缩进)
|
||||
3. 新特性添加请确认注释完备,如有必要,请在src/test/java下添加Junit测试用例
|
||||
|
||||
### 修改描述(包括说明bug修复或者添加新特性)
|
||||
|
||||
1. [bug修复] balabala……
|
||||
#### 说明
|
||||
|
||||
1. 请确认你提交的PR是到'v5-dev'分支,否则我会手动修改代码并关闭PR。
|
||||
2. 请确认没有更改代码风格(如tab缩进)
|
||||
3. 新特性添加请确认注释完备,如有必要,请在src/test/java下添加Junit测试用例
|
||||
|
||||
### 修改描述(包括说明bug修复或者添加新特性)
|
||||
|
||||
1. [bug修复] balabala……
|
||||
2. [新特性] balabala……
|
66
.gitignore
vendored
66
.gitignore
vendored
@ -1,33 +1,33 @@
|
||||
# Eclipse
|
||||
.project
|
||||
.classpath
|
||||
.settings/
|
||||
|
||||
# Maven
|
||||
target/
|
||||
dependency-reduced-pom.xml
|
||||
pom.xml.versionsBackup
|
||||
.factorypath
|
||||
|
||||
# Gradle
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
#IDEA
|
||||
# idea ignore
|
||||
.idea/
|
||||
*.ipr
|
||||
*.iml
|
||||
*.iws
|
||||
|
||||
# temp ignore
|
||||
*.log
|
||||
*.cache
|
||||
*.diff
|
||||
*.patch
|
||||
*.tmp
|
||||
.jython_cache/
|
||||
|
||||
# system ignore
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
# Eclipse
|
||||
.project
|
||||
.classpath
|
||||
.settings/
|
||||
|
||||
# Maven
|
||||
target/
|
||||
dependency-reduced-pom.xml
|
||||
pom.xml.versionsBackup
|
||||
.factorypath
|
||||
|
||||
# Gradle
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
#IDEA
|
||||
# idea ignore
|
||||
.idea/
|
||||
*.ipr
|
||||
*.iml
|
||||
*.iws
|
||||
|
||||
# temp ignore
|
||||
*.log
|
||||
*.cache
|
||||
*.diff
|
||||
*.patch
|
||||
*.tmp
|
||||
.jython_cache/
|
||||
|
||||
# system ignore
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
48
.travis.yml
48
.travis.yml
@ -1,24 +1,24 @@
|
||||
language: java
|
||||
|
||||
sudo: false # faster builds
|
||||
|
||||
install: true
|
||||
|
||||
jdk:
|
||||
- openjdk8
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- '$HOME/.m2'
|
||||
|
||||
script:
|
||||
- export TZ=Asia/Shanghai
|
||||
- mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V
|
||||
- mvn cobertura:cobertura -Dcobertura.report.format=xml -Dmaven.javadoc.skip.true
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
language: java
|
||||
|
||||
sudo: false # faster builds
|
||||
|
||||
install: true
|
||||
|
||||
jdk:
|
||||
- openjdk8
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- '$HOME/.m2'
|
||||
|
||||
script:
|
||||
- export TZ=Asia/Shanghai
|
||||
- mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V
|
||||
- mvn cobertura:cobertura -Dcobertura.report.format=xml -Dmaven.javadoc.skip.true
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
|
2090
CHANGELOG.md
2090
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
252
LICENSE
252
LICENSE
@ -1,127 +1,127 @@
|
||||
木兰宽松许可证, 第2版
|
||||
|
||||
木兰宽松许可证, 第2版
|
||||
2020年1月 http://license.coscl.org.cn/MulanPSL2
|
||||
|
||||
|
||||
您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第2版(“本许可证”)的如下条款的约束:
|
||||
|
||||
0. 定义
|
||||
|
||||
“软件”是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。
|
||||
|
||||
“贡献”是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。
|
||||
|
||||
“贡献者”是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。
|
||||
|
||||
“法人实体”是指提交贡献的机构及其“关联实体”。
|
||||
|
||||
“关联实体”是指,对“本许可证”下的行为方而言,控制、受控制或与其共同受控制的机构,此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。
|
||||
|
||||
1. 授予版权许可
|
||||
|
||||
每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。
|
||||
|
||||
2. 授予专利许可
|
||||
|
||||
每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括对“贡献”的修改或包含“贡献”的其他结合。如果您或您的“关联实体”直接或间接地,就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。
|
||||
|
||||
3. 无商标许可
|
||||
|
||||
“本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用除外。
|
||||
|
||||
4. 分发限制
|
||||
|
||||
您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。
|
||||
|
||||
5. 免责声明与责任限制
|
||||
|
||||
“软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。
|
||||
|
||||
6. 语言
|
||||
“本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致,以中文版为准。
|
||||
|
||||
条款结束
|
||||
|
||||
如何将木兰宽松许可证,第2版,应用到您的软件
|
||||
|
||||
如果您希望将木兰宽松许可证,第2版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步:
|
||||
|
||||
1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字;
|
||||
|
||||
2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中;
|
||||
|
||||
3, 请将如下声明文本放入每个源文件的头部注释中。
|
||||
|
||||
Copyright (c) [Year] [name of copyright holder]
|
||||
[Software Name] is licensed under Mulan PSL v2.
|
||||
You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
You may obtain a copy of Mulan PSL v2 at:
|
||||
http://license.coscl.org.cn/MulanPSL2
|
||||
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
See the Mulan PSL v2 for more details.
|
||||
|
||||
|
||||
Mulan Permissive Software License,Version 2
|
||||
|
||||
Mulan Permissive Software License,Version 2 (Mulan PSL v2)
|
||||
January 2020 http://license.coscl.org.cn/MulanPSL2
|
||||
|
||||
Your reproduction, use, modification and distribution of the Software shall be subject to Mulan PSL v2 (this License) with the following terms and conditions:
|
||||
|
||||
0. Definition
|
||||
|
||||
Software means the program and related documents which are licensed under this License and comprise all Contribution(s).
|
||||
|
||||
Contribution means the copyrightable work licensed by a particular Contributor under this License.
|
||||
|
||||
Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License.
|
||||
|
||||
Legal Entity means the entity making a Contribution and all its Affiliates.
|
||||
|
||||
Affiliates means entities that control, are controlled by, or are under common control with the acting entity under this License, ‘control’ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity.
|
||||
|
||||
1. Grant of Copyright License
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or distribute its Contribution, with modification or not.
|
||||
|
||||
2. Grant of Patent License
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to make, have made, use, offer for sale, sell, import or otherwise transfer its Contribution, where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, or by combination of the Contribution with the Software to which the Contribution was contributed. The patent license shall not apply to any modification of the Contribution, and any other combination which includes the Contribution. If you or your Affiliates directly or indirectly institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that the Software or any Contribution in it infringes patents, then any patent license granted to you under this License for the Software shall terminate as of the date such litigation or activity is filed or taken.
|
||||
|
||||
3. No Trademark License
|
||||
|
||||
No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in Section 4.
|
||||
|
||||
4. Distribution Restriction
|
||||
|
||||
You may distribute the Software in any medium with or without modification, whether in source or executable forms, provided that you provide recipients with a copy of this License and retain copyright, patent, trademark and disclaimer statements in the Software.
|
||||
|
||||
5. Disclaimer of Warranty and Limitation of Liability
|
||||
|
||||
THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THE SOFTWARE OR THE CONTRIBUTION IN IT, NO MATTER HOW IT’S CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
6. Language
|
||||
|
||||
THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL.
|
||||
|
||||
END OF THE TERMS AND CONDITIONS
|
||||
|
||||
How to Apply the Mulan Permissive Software License,Version 2 (Mulan PSL v2) to Your Software
|
||||
|
||||
To apply the Mulan PSL v2 to your work, for easy identification by recipients, you are suggested to complete following three steps:
|
||||
|
||||
i Fill in the blanks in following statement, including insert your software name, the year of the first publication of your software, and your name identified as the copyright owner;
|
||||
|
||||
ii Create a file named “LICENSE” which contains the whole context of this License in the first directory of your software package;
|
||||
|
||||
iii Attach the statement to the appropriate annotated syntax at the beginning of each source file.
|
||||
|
||||
|
||||
Copyright (c) [Year] [name of copyright holder]
|
||||
[Software Name] is licensed under Mulan PSL v2.
|
||||
You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
You may obtain a copy of Mulan PSL v2 at:
|
||||
http://license.coscl.org.cn/MulanPSL2
|
||||
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
木兰宽松许可证, 第2版
|
||||
|
||||
木兰宽松许可证, 第2版
|
||||
2020年1月 http://license.coscl.org.cn/MulanPSL2
|
||||
|
||||
|
||||
您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第2版(“本许可证”)的如下条款的约束:
|
||||
|
||||
0. 定义
|
||||
|
||||
“软件”是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。
|
||||
|
||||
“贡献”是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。
|
||||
|
||||
“贡献者”是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。
|
||||
|
||||
“法人实体”是指提交贡献的机构及其“关联实体”。
|
||||
|
||||
“关联实体”是指,对“本许可证”下的行为方而言,控制、受控制或与其共同受控制的机构,此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。
|
||||
|
||||
1. 授予版权许可
|
||||
|
||||
每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。
|
||||
|
||||
2. 授予专利许可
|
||||
|
||||
每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括对“贡献”的修改或包含“贡献”的其他结合。如果您或您的“关联实体”直接或间接地,就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。
|
||||
|
||||
3. 无商标许可
|
||||
|
||||
“本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用除外。
|
||||
|
||||
4. 分发限制
|
||||
|
||||
您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。
|
||||
|
||||
5. 免责声明与责任限制
|
||||
|
||||
“软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。
|
||||
|
||||
6. 语言
|
||||
“本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致,以中文版为准。
|
||||
|
||||
条款结束
|
||||
|
||||
如何将木兰宽松许可证,第2版,应用到您的软件
|
||||
|
||||
如果您希望将木兰宽松许可证,第2版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步:
|
||||
|
||||
1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字;
|
||||
|
||||
2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中;
|
||||
|
||||
3, 请将如下声明文本放入每个源文件的头部注释中。
|
||||
|
||||
Copyright (c) [Year] [name of copyright holder]
|
||||
[Software Name] is licensed under Mulan PSL v2.
|
||||
You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
You may obtain a copy of Mulan PSL v2 at:
|
||||
http://license.coscl.org.cn/MulanPSL2
|
||||
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
See the Mulan PSL v2 for more details.
|
||||
|
||||
|
||||
Mulan Permissive Software License,Version 2
|
||||
|
||||
Mulan Permissive Software License,Version 2 (Mulan PSL v2)
|
||||
January 2020 http://license.coscl.org.cn/MulanPSL2
|
||||
|
||||
Your reproduction, use, modification and distribution of the Software shall be subject to Mulan PSL v2 (this License) with the following terms and conditions:
|
||||
|
||||
0. Definition
|
||||
|
||||
Software means the program and related documents which are licensed under this License and comprise all Contribution(s).
|
||||
|
||||
Contribution means the copyrightable work licensed by a particular Contributor under this License.
|
||||
|
||||
Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License.
|
||||
|
||||
Legal Entity means the entity making a Contribution and all its Affiliates.
|
||||
|
||||
Affiliates means entities that control, are controlled by, or are under common control with the acting entity under this License, ‘control’ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity.
|
||||
|
||||
1. Grant of Copyright License
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or distribute its Contribution, with modification or not.
|
||||
|
||||
2. Grant of Patent License
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to make, have made, use, offer for sale, sell, import or otherwise transfer its Contribution, where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, or by combination of the Contribution with the Software to which the Contribution was contributed. The patent license shall not apply to any modification of the Contribution, and any other combination which includes the Contribution. If you or your Affiliates directly or indirectly institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that the Software or any Contribution in it infringes patents, then any patent license granted to you under this License for the Software shall terminate as of the date such litigation or activity is filed or taken.
|
||||
|
||||
3. No Trademark License
|
||||
|
||||
No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in Section 4.
|
||||
|
||||
4. Distribution Restriction
|
||||
|
||||
You may distribute the Software in any medium with or without modification, whether in source or executable forms, provided that you provide recipients with a copy of this License and retain copyright, patent, trademark and disclaimer statements in the Software.
|
||||
|
||||
5. Disclaimer of Warranty and Limitation of Liability
|
||||
|
||||
THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THE SOFTWARE OR THE CONTRIBUTION IN IT, NO MATTER HOW IT’S CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
6. Language
|
||||
|
||||
THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL.
|
||||
|
||||
END OF THE TERMS AND CONDITIONS
|
||||
|
||||
How to Apply the Mulan Permissive Software License,Version 2 (Mulan PSL v2) to Your Software
|
||||
|
||||
To apply the Mulan PSL v2 to your work, for easy identification by recipients, you are suggested to complete following three steps:
|
||||
|
||||
i Fill in the blanks in following statement, including insert your software name, the year of the first publication of your software, and your name identified as the copyright owner;
|
||||
|
||||
ii Create a file named “LICENSE” which contains the whole context of this License in the first directory of your software package;
|
||||
|
||||
iii Attach the statement to the appropriate annotated syntax at the beginning of each source file.
|
||||
|
||||
|
||||
Copyright (c) [Year] [name of copyright holder]
|
||||
[Software Name] is licensed under Mulan PSL v2.
|
||||
You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
You may obtain a copy of Mulan PSL v2 at:
|
||||
http://license.coscl.org.cn/MulanPSL2
|
||||
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
See the Mulan PSL v2 for more details.
|
416
README.md
416
README.md
@ -1,209 +1,209 @@
|
||||
<p align="center">
|
||||
<a href="https://hutool.cn/"><img src="https://cdn.jsdelivr.net/gh/looly/hutool-site/images/logo.jpg" width="45%"></a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<strong>A set of tools that keep Java sweet.</strong>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://hutool.cn">https://hutool.cn/</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a target="_blank" href="https://search.maven.org/artifact/cn.hutool/hutool-all">
|
||||
<img src="https://img.shields.io/maven-central/v/cn.hutool/hutool-all.svg?label=Maven%20Central" />
|
||||
</a>
|
||||
<a target="_blank" href="https://license.coscl.org.cn/MulanPSL2/">
|
||||
<img src="https://img.shields.io/:license-MulanPSL2-blue.svg" />
|
||||
</a>
|
||||
<a target="_blank" href="https://www.oracle.com/technetwork/java/javase/downloads/index.html">
|
||||
<img src="https://img.shields.io/badge/JDK-8+-green.svg" />
|
||||
</a>
|
||||
<a target="_blank" href="https://travis-ci.org/looly/hutool">
|
||||
<img src="https://travis-ci.org/looly/hutool.svg?branch=v4-master" />
|
||||
</a>
|
||||
<a href="https://www.codacy.com/app/looly/hutool?utm_source=github.com&utm_medium=referral&utm_content=looly/hutool&utm_campaign=Badge_Grade">
|
||||
<img src="https://api.codacy.com/project/badge/Grade/3e1b8a70248c46579b7b0d01d60c6377"/>
|
||||
</a>
|
||||
<a href="https://codecov.io/gh/looly/hutool">
|
||||
<img src="https://codecov.io/gh/looly/hutool/branch/v4-master/graph/badge.svg" />
|
||||
</a>
|
||||
<a target="_blank" href="https://gitter.im/hutool/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge">
|
||||
<img src="https://badges.gitter.im/hutool/Lobby.svg" />
|
||||
</a>
|
||||
<a target="_blank" href='https://gitee.com/loolly/hutool/stargazers'>
|
||||
<img src='https://gitee.com/loolly/hutool/badge/star.svg?theme=gvp' alt='star'/>
|
||||
</a>
|
||||
<a target="_blank" href='https://github.com/looly/hutool'>
|
||||
<img src="https://img.shields.io/github/stars/looly/hutool.svg?style=social" alt="github star"/>
|
||||
</a>
|
||||
<a target="_blank" href='https://app.netlify.com/sites/hutool/deploys'>
|
||||
<img src="https://api.netlify.com/api/v1/badges/7e0824f9-5f9a-4df0-89dd-b2fccfbeccb1/deploy-status" alt="netlify"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<br/>
|
||||
<p align="center">
|
||||
<a href="https://qm.qq.com/cgi-bin/qm/qr?k=0wwldaU0E8r-ZzHl_wma33W7420zwXYi&jump_from=webapi"><img src="https://img.shields.io/badge/QQ%E7%BE%A4%E2%91%A4-956375658-orange"/></a>
|
||||
</p>
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
[**English Documentation**](README-EN.md)
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
## 简介
|
||||
Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。
|
||||
|
||||
Hutool中的工具方法来自每个用户的精雕细琢,它涵盖了Java开发底层代码中的方方面面,它既是大型项目开发中解决小问题的利器,也是小型项目中的效率担当;
|
||||
|
||||
Hutool是项目中“util”包友好的替代,它节省了开发人员对项目中公用类和公用工具方法的封装时间,使开发专注于业务,同时可以最大限度的避免封装不完善带来的bug。
|
||||
|
||||
### Hutool名称的由来
|
||||
|
||||
Hutool = Hu + tool,是原公司项目底层代码剥离后的开源库,“Hu”是公司名称的表示,tool表示工具。Hutool谐音“糊涂”,一方面简洁易懂,一方面寓意“难得糊涂”。
|
||||
|
||||
### Hutool如何改变我们的coding方式
|
||||
|
||||
Hutool的目标是使用一个工具方法代替一段复杂代码,从而最大限度的避免“复制粘贴”代码的问题,彻底改变我们写代码的方式。
|
||||
|
||||
以计算MD5为例:
|
||||
|
||||
- 【以前】打开搜索引擎 -> 搜“Java MD5加密” -> 打开某篇博客-> 复制粘贴 -> 改改好用
|
||||
- 【现在】引入Hutool -> SecureUtil.md5()
|
||||
|
||||
Hutool的存在就是为了减少代码搜索成本,避免网络上参差不齐的代码出现导致的bug。
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
## 包含组件
|
||||
一个Java基础工具类,对文件、流、加密解密、转码、正则、线程、XML等JDK方法进行封装,组成各种Util工具类,同时提供以下组件:
|
||||
|
||||
| 模块 | 介绍 |
|
||||
| -------------------|---------------------------------------------------------------------------------- |
|
||||
| hutool-aop | JDK动态代理封装,提供非IOC下的切面支持 |
|
||||
| hutool-bloomFilter | 布隆过滤,提供一些Hash算法的布隆过滤 |
|
||||
| hutool-cache | 简单缓存实现 |
|
||||
| hutool-core | 核心,包括Bean操作、日期、各种Util等 |
|
||||
| hutool-cron | 定时任务模块,提供类Crontab表达式的定时任务 |
|
||||
| hutool-crypto | 加密解密模块,提供对称、非对称和摘要算法封装 |
|
||||
| hutool-db | JDBC封装后的数据操作,基于ActiveRecord思想 |
|
||||
| hutool-dfa | 基于DFA模型的多关键字查找 |
|
||||
| hutool-extra | 扩展模块,对第三方封装(模板引擎、邮件、Servlet、二维码、Emoji、FTP、分词等) |
|
||||
| hutool-http | 基于HttpUrlConnection的Http客户端封装 |
|
||||
| hutool-log | 自动识别日志实现的日志门面 |
|
||||
| hutool-script | 脚本执行封装,例如Javascript |
|
||||
| hutool-setting | 功能更强大的Setting配置文件和Properties封装 |
|
||||
| hutool-system | 系统参数调用封装(JVM信息等) |
|
||||
| hutool-json | JSON实现 |
|
||||
| hutool-captcha | 图片验证码实现 |
|
||||
| hutool-poi | 针对POI中Excel和Word的封装 |
|
||||
| hutool-socket | 基于Java的NIO和AIO的Socket封装 |
|
||||
|
||||
可以根据需求对每个模块单独引入,也可以通过引入`hutool-all`方式引入所有模块。
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
## 文档
|
||||
|
||||
[中文文档](https://www.hutool.cn/docs/)
|
||||
|
||||
[参考API](https://apidoc.gitee.com/loolly/hutool/)
|
||||
|
||||
[视频介绍](https://www.bilibili.com/video/BV1bQ4y1M7d9?p=2)
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
## 安装
|
||||
|
||||
### Maven
|
||||
在项目的pom.xml的dependencies中加入以下内容:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.5.8</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### Gradle
|
||||
```
|
||||
compile 'cn.hutool:hutool-all:5.5.8'
|
||||
```
|
||||
|
||||
### 非Maven项目
|
||||
|
||||
点击以下任一链接,下载`hutool-all-X.X.X.jar`即可:
|
||||
|
||||
- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.5.8/)
|
||||
- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.5.8/)
|
||||
|
||||
> 注意
|
||||
> Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。
|
||||
> 如果你的项目使用JDK7,请使用Hutool 4.x版本
|
||||
|
||||
### 编译安装
|
||||
|
||||
访问Hutool的Gitee主页:[https://gitee.com/loolly/hutool](https://gitee.com/loolly/hutool) 下载整个项目源码(v5-master或v5-dev分支都可)然后进入Hutool项目目录执行:
|
||||
|
||||
```sh
|
||||
./hutool.sh install
|
||||
```
|
||||
|
||||
然后就可以使用Maven引入了。
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
## 添砖加瓦
|
||||
|
||||
### 分支说明
|
||||
|
||||
Hutool的源码分为两个分支,功能如下:
|
||||
|
||||
| 分支 | 作用 |
|
||||
|-----------|---------------------------------------------------------------|
|
||||
| v5-master | 主分支,release版本使用的分支,与中央库提交的jar一致,不接收任何pr或修改 |
|
||||
| v5-dev | 开发分支,默认为下个版本的SNAPSHOT版本,接受修改或pr |
|
||||
|
||||
### 提供bug反馈或建议
|
||||
|
||||
提交问题反馈请说明正在使用的JDK版本呢、Hutool版本和相关依赖库版本。
|
||||
|
||||
- [Gitee issue](https://gitee.com/loolly/hutool/issues)
|
||||
- [Github issue](https://github.com/looly/hutool/issues)
|
||||
|
||||
|
||||
### 贡献代码的步骤
|
||||
|
||||
1. 在Gitee或者Github上fork项目到自己的repo
|
||||
2. 把fork过去的项目也就是你的项目clone到你的本地
|
||||
3. 修改代码(记得一定要修改v5-dev分支)
|
||||
4. commit后push到自己的库(v5-dev分支)
|
||||
5. 登录Gitee或Github在你首页可以看到一个 pull request 按钮,点击它,填写一些说明信息,然后提交即可。
|
||||
6. 等待维护者合并
|
||||
|
||||
### PR遵照的原则
|
||||
|
||||
Hutool欢迎任何人为Hutool添砖加瓦,贡献代码,不过维护者是一个强迫症患者,为了照顾病人,需要提交的pr(pull request)符合一些规范,规范如下:
|
||||
|
||||
1. 注释完备,尤其每个新增的方法应按照Java文档规范标明方法说明、参数说明、返回值说明等信息,必要时请添加单元测试,如果愿意,也可以加上你的大名。
|
||||
2. Hutool的缩进按照Eclipse(~~不要跟我说IDEA多好用,维护者非常懒,学不会~~,IDEA真香,改了Eclipse快捷键后舒服多了)默认(tab)缩进,所以请遵守(不要和我争执空格与tab的问题,这是一个病人的习惯)。
|
||||
3. 新加的方法不要使用第三方库的方法,Hutool遵循无依赖原则(除非在extra模块中加方法工具)。
|
||||
4. 请pull request到`v5-dev`分支。Hutool在5.x版本后使用了新的分支:`v5-master`是主分支,表示已经发布中央库的版本,这个分支不允许pr,也不允许修改。
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
## 捐赠
|
||||
|
||||
如果你觉得Hutool不错,可以捐赠请维护者吃包辣条~,在此表示感谢^_^。
|
||||
|
||||
点击以下链接,将页面拉到最下方点击“捐赠”即可。
|
||||
|
||||
[前往捐赠](https://gitee.com/loolly/hutool)
|
||||
|
||||
## 公众号
|
||||
|
||||
欢迎关注Hutool合作的公众号。
|
||||
|
||||
<p align="center">
|
||||
<a href="https://hutool.cn/"><img src="https://cdn.jsdelivr.net/gh/looly/hutool-site/images/logo.jpg" width="45%"></a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<strong>A set of tools that keep Java sweet.</strong>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://hutool.cn">https://hutool.cn/</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a target="_blank" href="https://search.maven.org/artifact/cn.hutool/hutool-all">
|
||||
<img src="https://img.shields.io/maven-central/v/cn.hutool/hutool-all.svg?label=Maven%20Central" />
|
||||
</a>
|
||||
<a target="_blank" href="https://license.coscl.org.cn/MulanPSL2/">
|
||||
<img src="https://img.shields.io/:license-MulanPSL2-blue.svg" />
|
||||
</a>
|
||||
<a target="_blank" href="https://www.oracle.com/technetwork/java/javase/downloads/index.html">
|
||||
<img src="https://img.shields.io/badge/JDK-8+-green.svg" />
|
||||
</a>
|
||||
<a target="_blank" href="https://travis-ci.org/looly/hutool">
|
||||
<img src="https://travis-ci.org/looly/hutool.svg?branch=v4-master" />
|
||||
</a>
|
||||
<a href="https://www.codacy.com/app/looly/hutool?utm_source=github.com&utm_medium=referral&utm_content=looly/hutool&utm_campaign=Badge_Grade">
|
||||
<img src="https://api.codacy.com/project/badge/Grade/3e1b8a70248c46579b7b0d01d60c6377"/>
|
||||
</a>
|
||||
<a href="https://codecov.io/gh/looly/hutool">
|
||||
<img src="https://codecov.io/gh/looly/hutool/branch/v4-master/graph/badge.svg" />
|
||||
</a>
|
||||
<a target="_blank" href="https://gitter.im/hutool/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge">
|
||||
<img src="https://badges.gitter.im/hutool/Lobby.svg" />
|
||||
</a>
|
||||
<a target="_blank" href='https://gitee.com/loolly/hutool/stargazers'>
|
||||
<img src='https://gitee.com/loolly/hutool/badge/star.svg?theme=gvp' alt='star'/>
|
||||
</a>
|
||||
<a target="_blank" href='https://github.com/looly/hutool'>
|
||||
<img src="https://img.shields.io/github/stars/looly/hutool.svg?style=social" alt="github star"/>
|
||||
</a>
|
||||
<a target="_blank" href='https://app.netlify.com/sites/hutool/deploys'>
|
||||
<img src="https://api.netlify.com/api/v1/badges/7e0824f9-5f9a-4df0-89dd-b2fccfbeccb1/deploy-status" alt="netlify"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<br/>
|
||||
<p align="center">
|
||||
<a href="https://qm.qq.com/cgi-bin/qm/qr?k=0wwldaU0E8r-ZzHl_wma33W7420zwXYi&jump_from=webapi"><img src="https://img.shields.io/badge/QQ%E7%BE%A4%E2%91%A4-956375658-orange"/></a>
|
||||
</p>
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
[**English Documentation**](README-EN.md)
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
## 简介
|
||||
Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。
|
||||
|
||||
Hutool中的工具方法来自每个用户的精雕细琢,它涵盖了Java开发底层代码中的方方面面,它既是大型项目开发中解决小问题的利器,也是小型项目中的效率担当;
|
||||
|
||||
Hutool是项目中“util”包友好的替代,它节省了开发人员对项目中公用类和公用工具方法的封装时间,使开发专注于业务,同时可以最大限度的避免封装不完善带来的bug。
|
||||
|
||||
### Hutool名称的由来
|
||||
|
||||
Hutool = Hu + tool,是原公司项目底层代码剥离后的开源库,“Hu”是公司名称的表示,tool表示工具。Hutool谐音“糊涂”,一方面简洁易懂,一方面寓意“难得糊涂”。
|
||||
|
||||
### Hutool如何改变我们的coding方式
|
||||
|
||||
Hutool的目标是使用一个工具方法代替一段复杂代码,从而最大限度的避免“复制粘贴”代码的问题,彻底改变我们写代码的方式。
|
||||
|
||||
以计算MD5为例:
|
||||
|
||||
- 【以前】打开搜索引擎 -> 搜“Java MD5加密” -> 打开某篇博客-> 复制粘贴 -> 改改好用
|
||||
- 【现在】引入Hutool -> SecureUtil.md5()
|
||||
|
||||
Hutool的存在就是为了减少代码搜索成本,避免网络上参差不齐的代码出现导致的bug。
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
## 包含组件
|
||||
一个Java基础工具类,对文件、流、加密解密、转码、正则、线程、XML等JDK方法进行封装,组成各种Util工具类,同时提供以下组件:
|
||||
|
||||
| 模块 | 介绍 |
|
||||
| -------------------|---------------------------------------------------------------------------------- |
|
||||
| hutool-aop | JDK动态代理封装,提供非IOC下的切面支持 |
|
||||
| hutool-bloomFilter | 布隆过滤,提供一些Hash算法的布隆过滤 |
|
||||
| hutool-cache | 简单缓存实现 |
|
||||
| hutool-core | 核心,包括Bean操作、日期、各种Util等 |
|
||||
| hutool-cron | 定时任务模块,提供类Crontab表达式的定时任务 |
|
||||
| hutool-crypto | 加密解密模块,提供对称、非对称和摘要算法封装 |
|
||||
| hutool-db | JDBC封装后的数据操作,基于ActiveRecord思想 |
|
||||
| hutool-dfa | 基于DFA模型的多关键字查找 |
|
||||
| hutool-extra | 扩展模块,对第三方封装(模板引擎、邮件、Servlet、二维码、Emoji、FTP、分词等) |
|
||||
| hutool-http | 基于HttpUrlConnection的Http客户端封装 |
|
||||
| hutool-log | 自动识别日志实现的日志门面 |
|
||||
| hutool-script | 脚本执行封装,例如Javascript |
|
||||
| hutool-setting | 功能更强大的Setting配置文件和Properties封装 |
|
||||
| hutool-system | 系统参数调用封装(JVM信息等) |
|
||||
| hutool-json | JSON实现 |
|
||||
| hutool-captcha | 图片验证码实现 |
|
||||
| hutool-poi | 针对POI中Excel和Word的封装 |
|
||||
| hutool-socket | 基于Java的NIO和AIO的Socket封装 |
|
||||
|
||||
可以根据需求对每个模块单独引入,也可以通过引入`hutool-all`方式引入所有模块。
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
## 文档
|
||||
|
||||
[中文文档](https://www.hutool.cn/docs/)
|
||||
|
||||
[参考API](https://apidoc.gitee.com/loolly/hutool/)
|
||||
|
||||
[视频介绍](https://www.bilibili.com/video/BV1bQ4y1M7d9?p=2)
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
## 安装
|
||||
|
||||
### Maven
|
||||
在项目的pom.xml的dependencies中加入以下内容:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.5.8</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### Gradle
|
||||
```
|
||||
compile 'cn.hutool:hutool-all:5.5.8'
|
||||
```
|
||||
|
||||
### 非Maven项目
|
||||
|
||||
点击以下任一链接,下载`hutool-all-X.X.X.jar`即可:
|
||||
|
||||
- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.5.8/)
|
||||
- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.5.8/)
|
||||
|
||||
> 注意
|
||||
> Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。
|
||||
> 如果你的项目使用JDK7,请使用Hutool 4.x版本
|
||||
|
||||
### 编译安装
|
||||
|
||||
访问Hutool的Gitee主页:[https://gitee.com/loolly/hutool](https://gitee.com/loolly/hutool) 下载整个项目源码(v5-master或v5-dev分支都可)然后进入Hutool项目目录执行:
|
||||
|
||||
```sh
|
||||
./hutool.sh install
|
||||
```
|
||||
|
||||
然后就可以使用Maven引入了。
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
## 添砖加瓦
|
||||
|
||||
### 分支说明
|
||||
|
||||
Hutool的源码分为两个分支,功能如下:
|
||||
|
||||
| 分支 | 作用 |
|
||||
|-----------|---------------------------------------------------------------|
|
||||
| v5-master | 主分支,release版本使用的分支,与中央库提交的jar一致,不接收任何pr或修改 |
|
||||
| v5-dev | 开发分支,默认为下个版本的SNAPSHOT版本,接受修改或pr |
|
||||
|
||||
### 提供bug反馈或建议
|
||||
|
||||
提交问题反馈请说明正在使用的JDK版本呢、Hutool版本和相关依赖库版本。
|
||||
|
||||
- [Gitee issue](https://gitee.com/loolly/hutool/issues)
|
||||
- [Github issue](https://github.com/looly/hutool/issues)
|
||||
|
||||
|
||||
### 贡献代码的步骤
|
||||
|
||||
1. 在Gitee或者Github上fork项目到自己的repo
|
||||
2. 把fork过去的项目也就是你的项目clone到你的本地
|
||||
3. 修改代码(记得一定要修改v5-dev分支)
|
||||
4. commit后push到自己的库(v5-dev分支)
|
||||
5. 登录Gitee或Github在你首页可以看到一个 pull request 按钮,点击它,填写一些说明信息,然后提交即可。
|
||||
6. 等待维护者合并
|
||||
|
||||
### PR遵照的原则
|
||||
|
||||
Hutool欢迎任何人为Hutool添砖加瓦,贡献代码,不过维护者是一个强迫症患者,为了照顾病人,需要提交的pr(pull request)符合一些规范,规范如下:
|
||||
|
||||
1. 注释完备,尤其每个新增的方法应按照Java文档规范标明方法说明、参数说明、返回值说明等信息,必要时请添加单元测试,如果愿意,也可以加上你的大名。
|
||||
2. Hutool的缩进按照Eclipse(~~不要跟我说IDEA多好用,维护者非常懒,学不会~~,IDEA真香,改了Eclipse快捷键后舒服多了)默认(tab)缩进,所以请遵守(不要和我争执空格与tab的问题,这是一个病人的习惯)。
|
||||
3. 新加的方法不要使用第三方库的方法,Hutool遵循无依赖原则(除非在extra模块中加方法工具)。
|
||||
4. 请pull request到`v5-dev`分支。Hutool在5.x版本后使用了新的分支:`v5-master`是主分支,表示已经发布中央库的版本,这个分支不允许pr,也不允许修改。
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
## 捐赠
|
||||
|
||||
如果你觉得Hutool不错,可以捐赠请维护者吃包辣条~,在此表示感谢^_^。
|
||||
|
||||
点击以下链接,将页面拉到最下方点击“捐赠”即可。
|
||||
|
||||
[前往捐赠](https://gitee.com/loolly/hutool)
|
||||
|
||||
## 公众号
|
||||
|
||||
欢迎关注Hutool合作的公众号。
|
||||
|
||||

|
@ -1,148 +1,148 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<description>Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。</description>
|
||||
<url>https://github.com/looly/hutool</url>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-aop</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-bloomFilter</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-cache</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-crypto</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-db</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-dfa</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-extra</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-http</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-log</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-script</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-setting</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-system</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-cron</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-json</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-poi</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-captcha</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-socket</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.2.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<!-- 创建源码jar -->
|
||||
<createSourcesJar>true</createSourcesJar>
|
||||
<artifactSet>
|
||||
<includes>
|
||||
<include>${project.groupId}:*:*</include>
|
||||
</includes>
|
||||
</artifactSet>
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/maven/**</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<description>Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。</description>
|
||||
<url>https://github.com/looly/hutool</url>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-aop</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-bloomFilter</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-cache</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-crypto</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-db</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-dfa</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-extra</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-http</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-log</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-script</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-setting</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-system</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-cron</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-json</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-poi</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-captcha</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-socket</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.2.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<!-- 创建源码jar -->
|
||||
<createSourcesJar>true</createSourcesJar>
|
||||
<artifactSet>
|
||||
<includes>
|
||||
<include>${project.groupId}:*:*</include>
|
||||
</includes>
|
||||
</artifactSet>
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/maven/**</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
@ -1,67 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2017 hutool.cn
|
||||
*
|
||||
* Licensed 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 cn.hutool;
|
||||
|
||||
import cn.hutool.core.lang.ConsoleTable;
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Hutool中的工具方法来自于每个用户的精雕细琢,它涵盖了Java开发底层代码中的方方面面,它既是大型项目开发中解决小问题的利器,也是小型项目中的效率担当;<br>
|
||||
* </p>
|
||||
*
|
||||
* <p>Hutool是项目中“util”包友好的替代,它节省了开发人员对项目中公用类和公用工具方法的封装时间,使开发专注于业务,同时可以最大限度的避免封装不完善带来的bug。</p>
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class Hutool {
|
||||
|
||||
public static final String AUTHOR = "Looly";
|
||||
|
||||
private Hutool() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示Hutool所有的工具类
|
||||
*
|
||||
* @return 工具类名集合
|
||||
* @since 5.5.2
|
||||
*/
|
||||
public static Set<Class<?>> getAllUtils() {
|
||||
return ClassUtil.scanPackage("cn.hutool",
|
||||
(clazz) -> (false == clazz.isInterface()) && StrUtil.endWith(clazz.getSimpleName(), "Util"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 控制台打印所有工具类
|
||||
*/
|
||||
public static void printAllUtils() {
|
||||
final Set<Class<?>> allUtils = getAllUtils();
|
||||
final ConsoleTable consoleTable = ConsoleTable.create().addHeader("工具类名", "所在包");
|
||||
for (Class<?> clazz : allUtils) {
|
||||
consoleTable.addBody(clazz.getSimpleName(), clazz.getPackage().getName());
|
||||
}
|
||||
consoleTable.print();
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Copyright (C) 2017 hutool.cn
|
||||
*
|
||||
* Licensed 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 cn.hutool;
|
||||
|
||||
import cn.hutool.core.lang.ConsoleTable;
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Hutool中的工具方法来自于每个用户的精雕细琢,它涵盖了Java开发底层代码中的方方面面,它既是大型项目开发中解决小问题的利器,也是小型项目中的效率担当;<br>
|
||||
* </p>
|
||||
*
|
||||
* <p>Hutool是项目中“util”包友好的替代,它节省了开发人员对项目中公用类和公用工具方法的封装时间,使开发专注于业务,同时可以最大限度的避免封装不完善带来的bug。</p>
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class Hutool {
|
||||
|
||||
public static final String AUTHOR = "Looly";
|
||||
|
||||
private Hutool() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示Hutool所有的工具类
|
||||
*
|
||||
* @return 工具类名集合
|
||||
* @since 5.5.2
|
||||
*/
|
||||
public static Set<Class<?>> getAllUtils() {
|
||||
return ClassUtil.scanPackage("cn.hutool",
|
||||
(clazz) -> (false == clazz.isInterface()) && StrUtil.endWith(clazz.getSimpleName(), "Util"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 控制台打印所有工具类
|
||||
*/
|
||||
public static void printAllUtils() {
|
||||
final Set<Class<?>> allUtils = getAllUtils();
|
||||
final ConsoleTable consoleTable = ConsoleTable.create().addHeader("工具类名", "所在包");
|
||||
for (Class<?> clazz : allUtils) {
|
||||
consoleTable.addBody(clazz.getSimpleName(), clazz.getPackage().getName());
|
||||
}
|
||||
consoleTable.print();
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
/**
|
||||
* Hutool是Hu + tool的自造词,前者致敬我的“前任公司”,后者为工具之意,谐音“糊涂”,寓意追求“万事都作糊涂观,无所谓失,无所谓得”的境界。
|
||||
*
|
||||
* <p>
|
||||
* Hutool是一个Java工具包,也只是一个工具包,它帮助我们简化每一行代码,减少每一个方法,让Java语言也可以“甜甜的”。<br>
|
||||
* Hutool最初是我项目中“util”包的一个整理,后来慢慢积累并加入更多非业务相关功能,并广泛学习其它开源项目精髓,经过自己整理修改,最终形成丰富的开源工具集。
|
||||
* </p>
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* Hutool是Hu + tool的自造词,前者致敬我的“前任公司”,后者为工具之意,谐音“糊涂”,寓意追求“万事都作糊涂观,无所谓失,无所谓得”的境界。
|
||||
*
|
||||
* <p>
|
||||
* Hutool是一个Java工具包,也只是一个工具包,它帮助我们简化每一行代码,减少每一个方法,让Java语言也可以“甜甜的”。<br>
|
||||
* Hutool最初是我项目中“util”包的一个整理,后来慢慢积累并加入更多非业务相关功能,并广泛学习其它开源项目精髓,经过自己整理修改,最终形成丰富的开源工具集。
|
||||
* </p>
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool;
|
@ -1,46 +1,46 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-aop</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<description>Hutool 动态代理(AOP)</description>
|
||||
|
||||
<properties>
|
||||
<!-- versions -->
|
||||
<cglib.version>3.3.0</cglib.version>
|
||||
<spring.version>5.3.1</spring.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cglib</groupId>
|
||||
<artifactId>cglib</artifactId>
|
||||
<version>${cglib.version}</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-core</artifactId>
|
||||
<version>${spring.version}</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-aop</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<description>Hutool 动态代理(AOP)</description>
|
||||
|
||||
<properties>
|
||||
<!-- versions -->
|
||||
<cglib.version>3.3.0</cglib.version>
|
||||
<spring.version>5.3.1</spring.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cglib</groupId>
|
||||
<artifactId>cglib</artifactId>
|
||||
<version>${cglib.version}</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-core</artifactId>
|
||||
<version>${spring.version}</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
@ -1,74 +1,74 @@
|
||||
package cn.hutool.aop;
|
||||
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
import cn.hutool.aop.aspects.Aspect;
|
||||
import cn.hutool.aop.proxy.ProxyFactory;
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
|
||||
/**
|
||||
* 代理工具类
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public final class ProxyUtil {
|
||||
|
||||
/**
|
||||
* 使用切面代理对象
|
||||
*
|
||||
* @param <T> 切面对象类型
|
||||
* @param target 目标对象
|
||||
* @param aspectClass 切面对象类
|
||||
* @return 代理对象
|
||||
*/
|
||||
public static <T> T proxy(T target, Class<? extends Aspect> aspectClass){
|
||||
return ProxyFactory.createProxy(target, aspectClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用切面代理对象
|
||||
*
|
||||
* @param <T> 被代理对象类型
|
||||
* @param target 被代理对象
|
||||
* @param aspect 切面对象
|
||||
* @return 代理对象
|
||||
*/
|
||||
public static <T> T proxy(T target, Aspect aspect){
|
||||
return ProxyFactory.createProxy(target, aspect);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建动态代理对象<br>
|
||||
* 动态代理对象的创建原理是:<br>
|
||||
* 假设创建的代理对象名为 $Proxy0<br>
|
||||
* 1、根据传入的interfaces动态生成一个类,实现interfaces中的接口<br>
|
||||
* 2、通过传入的classloder将刚生成的类加载到jvm中。即将$Proxy0类load<br>
|
||||
* 3、调用$Proxy0的$Proxy0(InvocationHandler)构造函数 创建$Proxy0的对象,并且用interfaces参数遍历其所有接口的方法,这些实现方法的实现本质上是通过反射调用被代理对象的方法<br>
|
||||
* 4、将$Proxy0的实例返回给客户端。 <br>
|
||||
* 5、当调用代理类的相应方法时,相当于调用 {@link InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])} 方法
|
||||
*
|
||||
*
|
||||
* @param <T> 被代理对象类型
|
||||
* @param classloader 被代理类对应的ClassLoader
|
||||
* @param invocationHandler {@link InvocationHandler} ,被代理类通过实现此接口提供动态代理功能
|
||||
* @param interfaces 代理类中需要实现的被代理类的接口方法
|
||||
* @return 代理类
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T newProxyInstance(ClassLoader classloader, InvocationHandler invocationHandler, Class<?>... interfaces) {
|
||||
return (T) Proxy.newProxyInstance(classloader, interfaces, invocationHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建动态代理对象
|
||||
*
|
||||
* @param <T> 被代理对象类型
|
||||
* @param invocationHandler {@link InvocationHandler} ,被代理类通过实现此接口提供动态代理功能
|
||||
* @param interfaces 代理类中需要实现的被代理类的接口方法
|
||||
* @return 代理类
|
||||
*/
|
||||
public static <T> T newProxyInstance(InvocationHandler invocationHandler, Class<?>... interfaces) {
|
||||
return newProxyInstance(ClassUtil.getClassLoader(), invocationHandler, interfaces);
|
||||
}
|
||||
}
|
||||
package cn.hutool.aop;
|
||||
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
import cn.hutool.aop.aspects.Aspect;
|
||||
import cn.hutool.aop.proxy.ProxyFactory;
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
|
||||
/**
|
||||
* 代理工具类
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public final class ProxyUtil {
|
||||
|
||||
/**
|
||||
* 使用切面代理对象
|
||||
*
|
||||
* @param <T> 切面对象类型
|
||||
* @param target 目标对象
|
||||
* @param aspectClass 切面对象类
|
||||
* @return 代理对象
|
||||
*/
|
||||
public static <T> T proxy(T target, Class<? extends Aspect> aspectClass){
|
||||
return ProxyFactory.createProxy(target, aspectClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用切面代理对象
|
||||
*
|
||||
* @param <T> 被代理对象类型
|
||||
* @param target 被代理对象
|
||||
* @param aspect 切面对象
|
||||
* @return 代理对象
|
||||
*/
|
||||
public static <T> T proxy(T target, Aspect aspect){
|
||||
return ProxyFactory.createProxy(target, aspect);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建动态代理对象<br>
|
||||
* 动态代理对象的创建原理是:<br>
|
||||
* 假设创建的代理对象名为 $Proxy0<br>
|
||||
* 1、根据传入的interfaces动态生成一个类,实现interfaces中的接口<br>
|
||||
* 2、通过传入的classloder将刚生成的类加载到jvm中。即将$Proxy0类load<br>
|
||||
* 3、调用$Proxy0的$Proxy0(InvocationHandler)构造函数 创建$Proxy0的对象,并且用interfaces参数遍历其所有接口的方法,这些实现方法的实现本质上是通过反射调用被代理对象的方法<br>
|
||||
* 4、将$Proxy0的实例返回给客户端。 <br>
|
||||
* 5、当调用代理类的相应方法时,相当于调用 {@link InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])} 方法
|
||||
*
|
||||
*
|
||||
* @param <T> 被代理对象类型
|
||||
* @param classloader 被代理类对应的ClassLoader
|
||||
* @param invocationHandler {@link InvocationHandler} ,被代理类通过实现此接口提供动态代理功能
|
||||
* @param interfaces 代理类中需要实现的被代理类的接口方法
|
||||
* @return 代理类
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T newProxyInstance(ClassLoader classloader, InvocationHandler invocationHandler, Class<?>... interfaces) {
|
||||
return (T) Proxy.newProxyInstance(classloader, interfaces, invocationHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建动态代理对象
|
||||
*
|
||||
* @param <T> 被代理对象类型
|
||||
* @param invocationHandler {@link InvocationHandler} ,被代理类通过实现此接口提供动态代理功能
|
||||
* @param interfaces 代理类中需要实现的被代理类的接口方法
|
||||
* @return 代理类
|
||||
*/
|
||||
public static <T> T newProxyInstance(InvocationHandler invocationHandler, Class<?>... interfaces) {
|
||||
return newProxyInstance(ClassUtil.getClassLoader(), invocationHandler, interfaces);
|
||||
}
|
||||
}
|
||||
|
@ -1,49 +1,49 @@
|
||||
package cn.hutool.aop.aspects;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 切面接口
|
||||
*
|
||||
* @author looly
|
||||
* @author ted.L
|
||||
* @since 4.18
|
||||
*/
|
||||
public interface Aspect {
|
||||
|
||||
/**
|
||||
* 目标方法执行前的操作
|
||||
*
|
||||
* @param target 目标对象
|
||||
* @param method 目标方法
|
||||
* @param args 参数
|
||||
* @return 是否继续执行接下来的操作
|
||||
*/
|
||||
boolean before(Object target, Method method, Object[] args);
|
||||
|
||||
/**
|
||||
* 目标方法执行后的操作
|
||||
* 如果 target.method 抛出异常且
|
||||
*
|
||||
* @param target 目标对象
|
||||
* @param method 目标方法
|
||||
* @param args 参数
|
||||
* @param returnVal 目标方法执行返回值
|
||||
* @return 是否允许返回值(接下来的操作)
|
||||
* @see Aspect#afterException 返回true,则不会执行此操作
|
||||
* 如果
|
||||
* @see Aspect#afterException 返回false,则无论target.method是否抛出异常,均会执行此操作
|
||||
*/
|
||||
boolean after(Object target, Method method, Object[] args, Object returnVal);
|
||||
|
||||
/**
|
||||
* 目标方法抛出异常时的操作
|
||||
*
|
||||
* @param target 目标对象
|
||||
* @param method 目标方法
|
||||
* @param args 参数
|
||||
* @param e 异常
|
||||
* @return 是否允许抛出异常
|
||||
*/
|
||||
boolean afterException(Object target, Method method, Object[] args, Throwable e);
|
||||
}
|
||||
package cn.hutool.aop.aspects;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 切面接口
|
||||
*
|
||||
* @author looly
|
||||
* @author ted.L
|
||||
* @since 4.18
|
||||
*/
|
||||
public interface Aspect {
|
||||
|
||||
/**
|
||||
* 目标方法执行前的操作
|
||||
*
|
||||
* @param target 目标对象
|
||||
* @param method 目标方法
|
||||
* @param args 参数
|
||||
* @return 是否继续执行接下来的操作
|
||||
*/
|
||||
boolean before(Object target, Method method, Object[] args);
|
||||
|
||||
/**
|
||||
* 目标方法执行后的操作
|
||||
* 如果 target.method 抛出异常且
|
||||
*
|
||||
* @param target 目标对象
|
||||
* @param method 目标方法
|
||||
* @param args 参数
|
||||
* @param returnVal 目标方法执行返回值
|
||||
* @return 是否允许返回值(接下来的操作)
|
||||
* @see Aspect#afterException 返回true,则不会执行此操作
|
||||
* 如果
|
||||
* @see Aspect#afterException 返回false,则无论target.method是否抛出异常,均会执行此操作
|
||||
*/
|
||||
boolean after(Object target, Method method, Object[] args, Object returnVal);
|
||||
|
||||
/**
|
||||
* 目标方法抛出异常时的操作
|
||||
*
|
||||
* @param target 目标对象
|
||||
* @param method 目标方法
|
||||
* @param args 参数
|
||||
* @param e 异常
|
||||
* @return 是否允许抛出异常
|
||||
*/
|
||||
boolean afterException(Object target, Method method, Object[] args, Throwable e);
|
||||
}
|
||||
|
@ -1,33 +1,33 @@
|
||||
package cn.hutool.aop.aspects;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 简单切面类,不做任何操作<br>
|
||||
* 可以继承此类实现自己需要的方法即可
|
||||
*
|
||||
* @author Looly, ted.L
|
||||
*/
|
||||
public class SimpleAspect implements Aspect, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public boolean before(Object target, Method method, Object[] args) {
|
||||
//继承此类后实现此方法
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean after(Object target, Method method, Object[] args, Object returnVal) {
|
||||
//继承此类后实现此方法
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean afterException(Object target, Method method, Object[] args, Throwable e) {
|
||||
//继承此类后实现此方法
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
package cn.hutool.aop.aspects;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 简单切面类,不做任何操作<br>
|
||||
* 可以继承此类实现自己需要的方法即可
|
||||
*
|
||||
* @author Looly, ted.L
|
||||
*/
|
||||
public class SimpleAspect implements Aspect, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public boolean before(Object target, Method method, Object[] args) {
|
||||
//继承此类后实现此方法
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean after(Object target, Method method, Object[] args, Object returnVal) {
|
||||
//继承此类后实现此方法
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean afterException(Object target, Method method, Object[] args, Throwable e) {
|
||||
//继承此类后实现此方法
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,33 +1,33 @@
|
||||
package cn.hutool.aop.aspects;
|
||||
|
||||
import cn.hutool.core.date.TimeInterval;
|
||||
import cn.hutool.core.lang.Console;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 通过日志打印方法的执行时间的切面
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class TimeIntervalAspect extends SimpleAspect {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final TimeInterval interval = new TimeInterval();
|
||||
|
||||
@Override
|
||||
public boolean before(Object target, Method method, Object[] args) {
|
||||
interval.start();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean after(Object target, Method method, Object[] args, Object returnVal) {
|
||||
Console.log("Method [{}.{}] execute spend [{}]ms return value [{}]",
|
||||
target.getClass().getName(), //
|
||||
method.getName(), //
|
||||
interval.intervalMs(), //
|
||||
returnVal);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
package cn.hutool.aop.aspects;
|
||||
|
||||
import cn.hutool.core.date.TimeInterval;
|
||||
import cn.hutool.core.lang.Console;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 通过日志打印方法的执行时间的切面
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class TimeIntervalAspect extends SimpleAspect {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final TimeInterval interval = new TimeInterval();
|
||||
|
||||
@Override
|
||||
public boolean before(Object target, Method method, Object[] args) {
|
||||
interval.start();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean after(Object target, Method method, Object[] args, Object returnVal) {
|
||||
Console.log("Method [{}.{}] execute spend [{}]ms return value [{}]",
|
||||
target.getClass().getName(), //
|
||||
method.getName(), //
|
||||
interval.intervalMs(), //
|
||||
returnVal);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* 切面实现,提供一些基本的切面实现
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* 切面实现,提供一些基本的切面实现
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.aop.aspects;
|
@ -1,60 +1,60 @@
|
||||
package cn.hutool.aop.interceptor;
|
||||
|
||||
import cn.hutool.aop.aspects.Aspect;
|
||||
import net.sf.cglib.proxy.MethodInterceptor;
|
||||
import net.sf.cglib.proxy.MethodProxy;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Cglib实现的动态代理切面
|
||||
*
|
||||
* @author looly, ted.L
|
||||
*/
|
||||
public class CglibInterceptor implements MethodInterceptor, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Object target;
|
||||
private final Aspect aspect;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param target 被代理对象
|
||||
* @param aspect 切面实现
|
||||
*/
|
||||
public CglibInterceptor(Object target, Aspect aspect) {
|
||||
this.target = target;
|
||||
this.aspect = aspect;
|
||||
}
|
||||
|
||||
public Object getTarget() {
|
||||
return this.target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||
final Object target = this.target;
|
||||
Object result = null;
|
||||
// 开始前回调
|
||||
if (aspect.before(target, method, args)) {
|
||||
try {
|
||||
// result = proxy.invokeSuper(obj, args);
|
||||
result = proxy.invoke(target, args);
|
||||
} catch (InvocationTargetException e) {
|
||||
// 异常回调(只捕获业务代码导致的异常,而非反射导致的异常)
|
||||
if (aspect.afterException(target, method, args, e.getTargetException())) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 结束执行回调
|
||||
if (aspect.after(target, method, args, result)) {
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
package cn.hutool.aop.interceptor;
|
||||
|
||||
import cn.hutool.aop.aspects.Aspect;
|
||||
import net.sf.cglib.proxy.MethodInterceptor;
|
||||
import net.sf.cglib.proxy.MethodProxy;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Cglib实现的动态代理切面
|
||||
*
|
||||
* @author looly, ted.L
|
||||
*/
|
||||
public class CglibInterceptor implements MethodInterceptor, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Object target;
|
||||
private final Aspect aspect;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param target 被代理对象
|
||||
* @param aspect 切面实现
|
||||
*/
|
||||
public CglibInterceptor(Object target, Aspect aspect) {
|
||||
this.target = target;
|
||||
this.aspect = aspect;
|
||||
}
|
||||
|
||||
public Object getTarget() {
|
||||
return this.target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||
final Object target = this.target;
|
||||
Object result = null;
|
||||
// 开始前回调
|
||||
if (aspect.before(target, method, args)) {
|
||||
try {
|
||||
// result = proxy.invokeSuper(obj, args);
|
||||
result = proxy.invoke(target, args);
|
||||
} catch (InvocationTargetException e) {
|
||||
// 异常回调(只捕获业务代码导致的异常,而非反射导致的异常)
|
||||
if (aspect.afterException(target, method, args, e.getTargetException())) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 结束执行回调
|
||||
if (aspect.after(target, method, args, result)) {
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -1,66 +1,66 @@
|
||||
package cn.hutool.aop.interceptor;
|
||||
|
||||
import cn.hutool.aop.aspects.Aspect;
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* JDK实现的动态代理切面
|
||||
*
|
||||
* @author Looly
|
||||
* @author ted.L
|
||||
*/
|
||||
public class JdkInterceptor implements InvocationHandler, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Object target;
|
||||
private final Aspect aspect;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param target 被代理对象
|
||||
* @param aspect 切面实现
|
||||
*/
|
||||
public JdkInterceptor(Object target, Aspect aspect) {
|
||||
this.target = target;
|
||||
this.aspect = aspect;
|
||||
}
|
||||
|
||||
public Object getTarget() {
|
||||
return this.target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
final Object target = this.target;
|
||||
final Aspect aspect = this.aspect;
|
||||
Object result = null;
|
||||
|
||||
// 开始前回调
|
||||
if (aspect.before(target, method, args)) {
|
||||
ReflectUtil.setAccessible(method);
|
||||
|
||||
try {
|
||||
result = method.invoke(ClassUtil.isStatic(method) ? null : target, args);
|
||||
} catch (InvocationTargetException e) {
|
||||
// 异常回调(只捕获业务代码导致的异常,而非反射导致的异常)
|
||||
if (aspect.afterException(target, method, args, e.getTargetException())) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 结束执行回调
|
||||
if (aspect.after(target, method, args, result)) {
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
package cn.hutool.aop.interceptor;
|
||||
|
||||
import cn.hutool.aop.aspects.Aspect;
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* JDK实现的动态代理切面
|
||||
*
|
||||
* @author Looly
|
||||
* @author ted.L
|
||||
*/
|
||||
public class JdkInterceptor implements InvocationHandler, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Object target;
|
||||
private final Aspect aspect;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param target 被代理对象
|
||||
* @param aspect 切面实现
|
||||
*/
|
||||
public JdkInterceptor(Object target, Aspect aspect) {
|
||||
this.target = target;
|
||||
this.aspect = aspect;
|
||||
}
|
||||
|
||||
public Object getTarget() {
|
||||
return this.target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
final Object target = this.target;
|
||||
final Aspect aspect = this.aspect;
|
||||
Object result = null;
|
||||
|
||||
// 开始前回调
|
||||
if (aspect.before(target, method, args)) {
|
||||
ReflectUtil.setAccessible(method);
|
||||
|
||||
try {
|
||||
result = method.invoke(ClassUtil.isStatic(method) ? null : target, args);
|
||||
} catch (InvocationTargetException e) {
|
||||
// 异常回调(只捕获业务代码导致的异常,而非反射导致的异常)
|
||||
if (aspect.afterException(target, method, args, e.getTargetException())) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 结束执行回调
|
||||
if (aspect.after(target, method, args, result)) {
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,65 +1,65 @@
|
||||
package cn.hutool.aop.interceptor;
|
||||
|
||||
import cn.hutool.aop.aspects.Aspect;
|
||||
import org.springframework.cglib.proxy.MethodInterceptor;
|
||||
import org.springframework.cglib.proxy.MethodProxy;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Spring-cglib实现的动态代理切面
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
public class SpringCglibInterceptor implements MethodInterceptor, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Object target;
|
||||
private final Aspect aspect;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param target 被代理对象
|
||||
* @param aspect 切面实现
|
||||
*/
|
||||
public SpringCglibInterceptor(Object target, Aspect aspect) {
|
||||
this.target = target;
|
||||
this.aspect = aspect;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得目标对象
|
||||
*
|
||||
* @return 目标对象
|
||||
*/
|
||||
public Object getTarget() {
|
||||
return this.target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||
final Object target = this.target;
|
||||
Object result = null;
|
||||
// 开始前回调
|
||||
if (aspect.before(target, method, args)) {
|
||||
try {
|
||||
// result = proxy.invokeSuper(obj, args);
|
||||
result = proxy.invoke(target, args);
|
||||
} catch (InvocationTargetException e) {
|
||||
// 异常回调(只捕获业务代码导致的异常,而非反射导致的异常)
|
||||
if (aspect.afterException(target, method, args, e.getTargetException())) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 结束执行回调
|
||||
if (aspect.after(target, method, args, result)) {
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
package cn.hutool.aop.interceptor;
|
||||
|
||||
import cn.hutool.aop.aspects.Aspect;
|
||||
import org.springframework.cglib.proxy.MethodInterceptor;
|
||||
import org.springframework.cglib.proxy.MethodProxy;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Spring-cglib实现的动态代理切面
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
public class SpringCglibInterceptor implements MethodInterceptor, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Object target;
|
||||
private final Aspect aspect;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param target 被代理对象
|
||||
* @param aspect 切面实现
|
||||
*/
|
||||
public SpringCglibInterceptor(Object target, Aspect aspect) {
|
||||
this.target = target;
|
||||
this.aspect = aspect;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得目标对象
|
||||
*
|
||||
* @return 目标对象
|
||||
*/
|
||||
public Object getTarget() {
|
||||
return this.target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||
final Object target = this.target;
|
||||
Object result = null;
|
||||
// 开始前回调
|
||||
if (aspect.before(target, method, args)) {
|
||||
try {
|
||||
// result = proxy.invokeSuper(obj, args);
|
||||
result = proxy.invoke(target, args);
|
||||
} catch (InvocationTargetException e) {
|
||||
// 异常回调(只捕获业务代码导致的异常,而非反射导致的异常)
|
||||
if (aspect.afterException(target, method, args, e.getTargetException())) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 结束执行回调
|
||||
if (aspect.after(target, method, args, result)) {
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* 代理拦截器实现
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* 代理拦截器实现
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.aop.interceptor;
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* JDK动态代理封装,提供非IOC下的切面支持
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* JDK动态代理封装,提供非IOC下的切面支持
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.aop;
|
@ -1,25 +1,25 @@
|
||||
package cn.hutool.aop.proxy;
|
||||
|
||||
import cn.hutool.aop.aspects.Aspect;
|
||||
import cn.hutool.aop.interceptor.CglibInterceptor;
|
||||
import net.sf.cglib.proxy.Enhancer;
|
||||
|
||||
/**
|
||||
* 基于Cglib的切面代理工厂
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
public class CglibProxyFactory extends ProxyFactory{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T proxy(T target, Aspect aspect) {
|
||||
final Enhancer enhancer = new Enhancer();
|
||||
enhancer.setSuperclass(target.getClass());
|
||||
enhancer.setCallback(new CglibInterceptor(target, aspect));
|
||||
return (T) enhancer.create();
|
||||
}
|
||||
|
||||
}
|
||||
package cn.hutool.aop.proxy;
|
||||
|
||||
import cn.hutool.aop.aspects.Aspect;
|
||||
import cn.hutool.aop.interceptor.CglibInterceptor;
|
||||
import net.sf.cglib.proxy.Enhancer;
|
||||
|
||||
/**
|
||||
* 基于Cglib的切面代理工厂
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
public class CglibProxyFactory extends ProxyFactory{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T proxy(T target, Aspect aspect) {
|
||||
final Enhancer enhancer = new Enhancer();
|
||||
enhancer.setSuperclass(target.getClass());
|
||||
enhancer.setCallback(new CglibInterceptor(target, aspect));
|
||||
return (T) enhancer.create();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,22 +1,22 @@
|
||||
package cn.hutool.aop.proxy;
|
||||
|
||||
import cn.hutool.aop.ProxyUtil;
|
||||
import cn.hutool.aop.aspects.Aspect;
|
||||
import cn.hutool.aop.interceptor.JdkInterceptor;
|
||||
|
||||
/**
|
||||
* JDK实现的切面代理
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
public class JdkProxyFactory extends ProxyFactory {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public <T> T proxy(T target, Aspect aspect) {
|
||||
return ProxyUtil.newProxyInstance(//
|
||||
target.getClass().getClassLoader(), //
|
||||
new JdkInterceptor(target, aspect), //
|
||||
target.getClass().getInterfaces());
|
||||
}
|
||||
}
|
||||
package cn.hutool.aop.proxy;
|
||||
|
||||
import cn.hutool.aop.ProxyUtil;
|
||||
import cn.hutool.aop.aspects.Aspect;
|
||||
import cn.hutool.aop.interceptor.JdkInterceptor;
|
||||
|
||||
/**
|
||||
* JDK实现的切面代理
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
public class JdkProxyFactory extends ProxyFactory {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public <T> T proxy(T target, Aspect aspect) {
|
||||
return ProxyUtil.newProxyInstance(//
|
||||
target.getClass().getClassLoader(), //
|
||||
new JdkInterceptor(target, aspect), //
|
||||
target.getClass().getInterfaces());
|
||||
}
|
||||
}
|
||||
|
@ -1,73 +1,73 @@
|
||||
package cn.hutool.aop.proxy;
|
||||
|
||||
import cn.hutool.aop.aspects.Aspect;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.ServiceLoaderUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 代理工厂<br>
|
||||
* 根据用户引入代理库的不同,产生不同的代理对象
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
public abstract class ProxyFactory implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 创建代理
|
||||
*
|
||||
* @param <T> 代理对象类型
|
||||
* @param target 被代理对象
|
||||
* @param aspectClass 切面实现类,自动实例化
|
||||
* @return 代理对象
|
||||
* @since 5.3.1
|
||||
*/
|
||||
public <T> T proxy(T target, Class<? extends Aspect> aspectClass) {
|
||||
return proxy(target, ReflectUtil.newInstanceIfPossible(aspectClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建代理
|
||||
*
|
||||
* @param <T> 代理对象类型
|
||||
* @param target 被代理对象
|
||||
* @param aspect 切面实现
|
||||
* @return 代理对象
|
||||
*/
|
||||
public abstract <T> T proxy(T target, Aspect aspect);
|
||||
|
||||
/**
|
||||
* 根据用户引入Cglib与否自动创建代理对象
|
||||
*
|
||||
* @param <T> 切面对象类型
|
||||
* @param target 目标对象
|
||||
* @param aspectClass 切面对象类
|
||||
* @return 代理对象
|
||||
*/
|
||||
public static <T> T createProxy(T target, Class<? extends Aspect> aspectClass) {
|
||||
return createProxy(target, ReflectUtil.newInstance(aspectClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户引入Cglib与否自动创建代理对象
|
||||
*
|
||||
* @param <T> 切面对象类型
|
||||
* @param target 被代理对象
|
||||
* @param aspect 切面实现
|
||||
* @return 代理对象
|
||||
*/
|
||||
public static <T> T createProxy(T target, Aspect aspect) {
|
||||
return create().proxy(target, aspect);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户引入Cglib与否创建代理工厂
|
||||
*
|
||||
* @return 代理工厂
|
||||
*/
|
||||
public static ProxyFactory create() {
|
||||
return ServiceLoaderUtil.loadFirstAvailable(ProxyFactory.class);
|
||||
}
|
||||
}
|
||||
package cn.hutool.aop.proxy;
|
||||
|
||||
import cn.hutool.aop.aspects.Aspect;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.ServiceLoaderUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 代理工厂<br>
|
||||
* 根据用户引入代理库的不同,产生不同的代理对象
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
public abstract class ProxyFactory implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 创建代理
|
||||
*
|
||||
* @param <T> 代理对象类型
|
||||
* @param target 被代理对象
|
||||
* @param aspectClass 切面实现类,自动实例化
|
||||
* @return 代理对象
|
||||
* @since 5.3.1
|
||||
*/
|
||||
public <T> T proxy(T target, Class<? extends Aspect> aspectClass) {
|
||||
return proxy(target, ReflectUtil.newInstanceIfPossible(aspectClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建代理
|
||||
*
|
||||
* @param <T> 代理对象类型
|
||||
* @param target 被代理对象
|
||||
* @param aspect 切面实现
|
||||
* @return 代理对象
|
||||
*/
|
||||
public abstract <T> T proxy(T target, Aspect aspect);
|
||||
|
||||
/**
|
||||
* 根据用户引入Cglib与否自动创建代理对象
|
||||
*
|
||||
* @param <T> 切面对象类型
|
||||
* @param target 目标对象
|
||||
* @param aspectClass 切面对象类
|
||||
* @return 代理对象
|
||||
*/
|
||||
public static <T> T createProxy(T target, Class<? extends Aspect> aspectClass) {
|
||||
return createProxy(target, ReflectUtil.newInstance(aspectClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户引入Cglib与否自动创建代理对象
|
||||
*
|
||||
* @param <T> 切面对象类型
|
||||
* @param target 被代理对象
|
||||
* @param aspect 切面实现
|
||||
* @return 代理对象
|
||||
*/
|
||||
public static <T> T createProxy(T target, Aspect aspect) {
|
||||
return create().proxy(target, aspect);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户引入Cglib与否创建代理工厂
|
||||
*
|
||||
* @return 代理工厂
|
||||
*/
|
||||
public static ProxyFactory create() {
|
||||
return ServiceLoaderUtil.loadFirstAvailable(ProxyFactory.class);
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +1,25 @@
|
||||
package cn.hutool.aop.proxy;
|
||||
|
||||
import cn.hutool.aop.aspects.Aspect;
|
||||
import cn.hutool.aop.interceptor.SpringCglibInterceptor;
|
||||
import org.springframework.cglib.proxy.Enhancer;
|
||||
|
||||
/**
|
||||
* 基于Spring-cglib的切面代理工厂
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
public class SpringCglibProxyFactory extends ProxyFactory{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T proxy(T target, Aspect aspect) {
|
||||
final Enhancer enhancer = new Enhancer();
|
||||
enhancer.setSuperclass(target.getClass());
|
||||
enhancer.setCallback(new SpringCglibInterceptor(target, aspect));
|
||||
return (T) enhancer.create();
|
||||
}
|
||||
|
||||
}
|
||||
package cn.hutool.aop.proxy;
|
||||
|
||||
import cn.hutool.aop.aspects.Aspect;
|
||||
import cn.hutool.aop.interceptor.SpringCglibInterceptor;
|
||||
import org.springframework.cglib.proxy.Enhancer;
|
||||
|
||||
/**
|
||||
* 基于Spring-cglib的切面代理工厂
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
public class SpringCglibProxyFactory extends ProxyFactory{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T proxy(T target, Aspect aspect) {
|
||||
final Enhancer enhancer = new Enhancer();
|
||||
enhancer.setSuperclass(target.getClass());
|
||||
enhancer.setCallback(new SpringCglibInterceptor(target, aspect));
|
||||
return (T) enhancer.create();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* 代理实现
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* 代理实现
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.aop.proxy;
|
@ -1,3 +1,3 @@
|
||||
cn.hutool.aop.proxy.CglibProxyFactory
|
||||
cn.hutool.aop.proxy.SpringCglibProxyFactory
|
||||
cn.hutool.aop.proxy.CglibProxyFactory
|
||||
cn.hutool.aop.proxy.SpringCglibProxyFactory
|
||||
cn.hutool.aop.proxy.JdkProxyFactory
|
@ -1,88 +1,88 @@
|
||||
package cn.hutool.aop.test;
|
||||
|
||||
import cn.hutool.aop.ProxyUtil;
|
||||
import cn.hutool.aop.aspects.TimeIntervalAspect;
|
||||
import cn.hutool.core.lang.Console;
|
||||
import lombok.Data;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* AOP模块单元测试
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class AopTest {
|
||||
|
||||
@Test
|
||||
public void aopTest() {
|
||||
Animal cat = ProxyUtil.proxy(new Cat(), TimeIntervalAspect.class);
|
||||
String result = cat.eat();
|
||||
Assert.assertEquals("猫吃鱼", result);
|
||||
cat.seize();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void aopByAutoCglibTest() {
|
||||
Dog dog = ProxyUtil.proxy(new Dog(), TimeIntervalAspect.class);
|
||||
String result = dog.eat();
|
||||
Assert.assertEquals("狗吃肉", result);
|
||||
|
||||
dog.seize();
|
||||
}
|
||||
|
||||
interface Animal {
|
||||
String eat();
|
||||
|
||||
void seize();
|
||||
}
|
||||
|
||||
/**
|
||||
* 有接口
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
static class Cat implements Animal {
|
||||
|
||||
@Override
|
||||
public String eat() {
|
||||
return "猫吃鱼";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seize() {
|
||||
Console.log("抓了条鱼");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 无接口
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
static class Dog {
|
||||
public String eat() {
|
||||
return "狗吃肉";
|
||||
}
|
||||
|
||||
public void seize() {
|
||||
Console.log("抓了只鸡");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCGLIBProxy() {
|
||||
TagObj target = new TagObj();
|
||||
//目标类设置标记
|
||||
target.setTag("tag");
|
||||
|
||||
TagObj proxy = ProxyUtil.proxy(target, TimeIntervalAspect.class);
|
||||
//代理类获取标记tag (断言错误)
|
||||
Assert.assertEquals("tag", proxy.getTag());
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class TagObj{
|
||||
private String tag;
|
||||
}
|
||||
}
|
||||
package cn.hutool.aop.test;
|
||||
|
||||
import cn.hutool.aop.ProxyUtil;
|
||||
import cn.hutool.aop.aspects.TimeIntervalAspect;
|
||||
import cn.hutool.core.lang.Console;
|
||||
import lombok.Data;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* AOP模块单元测试
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class AopTest {
|
||||
|
||||
@Test
|
||||
public void aopTest() {
|
||||
Animal cat = ProxyUtil.proxy(new Cat(), TimeIntervalAspect.class);
|
||||
String result = cat.eat();
|
||||
Assert.assertEquals("猫吃鱼", result);
|
||||
cat.seize();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void aopByAutoCglibTest() {
|
||||
Dog dog = ProxyUtil.proxy(new Dog(), TimeIntervalAspect.class);
|
||||
String result = dog.eat();
|
||||
Assert.assertEquals("狗吃肉", result);
|
||||
|
||||
dog.seize();
|
||||
}
|
||||
|
||||
interface Animal {
|
||||
String eat();
|
||||
|
||||
void seize();
|
||||
}
|
||||
|
||||
/**
|
||||
* 有接口
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
static class Cat implements Animal {
|
||||
|
||||
@Override
|
||||
public String eat() {
|
||||
return "猫吃鱼";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seize() {
|
||||
Console.log("抓了条鱼");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 无接口
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
static class Dog {
|
||||
public String eat() {
|
||||
return "狗吃肉";
|
||||
}
|
||||
|
||||
public void seize() {
|
||||
Console.log("抓了只鸡");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCGLIBProxy() {
|
||||
TagObj target = new TagObj();
|
||||
//目标类设置标记
|
||||
target.setTag("tag");
|
||||
|
||||
TagObj proxy = ProxyUtil.proxy(target, TimeIntervalAspect.class);
|
||||
//代理类获取标记tag (断言错误)
|
||||
Assert.assertEquals("tag", proxy.getTag());
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class TagObj{
|
||||
private String tag;
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,24 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-bloomFilter</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<description>Hutool 布隆过滤器</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-bloomFilter</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<description>Hutool 布隆过滤器</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
@ -1,145 +1,145 @@
|
||||
package cn.hutool.bloomfilter;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.HashUtil;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.util.BitSet;
|
||||
|
||||
/**
|
||||
* BloomFilter实现方式2,此方式使用BitSet存储。<br>
|
||||
* Hash算法的使用使用固定顺序,只需指定个数即可
|
||||
* @author loolly
|
||||
*
|
||||
*/
|
||||
public class BitSetBloomFilter implements BloomFilter{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final BitSet bitSet;
|
||||
private final int bitSetSize;
|
||||
private final int addedElements;
|
||||
private final int hashFunctionNumber;
|
||||
|
||||
/**
|
||||
* 构造一个布隆过滤器,过滤器的容量为c * n 个bit.
|
||||
*
|
||||
* @param c 当前过滤器预先开辟的最大包含记录,通常要比预计存入的记录多一倍.
|
||||
* @param n 当前过滤器预计所要包含的记录.
|
||||
* @param k 哈希函数的个数,等同每条记录要占用的bit数.
|
||||
*/
|
||||
public BitSetBloomFilter(int c, int n, int k) {
|
||||
this.hashFunctionNumber = k;
|
||||
this.bitSetSize = (int) Math.ceil(c * k);
|
||||
this.addedElements = n;
|
||||
this.bitSet = new BitSet(this.bitSetSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过文件初始化过滤器.
|
||||
*
|
||||
* @param path 文件路径
|
||||
* @param charset 字符集
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
public void init(String path, String charset) throws IOException {
|
||||
BufferedReader reader = FileUtil.getReader(path, charset);
|
||||
try {
|
||||
String line;
|
||||
while(true) {
|
||||
line = reader.readLine();
|
||||
if(line == null) {
|
||||
break;
|
||||
}
|
||||
this.add(line);
|
||||
}
|
||||
}finally {
|
||||
IoUtil.close(reader);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(String str) {
|
||||
if (contains(str)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int[] positions = createHashes(str, hashFunctionNumber);
|
||||
for (int value : positions) {
|
||||
int position = Math.abs(value % bitSetSize);
|
||||
bitSet.set(position, true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判定是否包含指定字符串
|
||||
* @param str 字符串
|
||||
* @return 是否包含,存在误差
|
||||
*/
|
||||
@Override
|
||||
public boolean contains(String str) {
|
||||
int[] positions = createHashes(str, hashFunctionNumber);
|
||||
for (int i : positions) {
|
||||
int position = Math.abs(i % bitSetSize);
|
||||
if (!bitSet.get(position)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 得到当前过滤器的错误率.
|
||||
*/
|
||||
public double getFalsePositiveProbability() {
|
||||
// (1 - e^(-k * n / m)) ^ k
|
||||
return Math.pow((1 - Math.exp(-hashFunctionNumber * (double) addedElements / bitSetSize)), hashFunctionNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字符串的字节表示进行多哈希编码.
|
||||
*
|
||||
* @param str 待添加进过滤器的字符串字节表示.
|
||||
* @param hashNumber 要经过的哈希个数.
|
||||
* @return 各个哈希的结果数组.
|
||||
*/
|
||||
public static int[] createHashes(String str, int hashNumber) {
|
||||
int[] result = new int[hashNumber];
|
||||
for(int i = 0; i < hashNumber; i++) {
|
||||
result[i] = hash(str, i);
|
||||
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算Hash值
|
||||
* @param str 被计算Hash的字符串
|
||||
* @param k Hash算法序号
|
||||
* @return Hash值
|
||||
*/
|
||||
public static int hash(String str, int k) {
|
||||
switch (k) {
|
||||
case 0:
|
||||
return HashUtil.rsHash(str);
|
||||
case 1:
|
||||
return HashUtil.jsHash(str);
|
||||
case 2:
|
||||
return HashUtil.elfHash(str);
|
||||
case 3:
|
||||
return HashUtil.bkdrHash(str);
|
||||
case 4:
|
||||
return HashUtil.apHash(str);
|
||||
case 5:
|
||||
return HashUtil.djbHash(str);
|
||||
case 6:
|
||||
return HashUtil.sdbmHash(str);
|
||||
case 7:
|
||||
return HashUtil.pjwHash(str);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
package cn.hutool.bloomfilter;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.HashUtil;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.util.BitSet;
|
||||
|
||||
/**
|
||||
* BloomFilter实现方式2,此方式使用BitSet存储。<br>
|
||||
* Hash算法的使用使用固定顺序,只需指定个数即可
|
||||
* @author loolly
|
||||
*
|
||||
*/
|
||||
public class BitSetBloomFilter implements BloomFilter{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final BitSet bitSet;
|
||||
private final int bitSetSize;
|
||||
private final int addedElements;
|
||||
private final int hashFunctionNumber;
|
||||
|
||||
/**
|
||||
* 构造一个布隆过滤器,过滤器的容量为c * n 个bit.
|
||||
*
|
||||
* @param c 当前过滤器预先开辟的最大包含记录,通常要比预计存入的记录多一倍.
|
||||
* @param n 当前过滤器预计所要包含的记录.
|
||||
* @param k 哈希函数的个数,等同每条记录要占用的bit数.
|
||||
*/
|
||||
public BitSetBloomFilter(int c, int n, int k) {
|
||||
this.hashFunctionNumber = k;
|
||||
this.bitSetSize = (int) Math.ceil(c * k);
|
||||
this.addedElements = n;
|
||||
this.bitSet = new BitSet(this.bitSetSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过文件初始化过滤器.
|
||||
*
|
||||
* @param path 文件路径
|
||||
* @param charset 字符集
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
public void init(String path, String charset) throws IOException {
|
||||
BufferedReader reader = FileUtil.getReader(path, charset);
|
||||
try {
|
||||
String line;
|
||||
while(true) {
|
||||
line = reader.readLine();
|
||||
if(line == null) {
|
||||
break;
|
||||
}
|
||||
this.add(line);
|
||||
}
|
||||
}finally {
|
||||
IoUtil.close(reader);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(String str) {
|
||||
if (contains(str)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int[] positions = createHashes(str, hashFunctionNumber);
|
||||
for (int value : positions) {
|
||||
int position = Math.abs(value % bitSetSize);
|
||||
bitSet.set(position, true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判定是否包含指定字符串
|
||||
* @param str 字符串
|
||||
* @return 是否包含,存在误差
|
||||
*/
|
||||
@Override
|
||||
public boolean contains(String str) {
|
||||
int[] positions = createHashes(str, hashFunctionNumber);
|
||||
for (int i : positions) {
|
||||
int position = Math.abs(i % bitSetSize);
|
||||
if (!bitSet.get(position)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 得到当前过滤器的错误率.
|
||||
*/
|
||||
public double getFalsePositiveProbability() {
|
||||
// (1 - e^(-k * n / m)) ^ k
|
||||
return Math.pow((1 - Math.exp(-hashFunctionNumber * (double) addedElements / bitSetSize)), hashFunctionNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字符串的字节表示进行多哈希编码.
|
||||
*
|
||||
* @param str 待添加进过滤器的字符串字节表示.
|
||||
* @param hashNumber 要经过的哈希个数.
|
||||
* @return 各个哈希的结果数组.
|
||||
*/
|
||||
public static int[] createHashes(String str, int hashNumber) {
|
||||
int[] result = new int[hashNumber];
|
||||
for(int i = 0; i < hashNumber; i++) {
|
||||
result[i] = hash(str, i);
|
||||
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算Hash值
|
||||
* @param str 被计算Hash的字符串
|
||||
* @param k Hash算法序号
|
||||
* @return Hash值
|
||||
*/
|
||||
public static int hash(String str, int k) {
|
||||
switch (k) {
|
||||
case 0:
|
||||
return HashUtil.rsHash(str);
|
||||
case 1:
|
||||
return HashUtil.jsHash(str);
|
||||
case 2:
|
||||
return HashUtil.elfHash(str);
|
||||
case 3:
|
||||
return HashUtil.bkdrHash(str);
|
||||
case 4:
|
||||
return HashUtil.apHash(str);
|
||||
case 5:
|
||||
return HashUtil.djbHash(str);
|
||||
case 6:
|
||||
return HashUtil.sdbmHash(str);
|
||||
case 7:
|
||||
return HashUtil.pjwHash(str);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,32 +1,32 @@
|
||||
package cn.hutool.bloomfilter;
|
||||
|
||||
/**
|
||||
* 布隆过滤器工具
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.1.5
|
||||
*/
|
||||
public class BloomFilterUtil {
|
||||
|
||||
/**
|
||||
* 创建一个BitSet实现的布隆过滤器,过滤器的容量为c * n 个bit.
|
||||
*
|
||||
* @param c 当前过滤器预先开辟的最大包含记录,通常要比预计存入的记录多一倍.
|
||||
* @param n 当前过滤器预计所要包含的记录.
|
||||
* @param k 哈希函数的个数,等同每条记录要占用的bit数.
|
||||
* @return BitSetBloomFilter
|
||||
*/
|
||||
public static BitSetBloomFilter createBitSet(int c, int n, int k) {
|
||||
return new BitSetBloomFilter(c, n, k);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建BitMap实现的布隆过滤器
|
||||
*
|
||||
* @param m BitMap的大小
|
||||
* @return BitMapBloomFilter
|
||||
*/
|
||||
public static BitMapBloomFilter createBitMap(int m) {
|
||||
return new BitMapBloomFilter(m);
|
||||
}
|
||||
}
|
||||
package cn.hutool.bloomfilter;
|
||||
|
||||
/**
|
||||
* 布隆过滤器工具
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.1.5
|
||||
*/
|
||||
public class BloomFilterUtil {
|
||||
|
||||
/**
|
||||
* 创建一个BitSet实现的布隆过滤器,过滤器的容量为c * n 个bit.
|
||||
*
|
||||
* @param c 当前过滤器预先开辟的最大包含记录,通常要比预计存入的记录多一倍.
|
||||
* @param n 当前过滤器预计所要包含的记录.
|
||||
* @param k 哈希函数的个数,等同每条记录要占用的bit数.
|
||||
* @return BitSetBloomFilter
|
||||
*/
|
||||
public static BitSetBloomFilter createBitSet(int c, int n, int k) {
|
||||
return new BitSetBloomFilter(c, n, k);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建BitMap实现的布隆过滤器
|
||||
*
|
||||
* @param m BitMap的大小
|
||||
* @return BitMapBloomFilter
|
||||
*/
|
||||
public static BitMapBloomFilter createBitMap(int m) {
|
||||
return new BitMapBloomFilter(m);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* BitMap实现
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* BitMap实现
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.bloomfilter.bitMap;
|
@ -1,25 +1,25 @@
|
||||
package cn.hutool.bloomfilter.filter;
|
||||
|
||||
import cn.hutool.core.util.HashUtil;
|
||||
|
||||
/**
|
||||
* 默认Bloom过滤器,使用Java自带的Hash算法
|
||||
*
|
||||
* @author loolly
|
||||
*/
|
||||
public class DefaultFilter extends AbstractFilter {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public DefaultFilter(long maxValue, int machineNumber) {
|
||||
super(maxValue, machineNumber);
|
||||
}
|
||||
|
||||
public DefaultFilter(long maxValue) {
|
||||
super(maxValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long hash(String str) {
|
||||
return HashUtil.javaDefaultHash(str) % size;
|
||||
}
|
||||
}
|
||||
package cn.hutool.bloomfilter.filter;
|
||||
|
||||
import cn.hutool.core.util.HashUtil;
|
||||
|
||||
/**
|
||||
* 默认Bloom过滤器,使用Java自带的Hash算法
|
||||
*
|
||||
* @author loolly
|
||||
*/
|
||||
public class DefaultFilter extends AbstractFilter {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public DefaultFilter(long maxValue, int machineNumber) {
|
||||
super(maxValue, machineNumber);
|
||||
}
|
||||
|
||||
public DefaultFilter(long maxValue) {
|
||||
super(maxValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long hash(String str) {
|
||||
return HashUtil.javaDefaultHash(str) % size;
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,21 @@
|
||||
package cn.hutool.bloomfilter.filter;
|
||||
|
||||
import cn.hutool.core.util.HashUtil;
|
||||
|
||||
public class ELFFilter extends AbstractFilter {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ELFFilter(long maxValue, int machineNumber) {
|
||||
super(maxValue, machineNumber);
|
||||
}
|
||||
|
||||
public ELFFilter(long maxValue) {
|
||||
super(maxValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long hash(String str) {
|
||||
return HashUtil.elfHash(str) % size;
|
||||
}
|
||||
|
||||
}
|
||||
package cn.hutool.bloomfilter.filter;
|
||||
|
||||
import cn.hutool.core.util.HashUtil;
|
||||
|
||||
public class ELFFilter extends AbstractFilter {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ELFFilter(long maxValue, int machineNumber) {
|
||||
super(maxValue, machineNumber);
|
||||
}
|
||||
|
||||
public ELFFilter(long maxValue) {
|
||||
super(maxValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long hash(String str) {
|
||||
return HashUtil.elfHash(str) % size;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,21 +1,21 @@
|
||||
package cn.hutool.bloomfilter.filter;
|
||||
|
||||
import cn.hutool.core.util.HashUtil;
|
||||
|
||||
public class FNVFilter extends AbstractFilter {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public FNVFilter(long maxValue, int machineNum) {
|
||||
super(maxValue, machineNum);
|
||||
}
|
||||
|
||||
public FNVFilter(long maxValue) {
|
||||
super(maxValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long hash(String str) {
|
||||
return HashUtil.fnvHash(str);
|
||||
}
|
||||
|
||||
}
|
||||
package cn.hutool.bloomfilter.filter;
|
||||
|
||||
import cn.hutool.core.util.HashUtil;
|
||||
|
||||
public class FNVFilter extends AbstractFilter {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public FNVFilter(long maxValue, int machineNum) {
|
||||
super(maxValue, machineNum);
|
||||
}
|
||||
|
||||
public FNVFilter(long maxValue) {
|
||||
super(maxValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long hash(String str) {
|
||||
return HashUtil.fnvHash(str);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,31 +1,31 @@
|
||||
package cn.hutool.bloomfilter.filter;
|
||||
|
||||
|
||||
public class HfFilter extends AbstractFilter {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public HfFilter(long maxValue, int machineNum) {
|
||||
super(maxValue, machineNum);
|
||||
}
|
||||
|
||||
public HfFilter(long maxValue) {
|
||||
super(maxValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long hash(String str) {
|
||||
int length = str.length() ;
|
||||
long hash = 0;
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
hash += str.charAt(i) * 3 * i;
|
||||
}
|
||||
|
||||
if (hash < 0) {
|
||||
hash = -hash;
|
||||
}
|
||||
|
||||
return hash % size;
|
||||
}
|
||||
|
||||
}
|
||||
package cn.hutool.bloomfilter.filter;
|
||||
|
||||
|
||||
public class HfFilter extends AbstractFilter {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public HfFilter(long maxValue, int machineNum) {
|
||||
super(maxValue, machineNum);
|
||||
}
|
||||
|
||||
public HfFilter(long maxValue) {
|
||||
super(maxValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long hash(String str) {
|
||||
int length = str.length() ;
|
||||
long hash = 0;
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
hash += str.charAt(i) * 3 * i;
|
||||
}
|
||||
|
||||
if (hash < 0) {
|
||||
hash = -hash;
|
||||
}
|
||||
|
||||
return hash % size;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,24 +1,24 @@
|
||||
package cn.hutool.bloomfilter.filter;
|
||||
|
||||
public class HfIpFilter extends AbstractFilter {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public HfIpFilter(long maxValue, int machineNum) {
|
||||
super(maxValue, machineNum);
|
||||
}
|
||||
|
||||
public HfIpFilter(long maxValue) {
|
||||
super(maxValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long hash(String str) {
|
||||
int length = str.length();
|
||||
long hash = 0;
|
||||
for (int i = 0; i < length; i++) {
|
||||
hash += str.charAt(i % 4) ^ str.charAt(i);
|
||||
}
|
||||
return hash % size;
|
||||
}
|
||||
|
||||
}
|
||||
package cn.hutool.bloomfilter.filter;
|
||||
|
||||
public class HfIpFilter extends AbstractFilter {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public HfIpFilter(long maxValue, int machineNum) {
|
||||
super(maxValue, machineNum);
|
||||
}
|
||||
|
||||
public HfIpFilter(long maxValue) {
|
||||
super(maxValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long hash(String str) {
|
||||
int length = str.length();
|
||||
long hash = 0;
|
||||
for (int i = 0; i < length; i++) {
|
||||
hash += str.charAt(i % 4) ^ str.charAt(i);
|
||||
}
|
||||
return hash % size;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,30 +1,30 @@
|
||||
package cn.hutool.bloomfilter.filter;
|
||||
|
||||
|
||||
public class JSFilter extends AbstractFilter {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public JSFilter(long maxValue, int machineNum) {
|
||||
super(maxValue, machineNum);
|
||||
}
|
||||
|
||||
public JSFilter(long maxValue) {
|
||||
super(maxValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long hash(String str) {
|
||||
int hash = 1315423911;
|
||||
|
||||
for (int i = 0; i < str.length(); i++) {
|
||||
hash ^= ((hash << 5) + str.charAt(i) + (hash >> 2));
|
||||
}
|
||||
|
||||
if(hash<0) {
|
||||
hash*=-1 ;
|
||||
}
|
||||
|
||||
return hash % size;
|
||||
}
|
||||
|
||||
}
|
||||
package cn.hutool.bloomfilter.filter;
|
||||
|
||||
|
||||
public class JSFilter extends AbstractFilter {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public JSFilter(long maxValue, int machineNum) {
|
||||
super(maxValue, machineNum);
|
||||
}
|
||||
|
||||
public JSFilter(long maxValue) {
|
||||
super(maxValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long hash(String str) {
|
||||
int hash = 1315423911;
|
||||
|
||||
for (int i = 0; i < str.length(); i++) {
|
||||
hash ^= ((hash << 5) + str.charAt(i) + (hash >> 2));
|
||||
}
|
||||
|
||||
if(hash<0) {
|
||||
hash*=-1 ;
|
||||
}
|
||||
|
||||
return hash % size;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,21 +1,21 @@
|
||||
package cn.hutool.bloomfilter.filter;
|
||||
|
||||
import cn.hutool.core.util.HashUtil;
|
||||
|
||||
public class PJWFilter extends AbstractFilter {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public PJWFilter(long maxValue, int machineNum) {
|
||||
super(maxValue, machineNum);
|
||||
}
|
||||
|
||||
public PJWFilter(long maxValue) {
|
||||
super(maxValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long hash(String str) {
|
||||
return HashUtil.pjwHash(str) % size;
|
||||
}
|
||||
|
||||
}
|
||||
package cn.hutool.bloomfilter.filter;
|
||||
|
||||
import cn.hutool.core.util.HashUtil;
|
||||
|
||||
public class PJWFilter extends AbstractFilter {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public PJWFilter(long maxValue, int machineNum) {
|
||||
super(maxValue, machineNum);
|
||||
}
|
||||
|
||||
public PJWFilter(long maxValue) {
|
||||
super(maxValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long hash(String str) {
|
||||
return HashUtil.pjwHash(str) % size;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,21 +1,21 @@
|
||||
package cn.hutool.bloomfilter.filter;
|
||||
|
||||
import cn.hutool.core.util.HashUtil;
|
||||
|
||||
public class RSFilter extends AbstractFilter {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public RSFilter(long maxValue, int machineNum) {
|
||||
super(maxValue, machineNum);
|
||||
}
|
||||
|
||||
public RSFilter(long maxValue) {
|
||||
super(maxValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long hash(String str) {
|
||||
return HashUtil.rsHash(str) % size;
|
||||
}
|
||||
|
||||
}
|
||||
package cn.hutool.bloomfilter.filter;
|
||||
|
||||
import cn.hutool.core.util.HashUtil;
|
||||
|
||||
public class RSFilter extends AbstractFilter {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public RSFilter(long maxValue, int machineNum) {
|
||||
super(maxValue, machineNum);
|
||||
}
|
||||
|
||||
public RSFilter(long maxValue) {
|
||||
super(maxValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long hash(String str) {
|
||||
return HashUtil.rsHash(str) % size;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,21 +1,21 @@
|
||||
package cn.hutool.bloomfilter.filter;
|
||||
|
||||
import cn.hutool.core.util.HashUtil;
|
||||
|
||||
public class SDBMFilter extends AbstractFilter {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public SDBMFilter(long maxValue, int machineNum) {
|
||||
super(maxValue, machineNum);
|
||||
}
|
||||
|
||||
public SDBMFilter(long maxValue) {
|
||||
super(maxValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long hash(String str) {
|
||||
return HashUtil.sdbmHash(str) % size;
|
||||
}
|
||||
|
||||
}
|
||||
package cn.hutool.bloomfilter.filter;
|
||||
|
||||
import cn.hutool.core.util.HashUtil;
|
||||
|
||||
public class SDBMFilter extends AbstractFilter {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public SDBMFilter(long maxValue, int machineNum) {
|
||||
super(maxValue, machineNum);
|
||||
}
|
||||
|
||||
public SDBMFilter(long maxValue) {
|
||||
super(maxValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long hash(String str) {
|
||||
return HashUtil.sdbmHash(str) % size;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,22 +1,22 @@
|
||||
package cn.hutool.bloomfilter.filter;
|
||||
|
||||
import cn.hutool.core.util.HashUtil;
|
||||
|
||||
|
||||
public class TianlFilter extends AbstractFilter {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public TianlFilter(long maxValue, int machineNum) {
|
||||
super(maxValue, machineNum);
|
||||
}
|
||||
|
||||
public TianlFilter(long maxValue) {
|
||||
super(maxValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long hash(String str) {
|
||||
return HashUtil.tianlHash(str) % size;
|
||||
}
|
||||
|
||||
}
|
||||
package cn.hutool.bloomfilter.filter;
|
||||
|
||||
import cn.hutool.core.util.HashUtil;
|
||||
|
||||
|
||||
public class TianlFilter extends AbstractFilter {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public TianlFilter(long maxValue, int machineNum) {
|
||||
super(maxValue, machineNum);
|
||||
}
|
||||
|
||||
public TianlFilter(long maxValue) {
|
||||
super(maxValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long hash(String str) {
|
||||
return HashUtil.tianlHash(str) % size;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* 各种Hash算法的过滤器实现
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* 各种Hash算法的过滤器实现
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.bloomfilter.filter;
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* 布隆过滤,提供一些Hash算法的布隆过滤
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* 布隆过滤,提供一些Hash算法的布隆过滤
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.bloomfilter;
|
@ -1,55 +1,55 @@
|
||||
package cn.hutool.bloomfilter;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import cn.hutool.bloomfilter.bitMap.IntMap;
|
||||
import cn.hutool.bloomfilter.bitMap.LongMap;
|
||||
|
||||
public class BitMapBloomFilterTest {
|
||||
|
||||
@Test
|
||||
public void filterTest() {
|
||||
BitMapBloomFilter filter = new BitMapBloomFilter(10);
|
||||
filter.add("123");
|
||||
filter.add("abc");
|
||||
filter.add("ddd");
|
||||
|
||||
Assert.assertTrue(filter.contains("abc"));
|
||||
Assert.assertTrue(filter.contains("ddd"));
|
||||
Assert.assertTrue(filter.contains("123"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testIntMap(){
|
||||
IntMap intMap = new IntMap();
|
||||
|
||||
for (int i = 0 ; i < 32; i++) {
|
||||
intMap.add(i);
|
||||
}
|
||||
intMap.remove(30);
|
||||
|
||||
|
||||
for (int i = 0; i < 32; i++) {
|
||||
System.out.println(i + "是否存在-->" + intMap.contains(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testLongMap(){
|
||||
LongMap longMap = new LongMap();
|
||||
|
||||
for (int i = 0 ; i < 64; i++) {
|
||||
longMap.add(i);
|
||||
}
|
||||
longMap.remove(30);
|
||||
|
||||
|
||||
for (int i = 0; i < 64; i++) {
|
||||
System.out.println(i + "是否存在-->" + longMap.contains(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
package cn.hutool.bloomfilter;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import cn.hutool.bloomfilter.bitMap.IntMap;
|
||||
import cn.hutool.bloomfilter.bitMap.LongMap;
|
||||
|
||||
public class BitMapBloomFilterTest {
|
||||
|
||||
@Test
|
||||
public void filterTest() {
|
||||
BitMapBloomFilter filter = new BitMapBloomFilter(10);
|
||||
filter.add("123");
|
||||
filter.add("abc");
|
||||
filter.add("ddd");
|
||||
|
||||
Assert.assertTrue(filter.contains("abc"));
|
||||
Assert.assertTrue(filter.contains("ddd"));
|
||||
Assert.assertTrue(filter.contains("123"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testIntMap(){
|
||||
IntMap intMap = new IntMap();
|
||||
|
||||
for (int i = 0 ; i < 32; i++) {
|
||||
intMap.add(i);
|
||||
}
|
||||
intMap.remove(30);
|
||||
|
||||
|
||||
for (int i = 0; i < 32; i++) {
|
||||
System.out.println(i + "是否存在-->" + intMap.contains(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testLongMap(){
|
||||
LongMap longMap = new LongMap();
|
||||
|
||||
for (int i = 0 ; i < 64; i++) {
|
||||
longMap.add(i);
|
||||
}
|
||||
longMap.remove(30);
|
||||
|
||||
|
||||
for (int i = 0; i < 64; i++) {
|
||||
System.out.println(i + "是否存在-->" + longMap.contains(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,111 +1,111 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-bom</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<description>提供丰富的Java工具方法,此模块为Hutool所有模块汇总,最终形式为拆分开的多个jar包,可以通过exclude方式排除不需要的模块</description>
|
||||
<url>https://github.com/looly/hutool</url>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-aop</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-bloomFilter</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-cache</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-crypto</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-db</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-dfa</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-extra</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-http</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-log</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-script</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-setting</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-system</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-cron</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-json</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-poi</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-captcha</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-socket</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-bom</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<description>提供丰富的Java工具方法,此模块为Hutool所有模块汇总,最终形式为拆分开的多个jar包,可以通过exclude方式排除不需要的模块</description>
|
||||
<url>https://github.com/looly/hutool</url>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-aop</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-bloomFilter</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-cache</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-crypto</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-db</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-dfa</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-extra</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-http</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-log</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-script</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-setting</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-system</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-cron</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-json</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-poi</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-captcha</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-socket</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
@ -1,24 +1,24 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-cache</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<description>Hutool 缓存</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-cache</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<description>Hutool 缓存</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
@ -1,172 +1,172 @@
|
||||
package cn.hutool.cache;
|
||||
|
||||
import cn.hutool.cache.impl.CacheObj;
|
||||
import cn.hutool.core.lang.func.Func0;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* 缓存接口
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
* @author Looly, jodd
|
||||
*/
|
||||
public interface Cache<K, V> extends Iterable<V>, Serializable {
|
||||
|
||||
/**
|
||||
* 返回缓存容量,<code>0</code>表示无大小限制
|
||||
*
|
||||
* @return 返回缓存容量,<code>0</code>表示无大小限制
|
||||
*/
|
||||
int capacity();
|
||||
|
||||
/**
|
||||
* 缓存失效时长, <code>0</code> 表示没有设置,单位毫秒
|
||||
*
|
||||
* @return 缓存失效时长, <code>0</code> 表示没有设置,单位毫秒
|
||||
*/
|
||||
long timeout();
|
||||
|
||||
/**
|
||||
* 将对象加入到缓存,使用默认失效时长
|
||||
*
|
||||
* @param key 键
|
||||
* @param object 缓存的对象
|
||||
* @see Cache#put(Object, Object, long)
|
||||
*/
|
||||
void put(K key, V object);
|
||||
|
||||
/**
|
||||
* 将对象加入到缓存,使用指定失效时长<br>
|
||||
* 如果缓存空间满了,{@link #prune()} 将被调用以获得空间来存放新对象
|
||||
*
|
||||
* @param key 键
|
||||
* @param object 缓存的对象
|
||||
* @param timeout 失效时长,单位毫秒
|
||||
*/
|
||||
void put(K key, V object, long timeout);
|
||||
|
||||
/**
|
||||
* 从缓存中获得对象,当对象不在缓存中或已经过期返回<code>null</code>
|
||||
* <p>
|
||||
* 调用此方法时,会检查上次调用时间,如果与当前时间差值大于超时时间返回<code>null</code>,否则返回值。
|
||||
* <p>
|
||||
* 每次调用此方法会刷新最后访问时间,也就是说会重新计算超时时间。
|
||||
*
|
||||
* @param key 键
|
||||
* @return 键对应的对象
|
||||
* @see #get(Object, boolean)
|
||||
*/
|
||||
default V get(K key) {
|
||||
return get(key, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从缓存中获得对象,当对象不在缓存中或已经过期返回Func0回调产生的对象
|
||||
* <p>
|
||||
* 调用此方法时,会检查上次调用时间,如果与当前时间差值大于超时时间返回<code>null</code>,否则返回值。
|
||||
* <p>
|
||||
* 每次调用此方法会刷新最后访问时间,也就是说会重新计算超时时间。
|
||||
*
|
||||
* @param key 键
|
||||
* @param supplier 如果不存在回调方法,用于生产值对象
|
||||
* @return 值对象
|
||||
*/
|
||||
default V get(K key, Func0<V> supplier) {
|
||||
return get(key, true, supplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从缓存中获得对象,当对象不在缓存中或已经过期返回Func0回调产生的对象
|
||||
* <p>
|
||||
* 调用此方法时,会检查上次调用时间,如果与当前时间差值大于超时时间返回<code>null</code>,否则返回值。
|
||||
* <p>
|
||||
* 每次调用此方法会刷新最后访问时间,也就是说会重新计算超时时间。
|
||||
*
|
||||
* @param key 键
|
||||
* @param isUpdateLastAccess 是否更新最后访问时间,即重新计算超时时间。
|
||||
* @param supplier 如果不存在回调方法,用于生产值对象
|
||||
* @return 值对象
|
||||
*/
|
||||
V get(K key, boolean isUpdateLastAccess, Func0<V> supplier);
|
||||
|
||||
/**
|
||||
* 从缓存中获得对象,当对象不在缓存中或已经过期返回<code>null</code>
|
||||
* <p>
|
||||
* 调用此方法时,会检查上次调用时间,如果与当前时间差值大于超时时间返回<code>null</code>,否则返回值。
|
||||
*
|
||||
* @param key 键
|
||||
* @param isUpdateLastAccess 是否更新最后访问时间,即重新计算超时时间。
|
||||
* @return 键对应的对象
|
||||
*/
|
||||
V get(K key, boolean isUpdateLastAccess);
|
||||
|
||||
/**
|
||||
* 返回包含键和值得迭代器
|
||||
*
|
||||
* @return 缓存对象迭代器
|
||||
* @since 4.0.10
|
||||
*/
|
||||
Iterator<CacheObj<K, V>> cacheObjIterator();
|
||||
|
||||
/**
|
||||
* 从缓存中清理过期对象,清理策略取决于具体实现
|
||||
*
|
||||
* @return 清理的缓存对象个数
|
||||
*/
|
||||
int prune();
|
||||
|
||||
/**
|
||||
* 缓存是否已满,仅用于有空间限制的缓存对象
|
||||
*
|
||||
* @return 缓存是否已满,仅用于有空间限制的缓存对象
|
||||
*/
|
||||
boolean isFull();
|
||||
|
||||
/**
|
||||
* 从缓存中移除对象
|
||||
*
|
||||
* @param key 键
|
||||
*/
|
||||
void remove(K key);
|
||||
|
||||
/**
|
||||
* 清空缓存
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* 缓存的对象数量
|
||||
*
|
||||
* @return 缓存的对象数量
|
||||
*/
|
||||
int size();
|
||||
|
||||
/**
|
||||
* 缓存是否为空
|
||||
*
|
||||
* @return 缓存是否为空
|
||||
*/
|
||||
boolean isEmpty();
|
||||
|
||||
/**
|
||||
* 是否包含key
|
||||
*
|
||||
* @param key KEY
|
||||
* @return 是否包含key
|
||||
*/
|
||||
boolean containsKey(K key);
|
||||
|
||||
/**
|
||||
* 设置监听
|
||||
*
|
||||
* @param listener 监听
|
||||
* @return this
|
||||
* @since 5.5.2
|
||||
*/
|
||||
default Cache<K, V> setListener(CacheListener<K, V> listener){
|
||||
return this;
|
||||
}
|
||||
}
|
||||
package cn.hutool.cache;
|
||||
|
||||
import cn.hutool.cache.impl.CacheObj;
|
||||
import cn.hutool.core.lang.func.Func0;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* 缓存接口
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
* @author Looly, jodd
|
||||
*/
|
||||
public interface Cache<K, V> extends Iterable<V>, Serializable {
|
||||
|
||||
/**
|
||||
* 返回缓存容量,<code>0</code>表示无大小限制
|
||||
*
|
||||
* @return 返回缓存容量,<code>0</code>表示无大小限制
|
||||
*/
|
||||
int capacity();
|
||||
|
||||
/**
|
||||
* 缓存失效时长, <code>0</code> 表示没有设置,单位毫秒
|
||||
*
|
||||
* @return 缓存失效时长, <code>0</code> 表示没有设置,单位毫秒
|
||||
*/
|
||||
long timeout();
|
||||
|
||||
/**
|
||||
* 将对象加入到缓存,使用默认失效时长
|
||||
*
|
||||
* @param key 键
|
||||
* @param object 缓存的对象
|
||||
* @see Cache#put(Object, Object, long)
|
||||
*/
|
||||
void put(K key, V object);
|
||||
|
||||
/**
|
||||
* 将对象加入到缓存,使用指定失效时长<br>
|
||||
* 如果缓存空间满了,{@link #prune()} 将被调用以获得空间来存放新对象
|
||||
*
|
||||
* @param key 键
|
||||
* @param object 缓存的对象
|
||||
* @param timeout 失效时长,单位毫秒
|
||||
*/
|
||||
void put(K key, V object, long timeout);
|
||||
|
||||
/**
|
||||
* 从缓存中获得对象,当对象不在缓存中或已经过期返回<code>null</code>
|
||||
* <p>
|
||||
* 调用此方法时,会检查上次调用时间,如果与当前时间差值大于超时时间返回<code>null</code>,否则返回值。
|
||||
* <p>
|
||||
* 每次调用此方法会刷新最后访问时间,也就是说会重新计算超时时间。
|
||||
*
|
||||
* @param key 键
|
||||
* @return 键对应的对象
|
||||
* @see #get(Object, boolean)
|
||||
*/
|
||||
default V get(K key) {
|
||||
return get(key, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从缓存中获得对象,当对象不在缓存中或已经过期返回Func0回调产生的对象
|
||||
* <p>
|
||||
* 调用此方法时,会检查上次调用时间,如果与当前时间差值大于超时时间返回<code>null</code>,否则返回值。
|
||||
* <p>
|
||||
* 每次调用此方法会刷新最后访问时间,也就是说会重新计算超时时间。
|
||||
*
|
||||
* @param key 键
|
||||
* @param supplier 如果不存在回调方法,用于生产值对象
|
||||
* @return 值对象
|
||||
*/
|
||||
default V get(K key, Func0<V> supplier) {
|
||||
return get(key, true, supplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从缓存中获得对象,当对象不在缓存中或已经过期返回Func0回调产生的对象
|
||||
* <p>
|
||||
* 调用此方法时,会检查上次调用时间,如果与当前时间差值大于超时时间返回<code>null</code>,否则返回值。
|
||||
* <p>
|
||||
* 每次调用此方法会刷新最后访问时间,也就是说会重新计算超时时间。
|
||||
*
|
||||
* @param key 键
|
||||
* @param isUpdateLastAccess 是否更新最后访问时间,即重新计算超时时间。
|
||||
* @param supplier 如果不存在回调方法,用于生产值对象
|
||||
* @return 值对象
|
||||
*/
|
||||
V get(K key, boolean isUpdateLastAccess, Func0<V> supplier);
|
||||
|
||||
/**
|
||||
* 从缓存中获得对象,当对象不在缓存中或已经过期返回<code>null</code>
|
||||
* <p>
|
||||
* 调用此方法时,会检查上次调用时间,如果与当前时间差值大于超时时间返回<code>null</code>,否则返回值。
|
||||
*
|
||||
* @param key 键
|
||||
* @param isUpdateLastAccess 是否更新最后访问时间,即重新计算超时时间。
|
||||
* @return 键对应的对象
|
||||
*/
|
||||
V get(K key, boolean isUpdateLastAccess);
|
||||
|
||||
/**
|
||||
* 返回包含键和值得迭代器
|
||||
*
|
||||
* @return 缓存对象迭代器
|
||||
* @since 4.0.10
|
||||
*/
|
||||
Iterator<CacheObj<K, V>> cacheObjIterator();
|
||||
|
||||
/**
|
||||
* 从缓存中清理过期对象,清理策略取决于具体实现
|
||||
*
|
||||
* @return 清理的缓存对象个数
|
||||
*/
|
||||
int prune();
|
||||
|
||||
/**
|
||||
* 缓存是否已满,仅用于有空间限制的缓存对象
|
||||
*
|
||||
* @return 缓存是否已满,仅用于有空间限制的缓存对象
|
||||
*/
|
||||
boolean isFull();
|
||||
|
||||
/**
|
||||
* 从缓存中移除对象
|
||||
*
|
||||
* @param key 键
|
||||
*/
|
||||
void remove(K key);
|
||||
|
||||
/**
|
||||
* 清空缓存
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* 缓存的对象数量
|
||||
*
|
||||
* @return 缓存的对象数量
|
||||
*/
|
||||
int size();
|
||||
|
||||
/**
|
||||
* 缓存是否为空
|
||||
*
|
||||
* @return 缓存是否为空
|
||||
*/
|
||||
boolean isEmpty();
|
||||
|
||||
/**
|
||||
* 是否包含key
|
||||
*
|
||||
* @param key KEY
|
||||
* @return 是否包含key
|
||||
*/
|
||||
boolean containsKey(K key);
|
||||
|
||||
/**
|
||||
* 设置监听
|
||||
*
|
||||
* @param listener 监听
|
||||
* @return this
|
||||
* @since 5.5.2
|
||||
*/
|
||||
default Cache<K, V> setListener(CacheListener<K, V> listener){
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,20 @@
|
||||
package cn.hutool.cache;
|
||||
|
||||
/**
|
||||
* 缓存监听,用于实现缓存操作时的回调监听,例如缓存对象的移除事件等
|
||||
*
|
||||
* @param <K> 缓存键
|
||||
* @param <V> 缓存值
|
||||
* @author looly
|
||||
* @since 5.5.2
|
||||
*/
|
||||
public interface CacheListener<K, V> {
|
||||
|
||||
/**
|
||||
* 对象移除回调
|
||||
*
|
||||
* @param key 键
|
||||
* @param cachedObject 被缓存的对象
|
||||
*/
|
||||
void onRemove(K key, V cachedObject);
|
||||
}
|
||||
package cn.hutool.cache;
|
||||
|
||||
/**
|
||||
* 缓存监听,用于实现缓存操作时的回调监听,例如缓存对象的移除事件等
|
||||
*
|
||||
* @param <K> 缓存键
|
||||
* @param <V> 缓存值
|
||||
* @author looly
|
||||
* @since 5.5.2
|
||||
*/
|
||||
public interface CacheListener<K, V> {
|
||||
|
||||
/**
|
||||
* 对象移除回调
|
||||
*
|
||||
* @param key 键
|
||||
* @param cachedObject 被缓存的对象
|
||||
*/
|
||||
void onRemove(K key, V cachedObject);
|
||||
}
|
||||
|
@ -1,128 +1,128 @@
|
||||
package cn.hutool.cache;
|
||||
|
||||
import cn.hutool.cache.impl.FIFOCache;
|
||||
import cn.hutool.cache.impl.LFUCache;
|
||||
import cn.hutool.cache.impl.LRUCache;
|
||||
import cn.hutool.cache.impl.NoCache;
|
||||
import cn.hutool.cache.impl.TimedCache;
|
||||
import cn.hutool.cache.impl.WeakCache;
|
||||
|
||||
/**
|
||||
* 缓存工具类
|
||||
* @author Looly
|
||||
*@since 3.0.1
|
||||
*/
|
||||
public class CacheUtil {
|
||||
|
||||
/**
|
||||
* 创建FIFO(first in first out) 先进先出缓存.
|
||||
*
|
||||
* @param <K> Key类型
|
||||
* @param <V> Value类型
|
||||
* @param capacity 容量
|
||||
* @param timeout 过期时长,单位:毫秒
|
||||
* @return {@link FIFOCache}
|
||||
*/
|
||||
public static <K, V> FIFOCache<K, V> newFIFOCache(int capacity, long timeout){
|
||||
return new FIFOCache<>(capacity, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建FIFO(first in first out) 先进先出缓存.
|
||||
*
|
||||
* @param <K> Key类型
|
||||
* @param <V> Value类型
|
||||
* @param capacity 容量
|
||||
* @return {@link FIFOCache}
|
||||
*/
|
||||
public static <K, V> FIFOCache<K, V> newFIFOCache(int capacity){
|
||||
return new FIFOCache<>(capacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建LFU(least frequently used) 最少使用率缓存.
|
||||
*
|
||||
* @param <K> Key类型
|
||||
* @param <V> Value类型
|
||||
* @param capacity 容量
|
||||
* @param timeout 过期时长,单位:毫秒
|
||||
* @return {@link LFUCache}
|
||||
*/
|
||||
public static <K, V> LFUCache<K, V> newLFUCache(int capacity, long timeout){
|
||||
return new LFUCache<>(capacity, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建LFU(least frequently used) 最少使用率缓存.
|
||||
*
|
||||
* @param <K> Key类型
|
||||
* @param <V> Value类型
|
||||
* @param capacity 容量
|
||||
* @return {@link LFUCache}
|
||||
*/
|
||||
public static <K, V> LFUCache<K, V> newLFUCache(int capacity){
|
||||
return new LFUCache<>(capacity);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建LRU (least recently used)最近最久未使用缓存.
|
||||
*
|
||||
* @param <K> Key类型
|
||||
* @param <V> Value类型
|
||||
* @param capacity 容量
|
||||
* @param timeout 过期时长,单位:毫秒
|
||||
* @return {@link LRUCache}
|
||||
*/
|
||||
public static <K, V> LRUCache<K, V> newLRUCache(int capacity, long timeout){
|
||||
return new LRUCache<>(capacity, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建LRU (least recently used)最近最久未使用缓存.
|
||||
*
|
||||
* @param <K> Key类型
|
||||
* @param <V> Value类型
|
||||
* @param capacity 容量
|
||||
* @return {@link LRUCache}
|
||||
*/
|
||||
public static <K, V> LRUCache<K, V> newLRUCache(int capacity){
|
||||
return new LRUCache<>(capacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建定时缓存.
|
||||
*
|
||||
* @param <K> Key类型
|
||||
* @param <V> Value类型
|
||||
* @param timeout 过期时长,单位:毫秒
|
||||
* @return {@link TimedCache}
|
||||
*/
|
||||
public static <K, V> TimedCache<K, V> newTimedCache(long timeout){
|
||||
return new TimedCache<>(timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建弱引用缓存.
|
||||
*
|
||||
* @param <K> Key类型
|
||||
* @param <V> Value类型
|
||||
* @param timeout 过期时长,单位:毫秒
|
||||
* @return {@link WeakCache}
|
||||
* @since 3.0.7
|
||||
*/
|
||||
public static <K, V> WeakCache<K, V> newWeakCache(long timeout){
|
||||
return new WeakCache<>(timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建无缓存实现.
|
||||
*
|
||||
* @param <K> Key类型
|
||||
* @param <V> Value类型
|
||||
* @return {@link NoCache}
|
||||
*/
|
||||
public static <K, V> NoCache<K, V> newNoCache(){
|
||||
return new NoCache<>();
|
||||
}
|
||||
}
|
||||
package cn.hutool.cache;
|
||||
|
||||
import cn.hutool.cache.impl.FIFOCache;
|
||||
import cn.hutool.cache.impl.LFUCache;
|
||||
import cn.hutool.cache.impl.LRUCache;
|
||||
import cn.hutool.cache.impl.NoCache;
|
||||
import cn.hutool.cache.impl.TimedCache;
|
||||
import cn.hutool.cache.impl.WeakCache;
|
||||
|
||||
/**
|
||||
* 缓存工具类
|
||||
* @author Looly
|
||||
*@since 3.0.1
|
||||
*/
|
||||
public class CacheUtil {
|
||||
|
||||
/**
|
||||
* 创建FIFO(first in first out) 先进先出缓存.
|
||||
*
|
||||
* @param <K> Key类型
|
||||
* @param <V> Value类型
|
||||
* @param capacity 容量
|
||||
* @param timeout 过期时长,单位:毫秒
|
||||
* @return {@link FIFOCache}
|
||||
*/
|
||||
public static <K, V> FIFOCache<K, V> newFIFOCache(int capacity, long timeout){
|
||||
return new FIFOCache<>(capacity, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建FIFO(first in first out) 先进先出缓存.
|
||||
*
|
||||
* @param <K> Key类型
|
||||
* @param <V> Value类型
|
||||
* @param capacity 容量
|
||||
* @return {@link FIFOCache}
|
||||
*/
|
||||
public static <K, V> FIFOCache<K, V> newFIFOCache(int capacity){
|
||||
return new FIFOCache<>(capacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建LFU(least frequently used) 最少使用率缓存.
|
||||
*
|
||||
* @param <K> Key类型
|
||||
* @param <V> Value类型
|
||||
* @param capacity 容量
|
||||
* @param timeout 过期时长,单位:毫秒
|
||||
* @return {@link LFUCache}
|
||||
*/
|
||||
public static <K, V> LFUCache<K, V> newLFUCache(int capacity, long timeout){
|
||||
return new LFUCache<>(capacity, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建LFU(least frequently used) 最少使用率缓存.
|
||||
*
|
||||
* @param <K> Key类型
|
||||
* @param <V> Value类型
|
||||
* @param capacity 容量
|
||||
* @return {@link LFUCache}
|
||||
*/
|
||||
public static <K, V> LFUCache<K, V> newLFUCache(int capacity){
|
||||
return new LFUCache<>(capacity);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建LRU (least recently used)最近最久未使用缓存.
|
||||
*
|
||||
* @param <K> Key类型
|
||||
* @param <V> Value类型
|
||||
* @param capacity 容量
|
||||
* @param timeout 过期时长,单位:毫秒
|
||||
* @return {@link LRUCache}
|
||||
*/
|
||||
public static <K, V> LRUCache<K, V> newLRUCache(int capacity, long timeout){
|
||||
return new LRUCache<>(capacity, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建LRU (least recently used)最近最久未使用缓存.
|
||||
*
|
||||
* @param <K> Key类型
|
||||
* @param <V> Value类型
|
||||
* @param capacity 容量
|
||||
* @return {@link LRUCache}
|
||||
*/
|
||||
public static <K, V> LRUCache<K, V> newLRUCache(int capacity){
|
||||
return new LRUCache<>(capacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建定时缓存.
|
||||
*
|
||||
* @param <K> Key类型
|
||||
* @param <V> Value类型
|
||||
* @param timeout 过期时长,单位:毫秒
|
||||
* @return {@link TimedCache}
|
||||
*/
|
||||
public static <K, V> TimedCache<K, V> newTimedCache(long timeout){
|
||||
return new TimedCache<>(timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建弱引用缓存.
|
||||
*
|
||||
* @param <K> Key类型
|
||||
* @param <V> Value类型
|
||||
* @param timeout 过期时长,单位:毫秒
|
||||
* @return {@link WeakCache}
|
||||
* @since 3.0.7
|
||||
*/
|
||||
public static <K, V> WeakCache<K, V> newWeakCache(long timeout){
|
||||
return new WeakCache<>(timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建无缓存实现.
|
||||
*
|
||||
* @param <K> Key类型
|
||||
* @param <V> Value类型
|
||||
* @return {@link NoCache}
|
||||
*/
|
||||
public static <K, V> NoCache<K, V> newNoCache(){
|
||||
return new NoCache<>();
|
||||
}
|
||||
}
|
||||
|
@ -1,82 +1,82 @@
|
||||
package cn.hutool.cache;
|
||||
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* 全局缓存清理定时器池,用于在需要过期支持的缓存对象中超时任务池
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
public enum GlobalPruneTimer {
|
||||
/**
|
||||
* 单例对象
|
||||
*/
|
||||
INSTANCE;
|
||||
|
||||
/**
|
||||
* 缓存任务计数
|
||||
*/
|
||||
private final AtomicInteger cacheTaskNumber = new AtomicInteger(1);
|
||||
|
||||
/**
|
||||
* 定时器
|
||||
*/
|
||||
private ScheduledExecutorService pruneTimer;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*/
|
||||
GlobalPruneTimer() {
|
||||
create();
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动定时任务
|
||||
*
|
||||
* @param task 任务
|
||||
* @param delay 周期
|
||||
* @return {@link ScheduledFuture}对象,可手动取消此任务
|
||||
*/
|
||||
public ScheduledFuture<?> schedule(Runnable task, long delay) {
|
||||
return this.pruneTimer.scheduleAtFixedRate(task, delay, delay, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建定时器
|
||||
*/
|
||||
public void create() {
|
||||
if (null != pruneTimer) {
|
||||
shutdownNow();
|
||||
}
|
||||
this.pruneTimer = new ScheduledThreadPoolExecutor(1, r -> ThreadUtil.newThread(r, StrUtil.format("Pure-Timer-{}", cacheTaskNumber.getAndIncrement())));
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁全局定时器
|
||||
*/
|
||||
public void shutdown() {
|
||||
if (null != pruneTimer) {
|
||||
pruneTimer.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁全局定时器
|
||||
*
|
||||
* @return 销毁时未被执行的任务列表
|
||||
*/
|
||||
public List<Runnable> shutdownNow() {
|
||||
if (null != pruneTimer) {
|
||||
return pruneTimer.shutdownNow();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
package cn.hutool.cache;
|
||||
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* 全局缓存清理定时器池,用于在需要过期支持的缓存对象中超时任务池
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
public enum GlobalPruneTimer {
|
||||
/**
|
||||
* 单例对象
|
||||
*/
|
||||
INSTANCE;
|
||||
|
||||
/**
|
||||
* 缓存任务计数
|
||||
*/
|
||||
private final AtomicInteger cacheTaskNumber = new AtomicInteger(1);
|
||||
|
||||
/**
|
||||
* 定时器
|
||||
*/
|
||||
private ScheduledExecutorService pruneTimer;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*/
|
||||
GlobalPruneTimer() {
|
||||
create();
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动定时任务
|
||||
*
|
||||
* @param task 任务
|
||||
* @param delay 周期
|
||||
* @return {@link ScheduledFuture}对象,可手动取消此任务
|
||||
*/
|
||||
public ScheduledFuture<?> schedule(Runnable task, long delay) {
|
||||
return this.pruneTimer.scheduleAtFixedRate(task, delay, delay, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建定时器
|
||||
*/
|
||||
public void create() {
|
||||
if (null != pruneTimer) {
|
||||
shutdownNow();
|
||||
}
|
||||
this.pruneTimer = new ScheduledThreadPoolExecutor(1, r -> ThreadUtil.newThread(r, StrUtil.format("Pure-Timer-{}", cacheTaskNumber.getAndIncrement())));
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁全局定时器
|
||||
*/
|
||||
public void shutdown() {
|
||||
if (null != pruneTimer) {
|
||||
pruneTimer.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁全局定时器
|
||||
*
|
||||
* @return 销毁时未被执行的任务列表
|
||||
*/
|
||||
public List<Runnable> shutdownNow() {
|
||||
if (null != pruneTimer) {
|
||||
return pruneTimer.shutdownNow();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -1,134 +1,134 @@
|
||||
package cn.hutool.cache.file;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.Serializable;
|
||||
|
||||
import cn.hutool.cache.Cache;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
|
||||
/**
|
||||
* 文件缓存,以解决频繁读取文件引起的性能问题
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractFileCache implements Serializable{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 容量 */
|
||||
protected final int capacity;
|
||||
/** 缓存的最大文件大小,文件大于此大小时将不被缓存 */
|
||||
protected final int maxFileSize;
|
||||
/** 默认超时时间,0表示无默认超时 */
|
||||
protected final long timeout;
|
||||
/** 缓存实现 */
|
||||
protected final Cache<File, byte[]> cache;
|
||||
|
||||
/** 已使用缓存空间 */
|
||||
protected int usedSize;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
* @param capacity 缓存容量
|
||||
* @param maxFileSize 文件最大大小
|
||||
* @param timeout 默认超时时间,0表示无默认超时
|
||||
*/
|
||||
public AbstractFileCache(int capacity, int maxFileSize, long timeout) {
|
||||
this.capacity = capacity;
|
||||
this.maxFileSize = maxFileSize;
|
||||
this.timeout = timeout;
|
||||
this.cache = initCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 缓存容量(byte数)
|
||||
*/
|
||||
public int capacity() {
|
||||
return capacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 已使用空间大小(byte数)
|
||||
*/
|
||||
public int getUsedSize() {
|
||||
return usedSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 允许被缓存文件的最大byte数
|
||||
*/
|
||||
public int maxFileSize() {
|
||||
return maxFileSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 缓存的文件数
|
||||
*/
|
||||
public int getCachedFilesCount() {
|
||||
return cache.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 超时时间
|
||||
*/
|
||||
public long timeout() {
|
||||
return this.timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空缓存
|
||||
*/
|
||||
public void clear() {
|
||||
cache.clear();
|
||||
usedSize = 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- get
|
||||
|
||||
/**
|
||||
* 获得缓存过的文件bytes
|
||||
* @param path 文件路径
|
||||
* @return 缓存过的文件bytes
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public byte[] getFileBytes(String path) throws IORuntimeException {
|
||||
return getFileBytes(new File(path));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得缓存过的文件bytes
|
||||
* @param file 文件
|
||||
* @return 缓存过的文件bytes
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public byte[] getFileBytes(File file) throws IORuntimeException {
|
||||
byte[] bytes = cache.get(file);
|
||||
if (bytes != null) {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// add file
|
||||
bytes = FileUtil.readBytes(file);
|
||||
|
||||
if ((maxFileSize != 0) && (file.length() > maxFileSize)) {
|
||||
//大于缓存空间,不缓存,直接返回
|
||||
return bytes;
|
||||
}
|
||||
|
||||
usedSize += bytes.length;
|
||||
|
||||
//文件放入缓存,如果usedSize > capacity,purge()方法将被调用
|
||||
cache.put(file, bytes);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- protected method start
|
||||
/**
|
||||
* 初始化实现文件缓存的缓存对象
|
||||
* @return {@link Cache}
|
||||
*/
|
||||
protected abstract Cache<File, byte[]> initCache();
|
||||
// ---------------------------------------------------------------- protected method end
|
||||
|
||||
}
|
||||
package cn.hutool.cache.file;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.Serializable;
|
||||
|
||||
import cn.hutool.cache.Cache;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
|
||||
/**
|
||||
* 文件缓存,以解决频繁读取文件引起的性能问题
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractFileCache implements Serializable{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 容量 */
|
||||
protected final int capacity;
|
||||
/** 缓存的最大文件大小,文件大于此大小时将不被缓存 */
|
||||
protected final int maxFileSize;
|
||||
/** 默认超时时间,0表示无默认超时 */
|
||||
protected final long timeout;
|
||||
/** 缓存实现 */
|
||||
protected final Cache<File, byte[]> cache;
|
||||
|
||||
/** 已使用缓存空间 */
|
||||
protected int usedSize;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
* @param capacity 缓存容量
|
||||
* @param maxFileSize 文件最大大小
|
||||
* @param timeout 默认超时时间,0表示无默认超时
|
||||
*/
|
||||
public AbstractFileCache(int capacity, int maxFileSize, long timeout) {
|
||||
this.capacity = capacity;
|
||||
this.maxFileSize = maxFileSize;
|
||||
this.timeout = timeout;
|
||||
this.cache = initCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 缓存容量(byte数)
|
||||
*/
|
||||
public int capacity() {
|
||||
return capacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 已使用空间大小(byte数)
|
||||
*/
|
||||
public int getUsedSize() {
|
||||
return usedSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 允许被缓存文件的最大byte数
|
||||
*/
|
||||
public int maxFileSize() {
|
||||
return maxFileSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 缓存的文件数
|
||||
*/
|
||||
public int getCachedFilesCount() {
|
||||
return cache.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 超时时间
|
||||
*/
|
||||
public long timeout() {
|
||||
return this.timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空缓存
|
||||
*/
|
||||
public void clear() {
|
||||
cache.clear();
|
||||
usedSize = 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- get
|
||||
|
||||
/**
|
||||
* 获得缓存过的文件bytes
|
||||
* @param path 文件路径
|
||||
* @return 缓存过的文件bytes
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public byte[] getFileBytes(String path) throws IORuntimeException {
|
||||
return getFileBytes(new File(path));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得缓存过的文件bytes
|
||||
* @param file 文件
|
||||
* @return 缓存过的文件bytes
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public byte[] getFileBytes(File file) throws IORuntimeException {
|
||||
byte[] bytes = cache.get(file);
|
||||
if (bytes != null) {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// add file
|
||||
bytes = FileUtil.readBytes(file);
|
||||
|
||||
if ((maxFileSize != 0) && (file.length() > maxFileSize)) {
|
||||
//大于缓存空间,不缓存,直接返回
|
||||
return bytes;
|
||||
}
|
||||
|
||||
usedSize += bytes.length;
|
||||
|
||||
//文件放入缓存,如果usedSize > capacity,purge()方法将被调用
|
||||
cache.put(file, bytes);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- protected method start
|
||||
/**
|
||||
* 初始化实现文件缓存的缓存对象
|
||||
* @return {@link Cache}
|
||||
*/
|
||||
protected abstract Cache<File, byte[]> initCache();
|
||||
// ---------------------------------------------------------------- protected method end
|
||||
|
||||
}
|
||||
|
@ -1,63 +1,63 @@
|
||||
package cn.hutool.cache.file;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import cn.hutool.cache.Cache;
|
||||
import cn.hutool.cache.impl.LFUCache;
|
||||
|
||||
/**
|
||||
* 使用LFU缓存文件,以解决频繁读取文件引起的性能问题
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class LFUFileCache extends AbstractFileCache{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 构造<br>
|
||||
* 最大文件大小为缓存容量的一半<br>
|
||||
* 默认无超时
|
||||
* @param capacity 缓存容量
|
||||
*/
|
||||
public LFUFileCache(int capacity) {
|
||||
this(capacity, capacity / 2, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造<br>
|
||||
* 默认无超时
|
||||
* @param capacity 缓存容量
|
||||
* @param maxFileSize 最大文件大小
|
||||
*/
|
||||
public LFUFileCache(int capacity, int maxFileSize) {
|
||||
this(capacity, maxFileSize, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
* @param capacity 缓存容量
|
||||
* @param maxFileSize 文件最大大小
|
||||
* @param timeout 默认超时时间,0表示无默认超时
|
||||
*/
|
||||
public LFUFileCache(int capacity, int maxFileSize, long timeout) {
|
||||
super(capacity, maxFileSize, timeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Cache<File, byte[]> initCache() {
|
||||
return new LFUCache<File, byte[]>(LFUFileCache.this.capacity, LFUFileCache.this.timeout) {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public boolean isFull() {
|
||||
return LFUFileCache.this.usedSize > this.capacity;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemove(File key, byte[] cachedObject) {
|
||||
usedSize -= cachedObject.length;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
package cn.hutool.cache.file;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import cn.hutool.cache.Cache;
|
||||
import cn.hutool.cache.impl.LFUCache;
|
||||
|
||||
/**
|
||||
* 使用LFU缓存文件,以解决频繁读取文件引起的性能问题
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class LFUFileCache extends AbstractFileCache{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 构造<br>
|
||||
* 最大文件大小为缓存容量的一半<br>
|
||||
* 默认无超时
|
||||
* @param capacity 缓存容量
|
||||
*/
|
||||
public LFUFileCache(int capacity) {
|
||||
this(capacity, capacity / 2, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造<br>
|
||||
* 默认无超时
|
||||
* @param capacity 缓存容量
|
||||
* @param maxFileSize 最大文件大小
|
||||
*/
|
||||
public LFUFileCache(int capacity, int maxFileSize) {
|
||||
this(capacity, maxFileSize, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
* @param capacity 缓存容量
|
||||
* @param maxFileSize 文件最大大小
|
||||
* @param timeout 默认超时时间,0表示无默认超时
|
||||
*/
|
||||
public LFUFileCache(int capacity, int maxFileSize, long timeout) {
|
||||
super(capacity, maxFileSize, timeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Cache<File, byte[]> initCache() {
|
||||
return new LFUCache<File, byte[]>(LFUFileCache.this.capacity, LFUFileCache.this.timeout) {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public boolean isFull() {
|
||||
return LFUFileCache.this.usedSize > this.capacity;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemove(File key, byte[] cachedObject) {
|
||||
usedSize -= cachedObject.length;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,63 +1,63 @@
|
||||
package cn.hutool.cache.file;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import cn.hutool.cache.Cache;
|
||||
import cn.hutool.cache.impl.LRUCache;
|
||||
|
||||
/**
|
||||
* 使用LRU缓存文件,以解决频繁读取文件引起的性能问题
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class LRUFileCache extends AbstractFileCache{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 构造<br>
|
||||
* 最大文件大小为缓存容量的一半<br>
|
||||
* 默认无超时
|
||||
* @param capacity 缓存容量
|
||||
*/
|
||||
public LRUFileCache(int capacity) {
|
||||
this(capacity, capacity / 2, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造<br>
|
||||
* 默认无超时
|
||||
* @param capacity 缓存容量
|
||||
* @param maxFileSize 最大文件大小
|
||||
*/
|
||||
public LRUFileCache(int capacity, int maxFileSize) {
|
||||
this(capacity, maxFileSize, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
* @param capacity 缓存容量
|
||||
* @param maxFileSize 文件最大大小
|
||||
* @param timeout 默认超时时间,0表示无默认超时
|
||||
*/
|
||||
public LRUFileCache(int capacity, int maxFileSize, long timeout) {
|
||||
super(capacity, maxFileSize, timeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Cache<File, byte[]> initCache() {
|
||||
return new LRUCache<File, byte[]>(LRUFileCache.this.capacity, super.timeout) {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public boolean isFull() {
|
||||
return LRUFileCache.this.usedSize > this.capacity;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemove(File key, byte[] cachedObject) {
|
||||
usedSize -= cachedObject.length;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
package cn.hutool.cache.file;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import cn.hutool.cache.Cache;
|
||||
import cn.hutool.cache.impl.LRUCache;
|
||||
|
||||
/**
|
||||
* 使用LRU缓存文件,以解决频繁读取文件引起的性能问题
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class LRUFileCache extends AbstractFileCache{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 构造<br>
|
||||
* 最大文件大小为缓存容量的一半<br>
|
||||
* 默认无超时
|
||||
* @param capacity 缓存容量
|
||||
*/
|
||||
public LRUFileCache(int capacity) {
|
||||
this(capacity, capacity / 2, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造<br>
|
||||
* 默认无超时
|
||||
* @param capacity 缓存容量
|
||||
* @param maxFileSize 最大文件大小
|
||||
*/
|
||||
public LRUFileCache(int capacity, int maxFileSize) {
|
||||
this(capacity, maxFileSize, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
* @param capacity 缓存容量
|
||||
* @param maxFileSize 文件最大大小
|
||||
* @param timeout 默认超时时间,0表示无默认超时
|
||||
*/
|
||||
public LRUFileCache(int capacity, int maxFileSize, long timeout) {
|
||||
super(capacity, maxFileSize, timeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Cache<File, byte[]> initCache() {
|
||||
return new LRUCache<File, byte[]>(LRUFileCache.this.capacity, super.timeout) {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public boolean isFull() {
|
||||
return LRUFileCache.this.usedSize > this.capacity;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemove(File key, byte[] cachedObject) {
|
||||
usedSize -= cachedObject.length;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* 提供针对文件的缓存实现
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* 提供针对文件的缓存实现
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.cache.file;
|
@ -1,90 +1,90 @@
|
||||
package cn.hutool.cache.impl;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* 缓存对象
|
||||
* @author Looly
|
||||
*
|
||||
* @param <K> Key类型
|
||||
* @param <V> Value类型
|
||||
*/
|
||||
public class CacheObj<K, V> implements Serializable{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
protected final K key;
|
||||
protected final V obj;
|
||||
|
||||
/** 上次访问时间 */
|
||||
private volatile long lastAccess;
|
||||
/** 访问次数 */
|
||||
protected AtomicLong accessCount = new AtomicLong();
|
||||
/** 对象存活时长,0表示永久存活*/
|
||||
private final long ttl;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param key 键
|
||||
* @param obj 值
|
||||
* @param ttl 超时时长
|
||||
*/
|
||||
protected CacheObj(K key, V obj, long ttl) {
|
||||
this.key = key;
|
||||
this.obj = obj;
|
||||
this.ttl = ttl;
|
||||
this.lastAccess = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否过期
|
||||
*
|
||||
* @return 是否过期
|
||||
*/
|
||||
boolean isExpired() {
|
||||
if(this.ttl > 0) {
|
||||
// 此处不考虑时间回拨
|
||||
return (System.currentTimeMillis() - this.lastAccess) > this.ttl;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取值
|
||||
*
|
||||
* @param isUpdateLastAccess 是否更新最后访问时间
|
||||
* @return 获得对象
|
||||
* @since 4.0.10
|
||||
*/
|
||||
V get(boolean isUpdateLastAccess) {
|
||||
if(isUpdateLastAccess) {
|
||||
lastAccess = System.currentTimeMillis();
|
||||
}
|
||||
accessCount.getAndIncrement();
|
||||
return this.obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取键
|
||||
* @return 键
|
||||
* @since 4.0.10
|
||||
*/
|
||||
public K getKey() {
|
||||
return this.key;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取值
|
||||
* @return 值
|
||||
* @since 4.0.10
|
||||
*/
|
||||
public V getValue() {
|
||||
return this.obj;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CacheObj [key=" + key + ", obj=" + obj + ", lastAccess=" + lastAccess + ", accessCount=" + accessCount + ", ttl=" + ttl + "]";
|
||||
}
|
||||
}
|
||||
package cn.hutool.cache.impl;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* 缓存对象
|
||||
* @author Looly
|
||||
*
|
||||
* @param <K> Key类型
|
||||
* @param <V> Value类型
|
||||
*/
|
||||
public class CacheObj<K, V> implements Serializable{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
protected final K key;
|
||||
protected final V obj;
|
||||
|
||||
/** 上次访问时间 */
|
||||
private volatile long lastAccess;
|
||||
/** 访问次数 */
|
||||
protected AtomicLong accessCount = new AtomicLong();
|
||||
/** 对象存活时长,0表示永久存活*/
|
||||
private final long ttl;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param key 键
|
||||
* @param obj 值
|
||||
* @param ttl 超时时长
|
||||
*/
|
||||
protected CacheObj(K key, V obj, long ttl) {
|
||||
this.key = key;
|
||||
this.obj = obj;
|
||||
this.ttl = ttl;
|
||||
this.lastAccess = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否过期
|
||||
*
|
||||
* @return 是否过期
|
||||
*/
|
||||
boolean isExpired() {
|
||||
if(this.ttl > 0) {
|
||||
// 此处不考虑时间回拨
|
||||
return (System.currentTimeMillis() - this.lastAccess) > this.ttl;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取值
|
||||
*
|
||||
* @param isUpdateLastAccess 是否更新最后访问时间
|
||||
* @return 获得对象
|
||||
* @since 4.0.10
|
||||
*/
|
||||
V get(boolean isUpdateLastAccess) {
|
||||
if(isUpdateLastAccess) {
|
||||
lastAccess = System.currentTimeMillis();
|
||||
}
|
||||
accessCount.getAndIncrement();
|
||||
return this.obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取键
|
||||
* @return 键
|
||||
* @since 4.0.10
|
||||
*/
|
||||
public K getKey() {
|
||||
return this.key;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取值
|
||||
* @return 值
|
||||
* @since 4.0.10
|
||||
*/
|
||||
public V getValue() {
|
||||
return this.obj;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CacheObj [key=" + key + ", obj=" + obj + ", lastAccess=" + lastAccess + ", accessCount=" + accessCount + ", ttl=" + ttl + "]";
|
||||
}
|
||||
}
|
||||
|
@ -1,73 +1,73 @@
|
||||
package cn.hutool.cache.impl;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/**
|
||||
* {@link cn.hutool.cache.impl.AbstractCache} 的CacheObj迭代器.
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
* @since 4.0.10
|
||||
*/
|
||||
public class CacheObjIterator<K, V> implements Iterator<CacheObj<K, V>>, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Iterator<CacheObj<K, V>> iterator;
|
||||
private CacheObj<K, V> nextValue;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param iterator 原{@link Iterator}
|
||||
*/
|
||||
CacheObjIterator(Iterator<CacheObj<K, V>> iterator) {
|
||||
this.iterator = iterator;
|
||||
nextValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 是否有下一个值
|
||||
*/
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return nextValue != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 下一个值
|
||||
*/
|
||||
@Override
|
||||
public CacheObj<K, V> next() {
|
||||
if (false == hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
final CacheObj<K, V> cachedObject = nextValue;
|
||||
nextValue();
|
||||
return cachedObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从缓存中移除没有过期的当前值,此方法不支持
|
||||
*/
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException("Cache values Iterator is not support to modify.");
|
||||
}
|
||||
|
||||
/**
|
||||
* 下一个值,当不存在则下一个值为null
|
||||
*/
|
||||
private void nextValue() {
|
||||
while (iterator.hasNext()) {
|
||||
nextValue = iterator.next();
|
||||
if (nextValue.isExpired() == false) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
nextValue = null;
|
||||
}
|
||||
}
|
||||
package cn.hutool.cache.impl;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/**
|
||||
* {@link cn.hutool.cache.impl.AbstractCache} 的CacheObj迭代器.
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
* @since 4.0.10
|
||||
*/
|
||||
public class CacheObjIterator<K, V> implements Iterator<CacheObj<K, V>>, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Iterator<CacheObj<K, V>> iterator;
|
||||
private CacheObj<K, V> nextValue;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param iterator 原{@link Iterator}
|
||||
*/
|
||||
CacheObjIterator(Iterator<CacheObj<K, V>> iterator) {
|
||||
this.iterator = iterator;
|
||||
nextValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 是否有下一个值
|
||||
*/
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return nextValue != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 下一个值
|
||||
*/
|
||||
@Override
|
||||
public CacheObj<K, V> next() {
|
||||
if (false == hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
final CacheObj<K, V> cachedObject = nextValue;
|
||||
nextValue();
|
||||
return cachedObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从缓存中移除没有过期的当前值,此方法不支持
|
||||
*/
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException("Cache values Iterator is not support to modify.");
|
||||
}
|
||||
|
||||
/**
|
||||
* 下一个值,当不存在则下一个值为null
|
||||
*/
|
||||
private void nextValue() {
|
||||
while (iterator.hasNext()) {
|
||||
nextValue = iterator.next();
|
||||
if (nextValue.isExpired() == false) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
nextValue = null;
|
||||
}
|
||||
}
|
||||
|
@ -1,48 +1,48 @@
|
||||
package cn.hutool.cache.impl;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* {@link cn.hutool.cache.impl.AbstractCache} 的值迭代器.
|
||||
* @author looly
|
||||
*
|
||||
* @param <V> 迭代对象类型
|
||||
*/
|
||||
public class CacheValuesIterator<V> implements Iterator<V>, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final CacheObjIterator<?, V> cacheObjIter;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
* @param iterator 原{@link CacheObjIterator}
|
||||
*/
|
||||
CacheValuesIterator(CacheObjIterator<?, V> iterator) {
|
||||
this.cacheObjIter = iterator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 是否有下一个值
|
||||
*/
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return this.cacheObjIter.hasNext();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 下一个值
|
||||
*/
|
||||
@Override
|
||||
public V next() {
|
||||
return cacheObjIter.next().getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从缓存中移除没有过期的当前值,不支持此方法
|
||||
*/
|
||||
@Override
|
||||
public void remove() {
|
||||
cacheObjIter.remove();
|
||||
}
|
||||
}
|
||||
package cn.hutool.cache.impl;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* {@link cn.hutool.cache.impl.AbstractCache} 的值迭代器.
|
||||
* @author looly
|
||||
*
|
||||
* @param <V> 迭代对象类型
|
||||
*/
|
||||
public class CacheValuesIterator<V> implements Iterator<V>, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final CacheObjIterator<?, V> cacheObjIter;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
* @param iterator 原{@link CacheObjIterator}
|
||||
*/
|
||||
CacheValuesIterator(CacheObjIterator<?, V> iterator) {
|
||||
this.cacheObjIter = iterator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 是否有下一个值
|
||||
*/
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return this.cacheObjIter.hasNext();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 下一个值
|
||||
*/
|
||||
@Override
|
||||
public V next() {
|
||||
return cacheObjIter.next().getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从缓存中移除没有过期的当前值,不支持此方法
|
||||
*/
|
||||
@Override
|
||||
public void remove() {
|
||||
cacheObjIter.remove();
|
||||
}
|
||||
}
|
||||
|
@ -1,73 +1,73 @@
|
||||
package cn.hutool.cache.impl;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
/**
|
||||
* FIFO(first in first out) 先进先出缓存.
|
||||
*
|
||||
* <p>
|
||||
* 元素不停的加入缓存直到缓存满为止,当缓存满时,清理过期缓存对象,清理后依旧满则删除先入的缓存(链表首部对象)<br>
|
||||
* 优点:简单快速 <br>
|
||||
* 缺点:不灵活,不能保证最常用的对象总是被保留
|
||||
* </p>
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
* @author Looly
|
||||
*/
|
||||
public class FIFOCache<K, V> extends AbstractCache<K, V> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 构造,默认对象不过期
|
||||
*
|
||||
* @param capacity 容量
|
||||
*/
|
||||
public FIFOCache(int capacity) {
|
||||
this(capacity, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param capacity 容量
|
||||
* @param timeout 过期时长
|
||||
*/
|
||||
public FIFOCache(int capacity, long timeout) {
|
||||
this.capacity = capacity;
|
||||
this.timeout = timeout;
|
||||
cacheMap = new LinkedHashMap<>(Math.max(1 << 4, capacity >>> 7), 1.0f, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 先进先出的清理策略<br>
|
||||
* 先遍历缓存清理过期的缓存对象,如果清理后还是满的,则删除第一个缓存对象
|
||||
*/
|
||||
@Override
|
||||
protected int pruneCache() {
|
||||
int count = 0;
|
||||
CacheObj<K, V> first = null;
|
||||
|
||||
// 清理过期对象并找出链表头部元素(先入元素)
|
||||
Iterator<CacheObj<K, V>> values = cacheMap.values().iterator();
|
||||
while (values.hasNext()) {
|
||||
CacheObj<K, V> co = values.next();
|
||||
if (co.isExpired()) {
|
||||
values.remove();
|
||||
count++;
|
||||
}
|
||||
if (first == null) {
|
||||
first = co;
|
||||
}
|
||||
}
|
||||
|
||||
// 清理结束后依旧是满的,则删除第一个被缓存的对象
|
||||
if (isFull() && null != first) {
|
||||
cacheMap.remove(first.key);
|
||||
onRemove(first.key, first.obj);
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
package cn.hutool.cache.impl;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
/**
|
||||
* FIFO(first in first out) 先进先出缓存.
|
||||
*
|
||||
* <p>
|
||||
* 元素不停的加入缓存直到缓存满为止,当缓存满时,清理过期缓存对象,清理后依旧满则删除先入的缓存(链表首部对象)<br>
|
||||
* 优点:简单快速 <br>
|
||||
* 缺点:不灵活,不能保证最常用的对象总是被保留
|
||||
* </p>
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
* @author Looly
|
||||
*/
|
||||
public class FIFOCache<K, V> extends AbstractCache<K, V> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 构造,默认对象不过期
|
||||
*
|
||||
* @param capacity 容量
|
||||
*/
|
||||
public FIFOCache(int capacity) {
|
||||
this(capacity, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param capacity 容量
|
||||
* @param timeout 过期时长
|
||||
*/
|
||||
public FIFOCache(int capacity, long timeout) {
|
||||
this.capacity = capacity;
|
||||
this.timeout = timeout;
|
||||
cacheMap = new LinkedHashMap<>(Math.max(1 << 4, capacity >>> 7), 1.0f, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 先进先出的清理策略<br>
|
||||
* 先遍历缓存清理过期的缓存对象,如果清理后还是满的,则删除第一个缓存对象
|
||||
*/
|
||||
@Override
|
||||
protected int pruneCache() {
|
||||
int count = 0;
|
||||
CacheObj<K, V> first = null;
|
||||
|
||||
// 清理过期对象并找出链表头部元素(先入元素)
|
||||
Iterator<CacheObj<K, V>> values = cacheMap.values().iterator();
|
||||
while (values.hasNext()) {
|
||||
CacheObj<K, V> co = values.next();
|
||||
if (co.isExpired()) {
|
||||
values.remove();
|
||||
count++;
|
||||
}
|
||||
if (first == null) {
|
||||
first = co;
|
||||
}
|
||||
}
|
||||
|
||||
// 清理结束后依旧是满的,则删除第一个被缓存的对象
|
||||
if (isFull() && null != first) {
|
||||
cacheMap.remove(first.key);
|
||||
onRemove(first.key, first.obj);
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
@ -1,95 +1,95 @@
|
||||
package cn.hutool.cache.impl;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* LFU(least frequently used) 最少使用率缓存<br>
|
||||
* 根据使用次数来判定对象是否被持续缓存<br>
|
||||
* 使用率是通过访问次数计算的。<br>
|
||||
* 当缓存满时清理过期对象。<br>
|
||||
* 清理后依旧满的情况下清除最少访问(访问计数最小)的对象并将其他对象的访问数减去这个最小访问数,以便新对象进入后可以公平计数。
|
||||
*
|
||||
* @author Looly,jodd
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
*/
|
||||
public class LFUCache<K, V> extends AbstractCache<K, V> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param capacity 容量
|
||||
*/
|
||||
public LFUCache(int capacity) {
|
||||
this(capacity, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param capacity 容量
|
||||
* @param timeout 过期时长
|
||||
*/
|
||||
public LFUCache(int capacity, long timeout) {
|
||||
if(Integer.MAX_VALUE == capacity) {
|
||||
capacity -= 1;
|
||||
}
|
||||
|
||||
this.capacity = capacity;
|
||||
this.timeout = timeout;
|
||||
cacheMap = new HashMap<>(capacity + 1, 1.0f);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- prune
|
||||
|
||||
/**
|
||||
* 清理过期对象。<br>
|
||||
* 清理后依旧满的情况下清除最少访问(访问计数最小)的对象并将其他对象的访问数减去这个最小访问数,以便新对象进入后可以公平计数。
|
||||
*
|
||||
* @return 清理个数
|
||||
*/
|
||||
@Override
|
||||
protected int pruneCache() {
|
||||
int count = 0;
|
||||
CacheObj<K, V> comin = null;
|
||||
|
||||
// 清理过期对象并找出访问最少的对象
|
||||
Iterator<CacheObj<K, V>> values = cacheMap.values().iterator();
|
||||
CacheObj<K, V> co;
|
||||
while (values.hasNext()) {
|
||||
co = values.next();
|
||||
if (co.isExpired() == true) {
|
||||
values.remove();
|
||||
onRemove(co.key, co.obj);
|
||||
count++;
|
||||
continue;
|
||||
}
|
||||
|
||||
//找出访问最少的对象
|
||||
if (comin == null || co.accessCount.get() < comin.accessCount.get()) {
|
||||
comin = co;
|
||||
}
|
||||
}
|
||||
|
||||
// 减少所有对象访问量,并清除减少后为0的访问对象
|
||||
if (isFull() && comin != null) {
|
||||
long minAccessCount = comin.accessCount.get();
|
||||
|
||||
values = cacheMap.values().iterator();
|
||||
CacheObj<K, V> co1;
|
||||
while (values.hasNext()) {
|
||||
co1 = values.next();
|
||||
if (co1.accessCount.addAndGet(-minAccessCount) <= 0) {
|
||||
values.remove();
|
||||
onRemove(co1.key, co1.obj);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
package cn.hutool.cache.impl;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* LFU(least frequently used) 最少使用率缓存<br>
|
||||
* 根据使用次数来判定对象是否被持续缓存<br>
|
||||
* 使用率是通过访问次数计算的。<br>
|
||||
* 当缓存满时清理过期对象。<br>
|
||||
* 清理后依旧满的情况下清除最少访问(访问计数最小)的对象并将其他对象的访问数减去这个最小访问数,以便新对象进入后可以公平计数。
|
||||
*
|
||||
* @author Looly,jodd
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
*/
|
||||
public class LFUCache<K, V> extends AbstractCache<K, V> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param capacity 容量
|
||||
*/
|
||||
public LFUCache(int capacity) {
|
||||
this(capacity, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param capacity 容量
|
||||
* @param timeout 过期时长
|
||||
*/
|
||||
public LFUCache(int capacity, long timeout) {
|
||||
if(Integer.MAX_VALUE == capacity) {
|
||||
capacity -= 1;
|
||||
}
|
||||
|
||||
this.capacity = capacity;
|
||||
this.timeout = timeout;
|
||||
cacheMap = new HashMap<>(capacity + 1, 1.0f);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- prune
|
||||
|
||||
/**
|
||||
* 清理过期对象。<br>
|
||||
* 清理后依旧满的情况下清除最少访问(访问计数最小)的对象并将其他对象的访问数减去这个最小访问数,以便新对象进入后可以公平计数。
|
||||
*
|
||||
* @return 清理个数
|
||||
*/
|
||||
@Override
|
||||
protected int pruneCache() {
|
||||
int count = 0;
|
||||
CacheObj<K, V> comin = null;
|
||||
|
||||
// 清理过期对象并找出访问最少的对象
|
||||
Iterator<CacheObj<K, V>> values = cacheMap.values().iterator();
|
||||
CacheObj<K, V> co;
|
||||
while (values.hasNext()) {
|
||||
co = values.next();
|
||||
if (co.isExpired() == true) {
|
||||
values.remove();
|
||||
onRemove(co.key, co.obj);
|
||||
count++;
|
||||
continue;
|
||||
}
|
||||
|
||||
//找出访问最少的对象
|
||||
if (comin == null || co.accessCount.get() < comin.accessCount.get()) {
|
||||
comin = co;
|
||||
}
|
||||
}
|
||||
|
||||
// 减少所有对象访问量,并清除减少后为0的访问对象
|
||||
if (isFull() && comin != null) {
|
||||
long minAccessCount = comin.accessCount.get();
|
||||
|
||||
values = cacheMap.values().iterator();
|
||||
CacheObj<K, V> co1;
|
||||
while (values.hasNext()) {
|
||||
co1 = values.next();
|
||||
if (co1.accessCount.addAndGet(-minAccessCount) <= 0) {
|
||||
values.remove();
|
||||
onRemove(co1.key, co1.obj);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
@ -1,71 +1,71 @@
|
||||
package cn.hutool.cache.impl;
|
||||
|
||||
import cn.hutool.core.map.FixedLinkedHashMap;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* LRU (least recently used)最近最久未使用缓存<br>
|
||||
* 根据使用时间来判定对象是否被持续缓存<br>
|
||||
* 当对象被访问时放入缓存,当缓存满了,最久未被使用的对象将被移除。<br>
|
||||
* 此缓存基于LinkedHashMap,因此当被缓存的对象每被访问一次,这个对象的key就到链表头部。<br>
|
||||
* 这个算法简单并且非常快,他比FIFO有一个显著优势是经常使用的对象不太可能被移除缓存。<br>
|
||||
* 缺点是当缓存满时,不能被很快的访问。
|
||||
* @author Looly,jodd
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
*/
|
||||
public class LRUCache<K, V> extends AbstractCache<K, V> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 构造<br>
|
||||
* 默认无超时
|
||||
* @param capacity 容量
|
||||
*/
|
||||
public LRUCache(int capacity) {
|
||||
this(capacity, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
* @param capacity 容量
|
||||
* @param timeout 默认超时时间,单位:毫秒
|
||||
*/
|
||||
public LRUCache(int capacity, long timeout) {
|
||||
if(Integer.MAX_VALUE == capacity) {
|
||||
capacity -= 1;
|
||||
}
|
||||
|
||||
this.capacity = capacity;
|
||||
this.timeout = timeout;
|
||||
|
||||
//链表key按照访问顺序排序,调用get方法后,会将这次访问的元素移至头部
|
||||
cacheMap = new FixedLinkedHashMap<>(capacity);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- prune
|
||||
|
||||
/**
|
||||
* 只清理超时对象,LRU的实现会交给<code>LinkedHashMap</code>
|
||||
*/
|
||||
@Override
|
||||
protected int pruneCache() {
|
||||
if (isPruneExpiredActive() == false) {
|
||||
return 0;
|
||||
}
|
||||
int count = 0;
|
||||
Iterator<CacheObj<K, V>> values = cacheMap.values().iterator();
|
||||
CacheObj<K, V> co;
|
||||
while (values.hasNext()) {
|
||||
co = values.next();
|
||||
if (co.isExpired()) {
|
||||
values.remove();
|
||||
onRemove(co.key, co.obj);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
package cn.hutool.cache.impl;
|
||||
|
||||
import cn.hutool.core.map.FixedLinkedHashMap;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* LRU (least recently used)最近最久未使用缓存<br>
|
||||
* 根据使用时间来判定对象是否被持续缓存<br>
|
||||
* 当对象被访问时放入缓存,当缓存满了,最久未被使用的对象将被移除。<br>
|
||||
* 此缓存基于LinkedHashMap,因此当被缓存的对象每被访问一次,这个对象的key就到链表头部。<br>
|
||||
* 这个算法简单并且非常快,他比FIFO有一个显著优势是经常使用的对象不太可能被移除缓存。<br>
|
||||
* 缺点是当缓存满时,不能被很快的访问。
|
||||
* @author Looly,jodd
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
*/
|
||||
public class LRUCache<K, V> extends AbstractCache<K, V> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 构造<br>
|
||||
* 默认无超时
|
||||
* @param capacity 容量
|
||||
*/
|
||||
public LRUCache(int capacity) {
|
||||
this(capacity, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
* @param capacity 容量
|
||||
* @param timeout 默认超时时间,单位:毫秒
|
||||
*/
|
||||
public LRUCache(int capacity, long timeout) {
|
||||
if(Integer.MAX_VALUE == capacity) {
|
||||
capacity -= 1;
|
||||
}
|
||||
|
||||
this.capacity = capacity;
|
||||
this.timeout = timeout;
|
||||
|
||||
//链表key按照访问顺序排序,调用get方法后,会将这次访问的元素移至头部
|
||||
cacheMap = new FixedLinkedHashMap<>(capacity);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- prune
|
||||
|
||||
/**
|
||||
* 只清理超时对象,LRU的实现会交给<code>LinkedHashMap</code>
|
||||
*/
|
||||
@Override
|
||||
protected int pruneCache() {
|
||||
if (isPruneExpiredActive() == false) {
|
||||
return 0;
|
||||
}
|
||||
int count = 0;
|
||||
Iterator<CacheObj<K, V>> values = cacheMap.values().iterator();
|
||||
CacheObj<K, V> co;
|
||||
while (values.hasNext()) {
|
||||
co = values.next();
|
||||
if (co.isExpired()) {
|
||||
values.remove();
|
||||
onRemove(co.key, co.obj);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
@ -1,87 +1,87 @@
|
||||
package cn.hutool.cache.impl;
|
||||
|
||||
import cn.hutool.cache.GlobalPruneTimer;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
|
||||
/**
|
||||
* 定时缓存<br>
|
||||
* 此缓存没有容量限制,对象只有在过期后才会被移除
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
*/
|
||||
public class TimedCache<K, V> extends AbstractCache<K, V> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 正在执行的定时任务 */
|
||||
private ScheduledFuture<?> pruneJobFuture;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param timeout 超时(过期)时长,单位毫秒
|
||||
*/
|
||||
public TimedCache(long timeout) {
|
||||
this(timeout, new HashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param timeout 过期时长
|
||||
* @param map 存储缓存对象的map
|
||||
*/
|
||||
public TimedCache(long timeout, Map<K, CacheObj<K, V>> map) {
|
||||
this.capacity = 0;
|
||||
this.timeout = timeout;
|
||||
this.cacheMap = map;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- prune
|
||||
/**
|
||||
* 清理过期对象
|
||||
*
|
||||
* @return 清理数
|
||||
*/
|
||||
@Override
|
||||
protected int pruneCache() {
|
||||
int count = 0;
|
||||
Iterator<CacheObj<K, V>> values = cacheMap.values().iterator();
|
||||
CacheObj<K, V> co;
|
||||
while (values.hasNext()) {
|
||||
co = values.next();
|
||||
if (co.isExpired()) {
|
||||
values.remove();
|
||||
onRemove(co.key, co.obj);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- auto prune
|
||||
/**
|
||||
* 定时清理
|
||||
*
|
||||
* @param delay 间隔时长,单位毫秒
|
||||
*/
|
||||
public void schedulePrune(long delay) {
|
||||
this.pruneJobFuture = GlobalPruneTimer.INSTANCE.schedule(this::prune, delay);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消定时清理
|
||||
*/
|
||||
public void cancelPruneSchedule() {
|
||||
if (null != pruneJobFuture) {
|
||||
pruneJobFuture.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
package cn.hutool.cache.impl;
|
||||
|
||||
import cn.hutool.cache.GlobalPruneTimer;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
|
||||
/**
|
||||
* 定时缓存<br>
|
||||
* 此缓存没有容量限制,对象只有在过期后才会被移除
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
*/
|
||||
public class TimedCache<K, V> extends AbstractCache<K, V> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 正在执行的定时任务 */
|
||||
private ScheduledFuture<?> pruneJobFuture;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param timeout 超时(过期)时长,单位毫秒
|
||||
*/
|
||||
public TimedCache(long timeout) {
|
||||
this(timeout, new HashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param timeout 过期时长
|
||||
* @param map 存储缓存对象的map
|
||||
*/
|
||||
public TimedCache(long timeout, Map<K, CacheObj<K, V>> map) {
|
||||
this.capacity = 0;
|
||||
this.timeout = timeout;
|
||||
this.cacheMap = map;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- prune
|
||||
/**
|
||||
* 清理过期对象
|
||||
*
|
||||
* @return 清理数
|
||||
*/
|
||||
@Override
|
||||
protected int pruneCache() {
|
||||
int count = 0;
|
||||
Iterator<CacheObj<K, V>> values = cacheMap.values().iterator();
|
||||
CacheObj<K, V> co;
|
||||
while (values.hasNext()) {
|
||||
co = values.next();
|
||||
if (co.isExpired()) {
|
||||
values.remove();
|
||||
onRemove(co.key, co.obj);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- auto prune
|
||||
/**
|
||||
* 定时清理
|
||||
*
|
||||
* @param delay 间隔时长,单位毫秒
|
||||
*/
|
||||
public void schedulePrune(long delay) {
|
||||
this.pruneJobFuture = GlobalPruneTimer.INSTANCE.schedule(this::prune, delay);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消定时清理
|
||||
*/
|
||||
public void cancelPruneSchedule() {
|
||||
if (null != pruneJobFuture) {
|
||||
pruneJobFuture.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,24 +1,24 @@
|
||||
package cn.hutool.cache.impl;
|
||||
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
/**
|
||||
* 弱引用缓存<br>
|
||||
* 对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。<br>
|
||||
* 丢弃某个键时,其条目从映射中有效地移除。<br>
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
* @param <K> 键
|
||||
* @param <V> 值
|
||||
* @author looly
|
||||
* @since 3.0.7
|
||||
*/
|
||||
public class WeakCache<K, V> extends TimedCache<K, V>{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public WeakCache(long timeout) {
|
||||
super(timeout, new WeakHashMap<K, CacheObj<K, V>>());
|
||||
}
|
||||
|
||||
}
|
||||
package cn.hutool.cache.impl;
|
||||
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
/**
|
||||
* 弱引用缓存<br>
|
||||
* 对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。<br>
|
||||
* 丢弃某个键时,其条目从映射中有效地移除。<br>
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
* @param <K> 键
|
||||
* @param <V> 值
|
||||
* @author looly
|
||||
* @since 3.0.7
|
||||
*/
|
||||
public class WeakCache<K, V> extends TimedCache<K, V>{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public WeakCache(long timeout) {
|
||||
super(timeout, new WeakHashMap<K, CacheObj<K, V>>());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* 提供各种缓存实现
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* 提供各种缓存实现
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.cache.impl;
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* 提供简易的缓存实现,此模块参考了jodd工具中的Cache模块
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* 提供简易的缓存实现,此模块参考了jodd工具中的Cache模块
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.cache;
|
@ -1,85 +1,85 @@
|
||||
package cn.hutool.cache.test;
|
||||
|
||||
import cn.hutool.cache.Cache;
|
||||
import cn.hutool.cache.impl.FIFOCache;
|
||||
import cn.hutool.cache.impl.LRUCache;
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* 缓存单元测试
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
public class CacheConcurrentTest {
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void fifoCacheTest() {
|
||||
int threadCount = 4000;
|
||||
final Cache<String, String> cache = new FIFOCache<>(3);
|
||||
|
||||
// 由于缓存容量只有3,当加入第四个元素的时候,根据FIFO规则,最先放入的对象将被移除
|
||||
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
ThreadUtil.execute(() -> {
|
||||
cache.put("key1", "value1", System.currentTimeMillis() * 3);
|
||||
cache.put("key2", "value2", System.currentTimeMillis() * 3);
|
||||
cache.put("key3", "value3", System.currentTimeMillis() * 3);
|
||||
cache.put("key4", "value4", System.currentTimeMillis() * 3);
|
||||
ThreadUtil.sleep(1000);
|
||||
cache.put("key5", "value5", System.currentTimeMillis() * 3);
|
||||
cache.put("key6", "value6", System.currentTimeMillis() * 3);
|
||||
cache.put("key7", "value7", System.currentTimeMillis() * 3);
|
||||
cache.put("key8", "value8", System.currentTimeMillis() * 3);
|
||||
Console.log("put all");
|
||||
});
|
||||
}
|
||||
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
ThreadUtil.execute(() -> show(cache));
|
||||
}
|
||||
|
||||
System.out.println("==============================");
|
||||
ThreadUtil.sleep(10000);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void lruCacheTest() {
|
||||
int threadCount = 40000;
|
||||
final Cache<String, String> cache = new LRUCache<>(1000);
|
||||
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
final int index = i;
|
||||
ThreadUtil.execute(() -> {
|
||||
cache.put("key1"+ index, "value1");
|
||||
cache.put("key2"+ index, "value2", System.currentTimeMillis() * 3);
|
||||
|
||||
int size = cache.size();
|
||||
int capacity = cache.capacity();
|
||||
if(size > capacity) {
|
||||
Console.log("{} {}", size, capacity);
|
||||
}
|
||||
ThreadUtil.sleep(1000);
|
||||
size = cache.size();
|
||||
capacity = cache.capacity();
|
||||
if(size > capacity) {
|
||||
Console.log("## {} {}", size, capacity);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ThreadUtil.sleep(5000);
|
||||
}
|
||||
|
||||
private void show(Cache<String, String> cache) {
|
||||
|
||||
for (Object tt : cache) {
|
||||
Console.log(tt);
|
||||
}
|
||||
}
|
||||
}
|
||||
package cn.hutool.cache.test;
|
||||
|
||||
import cn.hutool.cache.Cache;
|
||||
import cn.hutool.cache.impl.FIFOCache;
|
||||
import cn.hutool.cache.impl.LRUCache;
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* 缓存单元测试
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
public class CacheConcurrentTest {
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void fifoCacheTest() {
|
||||
int threadCount = 4000;
|
||||
final Cache<String, String> cache = new FIFOCache<>(3);
|
||||
|
||||
// 由于缓存容量只有3,当加入第四个元素的时候,根据FIFO规则,最先放入的对象将被移除
|
||||
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
ThreadUtil.execute(() -> {
|
||||
cache.put("key1", "value1", System.currentTimeMillis() * 3);
|
||||
cache.put("key2", "value2", System.currentTimeMillis() * 3);
|
||||
cache.put("key3", "value3", System.currentTimeMillis() * 3);
|
||||
cache.put("key4", "value4", System.currentTimeMillis() * 3);
|
||||
ThreadUtil.sleep(1000);
|
||||
cache.put("key5", "value5", System.currentTimeMillis() * 3);
|
||||
cache.put("key6", "value6", System.currentTimeMillis() * 3);
|
||||
cache.put("key7", "value7", System.currentTimeMillis() * 3);
|
||||
cache.put("key8", "value8", System.currentTimeMillis() * 3);
|
||||
Console.log("put all");
|
||||
});
|
||||
}
|
||||
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
ThreadUtil.execute(() -> show(cache));
|
||||
}
|
||||
|
||||
System.out.println("==============================");
|
||||
ThreadUtil.sleep(10000);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void lruCacheTest() {
|
||||
int threadCount = 40000;
|
||||
final Cache<String, String> cache = new LRUCache<>(1000);
|
||||
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
final int index = i;
|
||||
ThreadUtil.execute(() -> {
|
||||
cache.put("key1"+ index, "value1");
|
||||
cache.put("key2"+ index, "value2", System.currentTimeMillis() * 3);
|
||||
|
||||
int size = cache.size();
|
||||
int capacity = cache.capacity();
|
||||
if(size > capacity) {
|
||||
Console.log("{} {}", size, capacity);
|
||||
}
|
||||
ThreadUtil.sleep(1000);
|
||||
size = cache.size();
|
||||
capacity = cache.capacity();
|
||||
if(size > capacity) {
|
||||
Console.log("## {} {}", size, capacity);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ThreadUtil.sleep(5000);
|
||||
}
|
||||
|
||||
private void show(Cache<String, String> cache) {
|
||||
|
||||
for (Object tt : cache) {
|
||||
Console.log(tt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,109 +1,109 @@
|
||||
package cn.hutool.cache.test;
|
||||
|
||||
import cn.hutool.cache.Cache;
|
||||
import cn.hutool.cache.CacheUtil;
|
||||
import cn.hutool.cache.impl.TimedCache;
|
||||
import cn.hutool.core.date.DateUnit;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* 缓存测试用例
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class CacheTest {
|
||||
|
||||
@Test
|
||||
public void fifoCacheTest(){
|
||||
Cache<String,String> fifoCache = CacheUtil.newFIFOCache(3);
|
||||
fifoCache.setListener((key, value)->{
|
||||
// 监听测试,此测试中只有key1被移除,测试是否监听成功
|
||||
Assert.assertEquals("key1", key);
|
||||
Assert.assertEquals("value1", value);
|
||||
});
|
||||
|
||||
fifoCache.put("key1", "value1", DateUnit.SECOND.getMillis() * 3);
|
||||
fifoCache.put("key2", "value2", DateUnit.SECOND.getMillis() * 3);
|
||||
fifoCache.put("key3", "value3", DateUnit.SECOND.getMillis() * 3);
|
||||
fifoCache.put("key4", "value4", DateUnit.SECOND.getMillis() * 3);
|
||||
|
||||
//由于缓存容量只有3,当加入第四个元素的时候,根据FIFO规则,最先放入的对象将被移除
|
||||
String value1 = fifoCache.get("key1");
|
||||
Assert.assertNull(value1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lfuCacheTest(){
|
||||
Cache<String, String> lfuCache = CacheUtil.newLFUCache(3);
|
||||
lfuCache.put("key1", "value1", DateUnit.SECOND.getMillis() * 3);
|
||||
//使用次数+1
|
||||
lfuCache.get("key1");
|
||||
lfuCache.put("key2", "value2", DateUnit.SECOND.getMillis() * 3);
|
||||
lfuCache.put("key3", "value3", DateUnit.SECOND.getMillis() * 3);
|
||||
lfuCache.put("key4", "value4", DateUnit.SECOND.getMillis() * 3);
|
||||
|
||||
//由于缓存容量只有3,当加入第四个元素的时候,根据LFU规则,最少使用的将被移除(2,3被移除)
|
||||
String value1 = lfuCache.get("key1");
|
||||
String value2 = lfuCache.get("key2");
|
||||
String value3 = lfuCache.get("key3");
|
||||
Assert.assertNotNull(value1);
|
||||
Assert.assertNull(value2);
|
||||
Assert.assertNull(value3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lruCacheTest(){
|
||||
Cache<String, String> lruCache = CacheUtil.newLRUCache(3);
|
||||
//通过实例化对象创建
|
||||
// LRUCache<String, String> lruCache = new LRUCache<String, String>(3);
|
||||
lruCache.put("key1", "value1", DateUnit.SECOND.getMillis() * 3);
|
||||
lruCache.put("key2", "value2", DateUnit.SECOND.getMillis() * 3);
|
||||
lruCache.put("key3", "value3", DateUnit.SECOND.getMillis() * 3);
|
||||
//使用时间推近
|
||||
lruCache.get("key1");
|
||||
lruCache.put("key4", "value4", DateUnit.SECOND.getMillis() * 3);
|
||||
|
||||
String value1 = lruCache.get("key1");
|
||||
Assert.assertNotNull(value1);
|
||||
//由于缓存容量只有3,当加入第四个元素的时候,根据LRU规则,最少使用的将被移除(2被移除)
|
||||
String value2 = lruCache.get("key2");
|
||||
Assert.assertNull(value2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void timedCacheTest(){
|
||||
TimedCache<String, String> timedCache = CacheUtil.newTimedCache(4);
|
||||
// TimedCache<String, String> timedCache = new TimedCache<String, String>(DateUnit.SECOND.getMillis() * 3);
|
||||
timedCache.put("key1", "value1", 1);//1毫秒过期
|
||||
timedCache.put("key2", "value2", DateUnit.SECOND.getMillis() * 5);//5秒过期
|
||||
timedCache.put("key3", "value3");//默认过期(4毫秒)
|
||||
timedCache.put("key4", "value4", Long.MAX_VALUE);//永不过期
|
||||
|
||||
//启动定时任务,每5毫秒秒检查一次过期
|
||||
timedCache.schedulePrune(5);
|
||||
//等待5毫秒
|
||||
ThreadUtil.sleep(5);
|
||||
|
||||
//5毫秒后由于value2设置了5毫秒过期,因此只有value2被保留下来
|
||||
String value1 = timedCache.get("key1");
|
||||
Assert.assertNull(value1);
|
||||
String value2 = timedCache.get("key2");
|
||||
Assert.assertEquals("value2", value2);
|
||||
|
||||
//5毫秒后,由于设置了默认过期,key3只被保留4毫秒,因此为null
|
||||
String value3 = timedCache.get("key3");
|
||||
Assert.assertNull(value3);
|
||||
|
||||
String value3Supplier = timedCache.get("key3", () -> "Default supplier");
|
||||
Assert.assertEquals("Default supplier", value3Supplier);
|
||||
|
||||
// 永不过期
|
||||
String value4 = timedCache.get("key4");
|
||||
Assert.assertEquals("value4", value4);
|
||||
|
||||
//取消定时清理
|
||||
timedCache.cancelPruneSchedule();
|
||||
}
|
||||
}
|
||||
package cn.hutool.cache.test;
|
||||
|
||||
import cn.hutool.cache.Cache;
|
||||
import cn.hutool.cache.CacheUtil;
|
||||
import cn.hutool.cache.impl.TimedCache;
|
||||
import cn.hutool.core.date.DateUnit;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* 缓存测试用例
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class CacheTest {
|
||||
|
||||
@Test
|
||||
public void fifoCacheTest(){
|
||||
Cache<String,String> fifoCache = CacheUtil.newFIFOCache(3);
|
||||
fifoCache.setListener((key, value)->{
|
||||
// 监听测试,此测试中只有key1被移除,测试是否监听成功
|
||||
Assert.assertEquals("key1", key);
|
||||
Assert.assertEquals("value1", value);
|
||||
});
|
||||
|
||||
fifoCache.put("key1", "value1", DateUnit.SECOND.getMillis() * 3);
|
||||
fifoCache.put("key2", "value2", DateUnit.SECOND.getMillis() * 3);
|
||||
fifoCache.put("key3", "value3", DateUnit.SECOND.getMillis() * 3);
|
||||
fifoCache.put("key4", "value4", DateUnit.SECOND.getMillis() * 3);
|
||||
|
||||
//由于缓存容量只有3,当加入第四个元素的时候,根据FIFO规则,最先放入的对象将被移除
|
||||
String value1 = fifoCache.get("key1");
|
||||
Assert.assertNull(value1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lfuCacheTest(){
|
||||
Cache<String, String> lfuCache = CacheUtil.newLFUCache(3);
|
||||
lfuCache.put("key1", "value1", DateUnit.SECOND.getMillis() * 3);
|
||||
//使用次数+1
|
||||
lfuCache.get("key1");
|
||||
lfuCache.put("key2", "value2", DateUnit.SECOND.getMillis() * 3);
|
||||
lfuCache.put("key3", "value3", DateUnit.SECOND.getMillis() * 3);
|
||||
lfuCache.put("key4", "value4", DateUnit.SECOND.getMillis() * 3);
|
||||
|
||||
//由于缓存容量只有3,当加入第四个元素的时候,根据LFU规则,最少使用的将被移除(2,3被移除)
|
||||
String value1 = lfuCache.get("key1");
|
||||
String value2 = lfuCache.get("key2");
|
||||
String value3 = lfuCache.get("key3");
|
||||
Assert.assertNotNull(value1);
|
||||
Assert.assertNull(value2);
|
||||
Assert.assertNull(value3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lruCacheTest(){
|
||||
Cache<String, String> lruCache = CacheUtil.newLRUCache(3);
|
||||
//通过实例化对象创建
|
||||
// LRUCache<String, String> lruCache = new LRUCache<String, String>(3);
|
||||
lruCache.put("key1", "value1", DateUnit.SECOND.getMillis() * 3);
|
||||
lruCache.put("key2", "value2", DateUnit.SECOND.getMillis() * 3);
|
||||
lruCache.put("key3", "value3", DateUnit.SECOND.getMillis() * 3);
|
||||
//使用时间推近
|
||||
lruCache.get("key1");
|
||||
lruCache.put("key4", "value4", DateUnit.SECOND.getMillis() * 3);
|
||||
|
||||
String value1 = lruCache.get("key1");
|
||||
Assert.assertNotNull(value1);
|
||||
//由于缓存容量只有3,当加入第四个元素的时候,根据LRU规则,最少使用的将被移除(2被移除)
|
||||
String value2 = lruCache.get("key2");
|
||||
Assert.assertNull(value2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void timedCacheTest(){
|
||||
TimedCache<String, String> timedCache = CacheUtil.newTimedCache(4);
|
||||
// TimedCache<String, String> timedCache = new TimedCache<String, String>(DateUnit.SECOND.getMillis() * 3);
|
||||
timedCache.put("key1", "value1", 1);//1毫秒过期
|
||||
timedCache.put("key2", "value2", DateUnit.SECOND.getMillis() * 5);//5秒过期
|
||||
timedCache.put("key3", "value3");//默认过期(4毫秒)
|
||||
timedCache.put("key4", "value4", Long.MAX_VALUE);//永不过期
|
||||
|
||||
//启动定时任务,每5毫秒秒检查一次过期
|
||||
timedCache.schedulePrune(5);
|
||||
//等待5毫秒
|
||||
ThreadUtil.sleep(5);
|
||||
|
||||
//5毫秒后由于value2设置了5毫秒过期,因此只有value2被保留下来
|
||||
String value1 = timedCache.get("key1");
|
||||
Assert.assertNull(value1);
|
||||
String value2 = timedCache.get("key2");
|
||||
Assert.assertEquals("value2", value2);
|
||||
|
||||
//5毫秒后,由于设置了默认过期,key3只被保留4毫秒,因此为null
|
||||
String value3 = timedCache.get("key3");
|
||||
Assert.assertNull(value3);
|
||||
|
||||
String value3Supplier = timedCache.get("key3", () -> "Default supplier");
|
||||
Assert.assertEquals("Default supplier", value3Supplier);
|
||||
|
||||
// 永不过期
|
||||
String value4 = timedCache.get("key4");
|
||||
Assert.assertEquals("value4", value4);
|
||||
|
||||
//取消定时清理
|
||||
timedCache.cancelPruneSchedule();
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,19 @@
|
||||
package cn.hutool.cache.test;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import cn.hutool.cache.file.LFUFileCache;
|
||||
|
||||
/**
|
||||
* 文件缓存单元测试
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
public class FileCacheTest {
|
||||
@Test
|
||||
public void lfuFileCacheTest() {
|
||||
LFUFileCache cache = new LFUFileCache(1000, 500, 2000);
|
||||
Assert.assertNotNull(cache);
|
||||
}
|
||||
}
|
||||
package cn.hutool.cache.test;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import cn.hutool.cache.file.LFUFileCache;
|
||||
|
||||
/**
|
||||
* 文件缓存单元测试
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
public class FileCacheTest {
|
||||
@Test
|
||||
public void lfuFileCacheTest() {
|
||||
LFUFileCache cache = new LFUFileCache(1000, 500, 2000);
|
||||
Assert.assertNotNull(cache);
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,24 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-captcha</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<description>Hutool 验证码工具</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-captcha</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<description>Hutool 验证码工具</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
@ -1,255 +1,255 @@
|
||||
package cn.hutool.captcha;
|
||||
|
||||
import cn.hutool.captcha.generator.CodeGenerator;
|
||||
import cn.hutool.captcha.generator.RandomGenerator;
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.img.ImgUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
|
||||
import java.awt.AlphaComposite;
|
||||
import java.awt.Color;
|
||||
import java.awt.Font;
|
||||
import java.awt.Image;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* 抽象验证码<br>
|
||||
* 抽象验证码实现了验证码字符串的生成、验证,验证码图片的写出<br>
|
||||
* 实现类通过实现{@link #createImage(String)} 方法生成图片对象
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
public abstract class AbstractCaptcha implements ICaptcha {
|
||||
private static final long serialVersionUID = 3180820918087507254L;
|
||||
|
||||
/**
|
||||
* 图片的宽度
|
||||
*/
|
||||
protected int width;
|
||||
/**
|
||||
* 图片的高度
|
||||
*/
|
||||
protected int height;
|
||||
/**
|
||||
* 验证码干扰元素个数
|
||||
*/
|
||||
protected int interfereCount;
|
||||
/**
|
||||
* 字体
|
||||
*/
|
||||
protected Font font;
|
||||
/**
|
||||
* 验证码
|
||||
*/
|
||||
protected String code;
|
||||
/**
|
||||
* 验证码图片
|
||||
*/
|
||||
protected byte[] imageBytes;
|
||||
/**
|
||||
* 验证码生成器
|
||||
*/
|
||||
protected CodeGenerator generator;
|
||||
/**
|
||||
* 背景色
|
||||
*/
|
||||
protected Color background;
|
||||
/**
|
||||
* 文字透明度
|
||||
*/
|
||||
protected AlphaComposite textAlpha;
|
||||
|
||||
/**
|
||||
* 构造,使用随机验证码生成器生成验证码
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @param codeCount 字符个数
|
||||
* @param interfereCount 验证码干扰元素个数
|
||||
*/
|
||||
public AbstractCaptcha(int width, int height, int codeCount, int interfereCount) {
|
||||
this(width, height, new RandomGenerator(codeCount), interfereCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @param generator 验证码生成器
|
||||
* @param interfereCount 验证码干扰元素个数
|
||||
*/
|
||||
public AbstractCaptcha(int width, int height, CodeGenerator generator, int interfereCount) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.generator = generator;
|
||||
this.interfereCount = interfereCount;
|
||||
// 字体高度设为验证码高度-2,留边距
|
||||
this.font = new Font(Font.SANS_SERIF, Font.PLAIN, (int) (this.height * 0.75));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createCode() {
|
||||
generateCode();
|
||||
|
||||
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ImgUtil.writePng(createImage(this.code), out);
|
||||
this.imageBytes = out.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成验证码字符串
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
protected void generateCode() {
|
||||
this.code = generator.generate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据生成的code创建验证码图片
|
||||
*
|
||||
* @param code 验证码
|
||||
* @return Image
|
||||
*/
|
||||
protected abstract Image createImage(String code);
|
||||
|
||||
@Override
|
||||
public String getCode() {
|
||||
if (null == this.code) {
|
||||
createCode();
|
||||
}
|
||||
return this.code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(String userInputCode) {
|
||||
return this.generator.verify(getCode(), userInputCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证码写出到文件
|
||||
*
|
||||
* @param path 文件路径
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public void write(String path) throws IORuntimeException {
|
||||
this.write(FileUtil.touch(path));
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证码写出到文件
|
||||
*
|
||||
* @param file 文件
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public void write(File file) throws IORuntimeException {
|
||||
try (OutputStream out = FileUtil.getOutputStream(file)) {
|
||||
this.write(out);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutputStream out) {
|
||||
IoUtil.write(out, false, getImageBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图形验证码图片bytes
|
||||
*
|
||||
* @return 图形验证码图片bytes
|
||||
* @since 4.5.17
|
||||
*/
|
||||
public byte[] getImageBytes() {
|
||||
if (null == this.imageBytes) {
|
||||
createCode();
|
||||
}
|
||||
return this.imageBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取验证码图
|
||||
*
|
||||
* @return 验证码图
|
||||
*/
|
||||
public BufferedImage getImage() {
|
||||
return ImgUtil.read(IoUtil.toStream(getImageBytes()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得图片的Base64形式
|
||||
*
|
||||
* @return 图片的Base64
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public String getImageBase64() {
|
||||
return Base64.encode(getImageBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图片带文件格式的 Base64
|
||||
*
|
||||
* @return 图片带文件格式的 Base64
|
||||
* @since 5.3.11
|
||||
*/
|
||||
public String getImageBase64Data(){
|
||||
return URLUtil.getDataUriBase64("image/png", getImageBase64());
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义字体
|
||||
*
|
||||
* @param font 字体
|
||||
*/
|
||||
public void setFont(Font font) {
|
||||
this.font = font;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取验证码生成器
|
||||
*
|
||||
* @return 验证码生成器
|
||||
*/
|
||||
public CodeGenerator getGenerator() {
|
||||
return generator;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置验证码生成器
|
||||
*
|
||||
* @param generator 验证码生成器
|
||||
*/
|
||||
public void setGenerator(CodeGenerator generator) {
|
||||
this.generator = generator;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置背景色
|
||||
*
|
||||
* @param background 背景色
|
||||
* @since 4.1.22
|
||||
*/
|
||||
public void setBackground(Color background) {
|
||||
this.background = background;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置文字透明度
|
||||
*
|
||||
* @param textAlpha 文字透明度,取值0~1,1表示不透明
|
||||
* @since 4.5.17
|
||||
*/
|
||||
public void setTextAlpha(float textAlpha) {
|
||||
this.textAlpha = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, textAlpha);
|
||||
}
|
||||
|
||||
}
|
||||
package cn.hutool.captcha;
|
||||
|
||||
import cn.hutool.captcha.generator.CodeGenerator;
|
||||
import cn.hutool.captcha.generator.RandomGenerator;
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.img.ImgUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
|
||||
import java.awt.AlphaComposite;
|
||||
import java.awt.Color;
|
||||
import java.awt.Font;
|
||||
import java.awt.Image;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* 抽象验证码<br>
|
||||
* 抽象验证码实现了验证码字符串的生成、验证,验证码图片的写出<br>
|
||||
* 实现类通过实现{@link #createImage(String)} 方法生成图片对象
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
public abstract class AbstractCaptcha implements ICaptcha {
|
||||
private static final long serialVersionUID = 3180820918087507254L;
|
||||
|
||||
/**
|
||||
* 图片的宽度
|
||||
*/
|
||||
protected int width;
|
||||
/**
|
||||
* 图片的高度
|
||||
*/
|
||||
protected int height;
|
||||
/**
|
||||
* 验证码干扰元素个数
|
||||
*/
|
||||
protected int interfereCount;
|
||||
/**
|
||||
* 字体
|
||||
*/
|
||||
protected Font font;
|
||||
/**
|
||||
* 验证码
|
||||
*/
|
||||
protected String code;
|
||||
/**
|
||||
* 验证码图片
|
||||
*/
|
||||
protected byte[] imageBytes;
|
||||
/**
|
||||
* 验证码生成器
|
||||
*/
|
||||
protected CodeGenerator generator;
|
||||
/**
|
||||
* 背景色
|
||||
*/
|
||||
protected Color background;
|
||||
/**
|
||||
* 文字透明度
|
||||
*/
|
||||
protected AlphaComposite textAlpha;
|
||||
|
||||
/**
|
||||
* 构造,使用随机验证码生成器生成验证码
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @param codeCount 字符个数
|
||||
* @param interfereCount 验证码干扰元素个数
|
||||
*/
|
||||
public AbstractCaptcha(int width, int height, int codeCount, int interfereCount) {
|
||||
this(width, height, new RandomGenerator(codeCount), interfereCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @param generator 验证码生成器
|
||||
* @param interfereCount 验证码干扰元素个数
|
||||
*/
|
||||
public AbstractCaptcha(int width, int height, CodeGenerator generator, int interfereCount) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.generator = generator;
|
||||
this.interfereCount = interfereCount;
|
||||
// 字体高度设为验证码高度-2,留边距
|
||||
this.font = new Font(Font.SANS_SERIF, Font.PLAIN, (int) (this.height * 0.75));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createCode() {
|
||||
generateCode();
|
||||
|
||||
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ImgUtil.writePng(createImage(this.code), out);
|
||||
this.imageBytes = out.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成验证码字符串
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
protected void generateCode() {
|
||||
this.code = generator.generate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据生成的code创建验证码图片
|
||||
*
|
||||
* @param code 验证码
|
||||
* @return Image
|
||||
*/
|
||||
protected abstract Image createImage(String code);
|
||||
|
||||
@Override
|
||||
public String getCode() {
|
||||
if (null == this.code) {
|
||||
createCode();
|
||||
}
|
||||
return this.code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(String userInputCode) {
|
||||
return this.generator.verify(getCode(), userInputCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证码写出到文件
|
||||
*
|
||||
* @param path 文件路径
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public void write(String path) throws IORuntimeException {
|
||||
this.write(FileUtil.touch(path));
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证码写出到文件
|
||||
*
|
||||
* @param file 文件
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public void write(File file) throws IORuntimeException {
|
||||
try (OutputStream out = FileUtil.getOutputStream(file)) {
|
||||
this.write(out);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutputStream out) {
|
||||
IoUtil.write(out, false, getImageBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图形验证码图片bytes
|
||||
*
|
||||
* @return 图形验证码图片bytes
|
||||
* @since 4.5.17
|
||||
*/
|
||||
public byte[] getImageBytes() {
|
||||
if (null == this.imageBytes) {
|
||||
createCode();
|
||||
}
|
||||
return this.imageBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取验证码图
|
||||
*
|
||||
* @return 验证码图
|
||||
*/
|
||||
public BufferedImage getImage() {
|
||||
return ImgUtil.read(IoUtil.toStream(getImageBytes()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得图片的Base64形式
|
||||
*
|
||||
* @return 图片的Base64
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public String getImageBase64() {
|
||||
return Base64.encode(getImageBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图片带文件格式的 Base64
|
||||
*
|
||||
* @return 图片带文件格式的 Base64
|
||||
* @since 5.3.11
|
||||
*/
|
||||
public String getImageBase64Data(){
|
||||
return URLUtil.getDataUriBase64("image/png", getImageBase64());
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义字体
|
||||
*
|
||||
* @param font 字体
|
||||
*/
|
||||
public void setFont(Font font) {
|
||||
this.font = font;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取验证码生成器
|
||||
*
|
||||
* @return 验证码生成器
|
||||
*/
|
||||
public CodeGenerator getGenerator() {
|
||||
return generator;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置验证码生成器
|
||||
*
|
||||
* @param generator 验证码生成器
|
||||
*/
|
||||
public void setGenerator(CodeGenerator generator) {
|
||||
this.generator = generator;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置背景色
|
||||
*
|
||||
* @param background 背景色
|
||||
* @since 4.1.22
|
||||
*/
|
||||
public void setBackground(Color background) {
|
||||
this.background = background;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置文字透明度
|
||||
*
|
||||
* @param textAlpha 文字透明度,取值0~1,1表示不透明
|
||||
* @since 4.5.17
|
||||
*/
|
||||
public void setTextAlpha(float textAlpha) {
|
||||
this.textAlpha = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, textAlpha);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,109 +1,109 @@
|
||||
package cn.hutool.captcha;
|
||||
|
||||
/**
|
||||
* 图形验证码工具
|
||||
*
|
||||
* @author looly
|
||||
* @since 3.1.2
|
||||
*/
|
||||
public class CaptchaUtil {
|
||||
|
||||
/**
|
||||
* 创建线干扰的验证码,默认5位验证码,150条干扰线
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @return {@link LineCaptcha}
|
||||
*/
|
||||
public static LineCaptcha createLineCaptcha(int width, int height) {
|
||||
return new LineCaptcha(width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建线干扰的验证码
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @param codeCount 字符个数
|
||||
* @param lineCount 干扰线条数
|
||||
* @return {@link LineCaptcha}
|
||||
*/
|
||||
public static LineCaptcha createLineCaptcha(int width, int height, int codeCount, int lineCount) {
|
||||
return new LineCaptcha(width, height, codeCount, lineCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建圆圈干扰的验证码,默认5位验证码,15个干扰圈
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @return {@link CircleCaptcha}
|
||||
* @since 3.2.3
|
||||
*/
|
||||
public static CircleCaptcha createCircleCaptcha(int width, int height) {
|
||||
return new CircleCaptcha(width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建圆圈干扰的验证码
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @param codeCount 字符个数
|
||||
* @param circleCount 干扰圆圈条数
|
||||
* @return {@link CircleCaptcha}
|
||||
* @since 3.2.3
|
||||
*/
|
||||
public static CircleCaptcha createCircleCaptcha(int width, int height, int codeCount, int circleCount) {
|
||||
return new CircleCaptcha(width, height, codeCount, circleCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建扭曲干扰的验证码,默认5位验证码
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @return {@link ShearCaptcha}
|
||||
* @since 3.2.3
|
||||
*/
|
||||
public static ShearCaptcha createShearCaptcha(int width, int height) {
|
||||
return new ShearCaptcha(width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建扭曲干扰的验证码,默认5位验证码
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @param codeCount 字符个数
|
||||
* @param thickness 干扰线宽度
|
||||
* @return {@link ShearCaptcha}
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public static ShearCaptcha createShearCaptcha(int width, int height, int codeCount, int thickness) {
|
||||
return new ShearCaptcha(width, height, codeCount, thickness);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建GIF验证码
|
||||
*
|
||||
* @param width 宽
|
||||
* @param height 高
|
||||
* @return {@link GifCaptcha}
|
||||
*/
|
||||
public static GifCaptcha createGifCaptcha(int width, int height) {
|
||||
return new GifCaptcha(width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建GIF验证码
|
||||
*
|
||||
* @param width 宽
|
||||
* @param height 高
|
||||
* @param codeCount 字符个数
|
||||
* @return {@link GifCaptcha}
|
||||
*/
|
||||
public static GifCaptcha createGifCaptcha(int width, int height, int codeCount) {
|
||||
return new GifCaptcha(width, height, codeCount);
|
||||
}
|
||||
}
|
||||
package cn.hutool.captcha;
|
||||
|
||||
/**
|
||||
* 图形验证码工具
|
||||
*
|
||||
* @author looly
|
||||
* @since 3.1.2
|
||||
*/
|
||||
public class CaptchaUtil {
|
||||
|
||||
/**
|
||||
* 创建线干扰的验证码,默认5位验证码,150条干扰线
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @return {@link LineCaptcha}
|
||||
*/
|
||||
public static LineCaptcha createLineCaptcha(int width, int height) {
|
||||
return new LineCaptcha(width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建线干扰的验证码
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @param codeCount 字符个数
|
||||
* @param lineCount 干扰线条数
|
||||
* @return {@link LineCaptcha}
|
||||
*/
|
||||
public static LineCaptcha createLineCaptcha(int width, int height, int codeCount, int lineCount) {
|
||||
return new LineCaptcha(width, height, codeCount, lineCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建圆圈干扰的验证码,默认5位验证码,15个干扰圈
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @return {@link CircleCaptcha}
|
||||
* @since 3.2.3
|
||||
*/
|
||||
public static CircleCaptcha createCircleCaptcha(int width, int height) {
|
||||
return new CircleCaptcha(width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建圆圈干扰的验证码
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @param codeCount 字符个数
|
||||
* @param circleCount 干扰圆圈条数
|
||||
* @return {@link CircleCaptcha}
|
||||
* @since 3.2.3
|
||||
*/
|
||||
public static CircleCaptcha createCircleCaptcha(int width, int height, int codeCount, int circleCount) {
|
||||
return new CircleCaptcha(width, height, codeCount, circleCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建扭曲干扰的验证码,默认5位验证码
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @return {@link ShearCaptcha}
|
||||
* @since 3.2.3
|
||||
*/
|
||||
public static ShearCaptcha createShearCaptcha(int width, int height) {
|
||||
return new ShearCaptcha(width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建扭曲干扰的验证码,默认5位验证码
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @param codeCount 字符个数
|
||||
* @param thickness 干扰线宽度
|
||||
* @return {@link ShearCaptcha}
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public static ShearCaptcha createShearCaptcha(int width, int height, int codeCount, int thickness) {
|
||||
return new ShearCaptcha(width, height, codeCount, thickness);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建GIF验证码
|
||||
*
|
||||
* @param width 宽
|
||||
* @param height 高
|
||||
* @return {@link GifCaptcha}
|
||||
*/
|
||||
public static GifCaptcha createGifCaptcha(int width, int height) {
|
||||
return new GifCaptcha(width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建GIF验证码
|
||||
*
|
||||
* @param width 宽
|
||||
* @param height 高
|
||||
* @param codeCount 字符个数
|
||||
* @return {@link GifCaptcha}
|
||||
*/
|
||||
public static GifCaptcha createGifCaptcha(int width, int height, int codeCount) {
|
||||
return new GifCaptcha(width, height, codeCount);
|
||||
}
|
||||
}
|
||||
|
@ -1,100 +1,100 @@
|
||||
package cn.hutool.captcha;
|
||||
|
||||
import cn.hutool.core.img.GraphicsUtil;
|
||||
import cn.hutool.core.img.ImgUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
/**
|
||||
* 圆圈干扰验证码
|
||||
*
|
||||
* @author looly
|
||||
* @since 3.2.3
|
||||
*
|
||||
*/
|
||||
public class CircleCaptcha extends AbstractCaptcha {
|
||||
private static final long serialVersionUID = -7096627300356535494L;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
*/
|
||||
public CircleCaptcha(int width, int height) {
|
||||
this(width, height, 5);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @param codeCount 字符个数
|
||||
*/
|
||||
public CircleCaptcha(int width, int height, int codeCount) {
|
||||
this(width, height, codeCount, 15);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @param codeCount 字符个数
|
||||
* @param interfereCount 验证码干扰元素个数
|
||||
*/
|
||||
public CircleCaptcha(int width, int height, int codeCount, int interfereCount) {
|
||||
super(width, height, codeCount, interfereCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Image createImage(String code) {
|
||||
final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
final Graphics2D g = ImgUtil.createGraphics(image, ObjectUtil.defaultIfNull(this.background, Color.WHITE));
|
||||
|
||||
// 随机画干扰圈圈
|
||||
drawInterfere(g);
|
||||
|
||||
// 画字符串
|
||||
drawString(g, code);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------- Private method start
|
||||
/**
|
||||
* 绘制字符串
|
||||
*
|
||||
* @param g {@link Graphics2D}画笔
|
||||
* @param code 验证码
|
||||
*/
|
||||
private void drawString(Graphics2D g, String code) {
|
||||
// 指定透明度
|
||||
if (null != this.textAlpha) {
|
||||
g.setComposite(this.textAlpha);
|
||||
}
|
||||
GraphicsUtil.drawStringColourful(g, code, this.font, this.width, this.height);
|
||||
}
|
||||
|
||||
/**
|
||||
* 画随机干扰
|
||||
*
|
||||
* @param g {@link Graphics2D}
|
||||
*/
|
||||
private void drawInterfere(Graphics2D g) {
|
||||
final ThreadLocalRandom random = RandomUtil.getRandom();
|
||||
|
||||
for (int i = 0; i < this.interfereCount; i++) {
|
||||
g.setColor(ImgUtil.randomColor(random));
|
||||
g.drawOval(random.nextInt(width), random.nextInt(height), random.nextInt(height >> 1), random.nextInt(height >> 1));
|
||||
}
|
||||
}
|
||||
// ----------------------------------------------------------------------------------------------------- Private method end
|
||||
}
|
||||
package cn.hutool.captcha;
|
||||
|
||||
import cn.hutool.core.img.GraphicsUtil;
|
||||
import cn.hutool.core.img.ImgUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
/**
|
||||
* 圆圈干扰验证码
|
||||
*
|
||||
* @author looly
|
||||
* @since 3.2.3
|
||||
*
|
||||
*/
|
||||
public class CircleCaptcha extends AbstractCaptcha {
|
||||
private static final long serialVersionUID = -7096627300356535494L;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
*/
|
||||
public CircleCaptcha(int width, int height) {
|
||||
this(width, height, 5);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @param codeCount 字符个数
|
||||
*/
|
||||
public CircleCaptcha(int width, int height, int codeCount) {
|
||||
this(width, height, codeCount, 15);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @param codeCount 字符个数
|
||||
* @param interfereCount 验证码干扰元素个数
|
||||
*/
|
||||
public CircleCaptcha(int width, int height, int codeCount, int interfereCount) {
|
||||
super(width, height, codeCount, interfereCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Image createImage(String code) {
|
||||
final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
final Graphics2D g = ImgUtil.createGraphics(image, ObjectUtil.defaultIfNull(this.background, Color.WHITE));
|
||||
|
||||
// 随机画干扰圈圈
|
||||
drawInterfere(g);
|
||||
|
||||
// 画字符串
|
||||
drawString(g, code);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------- Private method start
|
||||
/**
|
||||
* 绘制字符串
|
||||
*
|
||||
* @param g {@link Graphics2D}画笔
|
||||
* @param code 验证码
|
||||
*/
|
||||
private void drawString(Graphics2D g, String code) {
|
||||
// 指定透明度
|
||||
if (null != this.textAlpha) {
|
||||
g.setComposite(this.textAlpha);
|
||||
}
|
||||
GraphicsUtil.drawStringColourful(g, code, this.font, this.width, this.height);
|
||||
}
|
||||
|
||||
/**
|
||||
* 画随机干扰
|
||||
*
|
||||
* @param g {@link Graphics2D}
|
||||
*/
|
||||
private void drawInterfere(Graphics2D g) {
|
||||
final ThreadLocalRandom random = RandomUtil.getRandom();
|
||||
|
||||
for (int i = 0; i < this.interfereCount; i++) {
|
||||
g.setColor(ImgUtil.randomColor(random));
|
||||
g.drawOval(random.nextInt(width), random.nextInt(height), random.nextInt(height >> 1), random.nextInt(height >> 1));
|
||||
}
|
||||
}
|
||||
// ----------------------------------------------------------------------------------------------------- Private method end
|
||||
}
|
||||
|
@ -1,40 +1,40 @@
|
||||
package cn.hutool.captcha;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 验证码接口,提供验证码对象接口定义
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
public interface ICaptcha extends Serializable{
|
||||
|
||||
/**
|
||||
* 创建验证码,实现类需同时生成随机验证码字符串和验证码图片
|
||||
*/
|
||||
void createCode();
|
||||
|
||||
/**
|
||||
* 获取验证码的文字内容
|
||||
*
|
||||
* @return 验证码文字内容
|
||||
*/
|
||||
String getCode();
|
||||
|
||||
/**
|
||||
* 验证验证码是否正确,建议忽略大小写
|
||||
*
|
||||
* @param userInputCode 用户输入的验证码
|
||||
* @return 是否与生成的一直
|
||||
*/
|
||||
boolean verify(String userInputCode);
|
||||
|
||||
/**
|
||||
* 将验证码写出到目标流中
|
||||
*
|
||||
* @param out 目标流
|
||||
*/
|
||||
void write(OutputStream out);
|
||||
}
|
||||
package cn.hutool.captcha;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 验证码接口,提供验证码对象接口定义
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
public interface ICaptcha extends Serializable{
|
||||
|
||||
/**
|
||||
* 创建验证码,实现类需同时生成随机验证码字符串和验证码图片
|
||||
*/
|
||||
void createCode();
|
||||
|
||||
/**
|
||||
* 获取验证码的文字内容
|
||||
*
|
||||
* @return 验证码文字内容
|
||||
*/
|
||||
String getCode();
|
||||
|
||||
/**
|
||||
* 验证验证码是否正确,建议忽略大小写
|
||||
*
|
||||
* @param userInputCode 用户输入的验证码
|
||||
* @return 是否与生成的一直
|
||||
*/
|
||||
boolean verify(String userInputCode);
|
||||
|
||||
/**
|
||||
* 将验证码写出到目标流中
|
||||
*
|
||||
* @param out 目标流
|
||||
*/
|
||||
void write(OutputStream out);
|
||||
}
|
||||
|
@ -1,96 +1,96 @@
|
||||
package cn.hutool.captcha;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import cn.hutool.core.img.GraphicsUtil;
|
||||
import cn.hutool.core.img.ImgUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
|
||||
/**
|
||||
* 使用干扰线方式生成的图形验证码
|
||||
*
|
||||
* @author looly
|
||||
* @since 3.1.2
|
||||
*/
|
||||
public class LineCaptcha extends AbstractCaptcha {
|
||||
private static final long serialVersionUID = 8691294460763091089L;
|
||||
|
||||
// -------------------------------------------------------------------- Constructor start
|
||||
/**
|
||||
* 构造,默认5位验证码,150条干扰线
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
*/
|
||||
public LineCaptcha(int width, int height) {
|
||||
this(width, height, 5, 150);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @param codeCount 字符个数
|
||||
* @param lineCount 干扰线条数
|
||||
*/
|
||||
public LineCaptcha(int width, int height, int codeCount, int lineCount) {
|
||||
super(width, height, codeCount, lineCount);
|
||||
}
|
||||
// -------------------------------------------------------------------- Constructor end
|
||||
|
||||
@Override
|
||||
public Image createImage(String code) {
|
||||
// 图像buffer
|
||||
final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
final Graphics2D g = GraphicsUtil.createGraphics(image, ObjectUtil.defaultIfNull(this.background, Color.WHITE));
|
||||
|
||||
// 干扰线
|
||||
drawInterfere(g);
|
||||
|
||||
// 字符串
|
||||
drawString(g, code);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------- Private method start
|
||||
/**
|
||||
* 绘制字符串
|
||||
*
|
||||
* @param g {@link Graphics}画笔
|
||||
* @param code 验证码
|
||||
*/
|
||||
private void drawString(Graphics2D g, String code) {
|
||||
// 指定透明度
|
||||
if (null != this.textAlpha) {
|
||||
g.setComposite(this.textAlpha);
|
||||
}
|
||||
GraphicsUtil.drawStringColourful(g, code, this.font, this.width, this.height);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制干扰线
|
||||
*
|
||||
* @param g {@link Graphics2D}画笔
|
||||
*/
|
||||
private void drawInterfere(Graphics2D g) {
|
||||
final ThreadLocalRandom random = RandomUtil.getRandom();
|
||||
// 干扰线
|
||||
for (int i = 0; i < this.interfereCount; i++) {
|
||||
int xs = random.nextInt(width);
|
||||
int ys = random.nextInt(height);
|
||||
int xe = xs + random.nextInt(width / 8);
|
||||
int ye = ys + random.nextInt(height / 8);
|
||||
g.setColor(ImgUtil.randomColor(random));
|
||||
g.drawLine(xs, ys, xe, ye);
|
||||
}
|
||||
}
|
||||
// ----------------------------------------------------------------------------------------------------- Private method start
|
||||
package cn.hutool.captcha;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import cn.hutool.core.img.GraphicsUtil;
|
||||
import cn.hutool.core.img.ImgUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
|
||||
/**
|
||||
* 使用干扰线方式生成的图形验证码
|
||||
*
|
||||
* @author looly
|
||||
* @since 3.1.2
|
||||
*/
|
||||
public class LineCaptcha extends AbstractCaptcha {
|
||||
private static final long serialVersionUID = 8691294460763091089L;
|
||||
|
||||
// -------------------------------------------------------------------- Constructor start
|
||||
/**
|
||||
* 构造,默认5位验证码,150条干扰线
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
*/
|
||||
public LineCaptcha(int width, int height) {
|
||||
this(width, height, 5, 150);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @param codeCount 字符个数
|
||||
* @param lineCount 干扰线条数
|
||||
*/
|
||||
public LineCaptcha(int width, int height, int codeCount, int lineCount) {
|
||||
super(width, height, codeCount, lineCount);
|
||||
}
|
||||
// -------------------------------------------------------------------- Constructor end
|
||||
|
||||
@Override
|
||||
public Image createImage(String code) {
|
||||
// 图像buffer
|
||||
final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
final Graphics2D g = GraphicsUtil.createGraphics(image, ObjectUtil.defaultIfNull(this.background, Color.WHITE));
|
||||
|
||||
// 干扰线
|
||||
drawInterfere(g);
|
||||
|
||||
// 字符串
|
||||
drawString(g, code);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------- Private method start
|
||||
/**
|
||||
* 绘制字符串
|
||||
*
|
||||
* @param g {@link Graphics}画笔
|
||||
* @param code 验证码
|
||||
*/
|
||||
private void drawString(Graphics2D g, String code) {
|
||||
// 指定透明度
|
||||
if (null != this.textAlpha) {
|
||||
g.setComposite(this.textAlpha);
|
||||
}
|
||||
GraphicsUtil.drawStringColourful(g, code, this.font, this.width, this.height);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制干扰线
|
||||
*
|
||||
* @param g {@link Graphics2D}画笔
|
||||
*/
|
||||
private void drawInterfere(Graphics2D g) {
|
||||
final ThreadLocalRandom random = RandomUtil.getRandom();
|
||||
// 干扰线
|
||||
for (int i = 0; i < this.interfereCount; i++) {
|
||||
int xs = random.nextInt(width);
|
||||
int ys = random.nextInt(height);
|
||||
int xe = xs + random.nextInt(width / 8);
|
||||
int ye = ys + random.nextInt(height / 8);
|
||||
g.setColor(ImgUtil.randomColor(random));
|
||||
g.drawLine(xs, ys, xe, ye);
|
||||
}
|
||||
}
|
||||
// ----------------------------------------------------------------------------------------------------- Private method start
|
||||
}
|
@ -1,199 +1,199 @@
|
||||
package cn.hutool.captcha;
|
||||
|
||||
import cn.hutool.core.img.GraphicsUtil;
|
||||
import cn.hutool.core.img.ImgUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
/**
|
||||
* 扭曲干扰验证码
|
||||
*
|
||||
* @author looly
|
||||
* @since 3.2.3
|
||||
*
|
||||
*/
|
||||
public class ShearCaptcha extends AbstractCaptcha {
|
||||
private static final long serialVersionUID = -7096627300356535494L;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
*/
|
||||
public ShearCaptcha(int width, int height) {
|
||||
this(width, height, 5);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @param codeCount 字符个数
|
||||
*/
|
||||
public ShearCaptcha(int width, int height, int codeCount) {
|
||||
this(width, height, codeCount, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @param codeCount 字符个数
|
||||
* @param thickness 干扰线宽度
|
||||
*/
|
||||
public ShearCaptcha(int width, int height, int codeCount, int thickness) {
|
||||
super(width, height, codeCount, thickness);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Image createImage(String code) {
|
||||
final BufferedImage image = new BufferedImage(this.width, this.height, BufferedImage.TYPE_INT_RGB);
|
||||
final Graphics2D g = GraphicsUtil.createGraphics(image, ObjectUtil.defaultIfNull(this.background, Color.WHITE));
|
||||
|
||||
// 画字符串
|
||||
drawString(g, code);
|
||||
|
||||
// 扭曲
|
||||
shear(g, this.width, this.height, ObjectUtil.defaultIfNull(this.background, Color.WHITE));
|
||||
// 画干扰线
|
||||
drawInterfere(g, 0, RandomUtil.randomInt(this.height) + 1, this.width, RandomUtil.randomInt(this.height) + 1, this.interfereCount, ImgUtil.randomColor());
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------- Private method start
|
||||
/**
|
||||
* 绘制字符串
|
||||
*
|
||||
* @param g {@link Graphics}画笔
|
||||
* @param code 验证码
|
||||
*/
|
||||
private void drawString(Graphics2D g, String code) {
|
||||
// 指定透明度
|
||||
if (null != this.textAlpha) {
|
||||
g.setComposite(this.textAlpha);
|
||||
}
|
||||
GraphicsUtil.drawStringColourful(g, code, this.font, this.width, this.height);
|
||||
}
|
||||
|
||||
/**
|
||||
* 扭曲
|
||||
*
|
||||
* @param g {@link Graphics}
|
||||
* @param w1 w1
|
||||
* @param h1 h1
|
||||
* @param color 颜色
|
||||
*/
|
||||
private void shear(Graphics g, int w1, int h1, Color color) {
|
||||
shearX(g, w1, h1, color);
|
||||
shearY(g, w1, h1, color);
|
||||
}
|
||||
|
||||
/**
|
||||
* X坐标扭曲
|
||||
*
|
||||
* @param g {@link Graphics}
|
||||
* @param w1 宽
|
||||
* @param h1 高
|
||||
* @param color 颜色
|
||||
*/
|
||||
private void shearX(Graphics g, int w1, int h1, Color color) {
|
||||
|
||||
int period = RandomUtil.randomInt(this.width);
|
||||
|
||||
int frames = 1;
|
||||
int phase = RandomUtil.randomInt(2);
|
||||
|
||||
for (int i = 0; i < h1; i++) {
|
||||
double d = (double) (period >> 1) * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames);
|
||||
g.copyArea(0, i, w1, 1, (int) d, 0);
|
||||
g.setColor(color);
|
||||
g.drawLine((int) d, i, 0, i);
|
||||
g.drawLine((int) d + w1, i, w1, i);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Y坐标扭曲
|
||||
*
|
||||
* @param g {@link Graphics}
|
||||
* @param w1 宽
|
||||
* @param h1 高
|
||||
* @param color 颜色
|
||||
*/
|
||||
private void shearY(Graphics g, int w1, int h1, Color color) {
|
||||
|
||||
int period = RandomUtil.randomInt(this.height >> 1);
|
||||
|
||||
int frames = 20;
|
||||
int phase = 7;
|
||||
for (int i = 0; i < w1; i++) {
|
||||
double d = (double) (period >> 1) * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames);
|
||||
g.copyArea(i, 0, 1, h1, 0, (int) d);
|
||||
g.setColor(color);
|
||||
// 擦除原位置的痕迹
|
||||
g.drawLine(i, (int) d, i, 0);
|
||||
g.drawLine(i, (int) d + h1, i, h1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 干扰线
|
||||
*
|
||||
* @param g {@link Graphics}
|
||||
* @param x1 x1
|
||||
* @param y1 y1
|
||||
* @param x2 x2
|
||||
* @param y2 y2
|
||||
* @param thickness 粗细
|
||||
* @param c 颜色
|
||||
*/
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private void drawInterfere(Graphics g, int x1, int y1, int x2, int y2, int thickness, Color c) {
|
||||
|
||||
// The thick line is in fact a filled polygon
|
||||
g.setColor(c);
|
||||
int dX = x2 - x1;
|
||||
int dY = y2 - y1;
|
||||
// line length
|
||||
double lineLength = Math.sqrt(dX * dX + dY * dY);
|
||||
|
||||
double scale = (double) (thickness) / (2 * lineLength);
|
||||
|
||||
// The x and y increments from an endpoint needed to create a
|
||||
// rectangle...
|
||||
double ddx = -scale * (double) dY;
|
||||
double ddy = scale * (double) dX;
|
||||
ddx += (ddx > 0) ? 0.5 : -0.5;
|
||||
ddy += (ddy > 0) ? 0.5 : -0.5;
|
||||
int dx = (int) ddx;
|
||||
int dy = (int) ddy;
|
||||
|
||||
// Now we can compute the corner points...
|
||||
int[] xPoints = new int[4];
|
||||
int[] yPoints = new int[4];
|
||||
|
||||
xPoints[0] = x1 + dx;
|
||||
yPoints[0] = y1 + dy;
|
||||
xPoints[1] = x1 - dx;
|
||||
yPoints[1] = y1 - dy;
|
||||
xPoints[2] = x2 - dx;
|
||||
yPoints[2] = y2 - dy;
|
||||
xPoints[3] = x2 + dx;
|
||||
yPoints[3] = y2 + dy;
|
||||
|
||||
g.fillPolygon(xPoints, yPoints, 4);
|
||||
}
|
||||
// ----------------------------------------------------------------------------------------------------- Private method end
|
||||
}
|
||||
package cn.hutool.captcha;
|
||||
|
||||
import cn.hutool.core.img.GraphicsUtil;
|
||||
import cn.hutool.core.img.ImgUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
/**
|
||||
* 扭曲干扰验证码
|
||||
*
|
||||
* @author looly
|
||||
* @since 3.2.3
|
||||
*
|
||||
*/
|
||||
public class ShearCaptcha extends AbstractCaptcha {
|
||||
private static final long serialVersionUID = -7096627300356535494L;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
*/
|
||||
public ShearCaptcha(int width, int height) {
|
||||
this(width, height, 5);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @param codeCount 字符个数
|
||||
*/
|
||||
public ShearCaptcha(int width, int height, int codeCount) {
|
||||
this(width, height, codeCount, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param width 图片宽
|
||||
* @param height 图片高
|
||||
* @param codeCount 字符个数
|
||||
* @param thickness 干扰线宽度
|
||||
*/
|
||||
public ShearCaptcha(int width, int height, int codeCount, int thickness) {
|
||||
super(width, height, codeCount, thickness);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Image createImage(String code) {
|
||||
final BufferedImage image = new BufferedImage(this.width, this.height, BufferedImage.TYPE_INT_RGB);
|
||||
final Graphics2D g = GraphicsUtil.createGraphics(image, ObjectUtil.defaultIfNull(this.background, Color.WHITE));
|
||||
|
||||
// 画字符串
|
||||
drawString(g, code);
|
||||
|
||||
// 扭曲
|
||||
shear(g, this.width, this.height, ObjectUtil.defaultIfNull(this.background, Color.WHITE));
|
||||
// 画干扰线
|
||||
drawInterfere(g, 0, RandomUtil.randomInt(this.height) + 1, this.width, RandomUtil.randomInt(this.height) + 1, this.interfereCount, ImgUtil.randomColor());
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------- Private method start
|
||||
/**
|
||||
* 绘制字符串
|
||||
*
|
||||
* @param g {@link Graphics}画笔
|
||||
* @param code 验证码
|
||||
*/
|
||||
private void drawString(Graphics2D g, String code) {
|
||||
// 指定透明度
|
||||
if (null != this.textAlpha) {
|
||||
g.setComposite(this.textAlpha);
|
||||
}
|
||||
GraphicsUtil.drawStringColourful(g, code, this.font, this.width, this.height);
|
||||
}
|
||||
|
||||
/**
|
||||
* 扭曲
|
||||
*
|
||||
* @param g {@link Graphics}
|
||||
* @param w1 w1
|
||||
* @param h1 h1
|
||||
* @param color 颜色
|
||||
*/
|
||||
private void shear(Graphics g, int w1, int h1, Color color) {
|
||||
shearX(g, w1, h1, color);
|
||||
shearY(g, w1, h1, color);
|
||||
}
|
||||
|
||||
/**
|
||||
* X坐标扭曲
|
||||
*
|
||||
* @param g {@link Graphics}
|
||||
* @param w1 宽
|
||||
* @param h1 高
|
||||
* @param color 颜色
|
||||
*/
|
||||
private void shearX(Graphics g, int w1, int h1, Color color) {
|
||||
|
||||
int period = RandomUtil.randomInt(this.width);
|
||||
|
||||
int frames = 1;
|
||||
int phase = RandomUtil.randomInt(2);
|
||||
|
||||
for (int i = 0; i < h1; i++) {
|
||||
double d = (double) (period >> 1) * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames);
|
||||
g.copyArea(0, i, w1, 1, (int) d, 0);
|
||||
g.setColor(color);
|
||||
g.drawLine((int) d, i, 0, i);
|
||||
g.drawLine((int) d + w1, i, w1, i);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Y坐标扭曲
|
||||
*
|
||||
* @param g {@link Graphics}
|
||||
* @param w1 宽
|
||||
* @param h1 高
|
||||
* @param color 颜色
|
||||
*/
|
||||
private void shearY(Graphics g, int w1, int h1, Color color) {
|
||||
|
||||
int period = RandomUtil.randomInt(this.height >> 1);
|
||||
|
||||
int frames = 20;
|
||||
int phase = 7;
|
||||
for (int i = 0; i < w1; i++) {
|
||||
double d = (double) (period >> 1) * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames);
|
||||
g.copyArea(i, 0, 1, h1, 0, (int) d);
|
||||
g.setColor(color);
|
||||
// 擦除原位置的痕迹
|
||||
g.drawLine(i, (int) d, i, 0);
|
||||
g.drawLine(i, (int) d + h1, i, h1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 干扰线
|
||||
*
|
||||
* @param g {@link Graphics}
|
||||
* @param x1 x1
|
||||
* @param y1 y1
|
||||
* @param x2 x2
|
||||
* @param y2 y2
|
||||
* @param thickness 粗细
|
||||
* @param c 颜色
|
||||
*/
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private void drawInterfere(Graphics g, int x1, int y1, int x2, int y2, int thickness, Color c) {
|
||||
|
||||
// The thick line is in fact a filled polygon
|
||||
g.setColor(c);
|
||||
int dX = x2 - x1;
|
||||
int dY = y2 - y1;
|
||||
// line length
|
||||
double lineLength = Math.sqrt(dX * dX + dY * dY);
|
||||
|
||||
double scale = (double) (thickness) / (2 * lineLength);
|
||||
|
||||
// The x and y increments from an endpoint needed to create a
|
||||
// rectangle...
|
||||
double ddx = -scale * (double) dY;
|
||||
double ddy = scale * (double) dX;
|
||||
ddx += (ddx > 0) ? 0.5 : -0.5;
|
||||
ddy += (ddy > 0) ? 0.5 : -0.5;
|
||||
int dx = (int) ddx;
|
||||
int dy = (int) ddy;
|
||||
|
||||
// Now we can compute the corner points...
|
||||
int[] xPoints = new int[4];
|
||||
int[] yPoints = new int[4];
|
||||
|
||||
xPoints[0] = x1 + dx;
|
||||
yPoints[0] = y1 + dy;
|
||||
xPoints[1] = x1 - dx;
|
||||
yPoints[1] = y1 - dy;
|
||||
xPoints[2] = x2 - dx;
|
||||
yPoints[2] = y2 - dy;
|
||||
xPoints[3] = x2 + dx;
|
||||
yPoints[3] = y2 + dy;
|
||||
|
||||
g.fillPolygon(xPoints, yPoints, 4);
|
||||
}
|
||||
// ----------------------------------------------------------------------------------------------------- Private method end
|
||||
}
|
||||
|
@ -1,48 +1,48 @@
|
||||
package cn.hutool.captcha.generator;
|
||||
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
|
||||
/**
|
||||
* 随机字符验证码生成器<br>
|
||||
* 可以通过传入的基础集合和长度随机生成验证码字符
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.1.2
|
||||
*/
|
||||
public abstract class AbstractGenerator implements CodeGenerator {
|
||||
private static final long serialVersionUID = 8685744597154953479L;
|
||||
|
||||
/** 基础字符集合,用于随机获取字符串的字符集合 */
|
||||
protected final String baseStr;
|
||||
/** 验证码长度 */
|
||||
protected final int length;
|
||||
|
||||
/**
|
||||
* 构造,使用字母+数字做为基础
|
||||
*
|
||||
* @param count 生成验证码长度
|
||||
*/
|
||||
public AbstractGenerator(int count) {
|
||||
this(RandomUtil.BASE_CHAR_NUMBER, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param baseStr 基础字符集合,用于随机获取字符串的字符集合
|
||||
* @param length 生成验证码长度
|
||||
*/
|
||||
public AbstractGenerator(String baseStr, int length) {
|
||||
this.baseStr = baseStr;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取长度验证码
|
||||
*
|
||||
* @return 验证码长度
|
||||
*/
|
||||
public int getLength() {
|
||||
return this.length;
|
||||
}
|
||||
}
|
||||
package cn.hutool.captcha.generator;
|
||||
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
|
||||
/**
|
||||
* 随机字符验证码生成器<br>
|
||||
* 可以通过传入的基础集合和长度随机生成验证码字符
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.1.2
|
||||
*/
|
||||
public abstract class AbstractGenerator implements CodeGenerator {
|
||||
private static final long serialVersionUID = 8685744597154953479L;
|
||||
|
||||
/** 基础字符集合,用于随机获取字符串的字符集合 */
|
||||
protected final String baseStr;
|
||||
/** 验证码长度 */
|
||||
protected final int length;
|
||||
|
||||
/**
|
||||
* 构造,使用字母+数字做为基础
|
||||
*
|
||||
* @param count 生成验证码长度
|
||||
*/
|
||||
public AbstractGenerator(int count) {
|
||||
this(RandomUtil.BASE_CHAR_NUMBER, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param baseStr 基础字符集合,用于随机获取字符串的字符集合
|
||||
* @param length 生成验证码长度
|
||||
*/
|
||||
public AbstractGenerator(String baseStr, int length) {
|
||||
this.baseStr = baseStr;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取长度验证码
|
||||
*
|
||||
* @return 验证码长度
|
||||
*/
|
||||
public int getLength() {
|
||||
return this.length;
|
||||
}
|
||||
}
|
||||
|
@ -1,29 +1,29 @@
|
||||
package cn.hutool.captcha.generator;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 验证码文字生成器
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.1.2
|
||||
*/
|
||||
public interface CodeGenerator extends Serializable{
|
||||
|
||||
/**
|
||||
* 生成验证码
|
||||
*
|
||||
* @return 验证码
|
||||
*/
|
||||
String generate();
|
||||
|
||||
/**
|
||||
* 验证用户输入的字符串是否与生成的验证码匹配<br>
|
||||
* 用户通过实现此方法定义验证码匹配方式
|
||||
*
|
||||
* @param code 生成的随机验证码
|
||||
* @param userInputCode 用户输入的验证码
|
||||
* @return 是否验证通过
|
||||
*/
|
||||
boolean verify(String code, String userInputCode);
|
||||
}
|
||||
package cn.hutool.captcha.generator;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 验证码文字生成器
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.1.2
|
||||
*/
|
||||
public interface CodeGenerator extends Serializable{
|
||||
|
||||
/**
|
||||
* 生成验证码
|
||||
*
|
||||
* @return 验证码
|
||||
*/
|
||||
String generate();
|
||||
|
||||
/**
|
||||
* 验证用户输入的字符串是否与生成的验证码匹配<br>
|
||||
* 用户通过实现此方法定义验证码匹配方式
|
||||
*
|
||||
* @param code 生成的随机验证码
|
||||
* @param userInputCode 用户输入的验证码
|
||||
* @return 是否验证通过
|
||||
*/
|
||||
boolean verify(String code, String userInputCode);
|
||||
}
|
||||
|
@ -1,84 +1,84 @@
|
||||
package cn.hutool.captcha.generator;
|
||||
|
||||
import cn.hutool.core.math.Calculator;
|
||||
import cn.hutool.core.util.CharUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
* 数字计算验证码生成器
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.1.2
|
||||
*/
|
||||
public class MathGenerator implements CodeGenerator {
|
||||
private static final long serialVersionUID = -5514819971774091076L;
|
||||
|
||||
private static final String operators = "+-*";
|
||||
|
||||
/** 参与计算数字最大长度 */
|
||||
private final int numberLength;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*/
|
||||
public MathGenerator() {
|
||||
this(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param numberLength 参与计算最大数字位数
|
||||
*/
|
||||
public MathGenerator(int numberLength) {
|
||||
this.numberLength = numberLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generate() {
|
||||
final int limit = getLimit();
|
||||
String number1 = Integer.toString(RandomUtil.randomInt(limit));
|
||||
String number2 = Integer.toString(RandomUtil.randomInt(limit));
|
||||
number1 = StrUtil.padAfter(number1, this.numberLength, CharUtil.SPACE);
|
||||
number2 = StrUtil.padAfter(number2, this.numberLength, CharUtil.SPACE);
|
||||
|
||||
return StrUtil.builder()//
|
||||
.append(number1)//
|
||||
.append(RandomUtil.randomChar(operators))//
|
||||
.append(number2)//
|
||||
.append('=').toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(String code, String userInputCode) {
|
||||
int result;
|
||||
try {
|
||||
result = Integer.parseInt(userInputCode);
|
||||
} catch (NumberFormatException e) {
|
||||
// 用户输入非数字
|
||||
return false;
|
||||
}
|
||||
|
||||
final int calculateResult = (int) Calculator.conversion(code);
|
||||
return result == calculateResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取验证码长度
|
||||
*
|
||||
* @return 验证码长度
|
||||
*/
|
||||
public int getLength() {
|
||||
return this.numberLength * 2 + 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据长度获取参与计算数字最大值
|
||||
*
|
||||
* @return 最大值
|
||||
*/
|
||||
private int getLimit() {
|
||||
return Integer.parseInt("1" + StrUtil.repeat('0', this.numberLength));
|
||||
}
|
||||
}
|
||||
package cn.hutool.captcha.generator;
|
||||
|
||||
import cn.hutool.core.math.Calculator;
|
||||
import cn.hutool.core.util.CharUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
* 数字计算验证码生成器
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.1.2
|
||||
*/
|
||||
public class MathGenerator implements CodeGenerator {
|
||||
private static final long serialVersionUID = -5514819971774091076L;
|
||||
|
||||
private static final String operators = "+-*";
|
||||
|
||||
/** 参与计算数字最大长度 */
|
||||
private final int numberLength;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*/
|
||||
public MathGenerator() {
|
||||
this(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param numberLength 参与计算最大数字位数
|
||||
*/
|
||||
public MathGenerator(int numberLength) {
|
||||
this.numberLength = numberLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generate() {
|
||||
final int limit = getLimit();
|
||||
String number1 = Integer.toString(RandomUtil.randomInt(limit));
|
||||
String number2 = Integer.toString(RandomUtil.randomInt(limit));
|
||||
number1 = StrUtil.padAfter(number1, this.numberLength, CharUtil.SPACE);
|
||||
number2 = StrUtil.padAfter(number2, this.numberLength, CharUtil.SPACE);
|
||||
|
||||
return StrUtil.builder()//
|
||||
.append(number1)//
|
||||
.append(RandomUtil.randomChar(operators))//
|
||||
.append(number2)//
|
||||
.append('=').toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(String code, String userInputCode) {
|
||||
int result;
|
||||
try {
|
||||
result = Integer.parseInt(userInputCode);
|
||||
} catch (NumberFormatException e) {
|
||||
// 用户输入非数字
|
||||
return false;
|
||||
}
|
||||
|
||||
final int calculateResult = (int) Calculator.conversion(code);
|
||||
return result == calculateResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取验证码长度
|
||||
*
|
||||
* @return 验证码长度
|
||||
*/
|
||||
public int getLength() {
|
||||
return this.numberLength * 2 + 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据长度获取参与计算数字最大值
|
||||
*
|
||||
* @return 最大值
|
||||
*/
|
||||
private int getLimit() {
|
||||
return Integer.parseInt("1" + StrUtil.repeat('0', this.numberLength));
|
||||
}
|
||||
}
|
||||
|
@ -1,47 +1,47 @@
|
||||
package cn.hutool.captcha.generator;
|
||||
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
* 随机字符验证码生成器<br>
|
||||
* 可以通过传入的基础集合和长度随机生成验证码字符
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.1.2
|
||||
*/
|
||||
public class RandomGenerator extends AbstractGenerator {
|
||||
private static final long serialVersionUID = -7802758587765561876L;
|
||||
|
||||
/**
|
||||
* 构造,使用字母+数字做为基础
|
||||
*
|
||||
* @param count 生成验证码长度
|
||||
*/
|
||||
public RandomGenerator(int count) {
|
||||
super(count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param baseStr 基础字符集合,用于随机获取字符串的字符集合
|
||||
* @param length 生成验证码长度
|
||||
*/
|
||||
public RandomGenerator(String baseStr, int length) {
|
||||
super(baseStr, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generate() {
|
||||
return RandomUtil.randomString(this.baseStr, this.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(String code, String userInputCode) {
|
||||
if (StrUtil.isNotBlank(userInputCode)) {
|
||||
return StrUtil.equalsIgnoreCase(code, userInputCode);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
package cn.hutool.captcha.generator;
|
||||
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
* 随机字符验证码生成器<br>
|
||||
* 可以通过传入的基础集合和长度随机生成验证码字符
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.1.2
|
||||
*/
|
||||
public class RandomGenerator extends AbstractGenerator {
|
||||
private static final long serialVersionUID = -7802758587765561876L;
|
||||
|
||||
/**
|
||||
* 构造,使用字母+数字做为基础
|
||||
*
|
||||
* @param count 生成验证码长度
|
||||
*/
|
||||
public RandomGenerator(int count) {
|
||||
super(count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param baseStr 基础字符集合,用于随机获取字符串的字符集合
|
||||
* @param length 生成验证码长度
|
||||
*/
|
||||
public RandomGenerator(String baseStr, int length) {
|
||||
super(baseStr, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generate() {
|
||||
return RandomUtil.randomString(this.baseStr, this.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(String code, String userInputCode) {
|
||||
if (StrUtil.isNotBlank(userInputCode)) {
|
||||
return StrUtil.equalsIgnoreCase(code, userInputCode);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* 验证码生成策略实现
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.1.2
|
||||
*/
|
||||
/**
|
||||
* 验证码生成策略实现
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.1.2
|
||||
*/
|
||||
package cn.hutool.captcha.generator;
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* 图片验证码实现
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* 图片验证码实现
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.captcha;
|
@ -1,111 +1,111 @@
|
||||
package cn.hutool.captcha;
|
||||
|
||||
import cn.hutool.captcha.generator.MathGenerator;
|
||||
import cn.hutool.core.lang.Console;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* 直线干扰验证码单元测试
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
public class CaptchaTest {
|
||||
|
||||
@Test
|
||||
public void lineCaptchaTest1() {
|
||||
// 定义图形验证码的长和宽
|
||||
LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100);
|
||||
Assert.assertNotNull(lineCaptcha.getCode());
|
||||
Assert.assertTrue(lineCaptcha.verify(lineCaptcha.getCode()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void lineCaptchaTest3() {
|
||||
// 定义图形验证码的长和宽
|
||||
LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 70, 4, 15);
|
||||
lineCaptcha.setBackground(Color.yellow);
|
||||
lineCaptcha.write("f:/test/captcha/tellow.png");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void lineCaptchaWithMathTest() {
|
||||
// 定义图形验证码的长和宽
|
||||
LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 80);
|
||||
lineCaptcha.setGenerator(new MathGenerator());
|
||||
lineCaptcha.setTextAlpha(0.8f);
|
||||
lineCaptcha.write("f:/captcha/math.png");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void lineCaptchaTest2() {
|
||||
|
||||
// 定义图形验证码的长和宽
|
||||
LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100);
|
||||
// LineCaptcha lineCaptcha = new LineCaptcha(200, 100, 4, 150);
|
||||
// 图形验证码写出,可以写出到文件,也可以写出到流
|
||||
lineCaptcha.write("f:/captcha/line.png");
|
||||
Console.log(lineCaptcha.getCode());
|
||||
// 验证图形验证码的有效性,返回boolean值
|
||||
lineCaptcha.verify("1234");
|
||||
|
||||
lineCaptcha.createCode();
|
||||
lineCaptcha.write("f:/captcha/line2.png");
|
||||
Console.log(lineCaptcha.getCode());
|
||||
// 验证图形验证码的有效性,返回boolean值
|
||||
lineCaptcha.verify("1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void circleCaptchaTest() {
|
||||
|
||||
// 定义图形验证码的长和宽
|
||||
CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(200, 100, 4, 20);
|
||||
// CircleCaptcha captcha = new CircleCaptcha(200, 100, 4, 20);
|
||||
// 图形验证码写出,可以写出到文件,也可以写出到流
|
||||
captcha.write("f:/captcha/circle.png");
|
||||
// 验证图形验证码的有效性,返回boolean值
|
||||
captcha.verify("1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void ShearCaptchaTest() {
|
||||
|
||||
// 定义图形验证码的长和宽
|
||||
ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(203, 100, 4, 4);
|
||||
// ShearCaptcha captcha = new ShearCaptcha(200, 100, 4, 4);
|
||||
// 图形验证码写出,可以写出到文件,也可以写出到流
|
||||
captcha.write("f:/captcha/shear.png");
|
||||
// 验证图形验证码的有效性,返回boolean值
|
||||
captcha.verify("1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void ShearCaptchaWithMathTest() {
|
||||
// 定义图形验证码的长和宽
|
||||
ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(200, 45, 4, 4);
|
||||
captcha.setGenerator(new MathGenerator());
|
||||
// ShearCaptcha captcha = new ShearCaptcha(200, 100, 4, 4);
|
||||
// 图形验证码写出,可以写出到文件,也可以写出到流
|
||||
captcha.write("f:/captcha/shear_math.png");
|
||||
// 验证图形验证码的有效性,返回boolean值
|
||||
captcha.verify("1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void GifCaptchaTest() {
|
||||
GifCaptcha captcha = CaptchaUtil.createGifCaptcha(200, 100, 4);
|
||||
captcha.write("d:/test/gif_captcha.gif");
|
||||
assert captcha.verify(captcha.getCode());
|
||||
}
|
||||
}
|
||||
package cn.hutool.captcha;
|
||||
|
||||
import cn.hutool.captcha.generator.MathGenerator;
|
||||
import cn.hutool.core.lang.Console;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* 直线干扰验证码单元测试
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
public class CaptchaTest {
|
||||
|
||||
@Test
|
||||
public void lineCaptchaTest1() {
|
||||
// 定义图形验证码的长和宽
|
||||
LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100);
|
||||
Assert.assertNotNull(lineCaptcha.getCode());
|
||||
Assert.assertTrue(lineCaptcha.verify(lineCaptcha.getCode()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void lineCaptchaTest3() {
|
||||
// 定义图形验证码的长和宽
|
||||
LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 70, 4, 15);
|
||||
lineCaptcha.setBackground(Color.yellow);
|
||||
lineCaptcha.write("f:/test/captcha/tellow.png");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void lineCaptchaWithMathTest() {
|
||||
// 定义图形验证码的长和宽
|
||||
LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 80);
|
||||
lineCaptcha.setGenerator(new MathGenerator());
|
||||
lineCaptcha.setTextAlpha(0.8f);
|
||||
lineCaptcha.write("f:/captcha/math.png");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void lineCaptchaTest2() {
|
||||
|
||||
// 定义图形验证码的长和宽
|
||||
LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100);
|
||||
// LineCaptcha lineCaptcha = new LineCaptcha(200, 100, 4, 150);
|
||||
// 图形验证码写出,可以写出到文件,也可以写出到流
|
||||
lineCaptcha.write("f:/captcha/line.png");
|
||||
Console.log(lineCaptcha.getCode());
|
||||
// 验证图形验证码的有效性,返回boolean值
|
||||
lineCaptcha.verify("1234");
|
||||
|
||||
lineCaptcha.createCode();
|
||||
lineCaptcha.write("f:/captcha/line2.png");
|
||||
Console.log(lineCaptcha.getCode());
|
||||
// 验证图形验证码的有效性,返回boolean值
|
||||
lineCaptcha.verify("1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void circleCaptchaTest() {
|
||||
|
||||
// 定义图形验证码的长和宽
|
||||
CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(200, 100, 4, 20);
|
||||
// CircleCaptcha captcha = new CircleCaptcha(200, 100, 4, 20);
|
||||
// 图形验证码写出,可以写出到文件,也可以写出到流
|
||||
captcha.write("f:/captcha/circle.png");
|
||||
// 验证图形验证码的有效性,返回boolean值
|
||||
captcha.verify("1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void ShearCaptchaTest() {
|
||||
|
||||
// 定义图形验证码的长和宽
|
||||
ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(203, 100, 4, 4);
|
||||
// ShearCaptcha captcha = new ShearCaptcha(200, 100, 4, 4);
|
||||
// 图形验证码写出,可以写出到文件,也可以写出到流
|
||||
captcha.write("f:/captcha/shear.png");
|
||||
// 验证图形验证码的有效性,返回boolean值
|
||||
captcha.verify("1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void ShearCaptchaWithMathTest() {
|
||||
// 定义图形验证码的长和宽
|
||||
ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(200, 45, 4, 4);
|
||||
captcha.setGenerator(new MathGenerator());
|
||||
// ShearCaptcha captcha = new ShearCaptcha(200, 100, 4, 4);
|
||||
// 图形验证码写出,可以写出到文件,也可以写出到流
|
||||
captcha.write("f:/captcha/shear_math.png");
|
||||
// 验证图形验证码的有效性,返回boolean值
|
||||
captcha.verify("1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void GifCaptchaTest() {
|
||||
GifCaptcha captcha = CaptchaUtil.createGifCaptcha(200, 100, 4);
|
||||
captcha.write("d:/test/gif_captcha.gif");
|
||||
assert captcha.verify(captcha.getCode());
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,15 @@
|
||||
package cn.hutool.captcha;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
public class CaptchaUtilTest {
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void createTest() {
|
||||
for(int i = 0; i < 1; i++) {
|
||||
CaptchaUtil.createShearCaptcha(320, 240);
|
||||
}
|
||||
}
|
||||
}
|
||||
package cn.hutool.captcha;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
public class CaptchaUtilTest {
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void createTest() {
|
||||
for(int i = 0; i < 1; i++) {
|
||||
CaptchaUtil.createShearCaptcha(320, 240);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
package cn.hutool.captcha;
|
||||
|
||||
import cn.hutool.captcha.generator.MathGenerator;
|
||||
import org.junit.Test;
|
||||
|
||||
public class GeneratorTest {
|
||||
@Test
|
||||
public void mathGeneratorTest(){
|
||||
final MathGenerator mathGenerator = new MathGenerator();
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
mathGenerator.verify(mathGenerator.generate(), "0");
|
||||
}
|
||||
}
|
||||
}
|
||||
package cn.hutool.captcha;
|
||||
|
||||
import cn.hutool.captcha.generator.MathGenerator;
|
||||
import org.junit.Test;
|
||||
|
||||
public class GeneratorTest {
|
||||
@Test
|
||||
public void mathGeneratorTest(){
|
||||
final MathGenerator mathGenerator = new MathGenerator();
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
mathGenerator.verify(mathGenerator.generate(), "0");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,19 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<description>Hutool核心,包括集合、字符串、Bean等工具</description>
|
||||
|
||||
</project>
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<description>Hutool核心,包括集合、字符串、Bean等工具</description>
|
||||
|
||||
</project>
|
||||
|
@ -1,26 +1,26 @@
|
||||
package cn.hutool.core.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 别名注解,使用此注解的字段、方法、参数等会有一个别名,用于Bean拷贝、Bean转Map等
|
||||
*
|
||||
* @author Looly
|
||||
* @since 5.1.1
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
|
||||
public @interface Alias {
|
||||
|
||||
/**
|
||||
* 别名值,即使用此注解要替换成的别名名称
|
||||
*
|
||||
* @return 别名值
|
||||
*/
|
||||
String value();
|
||||
}
|
||||
package cn.hutool.core.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 别名注解,使用此注解的字段、方法、参数等会有一个别名,用于Bean拷贝、Bean转Map等
|
||||
*
|
||||
* @author Looly
|
||||
* @since 5.1.1
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
|
||||
public @interface Alias {
|
||||
|
||||
/**
|
||||
* 别名值,即使用此注解要替换成的别名名称
|
||||
*
|
||||
* @return 别名值
|
||||
*/
|
||||
String value();
|
||||
}
|
||||
|
@ -1,218 +1,218 @@
|
||||
package cn.hutool.core.annotation;
|
||||
|
||||
import cn.hutool.core.exceptions.UtilException;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 注解工具类<br>
|
||||
* 快速获取注解对象、注解值等工具封装
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.0.9
|
||||
*/
|
||||
public class AnnotationUtil {
|
||||
|
||||
/**
|
||||
* 将指定的被注解的元素转换为组合注解元素
|
||||
*
|
||||
* @param annotationEle 注解元素
|
||||
* @return 组合注解元素
|
||||
*/
|
||||
public static CombinationAnnotationElement toCombination(AnnotatedElement annotationEle) {
|
||||
if (annotationEle instanceof CombinationAnnotationElement) {
|
||||
return (CombinationAnnotationElement) annotationEle;
|
||||
}
|
||||
return new CombinationAnnotationElement(annotationEle);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定注解
|
||||
*
|
||||
* @param annotationEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission
|
||||
* @param isToCombination 是否为转换为组合注解
|
||||
* @return 注解对象
|
||||
*/
|
||||
public static Annotation[] getAnnotations(AnnotatedElement annotationEle, boolean isToCombination) {
|
||||
return (null == annotationEle) ? null : (isToCombination ? toCombination(annotationEle) : annotationEle).getAnnotations();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定注解
|
||||
*
|
||||
* @param <A> 注解类型
|
||||
* @param annotationEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission
|
||||
* @param annotationType 注解类型
|
||||
* @return 注解对象
|
||||
*/
|
||||
public static <A extends Annotation> A getAnnotation(AnnotatedElement annotationEle, Class<A> annotationType) {
|
||||
return (null == annotationEle) ? null : toCombination(annotationEle).getAnnotation(annotationType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否包含指定注解指定注解
|
||||
*
|
||||
* @param annotationEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission
|
||||
* @param annotationType 注解类型
|
||||
* @return 是否包含指定注解
|
||||
* @since 5.4.2
|
||||
*/
|
||||
public static boolean hasAnnotation(AnnotatedElement annotationEle, Class<? extends Annotation> annotationType) {
|
||||
return null != getAnnotation(annotationEle, annotationType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定注解默认值<br>
|
||||
* 如果无指定的属性方法返回null
|
||||
*
|
||||
* @param <T> 注解值类型
|
||||
* @param annotationEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission
|
||||
* @param annotationType 注解类型
|
||||
* @return 注解对象
|
||||
* @throws UtilException 调用注解中的方法时执行异常
|
||||
*/
|
||||
public static <T> T getAnnotationValue(AnnotatedElement annotationEle, Class<? extends Annotation> annotationType) throws UtilException {
|
||||
return getAnnotationValue(annotationEle, annotationType, "value");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定注解属性的值<br>
|
||||
* 如果无指定的属性方法返回null
|
||||
*
|
||||
* @param <T> 注解值类型
|
||||
* @param annotationEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission
|
||||
* @param annotationType 注解类型
|
||||
* @param propertyName 属性名,例如注解中定义了name()方法,则 此处传入name
|
||||
* @return 注解对象
|
||||
* @throws UtilException 调用注解中的方法时执行异常
|
||||
*/
|
||||
public static <T> T getAnnotationValue(AnnotatedElement annotationEle, Class<? extends Annotation> annotationType, String propertyName) throws UtilException {
|
||||
final Annotation annotation = getAnnotation(annotationEle, annotationType);
|
||||
if (null == annotation) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Method method = ReflectUtil.getMethodOfObj(annotation, propertyName);
|
||||
if (null == method) {
|
||||
return null;
|
||||
}
|
||||
return ReflectUtil.invoke(annotation, method);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定注解中所有属性值<br>
|
||||
* 如果无指定的属性方法返回null
|
||||
*
|
||||
* @param annotationEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission
|
||||
* @param annotationType 注解类型
|
||||
* @return 注解对象
|
||||
* @throws UtilException 调用注解中的方法时执行异常
|
||||
*/
|
||||
public static Map<String, Object> getAnnotationValueMap(AnnotatedElement annotationEle, Class<? extends Annotation> annotationType) throws UtilException {
|
||||
final Annotation annotation = getAnnotation(annotationEle, annotationType);
|
||||
if (null == annotation) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Method[] methods = ReflectUtil.getMethods(annotationType, t -> {
|
||||
if (ArrayUtil.isEmpty(t.getParameterTypes())) {
|
||||
// 只读取无参方法
|
||||
final String name = t.getName();
|
||||
// 跳过自有的几个方法
|
||||
return (false == "hashCode".equals(name)) //
|
||||
&& (false == "toString".equals(name)) //
|
||||
&& (false == "annotationType".equals(name));
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
final HashMap<String, Object> result = new HashMap<>(methods.length, 1);
|
||||
for (Method method : methods) {
|
||||
result.put(method.getName(), ReflectUtil.invoke(annotation, method));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取注解类的保留时间,可选值 SOURCE(源码时),CLASS(编译时),RUNTIME(运行时),默认为 CLASS
|
||||
*
|
||||
* @param annotationType 注解类
|
||||
* @return 保留时间枚举
|
||||
*/
|
||||
public static RetentionPolicy getRetentionPolicy(Class<? extends Annotation> annotationType) {
|
||||
final Retention retention = annotationType.getAnnotation(Retention.class);
|
||||
if (null == retention) {
|
||||
return RetentionPolicy.CLASS;
|
||||
}
|
||||
return retention.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取注解类可以用来修饰哪些程序元素,如 TYPE, METHOD, CONSTRUCTOR, FIELD, PARAMETER 等
|
||||
*
|
||||
* @param annotationType 注解类
|
||||
* @return 注解修饰的程序元素数组
|
||||
*/
|
||||
public static ElementType[] getTargetType(Class<? extends Annotation> annotationType) {
|
||||
final Target target = annotationType.getAnnotation(Target.class);
|
||||
if (null == target) {
|
||||
return new ElementType[]{ElementType.TYPE, //
|
||||
ElementType.FIELD, //
|
||||
ElementType.METHOD, //
|
||||
ElementType.PARAMETER, //
|
||||
ElementType.CONSTRUCTOR, //
|
||||
ElementType.LOCAL_VARIABLE, //
|
||||
ElementType.ANNOTATION_TYPE, //
|
||||
ElementType.PACKAGE//
|
||||
};
|
||||
}
|
||||
return target.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否会保存到 Javadoc 文档中
|
||||
*
|
||||
* @param annotationType 注解类
|
||||
* @return 是否会保存到 Javadoc 文档中
|
||||
*/
|
||||
public static boolean isDocumented(Class<? extends Annotation> annotationType) {
|
||||
return annotationType.isAnnotationPresent(Documented.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否可以被继承,默认为 false
|
||||
*
|
||||
* @param annotationType 注解类
|
||||
* @return 是否会保存到 Javadoc 文档中
|
||||
*/
|
||||
public static boolean isInherited(Class<? extends Annotation> annotationType) {
|
||||
return annotationType.isAnnotationPresent(Inherited.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置新的注解的属性(字段)值
|
||||
*
|
||||
* @param annotation 注解对象
|
||||
* @param annotationField 注解属性(字段)名称
|
||||
* @param value 要更新的属性值
|
||||
* @since 5.5.2
|
||||
*/
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public static void setValue(Annotation annotation, String annotationField, Object value) {
|
||||
final Map memberValues = (Map) ReflectUtil.getFieldValue(Proxy.getInvocationHandler(annotation), "memberValues");
|
||||
memberValues.put(annotationField, value);
|
||||
}
|
||||
}
|
||||
package cn.hutool.core.annotation;
|
||||
|
||||
import cn.hutool.core.exceptions.UtilException;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 注解工具类<br>
|
||||
* 快速获取注解对象、注解值等工具封装
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.0.9
|
||||
*/
|
||||
public class AnnotationUtil {
|
||||
|
||||
/**
|
||||
* 将指定的被注解的元素转换为组合注解元素
|
||||
*
|
||||
* @param annotationEle 注解元素
|
||||
* @return 组合注解元素
|
||||
*/
|
||||
public static CombinationAnnotationElement toCombination(AnnotatedElement annotationEle) {
|
||||
if (annotationEle instanceof CombinationAnnotationElement) {
|
||||
return (CombinationAnnotationElement) annotationEle;
|
||||
}
|
||||
return new CombinationAnnotationElement(annotationEle);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定注解
|
||||
*
|
||||
* @param annotationEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission
|
||||
* @param isToCombination 是否为转换为组合注解
|
||||
* @return 注解对象
|
||||
*/
|
||||
public static Annotation[] getAnnotations(AnnotatedElement annotationEle, boolean isToCombination) {
|
||||
return (null == annotationEle) ? null : (isToCombination ? toCombination(annotationEle) : annotationEle).getAnnotations();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定注解
|
||||
*
|
||||
* @param <A> 注解类型
|
||||
* @param annotationEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission
|
||||
* @param annotationType 注解类型
|
||||
* @return 注解对象
|
||||
*/
|
||||
public static <A extends Annotation> A getAnnotation(AnnotatedElement annotationEle, Class<A> annotationType) {
|
||||
return (null == annotationEle) ? null : toCombination(annotationEle).getAnnotation(annotationType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否包含指定注解指定注解
|
||||
*
|
||||
* @param annotationEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission
|
||||
* @param annotationType 注解类型
|
||||
* @return 是否包含指定注解
|
||||
* @since 5.4.2
|
||||
*/
|
||||
public static boolean hasAnnotation(AnnotatedElement annotationEle, Class<? extends Annotation> annotationType) {
|
||||
return null != getAnnotation(annotationEle, annotationType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定注解默认值<br>
|
||||
* 如果无指定的属性方法返回null
|
||||
*
|
||||
* @param <T> 注解值类型
|
||||
* @param annotationEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission
|
||||
* @param annotationType 注解类型
|
||||
* @return 注解对象
|
||||
* @throws UtilException 调用注解中的方法时执行异常
|
||||
*/
|
||||
public static <T> T getAnnotationValue(AnnotatedElement annotationEle, Class<? extends Annotation> annotationType) throws UtilException {
|
||||
return getAnnotationValue(annotationEle, annotationType, "value");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定注解属性的值<br>
|
||||
* 如果无指定的属性方法返回null
|
||||
*
|
||||
* @param <T> 注解值类型
|
||||
* @param annotationEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission
|
||||
* @param annotationType 注解类型
|
||||
* @param propertyName 属性名,例如注解中定义了name()方法,则 此处传入name
|
||||
* @return 注解对象
|
||||
* @throws UtilException 调用注解中的方法时执行异常
|
||||
*/
|
||||
public static <T> T getAnnotationValue(AnnotatedElement annotationEle, Class<? extends Annotation> annotationType, String propertyName) throws UtilException {
|
||||
final Annotation annotation = getAnnotation(annotationEle, annotationType);
|
||||
if (null == annotation) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Method method = ReflectUtil.getMethodOfObj(annotation, propertyName);
|
||||
if (null == method) {
|
||||
return null;
|
||||
}
|
||||
return ReflectUtil.invoke(annotation, method);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定注解中所有属性值<br>
|
||||
* 如果无指定的属性方法返回null
|
||||
*
|
||||
* @param annotationEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission
|
||||
* @param annotationType 注解类型
|
||||
* @return 注解对象
|
||||
* @throws UtilException 调用注解中的方法时执行异常
|
||||
*/
|
||||
public static Map<String, Object> getAnnotationValueMap(AnnotatedElement annotationEle, Class<? extends Annotation> annotationType) throws UtilException {
|
||||
final Annotation annotation = getAnnotation(annotationEle, annotationType);
|
||||
if (null == annotation) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Method[] methods = ReflectUtil.getMethods(annotationType, t -> {
|
||||
if (ArrayUtil.isEmpty(t.getParameterTypes())) {
|
||||
// 只读取无参方法
|
||||
final String name = t.getName();
|
||||
// 跳过自有的几个方法
|
||||
return (false == "hashCode".equals(name)) //
|
||||
&& (false == "toString".equals(name)) //
|
||||
&& (false == "annotationType".equals(name));
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
final HashMap<String, Object> result = new HashMap<>(methods.length, 1);
|
||||
for (Method method : methods) {
|
||||
result.put(method.getName(), ReflectUtil.invoke(annotation, method));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取注解类的保留时间,可选值 SOURCE(源码时),CLASS(编译时),RUNTIME(运行时),默认为 CLASS
|
||||
*
|
||||
* @param annotationType 注解类
|
||||
* @return 保留时间枚举
|
||||
*/
|
||||
public static RetentionPolicy getRetentionPolicy(Class<? extends Annotation> annotationType) {
|
||||
final Retention retention = annotationType.getAnnotation(Retention.class);
|
||||
if (null == retention) {
|
||||
return RetentionPolicy.CLASS;
|
||||
}
|
||||
return retention.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取注解类可以用来修饰哪些程序元素,如 TYPE, METHOD, CONSTRUCTOR, FIELD, PARAMETER 等
|
||||
*
|
||||
* @param annotationType 注解类
|
||||
* @return 注解修饰的程序元素数组
|
||||
*/
|
||||
public static ElementType[] getTargetType(Class<? extends Annotation> annotationType) {
|
||||
final Target target = annotationType.getAnnotation(Target.class);
|
||||
if (null == target) {
|
||||
return new ElementType[]{ElementType.TYPE, //
|
||||
ElementType.FIELD, //
|
||||
ElementType.METHOD, //
|
||||
ElementType.PARAMETER, //
|
||||
ElementType.CONSTRUCTOR, //
|
||||
ElementType.LOCAL_VARIABLE, //
|
||||
ElementType.ANNOTATION_TYPE, //
|
||||
ElementType.PACKAGE//
|
||||
};
|
||||
}
|
||||
return target.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否会保存到 Javadoc 文档中
|
||||
*
|
||||
* @param annotationType 注解类
|
||||
* @return 是否会保存到 Javadoc 文档中
|
||||
*/
|
||||
public static boolean isDocumented(Class<? extends Annotation> annotationType) {
|
||||
return annotationType.isAnnotationPresent(Documented.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否可以被继承,默认为 false
|
||||
*
|
||||
* @param annotationType 注解类
|
||||
* @return 是否会保存到 Javadoc 文档中
|
||||
*/
|
||||
public static boolean isInherited(Class<? extends Annotation> annotationType) {
|
||||
return annotationType.isAnnotationPresent(Inherited.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置新的注解的属性(字段)值
|
||||
*
|
||||
* @param annotation 注解对象
|
||||
* @param annotationField 注解属性(字段)名称
|
||||
* @param value 要更新的属性值
|
||||
* @since 5.5.2
|
||||
*/
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public static void setValue(Annotation annotation, String annotationField, Object value) {
|
||||
final Map memberValues = (Map) ReflectUtil.getFieldValue(Proxy.getInvocationHandler(annotation), "memberValues");
|
||||
memberValues.put(annotationField, value);
|
||||
}
|
||||
}
|
||||
|
@ -1,128 +1,128 @@
|
||||
package cn.hutool.core.annotation;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 组合注解 对JDK的原生注解机制做一个增强,支持类似Spring的组合注解。<br>
|
||||
* 核心实现使用了递归获取指定元素上的注解以及注解的注解,以实现复合注解的获取。
|
||||
*
|
||||
* @author Succy,Looly
|
||||
* @since 4.0.9
|
||||
**/
|
||||
|
||||
public class CombinationAnnotationElement implements AnnotatedElement, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 元注解 */
|
||||
private static final Set<Class<? extends Annotation>> META_ANNOTATIONS = CollUtil.newHashSet(Target.class, //
|
||||
Retention.class, //
|
||||
Inherited.class, //
|
||||
Documented.class, //
|
||||
SuppressWarnings.class, //
|
||||
Override.class, //
|
||||
Deprecated.class//
|
||||
);
|
||||
|
||||
/** 注解类型与注解对象对应表 */
|
||||
private Map<Class<? extends Annotation>, Annotation> annotationMap;
|
||||
/** 直接注解类型与注解对象对应表 */
|
||||
private Map<Class<? extends Annotation>, Annotation> declaredAnnotationMap;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param element 需要解析注解的元素:可以是Class、Method、Field、Constructor、ReflectPermission
|
||||
*/
|
||||
public CombinationAnnotationElement(AnnotatedElement element) {
|
||||
init(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
|
||||
return annotationMap.containsKey(annotationClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
|
||||
Annotation annotation = annotationMap.get(annotationClass);
|
||||
return (annotation == null) ? null : (T) annotation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Annotation[] getAnnotations() {
|
||||
final Collection<Annotation> annotations = this.annotationMap.values();
|
||||
return annotations.toArray(new Annotation[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Annotation[] getDeclaredAnnotations() {
|
||||
final Collection<Annotation> annotations = this.declaredAnnotationMap.values();
|
||||
return annotations.toArray(new Annotation[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*
|
||||
* @param element 元素
|
||||
*/
|
||||
private void init(AnnotatedElement element) {
|
||||
final Annotation[] declaredAnnotations = element.getDeclaredAnnotations();
|
||||
this.declaredAnnotationMap = new HashMap<>();
|
||||
parseDeclared(declaredAnnotations);
|
||||
|
||||
final Annotation[] annotations = element.getAnnotations();
|
||||
if(Arrays.equals(declaredAnnotations, annotations)) {
|
||||
this.annotationMap = this.declaredAnnotationMap;
|
||||
}else {
|
||||
this.annotationMap = new HashMap<>();
|
||||
parse(annotations);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 进行递归解析注解,直到全部都是元注解为止
|
||||
*
|
||||
* @param annotations Class, Method, Field等
|
||||
*/
|
||||
private void parseDeclared(Annotation[] annotations) {
|
||||
Class<? extends Annotation> annotationType;
|
||||
// 直接注解
|
||||
for (Annotation annotation : annotations) {
|
||||
annotationType = annotation.annotationType();
|
||||
if (false == META_ANNOTATIONS.contains(annotationType)) {
|
||||
declaredAnnotationMap.put(annotationType, annotation);
|
||||
parseDeclared(annotationType.getDeclaredAnnotations());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 进行递归解析注解,直到全部都是元注解为止
|
||||
*
|
||||
* @param annotations Class, Method, Field等
|
||||
*/
|
||||
private void parse(Annotation[] annotations) {
|
||||
Class<? extends Annotation> annotationType;
|
||||
for (Annotation annotation : annotations) {
|
||||
annotationType = annotation.annotationType();
|
||||
if (false == META_ANNOTATIONS.contains(annotationType)) {
|
||||
annotationMap.put(annotationType, annotation);
|
||||
parse(annotationType.getAnnotations());
|
||||
}
|
||||
}
|
||||
}
|
||||
package cn.hutool.core.annotation;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 组合注解 对JDK的原生注解机制做一个增强,支持类似Spring的组合注解。<br>
|
||||
* 核心实现使用了递归获取指定元素上的注解以及注解的注解,以实现复合注解的获取。
|
||||
*
|
||||
* @author Succy,Looly
|
||||
* @since 4.0.9
|
||||
**/
|
||||
|
||||
public class CombinationAnnotationElement implements AnnotatedElement, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 元注解 */
|
||||
private static final Set<Class<? extends Annotation>> META_ANNOTATIONS = CollUtil.newHashSet(Target.class, //
|
||||
Retention.class, //
|
||||
Inherited.class, //
|
||||
Documented.class, //
|
||||
SuppressWarnings.class, //
|
||||
Override.class, //
|
||||
Deprecated.class//
|
||||
);
|
||||
|
||||
/** 注解类型与注解对象对应表 */
|
||||
private Map<Class<? extends Annotation>, Annotation> annotationMap;
|
||||
/** 直接注解类型与注解对象对应表 */
|
||||
private Map<Class<? extends Annotation>, Annotation> declaredAnnotationMap;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param element 需要解析注解的元素:可以是Class、Method、Field、Constructor、ReflectPermission
|
||||
*/
|
||||
public CombinationAnnotationElement(AnnotatedElement element) {
|
||||
init(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
|
||||
return annotationMap.containsKey(annotationClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
|
||||
Annotation annotation = annotationMap.get(annotationClass);
|
||||
return (annotation == null) ? null : (T) annotation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Annotation[] getAnnotations() {
|
||||
final Collection<Annotation> annotations = this.annotationMap.values();
|
||||
return annotations.toArray(new Annotation[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Annotation[] getDeclaredAnnotations() {
|
||||
final Collection<Annotation> annotations = this.declaredAnnotationMap.values();
|
||||
return annotations.toArray(new Annotation[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*
|
||||
* @param element 元素
|
||||
*/
|
||||
private void init(AnnotatedElement element) {
|
||||
final Annotation[] declaredAnnotations = element.getDeclaredAnnotations();
|
||||
this.declaredAnnotationMap = new HashMap<>();
|
||||
parseDeclared(declaredAnnotations);
|
||||
|
||||
final Annotation[] annotations = element.getAnnotations();
|
||||
if(Arrays.equals(declaredAnnotations, annotations)) {
|
||||
this.annotationMap = this.declaredAnnotationMap;
|
||||
}else {
|
||||
this.annotationMap = new HashMap<>();
|
||||
parse(annotations);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 进行递归解析注解,直到全部都是元注解为止
|
||||
*
|
||||
* @param annotations Class, Method, Field等
|
||||
*/
|
||||
private void parseDeclared(Annotation[] annotations) {
|
||||
Class<? extends Annotation> annotationType;
|
||||
// 直接注解
|
||||
for (Annotation annotation : annotations) {
|
||||
annotationType = annotation.annotationType();
|
||||
if (false == META_ANNOTATIONS.contains(annotationType)) {
|
||||
declaredAnnotationMap.put(annotationType, annotation);
|
||||
parseDeclared(annotationType.getDeclaredAnnotations());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 进行递归解析注解,直到全部都是元注解为止
|
||||
*
|
||||
* @param annotations Class, Method, Field等
|
||||
*/
|
||||
private void parse(Annotation[] annotations) {
|
||||
Class<? extends Annotation> annotationType;
|
||||
for (Annotation annotation : annotations) {
|
||||
annotationType = annotation.annotationType();
|
||||
if (false == META_ANNOTATIONS.contains(annotationType)) {
|
||||
annotationMap.put(annotationType, annotation);
|
||||
parse(annotationType.getAnnotations());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,21 +1,21 @@
|
||||
package cn.hutool.core.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 属性忽略注解,使用此注解的字段等会被忽略,主要用于Bean拷贝、Bean转Map等<br>
|
||||
* 此注解应用于字段时,忽略读取和设置属性值,应用于setXXX方法忽略设置值,应用于getXXX忽略读取值
|
||||
*
|
||||
* @author Looly
|
||||
* @since 5.4.2
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
|
||||
public @interface PropIgnore {
|
||||
|
||||
}
|
||||
package cn.hutool.core.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 属性忽略注解,使用此注解的字段等会被忽略,主要用于Bean拷贝、Bean转Map等<br>
|
||||
* 此注解应用于字段时,忽略读取和设置属性值,应用于setXXX方法忽略设置值,应用于getXXX忽略读取值
|
||||
*
|
||||
* @author Looly
|
||||
* @since 5.4.2
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
|
||||
public @interface PropIgnore {
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* 注解包,提供增强型注解和注解工具类
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* 注解包,提供增强型注解和注解工具类
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.core.annotation;
|
@ -1,330 +1,330 @@
|
||||
package cn.hutool.core.bean;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.map.CaseInsensitiveMap;
|
||||
import cn.hutool.core.util.BooleanUtil;
|
||||
import cn.hutool.core.util.ModifierUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Bean信息描述做为BeanInfo替代方案,此对象持有JavaBean中的setters和getters等相关信息描述<br>
|
||||
* 查找Getter和Setter方法时会:
|
||||
*
|
||||
* <pre>
|
||||
* 1. 忽略字段和方法名的大小写
|
||||
* 2. Getter查找getXXX、isXXX、getIsXXX
|
||||
* 3. Setter查找setXXX、setIsXXX
|
||||
* 4. Setter忽略参数值与字段值不匹配的情况,因此有多个参数类型的重载时,会调用首次匹配的
|
||||
* </pre>
|
||||
*
|
||||
* @author looly
|
||||
* @since 3.1.2
|
||||
*/
|
||||
public class BeanDesc implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Bean类
|
||||
*/
|
||||
private final Class<?> beanClass;
|
||||
/**
|
||||
* 属性Map
|
||||
*/
|
||||
private final Map<String, PropDesc> propMap = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param beanClass Bean类
|
||||
*/
|
||||
public BeanDesc(Class<?> beanClass) {
|
||||
Assert.notNull(beanClass);
|
||||
this.beanClass = beanClass;
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Bean的全类名
|
||||
*
|
||||
* @return Bean的类名
|
||||
*/
|
||||
public String getName() {
|
||||
return this.beanClass.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Bean的简单类名
|
||||
*
|
||||
* @return Bean的类名
|
||||
*/
|
||||
public String getSimpleName() {
|
||||
return this.beanClass.getSimpleName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段名-字段属性Map
|
||||
*
|
||||
* @param ignoreCase 是否忽略大小写,true为忽略,false不忽略
|
||||
* @return 字段名-字段属性Map
|
||||
*/
|
||||
public Map<String, PropDesc> getPropMap(boolean ignoreCase) {
|
||||
return ignoreCase ? new CaseInsensitiveMap<>(1, this.propMap) : this.propMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段属性列表
|
||||
*
|
||||
* @return {@link PropDesc} 列表
|
||||
*/
|
||||
public Collection<PropDesc> getProps() {
|
||||
return this.propMap.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取属性,如果不存在返回null
|
||||
*
|
||||
* @param fieldName 字段名
|
||||
* @return {@link PropDesc}
|
||||
*/
|
||||
public PropDesc getProp(String fieldName) {
|
||||
return this.propMap.get(fieldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得字段名对应的字段对象,如果不存在返回null
|
||||
*
|
||||
* @param fieldName 字段名
|
||||
* @return 字段值
|
||||
*/
|
||||
public Field getField(String fieldName) {
|
||||
final PropDesc desc = this.propMap.get(fieldName);
|
||||
return null == desc ? null : desc.getField();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Getter方法,如果不存在返回null
|
||||
*
|
||||
* @param fieldName 字段名
|
||||
* @return Getter方法
|
||||
*/
|
||||
public Method getGetter(String fieldName) {
|
||||
final PropDesc desc = this.propMap.get(fieldName);
|
||||
return null == desc ? null : desc.getGetter();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Setter方法,如果不存在返回null
|
||||
*
|
||||
* @param fieldName 字段名
|
||||
* @return Setter方法
|
||||
*/
|
||||
public Method getSetter(String fieldName) {
|
||||
final PropDesc desc = this.propMap.get(fieldName);
|
||||
return null == desc ? null : desc.getSetter();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------------ Private method start
|
||||
|
||||
/**
|
||||
* 初始化<br>
|
||||
* 只有与属性关联的相关Getter和Setter方法才会被读取,无关的getXXX和setXXX都被忽略
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
private BeanDesc init() {
|
||||
final Method[] methods = ReflectUtil.getMethods(this.beanClass);
|
||||
PropDesc prop;
|
||||
for (Field field : ReflectUtil.getFields(this.beanClass)) {
|
||||
if (false == ModifierUtil.isStatic(field)) {
|
||||
//只针对非static属性
|
||||
prop = createProp(field, methods);
|
||||
// 只有不存在时才放入,防止父类属性覆盖子类属性
|
||||
this.propMap.putIfAbsent(prop.getFieldName(), prop);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据字段创建属性描述<br>
|
||||
* 查找Getter和Setter方法时会:
|
||||
*
|
||||
* <pre>
|
||||
* 1. 忽略字段和方法名的大小写
|
||||
* 2. Getter查找getXXX、isXXX、getIsXXX
|
||||
* 3. Setter查找setXXX、setIsXXX
|
||||
* 4. Setter忽略参数值与字段值不匹配的情况,因此有多个参数类型的重载时,会调用首次匹配的
|
||||
* </pre>
|
||||
*
|
||||
* @param field 字段
|
||||
* @param methods 类中所有的方法
|
||||
* @return {@link PropDesc}
|
||||
* @since 4.0.2
|
||||
*/
|
||||
private PropDesc createProp(Field field, Method[] methods) {
|
||||
final PropDesc prop = findProp(field, methods, false);
|
||||
// 忽略大小写重新匹配一次
|
||||
if (null == prop.getter || null == prop.setter) {
|
||||
final PropDesc propIgnoreCase = findProp(field, methods, true);
|
||||
if (null == prop.getter) {
|
||||
prop.getter = propIgnoreCase.getter;
|
||||
}
|
||||
if (null == prop.setter) {
|
||||
prop.setter = propIgnoreCase.setter;
|
||||
}
|
||||
}
|
||||
|
||||
return prop;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找字段对应的Getter和Setter方法
|
||||
*
|
||||
* @param field 字段
|
||||
* @param methods 类中所有的方法
|
||||
* @param ignoreCase 是否忽略大小写匹配
|
||||
* @return PropDesc
|
||||
*/
|
||||
private PropDesc findProp(Field field, Method[] methods, boolean ignoreCase) {
|
||||
final String fieldName = field.getName();
|
||||
final Class<?> fieldType = field.getType();
|
||||
final boolean isBooleanField = BooleanUtil.isBoolean(fieldType);
|
||||
|
||||
Method getter = null;
|
||||
Method setter = null;
|
||||
String methodName;
|
||||
Class<?>[] parameterTypes;
|
||||
for (Method method : methods) {
|
||||
parameterTypes = method.getParameterTypes();
|
||||
if (parameterTypes.length > 1) {
|
||||
// 多于1个参数说明非Getter或Setter
|
||||
continue;
|
||||
}
|
||||
|
||||
methodName = method.getName();
|
||||
if (parameterTypes.length == 0) {
|
||||
// 无参数,可能为Getter方法
|
||||
if (isMatchGetter(methodName, fieldName, isBooleanField, ignoreCase)) {
|
||||
// 方法名与字段名匹配,则为Getter方法
|
||||
getter = method;
|
||||
}
|
||||
} else if (isMatchSetter(methodName, fieldName, isBooleanField, ignoreCase)) {
|
||||
// 只有一个参数的情况下方法名与字段名对应匹配,则为Setter方法
|
||||
setter = method;
|
||||
}
|
||||
if (null != getter && null != setter) {
|
||||
// 如果Getter和Setter方法都找到了,不再继续寻找
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new PropDesc(field, getter, setter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 方法是否为Getter方法<br>
|
||||
* 匹配规则如下(忽略大小写):
|
||||
*
|
||||
* <pre>
|
||||
* 字段名 -》 方法名
|
||||
* isName -》 isName
|
||||
* isName -》 isIsName
|
||||
* isName -》 getIsName
|
||||
* name -》 isName
|
||||
* name -》 getName
|
||||
* </pre>
|
||||
*
|
||||
* @param methodName 方法名
|
||||
* @param fieldName 字段名
|
||||
* @param isBooleanField 是否为Boolean类型字段
|
||||
* @param ignoreCase 匹配是否忽略大小写
|
||||
* @return 是否匹配
|
||||
*/
|
||||
private boolean isMatchGetter(String methodName, String fieldName, boolean isBooleanField, boolean ignoreCase) {
|
||||
// 全部转为小写,忽略大小写比较
|
||||
if (ignoreCase) {
|
||||
methodName = methodName.toLowerCase();
|
||||
fieldName = fieldName.toLowerCase();
|
||||
} else {
|
||||
fieldName = StrUtil.upperFirst(fieldName);
|
||||
}
|
||||
|
||||
if (false == methodName.startsWith("get") && false == methodName.startsWith("is")) {
|
||||
// 非标准Getter方法
|
||||
return false;
|
||||
}
|
||||
if ("getclass".equals(methodName)) {
|
||||
//跳过getClass方法
|
||||
return false;
|
||||
}
|
||||
|
||||
// 针对Boolean类型特殊检查
|
||||
if (isBooleanField) {
|
||||
if (fieldName.startsWith("is")) {
|
||||
// 字段已经是is开头
|
||||
if (methodName.equals(fieldName) // isName -》 isName
|
||||
|| methodName.equals("get" + fieldName)// isName -》 getIsName
|
||||
|| methodName.equals("is" + fieldName)// isName -》 isIsName
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
} else if (methodName.equals("is" + fieldName)) {
|
||||
// 字段非is开头, name -》 isName
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 包括boolean的任何类型只有一种匹配情况:name -》 getName
|
||||
return methodName.equals("get" + fieldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 方法是否为Setter方法<br>
|
||||
* 匹配规则如下(忽略大小写):
|
||||
*
|
||||
* <pre>
|
||||
* 字段名 -》 方法名
|
||||
* isName -》 setName
|
||||
* isName -》 setIsName
|
||||
* name -》 setName
|
||||
* </pre>
|
||||
*
|
||||
* @param methodName 方法名
|
||||
* @param fieldName 字段名
|
||||
* @param isBooleanField 是否为Boolean类型字段
|
||||
* @param ignoreCase 匹配是否忽略大小写
|
||||
* @return 是否匹配
|
||||
*/
|
||||
private boolean isMatchSetter(String methodName, String fieldName, boolean isBooleanField, boolean ignoreCase) {
|
||||
// 全部转为小写,忽略大小写比较
|
||||
methodName = methodName.toLowerCase();
|
||||
fieldName = fieldName.toLowerCase();
|
||||
|
||||
// 非标准Setter方法跳过
|
||||
if (false == methodName.startsWith("set")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 针对Boolean类型特殊检查
|
||||
if (isBooleanField && fieldName.startsWith("is")) {
|
||||
// 字段是is开头
|
||||
if (methodName.equals("set" + StrUtil.removePrefix(fieldName, "is"))// isName -》 setName
|
||||
|| methodName.equals("set" + fieldName)// isName -》 setIsName
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 包括boolean的任何类型只有一种匹配情况:name -》 setName
|
||||
return methodName.equals("set" + fieldName);
|
||||
}
|
||||
// ------------------------------------------------------------------------------------------------------ Private method end
|
||||
package cn.hutool.core.bean;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.map.CaseInsensitiveMap;
|
||||
import cn.hutool.core.util.BooleanUtil;
|
||||
import cn.hutool.core.util.ModifierUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Bean信息描述做为BeanInfo替代方案,此对象持有JavaBean中的setters和getters等相关信息描述<br>
|
||||
* 查找Getter和Setter方法时会:
|
||||
*
|
||||
* <pre>
|
||||
* 1. 忽略字段和方法名的大小写
|
||||
* 2. Getter查找getXXX、isXXX、getIsXXX
|
||||
* 3. Setter查找setXXX、setIsXXX
|
||||
* 4. Setter忽略参数值与字段值不匹配的情况,因此有多个参数类型的重载时,会调用首次匹配的
|
||||
* </pre>
|
||||
*
|
||||
* @author looly
|
||||
* @since 3.1.2
|
||||
*/
|
||||
public class BeanDesc implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Bean类
|
||||
*/
|
||||
private final Class<?> beanClass;
|
||||
/**
|
||||
* 属性Map
|
||||
*/
|
||||
private final Map<String, PropDesc> propMap = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param beanClass Bean类
|
||||
*/
|
||||
public BeanDesc(Class<?> beanClass) {
|
||||
Assert.notNull(beanClass);
|
||||
this.beanClass = beanClass;
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Bean的全类名
|
||||
*
|
||||
* @return Bean的类名
|
||||
*/
|
||||
public String getName() {
|
||||
return this.beanClass.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Bean的简单类名
|
||||
*
|
||||
* @return Bean的类名
|
||||
*/
|
||||
public String getSimpleName() {
|
||||
return this.beanClass.getSimpleName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段名-字段属性Map
|
||||
*
|
||||
* @param ignoreCase 是否忽略大小写,true为忽略,false不忽略
|
||||
* @return 字段名-字段属性Map
|
||||
*/
|
||||
public Map<String, PropDesc> getPropMap(boolean ignoreCase) {
|
||||
return ignoreCase ? new CaseInsensitiveMap<>(1, this.propMap) : this.propMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段属性列表
|
||||
*
|
||||
* @return {@link PropDesc} 列表
|
||||
*/
|
||||
public Collection<PropDesc> getProps() {
|
||||
return this.propMap.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取属性,如果不存在返回null
|
||||
*
|
||||
* @param fieldName 字段名
|
||||
* @return {@link PropDesc}
|
||||
*/
|
||||
public PropDesc getProp(String fieldName) {
|
||||
return this.propMap.get(fieldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得字段名对应的字段对象,如果不存在返回null
|
||||
*
|
||||
* @param fieldName 字段名
|
||||
* @return 字段值
|
||||
*/
|
||||
public Field getField(String fieldName) {
|
||||
final PropDesc desc = this.propMap.get(fieldName);
|
||||
return null == desc ? null : desc.getField();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Getter方法,如果不存在返回null
|
||||
*
|
||||
* @param fieldName 字段名
|
||||
* @return Getter方法
|
||||
*/
|
||||
public Method getGetter(String fieldName) {
|
||||
final PropDesc desc = this.propMap.get(fieldName);
|
||||
return null == desc ? null : desc.getGetter();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Setter方法,如果不存在返回null
|
||||
*
|
||||
* @param fieldName 字段名
|
||||
* @return Setter方法
|
||||
*/
|
||||
public Method getSetter(String fieldName) {
|
||||
final PropDesc desc = this.propMap.get(fieldName);
|
||||
return null == desc ? null : desc.getSetter();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------------ Private method start
|
||||
|
||||
/**
|
||||
* 初始化<br>
|
||||
* 只有与属性关联的相关Getter和Setter方法才会被读取,无关的getXXX和setXXX都被忽略
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
private BeanDesc init() {
|
||||
final Method[] methods = ReflectUtil.getMethods(this.beanClass);
|
||||
PropDesc prop;
|
||||
for (Field field : ReflectUtil.getFields(this.beanClass)) {
|
||||
if (false == ModifierUtil.isStatic(field)) {
|
||||
//只针对非static属性
|
||||
prop = createProp(field, methods);
|
||||
// 只有不存在时才放入,防止父类属性覆盖子类属性
|
||||
this.propMap.putIfAbsent(prop.getFieldName(), prop);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据字段创建属性描述<br>
|
||||
* 查找Getter和Setter方法时会:
|
||||
*
|
||||
* <pre>
|
||||
* 1. 忽略字段和方法名的大小写
|
||||
* 2. Getter查找getXXX、isXXX、getIsXXX
|
||||
* 3. Setter查找setXXX、setIsXXX
|
||||
* 4. Setter忽略参数值与字段值不匹配的情况,因此有多个参数类型的重载时,会调用首次匹配的
|
||||
* </pre>
|
||||
*
|
||||
* @param field 字段
|
||||
* @param methods 类中所有的方法
|
||||
* @return {@link PropDesc}
|
||||
* @since 4.0.2
|
||||
*/
|
||||
private PropDesc createProp(Field field, Method[] methods) {
|
||||
final PropDesc prop = findProp(field, methods, false);
|
||||
// 忽略大小写重新匹配一次
|
||||
if (null == prop.getter || null == prop.setter) {
|
||||
final PropDesc propIgnoreCase = findProp(field, methods, true);
|
||||
if (null == prop.getter) {
|
||||
prop.getter = propIgnoreCase.getter;
|
||||
}
|
||||
if (null == prop.setter) {
|
||||
prop.setter = propIgnoreCase.setter;
|
||||
}
|
||||
}
|
||||
|
||||
return prop;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找字段对应的Getter和Setter方法
|
||||
*
|
||||
* @param field 字段
|
||||
* @param methods 类中所有的方法
|
||||
* @param ignoreCase 是否忽略大小写匹配
|
||||
* @return PropDesc
|
||||
*/
|
||||
private PropDesc findProp(Field field, Method[] methods, boolean ignoreCase) {
|
||||
final String fieldName = field.getName();
|
||||
final Class<?> fieldType = field.getType();
|
||||
final boolean isBooleanField = BooleanUtil.isBoolean(fieldType);
|
||||
|
||||
Method getter = null;
|
||||
Method setter = null;
|
||||
String methodName;
|
||||
Class<?>[] parameterTypes;
|
||||
for (Method method : methods) {
|
||||
parameterTypes = method.getParameterTypes();
|
||||
if (parameterTypes.length > 1) {
|
||||
// 多于1个参数说明非Getter或Setter
|
||||
continue;
|
||||
}
|
||||
|
||||
methodName = method.getName();
|
||||
if (parameterTypes.length == 0) {
|
||||
// 无参数,可能为Getter方法
|
||||
if (isMatchGetter(methodName, fieldName, isBooleanField, ignoreCase)) {
|
||||
// 方法名与字段名匹配,则为Getter方法
|
||||
getter = method;
|
||||
}
|
||||
} else if (isMatchSetter(methodName, fieldName, isBooleanField, ignoreCase)) {
|
||||
// 只有一个参数的情况下方法名与字段名对应匹配,则为Setter方法
|
||||
setter = method;
|
||||
}
|
||||
if (null != getter && null != setter) {
|
||||
// 如果Getter和Setter方法都找到了,不再继续寻找
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new PropDesc(field, getter, setter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 方法是否为Getter方法<br>
|
||||
* 匹配规则如下(忽略大小写):
|
||||
*
|
||||
* <pre>
|
||||
* 字段名 -》 方法名
|
||||
* isName -》 isName
|
||||
* isName -》 isIsName
|
||||
* isName -》 getIsName
|
||||
* name -》 isName
|
||||
* name -》 getName
|
||||
* </pre>
|
||||
*
|
||||
* @param methodName 方法名
|
||||
* @param fieldName 字段名
|
||||
* @param isBooleanField 是否为Boolean类型字段
|
||||
* @param ignoreCase 匹配是否忽略大小写
|
||||
* @return 是否匹配
|
||||
*/
|
||||
private boolean isMatchGetter(String methodName, String fieldName, boolean isBooleanField, boolean ignoreCase) {
|
||||
// 全部转为小写,忽略大小写比较
|
||||
if (ignoreCase) {
|
||||
methodName = methodName.toLowerCase();
|
||||
fieldName = fieldName.toLowerCase();
|
||||
} else {
|
||||
fieldName = StrUtil.upperFirst(fieldName);
|
||||
}
|
||||
|
||||
if (false == methodName.startsWith("get") && false == methodName.startsWith("is")) {
|
||||
// 非标准Getter方法
|
||||
return false;
|
||||
}
|
||||
if ("getclass".equals(methodName)) {
|
||||
//跳过getClass方法
|
||||
return false;
|
||||
}
|
||||
|
||||
// 针对Boolean类型特殊检查
|
||||
if (isBooleanField) {
|
||||
if (fieldName.startsWith("is")) {
|
||||
// 字段已经是is开头
|
||||
if (methodName.equals(fieldName) // isName -》 isName
|
||||
|| methodName.equals("get" + fieldName)// isName -》 getIsName
|
||||
|| methodName.equals("is" + fieldName)// isName -》 isIsName
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
} else if (methodName.equals("is" + fieldName)) {
|
||||
// 字段非is开头, name -》 isName
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 包括boolean的任何类型只有一种匹配情况:name -》 getName
|
||||
return methodName.equals("get" + fieldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 方法是否为Setter方法<br>
|
||||
* 匹配规则如下(忽略大小写):
|
||||
*
|
||||
* <pre>
|
||||
* 字段名 -》 方法名
|
||||
* isName -》 setName
|
||||
* isName -》 setIsName
|
||||
* name -》 setName
|
||||
* </pre>
|
||||
*
|
||||
* @param methodName 方法名
|
||||
* @param fieldName 字段名
|
||||
* @param isBooleanField 是否为Boolean类型字段
|
||||
* @param ignoreCase 匹配是否忽略大小写
|
||||
* @return 是否匹配
|
||||
*/
|
||||
private boolean isMatchSetter(String methodName, String fieldName, boolean isBooleanField, boolean ignoreCase) {
|
||||
// 全部转为小写,忽略大小写比较
|
||||
methodName = methodName.toLowerCase();
|
||||
fieldName = fieldName.toLowerCase();
|
||||
|
||||
// 非标准Setter方法跳过
|
||||
if (false == methodName.startsWith("set")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 针对Boolean类型特殊检查
|
||||
if (isBooleanField && fieldName.startsWith("is")) {
|
||||
// 字段是is开头
|
||||
if (methodName.equals("set" + StrUtil.removePrefix(fieldName, "is"))// isName -》 setName
|
||||
|| methodName.equals("set" + fieldName)// isName -》 setIsName
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 包括boolean的任何类型只有一种匹配情况:name -》 setName
|
||||
return methodName.equals("set" + fieldName);
|
||||
}
|
||||
// ------------------------------------------------------------------------------------------------------ Private method end
|
||||
}
|
@ -1,27 +1,27 @@
|
||||
package cn.hutool.core.bean;
|
||||
|
||||
import cn.hutool.core.lang.SimpleCache;
|
||||
import cn.hutool.core.lang.func.Func0;
|
||||
|
||||
/**
|
||||
* Bean属性缓存<br>
|
||||
* 缓存用于防止多次反射造成的性能问题
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public enum BeanDescCache {
|
||||
INSTANCE;
|
||||
|
||||
private final SimpleCache<Class<?>, BeanDesc> bdCache = new SimpleCache<>();
|
||||
|
||||
/**
|
||||
* 获得属性名和{@link BeanDesc}Map映射
|
||||
* @param beanClass Bean的类
|
||||
* @param supplier 对象不存在时创建对象的函数
|
||||
* @return 属性名和{@link BeanDesc}映射
|
||||
* @since 5.4.2
|
||||
*/
|
||||
public BeanDesc getBeanDesc(Class<?> beanClass, Func0<BeanDesc> supplier){
|
||||
return bdCache.get(beanClass, supplier);
|
||||
}
|
||||
}
|
||||
package cn.hutool.core.bean;
|
||||
|
||||
import cn.hutool.core.lang.SimpleCache;
|
||||
import cn.hutool.core.lang.func.Func0;
|
||||
|
||||
/**
|
||||
* Bean属性缓存<br>
|
||||
* 缓存用于防止多次反射造成的性能问题
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public enum BeanDescCache {
|
||||
INSTANCE;
|
||||
|
||||
private final SimpleCache<Class<?>, BeanDesc> bdCache = new SimpleCache<>();
|
||||
|
||||
/**
|
||||
* 获得属性名和{@link BeanDesc}Map映射
|
||||
* @param beanClass Bean的类
|
||||
* @param supplier 对象不存在时创建对象的函数
|
||||
* @return 属性名和{@link BeanDesc}映射
|
||||
* @since 5.4.2
|
||||
*/
|
||||
public BeanDesc getBeanDesc(Class<?> beanClass, Func0<BeanDesc> supplier){
|
||||
return bdCache.get(beanClass, supplier);
|
||||
}
|
||||
}
|
||||
|
@ -1,32 +1,32 @@
|
||||
package cn.hutool.core.bean;
|
||||
|
||||
import cn.hutool.core.exceptions.ExceptionUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
* Bean异常
|
||||
* @author xiaoleilu
|
||||
*/
|
||||
public class BeanException extends RuntimeException{
|
||||
private static final long serialVersionUID = -8096998667745023423L;
|
||||
|
||||
public BeanException(Throwable e) {
|
||||
super(ExceptionUtil.getMessage(e), e);
|
||||
}
|
||||
|
||||
public BeanException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public BeanException(String messageTemplate, Object... params) {
|
||||
super(StrUtil.format(messageTemplate, params));
|
||||
}
|
||||
|
||||
public BeanException(String message, Throwable throwable) {
|
||||
super(message, throwable);
|
||||
}
|
||||
|
||||
public BeanException(Throwable throwable, String messageTemplate, Object... params) {
|
||||
super(StrUtil.format(messageTemplate, params), throwable);
|
||||
}
|
||||
}
|
||||
package cn.hutool.core.bean;
|
||||
|
||||
import cn.hutool.core.exceptions.ExceptionUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
* Bean异常
|
||||
* @author xiaoleilu
|
||||
*/
|
||||
public class BeanException extends RuntimeException{
|
||||
private static final long serialVersionUID = -8096998667745023423L;
|
||||
|
||||
public BeanException(Throwable e) {
|
||||
super(ExceptionUtil.getMessage(e), e);
|
||||
}
|
||||
|
||||
public BeanException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public BeanException(String messageTemplate, Object... params) {
|
||||
super(StrUtil.format(messageTemplate, params));
|
||||
}
|
||||
|
||||
public BeanException(String message, Throwable throwable) {
|
||||
super(message, throwable);
|
||||
}
|
||||
|
||||
public BeanException(Throwable throwable, String messageTemplate, Object... params) {
|
||||
super(StrUtil.format(messageTemplate, params), throwable);
|
||||
}
|
||||
}
|
||||
|
@ -1,69 +1,69 @@
|
||||
package cn.hutool.core.bean;
|
||||
|
||||
import cn.hutool.core.lang.SimpleCache;
|
||||
import cn.hutool.core.lang.func.Func0;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Bean属性缓存<br>
|
||||
* 缓存用于防止多次反射造成的性能问题
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public enum BeanInfoCache {
|
||||
INSTANCE;
|
||||
|
||||
private final SimpleCache<Class<?>, Map<String, PropertyDescriptor>> pdCache = new SimpleCache<>();
|
||||
private final SimpleCache<Class<?>, Map<String, PropertyDescriptor>> ignoreCasePdCache = new SimpleCache<>();
|
||||
|
||||
/**
|
||||
* 获得属性名和{@link PropertyDescriptor}Map映射
|
||||
*
|
||||
* @param beanClass Bean的类
|
||||
* @param ignoreCase 是否忽略大小写
|
||||
* @return 属性名和{@link PropertyDescriptor}Map映射
|
||||
*/
|
||||
public Map<String, PropertyDescriptor> getPropertyDescriptorMap(Class<?> beanClass, boolean ignoreCase) {
|
||||
return getCache(ignoreCase).get(beanClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得属性名和{@link PropertyDescriptor}Map映射
|
||||
*
|
||||
* @param beanClass Bean的类
|
||||
* @param ignoreCase 是否忽略大小写
|
||||
* @param supplier 缓存对象产生函数
|
||||
* @return 属性名和{@link PropertyDescriptor}Map映射
|
||||
* @since 5.4.1
|
||||
*/
|
||||
public Map<String, PropertyDescriptor> getPropertyDescriptorMap(
|
||||
Class<?> beanClass,
|
||||
boolean ignoreCase,
|
||||
Func0<Map<String, PropertyDescriptor>> supplier) {
|
||||
return getCache(ignoreCase).get(beanClass, supplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加入缓存
|
||||
*
|
||||
* @param beanClass Bean的类
|
||||
* @param fieldNamePropertyDescriptorMap 属性名和{@link PropertyDescriptor}Map映射
|
||||
* @param ignoreCase 是否忽略大小写
|
||||
*/
|
||||
public void putPropertyDescriptorMap(Class<?> beanClass, Map<String, PropertyDescriptor> fieldNamePropertyDescriptorMap, boolean ignoreCase) {
|
||||
getCache(ignoreCase).put(beanClass, fieldNamePropertyDescriptorMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据是否忽略字段名的大小写,返回不用Cache对象
|
||||
*
|
||||
* @param ignoreCase 是否忽略大小写
|
||||
* @return SimpleCache
|
||||
* @since 5.4.1
|
||||
*/
|
||||
private SimpleCache<Class<?>, Map<String, PropertyDescriptor>> getCache(boolean ignoreCase) {
|
||||
return ignoreCase ? ignoreCasePdCache : pdCache;
|
||||
}
|
||||
}
|
||||
package cn.hutool.core.bean;
|
||||
|
||||
import cn.hutool.core.lang.SimpleCache;
|
||||
import cn.hutool.core.lang.func.Func0;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Bean属性缓存<br>
|
||||
* 缓存用于防止多次反射造成的性能问题
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public enum BeanInfoCache {
|
||||
INSTANCE;
|
||||
|
||||
private final SimpleCache<Class<?>, Map<String, PropertyDescriptor>> pdCache = new SimpleCache<>();
|
||||
private final SimpleCache<Class<?>, Map<String, PropertyDescriptor>> ignoreCasePdCache = new SimpleCache<>();
|
||||
|
||||
/**
|
||||
* 获得属性名和{@link PropertyDescriptor}Map映射
|
||||
*
|
||||
* @param beanClass Bean的类
|
||||
* @param ignoreCase 是否忽略大小写
|
||||
* @return 属性名和{@link PropertyDescriptor}Map映射
|
||||
*/
|
||||
public Map<String, PropertyDescriptor> getPropertyDescriptorMap(Class<?> beanClass, boolean ignoreCase) {
|
||||
return getCache(ignoreCase).get(beanClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得属性名和{@link PropertyDescriptor}Map映射
|
||||
*
|
||||
* @param beanClass Bean的类
|
||||
* @param ignoreCase 是否忽略大小写
|
||||
* @param supplier 缓存对象产生函数
|
||||
* @return 属性名和{@link PropertyDescriptor}Map映射
|
||||
* @since 5.4.1
|
||||
*/
|
||||
public Map<String, PropertyDescriptor> getPropertyDescriptorMap(
|
||||
Class<?> beanClass,
|
||||
boolean ignoreCase,
|
||||
Func0<Map<String, PropertyDescriptor>> supplier) {
|
||||
return getCache(ignoreCase).get(beanClass, supplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加入缓存
|
||||
*
|
||||
* @param beanClass Bean的类
|
||||
* @param fieldNamePropertyDescriptorMap 属性名和{@link PropertyDescriptor}Map映射
|
||||
* @param ignoreCase 是否忽略大小写
|
||||
*/
|
||||
public void putPropertyDescriptorMap(Class<?> beanClass, Map<String, PropertyDescriptor> fieldNamePropertyDescriptorMap, boolean ignoreCase) {
|
||||
getCache(ignoreCase).put(beanClass, fieldNamePropertyDescriptorMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据是否忽略字段名的大小写,返回不用Cache对象
|
||||
*
|
||||
* @param ignoreCase 是否忽略大小写
|
||||
* @return SimpleCache
|
||||
* @since 5.4.1
|
||||
*/
|
||||
private SimpleCache<Class<?>, Map<String, PropertyDescriptor>> getCache(boolean ignoreCase) {
|
||||
return ignoreCase ? ignoreCasePdCache : pdCache;
|
||||
}
|
||||
}
|
||||
|
@ -1,291 +1,291 @@
|
||||
package cn.hutool.core.bean;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.text.StrBuilder;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.CharUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Bean路径表达式,用于获取多层嵌套Bean中的字段值或Bean对象<br>
|
||||
* 根据给定的表达式,查找Bean中对应的属性值对象。 表达式分为两种:
|
||||
* <ol>
|
||||
* <li>.表达式,可以获取Bean对象中的属性(字段)值或者Map中key对应的值</li>
|
||||
* <li>[]表达式,可以获取集合等对象中对应index的值</li>
|
||||
* </ol>
|
||||
*
|
||||
* 表达式栗子:
|
||||
*
|
||||
* <pre>
|
||||
* persion
|
||||
* persion.name
|
||||
* persons[3]
|
||||
* person.friends[5].name
|
||||
* ['person']['friends'][5]['name']
|
||||
* </pre>
|
||||
*
|
||||
* @author Looly
|
||||
* @since 4.0.6
|
||||
*/
|
||||
public class BeanPath implements Serializable{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 表达式边界符号数组 */
|
||||
private static final char[] EXP_CHARS = { CharUtil.DOT, CharUtil.BRACKET_START, CharUtil.BRACKET_END };
|
||||
|
||||
private boolean isStartWith = false;
|
||||
protected List<String> patternParts;
|
||||
|
||||
/**
|
||||
* 解析Bean路径表达式为Bean模式<br>
|
||||
* Bean表达式,用于获取多层嵌套Bean中的字段值或Bean对象<br>
|
||||
* 根据给定的表达式,查找Bean中对应的属性值对象。 表达式分为两种:
|
||||
* <ol>
|
||||
* <li>.表达式,可以获取Bean对象中的属性(字段)值或者Map中key对应的值</li>
|
||||
* <li>[]表达式,可以获取集合等对象中对应index的值</li>
|
||||
* </ol>
|
||||
*
|
||||
* 表达式栗子:
|
||||
*
|
||||
* <pre>
|
||||
* persion
|
||||
* persion.name
|
||||
* persons[3]
|
||||
* person.friends[5].name
|
||||
* ['person']['friends'][5]['name']
|
||||
* </pre>
|
||||
*
|
||||
* @param expression 表达式
|
||||
* @return BeanPath
|
||||
*/
|
||||
public static BeanPath create(String expression) {
|
||||
return new BeanPath(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param expression 表达式
|
||||
*/
|
||||
public BeanPath(String expression) {
|
||||
init(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Bean中对应表达式的值
|
||||
*
|
||||
* @param bean Bean对象或Map或List等
|
||||
* @return 值,如果对应值不存在,则返回null
|
||||
*/
|
||||
public Object get(Object bean) {
|
||||
return get(this.patternParts, bean, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表达式指定位置(或filed对应)的值<br>
|
||||
* 若表达式指向一个List则设置其坐标对应位置的值,若指向Map则put对应key的值,Bean则设置字段的值<br>
|
||||
* 注意:
|
||||
*
|
||||
* <pre>
|
||||
* 1. 如果为List,如果下标不大于List长度,则替换原有值,否则追加值
|
||||
* 2. 如果为数组,如果下标不大于数组长度,则替换原有值,否则追加值
|
||||
* </pre>
|
||||
*
|
||||
* @param bean Bean、Map或List
|
||||
* @param value 值
|
||||
*/
|
||||
public void set(Object bean, Object value) {
|
||||
set(bean, this.patternParts, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表达式指定位置(或filed对应)的值<br>
|
||||
* 若表达式指向一个List则设置其坐标对应位置的值,若指向Map则put对应key的值,Bean则设置字段的值<br>
|
||||
* 注意:
|
||||
*
|
||||
* <pre>
|
||||
* 1. 如果为List,如果下标不大于List长度,则替换原有值,否则追加值
|
||||
* 2. 如果为数组,如果下标不大于数组长度,则替换原有值,否则追加值
|
||||
* </pre>
|
||||
*
|
||||
* @param bean Bean、Map或List
|
||||
* @param patternParts 表达式块列表
|
||||
* @param value 值
|
||||
*/
|
||||
private void set(Object bean, List<String> patternParts, Object value) {
|
||||
Object subBean = get(patternParts, bean, true);
|
||||
if(null == subBean) {
|
||||
set(bean, patternParts.subList(0, patternParts.size() - 1), new HashMap<>());
|
||||
//set中有可能做过转换,因此此处重新获取bean
|
||||
subBean = get(patternParts, bean, true);
|
||||
}
|
||||
BeanUtil.setFieldValue(subBean, patternParts.get(patternParts.size() - 1), value);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------------------------------------------- Private method start
|
||||
/**
|
||||
* 获取Bean中对应表达式的值
|
||||
*
|
||||
* @param patternParts 表达式分段列表
|
||||
* @param bean Bean对象或Map或List等
|
||||
* @param ignoreLast 是否忽略最后一个值,忽略最后一个值则用于set,否则用于read
|
||||
* @return 值,如果对应值不存在,则返回null
|
||||
*/
|
||||
private Object get(List<String> patternParts, Object bean, boolean ignoreLast) {
|
||||
int length = patternParts.size();
|
||||
if (ignoreLast) {
|
||||
length--;
|
||||
}
|
||||
Object subBean = bean;
|
||||
boolean isFirst = true;
|
||||
String patternPart;
|
||||
for (int i = 0; i < length; i++) {
|
||||
patternPart = patternParts.get(i);
|
||||
subBean = getFieldValue(subBean, patternPart);
|
||||
if (null == subBean) {
|
||||
// 支持表达式的第一个对象为Bean本身(若用户定义表达式$开头,则不做此操作)
|
||||
if (isFirst && false == this.isStartWith && BeanUtil.isMatchName(bean, patternPart, true)) {
|
||||
subBean = bean;
|
||||
isFirst = false;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return subBean;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Object getFieldValue(Object bean, String expression) {
|
||||
if (StrUtil.isBlank(expression)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (StrUtil.contains(expression, ':')) {
|
||||
// [start:end:step] 模式
|
||||
final List<String> parts = StrUtil.splitTrim(expression, ':');
|
||||
int start = Integer.parseInt(parts.get(0));
|
||||
int end = Integer.parseInt(parts.get(1));
|
||||
int step = 1;
|
||||
if (3 == parts.size()) {
|
||||
step = Integer.parseInt(parts.get(2));
|
||||
}
|
||||
if (bean instanceof Collection) {
|
||||
return CollUtil.sub((Collection<?>) bean, start, end, step);
|
||||
} else if (ArrayUtil.isArray(bean)) {
|
||||
return ArrayUtil.sub(bean, start, end, step);
|
||||
}
|
||||
} else if (StrUtil.contains(expression, ',')) {
|
||||
// [num0,num1,num2...]模式或者['key0','key1']模式
|
||||
final List<String> keys = StrUtil.splitTrim(expression, ',');
|
||||
if (bean instanceof Collection) {
|
||||
return CollUtil.getAny((Collection<?>) bean, Convert.convert(int[].class, keys));
|
||||
} else if (ArrayUtil.isArray(bean)) {
|
||||
return ArrayUtil.getAny(bean, Convert.convert(int[].class, keys));
|
||||
} else {
|
||||
final String[] unWrappedKeys = new String[keys.size()];
|
||||
for (int i = 0; i < unWrappedKeys.length; i++) {
|
||||
unWrappedKeys[i] = StrUtil.unWrap(keys.get(i), '\'');
|
||||
}
|
||||
if (bean instanceof Map) {
|
||||
// 只支持String为key的Map
|
||||
return MapUtil.getAny((Map<String, ?>) bean, unWrappedKeys);
|
||||
} else {
|
||||
final Map<String, Object> map = BeanUtil.beanToMap(bean);
|
||||
return MapUtil.getAny(map, unWrappedKeys);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 数字或普通字符串
|
||||
return BeanUtil.getFieldValue(bean, expression);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*
|
||||
* @param expression 表达式
|
||||
*/
|
||||
private void init(String expression) {
|
||||
List<String> localPatternParts = new ArrayList<>();
|
||||
int length = expression.length();
|
||||
|
||||
final StrBuilder builder = StrUtil.strBuilder();
|
||||
char c;
|
||||
boolean isNumStart = false;// 下标标识符开始
|
||||
for (int i = 0; i < length; i++) {
|
||||
c = expression.charAt(i);
|
||||
if (0 == i && '$' == c) {
|
||||
// 忽略开头的$符,表示当前对象
|
||||
isStartWith = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ArrayUtil.contains(EXP_CHARS, c)) {
|
||||
// 处理边界符号
|
||||
if (CharUtil.BRACKET_END == c) {
|
||||
// 中括号(数字下标)结束
|
||||
if (false == isNumStart) {
|
||||
throw new IllegalArgumentException(StrUtil.format("Bad expression '{}':{}, we find ']' but no '[' !", expression, i));
|
||||
}
|
||||
isNumStart = false;
|
||||
// 中括号结束加入下标
|
||||
} else {
|
||||
if (isNumStart) {
|
||||
// 非结束中括号情况下发现起始中括号报错(中括号未关闭)
|
||||
throw new IllegalArgumentException(StrUtil.format("Bad expression '{}':{}, we find '[' but no ']' !", expression, i));
|
||||
} else if (CharUtil.BRACKET_START == c) {
|
||||
// 数字下标开始
|
||||
isNumStart = true;
|
||||
}
|
||||
// 每一个边界符之前的表达式是一个完整的KEY,开始处理KEY
|
||||
}
|
||||
if (builder.length() > 0) {
|
||||
localPatternParts.add(unWrapIfPossible(builder));
|
||||
}
|
||||
builder.reset();
|
||||
} else {
|
||||
// 非边界符号,追加字符
|
||||
builder.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
// 末尾边界符检查
|
||||
if (isNumStart) {
|
||||
throw new IllegalArgumentException(StrUtil.format("Bad expression '{}':{}, we find '[' but no ']' !", expression, length - 1));
|
||||
} else {
|
||||
if (builder.length() > 0) {
|
||||
localPatternParts.add(unWrapIfPossible(builder));
|
||||
}
|
||||
}
|
||||
|
||||
// 不可变List
|
||||
this.patternParts = Collections.unmodifiableList(localPatternParts);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对于非表达式去除单引号
|
||||
*
|
||||
* @param expression 表达式
|
||||
* @return 表达式
|
||||
*/
|
||||
private static String unWrapIfPossible(CharSequence expression) {
|
||||
if (StrUtil.containsAny(expression, " = ", " > ", " < ", " like ", ",")) {
|
||||
return expression.toString();
|
||||
}
|
||||
return StrUtil.unWrap(expression, '\'');
|
||||
}
|
||||
// ------------------------------------------------------------------------------------------------------------------------------------- Private method end
|
||||
}
|
||||
package cn.hutool.core.bean;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.text.StrBuilder;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.CharUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Bean路径表达式,用于获取多层嵌套Bean中的字段值或Bean对象<br>
|
||||
* 根据给定的表达式,查找Bean中对应的属性值对象。 表达式分为两种:
|
||||
* <ol>
|
||||
* <li>.表达式,可以获取Bean对象中的属性(字段)值或者Map中key对应的值</li>
|
||||
* <li>[]表达式,可以获取集合等对象中对应index的值</li>
|
||||
* </ol>
|
||||
*
|
||||
* 表达式栗子:
|
||||
*
|
||||
* <pre>
|
||||
* persion
|
||||
* persion.name
|
||||
* persons[3]
|
||||
* person.friends[5].name
|
||||
* ['person']['friends'][5]['name']
|
||||
* </pre>
|
||||
*
|
||||
* @author Looly
|
||||
* @since 4.0.6
|
||||
*/
|
||||
public class BeanPath implements Serializable{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 表达式边界符号数组 */
|
||||
private static final char[] EXP_CHARS = { CharUtil.DOT, CharUtil.BRACKET_START, CharUtil.BRACKET_END };
|
||||
|
||||
private boolean isStartWith = false;
|
||||
protected List<String> patternParts;
|
||||
|
||||
/**
|
||||
* 解析Bean路径表达式为Bean模式<br>
|
||||
* Bean表达式,用于获取多层嵌套Bean中的字段值或Bean对象<br>
|
||||
* 根据给定的表达式,查找Bean中对应的属性值对象。 表达式分为两种:
|
||||
* <ol>
|
||||
* <li>.表达式,可以获取Bean对象中的属性(字段)值或者Map中key对应的值</li>
|
||||
* <li>[]表达式,可以获取集合等对象中对应index的值</li>
|
||||
* </ol>
|
||||
*
|
||||
* 表达式栗子:
|
||||
*
|
||||
* <pre>
|
||||
* persion
|
||||
* persion.name
|
||||
* persons[3]
|
||||
* person.friends[5].name
|
||||
* ['person']['friends'][5]['name']
|
||||
* </pre>
|
||||
*
|
||||
* @param expression 表达式
|
||||
* @return BeanPath
|
||||
*/
|
||||
public static BeanPath create(String expression) {
|
||||
return new BeanPath(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param expression 表达式
|
||||
*/
|
||||
public BeanPath(String expression) {
|
||||
init(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Bean中对应表达式的值
|
||||
*
|
||||
* @param bean Bean对象或Map或List等
|
||||
* @return 值,如果对应值不存在,则返回null
|
||||
*/
|
||||
public Object get(Object bean) {
|
||||
return get(this.patternParts, bean, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表达式指定位置(或filed对应)的值<br>
|
||||
* 若表达式指向一个List则设置其坐标对应位置的值,若指向Map则put对应key的值,Bean则设置字段的值<br>
|
||||
* 注意:
|
||||
*
|
||||
* <pre>
|
||||
* 1. 如果为List,如果下标不大于List长度,则替换原有值,否则追加值
|
||||
* 2. 如果为数组,如果下标不大于数组长度,则替换原有值,否则追加值
|
||||
* </pre>
|
||||
*
|
||||
* @param bean Bean、Map或List
|
||||
* @param value 值
|
||||
*/
|
||||
public void set(Object bean, Object value) {
|
||||
set(bean, this.patternParts, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表达式指定位置(或filed对应)的值<br>
|
||||
* 若表达式指向一个List则设置其坐标对应位置的值,若指向Map则put对应key的值,Bean则设置字段的值<br>
|
||||
* 注意:
|
||||
*
|
||||
* <pre>
|
||||
* 1. 如果为List,如果下标不大于List长度,则替换原有值,否则追加值
|
||||
* 2. 如果为数组,如果下标不大于数组长度,则替换原有值,否则追加值
|
||||
* </pre>
|
||||
*
|
||||
* @param bean Bean、Map或List
|
||||
* @param patternParts 表达式块列表
|
||||
* @param value 值
|
||||
*/
|
||||
private void set(Object bean, List<String> patternParts, Object value) {
|
||||
Object subBean = get(patternParts, bean, true);
|
||||
if(null == subBean) {
|
||||
set(bean, patternParts.subList(0, patternParts.size() - 1), new HashMap<>());
|
||||
//set中有可能做过转换,因此此处重新获取bean
|
||||
subBean = get(patternParts, bean, true);
|
||||
}
|
||||
BeanUtil.setFieldValue(subBean, patternParts.get(patternParts.size() - 1), value);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------------------------------------------- Private method start
|
||||
/**
|
||||
* 获取Bean中对应表达式的值
|
||||
*
|
||||
* @param patternParts 表达式分段列表
|
||||
* @param bean Bean对象或Map或List等
|
||||
* @param ignoreLast 是否忽略最后一个值,忽略最后一个值则用于set,否则用于read
|
||||
* @return 值,如果对应值不存在,则返回null
|
||||
*/
|
||||
private Object get(List<String> patternParts, Object bean, boolean ignoreLast) {
|
||||
int length = patternParts.size();
|
||||
if (ignoreLast) {
|
||||
length--;
|
||||
}
|
||||
Object subBean = bean;
|
||||
boolean isFirst = true;
|
||||
String patternPart;
|
||||
for (int i = 0; i < length; i++) {
|
||||
patternPart = patternParts.get(i);
|
||||
subBean = getFieldValue(subBean, patternPart);
|
||||
if (null == subBean) {
|
||||
// 支持表达式的第一个对象为Bean本身(若用户定义表达式$开头,则不做此操作)
|
||||
if (isFirst && false == this.isStartWith && BeanUtil.isMatchName(bean, patternPart, true)) {
|
||||
subBean = bean;
|
||||
isFirst = false;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return subBean;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Object getFieldValue(Object bean, String expression) {
|
||||
if (StrUtil.isBlank(expression)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (StrUtil.contains(expression, ':')) {
|
||||
// [start:end:step] 模式
|
||||
final List<String> parts = StrUtil.splitTrim(expression, ':');
|
||||
int start = Integer.parseInt(parts.get(0));
|
||||
int end = Integer.parseInt(parts.get(1));
|
||||
int step = 1;
|
||||
if (3 == parts.size()) {
|
||||
step = Integer.parseInt(parts.get(2));
|
||||
}
|
||||
if (bean instanceof Collection) {
|
||||
return CollUtil.sub((Collection<?>) bean, start, end, step);
|
||||
} else if (ArrayUtil.isArray(bean)) {
|
||||
return ArrayUtil.sub(bean, start, end, step);
|
||||
}
|
||||
} else if (StrUtil.contains(expression, ',')) {
|
||||
// [num0,num1,num2...]模式或者['key0','key1']模式
|
||||
final List<String> keys = StrUtil.splitTrim(expression, ',');
|
||||
if (bean instanceof Collection) {
|
||||
return CollUtil.getAny((Collection<?>) bean, Convert.convert(int[].class, keys));
|
||||
} else if (ArrayUtil.isArray(bean)) {
|
||||
return ArrayUtil.getAny(bean, Convert.convert(int[].class, keys));
|
||||
} else {
|
||||
final String[] unWrappedKeys = new String[keys.size()];
|
||||
for (int i = 0; i < unWrappedKeys.length; i++) {
|
||||
unWrappedKeys[i] = StrUtil.unWrap(keys.get(i), '\'');
|
||||
}
|
||||
if (bean instanceof Map) {
|
||||
// 只支持String为key的Map
|
||||
return MapUtil.getAny((Map<String, ?>) bean, unWrappedKeys);
|
||||
} else {
|
||||
final Map<String, Object> map = BeanUtil.beanToMap(bean);
|
||||
return MapUtil.getAny(map, unWrappedKeys);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 数字或普通字符串
|
||||
return BeanUtil.getFieldValue(bean, expression);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*
|
||||
* @param expression 表达式
|
||||
*/
|
||||
private void init(String expression) {
|
||||
List<String> localPatternParts = new ArrayList<>();
|
||||
int length = expression.length();
|
||||
|
||||
final StrBuilder builder = StrUtil.strBuilder();
|
||||
char c;
|
||||
boolean isNumStart = false;// 下标标识符开始
|
||||
for (int i = 0; i < length; i++) {
|
||||
c = expression.charAt(i);
|
||||
if (0 == i && '$' == c) {
|
||||
// 忽略开头的$符,表示当前对象
|
||||
isStartWith = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ArrayUtil.contains(EXP_CHARS, c)) {
|
||||
// 处理边界符号
|
||||
if (CharUtil.BRACKET_END == c) {
|
||||
// 中括号(数字下标)结束
|
||||
if (false == isNumStart) {
|
||||
throw new IllegalArgumentException(StrUtil.format("Bad expression '{}':{}, we find ']' but no '[' !", expression, i));
|
||||
}
|
||||
isNumStart = false;
|
||||
// 中括号结束加入下标
|
||||
} else {
|
||||
if (isNumStart) {
|
||||
// 非结束中括号情况下发现起始中括号报错(中括号未关闭)
|
||||
throw new IllegalArgumentException(StrUtil.format("Bad expression '{}':{}, we find '[' but no ']' !", expression, i));
|
||||
} else if (CharUtil.BRACKET_START == c) {
|
||||
// 数字下标开始
|
||||
isNumStart = true;
|
||||
}
|
||||
// 每一个边界符之前的表达式是一个完整的KEY,开始处理KEY
|
||||
}
|
||||
if (builder.length() > 0) {
|
||||
localPatternParts.add(unWrapIfPossible(builder));
|
||||
}
|
||||
builder.reset();
|
||||
} else {
|
||||
// 非边界符号,追加字符
|
||||
builder.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
// 末尾边界符检查
|
||||
if (isNumStart) {
|
||||
throw new IllegalArgumentException(StrUtil.format("Bad expression '{}':{}, we find '[' but no ']' !", expression, length - 1));
|
||||
} else {
|
||||
if (builder.length() > 0) {
|
||||
localPatternParts.add(unWrapIfPossible(builder));
|
||||
}
|
||||
}
|
||||
|
||||
// 不可变List
|
||||
this.patternParts = Collections.unmodifiableList(localPatternParts);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对于非表达式去除单引号
|
||||
*
|
||||
* @param expression 表达式
|
||||
* @return 表达式
|
||||
*/
|
||||
private static String unWrapIfPossible(CharSequence expression) {
|
||||
if (StrUtil.containsAny(expression, " = ", " > ", " < ", " like ", ",")) {
|
||||
return expression.toString();
|
||||
}
|
||||
return StrUtil.unWrap(expression, '\'');
|
||||
}
|
||||
// ------------------------------------------------------------------------------------------------------------------------------------- Private method end
|
||||
}
|
||||
|
@ -1,222 +1,222 @@
|
||||
package cn.hutool.core.bean;
|
||||
|
||||
import cn.hutool.core.clone.CloneSupport;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 动态Bean,通过反射对Bean的相关方法做操作<br>
|
||||
* 支持Map和普通Bean
|
||||
*
|
||||
* @author Looly
|
||||
* @since 3.0.7
|
||||
*/
|
||||
public class DynaBean extends CloneSupport<DynaBean> implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Class<?> beanClass;
|
||||
private final Object bean;
|
||||
|
||||
/**
|
||||
* 创建一个DynaBean
|
||||
*
|
||||
* @param bean 普通Bean
|
||||
* @return DynaBean
|
||||
*/
|
||||
public static DynaBean create(Object bean) {
|
||||
return new DynaBean(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个DynaBean
|
||||
*
|
||||
* @param beanClass Bean类
|
||||
* @return DynaBean
|
||||
*/
|
||||
public static DynaBean create(Class<?> beanClass) {
|
||||
return new DynaBean(beanClass);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建一个DynaBean
|
||||
*
|
||||
* @param beanClass Bean类
|
||||
* @param params 构造Bean所需要的参数
|
||||
* @return DynaBean
|
||||
*/
|
||||
public static DynaBean create(Class<?> beanClass, Object... params) {
|
||||
return new DynaBean(beanClass, params);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------ Constructor start
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param beanClass Bean类
|
||||
* @param params 构造Bean所需要的参数
|
||||
*/
|
||||
public DynaBean(Class<?> beanClass, Object... params) {
|
||||
this(ReflectUtil.newInstance(beanClass, params));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param beanClass Bean类
|
||||
*/
|
||||
public DynaBean(Class<?> beanClass) {
|
||||
this(ReflectUtil.newInstance(beanClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param bean 原始Bean
|
||||
*/
|
||||
public DynaBean(Object bean) {
|
||||
Assert.notNull(bean);
|
||||
if (bean instanceof DynaBean) {
|
||||
bean = ((DynaBean) bean).getBean();
|
||||
}
|
||||
this.bean = bean;
|
||||
this.beanClass = ClassUtil.getClass(bean);
|
||||
}
|
||||
//------------------------------------------------------------------------ Constructor end
|
||||
|
||||
/**
|
||||
* 获得字段对应值
|
||||
*
|
||||
* @param <T> 属性值类型
|
||||
* @param fieldName 字段名
|
||||
* @return 字段值
|
||||
* @throws BeanException 反射获取属性值或字段值导致的异常
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T get(String fieldName) throws BeanException {
|
||||
if (Map.class.isAssignableFrom(beanClass)) {
|
||||
return (T) ((Map<?, ?>) bean).get(fieldName);
|
||||
} else {
|
||||
final PropDesc prop = BeanUtil.getBeanDesc(beanClass).getProp(fieldName);
|
||||
if (null == prop) {
|
||||
throw new BeanException("No public field or get method for {}", fieldName);
|
||||
}
|
||||
return (T) prop.getValue(bean);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有指定名称的bean属性
|
||||
*
|
||||
* @param fieldName 字段名
|
||||
* @return 是否有bean属性
|
||||
* @since 5.4.2
|
||||
*/
|
||||
public boolean containsProp(String fieldName) {
|
||||
return null != BeanUtil.getBeanDesc(beanClass).getProp(fieldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得字段对应值,获取异常返回{@code null}
|
||||
*
|
||||
* @param <T> 属性值类型
|
||||
* @param fieldName 字段名
|
||||
* @return 字段值
|
||||
* @since 3.1.1
|
||||
*/
|
||||
public <T> T safeGet(String fieldName) {
|
||||
try {
|
||||
return get(fieldName);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置字段值
|
||||
*
|
||||
* @param fieldName 字段名
|
||||
* @param value 字段值
|
||||
* @throws BeanException 反射获取属性值或字段值导致的异常
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public void set(String fieldName, Object value) throws BeanException {
|
||||
if (Map.class.isAssignableFrom(beanClass)) {
|
||||
((Map) bean).put(fieldName, value);
|
||||
} else {
|
||||
final PropDesc prop = BeanUtil.getBeanDesc(beanClass).getProp(fieldName);
|
||||
if (null == prop) {
|
||||
throw new BeanException("No public field or set method for {}", fieldName);
|
||||
}
|
||||
prop.setValue(bean, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行原始Bean中的方法
|
||||
*
|
||||
* @param methodName 方法名
|
||||
* @param params 参数
|
||||
* @return 执行结果,可能为null
|
||||
*/
|
||||
public Object invoke(String methodName, Object... params) {
|
||||
return ReflectUtil.invoke(this.bean, methodName, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得原始Bean
|
||||
*
|
||||
* @param <T> Bean类型
|
||||
* @return bean
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getBean() {
|
||||
return (T) this.bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得Bean的类型
|
||||
*
|
||||
* @param <T> Bean类型
|
||||
* @return Bean类型
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Class<T> getBeanClass() {
|
||||
return (Class<T>) this.beanClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((bean == null) ? 0 : bean.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final DynaBean other = (DynaBean) obj;
|
||||
if (bean == null) {
|
||||
return other.bean == null;
|
||||
} else return bean.equals(other.bean);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.bean.toString();
|
||||
}
|
||||
}
|
||||
package cn.hutool.core.bean;
|
||||
|
||||
import cn.hutool.core.clone.CloneSupport;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 动态Bean,通过反射对Bean的相关方法做操作<br>
|
||||
* 支持Map和普通Bean
|
||||
*
|
||||
* @author Looly
|
||||
* @since 3.0.7
|
||||
*/
|
||||
public class DynaBean extends CloneSupport<DynaBean> implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Class<?> beanClass;
|
||||
private final Object bean;
|
||||
|
||||
/**
|
||||
* 创建一个DynaBean
|
||||
*
|
||||
* @param bean 普通Bean
|
||||
* @return DynaBean
|
||||
*/
|
||||
public static DynaBean create(Object bean) {
|
||||
return new DynaBean(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个DynaBean
|
||||
*
|
||||
* @param beanClass Bean类
|
||||
* @return DynaBean
|
||||
*/
|
||||
public static DynaBean create(Class<?> beanClass) {
|
||||
return new DynaBean(beanClass);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建一个DynaBean
|
||||
*
|
||||
* @param beanClass Bean类
|
||||
* @param params 构造Bean所需要的参数
|
||||
* @return DynaBean
|
||||
*/
|
||||
public static DynaBean create(Class<?> beanClass, Object... params) {
|
||||
return new DynaBean(beanClass, params);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------ Constructor start
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param beanClass Bean类
|
||||
* @param params 构造Bean所需要的参数
|
||||
*/
|
||||
public DynaBean(Class<?> beanClass, Object... params) {
|
||||
this(ReflectUtil.newInstance(beanClass, params));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param beanClass Bean类
|
||||
*/
|
||||
public DynaBean(Class<?> beanClass) {
|
||||
this(ReflectUtil.newInstance(beanClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param bean 原始Bean
|
||||
*/
|
||||
public DynaBean(Object bean) {
|
||||
Assert.notNull(bean);
|
||||
if (bean instanceof DynaBean) {
|
||||
bean = ((DynaBean) bean).getBean();
|
||||
}
|
||||
this.bean = bean;
|
||||
this.beanClass = ClassUtil.getClass(bean);
|
||||
}
|
||||
//------------------------------------------------------------------------ Constructor end
|
||||
|
||||
/**
|
||||
* 获得字段对应值
|
||||
*
|
||||
* @param <T> 属性值类型
|
||||
* @param fieldName 字段名
|
||||
* @return 字段值
|
||||
* @throws BeanException 反射获取属性值或字段值导致的异常
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T get(String fieldName) throws BeanException {
|
||||
if (Map.class.isAssignableFrom(beanClass)) {
|
||||
return (T) ((Map<?, ?>) bean).get(fieldName);
|
||||
} else {
|
||||
final PropDesc prop = BeanUtil.getBeanDesc(beanClass).getProp(fieldName);
|
||||
if (null == prop) {
|
||||
throw new BeanException("No public field or get method for {}", fieldName);
|
||||
}
|
||||
return (T) prop.getValue(bean);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有指定名称的bean属性
|
||||
*
|
||||
* @param fieldName 字段名
|
||||
* @return 是否有bean属性
|
||||
* @since 5.4.2
|
||||
*/
|
||||
public boolean containsProp(String fieldName) {
|
||||
return null != BeanUtil.getBeanDesc(beanClass).getProp(fieldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得字段对应值,获取异常返回{@code null}
|
||||
*
|
||||
* @param <T> 属性值类型
|
||||
* @param fieldName 字段名
|
||||
* @return 字段值
|
||||
* @since 3.1.1
|
||||
*/
|
||||
public <T> T safeGet(String fieldName) {
|
||||
try {
|
||||
return get(fieldName);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置字段值
|
||||
*
|
||||
* @param fieldName 字段名
|
||||
* @param value 字段值
|
||||
* @throws BeanException 反射获取属性值或字段值导致的异常
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public void set(String fieldName, Object value) throws BeanException {
|
||||
if (Map.class.isAssignableFrom(beanClass)) {
|
||||
((Map) bean).put(fieldName, value);
|
||||
} else {
|
||||
final PropDesc prop = BeanUtil.getBeanDesc(beanClass).getProp(fieldName);
|
||||
if (null == prop) {
|
||||
throw new BeanException("No public field or set method for {}", fieldName);
|
||||
}
|
||||
prop.setValue(bean, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行原始Bean中的方法
|
||||
*
|
||||
* @param methodName 方法名
|
||||
* @param params 参数
|
||||
* @return 执行结果,可能为null
|
||||
*/
|
||||
public Object invoke(String methodName, Object... params) {
|
||||
return ReflectUtil.invoke(this.bean, methodName, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得原始Bean
|
||||
*
|
||||
* @param <T> Bean类型
|
||||
* @return bean
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getBean() {
|
||||
return (T) this.bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得Bean的类型
|
||||
*
|
||||
* @param <T> Bean类型
|
||||
* @return Bean类型
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Class<T> getBeanClass() {
|
||||
return (Class<T>) this.beanClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((bean == null) ? 0 : bean.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final DynaBean other = (DynaBean) obj;
|
||||
if (bean == null) {
|
||||
return other.bean == null;
|
||||
} else return bean.equals(other.bean);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.bean.toString();
|
||||
}
|
||||
}
|
||||
|
@ -1,382 +1,382 @@
|
||||
package cn.hutool.core.bean;
|
||||
|
||||
import cn.hutool.core.annotation.AnnotationUtil;
|
||||
import cn.hutool.core.annotation.PropIgnore;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
import cn.hutool.core.util.ModifierUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.TypeUtil;
|
||||
|
||||
import java.beans.Transient;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
/**
|
||||
* 属性描述,包括了字段、getter、setter和相应的方法执行
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
public class PropDesc {
|
||||
|
||||
/**
|
||||
* 字段
|
||||
*/
|
||||
final Field field;
|
||||
/**
|
||||
* Getter方法
|
||||
*/
|
||||
protected Method getter;
|
||||
/**
|
||||
* Setter方法
|
||||
*/
|
||||
protected Method setter;
|
||||
|
||||
/**
|
||||
* 构造<br>
|
||||
* Getter和Setter方法设置为默认可访问
|
||||
*
|
||||
* @param field 字段
|
||||
* @param getter get方法
|
||||
* @param setter set方法
|
||||
*/
|
||||
public PropDesc(Field field, Method getter, Method setter) {
|
||||
this.field = field;
|
||||
this.getter = ClassUtil.setAccessible(getter);
|
||||
this.setter = ClassUtil.setAccessible(setter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段名,如果存在Alias注解,读取注解的值作为名称
|
||||
*
|
||||
* @return 字段名
|
||||
*/
|
||||
public String getFieldName() {
|
||||
return ReflectUtil.getFieldName(this.field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段名称
|
||||
*
|
||||
* @return 字段名
|
||||
* @since 5.1.6
|
||||
*/
|
||||
public String getRawFieldName() {
|
||||
return null == this.field ? null : this.field.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段
|
||||
*
|
||||
* @return 字段
|
||||
*/
|
||||
public Field getField() {
|
||||
return this.field;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得字段类型<br>
|
||||
* 先获取字段的类型,如果字段不存在,则获取Getter方法的返回类型,否则获取Setter的第一个参数类型
|
||||
*
|
||||
* @return 字段类型
|
||||
*/
|
||||
public Type getFieldType() {
|
||||
if (null != this.field) {
|
||||
return TypeUtil.getType(this.field);
|
||||
}
|
||||
return findPropType(getter, setter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得字段类型<br>
|
||||
* 先获取字段的类型,如果字段不存在,则获取Getter方法的返回类型,否则获取Setter的第一个参数类型
|
||||
*
|
||||
* @return 字段类型
|
||||
*/
|
||||
public Class<?> getFieldClass() {
|
||||
if (null != this.field) {
|
||||
return TypeUtil.getClass(this.field);
|
||||
}
|
||||
return findPropClass(getter, setter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Getter方法,可能为{@code null}
|
||||
*
|
||||
* @return Getter方法
|
||||
*/
|
||||
public Method getGetter() {
|
||||
return this.getter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Setter方法,可能为{@code null}
|
||||
*
|
||||
* @return {@link Method}Setter 方法对象
|
||||
*/
|
||||
public Method getSetter() {
|
||||
return this.setter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查属性是否可读(即是否可以通过{@link #getValue(Object)}获取到值)
|
||||
* @param checkTransient 是否检查Transient关键字或注解
|
||||
* @return 是否可读
|
||||
* @since 5.4.2
|
||||
*/
|
||||
public boolean isReadable(boolean checkTransient){
|
||||
// 检查是否有getter方法或是否为public修饰
|
||||
if(null == this.getter && false == ModifierUtil.isPublic(this.field)){
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查transient关键字和@Transient注解
|
||||
if(checkTransient && isTransientForGet()){
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查@PropIgnore注解
|
||||
return false == isIgnoreGet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取属性值<br>
|
||||
* 首先调用字段对应的Getter方法获取值,如果Getter方法不存在,则判断字段如果为public,则直接获取字段值<br>
|
||||
* 此方法不检查任何注解,使用前需调用 {@link #isReadable(boolean)} 检查是否可读
|
||||
*
|
||||
* @param bean Bean对象
|
||||
* @return 字段值
|
||||
* @since 4.0.5
|
||||
*/
|
||||
public Object getValue(Object bean) {
|
||||
if (null != this.getter) {
|
||||
return ReflectUtil.invoke(bean, this.getter);
|
||||
} else if (ModifierUtil.isPublic(this.field)) {
|
||||
return ReflectUtil.getFieldValue(bean, this.field);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取属性值,自动转换属性值类型<br>
|
||||
* 首先调用字段对应的Getter方法获取值,如果Getter方法不存在,则判断字段如果为public,则直接获取字段值
|
||||
*
|
||||
* @param bean Bean对象
|
||||
* @param targetType 返回属性值需要转换的类型,null表示不转换
|
||||
* @param ignoreError 是否忽略错误,包括转换错误和注入错误
|
||||
* @return this
|
||||
* @since 5.4.2
|
||||
*/
|
||||
public Object getValue(Object bean, Type targetType, boolean ignoreError) {
|
||||
Object result = null;
|
||||
try {
|
||||
result = getValue(bean);
|
||||
} catch (Exception e) {
|
||||
if (false == ignoreError) {
|
||||
throw new BeanException(e, "Get value of [{}] error!", getFieldName());
|
||||
}
|
||||
}
|
||||
|
||||
if (null != result && null != targetType) {
|
||||
// 尝试将结果转换为目标类型,如果转换失败,返回原类型。
|
||||
final Object convertValue = Convert.convertWithCheck(targetType, result, null, ignoreError);
|
||||
if (null != convertValue) {
|
||||
result = convertValue;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查属性是否可读(即是否可以通过{@link #getValue(Object)}获取到值)
|
||||
* @param checkTransient 是否检查Transient关键字或注解
|
||||
* @return 是否可读
|
||||
* @since 5.4.2
|
||||
*/
|
||||
public boolean isWritable(boolean checkTransient){
|
||||
// 检查是否有getter方法或是否为public修饰
|
||||
if(null == this.setter && false == ModifierUtil.isPublic(this.field)){
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查transient关键字和@Transient注解
|
||||
if(checkTransient && isTransientForSet()){
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查@PropIgnore注解
|
||||
return false == isIgnoreSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置Bean的字段值<br>
|
||||
* 首先调用字段对应的Setter方法,如果Setter方法不存在,则判断字段如果为public,则直接赋值字段值<br>
|
||||
* 此方法不检查任何注解,使用前需调用 {@link #isWritable(boolean)} 检查是否可写
|
||||
*
|
||||
* @param bean Bean对象
|
||||
* @param value 值,必须与字段值类型匹配
|
||||
* @return this
|
||||
* @since 4.0.5
|
||||
*/
|
||||
public PropDesc setValue(Object bean, Object value) {
|
||||
if (null != this.setter) {
|
||||
ReflectUtil.invoke(bean, this.setter, value);
|
||||
} else if (ModifierUtil.isPublic(this.field)) {
|
||||
ReflectUtil.setFieldValue(bean, this.field, value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置属性值,可以自动转换字段类型为目标类型
|
||||
*
|
||||
* @param bean Bean对象
|
||||
* @param value 属性值,可以为任意类型
|
||||
* @param ignoreNull 是否忽略{@code null}值,true表示忽略
|
||||
* @param ignoreError 是否忽略错误,包括转换错误和注入错误
|
||||
* @return this
|
||||
* @since 5.4.2
|
||||
*/
|
||||
public PropDesc setValue(Object bean, Object value, boolean ignoreNull, boolean ignoreError) {
|
||||
if (ignoreNull && null == value) {
|
||||
return this;
|
||||
}
|
||||
|
||||
// 当类型不匹配的时候,执行默认转换
|
||||
if (null != value) {
|
||||
final Class<?> propClass = getFieldClass();
|
||||
if (false == propClass.isInstance(value)) {
|
||||
value = Convert.convertWithCheck(propClass, value, null, ignoreError);
|
||||
}
|
||||
}
|
||||
|
||||
// 属性赋值
|
||||
if (null != value || false == ignoreNull) {
|
||||
try {
|
||||
this.setValue(bean, value);
|
||||
} catch (Exception e) {
|
||||
if (false == ignoreError) {
|
||||
throw new BeanException(e, "Set value of [{}] error!", getFieldName());
|
||||
}
|
||||
// 忽略注入失败
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------ Private method start
|
||||
|
||||
/**
|
||||
* 通过Getter和Setter方法中找到属性类型
|
||||
*
|
||||
* @param getter Getter方法
|
||||
* @param setter Setter方法
|
||||
* @return {@link Type}
|
||||
*/
|
||||
private Type findPropType(Method getter, Method setter) {
|
||||
Type type = null;
|
||||
if (null != getter) {
|
||||
type = TypeUtil.getReturnType(getter);
|
||||
}
|
||||
if (null == type && null != setter) {
|
||||
type = TypeUtil.getParamType(setter, 0);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过Getter和Setter方法中找到属性类型
|
||||
*
|
||||
* @param getter Getter方法
|
||||
* @param setter Setter方法
|
||||
* @return {@link Type}
|
||||
*/
|
||||
private Class<?> findPropClass(Method getter, Method setter) {
|
||||
Class<?> type = null;
|
||||
if (null != getter) {
|
||||
type = TypeUtil.getReturnClass(getter);
|
||||
}
|
||||
if (null == type && null != setter) {
|
||||
type = TypeUtil.getFirstParamClass(setter);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查字段是否被忽略写,通过{@link PropIgnore} 注解完成,规则为:
|
||||
* <pre>
|
||||
* 1. 在字段上有{@link PropIgnore} 注解
|
||||
* 2. 在setXXX方法上有{@link PropIgnore} 注解
|
||||
* </pre>
|
||||
*
|
||||
* @return 是否忽略写
|
||||
* @since 5.4.2
|
||||
*/
|
||||
private boolean isIgnoreSet() {
|
||||
return AnnotationUtil.hasAnnotation(this.field, PropIgnore.class)
|
||||
|| AnnotationUtil.hasAnnotation(this.setter, PropIgnore.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查字段是否被忽略读,通过{@link PropIgnore} 注解完成,规则为:
|
||||
* <pre>
|
||||
* 1. 在字段上有{@link PropIgnore} 注解
|
||||
* 2. 在getXXX方法上有{@link PropIgnore} 注解
|
||||
* </pre>
|
||||
*
|
||||
* @return 是否忽略读
|
||||
* @since 5.4.2
|
||||
*/
|
||||
private boolean isIgnoreGet() {
|
||||
return AnnotationUtil.hasAnnotation(this.field, PropIgnore.class)
|
||||
|| AnnotationUtil.hasAnnotation(this.getter, PropIgnore.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段和Getter方法是否为Transient关键字修饰的
|
||||
*
|
||||
* @return 是否为Transient关键字修饰的
|
||||
* @since 5.3.11
|
||||
*/
|
||||
private boolean isTransientForGet() {
|
||||
boolean isTransient = ModifierUtil.hasModifier(this.field, ModifierUtil.ModifierType.TRANSIENT);
|
||||
|
||||
// 检查Getter方法
|
||||
if (false == isTransient && null != this.getter) {
|
||||
isTransient = ModifierUtil.hasModifier(this.getter, ModifierUtil.ModifierType.TRANSIENT);
|
||||
|
||||
// 检查注解
|
||||
if (false == isTransient) {
|
||||
isTransient = AnnotationUtil.hasAnnotation(this.getter, Transient.class);
|
||||
}
|
||||
}
|
||||
|
||||
return isTransient;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段和Getter方法是否为Transient关键字修饰的
|
||||
*
|
||||
* @return 是否为Transient关键字修饰的
|
||||
* @since 5.3.11
|
||||
*/
|
||||
private boolean isTransientForSet() {
|
||||
boolean isTransient = ModifierUtil.hasModifier(this.field, ModifierUtil.ModifierType.TRANSIENT);
|
||||
|
||||
// 检查Getter方法
|
||||
if (false == isTransient && null != this.setter) {
|
||||
isTransient = ModifierUtil.hasModifier(this.setter, ModifierUtil.ModifierType.TRANSIENT);
|
||||
|
||||
// 检查注解
|
||||
if (false == isTransient) {
|
||||
isTransient = AnnotationUtil.hasAnnotation(this.setter, Transient.class);
|
||||
}
|
||||
}
|
||||
|
||||
return isTransient;
|
||||
}
|
||||
//------------------------------------------------------------------------------------ Private method end
|
||||
}
|
||||
package cn.hutool.core.bean;
|
||||
|
||||
import cn.hutool.core.annotation.AnnotationUtil;
|
||||
import cn.hutool.core.annotation.PropIgnore;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
import cn.hutool.core.util.ModifierUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.TypeUtil;
|
||||
|
||||
import java.beans.Transient;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
/**
|
||||
* 属性描述,包括了字段、getter、setter和相应的方法执行
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
public class PropDesc {
|
||||
|
||||
/**
|
||||
* 字段
|
||||
*/
|
||||
final Field field;
|
||||
/**
|
||||
* Getter方法
|
||||
*/
|
||||
protected Method getter;
|
||||
/**
|
||||
* Setter方法
|
||||
*/
|
||||
protected Method setter;
|
||||
|
||||
/**
|
||||
* 构造<br>
|
||||
* Getter和Setter方法设置为默认可访问
|
||||
*
|
||||
* @param field 字段
|
||||
* @param getter get方法
|
||||
* @param setter set方法
|
||||
*/
|
||||
public PropDesc(Field field, Method getter, Method setter) {
|
||||
this.field = field;
|
||||
this.getter = ClassUtil.setAccessible(getter);
|
||||
this.setter = ClassUtil.setAccessible(setter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段名,如果存在Alias注解,读取注解的值作为名称
|
||||
*
|
||||
* @return 字段名
|
||||
*/
|
||||
public String getFieldName() {
|
||||
return ReflectUtil.getFieldName(this.field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段名称
|
||||
*
|
||||
* @return 字段名
|
||||
* @since 5.1.6
|
||||
*/
|
||||
public String getRawFieldName() {
|
||||
return null == this.field ? null : this.field.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段
|
||||
*
|
||||
* @return 字段
|
||||
*/
|
||||
public Field getField() {
|
||||
return this.field;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得字段类型<br>
|
||||
* 先获取字段的类型,如果字段不存在,则获取Getter方法的返回类型,否则获取Setter的第一个参数类型
|
||||
*
|
||||
* @return 字段类型
|
||||
*/
|
||||
public Type getFieldType() {
|
||||
if (null != this.field) {
|
||||
return TypeUtil.getType(this.field);
|
||||
}
|
||||
return findPropType(getter, setter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得字段类型<br>
|
||||
* 先获取字段的类型,如果字段不存在,则获取Getter方法的返回类型,否则获取Setter的第一个参数类型
|
||||
*
|
||||
* @return 字段类型
|
||||
*/
|
||||
public Class<?> getFieldClass() {
|
||||
if (null != this.field) {
|
||||
return TypeUtil.getClass(this.field);
|
||||
}
|
||||
return findPropClass(getter, setter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Getter方法,可能为{@code null}
|
||||
*
|
||||
* @return Getter方法
|
||||
*/
|
||||
public Method getGetter() {
|
||||
return this.getter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Setter方法,可能为{@code null}
|
||||
*
|
||||
* @return {@link Method}Setter 方法对象
|
||||
*/
|
||||
public Method getSetter() {
|
||||
return this.setter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查属性是否可读(即是否可以通过{@link #getValue(Object)}获取到值)
|
||||
* @param checkTransient 是否检查Transient关键字或注解
|
||||
* @return 是否可读
|
||||
* @since 5.4.2
|
||||
*/
|
||||
public boolean isReadable(boolean checkTransient){
|
||||
// 检查是否有getter方法或是否为public修饰
|
||||
if(null == this.getter && false == ModifierUtil.isPublic(this.field)){
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查transient关键字和@Transient注解
|
||||
if(checkTransient && isTransientForGet()){
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查@PropIgnore注解
|
||||
return false == isIgnoreGet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取属性值<br>
|
||||
* 首先调用字段对应的Getter方法获取值,如果Getter方法不存在,则判断字段如果为public,则直接获取字段值<br>
|
||||
* 此方法不检查任何注解,使用前需调用 {@link #isReadable(boolean)} 检查是否可读
|
||||
*
|
||||
* @param bean Bean对象
|
||||
* @return 字段值
|
||||
* @since 4.0.5
|
||||
*/
|
||||
public Object getValue(Object bean) {
|
||||
if (null != this.getter) {
|
||||
return ReflectUtil.invoke(bean, this.getter);
|
||||
} else if (ModifierUtil.isPublic(this.field)) {
|
||||
return ReflectUtil.getFieldValue(bean, this.field);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取属性值,自动转换属性值类型<br>
|
||||
* 首先调用字段对应的Getter方法获取值,如果Getter方法不存在,则判断字段如果为public,则直接获取字段值
|
||||
*
|
||||
* @param bean Bean对象
|
||||
* @param targetType 返回属性值需要转换的类型,null表示不转换
|
||||
* @param ignoreError 是否忽略错误,包括转换错误和注入错误
|
||||
* @return this
|
||||
* @since 5.4.2
|
||||
*/
|
||||
public Object getValue(Object bean, Type targetType, boolean ignoreError) {
|
||||
Object result = null;
|
||||
try {
|
||||
result = getValue(bean);
|
||||
} catch (Exception e) {
|
||||
if (false == ignoreError) {
|
||||
throw new BeanException(e, "Get value of [{}] error!", getFieldName());
|
||||
}
|
||||
}
|
||||
|
||||
if (null != result && null != targetType) {
|
||||
// 尝试将结果转换为目标类型,如果转换失败,返回原类型。
|
||||
final Object convertValue = Convert.convertWithCheck(targetType, result, null, ignoreError);
|
||||
if (null != convertValue) {
|
||||
result = convertValue;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查属性是否可读(即是否可以通过{@link #getValue(Object)}获取到值)
|
||||
* @param checkTransient 是否检查Transient关键字或注解
|
||||
* @return 是否可读
|
||||
* @since 5.4.2
|
||||
*/
|
||||
public boolean isWritable(boolean checkTransient){
|
||||
// 检查是否有getter方法或是否为public修饰
|
||||
if(null == this.setter && false == ModifierUtil.isPublic(this.field)){
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查transient关键字和@Transient注解
|
||||
if(checkTransient && isTransientForSet()){
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查@PropIgnore注解
|
||||
return false == isIgnoreSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置Bean的字段值<br>
|
||||
* 首先调用字段对应的Setter方法,如果Setter方法不存在,则判断字段如果为public,则直接赋值字段值<br>
|
||||
* 此方法不检查任何注解,使用前需调用 {@link #isWritable(boolean)} 检查是否可写
|
||||
*
|
||||
* @param bean Bean对象
|
||||
* @param value 值,必须与字段值类型匹配
|
||||
* @return this
|
||||
* @since 4.0.5
|
||||
*/
|
||||
public PropDesc setValue(Object bean, Object value) {
|
||||
if (null != this.setter) {
|
||||
ReflectUtil.invoke(bean, this.setter, value);
|
||||
} else if (ModifierUtil.isPublic(this.field)) {
|
||||
ReflectUtil.setFieldValue(bean, this.field, value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置属性值,可以自动转换字段类型为目标类型
|
||||
*
|
||||
* @param bean Bean对象
|
||||
* @param value 属性值,可以为任意类型
|
||||
* @param ignoreNull 是否忽略{@code null}值,true表示忽略
|
||||
* @param ignoreError 是否忽略错误,包括转换错误和注入错误
|
||||
* @return this
|
||||
* @since 5.4.2
|
||||
*/
|
||||
public PropDesc setValue(Object bean, Object value, boolean ignoreNull, boolean ignoreError) {
|
||||
if (ignoreNull && null == value) {
|
||||
return this;
|
||||
}
|
||||
|
||||
// 当类型不匹配的时候,执行默认转换
|
||||
if (null != value) {
|
||||
final Class<?> propClass = getFieldClass();
|
||||
if (false == propClass.isInstance(value)) {
|
||||
value = Convert.convertWithCheck(propClass, value, null, ignoreError);
|
||||
}
|
||||
}
|
||||
|
||||
// 属性赋值
|
||||
if (null != value || false == ignoreNull) {
|
||||
try {
|
||||
this.setValue(bean, value);
|
||||
} catch (Exception e) {
|
||||
if (false == ignoreError) {
|
||||
throw new BeanException(e, "Set value of [{}] error!", getFieldName());
|
||||
}
|
||||
// 忽略注入失败
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------ Private method start
|
||||
|
||||
/**
|
||||
* 通过Getter和Setter方法中找到属性类型
|
||||
*
|
||||
* @param getter Getter方法
|
||||
* @param setter Setter方法
|
||||
* @return {@link Type}
|
||||
*/
|
||||
private Type findPropType(Method getter, Method setter) {
|
||||
Type type = null;
|
||||
if (null != getter) {
|
||||
type = TypeUtil.getReturnType(getter);
|
||||
}
|
||||
if (null == type && null != setter) {
|
||||
type = TypeUtil.getParamType(setter, 0);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过Getter和Setter方法中找到属性类型
|
||||
*
|
||||
* @param getter Getter方法
|
||||
* @param setter Setter方法
|
||||
* @return {@link Type}
|
||||
*/
|
||||
private Class<?> findPropClass(Method getter, Method setter) {
|
||||
Class<?> type = null;
|
||||
if (null != getter) {
|
||||
type = TypeUtil.getReturnClass(getter);
|
||||
}
|
||||
if (null == type && null != setter) {
|
||||
type = TypeUtil.getFirstParamClass(setter);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查字段是否被忽略写,通过{@link PropIgnore} 注解完成,规则为:
|
||||
* <pre>
|
||||
* 1. 在字段上有{@link PropIgnore} 注解
|
||||
* 2. 在setXXX方法上有{@link PropIgnore} 注解
|
||||
* </pre>
|
||||
*
|
||||
* @return 是否忽略写
|
||||
* @since 5.4.2
|
||||
*/
|
||||
private boolean isIgnoreSet() {
|
||||
return AnnotationUtil.hasAnnotation(this.field, PropIgnore.class)
|
||||
|| AnnotationUtil.hasAnnotation(this.setter, PropIgnore.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查字段是否被忽略读,通过{@link PropIgnore} 注解完成,规则为:
|
||||
* <pre>
|
||||
* 1. 在字段上有{@link PropIgnore} 注解
|
||||
* 2. 在getXXX方法上有{@link PropIgnore} 注解
|
||||
* </pre>
|
||||
*
|
||||
* @return 是否忽略读
|
||||
* @since 5.4.2
|
||||
*/
|
||||
private boolean isIgnoreGet() {
|
||||
return AnnotationUtil.hasAnnotation(this.field, PropIgnore.class)
|
||||
|| AnnotationUtil.hasAnnotation(this.getter, PropIgnore.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段和Getter方法是否为Transient关键字修饰的
|
||||
*
|
||||
* @return 是否为Transient关键字修饰的
|
||||
* @since 5.3.11
|
||||
*/
|
||||
private boolean isTransientForGet() {
|
||||
boolean isTransient = ModifierUtil.hasModifier(this.field, ModifierUtil.ModifierType.TRANSIENT);
|
||||
|
||||
// 检查Getter方法
|
||||
if (false == isTransient && null != this.getter) {
|
||||
isTransient = ModifierUtil.hasModifier(this.getter, ModifierUtil.ModifierType.TRANSIENT);
|
||||
|
||||
// 检查注解
|
||||
if (false == isTransient) {
|
||||
isTransient = AnnotationUtil.hasAnnotation(this.getter, Transient.class);
|
||||
}
|
||||
}
|
||||
|
||||
return isTransient;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段和Getter方法是否为Transient关键字修饰的
|
||||
*
|
||||
* @return 是否为Transient关键字修饰的
|
||||
* @since 5.3.11
|
||||
*/
|
||||
private boolean isTransientForSet() {
|
||||
boolean isTransient = ModifierUtil.hasModifier(this.field, ModifierUtil.ModifierType.TRANSIENT);
|
||||
|
||||
// 检查Getter方法
|
||||
if (false == isTransient && null != this.setter) {
|
||||
isTransient = ModifierUtil.hasModifier(this.setter, ModifierUtil.ModifierType.TRANSIENT);
|
||||
|
||||
// 检查注解
|
||||
if (false == isTransient) {
|
||||
isTransient = AnnotationUtil.hasAnnotation(this.setter, Transient.class);
|
||||
}
|
||||
}
|
||||
|
||||
return isTransient;
|
||||
}
|
||||
//------------------------------------------------------------------------------------ Private method end
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user