development

Java jar 파일에서 리소스 파일을 어떻게 읽습니까?

big-blog 2020. 12. 25. 22:44
반응형

Java jar 파일에서 리소스 파일을 어떻게 읽습니까?


데스크톱 응용 프로그램으로 실행되는 별도의 jar에서 jar 파일 내의 XML 파일에 액세스하려고합니다. 필요한 파일의 URL을 가져올 수 있지만 FileReader (문자열)에 전달하면 "파일 이름, 디렉터리 이름 또는 볼륨 레이블 구문이 올바르지 않습니다."라는 FileNotFoundException이 발생합니다.

참고로 URL을 ImageIcon 생성자에 전달하여 동일한 항아리에서 이미지 리소스를 읽는 데 문제가 없습니다. 이것은 URL을 얻는 데 사용하는 방법이 정확하다는 것을 나타내는 것 같습니다.

URL url = getClass().getResource("/xxx/xxx/xxx/services.xml");
ServicesLoader jsl = new ServicesLoader( url.toString() );

ServicesLoader 클래스 내부에는

XMLReader xr = XMLReaderFactory.createXMLReader();
xr.setContentHandler( this );
xr.setErrorHandler( this );
xr.parse( new InputSource( new FileReader( filename )));

이 기술을 사용하여 XML 파일을 읽는 데있어 문제점은 무엇입니까?


사용하려는 것 같은데는 java.lang.Class.getResourceAsStream(String), 참조

http://java.sun.com/javase/6/docs/api/java/lang/Class.html#getResourceAsStream(java.lang.String)


이것이 데스크톱인지 웹 앱인지는 말하지 않습니다. getResourceAsStream()데스크톱 인 경우 적절한 ClassLoader 메서드를 사용하고 웹 앱인 경우 Context를 사용합니다.


생성자에 URL.toString대한 인수로 결과를 사용하는 것처럼 보입니다 FileReader. URL.toString약간 깨져서 대신 일반적으로 url.toURI().toString(). 어쨌든 문자열은 파일 경로가 아닙니다.

대신 다음 중 하나를 수행해야합니다.

  • URL전달 ServicesLoader하고 전화 openStream또는 이와 유사한 것을 허용하십시오 .
  • 사용 Class.getResourceAsStream하고 스트림을 전달하십시오 InputSource. (API가 약간 지저분하므로 null을 확인하는 것을 잊지 마십시오.)

문제는 XMLReader의 구문 분석 메서드를 호출하는 데 너무 많은 단계를 밟고 있다는 것입니다. parse 메서드는 InputSource를 허용하므로 FileReader를 사용할 이유가 없습니다. 위 코드의 마지막 줄을 다음으로 변경

xr.parse( new InputSource( filename ));

잘 작동합니다.


한 가지 문제는 동일한 리소스가 여러 jar 파일에있는 경우에 대한 것임을 지적하고 싶습니다. /org/node/foo.txt를 하나의 파일이 아니라 각각의 모든 jar 파일에서 읽고 싶다고 가정 해 보겠습니다.

나는 이전에이 같은 문제를 여러 번 겪었습니다. 나는 JDK 7에서 누군가가 클래스 경로 파일 시스템을 작성하기를 바라고 있었지만 아직은 아닙니다.

Spring에는 클래스 경로 리소스를 아주 멋지게로드 할 수있는 Resource 클래스가 있습니다.

여러 jar 파일에서 리소스를 읽는 문제를 해결하기 위해 작은 프로토 타입을 작성했습니다. 프로토 타입은 모든 엣지 케이스를 처리하지는 않지만 jar 파일에있는 디렉토리에서 리소스 검색을 처리합니다.

나는 꽤 오랫동안 Stack Overflow를 사용했습니다. 이것은 내가 질문에 대답했던 것을 기억하는 두 번째 대답입니다. 너무 오래 가면 용서 해주세요.

이것은 프로토 타입 리소스 리더입니다. 프로토 타입에는 강력한 오류 검사가 없습니다.

내가 설정 한 두 개의 프로토 타입 jar 파일이 있습니다.

 <pre>
         <dependency>
              <groupId>invoke</groupId>
              <artifactId>invoke</artifactId>
              <version>1.0-SNAPSHOT</version>
          </dependency>

          <dependency>
               <groupId>node</groupId>
               <artifactId>node</artifactId>
               <version>1.0-SNAPSHOT</version>
          </dependency>

jar 파일은 각각 / org / node / 아래에 resource.txt라는 파일이 있습니다.

이것은 classpath : //를 사용하는 핸들러의 프로토 타입 일뿐입니다.이 프로젝트의 로컬 리소스에도 resource.foo.txt가 있습니다.

