Hutool不糊涂(一)

●Hutool是什么?

●最甜的几块糖(Part 1)

●类型转换 

●日期与时间

●字段验证器

●配置工具

●日志工具


●Hutool是什么?

接下来的两篇文章,笔者将给大家安利一个能够明显提升Java开发效率的开源项目Hutool。官方对其描述为:A set of tools to keep java sweet。它其实是一套Java工具包,提供了许多与业务无关的常用方法,避免重复开发。截止笔者撰文,它在Github上已经收获了2033个Star,要知道,大名鼎鼎的Tomcat也不过2628,可见,它是受广大程序员推崇的。

笔者之所以要推荐这个开源项目给大家,最主要是因为其三大优势——

优势1:遵循Apache2.0开源许可协议,商业友好,可以无风险地接入使用;

优势2:提供的工具众多且实用,拥有完善的API手册,方便查找;

优势3:按需配置依赖,伸缩轻量灵活;

 

Hutool项目Github传送门:https://github.com/looly/hutool/

官方最新的API说明文档位于:https://apidoc.gitee.com/loolly/hutool/

使用示例位于:http://hutool.mydoc.io/#category_76195

 

●最甜的几块糖(Part 1)

以下章节将会重点介绍笔者觉得Hutool中最高效实用的几个类,涉及大量的举例代码,读者可以选择性的阅读,也可以作为手册,后期随时翻阅查看。需声明,以下代码均为笔者自行编写,非项目直接来源,大家无序对号入座,还请结合自己的实际业务去使用。

●类型转换 

该类几乎是一个万能的类型转换工具类,提供了大量平时常用的类型转换函数,相比传统写法,会更简洁。举例几个经典例子:

1、String转换为Date。不需要进行异常捕获,转换失败返回null,而不抛出异常;同时也提供单参数与双参数的重载函数,后者在转换失败时返回给定值,该特性Convert中其他大多数函数也具备,下同,不再赘述

 
  1. //传统写法

  2. String string = "2018-11-11 00:00:01";

  3. SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

  4. try {

  5. Date date = sdf.parse(string);

  6. } catch (ParseException e) {

  7. e.printStackTrace();

  8. Date date = new Date();

  9. }

  10.  
  11. //“糊涂”写法

  12. String string = "2018-11-11 00:00:01";

  13. Date date = Convert.toDate(string, new Date());

2、时间单位的转换,例如毫秒转化为天,避免了人为计算,也增加了代码的可读性,其中,该函数的第二第三个参数来自于java.util.concurrent下的枚举变量TimeUnit

 
  1. long milliseconds = 145248462254L;

  2. //传统写法

  3. long day1 = milliseconds/1000/60/60/24;

  4.  
  5. //“糊涂”写法

  6. long day2 = Convert.convertTime(milliseconds,TimeUnit.MILLISECONDS,TimeUnit.DAYS);

3、自定义转换器。如果Hutool提供的转换函数还不够用,想定义自己类的转换器,也是可以的。我们以代码为例,使用自定义转换器有两个步骤:一是继承Converter<T>接口,实现接口方法(插一句嘴,这是一个函数式接口,完全可以用Lambda的形式去实现,留给感兴趣的读者去尝试。本例将以传统方式书写);二是在转换器仓库ConverterRegistry中注册自定义的转换器,之后就可以使用了。

