Enterprise Integration Zone is brought to you in partnership with:

I'm Solomon Duskis, NYC consultant and a Java/J2EE guy. I work at Sungard Consulting Services in NYC. The postings on this site are my own and do not necessarily represent the positions, strategies or opinions of my employer. Solomon is a DZone MVB and is not an employee of DZone and has posted 22 posts at DZone. You can read more from them at their website. View Full User Profile

Getting REST Right in Spring 3.0

11.19.2009
| 95345 views |
  • submit to reddit

I've been working on getting a REST + Hibernate application using the goodies found in Spring 3.0 for my upcoming book - Spring Persistence with Hibernate. I'm unfortunately slightly disappointed at the current state of affairs (sorry Arjen and Chris). Spring 3.0 is in RC2, meaning it's had 2 Release Candidates, but the REST stuff is still not 100% right. I was able to work around issues, but I shouldn't have had to deal with the issues I dealt with had Spring REST been a battle tested in production. I'm going to levarage my work show you how to get a basic application up and running so that you can see exactly what I mean. My application is going to use maven, Spring 3.0, Spring MVC (REST), Spring OXM, Jetty, Hibernate and an in-memory database using H2. You can download a ZIP file: gallery.zip.

Maven is the logical first place to start. If you don't have maven installed, then do it. Choose a top level folder for your project (mine is c:\dev\springpersitence\gallery) and run mvn archetype:create -DgroupId=com.smartpants.artwork -DartifactId=gallery to create the project. (You can switch your groupId to match com.yourcompany.project). Update the pom.xml to import a whole bunch of dependencies from a whole bunch of repositories: We're also going to set up an embedded Jetty instance:

<?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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.smartpants.artwork</groupId>
<artifactId>gallery</artifactId>
<packaging>war</packaging>
<name>Artwork Gallery App</name>
<version>0.1</version>
<description />

<build>
<finalName>gallery-web-app</finalName>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>maven-jetty-plugin</artifactId>
<version>6.1.15</version>
<configuration>
<!--
By default the artifactId is taken, override it with
something simple
-->
<contextPath>/</contextPath>
</configuration>
</plugin>
</plugins>
</build>

<properties>
<spring.framework.version>3.0.0.RC2</spring.framework.version>
<org.slf4j.version>1.5.2</org.slf4j.version>
</properties>

<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.framework.version}</version>
<exclusions>
<exclusion>
<groupId>quartz</groupId>
<artifactId>quartz</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
<version>${spring.framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.framework.version}</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>2.1_3</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.0</version>
</dependency>

<!-- Hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-annotations</artifactId>
<version>3.4.0.GA</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate</artifactId>
<version>3.2.6.ga</version>
</dependency>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.11.0.GA</version>
</dependency>

<!-- Database -->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.2.122</version>
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
<version>1.1</version>
</dependency>

