Looly 5 years ago
parent
commit
cdfae52eb9

+ 1 - 0
CHANGELOG.md

@@ -26,6 +26,7 @@
 * 【core   】     增加compile包(pr#1243@Github)
 * 【core   】     增加ResourceClassLoader、CharSequenceResource、FileObjectResource
 * 【core   】     修改IoUtil.read(Reader)逻辑默认关闭Reader
+* 【core   】     ZipUtil增加Zip方法(pr#222@Gitee)
 
 ### Bug修复
 * 【cron   】     修复CronTimer可能死循环的问题(issue#1224@Github)

+ 19 - 6
hutool-core/src/main/java/cn/hutool/core/compiler/CompilerUtil.java

@@ -4,6 +4,7 @@ import javax.tools.DiagnosticListener;
 import javax.tools.JavaCompiler;
 import javax.tools.JavaFileManager;
 import javax.tools.JavaFileObject;
+import javax.tools.StandardJavaFileManager;
 import javax.tools.ToolProvider;
 
 /**
@@ -13,6 +14,7 @@ import javax.tools.ToolProvider;
  * @since 5.5.2
  */
 public class CompilerUtil {
+
 	/**
 	 * java 编译器
 	 */
@@ -29,21 +31,21 @@ public class CompilerUtil {
 	}
 
 	/**
-	 * 获取{@link JavaFileManager}
+	 * 获取{@link StandardJavaFileManager}
 	 *
-	 * @return {@link JavaFileManager}
+	 * @return {@link StandardJavaFileManager}
 	 */
-	public static JavaFileManager getFileManager() {
+	public static StandardJavaFileManager getFileManager() {
 		return SYSTEM_COMPILER.getStandardFileManager(null, null, null);
 	}
 
 	/**
 	 * 新建编译任务
 	 *
-	 * @param fileManager {@link JavaFileManager},用于管理已经编译好的文件
+	 * @param fileManager        {@link JavaFileManager},用于管理已经编译好的文件
 	 * @param diagnosticListener 诊断监听
-	 * @param options 选项,例如 -cpXXX等
-	 * @param compilationUnits 编译单元,即需要编译的对象
+	 * @param options            选项,例如 -cpXXX等
+	 * @param compilationUnits   编译单元,即需要编译的对象
 	 * @return {@link JavaCompiler.CompilationTask}
 	 */
 	public static JavaCompiler.CompilationTask getTask(
@@ -53,4 +55,15 @@ public class CompilerUtil {
 			Iterable<? extends JavaFileObject> compilationUnits) {
 		return SYSTEM_COMPILER.getTask(null, fileManager, diagnosticListener, options, null, compilationUnits);
 	}
+
+	/**
+	 * 获取{@link JavaSourceCompiler}
+	 *
+	 * @param parent 父{@link ClassLoader}
+	 * @return {@link JavaSourceCompiler}
+	 * @see JavaSourceCompiler#create(ClassLoader)
+	 */
+	public static JavaSourceCompiler getCompiler(ClassLoader parent) {
+		return JavaSourceCompiler.create(parent);
+	}
 }

+ 1 - 1
hutool-core/src/main/java/cn/hutool/core/compiler/JavaClassFileManager.java

@@ -70,7 +70,7 @@ class JavaClassFileManager extends ForwardingJavaFileManager<JavaFileManager> {
 	 */
 	@Override
 	public JavaFileObject getJavaFileForOutput(final Location location, final String className, final Kind kind, final FileObject sibling) {
-		final JavaFileObject javaFileObject = new JavaClassFileObject(className, kind);
+		final JavaFileObject javaFileObject = new JavaClassFileObject(className);
 		this.classFileObjectMap.put(className, new FileObjectResource(javaFileObject));
 		return javaFileObject;
 	}

+ 8 - 9
hutool-core/src/main/java/cn/hutool/core/compiler/JavaClassFileObject.java

@@ -1,22 +1,22 @@
 package cn.hutool.core.compiler;
 
 
+import cn.hutool.core.util.CharUtil;
+import cn.hutool.core.util.URLUtil;
+
 import javax.tools.SimpleJavaFileObject;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.net.URI;
 
 /**
- * Java 字节码文件对象
+ * Java 字节码文件对象,用于在内存中暂存class字节码,从而可以在ClassLoader中动态加载。
  *
  * @author lzpeng
- * @see JavaClassFileManager#getClassLoader(javax.tools.JavaFileManager.Location
- * @see JavaClassFileManager#getJavaFileForOutput(javax.tools.JavaFileManager.Location, java.lang.String, javax.tools.JavaFileObject.Kind, javax.tools.FileObject)
  * @since 5.5.2
  */
-final class JavaClassFileObject extends SimpleJavaFileObject {
+class JavaClassFileObject extends SimpleJavaFileObject {
 
 	/**
 	 * 字节码输出流
@@ -26,12 +26,11 @@ final class JavaClassFileObject extends SimpleJavaFileObject {
 	/**
 	 * 构造
 	 *
-	 * @param className 需要编译的类名
-	 * @param kind      需要编译的文件类型
+	 * @param className 编译后的class文件的类名
 	 * @see JavaClassFileManager#getJavaFileForOutput(javax.tools.JavaFileManager.Location, java.lang.String, javax.tools.JavaFileObject.Kind, javax.tools.FileObject)
 	 */
-	protected JavaClassFileObject(final String className, final Kind kind) {
-		super(URI.create("string:///" + className.replaceAll("\\.", "/") + kind.extension), kind);
+	protected JavaClassFileObject(String className) {
+		super(URLUtil.getStringURI(className.replace(CharUtil.DOT, CharUtil.SLASH) + Kind.CLASS.extension), Kind.CLASS);
 		this.byteArrayOutputStream = new ByteArrayOutputStream();
 	}
 

+ 80 - 47
hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceCompiler.java

@@ -2,6 +2,9 @@ package cn.hutool.core.compiler;
 
 import cn.hutool.core.io.FileUtil;
 import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.io.resource.FileResource;
+import cn.hutool.core.io.resource.Resource;
+import cn.hutool.core.io.resource.StringResource;
 import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.ArrayUtil;
 import cn.hutool.core.util.CharsetUtil;
@@ -11,7 +14,6 @@ import cn.hutool.core.util.URLUtil;
 
 import javax.tools.DiagnosticCollector;
 import javax.tools.JavaCompiler.CompilationTask;
-import javax.tools.JavaFileManager;
 import javax.tools.JavaFileObject;
 import javax.tools.StandardLocation;
 import java.io.File;
@@ -21,22 +23,44 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
 
 /**
  * Java 源码编译器
+ * <p>通过此类可以动态编译java源码,并加载到ClassLoader,从而动态获取加载的类。</p>
+ * <p>JavaSourceCompiler支持加载的源码类型包括:</p>
+ * <ul>
+ *     <li>源码文件</li>
+ *     <li>源码文件源码字符串</li>
+ * </ul>
+ *
+ * <p>使用方法如下:</p>
+ * <pre>
+ *     ClassLoader classLoader = JavaSourceCompiler.create(null)
+ *         .addSource(FileUtil.file("test-compile/b/B.java"))
+ *         .addSource("c.C", FileUtil.readUtf8String("test-compile/c/C.java"))
+ *         // 增加编译依赖的类库
+ *         .addLibrary(libFile)
+ *         .compile();
+ *     Class&lt;?&gt; clazz = classLoader.loadClass("c.C");
+ * </pre>
  *
  * @author lzpeng
  */
 public class JavaSourceCompiler {
 
 	/**
-	 * 待编译的文件 可以是 .java文件 压缩文件 文件夹 递归搜索文件夹内的zip包和jar包
+	 * 待编译的资源,支持:
+	 *
+	 * <ul>
+	 *     <li>源码字符串,使用{@link StringResource}</li>
+	 *     <li>源码文件、源码jar包或源码zip包,亦或者文件夹,使用{@link FileResource}</li>
+	 * </ul>
+	 * 可以是 .java文件 压缩文件 文件夹 递归搜索文件夹内的zip包和jar包
 	 */
-	private final List<File> sourceFileList = new ArrayList<>();
+	private final List<Resource> sourceList = new ArrayList<>();
 
 	/**
 	 * 编译时需要加入classpath中的文件 可以是 压缩文件 文件夹递归搜索文件夹内的zip包和jar包
@@ -44,15 +68,19 @@ public class JavaSourceCompiler {
 	private final List<File> libraryFileList = new ArrayList<>();
 
 	/**
-	 * 源码映射 key: 类名 value: 类源码
-	 */
-	private final Map<String, String> sourceCodeMap = new LinkedHashMap<>();
-
-	/**
 	 * 编译类时使用的父类加载器
 	 */
 	private final ClassLoader parentClassLoader;
 
+	/**
+	 * 创建Java源码编译器
+	 *
+	 * @param parent 父类加载器
+	 * @return Java源码编译器
+	 */
+	public static JavaSourceCompiler create(ClassLoader parent) {
+		return new JavaSourceCompiler(parent);
+	}
 
 	/**
 	 * 构造
@@ -63,27 +91,32 @@ public class JavaSourceCompiler {
 		this.parentClassLoader = ObjectUtil.defaultIfNull(parent, ClassLoaderUtil.getClassLoader());
 	}
 
-
 	/**
-	 * 创建Java源码编译器
+	 * 向编译器中加入待编译的资源<br>
+	 * 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
 	 *
-	 * @param parent 父类加载器
+	 * @param resources 待编译的资源,支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
 	 * @return Java源码编译器
 	 */
-	public static JavaSourceCompiler create(ClassLoader parent) {
-		return new JavaSourceCompiler(parent);
+	public JavaSourceCompiler addSource(Resource... resources) {
+		if (ArrayUtil.isNotEmpty(resources)) {
+			this.sourceList.addAll(Arrays.asList(resources));
+		}
+		return this;
 	}
 
-
 	/**
-	 * 向编译器中加入待编译的文件 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
+	 * 向编译器中加入待编译的文件<br>
+	 * 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
 	 *
 	 * @param files 待编译的文件 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
 	 * @return Java源码编译器
 	 */
-	public JavaSourceCompiler addSource(final File... files) {
+	public JavaSourceCompiler addSource(File... files) {
 		if (ArrayUtil.isNotEmpty(files)) {
-			this.sourceFileList.addAll(Arrays.asList(files));
+			for (File file : files) {
+				this.sourceList.add(new FileResource(file));
+			}
 		}
 		return this;
 	}
@@ -94,36 +127,36 @@ public class JavaSourceCompiler {
 	 * @param sourceCodeMap 源码Map key: 类名 value 源码
 	 * @return Java源码编译器
 	 */
-	public JavaSourceCompiler addSource(final Map<String, String> sourceCodeMap) {
+	public JavaSourceCompiler addSource(Map<String, String> sourceCodeMap) {
 		if (MapUtil.isNotEmpty(sourceCodeMap)) {
-			this.sourceCodeMap.putAll(sourceCodeMap);
+			sourceCodeMap.forEach(this::addSource);
 		}
 		return this;
 	}
 
 	/**
-	 * 加入编译Java源码时所需要的jar包
+	 * 向编译器中加入待编译的源码
 	 *
-	 * @param files 编译Java源码时所需要的jar包
-	 * @return Java源码编译器
+	 * @param className  类名
+	 * @param sourceCode 源码
+	 * @return Java文件编译器
 	 */
-	public JavaSourceCompiler addLibrary(final File... files) {
-		if (ArrayUtil.isNotEmpty(files)) {
-			this.libraryFileList.addAll(Arrays.asList(files));
+	public JavaSourceCompiler addSource(String className, String sourceCode) {
+		if (className != null && sourceCode != null) {
+			this.sourceList.add(new StringResource(sourceCode, className));
 		}
 		return this;
 	}
 
 	/**
-	 * 向编译器中加入待编译的源码Map
+	 * 加入编译Java源码时所需要的jar包,jar包中必须为字节码
 	 *
-	 * @param className  类名
-	 * @param sourceCode 源码
-	 * @return Java文件编译器
+	 * @param files 编译Java源码时所需要的jar包
+	 * @return Java源码编译器
 	 */
-	public JavaSourceCompiler addSource(final String className, final String sourceCode) {
-		if (className != null && sourceCode != null) {
-			this.sourceCodeMap.put(className, sourceCode);
+	public JavaSourceCompiler addLibrary(File... files) {
+		if (ArrayUtil.isNotEmpty(files)) {
+			this.libraryFileList.addAll(Arrays.asList(files));
 		}
 		return this;
 	}
@@ -138,15 +171,13 @@ public class JavaSourceCompiler {
 		final List<File> classPath = getClassPath();
 		final URL[] urLs = URLUtil.getURLs(classPath.toArray(new File[0]));
 		final URLClassLoader ucl = URLClassLoader.newInstance(urLs, this.parentClassLoader);
-		if (sourceCodeMap.isEmpty() && sourceFileList.isEmpty()) {
-			// 没有需要编译的源码
+		if (sourceList.isEmpty()) {
+			// 没有需要编译的源码文件返回加载zip或jar包的类加载器
 			return ucl;
 		}
 
-		// 没有需要编译的源码文件返回加载zip或jar包的类加载器
-
 		// 创建编译器
-		final JavaFileManager javaFileManager = new JavaClassFileManager(ucl, CompilerUtil.getFileManager());
+		final JavaClassFileManager javaFileManager = new JavaClassFileManager(ucl, CompilerUtil.getFileManager());
 
 		// classpath
 		final List<String> options = new ArrayList<>();
@@ -160,7 +191,7 @@ public class JavaSourceCompiler {
 		final DiagnosticCollector<? super JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
 		final List<JavaFileObject> javaFileObjectList = getJavaFileObject();
 		final CompilationTask task = CompilerUtil.getTask(javaFileManager, diagnosticCollector, options, javaFileObjectList);
-		try{
+		try {
 			if (task.call()) {
 				// 加载编译后的类
 				return javaFileManager.getClassLoader(StandardLocation.CLASS_OUTPUT);
@@ -180,7 +211,7 @@ public class JavaSourceCompiler {
 	private List<File> getClassPath() {
 		List<File> classPathFileList = new ArrayList<>();
 		for (File file : libraryFileList) {
-			List<File> jarOrZipFile = FileUtil.loopFiles(file, (subFile)-> JavaFileObjectUtil.isJarOrZipFile(subFile.getName()));
+			List<File> jarOrZipFile = FileUtil.loopFiles(file, (subFile) -> JavaFileObjectUtil.isJarOrZipFile(subFile.getName()));
 			classPathFileList.addAll(jarOrZipFile);
 			if (file.isDirectory()) {
 				classPathFileList.add(file);
@@ -195,16 +226,18 @@ public class JavaSourceCompiler {
 	 * @return 待编译的Java文件对象
 	 */
 	private List<JavaFileObject> getJavaFileObject() {
-		final List<JavaFileObject> collection = new ArrayList<>();
+		final List<JavaFileObject> list = new ArrayList<>();
 
-		// 源码文件
-		for (File file : sourceFileList) {
-			FileUtil.walkFiles(file, (subFile)-> collection.addAll(JavaFileObjectUtil.getJavaFileObjects(file)));
+		for (Resource resource : this.sourceList) {
+			if (resource instanceof FileResource) {
+				final File file = ((FileResource) resource).getFile();
+				FileUtil.walkFiles(file, (subFile) -> list.addAll(JavaFileObjectUtil.getJavaFileObjects(file)));
+			} else {
+				list.add(new JavaSourceFileObject(resource.getName(), resource.getStream()));
+			}
 		}
 
-		// 源码Map
-		collection.addAll(getJavaFileObjectByMap(this.sourceCodeMap));
-		return collection;
+		return list;
 	}
 
 	/**

+ 8 - 6
hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceFileObject.java

@@ -1,6 +1,8 @@
 package cn.hutool.core.compiler;
 
 import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.CharUtil;
+import cn.hutool.core.util.URLUtil;
 
 import javax.tools.SimpleJavaFileObject;
 import java.io.BufferedInputStream;
@@ -12,8 +14,8 @@ import java.nio.charset.Charset;
 /**
  * Java 源码文件对象,支持:<br>
  * <ol>
- *     <li>源文件</li>
- *     <li>代码内容</li>
+ *     <li>源文件,通过文件的uri传入</li>
+ *     <li>代码内容,通过流传入</li>
  * </ol>
  *
  * @author lzpeng
@@ -27,7 +29,7 @@ class JavaSourceFileObject extends SimpleJavaFileObject {
 	private InputStream inputStream;
 
 	/**
-	 * 构造
+	 * 构造,支持File等路径类型的源码
 	 *
 	 * @param uri  需要编译的文件uri
 	 */
@@ -36,7 +38,7 @@ class JavaSourceFileObject extends SimpleJavaFileObject {
 	}
 
 	/**
-	 * 构造
+	 * 构造,支持String类型的源码
 	 *
 	 * @param className 需要编译的类名
 	 * @param code      需要编译的类源码
@@ -46,13 +48,13 @@ class JavaSourceFileObject extends SimpleJavaFileObject {
 	}
 
 	/**
-	 * 构造
+	 * 构造,支持流中读取源码(例如zip或网络等)
 	 *
 	 * @param name        需要编译的文件名
 	 * @param inputStream 输入流
 	 */
 	protected JavaSourceFileObject(String name, InputStream inputStream) {
-		this(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension));
+		this(URLUtil.getStringURI(name.replace(CharUtil.DOT, CharUtil.SLASH) + Kind.SOURCE.extension));
 		this.inputStream = inputStream;
 	}
 

+ 5 - 1
hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java

@@ -183,7 +183,11 @@ public class FileUtil extends PathUtil {
 	}
 
 	/**
-	 * 递归遍历目录并处理目录下的文件
+	 * 递归遍历目录并处理目录下的文件,可以处理目录或文件:
+	 * <ul>
+	 *     <li>非目录则直接调用{@link Consumer}处理</li>
+	 *     <li>目录则递归调用此方法处理</li>
+	 * </ul>
 	 *
 	 * @param file     文件或目录,文件直接处理
 	 * @param consumer 文件处理器,只会处理文件

+ 43 - 8
hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java

@@ -1,21 +1,24 @@
 package cn.hutool.core.io.resource;
 
-import java.io.File;
-import java.nio.file.Path;
-
 import cn.hutool.core.io.FileUtil;
-import cn.hutool.core.util.StrUtil;
 import cn.hutool.core.util.URLUtil;
 
+import java.io.File;
+import java.io.InputStream;
+import java.io.Serializable;
+import java.net.URL;
+import java.nio.file.Path;
+
 /**
- * 文件资源访问对象
+ * 文件资源访问对象,支持{@link Path} 和 {@link File} 访问
  * 
  * @author looly
- *
  */
-public class FileResource extends UrlResource {
+public class FileResource implements Resource, Serializable {
 	private static final long serialVersionUID = 1L;
 
+	private final File file;
+
 	// ----------------------------------------------------------------------- Constructor start
 	/**
 	 * 构造
@@ -43,7 +46,7 @@ public class FileResource extends UrlResource {
 	 * @param fileName 文件名,如果为null获取文件本身的文件名
 	 */
 	public FileResource(File file, String fileName) {
-		super(URLUtil.getURL(file), StrUtil.isBlank(fileName) ? file.getName() : fileName);
+		this.file = file;
 	}
 
 	/**
@@ -56,4 +59,36 @@ public class FileResource extends UrlResource {
 	}
 	// ----------------------------------------------------------------------- Constructor end
 
+	@Override
+	public String getName() {
+		return this.file.getName();
+	}
+
+	@Override
+	public URL getUrl(){
+		return URLUtil.getURL(this.file);
+	}
+
+	@Override
+	public InputStream getStream() throws NoResourceException {
+		return FileUtil.getInputStream(this.file);
+	}
+
+	/**
+	 * 获取文件
+	 *
+	 * @return 文件
+	 */
+	public File getFile() {
+		return this.file;
+	}
+
+	/**
+	 * 返回路径
+	 * @return 返回URL路径
+	 */
+	@Override
+	public String toString() {
+		return (null == this.file) ? "null" : this.file.toString();
+	}
 }

+ 34 - 18
hutool-core/src/main/java/cn/hutool/core/io/resource/Resource.java

@@ -13,86 +13,102 @@ import java.nio.charset.Charset;
 
 /**
  * 资源接口定义<br>
- * 资源可以是文件、URL、ClassPath中的文件亦或者jar包中的文件
- * 
+ * <p>资源是数据表示的统称,我们可以将任意的数据封装为一个资源,然后读取其内容。</p>
+ * <p>资源可以是文件、URL、ClassPath中的文件亦或者jar(zip)包中的文件。</p>
+ * <p>
+ *     提供资源接口的意义在于,我们可以使用一个方法接收任意类型的数据,从而处理数据,
+ *     无需专门针对File、InputStream等写多个重载方法,同时也为更好的扩展提供了可能。
+ * </p>
+ * <p>使用非常简单,假设我们需要从classpath中读取一个xml,我们不用关心这个文件在目录中还是在jar中:</p>
+ * <pre>
+ *     Resource resource = new ClassPathResource("test.xml");
+ *     String xmlStr = resource.readUtf8Str();
+ * </pre>
+ * <p>同样,我们可以自己实现Resource接口,按照业务需要从任意位置读取数据,比如从数据库中。</p>
+ *
  * @author looly
  * @since 3.2.1
  */
 public interface Resource {
-	
+
 	/**
 	 * 获取资源名,例如文件资源的资源名为文件名
+	 *
 	 * @return 资源名
 	 * @since 4.0.13
 	 */
 	String getName();
-	
+
 	/**
-	 * 获得解析后的{@link URL}
+	 * 获得解析后的{@link URL},无对应URL的返回{@code null}
+	 *
 	 * @return 解析后的{@link URL}
 	 */
 	URL getUrl();
-	
+
 	/**
 	 * 获得 {@link InputStream}
+	 *
 	 * @return {@link InputStream}
 	 */
 	InputStream getStream();
 
 	/**
 	 * 将资源内容写出到流,不关闭输出流,但是关闭资源流
+	 *
 	 * @param out 输出流
 	 * @throws IORuntimeException IO异常
 	 * @since 5.3.5
 	 */
-	default void writeTo(OutputStream out) throws IORuntimeException{
+	default void writeTo(OutputStream out) throws IORuntimeException {
 		try (InputStream in = getStream()) {
 			IoUtil.copy(in, out);
 		} catch (IOException e) {
 			throw new IORuntimeException(e);
 		}
 	}
-	
+
 	/**
 	 * 获得Reader
+	 *
 	 * @param charset 编码
 	 * @return {@link BufferedReader}
 	 */
-	default BufferedReader getReader(Charset charset){
+	default BufferedReader getReader(Charset charset) {
 		return IoUtil.getReader(getStream(), charset);
 	}
-	
+
 	/**
 	 * 读取资源内容,读取完毕后会关闭流<br>
 	 * 关闭流并不影响下一次读取
-	 * 
+	 *
 	 * @param charset 编码
 	 * @return 读取资源内容
 	 * @throws IORuntimeException 包装{@link IOException}
 	 */
-	default String readStr(Charset charset) throws IORuntimeException{
+	default String readStr(Charset charset) throws IORuntimeException {
 		return IoUtil.read(getReader(charset));
 	}
-	
+
 	/**
 	 * 读取资源内容,读取完毕后会关闭流<br>
 	 * 关闭流并不影响下一次读取
-	 * 
+	 *
 	 * @return 读取资源内容
 	 * @throws IORuntimeException 包装IOException
 	 */
-	default String readUtf8Str() throws IORuntimeException{
+	default String readUtf8Str() throws IORuntimeException {
 		return readStr(CharsetUtil.CHARSET_UTF_8);
 	}
-	
+
 	/**
 	 * 读取资源内容,读取完毕后会关闭流<br>
 	 * 关闭流并不影响下一次读取
-	 * 
+	 *
 	 * @return 读取资源内容
 	 * @throws IORuntimeException 包装IOException
 	 */
-	default byte[] readBytes() throws IORuntimeException{
+	default byte[] readBytes() throws IORuntimeException {
 		return IoUtil.readBytes(getStream());
 	}
 }

+ 12 - 0
hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java

@@ -135,6 +135,18 @@ public class URLUtil {
 	}
 
 	/**
+	 * 获取string协议的URL,类似于string:///xxxxx
+	 *
+	 * @param content 正文
+	 * @return URL
+	 * @since 5.5.2
+	 */
+	public static URI getStringURI(CharSequence content){
+		final String contentStr = StrUtil.addPrefixIfNot(content, "string:///");
+		return URI.create(contentStr);
+	}
+
+	/**
 	 * 将URL字符串转换为URL对象,并做必要验证
 	 *
 	 * @param urlStr URL字符串

+ 45 - 15
hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java

@@ -355,19 +355,10 @@ public class ZipUtil {
 	 * @since 3.0.9
 	 */
 	public static File zip(File zipFile, String[] paths, InputStream[] ins, Charset charset) throws UtilException {
-		if (ArrayUtil.isEmpty(paths) || ArrayUtil.isEmpty(ins)) {
-			throw new IllegalArgumentException("Paths or ins is empty !");
-		}
-		if (paths.length != ins.length) {
-			throw new IllegalArgumentException("Paths length is not equals to ins length !");
-		}
-
 		ZipOutputStream out = null;
 		try {
 			out = getZipOutputStream(zipFile, charset);
-			for (int i = 0; i < paths.length; i++) {
-				add(ins[i], paths[i], out);
-			}
+			zip(out, paths, ins);
 		} finally {
 			IoUtil.close(out);
 		}
@@ -375,6 +366,45 @@ public class ZipUtil {
 	}
 
 	/**
+	 * 将文件流压缩到目标流中
+	 *
+	 * @param out   目标流,压缩完成自动关闭
+	 * @param paths 流数据在压缩文件中的路径或文件名
+	 * @param ins   要压缩的源,添加完成后自动关闭流
+	 * @since 5.5.2
+	 */
+	public static void zip(OutputStream out, String[] paths, InputStream[] ins) {
+		ZipOutputStream zipOutputStream = null;
+		try {
+			zipOutputStream = getZipOutputStream(out, DEFAULT_CHARSET);
+			zip(zipOutputStream, paths, ins);
+		} finally {
+			IoUtil.close(zipOutputStream);
+		}
+	}
+
+	/**
+	 * 将文件流压缩到目标流中
+	 *
+	 * @param zipOutputStream 目标流,压缩完成不关闭
+	 * @param paths           流数据在压缩文件中的路径或文件名
+	 * @param ins             要压缩的源,添加完成后自动关闭流
+	 * @throws IORuntimeException IO异常
+	 * @since 5.5.2
+	 */
+	public static void zip(ZipOutputStream zipOutputStream, String[] paths, InputStream[] ins) throws IORuntimeException {
+		if (ArrayUtil.isEmpty(paths) || ArrayUtil.isEmpty(ins)) {
+			throw new IllegalArgumentException("Paths or ins is empty !");
+		}
+		if (paths.length != ins.length) {
+			throw new IllegalArgumentException("Paths length is not equals to ins length !");
+		}
+		for (int i = 0; i < paths.length; i++) {
+			add(ins[i], paths[i], zipOutputStream);
+		}
+	}
+
+	/**
 	 * 对流中的数据加入到压缩文件<br>
 	 * 路径列表和流列表长度必须一致
 	 *
@@ -1056,10 +1086,10 @@ public class ZipUtil {
 	 * @param file 需要压缩的文件
 	 * @param path 在压缩文件中的路径
 	 * @param out  压缩文件存储对象
-	 * @throws UtilException IO异常
+	 * @throws IORuntimeException IO异常
 	 * @since 4.0.5
 	 */
-	private static void add(File file, String path, ZipOutputStream out) throws UtilException {
+	private static void add(File file, String path, ZipOutputStream out) throws IORuntimeException {
 		add(FileUtil.getInputStream(file), path, out);
 	}
 
@@ -1069,9 +1099,9 @@ public class ZipUtil {
 	 * @param in   需要压缩的输入流,使用完后自动关闭
 	 * @param path 压缩的路径
 	 * @param out  压缩文件存储对象
-	 * @throws UtilException IO异常
+	 * @throws IORuntimeException IO异常
 	 */
-	private static void add(InputStream in, String path, ZipOutputStream out) throws UtilException {
+	private static void add(InputStream in, String path, ZipOutputStream out) throws IORuntimeException {
 		if (null == in) {
 			return;
 		}
@@ -1079,7 +1109,7 @@ public class ZipUtil {
 			out.putNextEntry(new ZipEntry(path));
 			IoUtil.copy(in, out);
 		} catch (IOException e) {
-			throw new UtilException(e);
+			throw new IORuntimeException(e);
 		} finally {
 			IoUtil.close(in);
 			closeEntry(out);

+ 14 - 0
hutool-core/src/test/java/cn/hutool/core/io/resource/ResourceUtilTest.java

@@ -1,6 +1,8 @@
 package cn.hutool.core.io.resource;
 
+import cn.hutool.core.io.FileUtil;
 import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.StrUtil;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -10,6 +12,11 @@ public class ResourceUtilTest {
 	public void readXmlTest(){
 		final String str = ResourceUtil.readUtf8Str("test.xml");
 		Assert.assertNotNull(str);
+
+		Resource resource = new ClassPathResource("test.xml");
+		final String xmlStr = resource.readUtf8Str();
+
+		Assert.assertEquals(str, xmlStr);
 	}
 
 	@Test
@@ -19,4 +26,11 @@ public class ResourceUtilTest {
 		Assert.assertArrayEquals("testData".getBytes(), stringResource.readBytes());
 		Assert.assertArrayEquals("testData".getBytes(), IoUtil.readBytes(stringResource.getStream()));
 	}
+
+	@Test
+	public void fileResourceTest(){
+		final FileResource resource = new FileResource(FileUtil.file("test.xml"));
+		Assert.assertEquals("test.xml", resource.getName());
+		Assert.assertTrue(StrUtil.isNotEmpty(resource.readUtf8Str()));
+	}
 }

+ 6 - 6
hutool-setting/src/main/java/cn/hutool/setting/SettingLoader.java

@@ -2,7 +2,7 @@ package cn.hutool.setting;
 
 import cn.hutool.core.io.FileUtil;
 import cn.hutool.core.io.IoUtil;
-import cn.hutool.core.io.resource.UrlResource;
+import cn.hutool.core.io.resource.Resource;
 import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.CharUtil;
 import cn.hutool.core.util.CharsetUtil;
@@ -70,17 +70,17 @@ public class SettingLoader {
 	/**
 	 * 加载设置文件
 	 * 
-	 * @param urlResource 配置文件URL
+	 * @param resource 配置文件URL
 	 * @return 加载是否成功
 	 */
-	public boolean load(UrlResource urlResource) {
-		if (urlResource == null) {
+	public boolean load(Resource resource) {
+		if (resource == null) {
 			throw new NullPointerException("Null setting url define!");
 		}
-		log.debug("Load setting file [{}]", urlResource);
+		log.debug("Load setting file [{}]", resource);
 		InputStream settingStream = null;
 		try {
-			settingStream = urlResource.getStream();
+			settingStream = resource.getStream();
 			load(settingStream);
 		} catch (Exception e) {
 			log.error(e, "Load setting error!");

+ 7 - 16
hutool-setting/src/main/java/cn/hutool/setting/SettingUtil.java

@@ -1,6 +1,6 @@
 package cn.hutool.setting;
 
-import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.file.FileNameUtil;
 import cn.hutool.core.io.resource.NoResourceException;
 import cn.hutool.core.util.StrUtil;
 
@@ -27,22 +27,13 @@ public class SettingUtil {
 	 * @return 当前环境下配置文件
 	 */
 	public static Setting get(String name) {
-		Setting setting = SETTING_MAP.get(name);
-		if (null == setting) {
-			synchronized (SettingUtil.class) {
-				setting = SETTING_MAP.get(name);
-				if (null == setting) {
-					String filePath = name;
-					String extName = FileUtil.extName(filePath);
-					if (StrUtil.isEmpty(extName)) {
-						filePath = filePath + "." + Setting.EXT_NAME;
-					}
-					setting = new Setting(filePath, true);
-					SETTING_MAP.put(name, setting);
-				}
+		return SETTING_MAP.computeIfAbsent(name, (filePath)->{
+			final String extName = FileNameUtil.extName(filePath);
+			if (StrUtil.isEmpty(extName)) {
+				filePath = filePath + "." + Setting.EXT_NAME;
 			}
-		}
-		return setting;
+			return new Setting(filePath, true);
+		});
 	}
 
 	/**

+ 17 - 8
hutool-setting/src/main/java/cn/hutool/setting/dialect/Props.java

@@ -21,7 +21,6 @@ import cn.hutool.core.util.CharsetUtil;
 import cn.hutool.core.util.ReflectUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.log.StaticLog;
-import cn.hutool.setting.Setting;
 import cn.hutool.setting.SettingRuntimeException;
 
 import java.io.BufferedReader;
@@ -239,7 +238,7 @@ public final class Props extends Properties implements BasicTypeGetter<String>,
 		if (null != charset) {
 			this.charset = charset;
 		}
-		this.load(new UrlResource(propertiesUrl));
+		this.load(propertiesUrl);
 	}
 
 	/**
@@ -257,16 +256,26 @@ public final class Props extends Properties implements BasicTypeGetter<String>,
 
 	/**
 	 * 初始化配置文件
+	 *
+	 * @param url {@link URL}
+	 * @since 5.5.2
+	 */
+	public void load(URL url) {
+		load(new UrlResource(url));
+	}
+
+	/**
+	 * 初始化配置文件
 	 * 
-	 * @param urlResource {@link UrlResource}
+	 * @param resource {@link Resource}
 	 */
-	public void load(Resource urlResource) {
-		this.propertiesFileUrl = urlResource.getUrl();
+	public void load(Resource resource) {
+		this.propertiesFileUrl = resource.getUrl();
 		if (null == this.propertiesFileUrl) {
-			throw new SettingRuntimeException("Can not find properties file: [{}]", urlResource);
+			throw new SettingRuntimeException("Can not find properties file: [{}]", resource);
 		}
 
-		try (final BufferedReader reader = urlResource.getReader(charset)) {
+		try (final BufferedReader reader = resource.getReader(charset)) {
 			super.load(reader);
 		} catch (IOException e) {
 			throw new IORuntimeException(e);
@@ -277,7 +286,7 @@ public final class Props extends Properties implements BasicTypeGetter<String>,
 	 * 重新加载配置文件
 	 */
 	public void load() {
-		this.load(new UrlResource(this.propertiesFileUrl));
+		this.load(this.propertiesFileUrl);
 	}
 
 	/**

+ 16 - 16
hutool-setting/src/main/java/cn/hutool/setting/dialect/PropsUtil.java

@@ -20,7 +20,6 @@ public class PropsUtil {
 	 * 配置文件缓存
 	 */
 	private static final Map<String, Props> propsMap = new ConcurrentHashMap<>();
-	private static final Object lock = new Object();
 
 	/**
 	 * 获取当前环境下的配置文件<br>
@@ -30,22 +29,13 @@ public class PropsUtil {
 	 * @return 当前环境下配置文件
 	 */
 	public static Props get(String name) {
-		Props props = propsMap.get(name);
-		if (null == props) {
-			synchronized (lock) {
-				props = propsMap.get(name);
-				if (null == props) {
-					String filePath = name;
-					String extName = FileUtil.extName(filePath);
-					if (StrUtil.isEmpty(extName)) {
-						filePath = filePath + "." + Props.EXT_NAME;
-					}
-					props = new Props(filePath);
-					propsMap.put(name, props);
-				}
+		return propsMap.computeIfAbsent(name, (filePath)->{
+			final String extName = FileUtil.extName(filePath);
+			if (StrUtil.isEmpty(extName)) {
+				filePath = filePath + "." + Props.EXT_NAME;
 			}
-		}
-		return props;
+			return new Props(filePath);
+		});
 	}
 
 	/**
@@ -66,4 +56,14 @@ public class PropsUtil {
 		}
 		return null;
 	}
+
+	/**
+	 * 获取系统参数,例如用户在执行java命令时定义的 -Duse=hutool
+	 *
+	 * @return 系统参数Props
+	 * @since 5.5.2
+	 */
+	public static Props getSystemProps(){
+		return new Props(System.getProperties());
+	}
 }