Компиляция Java программ и разрешение зависимостей в runtime

от автора

Как можно совместить некоторые достоинства динамических языков со строгой типизацией в обычном Java коде?

Самое интересное на хабре, обычно происходит в комментариях к статье. Вот и в этот раз в комментариях к «Модуляризация в JavaSE без OSGI и Jigsaw» началось обсуждение что работа через reflection в java перечеркивает многие плюсы библиотеки mvn-classloader. В Groovy же с этой библиотекой работать просто и удобно:

def hawtIoConsole = MavenClassLoader.usingCentralRepo().forMavenCoordinates('io.hawt:hawtio-app:2.0.0').loadClass('io.hawt.app.App') Thread.currentThread().setContextClassLoader(hawtIoConsole.getClassLoader()) hawtIoConsole.main('--port','10090')

Попробуем это исправить с помощью java-as-script.jar, исходный код проекта доступен на github.

Динамическая компиляция

Стандарт JSR 199 — java Compiler API, существует довольно давно. Интерфейсы API присутствуют в java пакетах javax.tools.*. Но чтобы компилировать java код из памяти в память и потом запустить его, надо изрядно написать кода и побить в бубен. Реализация компилятора не идет в составе JRE и tools.jar нет в maven репозитариях.

Хотелось бы что-нибудь готовое, не велосипедить каждый раз и коллега подсказал проект Janino. Сам janino содержит свой компилятор подмножества java и хорошо подходит лишь для вычисления выраженией. Есть org.codehaus.janino:commons-compiler-jdk который использует JSR 199, но вот только сильно зависит от oracle/openjdk tools.jar. Доработав этот проект, java-as-script включает в себя eclipse java compiler и доработанный под него commons-compiler-jdk. Он самодостаточен и позволяет компилировать и загружать код java 8 даже в JRE.

Динамическое разрешение зависимостей

В Groovy есть удобный механизм Grape, который позволяет добавить любые зависимости из maven репозитариев в ваш скрипт.
Если объеденить компилятор java и mvn-classloader, то в Java программу можно добавить зависимости с помощью простых комментариев исходном коде.

//dependency:mvn:/org.slf4j:slf4j-simple:1.6.6 //dependency:mvn:/org.apache.camel:camel-core:2.18.0 

Приведу рабочий пример. Для запуска кода сигнализации на RaspberryPi достаточно лишь одного java файла.

java -Dlogin=...YOUR_EMAIL...@mail.ru -Dpassword=*******  -jar java-as-script-1.1.jar https://raw.githubusercontent.com/igor-suhorukov/alarm-system/master/src/main/java/com/github/igorsuhorukov/alarmsys/AlarmSystem.java 
package com.github.igorsuhorukov.alarmsys;  //dependency:mvn:/com.github.igor-suhorukov:mvn-classloader:1.8 //dependency:mvn:/org.apache.camel:camel-core:2.18.0 //dependency:mvn:/org.apache.camel:camel-mail:2.18.0 //dependency:mvn:/io.rhiot:camel-webcam:0.1.4 //dependency:mvn:/io.rhiot:camel-pi4j:0.1.4 //dependency:mvn:/org.slf4j:slf4j-simple:1.6.6 import com.github.igorsuhorukov.smreed.dropship.MavenClassLoader; import org.apache.camel.Endpoint; import org.apache.camel.Exchange; import org.apache.camel.Processor; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.impl.DefaultAttachment; import org.apache.camel.impl.DefaultCamelContext; import org.apache.camel.management.event.CamelContextStartedEvent; import org.apache.camel.management.event.CamelContextStoppedEvent; import org.apache.camel.support.EventNotifierSupport;  import javax.mail.util.ByteArrayDataSource; import java.lang.reflect.Method; import java.util.Date; import java.util.EventObject;  class AlarmSystem {     public static void main(String[] args) throws Exception{          String login = System.getProperty("login");         String password = System.getProperty("password");          DefaultCamelContext camelContext = new DefaultCamelContext();         camelContext.setName("Alarm system");         Endpoint mailEndpoint = camelContext.getEndpoint(String.format("smtps://smtp.mail.ru:465?username=%s&password=%s&contentType=text/html&debugMode=true", login, password));         camelContext.addRoutes(new RouteBuilder() {             @Override             public void configure() throws Exception {                 from("pi4j-gpio://3?mode=DIGITAL_INPUT&pullResistance=PULL_DOWN").routeId("GPIO read")                         .choice()                         .when(header("CamelPi4jPinState").isEqualTo("LOW"))                         .to("controlbus:route?routeId=RaspberryPI Alarm&action=resume")                         .otherwise()                         .to("controlbus:route?routeId=RaspberryPI Alarm&action=suspend");                  from("timer://capture_image?delay=200&period=5000")                         .routeId("RaspberryPI Alarm")                         .to("webcam:spycam?resolution=HD720")                         .setHeader("to").constant(login)                         .setHeader("from").constant(login)                         .setHeader("subject").constant("alarm image")                         .process(new Processor() {                             @Override                             public void process(Exchange it) throws Exception {                                 DefaultAttachment attachment = new DefaultAttachment(new ByteArrayDataSource(it.getIn().getBody(byte[].class), "image/jpeg"));                                 it.getIn().setBody(String.format("<html><head></head><body><img src=\"cid:alarm-image.jpeg\" /> %s</body></html>", new Date()));                                 attachment.addHeader("Content-ID", "<alarm-image.jpeg>");                                 it.getIn().addAttachmentObject("alarm-image.jpeg", attachment);                                 //set CL to avoid javax.activation.UnsupportedDataTypeException: no object DCH for MIME type multipart/mixed                                 Thread.currentThread().setContextClassLoader( getClass().getClassLoader() );                              }                         }).to(mailEndpoint);             }         });          registerLifecycleActions(camelContext, mailEndpoint, login);         camelContext.start();     } } 

java-as-script загружает исходный код программы по https протоколу, разрешает зависимости скрипта на java, указанные как комментарии //dependency:mvn:/, компилирует исходный код с этими зависимостями, загружает класс и запускает его main метод. При этом можно подключиться с помощью remote debugger и отлаживать скрипт как обычную программу на java.
ссылка на оригинал статьи https://habrahabr.ru/post/318544/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *