feat: expand struts demo lab

This commit is contained in:
Codex
2026-03-18 18:12:20 +08:00
parent 1f60832445
commit fba7b0497f
25 changed files with 1847 additions and 1447 deletions

View File

@@ -1,173 +1,54 @@
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="zh-CN">
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数据封装 - Struts2 学习</title>
<title>Model Binding Guide</title>
<style>
body { font-family: 'Segoe UI', 'PingFang SC', sans-serif; background: linear-gradient(135deg, #667eea, #764ba2); min-height: 100vh; margin: 0; padding: 20px; }
.container { max-width: 1200px; margin: 0 auto; }
.breadcrumb { background: white; padding: 15px 25px; border-radius: 10px; margin-bottom: 20px; }
.breadcrumb a { color: #667eea; text-decoration: none; }
.content { background: white; border-radius: 20px; padding: 40px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); }
h1 { color: #667eea; border-bottom: 3px solid #667eea; padding-bottom: 15px; }
h2 { color: #764ba2; margin-top: 30px; }
.section { margin: 25px 0; padding: 20px; background: #f8f9fa; border-radius: 10px; }
pre { background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 10px; overflow-x: auto; font-size: 0.9em; }
.keyword { color: #569cd6; }
.string { color: #ce9178; }
.comment { color: #6a9955; }
.btn { display: inline-block; padding: 12px 30px; background: #667eea; color: white; text-decoration: none; border-radius: 25px; margin-right: 10px; }
.note { background: #fff3e0; border-left: 4px solid #ff9800; padding: 15px; margin: 20px 0; border-radius: 0 10px 10px 0; }
body { margin: 0; padding: 24px; font-family: "Aptos", "Segoe UI", sans-serif; background: linear-gradient(135deg, #7c3aed, #a855f7); }
.shell { max-width: 980px; margin: 0 auto; background: rgba(255,255,255,0.96); border-radius: 28px; padding: 28px; box-shadow: 0 24px 60px rgba(0,0,0,0.18); }
.eyebrow { font-size: 12px; text-transform: uppercase; letter-spacing: 0.12em; color: #7c3aed; font-weight: 800; }
h1, h2 { margin: 10px 0 12px; }
p, li { color: #5e5570; line-height: 1.9; }
pre { background: #101827; color: #d9e7ff; padding: 18px; border-radius: 18px; overflow-x: auto; }
.links { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 18px; }
.btn { display: inline-flex; padding: 10px 14px; border-radius: 999px; text-decoration: none; font-weight: 700; background: #f0e9ff; color: #7c3aed; }
</style>
</head>
<body>
<div class="container">
<div class="breadcrumb">
<a href="/">🏠 首页</a> / 数据封装
</div>
<div class="content">
<h1>📦 数据封装 - 属性驱动 vs 模型驱动</h1>
<div class="note">
<strong>📝 本节要点:</strong> 掌握 Struts2 两种数据封装方式,将请求参数封装到 Java 对象
</div>
<h2>1. 两种封装方式对比</h2>
<div class="section">
<table style="width:100%; border-collapse: collapse;">
<tr style="background:#667eea; color:white;">
<th style="padding:10px;">方式</th>
<th style="padding:10px;">实现</th>
<th style="padding:10px;">适用场景</th>
</tr>
<tr style="background:#f8f9fa;">
<td style="padding:10px;"><strong>属性驱动</strong></td>
<td style="padding:10px;">Action 中定义属性 + getter/setter</td>
<td style="padding:10px;">简单场景,属性较少</td>
</tr>
<tr>
<td style="padding:10px;"><strong>模型驱动</strong></td>
<td style="padding:10px;">实现 ModelDriven 接口</td>
<td style="padding:10px;">复杂对象,表单字段多</td>
</tr>
</table>
</div>
<h2>2. 方式一:属性驱动 (Property Driven)</h2>
<h3>2.1 基本属性封装</h3>
<div class="section">
<pre><span class="comment">/**
* 直接在 Action 中定义属性
* 表单: &lt;input name="username"&gt; 会自动调用 setUsername()
*/</span>
<span class="keyword">public class</span> UserAction <span class="keyword">extends</span> ActionSupport {
<span class="comment">// Struts2 自动调用 setter 注入参数</span>
<span class="keyword">private</span> String username;
<span class="keyword">private</span> String email;
<span class="keyword">private</span> Integer age;
<span class="annotation">@Override</span>
<span class="keyword">public</span> String execute() {
System.out.println(username + <span class="string">" - "</span> + email + <span class="string">" - "</span> + age);
<span class="keyword">return</span> SUCCESS;
}
<span class="comment">// 必须提供 setter 和 getter</span>
<span class="keyword">public</span> String getUsername() { <span class="keyword">return</span> username; }
<span class="keyword">public void</span> setUsername(String username) { <span class="keyword">this</span>.username = username; }
<span class="keyword">public</span> String getEmail() { <span class="keyword">return</span> email; }
<span class="keyword">public void</span> setEmail(String email) { <span class="keyword">this</span>.email = email; }
<span class="keyword">public</span> Integer getAge() { <span class="keyword">return</span> age; }
<span class="keyword">public void</span> setAge(Integer age) { <span class="keyword">this</span>.age = age; }
}</pre>
</div>
<h3>2.2 复杂属性 (嵌套对象)</h3>
<div class="section">
<p>表单中的 <code>user.username</code> 会自动调用 setUser(用户对象).setUsername()</p>
<pre><span class="comment">// Action</span>
<span class="keyword">private</span> User user; <span class="comment">// 包含 username, email 属性的对象</span>
<div class="shell">
<div class="eyebrow">Guide</div>
<h1>Property binding vs model-driven binding</h1>
<p>This note helps explain how Struts2 turns request parameters into action state. It works well alongside the user form and validation examples.</p>
<span class="comment">// 表单</span>
<span class="keyword">&lt;input</span> name=<span class="string">"user.username"</span>/&gt;
<span class="keyword">&lt;input</span> name=<span class="string">"user.email"</span>/&gt;</pre>
</div>
<h2>3. 方式二:模型驱动 (ModelDriven)</h2>
<div class="note">
<strong>💡 推荐:</strong> 对于复杂表单使用模型驱动更清晰Action 和 Model 分离
</div>
<div class="section">
<pre><span class="keyword">import</span> com.opensymphony.xwork2.ModelDriven;
<h2>Property-driven</h2>
<ul>
<li>Define simple fields directly on the action.</li>
<li>Expose setters and getters for each form field.</li>
<li>Great for short demos and small forms.</li>
</ul>
<span class="comment">/**
* 实现 ModelDriven 接口
* 需要:
* 1. 实现 getModel() 方法
* 2. 泛型指定模型类型
*/</span>
<span class="keyword">public class</span> UserAction <span class="keyword">extends</span> ActionSupport <span class="keyword">implements</span> ModelDriven&lt;User&gt; {
<span class="comment">// 模型对象</span>
<span class="keyword">private</span> User user = <span class="keyword">new</span> User();
<span class="comment">/**
* 返回模型对象
* Struts2 会把参数注入到返回的对象中
*/</span>
<span class="annotation">@Override</span>
<span class="keyword">public</span> User getModel() {
<span class="keyword">return</span> user;
}
<span class="annotation">@Override</span>
<span class="keyword">public</span> String execute() {
<span class="comment">// 直接使用 user 对象</span>
System.out.println(user.getUsername());
<span class="keyword">return</span> SUCCESS;
<h2>Model-driven</h2>
<ul>
<li>Return a dedicated model object from <code>getModel()</code>.</li>
<li>Keep request data separate from action orchestration logic.</li>
<li>Better for larger forms and nested objects.</li>
</ul>
<pre>public class UserAction extends ActionSupport implements ModelDriven&lt;User&gt; {
private final User user = new User();
@Override
public User getModel() {
return user;
}
}</pre>
</div>
<h2>4. List/Map 属性封装</h2>
<div class="section">
<pre><span class="comment">// Action - 接收 List</span>
<span class="keyword">private</span> List&lt;User&gt; users;
<span class="keyword">public void</span> setUsers(List&lt;User&gt; users) { <span class="keyword">this</span>.users = users; }
<span class="keyword">public</span> List&lt;User&gt; getUsers() { <span class="keyword">return</span> users; }
<span class="comment">// 页面表单 - users[0].username, users[1].username</span>
<span class="keyword">&lt;input</span> name=<span class="string">"users[0].username"</span> value=<span class="string">"张三"</span>/&gt;
<span class="keyword">&lt;input</span> name=<span class="string">"users[1].username"</span> value=<span class="string">"李四"</span>/&gt;
<span class="comment">// Action - 接收 Map</span>
<span class="keyword">private</span> Map&lt;String, User&gt; userMap;
<span class="keyword">&lt;input</span> name=<span class="string">"userMap['one'].username"</span>/&gt;</pre>
</div>
<h2>5. 封装流程图</h2>
<div class="section">
<pre style="text-align: center; font-size: 1.1em;">
请求参数 → Action setter 方法 → 属性赋值
实现 ModelDriven → getModel() → 模型对象赋值
值栈 (Value Stack)</pre>
</div>
<div style="margin-top: 30px;">
<a href="/demo/upload" class="btn">下一节:文件上传 →</a>
<a href="/" class="btn" style="background: #764ba2;">← 返回首页</a>
</div>
<div class="links">
<a class="btn" href="../../user/form.jsp">Open user form</a>
<a class="btn" href="../../index.jsp">Back to portal</a>
</div>
</div>
</body>
</html>
</html>