كيف يمكنني تحميل الجرار ديناميكيًا في وقت التشغيل؟

StackOverflow https://stackoverflow.com/questions/60764

  •  09-06-2019
  •  | 
  •  

سؤال

لماذا يصعب القيام بذلك في جافا؟إذا كنت ترغب في الحصول على أي نوع من أنظمة الوحدات، فيجب أن تكون قادرًا على تحميل الجرار ديناميكيًا.قيل لي أن هناك طريقة للقيام بذلك عن طريق الكتابة بنفسك ClassLoader, ، ولكن هذا يتطلب الكثير من العمل لشيء يجب (في رأيي على الأقل) أن يكون سهلاً مثل استدعاء طريقة باستخدام ملف jar كوسيطة لها.

أي اقتراحات لرمز بسيط يفعل هذا؟

هل كانت مفيدة؟

المحلول

السبب في صعوبة الأمر هو الأمن.من المفترض أن تكون أدوات تحميل الفئات غير قابلة للتغيير؛لا يجب أن تكون قادرًا على إضافة فئات إليه في وقت التشغيل طوعًا أو كرها.أنا في الواقع مندهش جدًا من العمل مع أداة تحميل فئة النظام.إليك كيفية القيام بذلك من خلال إنشاء أداة تحميل الفصل الدراسي الخاصة بك:

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);

مؤلمة، ولكن هناك.

نصائح أخرى

الحل التالي غير قانوني، لأنه يستخدم الانعكاس لتجاوز التغليف، لكنه يعمل بشكل لا تشوبه شائبة:

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);

يجب عليك إلقاء نظرة على OSGi, ، على سبيل المثال.تنفيذها في منصة الكسوف.يفعل ذلك بالضبط.يمكنك تثبيت وإلغاء تثبيت وبدء وإيقاف ما يسمى بالحزم، وهي ملفات JAR بشكل فعال.لكنه يفعل أكثر من ذلك بقليل، لأنه يقدم على سبيل المثال.الخدمات التي يمكن اكتشافها ديناميكيًا في ملفات JAR في وقت التشغيل.

أو راجع مواصفات نظام وحدة جافا.

ماذا عن إطار محمل فئة 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");

هنا نسخة لم يتم إهمالها.لقد قمت بتعديل النسخة الأصلية لإزالة الوظيفة المهملة.

/**************************************************************************************************
 * 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();
    }
}

مع جافا 9, ، الإجابات مع URLClassLoader الآن أعط خطأ مثل:

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

وذلك لأن أدوات تحميل الفئة المستخدمة قد تغيرت.بدلاً من ذلك، للإضافة إلى محمل فئة النظام، يمكنك استخدام الأجهزة 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

تشغيل الوكيل:

يستخدم هذا بايت الأصدقاء وكيل مكتبة لإضافة الوكيل إلى 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());
    }
}

أفضل ما وجدته هو org.apache.xbean.classloader.JarFileClassLoader الذي هو جزء من اكس بين مشروع.

إليك طريقة قصيرة استخدمتها في الماضي لإنشاء مُحمل فئة من جميع ملفات 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.loadClass(name);

إذا كنت تعمل على نظام أندرويد، فإن الكود التالي يعمل:

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

الحل الذي اقترحه jodonnell جيد ولكن يجب تحسينه قليلاً.لقد استخدمت هذا المنشور لتطوير طلبي بنجاح.

تعيين الموضوع الحالي

أولا علينا أن نضيف

Thread.currentThread().setContextClassLoader(classLoader);

وإلا فلن تتمكن من تحميل الموارد (مثل Spring/context.xml) المخزنة في الجرة.

لا يشمل

الجرار الخاصة بك في محمل الفصل الأصلي وإلا فلن تتمكن من فهم من يقوم بتحميل ماذا.

أنظر أيضا حدثت مشكلة أثناء إعادة تحميل الجرة باستخدام URLClassLoader

ومع ذلك، يظل إطار OSGi هو أفضل طريقة.

نسخة أخرى من الحل الاختراقي من 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، يعطي بعض تحذيرات الإهمال ولكنه بمثابة حل مؤقت لأولئك الذين يستخدمون حل Alllain على JDK 11.

حل عملي آخر باستخدام الأجهزة يناسبني.يتمتع بميزة تعديل بحث أداة تحميل الفئة، وتجنب المشكلات المتعلقة برؤية الفئة للفئات التابعة:

إنشاء فئة وكيل

في هذا المثال، يجب أن يكون على نفس الجرة التي تم استدعاؤها بواسطة سطر الأوامر:

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، لذلك هذا المشنور يساعد على كيفية تغيير البيان.mf

جري

ال Launcher-Agent-Class مدعوم فقط على JDK 9+ وهو مسؤول عن تحميل الوكيل دون تعريفه بشكل صريح في سطر الأوامر:

 java -jar <your jar>

الطريقة التي تعمل بها JDK 6+ هي تحديد -javaagent دعوى:

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

إضافة جرة جديدة في وقت التشغيل

يمكنك بعد ذلك إضافة jar حسب الضرورة باستخدام الأمر التالي:

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

لم أجد أي مشاكل في استخدام هذا في الوثائق.

فيما يلي حل سريع لطريقة 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 الجديد، لذلك عليك أن تضع ذلك في الاعتبار.

يمكن أن يكون هذا استجابة متأخرة، يمكنني القيام بذلك على هذا النحو (مثال بسيط لـ 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 هذا في نفس البرنامج.

يرجى إلقاء نظرة على هذا المشروع الذي بدأته: ليب كائن الوكيل

سيقوم هذا lib بتحميل jar من نظام الملفات أو أي مكان آخر.سيتم تخصيص أداة تحميل فئة للجرة للتأكد من عدم وجود تعارضات في المكتبة.سيتمكن المستخدمون من إنشاء أي كائن من الجرة المحملة واستدعاء أي طريقة عليه.تم تصميم هذا lib لتحميل الجرار التي تم تجميعها في 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 أساليب المصنع، واستدعاء الوظائف الثابتة، وتنفيذ واجهة رد الاتصال.سأقوم بنشر المزيد من الأمثلة على الصفحة التمهيدية.

أنا شخصيا أجد ذلك java.util.ServiceLoader يقوم بهذه المهمة بشكل جيد.يمكنك الحصول على مثال هنا.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top