This topic covered in more detail:
Later Today @ 12:45
| z, ? | toggle help (this) | 
| space, → | next slide | 
| shift-space, ← | previous slide | 
| d | toggle debug mode | 
| ## <ret> | go to slide # | 
| c, t | table of contents (vi) | 
| f | toggle footer | 
| r | reload slides | 
| n | toggle notes | 
| p | run preshow | 
    // Equivalent to <mvc:annotation:driven/>
    @EnableWebMvc
    @Configuration
    public class WebConfig {
    }
@EnableWebMvc
@Configuration
public class WebConfig {
}
@EnableWebMvc
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
}
@EnableWebMvc
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
  @Override
  protected void addFormatters(FormatterRegistry registry) {
    // ...
  }
}
@EnableWebMvc
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
  @Override
  protected void addFormatters(FormatterRegistry registry) {
    // ...
  }
  @Override
  public void addInterceptors(InterceptorRegistry reg){
    // Equivalent to <mvc:interceptors>
  }
}
@EnableWebMvc
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
  @Override
  protected void addFormatters(FormatterRegistry registry) {
    // ...
  }
  @Override
  public void addInterceptors(InterceptorRegistry reg){
    // Equivalent to <mvc:interceptors>
  }
  // ... more available
}@EnableWebMvcWebMvcConfigurationSupport
@EnableWebMvc
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter{
  @Override
  public void addInterceptors(InterceptorRegistry reg){
    // Equivalent to <mvc:interceptors>
  }
  }
}
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter{
  @Override
  public void addInterceptors(InterceptorRegistry reg){
    // Equivalent to <mvc:interceptors>
  }
  }
}
@Configuration
public class WebConfig extends WebMvcConfigurationSupport{
  @Override
  public void addInterceptors(InterceptorRegistry reg){
    // Equivalent to <mvc:interceptors>
  }
  }
}
@Configuration
public class WebConfig extends WebMvcConfigurationSupport{
  @Override
  public void addInterceptors(InterceptorRegistry reg){
    // Equivalent to <mvc:interceptors>
  }
  @Override
  @Bean
  public RequestMappingHandlerAdapter 
                requestMappingHandlerAdapter() {
  }
}
@Configuration
public class WebConfig extends WebMvcConfigurationSupport{
  @Override
  public void addInterceptors(InterceptorRegistry reg){
    // Equivalent to <mvc:interceptors>
  }
  @Override
  @Bean
  public RequestMappingHandlerAdapter 
                requestMappingHandlerAdapter() {
      // Create or let "super" create and customize 
      // RequestMappingHandlerAdapter ...
  }
}DefaultAnnotationHandlerMappingAnnotationMethodHandlerAdapterAnnotationMethodHandlerExceptionResolverNo concept of selecting a method (rather than a controller) resulting in duplicated handler method selection, inability to split HTTP methods across controllers, etc.
RequestMappingHandlerMappingRequestMappingHandlerAdapterExceptionHandlerExceptionResolver@RequestMapping HandlerMapping@RequestMapping HandlerAdapter@ExceptionHandler ExceptionResolver
HandlerMethodHandlerMethodArgumentResolverHandlerMethodReturnValueHandler
@ResponseBody
@RequestMapping(
        method=RequestMethod.POST,
        header="Content-Type=application/json")
public String save(@RequestBody JavaBean javaBean) {
}
@ResponseBody
@RequestMapping(
        method=RequestMethod.POST, 
        consumes="application/json")
public String save(@RequestBody JavaBean javaBean) {
}headers="Content-Type=text/plain"consumes="text/plain"
    @ResponseBody
    @RequestMapping(
        method=RequestMethod.GET
        header="Accept=application/json")
    public JavaBean get() {
    }
    @ResponseBody
    @RequestMapping(
        method=RequestMethod.GET, 
        produces="application/json") 
    public JavaBean get() {
    }headers="Accept=text/plain"produces="text/plain""Produces" used not only for mapping"text/*"produces = "text/plain""*/*"Accept headerproduces = "application/json""*/*""application/json"produces = "text/plain;charset=UTF-8""text/plain""text/plain;charset=UTF-8""*/*" (or without Accept) header
    @RequestMapping 
    public String get(Model model) {
    }
    @ResponseBody
    @RequestMapping(produces="application/json") 
    public JavaBean get() {
    }
"*/*" (or without Accept) header
    @RequestMapping 
    public String get(Model model) {
        // This method is chosen
    }
    @ResponseBody
    @RequestMapping(produces="application/json") 
    public JavaBean get() {
    }
