วิธีโหลดไฟล์ JAR แบบไดนามิกที่รันไทม์?


308

ทำไมการทำเช่นนี้ใน Java จึงเป็นเรื่องยาก หากคุณต้องการมีระบบโมดูลใด ๆ คุณจะต้องสามารถโหลดไฟล์ JAR แบบไดนามิกได้ ฉันบอกว่ามีวิธีที่จะทำได้ด้วยการเขียนของคุณเองClassLoaderแต่นั่นเป็นงานที่ต้องทำหลายอย่างสำหรับสิ่งที่ควร (อย่างน้อยก็ในความคิดของฉัน) จะง่ายเหมือนการเรียกวิธีที่มีไฟล์ JAR เป็นอาร์กิวเมนต์

ข้อเสนอแนะสำหรับรหัสง่ายๆที่ทำเช่นนี้?


4
ฉันต้องการทำแบบเดียวกัน แต่ใช้ jar ที่โหลดในสภาพแวดล้อมแบบ sandbox เพิ่มเติม (เพื่อเหตุผลด้านความปลอดภัยอย่างชัดเจน) ตัวอย่างเช่นฉันต้องการปิดกั้นการเข้าถึงเครือข่ายและระบบไฟล์ทั้งหมด
Jus12

คำตอบ:


253

เหตุผลที่ยากคือความปลอดภัย Classloaders มีจุดมุ่งหมายที่จะไม่เปลี่ยนรูป; คุณไม่ควรเพิ่มคลาสเข้าไปใน runtime ได้เลย อันที่จริงฉันประหลาดใจมากที่ทำงานกับระบบ classloader นี่คือวิธีที่คุณสร้าง classloader ลูกของคุณเอง:

URLClassLoader child = new URLClassLoader(
        new URL[] {myJar.toURI().toURL()},
        this.getClass().getClassLoader()
);
Class classToLoad = Class.forName("com.MyClass", true, child);
Method method = classToLoad.getDeclaredMethod("myMethod");
Object instance = classToLoad.newInstance();
Object result = method.invoke(instance);

เจ็บปวด แต่ก็มี


16
ปัญหาเดียวของวิธีนี้คือคุณต้องรู้ว่าคลาสใดที่อยู่ในไห ตรงข้ามกับการโหลดไดเรกทอรีของ jars และอินสแตนซ์คลาส ฉันเข้าใจผิดหรือเปล่า?
Allain Lalonde

10
วิธีนี้ใช้งานได้ดีเมื่อทำงานใน IDE ของฉัน แต่เมื่อฉันสร้าง JAR ฉันจะได้รับ ClassNotFoundException เมื่อเรียก Class.forName ()
darrickc

29
การใช้วิธีนี้คุณต้องแน่ใจว่าคุณจะไม่เรียกวิธีการโหลดนี้มากกว่าหนึ่งครั้งสำหรับแต่ละชั้นเรียน เนื่องจากคุณกำลังสร้างคลาสโหลดเดอร์ใหม่สำหรับการดำเนินการโหลดทุกครั้งจึงไม่สามารถทราบได้ว่าคลาสนี้โหลดไปแล้วก่อนหน้า สิ่งนี้อาจมีผลกระทบที่เลวร้าย ตัวอย่างเช่นซิงเกิลตันไม่ทำงานเนื่องจากคลาสถูกโหลดหลายครั้งดังนั้นฟิลด์คงที่มีอยู่หลายครั้ง
Eduard Wirch

8
โรงงาน ถึงแม้จะมีการพึ่งพาชั้นเรียนอื่น ๆ ภายในขวด บรรทัดแรกไม่สมบูรณ์ ฉันใช้URLClassLoader child = new URLClassLoader (new URL[] {new URL("file://./my.jar")}, Main.class.getClassLoader());สมมติว่าไฟล์ jar ถูกเรียกใช้my.jarและอยู่ในไดเรกทอรีเดียวกัน
กราม

4
อย่าลืม URL url = file.toURI (). toURL ();
johnstosh

139

วิธีแก้ปัญหาต่อไปนี้เป็นการแฮ็กเนื่องจากใช้การสะท้อนเพื่อหลีกเลี่ยงการใส่ในแค็ปซูล

File file = ...
URL url = file.toURI().toURL();

