https://www.hoshino.asia/archives/hutool

文本操作

CSV文件处理工具-CsvUtil

介绍

逗号分隔值(Comma-Separated Values,CSV,有时也称为字符分隔值,因为分隔字符也可以不是逗号),其文件以纯文本形式存储表格数据(数字和文本)。

Hutool针对此格式,参考FastCSV项目做了对CSV文件读写的实现(Hutool实现完全独立,不依赖第三方)

CsvUtil是CSV工具类,主要封装了两个方法:

  • getReader 用于对CSV文件读取

  • getWriter 用于生成CSV文件

这两个方法分别获取CsvReader对象和CsvWriter,从而独立完成CSV文件的读写。

使用

读取CSV文件

读取为CsvRow
CsvReader reader = CsvUtil.getReader();
//从文件中读取CSV数据
CsvData data = reader.read(FileUtil.file("test.csv"));
List<CsvRow> rows = data.getRows();
//遍历行
for (CsvRow csvRow : rows) {
	//getRawList返回一个List列表,列表的每一项为CSV中的一个单元格(既逗号分隔部分)
	Console.log(csvRow.getRawList());
}

CsvRow对象还记录了一些其他信息,包括原始行号等。

读取为Bean列表
  1. 首先测试的CSV:test_bean.csv:

姓名,gender,focus,age
张三,男,无,33
李四,男,好对象,23
王妹妹,女,特别关注,22
  1. 定义Bean:

// lombok注解
@Data
private static class TestBean{
	// 如果csv中标题与字段不对应,可以使用alias注解设置别名
	@Alias("姓名")
	private String name;
	private String gender;
	private String focus;
	private Integer age;
}
  1. 读取

final CsvReader reader = CsvUtil.getReader();
//假设csv文件在classpath目录下
final List<TestBean> result = reader.read(
				ResourceUtil.getUtf8Reader("test_bean.csv"), TestBean.class);
  1. 输出:

CsvReaderTest.TestBean(name=张三, gender=男, focus=无, age=33)
CsvReaderTest.TestBean(name=李四, gender=男, focus=好对象, age=23)
CsvReaderTest.TestBean(name=王妹妹, gender=女, focus=特别关注, age=22)

生成CSV文件

//指定路径和编码
CsvWriter writer = CsvUtil.getWriter("e:/testWrite.csv", CharsetUtil.CHARSET_UTF_8);
//按行写出
writer.write(
	new String[] {"a1", "b1", "c1"}, 
	new String[] {"a2", "b2", "c2"}, 
	new String[] {"a3", "b3", "c3"}
);

乱码问题

CSV文件本身为一种简单文本格式,有编码区分,你可以使用任意编码。

但是当使用Excel读取CSV文件时,如果你的编码与系统编码不一致,会出现乱码的情况,解决方案如下:

  1. 可以将csv文本编码设置为与系统一致,如Windows下可以设置GBK

  2. 可以增加BOM头来指定编码,这样Excel可以自动识别bom头的编码完成解析。

Unicode编码转换工具-UnicodeUtil

介绍

此工具主要针对类似于\\u4e2d\\u6587这类Unicode字符做一些特殊转换。

使用

字符串转Unicode符

//第二个参数true表示跳过ASCII字符(只跳过可见字符)
String s = UnicodeUtil.toUnicode("aaa123中文", true);
//结果aaa123\\u4e2d\\u6587

Unicode转字符串

String str = "aaa\\U4e2d\\u6587\\u111\\urtyu\\u0026";
String res = UnicodeUtil.toString(str);
//结果aaa中文\\u111\\urtyu&

由于\\u111为非Unicode字符,因此原样输出。

可复用字符串生成器-StrBuilder

介绍

在JDK提供的StringBuilder中,拼接字符串变得更加高效和灵活,但是生成新的字符串需要重新构建StringBuilder对象,造成性能损耗和内存浪费,因此Hutool提供了可复用的StrBuilder

