Um guia para modularidade do Java 9

Um guia para modularidade do Java 9

1. Visão geral

O Java 9 apresenta um novo nível de abstração acima dos pacotes, conhecido formalmente como Java Platform Module System (JPMS) ou "Módulos", abreviado.

Neste tutorial, examinaremos o novo sistema e discutiremos seus vários aspectos.

Também construiremos um projeto simples para demonstrar todos os conceitos que aprenderemos neste guia.

2. O que é um módulo?

Primeiro de tudo, precisamos entender o que é um módulo antes que possamos entender como usá-lo.

Um Módulo é um grupo de pacotes e recursos intimamente relacionados, juntamente com um novo arquivo descritor de módulo.

Em outras palavras, é uma abstração de "pacote de pacotes Java" que nos permite tornar nosso código ainda mais reutilizável.

2.1. Pacotes

Os pacotes dentro de um módulo são idênticos aos pacotes Java que usamos desde o início do Java.

Quando criamos um módulo,we organize the code internally in packages just like we previously did with any other project.

Além de organizar nosso código, os pacotes são usados ​​para determinar qual código é acessível ao público fora do módulo. Passaremos mais tempo falando sobre isso mais adiante neste artigo.

2.2. Recursos

Cada módulo é responsável por seus recursos, como mídia ou arquivos de configuração.

Anteriormente, colocávamos todos os recursos no nível raiz de nosso projeto e gerenciamos manualmente quais recursos pertenciam a diferentes partes do aplicativo.

Com os módulos, podemos enviar as imagens e arquivos XML necessários com o módulo necessário, facilitando o gerenciamento de nossos projetos.

2.3. Descritor de Módulo

Quando criamos um módulo, incluímos um arquivo descritor que define vários aspectos do nosso novo módulo:

  • Name - o nome do nosso módulo

  • Dependencies - uma lista de outros módulos dos quais este módulo depende

  • Public Packages - uma lista de todos os pacotes que queremos acessíveis de fora do módulo

  • Services Offered - podemos fornecer implementações de serviço que podem ser consumidas por outros módulos

  • Services Consumed - permite que o módulo atual seja um consumidor de um serviço

  • Reflection Permissions - permite explicitamente que outras classes usem reflexão para acessar os membros privados de um pacote

As regras de nomeação de módulos são semelhantes à forma como denominamos pacotes (pontos são permitidos, traços não são). É muito comum usar nomes de estilo de projeto (my.module) ou DNS reverso (com.example.mymodule). Usaremos estilo de projeto neste guia.

Precisamos listar todos os pacotes que queremos que sejam públicos porque por padrão todos os pacotes são privados do módulo.

O mesmo vale para a reflexão. Por padrão, não podemos usar a reflexão sobre as classes que importamos de outro módulo.

Posteriormente neste artigo, veremos exemplos de como usar o arquivo descritor do módulo.

2.4. Tipos de Módulo

Existem quatro tipos de módulos no novo sistema de módulos:

  • System Modules – Esses são os módulos listados quando executamos o comandolist-modules acima. Eles incluem os módulos Java SE e JDK.

  • Application Modules - Esses módulos são o que geralmente queremos construir quando decidimos usar Módulos. Eles são nomeados e definidos no arquivomodule-info.class compilado incluído no JAR montado.

  • Automatic Modules - Podemos incluir módulos não oficiais adicionando arquivos JAR existentes ao caminho do módulo. O nome do módulo será derivado do nome do JAR. Os módulos automáticos terão acesso total de leitura a todos os outros módulos carregados pelo caminho.

  • Unnamed Module - quando uma classe ou JAR é carregado no classpath, mas não no caminho do módulo, ele é adicionado automaticamente ao módulo sem nome. É um módulo abrangente para manter a compatibilidade com versões anteriores de código Java escrito anteriormente.

2.5. Distribuição

Os módulos podem ser distribuídos de duas maneiras: como um arquivo JAR ou como um projeto compilado "explodido". É claro que isso é o mesmo que qualquer outro projeto Java; portanto, não deve ser surpresa.

Podemos criar projetos de múltiplos módulos compostos por um "aplicativo principal" e vários módulos de biblioteca.

Devemos ter cuidado porque só podemos ter um módulo por arquivo JAR.