URLClassLoader classLoader = (URLClassLoader)ClassLoader.getSystemClassLoader();
Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
method.setAccessible(true);
method.invoke(classLoader, url);

40
กิจกรรมทั้งหมดในการตอบสนองนี้ทำให้ฉันสงสัยว่าเรากำลังแฮ็กในการผลิตในระบบที่แตกต่างกันเท่าใด ฉันไม่แน่ใจว่าฉันต้องการทราบคำตอบหรือเปล่า
Andrei Savu

6
ใช้งานไม่ได้ดีถ้าตัวโหลดคลาสระบบเกิดขึ้นเป็นอย่างอื่นที่ไม่ใช่ URLClassLoader ...
กัส

6
Java 9+ เตือนว่าURLClassLoader.class.getDeclaredMethod("addURL", URL.class)เป็นการใช้ภาพสะท้อนที่ผิดกฎหมายและจะล้มเหลวในอนาคต
Charlweed

1
มีความคิดวิธีการอัปเดตรหัสนี้เพื่อทำงานกับ Java 9+ หรือไม่
FiReTiTi


51

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

หรือดูสเปคสำหรับระบบโมดูล Java


41

วิธีการเกี่ยวกับกรอบกระบอกชั้น JCL ? ฉันต้องยอมรับว่าฉันไม่ได้ใช้มัน แต่มันก็ดูดี

ตัวอย่างการใช้งาน:

JarClassLoader jcl = new JarClassLoader();
jcl.add("myjar.jar"); // Load jar file  
jcl.add(new URL("http://myserver.com/myjar.jar")); // Load jar from a URL
jcl.add(new FileInputStream("myotherjar.jar")); // Load jar file from stream
jcl.add("myclassfolder/"); // Load class folder  
jcl.add("myjarlib/"); // Recursively load all jar files in the folder/sub-folder(s)

JclObjectFactory factory = JclObjectFactory.getInstance();
// Create object of loaded class  
Object obj = factory.create(jcl, "mypackage.MyClass");

9
นอกจากนี้ยังมีข้อผิดพลาดบางอย่างเช่นการใช้งานที่สำคัญเช่น findResources (... ) เตรียมพร้อมที่จะใช้เวลาทั้งคืนเพื่อตรวจสอบว่าทำไมบางสิ่งบางอย่างไม่ทำงาน =)
Sergey Karpushin

ฉันยังคงสงสัยว่าคำกล่าวอ้างของ @ SergeyKarpushin ยังคงมีอยู่เนื่องจากโครงการได้รับการอัปเดตเมื่อเวลาผ่านไปเป็นเวอร์ชันหลักที่สอง อยากได้ยินประสบการณ์
Erdin Eray

2
@ErdinEray มันเป็นคำถามที่ดีมากที่ฉันถามตัวเองเช่นกันเนื่องจากเราถูก "บังคับ" ให้เปลี่ยนเป็น OpenJDK ฉันยังคงทำงานกับโปรเจ็กต์ Java และฉันไม่มีหลักฐานใด ๆ ที่ Open JDK จะทำให้คุณล้มเหลวในวันนี้ (ฉันมีปัญหาตอนนั้น) ฉันเดาว่าฉันถอนการอ้างสิทธิ์ของฉันจนกว่าฉันจะชนอย่างอื่น
Sergey Karpushin

20

นี่คือรุ่นที่ไม่ได้คัดค้าน ฉันแก้ไขต้นฉบับเพื่อลบฟังก์ชั่นที่เลิกใช้แล้ว

/**************************************************************************************************
 * Copyright (c) 2004, Federal University of So Carlos                                           *
 *                                                                                                *
 * All rights reserved.                                                                           *
 *                                                                                                *
 * Redistribution and use in source and binary forms, with or without modification, are permitted *
 * provided that the following conditions are met:                                                *
 *                                                                                                *
 *     * Redistributions of source code must retain the above copyright notice, this list of      *
 *       conditions and the following disclaimer.                                                 *
 *     * Redistributions in binary form must reproduce the above copyright notice, this list of   *
 *     * conditions and the following disclaimer in the documentation and/or other materials      *
 *     * provided with the distribution.                                                          *
 *     * Neither the name of the Federal University of So Carlos nor the names of its            *
 *     * contributors may be used to endorse or promote products derived from this software       *
 *     * without specific prior written permission.                                               *
 *                                                                                                *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS                            *
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT                              *
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR                          *
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR                  *
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,                          *
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,                            *
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR                             *
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF                         *
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING                           *
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS                             *
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.                                   *
 **************************************************************************************************/
