Para obter uma compreensão mais profunda do carregador de classe, você deve primeiro saber para que carga de classe é usada. Como o nome indica, ele é usado para carregar arquivos de classe na JVM para uso pelo programa. Sabemos que os programas Java podem carregar dinamicamente definições de classe, e esse mecanismo de carregamento dinâmico é implementado através do Classloader, para que você possa imaginar a importância do carregador de classe.
Depois de ver isso, alguns amigos podem pensar em uma pergunta, ou seja, como o carregador de classe é usado para carregar classes na JVM, como o Classloader é carregado? Não é uma aula de Java?
É isso mesmo, existe de fato um carregador de classe aqui que não está escrito no idioma Java, mas faz parte da implementação da JVM. Este carregador de classe é o bootstrap ClassLoader (Start Class Loader), esse carregador de classe carrega a API Java Core quando a JVM está em execução para atender às necessidades mais básicas do programa Java, incluindo o carregador de classe definido pelo usuário. O chamado carregador de classe definido pelo usuário refere-se ao carregador de classe implementado através do programa Java. Um é o ExtclassLoader. Este carregador de classe é usado para carregar a API de extensão Java, ou seja, a classe em /lib /ext, e a outra é o AppClassLoader. Este carregador de classe é usado para carregar a classe no diretório de configuração do Path na máquina do usuário. Geralmente, sem especificar o carregador de classe, as classes personalizadas do programador são carregadas pelo carregador de classe.
Ao executar um programa, a JVM inicia e executa o Bootstrap ClassLoader. O carregador de classe carrega a API Java Core (EXTClassLoader e AppClassLoader também são carregados no momento), depois chama o ExtclassLoader para carregar a API de extensão e, finalmente, o AppClassLoader carrega a classe definida no diretório ClassPath. Este é o processo de carregamento mais básico de um programa.
O acima explica brevemente o papel do carregador de classe e o processo de carregamento mais básico. Em seguida, explicaremos a maneira como o carregador de classe é carregado. Aqui temos que falar sobre o uso do modo de delegado pai para carregamento de classe.
Cada carregador de classe personalizado deve herdar o carregador de classe abstrato e cada carregador de classe terá um carregador de classe pai. Podemos ver que existe um método getParent () no carregador de classe abstrato, usado para retornar o pai do carregador de classe atual. Observe que esse pai não se refere à classe herdada, mas um carregador de classe especificado ao instanciar o carregador de classe. Se esse pai for nulo, o pai padrão do carregador de classe é o carregador de classe Bootstrap. Qual é o uso desse pai?
Podemos considerar esta situação. Suponha que tenhamos personalizado um ClientDefClassLoader e usamos este carregador de classe personalizado para carregar java.lang.string, então a string será carregada por este carregador de classe? De fato, a classe java.lang.string não é carregada pelo ClientDefclassLoader, mas é carregada pelo carregador de classe de bootstrap. Por que isso está acontecendo? De fato, esse é o motivo do modo de delegado pai, porque antes que qualquer carregador de classe personalizado carregue uma classe, ele delegará primeiro seu carregador de classe para carregar. Ele será carregado apenas por si só depois que o carregador de classe do pai não pode ser carregado com sucesso. No exemplo acima, como o java.lang.string é uma classe pertencente à API Java Core; portanto, ao usar o ClientDefClassLoader para carregá -lo, o carregador de classe delegará primeiro seu carregador de classe para carregar. Como mencionado acima, quando o pai do carregador de classe é nulo, o pai do carregador de classe é o carregador de classe de bootstrap; portanto, no nível superior do carregador de classe está o carregador de classe de bootstrap; portanto, finalmente delegar para o bootstrap quando o carregador de classe estiver presente, o bootstrap de classe retornará a classe String.
Vamos dar uma olhada em um código -fonte no ClassLoader:
Classe de carga sincronizada protegida (nome da string, resolução booleana) lança ClassNotFoundException {// primeiro verifique se a classe especificada pelo nome é carregada classe C = findLoadDclass (nome); if (c == null) {tente {if (pai! = null) {// Se o pai não for nulo, ligue para o pai do pai para carregar = parent.loadclass (nome, false); } else {// pai é nulo, chama BootstrapClassLoader para carregar C = FindBootStrapClass0 (nome); }} catch (classNotFoundException e) {// Se o carregamento ainda não for bem -sucedido, chame seu próprio findClass para carregar C = findClass (nome); }} if (resolve) {resolveclass (c); } retornar c; } A partir do código acima, podemos ver que o processo geral de carregar uma classe é o mesmo que o exemplo que eu dei antes. Quando queremos implementar uma classe personalizada, precisamos apenas implementar o método FindClass.
Por que usar esse modelo de delegação de pais?
A primeira razão é que isso pode evitar o carregamento repetido. Quando o pai carrega a classe, não há necessidade de o carregador de classe criança carregá -lo novamente.
A segunda razão é considerar fatores de segurança . Vamos imaginar que, se não usarmos esse modo de delegado, podemos substituir dinamicamente os tipos definidos na API Java Core a qualquer momento, o que representará um risco de segurança muito grande. O método do delegado pai pode evitar essa situação, porque a string já está carregada na inicialização, portanto a classe definida pelo usuário não pode carregar um carregador de classe personalizado.
O exposto acima é uma breve introdução ao mecanismo de carregamento do carregador de classe . Em seguida, tenho que explicar outra classe relacionada ao carregador de classe, ou seja, a classe. Cada arquivo de classe carregado pelo ClassLoader será referenciado pelo programador como uma instância da classe. Podemos tratar a classe como um modelo da classe comum. A JVM gera instâncias correspondentes com base neste modelo e é finalmente usada pelo programador.
Vemos que existe um método estático para o nome da classe. Este método é o mesmo que o método da classe de carga no carregador de classe. É usado para carregar a classe, mas os dois têm funções diferentes.
Classe <?> LOADCLASS (Nome da String)
Classe <?> LOADCLASS (nome da string, resolução booleana)
Vemos as duas declarações acima do método. O segundo parâmetro do segundo método é usado para definir se deve conectar a classe ao carregar a classe. Se verdadeiro, será conectado, caso contrário, não será conectado.
Falando em conexão, tenho que explicar aqui. Ao carregar uma classe pela JVM, é necessário seguir três etapas: carregando, conectando e inicializando. Carregar significa encontrar o arquivo de classe correspondente, lendo -o na JVM e inicializando que ele precisa ser discutido, o mais importante é falar sobre conexão.
A conexão é dividida em três etapas. O primeiro passo é verificar se a classe atende às especificações. O segundo passo é a preparação. É alocar memória para as variáveis de classe e definir o valor inicial padrão. O terceiro passo é a explicação. Esta etapa é opcional. De acordo com o segundo parâmetro do método da classe de carga acima, é determinado se uma explicação é necessária. A chamada explicação é baseada na definição do livro "JVM aprofundado", que é encontrar a entidade correspondente com base nas referências de símbolo na classe e, em seguida, substituir a referência de símbolo por uma referência direta. É um pouco profundo, haha, não vou explicar aqui. Se você quiser saber mais, leia "JVM aprofundado". Haha, se você continuar a explicar passo a passo, não saberá quando será concluído.
Vamos dar uma olhada no método da classe de carga com dois parâmetros. No documento da API Java, a definição desse método é protegida, o que significa que o método é protegido e o método que os usuários devem realmente usar é o de um parâmetro. O método da classe de carga de um parâmetro realmente chama o método com dois parâmetros e o segundo parâmetro padrão para false. Portanto, pode -se ver aqui que a classe de carregamento através da classe de carga não é realmente explicada ao carregar, para que a classe não seja inicializada. O método forname da classe é o oposto. Ao carregar com forname, a classe será explicada e inicializada. O Forname também possui outra versão do método, que pode definir se deve inicializar e definir o ClassLoader. Não vou falar mais sobre isso aqui.
Gostaria de saber se a explicação desses dois métodos de carregamento é clara o suficiente. Vamos dar um exemplo aqui. Por exemplo, ao carregar o driver JDBC , usamos o forname em vez do método da classe de carga do carregador de classe ao carregar drivers JDBC? Sabemos que o driver JDBC é através do DriverManager e deve ser registrado no DriverManager. Se a classe de motorista não for inicializada, ela não poderá ser registrada no DriverManager. Portanto, o forname deve ser usado em vez de uma classe de carga.
Através do Classloader, podemos personalizar o carregador de classe e personalizar o método de carregamento de que precisamos, como carregar da rede, carregar de outros formatos de arquivos etc. De fato, ainda existem muitas coisas que não foram mencionadas no carregador de classe, como algumas implementações dentro do carregador de classe, etc.
Através deste artigo, o editor espera que todos tenham algum entendimento do mecanismo de carga de classe, obrigado pelo seu apoio!