使用

StrBuilderStringBuilder使用方法基本一致,只是多了reset方法可以重新构建一个新的字符串而不必开辟新内存。

StrBuilder builder = StrBuilder.create();
builder.append("aaa").append("你好").append('r');
//结果:aaa你好r

多次构建字符串性能测试

我们模拟创建1000000次字符串对两者性能对比,采用TimeInterval计时:

//StringBuilder 
TimeInterval timer = DateUtil.timer();
StringBuilder b2 = new StringBuilder();
for(int i =0; i< 1000000; i++) {
	b2.append("test");
	b2 = new StringBuilder();
}
Console.log(timer.interval());
//StrBuilder
TimeInterval timer = DateUtil.timer();
StrBuilder builder = StrBuilder.create();
for(int i =0; i< 1000000; i++) {
	builder.append("test");
	builder.reset();
}
Console.log(timer.interval());

测试结果为:

StringBuilder: 39ms
StrBuilder   : 20ms

性能几乎翻倍。也欢迎用户自行测试。

字符串切割-StrSplitter

由来

在Java的String对象中提供了split方法用于通过某种字符串分隔符来把一个字符串分割为数组。但是有的时候我们对这种操作有不同的要求,默认方法无法满足,这包括:

  • 分割限制分割数

  • 分割后每个字符串是否需要去掉两端空格

  • 是否忽略空白项

  • 根据固定长度分割

  • 通过正则分隔

因此,StrSplitter应运而生。StrSplitter中全部为静态方法,方便快捷调用。

方法

基础方法

  • split 切分字符串,众多可选参数,返回结果为List

  • splitToArray 切分字符串,返回结果为数组

  • splitByRegex 根据正则切分字符串

  • splitByLength 根据固定长度切分字符串

栗子:

String str1 = "a, ,efedsfs,   ddf";
//参数:被切分字符串,分隔符逗号,0表示无限制分片数,去除两边空格,忽略空白项
List<String> split = StrSplitter.split(str1, ',', 0, true, true);

特殊方法

  • splitPath 切分字符串,分隔符为"/"

  • splitPathToArray 切分字符串,分隔符为"/",返回数组

注解

注解工具-AnnotationUtil

介绍

封装了注解获取等方法的工具类。

使用

方法介绍

  1. 注解获取相关方法:

  • getAnnotations 获取指定类、方法、字段、构造等上的注解列表

  • getAnnotation 获取指定类型注解

  • getAnnotationValue 获取指定注解属性的值

例子:

我们定义一个注解:

// Retention注解决定MyAnnotation注解的生命周期
@Retention(RetentionPolicy.RUNTIME)
// Target注解决定MyAnnotation注解可以加在哪些成分上,如加在类身上,或者属性身上,或者方法身上等成分
@Target({ ElementType.METHOD, ElementType.TYPE })
public @interface AnnotationForTest {
	
	/**
	 * 注解的默认属性值
	 * 
	 * @return 属性值
	 */
	String value();
}

给需要的类加上注解:

@AnnotationForTest("测试")
public static class ClassWithAnnotation{

}

获取注解中的值:

// value为"测试"
Object value = AnnotationUtil.getAnnotationValue(ClassWithAnnotation.class, AnnotationForTest.class);

注解属性获取相关方法:

  • getRetentionPolicy 获取注解类的保留时间,可选值 SOURCE(源码时),CLASS(编译时),RUNTIME(运行时),默认为 CLASS

  • getTargetType 获取注解类可以用来修饰哪些程序元素,如 TYPE, METHOD, CONSTRUCTOR, FIELD, PARAMETER 等

  • isDocumented 是否会保存到 Javadoc 文档中

  • isInherited 是否可以被继承,默认为 false

更多方法见API文档:

https://apidoc.gitee.com/loolly/hutool/cn/hutool/core/annotation/AnnotationUtil.html

