Por que é tão difícil fazer isso em Java? Se você deseja ter qualquer tipo de sistema de módulos, precisa poder carregar arquivos JAR dinamicamente. Disseram-me que há uma maneira de fazê-lo escrevendo o seu próprio ClassLoader
, mas isso dá muito trabalho para algo que deve (pelo menos em minha mente) ser tão fácil quanto chamar um método com um arquivo JAR como argumento.
Alguma sugestão para código simples que faz isso?
java
jar
classloader
Allain Lalonde
fonte
fonte
Respostas:
A razão pela qual é difícil é a segurança. Os classloaders devem ser imutáveis; você não deve poder adicionar classes a ela em tempo de execução. Estou realmente muito surpreso que funcione com o carregador de classe do sistema. Aqui está como você faz seu próprio carregador de classes filho:
Doloroso, mas aí está.
fonte
URLClassLoader child = new URLClassLoader (new URL[] {new URL("file://./my.jar")}, Main.class.getClassLoader());
assumir que o arquivo jar é chamadomy.jar
e está localizado no mesmo diretório.A solução a seguir é imprudente, pois usa a reflexão para ignorar o encapsulamento, mas funciona perfeitamente:
fonte
URLClassLoader.class.getDeclaredMethod("addURL", URL.class)
é um uso ilegal de reflexão e falhará no futuro.Você deve dar uma olhada no OSGi , por exemplo, implementado na plataforma Eclipse . Faz exatamente isso. É possível instalar, desinstalar, iniciar e parar os chamados pacotes configuráveis, que são efetivamente arquivos JAR. Mas faz um pouco mais, pois oferece, por exemplo, serviços que podem ser descobertos dinamicamente em arquivos JAR em tempo de execução.
Ou consulte a especificação para o Java Module System .
fonte
E a estrutura do carregador de classes JCL ? Eu tenho que admitir, eu não usei, mas parece promissor.
Exemplo de uso:
fonte
Aqui está uma versão que não foi preterida. Modifiquei o original para remover a funcionalidade descontinuada.
fonte
Enquanto a maioria das soluções listadas aqui são hacks (pré-JDK 9) difíceis de configurar (agentes) ou simplesmente não funcionam mais (pós-JDK 9), acho realmente chocante que ninguém tenha mencionado um método claramente documentado .
Você pode criar um carregador de classes de sistema personalizado e, em seguida, pode fazer o que quiser. Nenhuma reflexão é necessária e todas as classes compartilham o mesmo carregador de classes.
Ao iniciar a JVM, adicione este sinalizador:
O carregador de classes deve ter um construtor aceitando um carregador de classes, que deve ser definido como seu pai. O construtor será chamado na inicialização da JVM e o carregador de classes real do sistema será passado, a classe principal será carregada pelo carregador customizado.
Para adicionar jarros, basta ligar
ClassLoader.getSystemClassLoader()
e transmitir à sua turma.Confira esta implementação para obter um carregador de classe cuidadosamente criado. Observe que você pode alterar o
add()
método para público.fonte
Com o Java 9 , as respostas com
URLClassLoader
agora dão um erro como:Isso ocorre porque os carregadores de classes usados foram alterados. Em vez disso, para adicionar ao carregador de classes do sistema, você pode usar a API de Instrumentação por meio de um agente.
Crie uma classe de agente:
Inclua META-INF / MANIFEST.MF e coloque-o em um arquivo JAR com a classe do agente:
Execute o agente:
Isso usa a biblioteca byte-buddy-agent para incluir o agente na JVM em execução:
fonte
O melhor que encontrei é org.apache.xbean.classloader.JarFileClassLoader, que faz parte do projeto XBean .
Aqui está um método curto que eu usei no passado, para criar um carregador de classes a partir de todos os arquivos lib em um diretório específico
Em seguida, para usar o carregador de classe, basta:
fonte
Se você estiver trabalhando no Android, o seguinte código funcionará:
fonte
Aqui está uma solução rápida para o método de Allain para torná-lo compatível com as versões mais recentes do Java:
Observe que ele se baseia no conhecimento da implementação interna de JVM específica, portanto, não é o ideal e não é uma solução universal. Mas é uma solução rápida e fácil se você souber que usará o OpenJDK padrão ou o Oracle JVM. Também pode ser interrompido em algum momento no futuro quando a nova versão da JVM for lançada, portanto, é preciso ter isso em mente.
fonte
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
A solução proposta por jodonnell é boa, mas deve ser um pouco aprimorada. Eu usei este post para desenvolver meu aplicativo com sucesso.
Atribuir o segmento atual
Em primeiro lugar, temos que adicionar
ou você não poderá carregar o recurso (como spring / context.xml) armazenado no jar.
Não inclui
seus jars no carregador de classes pai ou você não conseguirá entender quem está carregando o quê.
consulte também Problema ao recarregar um jar usando URLClassLoader
No entanto, a estrutura OSGi continua sendo a melhor maneira.
fonte
Outra versão da solução hackish da Allain, que também funciona no JDK 11:
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.
fonte
Outra solução de trabalho usando a Instrumentação que funciona para mim. Tem a vantagem de modificar a pesquisa do carregador de classes, evitando problemas na visibilidade de classes para classes dependentes:
Criar uma classe de agente
Para este exemplo, ele deve estar no mesmo jar chamado pela linha de comando:
Modifique o MANIFEST.MF
Adicionando a referência ao agente:
Na verdade, eu uso o Netbeans, portanto, este post ajuda a alterar o manifest.mf
Corrida
Ele
Launcher-Agent-Class
é suportado apenas no JDK 9+ e é responsável por carregar o agente sem defini-lo explicitamente na linha de comandos:A maneira que funciona no JDK 6+ é definir o
-javaagent
argumento:Adicionando novo Jar em tempo de execução
Você pode adicionar jar conforme necessário, usando o seguinte comando:
Não encontrei nenhum problema ao usar isso na documentação.
fonte
Caso alguém o procure no futuro, isso funcionará para mim com o OpenJDK 13.0.2.
Eu tenho muitas classes que preciso instanciar dinamicamente no tempo de execução, cada uma potencialmente com um caminho de classe diferente.
Nesse código, eu já tenho um objeto chamado pack, que contém alguns metadados sobre a classe que estou tentando carregar. O método getObjectFile () retorna o local do arquivo de classe para a classe. O método getObjectRootPath () retorna o caminho para o diretório bin / que contém os arquivos de classe que incluem a classe que estou tentando instanciar. O método getLibPath () retorna o caminho para um diretório que contém os arquivos jar que constituem o caminho de classe para o módulo do qual a classe faz parte.
Eu estava usando a dependência do Maven: org.xeustechnologies: jcl-core: 2.8 para fazer isso antes, mas depois de passar pelo JDK 1.8, às vezes congelava e nunca voltava a ficar "aguardando referências" em Reference :: waitForReferencePendingList ().
Também estou mantendo um mapa dos carregadores de classes para que possam ser reutilizados se a classe que estou tentando instanciar estiver no mesmo módulo que uma classe que eu já instanciei, o que eu recomendaria.
fonte
por favor, dê uma olhada neste projeto que eu iniciei: proxy-lib lib
Essa lib carregará o jar do sistema de arquivos ou de qualquer outro local. Ele dedicará um carregador de classes ao 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. Essa lib foi projetada para carregar os jarros compilados no Java 8 a partir da base de código que suporta o Java 7.
Para criar um objeto:
O ObjectBuilder suporta métodos de fábrica, chamando funções estáticas e implementações de interface de retorno de chamada. estarei postando mais exemplos na página leia-me.
fonte
Isso pode ser uma resposta tardia, eu posso fazer isso da seguinte maneira (um exemplo simples para fastutil-8.2.2.jar) usando a classe jhplot.Web da DataMelt ( http://jwork.org/dmelt )
De acordo com a documentação, esse arquivo será baixado dentro de "lib / user" e carregado dinamicamente, para que você possa começar a usar imediatamente as classes desse arquivo jar no mesmo programa.
fonte
Eu precisava carregar um arquivo jar em tempo de execução para o Java 8 e o Java 9+ (os comentários acima não funcionam para ambas as versões). Aqui está o método para fazer isso (usando o Spring Boot 1.5.2, se estiver relacionado).
fonte
Pessoalmente, acho que o java.util.ServiceLoader faz o trabalho muito bem. Você pode obter um exemplo aqui .
fonte