Programming

JAR 파일 내의 파일을 나열하는 방법은 무엇입니까?

procodes 2020. 8. 9. 10:29
반응형

JAR 파일 내의 파일을 나열하는 방법은 무엇입니까?


디렉토리에서 모든 파일을 읽는 코드가 있습니다.

    File textFolder = new File("text_directory");

    File [] texFiles = textFolder.listFiles( new FileFilter() {
           public boolean accept( File file ) {
               return file.getName().endsWith(".txt");
           }
    });

잘 작동합니다. "text_directory"디렉토리에서 ".txt"로 끝나는 모든 파일로 배열을 채 웁니다.

JAR 파일 에서 비슷한 방식으로 디렉토리의 내용을 어떻게 읽을 수 있습니까?

그래서 제가 정말로하고 싶은 것은 JAR 파일 안에있는 모든 이미지를 나열하는 것입니다. 그래서 다음과 같이로드 할 수 있습니다.

ImageIO.read(this.getClass().getResource("CompanyLogo.png"));

( "CompanyLogo"가 "하드 코딩"되어 있기 때문에 작동하지만 JAR 파일 내의 이미지 수는 가변 길이가 10 ~ 200 개일 수 있습니다.)

편집하다

그래서 내 주요 문제는 다음과 같습니다 . 내 메인 클래스 가있는 JAR 파일이름 을 아는 방법 ?

나는 그것을 사용하여 읽을 수 있었다 java.util.Zip.

내 구조는 다음과 같습니다.

그들은 다음과 같습니다.

my.jar!/Main.class
my.jar!/Aux.class
my.jar!/Other.class
my.jar!/images/image01.png
my.jar!/images/image02a.png
my.jar!/images/imwge034.png
my.jar!/images/imagAe01q.png
my.jar!/META-INF/manifest 