比较器

概述

介绍

各种比较器(Comparator)实现和封装

提供的比较器

其它比较器

  • ReverseComparator 反转比较器,排序时提供反序

  • VersionComparator 版本比较器,支持如:1.3.20.8,6.82.20160101,8.5a/8.5c等版本形式

  • PropertyComparator Bean属性比较器,通过Bean的某个属性来对Bean对象进行排序

  • IndexedComparator 按照数组的顺序正序排列,数组的元素位置决定了对象的排序先后

  • ComparatorChain 比较器链。此链包装了多个比较器,最终比较结果按照比较器顺序综合多个比较器结果。

  • PinyinComparator 按照GBK拼音顺序对给定的汉字字符串排序。

比较工具-CompareUtil

介绍

在JDK提供的比较器中,对于null的比较没有考虑,Hutool封装了相关比较,可选null是按照最大值还是最小值对待。

// 当isNullGreater为true时,null始终最大,此处返回的compare > 0
int compare = CompareUtil.compare(null, "a", true);
// 当isNullGreater为false时,null始终最小,此处返回的compare < 0
int compare = CompareUtil.compare(null, "a", false);

版本比较器-VersionComparator

介绍

版本比较器用于比较版本号,支持的格式包括:

  • x.x.x(1.3.20)

  • x.x.yyyyMMdd(6.82.20160101)

  • 带字母的版本(8.5a/8.5c)

  • 带V的版本(V8.5)

使用

// -1
int compare = VersionComparator.INSTANCE.compare("1.12.1", "1.12.1c");
// 1
int compare = VersionComparator.INSTANCE.compare("V0.0.20170102", "V0.0.20170101");

异常

异常工具-ExceptionUtil

介绍

针对异常封装,例如包装为RuntimeException

方法

包装异常

假设系统抛出一个非Runtime异常,我们需要包装为Runtime异常,那么:

IORuntimeException e = ExceptionUtil.wrap(new IOException(), IORuntimeException.class);

获取入口方法

StackTraceElement ele = ExceptionUtil.getRootStackElement();
// main
ele.getMethodName();

异常转换

如果我们想把异常转换指定异常为来自或者包含指定异常,那么:

IOException ioException = new IOException();
IllegalArgumentException argumentException = new IllegalArgumentException(ioException);

IOException ioException1 = ExceptionUtil.convertFromOrSuppressedThrowable(argumentException, IOException.class, true);

其他方法

  • getMessage 获得完整消息,包括异常名

  • wrapRuntime 使用运行时异常包装编译异常

  • getCausedBy 获取由指定异常类引起的异常

  • isCausedBy 判断是否由指定异常类引起

  • stacktraceToString 堆栈转为完整字符串

其它方法见API文档:

https://apidoc.gitee.com/dromara/hutool/cn/hutool/core/exceptions/ExceptionUtil.html

其它异常封装

介绍

针对Hutool中常见异常封装

异常类

  • DependencyException 依赖异常

  • StatefulException 带有状态码的异常

  • UtilException 工具类异常

  • NotInitedException 未初始化异常

  • ValidateException 验证异常

数学

数学相关-MathUtil

介绍

此工具是NumberUtil的一个补充,NumberUtil偏向于简单数学计算的封装,MathUtil偏向复杂数学计算。

方法

  1. 排列

  • arrangementCount 计算排列数

  • arrangementSelect 排列选择(从列表中选择n个排列)

  1. 组合

  • combinationCount 计算组合数,即C(n, m) = n!/((n-m)! * m!)

  • combinationSelect 组合选择(从列表中选择n个组合)

线程和并发

线程工具-ThreadUtil

由来

并发在Java中算是一个比较难理解和容易出问题的部分,而并发的核心在线程。好在从JDK1.5开始Java提供了concurrent包可以很好的帮我们处理大部分并发、异步等问题。