有如下两个类:报警树节点类与通用树节点类,二者不是继承关系,前者特有报警状态,但没有后者是否显示的标志位。

 
  1. public class AlarmTreeNode {

  2. private AlarmTreeNode childTreeNode;

  3. private boolean isRoot;

  4. private String alarmName;

  5. private int deviceIndex;

  6. private byte alarmStatus;

  7.  
  8. public AlarmTreeNode(){

  9. this.childTreeNode = null;

  10. this.isRoot = false;

  11. this.alarmName = "";

  12. this.deviceIndex = 0;

  13. this.alarmStatus = 1;

  14. }

  15. public AlarmTreeNode(AlarmTreeNode childTreeNode, boolean isRoot, String alarmName, int deviceIndex, byte alarmStatus) {

  16. this.childTreeNode = childTreeNode;

  17. this.isRoot = isRoot;

  18. this.alarmName = alarmName;

  19. this.deviceIndex = deviceIndex;

  20. this.alarmStatus = alarmStatus;

  21. }

  22.  
  23. public AlarmTreeNode getChildTreeNode() {

  24. return childTreeNode;

  25. }

  26.  
  27. public void setChildTreeNode(AlarmTreeNode childTreeNode) {

  28. this.childTreeNode = childTreeNode;

  29. }

  30.  
  31. public boolean isRoot() {

  32. return isRoot;

  33. }

  34.  
  35. public void setRoot(boolean root) {

  36. isRoot = root;

  37. }

  38.  
  39. public String getAlarmName() {

  40. return alarmName;

  41. }

  42.  
  43. public void setAlarmName(String alarmName) {

  44. this.alarmName = alarmName;

  45. }

  46.  
  47. public int getDeviceIndex() {

  48. return deviceIndex;

  49. }

  50.  
  51. public void setDeviceIndex(int deviceIndex) {

  52. this.deviceIndex = deviceIndex;

  53. }

  54.  
  55. public byte getAlarmStatus() {

  56. return alarmStatus;

  57. }

  58.  
  59. public void setAlarmStatus(byte alarmStatus) {

  60. this.alarmStatus = alarmStatus;

  61. }

  62. }

 
  1. public class GeneralTreeNode {

  2. private GeneralTreeNode childTreeNode;

  3. private boolean isRoot;

  4. private String nodeName;

  5. private long nodeIndex;

  6. private boolean isDisplay;

  7.  
  8. public GeneralTreeNode() {

  9. this.childTreeNode = null;

  10. this.isRoot = false;

  11. this.nodeName = "";

  12. this.nodeIndex = 0L;

  13. this.isDisplay = false;

  14. }

  15.  
  16. public GeneralTreeNode(GeneralTreeNode childTreeNode, boolean isRoot, String nodeName, long nodeIndex, boolean isDisplay) {

  17. this.childTreeNode = childTreeNode;

  18. this.isRoot = isRoot;

  19. this.nodeName = nodeName;

  20. this.nodeIndex = nodeIndex;

  21. this.isDisplay = isDisplay;

  22. }

  23.  
  24. public GeneralTreeNode getChildTreeNode() {

  25. return childTreeNode;

  26. }

  27.  
  28. public void setChildTreeNode(GeneralTreeNode childTreeNode) {

  29. this.childTreeNode = childTreeNode;

  30. }

  31.  
  32. public boolean isRoot() {

  33. return isRoot;

  34. }

  35.  
  36. public void setRoot(boolean root) {

  37. isRoot = root;

  38. }

  39.  
  40. public String getNodeName() {

  41. return nodeName;

  42. }

  43.  
  44. public void setNodeName(String nodeName) {

  45. this.nodeName = nodeName;

  46. }

  47.  
  48. public long getNodeIndex() {

  49. return nodeIndex;

  50. }

  51.  
  52. public void setNodeIndex(long nodeIndex) {

  53. this.nodeIndex = nodeIndex;

  54. }

  55.  
  56. public boolean isDisplay() {

  57. return isDisplay;

  58. }

  59.  
  60. public void setDisplay(boolean display) {

  61. isDisplay = display;

  62. }

  63. }

