Advising Controllers in Spring

At some point of time we might come over a situation where we might want a certain piece of code in any of our Spring controller to be applied across all the other controllers in our application. For example we might want a @ExceptionHandler method to handle exceptions across multiple controllers. This would prevent duplicating the code across multiple controllers.

One way of doing this is to have a base controller and inherit it in all other controllers in the application. Spring offers a better way to doing this called as controller advice.

A controller advice can be any class annotated with @ControllerAdvice. It can have method with below annotations:

  • @ExceptionHandler
  • @InitBinder
  • @ModelAttribute

Note that @ControllerAdvice annotation is itself annotated with @Component, hence it would be automatically registered as a Spring bean by component scanning.


Sample Application

Let’s build a simple contact list application to demonstrate different aspects of controller advice. We are skipping the configuration part, however you can find the complete source code in the download section at the end of the article.

Let’s start by building out Contact object as shown below:

public class Contact {	
    private Integer id;
    private String name;
    private String email;
    private Date dob;
    private String mobile;

    -- getters & setters --
}

Now let’s build our controller.

@Controller
public class ContactController {
	
    @Autowired
    private ContactService contactService;
	
    @RequestMapping("/")
    public String getContactList(Model model) {
 	model.addAttribute("CONTACTLIST", 
  		contactService.getContactList());
 	return "index";
    }
	
    @RequestMapping(value = "/addContact", method=RequestMethod.GET)
    public String addNewContactView() {
 	return "addContact";
    }
	
    @RequestMapping(value = "/addContact", method=RequestMethod.POST)
    public String addContact(Contact contact, Model model){
    	model.addAttribute("CONTACTLIST", contactService.addContact(contact));
        return "index";
    }	
}

The controller has 3 simple method:

  1. getContactList() method simply attaches a contactlist to the model for displaying in the home page.
  2. addNewContactView() simple displays a form to add a new contact.
  3. addContact() accepts a new contact and add it the existing list of contacts.

Now let’s take a look at the service:

@Service
public class ContactService {
	
    private List<Contact> contactList;
	
    private void init() {		
        try{
            contactList = new ArrayList<Contact>();
	    SimpleDateFormat format = new SimpleDateFormat("dd-MM-yyyy");
	    
Contact contact1 = new Contact(); contact1.setId(121); contact1.setName("Ashutosh Meher"); contact1.setEmail("[email protected]"); contact1.setDob(format.parse("01-01-1988")); contact1.setMobile("9632587412"); Contact contact2 = new Contact(); contact2.setId(127); contact2.setName("Anubhav M"); contact2.setEmail("[email protected]"); contact2.setDob(format.parse("01-01-1987")); contact2.setMobile("9562485632"); contactList.add(contact1); contactList.add(contact2); }catch(Exception e){e.printStackTrace();} } public List<Contact> getContactList() { if(contactList == null || contactList.size()==0){ init(); } return contactList; } public List<Contact> addContact(Contact contact){ for(Contact con: contactList){ if(con.getEmail().equalsIgnoreCase(contact.getEmail())){ throw new DuplicateContactException(); } } contactList.add(contact); return contactList; } }

Below points sums up our service:

  • A private variable ‘contactList’ to store our contacts.
  • The init() method to initialize our contact list.
  • The getContactList() method to get the list of available contacts.
  • The addContact() method to add a new contact.

Lets take a look at the view:

View to display contactList - 'index.jsp'

<div>
    <c:forEach items="${CONTACTLIST}" var="item">
	<div>
	    <h3><c:out value="${item.name}" />(<c:out value="${item.id}" />) </h3>
	    <p>DOB: <fmt:formatDate pattern="dd MMM yyyy" value="${item.dob}" /></p>
	    <p>Email: <c:out value="${item.email}"></c:out></p>
	    <p>Mobile: <c:out value="${item.mobile}"></c:out></p>
	</div>
	<hr>
    </c:forEach>
</div>

Add new contact form - 'addContact.jsp'

<form action="addContact" method="post">
    <c:if test="${error != null}">
 	<div style="color: red;">
	    <c:out value="${error}" />
	</div>
    </c:if>
    <div>
        <label>Name: </label><input type="text" name="name">
    </div>
	<label>DOB: </label><input type="date" name="dob">
    <div>
        <label>Email: </label><input type="text" name="email">
    </div>
    <div>
        <label>Mobile: </label><input type="text" name="mobile">
    </div>
    <input type="submit" value="Submit">
</form>

That pretty much sums up the application code apart from the controller advice. The section below describes controller advice in details.


Controller Advice

To create a controller advice we must have a class annotated with @ControllerAdvice. We did not described it in the application code section. Here is our controller advice:

@ControllerAdvice
public class ControllerHelper {

    @InitBinder
    public void bindDate(WebDataBinder webDataBinder){...}
	