<!-- JUnit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.5</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${org.slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>${org.slf4j.version}</version>
</dependency>

<dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>maven-jetty-plugin</artifactId>
<version>6.1.15</version>
</dependency>

</dependencies>

<repositories>
<repository>
<id>ibiblio mirror</id>
<url>http://mirrors.ibiblio.org/pub/mirrors/maven2/</url>
</repository>
<repository>
<id>Springframework milestone</id>
<url>http://maven.springframework.org/milestone</url>
</repository>
<repository>
<id>jboss</id>
<url>http://repository.jboss.org/maven2</url>
</repository>
<repository>
<id>java.net</id>
<url>https://maven-repository.dev.java.net/nonav/repository</url>
<layout>legacy</layout>
</repository>
<repository>
<id>codehaus</id>
<url>http://repository.codehaus.org</url>
</repository>
<repository>
<id>atlassian</id>
<url>http://repository.atlassian.com/maven2</url>
</repository>
<repository>
<id>maven2-repository.dev.java.net</id>
<name>Java.net Repository for Maven</name>
<url>http://download.java.net/maven/2/</url>
<layout>default</layout>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>maven2-repository.dev.java.net</id>
<url>http://download.java.net/maven/2</url>
</pluginRepository>
<pluginRepository>
<id>maven-repository.dev.java.net</id>
<name>Java.net Maven 1 Repository (legacy)</name>
<url>http://download.java.net/maven/1</url>
<layout>legacy</layout>
</pluginRepository>
</pluginRepositories>
</project>

Spring XML Configuration

Next, you'll need a couple of Spring configuration files. We'll put those files in src/resources. The first file, spring-context is going to be to set up hibernate (including classpath scanning for @Entity object), create the database (using the new jdbc namespace), setup Spring classpath scanning (for Spring beans annotated with @Controller, @Repository & etc.) and setup transaction management:

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd">

<context:component-scan base-package="com.smartpants.artwork" />
<jdbc:embedded-database id="dataSource" type="H2" />

<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"
p:dataSource-ref="dataSource" p:lobHandler-ref="defaultLobHandler"
p:packagesToScan="com.smartpants.artwork.domain">
<property name="hibernateProperties">
<value>
hibernate.dialect=org.hibernate.dialect.H2Dialect
hibernate.show_sql=true
hibernate.hbm2ddl.auto=create
</value>
</property>
</bean>

<bean id="defaultLobHandler" class="org.springframework.jdbc.support.lob.DefaultLobHandler" />
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager"
p:sessionFactory-ref="sessionFactory" />
<tx:annotation-driven proxy-target-class="true" />
</beans>

We'll also create another Spring XML file which is more geared towards the REST aspects of our projects. We're going to setup Spring @MVC annotation processing, and XML processing through JAXB.

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jdbc="http://www.springframework.org/schema/jdbc"

xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd">

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

<bean id="marshallingHttpMessageConverter"
class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter"
p:marshaller-ref="jaxb2Marshaller" p:unmarshaller-ref="jaxb2Marshaller" />

<bean id="jaxb2Marshaller" class="com.smartpants.artwork.controllers.MyJaxb2Marshaller">
<property name="classesToBeBound">
<list>
<value>com.smartpants.artwork.domain.Person</value>
<value>com.smartpants.artwork.domain.People</value>
</list>
</property>
</bean>

<context:component-scan base-package="com.smartpants.artwork.controllers" />
<tx:annotation-driven proxy-target-class="true" />

</beans>

There are two things I don't like about this:

1.) Notice that I had to create com.smartpants.artwork.controllers.MyJaxb2Marshaller. That's to get around a problem that OXM has when working with Hibernate proxied objects, which IMHO is a fundamental test case for a Java REST system). Essentially, Spring OXM/JAXB only checks for the @XMLRootElement only on the class itself, and not on the parent classes (like the JAX-RS implementation other Spring subsystems). The hibernate proxy process subclasses your base object (in my case Person) and doesn't add the @XMLRootElement annotation to the subclass. My workaround:

package com.smartpants.artwork.controllers;

import javax.xml.bind.annotation.XmlRootElement;

import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;

public class MyJaxb2Marshaller extends Jaxb2Marshaller {

@Override
public boolean supports(Class clazz) {
return super.supports(clazz) ? true : AnnotationUtils.findAnnotation(clazz,
XmlRootElement.class) != null;
}
}

The Spring framework comes with some great annotation processing utilities that Spring OXM doesn't use... Like I said earlier, this doesn't seem battle tested to me.

2.) Why the heck do I need to create the "classesToBeBound" list? The JAX-RS frameworks don't need anything like that... My guess is that this is a legacy from OXM being a WS-* based system.

Domain objects

My single domain object is pretty simple. I need a Person table/entity in my system. It represents a logical user of my system. It plays the dual role of Hibernate object and an object for my REST interface.

package com.smartpants.artwork.domain;

