
Hola Habr! Mi nombre es Dima, soy desarrollador del equipo de "Arquitectura" en hh.ru. Entre otras cosas, facilito las cosas para los colegas. Ejecutar código en producción es una tarea típica. Por lo tanto, cuando escuché que hay problemas con esto, decidí solucionarlos.
No siempre los cambios de datos se pueden realizar de manera simple ACTUALIZAR / INSERTAR: a veces es necesario usar validación, buses de eventos y más. En tales casos, la solución más óptima es ejecutar código arbitrario directamente en la aplicación. Tenemos Java, por lo que cuando
apareció JEP-222 , inmediatamente pensé que JShell podría hacer que las secuencias de comandos sean convenientes nuevamente. No ocurrió un milagro y, por lo tanto, debajo del corte, encontrará una comparación no muy profunda de los intérpretes de Java más famosos (y "casi Java") para jvm con ejemplos. Invito a todos los interesados bajo cat.
Usamos
BeanShell para ejecutar scripts, y para 2019 es terrible: la última versión de
2016 , la falta de soporte para lambdas e incluso genéricos; todo esto nos obliga a escribir código que nadie ha escrito desde Java
1.4 .
Criterios
Antes de comenzar la comparación, formulamos los requisitos para el motor de secuencias de comandos incorporado. Después de rascarme la cabeza, hice la siguiente lista:
- soporte para la sintaxis java actual;
- la capacidad de pasar un contexto externo al intérprete;
- capacidad de interrumpir la ejecución;
- capacidad de redirigir E / S;
- retroalimentación informativa.
Cuanto más se parezca el lenguaje en el que escribimos los guiones al que estamos desarrollando, menos errores, recuerdan las manos. Pero cuando cometemos errores que se identificaron en la etapa de compilación, deberían permitir que el desarrollador los corrija: estas son indicaciones de los nombres de las variables, líneas, stacktracks faltantes, etc.
A continuación, los scripts deberían funcionar en un contexto específico, con acceso al contexto Spring, al registrador que servirá los scripts. Sin esa oportunidad de transferir el contexto, su recepción se convierte en una búsqueda.
Sin embargo, si el error se filtró en tiempo de ejecución, reiniciar toda la instancia para detener la ejecución es una mala idea, por lo que solo debe poder interrumpir la ejecución del script en cualquier momento.
Y el último: cualquier mensaje a la salida del sistema durante el trabajo del script solo tiene sentido en el contexto de este script. En los registros del sistema, tal conclusión es de poca utilidad. Por lo tanto, quiero poder redirigir estos mensajes en respuesta.
Así que vamos
- Soporte para la sintaxis actual de Java: sí
- la capacidad de transmitir contexto: no
- capacidad de interrumpir la ejecución: sí
- la capacidad de redirigir E / S: no
- retroalimentación informativa - sí
Debo decir de
inmediato que
JEP-222 no tiene como objetivo crear un intérprete incrustado; su objetivo es
REPL , es decir, la capacidad de crear prototipos de código rápidamente. Esto está plagado de una serie de consecuencias.
Primero, life no preparó el compilador de Java por el hecho de que puede declarar un método fuera de la clase y utilizar variables que aún no se han declarado en el cuerpo del método. Por lo tanto, la ejecución en sí está oculta detrás de una impresionante capa de abstracción.
En segundo lugar, REPL bien puede ejecutarse no localmente, sino en algún lugar de una máquina remota, por lo que la API está hecha con tales características en mente. Creo que esta es la razón principal por la que la API no tiene la capacidad de pasar un contexto externo al intérprete y redirigir las E / S.
Además, hay diferentes modos de inicio:
remoto , cuando el shell se conecta a la máquina a través de JDI, y
local . Como no existe la posibilidad de transmitir el contexto mediante programación, pero todavía queremos hacerlo, la esperanza permanece solo en el modo local y en que podamos usar la
generación de códigoPero, desafortunadamente, el modo local claramente no fue concebido como el principal:
este tipo de script llama al
punto muerto en el compilador. A pesar de que el mismo código en modo JDI funciona sin problemas.
Por lo tanto, tuve que abandonar el uso de JShell, aunque en general la API es extraña, pero comprensible: le damos la secuencia de comandos a la entrada, obtenemos la secuencia de eventos, para cada uno de ellos podemos verificar el estado, obtener errores e información de facturación. Los errores nos permiten identificar la expresión en la que se permitió:
ACTUALIZACIÓN: Se recopiló ScriptEngine de JShell . Pero para esto tuve que escribir mi
modo de inicio- soporte para la sintaxis java actual - no
- la capacidad de transmitir contexto: sí
- capacidad de interrumpir la ejecución: sí
- la capacidad de redirigir E / S: sí (pero requiere el uso de métodos especiales)
- retroalimentación informativa - sí
El fracaso nos hizo prestar atención a lo que estamos usando ahora. Después de un largo descanso, el proyecto pareció cobrar vida y, a juzgar por la
hoja de ruta , avanza con confianza hacia una versión que resolverá todos nuestros problemas: muchas funciones deberían funcionar ahora.
En el momento de escribir este artículo, beanshell admitía genéricos, pero las lambdas aún no funcionan. Quizás por la liberación de la situación la situación cambiará.
Pero en términos de integración, el motor es bastante amigable: el soporte para javax.scripting estándar, los errores de tiempo de ejecución son lo suficientemente detallados:

