Problemas e resoluções de migração do Java 9

Problemas e resoluções de migração do Java 9

1. Visão geral

A plataforma Java costumava ter uma arquitetura monolítica, agrupando todos os pacotes como uma única unidade.

No Java 9, isso foi simplificado com a introdução deJava Platform Module System (JPMS) ouModules para abreviar. Pacotes relacionados foram agrupados em módulos emodules replaced packages to become the basic unit of reuse.

Neste tutorial rápido, veremos alguns dos problemas relacionados aos módulos que podemos enfrentar quandomigrating an existing application to Java 9.

2. Exemplo Simples

Vamos dar uma olhada em um aplicativo Java 8 simples que contém quatro métodos, válidos no Java 8, mas desafiadores em versões futuras. Usaremos esses métodos para entender o impacto da migração para o Java 9.

O primeiro métodofetches the name of the JCE provider referenciado no aplicativo:

private static void getCrytpographyProviderName() {
    LOGGER.info("1. JCE Provider Name: {}\n", new SunJCE().getName());
}

O segundo método lista osnames of the classes in a stack trace:

private static void getCallStackClassNames() {
    StringBuffer sbStack = new StringBuffer();
    int i = 0;
    Class caller = Reflection.getCallerClass(i++);
    do {
        sbStack.append(i + ".").append(caller.getName())
            .append("\n");
        caller = Reflection.getCallerClass(i++);
    } while (caller != null);
    LOGGER.info("2. Call Stack:\n{}", sbStack);
}

O terceiro métodoconverts a Java object into XML:

private static void getXmlFromObject(Book book) throws JAXBException {
    Marshaller marshallerObj = JAXBContext.newInstance(Book.class).createMarshaller();
    marshallerObj.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

    StringWriter sw = new StringWriter();
    marshallerObj.marshal(book, sw);
    LOGGER.info("3. Xml for Book object:\n{}", sw);
}

E o método finalencodes a string to Base 64 using sun.misc.BASE64Encoder, from the JDK internal libraries:

private static void getBase64EncodedString(String inputString) {
    String encodedString = new BASE64Encoder().encode(inputString.getBytes());
    LOGGER.info("4. Base Encoded String: {}", encodedString);
}

Vamos invocar todos os métodos do método principal:

public static void main(String[] args) throws Exception {
    getCrytpographyProviderName();
    getCallStackClassNames();
    getXmlFromObject(new Book(100, "Java Modules Architecture"));
    getBase64EncodedString("Java");
}

Quando executamos esse aplicativo no Java 8, obtemos o seguinte:

> java -jar target\pre-jpms.jar
[INFO] 1. JCE Provider Name: SunJCE

[INFO] 2. Call Stack:
1.sun.reflect.Reflection
2.com.example.prejpms.App
3.com.example.prejpms.App

[INFO] 3. Xml for Book object:


    Java Modules Architecture


[INFO] 4. Base Encoded String: SmF2YQ==

Normalmente, as versões Java garantem compatibilidade com versões anteriores, mas oJPMS muda um pouco disso.

3. Execução em Java 9

Agora, vamos executar este aplicativo em Java 9:

>java -jar target\pre-jpms.jar
[INFO] 1. JCE Provider Name: SunJCE

[INFO] 2. Call Stack:
1.sun.reflect.Reflection
2.com.example.prejpms.App
3.com.example.prejpms.App

[ERROR] java.lang.NoClassDefFoundError: javax/xml/bind/JAXBContext
[ERROR] java.lang.NoClassDefFoundError: sun/misc/BASE64Encoder

Podemos ver que os dois primeiros métodos funcionam bem, enquanto os dois últimos falharam. Let’s investigate the cause of failure by analyzing the dependencies of our application. Usaremos a ferramentajdeps que acompanha o Java 9:

>jdeps target\pre-jpms.jar
   com.example.prejpms            -> com.sun.crypto.provider               JDK internal API (java.base)
   com.example.prejpms            -> java.io                               java.base
   com.example.prejpms            -> java.lang                             java.base
   com.example.prejpms            -> javax.xml.bind                        java.xml.bind
   com.example.prejpms            -> javax.xml.bind.annotation             java.xml.bind
   com.example.prejpms            -> org.slf4j                             not found
   com.example.prejpms            -> sun.misc                              JDK internal API (JDK removed internal API)
   com.example.prejpms            -> sun.reflect                           JDK internal API (jdk.unsupported)

A saída do comando fornece:

  • a lista de todos os pacotes dentro de nosso aplicativo na primeira coluna

  • a lista de todas as dependências em nosso aplicativo na segunda coluna

  • a localização das dependências na plataforma Java 9 -this can be a module name, or an internal JDK API, ou nenhum para bibliotecas de terceiros

4. Módulos preteridos

Vamos agora tentar resolver o primeiro errojava.lang.NoClassDefFoundError: javax/xml/bind/JAXBContext.

De acordo com a lista de dependências, sabemos que o pacotejava.xml.bind pertence ajava.xml.bind module que parece ser um módulo válido. Então, vamos dar uma olhada emofficial documentation for this module.

A documentação oficial diz que o módulojava.xml.bind está obsoleto para remoção em uma versão futura. Conseqüentemente, este módulo não é carregado por padrão no caminho de classe.

No entanto,Java provides a method to load modules on demand usando a opção–add-modules. Então, vamos tentar:

>java --add-modules java.xml.bind -jar target\pre-jpms.jar
...
INFO 3. Xml for Book object:


    Java Modules Architecture

...

Podemos ver que a execução foi bem-sucedida. Esta solução é rápida e fácil, mas não é a melhor solução.

Como solução de longo prazo, devemos adicionardependency como uma biblioteca de terceiros usando Maven:


    javax.xml.bind
    jaxb-api
    2.3.1

5. APIs internas do JDK

Vamos agora dar uma olhada no segundo errojava.lang.NoClassDefFoundError: sun/misc/BASE64Encoder.

Na lista de dependências, podemos ver que o pacotesun.misc é uma APIJDK internal.

APIs internas, como o nome sugere, são códigos privados, usados ​​internamente no JDK.

Em nosso exemplo,the internal API appears to have been removed from the JDK. Vamos verificar qual é a API alternativa para isso usando a opção–jdk-internals:

>jdeps --jdk-internals target\pre-jpms.jar
...
JDK Internal API                         Suggested Replacement
----------------                         ---------------------
com.sun.crypto.provider.SunJCE           Use java.security.Security.getProvider(provider-name) @since 1.3
sun.misc.BASE64Encoder                   Use java.util.Base64 @since 1.8
sun.reflect.Reflection                   Use java.lang.StackWalker @since 9

Podemos ver que o Java 9 recomenda o uso dejava.util.Base64 em vez desun.misc.Base64Encoder.. Consequentemente, uma alteração de código é obrigatória para que nosso aplicativo seja executado no Java 9. __

Observe que há duas outras APIs internas que estamos usando em nosso aplicativo para as quais a plataforma Java sugeriu substituições, mas não recebemos nenhum erro para estas:

  • Algumas APIs internas, como o módulosun.reflect.Reflectionwere considered critical to the platform and so were added to a JDK-specificjdk.unsupported. Este módulo está disponível por padrão no caminho de classe no Java 9.

  • Internal APIs like com.sun.crypto.provider.SunJCE are provided only on certain Java implementations. Contanto que o código que os usa seja executado na mesma implementação, ele não gerará erros.

Em todos os casosin this example, we’re using internal APIs, which is not a recommended practice. Portanto, a solução a longo prazo é substituí-los por APIs públicas adequadas fornecidas pela plataforma.

6. Conclusão

Neste artigo, vimos como o sistema de módulos foi introduzido emJava 9 can cause migration problems for some older applications using deprecated or internal APIs.

Também vimos como aplicar correções de curto e longo prazo para esses erros.

Como sempre, os exemplos deste artigo estão disponíveisover on GitHub.