import java.io.Serializable;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Version;
import javax.xml.bind.annotation.XmlRootElement;

@Entity
@XmlRootElement
public class Person implements Serializable {
private static final long serialVersionUID = 1L;

private Long id;
private String firstName;
private String lastName;
private String username;
private String password;
private int roleLevel;

private Integer version;

public Person() {

}

public Person(String firstName, String lastName, String username,
String password, int roleLevel) {
this.firstName = firstName;
this.lastName = lastName;
this.username = username;
this.password = password;
this.roleLevel = roleLevel;
}

@Id
@GeneratedValue
public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getFirstName() {
return firstName;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getLastName() {
return lastName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public int getRoleLevel() {
return roleLevel;
}

public void setRoleLevel(int roleLevel) {
this.roleLevel = roleLevel;
}

@Version
public Integer getVersion() {
System.out.println("getting version " + getLastName());
return version == null ? 1 : version;
}

public void setVersion(Integer version) {
this.version = version;
}

public enum RoleLevel {
ADMIN(1), GUEST(2), PUBLIC(3);
private final int level;

RoleLevel(int value) {
this.level = value;
}

public static RoleLevel getLevel(String roleName) {
return RoleLevel.valueOf(roleName);
}

public int getLevel() {
return this.level;
}
}

}

Using JAXB generally requires a wrapper object, so I decided to create a People JAXB object:

package com.smartpants.artwork.domain;

import java.io.Serializable;
import java.util.List;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class People implements Serializable {
private static final long serialVersionUID = 1L;

private List<Person> person;

public People() {
// empty constructor required for JAXB
}

public People(List person) {
this.person = person;
}

public List<Person> getPerson() {
return person;
}

public void setPerson(List person) {
this.person = person;
}
}


DAOs

The DAOs aren't exactly new technology, but I'll add them for completeness. Like a good boy, I created a PersonDao interface and PersonDaoHibernate implementation. I've gotten accustomed to Grails/GORM's niceties over the last few months, and the interface kind of bothered me... but that's a post for another time. The PersonDao:

package com.smartpants.artwork.dao;

import java.util.List;

import com.smartpants.artwork.domain.Person;
import com.smartpants.artwork.exception.AuthenticationException;

public interface PersonDao {

public Person getPerson(Long personId);

public void savePerson(Person person);

public List<Person> getPeople();

public Person getPersonByUsername(String username);

public Person authenticatePerson(String user, String password)
throws AuthenticationException;
}

The hibernate implementation (notice the super.setupSessionFactory() hack I had to do to get @Autowired to work):

package com.smartpants.artwork.dao.hibernate;

import static java.lang.String.format;

import java.util.List;

import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import com.smartpants.artwork.dao.PersonDao;
import com.smartpants.artwork.domain.Person;
import com.smartpants.artwork.exception.AuthenticationException;

@Repository
@Transactional
@SuppressWarnings("unchecked")
public class PersonDaoHibernate extends HibernateDaoSupport implements
PersonDao {

@Autowired
public void setupSessionFactory(SessionFactory sessionFactory) {
this.setSessionFactory(sessionFactory);
}

public Person getPerson(Long personId) throws DataAccessException {
return this.getHibernateTemplate().load(Person.class, personId);
}

@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public void savePerson(Person person) throws DataAccessException {
this.getHibernateTemplate().saveOrUpdate(person);
}

public List getPeople() throws DataAccessException {
return this.getHibernateTemplate().find("select people from Person people");
}

public Person getPersonByUsername(String username) {
List people = this.getHibernateTemplate().findByNamedParam(
"select people from Person people "
+ "where people.username = :username", "username", username);
Person person = getFirst(people);
if (person != null)
getHibernateTemplate().evict(person);
return person;
}

public Person authenticatePerson(String username, String password)
throws AuthenticationException {
List validUsers = this.getHibernateTemplate().findByNamedParam(
"select people from Person people "
+ "where people.username = :username "
+ "and people.password = :password",
new String[] { "username", "password" },
new String[] { username, password });

if (validUsers.isEmpty())
throw new AuthenticationException(format("Could not authenticate %s",
username));
return getFirst(validUsers);
}

private static <T> T getFirst(List<T> list) {
return CollectionUtils.isEmpty(list) ? null : list.get(0);
}
}


Controller

I wanted to build a really simple PersonController that did a POST for adding a new Person at "/people", with a GET at "/people" returning all Person objects (wrapped in a People object), and a GET at "/people/{id}". It's pretty straight forward, but is a bit more verbose that a JAX-RS equivalent:

package com.smartpants.artwork.controllers;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.view.RedirectView;

import com.smartpants.artwork.dao.PersonDao;
import com.smartpants.artwork.domain.People;
import com.smartpants.artwork.domain.Person;

@Controller
@RequestMapping("/people")
@Transactional()
public class PersonController {

private PersonDao personDao;

@Autowired
public void setPersonDao(PersonDao personDao) {
this.personDao = personDao;
}

@RequestMapping(method = RequestMethod.GET)
public People getAll() {
return new People(personDao.getPeople());
}

@RequestMapping(value = "/{id}", method = RequestMethod.GET)
@ResponseBody
public Person getPerson(@PathVariable("id") Long personId) {
return personDao.getPerson(personId);
}

@RequestMapping(method = RequestMethod.POST)
@Transactional(readOnly = false)
public View savePerson(@RequestBody Person person) {
personDao.savePerson(person);
return new RedirectView("/people/" + person.getId());
}
}


web.xml

The web.xml is pretty standard fare for Spring MVC:

<?xml version="1.0" encoding="ISO-8859-1"?>

<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/web-app_2_4.xsd"
version="2.4">

<display-name>ArtworkWeb</display-name>
<description>ArtWork</description>
<distributable />

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-context.xml</param-value>
</context-param>

<filter>
<filter-name>hibernateFilter</filter-name>
<filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
</filter>

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

<servlet>
<servlet-name>artworkWeb</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-dispatcher.xml</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>

<!--
- Dispatcher servlet mapping for the web user interface, - refering
to the "image" servlet above.
-->
<servlet-mapping>
<servlet-name>artworkWeb</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>

<filter-mapping>
<filter-name>hibernateFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

</web-app>

Jetty & RESTClient

I use RESTClient to test out HTTP. To run our new application, run mvn jetty:run in a command line in your project (I can't seem to get my Eclipse plugins to execute this for me... but theoretically that should work as well). If you'd like to debug your application, set a system variable MAVEN_OPTS to -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=4000,server=y,suspend=n.

You should get a long log that ends with statements that your /person/ mappings are mapped to PersonController methods. From there you can open up RESTClient, and open up a file with the following contents that set up a POST to /person:

<?xml version="1.0" encoding="UTF-8"?>
<rest-client version="2.3"><request><http-version>1.1</http-version><URL>http://localhost:8080/people</URL><method>POST</method><headers><header key="ACCEPT" value="application/xml"/></headers><body content-type="application/xml" charset="UTF-8"><person>

<firstName>Solomon</firstName>

<lastName>Solomon</lastName>

<username>Solomon</username>

<password>foo</password>

</person></body></request></rest-client>

Conclusion

I couldn't find much documentation, and had to ask the Spring guys and they were pretty helpful. I think that the Spring Framework's REST development approach is sound, but it doesn't seem to be battle tested yet. Meaning, it seems to be missing features that should exist if this subsystem were broadly deployed. It took me way too long to set up this simple test case, and I had to do a relatively ugly workaround for something that should have been there already. I like Spring's approach, but I'd be hesitant to use it on a big scale app, since you usually end up needing a some feature late in the game... the kind of features that don't exist in less mature platforms

Published at DZone with permission of Solomon Duskis, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Solomon Duskis replied on Thu, 2009/11/19 - 11:04am

There does seem to be quite a bit of XML... mostly from Maven and Spring namespaces. IMHO, real projects won't be as skewed as this simple projects in terms of XML to Java ratio. Most of the XML here is a 1 time set up which will enable a 95%+ Java code base.

Ryan Heaton replied on Thu, 2009/11/19 - 11:20am

Personally, I have been quite disappointed with Spring Web Services. I'm not sure why the effort into Spring WS couldn't have been put into better integration with other (in my opinion superior) technologies like the various implementations of JAX-WS and JAX-RS.

 And that's coming from someone who generally considers himself a fan of Spring framework.

Solomon Duskis replied on Thu, 2009/11/19 - 11:48am in response to: Ryan Heaton

I hope that the Spring crew reads this comment... I sadly agree with you as well. However, I do see a tremendous potential with Spring-WS and Spring @MVC REST. Only time will tell if that potential is realized.

Jilles Van Gurp replied on Thu, 2009/11/19 - 2:01pm

We have something similar based on RestEasy (jboss jax-rs implementation). It sucks. Piles and piles of XML, shitloads of duplication between classes, tons of configuration, and yours truly to debug it when it blows up in people's faces because any of the above files has huge potential for bugs to creep in that won't be detected until you try to run your code. And with people copy pasting shit all over the place because they have any clue what the spring magician will actually do with their xml gibberish, this is sort of happening all the time.

 After half a year of this crap, I'm more than ever in the KISS camp. Plain servlets, JDBC, httpclient (client side) and Xpath do the same job with a lot less work and the result can be a lot easier to maintain. Sure you need to know what you are doing but I don't see how that is any different with the above. The only XML will be web.xml (and even that will become optional with servlet 3.0) and your actual API (unless of course you use JSON) and you will have a fraction of the dependencies listed above. Also your application will build and start in no time at all, especially if you ditch maven (timewaster of the century IMHO).

Seriously, this is what rubyists, pythonists, etc. call major bloat. It doesn't really do anything you need while making what you actually want to achieve super tedious, verbose, and error prone. Oh and you throw compile time bug discovery out of the window as well because all of those xml dialects are just scripting languages.

Jose Noheda replied on Thu, 2009/11/19 - 2:39pm

I'd really wish to see the same example but AJAX oriented (that is, using Javascript/JSON instead of XML)

David Karr replied on Thu, 2009/11/19 - 3:06pm in response to: Jose Noheda

Although I'm very interested in the REST implementation in Spring MVC, I very recently implemented a REST service using Apache CXF, which has a strong Spring integration.  I found it was pretty easy to get it going, and configuration was mostly easy, with some hiccups.  Most of my mapping information is done with annotations (although I verified I can do this entirely in Spring XML also).  Specifying that a method returns either XML or JSON is trivial, as it looks at the "Accept" header by default.  I just return an object from the method, and it serializes it depending on that header value.  Out of the box, it uses the Jettison library for JSON serialization and deserialization, but I ended up using Jackson at one point because I preferred the way it allowed changing the serialized JSON structure.  Since then, I think there are additional options with CXF with Jettison that mitigate the concerns that I had.

Arjen Poutsma replied on Thu, 2009/11/19 - 4:15pm

Hey,

First, let me try to explain the rationale behind the Spring-WS project. AFAIK, Spring-WS is the only Java SOAP framework that has a 100% focus on contract-first Web services. As such, it makes the best practice an easier practice, offering features such as XSD to WSDL generation, support for six different XML marshalling frameworks (each with their strength and weaknesses), powerful XPath support and more. I'm not saying you should SOAP everywhere, nor that you should use Spring-WS everywhere, but when wsdl2java code generation just doesn't cut it anymore, I think Spring-WS is the best choice. But I'm obviously biased :).

Second, let me explain the reasons why we decided to expand our own set of annotations, rather than adopt or integrate with JAX-RS. Let me first say that I find JAX-RS one of the best JSRs out there. That said, we introduced the @Controller/@RequestMapping model in Spring 2.5 (November 2007), way before the final release of JAX-RS was out. One year ago, when starting work on Spring 3.0, we wanted to expand this model with REST support. At that time, JAX-RS final was there, and I spent quite a while researching possibilities to support it. In the end, we decided this wasn't in the best interest of existing Spring @MVC users, because

  • JAX-RS annotations overlap with the Spring MVC annotations introduced in 2.5.
  • JAX-RS is semantically different than Spring MVC, i.e. request-scoped by default vs. singleton by default
  • JAX-RS focusses on RESTful web services, while historically Spring MVC had a focus on web sites (though that changed in 3.0)
We also investigated options to add Spring DI to JAX-RS objects, but since all major JAX-RS implementations already had Spring support, there was little added value there.

Thirdly, I really wonder what you mean by "not battle tested yet". The core of MVC has been around since Spring 0.9 in 2003, the @Controller model since 2007, and the OXM abstraction (including Jaxb2Marshaller) since 2006. That does not mean that you will not find issues (this is an RC after all), but I am happy to say that one of the two issues you reported with the Jaxb2Marshaller has been resolved already, and I plan to fix the other tomorrow.

Cheers,

Arjen

cristian andres replied on Thu, 2009/11/19 - 4:49pm in response to: Jose Noheda

I'd wish to see the example but AJAX oriented. Thanks :-)

Solomon Duskis replied on Thu, 2009/11/19 - 5:51pm in response to: Arjen Poutsma

Re: "not battle test yet"
I definitely owe a more detailed explanation.  I'm definitely someone who wants Spring MVC REST to succeed, and I'm commenting from that perspective.  I really wished this stuff was all worked out last year.
You're right about the individual components, but the overall picture of Spring MVC REST doesn't seem to live up to the potential just yet.  That can change quickly, but for now here's a set of more specific criticism:
  1. The REST documentation is still in the works.  I've come to expect a high level of documentation and examples from the Spring Framework. That was one issue I was trying to address with this article... A good example is a tutorial and a feedback mechanism.  Notice the request for AJAX.
  2. As you said, OXM and Spring-WS was built around a contract first approach.  REST simply doesn't (or at least shouldn’t') follow the same paradigms.  There are going to be plenty of REST use cases that will be more problematic on Spring REST given that the underlying technology
  3. I'm an expert in both Spring and REST.  (Perhaps not the foremost expert, but it's still a provable claim ;)).  It still took me a while to chase down the details for this example. Once the REST functionality is more widely used (and it will be), creating this kind of example should take minutes rather than days.  For now, IMHO, it would take an expert consultant (like you... or me :)) to get a moderately complex Spring REST app off the ground.
  4. The shift from 2.5 to 3.0 had significant implications. It took me a while to get the Spring 3.0 maven dependencies. There are fundamental Spring 3.0 architecture shifts that will have to be understood in conjunction with the REST updates.  