/*
 * Created on Oct 6, 2004
 */
package tools;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

/**
 * Useful class for dynamically changing the classpath, adding classes during runtime. 
 */
public class ClasspathHacker {
    /**
     * Parameters of the method to add an URL to the System classes. 
     */
    private static final Class<?>[] parameters = new Class[]{URL.class};

    /**
     * Adds a file to the classpath.
     * @param s a String pointing to the file
     * @throws IOException
     */
    public static void addFile(String s) throws IOException {
        File f = new File(s);
        addFile(f);
    }

    /**
     * Adds a file to the classpath
     * @param f the file to be added
     * @throws IOException
     */
    public static void addFile(File f) throws IOException {
        addURL(f.toURI().toURL());
    }

    /**
     * Adds the content pointed by the URL to the classpath.
     * @param u the URL pointing to the content to be added
     * @throws IOException
     */
    public static void addURL(URL u) throws IOException {
        URLClassLoader sysloader = (URLClassLoader)ClassLoader.getSystemClassLoader();
        Class<?> sysclass = URLClassLoader.class;
        try {
            Method method = sysclass.getDeclaredMethod("addURL",parameters);
            method.setAccessible(true);
            method.invoke(sysloader,new Object[]{ u }); 
        } catch (Throwable t) {
            t.printStackTrace();
            throw new IOException("Error, could not add URL to system classloader");
        }        
    }

    public static void main(String args[]) throws IOException, SecurityException, ClassNotFoundException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException{
        addFile("C:\\dynamicloading.jar");
        Constructor<?> cs = ClassLoader.getSystemClassLoader().loadClass("test.DymamicLoadingTest").getConstructor(String.class);
        DymamicLoadingTest instance = (DymamicLoadingTest)cs.newInstance();
        instance.test();
    }
}

19
ฉันเกลียดที่จะชนเธรดเก่า แต่ฉันต้องการที่จะชี้ให้เห็นว่าเนื้อหาทั้งหมดใน stackoverflow เป็น CC ที่ได้รับอนุญาต คำชี้แจงลิขสิทธิ์ของคุณไม่มีประสิทธิภาพ stackoverflow.com/faq#editing
Huckle

43
หนอ ในทางเทคนิคเนื้อหาดั้งเดิมได้รับใบอนุญาต CC แต่ถ้าคุณโพสต์เนื้อหาที่มีลิขสิทธิ์ที่นี่จะไม่ลบข้อเท็จจริงที่ว่าเนื้อหานั้นมีลิขสิทธิ์ หากฉันโพสต์รูปภาพของ Mickey Mouse มันไม่ได้ทำให้เป็นลิขสิทธิ์ CC ดังนั้นฉันจะเพิ่มคำชี้แจงลิขสิทธิ์กลับ
Jason S

19

ในขณะที่การแก้ปัญหามากที่สุดอยู่ที่นี่มีทั้งแฮ็ก (pre JDK 9) ยากที่จะกำหนดค่า (ตัวแทน) หรือเพียงแค่ไม่ทำงานอีกต่อไป (โพสต์ JDK 9) ฉันคิดว่ามันจริงที่น่าตกใจว่าไม่มีใครกล่าวถึงวิธีการบันทึกไว้อย่างชัดเจน

คุณสามารถสร้างตัวโหลดคลาสระบบที่กำหนดเองจากนั้นคุณสามารถทำสิ่งที่คุณต้องการได้ฟรี ไม่ต้องการการสะท้อนและคลาสทั้งหมดแชร์ classloader เดียวกัน

เมื่อเริ่มต้น JVM เพิ่มแฟล็กนี้:

java -Djava.system.class.loader=com.example.MyCustomClassLoader

ตัวโหลดคลาสต้องมีตัวสร้างที่ยอมรับตัวโหลดคลาสซึ่งต้องตั้งค่าเป็นพาเรนต์ ตัวสร้างจะถูกเรียกเมื่อเริ่มต้น JVM และระบบจะโหลด classloader จริงคลาสหลักจะถูกโหลดโดยโหลดเดอร์ที่กำหนดเอง

