How to Set Up Jax-Rs Application Using Annotations Only (No Web.Xml)

How to set up JAX-RS Application using annotations only (no web.xml)?

It seems that all I needed to do is this (Servlet 3.0 and above)

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("/*")
public class MyApplication extends Application {
...
}

And no web.xml configuration was apparently needed (tried on Tomcat 7)

How to implement jaxrs Application without web.xml

You need to deploy your application into a Servlet 3.0 compliant container to take advantage of this functionality. Try GlassFish 3.x or Tomcat 7.

Annotation based Spring / JAX-RS integration with no web.xml

You can integrate Spring and Jersey in a XML-less mode.

You will need to extend the class com.sun.jersey.spi.spring.container.servlet.SpringServlet. e.g.:

package org.test;

import com.sun.jersey.spi.spring.container.servlet.SpringServlet;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;

@WebServlet(urlPatterns = {"/rest/*"}, initParams = {
@WebInitParam(name = "com.sun.jersey.config.property.packages",
value = "org.test.rest")})
public class JerseyServlet extends SpringServlet {

}

Actually, it is a servlet. The parameter com.sun.jersey.config.property.packages indicates where scanning the resources (classes with annotation @Path).

The following code is an example of resource:

package org.test.rest;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.test.service.DummyService;

@Path("test")
@Component
public class TestResource {

@Autowired
private DummyService service;

@GET
public String test() {
return service.sayHello();
}

}

Such resources need to be scanned by Spring for dependency injection. e.g.:

package org.test.rest;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("org.test.rest")
public class RestConfiguration {

}

And this configuration can be loaded by the initializer class. e.g.:

package org.test;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import org.test.rest.RestConfiguration;
import org.test.service.ServiceConfiguration;

public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{ServiceConfiguration.class, RestConfiguration.class};
}

@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{};
}

@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}

}

Maybe you want to see the pom.xml

<?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.test</groupId>
<artifactId>jersey-spring</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>

<name>jersey-spring</name>

<properties>
<endorsed.dir>${project.build.directory}/endorsed</endorsed.dir>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>com.sun.jersey.contribs</groupId>
<artifactId>jersey-spring</artifactId>
<version>1.18.3</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>7.0</version>
<scope>provided</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
<compilerArguments>
<endorseddirs>${endorsed.dir}</endorseddirs>
</compilerArguments>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<outputDirectory>${endorsed.dir}</outputDirectory>
<silent>true</silent>
<artifactItems>
<artifactItem>
<groupId>javax</groupId>
<artifactId>javaee-endorsed-api</artifactId>
<version>7.0</version>
<type>jar</type>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>

How to use Jersey as JAX-RS implementation without web.xml?

What @AlexNevidomsky wrote in his answer is correct, as far as how to implement the app configuration with no web.xml; you use an @ApplicationPath annotation on an Application subclass.

@ApplicationPath("/api")
public class AppConfig extends Application {}

For more information on deployment options, see the Jersey Docs: Chapter 4. Application Deployment and Runtime Environments

Or more commonly, with Jersey as implementation, we would extend ResourceConfig (which extends Application).

@ApplicationPath("api")
public class AppConfig extends ResourceConfig {
public AppConfig() {
packages("package.to.scan");
}
}

So how is this implemented...

First things first, not all Java EE servers use Jersey. Actually the only ones I know that use Jersey are Glassfish and WebLogic. JBoss uses Resteasy. Tom EE uses CXF. WebSphere uses Apache Wink. Those are the only ones I can think of.

So I guess the question is "How does the Server know how to load the JAX-RS application?"

Servlet 3.0 introduced the pluggability mechanism, which makes use of a ServletContainerInitializer. How it works is that when the Server/Servlet container is started, it scans jars for a META-INF/services folder with a file named javax.servlet.ServletContainerInitializer. This file should include one or more fully qualified names of implementations of the ServletContainerInitializer.

This interface has only one method

void onStartup(java.util.Set<java.lang.Class<?>> c, ServletContext ctx)

The Set<Class<?> will be a list of classes, fitting the criteria in the @HandlesTypes annotation on the ServletContainerInitializer implementation. If you look at Jersey's implementation

