Lidando com o “inferno Xerces” em Java / Maven?

732

No meu escritório, a simples menção da palavra Xerces é suficiente para incitar a raiva assassina dos desenvolvedores. Uma rápida olhada nas outras questões do Xerces no SO parece indicar que quase todos os usuários do Maven estão "tocados" por esse problema em algum momento. Infelizmente, entender o problema requer um pouco de conhecimento sobre a história do Xerces ...

História

  • O Xerces é o analisador XML mais utilizado no ecossistema Java. Quase todas as bibliotecas ou estruturas escritas em Java usam o Xerces em alguma capacidade (transitivamente, se não diretamente).

  • Os frascos Xerces incluídos nos binários oficiais são, até hoje, sem versão. Por exemplo, o jar de implementação do Xerces 2.11.0 é nomeado xercesImpl.jare não xercesImpl-2.11.0.jar.

  • A equipe do Xerces não usa o Maven , o que significa que eles não enviam um release oficial para o Maven Central .

  • O Xerces costumava ser lançado como um único jar ( xerces.jar), mas era dividido em dois jars, um contendo a API ( xml-apis.jar) e outro contendo as implementações dessas APIs ( xercesImpl.jar). Muitos POMs Maven mais antigos ainda declaram dependência xerces.jar. Em algum momento no passado, o Xerces também foi lançado como xmlParserAPIs.jar, do qual alguns POMs mais antigos também dependem.

  • As versões atribuídas aos jars xml-apis e xercesImpl por aqueles que implantam seus jars nos repositórios Maven geralmente são diferentes. Por exemplo, o xml-apis pode receber a versão 1.3.03 e o xercesImpl a versão 2.8.0, mesmo que ambos sejam do Xerces 2.8.0. Isso ocorre porque as pessoas frequentemente etiquetam o jar xml-apis com a versão das especificações implementadas. Existe uma discriminação muito boa, mas incompleta, aqui .

  • Para complicar, Xerces é o analisador XML usado na implementação de referência da API Java para XML Processing (JAXP), incluída no JRE. As classes de implementação são reembaladas no com.sun.*espaço para nome, o que torna perigoso acessá-las diretamente, pois elas podem não estar disponíveis em alguns JREs. No entanto, nem toda a funcionalidade do Xerces é exposta pelas APIs java.*e javax.*; por exemplo, não há API que exponha a serialização do Xerces.

  • Além da confusão, quase todos os contêineres de servlets (JBoss, Jetty, Glassfish, Tomcat etc.) são enviados com o Xerces em uma ou mais de suas /libpastas.

Problemas

Resolução de Conflitos

Por alguns - ou talvez por todos os motivos acima, muitas organizações publicam e consomem construções personalizadas do Xerces em seus POMs. Isso não é realmente um problema se você tiver um aplicativo pequeno e estiver usando apenas o Maven Central, mas rapidamente se tornará um problema para o software corporativo em que o Artifactory ou o Nexus estão fazendo proxy de vários repositórios (JBoss, Hibernate etc.):

xml-apis proxy por Artifactory

Por exemplo, a organização A pode publicar xml-apiscomo:

<groupId>org.apache.xerces</groupId>
<artifactId>xml-apis</artifactId>
<version>2.9.1</version>

Enquanto isso, a organização B pode publicar o mesmo jarque:

<groupId>xml-apis</groupId>
<artifactId>xml-apis</artifactId>
<version>1.3.04</version>

Embora B jarseja uma versão inferior a A jar, Maven não sabe que eles são o mesmo artefato porque possuem groupIds diferentes . Portanto, ele não pode executar a resolução de conflitos e os dois jars serão incluídos como dependências resolvidas:

dependências resolvidas com vários xml-apis

Classloader Hell