重点看一下我们自定义的转换类,它具有一个转换函数,用于将报警树节点类的对象转换为通用树节点类的对象。该类继承了Hutool的接口Converter<T>,泛型T为待返回的类型。实现接口的convert函数。第一个参数为待转换的对象;第二个参数为默认值。

 
  1. public class CustomConvert implements Converter<GeneralTreeNode> {

  2. public GeneralTreeNode convert(Object o, GeneralTreeNode generalTreeNode) throws IllegalArgumentException {

  3. if(null != o){

  4. GeneralTreeNode resultTreeNode = new GeneralTreeNode();

  5. //报警树节点→通用树节点:报警状态舍弃,子节点类型转换为通用的(递归),是否显示标志位设为ture

  6. AlarmTreeNode alarmTreeNode = (AlarmTreeNode) o;

  7. if(null != alarmTreeNode.getChildTreeNode()){

  8. resultTreeNode.setChildTreeNode(convert(alarmTreeNode.getChildTreeNode(),null));

  9. }

  10. else{

  11. resultTreeNode.setChildTreeNode(null);

  12. }

  13. resultTreeNode.setDisplay(true);

  14. resultTreeNode.setNodeIndex(alarmTreeNode.getDeviceIndex());

  15. resultTreeNode.setNodeName(alarmTreeNode.getAlarmName());

  16. resultTreeNode.setRoot(alarmTreeNode.isRoot());

  17. return resultTreeNode;

  18. }

  19. else{

  20. //如果对象为空,返回第二个参数中的默认值

  21. return generalTreeNode;

  22. }

  23. }

  24. }

之后我们在转换器仓库ConverterRegistry中注册上自定义的转换函数,这样就可以使用了。

 
  1. //注册自定义转换器

  2. ConverterRegistry converterRegistry = ConverterRegistry.getInstance();

  3. converterRegistry.putCustom(GeneralTreeNode.class, CustomConvert.class);

  4.  
  5. //使用自定义转换器

  6. AlarmTreeNode childTreeNode = new AlarmTreeNode();

  7. AlarmTreeNode alarmTreeNode = new AlarmTreeNode(childTreeNode, true, "报警根节点", 1001, (byte) 0);

  8. GeneralTreeNode generalTreeNode = converterRegistry.convert(GeneralTreeNode.class, alarmTreeNode, null);

  9.  
  10. AlarmTreeNode nullAlarmTreeNode = null;

  11. GeneralTreeNode nullGeneralTreeNode = converterRegistry.convert(GeneralTreeNode.class, nullAlarmTreeNode, null);

●日期与时间

Hutool提供了对Java原生Date的包装,并且提供了大量的工具类,操纵时间和日期。

1、时间格式化输出。传统方式做Date的格式化处理都需要用到SimpleDateFormat,导致项目中出现大量的模板代码,而Hutool的DateUtil工具类则很好解决了这个问题。

 
  1. //传统写法

  2. SimpleDateFormat sdfTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

  3. SimpleDateFormat sdfDate = new SimpleDateFormat("yyyy-MM-dd");

  4. Date date = new Date();

  5. String currentTime = sdfTime.format(date);

  6. String currentDate = sdfDate.format(date);

  7.  
  8. //“糊涂”写法

  9. String currentHutoolTime = DateUtil.now();

  10. String currentHutoolDate = DateUtil.today();

2、日期与时间这部分也提供了一个String转Date的函数,作用与之前的converter里的一样。它能解析一些常用的格式,如代码所示。

 
  1. //“糊涂”写法

  2. String dateStr1 = "2017-03-01";

  3. String dateStr2 = "2017-03-01 12:00";

  4. String dateStr3 = "2017-03-01 12:18:02";

  5. String dateStr4 = "2017-03-01 12:23:12.31";

  6. String dateStr5 = "15:09:32";

  7. Date date = DateUtil.parse(dateStr1);