不过,ExecutorServiceExecutors等众多概念依旧让我们使用这个包变得比较麻烦,如何才能隐藏这些概念?又如何用一个方法解决问题?ThreadUtil便为此而生。

原理

Hutool使用GlobalThreadPool持有一个全局的线程池,默认所有异步方法在这个线程池中执行。

方法

ThreadUtil.execute

直接在公共线程池中执行线程

ThreadUtil.newExecutor

获得一个新的线程池

ThreadUtil.execAsync

执行异步方法

ThreadUtil.newCompletionService

创建CompletionService,调用其submit方法可以异步执行多个任务,最后调用take方法按照完成的顺序获得其结果。若未完成,则会阻塞。

ThreadUtil.newCountDownLatch

新建一个CountDownLatch,一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。

ThreadUtil.sleep

挂起当前线程,是Thread.sleep的封装,通过返回boolean值表示是否被打断,而不是抛出异常。

ThreadUtil.safeSleep方法是一个保证挂起足够时间的方法,当给定一个挂起时间,使用此方法可以保证挂起的时间大于或等于给定时间,解决Thread.sleep挂起时间不足问题,此方法在Hutool-cron的定时器中使用保证定时任务执行的准确性。

ThreadUtil.getStackTrace

此部分包括两个方法:

  • getStackTrace 获得堆栈列表

  • getStackTraceElement 获得堆栈项

其它

  • createThreadLocal 创建本地线程对象

  • interupt 结束线程,调用此方法后,线程将抛出InterruptedException异常

  • waitForDie 等待线程结束. 调用 Thread.join() 并忽略 InterruptedException

  • getThreads 获取JVM中与当前线程同组的所有线程

  • getMainThread 获取进程的主线程

异步工具类-AsyncUtil

由来

JDK8中,提供了CompletableFuture进行异步执行

使用场景如异步调用微服务、异步查询数据库、异步运算大量数据等

方法

AsyncUtil.waitAll

等待所有任务执行完毕

AsyncUtil.waitAny

等待任意一个任务执行完毕

AsyncUtil.get

获取异步任务结果

自定义线程池-ExecutorBuilder

由来

在JDK中,提供了Executors用于创建自定义的线程池对象ExecutorService,但是考虑到线程池中存在众多概念,这些概念通过不同的搭配实现灵活的线程管理策略,单独使用Executors无法满足需求,所以构建了ExecutorBuilder

概念

  • corePoolSize 初始池大小

  • maxPoolSize 最大池大小(允许同时执行的最大线程数)

  • workQueue 队列,用于存储未执行的任务

  • handler 当线程阻塞(block)时的异常处理器,所谓线程阻塞即线程池和等待队列已满,无法处理线程时采取的策略

线程池对待线程的策略

  1. 如果池中任务数 < corePoolSize -> 放入立即执行

  2. 如果池中任务数 > corePoolSize -> 放入队列等待

  3. 队列满 -> 新建线程立即执行

  4. 执行中的线程 > maxPoolSize -> 触发handler(RejectedExecutionHandler)异常

workQueue线程池策略

  • SynchronousQueue 它将任务直接提交给线程而不保持它们。当运行线程小于maxPoolSize时会创建新线程,否则触发异常策略

  • LinkedBlockingQueue 默认无界队列,当运行线程大于corePoolSize时始终放入此队列,此时maxPoolSize无效。当构造LinkedBlockingQueue对象时传入参数,变为有界队列,队列满时,运行线程小于maxPoolSize时会创建新线程,否则触发异常策略

  • ArrayBlockingQueue 有界队列,相对无界队列有利于控制队列大小,队列满时,运行线程小于maxPoolSize时会创建新线程,否则触发异常策略

使用

默认线程池

策略如下:

  • 初始线程数为corePoolSize指定的大小

  • 没有最大线程数限制

  • 默认使用LinkedBlockingQueue,默认队列大小为1024(最大等待数1024)

  • 当运行线程大于corePoolSize放入队列,队列满后抛出异常

