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?
- Loading classes from maven in an isolated class loader and building a simple modular system based on classloader;
- MavenServiceLoader does the same as java.util.ServiceLoader, but also loads services from maven modules;
- Download binary files and resources from repositories;
- Creating a hierarchy of class loaders, if you really want to get confused in the tree loaders and the order of initialization.
- Download and run the application from the maven repository;
- UniversalURLStreamHandlerFactory to support hundreds of new java.net.URL protocols;
- “Bleeding” the Grape mechanism from Groovy to work with the maven's native provider Aether, and not the artisanal Ivy.
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):
')
- mavenSettings.offline = true - puts Aether in offline mode and tries to find modules only in the local maven cache of the repository.
- mavenSettings - the path to the settings.xml file
- mavenSettingsSecurity - path to the settings-security.xml file
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();
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
- forMavenCoordinates (java.lang.String gav) is in the simplest case an isolated loader that knows nothing about the classes already loaded by the application. It will reload them from the same maven dependencies and allows you to achieve the maximum degree of isolation of the load.
- forMavenCoordinates (java.lang.String gav, java.lang.ClassLoader parent) - calling this method allows you to specify the parent class loader. Module isolation is less, but it is possible to separate already loaded classes, for example loggers, own API, etc. Often the parent loader is the same MavenClassLoader or, for example, Thread.currentThread (). GetContextClassLoader ()
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:
- mvn: - allows you to read an artifact from the maven repository groupId: artifactId [: extension [: classifier]]: version [? custom_repository_URL]
- vfs: - 16 protocols from commons-vfs
- camel: trying to support 189 protocols from apache camel components
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!
