Apache Struts - Java Web开发框架

项目简介

Apache Struts是一个开源的Java Web应用框架,基于Model-View-Controller(MVC)设计模式。它为开发者提供了一个强大的框架来构建可维护、可扩展的Java Web应用程序。Struts最初由Craig McClanahan开发,后来成为Apache软件基金会的项目。

Struts通过将业务逻辑、数据和表现层分离,帮助开发者构建结构良好的Web应用程序。它提供了丰富的标签库、数据验证、国际化支持等功能,极大地简化了Java Web开发的复杂性。

主要特性

  • MVC架构:清晰的模型-视图-控制器分离
  • 声明式验证:XML和注解两种验证方式
  • 国际化支持:多语言和本地化功能
  • 标签库:丰富的JSP标签库
  • 插件机制:可扩展的插件架构
  • 异常处理:统一的异常处理机制

项目原理

MVC架构

Struts严格遵循MVC设计模式:

1
用户请求 → ActionServlet → RequestProcessor → Action → Model → View → 响应

Model(模型)

  • ActionForm:封装用户输入数据
  • Business Objects:业务逻辑对象
  • Data Access Objects:数据访问对象

View(视图)

  • JSP页面:用户界面展示
  • Struts标签:简化JSP开发
  • 国际化资源:多语言支持

Controller(控制器)

  • ActionServlet:前端控制器
  • Action类:业务逻辑控制
  • RequestProcessor:请求处理器

核心组件

ActionServlet

  • 前端控制器,接收所有HTTP请求
  • 根据配置文件分发请求到相应的Action
  • 管理应用程序的生命周期

Action

  • 业务逻辑的控制点
  • 处理用户请求并调用业务服务
  • 决定下一个要显示的视图

ActionForm

  • 封装HTML表单数据
  • 提供数据验证功能
  • 在不同层之间传递数据

struts-config.xml

  • 配置Action映射
  • 定义表单Bean
  • 配置全局转发和异常处理

使用场景

1. 企业级Web应用

构建大型企业级Web应用程序,如ERP、CRM、OA系统等。

2. 电子商务平台

开发在线购物网站、B2B平台等电子商务应用。

3. 内容管理系统

构建新闻发布系统、博客平台、论坛等内容管理应用。

4. 政府门户网站

开发政府部门的官方网站和在线服务平台。

5. 教育管理系统

构建学校管理系统、在线学习平台等教育相关应用。

具体案例

案例1:用户登录功能实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<!-- struts-config.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.3//EN"
"http://struts.apache.org/dtds/struts-config_1_3.dtd">

<struts-config>
<!-- Form Bean定义 -->
<form-beans>
<form-bean name="loginForm" type="com.example.form.LoginForm"/>
<form-bean name="userForm" type="com.example.form.UserForm"/>
</form-beans>

<!-- Action映射 -->
<action-mappings>
<action path="/login"
type="com.example.action.LoginAction"
name="loginForm"
scope="request"
validate="true"
input="/login.jsp">
<forward name="success" path="/welcome.jsp"/>
<forward name="failure" path="/login.jsp"/>
</action>

<action path="/userList"
type="com.example.action.UserListAction">
<forward name="success" path="/userList.jsp"/>
</action>
</action-mappings>

<!-- 全局转发 -->
<global-forwards>
<forward name="login" path="/login.jsp"/>
<forward name="home" path="/index.jsp"/>
</global-forwards>

<!-- 消息资源 -->
<message-resources parameter="com.example.resources.ApplicationResources"/>

<!-- 插件配置 -->
<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
<set-property property="pathnames" value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml"/>
</plug-in>
</struts-config>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// LoginForm.java - 表单Bean
package com.example.form;

import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionError;
import javax.servlet.http.HttpServletRequest;

public class LoginForm extends ActionForm {
private String username;
private String password;
private boolean rememberMe;

// 构造函数
public LoginForm() {
reset();
}

// 重置表单
public void reset(ActionMapping mapping, HttpServletRequest request) {
username = "";
password = "";
rememberMe = false;
}

// 表单验证
public ActionErrors validate(ActionMapping mapping, HttpServletRequest request) {
ActionErrors errors = new ActionErrors();

if (username == null || username.trim().length() == 0) {
errors.add("username", new ActionError("error.username.required"));
}

if (password == null || password.trim().length() == 0) {
errors.add("password", new ActionError("error.password.required"));
} else if (password.length() < 6) {
errors.add("password", new ActionError("error.password.minlength"));
}

return errors;
}

// Getter和Setter方法
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }

public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }

public boolean isRememberMe() { return rememberMe; }
public void setRememberMe(boolean rememberMe) { this.rememberMe = rememberMe; }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// LoginAction.java - 控制器
package com.example.action;

import org.apache.struts.action.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.example.form.LoginForm;
import com.example.service.UserService;
import com.example.model.User;

