⬆️ ⬇️

Modularization in JavaSE without OSGI and Jigsaw

mvn-classloader - class and resource loader from maven compatible repositories. This project allows you to add a feature-constrained and non-complex system of modules in a JavaSE application where all the power and complexity of OSGI is not needed.





About what else allows you to do mvn-classloader besides modules you will find out in the article.



The idea to write about quite obvious things arose when I read one of the most popular articles on dzone ModRun: Modularity for Java - Without Jigsaw and could not resist commenting on it. It’s sad when people don’t know how to search for solutions, create another bike that “doesn’t fit” in functionality and doesn’t look at what has long been implemented before them and is in the github and maven central repository.



What can mvn-classloader be useful for?





A few words about working in a bloody enterprise with an isolated network behind proxy repositories, where, again, the maven standard is de facto. This project has settings that are read by default from maven from ~ / .m2 / settings.xml and ~ / .m2 / settings-security.xml. And if maven is configured correctly and everything works, no additional configuration is required.

You can override these settings using the system property (passed as parameters with "-D" at the start of the virtual machine):

')



The project is available in the central maven repository com.github.igor-suhorukov: mvn-classloader: 1.8 and on github .



In order, we consider the available functionality and usage examples.



Loading classes from maven



The entry point for creating the bootloader is the com.github.igorsuhorukov.smreed.dropship.MavenClassLoader class.



The easiest way to get started is with the forMavenCoordinates (...) methods that load classes from the central maven repository or its mirror specified in the mirror-of section of the settings.xml file.



When you want to load classes from another repository by reference, you need to call the using method (link_to_your_maven_repository) .forMavenCoordinates (...).



The coordinates of the artifact in maven for the class loader are groupId: artifactId [: extension [: classifier]]: version



Let's start with the example of loading the class io.hawt.app.App from the io.hawt artifact: hawtio-app: 2.0.0. Let's create an isolated class loader, where we will have our own version of jetty and logger web server classes. This is needed to run the administrative console in the same jvm process as your application.



When working with classes in java, you will have to use reflection:



Class<?> hawtIoConsole = MavenClassLoader.usingCentralRepo().forMavenCoordinates("io.hawt:hawtio-app:2.0.0").loadClass("io.hawt.app.App"); Thread.currentThread().setContextClassLoader(hawtIoConsole.getClassLoader()); Method main = hawtIoConsole.getMethod("main", String[].class); main.invoke(null, (Object) new String[]{"--port","10090"}); 


In Groovy, the same code is written more concisely:



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


If the application modules need to interact with each other, then MavenServiceLoader as a primitive alternative to OSGI services will not be able to do without a few of its loaders, their hierarchy. A brief overview of using multiple classloaders can be found in the section “Creating a Class Loader Hierarchy”.



MavenServiceLoader



This service loader loads classes from maven modules using java.util.ServiceLoader. But in order for an artifact to provide a service, it is necessary to pack a service descriptor in META-INF / services artifact. You can read in detail about how ServiceLoader works in javadoc or peep as implemented using the example of service in the camel-url-handler and vfs-url-handler projects.



As an example, from the fact that you pass in the variable protocol - vfs or camel - the implementation of the URLStreamHandlerFactory service is loaded from the corresponding artifact vfs-url-handler or camel-url-handler.



 String artifact = System.getProperty(protocol+"MvnUrlHandler", String.format("com.github.igor-suhorukov:%s-url-handler:LATEST",protocol)); Collection<URLStreamHandlerFactory> urlStreamHandlerFactories = MavenServiceLoader.loadServices(artifact, URLStreamHandlerFactory.class); 


Download Binary Files and Resources from Repositories



With the project, you can load any binary artifact from the maven repository, simply by specifying additionally in the coordinates of the artifact classifier and type. As an example I will give you the download and launch of a web driver for chrome. In a webdriver for a real browser, besides the client-side API, there is also a part that makes the browser a puppet application in a separate process. Often it is downloaded manually and indicate the path in the application. But why, if you can do it automatically !?



