Spring MVC+Custom Multy Action support.

Download projects

Test (default tomcat 6.0.14 install)http://localhost:8080/eqs-pim-webapp-1.0.0/login.htm
Enter nothing on login.

This article tries to show how to put together relatively complex web application using Spring MVC and is an attempt to show some of the best practices of web application development and packaging.
To address some of the Java web development problems and to achieve simplicity of Ruby on Rails additional abstract classes and tag libraries have been introduce as part of this sample application.
You can use included source code as template for your future projects.


View and Controller.

Traditionally in MVC2 type of application you would have at least two different Controllers. One which prepares data for View to render and another which will get action request from View and process them.
This definitely is a good practice but does not really adhere to MVC paradigm where there is only one Controller for given View.
What I would like to have is one Controller that handles all rendering and action processing similarly to how it is in RoR.
Here is diagram that shows the difference between MVC2 and our approaches.


Figure 1.

Passing Action from JSP

What I have done is define Action tag that will pass “actionId” and additional action parameters to Spring controller as request parameters through URL query parameters or hidden input during POST action.
Additionally this tag will automatically get its name, title and image for html link or button from resource bundle.
If multiple submit button are required or there is need for HREF to submit a form the tag will auto generate necessary JavaScript to handle “onclick” events.
In most cases you only need to define “actionId” and controller path, the rest are filled automatically.
Here is example action tags:

<action:action action="logoff" targetUri="login.htm"/>

which will be rendered as:

<a href="/eq-pim-webapp/login.htm;jsessionid=B4F536972F875BD1C7A18DF18DC1F36B?action=logoff" title="Logoff and return to Login page" >Logoff</a>

or for form submit:

<action:action action="login" submitForm="true"/>
//targetUri is not used here since the controller to post form when //this action is invoked will be the one defines in form action

which will be rendered as:

<input type="hidden" name="action" value="login">
<input type="submit" value="Login">

Mapping action to a method in controller

This is another feature I like from RoR. Instead of handling every single action in one “handleRequest” method, what “AbstractActionMethodController” (the one that all your controllers will extend from) does is that it maps “actionId” to method names and action parameters to method parameters. This is more like Spring “MultiActionController” but I choose not to use it because I needed better control on how method parameters were getting parsed from request query parameters.

PropetyEditors are used to parse query parameters to non string parameters that action methods are taking.
Like “MultiActionController” it will pass HttpServletRequest and HttpServletResponse objects to action method if it has them as first parameters.

ModelAndView.

Here are some practical rules to help in deciding what to put in Model and how to choose a View.

  • Externalize the decision what View to use into Spring context file.
    Controller should not really care what View to use to render its Model, its job is to process user action and prepare Model for the view.
    This also should make our Controller more reusable.
  • View can be a JSP or another Controller. Usually you need to “forward” to JSP and to “redirect” to Controller. Spring already supports defining “forward” and “redirect” instruction in Spring context file so your Controller does not to hardcode them.
  • Always redirect after successfully processing action.
    If we don’t do this, the browser will still have URL to previously processed action and when user refreshes the browser, we will undesirable get request to re-process the same action again.
    This is true even if we use one Controller per action.
    This rule is related to RedirectAfterPost.
  • On failure when processing action you can use forward or redirect.
    If forward is used, then when user refreshes page you will repeat same action again but this time it is ok since the action failed processing first time and data integrity should have stayed unchanged.
  • Don’t redirect to JSP. Actually if you put your JSP inside WEB-INF directory you cant directly access it with Spring MVC. Instead if you want to redirect to JSP view due to rule #3 or #4 redirect to self controller which in turn will forward to JSP.
    Here is sample configuration which will do it:

    <bean name="/contacts.htm" class="com.equilibriums.pim.controller.contacts.ContactsController">
    <property name="contactsView">
    <value>contacts.page</value>
    </property>
    <property name="newContactView">
    <value>redirect:/contactEdit.htm</value>
    </property>
    <property name="editContactView">
    <value>redirect:/contactEdit.htm</value>
    </property>
    <property name="deleteContactFailedView">
    <value> redirect:/contacts.htm</value>
    </property>
    <property name="deleteContactSuccessView">
    <value>redirect:/contacts.htm</value>
    </property>
    <property name="contactService" ref="contactService"/>
    </bean>

    As, you can see we can change the forward/redirect behavior without modifying our “contacts.htm” controller.

  • Don’t use “forward” instead of “redirect” just to be able to pass non string object in between Controllers. Even though Spring will put Model data into URL as query parameters before it does redirect, it is still not enough since Model data now should be fetched using “request.getParameter” instead of “request.getAttribute” and non String Model data is converted into String using “toString” which in most cases is impossible to deserialize. What I have done, and which is common practice, is to define Spring Handler that will put model data into session and resurrect it in between redirects.
    This is quite usefull when passing messages in between controllers for display.
    In our example application “contact edit” Controller will pass successful saved message to “contacts” Controller.
    Again all this is totally transparent to Controllers.

i18n, resource bundles.

