资源管理RESOURCE
大约 13 分钟
Resource 作用分析
Spring 资源读取
- 在一个完整的项目应用中,经常会存在有读取资源数据文件的需要,这些资源数据文件可能存在于当前项目应用之中,也有可能存在于本地的数据磁盘之中,或者保存在远程服务器端
- 在原生的 Java 项目开发之中,文件资源可以通过 java.io.File 类进行定位,而网络资源可以利用 java.net.URL 类进行定位,但是项目中的资源(CLASSPATH 之中定位),或者说是 jar 文件中的资源读取就不这么方便了。
Resource 接口
- 为了解决资源读取的统一管理问题,在 Spring 开发框架中对这一功能进行了抽象,提供了 org.springframework.core.io.Resource 接口
Resource 资源读取
Resource 接口子类
- Resource 所提供的是一个资源读取的接口标准,而具体的资源读取位置是由 Resource 接口子类所定义的,该接口的常用子类定义如图所示,在该接口中提供了获取 InputStream 对象实例的处理方法,利用此方法就可以实现数据的读取
1、
package com.yootk.main;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
public class YootkResourceDemo {
public static void main(String[] args) throws Exception { // 该抛就抛
byte data [] = "沐言科技:www.yootk.com".getBytes(); // 字节数组
Resource resource = new ByteArrayResource(data); // 获取内存资源
InputStream input = resource.getInputStream(); // 获取输入流对象
ByteArrayOutputStream bos = new ByteArrayOutputStream(); // IO提供的内存输出流
int temp = 0;
while ((temp = input.read()) != -1) { // 现在有数据了
if (temp >= 'a' && temp <= 'z') { // 小写字母
bos.write(Character.toUpperCase(temp)); // 转大写字母
} else {
bos.write(temp); // 直接保存
}
}
input.close();
bos.close();
System.out.println(bos);
}
}
2、
package com.yootk.main;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
public class YootkResourceDemo {
public static void main(String[] args) throws Exception { // 该抛就抛
String path = "h:" + File.separator + "muyan" + File.separator + "message.txt";
Resource resource = new FileSystemResource(path); // 获取文件资源
InputStream input = resource.getInputStream(); // 获取输入流对象
ByteArrayOutputStream bos = new ByteArrayOutputStream(); // IO提供的内存输出流
int temp = 0;
while ((temp = input.read()) != -1) { // 现在有数据了
if (temp >= 'a' && temp <= 'z') { // 小写字母
bos.write(Character.toUpperCase(temp)); // 转大写字母
} else {
bos.write(temp); // 直接保存
}
}
input.close();
bos.close();
System.out.println(bos);
}
}
3、
package com.yootk.main;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
public class YootkResourceDemo {
public static void main(String[] args) throws Exception { // 该抛就抛
String path = "https://www.jd.com"; // 要读取的资源
Resource resource = new UrlResource(path); // 获取文件资源
InputStream input = resource.getInputStream(); // 获取输入流对象
ByteArrayOutputStream bos = new ByteArrayOutputStream(); // IO提供的内存输出流
int temp = 0;
while ((temp = input.read()) != -1) { // 现在有数据了
if (temp >= 'a' && temp <= 'z') { // 小写字母
bos.write(Character.toUpperCase(temp)); // 转大写字母
} else {
bos.write(temp); // 直接保存
}
}
input.close();
bos.close();
System.out.println(bos);
}
}
ClassPathResource
- 完整的项目应用之中,除了包含有程序代码之外,也可能会存在有若干个数据文件,这些文件由于都保存在项目应用环境下,所以此类的资源被统一称为 CLASSPATH 资源
1、
package com.yootk.main;
public class YootkClassPathDemo {
public static void main(String[] args) throws Exception { // 该抛就抛
System.out.println("1、项目路径:" + System.getProperty("user.dir"));
System.out.println("2、项目路径:" + YootkClassPathDemo.class.getClassLoader().getResource(""));
}
}
2、
package com.yootk.main;
import org.springframework.util.ClassUtils;
public class YootkClassPathDemo {
public static void main(String[] args) throws Exception { // 该抛就抛
System.out.println("1、ClassLoader" + YootkClassPathDemo.class.getClassLoader());
System.out.println("2、ClassLoader" + ClassUtils.getDefaultClassLoader());
}
}
3、
package com.yootk.main;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
public class YootkResourceDemo {
public static void main(String[] args) throws Exception { // 该抛就抛
Resource resource = new ClassPathResource("message.txt"); // 获取文件资源
InputStream input = resource.getInputStream(); // 获取输入流对象
ByteArrayOutputStream bos = new ByteArrayOutputStream(); // IO提供的内存输出流
int temp = 0;
while ((temp = input.read()) != -1) { // 现在有数据了
if (temp >= 'a' && temp <= 'z') { // 小写字母
bos.write(Character.toUpperCase(temp)); // 转大写字母
} else {
bos.write(temp); // 直接保存
}
}
input.close();
bos.close();
System.out.println(bos);
}
}
WritableResource
WritableResource 资源输出接
- Spring 在其早期设计时,为了便于资源的统一读取而提供了 Resource 接口,开发者可以利用该接口获取指定资源的 InputStream 对象实例,但是随着后期版本的更新以及实际的需要,在 Spring 3.x 版本后提供了一个 WritableResource 资源写入接口,该接口为 Resource 子接口,并且提供了 OutputStream 实例获取方法
1、
package com.yootk.main;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.WritableResource;
import java.io.File;
import java.io.OutputStream;
public class YootkResourceDemo {
public static void main(String[] args) throws Exception { // 该抛就抛
String path = "h:" + File.separator + "muyan" +
File.separator + "yootk" + File.separator + "message.txt";
File file = new File(path); // 定义文件对象
if (!file.getParentFile().exists()) { // 文件是否存在
file.getParentFile().mkdirs(); // 创建父目录
}
WritableResource resource = new FileSystemResource(file);
OutputStream output = resource.getOutputStream();
for (int x = 0; x < 10; x++) {
output.write("沐言科技:www.yootk.com\r\n".getBytes()); // 数据的写入
}
output.close();
}
}
资源读写与 NIO 支持
Resource 与 NIO 关联
- Spring4.x 版本之后为了进一步提升资源读写的处理性能,提供了与 NIO 的支持连接相关结构如图所示。在 Resource 接口之中提供了一个 readableChannel()方法,该方法可以返回一个 ReadableByteChannel 接口实例,而在 WritableResource 子接口之中也提供了一个 writableChannel()方法,用于返回 WritableBvteChannel 接口的实例,利用这两个接口实例即可实现 NIO 通道读写操作。
package com.yootk.main;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.WritableResource;
import java.io.File;
import java.nio.ByteBuffer;
import java.nio.channels.WritableByteChannel;
public class YootkResourceDemo {
public static void main(String[] args) throws Exception { // 该抛就抛
String path = "h:" + File.separator + "muyan" +
File.separator + "yootk" + File.separator + "message.txt";
File file = new File(path); // 定义文件对象
if (!file.getParentFile().isFile()) { // 文件是否存在
file.getParentFile().mkdirs(); // 创建父目录
file.createNewFile();
}
WritableResource resource = new FileSystemResource(file);
WritableByteChannel channel = resource.writableChannel(); // 写入通道
byte data [] = "沐言科技:www.yootk.com".getBytes(); // 写入资源
ByteBuffer buffer = ByteBuffer.wrap(data); // 定义字节缓冲区
channel.write(buffer); // 数据写入
channel.close();
}
}
2、
package com.yootk.main;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.WritableResource;
import java.io.File;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
public class YootkResourceDemo {
public static void main(String[] args) throws Exception { // 该抛就抛
String path = "h:" + File.separator + "muyan" +
File.separator + "yootk" + File.separator + "message.txt";
File file = new File(path);
if (!file.exists()) {
return; // 结束代码调用
}
Resource resource = new FileSystemResource(file);
ReadableByteChannel channel = resource.readableChannel(); // 读取通道
ByteBuffer buffer = ByteBuffer.allocate(30); // 分配字节缓冲区
channel.read(buffer); // 将数据读取到缓冲区之中
buffer.flip(); // 重设缓冲区
byte data [] = new byte[30]; // 通过字节数组读取缓冲区数据
int foot = 0; // 字节数组的处理角标
while(buffer.hasRemaining()) {
data[foot ++] = buffer.get(); // 获取字节数据
}
System.out.println(new String(data, 0, foot));
channel.close();
}
}
ResourceLoader
ResourceLoader
- Spring 提供的 Resource 接口是一个资源操作标准实现,如果开发者需要读取不同位置资源的时候,只需要使用其特定的子类即可完成,但是这样的做法明显会造成代码的耦合度增加,不符合当前的主流设计思想。那么此时最佳的做法是通过一个工厂类的形式来实现 Resource 的资源管理,所以在 Spring 中提供了 ResourceLoader 接口,该接口可以直接根据特定的资源定位标记来实现不同资源的获取
1、
package com.yootk.main;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import java.util.Scanner;
public class YootkResourceDemo {
public static void main(String[] args) throws Exception { // 该抛就抛
ResourceLoader loader = new DefaultResourceLoader(); // 实例化接口对象
Resource resource = loader.getResource("file:h:/muyan/messsage.txt");
Scanner scanner = new Scanner(resource.getInputStream()); // 获取输入流
scanner.useDelimiter("\n"); // 设置换行标记
while (scanner.hasNext()) { // 循环获取数据
String result = scanner.next();
System.out.println(result);
}
scanner.close();
}
}
2、
package com.yootk.main;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import java.util.Scanner;
public class YootkResourceDemo {
public static void main(String[] args) throws Exception { // 该抛就抛
ResourceLoader loader = new DefaultResourceLoader(); // 实例化接口对象
// ResourceUtils.CLASSPATH_URL_PREFIX + "message.txt";
Resource resource = loader.getResource("classpath:message.txt");
Scanner scanner = new Scanner(resource.getInputStream()); // 获取输入流
scanner.useDelimiter("\n"); // 设置换行标记
while (scanner.hasNext()) { // 循环获取数据
String result = scanner.next();
System.out.println(result);
}
scanner.close();
}
}
3、
package com.yootk.main;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import java.util.Scanner;
public class YootkResourceDemo {
public static void main(String[] args) throws Exception { // 该抛就抛
ResourceLoader loader = new DefaultResourceLoader(); // 实例化接口对象
Resource resource = loader.getResource("http://www.yootk.com");
Scanner scanner = new Scanner(resource.getInputStream()); // 获取输入流
scanner.useDelimiter("\n"); // 设置换行标记
while (scanner.hasNext()) { // 循环获取数据
String result = scanner.next();
System.out.println(result);
}
scanner.close();
}
}
Resource 资源注入
XML 配置注入资源
- 在项目开发之中很多的资源都是需要通过外部配置的,但是当有了 Resource 接口之后项目中就可以对资源读取的功能进行统一的处理,而这一点最明显的优势在于,可以直接通过 XML 配置文件的形式实现资源注入
1、
package com.yootk.resource;
import org.springframework.core.io.Resource;
public class MessageResource { // 消息资源读取
private Resource resource; // 配置资源实例
public void setResource(Resource resource) {
this.resource = resource;
}
public Resource getResource() {
return resource;
}
}
2、
<!-- 此时的Bean不使用注解的方式配置,而是直接通过XML配置文件的方式进行定义 -->
<bean id="messageResource" class="com.yootk.resource.MessageResource">
<!-- 此时需要配置一个字符串,那么这个字符串会自动的转为Resource接口实例 -->
<property name="resource" value="classpath:message.txt"/>
</bean>
3、
package com.yootk.test;
import com.yootk.resource.MessageResource;
import com.yootk.service.IDeptService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.util.Scanner;
// 表示要进行Spring配置文件的加载,后续也可能是进行配置类的加载
@ContextConfiguration(locations = {"classpath:spring/spring-base.xml"}) // 定义XML配置文件
@ExtendWith(SpringExtension.class) // 表示此时使用外部的测试工具(JUnit5)
public class TestMessageResource {
private static final Logger LOGGER =
LoggerFactory.getLogger(TestMessageResource.class) ;// 获取日志实例
@Autowired
private MessageResource messageResource; // 注入对象实例
@Test
public void testPrint() throws Exception { // 直接抛出异常
LOGGER.info("Resource接口实现类型:{}", this.messageResource.getResource().getClass());
Scanner scanner = new Scanner(this.messageResource.getResource().getInputStream()); // 获取输入流
scanner.useDelimiter("\n"); // 设置换行标记
StringBuffer buffer = new StringBuffer(); // 保存所有读取到的数据内容
while (scanner.hasNext()) { // 循环获取数据
String result = scanner.next();
buffer.append(result); // 连接读取数据项
}
scanner.close();
LOGGER.info("资源数据的内容:{}", buffer);
}
}
4、
<!-- 此时的Bean不使用注解的方式配置,而是直接通过XML配置文件的方式进行定义 -->
<bean id="messageResource" class="com.yootk.resource.MessageResource">
<!-- 此时需要配置一个字符串,那么这个字符串会自动的转为Resource接口实例 -->
<property name="resource" value="file:h:\\muyan\\message.txt"/>
</bean>
5、
<!-- 此时的Bean不使用注解的方式配置,而是直接通过XML配置文件的方式进行定义 -->
<bean id="messageResource" class="com.yootk.resource.MessageResource">
<!-- 此时需要配置一个字符串,那么这个字符串会自动的转为Resource接口实例 -->
<property name="resource" value="http://www.yootk.com"/>
</bean>
路径通配符
路径通配符
为了便于资源的定位与读取,在 Spring 开发框架里引用了 Ant 构建工具的之中所定义的通配符以实现不同层级或者名称匹配的资源加载,开发者可以使用如下几种通配符处理:
- ?:表示匹配任意的一位字符,例如:“spring?.xml”则表示匹配“spring1.xml”,"spring.xml",'springa.xm"
- *表示匹配零位、一位或多位字符,例如“spring-*.xml”则表示“spring-service.xml”'spring-action.xm'
- “**”:表示匹配任意的目录。
1、
package com.yootk.resource;
import org.springframework.core.io.Resource;
public class MessageResource { // 消息资源读取
private Resource resource[]; // 配置资源实例
public void setResource(Resource[] resource) {
this.resource = resource;
}
public Resource[] getResource() {
return resource;
}
}
2、
<bean id="messageResource" class="com.yootk.resource.MessageResource">
<!-- 此时编写了一个路径的匹配符号,这样只要符合此匹配规则的资源都会加载进来 -->
<property name="resource" value="classpath:muyan/spring?.xml"/>
</bean>
3、
package com.yootk.test;
import com.yootk.resource.MessageResource;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
// 表示要进行Spring配置文件的加载,后续也可能是进行配置类的加载
@ContextConfiguration(locations = {"classpath:spring/spring-base.xml"}) // 定义XML配置文件
@ExtendWith(SpringExtension.class) // 表示此时使用外部的测试工具(JUnit5)
public class TestMessageResource {
private static final Logger LOGGER =
LoggerFactory.getLogger(TestMessageResource.class) ;// 获取日志实例
@Autowired
private MessageResource messageResource; // 注入对象实例
@Test
public void testPrint() throws Exception { // 直接抛出异常
for (Resource resource : this.messageResource.getResource()) {
LOGGER.info("资源路径:{}", resource.getFile());
}
}
}
4、
<bean id="messageResource" class="com.yootk.resource.MessageResource">
<!-- 此时编写了一个路径的匹配符号,这样只要符合此匹配规则的资源都会加载进来 -->
<property name="resource" value="classpath:muyan/spring*.xml"/>
</bean>
5、
<!-- 此时的Bean不使用注解的方式配置,而是直接通过XML配置文件的方式进行定义 -->
<bean id="messageResource" class="com.yootk.resource.MessageResource">
<!-- 此时编写了一个路径的匹配符号,这样只要符合此匹配规则的资源都会加载进来 -->
<property name="resource" value="classpath:muyan/**/spring*.xml"/>
</bean>
6、
资源路径:H:\workspace\idea\yootk-spring\yootk-spring\base\build\resources\main\muyan\spring.xml
资源路径:H:\workspace\idea\yootk-spring\yootk-spring\base\build\resources\main\muyan\spring1.xml
资源路径:H:\workspace\idea\yootk-spring\yootk-spring\base\build\resources\main\muyan\spring2.xml
资源路径:H:\workspace\idea\yootk-spring\yootk-spring\base\build\resources\main\muyan\springYootk.xml
资源路径:H:\workspace\idea\yootk-spring\yootk-spring\base\build\resources\main\muyan\yootk\springa.xml
资源路径:H:\workspace\idea\yootk-spring\yootk-spring\base\build\resources\main\muyan\yootk\springb.xml
7、
<!-- 此时的Bean不使用注解的方式配置,而是直接通过XML配置文件的方式进行定义 -->
<bean id="messageResource" class="com.yootk.resource.MessageResource">
<!-- 此时编写了一个路径的匹配符号,这样只要符合此匹配规则的资源都会加载进来 -->
<property name="resource" value="classpath*:**/META-INF/*.MF"/>
</bean>
8、
package com.yootk.test;
import com.yootk.resource.MessageResource;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
// 表示要进行Spring配置文件的加载,后续也可能是进行配置类的加载
@ContextConfiguration(locations = {"classpath:spring/spring-base.xml"}) // 定义XML配置文件
@ExtendWith(SpringExtension.class) // 表示此时使用外部的测试工具(JUnit5)
public class TestMessageResource {
private static final Logger LOGGER =
LoggerFactory.getLogger(TestMessageResource.class) ;// 获取日志实例
@Autowired
private MessageResource messageResource; // 注入对象实例
@Test
public void testPrint() throws Exception { // 直接抛出异常
for (Resource resource : this.messageResource.getResource()) {
LOGGER.info("资源路径:{}", resource);
}
}
}
9、
0, hitCount = 3, missCount = 1]
资源路径:URL [jar:file:H:\workspace\gradle_repository\caches\modules-2\files-2.1\ch.qos.logback\logback-classic\1.2.11\4741689214e9d1e8408b206506cbe76d1c6a7d60\logback-classic-1.2.11.jar!/META-INF/MANIFEST.MF]
资源路径:URL [jar:file:H:\workspace\gradle_repository\caches\modules-2\files-2.1\org.slf4j\slf4j-api\1.7.36\6c62681a2f655b49963a5983b8b0950a6120ae14\slf4j-api-1.7.36.jar!/META-INF/MANIFEST.MF]
资源路径:URL [jar:file:H:\workspace\gradle_repository\caches\modules-2\files-2.1\org.springframework\spring-context-support\6.0.0-M3\d5d7afde0966bf823820babdc9168625324601c0\spring-context-support-6.0.0-M3.jar!/META-INF/MANIFEST.MF]
资源路径:URL [jar:file:H:\workspace\gradle_repository\caches\modules-2\files-2.1\org.springframework\spring-context\6.0.0-M3\6e1ed70ffbbc7441a24ec6423c6d641264df9b4b\spring-context-6.0.0-M3.jar!/META-INF/MANIFEST.MF]
资源路径:URL [jar:file:H:\workspace\gradle_repository\caches\modules-2\files-2.1\org.springframework\spring-aop\6.0.0-M3\e44fb1d853f622e8411054cbb5f037895738e0df\spring-aop-6.0.0-M3.jar!/META-INF/MANIFEST.MF]
资源路径:URL [jar:file:H:\workspace\gradle_repository\caches\modules-2\files-2.1\org.springframework\spring-beans\6.0.0-M3\39a8d8acd110bf9b05072a4459141b777bab57e5\spring-beans-6.0.0-M3.jar!/META-INF/MANIFEST.MF]
资源路径:URL [jar:file:H:\workspace\gradle_repository\caches\modules-2\files-2.1\org.springframework\spring-test\6.0.0-M3\e8bf1436f578b31419dbd6e8d9811be3e679bf3e\spring-test-6.0.0-M3.jar!/META-INF/MANIFEST.MF]
资源路径:URL [jar:file:H:\workspace\gradle_repository\caches\modules-2\files-2.1\org.springframework\spring-expression\6.0.0-M3\a0b940a234da87701702c67787b5944246073a6f\spring-expression-6.0.0-M3.jar!/META-INF/MANIFEST.MF]
资源路径:URL [jar:file:H:\workspace\gradle_repository\caches\modules-2\files-2.1\org.springframework\spring-core\6.0.0-M3\d2f58962ea47181eeeaae7a20f6c4b80501cd0bf\spring-core-6.0.0-M3.jar!/META-INF/MANIFEST.MF]
资源路径:URL [jar:file:H:\workspace\gradle_repository\caches\modules-2\files-2.1\org.junit.jupiter\junit-jupiter-engine\5.8.2\c598b4328d2f397194d11df3b1648d68d7d990e3\junit-jupiter-engine-5.8.2.jar!/META-INF/MANIFEST.MF]
资源路径:URL [jar:file:H:\workspace\gradle_repository\caches\modules-2\files-2.1\org.junit.jupiter\junit-jupiter-api\5.8.2\4c21029217adf07e4c0d0c5e192b6bf610c94bdc\junit-jupiter-api-5.8.2.jar!/META-INF/MANIFEST.MF]
资源路径:URL [jar:file:H:\workspace\gradle_repository\caches\modules-2\files-2.1\org.junit.vintage\junit-vintage-engine\5.8.2\64dde404f2db8b0e2ec6a53d31f4a076e298b1d1\junit-vintage-engine-5.8.2.jar!/META-INF/MANIFEST.MF]
资源路径:URL [jar:file:H:\workspace\gradle_repository\caches\modules-2\files-2.1\org.junit.platform\junit-platform-launcher\1.8.2\c334fcee82b81311ab5c426ec2d52d467c8d0b28\junit-platform-launcher-1.8.2.jar!/META-INF/MANIFEST.MF]
资源路径:URL [jar:file:H:\workspace\gradle_repository\caches\modules-2\files-2.1\org.junit.platform\junit-platform-engine\1.8.2\b737de09f19864bd136805c84df7999a142fec29\junit-platform-engine-1.8.2.jar!/META-INF/MANIFEST.MF]
资源路径:URL [jar:file:H:\workspace\gradle_repository\caches\modules-2\files-2.1\org.junit.platform\junit-platform-commons\1.8.2\32c8b8617c1342376fd5af2053da6410d8866861\junit-platform-commons-1.8.2.jar!/META-INF/MANIFEST.MF]
资源路径:URL [jar:file:H:\workspace\gradle_repository\caches\modules-2\files-2.1\ch.qos.logback\logback-core\1.2.11\a01230df5ca5c34540cdaa3ad5efb012f1f1f792\logback-core-1.2.11.jar!/META-INF/MANIFEST.MF]
资源路径:URL [jar:file:H:\workspace\gradle_repository\caches\modules-2\files-2.1\org.springframework\spring-jcl\6.0.0-M3\81e03555a2476762a8856b34a7144dc3e28a9932\spring-jcl-6.0.0-M3.jar!/META-INF/MANIFEST.MF]
资源路径:URL [jar:file:H:\workspace\gradle_repository\caches\modules-2\files-2.1\org.opentest4j\opentest4j\1.2.0\28c11eb91f9b6d8e200631d46e20a7f407f2a046\opentest4j-1.2.0.jar!/META-INF/MANIFEST.MF]
资源路径:URL [jar:file:H:\workspace\gradle_repository\caches\modules-2\files-2.1\junit\junit\4.13.2\8ac9e16d933b6fb43bc7f576336b8f4d7eb5ba12\junit-4.13.2.jar!/META-INF/MANIFEST.MF]
资源路径:URL [jar:file:C:\Users\yootk\.m2\repository\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar!/META-INF/MANIFEST.MF]
demo