On the other hand, here's some great potential in Spring @MVC REST, which I'll write about soon.

Pascal Lalonde replied on Thu, 2009/11/19 - 6:05pm in response to: Solomon Duskis

In my opinion, Spring is open source, it tells everything. I've been using Spring since 2.5, and it simplified my programmer's life. I don't care if I get stuck on some minor issues for days, because I don't get stuck at all on inner things that Spring all do for me. I can now concentrate on business development instead of always recoding the same thing. I don't care having xml's to configure, as long as it works, I honestly think that XML is part of a 2009 programmer's life. I've been in financial market, travelling market, sports market and they all required quite nice deployment scale. I am not a big guy in forums and blogs, but felt that I had to respond to this. Instead of wishes and critics, put your hands in the mud.

Seb Cha replied on Thu, 2009/11/19 - 6:10pm

REST with spring-mvc is definitely more powerful than RESTeasy, because we are not stuck with JAXB.

@Arjen

Can you confirm we can use a JibxMarshaller instead ?


Solomon Duskis replied on Thu, 2009/11/19 - 6:38pm in response to: Seb Cha

@Arjen: RESTEasy (and all JAX-RS) systems have a powerful marshaller plugin mechanism (which you knew about and mimicked).  However, you're right about RESTEasy not having it out of the box... time for new features :).

