0
0

Spring MVC

QuickGuide
4899 / 1
Jorge Simao Posted 22 May 20

Spring MVC — Overview

Spring MVC is a Web and REST-WS framework part of Spring Framework. It uses a simple request-processing workflow, it is highly configurable and flexible, and allows the web components — Controllers — to be written as simple Java classes (POJOs) with request handler methods with flexible signatures. Spring MVC is mostly agnostic about the View Description Language (VDL) used, and supports out-of-the-box integration with several view rendering technology. It also provides abstractions that make it a very convenient REST-WS framework, building on the features available for web apps.

Spring MVC Architecture & Setup

Spring MVC uses a single HttpServlet — the DispatcherServlet — that is responsible to dispatch each HTTP request to an handler method in an web-layer component (controller) — defined as a Spring managed bean. An handler method role is to select a view to render and provide the objects needed to support the rendering of the dynamic parts of the view — the Model objects. To support different view technologies a configurable ViewResolver strategy object is used to map the symbolic/logical name of the view returned by the handler method, to an actual view-resource in the file-system. Figure below depicts the architecture of Spring MVC and its request-processing life-cycle.

The DispatcherServlet always creates on initialization a Spring ApplicationContext — the bean container, which contains the controllers and optionally the configuration strategies for Spring MVC — such as the ViewResolver. A separated root ApplicationContext is also created, commonly, to hold components of lower layers — such as services, repositories, and infrastructure beans — created by a ContextLoaderListener (implementing the ServletContextListener interface). The Spring MVC created ApplicationContext is made a child of the root container, which allows the lower-layer beans to be injected in web-layer controllers. In the web environment the Spring containers are enhanced with additional features, such as support for request and session scope attributes. Figure below depicts the relationship between containers in a web environment.

In Servlet 3.0+ containers, the DispatcherServlet can be initialized in Java by defining a class implementing interface WebInitializer. For convenience, the abstract class can be extended and the root and MVC ApplicationContext initialized by specifying the configuration classes.

Example: WebApp & MVC Initialization in Java

public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {	
  @Override
  protected Class<?>[] getRootConfigClasses()
  { return new Class[] { AppConfig.class }; }

  @Override
  protected Class<?>[] getServletConfigClasses()
  { return new Class[] { WebConfig.class }; }

  @Override
  protected String[] getServletMappings()
  { return new String[] { "/mvc/*" }; }
}

Alternatively, the web app can be configured with XML in the WEB-INF/web.xml file, to define explicitly the DispatcherServlet and the ContextLoaderListener. Global and servlet parameter named contextConfigLocation is used to define, respectively, the XML spring bean files for the root and MVC WebApplicationContext.

Controllers

Web-layer components — the Controllers — are defined as Spring managed beans commonly annotated with stereotype annotation @Component to facilitate @ComponentScan. Controllers define request handler methods each responsible to handle a particular family of requests. Annotation @RequestMapping is used to define the dispatching rules, such as based on URL pattern and HTTP method. Annotations @{Get|Post|Put|Delete|Path}Mapping are equivalent to @RequestMapping entailing a specific HTTP method (since Spring 4.3). The main responsibility of an handler method is to select a view to render and to capture and expose model objects. A common approach is for handlers to return a String value with the name of the view to render, and define an out-parameter of type Model whose Map like API is used to expose model objects.

Since Spring beans are Java POJOs, the handler methods can have flexible signatures. Many different types and conventions are supported for the parameters of the handler methods. Several annotations are also used to instruct Spring MVC how/which value should be injected. Annotation @RequestParameter specifies the name of request parameter to inject (from the URL query string — part after ?). Annotation @PathVariable specifies a named URL segment to inject. When the names match the corresponding Java parameter names, the names can be omitted in both annotations (assuming debug or parameter information is being generated — i.e. no -g option or -p option for compiler). Type conversion is carried out automatically as needed.

Example: Controller with Handler for Listing Resource

@Controller
@RequestMapping(value = "/book")
public class BookController {	
  @Autowired
  private BookService service;

  @RequestMapping(method = RequestMethod.GET)
  public String topSelling(Model model,
    @RequestParam(value="n", defaultValue="100") Long n) {
    List<Book> books = findTopSellingBooks(n);
    model.addAttribute("books", books);		
    return "book/list";
  }
}

