feat(structs): add OGNL binding lab and deepen interceptor learning page
This commit is contained in:
71
src/main/java/com/example/struts2/OgnlLabAction.java
Normal file
71
src/main/java/com/example/struts2/OgnlLabAction.java
Normal file
@@ -0,0 +1,71 @@
|
||||
package com.example.struts2;
|
||||
|
||||
import com.opensymphony.xwork2.ActionSupport;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class OgnlLabAction extends ActionSupport {
|
||||
private String keyword;
|
||||
private Integer minAge;
|
||||
private DemoUser user = new DemoUser();
|
||||
private List<String> tags = new ArrayList<>();
|
||||
private List<DemoUser> users;
|
||||
private List<DemoUser> filteredUsers;
|
||||
|
||||
public String execute() {
|
||||
loadUsers();
|
||||
filteredUsers = users;
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
public String bind() {
|
||||
loadUsers();
|
||||
filteredUsers = users.stream()
|
||||
.filter(u -> keyword == null || keyword.isBlank() || u.getName().contains(keyword) || u.getEmail().contains(keyword))
|
||||
.filter(u -> minAge == null || u.getAge() >= minAge)
|
||||
.collect(Collectors.toList());
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
private void loadUsers() {
|
||||
users = Arrays.asList(
|
||||
new DemoUser("张三", "zhangsan@example.com", 22),
|
||||
new DemoUser("李四", "lisi@example.com", 28),
|
||||
new DemoUser("王五", "wangwu@example.com", 34),
|
||||
new DemoUser("赵六", "zhaoliu@example.com", 19)
|
||||
);
|
||||
}
|
||||
|
||||
public static class DemoUser {
|
||||
private String name;
|
||||
private String email;
|
||||
private Integer age;
|
||||
|
||||
public DemoUser() {}
|
||||
public DemoUser(String name, String email, Integer age) {
|
||||
this.name = name;
|
||||
this.email = email;
|
||||
this.age = age;
|
||||
}
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
public String getEmail() { return email; }
|
||||
public void setEmail(String email) { this.email = email; }
|
||||
public Integer getAge() { return age; }
|
||||
public void setAge(Integer age) { this.age = age; }
|
||||
}
|
||||
|
||||
public String getKeyword() { return keyword; }
|
||||
public void setKeyword(String keyword) { this.keyword = keyword; }
|
||||
public Integer getMinAge() { return minAge; }
|
||||
public void setMinAge(Integer minAge) { this.minAge = minAge; }
|
||||
public DemoUser getUser() { return user; }
|
||||
public void setUser(DemoUser user) { this.user = user; }
|
||||
public List<String> getTags() { return tags; }
|
||||
public void setTags(List<String> tags) { this.tags = tags; }
|
||||
public List<DemoUser> getUsers() { return users; }
|
||||
public List<DemoUser> getFilteredUsers() { return filteredUsers; }
|
||||
}
|
||||
@@ -64,6 +64,14 @@
|
||||
<action name="interceptor" class="com.example.struts2.InterceptorDemoAction">
|
||||
<result>/interceptor-demo.jsp</result>
|
||||
</action>
|
||||
|
||||
<action name="ognl" class="com.example.struts2.OgnlLabAction">
|
||||
<result>/ognl-lab.jsp</result>
|
||||
</action>
|
||||
|
||||
<action name="ognl_bind" class="com.example.struts2.OgnlLabAction" method="bind">
|
||||
<result>/ognl-lab.jsp</result>
|
||||
</action>
|
||||
|
||||
<action name="interceptor_api" class="com.example.struts2.InterceptorDemoAction">
|
||||
<interceptor-ref name="apiStack"/>
|
||||
|
||||
@@ -61,6 +61,11 @@
|
||||
<p>从列表页到表单页,再到新增/编辑/删除,形成完整的 Struts2 业务流学习闭环。</p>
|
||||
<a href="user" class="btn green">进入 CRUD</a>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>🧪 OGNL / 参数绑定实验</h3>
|
||||
<p>可视化体验普通字段、嵌套对象、多选列表是如何被 Struts2 自动绑定到 Action 的。</p>
|
||||
<a href="ognl" class="btn purple">进入实验室</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="timeline">
|
||||
|
||||
@@ -53,10 +53,29 @@
|
||||
<div class="card">
|
||||
<h3>🔗 拦截器链演示</h3>
|
||||
<p>当前使用: <code>customStack</code> (日志 + 计时 + 监控 + 默认栈)</p>
|
||||
<a class="api-link" href="interceptor_api">使用 API 限流栈</a>
|
||||
<a class="api-link" href="interceptor_api">切换到 API 限流栈</a>
|
||||
<a class="api-link" href="interceptor">恢复默认栈</a>
|
||||
<p>API 栈包含: 日志 + 限流 + 验证 + 计时 + 默认栈</p>
|
||||
<table>
|
||||
<tr><th>拦截器</th><th>作用</th><th>你应该观察什么</th></tr>
|
||||
<tr><td>LoggingInterceptor</td><td>记录请求进入/退出</td><td>看日志顺序,理解责任链</td></tr>
|
||||
<tr><td>TimingInterceptor</td><td>统计执行耗时</td><td>看 executionTime 如何注入 request</td></tr>
|
||||
<tr><td>MonitorInterceptor</td><td>累积 Action 调用统计</td><td>连续刷新看调用数增长</td></tr>
|
||||
<tr><td>RateLimitInterceptor</td><td>限制频率</td><td>API 栈里观察限流触发</td></tr>
|
||||
<tr><td>ValidationInterceptor</td><td>校验输入</td><td>思考错误应该在哪一层拦截</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>🧪 快速实验建议</h3>
|
||||
<ul>
|
||||
<li>先刷新当前页两次,看调用统计是否增加</li>
|
||||
<li>再切到 <code>interceptor_api</code>,对比多了哪些拦截器</li>
|
||||
<li>观察“先进入后退出”的洋葱模型</li>
|
||||
<li>思考:如果你要加鉴权,应放在哪个拦截器位置最合理?</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>📚 拦截器执行顺序</h3>
|
||||
<pre>
|
||||
|
||||
@@ -62,6 +62,11 @@
|
||||
<p>通过用户管理体验列表页、表单页、重定向和状态更新。</p>
|
||||
<a class="btn btn-green" href="user">打开用户管理</a>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>🧪 OGNL / 参数绑定实验</h3>
|
||||
<p>通过真正可交互的表单观察 Struts2 的字段绑定、嵌套对象绑定和集合绑定。</p>
|
||||
<a class="btn btn-purple" href="ognl">打开 OGNL 实验室</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>📚 学习路径</h2>
|
||||
|
||||
83
src/main/webapp/ognl-lab.jsp
Normal file
83
src/main/webapp/ognl-lab.jsp
Normal file
@@ -0,0 +1,83 @@
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<%@ taglib prefix="s" uri="/struts-tags" %>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>OGNL / 参数绑定实验室 - Struts2</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; max-width: 1000px; margin: 40px auto; padding: 20px; background:#f7f8fa; }
|
||||
h1 { color:#8e44ad; }
|
||||
.card { background:#fff; border-radius:12px; padding:20px; margin:18px 0; box-shadow:0 4px 14px rgba(0,0,0,.06); }
|
||||
.lab { background:#fff7e6; border-left:4px solid #fa8c16; padding:15px; border-radius:8px; margin:20px 0; }
|
||||
.form-row { display:flex; gap:12px; flex-wrap:wrap; margin:10px 0; }
|
||||
input, select { padding:10px; min-width:180px; }
|
||||
button { background:#8e44ad; color:#fff; border:none; padding:10px 18px; border-radius:6px; cursor:pointer; }
|
||||
table { width:100%; border-collapse:collapse; margin-top:12px; }
|
||||
th, td { border:1px solid #ddd; padding:10px; text-align:left; }
|
||||
th { background:#8e44ad; color:white; }
|
||||
code { background:#f0f0f0; padding:2px 6px; border-radius:4px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🧪 OGNL / 参数绑定实验室</h1>
|
||||
|
||||
<div class="lab">
|
||||
<strong>实验任务卡</strong>
|
||||
<ul>
|
||||
<li>输入关键字和最小年龄,观察 Struts2 如何自动绑定请求参数到 Action 字段</li>
|
||||
<li>注意 <code>keyword</code>、<code>minAge</code>、<code>user.name</code> 这类命名会如何映射</li>
|
||||
<li>观察页面里 <code><s:property></code>、<code><s:iterator></code> 如何读取 Action 中的数据</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>参数过滤实验</h3>
|
||||
<s:form action="ognl_bind" method="post">
|
||||
<div class="form-row">
|
||||
<s:textfield name="keyword" label="关键字" placeholder="例如:张 / example"/>
|
||||
<s:textfield name="minAge" label="最小年龄" placeholder="例如:20"/>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<s:textfield name="user.name" label="嵌套对象 user.name" placeholder="例如:测试用户"/>
|
||||
<s:checkboxlist name="tags" label="兴趣标签" list="{'MVC','OGNL','Interceptor','JSP'}"/>
|
||||
</div>
|
||||
<button type="submit">执行绑定与过滤</button>
|
||||
</s:form>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>绑定结果观察</h3>
|
||||
<p>keyword = <code><s:property value="keyword" default="(空)"/></code></p>
|
||||
<p>minAge = <code><s:property value="minAge" default="(空)"/></code></p>
|
||||
<p>user.name = <code><s:property value="user.name" default="(空)"/></code></p>
|
||||
<p>tags = <code><s:property value="tags" default="[]"/></code></p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>过滤后的用户列表</h3>
|
||||
<table>
|
||||
<tr><th>姓名</th><th>邮箱</th><th>年龄</th></tr>
|
||||
<s:iterator value="filteredUsers" var="u">
|
||||
<tr>
|
||||
<td><s:property value="#u.name"/></td>
|
||||
<td><s:property value="#u.email"/></td>
|
||||
<td><s:property value="#u.age"/></td>
|
||||
</tr>
|
||||
</s:iterator>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>学习点</h3>
|
||||
<ul>
|
||||
<li><code>keyword</code> → 直接绑定到 Action 同名字段</li>
|
||||
<li><code>user.name</code> → 绑定到嵌套对象属性</li>
|
||||
<li><code>tags</code> → 多选值绑定到 <code>List<String></code></li>
|
||||
<li><code><s:iterator></code> 会遍历 Action 暴露出来的集合</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<p><a href="learn">← 返回学习中心</a></p>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user