Different packaging with Spring Maven Plugin: Spring + Spark application - maven

I have a project which consists of three parts:
Spring Boot application
Spark Application
"Library" used by both of the above (having this library as separate JAR or similar causes quiet a bit of overhead and slowed down the development)
So what I want is a JAR that can be used to run the Spring Boot app (java -jar myapp.jar) as well as the Spark app (java -cp myapp.jar path.to.main.class).
It is also OK to have two JARs - but both would need to be fat JARs (meaning: include dependencies).
What I tried in the pom.xml is this:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
This creates (as expected) a fat JAR that can be used to run the Spring Boot app. But it cannot be used for the Spark app (as the classes and dependencies are somehow repackaged as I understand).
My second try was this:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<classifier>exec</classifier>
</configuration>
</execution>
</executions>
</plugin>
This creates the fat JAR as well plus another JAR just holding the classes that are implemented in my project - but without the dependencies. Therefore the Spark job does not start (as expected).
Any idea how to solve this situation?
Thanks!

I used the same technology stack for an application (Spring- for the web part and Apache Spark for the big data processing). I don't see the case where someone wants to build a fat jar for both the side, Spring + Spark (except the case where inside spark jobs you would use something from Spring). So, the approach that we use is to have to separate Maven module one for the Spring web part and one for the Apache Spark. For the Spring Boot we did not use the spring-boot-maven-plugin, but instead we used the following maven plugins, something like this:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.5.0</version>
<configuration>
<mainClass>com.Application</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.5.1</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<excludeArtifactIds>integration</excludeArtifactIds>
<outputDirectory>${project.build.directory}/lib/</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>com.Application</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
Like this we have a better controller use all the dependencies (e.g: put then in a lib folder and include them in MANIFEST)
For the Spark application you have two option:
run with spark-submit (personally i don't prefer it)
use SparkLauncher class from spark_launcher*.jar dependency (calling from web a Spark Job).
Building a fat jar for Spark application with only dependencies used in Spark code is desirable, cause you load only what you truly need. We can use maven-shade-plugin for this:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<artifactSet>
<includes>
// put here what you need to include
</includes>
</artifactSet>
</configuration>
</execution>
</executions>
</plugin>

Using maven shade plugin is not encouraged, unless and until there is no other option apart from using the maven shade plugin. Below is the informative link for not to use the shade plugin.
Downsides of using Shade plugin relocation feature
From my perspective for running spark application, spring boot maven plugin is the best option for managing everything, including library dependencies. Even if your application is used as a scheduled job, in which you need to use spark-submit/spark launcher for launching the application.
In other case where your java app using spark and spring both but have controller/api to use the application in that case also spring boot maven plugin is the best one.
There are only 2 types of Challanges when we use spark-submit/spark launcher to launch the application which is created using spring boot maven plugin.
1. Main Class
When we package fat jar/uber jar using spring boot maven plugin, it is packging the class files and java libraries in spring way and not the way sprak is expecting it.Inside the uber jar generated by the spring boot maven plugin, we have boot-inf,meta-inf and org folder.So when we give mainclass in spark submit or spark launcher as a parameter,It will not able to find that class as it will not be able to locate package/path specified in parameter, due to change of structure in jar file.Even after you specify the correct location which is starting with BOOT-INF for main class, it will not work because the way spring launches the application is using differrent main class.
Below is the link which shows main class that should be used for launching the fat jar generated by spring boot maven plugin.
https://docs.spring.io/spring-boot/docs/current/reference/html/executable-jar.html#appendix.executable-jar.launching
On high level if I inform ,File named as MANIFEST.MF inside the uber jar contains below entries.Where Main-Class is the actual main class which is used for initialization of spring related stuff, after which customized main class should get started which is Start-Class entry.
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.mycompany.project.MyApplication
So as a conclusion specifiying main class as "org.springframework.boot.loader.JarLauncher" in spark-submit or spark-launcher will resolve our issue for this problem.This will only work if you are using sparing boot maven plugin for packagin the jar.
External common libraries used in pom.xml+spark installation.
Another issue which might occure while using spark-submit or launcher application while launching uber jar, packaged using the spring boot maven plugin is jar conflicts.So the problem will be when we package jar using spring boot maven plugin, it is coping depedencies inside BOOT-INF/lib folder, let's say if you are using below depedency in pom.xml.
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10</version>
</dependency>
Now let's say this depedency already exist with differrent version in spark installation, in that case it will give conflict in classes.As it will be an issue of class loader ,its better not to use those which might create conflict due to which application might fail.I have faced this kind of issues related to logger classes or json libraries, as there are multiple json/logger library options are available.As a resolution you can excluded those classes or libraries or you can replace that library with alternative one.

Related

Can we bundle multiple spring-boot application as a war and deploy

We have multiple boot component as well as different schema for each component. We have a business requirement where we need to deliver everything as a war and has to be deployed as a single application on different machine.
I am trying to go with plugin architecture, so that one(new) aggregator microservice will be prepared where we can add every boot component as a plugin and then use them as a bundle(war) deployable.
By enabling a component with maven plugin we have created two jar one executable jar and another original jar. But how do i add them in to the new aggregator component as a plugin and access the rest layer from each component(plugin).
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.4.RELEASE</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<classifier>exec</classifier>
</configuration>
</plugin>
</plugins>
Do i need any change in approach/ how do i add a spring-boot jar as a plugin to another project? Any references or sample structure/code would be helpful & thanks in advance.

How should I run front end integration tests in spring boot?

I have a simple spring boot project that has a maven structure like this:
project
- backend (spring boot)
- frontend (angular)
I got the project configured so *IT.java classes run during integration test phase of maven and they are successfully testing the Spring Boot REST API with an H2 database.
I'm trying to configure the project so my cucumber tests that interact with the browser will run during the integration test phase of the project. The cucumber tests start up ok but fail for the simple reason the angular files don't get served by the spring boot application when it starts.
In backend/src/main/resources/static I have a simple index.html file. When the cucumber tests run they open a browser an I see the content of that file.
In the backend module (war) pom I copy the dist content from the angular build...
<build>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<executions>
<execution>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>some.package.Boot</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>${maven.war.plugin.version}</version>
<configuration>
<warName>${root.context}</warName>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<resources>
<resource>
<directory>../frontend/<appname>/dist/frontend</directory>
<!--<targetPath>src/main/resources/static</targetPath>-->
<targetPath>static</targetPath>
</resource>
</resources>
</build>
In the resultant war file the angular files are packaged up into WEB-INF/classes/static/.
You'll note from above I also tried getting the angular code copied into resources/static, but that puts the files in target/classes/src/main/resources/static, so that isn't the right approach!
Yet the cucumber tests still only see the content of the index.html file from src/main/resources/static when they run.
Potentially we could move the angular app into the same src tree as the java/webapp code, but that's not an option.
How can I persuade Spring Boot to use the war in target rather what it appears to be doing and service content from source?
Versions:
Java 11
Maven war plugin 3.2.2
Springboot version defined in parent pom as 2.1.3.RELEASE
spring-boot-starter-web
spring-boot-starter-data-rest
spring-boot-starter-data-jpa
spring-boot-starter-data-jdbc
spring-boot-starter-tomcat (not sure I need this one)

How to add test-jar as aspectLibrary in Maven

I have an aspect that I want to use in my test-classes. I don't want to add it to the main jar, as it would pull in test libraries like junit and mockito. While there's a configuration setting to add an aspectLibrary, it always adds the main jar, there's no way to specify the test-jar.
My aspectj plugin looks like this:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.7</version>
<configuration>
<aspectLibraries>
<aspectLibrary>
<groupId>aspect.test</groupId>
<artifactId>general</artifactId>
<type>jar</type>
</aspectLibrary>
</aspectLibraries>
</configuration>
<executions>
<execution>
<phase>process-sources</phase>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
I actually want to specify test-jar but that doesn't seem possible. Without the it defaults to the jar (obviously).
I also might have to configure aspectj-maven-plugin for the compile and test-compile goal... but first I need to know how to specify the test-jar. Any suggestions are welcome.
Please read the Maven JAR Plugin documentation, chapter How to create a jar containing test classes. There are two options listed:
the easy way: using type "test-jar" (will not work here)
the preferred way: creating a normal JAR containing only test classes, then importing it with scope "test"
We will choose the preferred way because it solves your problem. So basically you do the following:
Create a separate module for test helper aspects/classes, put everything under src/main/java, not src/test/java. AspectJ Maven plugin should have an execution with <goal>compile</goal> for that module.
Add that module as a test-scoped dependency wherever you need the test aspects
Refer to the module as an <acpectLibrary> from AspectJ Maven plugin and also be careful to only use <goal>test-compile</goal> in your plugin execution for that module so as to avoid to have the aspects woven into production code or to get error messages because the dependency has test scope and is unavailable for normal compile.
Because I do not want to fully quote 3 POMs and several classes here, I have created a little GitHub sample project for you. Just clone it, inspect all the files and their respective locations and - be happy. ;-)

