Jersey fails when creating uber jar with maven-ass

2019-01-08 02:08发布

问题:

I have created a maven jersey starter webapp. Also I have embedded jetty server in my app using jetty plugin.

My project is working fine when I run my project using mvn jetty:run command.

But when I package my project using mvn clean package command and run the jar file which has name jar-with-dependencies the project throws this exception while returning a json response from a jersey resource.

SEVERE: MessageBodyWriter not found for media type=application/json, type=class com.nitish.freecharge.model.Count, genericType=class com.nitish.freecharge.model.Count.

Here is my pom.xml file

http://maven.apache.org/maven-v4_0_0.xsd">

<modelVersion>4.0.0</modelVersion>

<groupId>com.nitish.freecharge</groupId>
<artifactId>wordcount</artifactId>
<packaging>war</packaging>
<version>2.0</version>
<name>wordcount</name>

<build>
    <finalName>wordcount</finalName>
    <resources>
        <resource>
            <directory>src/main/java</directory>
        </resource>
        <resource>
            <directory>src/main/resources</directory>
        </resource>
        <resource>
            <directory>src/main/webapp</directory>
        </resource>
    </resources>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>2.5.1</version>
            <inherited>true</inherited>
            <configuration>
                <source>1.7</source>
                <target>1.7</target>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-maven-plugin</artifactId>
            <version>9.3.0.v20150612</version>
            <configuration>
                <scanIntervalSeconds>5</scanIntervalSeconds>
                <webApp>
                    <contextPath>/wordcount</contextPath>
                </webApp>
                <httpConnector>
                    <!--host>localhost</host -->
                    <port>9999</port>
                </httpConnector>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <version>2.4</version>
            <executions>
                <execution>
                    <id>package-jar</id>
                    <phase>package</phase>
                    <goals>
                        <goal>jar</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-assembly-plugin</artifactId>
            <version>2.6</version>
            <configuration>
                <finalName>awesomeProject</finalName>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
                <appendAssemblyId>false</appendAssemblyId>
                <archive>
                    <manifest>
                        <mainClass>App</mainClass>
                    </manifest>
                </archive>
            </configuration>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>single</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.glassfish.jersey</groupId>
            <artifactId>jersey-bom</artifactId>
            <version>${jersey.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <dependency>
        <groupId>org.glassfish.jersey.core</groupId>
        <artifactId>jersey-server</artifactId>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.containers</groupId>
        <artifactId>jersey-container-servlet-core</artifactId>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.media</groupId>
        <artifactId>jersey-media-moxy</artifactId>
    </dependency>
    <dependency>
        <groupId>org.eclipse.jetty</groupId>
        <artifactId>jetty-server</artifactId>
        <version>9.3.8.v20160314</version>
    </dependency>
    <dependency>
        <groupId>org.eclipse.jetty</groupId>
        <artifactId>jetty-servlet</artifactId>
        <version>9.3.8.v20160314</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.4</version>
    </dependency>
</dependencies>
<properties>
    <jersey.version>2.22.2</jersey.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

I have created my Main Driver class as App.java in default package. Here is my App.java content

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;

public class App {
    public static void main(String []gg){

        Server server = new Server(9999);

        ServletContextHandler context = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
        context.setContextPath("/");
        server.setHandler(context);

        ServletHolder jerseyServlet = context.addServlet(org.glassfish.jersey.servlet.ServletContainer.class, "/wordcount/*");
        jerseyServlet.setInitOrder(1);
        jerseyServlet.setInitParameter("jersey.config.server.provider.packages","com.nitish.freecharge.resources");
        try {
            System.out.println("Starting the server..");
            server.start();
            System.out.println("Server started");
            server.join();
        } catch(Exception e) {
            System.out.println("Exception in starting the server ");
            e.printStackTrace();
        }
    }
}

Here is my only jersey resource class which gets executed when I access my project url after starting the server :

package com.nitish.freecharge.resources;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import com.nitish.freecharge.dao.FileDAO;
import com.nitish.freecharge.model.Count;

/**
 * Root resource (exposed at "count" path) which handles HTTP GET method and returns the count value;
 */
@Path("/count")
public class CountResource {

    private FileDAO fileDAO=new FileDAO();

    /**
     * Method handling HTTP GET requests. The returned object will be sent
     * to the client as "application/json" media type.
     *
     * @return String that will be returned as a application/json response.
     */
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @QueryParam("query")
    public Response getWordCount(@QueryParam("query")String query) {
        Error error=null;
        Count count=null;
        try{   
            if(query!=null){
                query=query.trim();
                if(query.length()>0 && query.matches("^[A-Za-z]+$")){
                    long c=fileDAO.getCount(query.toLowerCase());
                    count=new Count(c);
                }else{
                    error=new Error("Some Error Occured.Please Try Again With a new word!");
                }
            }else{
                error=new Error("Some Error Occured.Please Try Again!");
            }
        }catch(Exception e){
            error=new Error(e.getMessage());
            return Response.status(Status.INTERNAL_SERVER_ERROR).entity(error).build();
        }
        if(count!=null){
            return Response.status(Status.OK).entity(count).build();
        }else{
            return Response.status(Status.BAD_REQUEST).entity(error).build();
        }
    }
}

After packaging and run the complete embedded project using command java -jar awesomeProject.jar

I get this output on the server prompt

I have tried a lot and unable to package my embedded webapp in such a way that this issue gets resolved. I am new to maven and packaging. Kindly Help where I am committing mistake.

回答1:

If you look inside the MOXy jar, you will see a folder META-INF/services. In that folder, you will see a file named org.glassfish.jersey.internal.spi.AutoDiscoverable. The content of that file should be a single line

org.glassfish.jersey.moxy.json.internal.MoxyJsonAutoDiscoverable

What this file is for is to allow Jersey to discover the MoxyJsonAutoDiscoverable, which registers MOXy for Jersey. This service loader pattern allows Jersey to discover features and register them, without us having to register them ourselves.

The problem this poses when creating an uber jar is that there may be multiple jars with the same file, as different jars have different features to discover, but the file needs to be that exact name as that is how the service loader pattern works.

So you have a bunch of jars with the same file, but when you create the uber jar, you cannot have multiple files with the same name. It's just not possible. So only one of the files gets put into the final jar. Which one.. who knows. But that means that if MOXy's files is not that one file then its feature will not be auto-discovered, and we need to register it ourselves. So the classes are packaged in the uber jar, but the main feature component is just not registered. You could just register it yourself

jerseyServlet.setInitParameter("jersey.config.server.provider.classnames",
                               "org.glassfish.jersey.moxy.json.MoxyJsonFeature");

but what about all the other possible features that are possibly left out because their auto-discoverable file is not included?

For this reason, instead of the assembly plugin, you should use the maven-shade-plugin, which has transformers that allow us to concatenate the contents of service files into one file.

The configuration would look something like

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>2.3</version>
    <configuration>
        <createDependencyReducedPom>true</createDependencyReducedPom>
        <filters>
            <filter>
                <artifact>*:*</artifact>
                <excludes>
                    <exclude>META-INF/*.SF</exclude>
                    <exclude>META-INF/*.DSA</exclude>
                    <exclude>META-INF/*.RSA</exclude>
                </excludes>
            </filter>
        </filters>
    </configuration>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
            <configuration>
                <transformers>
                    <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
                    <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                        <mainClass>com.example.YourApp</mainClass>
                    </transformer>
                </transformers>
            </configuration>
        </execution>
    </executions>
</plugin>

The ServicesResorceTransformaer is what concatenates the files. This particular configuration of the plugin was taking from the Dropwizard getting started. You may want to check that out for further explanation.