In ‘Handling Exceptions In Spring MVC – Part 1’ we talked about how the servlet container can be set up to manage exceptions and status codes, as well as some of the issues you can face by having the servlet container be responsible for this. We also went over the HandlerExceptionResolver interface in Spring, including the SimpleMappingExceptionResolver implementation which is an excellent base for extension.
In this post I am going to show a possible extension to the SimpleMappingExceptionResolver – one which allows us to define exception priorities as well as sending different notifications based on the priorities. Included at the bottom of this post is a link to a sample web app which shows the use of the SimpleMappingExceptionResolver and the newly created one shown in the post. The web app uses both the 2.0 and 2.5 styled controllers and is only meant as a small example.
I mentioned in the previous post that this would be a ‘two post’ post. Well, after sitting down and planning out what I would like to cover, it seems it might be three posts, sorry. Also I would be very interested in other people’s views on this topic, as I believe there is no ‘right’ answer, but instead a different set of answers for a different set of situations.
Quick Refresh
The SimpleMappingExceptionResolver is a perfectly good implementation of the HandlerExceptionResolver interface. Its basic configuration is like so :
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<map>
<entry key="DataAccessException" value="data-error" />
<entry key="com.stuff.MyAppRuntimeException" value="app-unchecked-error" />
<entry key="com.stuff.MyAppCheckedException" value="app-checked-error" />
</map>
</property>
<property name="defaultErrorView" value="general-error"/>
</bean>
The above example shows three features of the SimpleMappingExceptionResolver (SMER) :
- default error page – All exceptions, checked and unchecked, will be resolved to either a specific page individual to them, or to a generic page which is set by the defaultErrorView.
- specific exceptions – As per the above example, “app-unchecked-error” and “app-checked-error” are the views to “com.stuff.MyAppCheckedException” and “com.stuff.MyAppRuntimeException” respectively.
- substring and subclass support – DataAccessException will match to org.springframework.dao.DataAccessException and com.myapp.DataAccessException, including any subclasses of either.
Have you read the SMER api docs? If you haven’t, READ IT!!!, it’s a wealth of information. In fact, if you really want to learn, have a look at the source code, download it from the Spring website and nosey through it, it’s the only true way to learn the api.
Extending SimpleMappingExceptionResolver – Stage One
Okay, enough about the SMER, let’s get onto extending. For our new HandlerExceptionResolver we should define some simple and clear requirements:
- When an Exception is handled by the resolver, a notification might be sent out
- Several different Notification services can be set up, each with a priority (user definable)
- Exceptions can be assigned a priority as to determine which Notification service to use
- If no priority is defined for an Exception then no notification is to be sent
- All exceptions handled should be logged with the priority assigned to the Exception type
Is that enough? Well, for this example it is. I am going to only show a possible way of extending SMER; not the perfect implementation, just a possible one.
For this we are going to need a NoficationService interface:
public interface NotificationService {
public void sendNotification(String message, Exception exception);
}
And a simple implementation which will send an email:
import org.springframework.mail.MailException;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;
public class EmailNotificationEmail implements NotificationService {
private MailSender mailSender;
private SimpleMailMessage templateMessage;
public static final Log log = LogFactory.getLog(EmailNotificationService.class);
public EmailNotificationService(MailSender mailSender, SimpleMailMessage templateMessage){
this.mailSender = mailSender;
this.templateMessage = templateMessage;
}
public void sendNotification(String message, Exception exception) {
SimpleMailMessage msg = new SimpleMailMessage(this.templateMessage);
msg.setText("Application Message - " + message + " : " + exception.getMessage());
try{
this.mailSender.send(msg);
} catch(MailException ex) {
// simply log it and go on...
log.fatal("Email Notification message could not sent", ex);
}
}
}
(have a read here about the email apis provided by Spring)
And this is to go in the applicationContext-services.xml
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
<property name="host" value="smtp.mycompany.com"/>
</bean>
<!-- this is a template message that we can pre-load with default state -->
<bean id="templateMessage" class="org.springframework.mail.SimpleMailMessage">
<property name="to" value="techsupport@mycompany.com"/>
<property name="from" value="my-app@mycompany.com"/>
<property name="subject" value="My-App has had a fatal exception occur"/>
</bean>
<bean id="emailNotificationService" class="com.example.myapp.EmailNotificationService">
<constructor-arg><ref bean="mailSender"></constructor-arg>
<constructor-arg><ref bean="templateMessage"/></constructor-arg>
</bean>
So far we have a NotificationService interface, EmailNotificationService implementation, and an application context to wire it all up. So now we need to create the SMER extension.
Extending SimpleMappingExceptionResolver – Stage Two
Below is the code for the NotifyingExceptionResolver. Please remember that formatting of the code isn’t great, but all code is available for download at the end of the post.
public class NotifyingExceptionResolver extends SimpleMappingExceptionResolver {
private Properties priorityExpMappings;
private Map priorityNSMappings;
private String notificationMessage = null;
private Log log = LogFactory.getLog(NotifyingExceptionResolver.class);
public void setPriorityExceptionMappings(Properties priorityExpMappings){
this.priorityExpMappings = priorityExpMappings;
}
public void setPriorityNotificationServicesMappings (Map priorityNSMappings){
this.priorityNSMappings = priorityNSMappings;
}
public void setNotificationMessage(String message){
this.notificationMessage = message;
}
@Override
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
log.warn("An Exception has occured in the application", ex);
sendNotification(ex);
return super.doResolveException(request, response, handler, ex);
}
private void sendNotification(Exception ex) {
String priority = resolvePriority(this.priorityExpMappings, ex);
NotificationService notificationService = resolveNotificationService (this.priorityNSMappings, priority);
String message = (notificationMessage == null ? priority : notificationMessage);
if(notificationService != null) {
log.debug("notification message was sent");
notificationService.sendNotification(message, ex);
}
}
private String resolvePriority(Properties priorityExpMappings, Exception ex){
return this.findMatchingViewName(priorityExpMappings, ex);
}
private NotificationService resolveNotificationService (Map priorityNSMappings, String priority){
NotificationService notificationService = null;
notificationService = priorityNSMappings.get(priority);
if (notificationService != null && logger.isDebugEnabled()) {
logger.debug("Resolving to a notification service for priority [" + priority + "]");
}
return notificationService;
}
}
You will notice two important variables – priorityExpMappings and priorityNSMappings. These variables hold the mappings between the exception types, priorities and notification services. You will also notice the method I have used as my hook in, doResolveException. As this method is where the action is, all I have done is added some more behavior and then called the super method. I have cheated a bit in the resolvePriority method by using the smarts behind findMatchingViewName to determine the priority linked to the exception. From there on in it’s pretty straightforward; I determine the priority based on the exception, find the notification service, and send a message.
Here is the application context definition which goes with it :
<bean class="com.mycompany.myapp.web.exceptionresolvers.NotifyingExceptionResolver">
<property name="exceptionMappings">
<map>
<entry key="SpecialCheckedApplicationError" value="errors/checked-error"/>
<entry key="SpecialRuntimeApplicationError" value="errors/runtime-error"/>
</map>
</property>
<property name="priorityExceptionMappings">
<map>
<entry key="SpecialRuntimeApplicationError" value="High" />
<entry key="SpecialCheckedApplicationError" value="High" />
</map>
</property>
<property name="priorityNotificationServicesMappings">
<map>
<entry key="High" value-ref="emailNotificationService" />
</map>
</property>
<property name="defaultErrorView" value="errors/general-error" />
</bean>
You will notice that I still define the exceptionMappings property and the defaultErrorView property. There is no doubt I have extended from the SMER; I have just added some cherries to an already creamed pie.
Would this be the best option all the time?
Is extending the SimpleMappingExceptionResolver best for all cases? No, nothing is best for all cases, but it is best for most. In fact, just using the SMER and not extending it is best for most, and extending it is only for required cases. To be honest, creating a notification system like I have is quite contrived as the logging system can use an smtp gateway to do what I have done, and creating new logging appenders isn’t hard either.
I have a lot more to cover and talk about, but this post is already 1000+ words. The final post in the series will expand on the question of what errors should be captured where; be it in the web.xml, controller, or handler exception resolver, checked and runtime.
This post merely shows that extending and customizing the SMER is not hard and very feasible, but just make sure you know why you are doing it.
Sample Application
To download the sample web app with email notification service, click here. This web app is built using Maven and thus requires a bit of knowledge of Maven to package up. If you’re not a Maven fan then it shouldn’t be that hard to refactor into a single package and source folder.
(Please note, make sure you download Spring MVC 2.5.2, 2.5.1 has issues with annotated controllers throwing checked exceptions)
References
Reporting Application Errors by Email (using logging)