How to process request parameters with the Spring MVC framework. Namely, the presentation tackles the three primary concerns when dealing with request parameters: data binding, data buffering and data validation. To this end, the Bean Validation API (JSR-303) is discussed, and the concept of MessageSource for localized error messages is introduced. Moreover, The Post/Redirect/Get (PRG) pattern is presented along with a possible implementation strategy.
4. Handling user input
Web apps o)en have to perform some logic against one or more
pieces of informa(on coming from the user
User User's
interaction +-------------+ information +-------------+
| | | |
+--------------> Browser +-------------------> Web app |
| | | |
+-------------+ +-------------+
5. Request parameters
This informa,on is made available through the request parameters
HTTP request
+-------------+
User | Request |
interaction +-------------+ | parameters | +-------------+
| | +-------------+ | |
+--------------> Browser +---------------------> Web app |
| | | |
+-------------+ +-------------+
6. Request parameters
Request parameters are sent as part of the HTTP request message
issued by the client
HTTP request
+-------------+
User | Request |
interaction +-------------+ | parameters | +-------------+
| | +-------------+ | |
+--------------> Browser +---------------------> Web app |
| | | |
+-------------+ +-------------+
9. Query string
Request parameters are specified as a sequence of URL
parameters appended to the request URL
http://my.server/resource?par1=val1&par2=val2
<----------------->
URL parameters
10. Query string
In other words, request parameters are appended to the
query string
http://my.server/resource?par1=val1&par2=val2
<----------------->
query string
12. Gree$ng by name
Assume that we want our applica2on to be able to accept the
name and the surname of the user to greet
13. Gree$ng by name
In order to do so, we first provide an addi1onal overload of
getRandomGreeting()
public interface GreetingService {
public String getRandomGreeting();
public String getRandomGreeting(String name, String surname);
}
14. Parsing request parameters
Second, we need a mechanism for parsing the HTTP request so as
to extract the related request parameters
HTTP request
+--------------------------------------+
| GET /webapp/greeting?name=Jon |
| &surname=Snow |
+--------------------------------------+
| |
| Host: myserver.com |
| User-Agent: ... |
| Accept-Encoding: ... |
| |
+--------------------------------------+
15. @RequestParam
One such mechanism is the @RequestParam annota.on
HTTP request
+--------------------------------------+
| GET /webapp/greeting?name=Jon |
| &surname=Snow |
+--------------------------------------+
| |
| Host: myserver.com |
| User-Agent: ... |
| Accept-Encoding: ... |
| |
+--------------------------------------+
16. @RequestParam
The @RequestParam annota)on binds request parameters to
method parameters
@RequestMapping("/custom-greeting")
public String getGreeting(@RequestParam("name") String name,
@RequestParam("surname") String surname,
Model model) { ... }
17. Request vs. method parameters
The name request parameter binds to the name method parameter
@RequestMapping("/custom-greeting")
public String getGreeting(@RequestParam("name") String name,
@RequestParam("surname") String surname,
Model model) { ... }
18. Request vs. method parameters
The surname request parameter binds to the surname method
parameter
@RequestMapping("/custom-greeting")
public String getGreeting(@RequestParam("name") String name,
@RequestParam("surname") String surname,
Model model) { ... }
19. @RequestParam
We can now add an addi+onal overload of getGreeting() and
associate it with the /custom-greeting endpoint
public class GreetingController {
@RequestMapping("/greeting")
public String getGreeting(Model model) { ... }
@RequestMapping("/custom-greeting")
public String getGreeting(@RequestParam("name") String name,
@RequestParam("surname") String surname,
Model model) { ... }
}
22. URL templates
To trigger the execu-on of getGreeting(), we should construct
a URL according to the following URL template
http://myserver.com/webapp/custom-greeting?name={name}
&surname={surname}
23. HTML forms
A possible way to construct URLs from a URL template is to use an
HTML form
<form action="..." method="...">
</form>
24. HTML forms
HTML forms are hypermedia controls that allow users to supply
data towards an applica7on endpoint
<form action="..." method="...">
</form>
25. HTML forms
We provide one text field per URL parameter, as well as a submit
bu3on
<form action="..." method="...">
<input type="text" name="name">
<input type="text" name="surname">
<button type="submit">Get your greeting!</button>
</form>
26. HTML forms
Since we want values to be sent as part of the query string, we
specify GET as the HTTP method
<form action="..." method="GET">
<input type="text" name="name">
<input type="text" name="surname">
<button type="submit">Get your greeting!</button>
</form>
27. HTML forms
Finally, we provide the endpoint where to send the parameters;
intui6vely we would like to do the following
<form action="/custom-greeting" method="GET">
<input type="text" name="name">
<input type="text" name="surname">
<button type="submit">Get your greeting!</button>
</form>
28. HTML forms
Since a single Web container may host several Web applica4ons,
the correct endpoint is /webapp/custom-greeting1
<form action="/webapp/custom-greeting" method="GET">
<input type="text" name="name">
<input type="text" name="surname">
<button type="submit">Get your greeting!</button>
</form>
1
Where webapp is the name of our Web applica4on
29. HTML forms
However, we would much prefer not to hard-code the name of the
Web applica7on
30. The <spring:url> tag
When using JSP as view technology, we can take advantage of the
<spring:url> tag
<spring:url value="/custom-greeting" var="customGreeting"/>
31. The <spring:url> tag
Given an URL, <spring:url> generates the corresponding
context-aware URL, which is then saved into a page variable
<spring:url value="/custom-greeting" var="customGreeting"/>
32. The <spring:url> tag
That is, links of the form /endpoint are automa2cally
transformed into links of the form /webapp/endpoint
/endpoint → /webapp/endpoint
33. HTML forms
The final HTML form thus reads
<spring:url value="/custom-greeting" var="customGreeting"/>
<form action="${customGreeting}" method="GET">
<input type="text" name="name">
<input type="text" name="surname">
<button type="submit">Get your greeting!</button>
</form>
34. The Spring tag library
The <spring:url> tag is part of the Spring tag library, which is
in turn part of Spring MVC
<spring:url value="/custom-greeting" var="customGreeting"/>
35. The Spring tag library
The Spring tag library can be imported into a JSP page by means of
the taglib direc8ve2
<%@ taglib prefix="spring"
uri="http://www.springframework.org/tags" %>
2
Remember that the taglib direc.ve does not actually load anything, we s.ll need to deploy the Spring tag library
together with the applica.on
38. En#ty body
The en&ty body is an op#onal part of HTTP messages
HTTP message
+----------------------------+
| Start line |
+----------------------------+
| |
| Headers |
| |
+----------------------------+
| |
| Entity body |
| |
+----------------------------+
39. En#ty body
Unlike the start line and the headers, the body can contain text or
binary data
HTTP message
+----------------------------+
| Start line |
+----------------------------+
| |
| Headers |
| |
+----------------------------+
| |
| Entity body |
| |
+----------------------------+
40. En#ty body
While any HTTP request is allowed to contain a body, some
servers will refuse to, e.g., process a GET request with a body3
3
One prominent example is Google (see references)
41. POST
The POST verb is among the HTTP verbs that explicitly admit the
presence of an en5ty body
HTTP message
+--------------------------------+
| POST /custom-greeting HTTP/1.1 |
+--------------------------------+
| |
| Content-Type: ... |
| |
+--------------------------------+
| |
| Entity body |
| |
+--------------------------------+
42. POST
As a ma&er of fact, the POST method is specifically designed to
send input data to the server
44. HTML form
As before, we can use an HTML form to issue a POST request
<spring:url value="/custom-greeting" var="customGreeting"/>
<form action="${customGreeting}" method="POST">
<input type="text" name="name"/>
<input type="text" name="surname"/>
<button type="submit">Get your greeting!</button>
</form>
45. HTML form
Since we want our parameters to be transferred as part of the
en4ty body, we specify POST as the HTTP method
<spring:url value="/custom-greeting" var="customGreeting"/>
<form action="${customGreeting}" method="POST">
<input type="text" name="name"/>
<input type="text" name="surname"/>
<button type="submit">Get your greeting!</button>
</form>
46. How can we decide between GET and POST requests?
47. "...the GET and HEAD methods SHOULD NOT have the significance of
taking an ac?on other than retrieval. These methods ought to be
considered "safe"."
(RFC 2616)
48. "This allows user agents to represent other methods, such as POST,
PUT and DELETE, in a special way, so that the user is made aware of
the fact that a possibly unsafe ac/on is being requested."
(RFC 2616)
49. "The important dis0nc0on here is that the user did not request the
side-effects, so therefore cannot be held accountable for them."
(RFC 2616)
50. Unsafe ac)ons
The user is accountable for the POST requests she executes, as
they represent unsafe ac-ons on the Web applica:on
51. Unsafe ac)ons
Unsafe ac)ons may alter the state of the Web applica)on. For
instance, causing external informa)on sent as form data to be
stored
54. HTML form vs. Web apps
HTML forms give users a place where to enter data
55. Text parameters
The browser sends the data up to the server as a list of name-value
pairs
+--------------------------------------------------+
| POST /webapp/custom-greeting HTTP/1.1 |
+--------------------------------------------------+
| Host: myserver.com |
| User-Agent: ... |
| Content-Type: application/x-www-form-urlencoded |
+--------------------------------------------------+
| name=Jon&surname=Snow |
| |
+--------------------------------------------------+
56. Text parameters
Everything is going to be transferred to the Web app as text
+--------------------------------------------------+
| POST /webapp/custom-greeting HTTP/1.1 |
+--------------------------------------------------+
| Host: myserver.com |
| User-Agent: ... |
| Content-Type: application/x-www-form-urlencoded |
+--------------------------------------------------+
| name=Jon&surname=Snow |
| |
+--------------------------------------------------+
57. HTML form vs. Web apps
But, what if...
• A field has to be interpreted as something different than a
String (e.g., as a Date)?
• The user forgets to provide a mandatory field? does she have to
re-type everything from scratch?
• We want to check that a field respects a given paDern?
58. HTML form vs. Web apps
But, what if...
• We need to perform data conversion?
• We need to perform data buffering?
• We need to perform data valida2on?
59. HTML form vs. Web apps
HTTP/HTML does not provide a component that can buffer,
validate4
and convert inputs coming from a form
4
Here, we are not considering HTML5 Constraint Valida8on API
60. HTML form vs. Web apps
When trying to solve these issues, HTML and HTTP are of no use
to us
61. HTML form vs. Web apps
This is how HTTP and HTML work, Web apps cannot control this
62. The "Sign-up" example
Let us assume that our Web applica2on requires the user to sign up
for a new account
63. The "Sign-up" example
To this end, we introduce:
• An annotated controller named AccountController
• A registra1on page
66. The Account bean
Once we get the data, we may want to store them in a JavaBean
public class Account {
private String name;
private String surname;
private String email;
private Date birthday;
// getters and setters
}
67. The Account bean
Which is the best way of moving from the request parameters to
the corresponding Account object?
+------------------------------------------------+
| POST /webapp/account HTTP/1.1 | public class Account {
+------------------------------------------------+
| Host: myserver.com | private String name;
| User-Agent: ... | private String surname;
| Content-Type: application/x-www-form-urlencoded| private String email;
+------------------------------------------------+ +-----> private Date birthday;
| name=Jon& |
| surname=Snow& | // getters and setters
| birthday=10-1-1956 | }
+------------------------------------------------+
68. The naïve solu-on
We could try to get each single request parameter individually
@RequestMapping("/account")
public String addAccount(@RequestParam("name") String name,
@RequestParam("surname") String surname,
@RequestParam("email") String email,
@RequestParam("birthday") Date birthday,
Model model) {
// we manually populate the Account object with
// the data coming from the user
Account a = new Account();
a.setName(name);
...
}
69. Method parameters in OOP
“The ideal number of arguments for a func5on is zero. Next comes one,
followed closely by two. Three arguments should be avoided where
possible. More than three requires very special jus5fica5on - and then
shouldn’t be used anyway.”
(B. Mar(n)
70. The naïve solu-on
By doing so, we effec/vely clu$ered the handler method signature
@RequestMapping("/account")
public String addAccount(@RequestParam("name") String name,
@RequestParam("surname") String surname,
@RequestParam("email") String email,
@RequestParam("birthday") Date birthday,
Model model) {
Account a = new Account();
a.setName(name);
...
}
71. Data binding
Wouldn’t it be great if data coming from the form could be
automa&cally bound to an Account object?
+------------------------------------------------+
| POST /webapp/account HTTP/1.1 | public class Account {
+------------------------------------------------+
| Host: myserver.com | private String name;
| User-Agent: ... | private String surname;
| Content-Type: application/x-www-form-urlencoded| private String email;
+------------------------------------------------+ +-----> private Date birthday;
| name=Jon& |
| surname=Snow& | // getters and setters
| birthday=10-1-1956 | }
+------------------------------------------------+
72. Data binding
Data binding is the process of binding the request parameters to a
so called form bean
public class Account {
private String name;
private String surname;
private String email;
private Date birthday;
// getters and setters
}
73. Data binding
The form bean is also called the form-backing bean, the form
object or the command object
public class Account { ... }
74. Data binding
It turns out that all we need to do is to declare an Account object
as a method parameter
@RequestMapping("/account")
public String addAccount(Account account) {
// account will be automatically populated
// with the request parameters
}
75. Data binding
The following sequence of opera3ons occurs:
• A new form bean is instan(ated
• The form bean is added to the model
• The form bean is populated from the request parameters
76. The form bean is a model a0ribute
The account form bean will be automa&cally added to the model
Model
+----------------+
| |
| account |
HTTP +-------------------+ +----------------+ +--------------------+
request | | | |
+--------> DispatcherServlet +----------------------> AccountController |
| | | |
+-------------------+ +--------------------+
77. The form bean is a model a0ribute
Hence, views can access and render the form bean content
Model Model
+--------------+ +--------------+
| | | |
| account | | account |
+----------+ +--------------+ +-------------------+ +--------------+ +--------------------+
| | | | | |
| View <--------------------+ DispatcherServlet <--------------------+ AccountController |
| | | | | |
+----------+ +-------------------+ +--------------------+
78. The form bean is a model a0ribute
<html>
<head>
<title>Thanks</title>
</head>
<body>
Hi, ${account.name} ${account.surname}.
You have successfully registered. <br/>
</body>
</html>
80. Default values
To this end, we add a marketingOk property in the Account
form bean
public class Account {
private String name;
private String surname;
private String email;
private Date birthday;
private boolean marketingOk;
// getters and setters
}
81. Default values
By default, we would like the marketingOk property to be true
public class Account {
private String name;
private String surname;
private String email;
private Date birthday;
private boolean marketingOk = true;
// getters and setters
}
82. Prepopula)ng Account
As a ma&er of fact, we are prepopula(ng the Account bean
public class Account {
private String name;
private String surname;
private String email;
private Date birthday;
private boolean marketingOk = true;
// getters and setters
}
84. Showing prepopulated data
Recall that we can send data to the view through the model object
+--------------------------------------+
| |
| +----------------------+ |
| Name: | | |
| +----------------------+ |
| |
| +----------------------+ |
| Surname: | | | +--------------+
| +----------------------+ | | |
| <----------+ Account bean |
| +----------------------+ | | |
| Date: | | | +--------------+
| +----------------------+ |
| |
| +-+ |
| |✓| Please send me product updates |
| +-+ |
| +-------------+ |
| | Sign-up! | |
| +-------------+ |
+--------------------------------------+
85. Showing prepopulated data
What if we let the AccountController return a prepopulated
form bean as part of the model?
Model Model
+--------------+ +--------------+
| | | |
| account | | account |
+----------+ +--------------+ +-------------------+ +--------------+ +--------------------+
| | | | | |
| View <--------------------+ DispatcherServlet <--------------------+ AccountController |
| | | | | |
+----------+ +-------------------+ +--------------------+
86. AccountController
@Controller
public class AccountController {
@RequestMapping(value="/account/new", method=RequestMethod.GET)
public String getEmptyAccount(Model model) {
model.addAttribute(new Account());
return "account/new";
}
@RequestMapping(value="/account/new", method=RequestMethod.POST)
public String addAccount(Account account) { ... }
}
87. Data binding-aware forms
Once the prepopulated Account object is available in the view, we
need to bind its content to the Web form
+--------------------------------------+
| |
| +----------------------+ |
| Name: | | |
| +----------------------+ |
| |
| +----------------------+ |
| Surname: | | | +--------------+
| +----------------------+ | | |
| <----------+ Account bean |
| +----------------------+ | | |
| Date: | | | +--------------+
| +----------------------+ |
| |
| +-+ |
| |✓| Please send me product updates |
| +-+ |
| +-------------+ |
| | Sign-up! | |
| +-------------+ |
+--------------------------------------+
88. Spring form tag library
To deal with prepopulated form beans, Spring provides a set of
data binding-aware tags
89. The revised registra-on form
<form:form modelAttribute="account">
Name: <form:input path="name"/> <br/>
Surname: <form:input path="surname"/> <br/>
Email: <form:input path="email"/> <br/>
Birthday: <form:input path="birthday"/> <br/>
<form:checkbox path="marketingOk"/>
Please send me product updates via e-mail <br/>
<button type="submit">Sign-up<button/>
</form:form>
90. Form tags vs. HTML tags
┌───────────────────────────────────┬───────────────────────────┐
│ Spring form tag library │ HTML │
├───────────────────────────────────┼───────────────────────────┤
│ <form:form> │ <form> │
├───────────────────────────────────┼───────────────────────────┤
│ <input:text> │ <input type="text"> │
├───────────────────────────────────┼───────────────────────────┤
│ <input:password> │ <input type="password"> │
├───────────────────────────────────┼───────────────────────────┤
│ <input:checkbox> │ <input type="checkbox"> │
└───────────────────────────────────┴───────────────────────────┘
91. Spring form tags
The <form:input>, <form:password> and
<form:checkbox> tags are data-binding versions of the
corresponding HTML elements
92. Spring form tags
The tag library does not provide anything for submit bu(ons, as
there is nothing to bind to
<button type="submit">Sign-up<button/>
93. Spring form tag library
To use the tags from the form library, the following direc7ve needs
to be added at the top of the JSP page:
<%@ taglib prefix=”form"
uri="http://www.springframework.org/tags/form" %>
95. Users make mistakes
No ma&er how intui/ve the registra/on form, users will
accidentally fill it out with invalid informa,on
+--------------------------------------+
| |
| +----------------------+ |
| Name: | | |
| +----------------------+ |
| |
| +----------------------+ |
| Surname: | | |
| +----------------------+ |
| |
| +----------------------+ |
| Date: | | |
| +----------------------+ |
| |
| +-+ |
| |✓| Please send me product updates |
| +-+ |
| +-------------+ |
| | Sign-up! | |
| +-------------+ |
+--------------------------------------+
96. Users make mistakes
When this happens, we want to explain the error in nontechnical
language and help the users to overcome it
+--------------------------------------+
| |
| +----------------------+ |
| Name: | | |
| +----------------------+ |
| |
| +----------------------+ |
| Surname: | | |
| +----------------------+ |
| |
| +----------------------+ |
| Date: | | |
| +----------------------+ |
| |
| +-+ |
| | | Please send me product updates |
| +-+ |
| +-------------+ |
| | Sign-up! | |
| +-------------+ |
+--------------------------------------+
97. Data valida)on
To detect user’s errors, we need to validate the form data that are
encapsulated in the form bean
public class Account {
private String name;
private String surname;
private String email;
private Date birthday;
private boolean marketingOk = true;
// getters and setters
}
98. Data valida)on
Example: the email property should respect the pa/ern
username@provider.tld
public class Account {
private String name;
private String surname;
private String email;
private Date birthday;
private boolean marketingOk = true;
// getters and setters
}
99. Bean Valida*on API
The Bean Valida*on API (JSR-303) is a specifica3on that defines a
metadata model and API for JavaBean valida3on
100. Bean Valida*on API
Using this API, it is possible to annotate bean proper4es with
declara*ve valida*on constraints
• @NotNull, @Pattern, @Size
101. Constraining Account
public class Account {
@Pattern(regexp="^[A-Z]{1}[a-z]+$")
@Size(min=2, max=50)
private String name;
@Pattern(regexp="^[A-Z]{1}[a-z]+$")
@Size(min=2, max=50)
private String surname;
@NotNull
@Email
private String email;
@NotNull
private Date birthday;
}
102. Constraining Account
We impose name and surname to start with a capital le2er and
have at least one addi4onal lowercase le2er
public class Account {
@Pattern(regexp="^[A-Z]{1}[a-z]+$")
@Size(min=2, max=50)
private String name;
@Pattern(regexp="^[A-Z]{1}[a-z]+$")
@Size(min=2, max=50)
private String surname;
}
103. Constraining Account
We impose email to respect the username@provider.tld
pa.ern
public class Account {
@NotNull @Email
private String email;
}
104. Hibernate Validator
As with any other Java Enterprise Edi3on API, the standard defines
only the API specifica,on
105. Hibernate Validator
We are going to use Hibernate Validator, which is the reference
implementa5on of the JSR-303 specifica5on
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.1.3.Final</version>
</dependency>
106. Custom annota*ons
Along with the standard constraints, Hibernate Validator provides
some custom annota*ons (e.g., @Email)
public class Account {
@Email
private String email;
}
107. Custom annota*ons
Remember: those annota)ons are Hibernate-specific and they will
not work with any other JSR-303 implementa)on
public class Account {
@Email
private String email;
}
108. Valida&ng account
Valida&on is achieved through the @Valid annota&on
@RequestMapping(...)
public String addAccount(@Valid Account account) { ... }
109. Valida&ng account
The @Valid annota)on causes the account object to be first
validated and then added to the model
@RequestMapping(...)
public String addAccount(@Valid Account account) { ... }
110. Valida&ng account
The handler method may ask for a BindingResult object, which
represent th result of the valida9on process
@RequestMapping(...)
public String addAccount(@Valid Account account,
BindingResult bindingResult) { ... }
111. Valida&ng account
We can then inspect the bindingResult object for possible
valida&on errors
@RequestMapping(...)
public String addAccount(@Valid Account account,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) return "account/new";
...
}
112. Data buffering
Since the account bean is part of the model, it will be sent back
to the view even in case of errors
Model Model
+--------------+ +--------------+
| | | |
| account | | account |
+----------+ +--------------+ +-------------------+ +--------------+ +--------------------+
| | | | | |
| View <--------------------+ DispatcherServlet <--------------------+ AccountController |
| | | | | |
+----------+ +-------------------+ +--------------------+
113. Data buffering
Hence, the user is not forced to re-enter the data from scratch
when asked to correct her mistakes
+--------------------------------------+
| |
| +----------------------+ |
| Name: | Jon | |
| +----------------------+ |
| |
| +----------------------+ |
| Surname: | 1234 | |
| +----------------------+ |
| |
| +----------------------+ |
| Date: | | |
| +----------------------+ |
| |
| +-+ |
| | | Please send me product updates |
| +-+ |
| +-------------+ |
| | Sign-up! | |
| +-------------+ |
+--------------------------------------+
114. Data buffering
Hence, the user can correct her mistakes, while keeping the correct
data untouched
+--------------------------------------+
| |
| +----------------------+ |
| Name: | Jon | |
| +----------------------+ |
| |
| +----------------------+ |
| Surname: | 1234 | |
| +----------------------+ |
| |
| +----------------------+ |
| Date: | | |
| +----------------------+ |
| |
| +-+ |
| | | Please send me product updates |
| +-+ |
| +-------------+ |
| | Sign-up! | |
| +-------------+ |
+--------------------------------------+
115. Error messages
Even if we returned the ini.al form, we s.ll have to inform the user
on the reason why the data have been rejected
+--------------------------------------+
| |
| +----------------------+ |
| Name: | Jon | |
| +----------------------+ |
| |
| +----------------------+ | "Hey user, the
| Surname: | snow <----------+ surname should
| +----------------------+ | start with a
| | capital letter"
| +----------------------+ |
| Date: | | |
| +----------------------+ |
| |
| +-+ |
| | | Please send me product updates |
| +-+ |
| +-------------+ |
| | Sign-up! | |
| +-------------+ |
+--------------------------------------+
116. Error messages
To this end, the BindingResult object is automa&cally inserted
into the model and sent back to the view
Model Model
+--------------+ +--------------+
| account | | account |
+--------------+ +--------------+
|bindingResult | |bindingResult |
+----------+ +--------------+ +-------------------+ +--------------+ +--------------------+
| | | | | |
| View <--------------------+ DispatcherServlet <--------------------+ AccountController |
| | | | | |
+----------+ +-------------------+ +--------------------+
124. Message interpola.on
We can define the message descriptor of each property through
the message a4ribute
public class Account {
@NotNull(message = "the email address cannot be empty")
@Email(message = "please provide a valid e-mail address")
private String email;
}
125. Message interpola.on
The problem with message a1ributes is that we are hard-coding
the error messages
public class Account {
@NotNull(message = "the email address cannot be empty")
@Email(message = "please provide a valid e-mail address")
private String email;
}
126. What if we want the messages to change according to, e.g., the user’s
locale?
127. Resource bundle
A be%er alterna+ve is to store the error messages in a separate file,
called the resource bundle
128. Resource bundle
By doing so, error messages can be updated independently of the
source code (and vice versa)
129. Resource bundle
We may devise the following resource bundle:
NotBlank.account.email=the email address cannot be empty
Email.account.email=please provide a valid e-mail address
NotNull.account.birthday=The date cannot be empty
134. Adding a MessageSource
We therefore change frontcontroller-servlet.xml in order
to have the Spring DI Container load the MessageSource5
<bean id="messageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="classpath:validationMessages" />
</bean>
5
Remember: the MessageSource bean must have the id equal to messageSource
135. Using message codes
We can now specify the message code in lieu of the message itself
public class Account {
@NotBlank(message = "{NotBlank.account.email}")
@Email(message = "{Email.account.email}")
private String email;
}
137. Duplicate submissions
Most of the )mes, form data are submi2ed as POST requests
+--------------+ +--------------+
| +------- POST --------> |
| Browser | | Web app |
| <------ 200 OK -------+ |
+--------------+ +--------------+
138. Duplicate submissions
What if the user presses refresh on the browser?
+--------------+ +--------------+
| +------- POST --------> |
| Browser | | Web app |
| <------ 200 OK -------+ |
+--------------+ +--------------+
139. Duplicate submissions
Since refreshing means resending the latest HTTP request, it will
cause the HTTP POST to be resubmi+ed
+--------------+ +--------------+
+-------> +------- POST --------> |
Refresh | | Browser | | Web app |
+-------+ <------ 200 OK -------+ |
+--------------+ +--------------+
140. Duplicate submissions
Given the unsafe nature of POST, resending the latest POST
request may lead to unwanted results
+--------------+ +--------------+
+-------> +------- POST --------> |
Refresh | | Browser | | Web app |
+-------+ <------ 200 OK -------+ |
+--------------+ +--------------+
141. The PRG pa*ern
The Post/Redirect/Get (PRG) pa/ern solves the duplicate
submission problem
142. The PRG pa*ern
According to the PRG pa2ern, the Web app should not immediately
return the outcome of the POST opera?ons
+--------------+ +--------------+
| +------- POST --------> |
| Browser | | Web app |
| <------ 200 OK -------+ |
+--------------+ +--------------+
143. The PRG pa*ern
Instead, the Web applica1on should answer with a redirect
response
+--------------+ +--------------+
| +------- POST --------> |
| Browser | | Web app |
| <--- 3xx REDIRECT ----+ |
+--------------+ +--------------+
144. The PRG pa*ern
This causes the client to automa/cally issue a new GET request, to
which the Web app finally answers with the actual content
+--------------+ +--------------+
| +------- GET -------> |
| Browser | | Web app |
| <------ 200 OK -------+ |
+--------------+ +--------------+
145. The PRG pa*ern
Thus, if the user refreshes the page, the GET request will be send,
instead of the original HTTP POST
+--------------+ +--------------+
+-------> +------- GET -------> |
Refresh | | Browser | | Web app |
+-------+ <------ 200 OK -------+ |
+--------------+ +--------------+
146. Redirec'ng
In order to force a redirect, it is sufficient to return the redirect
URL prefixed with the label redirect:
@RequestMapping(...)
public String addAccount(@Valid Account account,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) return "account/edit";
service.saveAccount(account);
return "redirect:/account/thanks";
}
147. Redirec'ng
The redirect: prefix is used as a special indica+on that a
redirect is needed
@RequestMapping(...)
public String addAccount(@Valid Account account,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) return "account/edit";
service.saveAccount(account);
return "redirect:/account/thanks";
}
148. Redirec'ng
Note that what follows redirect: is used as the redirect URL
@RequestMapping(...)
public String addAccount(@Valid Account account,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) return "account/edit";
service.saveAccount(account);
return "redirect:/account/thanks";
}
149. Redirec'ng
That is, /account/thanks has to be intended as
http://myserver.com/webapp/account/thanks
@RequestMapping(...)
public String addAccount(@Valid Account account,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) return "account/edit";
service.saveAccount(account);
return "redirect:/account/thanks";
}
150. View controllers
We need to associate a handler method with /account/thanks
@Controller
@RequestMapping("/account")
public class AccountController {
@RequestMapping("/thanks")
public String getThanks() { return "thanks"; }
}
151. View controllers
The sole responsibility of such a handler method would be to
return the /account/thanks view name
@RequestMapping("/thanks")
public String getThanks() { return "thanks"; }
152. View controllers
Star%ng from Spring 3, it is possible to declara've set up
controllers whose unique responsibility is to return a view name
153. View controllers
The following declares in frontcontroller-servlet.xml a
controller capable of answering to /account/thanks
<mvc:view-controller path="/account/thanks”
view-name="account/thanks"/>
154. Model life)me
As the model contains the data to be rendered by the view, its
life6me is limited by the request/response lifecycle
155. Model life)me
In other words, a new model object is created for each request
that hits DispatcherServlet
+-------+
HTTP | Model |
request +---------------------+ +-------+ +--------------+
| | | |
+-------> DispatcherServlet +-------------> Controller |
| | | |
+---------------------+ +--------------+
156. PRG pa'ern vs. model a'ributes
A redirect creates a new request, hence causing the model
a3ributes to be discarded
+--------------+ +--------------+
| +------- POST --------> |
| Browser | | Web app |
| <--- 3xx REDIRECT ----+ |
+--------------+ +--------------+
157. What if we want to retain some model a1ributes?
158. The flash scope
A possible solu+on is store a0ributes of interest in the flash scope
159. The flash scope
The flash scope works similarly to the session scope
+-----+ Request +-----+
| +----------------> | +--+-------+-+
| | | | | |
| | Response | | | F |
| <----------------+ | | l |
| B | | W | | a |
| r | Request | e | S | s |
| o +----------------> b | e | h |
| w | | | s | |
| s | Response | a | s | |
| e <----------------+ p +------ i | ------v--
| r | | p | o |
| | Request | | n |
| +----------------> | |
| | | | |
| | Response | | |
| <----------------+ +---------v---
+-----+ +-----+
160. The flash scope
The difference is that flash a&ributes are kept solely for the
subsequent request
+-----+ Request +-----+
| +----------------> | +--+-------+-+
| | | | | |
| | Response | | | F |
| <----------------+ | | l |
| B | | W | | a |
| r | Request | e | S | s |
| o +----------------> b | e | h |
| w | | | s | |
| s | Response | a | s | |
| e <----------------+ p +------ i | ------v--
| r | | p | o |
| | Request | | n |
| +----------------> | |
| | | | |
| | Response | | |
| <----------------+ +---------v---
+-----+ +-----+
161. The flash scope
Flash a'ributes are stored before the redirect and made available
as model a'ributes a2er the redirect
+-----+ Request +-----+
| +----------------> | +--+-------+-+
| | | | | |
| | Response | | | F |
| <----------------+ | | l |
| B | | W | | a |
| r | Request | e | S | s |
| o +----------------> b | e | h |
| w | | | s | |
| s | Response | a | s | |
| e <----------------+ p +------ i | ------v--
| r | | p | o |
| | Request | | n |
| +----------------> | |
| | | | |
| | Response | | |
| <----------------+ +---------v---
+-----+ +-----+
162. RedirectAttributes
A handler method can declare an argument of type
RedirectAttributes...
@RequestMapping(...)
public String addAccount(@Valid Account account,
BindingResult bindingResult,
RedirectAttributes redirectAttributes) {...}
163. RedirectAttributes
...and use its addFlashAttribute() method to add a.ributes in
the flash scope
@RequestMapping(...)
public String addAccount(@Valid Account account,
BindingResult bindingResult,
RedirectAttributes redirectAttributes) {
if (bindingResult.hasErrors()) return "account/new";
redirectAttributes.addFlashAttribute("account", account);
return ”redirect:/account/thanks”;
}
164. The "Thank you" page
account is accessible in the view, since it has been automa2cally
inserted into the next request model
<html>
<head>
<title>Thanks</title>
</head>
<body>
Hi, ${account.name} ${account.surname}.
You have been successfully registered. <br/>
</body>
</html>
166. Form beans
Form beans are versa&le objects, as they play different roles at the
same 1me
• Data binder
• Data buffer
• Data validator
167. Data binder
Developers can work with a trusty POJO and leave all the HTML/
HTTP issues to the framework
• Binding
• Coercion
168. Data buffer
The form bean is not the des0na0on of the input, but a buffer that
preserves the input un0l it can be validated and commi7ed to the
service layer
169. Data validator
Many fields must be the correct type before they can be processed
by the business logic
171. References
• Stackoverflow, GET vs. POST does it really ma;er?
• Stackoverflow, HTTP GET with request body
• R. Fielding, Introductory REST ArHcle
• IETF, Hypertext Transfer Protocol – HTTP/1.1 (RFC 2616)
• SpringSource, Spring Framework Reference
173. References
• Craig Walls, Spring in Ac1on (3rd ed.), Manning Publica1ons
• Willie Wheeler, Spring in Prac1ce, Manning Publica1ons
• T. N. Husted et al., Struts 1 In Ac1on, Manning Publica1ons