É isso, desde o conflito do pacote Jar até o mecanismo de carregamento de classes

Assumi um sistema com um senso de idade e planejei escrever uma série de artigos sobre refatoração e problemas encontrados. [Reconstrução 01], falar sobre os conflitos e princípios do pacote Jar.

fundo

Atualmente, o gerenciamento de projetos no mercado é baseado em Maven ou Gradle, e recentemente assumiu um conjunto de projetos que adicionam pacotes jar de forma puramente manual.

Para projetos que adicionam pacotes jar puramente manualmente, tem sido assim por muitos anos, e o pessoal técnico que trabalha há três ou cinco anos pode não ter experimentado. É descobrir os pacotes jar necessários no projeto um por um, adicioná-los a um diretório lib e adicionar manualmente as dependências do pacote jar no IDE.

Adicionar dependências de pacotes jar dessa forma não é apenas trabalhoso, mas também propenso a conflitos de pacotes jar.Ao mesmo tempo, analisar métodos de conflito só pode depender da experiência.

Recentemente, encontrei uma situação assim: um projeto pode ser iniciado normalmente no ambiente do desenvolvedor A, mas não pode ser iniciado em B, e a informação de exceção é que nenhuma classe pode ser encontrada.

Pessoas com pouca experiência em desenvolvimento podem concluir imediatamente que isso é causado por conflitos de pacotes jar. Vamos dar uma olhada em como resolver e estender os pontos de conhecimento.

Solução temporária

Como é temporariamente impossível refatorar o projeto em grande escala, não ouso substituir e atualizar facilmente o pacote Jar. Apenas meios temporários podem ser usados ​​para resolvê-lo.

Aqui estão alguns passos para se preparar para emergências, que geralmente são dicas para resolver problemas de dependência de Jar.

Primeiro: Procure no IDE a classe não encontrada na exceção. Por exemplo, no sistema operacional IDEA MAC, a tecla de atalho que uso é command + shift + n.

Encontrar conflitos

Tomando a classe Assert como exemplo, você pode ver que existem muitos pacotes que contêm Assert, mas o programa de inicialização informa que um método dessa classe não pode ser encontrado.O problema está basicamente no conflito do pacote Jar.

Segundo, após localizar o conflito do pacote Jar, localize o pacote Jar que o sistema deve usar.

Por exemplo, as classes em spring-core que precisam ser usadas aqui, não as classes em spring.jar. Em seguida, você pode usar o mecanismo de sequência de carregamento de classe da JVM para permitir que a JVM carregue o pacote jar spring-core primeiro.

Ponto de conhecimento: A JVM carrega os pacotes jar no mesmo diretório na ordem dos pacotes jar. Uma vez que uma classe com o mesmo nome de caminho completo é carregada, a mesma classe posteriormente não será carregada.

Portanto, uma solução temporária é ajustar a ordem em que a JVM compila (carrega) os pacotes Jar. Isso é suportado no Eclipse e no Idea e pode ser ajustado manualmente.

Método de ajuste no Eclipse:

Ordem de ajuste do Eclipse

Método de ajuste no Idea:

Ideia ajustar a ordem

Ajuste o pacote jar que precisa ser carregado primeiro, para que possa ser carregado primeiro e, finalmente, resolva o problema do conflito do pacote jar temporariamente.

Uma extensão do mecanismo de carregamento de classe

O acima é apenas uma solução temporária limitada pelo status quo do projeto, e deve ser transformada e atualizada no final, com base no Maven ou Gradle para gerenciamento de pacotes Jar, e ao mesmo tempo para resolver o problema de conflitos de pacotes Jar .

Nesta solução temporária, um ponto de conhecimento chave da JVM está envolvido: o problema de isolamento do carregador de classes da JVM e o mecanismo de delegação pai. Se não houver conhecimento relevante do mecanismo de carregamento de classe da JVM, talvez nem seja possível pensar na solução temporária acima.