3、比较好用的是计算时间差的相关工具函数,提供了计算各种单位的时间差,以及格式化组合的时间差。传统的写法不仅代码冗长,还需要考虑闰年等因素,非常麻烦。

 
  1. //“糊涂”写法

  2. String dateStr1 = "2017-03-01 18:01:14";

  3. String dateStr2 = "2018-11-11 00:00:00";

  4. Date date1 = DateUtil.parse(dateStr1);

  5. Date date2 = DateUtil.parse(dateStr2);

  6. //参数1、2谁前谁后也不影响,没必要非得小时间在前,非常方便

  7. Long timeBetween1 = DateUtil.between(date1,date2,DateUnit.SECOND);

  8. Long timeBetween2 = DateUtil.between(date1,date2,DateUnit.WEEK);

  9. String timeBetween3 = DateUtil.formatBetween(date1,date2, BetweenFormater.Level.SECOND);

  10. System.out.printf("俩时间差%d秒\n", timeBetween1);

  11. System.out.printf("俩时间差%s周\n", timeBetween2);

  12. System.out.printf("俩时间差(精确到秒)为:%s\n", timeBetween3);

4、除了时间差,经常会遇到做时间的加减计算,也就是时间偏移量的问题,看看传统写法与Hutool的封装:

 
  1. String dateStr = "2018-11-11 00:10:15.124";

  2. //传统写法

  3. SimpleDateFormat sdf= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");

  4. Date date = null;

  5. try {

  6. date = sdf.parse(dateStr);

  7. } catch (ParseException e) {

  8. e.printStackTrace();

  9. }

  10. Calendar dataTime = Calendar.getInstance();

  11. dataTime.setTime(date);

  12. dataTime.add(Calendar.MINUTE,10);

  13. System.out.printf("处理后的时间:%s\n",sdf.format(dataTime.getTime()));

  14.  
  15. //"糊涂"写法

  16. Date hutoolDate = DateUtil.parse(dateStr);

  17. Date newDate = DateUtil.offsetMinute(hutoolDate,10);

  18. System.out.printf("处理后的时间:%s\n",DateUtil.format(newDate,sdf));

5、最后来看一下计时器。可以用于记录代码执行时间。

 
  1. //传统写法

  2. long millseconds1 = System.currentTimeMillis();

  3. long count1 = 1;

  4. for(int i=1 ; i<1000 ;i++){

  5. count1+=i;

  6. Thread.sleep(1);

  7. }

  8. System.out.printf("计算结果%d --- ",count1);

  9. long millseconds2 = System.currentTimeMillis();

  10. System.out.printf("代码运行了%d毫秒\n",millseconds2-millseconds1);

  11.  
  12. //“糊涂”写法

  13. TimeInterval timer = DateUtil.timer();

  14. long count2 = 1;

  15. for(int i=1 ; i<1000 ;i++){

  16. count2+=i;

  17. Thread.sleep(1);

  18. }

  19. System.out.printf("计算结果%d --- ",count2);

  20. System.out.printf("代码运行了%d毫秒\n",timer.interval());

●字段验证器

我们都知道,在互联网行业,服务器后台在接收到页面传来的请求时,几乎都要做参数校验的,目的是防止SQL注入等攻击,引起系统故障。为了避免重复开发参数校验的相关代码,Hutool也为大家提供了好用的字段校验器,能满足常用的参数格式校验。

1、常用的校验函数

 
  1. String parameter = "+8610000000000";

  2. int min = 5;

  3. int max = 8;

  4.  
  5. //验证是否是身份证

  6. boolean isCitizenId = Validator.isCitizenId(parameter);

  7. //验证是否是Email

  8. boolean isEmail = Validator.isEmail(parameter);

  9. //验证是否是汉字

  10. boolean isChinese = Validator.isChinese(parameter);

  11. //验证是否是生日

  12. boolean isBirthday = Validator.isBirthday(parameter);

  13. //验证是否是Ipv4地址

  14. boolean isIpv4 = Validator.isIpv4(parameter);

  15. //验证是否是mac地址

  16. boolean isMac = Validator.isMac(parameter);

  17. //验证是否是中国的手机号

  18. boolean isMobile = Validator.isMobile(parameter);

  19. //验证是否是中国的车牌号

  20. boolean isPlateNumber = Validator.isPlateNumber(parameter);

  21. //验证是否是满足长度的仅含英文、数字、下划线的字符串

  22. boolean isGeneral = Validator.isGeneral(parameter, min, max);