지금은 다음을 사용하여 "images / image01.png"인스턴스를로드 할 수 있습니다.

    ImageIO.read(this.getClass().getResource("images/image01.png));

그러나 파일 이름을 알고 있기 때문에 나머지는 동적으로로드해야합니다.


CodeSource src = MyClass.class.getProtectionDomain().getCodeSource();
if (src != null) {
  URL jar = src.getLocation();
  ZipInputStream zip = new ZipInputStream(jar.openStream());
  while(true) {
    ZipEntry e = zip.getNextEntry();
    if (e == null)
      break;
    String name = e.getName();
    if (name.startsWith("path/to/your/dir/")) {
      /* Do something with this entry. */
      ...
    }
  }
} 
else {
  /* Fail... */
}

Java 7에서는 FileSystemJAR (zip) 파일에서을 생성 한 다음 NIO의 디렉토리 검색 및 필터링 메커니즘을 사용하여 검색 할 수 있습니다. 이렇게하면 JAR 및 "폭발 된"디렉토리를 처리하는 코드를 더 쉽게 작성할 수 있습니다.


IDE 및 .jar 파일 모두에서 작동하는 코드 :

import java.io.*;
import java.net.*;
import java.nio.file.*;
import java.util.*;
import java.util.stream.*;

public class ResourceWalker {
    public static void main(String[] args) throws URISyntaxException, IOException {
        URI uri = ResourceWalker.class.getResource("/resources").toURI();
        Path myPath;
        if (uri.getScheme().equals("jar")) {
            FileSystem fileSystem = FileSystems.newFileSystem(uri, Collections.<String, Object>emptyMap());
            myPath = fileSystem.getPath("/resources");
        } else {
            myPath = Paths.get(uri);
        }
        Stream<Path> walk = Files.walk(myPath, 1);
        for (Iterator<Path> it = walk.iterator(); it.hasNext();){
            System.out.println(it.next());
        }
    }
}

erickson의 대답 은 완벽하게 작동했습니다.

다음은 작동 코드입니다.

CodeSource src = MyClass.class.getProtectionDomain().getCodeSource();
List<String> list = new ArrayList<String>();

if( src != null ) {
    URL jar = src.getLocation();
    ZipInputStream zip = new ZipInputStream( jar.openStream());
    ZipEntry ze = null;

    while( ( ze = zip.getNextEntry() ) != null ) {
        String entryName = ze.getName();
        if( entryName.startsWith("images") &&  entryName.endsWith(".png") ) {
            list.add( entryName  );
        }
    }

 }
 webimages = list.toArray( new String[ list.size() ] );

그리고 다음과 같이 내로드 방법을 수정했습니다.

File[] webimages = ... 
BufferedImage image = ImageIO.read(this.getClass().getResource(webimages[nextIndex].getName() ));

이에:

String  [] webimages = ...

BufferedImage image = ImageIO.read(this.getClass().getResource(webimages[nextIndex]));

여러 가지 이유로 매우 안전하지 않은 솔루션이기 때문에 acheron55의 답변 을 확장하고 싶습니다 .

  1. FileSystem개체를 닫지 않습니다 .
  2. FileSystem개체가 이미 존재 하는지 확인하지 않습니다 .
  3. 스레드로부터 안전하지 않습니다.

이것은 다소 안전한 해결책입니다.

private static ConcurrentMap<String, Object> locks = new ConcurrentHashMap<>();

public void walk(String path) throws Exception {

    URI uri = getClass().getResource(path).toURI();
    if ("jar".equals(uri.getScheme()) {
        safeWalkJar(path, uri);
    } else {
        Files.walk(Paths.get(path));
    }
}

private void safeWalkJar(String path, URI uri) throws Exception {

    synchronized (getLock(uri)) {    
        // this'll close the FileSystem object at the end
        try (FileSystem fs = getFileSystem(uri)) {
            Files.walk(fs.getPath(path));
        }
    }
}

private Object getLock(URI uri) {

    String fileName = parseFileName(uri);  
    locks.computeIfAbsent(fileName, s -> new Object());
    return locks.get(fileName);
}

private String parseFileName(URI uri) {

    String schemeSpecificPart = uri.getSchemeSpecificPart();
    return schemeSpecificPart.substring(0, schemeSpecificPart.indexOf("!"));
}

private FileSystem getFileSystem(URI uri) throws IOException {

    try {
        return FileSystems.getFileSystem(uri);
    } catch (FileSystemNotFoundException e) {
        return FileSystems.newFileSystem(uri, Collections.<String, String>emptyMap());
    }
}   

파일 이름을 동기화 할 필요가 없습니다. 매번 동일한 객체에서 동기화 할 수 있습니다 (또는 메서드를 만들 수 있습니다 synchronized). 순전히 최적화입니다.

FileSystem동일한 파일에 대해 인터페이스 를 사용하는 코드에 다른 부분이있을 수 있고 (단일 스레드 응용 프로그램에서도) 간섭을 일으킬 수 있기 때문에 이것이 여전히 문제 해결 방법이라고 말할 수 있습니다 .
또한 nulls를 확인하지 않습니다 (예 : on getClass().getResource().

이 특정 Java NIO 인터페이스는 스레드로부터 안전하지 않은 전역 / 싱글 톤 리소스를 도입하고 문서가 매우 모호합니다 (제공 업체별 구현으로 인해 많은 알 수 없음). 결과는 다른 FileSystem공급자 (JAR 아님)에 따라 다를 수 있습니다 . 그럴 수있는 이유가있을 수 있습니다. 모르겠습니다. 구현을 조사하지 않았습니다.


그래서 내 주요 문제는 내 메인 클래스가 사는 항아리의 이름을 아는 방법입니다.

프로젝트가 Jar에 압축되어 있다고 가정하면 (반드시 사실은 아닙니다!), ClassLoader.getResource () 또는 findResource ()를 클래스 이름 (.class가 뒤 따름)과 함께 사용하여 지정된 클래스가 포함 된 jar를 가져올 수 있습니다. 반환되는 URL에서 jar 이름을 구문 분석해야합니다 (그다지 어렵지 않음). 독자를위한 연습으로 남겨 둘 것입니다. :-)

클래스가 항아리의 일부가 아닌 경우를 테스트해야합니다.


다음은 "패키지에서 모든 JUnits 실행"을 위해 작성한 방법입니다. 필요에 맞게 조정할 수 있어야합니다.

private static void findClassesInJar(List<String> classFiles, String path) throws IOException {
    final String[] parts = path.split("\\Q.jar\\\\E");
    if (parts.length == 2) {
        String jarFilename = parts[0] + ".jar";
        String relativePath = parts[1].replace(File.separatorChar, '/');
        JarFile jarFile = new JarFile(jarFilename);
        final Enumeration<JarEntry> entries = jarFile.entries();
        while (entries.hasMoreElements()) {
            final JarEntry entry = entries.nextElement();
            final String entryName = entry.getName();
            if (entryName.startsWith(relativePath)) {
                classFiles.add(entryName.replace('/', File.separatorChar));
            }
        }
    }
}

편집 : 아,이 경우이 스 니펫도 필요할 수 있습니다 (동일한 사용 사례 :))

private static File findClassesDir(Class<?> clazz) {
    try {
        String path = clazz.getProtectionDomain().getCodeSource().getLocation().getFile();
        final String codeSourcePath = URLDecoder.decode(path, "UTF-8");
        final String thisClassPath = new File(codeSourcePath, clazz.getPackage().getName().repalce('.', File.separatorChar));
    } catch (UnsupportedEncodingException e) {
        throw new AssertionError("impossible", e);
    }
}

다음은 Reflections 라이브러리를 사용하여 리소스 콘텐츠를 가져 오기 위해 몇 가지 Guava 특전으로 강화 된 정규식 이름 패턴으로 클래스 경로를 재귀 적으로 스캔 하는 예입니다 .

Reflections reflections = new Reflections("com.example.package", new ResourcesScanner());
Set<String> paths = reflections.getResources(Pattern.compile(".*\\.template$"));

Map<String, String> templates = new LinkedHashMap<>();
for (String path : paths) {
    log.info("Found " + path);
    String templateName = Files.getNameWithoutExtension(path);
    URL resource = getClass().getClassLoader().getResource(path);
    String text = Resources.toString(resource, StandardCharsets.UTF_8);
    templates.put(templateName, text);
}

이것은 항아리와 폭발 클래스 모두에서 작동합니다.


jar 파일은 구조화 된 매니페스트가있는 zip 파일입니다. 일반적인 자바 zip 도구를 사용하여 jar 파일을 열고 그런 방식으로 파일 내용을 스캔하고 스트림을 확장 할 수 있습니다. 그런 다음 getResourceAsStream 호출에서이를 사용하면 모든 것이 엉망이됩니다.

수정 / 설명 후

It took me a minute to remember all the bits and pieces and I'm sure there are cleaner ways to do it, but I wanted to see that I wasn't crazy. In my project image.jpg is a file in some part of the main jar file. I get the class loader of the main class (SomeClass is the entry point) and use it to discover the image.jpg resource. Then some stream magic to get it into this ImageInputStream thing and everything is fine.

InputStream inputStream = SomeClass.class.getClassLoader().getResourceAsStream("image.jpg");
JPEGImageReaderSpi imageReaderSpi = new JPEGImageReaderSpi();
ImageReader ir = imageReaderSpi.createReaderInstance();
ImageInputStream iis = new MemoryCacheImageInputStream(inputStream);
ir.setInput(iis);
....
ir.read(0); //will hand us a buffered image

Given an actual JAR file, you can list the contents using JarFile.entries(). You will need to know the location of the JAR file though - you can't just ask the classloader to list everything it could get at.

You should be able to work out the location of the JAR file based on the URL returned from ThisClassName.class.getResource("ThisClassName.class"), but it may be a tiny bit fiddly.


Some time ago I made a function that gets classess from inside JAR:

public static Class[] getClasses(String packageName) 
throws ClassNotFoundException{
    ArrayList<Class> classes = new ArrayList<Class> ();

    packageName = packageName.replaceAll("\\." , "/");
    File f = new File(jarName);
    if(f.exists()){
        try{
            JarInputStream jarFile = new JarInputStream(
                    new FileInputStream (jarName));
            JarEntry jarEntry;

            while(true) {
                jarEntry=jarFile.getNextJarEntry ();
                if(jarEntry == null){
                    break;
                }
                if((jarEntry.getName ().startsWith (packageName)) &&
                        (jarEntry.getName ().endsWith (".class")) ) {
                    classes.add(Class.forName(jarEntry.getName().
                            replaceAll("/", "\\.").
                            substring(0, jarEntry.getName().length() - 6)));
                }
            }
        }
        catch( Exception e){
            e.printStackTrace ();
        }
        Class[] classesA = new Class[classes.size()];
        classes.toArray(classesA);
        return classesA;
    }else
        return null;
}

I've ported acheron55's answer to Java 7 and closed the FileSystem object. This code works in IDE's, in jar files and in a jar inside a war on Tomcat 7; but note that it does not work in a jar inside a war on JBoss 7 (it gives FileSystemNotFoundException: Provider "vfs" not installed, see also this post). Furthermore, like the original code, it is not thread safe, as suggested by errr. For these reasons I have abandoned this solution; however, if you can accept these issues, here is my ready-made code:

import java.io.IOException;
import java.net.*;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Collections;

public class ResourceWalker {

    public static void main(String[] args) throws URISyntaxException, IOException {
        URI uri = ResourceWalker.class.getResource("/resources").toURI();
        System.out.println("Starting from: " + uri);
        try (FileSystem fileSystem = (uri.getScheme().equals("jar") ? FileSystems.newFileSystem(uri, Collections.<String, Object>emptyMap()) : null)) {
            Path myPath = Paths.get(uri);
            Files.walkFileTree(myPath, new SimpleFileVisitor<Path>() { 
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    System.out.println(file);
                    return FileVisitResult.CONTINUE;
                }
            });
        }
    }
}

There are two very useful utilities both called JarScan:

  1. www.inetfeedback.com/jarscan

  2. jarscan.dev.java.net

See also this question: JarScan, scan all JAR files in all subfolders for specific class


public static ArrayList<String> listItems(String path) throws Exception{
    InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(path);
    byte[] b = new byte[in.available()];
    in.read(b);
    String data = new String(b);
    String[] s = data.split("\n");
    List<String> a = Arrays.asList(s);
    ArrayList<String> m = new ArrayList<>(a);
    return m;
}

Just a different way of listing/reading files from a jar URL and it does it recursively for nested jars

https://gist.github.com/trung/2cd90faab7f75b3bcbaa

URL urlResource = Thead.currentThread().getContextClassLoader().getResource("foo");
JarReader.read(urlResource, new InputStreamCallback() {
    @Override
    public void onFile(String name, InputStream is) throws IOException {
        // got file name and content stream 
    }
});

The most robust mechanism for listing all resources in the classpath is currently to use this pattern with ClassGraph, because it handles the widest possible array of classpath specification mechanisms, including the new JPMS module system. (I am the author of ClassGraph.)

How to know the name of the JAR file where my main class lives?

URI mainClasspathElementURI;
try (ScanResult scanResult = new ClassGraph().whitelistPackages("x.y.z")
        .enableClassInfo().scan()) {
    mainClasspathElementURI =
            scanResult.getClassInfo("x.y.z.MainClass").getClasspathElementURI();
}

How can I read the contents of a directory in a similar fashion within a JAR file?

List<String> classpathElementResourcePaths;
try (ScanResult scanResult = new ClassGraph().overrideClasspath(mainClasspathElementURI)
        .scan()) {
    classpathElementResourcePaths = scanResult.getAllResources().getPaths();
}

There are lots of other ways to deal with resources too.

참고URL : https://stackoverflow.com/questions/1429172/how-to-list-the-files-inside-a-jar-file

반응형