本部分内容以实现公司员工的增、删、改、查等操作来贯穿始终,以理论分析和实践相结合的方式来揭开modelDriven与paramsPrepareParams拦截器的神秘面纱……
需求:实现公司员工的增、删、改、查操作,主要界面如下图所示:
(1). 欢迎页面通过请求超链接到员工列表显示页面;
(2). 员工显示页面包括信息输入提交部分和员工列表显示部分;
(3). 信息输入提交部分可新增员工信息并提交保存;
(4). 每个员工信息均对应编辑按钮和删除按钮,分别可执行编辑和删除操作;
特殊说明: Struts 2.0设计上要求modelDriven在params拦截器之前调用,而业务中prepare要负责准备model,准备model又需要参数,即需要在 prepare之前运行params拦截器设置相关参数,这就是创建paramsPrepareParamsStack拦截器栈的原因。
一、Params拦截器
作用:将表单字段映射到值栈栈顶对象的各个属性中;如果某个字段在模型里没有匹配的属性,Param拦截器将尝试值栈中的下一个对象属性。
二、modelDriven拦截器
作用:用户执行请求时,ModelDriven拦截器将调用对应Action对象的getModel()方法,并将返回的模型(对象实例)压入到值栈中。
核心:执行ModelDrivenInterceptor的intercept()方法
public String intercept(ActionInvocation invocation) throws Exception {
//获取Action对象: EmployeeAction对象, 此时其已经实现ModelDriven 接口
//public class EmployeeAction implements RequestAware, ModelDriven<Employee>
Object action = invocation.getAction();
//判断action是否是ModelDriven的实例
if (action instanceof ModelDriven) {
//强制转换为ModelDriven类型
ModelDriven modelDriven = (ModelDriven) action;
//获取值栈
ValueStack stack = invocation.getStack();
//调用ModelDriven接口的getModel()方法
//即调用EmployeeAction的getModel()方法
/*
public Employee getModel() {
employee = new Employee();
return employee;
}
*/
Object model = modelDriven.getModel();
if (model != null) {
//把getModel()方法的返回值压入到值栈的栈顶. 实际压入的是EmployeeAction的employee成员变量
stack.push(model);
}
if (refreshModelBeforeResult) {
invocation.addPreResultListener(new RefreshModelBeforeResult(modelDriven, model));
}
}
return invocation.invoke();
}
结论:将请求参数的值赋给栈顶对象对应的属性; 若没有对应的属性,则查询值栈中下一个对象对应的属性……
注意: getModel()方法不能直接用return new Action();
实现,因为其与Action对象的成员变量没有关联。
三、paramsPrepareParams拦截器
作用:Struts2.0中的modelDriven拦截器负责把Action类以外的一个对象压入到值栈栈顶,而prepare拦截器负责为getModel()方法准备model。
具体作用:
- 若Action类实现Preparable接口,则其需实现prepare()方法;
- 该拦截器将调用prepareActionMethodName()方法、prepareDoActionMethodName()方法或prepare()方法;
- 该拦截器根据firstCallPrepareDo属性值决定 prepareActionMethodName 和prepareDoActionMethodName的获取顺序;
- 该拦截器会根据alwaysInvokePrepare属性值决定是否调用实现了Preparable接口的 Action类的prepare()方法。
3.1 PrepareInterceptor类的doIntercept方法
public String doIntercept(ActionInvocation invocation) throws Exception {
//获取Action实例
Object action = invocation.getAction();
//判断Action是否实现了Preparable接口
if (action instanceof Preparable) {
try {
String[] prefixes;
//根据当前拦截器的firstCallPrepareDo(默认为false)属性确定prefixes
if (firstCallPrepareDo) {
prefixes = new String[] {ALT_PREPARE_PREFIX, PREPARE_PREFIX};
} else {
prefixes = new String[] {PREPARE_PREFIX, ALT_PREPARE_PREFIX};
}
//若为false, 则prefixes: prepare, prepareDo
//调用前缀方法.
PrefixMethodInvocationUtil.invokePrefixMethod(invocation, prefixes);
}
catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof Exception) {
throw (Exception) cause;
} else if(cause instanceof Error) {
throw (Error) cause;
} else {
throw e;
}
}
//根据当前拦截器的alwaysInvokePrepare(默认是 true)属性决定是否调用Action类对象的prepar()方法
if (alwaysInvokePrepare) {
((Preparable) action).prepare();
}
}
return invocation.invoke();
}
3.2 PrefixMethodInvocationUtil类的invokePrefixMethod方法
public static void invokePrefixMethod(ActionInvocation actionInvocation, String[] prefixes) throws InvocationTargetException, IllegalAccessException {
//获取Action实例
Object action = actionInvocation.getAction();
//获取要调用的Action方法的名字(update)
String methodName = actionInvocation.getProxy().getMethod();
if (methodName == null) {
// if null returns (possible according to the docs), use the default execute
methodName = DEFAULT_INVOCATION_METHODNAME;
}
//获取前缀方法
Method method = getPrefixedMethod(prefixes, methodName, action);
//若方法不为null, 则通过反射调用前缀方法
if (method != null) {
method.invoke(action, new Object[0]);
}
}
3.3 PrefixMethodInvocationUtil类的getPrefixedMethod方法
public static Method getPrefixedMethod(String[] prefixes, String methodName, Object action) {
assert(prefixes != null);
//把方法的首字母变为大写
String capitalizedMethodName = capitalizeMethodName(methodName);
//遍历前缀数组
for (String prefixe : prefixes) {
//通过拼接的方式得到前缀方法名: 第一次prepareUpdate, 第二次prepareDoUpdate
String prefixedMethodName = prefixe + capitalizedMethodName;
try {
//利用反射获从action中获取对应的方法,若有直接返回,并结束循环.
return action.getClass().getMethod(prefixedMethodName, EMPTY_CLASS_ARRAY);
}
catch (NoSuchMethodException e) {
// hmm -- OK, try next prefix
if (LOG.isDebugEnabled()) {
LOG.debug("cannot find method [#0] in action [#1]", prefixedMethodName, action.toString());
}
}
}
return null;
}
四、需求实现
4.1 准备工作
- 搭建Struts2开发环境,包括加入所依赖的jar包、在web.xml文件中配置struts2、添加struts2的配置文件struts.xml;
- 欢迎页面超链接到员工列表显示页面,包括编写 index.jsp -> struts.xml -> EmployeeAction类 -> emp-list.jsp;
- 员工列表显示页面内容,包括信息输入提交部分和员工列表显示部分。
4.2 准备数据并编写DAO实现CRUD操作
public class Dao {
/**
* 模拟数据库,用于存取数据
* 注意:用HashMap时将乱序排列,可采用LinkedHashMap来实现
*/
private static Map<Integer, Employee> emps = new LinkedHashMap<>();
static {
emps.put(1001, new Employee(1001, "AA", "aa", "aa@163.com"));
emps.put(1002, new Employee(1002, "BB", "bb", "bb@163.com"));
emps.put(1003, new Employee(1003, "CC", "cc", "cc@163.com"));
emps.put(1004, new Employee(1004, "DD", "dd", "dd@163.com"));
emps.put(1005, new Employee(1005, "EE", "ee", "ee@163.com"));
}
/**
* 增:添加一条员工信息
* @param emp
*/
public void add(Employee emp) {
// 1. 根据当前系统时间生成随机ID
long time = System.currentTimeMillis();
emp.setEmployeeId((int) time);
// 2. 添加员工信息到数据库
emps.put(emp.getEmployeeId(), emp);
}
/**
* 删:删除一条员工信息
* @param employeeId
*/
public void delete(Integer employeeId) {
emps.remove(employeeId);
}
/**
* 改:修改一条员工信息
* @param emp
*/
public void update(Employee emp) {
// Map特性:key相同自动替换
emps.put(emp.getEmployeeId(), emp);
}
/**
* 查:根据ID查找对应的员工信息
* @param employeeId
* @return
*/
public Employee get(Integer employeeId) {
return emps.get(employeeId);
}
/**
* 查:查找所有员工信息
* @return
*/
public List<Employee> getAll() {
return new ArrayList<>(emps.values());
}
}
4.3 查找所有员工信息并显示
- 重点1:数据库中获取到所有员工信息后,可将其保存在请求域中,用于在页面显示;
- 重点2:员工信息列表显示时,需根据List中元素个数来动态添加数据行,注意正确获取对应参数值。
核心代码如下:
public class EmployeeAction implements RequestAware {
private Dao dao = new Dao();
/**
* 查找所有员工信息
* @return
*/
public String list() {
// 获取所有员工信息,并保存在request请求域中,用于在emp-list界面显示
request.put("emps", dao.getAll());
return "list";
}
// 获取request请求域所对应的Map对象
private Map<String, Object> request;
@Override
public void setRequest(Map<String, Object> arg0) {
this.request = arg0;
}
}
4.4 删除一条员工信息
核心代码如下:
public class EmployeeAction{
private Dao dao = new Dao();
/**
* 删除一条员工信息
* 实现方法:在当前Action类中定义employeeId属性,用于接收请求参数
* 注意:返回结果的类型应为redirectAction,即重定向到action
* 若使用chain,则到达目标页面后,地址栏显示的仍为emp-delete.do,会重复提交
* @return
*/
private Integer employeeId;
public void setEmployeeId(Integer employeeId) {
this.employeeId = employeeId;
}
public String delete() {
// 获取请求参数employeeId
dao.delete(employeeId);
return "success";
}
}
4.5 添加一条员工信息
- 方案1:在当前Action类中定义firstName、lastName和email属性,接收请求参数(不完美);
- 方案2:采用ModelDriven拦截器将Action与Model分离开来;
- 实现2:定义Employee属性 -> 实现ModelDriven接口 -> getModel()方法中实例化属性并返回。
方案2具体实现原理:
方案1核心代码如下:
public class EmployeeAction implements RequestAware {
private Dao dao = new Dao();
/**
* 添加一条员工信息
* 方案1:在当前Action类中定义firstName、lastName和email属性,用于接收请求参数(不完美)
* 方案2:采用ModelDriven拦截器将Action与Model分离开来
* 因为某些Action类并不代表任何Model对象,其功能仅限于提供显示服务
* @return
*/
private String firstName;
private String lastName;
private String email;
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public void setEmail(String email) {
this.email = email;
}
public String save() {
// 获取请求参数并封装为Employee对象,添加到数据库中
dao.add(new Employee(null, firstName, lastName, email));
// 返回结果类型仍为redirectAction
return "success";
}
}
方案2核心代码如下:
public class EmployeeAction implements RequestAware, ModelDriven<Employee> {
private Dao dao = new Dao();
// 此时需修改删除操作为下:
public String delete() {
// 获取请求参数employeeId
dao.delete(employee.getEmployeeId());
return "success";
}
/**
* 添加一条员工信息
* 方案1:在当前Action类中定义firstName、lastName和email属性,用于接收请求参数(不完美)
* 方案2:采用ModelDriven拦截器将Action与Model分离开来
* 因为某些Action类并不代表任何Model对象,其功能仅限于提供显示服务
* @return
*/
// 方案2:定义成员变量employee,利用ModelDriven的getModel()方法为其创建对象并压入值栈
private Employee employee;
public String save() {
// 获取请求参数并封装为Employee对象,添加到数据库中
dao.add(employee);
// 返回结果类型仍为redirectAction
return "success";
}
@Override
public Employee getModel() {
// 注意:不能直接使用return new Employee();
employee = new Employee();
return employee;
}
}
4.6 编辑一条员工信息
实现方案:在应用程序的struts.xml文件中,将默认拦截器栈配置为paramsPrepareParamsStack;
实现原理:该拦截器栈的特点在于params -> modelDriven -> params,即可以先将请求参数赋给Action类中对应的属性,再根据该属性值决定压入值栈栈顶的对象,进而为值栈栈顶的对象属性赋值。
对于编辑操作而言,其具体实现原理如下图所示:
核心代码如下所示:
public class EmployeeAction implements RequestAware, ModelDriven<Employee> {
private Dao dao = new Dao();
/**
* 4. 更新一个员工信息
* 问题:如何实现员工信息的回显?
* 在genModel()方法中根据请求参数employeeId获取Employee对象并返回
* @return
*/
private Integer employeeId;
public void setEmployeeId(Integer employeeId) {
this.employeeId = employeeId;
}
public String edit() {
/*************************思路1:较为麻烦*************************/
// // 此时栈顶对象即为employee,其除employeeId属性外均为null
// // 1. 根据请求参数employeeId获取对应的Employee对象
// Employee emp = dao.get(employee.getEmployeeId());
// // 2. 封装栈顶对象employee的属性信息
// employee.setFirstName(emp.getFirstName());
// employee.setLastName(emp.getLastName());
// employee.setEmail(emp.getEmail());
/*************************思路2:不可行**************************/
// // 经过重新赋值的employee对象不再是栈顶对象
// employee = dao.get(employee.getEmployeeId());
/*************************思路3:有浪费**************************/
// // 将获取的emp对象直接压入值栈,但有浪费之嫌(此时值栈中有两个Employee对象)
// ActionContext.getContext().getValueStack().push(dao.get(employee.getEmployeeId()));
/*************************解决方案******************************/
// 方案:在genModel()方法中根据请求参数employeeId获取Employee对象并返回
// 判断是Add还是Edit(判定标准:是否有employeeId参数),Add新建而Edit从数据库中获取
// 注意:若通过employeeId来判断,则需要在modelDriven拦截器执行前先执行一个params拦截器!
// 此时可以通过使用paramsPrepareParams拦截器栈来实现,在struts.xml文件中配置即可。
return "edit";
}
@Override
public Employee getModel() {
if(employeeId == null) {
// 注意:不能直接使用return new Employee();
employee = new Employee();
} else {
employee = dao.get(employeeId);
}
return employee;
}
public String update() {
dao.update(employee);
return "success";
}
}
4.7 性能优化
不足之处:
- 删除操作时,employeeId!=null,getModel()方法 数据库中加载了一个对象,多余;
- 查询所有员工信息时,新建了Employee()对象,浪费;
- 更新操作时,具体情况具体分析;
解决方案:使用PrepareInterceptor拦截器和Preparable接口即可。
具体实现:
- 为各个ActionMethod准备prepare[ActionMethdName]方法,而抛弃掉原有的prepare()方法;
- 将PrepareInterceptor的alwaysInvokePrepare属性置为false,以避免Struts2框架再调用prepare()方法。
具体原理:
- 若Action类实现了Preparable接口,则Struts2尝试执行prepare[ActionMethodName]方法;若不存在,则尝试执行prepareDo[ActionMethodName]方法;否则均不执行。
- 若PrepareInterceptor的alwaysInvokePrepare属性为false,则Struts2将不调用实现了Preparable接口的 Action类的prepare()方法。
如何在配置文件中为拦截器栈的属性赋值?
<!-- 配置PrepareInterceptor的alwaysInvokePrepare属性为false -->
<interceptors>
<interceptor-stack name="crudStack">
<interceptor-ref name="paramsPrepareParamsStack">
<param name="prepare.alwaysInvokePrepare">false</param>
</interceptor-ref>
</interceptor-stack>
</interceptors>
<!-- 配置默认拦截器栈为paramsPrepareParamsStack:params->modelDriven->params -->
<default-interceptor-ref name="crudStack"></default-interceptor-ref>
附录:具体实现代码
代码结构:
EmployeeAction.java:
package com.qiaobc.crud.action;
import java.util.Map;
import org.apache.struts2.interceptor.RequestAware;
import com.opensymphony.xwork2.ModelDriven;
import com.opensymphony.xwork2.Preparable;
import com.qiaobc.crud.dao.Dao;
import com.qiaobc.crud.domin.Employee;
public class EmployeeAction implements RequestAware, ModelDriven<Employee>, Preparable {
private Dao dao = new Dao();
/**
* 1. 查找所有员工信息
* @return
*/
public String list() {
// 获取所有员工信息,并保存在request请求域中
request.put("emps", dao.getAll());
return "list";
}
// 获取request请求域所对应的Map对象
private Map<String, Object> request;
@Override
public void setRequest(Map<String, Object> arg0) {
this.request = arg0;
}
/**
* 2. 删除一条员工信息
* 方法:在当前Action类中定义employeeId属性,用于接收请求参数
* 注意:返回结果的类型应为redirectAction,即重定向到action
* 若使用chain,则到达目标页面后,地址栏显示的仍为emp-delete.do,会重复提交
* @return
*/
// private Integer employeeId;
//
// public void setEmployeeId(Integer employeeId) {
// this.employeeId = employeeId;
// }
public String delete() {
// 获取请求参数employeeId
dao.delete(employeeId);
return "success";
}
/**
* 3. 添加一条员工信息
* 方案1:在当前Action类中定义firstName、lastName和email属性,用于接收请求参数(不完美)
* 方案2:采用ModelDriven拦截器将Action与Model分离开来
* 因为某些Action类并不代表任何Model对象,其功能仅限于提供显示服务
* @return
*/
// private String firstName;
//
// private String lastName;
//
// private String email;
//
// public void setFirstName(String firstName) {
// this.firstName = firstName;
// }
//
// public void setLastName(String lastName) {
// this.lastName = lastName;
// }
//
// public void setEmail(String email) {
// this.email = email;
// }
//
// public String save() {
// // 获取请求参数并封装为Employee对象,添加到数据库中
// dao.add(new Employee(null, firstName, lastName, email));
// // 返回结果类型仍为redirectAction
// return "success";
// }
// 方案2:定义成员变量employee,利用ModelDriven的getModel()方法为其创建对象并压入值栈
private Employee employee;
public String save() {
// 获取请求参数并封装为Employee对象,添加到数据库中
dao.add(employee);
// 返回结果类型仍为redirectAction
return "success";
}
public void prepareSave() {
employee = new Employee();
}
/**
* 4. 更新一个用户信息
* 问题:如何实现用户信息的回显?
* 在genModel()方法中根据请求参数employeeId获取Employee对象并返回
* @return
*/
private Integer employeeId;
public void setEmployeeId(Integer employeeId) {
this.employeeId = employeeId;
}
public String edit() {
/*************************思路1:较为麻烦*************************/
// // 此时栈顶对象即为employee,其除employeeId属性外均为null
// // 1. 根据请求参数employeeId获取对应的Employee对象
// Employee emp = dao.get(employee.getEmployeeId());
// // 2. 封装栈顶对象employee的属性信息
// employee.setFirstName(emp.getFirstName());
// employee.setLastName(emp.getLastName());
// employee.setEmail(emp.getEmail());
/*************************思路2:不可行**************************/
// // 经过重新赋值的employee对象不再是栈顶对象
// employee = dao.get(employee.getEmployeeId());
/*************************思路3:有浪费**************************/
// // 将获取的emp对象直接压入值栈,但有浪费之嫌(此时值栈中有两个Employee对象)
// ActionContext.getContext().getValueStack().push(dao.get(employee.getEmployeeId()));
/*************************解决方案******************************/
// 方案:在genModel()方法中根据请求参数employeeId获取Employee对象并返回
// 判断是Add还是Edit(判定标准:是否有employeeId参数),Add新建而Edit从数据库中获取
// 注意:若通过employeeId来判断,则需要在modelDriven拦截器执行前先执行一个params拦截器!
// 此时可以通过使用paramsPrepareParams拦截器栈来实现,在struts.xml文件中配置即可。
return "edit";
}
public void prepareEdit() {
employee = dao.get(employeeId);
}
@Override
public Employee getModel() {
// if(employeeId == null) {
// // 注意:不能直接使用return new Employee();
// employee = new Employee();
// } else {
// employee = dao.get(employeeId);
// }
return employee;
}
public String update() {
dao.update(employee);
return "success";
}
public void prepareUpdate() {
employee = new Employee();
}
// 配置PrepareInterceptor的alwaysInvokePrepare属性为false,使当前方法不执行
@Override
public void prepare() throws Exception {
System.out.println("prepare...");
}
}
Dao.java:
package com.qiaobc.crud.dao;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.qiaobc.crud.domin.Employee;
public class Dao {
/**
* 模拟数据库,用于存取数据
* 注意:用HashMap时将乱序排列,可采用LinkedHashMap来实现
*/
private static Map<Integer, Employee> emps = new LinkedHashMap<>();
static {
emps.put(1001, new Employee(1001, "AA", "aa", "aa@163.com"));
emps.put(1002, new Employee(1002, "BB", "bb", "bb@163.com"));
emps.put(1003, new Employee(1003, "CC", "cc", "cc@163.com"));
emps.put(1004, new Employee(1004, "DD", "dd", "dd@163.com"));
emps.put(1005, new Employee(1005, "EE", "ee", "ee@163.com"));
}
/**
* 增:添加一条员工信息
* @param emp
*/
public void add(Employee emp) {
// 1. 根据当前系统时间生成随机ID
long time = System.currentTimeMillis();
emp.setEmployeeId((int) time);
// 2. 添加员工信息到数据库
emps.put(emp.getEmployeeId(), emp);
}
/**
* 删:删除一条员工信息
* @param employeeId
*/
public void delete(Integer employeeId) {
emps.remove(employeeId);
}
/**
* 改:修改一条员工信息
* @param emp
*/
public void update(Employee emp) {
// Map特性:key相同自动替换
emps.put(emp.getEmployeeId(), emp);
}
/**
* 查:根据ID查找对应的员工信息
* @param employeeId
* @return
*/
public Employee get(Integer employeeId) {
return emps.get(employeeId);
}
/**
* 查:查找所有员工信息
* @return
*/
public List<Employee> getAll() {
return new ArrayList<>(emps.values());
}
}
Employee.java:
package com.qiaobc.crud.domin;
public class Employee {
private Integer employeeId;
private String firstName;
private String lastName;
private String email;
public Integer getEmployeeId() {
return employeeId;
}
public void setEmployeeId(Integer employeeId) {
this.employeeId = employeeId;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Employee() {
super();
}
public Employee(Integer employeeId, String firstName, String lastName,
String email) {
super();
this.employeeId = employeeId;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
@Override
public String toString() {
return "Employee [employeeId=" + employeeId + ", firstName="
+ firstName + ", lastName=" + lastName + ", email=" + email
+ "]";
}
}
struts.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
"http://struts.apache.org/dtds/struts-2.3.dtd">
<struts>
<!-- 配置Struts2可拦截请求的扩展名 -->
<constant name="struts.action.extension" value="action,do,,"></constant>
<!-- Add packages here -->
<package name="struts-crud" namespace="/" extends="struts-default">
<!-- 配置PrepareInterceptor的alwaysInvokePrepare属性为false -->
<interceptors>
<interceptor-stack name="crudStack">
<interceptor-ref name="paramsPrepareParamsStack">
<param name="prepare.alwaysInvokePrepare">false</param>
</interceptor-ref>
</interceptor-stack>
</interceptors>
<!-- 配置默认拦截器栈为paramsPrepareParamsStack:params->modelDriven->params -->
<default-interceptor-ref name="crudStack"></default-interceptor-ref>
<action name="emp-*" class="com.qiaobc.crud.action.EmployeeAction" method="{1}">
<result name="{1}">/emp-{1}.jsp</result>
<!-- 员工信息删除后需再一次获取所有员工信息 -->
<result name="success" type="redirectAction">emp-list</result>
</action>
</package>
</struts>
emp-edit.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!-- 导入Stduts2提供的标签库 -->
<%@ taglib prefix="s" uri="/struts-tags"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 tdansitional//EN" "http://www.w3.org/td/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h3>Edit Employee :</h3>
<!-- 可输入员工信息,并提交保存 -->
<s:form action="emp-update.do">
<s:hidden name="employeeId" label="ID"></s:hidden>
<s:textfield name="firstName" label="FirstName"></s:textfield>
<s:textfield name="lastName" label="LastName"></s:textfield>
<s:textfield name="email" label="E-mail"></s:textfield>
<s:submit></s:submit>
</s:form>
</body>
</html>
emp-list.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!-- 导入Stduts2提供的标签库 -->
<%@ taglib prefix="s" uri="/struts-tags"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 tdansitional//EN" "http://www.w3.org/td/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h3>Add New Employee :</h3>
<!-- 可输入员工信息,并提交保存 -->
<s:form action="emp-save.do">
<s:textfield name="firstName" label="FirstName"></s:textfield>
<s:textfield name="lastName" label="LastName"></s:textfield>
<s:textfield name="email" label="E-mail"></s:textfield>
<s:submit></s:submit>
</s:form>
<br>
<hr>
<br>
<s:debug></s:debug>
<h3>List All Employees :</h3>
<table cellpadding="10" cellspacing="0" border="1">
<thead>
<tr>
<td>ID</td>
<td>FirstName</td>
<td>LastName</td>
<td>E-mail</td>
<td>Edit</td>
<td>Delete</td>
</tr>
</thead>
<tbody>
<!-- 需要遍历显示 -->
<s:iterator value="#request.emps">
<tr>
<td>${employeeId }</td>
<td>${firstName }</td>
<td>${lastName }</td>
<td>${email }</td>
<td><a href="emp-edit.do?employeeId=${employeeId }">Edit</a></td>
<td><a href="emp-delete.do?employeeId=${employeeId }">Delete</a></td>
</tr>
</s:iterator>
</tbody>
</table>
</body>
</html>
index.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<a href="emp-list.do">List All Employees...</a>
</body>
</html>