Why does servletContext.getResourceAsStream get files outside of the executable jar? - spring-boot

I'm in the process of switching from war-packaging of my Spring Boot app to using an executable jar.
My resources are under src/main/webapp and, according to Spring Boot docs (if I understand correctly), should not even be included in the final executable jar:
Do not use the src/main/webapp directory if your application is packaged as a jar. Although this directory is a common standard, it works only with war packaging, and it is silently ignored by most build tools if you generate a jar.
This is confirmed by the fact that in the target jar I cannot find these resources at all. Unfortunately the executable jar structure description seems to say nothing about where the resources are placed.
However, to my surprise, calling servletContext.getResourceAsStream("/myresource.js") in the application DOES find the resource correctly. And the resource seems to be served from my src/main/webapp directory (even when running the jar in standalone manner e.g. via java -jar myjar.jar.
Also:
calling servletContext.getResourcePaths lists the contents of my src/main/webapp directory
calling servletContext.getRealPath("/") returns the exact path on the filesystem to my source files e.g. c:\path\to\my\project\src\main\webapp.
Why is this happening? I would expect only what's inside my jar to be available to the application. Does the embedded tomcat place my source files in the context root for some reason? Any links to relevant documentation would be appreciated.
P.S. In my pom.xml you will see standard things like:
<packaging>jar</packaging>
...
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
...
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.0.1.RELEASE</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
...
<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
...

Related

Spring boot generated jar file just work inside root of project

I am new in Spring boot and I think this issue is very basic.
I created an application using Spring Boot and everything is good in dev environment. But when I copy the jar file from target directory to another machine and run "java -jar" it doesn't render jsp pages with following error:
There was an unexpected error (type=Not Found, status=404)
when I copy src folder from project root to same location it works fine.
It seems the jar file just work from project's root.
Here is my configurations:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Boot Section -->
<!-- Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
<resource>
<directory>src/main/webapp</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
and my project structure:
and I run following at command line:
mvn clean package
I have following in my target directory:
I can run the application using
java -jar target/springboot-in-10-steps-0.0.1.jar
Application works as expected and it renders welcome page.
When I go to target directory and run same command:
java -jar springboot-in-10-steps-0.0.1.jar
Application gets launch but it doesn't render the welcome page.
When I copy src to target everything is fine and I can see welcome page. It seems Spring boot doesn't find WEB-INF/jsp directory in the jar file.
Did I miss something in spring boot configuration or application.properties?
I could fix it by changing packaging from jar to war and interestingly this works:
java -jar springboot-in-10-steps-0.0.1.war
I think there are ways to change the structure of files and folders within generated jar file using repackage features.

Import spring boot app into another project

So I am attempting to add a spring boot executable jar as a dependency in another project (Testing framework).
However once added to the pom and imported. Java imports don't work properly. If I look inside the jar all packages are prepended with:
BOOT-INF/classes.some.package.classname.class
There is also some spring boot related packages, MANIFEST etc etc.
Not if I switch the spring boot app's build to just install and deploy a regular jar using the spring-boot-maven-plugin
This changes and everything works fine. Unfortunately this is not a solution for us as we lean on the executable jar as part of our release process.
Can I build a deploy both versions of the jar and use a classifier to determine each?
Thanks
Turns out this exact scenario can be achieved using the spring-boot-maven-plugin.
Spring boot app's pom:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>1.4.1.RELEASE</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<classifier>exec</classifier>
</configuration>
</execution>
</executions>
...
</plugin>
project using the spring boot jar can be added as normal:
<dependency>
<groupId>com.springboot</groupId>
<artifactId>app</artifactId>
<version>1.0.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
OR if you want to reference the executible jar
<dependency>
<groupId>com.springboot</groupId>
<artifactId>app</artifactId>
<version>1.0.0-SNAPSHOT</version>
<scope>test</scope>
<classifier>exec</classifier>
</dependency>

Spring boot with maven multi module project

I have a maven multi module project designed like the first answer in following SO post:
Multi-module maven with Spring Boot
Now I want a common maven module that can contain some models to be used by multiple microservices. If I make this common project as a child of the first level parent pom (so that all dependencies injected by boot like jpa, jackson etc are available to common), then STS/Spring is detecting it as a boot application and complains about no Main class on maven build.
Can someone suggest how I can achieve this?
Current Code:
parent pom.xml: (Only relevant parts included)
<project>
<name>...</name>
<groupId>...</groupId>
<artifactId>...</artifactId>
<packaging>pom</packaging>
<version>...</version>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-parent</artifactId>
<version>Brixton.M3</version>
<relativePath />
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
child (common module) pom.xml (only relevant parts), not to be boot app:
<project>
<artifactId>...</artifactId>
<version>...</version>
<name>...</name>
<parent>
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>...</version>
</parent>
</project>
I don't have all the details regarding your project but my best guess is that the spring-boot-maven-plugin is defined on the parent (or you are using the spring-boot-starter-parent in your root pom). This effectively ask the build to package your module as a Spring Boot app (which is not what you want).
STS probably looks for that hint to figure out if a module contains a Spring Boot application or not. Maybe it would be nicer if it looks for a main class annotated with #EnableAutoConfiguration (or SpringBootApplication).
You can fix the problem easily (from the build side) by specifying the skip property of the repackage goal
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
If STS still picks up the module as a Spring Boot app, I'd create an issue in their tracker
Normally, Spring Boot won't start a web container if it's not present in the module.
I would suggest you to analyse your dendencies using the command
mvn dependency:tree
One more brute-force way of ensuring is use this configuration in your application.properties
spring.main.web-environment=false
Here are two ways to fix this:
You can add the skip property like #Stephane Nicoll mentioned. However, this will completely ignore the test cases inside that module. https://docs.spring.io/spring-boot/docs/current/maven-plugin/examples/it-skip.html
Another option is to add a classifier property to make a separate executable jar out of this module. https://docs.spring.io/spring-boot/docs/current/maven-plugin/examples/repackage-classifier.html
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<classifier>exec</classifier>
</configuration>
</execution>
This fix will make sure the dependent module get its required jar and the source module will still be an executable one.

Add external library .jar to Spring boot .jar internal /lib

I have an external .jar that cannot be imported from public repositories using pom.xml, it's sqljdbc41.jar.
I can run the project locally from my IDE, and everything will work. I referenced the library after downloading it like so:
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>sqljdbc41</artifactId>
<version>4.1</version>
<scope>system</scope>
<systemPath>${basedir}/lib/sqljdbc41.jar</systemPath>
</dependency>
When I run mvn clean package to create my .jar file and try to run the created .jar, a mistake will pop up, which mentions the SQL Server references are not valid. I then extracted my .jar file and true enough, everything that is referenced in the pom.xml file properly gets downloaded and added, however, my SQL Server does not.
I can, in a very hacky way* just manually add the sqljdbc41.jar to my /lib folder after it's been compiled as a .jar, and it'll work, however that seems highly unoptimal. What would be a better approach?
*Opening the .jar file with Winrar, going to the /lib folder, manually selecting my sqljdbc41.jar file, then make sure to select the No Compression option bottom left where Winrar gives you compression options, in case you find this by Google and no one answered.
you can set 'includeSystemScope' to true.
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<includeSystemScope>true</includeSystemScope>
</configuration>
</plugin>
You could install the sqljdbc41.jar in your local repository :
mvn install:install-file -Dfile=path/to/sqljdbc41.jar -DgroupId=com.microsoft.sqlserver -DartifactId=sqljdbc41 -Dversion=4.1 -Dpackaging=jar
And then declare the dependency as a standard dependency :
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>sqljdbc41</artifactId>
<version>4.1</version>
</dependency>
If you use a remote artifact repository (nexus, archiva...) you also need to deploy the artifact on this repository. You can find more here : https://maven.apache.org/guides/mini/guide-3rd-party-jars-remote.html
Another way, you can put it into the resources folder, such as resources/lib/xxx.jar, then config the pom.xml like this:
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>sqljdbc41</artifactId>
<version>4.1</version>
<scope>system</scope>
<systemPath>${basedir}/src/main/resources/lib/sqljdbc41.jar</systemPath>
</dependency>
In Spring Boot: I also faced similar issue and below code helped me.
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>1.5.7.RELEASE</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
<configuration>
<includeSystemScope>true</includeSystemScope>
</configuration>
</plugin>
</plugins>
</build>
It works for me:
project {root folder}/libs/ojdbc-11.2.0.3.jar
pom.xml:
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc</artifactId>
<version>11.2.0.3</version>
<scope>system</scope>
<systemPath>${basedir}/libs/ojdbc-11.2.0.3.jar</systemPath>
</dependency>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<includeSystemScope>true</includeSystemScope>
</configuration>
</plugin>
In my case, the fault was providing a version number without "dot" in tag:
<dependency>
<groupId>jdk.tools</groupId>
<artifactId>jdk.tools</artifactId>
<scope>system</scope>
<version>1</version>
<systemPath>${basedir}/src/main/resources/lib/tools.jar</systemPath>
</dependency>
This one works:
<dependency>
<groupId>jdk.tools</groupId>
<artifactId>jdk.tools</artifactId>
<scope>system</scope>
<version>1.8</version>
<systemPath>${basedir}/src/main/resources/lib/tools.jar</systemPath>
</dependency>
When Spring-Boot projects are used with maven or gradle plugins they packaged the applicaiton by default as executable jars.
These executable jars cannot be used as dependency in any another Spring-Boot project because the executable jar add classes in BOOT-INF/classes folder. This means that they cannot be found when the executable jar is used as a dependency because the dependency jar will also have the same class path structure as shown below.
If we want to use project-A as a maven dependency in project-B then we must have two artifacts. To produce the two artifacts, one that can be used as a dependency and one that is executable, a classifier must be specified. This classifier is applied to the name of the executable archive, leaving the default archive for use as a dependency.
To configure a classifier of exec in Maven, you can use the following configuration:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<classifier>exec</classifier>
</configuration>
</plugin>
</plugins>
</build>
So the MAJIC WORD here is <classifier>exec</classifier> this will create a jar structure as below and then it could easily be conusmed by spring-boot project as maven dependency jar on class path.
The above plugin need to be add in project-A pom that is going to be used as dependency in project-B. Same is explained in spring documentation section 16.5. as well.
In order to work through the local repository, the target .jar file that we will work with must be in the s2 folder. Several methods can be used for this:
The file can be taken manually and put in the relevant place (not
preferred). The same process can be done by installing it via the
console.
Relevant Remote URL is written in the .pom file dependencies and
automatically places it in the s2 folder when Intellij is refreshed
(validate) in the IDE used.
The same process can be done by addressing the .pom file dependencies via the centeral repository.
Attention: ComponentScan should not be forgotten for the related jar work on SpringBot.

Spring-boot application can only be launched with spring-boot:run when forking - java -jar fails

I have a Spring Boot web application that I cannot start when using the executable jar directly.
I am using Spring Boot 1.2.0.RELEASE, Maven 3.0.5, Java 1.7.0_72.
I have a requirement to use the hp-roman8 character set - in order to handle incoming requests from some remote legacy systems. To provide the hp-roman8 charset I use net.freeutils.jcharset in version 1.5.
The jcharset artifact is installed in my local repository
However when launching my application using java -jar the application fails to start and I get "java.nio.charset.UnsupportedCharsetException: hp-roman8" as cause.
The same error occurs if I do mvn spring-boot:run unless I configure spring-boot-maven-plugin to always fork.
With <fork>true</fork> spring-boot:run starts the application successfully and the hp-roman8 charset is available on the classpath.
However <fork>true</fork> has no effect on the created jar, so I am still unable to launch my application using java -jar - and continue to get the "java.nio.charset.UnsupportedCharsetException: hp-roman8".
The jcharset-1.5.jar is included correctly in the created executable jar file next to the rest of the dependencies in the path "lib/jcharset-1.5.jar" so I don't quite understand why it is not available on the classpath when launching the jar.
Have any of you seen similar behavior, or have any ideas as to what I could try out in order to troubleshoot or even resolve this problem?
update:
I have also tried changing the main-class to use the PropertiesLauncher instead (using the <layout>ZIP</layout> tag in the plugin configuration) - see http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#build-tool-plugins-maven-packaging.
Afterwards I added loader.path to my application.properties. Even if I specify the absolute path to jcharset-1.5.jar I still get the UnsupportedCharsetException.
I also tried using an exploded archive but still no go.
You could use Maven's shade plugin rather than Spring Boot's Maven plugin. The main difference is that the shade plugin takes all of your project's dependencies and packages them directly in the jar file, i.e. it doesn't use nested jars. While this has some disadvantages, it does mean that a single class loader is used to load all of your application's classes and, therefore, JCharset is available to the application class loader.
When you're using the Shade plugin, you shouldn't use Spring Boot's starter parent. You may want to import Boot's dependency management instead.
Your pom would look something like this:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-sample-jcharset</artifactId>
<version>0.1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-boot-sample-jcharset</name>
<description>Spring Boot sample showing the use of JCharset in an executable jar</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.7</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.2.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- Any additional dependencies, including JCharset -->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>sample.jcharset.SampleJCharsetApplication</Main-Class>
</manifestEntries>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

Resources