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