标签:
struts2ajaxvalidation杂谈 |
分类: 转载 |
Support for Ajax and JavaScript takes the pain out of Web-form validation
Writing code to validate Web-form input can be even more of a chore than implementing form-processing logic. But help is at hand, thanks to the Struts 2 framework. Oleg Mikheev looks under the hood of the Struts 2 validation mechanism and shows you how its Java, JavaScript, and Ajax support can take the pain out of Web-form validation.
Web applications often require user input, which can range from simple username/password values to data entered into a complex form with dependent fields. The task of validating Web-form input is often more complex than implementing the logic to execute on the data after it's submitted. A validation framework can help simplify validation coding -- and the more complex your validation rules, the more pain the framework can spare you. Apache Struts was one of the first Web application frameworks designed to automate Web forms processing, and it is the best-known. This article explores the powerful form-validation options offered by Struts' successor, Struts 2. I'll delve briefly into Struts 2's underlying validation architecture, then show you how the framework supports both server- and client-side validation, including advanced Ajax validation. The article features an example Web application with full source code and binaries ready for deployment into a servlet container.
Struts 2's validation architecture
Struts 2 is based on WebWork, another powerful but less popular Web application framework. Struts 2 shares WebWork's approach to validation. The only difference is that WebWork relies on the validation framework in XWork 1, while Struts 2 uses the improved and customized XWork 2. XWork is a comprehensive command-pattern framework that does a huge part of the work related to Struts 2 configuration, instantiation, and runtime processing. (Although XWork is mostly known for its connection with WebWork and Struts 2, it can be used as a separate framework.)
The key component in XWork is Action.
Action is basically a class that contains the code
that you want to execute on a specific request originating from the
browser. Another important XWork component is
Interceptor. As its name suggests,
Interceptor intercepts calls to Action to
do more processing on them -- a mechanism quite similar to the
concept of aspect-oriented programming (AOP). XWork comes with a
number of already implemented interceptors, and Struts 2 adds some
more of its own. You are free to implement your own custom
interceptors too.
Struts 2 actions and interceptors are configured in a struts.xml
file. Struts 2 looks for struts.xml in WEB-INF/classes (unlike
Struts 1, which stores its struts.xml in the WEB-INF folder).
Configurations can be grouped in a package -- another
XWork component that represents a logical configuration unit that
groups other components. Packages can be extended and overridden by
"sub" packages. In most cases you won't create your Struts 2
configuration from scratch; instead it will extend the default
configuration that is stored in the struts-default
package. The Struts 2 default configuration is stored in the
struts-core.jar archive's root folder in the struts-default.xml
file. You can examine this file if you are interested in deeper
exploration of Struts 2 defaults and internals.
Struts 2 interceptors can be grouped into an
InterceptorStack. The struts-default
package defines a number of interceptor stacks, but in most cases
you'll use the defaultStack. Struts 2 validation is
handled by a validation interceptor that is included
in the defaultStack.
Taking action
Let's consider a simple Struts 2 action, called
FirstAction, with three fields:
name:Stringage:Integer-
gender:Gender<Enum>
(I'll explain later why objects are preferred to simple Java
types.) Depending on the value of
gender:Gender<Enum>,
FirstAction opens either Male.jsp or Female.jsp, as
shown in Listing 1:
Listing 1. Struts configuration
<struts>
<package name="test" extends="struts-default">
<action name="First" class="struts2validation.FirstAction">
<interceptor-ref name="defaultStack"/>
<result name="female">/Female.jsp</result>
<result>/Male.jsp</result>
</action>
</package>
</struts>
The name attribute can be omitted from one of the
two <result> tags. That
result's name automatically defaults to success.
Server-side validation
Once an HTML form is submitted, the request is intercepted by
all interceptors from the defaultStack, including
validation, which is an XWork
ValidationInterceptor. Starting with Struts 2.0.4, the
XWork ValidationInterceptor is extended by
AnnotationValidationInte, which adds
functionality to disable validation of action methods that are
annotated by a @SkipValidation annotation.
The validation framework looks for an XML validation configuration whose name is ActionClass-validation.xml, where ActionClass is the name of the related action class. The validation configuration must be located in the same package as the action class itself. Because a single action class could be used in different actions, it is possible to have a validation configuration for each of the actions, in which case the file should be named ActionClass-ActionName-validation.xml. Struts 2 supports object-oriented programming concepts and considers validation configurations for all classes that the action extends and all interfaces that it implements.
Now I'll demonstrate a simple validation. I'll create a
validation configuration for the FirstAction class and
call it FirstAction-validation.xml.
First, though, I need to create a FirstInput.jsp file with a form, using Struts 2 custom tags. Custom tags use templates for rendering HTML code. Themes are collections of templates grouped by their output methods. I'll discuss themes later. Right now, you only need to know that four themes come with Struts 2:
simple(so simple that it doesn't provide validation support)xhtmlcss_htmlajax
The example form will use the xhtml theme. The
<s:actionerror> tag
displays any action errors. The
<form> tag produces an
HTML form with two HTML inputs (a
<s:textfield> tag and a
<s:submit> tag for a Submit button).
Listing 2 shows the form for FirstInput.jsp:
Listing 2. FirstInput.jsp form
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<body><s:actionerror/>
<s:form action="First" theme="xhtml">
<s:textfield label="Name" name="name" required="true"/>
<s:textfield label="Age" name="age" required="true"/>
<s:textfield label="Gender (Male/Female)" name="gender"/>
<s:submit/>
</s:form>
</body>
</html>
Note that the required attribute in the
<s:textfield> element
has nothing to do with validation. It is used by some templates to
display an asterisk next to the field.
Gender is a Java Enum, which needs to
be handled specifically. Type conversion in Struts 2 is beyond this
article's scope, so I'll just create a
FirstAction-conversion.properties file for FirstAction
in the same folder, with this content:
gender=com.opensymphony.xwork2.util.EnumTypeConverter
FirstAction, shown in Listing 3, uses simple logic
to distinguish between male and female:
Listing 3. FirstAction class
public class FirstAction extends ActionSupport {
public String execute() throws Exception {
if(Gender.Female.equals(gender)) {
return "female";
} else {
return SUCCESS;
}
} ...
If the user enters Male in the form, the
success result (which is mapped to Male.jsp) returns.
Even without seeing any validation work yet, you can probably
already get the feel of Struts 2 validation. If you enter a
nonnumeric value in the age field and submit the form, a validation
message will appear: "Invalid field value." The same is true for
the gender field: you can only enter Male or
Female there; otherwise a validation message appears.
This is the result of the conversionError interceptor
from the defaultStack making sure that all parameters
are of the type that the action can accept.
Now it's time to introduce some custom validation into the form.
The simplest XWork validator that comes with Struts 2 is the
required validator. It only checks for null values and
is often confused with requiredstring, which checks
for empty strings and can be instructed to trim strings before the
check. Since in the case of the example all three parameters are
always present in the request, there is no need for the
required validator, and I'll use
requiredstring. I'll put it into the
FirstAction-validation.xml file for the FirstAction,
as shown in Listing 4:
Listing 4. Action validation configuration:
requiredstring validator
<validators>
<validator type="requiredstring">
<param name="fieldName">name</param>
<message>Name is required</message>
</validator>...
This results in a "Name is required" message appearing after form submission if nothing was entered in the name input field.
You can configure a field to have several validations applied. To make the configuration file more readable and maintainable, you can configure validators in two ways:
- Field-wise: You nest
<field-validator>entries in the<field>entry. - Validator-wise: You create a standalone
<validator>entry. The standalone validator can either work on its own or be tied to a certain field with a<param name=fieldName>entry.
A name-validation configuration doing exactly the same job as Listing 4, but using field-wise validation, could look like Listing 5:
Listing 5. Action validation configuration: Field-wise validation
<field name="name">
<field-validator type="requiredstring">
<param name="trim">true</param>
<message>Name is required</message>
</field-validator>
</field>...
To take advantage of multiple validators per one field, Listing 6 introduces a requirement for age to be a required field whose value must be between 21 and 122 inclusive:
Listing 6. Action validation configuration: Multiple validations per field
<field name="age">
<field-validator type="required">
<message>Age is required</message>
</field-validator>
<field-validator type="int">
<param name="min">21</param>
<message>You must be an adult</message>
</field-validator>
<field-validator type="int">
<param name="max">122</param>
<message>The oldest human ever was 122 years old</message>
</field-validator>
</field>...
The required validator in Listing 6 tries to
receive a value from the object graph. This value will already be
of the exact type defined in the action. That's why it is
impossible to check simple Java types with the
required validator. Even if field values are empty
(null), they are cast to a corresponding value, 0 in
case of int.




加载中…