java
/**
* Finds the resource with the given name. A resource is some data
* (images, audio, text, etc) that can be accessed by class code in a way
* that is independent of the location of the code.
*
* <p> The name of a resource is a '<tt>/</tt>'-separated path name that
* identifies the resource.
*
* <p> This method will first search the parent class loader for the
* resource; if the parent is <tt>null</tt> the path of the class loader
* built-in to the virtual machine is searched. That failing, this method
* will invoke {@link #findResource(String)} to find the resource. </p>
*
* @apiNote When overriding this method it is recommended that an
* implementation ensures that any delegation is consistent with the {@link
* #getResources(java.lang.String) getResources(String)} method.
*
* @param name
* The resource name
*
* @return A <tt>URL</tt> object for reading the resource, or
* <tt>null</tt> if the resource could not be found or the invoker
* doesn't have adequate privileges to get the resource.
*
* @since 1.1
*/
public URL getResource(String name) {
URL url;
if (parent != null) {
url = parent.getResource(name);
} else {
url = getBootstrapResource(name);
}
if (url == null) {
url = findResource(name);
}
return url;
}
/**
* Finds all the resources with the given name. A resource is some data
* (images, audio, text, etc) that can be accessed by class code in a way
* that is independent of the location of the code.
*
* <p>The name of a resource is a <tt>/</tt>-separated path name that
* identifies the resource.
*
* <p> The search order is described in the documentation for {@link
* #getResource(String)}. </p>
*
* @apiNote When overriding this method it is recommended that an
* implementation ensures that any delegation is consistent with the {@link
* #getResource(java.lang.String) getResource(String)} method. This should
* ensure that the first element returned by the Enumeration's
* {@code nextElement} method is the same resource that the
* {@code getResource(String)} method would return.
*
* @param name
* The resource name
*
* @return An enumeration of {@link java.net.URL <tt>URL</tt>} objects for
* the resource. If no resources could be found, the enumeration
* will be empty. Resources that the class loader doesn't have
* access to will not be in the enumeration.
*
* @throws IOException
* If I/O errors occur
*
* @see #findResources(String)
*
* @since 1.2
*/
public Enumeration<URL> getResources(String name) throws IOException {
@SuppressWarnings("unchecked")
Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[2];
if (parent != null) {
tmp[0] = parent.getResources(name);
} else {
tmp[0] = getBootstrapResources(name);
}
tmp[1] = findResources(name);
return new CompoundEnumeration<>(tmp);
}
/**
* Finds the resource with the given name. Class loader implementations
* should override this method to specify where to find resources.
*
* @param name
* The resource name
*
* @return A <tt>URL</tt> object for reading the resource, or
* <tt>null</tt> if the resource could not be found
*
* @since 1.2
*/
protected URL findResource(String name) {
return null;
}
/**
* Returns an enumeration of {@link java.net.URL <tt>URL</tt>} objects
* representing all the resources with the given name. Class loader
* implementations should override this method to specify where to load
* resources from.
*
* @param name
* The resource name
*
* @return An enumeration of {@link java.net.URL <tt>URL</tt>} objects for
* the resources
*
* @throws IOException
* If I/O errors occur
*
* @since 1.2
*/
protected Enumeration<URL> findResources(String name) throws IOException {
return java.util.Collections.emptyEnumeration();
}
/**
* Registers the caller as parallel capable.
* The registration succeeds if and only if all of the following
* conditions are met:
* <ol>
* <li> no instance of the caller has been created</li>
* <li> all of the super classes (except class Object) of the caller are
* registered as parallel capable</li>
* </ol>
* <p>Note that once a class loader is registered as parallel capable, there
* is no way to change it back.</p>
*
* @return true if the caller is successfully registered as
* parallel capable and false if otherwise.
*
* @since 1.7
*/
@CallerSensitive
protected static boolean registerAsParallelCapable() {
Class<? extends ClassLoader> callerClass =
Reflection.getCallerClass().asSubclass(ClassLoader.class);
return ParallelLoaders.register(callerClass);
}
/**
* Find a resource of the specified name from the search path used to load
* classes. This method locates the resource through the system class
* loader (see {@link #getSystemClassLoader()}).
*
* @param name
* The resource name
*
* @return A {@link java.net.URL <tt>URL</tt>} object for reading the
* resource, or <tt>null</tt> if the resource could not be found
*
* @since 1.1
*/
public static URL getSystemResource(String name) {
ClassLoader system = getSystemClassLoader();
if (system == null) {
return getBootstrapResource(name);
}
return system.getResource(name);
}
/**
* Finds all resources of the specified name from the search path used to
* load classes. The resources thus found are returned as an
* {@link java.util.Enumeration <tt>Enumeration</tt>} of {@link
* java.net.URL <tt>URL</tt>} objects.
*
* <p> The search order is described in the documentation for {@link
* #getSystemResource(String)}. </p>
*
* @param name
* The resource name
*
* @return An enumeration of resource {@link java.net.URL <tt>URL</tt>}
* objects
*
* @throws IOException
* If I/O errors occur
* @since 1.2
*/
public static Enumeration<URL> getSystemResources(String name)
throws IOException
{
ClassLoader system = getSystemClassLoader();
if (system == null) {
return getBootstrapResources(name);
}
return system.getResources(name);
}
/**
* Find resources from the VM's built-in classloader.
*/
private static URL getBootstrapResource(String name) {
URLClassPath ucp = getBootstrapClassPath();
Resource res = ucp.getResource(name);
return res != null ? res.getURL() : null;
}
/**
* Find resources from the VM's built-in classloader.
*/
private static Enumeration<URL> getBootstrapResources(String name)
throws IOException
{
final Enumeration<Resource> e =
getBootstrapClassPath().getResources(name);
return new Enumeration<URL> () {
public URL nextElement() {
return e.nextElement().getURL();
}
public boolean hasMoreElements() {
return e.hasMoreElements();
}
};
}
// Returns the URLClassPath that is used for finding system resources.
static URLClassPath getBootstrapClassPath() {
return sun.misc.Launcher.getBootstrapClassPath();
}
/**
* Returns an input stream for reading the specified resource.
*
* <p> The search order is described in the documentation for {@link
* #getResource(String)}. </p>
*
* @param name
* The resource name
*
* @return An input stream for reading the resource, or <tt>null</tt>
* if the resource could not be found
*
* @since 1.1
*/
public InputStream getResourceAsStream(String name) {
URL url = getResource(name);
try {
return url != null ? url.openStream() : null;
} catch (IOException e) {
return null;
}
}
/**
* Open for reading, a resource of the specified name from the search path
* used to load classes. This method locates the resource through the
* system class loader (see {@link #getSystemClassLoader()}).
*
* @param name
* The resource name
*
* @return An input stream for reading the resource, or <tt>null</tt>
* if the resource could not be found
*
* @since 1.1
*/
public static InputStream getSystemResourceAsStream(String name) {
URL url = getSystemResource(name);
try {
return url != null ? url.openStream() : null;
} catch (IOException e) {
return null;
}
}
以上代码是 Java 标准库中 java.lang.ClassLoader 类的一部分,主要涉及 资源(resource)加载机制。理解这些方法对深入掌握 Java 类加载机制、模块化系统、以及框架(如 Spring、Hibernate、MyBatis 等)如何加载配置文件、模板、静态资源等至关重要。
一、核心概念:什么是"Resource"?
在 Java 中,Resource 是指与类代码逻辑相关的非代码数据,例如:
- 配置文件(如
application.properties) - XML 映射文件(如 MyBatis 的
mapper.xml) - 图片、音频、文本等静态资源
- 国际化语言包(
.properties)
这些资源通常和 .class 文件一起打包在 JAR/WAR 中,或者放在 classpath 目录下。
✅ 关键点 :Resource 的路径是相对于 classpath 根目录 的,用
/分隔(不是平台相关的\或/)。
二、核心方法解析
1. public URL getResource(String name)
功能:
根据资源名(如 "config/app.properties")返回一个 URL,可用于读取该资源。
搜索顺序(双亲委派模型):
- 先委托 父类加载器(parent ClassLoader)查找;
- 如果父加载器为
null(即启动类加载器 Bootstrap ClassLoader),则调用getBootstrapResource(); - 若仍未找到,则调用子类可重写的
findResource(name)方法(由当前 ClassLoader 自己查找)。
🔁 这体现了 双亲委派模型(Parent Delegation Model),保证核心资源优先由上级加载器处理,避免重复或冲突。
返回值:
- 成功:
URL对象(如jar:file:/xxx.jar!/config/app.properties) - 失败:
null
2. public Enumeration<URL> getResources(String name)
功能:
返回所有匹配名称的资源(可能有多个同名资源分布在不同 JAR 或目录中)。
使用场景:
- SPI(Service Provider Interface)机制:如
META-INF/services/javax.servlet.ServletContainerInitializer - 多模块项目中合并多个
application.properties
注意:
- 返回的是
Enumeration<URL>,需遍历。 - 第一个元素应与
getResource()返回结果一致(API 注释强调一致性)。
3. protected URL findResource(String name) 和 protected Enumeration<URL> findResources(String name)
作用:
这两个是 模板方法 ,供自定义 ClassLoader 子类实现具体资源查找逻辑。
- 默认实现:
findResource返回null,findResources返回空枚举。 - 实际实现如
URLClassLoader会从指定的 URL 路径(如 JAR、目录)中查找资源。
4. 静态方法:getSystemResource / getSystemResources / getSystemResourceAsStream
功能:
通过 系统类加载器(System ClassLoader) 加载资源,等价于:
java
ClassLoader.getSystemClassLoader().getResource(name);
📌 系统类加载器通常是
AppClassLoader(应用类加载器),负责加载-classpath指定的类和资源。
使用场景:
- 工具类中直接读取 classpath 资源(不依赖当前类的 ClassLoader)
- 启动时加载全局配置
5. getResourceAsStream(String name)
功能:
直接返回 InputStream,省去手动 openStream() 的步骤。
内部实现:
java
URL url = getResource(name);
return url != null ? url.openStream() : null;
⚠️ 注意:如果资源不存在或 I/O 出错,返回
null(不会抛异常!)
三、常见使用方式示例
1. 从当前类的 ClassLoader 读取资源
java
InputStream is = MyClass.class.getClassLoader().getResourceAsStream("config/db.properties");
2. 从当前类所在包的相对路径读取(注意开头无 /)
java
// 假设 MyClass 在 com.example 包下
InputStream is = MyClass.class.getResourceAsStream("local-config.txt");
// 实际路径:com/example/local-config.txt
💡
Class.getResource()与ClassLoader.getResource()的区别:
Class.getResource(path):
- 若
path以/开头 → 从 classpath 根开始- 否则 → 从当前类所在包开始
ClassLoader.getResource(path):始终从 classpath 根开始
四、在主流框架中的应用
1. Spring Framework
ResourceLoader接口抽象了资源加载,底层大量使用ClassLoader.getResource()。@PropertySource("classpath:app.properties")→ 通过 ClassLoader 查找。ApplicationContext启动时扫描META-INF/spring.factories(使用getResources()支持多 JAR 合并)。
2. MyBatis
-
Mapper XML 文件通常通过:
javaInputStream is = Resources.getResourceAsStream("mappers/UserMapper.xml");其中
Resources工具类内部调用ClassLoader.getResourceAsStream()。
3. Hibernate / JPA
persistence.xml必须位于META-INF/persistence.xml,由 PersistenceProvider 通过ClassLoader.getResources("META-INF/persistence.xml")发现。
4. SPI 机制(Java ServiceLoader)
-
ServiceLoader.load(MyService.class)会调用:javaclassloader.getResources("META-INF/services/com.example.MyService");从而发现所有 JAR 中提供的实现。
五、注意事项 & 最佳实践
| 问题 | 建议 |
|---|---|
| 资源路径错误 | 使用 / 分隔,不要用 \;确认是否从 classpath 根开始 |
| 打包后资源找不到 | 确保构建工具(Maven/Gradle)将资源复制到 target/classes |
| 多个同名资源 | 使用 getResources() 遍历,避免只取第一个导致遗漏 |
| 安全管理器限制 | 在受限环境(如 Applet)中可能返回 null |
| 流未关闭 | 使用 try-with-resources 确保 InputStream 关闭 |
六、总结
| 方法 | 用途 | 是否静态 | 委托父加载器 |
|---|---|---|---|
getResource() |
获取单个资源 URL | 否 | ✅ |
getResources() |
获取所有同名资源 | 否 | ✅ |
getSystemResource() |
通过系统类加载器获取 | ✅ | ❌(直接用系统加载器) |
getResourceAsStream() |
直接获取输入流 | 否 | ✅ |
findResource() |
子类实现具体查找逻辑 | 否 | ❌(自身实现) |
💡 核心思想 :Java 的资源加载机制基于 ClassLoader 的双亲委派模型,保证了资源查找的安全性、一致性和可扩展性。理解它,就掌握了 Java 应用加载配置、插件、静态文件的底层逻辑。
如果你正在开发自定义 ClassLoader、模块化系统、或需要动态加载资源(如热部署、插件系统),这些方法就是你的基石。需要我进一步举例说明如何自定义 ClassLoader 实现资源加载吗?