Example: Handler Method to Retrieve Resource

@GetMapping("/book/{id}")
public String show(@PathVariable("id") Long id, Model model) {
  Book book = service.findBookById(id);
  if (book==null) {
    throw new NotFoundException(Book.class, id);
  }
  model.addAttribute("book", book);		
  return "book/show";
}

Table below summarizes the class and method level annotations supported by Spring MVC:

Type/AnnotationDescription
@ControllerWeb Component Stereotype (detected with @ComponentScan)
@RestControllerREST-WS Component Stereotype
@ControllerAdviceCommon Behavior across Controllers
@RequestMappingDefines dispatching/mapping rule
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping
Similar to @RequestMapping but implying a specific HTTP method
@ResponseStatusSet HTTP response status
@ExceptionHandlerHandler for throw exception
@ModelAttributeName for model attribute
@InitBinderInitialize DataBinder
@SessionAttributeDeclares names of session attributes

Table below summarizes the types and annotation supported in Spring MVC for the handler method parameters. Some of them are described in more detail in following sections.

Type/AnnotationDescription
Model,Map,ModelMapContainer for model objects (out param)
@RequestParamA named request/query parameter
@PathVariableA named URL path segment
@ModelAttributeName for model attribute
@ValidValidate Form Model Object
Errors, BindingResultCollector of binding/validation errors
@HeaderAttributeInject value of HTTP request header
@MatrixAttributeInject parameter — matrix syntax
@DateTimeFormatParse format for Dates
@NumberFormatParse format for Numbefrss
@RequestBobyMap parameter from request body
@ResponseBodyMap object to response body
HttpEntity<>Descriptor for request headers&body
@SessionAttribute
@RequestAttribute
Lookup session/request attribute
LocaleLocale for request
TimeZone/ZoneId+Java8Timezone for request
ThemeTheme for request
PrincipalAuthenticated user for request
@RequestPartUploaded part of multipart/form-data
WebRequest
WebNativeRequest
Request/response descriptor (Spring)
HttpMethodHTTP method for request
HttpServletRequest
HttpServletResponse
Request & response descriptors (Servlet API)
HttpSessionSession descriptor
SessionStatusHandler for session (attributes)
InputStream/ReaderRequest (socket) input stream
OutputStrem/Writer(Socket) output stream for responsev

Views

Spring MVC supports several view rendering technologies — such as JSP, FreeMarker, Velocity, and ThymeLeaf — and can be extended to support more. View resources are represented by objects of type View, which are able to render its content to the socket output stream. Handlers can return View objects directly, or values of type ModelAndView combining two pieces of information. More commonly, however, they to return a logical view name as a String — which is mapped to a View object by a ViewResolver strategy. This allow controllers to be written completely independent of the view technology in use. To configure a ViewResolver it should be defined as a Spring bean in the MVC WebApplicationContext. For JSP pages, this is the InternalResourceViewResolver which can be configured with a simple prefix—suffix rule to map logical view names to file-system locations.

Example: Java Config — InternalResourceViewResolver

@Configuration
public class WebConfig {
  @Bean
  public ViewResolver viewResolver() {
    return new InternalResourceViewResolver("/WEB-INF/views/", ".jsp");
  }
}

Forms — Data Binding & Validation

Form processing is an integral part of web apps. In Spring MVC two handler methods are used to implement the form processing workflow — one handler mapped to HTTP GET used to retrieve the form page, and another handler to process the form submission — mapped to an HTTP POST, when creating a new resource, or HTTP PUT when updating an existing resource.

Domain or form objects can be used as parameters of handler methods. This triggers a DataBinder (set up by Spring MVC) to map request/form parameters to the value of the corresponding (same name) properties in the domain/form object. Type conversion errors occurred during data-binding are captured and added to an object of type BindingResult. Annotation @Valid is used to validate property values after binding, often according to JSR-330 Java bean validation annotations. Validation errors are also captured and added to the BindingResult. If BindingResult is declared as parameter of the handler, the handler is still called even when an error occurred. (BindingResult parameter should immediately follow the domain/form parameter.)

