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) : "";
}
});
}
}
Leave a Reply