Quando configuramos nosso arquivo de compilação, precisamos nos certificar de agrupar cada módulo em nosso projeto como um jar separado.

3. Módulos Padrão

Quando instalamos o Java 9, podemos ver que o JDK agora tem uma nova estrutura.

Eles pegaram todos os pacotes originais e os moveram para o novo sistema de módulos.

Podemos ver o que são esses módulos digitando na linha de comando:

java --list-modules

Esses módulos são divididos em quatro grupos principais:java, javafx, jdk, andOracle.

Módulosjava são as classes de implementação para o núcleo SE Language Specification.

Módulosjavafx são as bibliotecas FX UI.

Qualquer coisa necessária ao próprio JDK é mantida nos módulosjdk.

E finalmente,anything that is Oracle-specific is in the oracle modules.

4. Declarações de Módulo

Para configurar um módulo, precisamos colocar um arquivo especial na raiz de nossos pacotes chamadomodule-info.java.

Esse arquivo é conhecido como descritor do módulo e contém todos os dados necessários para criar e usar nosso novo módulo.

Construímos o módulo com uma declaração cujo corpo está vazio ou é composto pelas diretivas do módulo:

module myModuleName {
    // all directives are optional
}

Começamos a declaração do módulo com a palavra-chavemodule, e seguimos com o nome do módulo.

O módulo funcionará com esta declaração, mas normalmente precisaremos de mais informações.

É aí que as diretivas do módulo entram.

4.1. Requer

Nossa primeira diretiva érequires. Esta diretiva do módulo nos permite declarar dependências do módulo:

module my.module {
    requires module.name;
}

Agora,my.module temboth a runtime and a compile-time dependency emmodule.name.

E todos os tipos públicos exportados de uma dependência são acessíveis pelo nosso módulo quando usamos essa diretiva.

4.2. Requer Estático

Às vezes, escrevemos código que faz referência a outro módulo, mas que os usuários da nossa biblioteca nunca desejam usar.

Por exemplo, podemos escrever uma função de utilitário que imprima bastante nosso estado interno quando outro módulo de registro estiver presente. Porém, nem todo consumidor de nossa biblioteca deseja essa funcionalidade e não deseja incluir uma biblioteca de registro extra.

Nesses casos, queremos usar uma dependência opcional. Usando a diretivarequires static, criamos uma dependência apenas em tempo de compilação:

module my.module {
    requires static module.name;
}

4.3. Requer transitivo

Geralmente trabalhamos com bibliotecas para facilitar nossa vida.

Mas, precisamos ter certeza de que qualquer módulo que traga nosso código também trará essas dependências "transitivas" extras ou elas não funcionarão.

Felizmente, podemos usar a diretivarequires transitive para forçar qualquer consumidor downstream a ler nossas dependências necessárias:

module my.module {
    requires transitive module.name;
}

Agora, quando um desenvolvedorrequires my.module, ele também não precisará dizerrequires module.name para que nosso módulo ainda funcione.

4.4. Exportações

By default, a module doesn’t expose any of its API to other modules. Estestrong encapsulation foi um dos principais motivadores para a criação do sistema de módulos em primeiro lugar.

Nosso código é significativamente mais seguro, mas agora precisamos abrir explicitamente nossa API para o mundo, se quisermos que seja utilizável.

Usamos a diretivaexports para expor todos os membros públicos do pacote nomeado:

module my.module {
    exports com.my.package.name;
}

Agora, quando alguém fizerrequires my.module, eles terão acesso aos tipos públicos em nosso pacotecom.my.package.name, mas não a qualquer outro pacote.

4.5. Exporta ... para

Podemos usarexports…to para abrir nossas aulas públicas para o mundo.

Mas e se não quisermos que o mundo inteiro acesse nossa API?

Podemos restringir quais módulos têm acesso às nossas APIs usando a diretivaexports…to.

Semelhante à diretivaexports, declaramos um pacote como exportado. Mas, também listamos quais módulos estamos permitindo importar este pacote como umrequires. Vamos ver como é:

module my.module {
    export com.my.package.name to com.specific.package;
}

4.6. Uses

Umservice é uma implementação de uma interface específica ou classe abstrata que pode serconsumed por outras classes.

Designamos os serviços que nosso módulo consome com a diretivauses.