    @ExceptionHandler(DuplicateContactException.class)
    public ModelAndView handleDuplicate(){...}
	
    @ModelAttribute
    private void populateUserId(Contact contact){...}
	
}

This class acts as an advice for all the controller in the application. Hence, the three method in this class applies to all controllers throughout the application.


@ExceptionHandler

@ExceptionHandler annotation can be used in Spring controller to handle an exception thrown from within the controller. For more details see article Exception Handling in Spring.

Here we are using @ExceptionHandler in a controller advice. This means it would be applicable to all controllers on the application eliminating the need to duplicate the code in multiple controllers.

Notice that we throw an exception when we find a duplicate entry being added to the contact list in the addContact() method of our service.

if(con.getEmail().equalsIgnoreCase(contact.getEmail())){
    throw new DuplicateContactException();
}

DuplicateContactException is a custom exception which is thrown when we try to add a contact with an email with already exists in our database.

Shown below is the controller advice method which handles the exception. Its simple adds an error message and and passes the message along with the view(‘addContact’);

@ExceptionHandler(DuplicateContactException.class)
public ModelAndView handleDuplicate(){
	ModelAndView mv = new ModelAndView();
	mv.addObject("error", "Contact Already Exist!");
	mv.setViewName("addContact");
	return mv;
}

Note that @ExceptionHandler method doesn’t support a Model parameter, hence we are using ModelAndView as an alternative.


@InitBinder

We already know that Spring automatically binds form data to Model parameters for simple data types. But what about complex data type such as a Date object. @InitBinder annotation comes in handy in such cases. It helps us bind complex data type to model objects.

Similar to @ExceptionHandler, @InitBinder can also be used in a controller advice such that it can be application across all controller in the application.

Shown below in the initbinder method of our controller advice which is used to bind 'dob' of our contact.

@InitBinder
public void bindDate(WebDataBinder webDataBinder){
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
    dateFormat.setLenient(true);
    webDataBinder.registerCustomEditor(
	Date.class, new CustomDateEditor(dateFormat, false));
}

@ModelAttribute

@ModelAttribute annotation in a method is used to inject any particular attribute to a model. Note that the attribute will always be injected in all the controller methods throughout the application wherever the model is accepted as a parameter.

Below is the code from our application where we populate the contact ID in our contact model.

@ModelAttribute
private void populateUserId(Contact contact){
    if(contact.getId() == null){
	Random random = new Random();
	contact.setId(random.nextInt(500));
    }
}

Download Source Code

You can download the source code of the sample Spring Application discussed in this article.

POPULAR ARTICLES

Creating Conditional Beans in Spring

The concept of condition beans enables Spring to restrict the creation of any bean depending on the evaluation of a condition. These beans get created only when a preset condition is evaluated as true

View Article

Accepting Request Param and Path Variable in Spring Controller

Spring MVC provides various ways through which a client browser can pass data to the Controller. In this article we will discuss about accepting Request Parameters and Path Variables in Spring Contr..

View Article

Generate Namespace & Schema Information using JAXB

Most xml documents used in enterprise applications makes use of namespace to avoid element name conflicts. This article talks about generating these namespace and schema information when marshaling...

View Article

Switching Database Profile using Spring Profiles

We are most likely to have separate db configuration for different environment like development and production environment. Spring profiles provide a convenient way to switch db profiles at runtime.

View Article

SQL and its Sub-Languages

SQL (Structured Query Language) is a language understood by most modern databases. It is an ANSI (American National Standard Institute) standard language which is used to manipulate databases.

View Article

Introducing JUnit Rule

Junit Rules allows developers to add additional functionalities that can applied to all test methods in a test class. It is similar to the concept of custom test runners but with reduced restrictions.

View Article

Addressing Ambiguity in Spring Autowiring

Spring autowiring is powerful concept, but we should be very cautious while using it. We may end up in creating ambiguity while autowiring beans, which will cause autowiring to fail.

View Article

Creating and Using Synonym in Oracle Database

Synonyms are database objects used to provide duplicate names to existing objects in the database. It is just an alternate name used to hide the original name of the object.

View Article

Creating and Using Sequence in Oracle Database

A sequence is used to auto-generate numbers in ascending or descending order which can serve as a primary key or a part of it (in case of composite key).

View Article

Creating and Manipulating Constraints in Oracle Database

Constraints are used to impose certain rules on columns to avoid invalid data entry into the table. If any of the constraint is violated the operation fails.

View Article

Integrating Log4J with Perf4J for Performance Logging

Perf4j is an open source logging framework used primarily for monitoring performance statistics in java applications. Log4j has the ability to integrate with perf4j to capture performance data.

View Article

Tagging in GIT

Tagging allows us to mark a specific point in the commit history or snapshot. A tag is typically used to mark a project release. This article shows how to create tags in Git.

View Article