public class LoginAction extends Action {

public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response) throws Exception {

LoginForm loginForm = (LoginForm) form;

try {
// 获取用户服务
UserService userService = new UserService();

// 验证用户登录
User user = userService.authenticate(loginForm.getUsername(), loginForm.getPassword());

if (user != null) {
// 登录成功
HttpSession session = request.getSession();
session.setAttribute("currentUser", user);
session.setAttribute("username", user.getUsername());

// 处理记住我功能
if (loginForm.isRememberMe()) {
// 设置Cookie等逻辑
handleRememberMe(response, user);
}

// 记录登录日志
userService.logLogin(user.getId(), request.getRemoteAddr());

// 重定向到成功页面
return mapping.findForward("success");

} else {
// 登录失败
ActionErrors errors = new ActionErrors();
errors.add(ActionErrors.GLOBAL_ERROR,
new ActionError("error.login.invalid"));
saveErrors(request, errors);

return mapping.findForward("failure");
}

} catch (Exception e) {
// 异常处理
ActionErrors errors = new ActionErrors();
errors.add(ActionErrors.GLOBAL_ERROR,
new ActionError("error.system.unavailable"));
saveErrors(request, errors);

return mapping.findForward("failure");
}
}

private void handleRememberMe(HttpServletResponse response, User user) {
// 实现记住我功能
Cookie cookie = new Cookie("rememberedUser", user.getUsername());
cookie.setMaxAge(7 * 24 * 60 * 60); // 7天
cookie.setPath("/");
response.addCookie(cookie);
}
}

案例2:数据验证配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<!-- validation.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE form-validation PUBLIC
"-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.3.0//EN"
"http://jakarta.apache.org/commons/dtds/validator_1_3_0.dtd">

<form-validation>
<formset>
<!-- 登录表单验证 -->
<form name="loginForm">
<field property="username" depends="required,minlength,maxlength">
<arg0 key="label.username"/>
<arg1 key="${var:minlength}" name="minlength" resource="false"/>
<arg2 key="${var:maxlength}" name="maxlength" resource="false"/>
<var>
<var-name>minlength</var-name>
<var-value>3</var-value>
</var>
<var>
<var-name>maxlength</var-name>
<var-value>20</var-value>
</var>
</field>

<field property="password" depends="required,minlength">
<arg0 key="label.password"/>
<arg1 key="${var:minlength}" name="minlength" resource="false"/>
<var>
<var-name>minlength</var-name>
<var-value>6</var-value>
</var>
</field>
</form>

<!-- 用户注册表单验证 -->
<form name="userForm">
<field property="email" depends="required,email">
<arg0 key="label.email"/>
</field>

<field property="phone" depends="mask">
<arg0 key="label.phone"/>
<var>
<var-name>mask</var-name>
<var-value>^\d{11}$</var-value>
</var>
</field>

<field property="birthdate" depends="date">
<arg0 key="label.birthdate"/>
<var>
<var-name>datePattern</var-name>
<var-value>yyyy-MM-dd</var-value>
</var>
</field>
</form>
</formset>
</form-validation>

案例3:JSP页面实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
<%-- login.jsp --%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="html" uri="http://struts.apache.org/tags-html" %>
<%@ taglib prefix="bean" uri="http://struts.apache.org/tags-bean" %>
<%@ taglib prefix="logic" uri="http://struts.apache.org/tags-logic" %>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title><bean:message key="title.login"/></title>
<link rel="stylesheet" type="text/css" href="css/style.css">
<script type="text/javascript" src="js/validation.js"></script>
</head>
<body>
<div class="login-container">
<h2><bean:message key="header.login"/></h2>

<!-- 显示错误信息 -->
<logic:messagesPresent>
<div class="error-messages">
<html:errors/>
</div>
</logic:messagesPresent>

<!-- 登录表单 -->
<html:form action="/login" method="post" onsubmit="return validateLoginForm(this)">
<div class="form-group">
<label for="username"><bean:message key="label.username"/>:</label>
<html:text property="username" styleClass="form-control" styleId="username"/>
</div>

<div class="form-group">
<label for="password"><bean:message key="label.password"/>:</label>
<html:password property="password" styleClass="form-control" styleId="password"/>
</div>

<div class="form-group">
<html:checkbox property="rememberMe" styleId="rememberMe"/>
<label for="rememberMe"><bean:message key="label.rememberme"/></label>
</div>

<div class="form-group">
<html:submit styleClass="btn btn-primary">
<bean:message key="button.login"/>
</html:submit>
<html:reset styleClass="btn btn-secondary">
<bean:message key="button.reset"/>
</html:reset>
</div>
</html:form>

<div class="links">
<html:link page="/register.jsp">
<bean:message key="link.register"/>
</html:link>
|
<html:link page="/forgotPassword.jsp">
<bean:message key="link.forgot.password"/>
</html:link>
</div>
</div>

<script>
function validateLoginForm(form) {
var username = form.username.value;
var password = form.password.value;

if (username.trim() == '') {
alert('<bean:message key="error.username.required"/>');
form.username.focus();
return false;
}

if (password.trim() == '') {
alert('<bean:message key="error.password.required"/>');
form.password.focus();
return false;
}

return true;
}
</script>
</body>
</html>