Observe quethe class name we use is either the interface or abstract class of the service, not the implementation class:

module my.module {
    uses class.name;
}

Devemos observar aqui que há uma diferença entre uma diretivarequires e a diretivauses.

Podemosrequire um módulo que fornece um serviço que queremos consumir, mas esse serviço implementa uma interface de uma de suas dependências transitivas.

Em vez de forçar nosso módulo a exigir dependências transitivasall apenas no caso, usamos a diretivauses para adicionar a interface necessária ao caminho do módulo.

4.7. Fornece ... Com

Um módulo também pode ser umservice provider que outros módulos podem consumir.

A primeira parte da diretiva é a palavra-chaveprovides. Aqui é onde colocamos a interface ou o nome da classe abstrata.

Em seguida, temos a diretivawith, onde fornecemos o nome da classe de implementação queimplementsa interface ouextendsa classe abstrata.

Aqui está o que parece ser feito:

module my.module {
    provides MyInterface with MyInterfaceImpl;
}

4.8. Open

Mencionamos anteriormente que o encapsulamento era um motivador para o design deste sistema de módulos.

Antes do Java 9, era possível usar a reflexão para examinar cada tipo e membro em um pacote, mesmo osprivate. Nada foi realmente encapsulado, o que pode abrir todos os tipos de problemas para os desenvolvedores das bibliotecas.

Como o Java 9 impõestrong encapsulation,we now have to explicitly grant permission for other modules to reflect on our classes.

Se quisermos continuar permitindo a reflexão completa como as versões anteriores do Java faziam, podemos simplesmenteopen todo o módulo:

open module my.module {
}

4.9. Abre

Se precisarmos permitir a reflexão de tipos privados, mas não quisermos todo o nosso código exposto,we can use the opens directive to expose specific packages.

Mas lembre-se, isso abrirá o pacote para o mundo inteiro, portanto, certifique-se de que é isso que você deseja:

module my.module {
  opens com.my.package;
}

4.10. Abre ... para

Ok, então a reflexão é ótima às vezes, mas ainda queremos o máximo de segurança possível comencapsulation. We can selectively open our packages to a pre-approved list of modules, in this case, using the opens…to directive:

module my.module {
    opens com.my.package to moduleOne, moduleTwo, etc.;
}

5. Opções de linha de comando

Até agora, o suporte para módulos Java 9 foi adicionado ao Maven e Gradle, então você não precisa fazer muita construção manual de seus projetos. No entanto, ainda é importante saberhow para usar o sistema de módulo a partir da linha de comando.

Estaremos usando a linha de comando para nosso exemplo completo abaixo para ajudar a solidificar como todo o sistema funciona em nossas mentes.

  • module-path– Usamos a opção–module-path para especificar o caminho do módulo. Esta é uma lista de um ou mais diretórios que contêm seus módulos.

  • add-reads - Em vez de confiar no arquivo de declaração do módulo, podemos usar a linha de comando equivalente à diretivarequires; –add-reads.

  • add-exports– Substituição de linha de comando para a diretivaexports.

  • add-opens– Substitua a cláusulaopen no arquivo de declaração do módulo.

  • add-modules–  Adiciona a lista de módulos ao conjunto padrão de módulos

  • list-modules–  Imprime uma lista de todos os módulos e suas strings de versão

  • patch-module - Adicionar ou substituir classes em módulos

  • illegal-access=permit|warn|deny - relaxa o encapsulamento forte mostrando um único aviso global, mostra todos os avisos ou falha com erros. O padrão épermit.

6. Visibilidade

Deveríamos gastar um pouco de tempo conversando sobre a visibilidade do nosso código.

A lot of libraries depend on reflection to work their magic (vêm à mente JUnit e Spring).

Por padrão no Java 9, teremosonly acesso a classes, métodos e campos públicos em nossos pacotes exportados. Mesmo se usarmos a reflexão para obter acesso a membros não públicos e ligar parasetAccessible(true), we não será capaz de acessar esses membros.

Podemos usar as opçõesopen,opens eopens…to para conceder acesso somente em tempo de execução para reflexão. Nota,this is runtime-only!

Não poderemos compilar com tipos privados e nunca deveríamos precisar fazer isso de qualquer maneira.

