Embracing the Messiness in Search of Epic Solutions

Spring MVC: Centralizing Common Configurations using @ControllerAdvice

Posted

in

Handling Exceptions using HandlerExceptionResolver

Once upon a time, Spring 2.0 introduced HandlerExceptionResolver to handle thrown exceptions. While this approach works, it is like throwing a big net to catch all types of fish. If we are only interested in catching piranhas and clownfish, this approach becomes a little tedious because we need to weed out the types of fish we don’t care.

@Controller
public class ErrorController implements HandlerExceptionResolver {

    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest,
                                         HttpServletResponse httpServletResponse,
                                         Object o,
                                         Exception e) {

        ModelAndView modelAndView = new ModelAndView("error");

        if (e instanceof PiranhaException) {
            // stir fry it
        }
        else if (e instanceof ClownfishException) {
            // make me laugh first
        }
        else {
            // don't care...
        }

        return modelAndView;
    }

    @RequestMapping(value = "/error", method = RequestMethod.GET)
    public String error() {
        return "error";
    }
}

Handling Exceptions using @ExceptionHandler

Then, Spring 3.0 introduced a more elegant way to handle exceptions using @ExceptionHandler. Now, we can easily catch the types of fish we need and handle accordingly.

@Controller
@RequestMapping(value = "/fish")
public class FishController {

    @ExceptionHandler(PiranhaException.class)
    public String piranhaException(Exception e) {
        // stir fry it
        return "error";
    }

    @ExceptionHandler(ClownfishException.class)
    public String clownfishException(Exception e) {
        // make me laugh first
        return "error";
    }

    @RequestMapping(method = RequestMethod.GET)
    public String home() {
        return "home";
    }
}

While this is a more elegant solution than the previous approach, @ExceptionHandler has to be defined within the Controller class. If we want to apply the same exception handling across multiple controllers, we either have to define @ExceptionHandler in every Controller class or create something “clever” such as extending a parent controller that contains the common configurations.

Handling Exceptions using @ControllerAdvice

Then, Spring 3.2 introduced an even better approach to centralize common configurations using @ControllerAdvice. Instead of defining @ExceptionHandler all over places or extending a parent controller, we can create a separate class that contains these common configurations and annotate it with @ControllerAdvice.

@ControllerAdvice
public class FishControllerAdvice {

    @ExceptionHandler(PiranhaException.class)
    public String piranhaException(Exception e) {
        // stir fry it
        return "error";
    }

    @ExceptionHandler(ClownfishException.class)
    public String clownfishException(Exception e) {
        // make me laugh first
        return "error";
    }
}

… and now, the controller will look as simple as this:-

@Controller
@RequestMapping(value = "/fish")
public class FishController {

    @RequestMapping(method = RequestMethod.GET)
    public String home() {
        return "home";
    }
}

In fact, @ControllerAdvice can be used to define @ExceptionHandler, @ModelAttribute and @InitBinder that apply to all @RequestMapping methods.

Here’s an example:-

@ControllerAdvice
public class MyAdviceToAllFish {

    @ExceptionHandler(PiranhaException.class)
    public String piranhaException(Exception e) {
        // stir fry it
        return "error";
    }

    @ExceptionHandler(ClownfishException.class)
    public String clownfishException(Exception e) {
        // make me laugh first
        return "error";
    }

    // Catch all other exceptions
    @ExceptionHandler(Exception.class)
    public String exception(Exception e) {
        // when shit happens
        return "error";
    }

    // Return a list of tech support emails
    @ModelAttribute("techSupportEmails")
    public List<String> getTechSupportEmails() {
        return Arrays.asList("[email protected]", "[email protected]");
    }

    // Handles `String` and Joda Time's `LocalDate` object
    @InitBinder
    private void initBinder(WebDataBinder binder) {
        // Trim string and set empty string as `null` value
        binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));

        // Convert Joda Time's `LocalDate` object to text, vice versa
        binder.registerCustomEditor(LocalDate.class, new PropertyEditorSupport() {
            private final DateTimeFormatter pattern = DateTimeFormat.forPattern("MM/dd/yyyy");

            @Override
            public void setAsText(String text) throws IllegalArgumentException {
                setValue(StringUtils.isBlank(text) ? null : pattern.parseLocalDate(text));
            }

            @Override
            public String getAsText() throws IllegalArgumentException {
                LocalDate localDate = (LocalDate) getValue();
                return localDate != null ? pattern.print(localDate) : "";
            }
        });
    }
}

Tags:

Comments

Leave a Reply