API Java 9 java.lang.Module
1. Introdução
SeguindoA Guide to Java 9 Modularity, neste artigo, vamos explorar a APIjava.lang.Module que foi introduzida junto com o Java Platform Module System.
Essa API fornece uma maneira de acessar um módulo programaticamente, para recuperar informações específicas de um módulo e, geralmente, para trabalhar com ele e seusModuleDescriptor.
2. Informações do Módulo de Leitura
A classeModule representa módulos nomeados e não nomeados. Named modules have a name and are constructed by the Java Virtual Machine when it creates a module layer, usando um gráfico de módulos como definição.
Um módulo sem nome não tem um nome e há um para cadaClassLoader.All types that aren’t in a named module are members of the unnamed module related to their class loader.
A parte interessante da classeModule é que ela expõe métodos que nos permitem recuperar informações do módulo, como o nome do módulo, o carregador de classe do módulo e os pacotes dentro do módulo.
Vamos ver como é possível descobrir se um módulo tem ou não nome.
2.1. Nomeado ou Sem Nome
Usando o métodoisNamed(), podemos identificar se um módulo é nomeado ou não.
Vamos ver como podemos ver se uma determinada classe, comoHashMap, faz parte de um módulo nomeado e como podemos recuperar seu nome:
Class hashMapClass = HashMap.class;
Module javaBaseModule = hashMapClass.getModule();
assertThat(javaBaseModule.isNamed(), is(true));
assertThat(javaBaseModule.getName(), is("java.base"));
Vamos agora definir uma classePerson:
public class Person {
private String name;
// constructor, getters and setters
}
Da mesma forma, como fizemos para a classeHashMap, podemos verificar se a classePerson faz parte de um módulo nomeado:
Class personClass = Person.class;
Module module = personClass.getModule();
assertThat(module.isNamed(), is(false));
assertThat(module.getName(), is(nullValue()));
2.2. Pacotes
Ao trabalhar com um módulo, pode ser importante saber quais pacotes estão disponíveis no módulo.
Vamos ver como podemos verificar se um determinado pacote, por exemplo,java.lang.annotation, está contido em um determinado módulo:
assertTrue(javaBaseModule.getPackages().contains("java.lang.annotation"));
assertFalse(javaBaseModule.getPackages().contains("java.sql"));
2.3. Anotações
Da mesma forma, para os pacotes,it’s possible to retrieve the annotations that are present in the module using the getAnnotations() method.
Se não houver anotações presentes em um módulo nomeado, o método retornará uma matriz vazia.
Vamos ver quantas anotações estão presentes no módulojava.base:
assertThat(javaBaseModule.getAnnotations().length, is(0));
Quando chamado em um módulo sem nome, o métodogetAnnotations() retornará um array vazio.
2.4. ClassLoader
Graças ao métodogetClassLoader() disponível na classeModule, podemos recuperar oClassLoader para um determinado módulo:
assertThat(
module.getClassLoader().getClass().getName(),
is("jdk.internal.loader.ClassLoaders$AppClassLoader")
);
2.5. Camada
Outra informação valiosa que pode ser extraída de um módulo é oModuleLayer, que representa uma camada de módulos na máquina virtual Java.
Uma camada de módulo informa a JVM sobre as classes que podem ser carregadas dos módulos. Dessa forma, a JVM sabe exatamente de qual módulo cada classe é membro.
UmModuleLayer contém informações relacionadas à sua configuração, a camada pai e o conjunto de módulos disponíveis na camada.
Vamos ver como recuperar oModuleLayer de um determinado módulo:
ModuleLayer javaBaseModuleLayer = javaBaseModule.getLayer();
Depois de recuperar oModuleLayer, podemos acessar suas informações:
assertTrue(javaBaseModuleLayer.configuration().findModule("jaa.base").isPresent());
assertThat(javaBaseModuleLayer.configuration().modules().size(), is(78));
Um caso especial é a camada de inicialização, criada quando o Java Virtual Machine é iniciado. A camada de inicialização é a única camada que contém o módulojava.base.
3. Lidando comModuleDescriptor
UmModuleDescriptor descreve um módulo nomeado e define métodos para obter cada um de seus componentes.
Os objetosModuleDescriptor são imutáveis e seguros para uso por vários threads simultâneos.
Vamos começar vendo como podemos recuperar umModuleDescriptor.
3.1. Recuperando umModuleDescriptor
Uma vez queModuleDescriptor está firmemente conectado a umModule, é possível recuperá-lo diretamente de umModule:
ModuleDescriptor moduleDescriptor = javaBaseModule.getDescriptor();
3.2. Criando umModuleDescriptor
It’s also possible to create a module descriptor using the ModuleDescriptor.Builder class ou lendo a forma binária de uma declaração de módulo, omodule-info.class.
Vamos ver como criamos um descritor de módulo usando a APIModuleDescriptor.Builder:
ModuleDescriptor.Builder moduleBuilder = ModuleDescriptor
.newModule("example.base");
ModuleDescriptor moduleDescriptor = moduleBuilder.build();
assertThat(moduleDescriptor.name(), is("example.base"));
Com isso, criamos um módulo normal, mas caso desejemos criar um módulo aberto ou automático, podemos usar respectivamente o métodonewOpenModule() ounewAutomaticModule().
3.3. Classificando um Módulo
Um descritor de módulo descreve um módulo normal, aberto ou automático.
Graças ao método disponível emModuleDescriptor, é possível identificar o tipo de módulo:
ModuleDescriptor moduleDescriptor = javaBaseModule.getDescriptor();
assertFalse(moduleDescriptor.isAutomatic());
assertFalse(moduleDescriptor.isOpen());
3.4. Recuperando Requer
Com um descritor de módulo, é possível recuperar o conjunto deRequires, representando as dependências do módulo.
Isso é possível usando o métodorequires():
Set javaBaseRequires = javaBaseModule.getDescriptor().requires();
Set javaSqlRequires = javaSqlModule.getDescriptor().requires();
Set javaSqlRequiresNames = javaSqlRequires.stream()
.map(Requires::name)
.collect(Collectors.toSet());
assertThat(javaBaseRequires, empty());
assertThat(javaSqlRequires.size(), is(3));
assertThat(
javaSqlRequiresNames,
containsInAnyOrder("java.base", "java.xml", "java.logging")
);
All modules, except java.base, have the java.base module as a dependency.
No entanto, se o módulo for um módulo automático, o conjunto de dependências estará vazio, exceto a dejava.base.
3.5. Recuperando Fornece
Com o métodoprovides(), é possível recuperar a lista de serviços que o módulo fornece:
Set javaBaseProvides = javaBaseModule.getDescriptor().provides();
Set javaSqlProvides = javaSqlModule.getDescriptor().provides();
Set javaBaseProvidesService = javaBaseProvides.stream()
.map(Provides::service)
.collect(Collectors.toSet());
assertThat(
javaBaseProvidesService,
contains("java.nio.file.spi.FileSystemProvider")
);
assertThat(javaSqlProvides, empty());
3.6. Recuperando Exportações
Usando o métodoexports(), podemos descobrir se os módulos exportam pacotes e quais em particular:
Set javaBaseExports = javaBaseModule.getDescriptor().exports();
Set javaSqlExports = javaSqlModule.getDescriptor().exports();
Set javaSqlExportsSource = javaSqlExports.stream()
.map(Exports::source)
.collect(Collectors.toSet());
assertThat(javaBaseExports.size(), is(108));
assertThat(javaSqlExports.size(), is(3));
assertThat(
javaSqlExportsSource,
containsInAnyOrder("java.sql","javax.transaction.xa", "javax.sql")
);
Como um caso especial, se o módulo for automático, o conjunto de pacotes exportados ficará vazio.
3.7. Recuperando Usos
Com o métodouses(), é possível recuperar o conjunto de dependências de serviço do módulo:
Set javaBaseUses = javaBaseModule.getDescriptor().uses();
Set javaSqlUses = javaSqlModule.getDescriptor().uses();
assertThat(javaBaseUses.size(), is(34));
assertThat(javaSqlUses, contains("java.sql.Driver"));
Caso o módulo seja automático, o conjunto de dependências ficará vazio.
3.8. Recuperando aberturas
Sempre que quisermos recuperar a lista dos pacotes abertos de um módulo, podemos usar o métodoopens():
Set javaBaseUses = javaBaseModule.getDescriptor().opens();
Set javaSqlUses = javaSqlModule.getDescriptor().opens();
assertThat(javaBaseUses, empty());
assertThat(javaSqlUses, empty());
O aparelho estará vazio se o módulo for aberto ou automático.
4. Lidando com Módulos
Trabalhando com a APIModule, além de ler informações do módulo, podemos atualizar uma definição de módulo.
4.1. Adicionando Exportações
Vamos ver como podemos atualizar um módulo, exportando o pacote fornecido de um determinado módulo:
Module updatedModule = module.addExports(
"com.example.java9.modules", javaSqlModule);
assertTrue(updatedModule.isExported("com.example.java9.modules"));
Isso pode ser feito apenas se o módulo do chamador for o módulo do qual o código é membro.
Como observação lateral, não haverá efeitos se o pacote já tiver sido exportado pelo módulo ou se o módulo for aberto.
4.2. Adicionar leituras
Quando queremos atualizar um módulo para ler um determinado módulo, podemos usar o métodoaddReads():
Module updatedModule = module.addReads(javaSqlModule);
assertTrue(updatedModule.canRead(javaSqlModule));
Este método não faz nada se adicionarmos o próprio módulo, pois todos os módulos se lêem.
Da mesma forma, esse método não faz nada se o módulo for um módulo sem nome ou se já ler o outro.
4.3. Adicionando aberturas
Quando queremos atualizar um módulo que abriu um pacote pelo menos para o módulo do chamador, podemos usaraddOpens() para abrir o pacote para outro módulo:
Module updatedModule = module.addOpens(
"com.example.java9.modules", javaSqlModule);
assertTrue(updatedModule.isOpen("com.example.java9.modules", javaSqlModule));
Este método não tem efeito se o pacote já estiver aberto para o módulo especificado.
4.4. Adicionando Usos
Sempre que quisermos atualizar um módulo adicionando uma dependência de serviço, o métodoaddUses() é nossa escolha:
Module updatedModule = module.addUses(Driver.class);
assertTrue(updatedModule.canUse(Driver.class));
Este método não faz nada quando chamado em um módulo não nomeado ou em um módulo automático.
5. Conclusão
Neste artigo, exploramos o uso da APIjava.lang.Module, onde aprendemos como recuperar informações de um módulo, como usarModuleDescriptor para acessar informações adicionais sobre um módulo e como manipulá-lo.
Como sempre, todos os exemplos de código neste artigo podem ser encontradosover on GitHub.