วิธีรับพา ธ ไปยังทรัพยากรในไฟล์ Java JAR


166

ฉันพยายามหาเส้นทางไปยังทรัพยากร แต่ฉันไม่มีโชค

ใช้งานได้ (ทั้งใน IDE และ JAR) แต่ด้วยวิธีนี้ฉันไม่สามารถรับพา ธ ไปยังไฟล์ได้เพียงเนื้อหาไฟล์:

ClassLoader classLoader = getClass().getClassLoader();
PrintInputStream(classLoader.getResourceAsStream("config/netclient.p"));

ถ้าฉันทำสิ่งนี้:

ClassLoader classLoader = getClass().getClassLoader();
File file = new File(classLoader.getResource("config/netclient.p").getFile());

ผลลัพธ์คือ: java.io.FileNotFoundException: file:/path/to/jarfile/bot.jar!/config/netclient.p (No such file or directory)

มีวิธีรับพา ธ ไปยังไฟล์ทรัพยากรหรือไม่?


1
ใช่. ฉันมีคลาสที่ฉันต้องการทำงานกับทั้งสองโฟลเดอร์ด้านนอก (ในกรณีที่ฉันต้องการเปลี่ยนพารามิเตอร์ของไฟล์ปรับแต่ง) และ JAR ที่ซ่อนไฟล์กำหนดค่าการนำไปใช้งานให้กับผู้ใช้ (เช่นสามารถแจกจ่ายได้ JAR กับทุกคน)
no_ripcord

1
ดังนั้นคลาสเพิ่งได้รับ PATH ไปยังไฟล์ (ไฟล์กำหนดค่า)
no_ripcord

3
จากนั้นคุณควรมีคลาสจัดการกับอินพุตสตรีมซึ่งคุณสามารถได้รับจากแหล่งใดก็ได้
Carl Manaster

1
ใช่ฉันรู้. แต่มันจะชัดเจนและสะอาดขึ้นกว่าเดิม แต่ขอบคุณอยู่ดี
no_ripcord

1
คุณกำลังพยายามทำสิ่งที่ชอบตัวเลือก # 4 ในคำถามนี้หรือไม่? stackoverflow.com/questions/775389/…
erickson

คำตอบ:


71

นี่คือเจตนา เนื้อหาของ "ไฟล์" อาจไม่สามารถใช้เป็นไฟล์ได้ จำไว้ว่าคุณกำลังจัดการกับคลาสและแหล่งข้อมูลที่อาจเป็นส่วนหนึ่งของไฟล์ JAR หรือทรัพยากรประเภทอื่น ตัวโหลดคลาสไม่จำเป็นต้องจัดเตรียมการจัดการไฟล์ให้กับรีซอร์สตัวอย่างเช่นไฟล์ jar อาจไม่ได้ถูกขยายเป็นไฟล์แต่ละไฟล์ในระบบไฟล์

ทุกสิ่งที่คุณสามารถทำได้โดยการเรียกใช้ java.io.File สามารถทำได้โดยการคัดลอกสตรีมไปยังไฟล์ชั่วคราวและทำเช่นเดียวกันหาก java.io.File นั้นจำเป็นอย่างยิ่ง


6
คุณสามารถเพิ่ม 'rsrc:' เมื่อคุณโทรหาทรัพยากรของคุณเพื่อเปิด เหมือนไฟล์ใหม่ ("rsrc: filename.txt") สิ่งนี้จะโหลด filename.txt ซึ่งบรรจุไว้ในรากของขวดของคุณ
gipsh

63

เมื่อโหลดทรัพยากรให้แน่ใจว่าคุณสังเกตเห็นความแตกต่างระหว่าง:

getClass().getClassLoader().getResource("com/myorg/foo.jpg") //relative path

และ

getClass().getResource("/com/myorg/foo.jpg")); //note the slash at the beginning

ฉันเดาว่าความสับสนนี้ทำให้เกิดปัญหาส่วนใหญ่เมื่อโหลดทรัพยากร


นอกจากนี้เมื่อคุณกำลังโหลดรูปภาพจะใช้งานง่ายกว่าgetResourceAsStream():

BufferedImage image = ImageIO.read(getClass().getResourceAsStream("/com/myorg/foo.jpg"));

เมื่อคุณต้องโหลดไฟล์ (ไม่ใช่ภาพ) จากไฟล์เก็บถาวร JAR คุณอาจลองทำสิ่งนี้:

