Reputation: 4377
I think I have been running into the following issue Jersey doesn't always work with Spring Boot fat jars. The workaround should be to set the Jersey dependencies in the POM
to requiresUnpack
.
My POM
looks 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>net.hagstrom</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<requiresUnpack>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
</dependency>
</requiresUnpack>
</configuration>
<version>1.4.3.RELEASE</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
But I still get the following error when I try to run the JAR that I built with mvn package
:
2017-01-13 10:44:28.229 ERROR 9289 --- [ost-startStop-1] o.s.b.c.embedded.tomcat.TomcatStarter : Error starting Tomcat context. Exception: org.springframework.beans.factory.UnsatisfiedDependencyException. Message: Error creating bean with name 'org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration': Unsatisfied dependency expressed through constructor parameter 1; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jerseyConfig' defined in URL [jar:file:/home/mikael/Dev/Java/Java%20Programs/springBootDemo/target/demo-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes!/net/hagstrom/JerseyConfig.class]: Bean instantiation via constructor failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [net.hagstrom.JerseyConfig]: Constructor threw exception; nested exception is org.glassfish.jersey.server.internal.scanning.ResourceFinderException: java.io.FileNotFoundException: /home/mikael/Dev/Java/Java Programs/springBootDemo/target/demo-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes (No such file or directory)
Running the JAR that I built in IDE with Artifacts works just fine.
Is there something wrong in my POM
or the way I build the JAR with Maven?
Upvotes: 5
Views: 5233
Reputation: 1005
The problem is that Jersey cannot scan classes in the new "fat boot jar". This occurs when you try to use the packages("some.package.to.scan")
method of the ResourceConfig
class.
However, you can achive the same effect using Spring classpath scanning facilities. This way you can scan a package similarily to config.packages():
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AnnotationTypeFilter(Provider.class));
scanner.addIncludeFilter(new AnnotationTypeFilter(Path.class));
config.registerClasses(scanner.findCandidateComponents("your.package.to.scan").stream()
.map(beanDefinition -> ClassUtils.resolveClassName(beanDefinition.getBeanClassName(), config.getClassLoader()))
.collect(Collectors.toSet()));
Note: please have a look at the source of org.glassfish.jersey.server.internal.scanning.AnnotationAcceptingListener
. This is the stock solution and you can see that it does the same: it scans for classes annotated with @Path
or @Provider
(but doesn't manage to find anything because of the broken scanning mechanism).
(Using the older version of the boot plugin worked for me too, but I tried to avoid it.)
Upvotes: 2
Reputation: 7383
I found the issue.
Spring Boot 1.4 changed the internal Jar Structure to facilitate the Spring Boot bootstrap process.
https://github.com/spring-projects/spring-boot/issues/1468#issuecomment-267357809
You can leave this version 1.4.3:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
But spring-boot-jersey fat-jar to be executable without errors it`s need to downgrade plugin version to 1.3.8 like this:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>1.3.8.RELEASE</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
Upvotes: 1
Reputation: 3475
This might have been fixed already. I have published a couple of blog posts related to Creating APIs using Spring Boot
, Jersey 2
and Docker
and documenting them using Swagger
available at: http://tech.asimio.net/2016/04/05/Microservices-using-Spring-Boot-Jersey-Swagger-and-Docker.html and http://tech.asimio.net/2016/05/07/Documenting-multiple-REST-API-versions-using-Spring-Boot-Jersey-and-Swagger.html, both with accompanying source code and I didn't need to unpack
and repackage
Jersey 2
dependencies.
On the other hand, when I was working on the accompanying source code for another blog about Services Registration and Discovery using Spring Cloud, Eureka, Ribbon and Feign, I was integrating Spring Boot
and Jersey 1
(Jersey 1
doesn't have a Spring Boot
starter I do recall to work-around unpackaging Jersey 1
dependencies a needed to create a multi-module Maven
project for that specific API service.
Upvotes: 2