ExecutorService executor = ExecutorBuilder builder = ExecutorBuilder.create()..build();

单线程线程池

  • 初始线程数为 1

  • 最大线程数为 1

  • 默认使用LinkedBlockingQueue,默认队列大小为1024

  • 同时只允许一个线程工作,剩余放入队列等待,等待数超过1024报错

ExecutorService executor = ExecutorBuilder.create()//
	.setCorePoolSize(1)//
	.setMaxPoolSize(1)//
	.setKeepAliveTime(0)//
	.build();

更多选项的线程池

  • 初始5个线程

  • 最大10个线程

  • 有界等待队列,最大等待数是100

ExecutorService executor = ExecutorBuilder.create()
	.setCorePoolSize(5)
	.setMaxPoolSize(10)
	.setWorkQueue(new LinkedBlockingQueue<>(100))
	.build();

特殊策略的线程池

  • 初始5个线程

  • 最大10个线程

  • 它将任务直接提交给线程而不保持它们。当运行线程小于maxPoolSize时会创建新线程,否则触发异常策略

ExecutorService executor = ExecutorBuilder.create()
	.setCorePoolSize(5)
	.setMaxPoolSize(10)
	.useSynchronousQueue()
	.build();

高并发测试-ConcurrencyTester

由来

很多时候,我们需要简单模拟N个线程调用某个业务测试其并发状况,于是Hutool提供了一个简单的并发测试类——ConcurrencyTester。

使用

ConcurrencyTester tester = ThreadUtil.concurrencyTest(100, () -> {
	// 测试的逻辑内容
	long delay = RandomUtil.randomLong(100, 1000);
	ThreadUtil.sleep(delay);
	Console.log("{} test finished, delay: {}", Thread.currentThread().getName(), delay);
});

// 获取总的执行时间,单位毫秒
Console.log(tester.getInterval());

图片

图片工具-ImgUtil

介绍

针对awt中图片处理进行封装,这些封装包括:缩放、裁剪、转为黑白、加水印等操作。

方法介绍

scale 缩放图片

提供两种重载方法:其中一个是按照长宽缩放,另一种是按照比例缩放。

ImgUtil.scale(
    FileUtil.file("d:/face.jpg"), 
    FileUtil.file("d:/face_result.jpg"), 
    0.5f//缩放比例
);

cut 剪裁图片

ImgUtil.cut(
    FileUtil.file("d:/face.jpg"), 
    FileUtil.file("d:/face_result.jpg"), 
    new Rectangle(200, 200, 100, 100)//裁剪的矩形区域
);

slice 按照行列剪裁切片(将图片分为10行和10列)

ImgUtil.slice(FileUtil.file("e:/test2.png"), FileUtil.file("e:/dest/"), 10, 10);

convert 图片类型转换,支持GIF->JPG、GIF->PNG、PNG->JPG、PNG->GIF(X)、BMP->PNG等

ImgUtil.convert(FileUtil.file("e:/test2.png"), FileUtil.file("e:/test2Convert.jpg"));

gray 彩色转为黑白

ImgUtil.gray(FileUtil.file("d:/logo.png"), FileUtil.file("d:/result.png"));

pressText 添加文字水印

ImgUtil.pressText(//
    FileUtil.file("e:/pic/face.jpg"), //
    FileUtil.file("e:/pic/test2_result.png"), //
    "版权所有", Color.WHITE, //文字
    new Font("黑体", Font.BOLD, 100), //字体
    0, //x坐标修正值。 默认在中间,偏移量相对于中间偏移
    0, //y坐标修正值。 默认在中间,偏移量相对于中间偏移
    0.8f//透明度:alpha 必须是范围 [0.0, 1.0] 之内(包含边界值)的一个浮点数字
);

pressImage 添加图片水印