File file = null;
String resource = "/com/myorg/foo.xml";
URL res = getClass().getResource(resource);
if (res.getProtocol().equals("jar")) {
    try {
        InputStream input = getClass().getResourceAsStream(resource);
        file = File.createTempFile("tempfile", ".tmp");
        OutputStream out = new FileOutputStream(file);
        int read;
        byte[] bytes = new byte[1024];

        while ((read = input.read(bytes)) != -1) {
            out.write(bytes, 0, read);
        }
        out.close();
        file.deleteOnExit();
    } catch (IOException ex) {
        Exceptions.printStackTrace(ex);
    }
} else {
    //this will probably work in your IDE, but not from a JAR
    file = new File(res.getFile());
}

if (file != null && !file.exists()) {
    throw new RuntimeException("Error: File " + file + " not found!");
}

3
+1 สิ่งนี้ใช้ได้สำหรับฉัน ตรวจสอบให้แน่ใจว่าคุณวางไฟล์ที่คุณต้องการอ่านในbinโฟลเดอร์และไปที่ไดเรกทอรีของคลาสที่โหลดในทรัพยากรก่อนที่จะใช้เส้นทาง `/com/myorg/filename.ext '
rayryeng

+1 สิ่งนี้ใช้งานได้สำหรับฉัน ... ฉันเข้าใจว่าอาจมีความเสี่ยงด้านความปลอดภัยที่เกี่ยวข้องกับวิธีการนี้ดังนั้นเจ้าของแอปพลิเคชันจึงต้องระวัง
LucasA

คุณสามารถชี้แจงข้อความนี้ "จะดีกว่าเสมอในการโหลดทรัพยากรด้วย getResourceAsStream ()"? สิ่งนี้จะช่วยแก้ปัญหาได้อย่างไร
Luca S.

@LucaS นั่นเป็นความหมายสำหรับกรณีของภาพขออภัยมันไม่ชัดเจนมาก วิธีการนี้ควรเป็นแพลตฟอร์มอิสระแม้ว่าจะเป็นวิธีที่ไม่สมบูรณ์ก็ตาม
Tombart

@LucasA คุณช่วยอธิบายความเสี่ยงที่คุณพูดถึงได้หรือไม่?
ZX9

25

คำตอบหนึ่งบรรทัดคือ -

String path = this.getClass().getClassLoader().getResource(<resourceFileName>).toExternalForm()

getResourceวิธีการโดยทั่วไปให้ URL จาก URL นี้คุณสามารถแยกเส้นทางโดยการโทรtoExternalForm()

อ้างอิง:

getResource () , toExternalForm ()


7
ในขณะที่ทำงานจากสภาพแวดล้อมของฉัน (IntelliJ) สิ่งนี้จะสร้าง URL ไฟล์แบบง่ายซึ่งทำงานได้ในทุกกรณี อย่างไรก็ตามเมื่อทำงานจาก jar เองฉันจะได้ URI คล้ายกับ jar: file: /path/to/jar/jarname.jar! /file_in_jar.mp4 ไม่ใช่ทุกสิ่งที่สามารถใช้ URI ที่ขึ้นต้นด้วย jar กรณีตรงจุดสื่อ JavaFX
Noah Ternullo

1
ฉันชอบคำตอบนี้ดีที่สุด ได้รับในกรณีส่วนใหญ่มันอาจจะดีกว่าที่จะเพียงแค่คว้า InputStream ไปยังทรัพยากรเมื่อมันอยู่ในไฟล์ jar แต่ถ้าด้วยเหตุผลบางอย่างที่คุณต้องการเส้นทางจริงๆนี่คือสิ่งที่ได้ผล ฉันต้องการเส้นทางเพื่อมอบให้กับวัตถุบุคคลที่สาม ดังนั้นขอบคุณ!
Mario

1
วิธีการแก้ปัญหาข้างต้นไม่ทำงานใน IDE ใน Intellijit เพิ่มfile:/เส้นทางซึ่งใช้งานได้ใน jar แต่ไม่ได้อยู่ใน IDE
Tayab Hussain

การตอบสนองของคุณแก้ไขปัญหาที่ฉันพยายามทำงานเป็นเวลาอย่างน้อย 2 วันแล้ว TYVM !!
Jonathan

12

ฉันใช้เวลาสักครู่ยุ่งกับปัญหานี้เพราะไม่มีวิธีแก้ปัญหาที่ฉันพบว่าใช้งานได้จริงแปลกพอ! ไดเรกทอรีการทำงานไม่ใช่ไดเรกทอรีของ JAR บ่อยครั้งโดยเฉพาะอย่างยิ่งหาก JAR (หรือโปรแกรมใด ๆ สำหรับเรื่องนั้น) ถูกเรียกใช้จาก Start Menu ใน Windows ดังนั้นนี่คือสิ่งที่ฉันทำและใช้งานได้กับไฟล์. class ที่ทำงานจากภายนอก JAR เช่นเดียวกับที่ใช้กับ JAR (ฉันทดสอบเฉพาะใน Windows 7)

try {
    //Attempt to get the path of the actual JAR file, because the working directory is frequently not where the file is.
    //Example: file:/D:/all/Java/TitanWaterworks/TitanWaterworks-en.jar!/TitanWaterworks.class
    //Another example: /D:/all/Java/TitanWaterworks/TitanWaterworks.class
    PROGRAM_DIRECTORY = getClass().getClassLoader().getResource("TitanWaterworks.class").getPath(); // Gets the path of the class or jar.

    //Find the last ! and cut it off at that location. If this isn't being run from a jar, there is no !, so it'll cause an exception, which is fine.
    try {
        PROGRAM_DIRECTORY = PROGRAM_DIRECTORY.substring(0, PROGRAM_DIRECTORY.lastIndexOf('!'));
    } catch (Exception e) { }

    //Find the last / and cut it off at that location.
    PROGRAM_DIRECTORY = PROGRAM_DIRECTORY.substring(0, PROGRAM_DIRECTORY.lastIndexOf('/') + 1);
    //If it starts with /, cut it off.
    if (PROGRAM_DIRECTORY.startsWith("/")) PROGRAM_DIRECTORY = PROGRAM_DIRECTORY.substring(1, PROGRAM_DIRECTORY.length());
    //If it starts with file:/, cut that off, too.
    if (PROGRAM_DIRECTORY.startsWith("file:/")) PROGRAM_DIRECTORY = PROGRAM_DIRECTORY.substring(6, PROGRAM_DIRECTORY.length());
} catch (Exception e) {
    PROGRAM_DIRECTORY = ""; //Current working directory instead.
}

8

หากnetclient.pอยู่ในไฟล์ JAR จะไม่มีพา ธ เนื่องจากไฟล์นั้นอยู่ในไฟล์อื่น file:/path/to/jarfile/bot.jar!/config/netclient.pในกรณีที่เส้นทางที่ดีที่สุดที่คุณสามารถมีมัน


เมื่อฉันลองและแปลง URL ของรูปแบบนี้ (... bot.jar! / config / ... ) เป็น URI จะบอกว่าเส้นทางนั้นไม่ได้เป็นแบบลำดับชั้น
gEdringer

7

คุณต้องเข้าใจเส้นทางภายในไฟล์ jar
เพียงอ้างถึงมันญาติ ดังนั้นหากคุณมีไฟล์ (myfile.txt) อยู่ใน foo.jar ภายใต้\src\main\resourcesไดเรกทอรี (สไตล์ maven) คุณจะอ้างถึงเช่น:

src/main/resources/myfile.txt

หากคุณถ่ายโอน jar ของคุณโดยใช้jar -tvf myjar.jar คุณจะเห็นผลลัพธ์และเส้นทางสัมพัทธ์ภายในไฟล์ jar และใช้ FORWARD SLASHES


คุณต้องใช้เครื่องหมายทับซ้ายหรือขวาบน Windows นั่นหมายความว่าคุณไม่สามารถใช้งานFile.separatorได้
str

3

ในกรณีของฉันฉันใช้วัตถุ URL แทน Path

ไฟล์

File file = new File("my_path");
URL url = file.toURI().toURL();

ทรัพยากรใน classpath ใช้ classloader

URL url = MyClass.class.getClassLoader().getResource("resource_name")

เมื่อฉันต้องการอ่านเนื้อหาฉันสามารถใช้รหัสต่อไปนี้:

InputStream stream = url.openStream();

และคุณสามารถเข้าถึงเนื้อหาโดยใช้ InputStream


3

นี่คือรหัสเดียวกันจากผู้ใช้ Tombart ที่มีการปิดและปิดสตรีมเพื่อหลีกเลี่ยงการคัดลอกเนื้อหาไฟล์ชั่วคราวที่ไม่สมบูรณ์จากทรัพยากร jar และมีชื่อไฟล์ temp ที่ไม่ซ้ำกัน

File file = null;
String resource = "/view/Trial_main.html" ;
URL res = getClass().getResource(resource);
if (res.toString().startsWith("jar:")) {
    try {
        InputStream input = getClass().getResourceAsStream(resource);
        file = File.createTempFile(new Date().getTime()+"", ".html");
        OutputStream out = new FileOutputStream(file);
        int read;
        byte[] bytes = new byte[1024];

        while ((read = input.read(bytes)) != -1) {
            out.write(bytes, 0, read);
        }
        out.flush();
        out.close();
        input.close();
        file.deleteOnExit();
    } catch (IOException ex) {
        ex.printStackTrace();
    }
} else {
    //this will probably work in your IDE, but not from a JAR
    file = new File(res.getFile());
}
         

2

ไฟล์เป็นสิ่งที่เป็นนามธรรมสำหรับไฟล์ในระบบไฟล์และระบบไฟล์ไม่รู้อะไรเลยเกี่ยวกับเนื้อหาของ JAR

ลองใช้ URI ฉันคิดว่ามี jar: // โปรโตคอลที่อาจเป็นประโยชน์ต่อผู้ซื้อของคุณ


1

เส้นทางต่อไปนี้ใช้ได้กับฉัน: classpath:/path/to/resource/in/jar


1
@Anatoly คุณช่วยแบ่งปันข้อมูลเพิ่มเติมได้จากข้างบน
Kasun Siyambalapitiya

1
private static final String FILE_LOCATION = "com/input/file/somefile.txt";

//Method Body


InputStream invalidCharacterInputStream = URLClassLoader.getSystemResourceAsStream(FILE_LOCATION);

การได้มาจากสิ่งนี้getSystemResourceAsStreamเป็นตัวเลือกที่ดีที่สุด โดยการรับอินพุตสตรีมแทนไฟล์หรือ URL ทำงานในไฟล์ JAR และเป็นแบบสแตนด์อะโลน


1

อาจจะช้าไปหน่อย แต่คุณอาจใช้ห้องสมุดของฉันKResourceLoaderเพื่อรับทรัพยากรจากโถของคุณ:

File resource = getResource("file.txt")

0

เมื่ออยู่ในไฟล์ jar ทรัพยากรจะอยู่ในลำดับชั้นของแพคเกจอย่างแน่นอน (ไม่ใช่ลำดับชั้นของระบบไฟล์) ดังนั้นหากคุณมีคลาส com.example.Sweet กำลังโหลดทรัพยากรชื่อ "./default.conf" ดังนั้นชื่อของทรัพยากรจะถูกระบุเป็น "/com/example/default.conf"

แต่ถ้าอยู่ในโหลก็ไม่ใช่ไฟล์ ...


0

ภายในโฟลเดอร์ ressources ของคุณ (java / main / resources) ของ jar เพิ่มไฟล์ของคุณ (เราสมมติว่าคุณได้เพิ่มไฟล์ xml ชื่อimports.xml ) หลังจากนั้นคุณฉีดResourceLoaderถ้าคุณใช้สปริงอย่างเบลโลว์

@Autowired
private ResourceLoader resourceLoader;

ฟังก์ชั่นภายในทัวร์เขียนรหัสตะโกนเพื่อโหลดไฟล์:

    Resource resource = resourceLoader.getResource("classpath:imports.xml");
    try{
        File file;
        file = resource.getFile();//will load the file
...
    }catch(IOException e){e.printStackTrace();}

0

อาจใช้วิธีนี้เพื่อแก้ปัญหาอย่างรวดเร็ว

public class TestUtility
{ 
    public static File getInternalResource(String relativePath)
    {
        File resourceFile = null;
        URL location = TestUtility.class.getProtectionDomain().getCodeSource().getLocation();
        String codeLocation = location.toString();
        try{
            if (codeLocation.endsWith(".jar"){
                //Call from jar
                Path path = Paths.get(location.toURI()).resolve("../classes/" + relativePath).normalize();
                resourceFile = path.toFile();
            }else{
                //Call from IDE
                resourceFile = new File(TestUtility.class.getClassLoader().getResource(relativePath).getPath());
            }
        }catch(URISyntaxException ex){
            ex.printStackTrace();
        }
        return resourceFile;
    }
}

คุณข้ามบริบทบางอย่างหรือไม่ สิ่งนี้ให้ฉัน:java.lang.NullPointerException: Attempt to invoke virtual method 'java.security.CodeSource java.security.ProtectionDomain.getCodeSource()' on a null object reference
Allen Luce

ฉันแก้ไขคำตอบและเขียนวิธีการทั้งหมดที่ฉันใช้ในซอฟต์แวร์
gbii

0

ติดตามรหัส!

/ src / main / ทรัพยากร / ไฟล์

streamToFile(getClass().getClassLoader().getResourceAsStream("file"))

public static File streamToFile(InputStream in) {
    if (in == null) {
        return null;
    }

    try {
        File f = File.createTempFile(String.valueOf(in.hashCode()), ".tmp");
        f.deleteOnExit();

        FileOutputStream out = new FileOutputStream(f);
        byte[] buffer = new byte[1024];

        int bytesRead;
        while ((bytesRead = in.read(buffer)) != -1) {
            out.write(buffer, 0, bytesRead);
        }

        return f;
    } catch (IOException e) {
        LOGGER.error(e.getMessage(), e);
        return null;
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.