Solomon Duskis replied on Thu, 2009/11/19 - 6:54pm in response to: Pascal Lalonde

euphreme: I like Spring as well.  My point in writing this was to help the Spring framework along by 1) giving a step by step tutorial for interesting Spring 3.0 2) finding bugs :)

Manuel Jordan replied on Thu, 2009/11/19 - 10:23pm

Solomon

Your book that appear in Amazon does not appear here

http://www.apress.com/book/catalog?category=32

Regards

Solomon Duskis replied on Fri, 2009/11/20 - 7:44am

@Arjen, sorry about that last comment... It should have been directed at bugsan.

Solomon Duskis replied on Fri, 2009/11/20 - 7:45am

@Manuel: Thanks for the heads up.  I'll investigate

Ricardo Borillo replied on Sat, 2009/12/19 - 9:56am

I really love Spring, but i have all my problems solved with Jersey JAX-RS, Spring integration and HyperJAXB3 (for JAXB/JPA management). Now, my model is always consistent and i only have one entity for persistence, marshalling and testing.

Cory Radcliff replied on Thu, 2010/05/27 - 6:45pm

Hi-

Great article. I was following it to help get my first Spring Rest project going, and I ran into a road block. After running your example code as well, I hit the same road block. Any help would be appreciated.

 

When using RestClient to test, I get the following error:

 <h1>HTTP Status 500 - </h1>