Se devemos ter acesso a um módulo para reflexão, e não somos os proprietários desse módulo (ou seja, não podemos usar a diretivaopens…to), então é possível usar a linha de comando–add-opens opção de permitir o acesso de reflexão dos próprios módulos ao módulo bloqueado em tempo de execução.

A única ressalva aqui é que você precisa ter acesso aos argumentos da linha de comando que são usados ​​para executar um módulo para que isso funcione.

7. Juntando tudo

Agora que sabemos o que é um módulo e como usá-los, vamos prosseguir e construir um projeto simples para demonstrar todos os conceitos que acabamos de aprender.

Para manter as coisas simples, não usaremos Maven ou Gradle. Em vez disso, contaremos com as ferramentas de linha de comando para construir nossos módulos.

7.1. Configurando Nosso Projeto

Primeiro, precisamos configurar nossa estrutura de projeto. Vamos criar vários diretórios para organizar nossos arquivos.

Comece criando a pasta do projeto:

mkdir module-project
cd module-project

Esta é a base de todo o nosso projeto; portanto, adicione arquivos aqui, como arquivos de compilação Maven ou Gradle, outros diretórios de origem e recursos.

Também colocamos um diretório para armazenar todos os nossos módulos específicos do projeto.

Em seguida, criamos um diretório de módulo:

mkdir simple-modules

Esta é a aparência de nossa estrutura de projeto:

module-project
|- // src if we use the default package
|- // build files also go at this level
|- simple-modules
  |- hello.modules
    |- com
      |- example
        |- modules
          |- hello
  |- main.app
    |- com
      |- example
        |- modules
          |- main

7.2. Nosso primeiro módulo

Agora que temos a estrutura básica no lugar, vamos adicionar nosso primeiro módulo.

No diretóriosimple-modules , crie um novo diretório chamadohello.modules.

We can name this anything we want but follow package naming rules (ou seja, pontos para separar palavras, etc.). Podemos até usar o nome do nosso pacote principal como o nome do módulo, se quisermos, mas geralmente queremos manter o mesmo nome que usaríamos para criar um JAR desse módulo.

Sob o nosso novo módulo, podemos criar os pacotes que queremos. No nosso caso, vamos criar uma estrutura de pacote:

com.example.modules.hello

Em seguida, crie uma nova classe chamadaHelloModules.java neste pacote. Manteremos o código simples:

package com.example.modules.hello;

public class HelloModules {
    public static void doSomething() {
        System.out.println("Hello, Modules!");
    }
}

E, finalmente, no diretório raizhello.modules, adicione nosso descritor de módulo; module-info.java:

module hello.modules {
    exports com.example.modules.hello;
}

Para manter este exemplo simples, tudo o que estamos fazendo é exportar todos os membros públicos do pacotecom.example.modules.hello .

7.3. Nosso segundo módulo

Nosso primeiro módulo é ótimo, mas não faz nada.

Podemos criar um segundo módulo que o usa agora.

Em nosso diretóriosimple-modules, crie outro diretório de módulo chamadomain.app. Vamos começar com o descritor do módulo desta vez:

module main.app {
    requires hello.modules;
}

Não precisamos expor nada ao mundo exterior. Em vez disso, tudo o que precisamos fazer é depender do nosso primeiro módulo, para ter acesso às classes públicas que ele exporta.

Agora podemos criar um aplicativo que o utilize.

Crie uma nova estrutura de pacote:com.example.modules.main.

Agora, crie um novo arquivo de classe chamadoMainApp.java.

package com.example.modules.main;

import com.example.modules.hello.HelloModules;

public class MainApp {
    public static void main(String[] args) {
        HelloModules.doSomething();
    }
}

E esse é todo o código que precisamos para demonstrar os módulos. Nosso próximo passo é criar e executar esse código na linha de comando.

7.4. Construindo Nossos Módulos

Para criar nosso projeto, podemos criar um script bash simples e colocá-lo na raiz do nosso projeto.

Crie um arquivo chamadocompile-simple-modules.sh:

#!/usr/bin/env bash
javac -d outDir --module-source-path simple-modules $(find simple-modules -name "*.java")

Este comando tem duas partes, os comandosjavacefind.

O comandofind simplesmente exibe uma lista de todos os arquivos.java em nosso diretório de módulos simples. Podemos então alimentar essa lista diretamente no compilador Java.

