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 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.
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.
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.
@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";
}
}
@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/Annotation | Description |
---|---|
@Controller | Web Component Stereotype (detected with @ComponentScan) |
@RestController | REST-WS Component Stereotype |
@ControllerAdvice | Common Behavior across Controllers |
@RequestMapping | Defines dispatching/mapping rule |
@GetMapping @PostMapping@PutMapping @DeleteMapping @PatchMapping | Similar to @RequestMapping but implying a specific HTTP method |
@ResponseStatus | Set HTTP response status |
@ExceptionHandler | Handler for throw exception |
@ModelAttribute | Name for model attribute |
@InitBinder | Initialize DataBinder |
@SessionAttribute | Declares 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/Annotation | Description |
---|---|
Model ,Map ,ModelMap | Container for model objects (out param) |
@RequestParam | A named request/query parameter |
@PathVariable | A named URL path segment |
@ModelAttribute | Name for model attribute |
@Valid | Validate Form Model Object |
Errors , BindingResult | Collector of binding/validation errors |
@HeaderAttribute | Inject value of HTTP request header |
@MatrixAttribute | Inject parameter — matrix syntax |
@DateTimeFormat | Parse format for Dates |
@NumberFormat | Parse format for Numbefrss |
@RequestBoby | Map parameter from request body |
@ResponseBody | Map object to response body |
HttpEntity<> | Descriptor for request headers&body |
@SessionAttribute @RequestAttribute | Lookup session/request attribute |
Locale | Locale for request |
TimeZone /ZoneId +Java8 | Timezone for request |
Theme | Theme for request |
Principal | Authenticated user for request |
@RequestPart | Uploaded part of multipart/form-data |
WebRequest WebNativeRequest | Request/response descriptor (Spring) |
HttpMethod | HTTP method for request |
HttpServletRequest HttpServletResponse | Request & response descriptors (Servlet API) |
HttpSession | Session descriptor |
SessionStatus | Handler for session (attributes) |
InputStream/Reader | Request (socket) input stream |
OutputStrem/Writer | (Socket) output stream for responsev |
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.
@Configuration
public class WebConfig {
@Bean
public ViewResolver viewResolver() {
return new InternalResourceViewResolver("/WEB-INF/views/", ".jsp");
}
}
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.
@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();
}
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;
}
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 HttpMessageConverte
r. 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
.
@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;
}
}
@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);
}
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.
@ResponseBody
@ExceptionHandler
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Object handleError(RuntimeException e) {
return new ErrorResponse(e);
}
@SuppressWarnings("serial")
@ResponseStatus(HttpStatus.NOT_FOUND)
public class NotFoundException extends RuntimeException {
public NotFoundException(Class<?> type, Long id) {..}
}
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
.
@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"));
}
}
@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 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>
.
@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.
@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 Type | Description |
---|---|
HandlerMapping | Selects the handler (method) to invoke |
HandlerAdapter | Invokes handler (method) |
HandlerExceptionResolver | Maps Exceptions to error views |
ViewResolver | Maps view names to View objects |
LocaleResolver & LocaleContextResolver | Retrieve the Locale for request |
ThemeResolver | Retrieve the Theme for request |
MultipartResolver | Managed for File Uploads |
FlashMapManager | Manager for Flash Scope Map |
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()
.
@EnableWebMvc
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
...
@Override
public void addResourceHandlers(ResourceHandlerRegistry
registry) {
registry.addResourceHandler("/**")
.addResourceLocations("/", "classpath:/static/");
}
}
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.
@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");
}
}
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.
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.parameterName("_fmt").mediaType("json", MediaType.APPLICATION_JSON);
}
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;
}
@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 (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()
.
@CrossOrigin(origins="http://myotherapp.com")
@Controller
public class PortfolioController { ... }
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowCredentials(true)
.allowedOrigins("myotherapp.dashboard.com");
}
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.
@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";
}
@Bean
public MultipartResolver multipartResolver() {
return new CommonsMultipartResolver();
}
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()
.
@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);
}
};
}
Comments and Discussion