Modularidade em Java 9

A principal inovação em Java 9 foi a introdução da modularidade. Houve muita conversa sobre esse recurso, a data de lançamento foi adiada várias vezes para concluir tudo corretamente. Neste post, falaremos sobre o que fornece o mecanismo dos módulos e o que o Java 9 útil trouxe em geral. A base do post foi o relatório do meu colega Sergei Malkevich .




Para implementar os módulos nesta versão do Java, um projeto inteiro foi alocado - Project Jigsaw - que inclui vários JEP e JSR.



Para fãs da documentação oficial, você pode ler mais sobre cada JEP aqui .

Mais sobre o Project Jigsaw


O Project Jigsaw, que implementa a modularidade, começou a ser desenvolvido em 2005: o primeiro JSR 277 foi lançado e, já em 2008, o trabalho direto no projeto foi iniciado. O lançamento ocorreu apenas em 2017. Ou seja, para estragar os módulos em Java, foram necessários quase 10 anos. O que, de fato, enfatiza a escala completa do trabalho e as mudanças que foram feitas durante a implementação da modularidade.

Quais são os objetivos estabelecidos pelos desenvolvedores:

  • facilitar o desenvolvimento de grandes aplicativos e bibliotecas;
  • Melhorar a segurança do Java SE em geral e do JDK em particular;
  • aumentar o desempenho do aplicativo;
  • crie a capacidade de reduzir o tamanho do JRE para executar em dispositivos pequenos, para não consumir muita memória;
  • JAR HELL (mais sobre isso mais tarde).

Que Java 9 útil trouxe


Antes da versão 9, o JDK e o JRE eram monolíticos. Seu tamanho aumentou a cada lançamento. O Java 8 já ocupava centenas de megabytes, e tudo isso os desenvolvedores tinham que "carregar com eles" todas as vezes para poder executar aplicativos Java. Somente o rt.jar pesa cerca de 60 Mb. Bem, aqui também adicionamos um início lento e alto consumo de memória. O Java 9 veio em socorro

No JDK 9Foi introduzida a separação de módulos, ou seja, o JDK foi dividido em 73 módulos. E a cada nova versão, o número desses módulos está crescendo. Na versão 11, esse número é próximo a 100. Essa separação permitiu que os desenvolvedores criassem o utilitário JLINK. Usando o JLINK, você pode criar conjuntos JRE personalizados que incluirão apenas os módulos "necessários" que seu aplicativo realmente precisa. Portanto, um aplicativo simples e algum customJRE com um conjunto mínimo (ou pequeno) de módulos podem eventualmente caber em 20 Mb, o que é uma boa notícia.

A lista de módulos pode ser encontrada aqui .

Com o advento do Java 9, a estrutura do JDK mudou: agora é idêntica à estrutura do JRE. Se anteriormente o JDK incluía a pasta JRE, onde bin está novamente e os arquivos são duplicados, agora tudo se parece com isso:



Módulos


Na realidade. O que é um módulo? Um módulo é um novo nível de agregação de pacotes e recursos (orig. “Um grupo reutilizável e nomeado de pacotes relacionados, assim como recursos e um descritor de módulo” ).

Os módulos são entregues em arquivos JAR com pacotes e descritor de módulo
module-info.java . O arquivo module-info.java contém uma descrição do módulo:
nome, dependências, pacotes exportados, serviços consumidos e fornecidos, permissões para acesso à reflexão.

Exemplos de descrições de descritores de módulo:

module java.sql {
    requires transitive java.logging;
    requires transitive java.transaction.xa;
    requires transitive java.xml;

    exports java.sql;
    exports javax.sql;

    uses java.sql.Driver;
}

module jdk.javadoc {
   requires java.xml;
   
   requires transitive java.compiler;
   requires transitive jdk.compiler;
   
   exports jdk.javadoc.doclet;
   
   provides java.util.spi.ToolProvider with
       jdk.javadoc.internal.tool.JavadocToolProvider;
   
