MVC and Spring surf
One of things I missed in Spring Surf are Models and Controllers. I tried many ways to get them working, but still no useful solution. So I chose really simple way to do that – to define another dispatcher and to play with urlrewrite. In this post I’ll describe what I did and as a little bonus you’ll find a breathtaking example 🙂 – YUI based Ajax whisperer.
Let’s start with empty Spring Surf skeleton:
project --topLevelPackage cz.shmoula.controllers surf install perform eclipse
Now add dispatcher – in web.xml we have to define new servlet with name controller, loaded as second and with url-pattern /app (default dispatcher is mapped to /service), which is filtered by urlrewrite.
<servlet> <servlet-name>controller</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>2</load-on-startup> </servlet> <servlet-mapping> <servlet-name>controller</servlet-name> <url-pattern>/app/*</url-pattern> </servlet-mapping>
In next step we’ll need web application config – Spring names them as -servlet.xml prefixed with servlet name. So let’s create controller-servlet.xml in WEB-INF (in STS New/Spring bean configuration file) and add some useful stuff there: for first we want to use annotations for our controllers, so let’s add there two beans – DefaultAnnotationHandlerMapping and AnnotationMethodHandlerAdapter.
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" /> <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />
Then we need to define where are our controllers lying, so let’s define base package.
<context:component-scan base-package="cz.shmoula.controllers.controller" use-default-filters="false"> <context:include-filter expression="org.springframework.stereotype.Controller" type="annotation" /> </context:component-scan>
Last thing is to tell, where will be our views and which suffix they use.
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/" /> <property name="suffix" value=".jsp" /> </bean>
Okay, folks, that’s almost all! Now the interesting part: Writing the controller. Make a class in package we defined earlier. It should extend some AbstractController or another stuff, but it’s not necessary, we’ll simple it! 😉 Put a test method inside:
package cz.shmoula.controllers.controller; @Controller public class AutocompleteController{ @RequestMapping(value = "/test") public String test(Model model){ model.addAttribute("attribute", "Some sample string"); return "test"; } }
That’s our controller. Now we need a view. Previously we defined parameters for view resolver and in controller we’re returning name of our model. Putted together result is: /WEB-INF/jsp/test.jsp
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Test page backed by controller</title> </head> <body> <h1>Hi, what we have there?</h1> <p>${attribute}</p> </body> </html>
Last thing to do is to define some rules for urlrewrite. We’ll try to forward all /app/ requests to our dispatcher and anything else to Default Spring Dispatcher. That’s done by these rules (urlrewrite.xml):
<rule> <from>/app/(.*)</from> <to>/app/$1</to> </rule> <rule> <from>/</from> <to>/service/</to> </rule>
Done? So let’s test it! Run it up and open browser at http://localhost:8080/controllers/app/test. Does it work? I hope so!
That was the first part – worst one, now came pure fun – some functionality. We want to implement some buzzlike thingy as Autocompletion is – whisperer. To keep it simple our model layer will be just a Collection with Strings, so let’s modify our controller with constructor:
private Collection<String> termList = new HashSet<String>(); public AutocompleteController() { termList.add("Prague"); termList.add("Paris"); termList.add("Poznan"); }
Now we need controller code – something returning some results. So at /autocomplete will be routine, which waits for „query“parameter with part of String, which tries to find in termList and return:
@RequestMapping(value = "/autocomplete") public void autocomplete(HttpServletRequest httpRequest, Model model) { String query = httpRequest.getParameter("query"); String response = ""; if (query != null) { for (Iterator<String> i = termList.iterator(); i.hasNext();) { String term = i.next(); if (term.startsWith(query)) response += "n" + term; } } model.addAttribute("response", response); }
Now we have to render our model through something, so add view /WEB-INF/jsp/autocomplete.jsp:
${response }
And now to add all that into widget. Let’s extend calendar widget in /WEB-INF/webscripts/calendar – first add styles and YUI scripts into calendar.get.head.ftl:
<link type="text/css" rel="stylesheet" href="http://yui.yahooapis.com/2.8.2r1/build/autocomplete/assets/skins/sam/autocomplete.css"> <script src="http://yui.yahooapis.com/2.8.2r1/build/yahoo-dom-event/yahoo-dom-event.js"></script> <script src="http://yui.yahooapis.com/2.8.2r1/build/datasource/datasource-min.js"></script> <script src="http://yui.yahooapis.com/2.8.2r1/build/connection/connection-min.js"></script> <script src="http://yui.yahooapis.com/2.8.2r1/build/autocomplete/autocomplete-min.js"></script> <style type="text/css"> #myAutoComplete { width:25em; padding-bottom:2em; } </style>
The last step is to create div and put a script into calendar.get.html.ftl:
<div> <label for="myInput">Autocomplete:</label> <div id="myAutoComplete"> <input id="myInput" type="text"> <div id="myContainer"></div> </div> </div> <script type="text/javascript"> YAHOO.example.BasicRemote = function() { var oDS = new YAHOO.util.XHRDataSource("/controllers/app/autocomplete"); oDS.responseType = YAHOO.util.XHRDataSource.TYPE_TEXT; oDS.responseSchema = { recordDelim: "n", fieldDelim: "t" }; oDS.maxCacheEntries = 5; var oAC = new YAHOO.widget.AutoComplete("myInput", "myContainer", oDS); return { oDS: oDS, oAC: oAC }; }(); </script>
And that should be all, now let’s test it, if it works! So open http://localhost:8080/controllers/calendar and voila…
Ok, that’s all for this post, looks like it works! So you can download backup of this code somewhere around here and enjoy it on your own devbox. Keep looking forward for next stuff, maybe using facebook login with Spring social, who knows ;-).
Hi,
could you show your urlrewrite.xml ?
I followed your tutorial but when I try to access:
http://localhost:8080/controllers/app/test I get this error:
javax.servlet.ServletException: Could not resolve view with name ‚app/test‘ in servlet with name ‚Spring MVC Dispatcher Servlet‘
org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1042)
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:621)
javax.servlet.http.HttpServlet.service(HttpServlet.java:722)
org.tuckey.web.filters.urlrewrite.NormalRewrittenUrl.doRewrite(NormalRewrittenUrl.java:195)
org.tuckey.web.filters.urlrewrite.RuleChain.handleRewrite(RuleChain.java:159)
org.tuckey.web.filters.urlrewrite.RuleChain.doRules(RuleChain.java:141)
org.tuckey.web.filters.urlrewrite.UrlRewriter.processRequest(UrlRewriter.java:90)
org.tuckey.web.filters.urlrewrite.UrlRewriteFilter.doFilter(UrlRewriteFilter.java:417)