Spring Web MVC and Advising Controllers
In this tutorial, we will:
- learn about the flow of request and response inside Spring Web MVC
- customise the Dispatcher Servlet configuration
- create an Apache Tiles view
- learn how to handle exceptions
As mentioned before in our previous articles of the series, Spring framework is an open source Java platform that provides MVC infrastructure support for developing robust Java applications very easily and very rapidly using recommended MVC pattern.
Look’s like we have a lot to cover this time, so let’s get started.
Spring Web MVC
Based on the Model-View-Controller pattern, Spring MVC helps you build web-based applications that are flexible and contain loosely coupled components, just like inside the Spring framework.
Request and Response
As soon as a user clicks a link in their web browser, a request gets to work. Just like a courier, a request’s job is to carry some information from a source to a destination. From the time it leaves the client browser, it makes several stops, each time dropping off some information and picking up some more.
When the request leaves the browser:
- It carries information that user is asking for and a URL. It may also contain information submitted by a user in a web form. The first stop is at Spring’s DispatcherServlet. A DispatcherServlet job is to send this request on to an MVC controller.
- DispatcherServlet consults the handler mapping to decide which controller should receive the request. This is why a URL carried by a request object is important.
- At the Controller, the request drops off the information and waits for the controller to process it. Once the Controller has prepared the model data (which is needed to be sent back to the user), it possibly binds that data with a view (JSP or an HTML page) to send it back to the DispatcherServlet.
- The Controller only returns a view name, just a string which is the logical name of the view.
- DispatcherServlet now consults a view resolver to map the logical view name to a specific view implementation which may or may not be a JSP.
- In its final stop at the view implementation, it delivers the model data. The request’s job is now done.
- The view will use the model data obtained from request object and correctly render output. This is delivered back to the user by a (not so hard working) response object.
Configure DispatcherServlet
DispatcherServlet is the centerpiece of Spring MVC. It’s where the request hits first and gets routed through all the other components of the framework project.
Instead of a web.xml, we’re going to use Java based configuration classes to configure Spring MVC in our project. Let’s look at some Java class below:
package com.discoversdk.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class MvcConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { RootConfig.class };
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { WebConfig.class };
}
@Override
protected String[] getServletMappings() {
// Map servlet mappings to /
return new String[] {" / "};
}
}
The first method getServletMappings() identifies one or more paths that DispatcherServlet is mapped to, which is in this case is /, the application’s default servlet.
Next, the @Configuration bean class returned by getRootConfigClasses() will define beans for DispatcherServlet’s application context.
Finally, the getServletConfigClasses() defines @Configuration bean classes which will be used to configure the application context created by ContextLoaderListener.
Next, let’s define our WebConfig and RootConfig classes. Our WebConfig class looks like:
package com.discoversdk.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
@Configuration
@EnableWebMvc
@ComponentScan("com.discoversdk")
public class WebConfig extends WebMvcConfigurerAdapter {
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");
viewResolver.setExposeContextBeansAsAttributes(true);
return viewResolver;
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
Let’s look at various parts of the above class, step by step:
- @ComponentScan annotation makes sure that the specified package is scanned for Spring components like Services, Repository and Controllers.
- Next, we add a ViewResolver bean. It is configured to look for JSP files with a prefix and a suffix.
- Finally, by overriding configureDefaultServletHandling() and calling enable, we asked DispatcherServlet to forward a request for static resources to the servlet container’s default servlet and not handle them itself.
Finally the RootConfig class will look like:
package com.discoversdk.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@Configuration
@ComponentScan(basePackages = "com.discoversdk",
excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = EnableWebMvc.class)})
public class RootConfig {
}
Let’s leave the RootConfig class alone for now. Just so that we don’t feel incomplete at the moment, let’s set up a quick controller which can handle a request to “/” URI, the home page. Here comes the HomeController.java class:
package com.discoversdk.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class HomeController {
@RequestMapping(value = "/", method = RequestMethod.GET)
public String home() {
return "home";
}
}
Clearly, it returns a JSP view, named home.jsp.
Setting up Apache Tiles view
In order to use Apache Tiles 3 with Spring, we need to start with four maven dependency:
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-api</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-servlet</artifactId>
<version>3.0.7</version>
</dependency>
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-core</artifactId>
<version>3.0.7</version>
</dependency>
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-jsp</artifactId>
<version>3.0.7</version>
</dependency>
Next, we need to define a couple of beans. Note that these beans will be defined inside the WebConfig class.
@Bean
public TilesConfigurer tilesConfigurer() {
TilesConfigurer tilesConfigurer = new TilesConfigurer();
tilesConfigurer.setDefinitions(new String[] {"/WEB-INF/layout/tiles.xml"});
tilesConfigurer.setCheckRefresh(true);
return tilesConfigurer;
}
A view resolver bean for Tiles 3, as:
@Bean
public ViewResolver viewResolver() {
return new TilesViewResolver();
}
Finally, let’s define the tiles.
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE tiles-definitions PUBLIC
"-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN"
"http://tiles.apache.org/dtds/tiles-config_3_0.dtd">
<tiles-definitions>
<definition name="base" template="/WEB-INF/layouts/page.jsp">
<put-attribute name="header" value="/WEB-INF/layouts/header.jsp" />
<put-attribute name="footer" value="/WEB-INF/layouts/footer.jsp" />
</definition>
</tiles-definitions>
Now that’s done, let’s finally put all of the above to use in defining a simple layout with a header, body, and footer. Let’s look as page.jsp, made using Tiles syntax.
<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="t" %>
<html>
<head>
<title><tiles:getAsString name="title"/></title>
<link rel="stylesheet"
type="text/css"
href="<s:url value="/resources/style.css" />" >
</head>
<body>
<div id="header">
<t:insertAttribute name="header" />
</div>
<div id="content">
<t:insertAttribute name="content" />
</div>
<div id="footer">
<t:insertAttribute name="footer" />
</div>
</body>
</html>
Now that was fast. With Apache Tiles, you can create clean and concise pages that contain only the main parts of your page when common parts like header and footer lie somewhere else.
Handling Exceptions
Now, it's all well and good to assume that everything works well in our project, but sadly, that’s not how real world Spring projects are. What if, while processing a request, an exception is thrown. What will the client receive, because he must receive something in all cases.
To send an exception message to a client, we must convert the exception to a formatted response message.
Spring offers a handful ways to translate exceptions to meaningful responses:
- Certain Spring exceptions are automatically converted to specific HTTP status codes.
- An exception can be annotated with @ResponseStatus to bind it to an HTTP status code.
- A method can be annotated with @ExceptionHandler to handle the exception.
Mapping Exceptions to HTTP Status Codes
Certain Spring exceptions are automatically converted to specific HTTP status codes. Let’s look at some automatic examples first:
Spring Exception |
HTTP Status Code |
BindException |
400 - Bad Request |
ConversionNotSupportedException |
500 - Internal Server Error |
TypeMismatchException |
400 - Bad Request |
HttpMediaTypeNotSupportedException |
415 - Unsupported Media Type |
HttpMediaTypeNotAcceptableException |
406 - Not Acceptable |
HttpMessageNotReadableException |
400 - Bad Request |
Now, let’s demonstrate a request handling method from our app that could result in an HTTP 404 status but doesn’t:
@RequestMapping(value = "/{userId}", method = RequestMethod.GET)
public String myPage(@PathVariable("userId") long userId,
Model model) {
User user = userRepository.findOne(userId);
if(user == null) {
throw new UserNotFoundException();
}
model.addAttribute(user);
return "userPage";
}
For now, a UserNotFoundException looks like this:
package com.discoversdk.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value = HttpStatus.NOT_FOUND,
reason = "User Not Found")
public class UserNotFoundException extends RuntimeException {
}
After introducing UserNotFoundException with a @ResponseStatus annotation, if a UserNotFoundException were to be thrown, the response would have a status code of 404 and a reason of User not found.
Writing Exception Handling methods
Mapping exceptions to status code is simple and sufficient for many cases. But what if you want the error response to contain more than the HTTP status code and a message; maybe you would like to handle the exception the same way you would handle the request.
Let’s look at another example in which we attempt to save a user, but a DuplicateUserException is thrown. Let’s look at some code for this:
@RequestMapping(method = RequestMethod.POST)
public String saveUser(UserForm form, Model model) {
try {
userRepository.save(new User(form.getName(), form.getAge(), new Date()));
return "redirect:/userDashboard";
} catch(DuplicateUserException ex) {
return "error/duplicate";
}
}
There’s nothing difficult to understand in this code. We simply throw an exception if a duplicate user was saved to database. To handle the exception, we can simply add a method like so:
@ExceptionHandler(DuplicateUserException.class)
public String handleDuplicateUser() {
return "error/duplicate";
}
This method is present in the same controller as the RequestMapping controller. But there should be a way to put all ExceptionHandler methods in one place or at least, separate from the controllers. We can advise controllers as well.
Conclusion
We’ve covered quite a bit in the article—we configured Apache Tiles and looked at the concept of mapping exceptions to our own methods so that the user always sees a beautiful and well formatted response from our side.
In the next article, we will continue our journey and study some important concepts regarding advising controllers, configuring a flow executor and much more.
Recent Stories
Top DiscoverSDK Experts
Compare Products
Select up to three two products to compare by clicking on the compare icon () of each product.
{{compareToolModel.Error}}
{{CommentsModel.TotalCount}} Comments
Your Comment