API Java 9 java.lang.Module

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.