Como mencionado acima, o JRE é enviado com o Xerces no JAXP RI. Embora seja bom marcar todas as dependências do Xerces Maven como <exclusion>s ou como<provided>, o código de terceiros do qual você depende pode ou não funcionar com a versão fornecida no JAXP do JDK que você está usando. Além disso, você tem os frascos Xerces enviados no contêiner do servlet para lidar. Isso deixa você com várias opções: Você exclui a versão do servlet e espera que seu contêiner seja executado na versão JAXP? É melhor deixar a versão do servlet e esperar que suas estruturas de aplicativos sejam executadas na versão do servlet? Se um ou dois dos conflitos não resolvidos descritos acima conseguirem entrar no seu produto (fácil de acontecer em uma organização grande), você se encontrará rapidamente no inferno do carregador de classes, imaginando qual versão do Xerces o carregador de classe está escolhendo no tempo de execução e se é ou não vai escolher o mesmo jar no Windows e Linux (provavelmente não).

Soluções?

Nós tentamos marcar todas as dependências Xerces Maven como <provided>ou como <exclusion>, mas isso é difícil de aplicar (especialmente com uma grande equipe) dado que os artefatos têm tantos aliases ( xml-apis, xerces, xercesImpl, xmlParserAPIs, etc.). Além disso, nossas bibliotecas / estruturas de terceiros podem não ser executadas na versão JAXP ou na versão fornecida por um contêiner de servlet.

Como podemos resolver melhor esse problema com o Maven? Temos que exercer um controle tão refinado sobre nossas dependências e depois confiar no carregamento de classes em camadas? Existe alguma maneira de excluir globalmente todas as dependências do Xerces e forçar todas as nossas estruturas / bibliotecas a usar a versão JAXP?


ATUALIZAÇÃO : Joshua Spiewak enviou uma versão corrigida dos scripts de construção do Xerces para o XERCESJ-1454 que permite o upload para o Maven Central. Vote / assista / contribua para esse problema e vamos corrigir esse problema de uma vez por todas.

Justin Garrick
fonte
8
Obrigado por esta pergunta detalhada. Eu não entendo a motivação da equipe xerces. Eu imagino que eles tenham orgulho do produto e tenham prazer em usá-lo, mas o estado atual de xerces e desagradável é vergonhoso. Mesmo assim, eles podem fazer o que querem, mesmo que não faça sentido para mim. Gostaria de saber se os caras do tipo sonat têm alguma sugestão.
Travis Schneeberger
35
Talvez esse tópico esteja fora do tópico, mas este é provavelmente o melhor post que eu já vi. Mais relacionado à questão, o que você descreve é ​​uma das questões mais dolorosas que podemos encontrar. Ótima iniciativa!
Jean-Rémy Revy
2
@TravisSchneeberger Grande parte da complexidade é que a Sun optou por usar o Xerces no próprio JRE. Você dificilmente pode culpar o pessoal do Xerces por isso.
Thorbjørn Ravn Andersen
Normalmente, tentamos encontrar uma versão do Xerces que satisfaça todas as bibliotecas dependentes por tentativa e erro; se não for possível, refatorar os WARs para dividir o aplicativo em WARs separados (carregadores de classes separados). Esta ferramenta (eu escrevi) ajuda a compreender o que está acontecendo jhades.org , permitindo consultar o classpath para frascos, e as aulas - que também trabalha no caso quando o servidor não é iniciado ainda
Universidade Angular
Apenas um comentário rápido, se você está recebendo esse erro ao iniciar o servicemix a partir do git bash no Windows: inicie-o no cmd "normal".
Albert Hendriks

Respostas:

112

Existem 2.11.0 JARs (e JARs de origem!) Do Xerces no Maven Central desde 20 de fevereiro de 2013! Veja Xerces no Maven Central . Gostaria de saber por que eles não resolveram https://issues.apache.org/jira/browse/XERCESJ-1454 ...

Eu usei:

<dependency>
    <groupId>xerces</groupId>
    <artifactId>xercesImpl</artifactId>
    <version>2.11.0</version>
</dependency>

e todas as dependências foram bem resolvidas - até adequadas xml-apis-1.4.01!