그것들을 모두 골라서 출력합니다.

   

    package com.foo;

    import java.io.File;
    import java.io.FileReader;
    import java.io.InputStreamReader;
    import java.io.Reader;
    import java.net.URI;
    import java.net.URL;
    import java.util.Enumeration;
    import java.util.zip.ZipEntry;
    import java.util.zip.ZipFile;

    /**
    * Prototype resource reader.
    * This prototype is devoid of error checking.
    *
    *
    * I have two prototype jar files that I have setup.
    * <pre>
    *             <dependency>
    *                  <groupId>invoke</groupId>
    *                  <artifactId>invoke</artifactId>
    *                  <version>1.0-SNAPSHOT</version>
    *              </dependency>
    *
    *              <dependency>
    *                   <groupId>node</groupId>
    *                   <artifactId>node</artifactId>
    *                   <version>1.0-SNAPSHOT</version>
    *              </dependency>
    * </pre>
    * The jar files each have a file under /org/node/ called resource.txt.
    * <br />
    * This is just a prototype of what a handler would look like with classpath://
    * I also have a resource.foo.txt in my local resources for this project.
    * <br />
    */
    public class ClasspathReader {

        public static void main(String[] args) throws Exception {

            /* This project includes two jar files that each have a resource located
               in /org/node/ called resource.txt.
             */


            /* 
              Name space is just a device I am using to see if a file in a dir
              starts with a name space. Think of namespace like a file extension 
              but it is the start of the file not the end.
            */
            String namespace = "resource";

            //someResource is classpath.
            String someResource = args.length > 0 ? args[0] :
                    //"classpath:///org/node/resource.txt";   It works with files
                    "classpath:///org/node/";                 //It also works with directories

            URI someResourceURI = URI.create(someResource);

            System.out.println("URI of resource = " + someResourceURI);

            someResource = someResourceURI.getPath();

            System.out.println("PATH of resource =" + someResource);

            boolean isDir = !someResource.endsWith(".txt");


            /** Classpath resource can never really start with a starting slash.
             * Logically they do, but in reality you have to strip it.
             * This is a known behavior of classpath resources.
             * It works with a slash unless the resource is in a jar file.
             * Bottom line, by stripping it, it always works.
             */
            if (someResource.startsWith("/")) {
                someResource = someResource.substring(1);
            }

              /* Use the ClassLoader to lookup all resources that have this name.
                 Look for all resources that match the location we are looking for. */
            Enumeration resources = null;

            /* Check the context classloader first. Always use this if available. */
            try {
                resources = 
                    Thread.currentThread().getContextClassLoader().getResources(someResource);
            } catch (Exception ex) {
                ex.printStackTrace();
            }

            if (resources == null || !resources.hasMoreElements()) {
                resources = ClasspathReader.class.getClassLoader().getResources(someResource);
            }

            //Now iterate over the URLs of the resources from the classpath
            while (resources.hasMoreElements()) {
                URL resource = resources.nextElement();


                /* if the resource is a file, it just means that we can use normal mechanism
                    to scan the directory.
                */
                if (resource.getProtocol().equals("file")) {
                    //if it is a file then we can handle it the normal way.
                    handleFile(resource, namespace);
                    continue;
                }

                System.out.println("Resource " + resource);

               /*

                 Split up the string that looks like this:
                 jar:file:/Users/rick/.m2/repository/invoke/invoke/1.0-SNAPSHOT/invoke-1.0-SNAPSHOT.jar!/org/node/
                 into
                    this /Users/rick/.m2/repository/invoke/invoke/1.0-SNAPSHOT/invoke-1.0-SNAPSHOT.jar
                 and this
                     /org/node/
                */
                String[] split = resource.toString().split(":");
                String[] split2 = split[2].split("!");
                String zipFileName = split2[0];
                String sresource = split2[1];

                System.out.printf("After split zip file name = %s," +
                        " \nresource in zip %s \n", zipFileName, sresource);


                /* Open up the zip file. */
                ZipFile zipFile = new ZipFile(zipFileName);


                /*  Iterate through the entries.  */
                Enumeration entries = zipFile.entries();

                while (entries.hasMoreElements()) {
                    ZipEntry entry = entries.nextElement();
                    /* If it is a directory, then skip it. */
                    if (entry.isDirectory()) {
                        continue;
                    }

                    String entryName = entry.getName();
                    System.out.printf("zip entry name %s \n", entryName);

                    /* If it does not start with our someResource String
                       then it is not our resource so continue.
                    */
                    if (!entryName.startsWith(someResource)) {
                        continue;
                    }


                    /* the fileName part from the entry name.
                     * where /foo/bar/foo/bee/bar.txt, bar.txt is the file
                     */
                    String fileName = entryName.substring(entryName.lastIndexOf("/") + 1);
                    System.out.printf("fileName %s \n", fileName);

                    /* See if the file starts with our namespace and ends with our extension.        
                     */
                    if (fileName.startsWith(namespace) && fileName.endsWith(".txt")) {


                        /* If you found the file, print out 
                           the contents fo the file to System.out.*/
                        try (Reader reader = new InputStreamReader(zipFile.getInputStream(entry))) {
                            StringBuilder builder = new StringBuilder();
                            int ch = 0;
                            while ((ch = reader.read()) != -1) {
                                builder.append((char) ch);

                            }
                            System.out.printf("zip fileName = %s\n\n####\n contents of file %s\n###\n", entryName, builder);
                        } catch (Exception ex) {
                            ex.printStackTrace();
                        }
                    }

                    //use the entry to see if it's the file '1.txt'
                    //Read from the byte using file.getInputStream(entry)
                }

            }


        }

        /**
         * The file was on the file system not a zip file,
         * this is here for completeness for this example.
         * otherwise.
         *
         * @param resource
         * @param namespace
         * @throws Exception
         */
        private static void handleFile(URL resource, String namespace) throws Exception {
            System.out.println("Handle this resource as a file " + resource);
            URI uri = resource.toURI();
            File file = new File(uri.getPath());


            if (file.isDirectory()) {
                for (File childFile : file.listFiles()) {
                    if (childFile.isDirectory()) {
                        continue;
                    }
                    String fileName = childFile.getName();
                    if (fileName.startsWith(namespace) && fileName.endsWith("txt")) {

                        try (FileReader reader = new FileReader(childFile)) {
                            StringBuilder builder = new StringBuilder();
                            int ch = 0;
                            while ((ch = reader.read()) != -1) {
                                builder.append((char) ch);

                            }
                            System.out.printf("fileName = %s\n\n####\n contents of file %s\n###\n", childFile, builder);
                        } catch (Exception ex) {
                            ex.printStackTrace();
                        }

                    }

                }
            } else {
                String fileName = file.getName();
                if (fileName.startsWith(namespace) && fileName.endsWith("txt")) {

                    try (FileReader reader = new FileReader(file)) {
                        StringBuilder builder = new StringBuilder();
                        int ch = 0;
                        while ((ch = reader.read()) != -1) {
                            builder.append((char) ch);

                        }
                        System.out.printf("fileName = %s\n\n####\n contents of file %s\n###\n", fileName, builder);
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }

                }

            }
        }

    }


   