A common pattern is to redirect the browser after the form POST/PUT to the page showing the details of the resource just created/modified. This is done by returning a string with prefix redirect: , rather than a view name.

Example: Form Retrieval & Submission

@GetMapping("/book")
public String createGET(Book book) {
	return "book/create";
}
@PostMapping("/book")
public String createPOST(@Valid Book book, BindingResult errors) {
  if (errors.hasErrors()) {
	return "book/create";
  }
  book = service.addBook(book);
  return "redirect:book/show/" + book.getId();
}

Example: Validation Annotations in Form Object

public class BookForm {	
	@Length(min=1, max=256)
	private String title;

	@Length(min=2, max=256)
	private String author;

	@Pattern(regexp="^(97(8|9))?\\d{9}(\\d|X)$")
	private String isbn;
	
	@Min(0)
	private Integer pageCount;
	
	@Past
	private Date publishDate;
}

Rest Controllers

Spring MVC is also an effective REST-WS framework — and only a few extra mechanisms are needed beyond what is used to build web apps. The HttpMessageConverter abstraction is used to support automatic decoding&encoding of content sent in requests/responses, according to the MIME types specified in the HTTP content-negotiation headers. (Accept on GET requests, and Content-Type on POST/PUT requests and GET responses.)

The annotation @RequestBody is used in handler parameters to decoded the request body and map it to a Java object using a matching HttpMessageConverter. Similarly, the annotation @ResponseBody is used in handlers to encode the returned object to the HTTP response body. Stereotype annotation @RestController can be used to make the semantics of @ResponseBody the default for a controller. HTTP status codes for responses can be convenient set with annotation @ResponseStatus.

Example: Rest Controller for Resource Retreival

@RestController
@RequestMapping("/api/book")
public class BookRestController {
	@Autowired
	private BookService service;
	
	@GetMapping("/")
	public Object topSelling(@RequestParam(value="n") Long n) {
		return service.findTopSellingBooks(n);
	}
	
	@GetMapping("/{id}")
	public Object details(@PathVariable("id") Long id) {
		Book book = service.findBookById(id);
		if (book==null) {
			throw new NotFoundException(Book.class, id);
		}
		return book;
	}
}

Example: Resource Creation REST Endpoint

@PostMapping("/book/")
@ResponseStatus(HttpStatus.CREATED)
public HttpEntity<Void> create(@Valid @RequestBody Book book,
       HttpServletRequest request) throws URISyntaxException {
  book = service.addBook(book);		
  HttpHeaders headers = new HttpHeaders();
  headers.setLocation(new URI(request.getRequestURI()+"/"+book.getId()));
  return new HttpEntity<Void>(headers);
}

Error Handling

Controllers can declare exception handling methods to deal with errors that occurred during the execution of request handler methods (e.g. to set HTTP status codes, encode and send error descriptors in REST-WS, or render error views in web apps). The annotation @ExceptionHandler declares a method as an exception handler. Attribute exception() specifies the base class of the exception to be handled. The signature of exception handler methods can be a flexible as a regular request handler method.

A complementary approach for error handling is to annotate custom Exception with annotation @ResponseStatus. Spring MVC will set the specified HTTP status code if that exception is thrown and no matching exception handler is found. This approach is not fully general however, as it requires the definition of custom Exceptions.

It is also possible to configure globally how Spring MVC deals with exceptions by defining a bean of type HandlerExceptionResolver, and implementing method resolveException(). The implementation SimpleMappingExceptionResolver is used to map specific Java exception to errors view.

Example: ExceptionHandler in Controller

@ResponseBody
@ExceptionHandler
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Object handleError(RuntimeException e)  {
	return new ErrorResponse(e);
}

Example: Custom Exception w/ HTTP Status Code

@SuppressWarnings("serial")
@ResponseStatus(HttpStatus.NOT_FOUND)
public class NotFoundException extends RuntimeException {
  public NotFoundException(Class<?> type, Long id) {..}
}

Controller Advise

Stereotype annotation @ControllerAdvice defines classes that contain definitions that apply to all (or a sub-set) of controllers, including: common model attributes — methods annotated with @ModelAttribute, data-binder initialization — methods annotated with @InitBinder. and exception handlers — method annotated with @ExceptionHandler.

Example: ControllerAdvice for WebApps