A única coisa que temos que fazer diferente das versões anteriores do Java é fornecer um parâmetromodule-source-path para informar ao compilador que ele está construindo módulos.

Assim que executarmos este comando, teremos uma pastaoutDir com dois módulos compilados dentro.

7.5. Executando nosso código

E agora podemos finalmente executar nosso código para verificar se os módulos estão funcionando corretamente.

Crie outro arquivo na raiz do projeto:run-simple-module-app.sh.

#!/usr/bin/env bash
java --module-path outDir -m main.app/com.example.modules.main.MainApp

Para executar um módulo, devemos fornecer pelo menosmodule-pathe a classe principal. Se tudo funcionar, você deverá ver:

>$ ./run-simple-module-app.sh
Hello, Modules!

7.6. Adicionando um serviço

Agora que temos um entendimento básico de como construir um módulo, vamos complicar um pouco mais.

Veremos como usar as diretivasprovides…witheuses.

Comece definindo um novo arquivo no módulohello.modules chamadoHelloInterface.java:

public interface HelloInterface {
    void sayHello();
}

Para tornar as coisas mais fáceis, vamos implementar esta interface com nossa classeHelloModules.java existente:

public class HelloModules implements HelloInterface {
    public static void doSomething() {
        System.out.println("Hello, Modules!");
    }

    public void sayHello() {
        System.out.println("Hello!");
    }
}

Isso é tudo que precisamos fazer para criar umservice.

Agora, precisamos dizer ao mundo que nosso módulo fornece esse serviço.

Adicione o seguinte ao nossomodule-info.java:

provides com.example.modules.hello.HelloInterface with com.example.modules.hello.HelloModules;

Como podemos ver, declaramos a interface e qual classe a implementa.

Em seguida, precisamos consumir esseservice. Em nosso módulomain.app, vamos adicionar o seguinte ao nossomodule-info.java:

uses com.example.modules.hello.HelloInterface;

Finalmente, em nosso método principal, podemos usar este serviço como este:

HelloModules module = new HelloModules();
module.sayHello();

Compile e execute:

#> ./run-simple-module-app.sh
Hello, Modules!
Hello!

Usamos essas diretivas para ser muito mais explícito sobre como nosso código deve ser usado.

Podemos colocar a implementação em um pacote privado enquanto expomos a interface em um pacote público.

Isso torna nosso código muito mais seguro, com muito pouco custo adicional.

Vá em frente e experimente algumas das outras diretrizes para aprender mais sobre os módulos e como eles funcionam.

8. Adicionando módulos ao módulo sem nome

The unnamed module concept is similar to the default package. Portanto, não é considerado um módulo real, mas pode ser visto como o módulo padrão.

Se uma classe não for membro de um módulo nomeado, ela será automaticamente considerada como parte desse módulo não nomeado.

Às vezes, para garantir módulos específicos de plataforma, biblioteca ou provedor de serviços no gráfico do módulo, precisamos adicionar módulos ao conjunto raiz padrão. Por exemplo, quando tentamos executar os programas Java 8 como estão no compilador Java 9, podemos precisar adicionar módulos.

Em geral,the option to add the named modules to the default set of root modules is *–add-modules <module>*(,<module>)* em que<module> é um nome de módulo.

Por exemplo, para fornecer acesso a todos os módulosjava.xml.bind, a sintaxe seria:

--add-modules java.xml.bind

Para usar isso no Maven, podemos incorporar o mesmo aomaven-compiler-plugin:


    org.apache.maven.plugins
    maven-compiler-plugin
    3.8.0
    
        9
        9
        
            --add-modules
            java.xml.bind
        
    

9. Conclusão

Neste extenso guia, abordamos e abordamos o básico do novo sistema Java 9 Module.

Começamos falando sobre o que é um módulo.

Em seguida, falamos sobre como descobrir quais módulos estão incluídos no JDK.

Também abordamos o arquivo de declaração do módulo em detalhes.

Completamos a teoria falando sobre os vários argumentos de linha de comando de que precisaremos para construir nossos módulos.

Finalmente, colocamos em prática todo o nosso conhecimento anterior e criamos um aplicativo simples, construído sobre o sistema de módulos.

Para ver este código e mais, certifique-se decheck it out over on Github.