Carregadores de Classes em Java
1. Introdução aos Class Loaders
Os carregadores de classes são responsáveis porloading Java classes during runtime dynamically to the JVM (Java Virtual Machine). Além disso, eles fazem parte do JRE (Java Runtime Environment). Portanto, a JVM não precisa saber sobre os arquivos ou sistemas de arquivos subjacentes para executar programas Java graças aos carregadores de classes.
Além disso, essas classes Java não são carregadas na memória de uma vez, mas quando exigidas por um aplicativo. É aqui que os carregadores de classe entram em cena. Eles são responsáveis por carregar as classes na memória.
Neste tutorial, vamos falar sobre os diferentes tipos de carregadores de classes integrados, como eles funcionam e uma introdução à nossa própria implementação customizada.
Leitura adicional:
Entendendo vazamentos de memória em Java
Aprenda o que há vazamentos de memória em Java, como reconhecê-los em tempo de execução, o que os causa e estratégias para evitá-los.
ClassNotFoundException vs NoClassDefFoundError
Aprenda sobre as diferenças entre ClassNotFoundException e NoClassDefFoundError.
2. Tipos de carregadores de classe integrados
Vamos começar aprendendo como diferentes classes são carregadas usando vários carregadores de classe usando um exemplo simples:
public void printClassLoaders() throws ClassNotFoundException {
System.out.println("Classloader of this class:"
+ PrintClassLoader.class.getClassLoader());
System.out.println("Classloader of Logging:"
+ Logging.class.getClassLoader());
System.out.println("Classloader of ArrayList:"
+ ArrayList.class.getClassLoader());
}
Quando executado, o método acima é impresso:
Class loader of this class:[email protected]
Class loader of Logging:[email protected]
Class loader of ArrayList:null
Como podemos ver, existem três carregadores de classes diferentes aqui; aplicativo, extensão e bootstrap (exibidos comonull).
O carregador de classes de aplicativos carrega a classe em que o método de exemplo está contido. An application or system class loader loads our own files in the classpath.
Em seguida, a extensão carrega a classeLogging. Extension class loaders load classes that are an extension of the standard core Java classes.
Finalmente, o bootstrap carrega a classeArrayList. A bootstrap or primordial class loader is the parent of all the others.
No entanto, podemos ver que a última saída, paraArrayList, ela exibenull na saída. This is because the bootstrap class loader is written in native code, not Java – so it doesn’t show up as a Java class. Devido a esse motivo, o comportamento do carregador de classes de bootstrap será diferente nas JVMs.
Vamos agora discutir mais detalhes sobre cada um desses carregadores de classe.
2.1. Bootstrap Class Loader
[.s1] # Classes de Java são carregadas por uma instância dejava.lang.ClassLoader. No entanto, os carregadores de classes são as próprias classes. Portanto, a questão é: quem carrega o própriojava.lang.ClassLoader? #
É aqui que o bootstrap ou o carregador de classe primordial entra em cena.
[.s1] # É principalmente responsável por carregar as classes internas do JDK, normalmentert.jar e outras bibliotecas centrais localizadas em$JAVA_HOME/jre/lib directory. Além disso,Bootstrap class loader serves as a parent of all the other ClassLoader instances. #
This bootstrap class loader is part of the core JVM and is written in native code conforme indicado no exemplo acima. Diferentes plataformas podem ter diferentes implementações desse carregador de classes específico.
2.2. Carregador de classe de extensão
Oextension class loader is a child of the bootstrap class loader and takes care of loading the extensions of the standard core Java classes para que esteja disponível para todos os aplicativos em execução na plataforma.
O carregador de classes de extensão é carregado do diretório de extensões JDK, geralmente o diretório$JAVA_HOME/lib/ext ou qualquer outro diretório mencionado na propriedade de sistemajava.ext.dirs.
2.3. Carregador de classe de sistema
O carregador de classes do sistema ou aplicativo, por outro lado, cuida de carregar todas as classes de nível de aplicativo na JVM. It loads files found in the classpath environment variable, -classpath or -cp command line option. Além disso, é filho do carregador de classe Extensions.
3. Como funcionam os carregadores de classes?
Os carregadores de classes fazem parte do Java Runtime Environment. Quando a JVM solicita uma classe, o carregador de classe tenta localizar a classe e carregar a definição da classe no tempo de execução usando o nome de classe totalmente qualificado.
Ojava.lang.ClassLoader.loadClass() method is responsible for loading the class definition into runtime. Ele tenta carregar a classe com base em um nome totalmente qualificado.
[.s1] #Se a classe ainda não estiver carregada, ela delega a solicitação ao carregador de classe pai. Esse processo acontece recursivamente. #
Eventualmente, se o carregador de classe pai não encontrar a classe, a classe filha chamará o métodojava.net.URLClassLoader.findClass() para procurar classes no próprio sistema de arquivos.
Se o último carregador de classe filho também não for capaz de carregar a classe, ele lançajava.lang.NoClassDefFoundError or java.lang.ClassNotFoundException.
Vejamos um exemplo de saída quando ClassNotFoundException é lançada.
java.lang.ClassNotFoundException: com.example.classloader.SampleClassLoader
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:348)
Se percorrermos a sequência de eventos desde a chamada dejava.lang.Class.forName(), podemos entender que ele primeiro tenta carregar a classe por meio do carregador de classe pai e, em seguida,java.net.URLClassLoader.findClass() para procurar a própria classe.
Quando ainda não encontra a classe, ele lança umClassNotFoundException.
Existem três características importantes dos carregadores de classes.
3.1. Modelo de Delegação
[.s1] #Carregadores de classe seguem o modelo de delegação ondeon request to find a class or resource, a ClassLoader instance will delegate the search of the class or resource to the parent class loader. #
[.s1] # Digamos que temos uma solicitação para carregar uma classe de aplicativo na JVM. O carregador de classes do sistema primeiro delega o carregamento dessa classe para seu carregador de classes de extensão pai que, por sua vez, o delega para o carregador de classes de bootstrap. #
Somente se o bootstrap e, em seguida, o carregador de classe de extensão não tiver êxito no carregamento da classe, o carregador de classes do sistema tentará carregar a própria classe.
3.2. Aulas Únicas
[.s1] #Como consequência do modelo de delegação, é fácil garantirunique classes as we always try to delegate upwards. #
[.s1] #Se o carregador de classe pai não for capaz de encontrar a classe, somente então a instância atual tentaria fazer isso sozinha. #
3.3. Visibilidade
[.s1] #Além disso,children class loaders are visible to classes loaded by its parent class loaders. #
[.s1] #Para instância, as classes carregadas pelo carregador de classes do sistema têm visibilidade nas classes carregadas pela extensão e carregadores de classes Bootstrap, mas não vice-versa. #
[.s1] #Para ilustrar isso, se a Classe A for carregada por um carregador de classes do aplicativo e a classe B for carregada pelo carregador de classes de extensões, as classes A e B são visíveis no que diz respeito às outras classes carregadas pelo carregador de classes do aplicativo . #
A classe B, no entanto, é a única classe visível no que diz respeito a outras classes carregadas pelo carregador de classes de extensão.
4. ClassLoader personalizado
O carregador de classes interno seria suficiente na maioria dos casos em que os arquivos já estão no sistema de arquivos.
No entanto, em cenários em que precisamos carregar classes do disco rígido local ou de uma rede, podemos precisar usar carregadores de classes personalizados.
Nesta seção, cobriremos alguns outros casos de uso para carregadores de classes personalizados e demonstraremos como criar um.
4.1. Casos de uso de carregadores de classe personalizada
Carregadores de classes personalizados são úteis para mais do que apenas carregar a classe durante o tempo de execução, alguns casos de uso podem incluir:
-
Ajudando a modificar o bytecode existente, por exemplo agentes de tecelagem
-
Criação de classes dinamicamente adequadas às necessidades do usuário. e.g in JDBC, switching between different driver implementations is done through dynamic class loading.
-
Implementando um mecanismo de versão de classe ao carregar diferentes códigos de código para classes com os mesmos nomes e pacotes. Isso pode ser feito através do carregador de classes de URL (carregar jarros via URLs) ou de carregadores de classes personalizados.
Existem exemplos mais concretos em que carregadores de classes personalizadas podem ser úteis.
Browsers, for instance, use a custom class loader to load executable content from a website. Um navegador pode carregar miniaplicativos de diferentes páginas da web usando carregadores de classes separados. O visualizador de miniaplicativos que é usado para executar miniaplicativos contém umClassLoader que acessa um site em um servidor remoto em vez de procurar no sistema de arquivos local.
E, em seguida, carrega os arquivos brutos de bytecode via HTTP e os transforma em classes dentro da JVM. Mesmo que essesapplets have the same name, they are considered as different components if loaded by different class loaders.
Agora que entendemos por que os carregadores de classes personalizados são relevantes, vamos implementar uma subclasse deClassLoader para estender e resumir a funcionalidade de como a JVM carrega classes.
4.2. Criando nosso carregador de classe personalizado
Para fins de ilustração, digamos que precisamos carregar classes de um arquivo usando um carregador de classes personalizado.
Precisamos estender a classeClassLoadere substituir o métodofindClass():
public class CustomClassLoader extends ClassLoader {
@Override
public Class findClass(String name) throws ClassNotFoundException {
byte[] b = loadClassFromFile(name);
return defineClass(name, b, 0, b.length);
}
private byte[] loadClassFromFile(String fileName) {
InputStream inputStream = getClass().getClassLoader().getResourceAsStream(
fileName.replace('.', File.separatorChar) + ".class");
byte[] buffer;
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
int nextValue = 0;
try {
while ( (nextValue = inputStream.read()) != -1 ) {
byteStream.write(nextValue);
}
} catch (IOException e) {
e.printStackTrace();
}
buffer = byteStream.toByteArray();
return buffer;
}
}
No exemplo acima, definimos um carregador de classes personalizado que estende o carregador de classes padrão e carrega uma matriz de bytes do arquivo especificado.
5. Compreendendojava.lang.ClassLoader
Vamos discutir alguns métodos essenciais da classejava.lang.ClassLoader para obter uma imagem mais clara de como funciona.
5.1. O MétodoloadClass()
public Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Este método é responsável por carregar a classe com um parâmetro de nome. O parâmetro name refere-se ao nome completo da classe.
A Java Virtual Machine invoca o métodoloadClass() para resolver as referências de classe, definindo resolver paratrue. No entanto, nem sempre é necessário resolver uma classe. If we only need to determine if the class exists or not, then resolve parameter is set to false.
Este método serve como um ponto de entrada para o carregador de classes.
Podemos tentar entender o funcionamento interno do métodoloadClass() a partir do código-fonte dejava.lang.ClassLoader:
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.
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
A implementação padrão do método procura por classes na seguinte ordem:
-
Invoca o métodofindLoadedClass(String) para ver se a classe já está carregada.
-
Invoca o métodoloadClass(String) no carregador de classes pai.
-
Invoque o métodofindClass(String) para encontrar a classe.
5.2. O MétododefineClass()
protected final Class> defineClass(
String name, byte[] b, int off, int len) throws ClassFormatError
Este método é responsável pela conversão de uma matriz de bytes em uma instância de uma classe. E antes de usarmos a classe, precisamos resolvê-la.
Caso os dados não contenham uma classe válida, ele lança umClassFormatError.
Além disso, não podemos substituir esse método, pois ele está marcado como final.
5.3. O MétodofindClass()
protected Class> findClass(
String name) throws ClassNotFoundException
Este método localiza a classe com o nome completo como parâmetro. Precisamos substituir esse método nas implementações do carregador de classes personalizadas que seguem o modelo de delegação para carregar classes.
Além disso,loadClass() invoca este método se o carregador de classe pai não conseguiu encontrar a classe solicitada.
A implementação padrão lança umClassNotFoundException se nenhum pai do carregador de classes encontrar a classe.
5.4. O MétodogetParent()
public final ClassLoader getParent()
Este método retorna o carregador de classes pai para delegação.
Algumas implementações como a vista anteriormente na Seção 2. usenull para representar o carregador de classes de bootstrap.
5.5. O MétodogetResource()
public URL getResource(String name)
Este método tenta encontrar um recurso com o nome fornecido.
Primeiro ele delegará ao carregador de classes pai do recurso. If the parent is null, the path of the class loader built into the virtual machine is searched.
Se isso falhar, o método invocaráfindResource(String) para encontrar o recurso. O nome do recurso especificado como uma entrada pode ser relativo ou absoluto para o caminho de classe.
Ele retorna um objeto URL para ler o recurso, ou null se o recurso não foi encontrado ou se o invocador não tem privilégios adequados para retornar o recurso.
É importante observar que Java carrega recursos do classpath.
Por fim,resource loading in Java is considered location-independent, pois não importa onde o código está sendo executado, desde que o ambiente esteja configurado para encontrar os recursos.
6. Carregadores de Classes de Contexto
Em geral, os carregadores de classes de contexto fornecem um método alternativo ao esquema de delegação de carregamento de classes introduzido no J2SE.
Como aprendemos antes,classloaders in a JVM follow a hierarchical model such that every class loader has a single parent with the exception of the bootstrap class loader.
No entanto, às vezes, quando as classes principais da JVM precisam carregar dinamicamente classes ou recursos fornecidos pelos desenvolvedores de aplicativos, podemos encontrar um problema.
Por exemplo, em JNDI, a funcionalidade principal é implementada por classes de bootstrap emrt.jar.. Mas essas classes JNDI podem carregar provedores JNDI implementados por fornecedores independentes (implementados no classpath do aplicativo). Este cenário exige que o carregador de classes de autoinicialização (carregador de classes pai) carregue uma classe visível para o carregador de aplicativos (carregador de classes filho).
A delegação J2SE não funciona aqui e para contornar este problema, precisamos encontrar formas alternativas de carregamento de classe. E isso pode ser alcançado usando carregadores de contexto de thread.
A classejava.lang.Thread tem um métodogetContextClassLoader() that returns the ContextClassLoader for the particular thread. OContextClassLoader é fornecido pelo criador do segmento ao carregar recursos e classes.
Se o valor não for definido, o padrão é o contexto do carregador de classes do encadeamento pai.
7. Conclusão
Carregadores de classes são essenciais para executar um programa Java. Fornecemos uma boa introdução como parte deste artigo.
Falamos sobre diferentes tipos de carregadores de classes, a saber - carregadores de classes Bootstrap, Extensions e System. O Bootstrap serve como pai de todos eles e é responsável por carregar as classes internas do JDK. Extensões e sistema, por outro lado, carregam classes do diretório de extensões Java e do caminho da classe, respectivamente.
Depois, falamos sobre como os carregadores de classe funcionam e discutimos alguns recursos, como delegação, visibilidade e exclusividade, seguidos por uma breve explicação de como criar um personalizado. Por fim, fornecemos uma introdução aos carregadores de classes Context.
Amostras de código, como sempre, podem ser encontradasover on GitHub.