Problemas de isolamento do carregador de classe

Cada carregador de classes tem seu próprio namespace para armazenar classes carregadas. Quando um carregador de classes carrega uma classe, ele pesquisa o Fully Qualified Class Name armazenado no namespace para verificar se a classe já foi carregada .

A única identificação de uma classe pela JVM é ClassLoader id + PackageName + ClassName, portanto, pode haver duas classes com exatamente o mesmo nome de pacote e nome de classe em um programa em execução. E se essas duas classes não forem carregadas por um ClassLoader, é impossível forçar uma instância de uma classe em outra classe, que é o isolamento do ClassLoader.

Para resolver o problema de isolamento dos carregadores de classes , a JVM introduz um mecanismo de delegação pai .

Mecanismo de delegação dos pais

O núcleo do mecanismo de delegação pai tem dois pontos: primeiro, verifique se a classe foi carregada de baixo para cima ; segundo, tente carregar a classe de cima para baixo .

carregador de classes

Geralmente, existem quatro tipos de carregadores de classes: carregadores de classes de inicialização, carregadores de classes de extensão, carregadores de classes de aplicativos e carregadores de classes customizados.

Independentemente do carregador de classes personalizado por enquanto, o processo de execução específico do carregador de classes do próprio JDK é o seguinte:

Primeiro: quando o AppClassLoader carrega uma classe, ele delega a solicitação de carregamento de classe ao carregador de classes pai ExtClassLoader para concluir;

Segundo: quando o ExtClassLoader carrega uma classe, ele delega a solicitação de carregamento de classe ao BootStrapClassLoader para concluir;

Terceiro: Se BootStrapClassLoader falhar ao carregar (por exemplo, a classe não for encontrada em %JAVA_HOME%/jre/lib), ExtClassLoader será usado para tentar carregar;

Quarto: Se ExtClassLoader também falhar ao carregar, AppClassLoader será usado para carregar, e se AppClassLoader também falhar, uma exceção ClassNotFoundException será relatada.

Implementação de delegação pai do ClassLoader

ClassLoader implementa o mecanismo de delegação pai por meio do método loadClass() para carregamento dinâmico de classe .

