Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Boot JAR built with Maven using requiresUnpack not working

Tags:

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?

like image 556
g3blv Avatar asked Jan 13 '17 12:01

g3blv


2 Answers

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.

like image 93
ootero Avatar answered Sep 22 '22 11:09

ootero


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.)

like image 24
mihu86 Avatar answered Sep 24 '22 11:09

mihu86