E o que é mais importante (e o que não era óbvio no passado) - o JAR no Maven Central é o mesmo JAR da Xerces-J-bin.2.11.0.zipdistribuição oficial .

No entanto, não consegui encontrar a xml-schema-1.1-betaversão - não pode ser uma classifierversão do Maven por causa de dependências adicionais.

Grzegorz Grzybek
fonte
9
Embora seja muito confuso que xml-apis:xml-apis:1.4.01seja mais recente que xml-apis:xml-apis:2.0.2?? veja search.maven.org/…
Hendy Irawan
É confuso, mas é devido aos envios de terceiros de frascos Xerces sem versão, como justingarrik estava dizendo em seu post. xml-apis 2.9.1 é o mesmo que 1.3.04, portanto, nesse sentido, 1.4.01 é mais recente (e numericamente maior) que 1.3.04.
precisa saber é o seguinte
1
Se você possui o xercesImpl e o xml-apis no seu pom.xml, certifique-se de excluir a dependência do xml-apis! Caso contrário, 2.0.2 eleva sua cabeça feia.
MikeJRamsey56
64

Francamente, praticamente tudo que nós encontramos funciona muito bem w / a versão JAXP, então nós sempre excluir xml-apis e xercesImpl.

jtahlborn
fonte
13
Você poderia adicionar um snippet pom.xml para isso?
Chzbrgla #
10
Quando eu tento isso, recebo JavaMelody e Spring lançando java.lang.NoClassDefFoundError: org/w3c/dom/ElementTraversalem tempo de execução.
David Moles
Para adicionar à resposta de David Moles - vi meia dúzia de dependências transitivas precisam do ElementTraversal. Várias coisas no Spring e no Hadoop são mais comuns.
22814 Scott Carey
2
Se você receber java.lang.NoClassDefFoundError: org / W3C / dom / ElementTraversal tente adicionar xml-apis 1.4.01 para o seu pom (e excluir todas as outras versões dependentes)
Justin Rowe
1
ElementTraversal é uma nova classe adicionada no Xerces 11 e disponível na dependência xml-apis: xml-apis: 1.4.01. Portanto, você pode precisar copiar a classe manualmente para o seu projeto ou usar toda a dependência, o que causa classes duplicadas no carregador de classes. Mas no JDK9 essa classe foi incluída, portanto, no recurso, você pode precisar remover o dep.
Sergey Ponomarev
42

Você pode usar o plug-in maven enforcer com a regra de dependência banida. Isso permitiria banir todos os aliases que você não deseja e permitir apenas o que você deseja. Essas regras falharão na compilação do seu projeto quando violadas. Além disso, se esta regra se aplicar a todos os projetos em uma empresa, você poderá colocar a configuração do plug-in em um pom pai corporativo.

Vejo:

Travis Schneeberger
fonte
33

Eu sei que isso não responde à pergunta exatamente, mas para pessoas vindas do google que usam Gradle para o gerenciamento de dependências:

Eu consegui me livrar de todos os problemas do xerces / Java8 com o Gradle assim:

configurations {
    all*.exclude group: 'xml-apis'
    all*.exclude group: 'xerces'
}
netmikey
fonte
36
bom, com o maven você precisa de cerca de 4000 linhas de XML para fazer isso.
teknopaul
isso não resolveu o problema. alguma outra dica para as pessoas do Android-Gradle?
Nyxee
2
O XML @teknopaul é usado exclusivamente para configuração. Groovy é uma linguagem de programação de alto nível. Às vezes, você pode querer usar XML para explicitação, em vez de groovy para mágica.
Dragas
16

Eu acho que há uma pergunta que você precisa responder:

Existe um xerces * .jar com o qual tudo em seu aplicativo pode conviver?

Caso contrário, você está basicamente ferrado e precisaria usar algo como OSGI, que permite que você tenha versões diferentes de uma biblioteca carregadas ao mesmo tempo. Esteja avisado de que basicamente substitui os problemas da versão jar pelos problemas do carregador de classe ...

