Arjen Poutsma introduced REST support in Spring 3 through his two blogs REST in Spring 3: @MVC and REST in Spring 3: RestTemplate. Follow his instructions and Spring 3 PetClinic sample application, I got RESTful Web Service working in my Open Toast Project.
Just to provide a very simple service "get member object by member id", I created a new Web bundle project "org.opentoast.rest" by copying "org.opentoast.web" project. There is only one Java class in new project, MemebrController
:
@Controller public class MemberController { protected final Log log = LogFactory.getLog(getClass()); private MemberManager memberManager; @Autowired public MemberController(MemberManager memberManager){ this.memberManager = memberManager; } @RequestMapping(value="/members/{memberId}", method = RequestMethod.GET) public ModelAndView getMember(@PathVariable("memberId") Long id) { Member m = memberManager.getMemberById(id); ModelAndView mav = new ModelAndView("member"); mav.addObject("member", m); return mav; } }
It provides one RESTful service in the method getMember()
. It is annotated by @RequestMapping
with URI template /members/{memberId}
. Using @PathVariable("memberId")
, the request member id is passed to parameter id
. So with the injected MemberManager
, we can get the Member object by id and pass to returned ModelAndView object. The view name is "member"
, and result member object is put into model with key "member"
as well.
To setup REST support, there are few changes in config file module-context.xml
:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:oxm="http://www.springframework.org/schema/oxm" xsi:schemaLocation=" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd"> <context:component-scan base-package="org.opentoast.rest.controller"/> <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/> <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/> <bean class="org.springframework.web.servlet.view.BeanNameViewResolver"/> <bean id="member" class="org.springframework.web.servlet.view.xml.MarshallingView"> <property name="contentType" value="application/vnd.opentoast.rest+xml"/> <property name="marshaller" ref="marshaller"/> <property name="modelKey" value="member"/> </bean> <oxm:jaxb2-marshaller id="marshaller"> <oxm:class-to-be-bound name="org.opentoast.domain.Member"/> </oxm:jaxb2-marshaller> </beans>
Since we only want to support REST service with customized content type, we don’t need ContentNegotiatingViewResolver
, so only one view resolver is set in the context: org.springframework.web.servlet.view.BeanNameViewResolver
. It will resolve view named "member"
to bean "member"
, which is an instance of org.springframework.web.servlet.view.xml.MarshallingView
. The content type is "application/vnd.opentoast.rest+xml"
, modelKey is "member"
and reference to jaxb2 marshaller bean "marshaller"
.
The web.xml sets servlet with org.springframework.web.servlet.DispatcherServlet
, named opentoastrest
, and maps to /*
:
<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_2_5.xsd" version="2.5"> <display-name>Open Toast RESTful</display-name> <context-param> <param-name>contextClass</param-name> <param-value>com.springsource.server.web.dm.ServerOsgiBundleXmlWebApplicationContext</param-value> </context-param> <context-param> <param-name>contextConfigLocation</param-name> <param-value> /META-INF/spring/module-context.xml /META-INF/spring/osgi-context.xml </param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>opentoastrest</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value></param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>opentoastrest</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
You can check out the code from Google Code by running
svn checkout http://opentoastproject.googlecode.com/svn/tags/opentoast_20090919 opentoast
Then inside opentoast folder, run
mvn package
to build the whole application. Copy the par file at opentoast/org.opentoast.par/target/OpenToast.par
to springsource dm Server pickup folder, and start dm Server.
To test the RESTful Web Service, here is a client example code:
package org.opentoast.client; import java.util.HashMap; import java.util.Map; import org.opentoast.domain.Member; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.web.client.RestTemplate; public class RestClient { RestTemplate restTemplate; public RestClient(RestTemplate template) { this.restTemplate = template; } public void testMember() { Map<String, String> vars = new HashMap<String, String>(); vars.put("memberId", "1"); Member result = restTemplate.getForObject( "http://localhost:8080/opentoastrest/members/{memberId}", Member.class, vars); System.out.println(result); } public static void main(String[] args) { ApplicationContext ac = new ClassPathXmlApplicationContext( "/org/opentoast/client/appContext.xml"); RestClient client = (RestClient)ac.getBean("restClient"); client.testMember(); } }With xml config file
/org/opentoast/client/appContext.xml
:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:oxm="http://www.springframework.org/schema/oxm" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd"> <bean id="restClient" class="org.opentoast.client.RestClient"> <constructor-arg ref="restTemplate"/> </bean> <bean id="restTemplate" class="org.springframework.web.client.RestTemplate"> <property name="messageConverters"> <list> <bean class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter"> <constructor-arg ref="marshaller"/> <property name="supportedMediaTypes"> <list> <bean class="org.springframework.http.MediaType"> <constructor-arg value="application"/> <constructor-arg value="vnd.opentoast.rest+xml"/> </bean> </list> </property> </bean> </list> </property> </bean> <oxm:jaxb2-marshaller id="marshaller"> <oxm:class-to-be-bound name="org.opentoast.domain.Member"/> </oxm:jaxb2-marshaller> </beans>Run this test client program and you will get print out:
[Member 1: Jane Smith]
One big question I have now is "Do I need to expose my rich domain model as REST object model?"
JAXB2 marshaller requires Member class to be annotated with @XmlRootElement
. And my domain model class Member is already annotated with persistent API, so now it looks like:
@XmlRootElement @Entity @Table(name = "MEMBER") public class Member extends BaseEntity { ...
If I don't want REST client to see my rich domain model, I have to use DTO model, and map between those two models. That will defeat the purpose of Domain Driven Design. If I expose my rich domain model classes to client, it will be more problematic when I try to use GWT as client application running inside browser. So far I haven't found a good solution for GWT + REST.