Buenas tardes, colegas. Hace exactamente un mes, recibimos un contrato para la traducción de
Java moderno de Manning, que debería ser uno de nuestros nuevos productos más notables el próximo año. El problema de "Modern" y "Legacy" en Java es tan agudo que la necesidad de tal libro está bastante madura. La escala del desastre y cómo resolver problemas en Java 9 se describen brevemente en un artículo de Wayne Citrin, una traducción de la cual queremos ofrecerle hoy.
Cada pocos años, con el lanzamiento de una nueva versión de Java, los hablantes de JavaOne comienzan a saborear nuevas construcciones de lenguaje y API, y elogian sus virtudes. Y los entusiastas desarrolladores, mientras tanto, están ansiosos por introducir nuevas características. Tal imagen está lejos de ser realidad: no tiene en cuenta por completo que la mayoría de los programadores están ocupados
apoyando y finalizando aplicaciones existentes , y no escriben nuevas aplicaciones desde cero.
La mayoría de las aplicaciones, especialmente las comerciales, deben ser compatibles con versiones anteriores de Java que no sean compatibles con todas estas nuevas características de superduper. Finalmente, la mayoría de los clientes y usuarios finales, especialmente en el segmento de grandes empresas, desconfían de una actualización radical de la plataforma Java, y prefieren esperar hasta que se fortalezca.
Por lo tanto, tan pronto como el desarrollador intente una nueva oportunidad, se enfrenta a problemas. ¿Usaría los métodos de interfaz predeterminados en su código? Quizás, si tiene suerte y su aplicación no necesita interactuar con Java 7 o inferior. ¿Desea utilizar la clase
java.util.concurrent.ThreadLocalRandom
para generar números pseudoaleatorios en una aplicación multiproceso? No funcionará si su aplicación debe ejecutarse en Java 6, 7, 8 o 9 al mismo tiempo.
Con el lanzamiento de la nueva versión, los desarrolladores que admiten código heredado se sienten como niños obligados a mirar por la ventana de una pastelería. No se les permite entrar, por lo que su destino es la decepción y la frustración.
Entonces, ¿hay algo en la nueva versión de Java 9 para programadores involucrados en el soporte de código heredado? ¿Algo que podría facilitarles la vida? Afortunadamente si.
Lo que había que hacer con el soporte del código heredado es la aparición de Java 9Por supuesto, puede trasladar las capacidades de la nueva plataforma a
aplicaciones heredadas en las que debe cumplir con la compatibilidad con versiones anteriores. En particular, siempre hay oportunidades para aprovechar las nuevas API. Sin embargo, puede resultar un poco feo.
Por ejemplo, puede aplicar el enlace tardío si desea acceder a la nueva API cuando su aplicación también necesita trabajar con versiones anteriores de Java que no son compatibles con esta API. Suponga que desea usar la clase
java.util.stream.LongStream
, introducida en Java 8, y quiere usar el
anyMatch(LongPredicate)
de esta clase, pero la aplicación debe ser compatible con Java 7. Puede crear una clase auxiliar, como esta:
public classLongStreamHelper { private static Class longStreamClass; private static Class longPredicateClass; private static Method anyMatchMethod; static { try { longStreamClass = Class.forName("java.util.stream.LongStream"); longPredicateClass = Class.forName("java.util.function.LongPredicate"); anyMatchMethod = longStreamClass.getMethod("anyMatch", longPredicateClass): } catch (ClassNotFoundException e) { longStreamClass = null; longPredicateClass = null; anyMatchMethod = null } catch (NoSuchMethodException e) { longStreamClass = null; longPredicateClass = null; anyMatchMethod = null; } public static boolean anyMatch(Object theLongStream, Object thePredicate) throws NotImplementedException { if (longStreamClass == null) throw new NotImplementedException(); try { Boolean result = (Boolean) anyMatchMethod.invoke(theLongStream, thePredicate); return result.booleanValue(); } catch (Throwable e) {
Hay maneras de simplificar esta operación, o de hacerla más general o más efectiva: usted tiene la idea.
En lugar de llamar a
theLongStream.anyMatch(thePredicate)
, como lo haría en Java 8, puede llamar a
LongStreamHelper.anyMatch(theLongStream, thePredicate)
en cualquier versión de Java. Si está tratando con Java 8, esto funcionará, pero si con Java 7, el programa arrojará una
NotImplementedException
.
¿Por qué es esto feo? Debido a que el código puede volverse demasiado complicado si necesita acceder a muchas API (de hecho, incluso ahora, con una sola API, esto ya es inconveniente). Además, esta práctica no es de tipo seguro, ya que el código no puede mencionar directamente
LongStream
o
LongPredicate
. Finalmente, esta práctica es mucho menos eficiente, debido a la sobrecarga de la reflexión, así como
try-catch
bloques adicionales
try-catch
. Por lo tanto, aunque se puede hacer de esta manera, no es demasiado interesante y está lleno de errores debido al descuido.
Sí, puede acceder a las nuevas API y su código al mismo tiempo mantiene la compatibilidad con versiones anteriores, pero no tendrá éxito con las nuevas construcciones de lenguaje. Por ejemplo, supongamos que necesitamos usar expresiones lambda en el código que debería seguir siendo compatible con versiones anteriores y funcionar en Java 7. No tiene suerte. El compilador de Java no le permitirá especificar una versión del código fuente sobre el objetivo. Entonces, si establece el nivel de cumplimiento del código fuente en 1.8 (es decir, Java 8), y el nivel de cumplimiento objetivo es 1.7 (Java 7), entonces el compilador no lo permitirá.
Los archivos JAR de versiones múltiples lo ayudaránMás recientemente, ha aparecido otra gran oportunidad para usar las últimas características de Java, al tiempo que permite que las aplicaciones funcionen con versiones anteriores de Java, donde dichas aplicaciones no eran compatibles. En Java 9, esta característica se proporciona tanto para nuevas API como para nuevas construcciones de lenguaje Java: estamos hablando de
archivos JAR de múltiples versiones .
Los archivos JAR de versiones múltiples casi no difieren de los buenos archivos JAR antiguos, pero con una advertencia importante: ha aparecido un nuevo "nicho" en los nuevos archivos JAR, donde puede escribir clases que utilizan las últimas características de Java 9. Si trabaja con Java 9, entonces El JVM encontrará este "nicho", usará las clases de él e ignorará las clases del mismo nombre de la parte principal del archivo JAR.
Sin embargo, cuando se trabaja con Java 8 o inferior, la JVM no es consciente de la existencia de este "nicho". Ella lo ignora y usa clases de la parte principal del archivo JAR. Con el lanzamiento de Java 10, aparecerá un nuevo "nicho" similar para las clases que usan las características más relevantes de Java 10, etc.
En
JEP 238 , una propuesta para el desarrollo de Java, que describe archivos JAR voraces, se proporciona un ejemplo simple. Digamos que tenemos un archivo JAR con cuatro clases ejecutándose en Java 8 o inferior:
JAR root - A.class - B.class - C.class - D.class
Ahora imagine que después del lanzamiento de Java 9, reescribimos las clases A y B para que puedan usar las nuevas características específicas de Java 9. Luego sale Java 10, y reescribimos la clase A para que pueda usar las nuevas características de Java 10. , la aplicación aún debería funcionar bien con Java 8. El nuevo archivo JAR de varias versiones tiene este aspecto:
JAR root - A.class - B.class - C.class - D.class - META-INF Versions - 9 - A.class - B.class - 10 - A.class
El archivo JAR no solo ha adquirido una nueva estructura; ahora en su manifiesto se indica que este archivo tiene varias versiones.
Cuando ejecuta este archivo JAR en Java 8 JVM, ignora la sección
\META-INF\Versions
porque ni siquiera lo sospecha ni lo busca. Solo se utilizan las clases originales A, B, C y D.
Cuando se ejecuta bajo Java 9, se usan las clases ubicadas en
\META-INF\Versions\9
, además, se usan en lugar de las clases originales A y B, pero las clases en
\META-INF\Versions\10
ignoran.
Cuando se ejecuta en Java 10,
\META-INF\Versions
ambas ramas
\META-INF\Versions
; en particular, la versión A de Java 10, la versión B de Java 9 y las versiones predeterminadas C y D.
Entonces, si en su aplicación necesita la nueva API ProcessBuilder de Java 9, pero necesita asegurarse de que la aplicación continúe funcionando en Java 8, simplemente escriba las nuevas versiones de sus clases usando ProcessBuilder en la sección
\META-INF\Versions\9
del archivo JAR y deje las clases antiguas en la parte principal del archivo, que se usa de manera predeterminada. Esta es la forma más fácil de usar las nuevas características de Java 9 sin sacrificar la compatibilidad con versiones anteriores.
Java 9 JDK tiene una versión de la herramienta jar.exe que admite la creación de archivos JAR de varias versiones. Otras herramientas que no son JDK también brindan este soporte.
Java 9: módulos, módulos en todas partesEl sistema de módulos Java 9 (también conocido como Project Jigsaw) es sin duda el mayor cambio en Java 9. Uno de los objetivos de la modularización es fortalecer el mecanismo de encapsulación de Java para que el desarrollador pueda especificar qué API se proporcionan a otros componentes y pueda contar, que la JVM impondrá la encapsulación. La encapsulación es más poderosa con la modularización que con modificadores de acceso
public/protected/private
para clases o miembros de clase.
El segundo objetivo de la modularización es indicar qué módulos necesitan otros módulos para funcionar, y antes de iniciar la aplicación, asegúrese de que todos los módulos necesarios estén en su lugar. En este sentido, los módulos son más fuertes que el mecanismo tradicional de classpath, ya que las rutas de classpath no se verifican por adelantado, y los errores son posibles debido a la falta de clases necesarias. Por lo tanto, ya se puede detectar un classpath incorrecto cuando la aplicación tiene tiempo para trabajar el tiempo suficiente o después de que se haya lanzado muchas veces.
Todo el sistema de módulos es grande y complejo, y una discusión detallada sobre él está más allá del alcance de este artículo (Aquí hay una buena
explicación detallada ). Aquí prestaré atención a aquellos aspectos de la modularización que ayudan al desarrollador con el soporte de aplicaciones heredadas.
La modularización es algo bueno, y el desarrollador debe intentar dividir el nuevo código en módulos siempre que sea posible, incluso si el resto de la aplicación está (aún no) modularizada. Afortunadamente, esto es fácil de hacer gracias a la especificación para trabajar con módulos.
Primero, el archivo JAR se modulariza (y se convierte en un módulo) con la apariencia del archivo module-info.class (compilado de module-info.java) en la raíz del archivo JAR.
module-info.java
contiene metadatos, en particular, el nombre del módulo cuyos paquetes se exportan (es decir, se vuelven visibles desde el exterior), qué módulos requiere este módulo y alguna otra información.
La información en
module-info.class
solo es visible cuando la JVM la está buscando, es decir, el sistema trata los archivos JAR modularizados de la manera normal si funciona con versiones anteriores de Java (se supone que el código se compiló para funcionar con una versión anterior de Java Hablando estrictamente, se necesita un poco de química, y todavía es Java 9 que se especifica como la versión de destino de module-info.class, pero esto es real).
Por lo tanto, debe poder ejecutar archivos JAR modularizados con Java 8 y versiones posteriores, siempre que en otros aspectos también sean compatibles con versiones anteriores de Java. También tenga en cuenta que
module-info.class
pueden, con reservas,
ubicarse en áreas versionadas de archivos JAR de versiones múltiples .
En Java 9, hay una ruta de clase y una ruta de módulo. y una ruta de módulo. Classpath funciona como de costumbre. Si coloca un archivo JAR modularizado en un classpath, se desperdicia como cualquier otro archivo JAR. Es decir, si modularizó el archivo JAR y su aplicación aún no está lista para tratarlo como un módulo, puede colocarlo en el classpath, funcionará como siempre. Su código heredado debería manejarlo con bastante éxito.
También tenga en cuenta que la colección de todos los archivos JAR en el classpath se considera parte de un único módulo sin nombre. Tal módulo se considera el más común, sin embargo, exporta toda la información a otros módulos y puede referirse a cualquier otro módulo. Por lo tanto, si aún no tiene una aplicación Java modularizada, pero hay algunas bibliotecas antiguas que tampoco están modularizadas (y probablemente nunca lo harán), simplemente puede poner todas estas bibliotecas en el classpath, y todo el sistema funcionará bien.
Java 9 tiene una ruta de módulo que funciona junto con el classpath. Cuando se utilizan módulos de esta ruta, la JVM puede verificar (tanto en tiempo de compilación como en tiempo de ejecución) si todos los módulos necesarios están en su lugar e informar un error si faltan módulos. Todos los archivos JAR en el classpath, como miembros de un módulo sin nombre, son accesibles para los módulos enumerados en la ruta modular, y viceversa.
No es difícil transferir el archivo JAR de la ruta de clase a la ruta del módulo, y aprovechar al máximo la modularización. Primero, puede agregar el archivo
module-info.class
archivo JAR y luego colocar el archivo JAR modularizado en la ruta de los módulos. Tal módulo recién creado aún podrá acceder a todos los archivos JAR restantes en el classpath JAR, ya que ingresan al módulo sin nombre y permanecen en acceso.
También es posible que no desee modular el archivo JAR, o que el archivo JAR no le pertenezca a usted, sino a otra persona, por lo que no puede modularlo usted mismo. En este caso, el archivo JAR todavía se puede poner en la ruta del módulo, se convertirá en un módulo automático.
Un módulo automático se considera un módulo, incluso si no tiene un
module-info.class
. Este módulo tiene el mismo nombre que el archivo JAR que contiene, y otros módulos pueden solicitarlo explícitamente. Exporta automáticamente todas sus API y lecturas disponibles públicamente (es decir, requiere) todos los demás módulos con nombre, así como los módulos sin nombre.
Por lo tanto, un archivo JAR no modularizado de un classpath puede convertirse en un módulo sin hacer nada en absoluto. Los archivos JAR heredados se convierten automáticamente en módulos, simplemente carecen de información que determine si todos los módulos necesarios están en su lugar, o determinar qué falta.
No todos los archivos JAR no modulados se pueden mover a la ruta del módulo y convertirse en un módulo automático. Hay una regla: un
paquete puede ser parte de un solo módulo con nombre . Es decir, si un paquete está ubicado en más de un archivo JAR, entonces solo un archivo JAR con este paquete en la composición puede convertirse en un módulo automático. El resto puede permanecer en el classpath y unirse al módulo sin nombre.
A primera vista, el mecanismo descrito aquí parece complicado, pero en la práctica es muy simple. De hecho, en este caso es solo que puede dejar los archivos JAR antiguos en la ruta de clase o moverlos a la ruta del módulo. Puedes dividirlos en módulos o no. Y cuando sus archivos JAR antiguos están modularizados, puede dejarlos en la ruta de clase o moverlos a la ruta del módulo.
En la mayoría de los casos, todo debería simplemente funcionar, como antes. Sus archivos JAR heredados deben arraigarse en el nuevo sistema modular. Cuanto más module el código, más información de dependencia necesitará verificar, y los módulos y API faltantes se detectarán en etapas mucho más tempranas de desarrollo y probablemente le ahorrará mucho trabajo.
Java 9 "hágalo usted mismo": JDK modular y JlinkUno de los problemas con las aplicaciones Java heredadas es que el usuario final puede no funcionar con un entorno Java adecuado. Una forma de garantizar el estado de una aplicación Java es proporcionar un tiempo de ejecución con la aplicación. Java le permite crear JRE privados (redistribuibles) que se pueden distribuir dentro de la aplicación.
Aquí se explica cómo crear un JRE privado. Como regla general, se toma la jerarquía de archivos JRE, que se instala con el JDK, se guardan los archivos necesarios y se guardan los archivos opcionales con la funcionalidad que pueda necesitar en su aplicación.
El proceso es un poco problemático: debe mantener una jerarquía de archivos de instalación, teniendo cuidado, para no perder un solo archivo, ni un solo directorio. Esto en sí mismo no va a doler, sin embargo, todavía desea deshacerse de todo lo superfluo, ya que estos archivos ocupan espacio. Sí, es fácil rendirse y cometer tal error.
Entonces, ¿por qué no delegar este trabajo al JDK?
En Java 9, puede crear un entorno autónomo que se agrega a la aplicación, y en este entorno habrá todo lo necesario para que la aplicación funcione. Ya no tiene que preocuparse de que la computadora del usuario tenga el entorno incorrecto para ejecutar Java, no tiene que preocuparse de que usted mismo haya creado incorrectamente un JRE privado.
Un recurso clave para crear tales
imágenes ejecutables autónomas es un sistema modular. Ahora puede modularizar no solo su propio código, sino también el propio Java 9 JDK. Ahora la biblioteca de clases Java es una colección de módulos, y las herramientas JDK también consisten en módulos. El sistema de módulos requiere que especifique los módulos de clase base que se necesitan en su código, y que especifique los elementos JDK necesarios.
Para
reunirlo todo, Java 9 proporciona una nueva herramienta especial llamada
jlink . Al iniciar jlink, obtienes una jerarquía de archivos, exactamente los que necesitas para ejecutar tu aplicación, ni más ni menos. Tal conjunto será mucho más pequeño que el JRE estándar, además, será específico de la plataforma (es decir, se seleccionará para un sistema operativo y máquina específicos). Por lo tanto, si desea crear tales imágenes ejecutables para otras plataformas, deberá ejecutar jlink en el contexto de la instalación en cada plataforma específica para la que necesita dicha imagen.
También tenga en cuenta: si ejecuta jlink con una aplicación en la que nada está modularizado, la herramienta simplemente no tiene la información necesaria para exprimir el JRE, por lo que a jlink no le quedará más que empacar todo el JRE. Incluso en este caso, será un poco más conveniente para usted: jlink empaquetará el JRE por usted, por lo que no puede preocuparse por cómo copiar correctamente la jerarquía de archivos.
Con jlink, resulta más fácil empacar la aplicación y todo lo que necesita para ejecutarla, y no tiene que preocuparse de hacer algo mal. La herramienta solo empaquetará la parte del tiempo de ejecución que se requiere para que la aplicación funcione. Es decir, se garantiza que una aplicación Java heredada recibirá un entorno en el que estará operativa.
El encuentro de lo viejo y lo nuevo, Java- , , . Java 9, , API , ( ) , , Java.
Java 9: -, , , Java.
JAR- Java 9 JAR-, Java . , Java 9, Java 8 – .
Java, , JAR- , . , , « » .
JDK jlink, , . , Java – .
Java, Java 9 , – , , Java.