ในการเพิ่มเหยือกก็แค่โทรClassLoader.getSystemClassLoader()แล้วส่งไปที่ชั้นเรียนของคุณ

ลองใช้เครื่องมือนี้สำหรับ classloader ที่ออกแบบมาอย่างพิถีพิถัน โปรดทราบว่าคุณสามารถเปลี่ยนadd()วิธีการเป็นสาธารณะ


ขอบคุณ - มันมีประโยชน์จริง ๆ ! การอ้างอิงอื่น ๆ ทั้งหมดเกี่ยวกับวิธีการใช้เว็บสำหรับ JDK 8 หรือก่อนหน้า - ซึ่งมีปัญหาหลายประการ
Vishal Biyani

15

ด้วยJava 9คำตอบในURLClassLoaderตอนนี้ให้ข้อผิดพลาดเช่น:

java.lang.ClassCastException: java.base/jdk.internal.loader.ClassLoaders$AppClassLoader cannot be cast to java.base/java.net.URLClassLoader

นี่เป็นเพราะคลาสโหลดเดอร์ที่ใช้มีการเปลี่ยนแปลง ในการเพิ่มไปยังคลาสโหลดเดอร์ของระบบคุณสามารถใช้Instrumentation API ผ่านตัวแทน

สร้างคลาสตัวแทน

package ClassPathAgent;

import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.util.jar.JarFile;

public class ClassPathAgent {
    public static void agentmain(String args, Instrumentation instrumentation) throws IOException {
        instrumentation.appendToSystemClassLoaderSearch(new JarFile(args));
    }
}

เพิ่ม META-INF / MANIFEST.MF และวางไว้ในไฟล์ JAR พร้อมกับเอเจนต์คลาส:

Manifest-Version: 1.0
Agent-Class: ClassPathAgent.ClassPathAgent

เรียกใช้ตัวแทน:

สิ่งนี้ใช้ไลบรารีbyte-buddy-agentเพื่อเพิ่มเอเจนต์เข้ากับ JVM ที่กำลังรัน:

import java.io.File;

import net.bytebuddy.agent.ByteBuddyAgent;

public class ClassPathUtil {
    private static File AGENT_JAR = new File("/path/to/agent.jar");

    public static void addJarToClassPath(File jarFile) {
        ByteBuddyAgent.attach(AGENT_JAR, String.valueOf(ProcessHandle.current().pid()), jarFile.getPath());
    }
}

9

สิ่งที่ฉันพบที่ดีที่สุดคือorg.apache.xbean.classloader.JarFileClassLoaderซึ่งเป็นส่วนหนึ่งของXBeanโครงการ

นี่เป็นวิธีสั้น ๆ ที่ฉันเคยใช้ในอดีตเพื่อสร้างคลาสโหลดเดอร์จากไฟล์ lib ทั้งหมดในไดเรกทอรีเฉพาะ

public void initialize(String libDir) throws Exception {
    File dependencyDirectory = new File(libDir);
    File[] files = dependencyDirectory.listFiles();
    ArrayList<URL> urls = new ArrayList<URL>();
    for (int i = 0; i < files.length; i++) {
        if (files[i].getName().endsWith(".jar")) {
        urls.add(files[i].toURL());
        //urls.add(files[i].toURI().toURL());
        }
    }
    classLoader = new JarFileClassLoader("Scheduler CL" + System.currentTimeMillis(), 
        urls.toArray(new URL[urls.size()]), 
        GFClassLoader.class.getClassLoader());
}

จากนั้นให้ใช้ classloader เพียงทำ:

classLoader.loadClass(name);

โปรดทราบว่าโครงการไม่ได้รับการดูแลอย่างดี โรดแมพของพวกเขาสำหรับอนาคตมีหลายรุ่นสำหรับปี 2014 เช่น
Zero3

6

หากคุณกำลังทำงานบน Android รหัสต่อไปนี้ใช้งานได้:

String jarFile = "path/to/jarfile.jar";
DexClassLoader classLoader = new DexClassLoader(jarFile, "/data/data/" + context.getPackageName() + "/", null, getClass().getClassLoader());
Class<?> myClass = classLoader.loadClass("MyClass");

