

This lesson assumes you know the basic concepts presented in Lessons 1 and 2. I'll only be introducing a few new concepts in this lesson. I'm not going throw every file in this Lesson so you'll need to download the source code above in order to follow along.
New Concepts in Lesson 3:
When creating a DynaActionForm you do not actually create the concrete Java class. The class (DynaActionForm) is created for you by the Struts framework as it's defined in your struts-config. For this lesson our DynaActionForm defintion looks like:
<form-beans>
<form-bean name="employeeForm" type="org.apache.struts.validator.DynaValidatorForm">
<form-property name="name" type="java.lang.String"/>
<form-property name="age" type="java.lang.String"/>
<form-property name="departmentId" type="java.lang.String"/>
<form-property name="flavorIDs" type="java.lang.String[]"/>
<form-property name="dispatch" type="java.lang.String"/>
</form-bean>
</form-beans>
Looking at the form-bean definition above you see that we are using type org.apache.struts.validator.DynaValidatorForm. Magically Struts will make sure to create this bean for us using the form-property fields defined. Since we are actually going to capture more than one ice cream flavor that an employee likes, flavorIDs is defined as a String array. The "dispatch" property is the property that we are going to use in our DispatchAction class (more about that later). Remember, just like when using the standard ActionForm, you should keep these form fields set as Strings since an HTML form will only submit the request information in String format.
This DynaValidatorForm is used just like a normal ActionForm when it comes to how we define its use in our action mappings. The only 'tricky' thing is that standard getter and setters are not made since the DynaActionForms are backed by a HashMap. In order to get fields out of the DynaActionForm you would do:
String age = (String)dynaForm.get("age");
Similarly, if you need to define a property:
dynaForm.set("age","33");
You'll see an example of this usage in the SetUpEmployeeAction section. [ NOTE: Although I show here how to create DynaForms, there are some issues you might want to consider before using them. One drawback is you lose compile-time checks when using them. In other words, to manually set a DynaForm field you do something like: yourDynaForm.set("myProperty", myValue );. This will always compile but what if "myProperty" really isn't the field name that you have defined? You won't find out about until run time (yes, unit tests could help here). Creating a Constants class for your form field names helps, but you could still run into the same problem if the property in the actual form bean definition was changed, and it wasn't changed in the corresponding Constants class. Bugs can easily creep into code that are more difficult to track down using DynaForms. More than likely you also usually end up having to build a regular form bean which extends a DynaActionForm because you realize later on that you have to over-ride the reset method or you may even need some custom validation in the validate method. So now the only advantage you've gained with your DynaForm is you didn't have to create set/get methods but any IDE will do that task for you so it's not saving you that much. There are some advantages that I've found to using a DynaActioinForm that go beyond the scope of this article. Overall, I'll to stick with using regular ActionForms. ]
<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
<set-property property="pathnames"
value="/WEB-INF/validator-rules.xml,
/WEB-INF/validation.xml"/>
</plug-in>
The above tells us that our validation files (validation-rules.xml and validation.xml) will be found in our WEB-INF directory. Before I mention those files, take a look at part of our /employeeAction mapping:
<action path="/employeeAction"
type="net.reumann.EmployeeDispatchAction"
name="employeeForm"
scope="request"
validate="true"
parameter="dispatch"
input="/employeeForm.jsp"
>
<exception
key="exception.database.error"
type="net.reumann.DatabaseException"
path="/error.jsp"/>
<forward
name="insertSuccess"
path="/employeeForm.jsp"/>
<forward
name="updateSuccess"
path="/employeeForm.jsp"/>
</action>
Notice that we set the validate attribute to true in the above employeeAction mapping. Since we are setting validate="true" it means that Struts will try to find a matching rules definition for our form in the validator.xml file. If you use a form of type "ValidatorForm" (which in our case we did, we just used a class that extends it called a DynaValidatorForm) you need to make sure the name of your form matches one in the validator.xml (in this case 'employeeForm"). You can, however, also use a form of type ValidatorActionForm, which if you use this type of form, you can validate on the actual path mapping and you would provide that as the name to key off of in the validator.xml file (instead of the form name). This later technique is actually very useful since sometimes you will reuse your form for different mappings but want it validated differently depending on what type of action you are performing (for example an update action might have different validation rules than an insert but you might be using the same ActionForm).
Our validation.xml file looks like:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE form-validation PUBLIC
"-//Apache Software Foundation//DTD Commons
Validator Rules Configuration 1.1.3//EN"
"http://jakarta.apache.org/commons/dtds/validator_1_1_3.dtd">
<form-validation>
<formset>
<form name="employeeForm">
<field property="name"
depends="required">
<arg0 key="name.displayname"/>
</field>
<field property="age"
depends="required,integer">
<arg0 key="age.displayname"/>
</field>
</form>
</formset>
</form-validation>
The validation.xml file defines what we want validated. It requires a use of a validation-rules file which we provided as well (validation-rules.xml). The validation-rules.xml file that comes with the struts download covers most of your basic needs so you shouldn't have to alter it. You may have a custom validation situation, in which case, you can create additional rules definitions for Struts to use. In the above code you will see that we are validating that both name and age is 'required' and that age is an integer type. If validation fails, it will use the key found in our MessageResources file, so make sure you add the appropriate fields in there. (The struts blank example that comes with the download shows a bunch of them you can uuse). Our MessageResources file has them defined:
#-- validation errors
errors.required={0} is required.
errors.integer={0} must be a whole number.
If validation fails at one of the steps the property is substituted in for the argument {0} so that if a name wasn't provided an error would be created "Name is required" that we display on our form using the same code as we used in Lesson 2.
I only briefly covered the validation framework. You probably should take a minute or two to read over the validation section in Struts developer guide: http://struts.apache.org/struts-taglib/dev_validator.html or take a look at http://www.oracle.com/technology/oramag/oracle/04-jan/o14dev_struts.html.
[ NOTE: I'm not a big fan of having Struts calling the validation framework manually for you with validate="true". I'd rather call this validate method manually. Reasons why and an example of the approach I like to take: http://www.learntechnology.net/validate-manually.do ]
public final class SetUpEmployeeAction extends Action {
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
EmployeeService service = new EmployeeService();
Collection departments = service.getDepartments();
Collection flavors = service.getFlavors();
HttpSession session = request.getSession();
session.setAttribute( "departments", departments );
session.setAttribute( "flavors", flavors );
DynaActionForm employeeForm = (DynaActionForm)form;
employeeForm.set( "dispatch", "insert" );
request.setAttribute(mapping.getAttribute(), employeeForm);
return (mapping.findForward("continue"));
}
}
Nothing really too complex in the above code. Note our cast of ActionForm to type DynaActionForm and also take note of how the Dyna properties are set.
package net.reumann;
import org.apache.struts.action.*;
import org.apache.struts.actions.DispatchAction;
import org.apache.commons.beanutils.BeanUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public final class EmployeeDispatchAction extends DispatchAction {
public ActionForward insert(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
EmployeeService service = new EmployeeService();
DynaActionForm employeeForm = (DynaActionForm)form;
Employee employee = new Employee();
BeanUtils.copyProperties( employee, employeeForm );
service.insertEmployee( employee );
ActionMessages messages = new ActionMessages();
ActionMessage message =
new ActionMessage("message.employee.insert.success",
employee.getName());
messages.add( ActionMessages.GLOBAL_MESSAGE, message );
saveMessages( request, messages );
employeeForm.set("dispatch","update");
return (mapping.findForward("insertSuccess"));
}
public ActionForward update(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
EmployeeService service = new EmployeeService();
DynaActionForm employeeForm = (DynaActionForm)form;
Employee employee = new Employee();
BeanUtils.copyProperties( employee, employeeForm );
service.updateEmployee( employee );
ActionMessages messages = new ActionMessages();
ActionMessage message =
new ActionMessage("message.employee.update.success",
employee.getName());
messages.add( ActionMessages.GLOBAL_MESSAGE, message );
saveMessages( request, messages );
return (mapping.findForward("updateSuccess"));
}
}
I mentioned several times that you always want to go through an Action class. You can set up each action event (ie update, insert, delete) to always correspond to a unique Action class but if you want to condense related events into one Action class you can do so using a DipatchAction (or one of its subclasses like LookupDispatchAction). Typical CRUD events (create, retrieve, update, and delete) can be seen as related events. In this example we have an option for inserting an employee and also for updating an employee. We could have just kept the Lesson II InsertEmployeeAction and then created another UpdateEmployeeAction for handling the update request, but instead we are going to put both of these events into one EmployeeDispatchAction.
The DipatchAction works by using a parameter defined in your action-mapping to figure out which method to call in the DispatchAction class. In our action-mapping that submits to EmployeeDispatchAction we have the parameter defined as "dispatch." The value of dispatch provided when a form submits or passed as a param in the URL will cause the appropriate insert or update method to be executed.
<action path="/employeeAction"
type="net.reumann.EmployeeDispatchAction"
name="employeeForm"
scope="request"
validate="true"
parameter="dispatch"
input="/employeeForm.jsp"
>
<exception
key="exception.database.error"
type="net.reumann.DatabaseException"
path="/error.jsp"/>
<forward
name="insertSuccess"
path="/employeeForm.jsp"/>
<forward
name="updateSuccess"
path="/employeeForm.jsp"/>
</action>
Notice the "exception" forward in the mapping. It's declared with the type net.reumann.DatabaseException. If this type of Exception ends up making to our EmployeeDispatchAction it will be handled by this exception forward and will forward to our error.jsp page. To aid us in the display of our error message the key exception.database.error is looked up in our MessageResources file. Declaring Exceptions like this is a nice feature since it really cleans up our Action code - we don't have to handle the errors there. You could even declare exceptions at the global level. Since we'd probably always want to go the error.jsp page if a DatabaseException occured, we could create this as a global Exception in a global-exceptions section in the struts-config:
<global-exceptions>
<exception key="exception.database.error"
path="/error.jsp"
type="net.reumann.DatabaseException"/>
</global-exceptions>
You could even declare Exceptions in the web.xml but you won't get use of the MessageResources key when declared there. I still usually declare a global Exception in the web.xml to handle the non-specific Exceptions and forward to an error.jsp page. (I do this in the struts spring ibatis lesson.
<c:forEach var="flavor" items="${flavors}">
<html:multibox name="employeeForm" property="flavorIDs">
<c:out value="${flavor.flavorID}"/>
</html:multibox>
<c:out value="${flavor.description}"/>
</c:forEach>
Our employeeForm.jsp is pretty basic but it does use the html:multibox tag. This tag is nice since it allows us to make sure checkboxes our set to checked if any of our checkbox values match the value of an item in a collection. So in our case above we are looping over a collection of all flavors. During each iteration, if the value of flavorID in our current iteration (flavor.flavorID) matches a flavorID that is in our form property collection (flavorIDs), it will create the checkbox and mark it checked, otherwise, if not found, it creates the checkbox as unchecked.
评论列表