Sin embargo, usar transmisiones sin lambda es un infierno que arde en el infierno. Incluso podría ser más fácil escribir en otro idioma. Así que decidí echar un vistazo más de cerca al segmento "cerca de Java". Y el primer candidato para el papel de intérprete de guiones aquí, por supuesto.
- soporte para la sintaxis java actual - no
- la capacidad de transmitir contexto: no
- capacidad de interrumpir la ejecución: sí
- la capacidad de redirigir E / S: no
- retroalimentación informativa - sí
El código Java, con mucha suerte, será un código kotlin válido. Pero no logré lanzar nada, al menos de alguna manera adecuado, en Java en Kotlin, pero sin embargo, intentémoslo.
Kotlin ya ha
anunciado el soporte javax.scripting por un par de años.
El primer problema con el que tenemos que lidiar es el infierno de la dependencia.
Kotlin-compiler incluye las clases org.jdom que comenzaron a luchar con org.jdom en la aplicación y la envuelven ... Entonces, tenemos kotlin-compiler-embeddable, donde todas estas clases se colocan en paquetes personalizados.
Sin embargo, después de la configuración, resulta que la transferencia del contexto externo no funciona. Y ahora este es un problema grave, hasta que su solución no tenga sentido cavar más profundo. Si sabe cuál es el problema y cómo solucionarlo, escriba en los comentarios.
Los errores también son bastante detallados:

- Soporte para la sintaxis actual de Java: no, pero hay análogos
- la capacidad de transmitir contexto: sí
- capacidad de interrumpir la ejecución: sí
- la capacidad de redirigir E / S: sí
- retroalimentación informativa - sí
Grooves, además de admitir javax.scripting, proporciona su propia API más avanzada para la integración de intérpretes. Por ejemplo, es posible pasar una transformación AST, que le permite agregar una
interrupción condicional después de cada expresión . La cosa es tan poderosa que ya da
miedo .
Además, el código Java (y especialmente el beanhell) puede ser un código de ranura bastante válido.
La operación de integración y prueba fue exitosa, con la excepción de la inicialización de la hoja y la sintaxis lambda (tienen que estar entre llaves), los scripts de binshell existentes funcionaron sin problemas. Los errores son más que detallados:

Quizás hoy es el único intérprete que, por un lado, le permite escribir código para la muestra en 2019 y, por otro lado, cumple con todos los requisitos que es razonable presentarle al intérprete.
¿Qué conclusiones podemos sacar?
Primero y principal: REPL no es un motor de secuencias de comandos. Puede parecer un paso desde la línea de comandos hasta la integración en la aplicación, pero después de un examen más detallado resulta que estas herramientas tienen diferentes tareas, a veces contradiciéndose entre sí.
La segunda: hoy no existe una sola herramienta para ejecutar scripts en Java, por lo que si necesita dicha herramienta, prepárese para aprender una nueva sintaxis.