Se existir essa versão, você poderá fazer com que seu repositório retorne essa versão para todos os tipos de dependências. É um truque feio e acabaria com a mesma implementação de xerces em seu caminho de classe várias vezes, mas melhor do que ter várias versões diferentes de xerces.

Você pode excluir todas as dependências do xerces e adicionar uma à versão que deseja usar.

Gostaria de saber se você pode escrever algum tipo de estratégia de resolução de versão como um plugin para o maven. Essa provavelmente seria a melhor solução, mas, se possível, precisa de alguma pesquisa e codificação.

Para a versão contida no seu ambiente de tempo de execução, você deve ter certeza de que ele é removido do caminho de classe do aplicativo ou se os jars do aplicativo são considerados primeiro para carregamento de classe antes que a pasta lib do servidor seja considerada.

Então, para encerrar: é uma bagunça e isso não vai mudar.

Jens Schauder
fonte
1
A mesma classe do mesmo frasco carregado por diferentes ClassLoaders ainda é um ClassCastException (em todos os recipientes padrão)
Ajax
3
Exatamente. É por isso que eu escrevi: Esteja avisado que basicamente substitui problemas de versão frasco com problemas de carregador de classe
Jens Schauder
7

Há outra opção que não foi explorada aqui: declarar dependências do Xerces no Maven como opcional :

<dependency>
   <groupId>xerces</groupId>
   <artifactId>xercesImpl</artifactId>
   <version>...</version>
   <optional>true</optional>
</dependency>

Basicamente, o que isso faz é forçar todos os dependentes a declarar sua versão do Xerces ou seu projeto não será compilado. Se eles desejam substituir essa dependência, podem fazê-lo, mas serão os donos do problema em potencial.

Isso cria um forte incentivo para os projetos downstream:

  • Tome uma decisão ativa. Eles usam a mesma versão do Xerces ou usam outra coisa?
  • Na verdade, teste sua análise (por exemplo, através de testes de unidade) e carregamento de classe, bem como para não bagunçar seu caminho de classe.

Nem todos os desenvolvedores acompanham as novas dependências introduzidas (por exemplo, com mvn dependency:tree). Essa abordagem trará imediatamente o assunto à atenção deles.

Funciona muito bem em nossa organização. Antes de sua introdução, vivíamos no mesmo inferno que o OP está descrevendo.

Daniel
fonte
Devo literalmente usar ponto a ponto dentro do elemento version ou preciso usar uma versão real como a 2.6.2?
chrisinmtown
3
@chrisinmtown A versão real.
Daniel
6

Todo projeto de maven deve parar de depender de xerces, provavelmente não. APIs XML e um Impl fazem parte do Java desde a versão 1.4. Não há necessidade de depender de xerces ou APIs XML, é como dizer que você depende de Java ou Swing. Isso está implícito.

Se eu fosse o chefe de um repo maven, escreveria um script para remover recursivamente as dependências do xerces e escreveria uma leitura para mim dizendo que esse repo requer o Java 1.4.

Qualquer coisa que realmente quebre, porque faz referência ao Xerces diretamente por meio de importações do org.apache, precisa de uma correção de código para elevá-lo ao nível Java 1.4 (e já foi feito desde 2002) ou solução no nível da JVM por meio de bibliotecas endossadas, não em tom de mal-intencionado.

teknopaul
fonte
Ao executar o refator que você detalhou, também é necessário procurar os nomes dos pacotes e das classes no texto de seus arquivos e configurações Java. Você descobrirá que os desenvolvedores colocaram o FQN das classes Impl em seqüências constantes que são usadas por Class.forName e pelas construções semelhantes.
Derek Bennett
Isso pressupõe que todas as implementações do SAX fazem a mesma coisa, o que não é verdade. a biblioteca xercesImpl permite opções de configuração que as bibliotecas java.xml.parser não possuem.
Amalgovinus 3/01
6

Você deve depurar primeiro, para ajudar a identificar seu nível de inferno XML. Na minha opinião, o primeiro passo é adicionar

