Menos Classpath Hell com módulos do Java 9

Digamos que você tem um código que, por reflexão, usa a classe Resolucao12 para chamar o método designacao:

package br.gov.anvisa;

import java.lang.reflect.Method;

public class Main {
  public static void main(String[] args) throws Exception {
    Class classe = Class.forName("br.gov.anvisa.resolucoes.Resolucao12");
    Object objeto = classe.newInstance();
    Method metodo = classe.getMethod("designacao");
    System.out.println(metodo.invoke(objeto));
  }
}

Não há dependência em compile time a essa classe Resolucao12. Precisamos dela apenas em runtime.

Compilamos a classe Main e geramos o anvisa.jar.

Uma das implementações de Resolucao12 é a seguinte:

package br.gov.anvisa.resolucoes;

public class Resolucao12 {
  public String designacao() {
    return "Biscoito!";
  }
}

Vamos empacotar essa implementação em piraque.jar.

A outra implementação de Resolucao12 será:

package br.gov.anvisa.resolucoes;

public class Resolucao12 {
  public String designacao() {
    return "Bolacha!";
  }
}

Essa outra implementação ficará em tostines.jar.

Vamos colocar todos esses JARs no diretório libs:

libs
├── anvisa.jar
├── piraque.jar
└── tostines.jar

Na hora de executar a classe Main, vamos colocar o diretório libs no Classpath com a opção -cp:

java -cp "libs/*" br.gov.anvisa.Main

E aí? O que será retornado?

Biscoito!

Pô, Java… É bolacha!

No final das contas, o código invocado foi o do piraque.jar.

Classpath Hell

Funcionou, então tá bom! Mas peraí…

A questão é que, se tivermos duas versões da mesma classe no Classpath, não dá pra saber qual vai ser executada. Não dá erro, mas não dá pra saber o que vai ser executado.. Isso pode trazer surpresas, o que é um problema!

Em um projeto maior, pode ser que bibliotecas que você usa dependam de versões diferentes de outras bibliotecas, que tem as mesmas classes. Qual delas será carregada?

O fato de não sabermos o que vai acontecer no runtime dependendo da presença de JARs com as mesmas classes é o que chamamos de Classpath Hell.

Há ainda o ClassLoader Hell, conceito relacionado que ocorre bastante quando usamos servidores de aplicação.

Quem mexe há algum tempo com Java já deve ter sofrido com algo parecido com org.dom4j.DocumentFactory cannot be cast to org.dom4j.DocumentFactory.

Evitando surpresas com Java 9

Podemos executar os mesmos JARs do diretório libs como módulos automáticos usando a opção --module-path do Java 9:

java --module-path libs/ --module anvisa/br.gov.anvisa.Main

Teremos os módulos piraque e tostines, criados automaticamente a partir do piraque.jar e do tostines.jar, respectivamente.

Além disso, usamos o --module com o módulo anvisa, criado a partir do anvisa.jar, para executar a classe Main.

Ao contrário do que ocorreu antes, com um Module Path teremos um erro:

Error occurred during initialization of boot layer
java.lang.module.ResolutionException:
Modules piraque and tostines export package
br.gov.anvisa.resolucoes to module anvisa

Os módulos automáticos criados a partir de um JAR não modularizado exportam todos os seus pacotes.

Como tanto o módulo piraque como o módulo tostines exportam o pacote br.gov.anvisa.resolucoes, temos uma ResolutionException. E com uma mensagem surpreendentemente explicativa!

Arquitetura negativa

Com módulos do Java 9, podemos considerar que não teremos mais de uma versão da mesma classe em runtime. Uma coisa a menos pra dar errado!

Boa parte das técnicas para deixar o código menos complexo, fazendo com que possamos encaixá-lo nas nossas cabeças, tratam de limitar o que pode acontecer. Pense em imutabilidade ou encapsulamento.

É o que Michael Feathers chama de Arquitetura Negativa: as garantias que você tem do que não vai acontecer no seu software.

Anúncios