Depending on what operating system, you need to pass win32, linux64 or mac64 as the os value. At the same time, win32 works on 64-bit systems:



 String chromedriver = MavenClassLoader.usingCentralRepo().resolveArtifact("com.github.igor-suhorukov:chromedriver:bin:" + os + ":2.24").getFile(); //   linux     chmod(chromedriver); System.setProperty("webdriver.chrome.driver", chromedriver); 


Create class loader hierarchy



Combining the creation of class loaders from maven using the method calls to the com.github.igorsuhorukov.smreed.dropship.MavenClassLoader class method





And creating from the set of these loaders a new aggregated “root” loader. To do this, you can use the class loader from the ClassLoader array:



 com.github.igorsuhorukov.codehaus.spice.classman.runtime.JoinClassLoader(ClassLoader[] classLoaders, ClassLoader parent) 


If you go too far using this approach, after a while your application will surpass the OSGI / JEE server by the complexity of loading classes. Whether to get involved in this and confused in the hierarchy of class loaders is up to you, as a specialist. When such a tool is needed to solve a problem, it is available to you. In difficult cases and a large number of dependent modules with a complex order of initialization OSGI will be the best solution, as well as when a dynamic reload of classes is required.



Download and run the application from the maven repository



mvn-classloader can load and run classes from maven artifacts. This is easy to do — all you need to do is pass the groupId parameters to the command line: artifactId [: version] classname. I will give examples.



We start http proxy with one command:



 java -Dos.detected.classifier=windows-x86_64 -jar mvn-classloader-1.8.jar org.littleshoot:littleproxy:1.1.0 org.littleshoot.proxy.Launcher 


Or start your git gitblit server:



 java -jar mvn-classloader-1.8.jar org.eclipse.jetty:jetty-runner:9.4.0.v20161208 org.eclipse.jetty.runner.Runner http://gitblit.imtqy.com/gitblit-maven/com/gitblit/gitblit/1.8.0/gitblit-1.8.0.war 


UniversalURLStreamHandlerFactory



UniversalURLStreamHandlerFactory is a URL handler for loadable protocol implementations and is available in mvn-classloader. It loads them from the maven repository or uses the local repository cache.



Now for the URL protocols are supported:





It is easy to register a universal handler in a java program — just add initialization before using such exotic addresses in the URL:



 java.net.URL.setURLStreamHandlerFactory(new com.github.igorsuhorukov.url.handler.UniversalURLStreamHandlerFactory()); 


But it is necessary to remember about the limitation of the standard java library, that you can call java.net.URL.setURLStreamHandlerFactory only once for the duration of the program.



Examples of its use can be found in the java.net.URL article or the old horse will not spoil the furrow



"Bleeding" the Grape mechanism from Groovy



The mvn-classloader also contains the AetherGrapeEngine from the spring boot project and the minimum required dependencies for its operation.



Grape is a dependency resolution mechanism built into Groovy but not the best implementation for Ivy out of the box. About this told in the publication Street magic in scripts or what connects Groovy, Ivy and Maven? . And in almost every article of mine in the groovy hub, I give examples. The latest example is about alarm for a fridge with video registration in the form of a single groovy file:



java -Dlogin = ... YOUR_EMAIL ... @ mail.ru -Dpassword = ******* -jar groovy-grape-aether-2.4.5.4.jar AlarmSystem.groovy



Conclusion



A couple of years ago I found, combined and refined several open source technologies in one project. All the dependencies necessary for the work are packed in one mvn-classloader.jar file. Like every technology, mvn-classloader has its own area of ​​applicability - there is no dynamism and class reloading, as in OSGI it does not have work with classes at a lower level. But for the listed tasks, the mvn-classloader may be the best choice, including for projects with microservice architecture without JEE. At the main work, he actively used it to modify and test a complex distributed system as part of aspectj-scripting .



If you need any of the functionality listed in this article, simply add the dependency to the maven assembly:



 <dependency> <groupId>com.github.igor-suhorukov</groupId> <artifactId>mvn-classloader</artifactId> <version>1.8</version> </dependency> 


or gradle



 compile group: 'com.github.igor-suhorukov', name: 'mvn-classloader', version: '1.8' 


and start using in your projects!



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



All Articles