ImgUtil.pressImage(
    FileUtil.file("d:/picTest/1.jpg"), 
    FileUtil.file("d:/picTest/dest.jpg"), 
    ImgUtil.read(FileUtil.file("d:/picTest/1432613.jpg")), //水印图片
    0, //x坐标修正值。 默认在中间,偏移量相对于中间偏移
    0, //y坐标修正值。 默认在中间,偏移量相对于中间偏移
    0.1f
);

rotate 旋转图片

// 旋转180度
BufferedImage image = ImgUtil.rotate(ImageIO.read(FileUtil.file("e:/pic/366466.jpg")), 180);
ImgUtil.write(image, FileUtil.file("e:/pic/result.png"));

flip 水平翻转图片

ImgUtil.flip(FileUtil.file("d:/logo.png"), FileUtil.file("d:/result.png"));

图片编辑器-Img

介绍

针对awt中图片处理进行封装,这些封装包括:缩放、裁剪、转为黑白、加水印等操作。

方法介绍

图像切割

// 将face.jpg切割为原型保存为face_radis.png
Img.from(FileUtil.file("e:/pic/face.jpg"))
    .cut(0, 0, 200)//
    .write(FileUtil.file("e:/pic/face_radis.png"));

图片压缩

图片压缩只支持Jpg文件。

Img.from(FileUtil.file("e:/pic/1111.png"))
    .setQuality(0.8)//压缩比率
    .write(FileUtil.file("e:/pic/1111_target.jpg"));

网络

网络工具-NetUtil

由来

在日常开发中,网络连接这块儿必不可少。日常用到的一些功能,隐藏掉部分IP地址、绝对和相对路径的转换等等。

介绍

NetUtil 工具中主要的方法包括:

  1. longToIpv4 根据long值获取ipv4地址

  2. ipv4ToLong 根据ip地址计算出long型的数据

  3. isUsableLocalPort 检测本地端口可用性

  4. isValidPort 是否为有效的端口

  5. isInnerIP 判定是否为内网IP

  6. localIpv4s 获得本机的IP地址列表

  7. toAbsoluteUrl 相对URL转换为绝对URL

  8. hideIpPart 隐藏掉IP地址的最后一部分为 * 代替

  9. buildInetSocketAddress 构建InetSocketAddress

  10. getIpByHost 通过域名得到IP

  11. isInner 指定IP的long是否在指定范围内

使用

String ip= "127.0.0.1";
long iplong = 2130706433L;

//根据long值获取ip v4地址
String ip= NetUtil.longToIpv4(iplong);


//根据ip地址计算出long型的数据
long ip= NetUtil.ipv4ToLong(ip);

//检测本地端口可用性
boolean result= NetUtil.isUsableLocalPort(6379);

//是否为有效的端口
boolean result= NetUtil.isValidPort(6379);

//隐藏掉IP地址
 String result =NetUtil.hideIpPart(ip);

更多方法请见:

API文档-NetUtil

URL生成器-UrlBuilder

由来

在JDK中,我们可以借助URL对象完成URL的格式化,但是无法完成一些特殊URL的解析和处理,例如编码过的URL、不标准的路径和参数。在旧版本的hutool中,URL的规范完全靠字符串的替换来完成,不但效率低,而且处理过程及其复杂。于是在5.3.1之后,加入了UrlBuilder类,拆分URL的各个部分,分别处理和格式化,完成URL的规范化。

