📜 ⬆️ ⬇️

Compiling Java programs and resolving dependencies at runtime

How can I combine some of the advantages of dynamic languages ​​with strong typing in plain Java code?

The most interesting thing on Habré, usually occurs in the comments to the article. So this time in the comments to “Modularization in JavaSE without OSGI and Jigsaw” a discussion began that work through reflection in java crosses out many advantages of the mvn-classloader library . In Groovy, it’s easy and convenient to work with this library:

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') 

Let's try to fix this with java-as-script.jar , the project source code is available on github .

Dynamic compilation


The JSR 199 standard is the java Compiler API, which has been around for a long time. APIs are present in the java packages javax.tools. * . But in order to compile java code from memory to memory and then run it, you have to pretty write code and beat the tambourine . The compiler implementation does not come as part of the JRE and tools.jar is not in the maven repositories.
')
I would like something ready, not to ride every time, and my colleague suggested the Janino project. Janino itself contains its own java subset compiler and is well suited only for evaluation by an expression. There is org.codehaus.janino: commons-compiler-jdk which uses JSR 199, but it depends only heavily on oracle / openjdk tools.jar. Having finished this project, java-as-script includes eclipse java compiler and modified for it commons-compiler-jdk. It is self-sufficient and allows you to compile and load java 8 code even in the JRE.

Dynamic dependency resolution


Groovy has a convenient Grape mechanism that allows you to add any dependencies from the maven repositories to your script.

If you combine the java compiler and mvn-classloader , then you can add dependencies to your Java program using comments in the source code.

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

I will give a working example. To run the alarm code on the RaspberryPi , just one java file is enough. You can run the example from the command line:

 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(); } } 

To get rid of reflection in code, you need to specify the API to which the cast classes from the mvn-classloader loader are in the script dependencies and specify the loader that loaded the script class as the parent loader.

Implementing the launch of the script in an existing JVM program is quite simple:
 org.github.suhorukov.java.as.script.ScriptRunner#runScript(String scriptText, String[] scriptArgs) 

If you need a class loader, then just call
 org.github.suhorukov.java.as.script.JavaCompiler#compileScript(java.lang.String scriptText) 
and after that work with classes from a script in your program.

For quick debugging and generating pom.xml and java sources, you can run the program with the -DgenerateMavenProjectAndExit = true parameter
A pom file for maven and all necessary source directories will be created in the current directory. This allows you to develop a script in your familiar IDE with all its capabilities to work with java code and debug it.

java-as-script loads the source code of the program using any of the hundreds of java.net.URL protocols , resolves the script dependencies to java, specified as comments // dependency: mvn: /, compiles the source code with these dependencies, loads the class and runs his main method. At the same time, you can connect using remote debugger and debug the script as a regular java program.

Source: https://habr.com/ru/post/318544/


All Articles