6

นี่เป็นวิธีแก้ปัญหาอย่างรวดเร็วสำหรับวิธีของ Allain เพื่อให้เข้ากันได้กับ Java เวอร์ชันใหม่กว่า:

ClassLoader classLoader = ClassLoader.getSystemClassLoader();
try {
    Method method = classLoader.getClass().getDeclaredMethod("addURL", URL.class);
    method.setAccessible(true);
    method.invoke(classLoader, new File(jarPath).toURI().toURL());
} catch (NoSuchMethodException e) {
    Method method = classLoader.getClass()
            .getDeclaredMethod("appendToClassPathForInstrumentation", String.class);
    method.setAccessible(true);
    method.invoke(classLoader, jarPath);
}

โปรดทราบว่าขึ้นอยู่กับความรู้ในการใช้งาน JVM เฉพาะภายในดังนั้นจึงไม่เหมาะและไม่ใช่โซลูชันที่เป็นสากล แต่มันเป็นวิธีแก้ปัญหาที่ง่ายและรวดเร็วหากคุณรู้ว่าคุณกำลังใช้ OpenJDK มาตรฐานหรือ Oracle JVM มันอาจแตกในบางจุดในอนาคตเมื่อมีการเปิดตัว JVM เวอร์ชั่นใหม่ดังนั้นคุณต้องจำไว้


ด้วย Java 11.0.2 ฉันได้รับ:Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make void jdk.internal.loader.ClassLoaders$AppClassLoader.appendToClassPathForInstrumentation(java.lang.String) accessible: module java.base does not "opens jdk.internal.loader" to unnamed module @18ef96
Richard Żak

ทำงานร่วมกับ Java 8 EE ในสภาพแวดล้อมแอพพลิเคชันเซิร์ฟเวอร์
ม.ค.

4

โซลูชันที่เสนอโดย jodonnell นั้นดี แต่ควรปรับปรุงให้ดีขึ้นเล็กน้อย ฉันใช้โพสต์นี้เพื่อพัฒนาแอปพลิเคชันของฉันให้สำเร็จ

กำหนดเธรดปัจจุบัน

ประการแรกเราต้องเพิ่ม

Thread.currentThread().setContextClassLoader(classLoader);

หรือคุณจะไม่สามารถโหลดทรัพยากร (เช่น spring / context.xml) ที่เก็บไว้ใน jar

ไม่รวม

ขวดของคุณลงในโหลดเดอร์ระดับผู้ปกครองหรือคุณจะไม่สามารถเข้าใจได้ว่าใครกำลังโหลดอะไรอยู่

ดูที่ปัญหาในการรีโหลด jar โดยใช้ URLClassLoader

อย่างไรก็ตามกรอบ OSGi ยังคงเป็นวิธีที่ดีที่สุด


2
คำตอบของคุณดูเหมือนสับสนเล็กน้อยและอาจเหมาะสมกว่าในการแสดงความคิดเห็นต่อคำตอบของ jodonnell หากเป็นการปรับปรุงที่ง่าย
Zero3

4

อีกรุ่นของโซลูชันแฮ็คจาก Allain ที่ยังใช้งานได้กับ JDK 11:

File file = ...
URL url = file.toURI().toURL();
URLClassLoader sysLoader = new URLClassLoader(new URL[0]);

Method sysMethod = URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{URL.class});
sysMethod.setAccessible(true);
sysMethod.invoke(sysLoader, new Object[]{url});

บน JDK 11 จะมีคำเตือนการเลิกใช้งาน แต่ทำหน้าที่เป็นโซลูชันชั่วคราวสำหรับผู้ที่ใช้โซลูชัน Allain ใน JDK 11


ฉันสามารถลบ jar ได้ด้วยหรือไม่
user7294900

3

อีกวิธีการแก้ปัญหาการทำงานโดยใช้เครื่องมือที่เหมาะกับฉัน มันมีข้อดีของการปรับเปลี่ยนการค้นหาคลาสโหลดเดอร์หลีกเลี่ยงปัญหาการมองเห็นคลาสสำหรับคลาสที่ขึ้นต่อกัน:

สร้าง Agent Class

สำหรับตัวอย่างนี้จะต้องอยู่ใน jar เดียวกันที่เรียกใช้โดยบรรทัดคำสั่ง:

package agent;

import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.util.jar.JarFile;

public class Agent {
   public static Instrumentation instrumentation;

   public static void premain(String args, Instrumentation instrumentation) {
      Agent.instrumentation = instrumentation;
   }

   public static void agentmain(String args, Instrumentation instrumentation) {
      Agent.instrumentation = instrumentation;
   }

   public static void appendJarFile(JarFile file) throws IOException {
      if (instrumentation != null) {
         instrumentation.appendToSystemClassLoaderSearch(file);
      }
   }
}

แก้ไข MANIFEST.MF

การเพิ่มการอ้างอิงไปยังเอเจนต์:

Launcher-Agent-Class: agent.Agent
Agent-Class: agent.Agent
Premain-Class: agent.Agent

ฉันใช้ Netbeans จริง ๆ แล้วโพสต์นี้จะช่วยในการเปลี่ยน manifest.mf

วิ่ง

Launcher-Agent-Classได้รับการสนับสนุนเฉพาะใน JDK 9+ และเป็นผู้รับผิดชอบสำหรับการโหลดตัวแทนโดยไม่ต้องกำหนดอย่างชัดเจนไว้ในบรรทัดคำสั่ง:

 java -jar <your jar>

วิธีการทำงานกับ JDK 6+ นั้นเป็นการกำหนด-javaagentข้อโต้แย้ง:

java -javaagent:<your jar> -jar <your jar>

เพิ่ม Jar ใหม่ที่ Runtime

จากนั้นคุณสามารถเพิ่ม jar ตามความจำเป็นโดยใช้คำสั่งต่อไปนี้:

Agent.appendJarFile(new JarFile(<your file>));

ฉันไม่พบปัญหาใด ๆ ในการใช้เอกสารนี้


ด้วยเหตุผลบางอย่างเมื่อใช้โซลูชันนี้ฉันได้รับ "ข้อยกเว้นในเธรด" หลัก "java.lang.ClassNotFoundException: agent.Agent" ฉันบรรจุคลาส "Agent" ลงในแอปพลิเคชั่น "สงคราม" หลักของฉันดังนั้นฉันแน่ใจว่ามันอยู่ที่นั่น
Sergei Ledvanov

3

ในกรณีที่ทุกคนค้นหาสิ่งนี้ในอนาคตวิธีนี้ใช้ได้กับฉันด้วย OpenJDK 13.0.2

ฉันมีหลายคลาสที่ฉันต้องการอินสแตนซ์ของรันไทม์แบบไดนามิกแต่ละคลาสมีความแตกต่างกัน

ในรหัสนี้ฉันมีวัตถุที่เรียกว่า pack ซึ่งเก็บข้อมูลเมตาเกี่ยวกับคลาสที่ฉันพยายามโหลด เมธอด getObjectFile () ส่งคืนตำแหน่งของไฟล์คลาสสำหรับคลาส เมธอด getObjectRootPath () ส่งคืนพา ธ ไปยัง bin / directory ที่มีคลาสไฟล์ที่รวมคลาสที่ฉันพยายามอินสแตนซ์ เมธอด getLibPath () ส่งคืนพา ธ ไปยังไดเร็กทอรีที่มีไฟล์ jar ที่ประกอบเป็น classpath สำหรับโมดูลที่คลาสเป็นส่วนหนึ่งของ

File object = new File(pack.getObjectFile()).getAbsoluteFile();
Object packObject;
try {
    URLClassLoader classloader;

    List<URL> classpath = new ArrayList<>();
    classpath.add(new File(pack.getObjectRootPath()).toURI().toURL());
    for (File jar : FileUtils.listFiles(new File(pack.getLibPath()), new String[] {"jar"}, true)) {
        classpath.add(jar.toURI().toURL());
    }
    classloader = new URLClassLoader(classpath.toArray(new URL[] {}));

    Class<?> clazz = classloader.loadClass(object.getName());
    packObject = clazz.getDeclaredConstructor().newInstance();

} catch (Exception e) {
    e.printStackTrace();
    throw e;
}
return packObject;

