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
}
@EnableWebMvc
WebMvcConfigurationSupport
@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 ...
}
}
DefaultAnnotationHandlerMapping
AnnotationMethodHandlerAdapter
AnnotationMethodHandlerExceptionResolver
No concept of selecting a method (rather than a controller) resulting in duplicated handler method selection, inability to split HTTP methods across controllers, etc.
RequestMappingHandlerMapping
RequestMappingHandlerAdapter
ExceptionHandlerExceptionResolver
@RequestMapping
HandlerMapping
@RequestMapping
HandlerAdapter
@ExceptionHandler
ExceptionResolver
HandlerMethod
HandlerMethodArgumentResolver
HandlerMethodReturnValueHandler
@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";
}
RedirectAttributes
FlashMap
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);
// ..
FlashMapManager
DispatcherServlet
FlashMap
instancesUriTemplate
SPR-8662, SPR-8403, path segments & query params cannot be encoded fully
UriComponents
UriComponents
UriComponentsBuilder.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();
}
StandardServletMultipartResolver
MultipartFile
javax.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
argsMethodArgumentNotValidException
DefaultHandlerExceptionResolver
.. SC_BAD_REQUEST
(400
) response codeRequestDataValueProcessor
@Controller
and RestTemplate
tests