Pergunta

Por que é tão difícil fazer isso em Java?Se você deseja ter qualquer tipo de sistema de módulos, você precisa ser capaz de carregar jars dinamicamente.Disseram-me que há uma maneira de fazer isso escrevendo o seu próprio ClassLoader, mas isso é muito trabalhoso para algo que deveria (pelo menos na minha opinião) ser tão fácil quanto chamar um método com um arquivo jar como argumento.

Alguma sugestão de código simples que faça isso?

Foi útil?

Solução

A razão pela qual é difícil é a segurança.Classloaders devem ser imutáveis;você não deve ser capaz de adicionar classes a ele, quer queira quer não, em tempo de execução.Na verdade, estou muito surpreso que funcione com o carregador de classe do sistema.Veja como você faz isso criando seu próprio carregador de classe filho:

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

Doloroso, mas aí está.

Outras dicas

A solução a seguir é hackeada, pois usa reflexão para contornar o encapsulamento, mas funciona perfeitamente:

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

Você deveria dar uma olhada OSGi, por exemplo.implementado no Plataforma Eclipse.Faz exatamente isso.Você pode instalar, desinstalar, iniciar e parar os chamados pacotes configuráveis, que são efetivamente arquivos JAR.Mas faz um pouco mais, pois oferece, por ex.serviços que podem ser descobertos dinamicamente em arquivos JAR em tempo de execução.

Ou consulte a especificação do Sistema de módulos Java.

Que tal o Estrutura do carregador de classes JCL?Confesso que não usei, mas parece promissor.

Exemplo de uso:

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

Aqui está uma versão que não está obsoleta.Modifiquei o original para remover a funcionalidade obsoleta.

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

Com Java 9, as respostas com URLClassLoader agora dá um erro como:

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

Isso ocorre porque os carregadores de classe usados ​​foram alterados.Em vez disso, para adicionar ao carregador de classes do sistema, você pode usar o Instrumentação API por meio de um agente.

Crie uma classe de agente:

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

Adicione META-INF/MANIFEST.MF e coloque-o em um arquivo JAR com a classe do agente:

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

Execute o agente:

Isso usa o agente amigo de bytes biblioteca para adicionar o agente à JVM em execução:

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

O melhor que encontrei é org.apache.xbean.classloader.JarFileClassLoader que faz parte do XBean projeto.

Aqui está um método curto que usei no passado para criar um carregador de classes a partir de todos os arquivos lib em um diretório específico

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

Então para usar o classloader, basta fazer:

classLoader.loadClass(name);

Se você estiver trabalhando no Android, o seguinte código funciona:

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

A solução proposta por Jodonnell é boa, mas deveria ser um pouco melhorada.Usei este post para desenvolver meu aplicativo com sucesso.

Atribuir o tópico atual

Primeiramente temos que adicionar

Thread.currentThread().setContextClassLoader(classLoader);

ou você não poderá carregar recursos (como spring/context.xml) armazenados no jar.

Não inclui

seus jars no carregador de classe pai ou você não conseguirá entender quem está carregando o quê.

Veja também Problema ao recarregar um jar usando URLClassLoader

No entanto, a estrutura OSGi continua sendo o melhor caminho.

Outra versão da solução hackish de Allain, que também funciona no 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});

No JDK 11 ele fornece alguns avisos de descontinuação, mas serve como uma solução temporária para aqueles que usam a solução Allain no JDK 11.

Outra solução funcional usando Instrumentação que funciona para mim.Tem a vantagem de modificar a busca do carregador de classes, evitando problemas de visibilidade de classes para classes dependentes:

Crie uma classe de agente

Para este exemplo, ele deve estar no mesmo jar invocado pela linha de comando:

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

Modifique o MANIFEST.MF

Adicionando a referência ao agente:

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

Na verdade, eu uso o Netbeans, então esta postagem ajuda sobre como alterar o manifest.mf

Correndo

O Launcher-Agent-Class é suportado apenas no JDK 9+ e é responsável por carregar o agente sem defini-lo explicitamente na linha de comando:

 java -jar <your jar>

A maneira como funciona no JDK 6+ é definir o -javaagent argumento:

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

Adicionando novo Jar em tempo de execução

Você pode então adicionar jar conforme necessário usando o seguinte comando:

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

Não encontrei nenhum problema ao usar isso na documentação.

Aqui está uma solução rápida para o método de Allain para torná-lo compatível com versões mais recentes do 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);
}

Observe que depende do conhecimento da implementação interna de JVM específica, portanto não é ideal e não é uma solução universal.Mas é uma solução rápida e fácil se você souber que usará OpenJDK padrão ou Oracle JVM.Ele também pode quebrar em algum momento no futuro, quando uma nova versão da JVM for lançada, então você precisa manter isso em mente.

Esta pode ser uma resposta tardia, posso fazer assim (um exemplo simples para fastutil-8.2.2.jar) usando a classe jhplot.Web do 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

De acordo com a documentação, este arquivo será baixado dentro de "lib/user" e depois carregado dinamicamente, para que você possa começar imediatamente a usar as classes deste arquivo jar no mesmo programa.

por favor, dê uma olhada neste projeto que comecei: biblioteca de objeto proxy

Esta biblioteca carregará o jar do sistema de arquivos ou de qualquer outro local.Ele dedicará um carregador de classes para o jar para garantir que não haja conflitos de biblioteca.Os usuários poderão criar qualquer objeto do jar carregado e chamar qualquer método nele.Esta biblioteca foi projetada para carregar jars compilados em Java 8 a partir da base de código que suporta Java 7.

Para criar um objeto:

    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 oferece suporte a métodos de fábrica, chamadas de funções estáticas e implementações de interface de retorno de chamada.postarei mais exemplos na página leia-me.

Eu pessoalmente acho isso java.util.ServiceLoader faz o trabalho muito bem.Você pode obter um exemplo aqui.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top