An Intro to the Spring DispatcherServlet!

1. Introduction

Simply put, in the Front Controller design pattern, a single controller is responsible for directing incomingHttpRequests to all of an application’s other controllers and handlers.

Spring’s DispatcherServlet implements this pattern and is, therefore, responsible for correctly coordinating the HttpRequests to their right handlers.

In this article, we will examine the Spring DispatcherServlet’s request processing workflow and how to implement several of the interfaces that participate in this workflow.

2. DispatcherServlet Request Processing

Essentially, a DispatcherServlet handles an incoming HttpRequest, delegates the request, and processes that request according to the configured HandlerAdapter interfaces that have been implemented within the Spring application along with accompanying annotations specifying handlers, controller endpoints, and response objects.

Let’s get more in depth about how a DispatcherServlet processes a component:

  • the WebApplicationContext associated to a DispatcherServlet under the key DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE is searched for and made available to all of the elements of the process
  • The DispatcherServlet finds all implementations of the HandlerAdapter interface configured for your dispatcher using getHandler() – each found and configured implementation handles the request via handle()  through the remainder of the process
  • the LocaleResolver is optionally bound to the request to enable elements in the process to resolve the locale
  • the ThemeResolver is optionally bound to the request to let elements, such as views, determine which theme to use
  • if a MultipartResolver is specified, the request is inspected for MultipartFiles – any found are wrapped in a MultipartHttpServletRequest for further processing
  • HandlerExceptionResolver implementations declared in the WebApplicationContext pick up exceptions that are thrown during processing of the request

You can learn more about all the ways to register and set up a DispatcherServlet here.

3. HandlerAdapter Interfaces

The HandlerAdapter interface facilitates the use of controllers, servlets, HttpRequests, and HTTP paths through several specific interfaces. The HandlerAdapter interface thus plays an essential role through the many stages of the DispatcherServlet request processing workflow.

First, each HandlerAdapter implementation is placed into the HandlerExecutionChain from your dispatcher’s getHandler() method. Then, each of those implementations handle() the HttpServletRequest object as the execution chain proceeds.

In the following sections, we will explore a few of the most important and commonly used HandlerAdapters in greater detail.

3.1. Mappings

To understand mappings, we need to first look at how to annotate controllers since controllers are so essential to the HandlerMapping interface.

The SimpleControllerHandlerAdapter allows for the implementation of a controller explicitly without a @Controller annotation.

The RequestMappingHandlerAdapter supports methods annotated with the @RequestMapping annotation.

We’ll focus on the @Controller annotation here, but a helpful resource with several examples using the SimpleControllerHandlerAdapter is also available.

The @RequestMapping annotation sets the specific endpoint at which a handler will be available within the WebApplicationContext associated with it.

Let’s see an example of a Controller that exposes and handles the ‘/user/example’ endpoint:

1
2
3
4
5
6
7
8
9
10
@Controller
@RequestMapping("/user")
@ResponseBody
public class UserController {
 
    @GetMapping("/example")
    public User fetchUserExample() {
        // ...
    }
}

The paths specified by the @RequestMapping annotation are managed internally via the HandlerMapping interface.

The URLs structure is naturally relative to the DispatcherServlet itself – and determined by the servlet mapping.

Thus, if the DispatcherServlet is mapped to ‘/’, then all mappings are going to be covered by that mapping.

If, however, the servlet mapping is ‘/dispatcher‘ instead, then any @RequestMapping annotations are going to be relative to that root URL.