ฉันใช้การพึ่งพา Maven: org.xeustechnologies: jcl-core: 2.8 เพื่อทำสิ่งนี้มาก่อน แต่หลังจากผ่าน JDK 1.8 ที่ผ่านมาบางครั้งมันก็แข็งตัวและไม่กลับมาติดอยู่ที่ "กำลังรอการอ้างอิง" ที่ Reference :: waitForReferencePendingList ()

ฉันยังเก็บแผนที่ของรถตักของชั้นเรียนเพื่อให้พวกเขาสามารถนำกลับมาใช้ใหม่ได้ถ้าชั้นที่ฉันพยายามจะสร้างอินสแตนซ์อยู่ในโมดูลเดียวกันกับชั้นที่ฉันได้สร้างอินสแตนซ์แล้วซึ่งฉันอยากจะแนะนำ


2

โปรดดูโปรเจ็กต์ที่ฉันเริ่มต้น: proxy-object lib

lib นี้จะโหลด jar จากระบบไฟล์หรือตำแหน่งอื่น ๆ มันจะอุทิศคลาสโหลดเดอร์สำหรับ jar เพื่อให้แน่ใจว่าไม่มีข้อขัดแย้งของไลบรารี ผู้ใช้จะสามารถสร้างวัตถุใด ๆ จาก jar ที่โหลดและเรียกใช้วิธีการใด ๆ lib นี้ถูกออกแบบมาเพื่อโหลด jars ที่คอมไพล์ใน Java 8 จากรหัสฐานที่รองรับ Java 7

วิธีสร้างวัตถุ:

    File libDir = new File("path/to/jar");

    ProxyCallerInterface caller = ObjectBuilder.builder()
            .setClassName("net.proxy.lib.test.LibClass")
            .setArtifact(DirArtifact.builder()
                    .withClazz(ObjectBuilderTest.class)
                    .withVersionInfo(newVersionInfo(libDir))
                    .build())
            .build();
    String version = caller.call("getLibVersion").asString();

ObjectBuilder สนับสนุนวิธีการจากโรงงานการเรียกใช้ฟังก์ชันคงที่และการใช้อินเทอร์เฟซแบบย้อนกลับ ฉันจะโพสต์ตัวอย่างเพิ่มเติมในหน้า readme


2

นี่อาจเป็นการตอบสนองที่ล่าช้าฉันสามารถทำได้เช่นนี้ (ตัวอย่างง่าย ๆ สำหรับ fastutil-8.2.2.jar) โดยใช้คลาส jhplot.Web จาก DataMelt ( http://jwork.org/dmelt )

import jhplot.Web;
Web.load("http://central.maven.org/maven2/it/unimi/dsi/fastutil/8.2.2/fastutil-8.2.2.jar"); // now you can start using this library

ตามเอกสารประกอบไฟล์นี้จะถูกดาวน์โหลดภายใน "lib / user" จากนั้นโหลดแบบไดนามิกดังนั้นคุณสามารถเริ่มต้นได้ทันทีโดยใช้คลาสจากไฟล์ jar นี้ในโปรแกรมเดียวกัน


1

ฉันต้องการโหลดไฟล์ jar ตอนรันไทม์สำหรับทั้ง java 8 และ java 9+ (ความคิดเห็นด้านบนไม่สามารถใช้ได้กับทั้งสองเวอร์ชัน) นี่คือวิธีการทำ (ใช้ Spring Boot 1.5.2 หากเกี่ยวข้อง)

public static synchronized void loadLibrary(java.io.File jar) {
    try {            
        java.net.URL url = jar.toURI().toURL();
        java.lang.reflect.Method method = java.net.URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{java.net.URL.class});
        method.setAccessible(true); /*promote the method to public access*/
        method.invoke(Thread.currentThread().getContextClassLoader(), new Object[]{url});
    } catch (Exception ex) {
        throw new RuntimeException("Cannot load library from jar file '" + jar.getAbsolutePath() + "'. Reason: " + ex.getMessage());
    }
}

-2

ฉันเองพบว่าjava.util.ServiceLoaderทำงานได้ค่อนข้างดี คุณจะได้รับตัวอย่างที่นี่


11
ServiceLoader ไม่เพิ่มไฟล์ jar ในขณะใช้งานจริง ไฟล์ jar ต้องอยู่ใน classpath ก่อนหน้านี้
angelcervera
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.