Monday, August 26, 2013

How To create a global InitBinder in Spring with @ControllerAdvice

Date formatting could be an annoying problem when dealing with your views. I wrote an article about it when I was facing Json problems (see How To format dates in Json within Spring 3) but, as you may know, even if you don't need to use Json in your views, you'll probably have to face some troubles with date formats.

Sometimes you can avoid all your problems simply using the specific JSTL fmt in your jsp (see references). But what to do if you can't use JSTL in your jsp, or if a simple JSTL won't solve your problems?

Someone suggests to inject an InitBinder in your controller. What an InitBinder does is to grab your data from web request parameters and bind it to your JavaBean objects. So this could be a good solution if you have to deal with different kinds of data inputs in different controllers. But what if you have to always deal, for example, with dates? You could write a specific InitBinder for each of your controllers, but this is not a good procedure if you want to maximize your code efficiency by an accurate modularization (and this should be your aim, since you're using MVC paradigm).

What I suggest you to do is to create something like a single global InitBinder, which will be activated everytime your application needs it, without any controller restriction. There's a really simple way to do so, using Spring @ControllerAdvice annotation. A class annotated with @ControllerAdvice is a class that assists every controller, and it's easily autodetected through Spring classpath scanning.

If this is your case, and you want to create an InitBinder which manages your dates parsing them in (for example) dd/MM/yyyy format, here's what you can do.

The steps!
  1. Create a DateEditor class like this:
    public class DateEditor extends PropertyEditorSupport {
     
     public void setAsText(String value) {
            try {
                setValue(new SimpleDateFormat("dd/MM/yyyy").parse(value));
            } catch(ParseException e) {
                setValue(null);
            }
        }
    
        public String getAsText() {
         String s = "";
         if (getValue() != null) {
       s = new SimpleDateFormat("dd/MM/yyyy").format((Date) getValue());
      }
         return s;
        }
    
  2. Create a class annotated with @ControllerAdvice (I called it GlobalBindingInitializer):
    @ControllerAdvice
    public class GlobalBindingInitializer {
     
     /* Initialize a global InitBinder for dates instead of cloning its code in every Controller */
     
     @InitBinder
     public void binder(WebDataBinder binder) {
      binder.registerCustomEditor(Date.class, new DateEditor());
     }
    }
    
  3. In your Spring MVC configuration file (for example webmvc-config.xml) add the lines that allow Spring to scan the package in which you created your GlobalBindingInitializer class. For example, if you created GlobalBindingInitializer in the org.example.common package:
    <context:component-scan base-package="org.example.common" />
    

There's nothing else to do for your InitBinder date formatter to work. Remember that @ControllerAdvice is an extremely helpful annotation even when you have to handle exceptions, or if you want to create a global accessible @ModelAttribute for your application.

References

1 comment: