Arquivos Jar Multi-Release

Arquivos Jar Multi-Release

1. Visão geral

Java está em constante evolução e adicionando novos recursos ao JDK. E, se quisermos usar esses recursos em nossas APIs, isso poderá obrigar as dependências downstream a atualizar sua versão do JDK.

Às vezes,we are forced to wait on using new language features para permanecer compatível.

Neste tutorial, no entanto, aprenderemos sobre JARs de vários lançamentos (MRJAR) e comothey can simultaneously contain implementations compatible with disparate JDK versions.

2. Exemplo Simples

Vamos dar uma olhada em uma classe de utilitário chamadaDateHelper que tem um método para verificarleap years. Vamos supor que foi escrito usando JDK 7 e construído para rodar em JRE 7+:

public class DateHelper {
    public static boolean checkIfLeapYear(String dateStr) throws Exception {
        logger.info("Checking for leap year using Java 1 calendar API ");

        Calendar cal = Calendar.getInstance();
        cal.setTime(new SimpleDateFormat("yyyy-MM-dd").parse(dateStr));
        int year = cal.get(Calendar.YEAR);

        return (new GregorianCalendar()).isLeapYear(year);
    }
}

O métodocheckIfLeapYear seria invocado a partir do métodomain de nosso aplicativo de teste:

public class App {
    public static void main(String[] args) throws Exception {
        String dateToCheck = args[0];
        boolean isLeapYear = DateHelper.checkIfLeapYear(dateToCheck);
        logger.info("Date given " + dateToCheck + " is leap year: " + isLeapYear);
    }
}

Vamos avançar para hoje.

Sabemos que o Java 8 tem maisconcise way to parse the date. Então, gostaríamos de aproveitar isso e reescrever nossa lógica. For this, we need to switch to JDK 8+. However, that would mean our module would stop working on JRE 7 for which it was originally written.

E não queremos que isso aconteça, a menos que seja absolutamente necessário.

3. Arquivos Jar Multi-Release

A solução em Java 9 éleave the original class untouched and instead create a new version using the new JDK and package them together. No tempo de execução, a JVM (versão 9 ou superior) chamará qualquer uma dessas duas versõesgiving more preference to the highest version that the JVM supports.

Por exemplo, se um MRJAR contiver Java versão 7 (padrão), 9 e 10 da mesma classe, a JVM 10+ executará a versão 10 e a JVM 9 executará a versão 9. Nos dois casos, a versão padrão não é executada, pois existe uma versão mais apropriada para essa JVM.

Observe quepublic definitions of the new version of the class should exactly match the original version. Em outras palavras, não temos permissão para adicionar novas APIs públicas exclusivas para uma nova versão.

4. Estrutura de pastas

Como as classes em Java mapeiam diretamente para arquivos por seus nomes, não é possível criar uma nova versão deDateHelper no mesmo local. Portanto, precisamos criá-los em uma pasta separada.

Vamos começar criando uma pastajava9 no mesmo nível dejava. Depois disso, vamos clonar o arquivoDateHelper.java retendo sua estrutura de pasta do pacote e colocá-lo emjava9:

src/
    main/
        java/
            com/
                example/
                    multireleaseapp/
                        App.java
                        DateHelper.java
        java9/
            com/
                example/
                    multireleaseapp/
                        DateHelper.java

Some IDEs that don’t yet support MRJARs pode lançar erros para classes duplicadas deDateHelper.java.

Pegaremoshow to integrate this with build tools like Maven em outro tutorial. Por enquanto, vamos nos concentrar apenas nos fundamentos.

5. Alterações de código

Vamos reescrever a lógica da classe clonadajava9:

public class DateHelper {
    public static boolean checkIfLeapYear(String dateStr) throws Exception {
        logger.info("Checking for leap year using Java 9 Date Api");
        return LocalDate.parse(dateStr).isLeapYear();
    }
}

Observe aqui quewe’re not making any changes to the public method signatures of the cloned class but only changing the inner logic. At the same time, we’re not adding any new public methods.

Isso é muito importante porque a criação do jar falhará se essas duas regras não forem seguidas.

6. Compilação cruzada em Java

Compilação cruzada é o recurso em Java que pode compilar arquivos para execução em versões anteriores. Isso significa que não precisamos instalar versões separadas do JDK.

Vamos compilar nossas classes usando JDK 9 ou superior.

Primeiramente, compile o código antigo para a plataforma Java 7:

javac --release 7 -d classes src\main\java\com\example\multireleaseapp\*.java

Em segundo lugar, compile o novo código para a plataforma Java 9:

javac --release 9 -d classes-9 src\main\java9\com\example\multireleaseapp\*.java

A opçãorelease é usada para indicar a versão do compilador Java e o JRE de destino.

7. Criando o MRJAR

Por fim, crie o arquivo MRJAR usando a versão 9+:

jar --create --file target/mrjar.jar --main-class com.example.multireleaseapp.App
  -C classes . --release 9 -C classes-9 .

A opçãorelease seguida por um nome de pasta faz com que o conteúdo dessa pasta seja empacotado dentro do arquivo jar com o valor do número da versão:

com/
    example/
        multireleaseapp/
            App.class
            DateHelper.class
META-INF/
    versions/
        9/
            com/
                example/
                    multireleaseapp/
                        DateHelper.class
    MANIFEST.MF

O arquivoMANIFEST.MF tem a propriedade definida para permitir que a JVM saiba que este é um arquivo MRJAR:

Multi-Release: true

Conseqüentemente, a JVM carrega a classe apropriada no tempo de execução.

JVMs mais antigas ignoram a nova propriedade que indica que este é um arquivo MRJAR e o tratam como um arquivo JAR normal.

8. Teste

Finalmente, vamos testar nosso jar com Java 7 ou 8:

> java -jar target/mrjar.jar "2012-09-22"
Checking for leap year using Java 1 calendar API
Date given 2012-09-22 is leap year: true

E então, vamos testar o jar novamente no Java 9 ou posterior:

> java -jar target/mrjar.jar "2012-09-22"
Checking for leap year using Java 9 Date Api
Date given 2012-09-22 is leap year: true

9. Conclusão

Neste artigo, vimos como criar um arquivo jar multi-lançamento usando um exemplo simples.

Como sempre, a base de código para o aplicativo multi-lançamento está disponívelover on GitHub.