여기에서 샘플 출력으로 더 자세한 예를 볼 수 있습니다.


Outside of your technique, why not use the standard Java JarFile class to get the references you want? From there most of your problems should go away.


If you use resources extensively, you might consider using Commons VFS.

Also supports: * Local Files * FTP, SFTP * HTTP and HTTPS * Temporary Files "normal FS backed) * Zip, Jar and Tar (uncompressed, tgz or tbz2) * gzip and bzip2 * resources * ram - "ramdrive" * mime

There's also JBoss VFS - but it's not much documented.


I have 2 CSV files that I use to read data. The java program is exported as a runnable jar file. When you export it, you will figure out it doesn't export your resources with it.

I added a folder under project called data in eclipse. In that folder i stored my csv files.

When I need to reference those files I do it like this...

private static final String ZIP_FILE_LOCATION_PRIMARY = "free-zipcode-database-Primary.csv";
private static final String ZIP_FILE_LOCATION = "free-zipcode-database.csv";

private static String getFileLocation(){
    String loc = new File("").getAbsolutePath() + File.separatorChar +
        "data" + File.separatorChar;
    if (usePrimaryZipCodesOnly()){              
        loc = loc.concat(ZIP_FILE_LOCATION_PRIMARY);
    } else {
        loc = loc.concat(ZIP_FILE_LOCATION);
    }
    return loc;
}

Then when you put the jar in a location so it can be ran via commandline, make sure that you add the data folder with the resources into the same location as the jar file.


Here's a sample code on how to read a file properly inside a jar file (in this case, the current executing jar file)

Just change executable with the path of your jar file if it is not the current running one.

Then change the filePath to the path of the file you want to use inside the jar file. I.E. if your file is in

someJar.jar\img\test.gif

. Set the filePath to "img\test.gif"

File executable = new File(BrowserViewControl.class.getProtectionDomain().getCodeSource().getLocation().toURI());
JarFile jar = new JarFile(executable);
InputStream fileInputStreamReader = jar.getInputStream(jar.getJarEntry(filePath));
byte[] bytes = new byte[fileInputStreamReader.available()];

int sizeOrig = fileInputStreamReader.available();
int size = fileInputStreamReader.available();
int offset = 0;
while (size != 0){
    fileInputStreamReader.read(bytes, offset, size);
    offset = sizeOrig - fileInputStreamReader.available();
    size = fileInputStreamReader.available();
}

ReferenceURL : https://stackoverflow.com/questions/403256/how-do-i-read-a-resource-file-from-a-java-jar-file

반응형

'development' 카테고리의 다른 글

Xcode-LLDB 대상 생성 오류  (0) 2020.12.25
.NET BCL의 추적 대 디버그  (0) 2020.12.25
Sysinternals의 Portmon : 오류 2  (0) 2020.12.25
웹 사이트를 스파이더 링하고 URL 만 반환  (0) 2020.12.25
AutoMapper의 대안  (0) 2020.12.25