2、自定义的校验函数。当Hutool提供的字段校验器还无法满足大家的使用要求时,可以根据业务场景自定义正则表达式来进行校验,举个例子,我们要校验用户提交的注册邮箱是否满足:邮箱前缀只能是英文和数字的组合,并且需要英文开头,一共5位;邮箱后缀需要是csdn.com或者csdn.com.cn。那我们就可以这样去定义和使用自己的字段校验器。相比使用Pattern和Matcher的模板代码,要精简许多。

 
  1. //自定义正则表达式进行验证

  2. String regex = "[a-zA-Z]{1}[a-zA-Z0-9]{4}@(csdn.com)|(csdn.com.cn)";

  3.  
  4. //传统写法

  5. Pattern pattern = Pattern.compile(regex);

  6. Matcher matcher = pattern.matcher(parameter);

  7. boolean isMatchRegex = matcher.matches();

  8. System.out.printf("注册邮箱%s要求!\n",isMatchRegex?"符合":"不符合");

  9.  
  10. //“糊涂写法”

  11. boolean isMatchMyRegex = Validator.isMactchRegex(regex,parameter);

  12. System.out.printf("注册邮箱%s要求!\n",isMatchMyRegex?"符合":"不符合");

●配置工具

通过,我们的项目中都会有properties配置文件,例如配置数据源、系统相关参数等。使用中我们会发现,利用Properties来加载properties文件,存在固定步骤的模板代码,Hutool对其进行封装为Props,加载过程变得更加简洁,并且还提供了额外的拓展功能,例如读取的配置项直接转换为想要的类型(Properties的getProperty()获取到的是String类型,需要手动转换)

假设有如下配置文件config.properties

 
  1. ## 1 oracle, 2 hbase, 3 postgresql

  2. dbsource=3

  3.  
  4. ## pg db

  5. pg.driverClassName=org.postgresql.Driver

  6. pg.url=jdbc\:postgresql\://10.10.10.10\:5432/port

  7. pg.username=user

  8. pg.password=password

  9. pg.maxconnection=10

  10.  
  11. ## ip and port

  12. web_ip=10.10.10.10:80

  13. oss_ip=12.12.12.12:8088

 我们对其进行加载与获取配置项的操作

 
  1. //传统写法,通过Properties操作配置文件

  2. Properties properties = new Properties();

  3. InputStream inputestream = Thread.currentThread().getContextClassLoader().getResourceAsStream("config.properties");

  4. try {

  5. properties.load( inputestream);

  6. } catch (IOException e) {

  7. e.printStackTrace();

  8. }

  9. String webIp = properties.getProperty("web_ip");

  10. System.out.printf("获取到配置项web_ip:%s\n", webIp);

  11.  
  12. //"糊涂"写法,对Properties封装为Props,简化load操作

  13. Props props = new Props("config.properties");

  14. String huToolWebIp = props.getProperty("web_ip");

  15. System.out.printf("获取到配置项web_ip:%s\n",huToolWebIp);

  16. int huToolMaxConnection = props.getInt("pg.maxconnection");

  17. System.out.printf("获取到配置项最大连接数:%d\n", huToolMaxConnection);