"*/*" (or without Accept) header
    @RequestMapping 
    public String get(Model model) {
        // This method is chosen
        // As a match to '*/*'
    }
    @ResponseBody
    @RequestMapping(produces="application/json") 
    public JavaBean get() {
    }
@RequestMapping(
    value="/people/{firstName}/{lastName}/SSN")
public void search(Person person) {
    // person.getFirstName() is populated
    // person.getLastName()  is populated
}@PathVariable valuesView types "opt out"MappingJacksonJsonView, MarshallingView
@RequestMapping("/apps/edit/{slug}")
public String editForm(@PathVariable String slug){
}
@RequestMapping("/apps/edit/{slug}")
public String editForm(@PathVariable String slug){
    // No need to add "slug" to the model
}"redirect:" & URI Vars
@RequestMapping(
    value="/{year}/{month}/{slug}/rooms",
    method=RequestMethod.POST)
public String createRoom() {
    return "redirect:/{year}/{month}/{slug}";
}
@RequestMapping(
    value="/{year}/{month}/{slug}/rooms",
    method=RequestMethod.POST)
public String createRoom() {
    // No need to add "year", "month", & "slug"
    return "redirect:/{year}/{month}/{slug}";
}
@RequestMapping(
    value="/{year}/{month}/{slug}/rooms",
    method=RequestMethod.POST)
public String createRoom() {
    // No need to add "year", "month", & "slug"
    // They will be used in RedirectView
    return "redirect:/{year}/{month}/{slug}";
}@ModelAttribute argumentConverter<String, Account>
@RequestMapping(
        value="/{account}", 
        method = RequestMethod.PUT)
public String update(Account account) {
}
@RequestMapping(
        value="/{account}", 
        method = RequestMethod.PUT)
public String update(Account account) {
    // Account was retrieved from DB 
    // via Converter<String, Account>
}Example surprises ROO-2158, SPR-6796
@Controller
public class SomeController {
    @RequestMapping(method=POST)
    public String save(Model model) {
        // ...
        return "redirect:/action";
    }
}
@Controller
public class SomeController {
    @ModelAttribute
    public void populate(Model model) {
    }
    @RequestMapping(method=POST)
    public String save(Model model) {
        // ...
        return "redirect:/action";
    }
}
@Controller
public class SomeController {
    @ModelAttribute
    public void populate(Model model) {
        model.addAttribute(..);
    }
    @RequestMapping(method=POST)
    public String save(Model model) {
        // ...
        return "redirect:/action";
    }
}
@SessionAttributes(..)
@Controller
public class SomeController {
    @ModelAttribute
    public void populate(Model model) {
        model.addAttribute(..);
    }
    @RequestMapping(method=POST)
    public String save(Model model) {
        // ...
        return "redirect:/action";
    }
}
@RequestMapping(method=POST)
public String save(Foo foo, Errors errors, 
                   Model model) {
    if (errors.hasErrors()) {
      return "edit";
    }
    return "redirect:/action";
}
@RequestMapping(method=POST)
public String save(Foo foo, Errors errors, 
                   Model model) {
    if (errors.hasErrors()) {
      return "edit";
    }
    // Clear the model for redirect scenario
    model.asMap().clear()
    return "redirect:/action";
}
@RequestMapping(method=POST)
public String save(Foo foo, Errors errors, 
                   Model model) {
    if (errors.hasErrors()) {
      return "edit";
    }
    // Clear the model for redirect scenario
    model.asMap().clear()
    // Add attributes for redirect
    model.addAttribute("foo", foo.getId());
    model.addAttribute("queryParam", "value");
    return "redirect:/action/{foo}";
}RedirectAttributes
    @RequestMapping(method=POST)
    public String save(Foo foo, Errors errors, 
                       RedirectAttributes redirectAttrs){
      if (errors.hasErrors()) {
        return "edit"; 
      }
      redirectAttrs.addAttribute("id", foo.getId);
      redirectAttrs.addAttribute("queryParam", "value");
      return "redirect:/action/{id}";
    }RedirectAttributes formats added values with a DataBinder
    @RequestMapping(method=POST)
    public String save(Foo foo, Errors errors, 
                       RedirectAttributes redirectAttrs){
      redirectAttrs.addAttribute("date", new Date());
      return "redirect:/action";
    }
    @RequestMapping(method=POST)
    public String save(Foo foo, Errors errors, 
                       RedirectAttributes redirectAttrs){
      // "date" is formatted
      // RedirectView will append it as query param
      redirectAttrs.addAttribute("date", new Date());
      return "redirect:/action";
    }Model and RedirectAttributes
    @RequestMapping
    public String search(String text, 
                         Model model,
                         RedirectAttributes redirectAttrs){
        // Use Model if not redirecting
        // E.g. Ajax request
        // Use RedirectAttrs if redirecting
    }
  @RequestMapping(method=POST)
  public String save(Foo foo, Errors errors){
      // Will the "default" model be used?
      // Or will it be ignored?
      return "redirect:/action";
  }"IgnoreDefaultModelOnRedirect" PropertyRequestMappingHandlerAdapter"false" by default"true"
  @RequestMapping(method=POST)
  public String save(Entity entity, Errors errors, 
                     RedirectAttributes redirectAttrs){
    // ...
    return "redirect:/action";
  }
  @RequestMapping(method=POST)
  public String save(Entity entity, Errors errors, 
                     RedirectAttributes redirectAttrs){
    // ...
    redirectAttrs.addFlashAttribute("message", "Success!");
    return "redirect:/action";
  }
  @RequestMapping(method=POST)
  public String save(Entity entity, Errors errors, 
                     RedirectAttributes redirectAttrs){
    // ...
    redirectAttrs.addFlashAttribute("message", "Success!");
    // Attribute "message" will be available 
    // in model of controller following the redirect
    return "redirect:/action";
  }RedirectAttributesFlashMap underneathFlashMap
