A Comparison of Spring MVC and JAX-RS

2014/07/23 16:01
阅读数 154

Introduction

Over the past few years REST has become an important concept influencing the design of web frameworks, web protocols and applications. If you haven't been exposed to it, see this briefintroduction among many others you can find.

The ever growing importance of REST is not surprising considering the need for companies to expose web APIs that should be as simple and as deeply rooted in the architecture of the web as can be. Rich browser clients communicating via Ajax also seek much of the same. And there is no reason why any web application can't benefit from the architectural principles that have helped to scale the world wide web.

JAX-RS (JSR 311) is the Java API for RESTful Web Services. JAX-RS was created with the participation of Roy Fielding who defined REST in his thesis. It provides those seeking to build RESTful web services an alternative to JAX-WS (JSR-224). There are currently 4 JAX-RS implementations available, all of which provide support for using Spring. Jersey is the reference implementation and the one used in this article.

If you're a developer working with Spring you may be wondering (or you may have been asked) how Spring MVC compares to JAX-RS? Furthermore, if you have an existing Spring MVC application that uses the controller class hierarchy (SimpleFormController and friends), you may not be aware of the comprehensive REST support now available in Spring MVC.

This article will walk you through the REST features available in Spring 3 and will relate them to JAX-RS. Hopefully the exercise will help you to understand the similarities and the differences between the two programming models.

Before starting out, it may be helpful to point out that JAX-RS targets the development of web services (as opposed to HTML web applications) while Spring MVC has its roots in web application development. Spring 3 adds comprehensive REST support for both web applications and web services. This article however will focus on features relating to the development of web services. I believe this approach will make it easier to discuss Spring MVC in the context of JAX-RS.

A second note to make is that the REST features we'll be discussing are a part of the Spring Framework and are a continuation of the existing the Spring MVC programming model. Hence there is no "Spring REST framework" as you might be tempted to say. It's just Spring and Spring MVC. What this practically means is that if you have an existing Spring application you can expect to be able to use Spring MVC both for creating an HTML web layer and a RESTful web services layer.

About The Code Snippets

The code snippets shown throughout the article assume a simple domain model with two JPA-annotated entities named Account and Portfolio where an Account has many Portfolios. The persistence layer is configured with Spring and consists of a JPA repository implementation for retrieving and persisting entity instances. Jersey and Spring MVC will be used to build a web services layer for servicing client requests by calling the underlying Spring-managed application.

Bootstrap And Wire The Web Layer

We will assume the use of Spring for dependency injection in both the Spring MVC and the JAX-RS scenarios. The Spring MVC DispatcherServlet and the Jersey SpringServlet will delegate requests to Spring-managed REST-layer components (controllers or resources) that will in turn be wired with either business or persistence components as follows:

Both Jersey and Spring MVC will rely on Spring's ContextLoaderListener to load business and persistence components such as the JpaAccountRepository:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        classpath:META-INF/spring/module-config.xml
    </param-value>
</context-param>

<listener>
    <listener-class>
        org.springframework.web.context.ContextLoaderListener
    </listener-class>
</listener>

The ContextLoaderListener can be used in the context of any web or REST framework.

Set Up Spring-Managed JAX-RS Resources In Jersey

Jersey provides support for using Spring in the REST layer. It can be enabled in two simple steps (actually three if you include adding the build dependency on the maven artifact com.sun.jersey.contribs:jersey-spring).

Step one: add the following to your web.xml to ensure JAX-RS root resources can be created with Spring:

<servlet>
    <servlet-name>Jersey Web Application</servlet-name>
    <servlet-class>
        com.sun.jersey.spi.spring.container.servlet.SpringServlet
    </servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>Jersey Web Application</servlet-name>
    <url-pattern>/resources/*</url-pattern>
</servlet-mapping>

Step two: declare root JAX-RS resource classes with some Spring and some JAX-RS annotations:

@Path("/accounts/")
@Component
@Scope("prototype")
public class AccountResource {

    @Context
    UriInfo uriInfo;

    @Autowired
    private AccountRepository accountRepository;

}

The following is a description of the annotations:
@Component declares AccountResource as a Spring bean.
@Scope makes it a prototype Spring bean that's instantiated every time it is used (i.e. on each request). 
@Autowired requests a reference to an AccountRepository, which Spring will provide. 
@Path is a JAX-RS annotation that declares AccountResource as a "root" JAX-RS resource.
@Context is also a JAX-RS annotation requesting the injection of the request-specific UriInfo object.

JAX-RS has the notion of "root" resources (marked with @Path) and sub-resources. In the example above AccountResource is a root resource that handles paths starting with "/accounts/". Methods within AccountResource like getAccount() need only declare paths relative to the type-level path:

@Path("/accounts/")
@Component
@Scope("prototype")
public class AccountResource {

    @GET
    @Path("{username}")
    public Account getAccount(@PathParam("username") String username) {

    }

}

The path to "/accounts/{username}" where username is a path parameter and can take the value of any user name for a given account, will be handed to the getAccount() method.

Root resources are instantiated by the JAX-RS runtime (Spring in this case). Sub-resources on the other hand are instantiated by the application. For example to handle requests for "/accounts/{username}/portfolios/{portfolioName}", the AccountResource, as identified by the leading portion of the path ("/accounts"), will create an instance of a sub-resource to which the request will be delegated:

@Path("/accounts/")
@Component
@Scope("prototype")
public class AccountResource {

    @Path("{username}/portfolios/")
    public PortfolioResource getPortfolioResource(@PathParam("username") String username) {
        return new PortfolioResource(accountRepository, username, uriInfo);
    }

}

PortfolioResource itself is declared without annotations and will have all its dependencies passed down by the parent resource:

public class PortfolioResource {

    private AccountRepository accountRepository;
    private String username;
    private UriInfo uriInfo;

    public PortfolioResource(AccountRepository accountRepository, String username, UriInfo uriInfo) {
        this.accountRepository = accountRepository;
        this.username = username;
        this.uriInfo = uriInfo;
    }

}

Root and sub-resources in JAX-RS set up a processing chain that involves multiple resources:

Keep in mind resource classes are web service layer components and should remain focused on web service related processing such as translating input, preparing the response, setting the response code, etc. It's also worth pointing out that the practicalities of separating web services logic from business logic often necessitate wrapping business logic into a single method that serves as a transaction boundary.

Setting Up Spring MVC @Controller Classes

For Spring MVC we'll set up the DispatcherServlet with a contextConfigLocation parameter that points to Spring MVC configuration:

<servlet>
    <servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/spring/*.xml
        </param-value>
    </init-param>
</servlet>

To start off the annotation based programming model in Spring MVC (@MVC) requires only a small amount of configuration. This component-scan element tells Spring where to find @Controller-annotated classes.

<context:component-scan base-package="org.springframework.samples.stocks" />

After that we can declare an AccountController as follows:

@Controller
@RequestMapping("/accounts")
public class AccountController {

    @Autowired
    private AccountRepository accountRepository;

}

The @RequestMapping annotation maps this controller to all requests starting with "/accounts". Methods in AccountController like getAccount() need only declare paths relative to that:

@RequestMapping(value = "/{username}", method = GET)
public Account getAccount(@PathVariable String username) {

}

Spring MVC does not have the notion of root resources and sub-resources. Hence every controller is managed by Spring and not by the application:

@Controller
@RequestMapping("/accounts/{username}/portfolios")
public class PortfolioController {

    @Autowired
    private AccountRepository accountRepository;

}

Requests for "/accounts/{username}/portfolios" are delegated directly to the PortfolioController without any involvement from the AccountController. It's worth pointing out that this request could also be handled directly in the AccountController thereby removing the need for a PortfolioController.

Web Layer Component Scope

In JAX-RS, AccountResource was declared with per-request semantics. This is the default recommendation of JAX-RS. It enables the injection and storing of request-specific data (request headers, query parameters, cookie values) in the resource class itself. This applies to root-level resources, which are managed by JAX-RS. Sub-resources are instantiated by the application and do not benefit directly from this.

In Spring MVC controllers are always created as singletons. They accept request-specific data as method arguments, which is also an option in JAX-RS where resources can also be created as singletons.

Mapping Requests To Methods

Next we'll take a look at how Spring MVC and JAX-RS map requests to methods. Both @Path and @RequestMapping support extracting path variables from the URL:

@Path("/accounts/{username}")
@RequestMapping("/accounts/{username}")

Both frameworks also support the use of regular expressions for extracting path variables:

@Path("/accounts/{username:.*}")
@RequestMapping("/accounts/{username:.*}"

Spring MVC's @RequestMapping allows matching requests based on the presence or absence of query parameters:

@RequestMapping(parameters="foo")
@RequestMapping(parameters="!foo")

or based on the value of a query parameter:

@RequestMapping(parameters="foo=123")

@RequestMapping also allows matching requests based on presence (or absence) of headers:

@RequestMapping(headers="Foo-Header")
@RequestMapping(headers="!Foo-Header")

or based on the value of a header:

@RequestMapping(headers="content-type=text/*")

Working With Request Data

HTTP requests contain data that applications need to extract and work with. That includes HTTP headers, cookies, query string parameters, form parameters, or more extensive data sent in the body of the request (XML, JSON, etc.) In RESTful applications the URL itself can also carry valuable information such as which resource is being accessed via path parameters or what content type is needed via file extensions (.html, .pdf) The HttpServletRequest provides low level access to all that but doing so can be quite verbose.

Request Parameters, Cookies, and HTTP Headers

Spring MVC and JAX-RS have annotations for extracting such HTTP request values:

@GET @Path
public void foo(@QueryParam("q") String q, @FormParam("f") String f, @CookieParam("c") String c,
    @HeaderParam("h") String h, @MatrixParam("m") m) {
    // JAX-RS
}

@RequestMapping(method=GET)
public void foo(@RequestParam("q") String q, @CookieValue("c") String c, @RequestHeader("h") String h) {
    // Spring MVC
}

The above annotations are very similar except that JAX-RS supports the extraction of matrix parameters and has separate annotations for query string and form parameters. Matrix parameters are not commonly seen. They're similar to query string parameters but applied to specific path segments (e.g. GET /images;name=foo;type=gif). Form parameters will be discussed shortly.

JAX-RS allows placing the above annotations on fields and setters assuming a resource is declared with per-request scope.

Spring MVC has a space-saving feature that allows omitting names from the above annotations as long as the names match to the Java argument names. For example a request parameter called "q" requires the method argument to be called "q" as well:

public void foo(@RequestParam String q, @CookieValue c, @RequestHeader h) {

}

This is rather handy in method signatures that are already stretched long due to the use of annotations on every argument. Also bear in mind this feature requires that code isn't compiled without debugging symbols.

Type Conversion And Formatting Of HTTP Request Values

HTTP request values (headers, cookies, parameters) are invariably String-based and that requires parsing.

JAX-RS handles parsing of request data by looking for a valueOf() method or a constructor accepting a String in the custom target type. To be precise JAX-RS supports the following types of annotated method arguments including path variables, request parameters, HTTP header values, and cookies:

  • Primitive types.

  • Types that have a constructor that accepts a single String argument.

  • Types that have a static method named valueOf with a single String argument.

  • List<T>, Set<T>, or SortedSet<T>, where T satisfies 2 or 3 above.

In Spring 3 all of the above is supported as well. In addition, Spring 3 provides a new type conversion and formatting mechanism that can be annotation-driven.

Form Data

As mentioned above JAX-RS makes a distinction between query string parameters and form parameters. While Spring MVC has just one @RequestParam, it also provides a data binding mechanism for dealing with form input that Spring MVC users will be familiar with.

For example one option to process a form submitting 3 pieces of data is to declare a method with 3 arguments:

@RequestMapping(method=POST)
public void foo(@RequestParam String name, @RequestParam creditCardNumber, @RequestParam expirationDate) {
    Credit card = new CreditCard();
    card.setName(name);
    card.setCreditCardNumber(creditCardNumber);
    card.setExpirationDate(expirationDate);

}

However as the size of the form grows this approach becomes impractical. With data binding, a form object of an arbitrary structure including nested data (billing address, mailing address, etc.) can be created, populated and passed in by Spring MVC:

@RequestMapping(method=POST)
public void foo(CreditCard creditCard) {
    // POST /creditcard/1
    //		name=Bond
    //		creditCardNumber=1234123412341234
    //		expiration=12-12-2012
}

Form processing is important for working with web browsers. Web service clients on the other hand are more likely to submit XML or JSON-formatted data in the body of the request.

Processing Request Body Data

Both Spring MVC and JAX-RS automate the processing of request body data:

@POST
public Response createAccount(Account account) {
    // JAX_RS
}

@RequestMapping(method=POST)
public void createAccount(@RequestBody Account account) {
    // Spring MVC
}

Request Body Data In JAX-RS

In JAX-RS, entity providers of type MessageBodyReader are responsible for converting request body data. JAX-RS implementations are required to have a JAXB MessageBodyReader. Custom MessageBodyReader implementations annotated with @Provider may also be provided.

Request Body Data in Spring MVC

In Spring MVC you add @RequestBody to a method argument if you want it to be initialized from request body data. This is in contrast to initialization from form parameters as was shown earlier.

In Spring MVC, classes of type HttpMessageConverter are responsible for converting request body data. A marshalling Spring OXM HttpMessageConverter is provided out of the box. It enables use of JAXB, Castor, JiBX, XMLBeans, or XStream. There is also a JacksonHttpMessageConverter for use with JSON.

HttpMessageConverter types are registered with the AnnotationMethodHandlerAdapter, which adapts incoming requests to Spring MVC @Controllers. Here is the configuration:

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" >
    <property name="messageConverters" ref="marshallingConverter"/>
</bean>

<bean id="marshallingConverter" class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
    <constructor-arg ref="jaxb2Marshaller"/>
    <property name="supportedMediaTypes" value="application/vnd.stocks+xml"/>
</bean>

<oxm:jaxb2-marshaller id="jaxb2Marshaller"/>

The following diagram illustrates this configuration:

The new mvc custom namespace in Spring 3 automates the above configuration. All you need to do is add:

    <mvc:annotation-driven />

That will register a converter for reading and writing XML if JAXB is on the classpath and a converter for reading and writing JSON if Jackson is on the classpath.

 

Preparing The Response

A typical response may involve selecting the response code, setting HTTP response headers, adding data to the body of the response, as well as dealing with exceptions.

Setting Response Body Data With JAX-RS

To add data to the body of the response in JAX-RS, you simply return the object from the resource method:

@GET
@Path("{username}")
public Account getAccount(@PathParam("username") String username) {
    return accountRepository.findAccountByUsername(username);
}

JAX-RS will find an entity provider of type MessageBodyWriter that can convert the object to the required content type. JAX-RS implementations are required to have a JAXB MessageBodyWriter. Custom MessageBodyWriter implementations annotated with @Provider may also be provided.

Setting Response Body Data With Spring MVC

In Spring MVC a response is prepared through a view resolution process. This allows selecting from a range of view technologies. When working with web service clients, however, it may be more natural to bypass the view resolution process and use the object returned from the method :

@RequestMapping(value="/{username}", method=GET)
public @ResponseBody Account getAccount(@PathVariable String username) {
    return accountRepository.findAccountByUsername(username);
}

When a controller method or (its return type) is annotated with @ResponseBody, its return value is processed with an HttpMessageConverter and then used to set the response of the body. The same set of HttpMessageConverter types used for request body arguments is also used for the body of the response. Hence no further configuration is required.

Status Codes & Response Headers

JAX-RS provides a chained API for building a response:

@PUT @Path("{username}")
public Response updateAccount(Account account) {
    // ...
    return Response.noContent().build();	// 204 (No Content)
}

This can be used in conjunction with a UriBuilder to create links to entities for the Location response header:

@POST
public Response createAccount(Account account) {
    // ...
    URI accountLocation = uriInfo.getAbsolutePathBuilder().path(account.getUsername()).build();
    return Response.created(accountLocation).build();
}

The uriInfo used above is either injected into a root resources (with @Context) or passed down from parent to sub-resource. It allows appending to the path of the current request.

Spring MVC provides an annotation for setting the response code:

@RequestMapping(method=PUT)
@ResponseStatus(HttpStatus.NO_CONTENT)
public void updateAccount(@RequestBody Account account) {
    // ...
}

To set a location header you work directly with the HttpServletResponse objects:

@RequestMapping(method=POST)
@ResponseStatus(CREATED)
public void createAccount(@RequestBody Account account, HttpServletRequest request,
				HttpServletResponse response) {
    // ...
    String requestUrl = request.getRequestURL().toString();
    URI uri = new UriTemplate("{requestUrl}/{username}").expand(requestUrl, account.getUsername());
    response.setHeader("Location", uri.toASCIIString());
}

Exception Handling

JAX-RS allows resource methods to throw exceptions of type WebApplicationException, which contain a Response. The following example code translates a JPA NoResultException to the Jersey-specific NotFoundException, which causes the return of a 404 error:

@GET
@Path("{username}")
public Account getAccount(@PathParam("username") String username) {
    try {
        return accountRepository.findAccountByUsername(username);
    } catch (NoResultException e) {
        throw new NotFoundException();
    }
}

Instances of WebApplicationException encapsulate the logic necessary to produce a specific response but the exceptions must be caught in each individual resource class method.

Spring MVC allows defining controller-level methods for dealing with exceptions:

@Controller
@RequestMapping("/accounts")
public class AccountController {

    @ResponseStatus(NOT_FOUND)
    @ExceptionHandler({NoResultException.class})
    public void handle() {
        // ...
    }
}

If any controller methods throw JPA's NoResultException, the above handler method will be invoked to handle it and return a 404 error. That allows each controller to deal with exceptions as it sees fit from a single place.

Summary

Hopefully this article has helped you to see how Spring MVC can be used for building RESTful web services and how that compares to the JAX-RS programming model.

If you're a Spring MVC user you're probably developing HTML web applications. REST concepts apply to web services and to web applications alike especially in rich client interactions. In addition to the features discussed in this article, Spring 3 also adds support for RESTful web applications. Here is a partial list: a new JSP custom tag for building URLs from URL templates, a servlet filter for simulating form submissions based on HTTP PUT and DELETE, a ContentTypeNegotiatingViewResolver for automating view selection based on content type, new view implementations, and more. Last but not least is the updated and much improved Spring documentation.

About the Author

Rossen Stoyanchev is a senior consultant at SpringSource. In his career he has worked on a trading application, an investment accounting system and an e-commerce web application, amongst others. Within SpringSource Stoyanchev focuses on web technologies including consulting, training, and content development of the "Rich-Web Development With Spring" course, which enables attendees to become certified Spring web application developers.


展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部