@ControllerAdvice
public class WebAdvice  {
  @Autowired
  private UserService userService;
		
  @ModelAttribute
  public Object currentUser(Principal principal) {
    return userService.findByUsername(principal.getName());
  }
		
  @ExceptionHandler
  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  public String handleError(RuntimeException e)  {
	return "error";
  }
		
  @InitBinder
  public void initBinder(WebDataBinder binder) {
	binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
  }
}

Example: Controller Advice for REST-WS

@ControllerAdvice(basePackageClasses=BookRestController.class)
public class RestAdvice  {		
  @ResponseBody
  @ExceptionHandler
  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  public Object handleError(RuntimeException e)  {
    return new ErrorResponse(e);
  }
}

Spring MVC Configuration

Spring MVC can be configured with wide-range of strategies, such as: ViewResolver, HttpMessageConverter, and many other. Using annotation @EnableWebMvc in a configuration class makes several built-in strategies to be automatically installed, including several HttpMessageConverter for common MIME types (e.g. XML, JSON, etc.). Same effect can be achieve with XML configuration with element <mvc:annotation-config>.

Example: Enabling Built-in Configuration Strategies

@EnableWebMvc
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {	
 ...
}

Applications can customize Spring MVC by defining the strategy objects as Spring beans. An additional convenient approach is to make the configuration class annotated with @EnableWebMvc implement interface WebMvcConfigurer (or extend abstract class WebMvcConfigurerAdapter ), and override the callback methods to set up the configuration strategies.

Example: Configuring a Global Formatter

@EnableWebMvc
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {	
  ...
  @Override
  public void addFormatters(FormatterRegistry registry) {
    registry.addFormatter(new DateFormatter("yyyy-MM-dd"));
  }
}

Table below summarizes the strategy beans used by Spring MVC. Some of them are discussed in following sections.

Bean TypeDescription
HandlerMappingSelects the handler (method) to invoke
HandlerAdapterInvokes handler (method)
HandlerExceptionResolverMaps Exceptions to error views
ViewResolverMaps view names to View objects
LocaleResolver & LocaleContextResolverRetrieve the Locale for request
ThemeResolverRetrieve the Theme for request
MultipartResolverManaged for File Uploads
FlashMapManagerManager for Flash Scope Map

Static Resources

Spring MVC allows static resource for web apps (e.g. images, CSS, JavaScript files) to be loaded from locations other than the default location supported by the Servlet container (i.e. the root “/” of the app). This is set up by configuring an ResourceHttpRequestHandler as a bean, or by implementing WebMvcConfigurerAdapter.addResourceHandlers().

Example: Defining Static Resource Handlers

@EnableWebMvc
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {	
  ...
  @Override
  public void addResourceHandlers(ResourceHandlerRegistry 
      registry) {
    registry.addResourceHandler("/**")
            .addResourceLocations("/", "classpath:/static/");
  }
}

View Controllers

Some views don’t require model objects to be collected by a dedicated handler method (e.g. only renders template markup/text, or use common model objects captured by a @ControllerAdvice or HandlerInterceptor). In this case, the mapping between an URL pattern and a view name can be defined using a ViewController, set up in configuration method addViewControllers(). XML element <mvc:view-controller> can also be used for the same purpose.

Example: Defining Direct View-Controller Mappings

@EnableWebMvc
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {	
  ...
  @Override
  public void addViewControllers(ViewControllerRegistry             
     registry) {
    registry.addViewController("/").setViewName("home");
    registry.addViewController("/about").setViewName("about");
    registry.addViewController("/login").setViewName("login");
  }
}

Content Negotiation

It is possible to render multiple types of views (e.g. HTML, XLS, PDF) by defining multiple ViewResolvers. They are used by Spring MVC in order of priority, and the first one to return a non-null View object is used. Additionally, a “composite” ContentNegotiatingViewResolver can be defined to map view names to View object based on the details of the request (e.g. file extension, Accept header, or request parameter). This allows handler methods to return pure logical view names, and still match and work several view types. The details how the response MIME type is determined can be configure with a ContentNegotiationManager, or corresponding configuration method.

Example: Configuration of ContentNegotiationManager