案例4:国际化配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# ApplicationResources.properties (默认英文)
title.login=User Login
header.login=Please Login
label.username=Username
label.password=Password
label.rememberme=Remember Me
button.login=Login
button.reset=Reset
link.register=Register New Account
link.forgot.password=Forgot Password?

error.username.required=Username is required
error.password.required=Password is required
error.password.minlength=Password must be at least 6 characters
error.login.invalid=Invalid username or password
error.system.unavailable=System temporarily unavailable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# ApplicationResources_zh_CN.properties (中文)
title.login=用户登录
header.login=请登录
label.username=用户名
label.password=密码
label.rememberme=记住我
button.login=登录
button.reset=重置
link.register=注册新账户
link.forgot.password=忘记密码?

error.username.required=用户名不能为空
error.password.required=密码不能为空
error.password.minlength=密码至少需要6个字符
error.login.invalid=用户名或密码错误
error.system.unavailable=系统暂时不可用

案例5:业务逻辑服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
// UserService.java - 业务服务
package com.example.service;

import com.example.model.User;
import com.example.dao.UserDAO;
import java.security.MessageDigest;
import java.util.List;

public class UserService {
private UserDAO userDAO;

public UserService() {
this.userDAO = new UserDAO();
}

// 用户认证
public User authenticate(String username, String password) throws Exception {
if (username == null || password == null) {
return null;
}

// 获取用户信息
User user = userDAO.findByUsername(username);
if (user == null) {
return null;
}

// 验证密码
String hashedPassword = hashPassword(password);
if (user.getPassword().equals(hashedPassword)) {
// 更新最后登录时间
user.setLastLoginTime(new java.util.Date());
userDAO.update(user);
return user;
}

return null;
}

// 用户注册
public boolean registerUser(User user) throws Exception {
// 检查用户名是否已存在
if (userDAO.findByUsername(user.getUsername()) != null) {
throw new Exception("Username already exists");
}

// 检查邮箱是否已存在
if (userDAO.findByEmail(user.getEmail()) != null) {
throw new Exception("Email already exists");
}

// 密码加密
user.setPassword(hashPassword(user.getPassword()));
user.setCreateTime(new java.util.Date());
user.setActive(true);

return userDAO.save(user);
}

// 获取用户列表
public List<User> getUserList(int page, int pageSize) throws Exception {
return userDAO.findAll(page, pageSize);
}

// 更新用户信息
public boolean updateUser(User user) throws Exception {
return userDAO.update(user);
}

// 删除用户
public boolean deleteUser(int userId) throws Exception {
return userDAO.delete(userId);
}

// 记录登录日志
public void logLogin(int userId, String ipAddress) {
try {
// 记录登录日志的逻辑
System.out.println("User " + userId + " logged in from " + ipAddress);
} catch (Exception e) {
// 日志记录失败不应该影响登录流程
e.printStackTrace();
}
}

// 密码哈希
private String hashPassword(String password) throws Exception {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hashedBytes = md.digest(password.getBytes("UTF-8"));

StringBuilder sb = new StringBuilder();
for (byte b : hashedBytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
}

最佳实践

1. 项目结构组织

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
webapp/
├── WEB-INF/
│ ├── web.xml
│ ├── struts-config.xml
│ ├── validation.xml
│ ├── validator-rules.xml
│ ├── classes/
│ └── lib/
├── jsp/
│ ├── common/
│ ├── user/
│ └── admin/
├── css/
├── js/
└── images/

2. Action设计原则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 保持Action轻量级
public class UserAction extends DispatchAction {

// 每个方法只处理一个特定的业务操作
public ActionForward list(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) {
// 委托给业务服务
UserService service = new UserService();
List<User> users = service.getUserList();
request.setAttribute("users", users);
return mapping.findForward("success");
}

// 统一的异常处理
private ActionForward handleException(Exception e, ActionMapping mapping,
HttpServletRequest request) {
ActionErrors errors = new ActionErrors();
errors.add(ActionErrors.GLOBAL_ERROR, new ActionError("error.system"));
saveErrors(request, errors);
return mapping.findForward("error");
}
}

3. 性能优化建议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- web.xml 性能配置 -->
<context-param>
<param-name>org.apache.struts.action.LOCALE</param-name>
<param-value>true</param-value>
</context-param>

<context-param>
<param-name>org.apache.struts.util.CANCEL</param-name>
<param-value>true</param-value>
</context-param>

<!-- 启用压缩 -->
<filter>
<filter-name>CompressionFilter</filter-name>
<filter-class>com.example.filter.CompressionFilter</filter-class>
</filter>

Apache Struts作为经典的Java Web开发框架,其成熟的MVC架构和丰富的功能特性为企业级Web应用开发提供了稳定可靠的解决方案。虽然现在有更现代的框架,但Struts在维护现有系统和特定场景下仍有其价值。

版权所有,如有侵权请联系我