-Djavax.xml.parsers.SAXParserFactory=com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl
-Djavax.xml.transform.TransformerFactory=com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl
-Djavax.xml.parsers.DocumentBuilderFactory=com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl

para a linha de comando. Se isso funcionar, comece a excluir as bibliotecas. Caso contrário, adicione

-Djaxp.debug=1

para a linha de comando.

Derek Bennett
fonte
2

O que ajudaria, exceto a exclusão, são dependências modulares.

Com um carregamento de classe simples (aplicativo independente) ou semi-hierárquico (JBoss AS / EAP 5.x), isso era um problema.

Mas com estruturas modulares como OSGi e JBoss Modules , isso não é mais tão trabalhoso. As bibliotecas podem usar a biblioteca que desejarem, independentemente.

Obviamente, ainda é mais recomendável manter apenas uma única implementação e versão, mas se não houver outra maneira (usando recursos extras de mais bibliotecas), a modularização poderá salvá-lo.

Um bom exemplo de JBoss Modules em ação é, naturalmente, o JBoss AS 7 / EAP 6 / WildFly 8 , para o qual foi desenvolvido principalmente.

Exemplo de definição de módulo:

<?xml version="1.0" encoding="UTF-8"?>
<module xmlns="urn:jboss:module:1.1" name="org.jboss.msc">
    <main-class name="org.jboss.msc.Version"/>
    <properties>
        <property name="my.property" value="foo"/>
    </properties>
    <resources>
        <resource-root path="jboss-msc-1.0.1.GA.jar"/>
    </resources>
    <dependencies>
        <module name="javax.api"/>
        <module name="org.jboss.logging"/>
        <module name="org.jboss.modules"/>
        <!-- Optional deps -->
        <module name="javax.inject.api" optional="true"/>
        <module name="org.jboss.threads" optional="true"/>
    </dependencies>
</module>

Em comparação com o OSGi, o JBoss Modules é mais simples e rápido. Embora falte a certos recursos, é suficiente para a maioria dos projetos que estão (principalmente) sob controle de um fornecedor e permite uma inicialização rápida impressionante (devido à resolução de dependências paralelizadas).

Observe que há um esforço de modularização em andamento no Java 8 , mas o AFAIK é principalmente para modularizar o próprio JRE, sem ter certeza se ele será aplicável aos aplicativos.

Ondra Žižka
fonte
O módulo jboss é sobre modularização estática. Tem pouco a ver com a modularização em tempo de execução que o OSGi tem a oferecer - eu diria que eles se complementam. É um bom sistema embora.
eis
* complemento em vez de elogio
Robert Mikes
2

Aparentemente, xerces:xml-apis:1.4.01não está mais no maven central, que é, no entanto, o que faz xerces:xercesImpl:2.11.0referência.

Isso funciona para mim:

<dependency>
  <groupId>xerces</groupId>
  <artifactId>xercesImpl</artifactId>
  <version>2.11.0</version>
  <exclusions>
    <exclusion>
      <groupId>xerces</groupId>
      <artifactId>xml-apis</artifactId>
    </exclusion>
  </exclusions>
</dependency>
<dependency>
  <groupId>xml-apis</groupId>
  <artifactId>xml-apis</artifactId>
  <version>1.4.01</version>
</dependency>
thrau
fonte
1

Meu amigo, isso é muito simples, aqui está um exemplo:

<dependency>
    <groupId>xalan</groupId>
    <artifactId>xalan</artifactId>
    <version>2.7.2</version>
    <scope>${my-scope}</scope>
    <exclusions>
        <exclusion>
        <groupId>xml-apis</groupId>
        <artifactId>xml-apis</artifactId>
    </exclusion>
</dependency>

E se você quiser verificar no terminal (console do Windows para este exemplo) que sua árvore maven não tem problemas:

mvn dependency:tree -Dverbose | grep --color=always '(.* conflict\|^' | less -r
Eduardo
fonte