Java, Classpath, Classloading => Várias versões do mesmo jar / project

Eu sei que isso pode ser uma pergunta boba para codificadores experientes. Mas eu tenho uma biblioteca (um cliente http) que alguns dos outros frameworks / jars usados ​​no meu projeto requerem. Mas todos eles exigem diferentes versões principais como:

httpclient-v1.jar => Required by cralwer.jar httpclient-v2.jar => Required by restapi.jar httpclient-v3.jar => required by foobar.jar 

O classloader é inteligente o suficiente para separá-los de alguma forma? Mais provável que não? Como o Classloader lida com isso, no caso de uma class ser a mesma em todos os três jars. Qual deles está carregado e por quê?

O Classloader apenas capta exatamente um jar ou mistura classs arbitrariamente? Então, por exemplo, se uma class é carregada a partir do Version-1.jar, todas as outras classs carregadas do mesmo classloader irão para o mesmo jar?

Como você lida com esse problema?

Existe algum truque para de alguma forma “incorporar” os jars no “required.jar” para que sejam vistos como “one unit / package” pelo Classloader , ou de alguma forma ligados?

Problemas relacionados ao Classloader são um assunto bastante complexo. Você deve, em qualquer caso, ter em mente alguns fatos:

  • Carregadores de classs em um aplicativo geralmente são mais do que um único. O carregador de classs de bootstrap delega para o apropriado. Quando você instancia uma nova class, o classloader mais específico é chamado. Se ele não encontrar uma referência para a class que você está tentando carregar, ele delegará para seu pai, e assim por diante, até você chegar ao carregador de classs de autoboot. Se nenhum deles encontrar uma referência para a class que você está tentando carregar, você receberá uma ClassNotFoundException.

  • Se você tiver duas classs com o mesmo nome binário, pesquisáveis ​​pelo mesmo carregador de class e quiser saber qual delas você está carregando, só será possível inspecionar o modo como esse classloader específico tenta resolver um nome de class.

  • De acordo com a especificação da linguagem java, não há uma restrição de exclusividade para um nome binário de class, mas, até onde eu posso ver, ele deve ser exclusivo para cada classloader.

Eu posso descobrir uma maneira de carregar duas classs com o mesmo nome binário, e envolve tê-las carregadas (e todas as suas dependencies) por dois carregadores de classs diferentes, sobrescrevendo o comportamento padrão. Um exemplo grosseiro:

  ClassLoader loaderA = new MyClassLoader(libPathOne); ClassLoader loaderB = new MyClassLoader(libPathTwo); Object1 obj1 = loaderA.loadClass("first.class.binary.name", true) Object2 obj2 = loaderB.loadClass("second.class.binary.name", true); 

Eu sempre achei a personalização do carregador de classs uma tarefa complicada. Eu prefiro sugerir para evitar várias dependencies incompatíveis, se possível.

Cada classload escolhe exatamente uma class. Geralmente o primeiro encontrado.

O OSGi visa resolver o problema de várias versões do mesmo jar. Equinox e Apache Felix são as implementações comuns de código aberto para OSGi.

O Classloader carregará as classs do jar que estava no caminho de class primeiro. Normalmente, versões incompatíveis da biblioteca terão diferença nos pacotes, mas no caso improvável elas realmente são incompatíveis e não podem ser substituídas por uma – tente jarjar.

Os classloaders carregam a class sob demanda. Isso significa que a class exigida primeiro por seu aplicativo e bibliotecas relacionadas seria carregada antes de outras classs; a solicitação para carregar as classs dependentes é normalmente emitida durante o processo de carregamento e vinculação de uma class dependente.

É provável que você encontre LinkageError s afirmando que definições de class duplicadas foram encontradas para carregadores de classs normalmente não tentam determinar qual class deve ser carregada primeiro (se houver duas ou mais classs com o mesmo nome presentes no caminho de class do carregador). Às vezes, o classloader carregará a primeira class que está ocorrendo no caminho de class e ignorará as classs duplicadas, mas isso depende da implementação do carregador.

A prática recomendada para resolver esse tipo de erro é utilizar um carregador de class separado para cada conjunto de bibliotecas que tenham dependencies conflitantes. Dessa forma, se um carregador de class tentar carregar classs de uma biblioteca, as classs dependentes serão carregadas pelo mesmo carregador de classs que não tem access às outras bibliotecas e dependencies.