Spring has excellent support for internationalization. The sample app fully supports internalization without requiring to change the code or JSPs.
I have separated resource bundles into 3 categories.

  • Action resource bundles which contain Name, Images and Descriptions for each action.
  • Caption resource bundle which contains text that needs to internationalized in JSPs.
  • Message resource bundle that contain text used in Info or Error messages.

Here is content from each resource bundle files for login pages.

action.properties

# Login
login.login.Name=Login
login.login.ShortDescription=Login to application

login.logoff.Name=Logoff
login.logoff.ShortDescription=Logoff and return to Login page

caption.properties

#these will be used in login form page for labels
login.form.userName=User Name:
login.form.userPassword=User Password:

messages.properties

# LOGIN
login.invalid.user.error=Invalid user name or password.
login.logoff.before.login.info=Not logged in.

This resource files are configured in Spring like this:

<!–  SPECIFY RESOURCE BUNDLE FILES (actions.properties will simply be actions, remember ResourceBundleMessageSource wont be able to load from outside classpath which is classes folder) –>
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basenames">
<list>
<value>/WEB-INF/i18n/app/actions</value>
<value>/WEB-INF/i18n/app/captions</value>
<value>/WEB-INF/i18n/app/messages</value>

<value>/WEB-INF/i18n/login/actions</value>
<value>/WEB-INF/i18n/login/captions</value>
<value>/WEB-INF/i18n/login/messages</value>

<value>/WEB-INF/i18n/contacts/actions</value>
<value>/WEB-INF/i18n/contacts/captions</value>
<value>/WEB-INF/i18n/contacts/messages</value>
</list>
</property>
</bean>

The action descriptions are automatically loaded and used by ActionTag.
The captions, like messages, are rendered using “spring:message” tag, like this:

<spring:message code="login.form.userName" text="User Name:"/>

To pass messages from controller to View layers you need to add them into Model object, like this:

new ModelAndView( logoffView, "info", Arrays.asList( new DefaultMessageSourceResolvable("login.logoff.before.login.info") ) );

messages.jsp will take the list of DefaultMessageSourceResolvable from request and render them using “spring:message” tag.

message.jsp

<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>

<div class="infos">

<c:if test="${info != null}">
<table width="100%" border="0">
<tr>
<td align="left">
<c:forEach items="${info}" var="infoMessage">
<spring:message message="${infoMessage}"/>
<br />
</c:forEach>
</td>
</tr>
</table>
</c:if>

</div>

<div class="errors">

<c:if test="${error != null}">
<table width="100%" border="0">
<tr>
<td>
<c:forEach items="${error}" var="errorMessage">
<spring:message message="${errorMessage}"/>
<br />
</c:forEach>
</td>
</tr>
</table>
</c:if>

<spring:hasBindErrors name="command">
<c:if test="${errors.fieldErrorCount > 0}">
<table width="100%" border="0">
<tr>
<td>
Please fix the following (<c:out value="${errors.fieldErrorCount}"/>) errors in the form marked with red labels!
</td>
</tr>
<tr>
<td>
<c:forEach items="${errors.fieldErrors}" var="fieldError" varStatus="status">
<c:forEach items="${fieldError.codes}" var="fieldErrorCode">
<spring:message code="${fieldErrorCode}" text="" var="fieldErrorText" scope="page"/>
<c:if test="${fieldErrorText != ”}">
<c:out value="${fieldErrorText}"/>
<c:if test="${!status.last}"><br /></c:if>
</c:if>
</c:forEach>
</c:forEach>
</td>
</tr>
</table>
</c:if>
</spring:hasBindErrors>

</div>

Spring themes.

Spring themes allow us to switch in between themes without modifying CSS’s or their import statements.
In our example app we have CSS defined for grey theme and instead of directly importing it in our JSP we use “spring:theme” to dynamically get CSS path using “stylePrefix” (defined in grey.properties”) variable.

Packaging

Most of web application can contain non Java code resources such as JSPs, CSS and images and resource bundle files. I have decided to separate these resources into separate directories based on their functionality. This is totally personal preference and can be customized as needed.

3 comments to Spring MVC+Custom Multy Action support.

  • san

    I want to show the message when a button click.
    how can be use with tag.
    condition is if deptId is 5 i want to show the msg

  • tsolakp

    The code below will assume you have this tag in your JSP:

    and this method in your controller:

    public ModelAndView handleButtonLick(HttpServletRequest request, Long deptId){
    // delete contact from back end and update the list in session */
    if (deptId == 5) return new ModelAndView(successView).addObject(“info”,
    Arrays.asList( new DefaultMessageSourceResolvable( new String[]{“dept5.mmessage”} ) ) );
    return new ModelAndView(successView);
    }

  • tsolakp

    Thats common place to put css and image files. The reason they are not put under WEB-INF is that client browser needs to access them directly and if they are put under WEB-INF it wont since servlet spec wont allow.
    If you really need to hide them put them under WEB-INF and serve them through servlet that will read them as files and write into HttpServletResponse.
    You will also need to have JSP point to the path the servlet is configured agains instead of directly to css or image files.

Leave a Reply

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>