Vra

Hoekom is dit so moeilik om dit in Java te doen?As jy enige soort modulestelsel wil hê, moet jy potte dinamies kan laai.Daar is vir my gesê daar is 'n manier om dit te doen deur jou eie te skryf ClassLoader, maar dit is baie werk vir iets wat (in my gedagtes ten minste) so maklik behoort te wees soos om 'n metode met 'n jar-lêer as sy argument te noem.

Enige voorstelle vir eenvoudige kode wat dit doen?

Was dit nuttig?

Oplossing

Die rede is dit moeilik is sekuriteit. Classloaders is bedoel onveranderlike te wees; jy mag nie in staat wees om teen wil en dank klasse by te voeg om dit tydens looptyd. Ek is eintlik baie verbaas dat werk met die stelsel classloader. Hier is hoe jy dit doen om jou eie kind 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);

pynlik, maar daar is dit.

Ander wenke

Die volgende oplossing is hackish, as dit gebruik nadenke te inkapseling omseil, maar dit werk foutloos:

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

Jy moet 'n blik op OSGi , bv neem geïmplementeer in die Eclipse Platform . Dit doen presies dit. Jy kan installeer, verwyder, begin en stop so genoem bundels, wat effektief is JAR lêers. Maar dit doen 'n bietjie meer, as dit bv bied dienste wat dinamies kan ontdek in JAR lêers tydens looptyd.

Of sien die spesifikasie vir die Java Module System .

Hoe gaan dit met die JCL klas loader raamwerk ? Ek moet erken, ek het dit nie gebruik nie, maar dit lyk belowend.

Gebruik byvoorbeeld:

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

Hier is 'n weergawe wat nie afgekeur. Ek verander die oorspronklike aan die afgekeur funksies te verwyder.

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

Met Java 9 , die antwoorde met URLClassLoader nou 'n fout soos gee:

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

Dit is omdat die klas loaders gebruik verander het. In plaas daarvan, toe te voeg tot die stelsel klas loader, kan jy die Instrumentasie API deur 'n agent.

Skep 'n agent klas:

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

Voeg META-INF / MANIFEST.MF en sit dit in 'n pot lêer met die agent klas:

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

Begin die agent:

Dit maak gebruik van die byte-buddy-agent biblioteek om die agent te voeg aan die hardloop 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());
    }
}

Die beste wat ek gevind is org.apache.xbean.classloader.JarFileClassLoader wat deel is van die XBean projek.

Hier is 'n kort metode wat ek gebruik het in die verlede, 'n klas loader uit al die lib-lêers te skep in 'n spesifieke gids

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

Toe die classloader gebruik, net doen:

classLoader.loadClass(name);

As jy besig is op Android, die volgende kode werk:

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

Die oplossing wat deur jodonnell voorgestel is, is goed, maar moet 'n bietjie verbeter word.Ek het hierdie pos gebruik om my toepassing met sukses te ontwikkel.

Ken die huidige draad toe

Eerstens moet ons byvoeg

Thread.currentThread().setContextClassLoader(classLoader);

of jy sal nie hulpbron (soos spring/context.xml) wat in die pot gestoor is, kan laai nie.

Moenie insluit nie

jou potte in die ouerklaslaaier of jy sal nie kan verstaan ​​wie wat laai nie.

sien ook Kon nie 'n pot herlaai met URLClassLoader nie

OSGi-raamwerk bly egter die beste manier.

Nog 'n weergawe van die hackish oplossing van Allain, wat ook werk op 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});

Op JDK 11 dit gee 'n paar afkeuring waarskuwings maar dien as 'n tydelike oplossing diegene wat Allain oplossing op JDK 11 gebruik.

Nog 'n werkende oplossing met behulp van instrumentasie wat werk vir my. Dit het die voordeel van die wysiging van die klas loader soek, vermy probleme op klas sigbaarheid vir afhanklik klasse:

Skep 'n agent Klas

Vir hierdie voorbeeld, dit moet wees op dieselfde jar opgeroep deur die command line:

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

Pas die MANIFEST.MF

die verwysing na die agent Voeg:

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

Ek het eintlik gebruik Netbeans, so hierdie post help oor hoe om die manifest.mf verander

Running

Die Launcher-Agent-Class word alleen ondersteun op JDK 9+ en is verantwoordelik vir die laai van die agent sonder uitdruklik definieer dit op die command line:

 java -jar <your jar>

Die manier wat werk op JDK 6+ is die definisie van die -javaagent argument:

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

nuwe Jar Voeg by Runtime

Jy kan dan voeg jar as wat nodig is deur die volgende opdrag:

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

Ek het nie enige probleme te vind met behulp van hierdie op dokumentasie.

Hier is 'n vinnige oplossing vir metode Allain se dit versoenbaar is met nuwe weergawes van Java te maak:

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

Let daarop dat dit berus op kennis van interne implementering van spesifieke JVM, so dit is nie ideaal en dit is nie 'n universele oplossing. Maar dit is 'n vinnige en maklike tydelike oplossing as jy weet dat jy gaan standaard OpenJDK of Oracle JVM gebruik. Dit kan ook breek op 'n stadium in die toekoms wanneer nuwe JVM weergawe vrygestel word, sodat jy nodig het om in gedagte te hou dat.

Dit kan 'n laat reaksie wees, ek kan dit doen as dit ( 'n eenvoudige voorbeeld vir fastutil-8.2.2.jar) met behulp van jhplot.Web klas van 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

Volgens die dokumentasie, sal hierdie lêer af te laai binne "lib / gebruiker" en dan dinamies gelaai word, sodat jy kan dadelik begin gebruik klasse van hierdie jar-lêer in dieselfde program.

asseblief 'n blik op hierdie projek wat ek begin: proxy-voorwerp lib

Dit lib sal jar van lêer stelsel of enige ander plek te laai. Dit sal 'n klas loader vir die pot te wy om seker te maak daar is geen biblioteek konflikte. Gebruikers sal in staat wees om enige voorwerp deur middel van die gelaaide jar skep en noem enige metode op dit. Dit lib is ontwerp om pype saamgestel in Java 8 van die kode basis dat Java 7 ondersteun laai.

Om 'n voorwerp te skep:

    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 ondersteun fabriek metodes, roep statiese funksies, en roep terug koppelvlak implementering. Ek sal plaas meer voorbeelde op die readme bladsy.

Ek persoonlik vind dat java.util.ServiceLoader doen die job baie goed. Jy kan kry 'n voorbeeld hier .

Gelisensieer onder: CC-BY-SA met toeskrywing
Nie verbonde aan StackOverflow
scroll top