Spring MVC

Version: Spring 3.1

The goal is not to explain how Spring MVC is working because everyone can read. I would like to emphasis certain aspects of the library and best practices.

Keep it short

Spring controller has only 3 responsibilities:

  1. parse request to convert to object model
  2. delegate operation to services
  3. format response (HTML, JSON, PDF)

No any further responsibilities should be included. It means that controller code should be really short. not dealing with any of business related functionality.

Parsing date

Spring MVC provide lots of functionality to parse request to business object. There is no any problem parsing simple attributes and populating into objects. But sometimes it is not enough. For example when you want to accept Date and times you expect to got java.util.Data. In that case you must register custom editor:

1@InitBinder
2protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
3  SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");
4  binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
5  //for specific attribute only
6  binder.registerCustomEditor(Set.class, "place.room_uids" , new CommaDelimitedStringToIntegerEditor());
7}

Use the power of naming convention

When you implement mapping of URL to a method name use certain naming convention which helps you navigating in the code.

Example of not so useful naming convention:

1          @RequestMapping(value = "searchinterpreter.do")
2          public String displayForm(...) {
3                ...
4          }

What is the problem? From pure implementation point of view nothing. this code just maps searchinterpreter.do to call displayForm method. But for long term is cause navigation overhead. To be effective it is needed to jump to the proper code segment to do stuffs. But not knowing where to jump you could not jump there directly but have to search.

See the following, better naming convention:

1          @RequestMapping(value = "remove_all.do")
2          public String remove_all(...) {
3                ...
4          }

This naming convention will help to jump to the proper function just after having a look at the URL.

Another convention to indicate AJAX calls. this example using underscore to indicate that the function is used in AJAX requests:

1          @RequestMapping(value = "_clean.do")
2          @ResponseBody
3          public String _clean(...) {
4                ...
5          }

Use Redirect

After performing any operation which modifies a business entities (e.g: save) you have an option to render response from that controller (controller will be responsible to render the result). Using this solution your URL will be changed to (in this case) save.do there are problems:

Better solution to use redirect.

1          @RequestMapping(value = "remove_all.do")
2          public String remove_all(ModelMap model) {
3                Clipboard c = ClipboardHelper.get();
4                c.clearAllItems();
5                return "redirect:list.do";
6          }

In this example after executing the business it will send back a redirect response to the browser. After getting the response the browser will make a new request to the server with URL list.do. in this example the controller mapped to list.do is responsible for displaying the business model.

If you want to publish some message when redirecting you can use RedirectAttributes of the Spring framework.

1          @RequestMapping(value = "remove_all.do")
2          public String remove_all(ModelMap model, RedirectAttributes redirect) {
3                Clipboard c = ClipboardHelper.get();
4                c.clearAllItems();
5                redirect.addAttribute("message", "Message to display after redirect");
6                return "redirect:list.do";
7          }

JSON response

Producing JSON response is easy. Either let Spring serializing response object or you can compose your own response using Gson library.

Example 1:

1  @RequestMapping(value = "add.do")
2          public @ResponseBody
3          GenericResponse add(@ModelAttribute ClipboardData data, HttpServletRequest req, ModelMap model) {
4                ...
5          }

Example 2:

1        @RequestMapping(value = "clean.do")
2          @ResponseBody
3          public String _clean(@RequestParam(value = "ids[]", required = true) List<Long> ids, ModelMap model) {
4                ...
5                return new Gson().toJson(ImmutableMap.of("status","OK"));
6          }

JSON request

When working on dynamic user interface you might face the problem of composing complex request objects. such a complex objects are difficult to map to java beans using plain HTML forms and field names. Just too complex.

In that case you can parse input string directly using Gson:

1          @RequestMapping(value = "save_assignment.do", method = RequestMethod.POST)
2          @ResponseBody
3          public String save_assignment(@RequestParam(value = "data", required = true) String data, ModelMap model) {
4                log.debug("AssignmentRequest:{}", data);//which is in json string format
5                AssignmentRequest req = GsonHelper.fromJson(jsonString, AssignmentRequest.class);
6                ...

Tiles of webpage

Purpose, advantages and disadvantages of Tiles can be found in documentation.

but you can use tiles effectively buy applying the following concepts

tiles naming convention

Use the same naming convention as mentioned before in spring controller.

Controller:

 1        @Controller
 2        @RequestMapping(value = "/session")
 3        public class SessionController {
 4
 5          @RequestMapping(value = "list.do")
 6          public String list(...) {
 7                ...
 8            return "list-session";
 9          }
10        ...
11        }

Tiles config:

1          <definition name="list-session" extends="base.definition">
2                <put-attribute name="body" value="/view/session/list-session.jsp" />
3                ...
4          </definition>

You can see how the phrase list is displaying everywhere to find all components in a simple and straightforward way. when using such a convention you do not need to think about which jsp page to open even if you just now about the URL only.

Include dynamic CSS and JS dependency.

When developing complex pages it is normal that for certain pages you need some extra library or CSS to include. But when using tiles you have a framework to setup all core JS or CSS dependencies. But there are solution to that.

Base tiles template definition:

1          <definition name="base.definition" template="/view/application.jsp">
2                <put-attribute name="header" value="/view/header.jsp" />
3                <put-attribute name="menu" value="/view/menu.jsp" />
4                <put-attribute name="body" value="" />
5                <put-attribute name="footer" value="/view/footer.jsp" />
6                <put-list-attribute name="js_files"></put-list-attribute>
7                <put-list-attribute name="css_files"></put-list-attribute>
8          </definition>

Specific page definition:

 1          <definition name="view-session" extends="base.definition">
 2                <put-attribute name="title" value="View Session" />
 3                <put-attribute name="body" value="/view/session/view-session.jsp" />
 4                <put-list-attribute name="js_files">
 5                        <add-attribute>/js/view-session.js</add-attribute>
 6                </put-list-attribute>
 7                <put-list-attribute name="css_files">
 8                  <add-attribute>/view/assets/multiselect/jquery.multiselect.css</add-attribute>
 9                  <add-attribute>/view/assets/multiselect/jquery.multiselect.filter.css</add-attribute>
10                </put-list-attribute>    
11          </definition>

And the main JSP page (call it application.jsp)

 1                ...
 2                <!-- CSS which need for all the pages -->
 3                <link rel="stylesheet" type="text/css" href="<%=request.getContextPath()%>/view/assets/app/css/app.css" />
 4                <!-- css files needed for specifc pages only-->
 5                <tiles:useAttribute name="css_files" id="csslist" classname="java.util.List" />
 6                <c:forEach var="fileName" items="${csslist}">
 7                        <link rel="stylesheet" type="text/css" href="<%=request.getContextPath()%><c:out value='${fileName}' />" />
 8                </c:forEach>
 9                <!-- javascript required by all the pages -->
10                <jwr:script src="/js/common.js" useRandomParam="false" />
11                <!-- JS libraries needed for specific pages only -->
12                <tiles:useAttribute name="js_files" id="jslist" classname="java.util.List" />
13                <c:forEach var="fileName" items="${jslist}">
14                        <script type="text/javascript" src="<%=request.getContextPath()%><c:out value='${fileName}' />"></script>
15                </c:forEach>
16                <!-- remainng tiles template -->
17                <title><tiles:insertAttribute name="title" ignore="true" /></title>
18                </head>
19                <body>
20                        <div class="main">
21                                <div class="content">
22                                        <tiles:insertAttribute name="body" />
23                                </div>
24                                <tiles:insertAttribute name="footer" />
25                        </div>
26                </body>
27                </html>

Tips and Tricks

IE in Compatibility View

Doesn’t matter how good you web application is if it is not working in EP environment. Our experience that in EP Internet Explorer settings all EP domains is set up for using Compatibility View (see Tools -> Compatibility View).

In Compatibility View you shiny and perfect application

To ensure switching of Compatibility View you have to put one line into the header of HTML page:

1        <meta http-equiv="X-UA-Compatible" content="IE=8" /> 

Keep session alive

HTTP sessions are expiring after a while. That is normal. After session timeout re-authentication is needed. In most cases the default timeout is 30 minutes. If this period is not enough you could extend it by modifying server settings. But it still can be frustrating.

Solution: ping the server periodically.

Create an empty controller function:

1          @RequestMapping(value = "/ping.do")
2          @ResponseBody
3          public String ping(HttpSession session) {
4                assert null != session;
5                return "ping";
6          }

Call this controller periodically (e.g.: every 25 minutes):

1        function keepSessionAlive() {
2          $.get(CTX + '/ping.do');
3        }
4        $().ready(function() {
5          setInterval(function() {
6                keepSessionAlive();
7          }, 25 * 60 * 1000);
8        });

Spring test, spring file organize best practice

It is a good idea to split spring files into smaller chunks. It can help to reuse and separate configuration.

For example we have split spring config into several files:

Majority of spring.xml is just including other files like <import resource="spring-tiles.xml" />.

When setting up testing environment we could reuse these configuration. For example in the same project in test/resources we have the following files only:

spring-test.xml is the counterpart of spring.xml. Contains less include because they are not relevant while running test (e.g. spring-security.xml is not included), but reusing all other spring configurations (cache, web, violations, etc). But when running unit test we cannot DataSource provided by the servlet container. We have to override it (spring-datasource.xml in test/resources).

And finally we need our parent Spring test class:

 1        @RunWith(SpringJUnit4ClassRunner.class)
 2        @ContextConfiguration(locations = { "classpath:spring-test.xml" })
 3        @TransactionConfiguration
 4        @Transactional
 5        public abstract class ASpringWebTest {}
 6        ...
 7        public class SessionModifiedViolationFormatterTest extends ASpringWebTest {
 8        ...
 9        }

Character Encoding

We are working in multilibual environment.

Set chacacter encoding in web.xml

 1          <filter>
 2                <filter-name>CharacterEncodingFilter</filter-name>
 3                <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
 4                <init-param>
 5                  <param-name>encoding</param-name>
 6                  <param-value>UTF-8</param-value>
 7                </init-param>
 8                <init-param>
 9                  <param-name>forceEncoding</param-name>
10                  <param-value>true</param-value>
11                </init-param>
12          </filter>
13          <filter-mapping>
14                <filter-name>CharacterEncodingFilter</filter-name>
15                <url-pattern>/*</url-pattern>
16          </filter-mapping>

But it is still not enough for JSON. You musf extend the annotation of ajax methods with produces parameter:

1          @RequestMapping(value = "preassign.do", method = RequestMethod.POST, produces = "application/json; charset=utf-8")
2          @ResponseBody
3          public String preassign(@RequestParam(value = "data", required = true) String data, ModelMap model) {

Session scoped object

If you need an always available object in session scope it might be more easy to use Session scoped bean instead of manual session management.

 1@Component
 2@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
 3public class Clipboard {
 4...
 5}
 6
 7@Controller
 8//@Scope("prototype")
 9public class NotificationController {
10  @Autowired
11  private Clipboard clipboard;
12}

In the bacground it will create a clipboard proxy instance which will be injetced into controller singleton (one instace always). When in a na actual request the proxy class will take care of which (session level) instance of target class to use.


Technology stack
Jul 03, 2013
comments powered by Disqus

Links

Cool

RSS