按照Uniform Resource Identifier的标准定义,URL的结构如下:

  • [scheme:]scheme-specific-part[#fragment]

  • [scheme:][//authority][path][?query][#fragment]

  • [scheme:][//host:port][path][?query][#fragment]

按照这个格式,UrlBuilder将URL分成scheme、host、port、path、query、fragment部分,其中path和query较为复杂,又使用UrlPathUrlQuery分别封装。

使用

相比URL对象,UrlBuilder更加人性化,例如:

URL url = new URL("www.hutool.cn")

此时会报java.net.MalformedURLException: no protocol的错误,而使用UrlBuilder则会有默认协议:

// 输出 http://www.hutool.cn/
String buildUrl = UrlBuilder.create().setHost("www.hutool.cn").build();

完整构建

// https://www.hutool.cn/aaa/bbb?ie=UTF-8&wd=test
String buildUrl = UrlBuilder.create()
	.setScheme("https")
	.setHost("www.hutool.cn")
	.addPath("/aaa").addPath("bbb")
	.addQuery("ie", "UTF-8")
	.addQuery("wd", "test")
	.build();

中文编码

当参数中有中文时,自动编码中文,默认UTF-8编码,也可以调用setCharset方法自定义编码。

// https://www.hutool.cn/s?ie=UTF-8&ie=GBK&wd=%E6%B5%8B%E8%AF%95
String buildUrl = UrlBuilder.create()
	.setScheme("https")
	.setHost("www.hutool.cn")
	.addPath("/s")
	.addQuery("ie", "UTF-8")
	.addQuery("ie", "GBK")
	.addQuery("wd", "测试")
	.build();

解析

当有一个URL字符串时,可以使用of方法解析:

UrlBuilder builder = UrlBuilder.ofHttp("www.hutool.cn/aaa/bbb/?a=张三&b=%e6%9d%8e%e5%9b%9b#frag1", CharsetUtil.CHARSET_UTF_8);

// 输出张三
Console.log(builder.getQuery().get("a"));
// 输出李四
Console.log(builder.getQuery().get("b"));

我们发现这个例子中,原URL中的参数a是没有编码的,b是编码过的,当用户提供此类混合URL时,Hutool可以很好的识别并全部decode,当然,调用build()之后,会全部再encode。

特殊URL解析

有时候URL中会存在&amp;这种分隔符,谷歌浏览器会将此字符串转换为&使用,Hutool中也同样如此:

String urlStr = "https://mp.weixin.qq.com/s?__biz=MzI5NjkyNTIxMg==&amp;mid=100000465&amp;idx=1";
UrlBuilder builder = UrlBuilder.ofHttp(urlStr, CharsetUtil.CHARSET_UTF_8);

// https://mp.weixin.qq.com/s?__biz=MzI5NjkyNTIxMg==&mid=100000465&idx=1
Console.log(builder.build());

UrlBuilder主要应用于http模块,在构建HttpRequest时,用户传入的URL五花八门,为了做到最好的适应性,减少用户对URL的处理,使用UrlBuilder完成URL的规范化。

源码编译

源码编译工具-CompilerUtil

介绍

JDK提供了JavaCompiler用于动态编译java源码文件,然后通过类加载器加载,这种动态编译可以让Java有动态脚本的特性,Hutool针对此封装了对应工具。

使用

首先我们将编译需要依赖的class文件和jar文件打成一个包:

// 依赖A,编译B和C
final File libFile = ZipUtil.zip(FileUtil.file("lib.jar"),
		new String[]{"a/A.class", "a/A$1.class", "a/A$InnerClass.class"},
		new InputStream[]{
				FileUtil.getInputStream("test-compile/a/A.class"),
				FileUtil.getInputStream("test-compile/a/A$1.class"),
				FileUtil.getInputStream("test-compile/a/A$InnerClass.class")
		});

开始编译:

final ClassLoader classLoader = CompilerUtil.getCompiler(null)
	// 被编译的源码文件
	.addSource(FileUtil.file("test-compile/b/B.java"))
	// 被编译的源码字符串
	.addSource("c.C", FileUtil.readUtf8String("test-compile/c/C.java"))
	// 编译依赖的库
	.addLibrary(libFile)
	.compile();

加载编译好的类:

final Class<?> clazz = classLoader.loadClass("c.C");
// 实例化对象c
Object obj = ReflectUtil.newInstance(clazz);

Ciallo~(∠・ω< )⌒☆