Process and principle analysis of loading resource files under jar package with java

Recently, I encountered such a problem. The project is a spring cloud project. A main module (recorded as maincloud) depends on another sub module (recorded as subcloud). During the development process, it can run normally in the idea development tool environment. However, because the module needs to be packaged during testing, the subcloud project is made into a jar and placed under the maincloud. When running the jar package, it is found that it can not run, and the console throws the exception message of fileNotFoundException.


When checking the code under subcloud, I found that it is because subcloud needs to load the configuration File in maincloud during initialization. The loaded code is directly loaded through the File class. In the development environment, the runtime compiles the project resource File directly into the target classes directory, so it can run normally in the development environment.

reason

When the project is run as a jar package, the resource files in the jar package will not be automatically decompressed and released to the directory because it has been compiled and java has become a class bytecode File. Therefore, you can't read a File under the jar directly through the original File, so the problem arises here.

resolvent:

So how can we solve this problem? At that time, in order to facilitate me to directly use the common configuration package of apache, I replaced the code for reading the configuration file in subcloud, and changed all the code for reading the configuration file to the code for reading the configuration file. The problem was solved.
 

principle

static URL locateFromClasspath(String resourceName) {
    URL url = null;
    ClassLoader loader = Thread.currentThread().getContextClassLoader();
    if (loader != null) {
        url = loader.getResource(resourceName);
        if (url != null) {
            LOG.debug("Loading configuration from the context classpath (" + resourceName + ")");
        }
    }

    if (url == null) {
        url = ClassLoader.getSystemResource(resourceName);
        if (url != null) {
            LOG.debug("Loading configuration from the system classpath (" + resourceName + ")");
        }
    }

    return url;
}

First, it obtains a class loader of the current thread and finds the resourceName file in the class loader through the getresource method of the loader

loader. The code of getresource belongs to the code of JDK, and the method code of getresource is:

/**
     * 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;
    }

The name of a resource is a pathname separated by /, which identifies the resource

Through the code and comments, we can know that this code will first go to the loader of the parent node to load the resource file. If it cannot be found, it will go to the bootstrap loader,

If it still cannot be found, call the classLoader of the current class to find it. This is what we sometimes call the} two parent delegation model.

(the working process of the parent delegation model is: if a class loader receives a class loading request, it will not try to load the class itself, but delegate the request to the parent class loader to complete it. This is true for loaders at each level, so all class loading requests will be transmitted to the top-level startup class loader. Only when the parent loader feeds back to itself When the load request cannot be completed (the corresponding class is not found in the search scope of the loader), the child loader will try to load by itself)
 

public InputStream getInputStream(URL url) throws ConfigurationException {
    File file = ConfigurationUtils.fileFromURL(url);
    if (file != null && file.isDirectory()) {
        throw new ConfigurationException("Cannot load a configuration from a directory");
    } else {
        try {
            return url.openStream();
        } catch (Exception var4) {
            throw new ConfigurationException("Unable to load the configuration from the URL " + url, var4);
        }
    }
}

When the resource is found, the input stream of the file is obtained by calling the openStream() method of the url

Therefore, it is impossible to simply use File to read the File of jar package, because! Is not the format of the File resource locator

(resources in jar have their special URL form: jar: < URL >! / {entry}).

Therefore, if the class source code in the jar package uses file f = new file (relative path); It is impossible to locate file resources

import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;

import java.io.*;
import java.net.URL;
import java.util.Iterator;

public class ResourceReader {
    private static final String subMoudlePropertiesFile = "sys.properties";//Configuration file under jar
    private static final String innerPropertiesFile = "own.properties";//Internal profile

    public static void main(String[] args) throws InterruptedException {
        loadJarFileByConfiguration();
        Thread.sleep(1000);
        loadLocalFile();
        Thread.sleep(1000);
        loadJarFileByResource();
        Thread.sleep(1000);
        loadJarFileByFile();
    }

    /**
     * Load the resource File of the jar package through the File class
     */
    private static void loadJarFileByFile() {
        System.out.println("----------loadJarFileByFile---- begin------------");
        URL resource = ResourceReader.class.getClassLoader().getResource(subMoudlePropertiesFile);
        String path = resource.toString();
        System.out.println(path);
        try {
            File file = new File(path);
            FileInputStream fileInputStream = new FileInputStream(file);
            BufferedReader br = new BufferedReader(new InputStreamReader(fileInputStream));
            String s = "";
            while ((s = br.readLine()) != null)
                System.out.println(s);


        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("----------loadJarFileByFile---- end------------\n\n");
    }

----------loadJarFileByFile---- begin------------
java.io.FileNotFoundException: jar:file:\E:\idea_space\spring_hello\libs\subMoudle-1.0-SNAPSHOT.jar!\sys.properties (Incorrect syntax for file name, directory name, or volume label.)
jar:file:/E:/idea_space/spring_hello/libs/subMoudle-1.0-SNAPSHOT.jar!/sys.properties
----------loadJarFileByFile---- end------------


    /**
     * Read the configuration file through the apache configuration package
     */
    private static void loadJarFileByConfiguration() {
        System.out.println("----------loadJarFileByConfiguration---- begin------------");
        try {
            Configuration configuration = new PropertiesConfiguration(subMoudlePropertiesFile);
            Iterator<String> keys = configuration.getKeys();
            while (keys.hasNext()) {
                String next = keys.next();
                System.out.println("key:" + next + "\tvalue:" + configuration.getString(next));
            }
        } catch (ConfigurationException e) {
            e.printStackTrace();
        }
        System.out.println("----------loadJarFileByConfiguration---- end------------\n\n");
    }

----------loadJarFileByConfiguration---- begin------------
log4j:WARN No appenders could be found for logger (org.apache.commons.configuration.PropertiesConfiguration).
log4j:WARN Please initialize the log4j system properly.
key:username value:haiyangge
key:password value:haiyangge666
----------loadJarFileByConfiguration---- end------------



    /**
     * Read through the getResource method of the class loader
     */
    private static void loadJarFileByResource() {
        System.out.println("----------loadJarFileByResource---- begin------------");
        URL resource = ResourceReader.class.getClassLoader().getResource(subMoudlePropertiesFile);
        String path = resource.toString();
        System.out.println(path);
        try {
            InputStream is = resource.openStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String s = "";
            while ((s = br.readLine()) != null)
                System.out.println(s);


        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("----------loadJarFileByResource---- end------------\n\n");

    }



----------loadJarFileByResource---- begin------------
jar:file:/E:/idea_space/spring_hello/libs/subMoudle-1.0-SNAPSHOT.jar!/sys.properties
username=haiyangge
password=haiyangge666
----------loadJarFileByResource---- end------------


    /**
     * Read the configuration file in the current project
     */
    private static void loadLocalFile() {
        System.out.println("----------loadLocalFile---- begin------------");
        String path = ResourceReader.class.getClassLoader().getResource(innerPropertiesFile).getPath();
        System.out.println(path);

        try {
            FileReader fileReader = new FileReader(path);
            BufferedReader bufferedReader = new BufferedReader(fileReader);
            String strLine;
            while ((strLine = bufferedReader.readLine()) != null) {
                System.out.println("strLine:" + strLine);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("----------loadLocalFile---- begin------------\n\n");
    }
}

----------loadLocalFile---- begin------------
/E:/idea_space/spring_hello/target/classes/own.properties
strLine:db.username=9527
strLine:db.password=0839
----------loadLocalFile---- begin------------

Therefore, it is impossible to simply use File to read the File of jar package, because! Is not the format of the File resource locator

(resources in jar have their special URL form: jar: < URL >! / {entry}).

Therefore, when loading the resource file in the jar package, you should use the getResource method of classLoader to load. After obtaining the URL, you should use the openStream() method to open the stream. You should not load the native file.

Knowledge supplement: each thread is assigned a context class loader (unless the thread is created by local code). The loader is set through the Thread.setContextClassLoader() method. If you do not call this method after the thread is constructed, the thread will inherit the context class loader from its parent thread. If you do not make any settings in the whole application, all threads will use the system class loader as their own context loader


Original link: https://blog.csdn.net/puhaiyang/article/details/77409203

Keywords: Java jar

Added by Nicolas T on Sat, 18 Dec 2021 05:12:02 +0200