Remember that ‘/’ is not the same as ‘/*’ for servlet mappings! ‘/’ is the default mapping and exposes all URL’s to the dispatcher’s area of responsibility.

‘/*’  is confusing to a lot of newer Spring developers. It does not specify that all paths with the same URL context are under the dispatcher’s area of responsibility. Instead, it overrides and ignores the other dispatcher mappings. So, ‘/example’ will come up as a 404!

For that reason, ‘/*’ shouldn’t be used except in very limited circumstances (like configuring a filter).

3.2. HTTP Request Handling

The core responsibility of a DispatcherServlet is to dispatch incoming HttpRequests to the correct handlersspecified with the @Controller or @RestController annotations.

As a sidenote, the main difference between @Controller and @RestController is how the response is generated – the @RestController also defines @ResponseBody by default.

A writeup where we go into much greater depth regarding Spring’s controllers can be found here.

3.3. The ViewResolver Interface

A ViewResolver is attached to a DispatcherServlet as a configuration setting on an ApplicationContext object.

A ViewResolver determines both what kind of views are served by the dispatcher and from where they are served.

Here’s an example configuration which we’ll place into our WebMvcConfigurerAdapter for rendering JSP pages:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration
@EnableWebMvc
@ComponentScan("com.baeldung.springdispatcherservlet")
public class AppConfig extends WebMvcConfigurerAdapter {
    @Bean
    public UrlBasedViewResolver viewResolver() {
        UrlBasedViewResolver resolver
          = new UrlBasedViewResolver();
        resolver.setPrefix("/WEB-INF/jsp/");
        resolver.setSuffix(".jsp");
        resolver.setViewClass(JstlView.class);
        return resolver;
    }
}

Very straight-forward! There are three main parts to this:

  1. setting the prefix, which sets the default URL path to find the set views within
  2. the default view type which is set via the suffix
  3. setting a view class on the resolver which allows technologies like JSTL or Tiles to be associated with the rendered views

One common question involves how precisely a dispatcher’s ViewResolver and the overall project directory structure are related. Let’s take a look at the basics.

Here’s an example path configuration for an InternalViewResolver using Spring’s XML configuration:

1
<property name="prefix" value="/jsp/"/>

For the sake of our example, we’ll assume that our application is being hosted on:

This is the default address and port for a locally hosted Apache Tomcat server.

Assuming that our application is called dispatcherexample-1.0.0, our JSP views will be accessible from:

The path for these views within an ordinary Spring project with Maven is this:

1
2
3
4
5
6
7
src -|
     main -|
            java
            resources
            webapp -|
                    jsp
                    WEB-INF

The default location for views is within WEB-INF. The path specified for our InternalViewResolver in the snippet above determines the subdirectory of ‘src/main/webapp’ in which your views will be available.

 3.4. The LocaleResolver Interface

The primary way to customize session, request, or cookie information for our dispatcher is through theLocaleResolver interface.

CookieLocaleResolver is an implementation allowing the configuration of stateless application properties using cookies. Let’s add it to AppConfig.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Bean
public CookieLocaleResolver cookieLocaleResolverExample() {
    CookieLocaleResolver localeResolver
      = new CookieLocaleResolver();
    localeResolver.setDefaultLocale(Locale.ENGLISH);
    localeResolver.setCookieName("locale-cookie-resolver-example");
    localeResolver.setCookieMaxAge(3600);
    return localeResolver;
}
@Bean
public LocaleResolver sessionLocaleResolver() {
    SessionLocaleResolver localeResolver = new SessionLocaleResolver();
    localeResolver.setDefaultLocale(Locale.US);
    localResolver.setDefaultTimeZone(TimeZone.getTimeZone("UTC"));
    return localeResolver;
}

SessionLocaleResolver allows for session-specific configuration in a stateful application.

The setDefaultLocale() method represents a geographical, political, or cultural region, whereas setDefaultTimeZone() determines the relevant TimeZone object for the application Bean in question.

Both methods are available on each of the above implementations of LocaleResolver.

3.5. The ThemeResolver Interface

Spring provides stylistic theming for our views.

Let’s take a look at how to configure our dispatcher to handle themes.

First, let’s set up all the configuration necessary to find and use our static theme files. We need to set a static resource location for our ThemeSource to configure the actual Themes themselves (Theme objects contain all of the configuration information stipulated in those files). Add this to AppConfig:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/resources/**")
      .addResourceLocations("/", "/resources/")
      .setCachePeriod(3600)
      .resourceChain(true)
      .addResolver(new PathResourceResolver());
}
@Bean
public ResourceBundleThemeSource themeSource() {
    ResourceBundleThemeSource themeSource
      = new ResourceBundleThemeSource();
    themeSource.setDefaultEncoding("UTF-8");
    themeSource.setBasenamePrefix("themes.");
    return themeSource;
}

Requests managed by the DispatcherServlet can modify the theme through a specified parameter passed into setParamName() available on the ThemeChangeInterceptor object. Add to AppConfig:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Bean
public CookieThemeResolver themeResolver() {
    CookieThemeResolver resolver = new CookieThemeResolver();
    resolver.setDefaultThemeName("example");
    resolver.setCookieName("example-theme-cookie");
    return resolver;
}
@Bean
public ThemeChangeInterceptor themeChangeInterceptor() {
   ThemeChangeInterceptor interceptor
     = new ThemeChangeInterceptor();
   interceptor.setParamName("theme");
   return interceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(themeChangeInterceptor());
}

The following JSP tag is added to our view to make the correct styling appear:

1
<link rel="stylesheet" href="${ctx}/<spring:theme code='styleSheet'/>" type="text/css"/>

The following URL request renders the example theme using the ‘theme’ parameter passed into our configured ThemeChangeIntercepter:

3.6. The MultipartResolver Interface

A MultipartResolver implementation inspects a request for multiparts and wraps them in a MultipartHttpServletRequest for further processing by other elements in the process if at least one multipart is found. Add to AppConfig:

1
2
3
4
5
6
7
8
@Bean
public CommonsMultipartResolver multipartResolver()
  throws IOException {
    CommonsMultipartResolver resolver
      = new CommonsMultipartResolver();
    resolver.setMaxUploadSize(10000000);
    return resolver;
}

Now that we’ve configured our MultipartResolver bean, let’s set up a controller to process MultipartFile requests:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Controller
public class MultipartController {
    @Autowired
    ServletContext context;
    @PostMapping("/upload")
    public ModelAndView FileuploadController(
      @RequestParam("file") MultipartFile file)
      throws IOException {
        ModelAndView modelAndView = new ModelAndView("index");
        InputStream in = file.getInputStream();
        String path = new File(".").getAbsolutePath();
        FileOutputStream f = new FileOutputStream(
          path.substring(0, path.length()-1)
          + "/uploads/" + file.getOriginalFilename());
        int ch;
        while ((ch = in.read()) != -1) {
            f.write(ch);
        }
        f.flush();
        f.close();
        modelAndView.getModel()
          .put("message", "File uploaded successfully!");
        return modelAndView;
    }
}

We can use a normal form to submit a file to the specified endpoint. Uploaded files will be available in ‘CATALINA_HOME/bin/uploads’.

3.7. The HandlerExceptionResolver Interface

Spring’s HandlerExceptionResolver provides uniform error handling for an entire web application, a single controller, or a set of controllers.

To provide application-wide custom exception handling, create a class annotated with @ControllerAdvice:

1
2
3
4
5
6
7
8
9
@ControllerAdvice
public class ExampleGlobalExceptionHandler {
    @ExceptionHandler
    @ResponseBody
    public String handleExampleException(Exception e) {
        // ...
    }
}

Any methods within that class annotated with @ExceptionHandler will be available on every controller within dispatcher’s area of responsibility.

Implementations of the HandlerExceptionResolver interface in the DispatcherServlet’s ApplicationContext are available to intercept a specific controller under that dispatcher’s area of responsibility whenever @ExceptionHandler is used as an annotation, and the correct class is passed in as a parameter:

1
2
3
4
5
6
7
8
9
@Controller
public class FooController{
    @ExceptionHandler({ CustomException1.class, CustomException2.class })
    public void handleException() {
        // ...
    }
    // ...
}

The handleException() method will now serve as an exception handler for FooController in our example above if either exception CustomException1 or CustomException2 occurs.

Here’s an article that goes more in depth about exception handling in a Spring web application.

 4. Conclusion

In this tutorial, we’ve reviewed Spring’s DispatcherServlet and several ways to configure it.

As always, the source code used in this tutorial is available over on Github.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s