<b>exception</b>

javax.servlet.ServletException: Circular view path [people]: would dispatch back to the current handler URL [/gallery-web-app/people] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.)
    org.springframework.web.servlet.view.InternalResourceView.prepareForRendering(InternalResourceView.java:276)
    org.springframework.web.servlet.view.InternalResourceView.renderMergedOutputModel(InternalResourceView.java:215)
    org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:250)
    org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1060)
    org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:798)
    org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:716)
    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:647)
    org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:552)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:690)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
  

Cory Radcliff replied on Fri, 2010/05/28 - 5:30pm in response to: Cory Radcliff

I fixed my code, was a simple addition to the spring config files to give it an appropriate XML view.

Jay Kalathia replied on Mon, 2010/06/07 - 8:57pm in response to: Cory Radcliff

Hi Cory, I am getting the same error. Can you, please, share your spring config files? -Jay K

Gervais Blaise replied on Thu, 2010/12/02 - 11:27am

In many documentation about REST and Spring, we can view howto make a partial REST service with Spring. We can view howto implements CREATE, RETRIEVE and DELETE methods but never the UPDATE method with the PUT Http verb. Can you also explain howto make an UPDATE ?

Srinivas Kanneganti replied on Mon, 2011/02/14 - 12:58am

Could you please explain me how to handle multimedia content (images / video) in SPRING REST WS. The media is stored in DB.

Carla Brian replied on Thu, 2012/06/14 - 6:14pm

Thanks for sharing your tips and all. This is really helpful. I will download this tool now. - Joe Aldeguer

Robert Morschel replied on Tue, 2012/07/31 - 5:12am

IG Group have recently open-sourced their RESTdoclet solution, which is specifically aimed at services built using Spring’s REST framework. We use this internally to automatically generate documentation for all our RESTful services and publish these via an internal web portal. It also includes a fully functional sample REST application which demonstrates a number of Spring 3 REST features.   More info here: http://ig-group.github.com/RESTdoclet/   Robert @ IG Group

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.