it-swarm.asia

كيفية سرد الملفات داخل ملف JAR؟

لدي هذا الرمز الذي يقرأ جميع الملفات من الدليل.

    File textFolder = new File("text_directory");

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

إنه عمل رائع. تملأ الصفيف بكافة الملفات التي تنتهي بـ ".txt" من الدليل "text_directory".

كيف يمكنني قراءة محتويات الدليل بطريقة مماثلة داخل ملف 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));

ولكن فقط لأنني أعرف اسم الملف ، بالنسبة للباقي ، يجب أن أقوم بتحميله ديناميكيًا.

104
OscarRyz
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 ، يمكنك إنشاء FileSystem من ملف JAR (Zip) ، ثم استخدام آليات دليل وتصفية دليل NIO للبحث فيه. هذا من شأنه أن يسهل كتابة التعليمات البرمجية التي تتعامل مع JARs والدلائل "انفجرت".

84
erickson

التعليمات البرمجية التي تعمل لملفات 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());
        }
    }
}
64
acheron55

إريكسون الإجابة عملت تماما:

إليك رمز العمل.

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]));
20
OscarRyz

أرغب في التوسع في answer الجواب - لأنه حل غير آمن للغاية ، لعدة أسباب:

  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 (على سبيل المثال ، في getClass().getResource().

تعد واجهة Java NIO المعينة نوعًا من الرهيبة ، لأنها تقدم موردًا عامًا/أحاديًا غير آمن للخيط ، وتوثيقه غامض للغاية (الكثير من المجهول بسبب تطبيقات موفر معينة). قد تختلف النتائج لمقدمي FileSystem الآخرين (وليس JAR). ربما هناك سبب وجيه لكونه بهذه الطريقة ؛ لا أعرف ، لم أقم بالبحث في التطبيقات.

8
Eyal Roth

لذلك أعتقد أن مشكلتي الرئيسية هي كيفية معرفة اسم الجرة التي يعيش فيها صفي الرئيسي.

على افتراض أن مشروعك معبأ في Jar (ليس صحيحًا بالضرورة!) ، يمكنك استخدام ClassLoader.getResource () أو findResource () مع اسم الفئة (متبوعًا .class) للحصول على الجرة التي تحتوي على فئة معينة. سيتعين عليك تحليل اسم الجرة من عنوان URL الذي يتم إرجاعه (وليس بهذه الصعوبة) ، والذي سأتركه كتمرين للقارئ :-)

تأكد من اختبار الحالة التي لا يكون فيها الفصل جزءًا من الجرة.

5
Kevin Day

إليك طريقة كتبتها عن "تشغيل جميع 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);
    }
}
5
Ran Biron

لقد نقلت إجابة acheron55 إلى Java 7 وأغلقت الكائن FileSystem. يعمل هذا الرمز في IDE ، في ملفات جرة وفي جرة داخل حرب على Tomcat 7 ؛ لكن لاحظ أنه لا يعمل في جرة داخل حرب على JBoss 7 (يعطي FileSystemNotFoundException: Provider "vfs" not installed ، راجع أيضًا هذا المنشور ). علاوة على ذلك ، مثل الرمز الأصلي ، فإنه ليس آمنًا في سلاسل الرسائل ، كما هو مقترح بواسطة errr . لهذه الأسباب تخلت عن هذا الحل ؛ ومع ذلك ، إذا كنت تستطيع قبول هذه المشكلات ، فإليك التعليمات البرمجية الجاهزة:

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;
                }
            });
        }
    }
}
3
Pino

فيما يلي مثال على استخدام Reflections library لمسح classpath بشكل متكرر من خلال نمط اسم regex المُضاف مع زوج من 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);
}

هذا يعمل مع كل من الجرار والفصول انفجرت.

3
Vadzim

ملف jar هو مجرد ملف مضغوط به بيان منظم. يمكنك فتح ملف jar باستخدام أدوات Java Zip المعتادة ومسح محتويات الملف بهذه الطريقة ، وتضخيم التدفقات ، وما إلى ذلك ، ثم استخدم ذلك في مكالمة getResourceAsStream ، ويجب أن تكون جميعها مكتنزة.

تحرير/بعد التوضيح

استغرق الأمر دقيقة واحدة لتتذكر كل القطع والأجزاء ، وأنا متأكد من أن هناك طرقًا أنظف للقيام بذلك ، لكنني أردت أن أرى أنني لم أكن مجنونة. في مشروعي image.jpg هو ملف في جزء من ملف الجرة الرئيسي. أحصل على أداة تحميل الفئة للفئة الرئيسية (SomeClass هي نقطة الدخول) واستخدمها لاكتشاف مورد image.jpg. ثم بعض دفق السحر للحصول عليه في هذا الشيء ImageInputStream وكل شيء على ما يرام.

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
3
Mikeb

بالنظر إلى ملف JAR الفعلي ، يمكنك سرد المحتويات باستخدام JarFile.entries(). ستحتاج إلى معرفة موقع ملف JAR على الرغم من ذلك - لا يمكنك فقط أن تطلب من محمل الفصل أن يسرد كل ما يمكنه الحصول عليه.

يجب أن تكون قادرًا على تحديد موقع ملف JAR استنادًا إلى عنوان URL الذي تم إرجاعه من ThisClassName.class.getResource("ThisClassName.class") ، لكن قد يكون الأمر بسيطًا بعض الشيء.

3
Jon Skeet

هناك نوعان من الأدوات المساعدة المفيدة للغاية كلاهما يسمى JarScan:

  1. www.inetfeedback.com/jarscan

  2. jarscan.dev.Java.net

راجع أيضًا هذا السؤال:JarScan ، امسح جميع ملفات JAR في جميع المجلدات الفرعية لفئة معينة

1
jgran

مجرد طريقة مختلفة لإدراج/قراءة الملفات من عنوان URL للجرار وهي تقوم بذلك بشكل متكرر للجرار المتداخلة

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 
    }
});
0
erolagnab