   provides javax.tools.DocumentationTool with
       jdk.javadoc.internal.api.JavadocTool;
   
   provides javax.tools.Tool with
      jdk.javadoc.internal.api.JavadocTool;   
}

Após o módulo de palavras-chave, temos o nome do pacote jdk.javadoc , que depende de outro pacote java.xml e depende transitivamente de outros pacotes.

Vamos dar uma olhada em cada uma das palavras-chave:

  • requer indica os módulos dos quais o módulo atual depende;

  • requer transitivo - uma dependência transitiva - significa o seguinte: se o módulo m1 é transitivamente dependente do módulo m2 e temos algum terceiro módulo mX , que depende de m1 - o módulo mX também terá acesso a m2 ;

  • requer static permite especificar dependências em tempo de compilação;

  • exports , ( “”);

  • exports...to… : export com.my.package.name to com.specific.package; - () () ;

  • uses , :

    uses java.sql.Driver;

    , ;

  • provides , :

    provides javax.tools.Tool with
        jdk.javadoc.internal.api.JavadocTool;

    javax.tools.Tool, with — .

Um pouco sobre serviços.Vamos

dizer que temos vários módulos conectados que implementam um serviço abstrato - MyService . Ao criar o aplicativo, temos a oportunidade de decidir qual implementação de serviço usar "arrastando" os módulos de implementação de serviço necessários para --module-path :

Iterable<MyService> services = 
        ServiceLoader.load(MyService.class);

Portanto, o iterador retornado contém uma lista de implementações da interface do MyService. De fato, ele conterá todas as implementações encontradas nos módulos encontrados em --module-path .

Por que, em princípio, os serviços foram introduzidos? Eles são necessários para mostrar como nosso código será usado. Ou seja, há um papel semântico. Além disso, a modularidade é sobre encapsulamento e segurança, pois podemos tornar a implementação privada e excluir a possibilidade de acesso não autorizado por meio de reflexão.

Além disso, uma das opções para o uso de serviços é uma implementação bastante simples de plug-ins. Podemos implementar a interface do plug-in para nosso aplicativo e conectar módulos para trabalhar com eles.

Vamos voltar à sintaxe para descrever os módulos:

Até o 9ki, através da reflexão, tínhamos acesso a quase tudo e podíamos fazer o que quiséssemos e com o que queremos. E a 9ª versão, como já mencionado, permite que você se proteja do acesso à reflexão "ilegal".

Podemos abrir completamente o módulo para acesso à reflexão, declarando aberto :

open module my.module {
}

Ou, podemos especificar qualquer pacote para acesso à reflexão, declarando opens :

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

Aqui também é possível usar abre com.my.coolpackage para ... , dando assim acesso a reflexão para o com.my.coolpackage pacote do pacote que irá indicar após a .

Tipos de módulos


O Project Jigsaw classifica os módulos da seguinte maneira:

  • System Modules — Java SE JDK . , java --list-modules.

  • Application Modules — , , ( ), .

  • Automatic Modules — , Java JAR-. , , - . JAR- --module-path Java , JAR-.

  • Unnamed Module — , JAR-, --class-path. Java .

Class-path vs module-path


Com o advento dos módulos, um novo conceito apareceu - module-path . Em essência, esse é o mesmo caminho de classe , mas para os módulos.

O lançamento de um aplicativo modular é o seguinte:



No modo de inicialização normal, especificamos as opções e o caminho completo para a classe principal. Caso desejemos trabalhar com módulos, também especificamos opções e o parâmetro -m ou -module , que indica apenas que executaremos os módulos. Ou seja, traduzimos automaticamente nosso aplicativo para o modo modular. Em seguida, indicamos o nome do módulo e o caminho para a classe principal do módulo.

Além disso, se estiver no modo normal, estamos acostumados a trabalhar com as opções -cp e --class-path, no modo modular, prescrevemos um novo parâmetro -p e --module-path , que indica os caminhos para os módulos usados ​​no aplicativo.

Frequentemente, encontro o fato de que os desenvolvedores não mudam para a versão 9 ou superior, porque acreditam que terão que trabalhar com módulos. Embora, de fato, possamos executar nossos aplicativos no modo antigo, simplesmente sem escrever um parâmetro ou usar módulos, mas usando apenas outros chips novos.

Jar hell


Eu também quero insistir na diagonal no problema do Jar Hell.



O que é Jar Hell em poucas palavras? Por exemplo, nós temos algum tipo de nossa aplicação e depende do X biblioteca e a biblioteca Y . Ao mesmo tempo, essas duas bibliotecas dependem da biblioteca Z , mas de versões diferentes: X depende da versão 1 , Y da versão 2 . Bem, se a versão 2 é compatível com a versão 1, então não há problema. Caso contrário, é óbvio que estamos tendo um conflito de versão, ou seja, a mesma biblioteca não pode ser carregada na memória pelo mesmo carregador de classes.

Como você sai dessa situação? Existem métodos padrão que os desenvolvedores usaram desde o primeiro Java, por exemplo, excluir , alguém usa plug-ins para o Maven, que renomeiam os nomes dos pacotes raiz da biblioteca. Ou, os desenvolvedores estão procurando versões diferentes da biblioteca X para encontrar uma opção compatível.

Por que estou: os primeiros protótipos de quebra-cabeças implicavam que o módulo tinha uma versão e permitia o carregamento de várias versões através de diferentes ClassLoaders, mas depois foi abandonado. Como resultado, a “bala de prata”, que muitos estavam esperando, não deu certo.

Mas, logo de cara, estávamos um pouco a salvo de tais problemas. O Java 9 desativa pacotes divididos- pacotes que são divididos em vários módulos. Ou seja, se tivermos o pacote com.my.coolpackage em um módulo, não poderemos usá-lo em outro módulo dentro do mesmo aplicativo. Quando você inicia o aplicativo com módulos contendo os mesmos pacotes, simplesmente travamos. Essa pequena melhoria elimina a possibilidade de comportamento imprevisível em relação ao download de pacotes Split.

Além disso, além dos próprios módulos, há também um mecanismo de camada ou Camadas de serra de vaivém , que também ajuda a lidar com o problema de Jar Hell.

Uma camada de serra de vaivém pode ser definida como um sistema modular local. E aqui vale a pena notar que os pacotes Split mencionados acima são proibidos apenas dentro da estrutura de uma camada do Jigsaw. Módulos com os mesmos pacotes têm um lugar para estar, mas devem pertencer a diferentes camadas.

É assim:



Quando o aplicativo é iniciado, a camada de inicialização é criada , incluindo os módulos da plataforma carregados pelo Bootstrap, módulos adicionais da plataforma carregados pelo carregador da plataforma e nossos módulos de aplicativos carregados pelo carregador da Aplicação.

A qualquer momento, podemos criar nossas próprias camadas e "colocar" módulos de diferentes versões lá e não cair.

Há uma ótima e detalhada palestra no YouTube sobre o tópico: Escapando do inferno do jar com camadas de quebra-cabeças

Conclusão


O mecanismo de módulo do Java 9 abre novas possibilidades para nós, enquanto o suporte para bibliotecas hoje é bastante pequeno. Sim, as pessoas executam Spring, Spring Boot e assim por diante. Mas a maioria das bibliotecas não mudou para o uso completo dos módulos. Aparentemente, portanto, todas essas mudanças foram percebidas com ceticismo pela comunidade técnica. Os módulos nos proporcionam novas oportunidades, mas a questão da demanda permanece em aberto.

Por fim, ofereço uma seleção de materiais sobre este tópico: Resumo do módulo JDK do

Jigsaw do projeto Paul Deitel - Compreendendo os módulos Java 9 baeldung.com - Introdução ao Jigsaw do projeto Alex Buckley - Desenvolvimento modular com o JDK 9 Evgeny Kozlov - Módulos em Java










All Articles