配置文件除了properties,用得更多的是xml,但xml的书写和解析同样是有点复杂,不够精简的,因此Hutool给大家提供了一种新的配置文件,叫做setting文件,它的格式类似properties,吸收了其简洁直观的优点;同时,它支持分组,借鉴了xml中的思路。假设有如下配置文件config.setting。相比config.properties,我们可以用中括号[]定义分组,操作的时候更灵活。

 
  1. [database]

  2. ## 1 oracle, 2 hbase, 3 postgresql

  3. id = 1

  4. dbsource=3

  5.  
  6. ## pg db

  7. pg.driverClassName=org.postgresql.Driver

  8. pg.url=jdbc\:postgresql\://10.10.10.10\:5432/port

  9. pg.username=user

  10. pg.password=password

  11. pg.maxconnection=10

  12.  
  13. [server]

  14. ## ip and port

  15. id = 2

  16. web_ip=10.10.10.10:80

  17. oss_ip=12.12.12.12:8088

 我们对其进行加载与获取配置项的操作

 
  1. //"糊涂"写法,更进一步的解决方案,采用setting文件代替properties文件,支持更多功能

  2. Setting setting = new Setting("config.setting");

  3.  
  4. //支持分组、默认值

  5. String webIp = setting.getStr("web_ip","server","127.0.0.1");

  6. int datebaseSource = setting.getInt("dbsource","database",10);

  7. int cacheSize = setting.getInt("cachesize","database",500);

  8. System.out.printf("获取到配置项web_ip:%s, 最大连接数:%d, 缓存大小:%d Mb\n", webIp, datebaseSource, cacheSize);

  9.  
  10. int databaseID = setting.getInt("id","database");

  11. int serverID = setting.getInt("id","server");

  12. System.out.printf("databaseID为:%d , serverID为:%d\n", databaseID, serverID);

可以看到,我们除了可以使用分组 外,还可以设置默认值,方便配置文件中未填写时,系统给出一个默认的。

●日志工具

大家在项目中,日志的解决方案很多都选用日志门面slf4j再加上具体的日志实现,例如log4j,这么做最重要的好处在于slf4j采用了外观模式(Facade)的设计思路,能够为程序员提供一套统一的日志操作接口,无论后面真实的日志实现是log4j还是logback等,屏蔽了不同日志实现的差异性,再项目更换具体日志实现的时候不需要改动代码,仅处理下配置文件和引用的jar包即可,松耦合。值得一提的很多成熟的公司,例如阿里巴巴在其《阿里巴巴Java开发手册》中都有明确且强制的规定:应用中不可直接使用日志系统( Log4j、 Logback) 中的 API,而应依赖使用日志框架SLF4J 中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。

 
  1. public class LogTest {

  2. private static final Logger logger = LoggerFactory.getLogger(LogTest.class);

  3.  
  4. public void log4jAndSlf4jTest(){

  5. //slf4+log4j “传统”日志

  6. logger.info("当前毫秒: {},对应秒数为{}", System.currentTimeMillis(),System.currentTimeMillis()/1000);

  7. Exception e = new NullPointerException();

  8. //logger.error(e,"空指针异常,发生时间:","now"); 无法这样使用

  9. logger.error("空指针异常",e);

  10. }

  11. }

按理slf4j已经是很不错的日志门面框架的,但是Hutool依然提供了一个日志门面Log。这是为什么呢?我们看看上面的代码可以看到slf4j的另外一个好处在于支持占位符{},避免了字符串拼接,消耗资源更低,书写阅读也更清晰,这一点类似之前代码中System.out.printf()中的%s、%d等。然而,遗憾的是,如果在异常捕获中,想记录异常的同时使用占位符确是不可以的。并且,我们能看到,创建日志对象logger的时候,每个类都得去手动写LoggerFactory.getLogger()里的参数,笔者第一次学习slf4j的时候,就没有注意到这个,导致写到了别的类上。按理,这应该也是程序可以自动识别的。

基于此,Hutool提供了Log日志门面,与slf4j类似,并且改善了一些小细节,使用更加方便。它对具体日志实现的检测顺序是:Logback > Log4j > Log4j2 > Apache Commons Logging > JDK Logging > Console。

我们来看看代码。几乎是没有额外学习成本的,只要会使用slf4j就能顺利过渡到Log。

 
  1. public class LogTest {

  2. private static final Log log = LogFactory.get();

  3. public void hutoolLogTest(){

  4. //Log+log4j “糊涂”日志

  5. log.info("当前时间:{}", DateTime.now());

  6. Exception e = new NullPointerException();

  7. log.error(e,"空指针异常,类型{}",e.getClass());

  8. }

  9. }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值