spring boot loading jars (application dependencies and external file system jars)

I am trying to figure out what is the best way to setup a spring boot application in such a way that its has its own jar dependencies but additional jars are added to classpath at runtime when its being run as java -jar command. What approach makes more sense
Use the original jar (without dependencies added to it) and place all jars (application and runtime) in a folder on file system and use PropertiesLauncher to specify the loader.path to jars folder.
Use the fat jar (with application jars) place the additional jars on the filesystem and somehow include those as additional jars that need to be added to classpath. Not sure how this can be done.
Is there another better way to do this
The PropertiesLauncher was designed to work with fat jars, so you should be able to keep the fat jar and add as many additional dependencies as you like in an external location, e.g. with loader.path=/opt/app/lib:lib. I guess that's your option 2? If it doesn't work we can discuss in a github issue.
I resolved this issue using the following spring-boot-maven-plugin configuration, I had to build my Uber jar without excluded artifacts to create my external "lib" directory, then I added my excluded artifacts again and packaged my Uber jar with my application specific dependencies only.
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>1.3.1.RELEASE</version>
<configuration>
<layout>ZIP</layout>
<executable>true</executable>
<excludeArtifactIds>
<!-- My libs which will be packaged with my Uber jar-->
<!-- core,data-feeder,engine,lightspeed-tcp-api,order-manager,store,strategies,utils,viewer -->
<!-- Other libs -->
antlr,aopalliance,aspectjrt,aspectjweaver,classmate,commons-lang,
dom4j,h2,hibernate-commons-annotations,hibernate-core,hibernate-entitymanager,
hibernate-jpa-2.1-api,hibernate-validator,jackson-annotations,jackson-core,jackson-databind,
jandex,javassist,javax.transaction-api,jboss-logging,jboss-logging-annotations,jcl-over-slf4j,
jul-to-slf4j,log4j-over-slf4j,logback-classic,logback-core,mysql-connector-java,slf4j-api,
snakeyaml,spring-aop,spring-aspects,spring-beans,spring-boot,spring-boot-autoconfigure,
spring-boot-starter,spring-boot-starter-aop,spring-boot-starter-data-jpa,spring-boot-starter-jdbc,
spring-boot-starter-logging,spring-boot-starter-tomcat,spring-boot-starter-web,
spring-boot-starter-websocket,spring-context,spring-core,spring-data-commons,spring-data-jpa,
spring-expression,spring-jdbc,spring-messaging,spring-orm,spring-tx,spring-web,spring-webmvc,
spring-websocket,tomcat-embed-core,tomcat-embed-el,tomcat-embed-logging-juli,tomcat-embed-websocket,
tomcat-jdbc,tomcat-juli,validation-api,xml-apis
</excludeArtifactIds>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
Then, I added the following property to my "application.properties" which inside my jar "resources/" dir to specify my "lib" dir for Spring PropertiesLauncher where I put "lib" dir along with my jar in the same dir.
loader.path=lib/
Finally, I did run my jar using the following command
java -jar back-tester-0.0.1-beta-01.jar
Also, you can add the "loader.path" property to your command line without putting it in your "application.properties" like the following command but this way didn't work with me as I packaged my jar as an executable one which I'm running as linux service.
java -Dloader.path="lib/" -jar back-tester-0.0.1-beta-01.jar
Now, I successfully reduced my jar size from 29 M to only 1 M jar which contains only my application specific libs and it works out of the box.
thank you #Ashraf Sarhan, you rescue my two days :)
I added in pom file:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<layout>ZIP</layout>
<executable>true</executable>
<mainClass>vn.com.Mymainclass</mainClass>
<excludes>
<exclude>
<groupId>com.vn.groupId</groupId>
<artifactId>excluded-id-a</artifactId>
</exclude>
<exclude>
<groupId>com.vn.groupId</groupId>
<artifactId>excluded-id-b</artifactId>
</exclude>
</excludes>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
And Placed ./lib folder containing two jars of two files which excluded above beside with my-main-spring-boot-app.jar file, and I ran:
java -Dloader.path="lib/" -jar my-main-spring-boot-app.jar
It worked perfectly.

