Java资源加载机制及使用最佳实践

 更新时间:2025年12月13日 09:15:18   作者:lang20150928  
Java类加载器的资源加载机制,包括`getResource`、`getResources`、`findResource`、`findResources`等方法,本文给大家介绍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,可用于读取该资源。

搜索顺序(双亲委派模型):

  1. 先委托 父类加载器(parent ClassLoader)查找;
  2. 如果父加载器为 null(即启动类加载器 Bootstrap ClassLoader),则调用 getBootstrapResource()
  3. 若仍未找到,则调用子类可重写的 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 返回 nullfindResources 返回空枚举。
  • 实际实现如 URLClassLoader 会从指定的 URL 路径(如 JAR、目录)中查找资源。

4. 静态方法:getSystemResource/getSystemResources/getSystemResourceAsStream

功能:

通过 系统类加载器(System ClassLoader) 加载资源,等价于:

ClassLoader.getSystemClassLoader().getResource(name);

📌 系统类加载器通常是 AppClassLoader(应用类加载器),负责加载 -classpath 指定的类和资源。

使用场景:

  • 工具类中直接读取 classpath 资源(不依赖当前类的 ClassLoader)
  • 启动时加载全局配置

5.getResourceAsStream(String name)

功能:

直接返回 InputStream,省去手动 openStream() 的步骤。

内部实现:

URL url = getResource(name);
return url != null ? url.openStream() : null;

⚠️ 注意:如果资源不存在或 I/O 出错,返回 null(不会抛异常!)

三、常见使用方式示例

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() 的区别:

  • 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") 发现。

4.SPI 机制(Java ServiceLoader)

  • ServiceLoader.load(MyService.class) 会调用:
    classloader.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 实现资源加载吗?

到此这篇关于Java资源加载机制及使用最佳实践的文章就介绍到这了,更多相关java资源加载机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java ThreadPoolExecutor线程池内部处理流程解析

    java ThreadPoolExecutor线程池内部处理流程解析

    这篇文章主要为大家介绍了java ThreadPoolExecutor线程池内部处理流程解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-12-12
  • 如何把本地jar包导入maven并pom添加依赖

    如何把本地jar包导入maven并pom添加依赖

    这篇文章主要介绍了如何把本地jar包导入maven并pom添加依赖,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-11-11
  • javaweb购物车案列学习开发

    javaweb购物车案列学习开发

    这篇文章主要为大家详细介绍了javaweb购物车案列学习开发的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-05-05
  • HttpClient实现调用外部项目接口工具类的示例

    HttpClient实现调用外部项目接口工具类的示例

    下面小编就为大家带来一篇HttpClient实现调用外部项目接口工具类的示例。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-10-10
  • SpringBoot获取前台参数的六种方式以及统一响应

    SpringBoot获取前台参数的六种方式以及统一响应

    本文主要介绍了SpringBoot获取前台参数的六种方式以及统一响应,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-03-03
  • SSO单点登录系统实现原理及流程图解

    SSO单点登录系统实现原理及流程图解

    这篇文章主要介绍了SSO单点登录系统实现原理及流程图解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-12-12
  • JAVA三种拦截方式详解(原生过滤器Filter、springMVC拦截器、aop切面)

    JAVA三种拦截方式详解(原生过滤器Filter、springMVC拦截器、aop切面)

    在Java开发中方法拦截是一种常见的技术,可以用于在方法执行前后添加额外的逻辑或修改方法的行为,这篇文章主要给大家介绍了关于JAVA三种拦截方式的相关资料,文中介绍的方式分别是原生过滤器Filter、springMVC拦截器、aop切面,需要的朋友可以参考下
    2024-05-05
  • Java+mysql实现学籍管理系统

    Java+mysql实现学籍管理系统

    这篇文章主要为大家详细介绍了Java+mysql实现学籍管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-07-07
  • spring boot 如何指定profile启动

    spring boot 如何指定profile启动

    这篇文章主要介绍了spring boot 如何指定profile启动的操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • Java日常练习题,每天进步一点点(32)

    Java日常练习题,每天进步一点点(32)

    下面小编就为大家带来一篇Java基础的几道练习题(分享)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧,希望可以帮到你
    2021-07-07

最新评论