java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > java资源加载机制

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

作者: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 是指与类代码逻辑相关的非代码数据,例如:

这些资源通常和 .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),保证核心资源优先由上级加载器处理,避免重复或冲突。

返回值:

2.public Enumeration<URL> getResources(String name)

功能:

返回所有匹配名称的资源(可能有多个同名资源分布在不同 JAR 或目录中)。

使用场景:

注意:

3.protected URL findResource(String name)和protected Enumeration<URL> findResources(String name)

作用:

这两个是 模板方法,供自定义 ClassLoader 子类实现具体资源查找逻辑。

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

功能:

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

ClassLoader.getSystemClassLoader().getResource(name);

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

使用场景:

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

四、在主流框架中的应用

1.Spring Framework

2.MyBatis

3.Hibernate / JPA

4.SPI 机制(Java ServiceLoader)

五、注意事项 & 最佳实践

问题建议
资源路径错误使用 / 分隔,不要用 \;确认是否从 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资源加载机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文