How to obfuscate multi-module spring-boot project with proguard? - java

I tried to compile and obfuscate 2 projects where the one depends on the other one and both are built with the Spring boot maven plugin.
Let's call them for the sake of simplicity main and util projects.
The build has two stages. In the first stage the util project is built. In the second stage the main project which depends on the util project.
My problem is that Spring boot maven plugin creates nested jars. (https://docs.spring.io/spring-boot/docs/current/reference/html/executable-jar.html)
So if I try first repackage the projects with Spring boot maven plugin and after that obfuscate the repackaged jar which contains both the util and the main projects, then first proguard extracts the repackaged jar's content where the extracted content will contain the util jar. Then proguard won't obfuscate the content of this util jar because it is a jar and not a set of class files.
If I try first obfuscate the util project with proguard and after that repackaging with Spring boot maven plugin then the obfuscation will be done but when I try to compile the main project then it won't find the necessary symbols in the jar produced from the util project.
So how to obfuscate projects repackaged with Spring boot maven project?

In your main module declare proguard-maven-plugin and spring-boot-maven-plugin (proguard package phase must be executed before spring boot plugin repackage goal). Declare it only in module which is entry point - no need to declare it in submodules or parent pom.
Then, in your proguard plugin config add modules which you would like to obfuscate besides main jar:
<configuration>
<assembly>
<inclusions>
<inclusion>
<groupId>com.yourgroupid</groupId>
<artifactId>submodule</artifactId>
</inclusion>
</inclusions>
</assembly>
...
And in spring-boot-maven-plugin add exclusion, so spring not overwrites already obfuscated jar:
<configuration>
<mainClass>
com.yourapp.App
</mainClass>
<excludeGroupIds>com.yourgroupid</excludeGroupIds>
</configuration>
Hope that it will work for you!

Related

Why is Maven including multiple versions of the same dependency?

I have a Maven java web app (.WAR) project that includes several libraries, including the Wicket libraries (but I don't think the problem is wicket itself, but rather with maven).
Here's the problem: even tho I only include Wicket 6.20.0, the resulting .WAR contains two copies of the Wicket libraries: 6.20.0 and 6.18.0, as you can see in this screenshot:
Thinking of some conflicting imports I printed the dependency tree using the:
mvn dependency:tree
commnad... but there is no mention of Wicket 6.18.0 in the dependency tree! I also double-checked using Eclipse's "dependency hierarchy" view and I can confirm there's no trace of that import.
I even did a search for string "6.18.0" across the entire workspace with Eclipse, but it's nowhere to be found!
How can I find out what is causing the inclusion of that duplicate version of the library?
Maven doesn't work in this way.
The resolution of more than one dependency with the same artifactId and groupId but with a different version will result to a single dependency (the version used is no determinist).
The presence of two artifacts with the same artifactId and groupId but with two distinct versions in a same lib folder of the WAR is probably related to one of these :
you don't execute mvn clean package but only mvn package.
your use a bugged version of the Maven war plugin. Try to update it to check that.
you have a Maven plugin that copies Wicket jars 6.18.0 in the WEB-INF/lib folder of the target folder during the build of the component.
the maven WAR project you are building has as dependency an artifact of type WAR. In this case, the dependencies of the WAR dependency are so overlaid in the WAR project that you are building.
An interesting Maven issue about duplicated JAR because of WAR dependencies :
JARs with different versions can be in WEB-INF/lib with war as dependencies
Your answer and your comment indicate that actually you have a WAR dependency in your build.
Unfortunately, there is not really a good and long term effective solution to bypass this limitation.
As said in my comment, using the packagingExcludes property of the maven war plugin is a valid workaround for the actual issue :
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.4</version>
<configuration>
<!-- ... -->
<packagingExcludes>WEB-INF/lib/wicket-*-6.18.0.jar</packagingExcludes>
</configuration>
</plugin>
But beware, using that will do your build less robust through the time.
The day where you update the version of the WAR dependency and that in its new version, it pulls again a different version of wicket, you have still a risk to have duplicate jars with two distinct versions in your built WAR.
Using the overlay feature by specifying the overlay element of the maven-war-plugin is generally better as it focuses on the overlay applied for the war dependency. It fixes the problem early.
As a result, you could define to exclude any wicket JARs from the WAR dependency :
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<version>2.4</version>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<overlays>
<overlay>
<groupId>com.whatever.youlike</groupId>
<artifactId>myArtifact</artifactId>
<excludes>
<exclude>WEB-INF/lib/wicket-*.jar</exclude>
</excludes>
</overlay>
</overlays>
</configuration>
</plugin>
This way is better but this is still a workaround.
The day where the dependency WAR is updated and that it pulls new dependencies (other than Wicket) that are declared in your actual build but with different versions, you may finish with the same kind of issue.
I think that declaring a dependency on a WAR artifact should be done only as we don't have choice.
As poms and projects refactoring are possible, introducing a common JAR dependency which the two WARs depend on and that contains only common sources and resources for the two WARs makes really things simpler.
Well, I figured it out while poking around.
I had a dependency of type "war" in the project:
<dependency>
<groupId>com.whatever.youlike</groupId>
<artifactId>myArtifact</artifactId>
<version>1.0.7-SNAPSHOT</version>
<type>war</type>
</dependency>
Apparently (I wasn't aware of this, my fault here) these type of dependencies will include themselves in the classpath by copying all libs to the main WAR /libs folder, but these will NOT show app in the dependency tree / dependency hierarchy.
I solved by configuring an explicit exclusion in the WAR plugin:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.4</version>
<configuration>
<!-- ... -->
<packagingExcludes>WEB-INF/lib/wicket-*-6.18.0.jar</packagingExcludes>
</configuration>
</plugin>
Use clean install and the double dependency will probably be gone.
Because other libs can use same libs but different version or you tried different version and didn't make mvn clean
The command mvn dependency:tree is telling you the correct information - what you are looking at here is an eclipse / build issue.
Clear out all the target and build areas in your project. If need be, check it out from source control to a new folder.
Alternatively you can build your project in IntelliJ IDEA, and see if you get the correct dependencies (most likely you will).

Difference between spring-boot:repackage and mvn package

After reading Spring documentation and some other articles on web, I am still confused what is the difference between Spring Boot Maven plugin's spring-boot:repackage and a regular mvn package.
I've thought that mvn package creates a jar with all dependencies included, so what is really the reason to use the plugin by Spring?
The maven package goal and the spring-boot:repackage goal are different in nature. The spring-boot repackage goal is mainly intended to make a JAR or WAR executable from the command line itself using java -jar *.jar while the maven package goal take the compiled code and package it in its distributable format, such as a JAR.It is the spring-boot repackage goal that repackages the JAR produced by maven to specify the main class and make it executable using an embedded container.
Maven Package
The first, and most common way, to set the packaging for your project via the equally named POM element . Some of the
valid packaging values are jar, war, ear and pom. If no packaging
value has been specified, it will default to jar.
When a package is defined,each packaging contains a list of goals to bind to a particular phase ,the jar packaging will bind the
following goals to build phases of the default lifecycle :
process-resources,compile,process-test-resources,test-compile,test,package,install,deploy.
Spring-boot:repackage
Plugin to be included is :
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.1.4.RELEASE</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
The configuration repackages a jar or war that is built during the package phase of the Maven lifecycle.
So,Once spring-boot-maven-plugin has been included in your pom.xml, it automatically tries to rewrite archives to make them executable by using the spring-boot:repackage goal. You should configure your project to build a jar or war (as appropriate) by using the usual packaging element.
Reference : https://docs.spring.io/spring-boot/docs/current/reference/html/build-tool-plugins-maven-plugin.html
Spring repackage a jar or war that is built during the package phase of the Maven lifecycle. The following example shows both the repackaged jar, as well as the original jar, in the target directory:
$ mvn package
$ ls target/*.jar
target/myproject-1.0.0.jar target/myproject-1.0.0.jar.original
If you don’t include the configuration, you can run the plugin on its own (but only if the package goal is used as well). For example:
$ mvn package spring-boot:repackage
$ ls target/*.jar
target/myproject-1.0.0.jar target/myproject-1.0.0.jar.original
See more details from spring website using the link
mvn package creates a jar or war.
The spring boot plugin takes that jar or war and repackages them to make them executable from the command line (i.e. no app server needed).
From the plugin docs:
"Repackages existing JAR and WAR archives so that they can be executed from the command line using java -jar."

Import maven dependecies from a Spring Boot 1.4 application

I have troubles with importing classes from an existing Spring-Boot application into my new application after they changed the structure of the build jar file.
They changed the jar-file so that the applications own classes now are located in BOOT-INF/classes and not on the root of the jar-file.
But when I have a normal maven dependency to this Spring-boot application I can not import the existing classes from this application and into my new classes in my new application.
It worked just fine before they changed the structure...
The solution here is to refactor your code, so that the classes you're depending on in both your applications are available in a separate project.
Now you can use these classes by importing the dependency in both your projects:
<dependency>
<groupId>org.example</groupId>
<artifactId>example-shared</artifactId>
</dependency>
Make sure that you're not using the Spring boot maven plugin in this newly made shared project and you should probably not use any Spring boot starters either, since they load a lot of dependencies you may not need.
I found out that it is actually possible to use a Spring Boot application as a dependency. Even though it most likely is not recommended. But in some cases it just makes it easier.
This solution means that you can not use the executable archive.
"The executable archive cannot be used as a dependency as the executable jar format packages application classes in BOOT-INF/classes. This means that they cannot be found when the executable jar is used as a dependency."
The solution to my question is to include a configuration classifier to the spring-boot-maven-plugin. Like this for Maven:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<classifier>exec</classifier>
</configuration>
</plugin>
or like this for Gradle:
bootRepackage {
classifier = 'exec'
}

Netbeans Maven-based Module is not providing its dependencies

I want to accomplish what I think these directions describe.
I want to use an external, maven-based project from a rcp platform application.
I've used the new Project wizard to build a new maven-based application that include a maven-based module. I've added my external dependencies to the maven-based module.
I've also added the publicPackages section to my module's pom.
When I right-click on the module and go to ProjectProperties->PublicPackages I can see the correct packages listed with check marks.
My maven module builds just fine.
However, when I try to add the maven-module as a dependency of another module the packages listed in PublicPackages are not found.
If I peek inside the nbm I can see the jars I wanted exposed are under netbeans\modules\ext
Is there some way to build a maven-module that wraps another maven project?
The nbm-maven-plugin docs include an example which sounds alot like what I want to do:
Public packages declaration
By default all your module's packages (and classes) and private to the given module. If you want to expose any API to other modules, you will need to declare those public packages in your pom.xml. This includes not only your own classes but also any other 3rd party library classes that are packaged with your module and are to be exposed for reuse by other modules.
For example:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>nbm-maven-plugin</artifactId>
<version>3.8.1</version>
<extensions>true</extensions>
<configuration>
<publicPackages>
<publicPackage>org.foo.api</publicPackage>
<publicPackage>org.apache.commons.*</publicPackage>
</publicPackages>
</configuration>
</plugin>
there is a package org.foo.api made public (but not org.foo.api.impl package) and any package starting with org.apache.commons, so both org.apache.commons.io and org.apache.commons.exec packages are exposed to the outside
I am clearly not interpreting those doc correctly because this behavior is not what I'm seeing.

Maven WAR dependency - cannot resolve package?

I have a war dependency:
<dependency>
<groupId>my.package</groupId>
<artifactId>myservices</artifactId>
<version>0.3</version>
<type>war</type>
</dependency>
Now, this exists in my local repository, and the class exists at WEB-INF/classes/my/package/myservices. When I go to use myservices, however, I get package my.package does not exist. Intelli-J knows to change myservices into my.package.myservices, but trying to import seems to not work at all.
Is there something special I need to do with this war dependency?
As pointed out in the other answers:
In Maven, you cannot just load classes from a WAR artifact the way you can from a JAR artifact.
Therefore, the recommendation is to split off a separate Maven JAR project with the classes you want to reuse, and depend on this project in both the original WAR and the new project.
However, if for some reason you cannot / do not want to split the WAR project, you can also tell Maven that you need a JAR artifact in addition to the WAR. Put this into the POM of your WAR project:
<build>
...
<plugins>
...
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<attachClasses>true</attachClasses>
</configuration>
</plugin>
</plugins>
</build>
Then, when building the WAR project Maven will create a WAR and a JAR from it.
Source: Maven War plugin FAQ
Adapted from doc_180's comment, so it does not get overlooked.
It just doesn't work that way. war files are not supposed to be put on the classpath, but deployed to application servers (or servlet containers) that can deal with their special structure.
Of course you can probably find a custom classloader somewhere that can deal with java war files, but it's just not the way to do it.
Keep your code in a jar, include the jar in your war and in this application. But don't use a war as a dependency, unless you are building an EAR file.
WAR dependencies are handled VERY differently by Maven from JAR dependencies. They are treated as overlays.
http://maven.apache.org/plugins/maven-war-plugin/overlays.html
I think what you are looking for is something a bit different from a WAR overlay. WAR overlays merge the file structures with a "closest wins" model, but that means that things like web.xml are replaced by closest wins, not merged.
If you want merging (which is closer to what most people think of when they start talking about WAR dependencies) you should look at the Cargo uberwar plugin.
http://cargo.codehaus.org/Merging+WAR+files
If your goal is simply to share some classes between two WARs, you should probably just put those classes into a JAR project. Maven in particular is really designed to work on a pom.xml -> a single artifact model (JAR/WAR/etc). Trying to take a single pom.xml and have it emit, say, a JAR for some stuff and a WAR for other stuff is going to be very painful.
Incidentally, if you are working on a team larger than one person, you are going to want an artifact management server pretty fast (e.g. Artifactory, Nexus or Archiva) or you will go crazy dealing with this stuff. ;)
In a WAR, the classes must be located at WEB-INF/classes/... not at classes/....
Anyway I never have tried to reference other classes from a WAR (not JAR), and I do not know if this is possible.
Make sure the dependency is installed in your local repository.
The local repo should look like:
.m2/my/package/myservices/0.3/myservices-0.3.war
If this is not the case, then install the war into the local repository before using it in the dependency:
mvn install:install-file -Dfile="[path-to-war]" -DgroupId=my.package -DartifactId=myservices -Dversion=0.3 -Dpackaging=war -DcreateChecksum=true -DgeneratePom=true

Categories