@HandlesTypes({ Path.class, Provider.class, Application.class, ApplicationPath.class })
public final class JerseyServletContainerInitializer
implements ServletContainerInitializer {

You should notice some familiar annotation classes, as well as the Application.class. All these classes matching the criteria, while scanning, are added to the Set passed to the onStartup method.

If you scan the rest of the source code, you will see all the registration being done with all of those classes.

Resteasy uses

@HandlesTypes({Application.class, Path.class, Provider.class})
public class ResteasyServletInitializer implements ServletContainerInitializer

I won't get into to others.

Some source you can look at...

  • JerseyServletContainerInitializer source code
  • ResteasyServletInitializer source code
  • JAX-RS specs

Using a Shiro Filter on JAX-RS without Web.xml

You could add a web descriptor to your application. According to Servlet 3.0 specs, web.xml is optional and just extends and/or overrides your annotation configuration.

You could then define a Shiro filter in your web.xml as usual and JAX-RS related configuration will stay configured via annotations. This is a natural approach.

<filter>
<filter-name>ShiroFilter</filter-name>
<filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
<init-param>
<param-name>configPath</param-name>
<param-value>classpath:shiro.ini</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>ShiroFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>

If it is not an option for you and you don't want web.xml to arise in your application, you could also extend from ShiroFilter class and provide your own annotation configurations in order to get Shiro filter chained:

@WebFilter(initParams = { @WebInitParam(name = "configPath", value = "classpath:shiro.ini") })
public class MyShiroFilter extends ShiroFilter {
}

I didn't test that code above, this is just an approach.

How to deploy a JAX-RS application?

There are a number of options for deploying into a Java EE 6 container (more specifically a Servlet 3.0 implementation):

The simplest is:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_3_0.xsd" version="3.0">
<servlet>
<servlet-name>javax.ws.rs.core.Application</servlet-name>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>javax.ws.rs.core.Application</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
</web-app>

Then all the @Path and @Provider classes found in your web application will be available in the "default" JAX-RS application with a servlet URL pattern of "/rest/*".

If you have one or more classes that extends javax.ws.rs.core.Application, you can specify like so:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_3_0.xsd" version="3.0">
<servlet>
<servlet-name>com.example.jaxrs.MyApplication</servlet-name>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>com.example.jaxrs.MyApplication</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
</web-app>

You may want to do the above in case you wish to only return specific sets of @Path/@Provider classes on a URL (so you could have a second MyApplication2 with a different URL pattern above).

You can also skip the whole web.xml altogether and just annotate your MyApplication class wih @ApplicationPath which will serve as the URL pattern. I would recommend keeping the web.xml in any case because you will probably have to add other information about the web application there anyway.

If you're wondering where the servlet-class comes from, it is automatically added in by the environment. You can get an idea by looking at the Servlet 3.0 ServletContext.

Deployment of a JAX-RS application using web.xml, Servlet 3.0 and Jersey

Update: Since writing this answer, I've found out a way to avoid needing a web.xml on Tomcat using the official Glassfish Jersey implementation. Look here for details.

If you're using a standard Tomcat install (or some other servlet container), AFAIK you can't avoid explicitly telling it what servlets to start in the web.xml file*. Since you have to use web.xml anyway, the simplest way to get restful web services working is to forget extending javax.ws.rs.core.Application entirely and just specify the context path there. You can still use standard jax-rs annotations to declare the actual web services.

web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0"
>
<servlet>
<servlet-name>rest-test</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>com.domain.mypackage</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name> rest-test</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
</web-app>

Two noteworthy points:

  1. You will need to bundle a REST implementation in your WAR file, since servlet containers don't usually contain one. Since Jersey is the reference implementation for JAX-RS, that's the one I'm using in the servlet-class element above. You can replace this with Apache CXF implementation if you want.

  2. The init-param element tells Jersey which of your packages to search for Java files with web service annotations. Edit this to point to your web services. Note that if you opt to use apache CXF instead of Jersey, the stuff needed in any init-param elements will be different. Someone who knows CXF please post what they would be.

If you're using Maven, just add a dependency to jersey-servlet in the dependencies section of your pom.xml file:

<dependencies>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-servlet</artifactId>
<version>1.18.2</version>
</dependency>
...
</dependencies>

After this, declaring your web services is straight forward using the standard JAX-RS annotations in your Java classes:

package com.domain.mypackage;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.GET;
import javax.ws.rs.MatrixParam;
import javax.ws.rs.Path;

// It's good practice to include a version number in the path so you can have
// multiple versions deployed at once. That way consumers don't need to upgrade
// right away if things are working for them.
@Path("calc/1.0")
public class CalculatorV1_0 {
@GET
@Consumes("text/plain")
@Produces("text/plain")
@Path("addTwoNumbers")
public String add(@MatrixParam("firstNumber") int n1, @MatrixParam("secondNumber") int n2) {
return String.valueOf(n1 + n2);
}
}

This should be all you need. If your Tomcat install is running locally on port 8080 and you deploy your WAR file to the context myContext, going to

http://localhost:8080/myContext/rest/calc/1.0/addTwoNumbers;firstNumber=2;secondNumber=3

...should produce the expected result (5).

Cheers!

* Someone please correct me if you know of a way to a add the Jersey servlet to the context in Tomcat without using web.xml--maybe by using a context or life cycle listener?

Glassfish server could not locate resource simple jax-rs Hello World

By looking at the glassfish server application section I found the difference between a working web app and a non working jax-rs web app.

Basically Module javax.ws.rs.core.Application was missing in non working scenario. As I have stated in a comment section. Thanks @Yordan Boev for suggesting to look into admin panel.

Later extracted the *.war file and found that web.xml is not at all present in it.

In order to fix that I have to modify the directory structure as below:

Earlier when things were not working directory structure was as below:

.
├── pom.xml
├── src
│ └── main
│ └── java
│ ├── Hello.java
│ └── WEB-INF
│ └── web.xml

Now in order to make things work I had to put "WEB-INF" inside "webapp" directory and I have to bring "webapp" directory one level up i.e. it should be present under "main" directory as shown below:

.
├── pom.xml
├── src
│   └── main
│   ├── java
│   │   └── Hello.java
│   └── webapp
│   └── WEB-INF
│   └── web.xml


Related Topics



Leave a reply



Submit