spring mvc rest
DESCRIPTION
My Spring MVC and REST talk, updated for NFJS Reston, VA.TRANSCRIPT
Building RESTful applications with
Spring MVC
Craig Walls
E-mail: [email protected] Blog: http://www.springinaction.com Twitter: @habuma Slides: http://www.slideshare.net/habuma
Who Am I?
Java, Spring, and OSGi fanaticPrincipal Consultant with Improving
AuthorXDoclet in Action (Manning)
Spring in Action (Manning)
Modular Java (Pragmatic Bookshelf)
E-mail: [email protected] Blog: http://www.springinaction.com Twitter: habuma
What REST is
Web Serviceswith URLs
NOT!!!
E-mail: [email protected] Blog: http://www.springinaction.com Twitter: habuma
It’s about Resources
Things, not actions
Requests, not demands
Resources, not services
Transfer of state, not RPC
E-mail: [email protected] Blog: http://www.springinaction.com Twitter: habuma
The Pillars of REST
Resources
URIs/URLs
HTTP Methods
Representations
E-mail: [email protected] Blog: http://www.springinaction.com Twitter: habuma
The Pillars of REST
Resources
URIs/URLs
HTTP Methods
Representations
Produced as model data in Spring MVC Controllers
Supported in handler methods by @PathParam
@RequestMapping, HiddenHttpMethodFilter, <form:form>
Rendered by view resolversHTML, XML, JSON, RSS, Atom, PDF, Excel, etc.
Negotiated by ContentNegotiatingViewResolver
E-mail: [email protected] Blog: http://www.springinaction.com Twitter: habuma
Identifying Resources
E-mail: [email protected] Blog: http://www.springinaction.com Twitter: habuma
Demanding URLs
http://www.somestore.com/displayProduct?sku=1234
Direct ImperativeCommand
Resource identifier is pushed into
a query parameter
Not very cacheable orsearch engine friendly
Very service-y
E-mail: [email protected] Blog: http://www.springinaction.com Twitter: habuma
Resourceful URLs
http://www.somestore.com/product/1234
We’re requestinga product’s info
Cacheable
The focus ison the product,not the action
This URL alsoidentifies a product(within a context)
E-mail: [email protected] Blog: http://www.springinaction.com Twitter: habuma
Spring MVC overview
E-mail: [email protected] Blog: http://www.springinaction.com Twitter: habuma
DispatcherServlet
In /WEB-INF/web.xml<servlet> <servlet-name>spitter</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <load-on-startup>1</load-on-startup></servlet>
<servlet-mapping> <servlet-name>spitter</servlet-name> <url-pattern>/app/*</url-pattern></servlet-mapping>
E-mail: [email protected] Blog: http://www.springinaction.com Twitter: habuma
ContextLoaderListener
<context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/spitter-security.xml classpath:service-context.xml classpath:persistence-context.xml classpath:dataSource-context.xml classpath:setup-context.xml </param-value></context-param>
<listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class></listener>
In /WEB-INF/web.xml
E-mail: [email protected] Blog: http://www.springinaction.com Twitter: habuma
UrlRewriteFilter<filter> <filter-name>UrlRewriteFilter</filter-name> <filter-class> org.tuckey.web.filters.urlrewrite.UrlRewriteFilter </filter-class></filter><filter-mapping> <filter-name>UrlRewriteFilter</filter-name> <url-pattern>/*</url-pattern></filter-mapping>
In /WEB-INF/web.xml
<urlrewrite default-match-type="wildcard"> <rule> <from>/resources/**</from> <to>/resources/$1</to> </rule> <rule> <from>/**</from> <to>/app/$1</to> </rule> <outbound-rule> <from>/app/**</from> <to>/$1</to> </outbound-rule> </urlrewrite>
In /WEB-INF/urlrewrite.xml
/product/1234
/app/product/1234
E-mail: [email protected] Blog: http://www.springinaction.com Twitter: habuma
Essential Configuration
Scan for controller (and other) components<context:component-scan base-package="com.habuma.sample.mvc" />
Resolve JSP views<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/></bean>
<mvc:annotation-driven/>
Handle annotation-driven request mappings
E-mail: [email protected] Blog: http://www.springinaction.com Twitter: habuma
Use-case-based controllers
@Controllerpublic class DisplayProductController { @RequestMapping(value="/displayProduct.htm") public String showProductBySku(String sku, Map<String,Object> model) { Product product = // ... lookup product ... model.put("product", product); return "product"; } // ...}
VERB!!!
http://host/myApp/displayProduct.htm?sku=1234
E-mail: [email protected] Blog: http://www.springinaction.com Twitter: habuma
Resource-oriented controller
@Controller@RequestMapping("/product")public class ProductController { @RequestMapping(value="/{sku}", method=GET) public String showProductBySku( @PathVariable String sku, Map<String,Object> model) { Product product = // ... lookup product ... model.put("product", product); return "product"; }}
Noun
http://host/myApp/product/1234
Verb
E-mail: [email protected] Blog: http://www.springinaction.com Twitter: habuma
HTTP Methods
GET, DELETE, PUTIdempotent
Transfers state of resourcePOST
Not IdempotentSends data (not nec. state)
E-mail: [email protected] Blog: http://www.springinaction.com Twitter: habuma
PUT vs. POST
PUTUsed to transfer state to server
Useful when the resource’s URL is knownPOST
Used to send data to serverUseful when the resource’s URL is unknown
or when transferring partial state
E-mail: [email protected] Blog: http://www.springinaction.com Twitter: habuma
Handling DELETE
@Controller@RequestMapping("/product")public class ProductController {
...
@RequestMapping(value="/{sku}", method=DELETE) public String deleteProduct( @PathVariable String sku) { // ... delete product ... return "redirect:home"; }}
http://host/myApp/product/1234
E-mail: [email protected] Blog: http://www.springinaction.com Twitter: habuma
Handling PUT
@Controller@RequestMapping("/product")public class ProductController {
...
@RequestMapping(value="/{sku}", method=PUT) public String saveProduct( @PathVariable String sku, Product product) { // ... save product ... return "redirect:/product/" + sku; }}
http://host/myApp/product/1234
E-mail: [email protected] Blog: http://www.springinaction.com Twitter: habuma
Handling POST
@Controller@RequestMapping("/product")public class ProductController {
...
@RequestMapping(method=POST) public String setPrice(String sku, double price) { // ... update price ... return "redirect:/product/" + sku; }}
http://host/myApp/productFORM DATA: sku=1234
price=5.99
E-mail: [email protected] Blog: http://www.springinaction.com Twitter: habuma
Using PUT/DELETE in formsMost browsers only support GET and POST
Spring’s <form:form> supports all
<form:form method="DELETE" action="http://host/myApp/product/1234">...</form:form>
<form method="POST" action="http://host/myApp/product/1234"> <input type="hidden" name="_method" value="DELETE">...</form>
Hidden method field
E-mail: [email protected] Blog: http://www.springinaction.com Twitter: habuma
HiddenHttpMethodFilter<filter> <filter-name>httpMethodFilter</filter-name> <filter-class> org.springframework.web.filter.HiddenHttpMethodFilter </filter-class></filter>
<filter-mapping> <filter-name>httpMethodFilter</filter-name> <url-pattern>/*</url-pattern></filter-mapping>
POST_method=DELETE
DELETE
Hid
denH
ttpM
etho
dFilt
er
E-mail: [email protected] Blog: http://www.springinaction.com Twitter: habuma
Data representation
Spring MVCViews
RSS/Atom
Excel
JSON
E-mail: [email protected] Blog: http://www.springinaction.com Twitter: habuma
Negotiating Content
ContentNegotiating
ViewResolver
Request
E-mail: [email protected] Blog: http://www.springinaction.com Twitter: habuma
Choosing a view
1. Determine the media type
HTTP Accept header
URL path extension/mediaTypes
format parameter
URL path extension/JAF
2. Find a view resolver thatserves that media type
E-mail: [email protected] Blog: http://www.springinaction.com Twitter: habuma
ContentNegotiatingViewResolver
<bean class="org.springframework.web.servlet.view. ContentNegotiatingViewResolver"> <property name="mediaTypes"> <map> <entry key="htm" value="text/html"/> <entry key="json" value="application/json"/> </map> </property> <property name="defaultViews"> <list> <bean class="org.springframework.web.servlet.view.json. MappingJacksonJsonView" /> </list> </property></bean>
<bean id="spittles" class="com.habuma.spitter.mvc.view.SpittlesAtomView"/>
E-mail: [email protected] Blog: http://www.springinaction.com Twitter: habuma
Spring’s new views
MappingJacksonJsonView
MarshallingView
AbstractAtomFeedView
AbstractRssFeedView
E-mail: [email protected] Blog: http://www.springinaction.com Twitter: habuma
XML Marshalling View
<bean id="oxmMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller" />
<bean id="marshallingHttpMessageConverter" class="org.springframework.http.converter.xml. MarshallingHttpMessageConverter"> <property name="marshaller" ref="oxmMarshaller" /> <property name="unmarshaller" ref="oxmMarshaller" /></bean>
E-mail: [email protected] Blog: http://www.springinaction.com Twitter: habuma
Sample RSS Viewpublic class SpittlesRssView extends AbstractRssFeedView {
@Override protected List<Item> buildFeedItems( Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
@SuppressWarnings("unchecked") List<Spittle> spittles = (List<Spittle>) model.get("spittles"); List<Item> items = new ArrayList<Item>(); for (Spittle spittle : spittles) { Item item = new Item(); item.setTitle(spittle.getText()); item.setPubDate(spittle.getWhen()); item.setAuthor(spittle.getSpitter().getFullName()); items.add(item); } return items; }}
E-mail: [email protected] Blog: http://www.springinaction.com Twitter: habuma
Sample Atom Viewpublic class SpittlesAtomView extends AbstractAtomFeedView {
@Override protected List<Entry> buildFeedEntries( Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { @SuppressWarnings("unchecked") List<Spittle> spittles = (List<Spittle>) model.get("spittles"); List<Entry> entries = new ArrayList<Entry>(); for (Spittle spittle : spittles) { Entry entry = new Entry(); entry.setTitle(spittle.getText()); entry.setCreated(spittle.getWhen()); entry.setAuthors(asList(spittle.getSpitter().getFullName())); entries.add(entry); } return entries; }}
E-mail: [email protected] Blog: http://www.springinaction.com Twitter: habuma
ETags
<filter> <filter-name>etagFilter</filter-name> <filter-class>org.springframework.web.filter.ShallowEtagHeaderFilter</filter-class></filter> <filter-mapping> <filter-name>etagFilter</filter-name> <servlet-name>spitter</servlet-name></filter-mapping>
Returns HTTP 304 if content is unmodifiedif-none-match
(MD5 Hash comparison)
Saves bandwidth
E-mail: [email protected] Blog: http://www.springinaction.com Twitter: habuma
RestTemplate
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
RestTemplate rest = (RestTemplate) context.getBean("restTemplate");Map result = rest.getForObject( "http://localhost:8080/mugbooks/book/1.json", Map.class);
In Spring context: (or yes, it could just be new’d up in Java)
In Java:
E-mail: [email protected] Blog: http://www.springinaction.com Twitter: habuma
Summary
•Spring provides a flexible web MVC framework
•Full support for REST as of Spring 3.0
•Can consume REST services via a template
Thank You
Don’t forget the evals!