The web module has a simple form for creating and editing a Person, and also has a basic search page. The application also has internationalization and uses Tiles for templating.
Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Simple Spring MVC Web Module Bundle-Description: Simple Spring MVC Web Module Bundle-SymbolicName: org.springbyexample.sdms.simpleForm.webModule Bundle-Version: 1.0.0 Bundle-Vendor: Spring by Example Module-Type: Web Web-ContextPath: simple-form Web-DispatcherServletUrlPatterns: *.html Import-Bundle: com.springsource.org.apache.taglibs.standard, com.springsource.javax.servlet Import-Library: org.aspectj;version="[1.6.0,1.7.0)", org.springframework.spring;version="[2.5.5,3.0.0)", org.hibernate.ejb;version="[3.3.2.GA,3.3.2.GA]", org.apache.tiles;version="[2.0.5.osgi,2.0.5.osgi]" Import-Package: org.springbyexample.sdms.simpleForm.orm.bean;version="[1.0.0,1.1.0]", org.springbyexample.sdms.simpleForm.orm.dao;version="[1.0.0,1.1.0]"
The symbolic name the bundle is deployed under. | |
The version the bundle is deployed under. | |
Declares the module type indicating this is a web module. This is used by the Spring dm Server. | |
The Web-ContextPath property configures the web application's context path. | |
The Web-DispatcherServletUrlPatterns property configures the dispatch servlet to route any matches for the pattern '*.html' to controllers. | |
The Import-Package property imports the the persistence beans and the Person DAO interface from the Person DAO bundle. |
The bundle-context.xml isn't used in this example, but bundle-context-osgi.xml makes a reference to the Person service and exposes it as a bean. The Spring web configuration is in webmvc-context.xml.
The reference to the OSGi service PersonDao
is exposed as a bean
named personDao.
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd"> <reference id="personDao" interface="org.springbyexample.sdms.simpleForm.orm.dao.PersonDao"/> </beans:beans>
This standard Spring MVC configuration file scans for controller classes, creates handlers, configures Tiles, and also internationalization.
The classnameControllerMappings bean enables convention based mappings for reduced configuration.
It is configured to be case sensitive, so a controller called StudentPersonController
would map to the URL
'/studentPerson'. Although it defaults to case insensitive and would then map to the URL '/studentperson'.
The default handler is set with the UrlFilenameViewController
, which will handle any requests not handled by a convention based controller.
<?xml version="1.0" encoding="UTF-8"?> <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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="org.springbyexample.sdms.simpleForm.web.mvc" /> <!-- Enables /[resource]/[action] to [Resource]Controller class mapping --> <bean id="classnameControllerMappings" class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping" p:order="1" p:interceptors-ref="localeChangeInterceptor" p:caseSensitive="true"> <property name="defaultHandler"> <bean class="org.springframework.web.servlet.mvc.UrlFilenameViewController" /> </property> </bean> <!-- Enables annotated POJO @Controllers --> <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" /> <!-- Enables plain Controllers --> <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" /> <bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles2.TilesConfigurer"> <property name="definitions"> <list> <value>/WEB-INF/tiles-defs/templates.xml</value> </list> </property> </bean> <bean id="tilesViewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver" p:order="2" p:viewClass="org.springframework.web.servlet.view.tiles2.TilesView" /> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource" p:basenames="messages" /> <!-- Declare the Interceptor --> <bean id="localeChangeInterceptor" class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" p:paramName="locale" /> <!-- Declare the Resolver --> <bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver" /> </beans>
A simple person form using Spring's custom JSP tags.
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%> <h1><fmt:message key="person.form.title"/></h1> <c:if test="${not empty statusMessageKey}"> <p><fmt:message key="${statusMessageKey}"/></p> </c:if> <c:url var="url" value="/person/form.html" /> <form:form action="${url}" commandName="person"> <form:hidden path="id" /> <fieldset> <div class="form-row"> <label for="firstName"><fmt:message key="person.form.firstName"/>:</label> <span class="input"><form:input path="firstName" /></span> </div> <div class="form-row"> <label for="lastName"><fmt:message key="person.form.lastName"/>:</label> <span class="input"><form:input path="lastName" /></span> </div> <div class="form-buttons"> <div class="button"><input name="submit" type="submit" value="<fmt:message key="button.save"/>" /></div> </div> </fieldset> </form:form>
This is the search page and next to each record displayed is an 'edit' and 'delete' link.
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%> <h1><fmt:message key="person.search.title"/></h1> <table class="search"> <tr> <th><fmt:message key="person.form.firstName"/></th> <th><fmt:message key="person.form.lastName"/></th> </tr> <c:forEach var="person" items="${persons}" varStatus="status"> <tr> <c:set var="personFormId" value="person${status.index}"/> <c:url var="editUrl" value="/person/form.html"> <c:param name="id" value="${person.id}" /> </c:url> <c:url var="deleteUrl" value="/person/delete.html"/> <form id="${personFormId}" action="${deleteUrl}" method="POST"> <input id="id" name="id" type="hidden" value="${person.id}"/> </form> <td>${person.firstName}</td> <td>${person.lastName}</td> <td> <a href='<c:out value="${editUrl}"/>'><fmt:message key="button.edit"/></a> <a href="javascript:document.forms['${personFormId}'].submit();"><fmt:message key="button.delete"/></a> </td> </tr> </c:forEach> </table>
Below is an example of an alternative way to handle a delete following a REST style approach, although the following JavaScript won't work in IE. A library like Dojo should be used for the AJAX call to make the JavaScript browser independent.
The delete method in the controller would be made to only accept an HTTP DELETE. A standard HTML
form can't submit this type of request, but JavaScript's XMLHttpRequest
can.
Ideally in a more complex example, Spring JS would be used to decorate the link with JavaScript and there would be a
URL that would still function with a standard GET or POST in case the user has JavaScript disabled in their browser.
The delete link calls the JavaScript function deletePerson
passing
in the URL that should be called by XMLHttpRequest
using an HTTP DELETE
(instead of the usual GET or POST). Before the delete is processed it is confirmed
by a JavaScript popup.
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%> <script language="javascript"> //<!-- function deletePerson(url){ var confirmed = confirm('<fmt:message key="person.form.confirmDelete"/>'); if (confirmed) { var request = new XMLHttpRequest(); request.open('DELETE', url, true); request.onreadystatechange = function () { window.location.reload(true); }; request.send(null); } } // --> </script> <h1><fmt:message key="person.search.title"/></h1> <table class="search"> <tr> <th><fmt:message key="person.form.firstName"/></th> <th><fmt:message key="person.form.lastName"/></th> </tr> <c:forEach var="person" items="${persons}"> <tr> <c:url var="editUrl" value="/person/form.html"> <c:param name="id" value="${person.id}" /> </c:url> <c:url var="deleteUrl" value="/person/delete.html"> <c:param name="id" value="${person.id}" /> </c:url> <td>${person.firstName}</td> <td>${person.lastName}</td> <td> <a href='<c:out value="${editUrl}"/>'><fmt:message key="button.edit"/></a> <a href="javascript:deletePerson('<c:out value="${deleteUrl}"/>')"><fmt:message key="button.delete"/></a> </td> </tr> </c:forEach> </table>
Because of the classnameControllerMappings bean, the controller is automatically mapped to
handle '/person/*' based on it's name (ex: [Path]Controller). Then the method names are used to match
the rest of the path. So, '/person/form' will match the form
method. The
@ModelAttribute
will be called before each path mapped to the controller. For example,
when '/person/form' is called, the newRequest
method will be called first so an instance
of Person
is available to bind to the form.
The search method doesn't currently use the person model attribute from the newRequest
method, but in a
more advanced example it likely would. For example using the first and last name fields to perform a like search
in the database.
Example 3. Simple Spring MVC PersonController
@Controller public class PersonController { private static final String SEARCH_VIEW_KEY = "redirect:search.html"; private static final String SEARCH_MODEL_KEY = "persons"; @Autowired protected PersonDao personDao = null; /** * For every request for this controller, this will * create a person instance for the form. */ @ModelAttribute public Person newRequest(@RequestParam(required=false) Integer id) { return (id != null ? personDao.findPersonById(id) : new Person()); } /** * <p>Person form request.</p> * * <p>Expected HTTP GET and request '/person/form'.</p> */ @RequestMapping(method=RequestMethod.GET) public void form() {} /** * <p>Saves a person.</p> * * <p>Expected HTTP POST and request '/person/form'.</p> */ @RequestMapping(method=RequestMethod.POST) public void form(Person person, Model model) { if (person.getCreated() == null) { person.setCreated(new Date()); } Person result = personDao.save(person); // set id from create if (person.getId() == null) { person.setId(result.getId()); } model.addAttribute("statusMessageKey", "person.form.msg.success"); } /** * <p>Deletes a person.</p> * * <p>Expected HTTP POST and request '/person/delete'.</p> */ @RequestMapping(method=RequestMethod.POST) public String delete(Person person) { personDao.delete(person); return SEARCH_VIEW_KEY; } /** * <p>Searches for all persons and returns them in a * <code>Collection</code>.</p> * * <p>Expected HTTP GET and request '/person/search'.</p> */ @RequestMapping(method=RequestMethod.GET) public @ModelAttribute(SEARCH_MODEL_KEY) Collection<Person> search() { return personDao.findPersons(); } }