// Before redirect
FlashMap flashMap = 
  RequestContextUtils.getOutputFlashMap(request);
// ..FlashMap
// Before redirect
FlashMap flashMap = 
  RequestContextUtils.getOutputFlashMap(request);
// After redirect
Map<String, String> map = 
  RequestContextUtils.getInputFlashMap(request);
// ..FlashMapManagerDispatcherServletFlashMap instancesUriTemplateSPR-8662, SPR-8403, path segments & query params cannot be encoded fully
UriComponentsUriComponentsUriComponentsBuilder.fromPath(String)UriComponentsBuilder.fromUriString(String)
UriComponents uriComponents = 
  UriComponentsBuilder.fromPath("{path}")
    .queryParam("id", 123)
    .fragment("a")
    .build()
    .expand("/some path")
    .encode();// Full control over every step
// vs all in one shot
UriComponents uriComponents = 
  UriComponentsBuilder.fromPath("{path}")
    .queryParam("id", 123)
    .fragment("a")
    .build()
    .expand("/some path")
    .encode();UriTemplate & UriUtils ?UriComponents internallyUriComponents For Redirect"redirect:"@ResponseBody methods too
@RequestMapping
public String handle() {
    // ...
  UriComponents redirectUri = 
      UriComponentsBuilder.fromPath("/path/{id}")
        .query("q={q}")
        .build()
        .expand("123", "q1")
        .encode();
  return "redirect:" + redirectUri.toUriString();
}StandardServletMultipartResolverMultipartFilejavax.servlet.http.Part argsMultipartFile Example
@RequestMapping(method = RequestMethod.POST)
public void create(
        @RequestParam("file") MultipartFile file){
    InputStream in = file.getInputStream();
    // ...
}javax.servlet.http.Part Example
@RequestMapping(method = RequestMethod.POST)
public void create(
        @RequestParam("file") Part part){
    InputStream in = part.getInputStream();
    // ...
}@RequestPart@RequestBody but for the part of a "multipart/form-data" requestHttpMessageConverter
@RequestMapping(
    method = RequestMethod.POST, 
    consumes = { "multipart/form-data" })
public ResponseEntity<Object> void create(
    @RequestPart("json-data") @Valid JavaBean javaBean, 
    @RequestPart("file-data") MultipartFile file) {
   // ...
} POST /someUrl
Content-Type: multipart/mixed
--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
Content-Disposition: json-data; name="meta-data"
Content-Type: application/json; charset=UTF-8
Content-Transfer-Encoding: 8bit
{
  "name": "value"
}
--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
Content-Disposition: form-data; name="file"; filename="file.properties"
Content-Type: text/xml
Content-Transfer-Encoding: 8bit
... File Data ...
@Valid@RequestBody & @RequestPart argsMethodArgumentNotValidExceptionDefaultHandlerExceptionResolver.. SC_BAD_REQUEST (400) response  codeRequestDataValueProcessor@Controller and RestTemplate tests