O código fonte deste método é o seguinte:

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException{
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

O próprio método loadClass é um processo recursivo de chamada ascendente, que pode ser visto na chamada de parent.loadClass no código acima.

Antes de realizar outras operações, primeiro verifique se a classe especificada foi carregada pelo método findLoadedClass começando pelo carregador de classes inferior. Caso tenha sido carregado, decida se deseja realizar o processo de conexão de acordo com o parâmetro resolve e retorne o objeto Class.

E conflitos de pacotes Jar geralmente ocorrem aqui.Quando a primeira classe com o mesmo nome é carregada, ela retornará diretamente ao fazer o check-in nesta etapa e não carregará as classes realmente necessárias. Então, quando o programa usar a classe, ele lançará uma exceção de que a classe não pode ser encontrada ou o método da classe não pode ser encontrado.

Carregar ordem de pacotes Jar

Vimos acima que uma vez que uma classe é carregada, classes com o mesmo nome qualificado globalmente podem não ser carregadas. A ordem na qual os pacotes Jar são carregados determina diretamente a ordem na qual as classes são carregadas.

Os seguintes fatores geralmente são usados ​​para determinar a ordem de carregamento dos pacotes Jar:

  • Primeiro, o caminho de carregamento onde o pacote Jar está localizado. Ou seja, o carregador de classes que carrega o pacote Jar está no nível da estrutura em árvore do carregador de classes JVM. Os caminhos dos pacotes Jar carregados pelos quatro carregadores de classes mencionados acima têm prioridades diferentes.
  • Em segundo lugar, a ordem de carregamento do arquivo do sistema de arquivos. Como o ClassLoader do Tomcat, Resin e outros contêineres obtém a lista de arquivos sob o caminho de carregamento em nenhuma ordem, que depende da ordem retornada pelo sistema de arquivos subjacente. Quando os sistemas de arquivos de diferentes ambientes são inconsistentes, haverá alguns ambientes .Problema, alguns ambientes entram em conflito.

O problema que encontrei pertence a uma ramificação do segundo fator, ou seja, a ordem de carregamento de diferentes pacotes Jar no mesmo diretório é diferente. Portanto, o problema foi resolvido temporariamente ajustando a ordem de carregamento dos pacotes Jar.

Manifestações comuns de conflitos de pacotes Jar

Conflitos de pacotes Jar geralmente são muito estranhos e difíceis de solucionar, mas também existem algumas manifestações comuns.

  • Lança java.lang.ClassNotFoundException: uma exceção típica, principalmente porque a classe não existe na dependência. Existem duas razões para isso: primeiro, esta classe não é realmente introduzida; segundo, devido ao conflito de pacotes Jar, o mecanismo de arbitragem Maven seleciona a versão errada, resultando em nenhuma classe no pacote Jar carregado.
  • lança java.lang.NoSuchMethodError: O método específico não pôde ser encontrado. Conflitos de pacote Jar, resultando na seleção da versão de dependência errada, o par de classes na versão de dependência não possui o método ou o método foi atualizado.
  • Lança java.lang.NoClassDefFoundError, java.lang.LinkageError, etc. pelos mesmos motivos acima.
  • Nenhuma exceção, mas resultados esperados diferentes: a versão errada é carregada, versões diferentes têm implementações subjacentes diferentes, resultando em resultados esperados inconsistentes.

Carregando a ordem de pacotes e classes Jar quando o Tomcat é iniciado

Por fim, classifique a ordem de carregamento dos pacotes e classes Jar quando o Tomcat for iniciado, incluindo os diretórios carregados por padrão pelos diferentes tipos de carregadores de classes mencionados acima:

  • A API do núcleo java no diretório $java_home/lib;
  • O pacote jar da extensão java no diretório $java_home/lib/ext;
  • As classes e pacotes jar no diretório apontados por java -classpath/-Djava.class.path;
  • O diretório $CATALINA_HOME/common é carregado de cima para baixo na ordem das pastas;
  • O diretório $CATALINA_HOME/server é carregado de cima para baixo na ordem das pastas;
  • O diretório $CATALINA_BASE/shared é carregado de cima para baixo na ordem das pastas;
  • O arquivo de classe sob o caminho do projeto /WEB-INF/classes;
  • O arquivo jar sob o caminho do projeto /WEB-INF/lib;

No diretório acima, os pacotes Jar na mesma pasta são carregados em ordem de cima para baixo. Se um arquivo de classe já tiver sido carregado na JVM, o mesmo arquivo de classe a seguir não será carregado.

resumo

Conflito de pacote jar é um problema muito comum em nosso desenvolvimento diário.Se pudermos entender bem a causa do conflito e o mecanismo subjacente, podemos melhorar muito a capacidade de resolução de problemas e a influência da equipe. Portanto, tais perguntas são feitas em muitas entrevistas.

Neste artigo, focamos nas causas e soluções de conflitos de pacotes Jar quando as dependências são adicionadas manualmente. Ao resolver este problema, algumas estratégias de gerenciamento de conflitos de pacotes Jar do Maven são frequentemente projetadas, como o princípio de trânsito de dependência, o princípio do caminho mais curto, o princípio da primeira declaração, etc. Falaremos sobre isso em detalhes no próximo artigo.

Autor: Programa Novos Horizontes

Link original:
https://mp.weixin.qq.com/s/IfaxCCSt1WTboFAP3RsG9w

Se você acha que este artigo é útil para você, você pode retweetar, seguir e apoiar

Acho que você gosta

Origin blog.csdn.net/m0_67645544/article/details/124432600
Recomendado
Clasificación