maven-bundle-plugin problem (OSGI)

I have a project with produces an OSGI bundle using the maven-bundle-plugin. The configuration looks like this:
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>2.1.0</version>
<extensions>true</extensions>
<inherited>true</inherited>
<configuration>
<instructions>
<Export-Package>${bubble.osgi.exports}</Export-Package>
<Import-Package>${bubble.osgi.imports}</Import-Package>
<Embed-Dependency>${bubble.osgi.embed.dependencies}</Embed-Dependency>
<_versionpolicy>[$(version;==;$(#)),$(version;+;$(#)))</_versionpolicy>
</instructions>
<versions>
<bubble.osgi.version.clean>${project.version}</bubble.osgi.version.clean>
</versions>
</configuration>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>cleanVersions</goal>
</goals>
</execution>
</executions>
</plugin>
The outcome of the project is a single jar file with all the osgi stuff, embedded dependencies etc. included. I rather would like to have 2 jars as outcome, one with the osgi stuff included and one without, as the embedded dependencies cause problems when using it just as a plain jar.
Is there any other way than using the maven assembly plugin for this?
You can move maven-bundle-plugin configuration into separate profile and use something like
mvn package -Posgi
or default. But there will only one artifact produced per build.
I would recommend to use 2 different pom.xml files (in the project) which will produces 2 different artifacts.
The default pom.xml should generate the plain library like foo:bar:1.0.0
Another pom-osgi.xml should generate the OSGi library like foor:bar-osgi:1.0.0
To build the library with another pom use
mvn -f pom-osgi.xml install

Resources