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 類加載機制、模塊化系統(tǒng)、以及框架(如 Spring、Hibernate、MyBatis 等)如何加載配置文件、模板、靜態(tài)資源等至關重要。
一、核心概念:什么是“Resource”?
在 Java 中,Resource 是指與類代碼邏輯相關的非代碼數(shù)據(jù),例如:
- 配置文件(如
application.properties) - XML 映射文件(如 MyBatis 的
mapper.xml) - 圖片、音頻、文本等靜態(tài)資源
- 國際化語言包(
.properties)
這些資源通常和 .class 文件一起打包在 JAR/WAR 中,或者放在 classpath 目錄下。
? 關鍵點:Resource 的路徑是相對于 classpath 根目錄 的,用
/分隔(不是平臺相關的\或/)。
二、核心方法解析
1.public URL getResource(String name)
功能:
根據(jù)資源名(如 "config/app.properties")返回一個 URL,可用于讀取該資源。
搜索順序(雙親委派模型):
- 先委托 父類加載器(parent ClassLoader)查找;
- 如果父加載器為
null(即啟動類加載器 Bootstrap ClassLoader),則調用getBootstrapResource(); - 若仍未找到,則調用子類可重寫的
findResource(name)方法(由當前 ClassLoader 自己查找)。
?? 這體現(xiàn)了 雙親委派模型(Parent Delegation Model),保證核心資源優(yōu)先由上級加載器處理,避免重復或沖突。
返回值:
- 成功:
URL對象(如jar:file:/xxx.jar!/config/app.properties) - 失?。?code>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 子類實現(xiàn)具體資源查找邏輯。
- 默認實現(xiàn):
findResource返回null,findResources返回空枚舉。 - 實際實現(xiàn)如
URLClassLoader會從指定的 URL 路徑(如 JAR、目錄)中查找資源。
4. 靜態(tài)方法:getSystemResource/getSystemResources/getSystemResourceAsStream
功能:
通過 系統(tǒng)類加載器(System ClassLoader) 加載資源,等價于:
ClassLoader.getSystemClassLoader().getResource(name);
?? 系統(tǒng)類加載器通常是
AppClassLoader(應用類加載器),負責加載-classpath指定的類和資源。
使用場景:
- 工具類中直接讀取 classpath 資源(不依賴當前類的 ClassLoader)
- 啟動時加載全局配置
5.getResourceAsStream(String name)
功能:
直接返回 InputStream,省去手動 openStream() 的步驟。
內部實現(xiàn):
URL url = getResource(name); return url != null ? url.openStream() : null;
?? 注意:如果資源不存在或 I/O 出錯,返回
null(不會拋異常?。?/p>
三、常見使用方式示例
1. 從當前類的 ClassLoader 讀取資源
InputStream is = MyClass.class.getClassLoader().getResourceAsStream("config/db.properties");
2. 從當前類所在包的相對路徑讀取(注意開頭無/)
// 假設 MyClass 在 com.example 包下
InputStream is = MyClass.class.getResourceAsStream("local-config.txt");
// 實際路徑:com/example/local-config.txt
?? Class.getResource() 與 ClassLoader.getResource() 的區(qū)別:
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 文件通常通過:其中
InputStream 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")發(fā)現(xiàn)。
4.SPI 機制(Java ServiceLoader)
ServiceLoader.load(MyService.class)會調用:從而發(fā)現(xiàn)所有 JAR 中提供的實現(xiàn)。classloader.getResources("META-INF/services/com.example.MyService");
五、注意事項 & 最佳實踐
| 問題 | 建議 |
|---|---|
| 資源路徑錯誤 | 使用 / 分隔,不要用 \;確認是否從 classpath 根開始 |
| 打包后資源找不到 | 確保構建工具(Maven/Gradle)將資源復制到 target/classes |
| 多個同名資源 | 使用 getResources() 遍歷,避免只取第一個導致遺漏 |
| 安全管理器限制 | 在受限環(huán)境(如 Applet)中可能返回 null |
| 流未關閉 | 使用 try-with-resources 確保 InputStream 關閉 |
六、總結
| 方法 | 用途 | 是否靜態(tài) | 委托父加載器 |
|---|---|---|---|
getResource() | 獲取單個資源 URL | 否 | ? |
getResources() | 獲取所有同名資源 | 否 | ? |
getSystemResource() | 通過系統(tǒng)類加載器獲取 | ? | ?(直接用系統(tǒng)加載器) |
getResourceAsStream() | 直接獲取輸入流 | 否 | ? |
findResource() | 子類實現(xiàn)具體查找邏輯 | 否 | ?(自身實現(xiàn)) |
?? 核心思想:Java 的資源加載機制基于 ClassLoader 的雙親委派模型,保證了資源查找的安全性、一致性和可擴展性。理解它,就掌握了 Java 應用加載配置、插件、靜態(tài)文件的底層邏輯。
如果你正在開發(fā)自定義 ClassLoader、模塊化系統(tǒng)、或需要動態(tài)加載資源(如熱部署、插件系統(tǒng)),這些方法就是你的基石。需要我進一步舉例說明如何自定義 ClassLoader 實現(xiàn)資源加載嗎?
到此這篇關于Java資源加載機制及使用最佳實踐的文章就介紹到這了,更多相關java資源加載機制內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Spring注解@Configuration和@Component區(qū)別詳解
@Component和@Configuration都可以作為配置類,之前一直都沒覺得這兩個用起來有什么差別,可能有時程序跑的和自己想的有所區(qū)別也沒注意到,下面這篇文章主要給大家介紹了關于Spring注解@Configuration和@Component區(qū)別的相關資料,需要的朋友可以參考下2023-04-04
Java中使用While語句自增運算遍歷數(shù)組典型實例
這篇文章主要介紹了Java中使用While語句自增運算遍歷數(shù)組典型實例,本文直接給出實例代碼,并對每一句代碼都注解了詳細注釋,需要的朋友可以參考下2015-06-06
搭建 springboot selenium 網頁文件轉圖片環(huán)境的詳細教程
這篇文章主要介紹了搭建 springboot selenium 網頁文件轉圖片環(huán)境,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-08-08
Spring Bean生命周期之BeanDefinition的合并過程詳解
這篇文章主要為大家詳細介紹了Spring Bean生命周期之BeanDefinition的合并過程,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-03-03
詳解elasticsearch之metric聚合實現(xiàn)示例
這篇文章主要為大家介紹了elasticsearch之metric聚合實現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-01-01
Mybatis-Plus實現(xiàn)用戶ID自增出現(xiàn)的問題解決
項目基于 SpringBoot + MybatisPlus 3.5.2 使用數(shù)據(jù)庫自增ID時, 出現(xiàn)重復鍵的問題,本文就來介紹一下解決方法,感興趣的可以了解一下2023-09-09
探究Android系統(tǒng)中解析JSON數(shù)據(jù)的方式
這篇文章主要介紹了探究Android系統(tǒng)中解析JSON數(shù)據(jù)的方式,文中講到了使用Java代碼實現(xiàn)的處理JSON的一些主要方法,需要的朋友可以參考下2015-07-07
idea編寫yml、yaml文件以及其優(yōu)先級的使用
本文主要介紹了idea編寫yml、yaml文件以及其優(yōu)先級的使用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-07-07