@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
  configurer.parameterName("_fmt").mediaType("json", MediaType.APPLICATION_JSON);
}

Internationalization & Personalization

Message rendering and (date-time&number) formatting is locale (language/region) sensitive. The locale for each request is determined by a bean of type LocaleResolver. By default, the request header Accept-Language determines the locale (i.e. the browser settings define the locale). To allow users (or the app) to change the locale, an alternative LocaleResolver supporting locale change and persistence should be used — such as CookieLocaleResolver or SessionLocaleResolver. The interceptor LocaleChangeInterceptor should also be configured to change the locale when a defined request parameter is set. Similar approach can be used to change the look&fell of a pages by changing the Theme.

@Bean
public LocaleContextResolver localeResolver() {
  CookieLocaleResolver resolver = new CookieLocaleResolver();
  resolver.setCookieName("LOCALE");
  return resolver;
}

Example: Register Locale&Theme Change Interceptors

@EnableWebMvc
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {	
  ...
  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new LocaleChangeInterceptor());
    registry.addInterceptor(new ThemeChangeInterceptor())
     .addPathPatterns("/**").excludePathPatterns("/admin/**");
  }
}

CORS

CORS (Cross-Origin Resource Sharing) is a W3C standard to “by pass” the same-orign policy of browsers in a secured way — e.g. as long as requests come from trusted sites. Spring MVC +4.2 has built in support for CORS. Controller or handler method specific CORS configuration is set with annotation @CrossOrigin. Global CORS configuration can be defined in configuration method addCorsMappings().

Example: Controller Local CORS Configuration

@CrossOrigin(origins="http://myotherapp.com")
@Controller
public class PortfolioController { ... }

Example: Global CORS Configuration

@Override
public void addCorsMappings(CorsRegistry registry) {
  registry.addMapping("/**").allowCredentials(true)
          .allowedOrigins("myotherapp.dashboard.com");
}

Multi-Part File Uploading

File uploading in web apps is done through submission of an HTTP request with MIME type multipart/form-data (e.g. in a browser by setting attribute enctype of an HTML <form>). In Spring MVC, processing of multi-part requests is done by a bean type MultipartResolver, that decodes the request body and makes it available wrapped in object of type MultipartFile. Two implementation are available out-of-the-box: CommonsMultipartResolver — based on Apache commons library for multi-part file uploading, and StandardServletMultipartResolver — supported natively by the Servlet +3.0 containers.

Access to the uploaded file content can be done by injecting the MultipartFile in a parameter of an handler method annotated with @RequestParam. Alternatively, use annotation @RequestPart which further invokes an HttpMesageConverter to map the request body.

Example: Handling File Upload

@PostMapping("/upload")
public String uploadPhoto(@RequestParam("name") String name, 
 @RequestParam("file") MultipartFile file) throws IOException {
  if (!file.isEmpty()) {
    saveToFile(storeFolder  + name, file.getBytes());
  }
  return "redirect:photoalbum";
}

Example: Configuring a MultipartResolver as a Bean

@Bean
public MultipartResolver multipartResolver() {
	return new CommonsMultipartResolver();
}

Asynchronous Request Processing

Spring MVC support asynchronous request processing for Servlet +3.0 containers. In asynchronous request processing the thread that deliver the request is released quickly, and another thread is used to complete the request. This is useful in cases where processing takes long to complete. Two idioms are supported by Spring MVC. In the simplest, handler methods return an instance of a Callable — the Java interface used to model a concurrent task that computes and returns a value. In this case, Spring MVC schedules a separated thread, to invoke the Callable and send the response to the client.

In the second idiom, the handler method returns an instance of a DeferredResult. In this case, the application is responsible save the request for processing, and to schedule/manage the thread that completes the processing (using a TaskExecutor). The concurrent thread signals the completion of the processing by calling method DeferredResult.setResult().

Example: Asynchronous Processing w/ Callable

@GetMapping("/report")
public Callable<ModelAndView> generateReport(
    final @RequestParam(required=false) Date since) {
  return new Callable<ModelAndView>() {
    public ModelAndView call() throws Exception {
      List<Order> orders = reportService.findAllSince(since);
      return new ModelAndView("report/show", "orders", orders);
    }
  };
}

Resources

Comments and Discussion

Content