Apache Struts 常用工具与 API 详解
Struts 6.12.0 常用工具与 API 详解:archetype/依赖树/jetty:run,config-browser 与 Manager 文本接口,诊断命令,ActionSupport/标签库/上传 API,附实测回显。
Apache Struts 常用工具与 API 详解
目标:面向 Apache Struts 6.12.0 的日常开发与运维场景,整理常用工具链(Maven archetype、dependency:tree、jetty:run、config-browser 插件、Tomcat Manager 文本接口、诊断命令)与核心开发 API(ActionSupport、结果类型、标签库、文件上传、i18n),每个工具与 API 均附实测命令与真实回显。实测环境:Ubuntu 22.04.5 + Tomcat 9.0.58 + OpenJDK 17.0.20.1 + Maven 3.6.3。
一、工具与 API 总览
| 类别 | 工具 / API | 用途 |
|---|---|---|
| 工程生成 | mvn archetype:generate | 按 struts2-archetype-blank 模板生成工程 |
| 构建打包 | mvn package + maven-war-plugin | 测试并产出 WAR |
| 依赖分析 | mvn dependency:tree | 查看 struts2-core 传递依赖 |
| 开发期运行 | mvn jetty:run | 内嵌 Jetty 免部署运行 |
| 配置可视化 | struts2-config-browser-plugin | 浏览器查看已加载的 Action 配置 |
| 应用部署 | Tomcat Manager 文本接口 | list / undeploy / deploy |
| 诊断排查 | ps / ss / journalctl / curl / unzip | 进程、端口、日志、包结构、HTTP 探测 |
| 核心 API | ActionSupport 与结果常量 | Action 基类、SUCCESS/INPUT 等返回值 |
| 国际化 API | getText + 资源文件 | 文案渲染与 request_locale 切换 |
| 标签库 API | s:property / s:iterator / s:form 等 | 值栈取值与表单渲染 |
| 上传 API | File/uploadContentType/uploadFileName 三件套 | multipart 文件接收 |
二、Maven 工具链
2.1 archetype:generate(工程生成)
常用参数(均为实测命令中出现的形式):
| 参数 | 实测取值 | 说明 |
|---|---|---|
| -B | — | 批处理模式,关闭交互 |
| -DgroupId | com.example.struts | 工程 groupId |
| -DartifactId | struts2-demo | 工程名(生成同名目录) |
| -Dversion | 1.0.0 | 工程初始版本 |
| -DarchetypeGroupId | org.apache.struts | 模板组织 |
| -DarchetypeArtifactId | struts2-archetype-blank | 空白工程模板 |
| -DarchetypeVersion | 6.0.0 | 模板版本(当前 archetype 最新发布版) |
| -DinteractiveMode | false | 非交互模式 |
实测命令与回显(节选):
cd /root
mvn archetype:generate -B -DgroupId=com.example.struts -DartifactId=struts2-demo -Dversion=1.0.0 \
-DarchetypeGroupId=org.apache.struts -DarchetypeArtifactId=struts2-archetype-blank \
-DarchetypeVersion=6.0.0 -DinteractiveMode=false
[INFO] Parameter: artifactId, Value: struts2-demo
[INFO] Parameter: version, Value: 1.0.0
[INFO] Parameter: package, Value: com.example.struts
[INFO] Project created from Archetype in dir: /root/struts2-demo
[INFO] BUILD SUCCESS
[INFO] Total time: 03:30 min
2.2 mvn package 与 maven-war-plugin
archetype 生成的 pom 未固定 maven-war-plugin 版本,Maven 3.6.3 下默认解析到 2.2,JDK 17 打包实测报错:
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-war-plugin:2.2:war (default-war) on project struts2-demo: Execution default-war of goal org.apache.maven.plugins:maven-war-plugin:2.2:war failed: Unable to load the mojo 'war' in the plugin 'org.apache.maven.plugins:maven-war-plugin:2.2' due to an API incompatibility: org.codehaus.plexus.component.repository.exception.ComponentLookupException: Cannot access defaults field of Properties
修复:pom.xml 的 build/plugins 首位固定新版:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.4.0</version>
</plugin>
修复后 mvn package 实测:6 个测试全部通过,产出 9.9M WAR:
Tests run: 6, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS
[INFO] Total time: 01:05 min
ls -lh target/*.war
-rw-r--r-- 1 root root 9.9M Oct 12 05:17 target/struts2-demo-1.0.0.war
2.3 dependency:tree(依赖分析)
cd /root/struts2-demo && mvn dependency:tree
实测回显(struts2-core 与 config-browser 两个子树):
[INFO] +- org.apache.struts:struts2-core:jar:6.12.0:compile
[INFO] | +- org.freemarker:freemarker:jar:2.3.35:compile
[INFO] | +- com.github.ben-manes.caffeine:caffeine:jar:2.9.3:compile
[INFO] | | +- org.checkerframework:checker-qual:jar:3.19.0:compile
[INFO] | | \- com.google.errorprone:error_prone_annotations:jar:2.10.0:compile
[INFO] | +- ognl:ognl:jar:3.3.5:compile
[INFO] | | \- org.javassist:javassist:jar:3.29.0-GA:compile
[INFO] | +- commons-fileupload:commons-fileupload:jar:1.6.0:compile
[INFO] | +- commons-io:commons-io:jar:2.15.1:compile
[INFO] | +- org.apache.commons:commons-lang3:jar:3.20.0:compile
[INFO] | \- org.apache.commons:commons-text:jar:1.15.0:compile
[INFO] +- org.apache.struts:struts2-config-browser-plugin:jar:6.12.0:compile
[INFO] | \- org.apache.struts:struts2-velocity-plugin:jar:6.12.0:compile
[INFO] | +- org.apache.velocity:velocity-engine-core:jar:2.4.1:compile
[INFO] | | \- org.slf4j:slf4j-api:jar:1.7.36:compile
💡 排障用途:升级 Struts 版本或排查依赖冲突时,用该命令核对实际解析版本(如 commons-fileupload 1.6.0、ognl 3.3.5)。
2.4 jetty:run(开发期内嵌运行)
pom 已内置 jetty-maven-plugin(contextPath /struts2-demo),开发期免打包免部署:
cd /root/struts2-demo && nohup mvn jetty:run -Djetty.http.port=8081 > /tmp/jetty.log 2>&1 &
首次运行需下载插件依赖(实测约 151 秒),启动完成后 /tmp/jetty.log 出现:
[INFO] Started ServerConnector@64d53f0d{HTTP/1.1, (http/1.1)}{0.0.0.0:8081}
[INFO] Started @151097ms
[INFO] Started Jetty Server
验证(端口 8081、路径带 contextPath):
curl -s 'http://localhost:8081/struts2-demo/hello.action?name=Jetty' | grep '<h2>'
<h2>Hello, Jetty! This page is rendered by Struts tags.</h2>
三、struts2-config-browser-plugin(配置浏览器)
pom 中由 archetype 预置依赖 org.apache.struts:struts2-config-browser-plugin:6.12.0,部署后自动暴露可视化配置页面。实测访问(302 跳转到 actionNames 页):
curl -s -o /dev/null -w 'HTTP %{http_code}\n' 'http://localhost:8080/struts2-demo/config-browser/index.action'
curl -sI 'http://localhost:8080/struts2-demo/config-browser/index.action' | grep -i '^HTTP\|^Location'
实测回显:
HTTP 302
HTTP/1.1 302
Location: /struts2-demo/config-browser/actionNames.action
curl -s 'http://localhost:8080/struts2-demo/config-browser/actionNames.action' | grep -oE '<title>[^<]*</title>'
实测回显:
<title>Actions in namespace</title>
⚠️ 安全提示:config-browser 会暴露应用全部 Action、namespace 与配置明细,仅限开发环境使用,生产环境应移除该依赖或屏蔽对应 URL。
四、Tomcat Manager 文本接口(部署工具)
4.1 账号配置
/etc/tomcat9/tomcat-users.xml 的 <tomcat-users> 段内追加(演示账号 admin / q1w2e3r4):
<role rolename="manager-gui"/>
<role rolename="manager-script"/>
<user username="admin" password="q1w2e3r4" roles="manager-gui,manager-script"/>
</tomcat-users>
重启生效并查看应用列表:
systemctl restart tomcat9 && sleep 9
curl -s -u admin:q1w2e3r4 'http://localhost:8080/manager/text/list'
实测回显:
OK - Listed applications for virtual host [localhost]
/:running:0:ROOT
/host-manager:running:0:/usr/share/tomcat9-admin/host-manager
/struts2-demo:running:3:struts2-demo
/manager:running:0:/usr/share/tomcat9-admin/manager
4.2 卸载与部署
卸载应用:
curl -s -u admin:q1w2e3r4 'http://localhost:8080/manager/text/undeploy?path=/struts2-demo'
OK - Undeployed application at context path [/struts2-demo]
上传 war 部署(--upload-file + update=true):
curl -s -u admin:q1w2e3r4 --upload-file /root/struts2-demo/target/struts2-demo-1.0.0.war 'http://localhost:8080/manager/text/deploy?path=/struts2-demo&update=true'
OK - Deployed application at context path [/struts2-demo]
部署后验证:
curl -s -o /dev/null -w 'app after manager deploy: HTTP %{http_code}\n' 'http://localhost:8080/struts2-demo/hello.action?name=M'
app after manager deploy: HTTP 200
五、诊断与排查命令
5.1 进程与端口
确认 Tomcat 进程的 JVM 参数(核对堆限制是否生效):
ps -ef | grep '[j]ava' | head -2
实测回显(堆参数位于 -classpath 之前):
tomcat 6295 1 99 05:06 ? 00:00:07 /usr/lib/jvm/default-java/bin/java -Djava.util.logging.config.file=/var/lib/tomcat9/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djava.awt.headless=true -Xms256m -Xmx512m -Xmn128m -Djdk.tls.ephemeralDHKeySize=2048 -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -Dorg.apache.catalina.security.SecurityListener.UMASK=0027 -Dignore.endorsed.dirs= -classpath /usr/share/tomcat9/bin/bootstrap.jar:/usr/share/tomcat9/bin/tomcat-juli.jar -Dcatalina.base=/var/lib/tomcat9 -Dcatalina.home=/usr/share/tomcat9 -Djava.io.tmpdir=/tmp org.apache.catalina.startup.Bootstrap start
确认 8080 监听:
ss -tlnp | grep 8080
LISTEN 0 100 *:8080 *:* users:(("java",pid=7892,fd=37))
5.2 日志
应用层日志(log4j2 输出经 systemd 收集,Ubuntu 的 tomcat9 无 catalina.out):
journalctl -u tomcat9 --no-pager | grep -iE 'struts|devMode' | head
实测回显:
Oct 12 05:18:05 8a0740394d5a tomcat9[6295]: Deploying web application archive [/var/lib/tomcat9/webapps/struts2-demo.war]
Oct 12 05:18:07 8a0740394d5a tomcat9[6295]: 2026-10-12 05:18:07,504 WARN [Catalina-utility-1] ognl.SecurityMemberAccess (SecurityMemberAccess.java:544) - Working in devMode, using devMode excluded classes and packages!
Tomcat 自身部署日志(/var/log/tomcat9/catalina.2026-10-12.log):
12-Oct-2026 05:18:05.350 INFO [Catalina-utility-1] org.apache.catalina.startup.HostConfig.deployWAR Deploying web application archive [/var/lib/tomcat9/webapps/struts2-demo.war]
12-Oct-2026 05:18:07.752 INFO [Catalina-utility-1] org.apache.catalina.startup.HostConfig.deployWAR Deployment of web application archive [/var/lib/tomcat9/webapps/struts2-demo.war] has finished in [2,402] ms
日志文件清单:/var/log/tomcat9/catalina.<date>.log(运行日志)、/var/log/tomcat9/localhost.<date>.log(应用异常)、/var/log/tomcat9/localhost_access_log.<date>.txt(访问日志)、journalctl -u tomcat9(应用 stdout 与 log4j2 输出)。
5.3 WAR 包结构查看
unzip -l /var/lib/tomcat9/webapps/struts2-demo.war | head -12
实测回显:
Archive: /var/lib/tomcat9/webapps/struts2-demo.war
Length Date Time Name
--------- ---------- ----- ----
0 2026-10-12 05:23 META-INF/
81 2026-10-12 05:23 META-INF/MANIFEST.MF
0 2026-10-12 05:17 WEB-INF/
0 2026-10-12 05:17 WEB-INF/lib/
0 2026-10-12 05:22 WEB-INF/example/
0 2026-10-12 05:17 WEB-INF/classes/
0 2026-10-12 05:17 WEB-INF/classes/com/
0 2026-10-12 05:17 WEB-INF/classes/com/example/
0 2026-10-12 05:17 WEB-INF/classes/com/example/struts/
5.4 curl 探测技巧
状态码与重定向目标一次拿到:
curl -s -o /dev/null -w 'HTTP %{http_code} redirect->%{redirect_url}\n' -X POST -d 'username=admin&password=q1w2e3r4' 'http://localhost:8080/struts2-demo/example/Login_execute.action'
实测回显:
HTTP 302 redirect->http://localhost:8080/struts2-demo/example/Menu.action
只看响应头(定位 302 的 Location):
curl -sI 'http://localhost:8080/struts2-demo/goHello.action' | grep -i '^HTTP\|^Location'
实测回显:
HTTP/1.1 302
Location: /struts2-demo/hello.action?name=Redirected
multipart 上传模拟(-F 自动构造 multipart/form-data):
curl -s -F 'upload=@/tmp/small.bin;type=application/octet-stream' 'http://localhost:8080/struts2-demo/upload.action' | grep -E 'Saved'
实测回显:
<p>Saved small.bin (1048576 bytes, content-type=application/octet-stream) to /tmp/small.bin</p>
六、Action 开发核心 API
6.1 ActionSupport 与结果常量
Action 基类链:ExampleSupport extends ActionSupport,业务 Action 继承 ExampleSupport(archetype 组织方式)。execute() 返回 String 结果码,常用常量:SUCCESS、INPUT(校验失败回填表单)、ERROR、LOGIN、NONE。
HelloWorld(getText 用法)与 Login(INPUT 用法)节选:
public class HelloWorld extends ExampleSupport {
public String execute() throws Exception {
setMessage(getText(MESSAGE));
return SUCCESS;
}
public static final String MESSAGE = "HelloWorld.message";
private String message;
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
}
public class Login extends ExampleSupport {
public String execute() throws Exception {
if (isInvalid(getUsername())) return INPUT;
if (isInvalid(getPassword())) return INPUT;
return SUCCESS;
}
}
自定义 Action 同样继承 ActionSupport,请求参数由同名 setter 自动注入(如 ?name=Struts 注入 setName):
public class HelloAction extends ActionSupport {
private String name;
@Override
public String execute() throws Exception {
if (name == null || name.trim().isEmpty()) {
name = "World";
}
return SUCCESS;
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
实测(无参数时兜底 World):
curl -s 'http://localhost:8080/struts2-demo/hello.action' | grep -E '<h2>'
<h2>Hello, World! This page is rendered by Struts tags.</h2>
6.2 国际化 API:getText 与 request_locale
资源文件 src/main/resources/com/example/struts/example/package.properties(节选):
HelloWorld.message = Struts is up and running...
requiredstring= ${getText(fieldName)} is required.
password = Password
username = User Name
Missing.message = This feature is under construction. Please try again in the next iteration.
getText(key) 按当前 Locale 取值,Action 中 setMessage(getText(MESSAGE)) 渲染出的页面:
curl -s 'http://localhost:8080/struts2-demo/example/HelloWorld.action' | grep -E '<h2>|<title>'
<title>Struts is up and running...</title>
<h2>Struts is up and running...</h2>
I18nInterceptor 支持用 request_locale 参数切换语言(对应 package_es.properties):
curl -s 'http://localhost:8080/struts2-demo/example/HelloWorld.action?request_locale=es' | grep -E '<h2>|<title>'
<title>¡Struts está bien!...</title>
<h2>¡Struts está bien!...</h2>
6.3 值栈标签:s:property 与 s:iterator
Hello.jsp 使用两个高频标签:s:property(取值栈属性)与 s:iterator(遍历集合):
<h2>Hello, <s:property value="name"/>! This page is rendered by Struts tags.</h2>
<ul>
<s:iterator value="features">
<li><s:property/></li>
</s:iterator>
</ul>
实测渲染结果(?name=Struts):
<h2>Hello, Struts! This page is rendered by Struts tags.</h2>
<ul>
<li>Apache Struts 6.12.0</li>
<li>Apache Tomcat 9.0.58</li>
<li>OpenJDK 17.0.20.1</li>
</ul>
6.4 表单标签与校验框架
Upload.jsp 的 s:form/s:file/s:submit 自动生成上传表单(enctype 由标签属性指定):
<s:form action="upload" method="post" enctype="multipart/form-data">
<s:file name="upload" label="Select file"/>
<s:submit value="Upload"/>
</s:form>
<s:actionerror/>
<p><s:property value="message"/></p>
校验框架:Login-validation.xml 对字段做 requiredstring 校验,校验失败自动回到 result name="input" 的页面并渲染错误信息:
<validators>
<field name="username">
<field-validator type="requiredstring">
<message key="requiredstring"/>
</field-validator>
</field>
<field name="password">
<field-validator type="requiredstring">
<message key="requiredstring"/>
</field-validator>
</field>
</validators>
空表单提交实测(错误文案来自 i18n 资源 requiredstring 模板):
curl -s -X POST 'http://localhost:8080/struts2-demo/example/Login_execute.action' | grep -E 'errorMessage' | head -4
实测回显:
<td class="tdErrorMessage" colspan="2"><span class="errorMessage">User Name is required.</span></td>
<td class="tdErrorMessage" colspan="2"><span class="errorMessage">Password is required.</span></td>
6.5 文件上传 API:三件套字段
UploadAction 通过「表单字段名 + 固定后缀」三个属性接收上传(表单字段名为 upload):
| Action 属性 | 类型 | 注入内容 |
|---|---|---|
| upload | java.io.File | 上传的临时文件 |
| uploadContentType | String | 内容类型(Content-Type) |
| uploadFileName | String | 原始文件名 |
核心处理逻辑(复制到目标目录):
File dest = new File("/tmp", uploadFileName);
FileInputStream in = new FileInputStream(upload);
FileOutputStream out = new FileOutputStream(dest);
byte[] buf = new byte[8192];
int n;
while ((n = in.read(buf)) > 0) {
out.write(buf, 0, n);
}
in.close();
out.close();
message = "Saved " + uploadFileName + " (" + upload.length() + " bytes, content-type=" + uploadContentType + ") to /tmp/" + uploadFileName;
实测上传 1MB 文件的页面回显:
<p>Saved small.bin (1048576 bytes, content-type=application/octet-stream) to /tmp/small.bin</p>
⚠️ 注意:upload 为 null 时应返回 INPUT(本示例如此实现);大小上限由
struts.multipart.maxSize控制,超限时 fileUpload 拦截器捕获错误并回填到 input 页面。
七、结果类型 API
struts-default.xml 内置结果类型(实取节选,namespace 全名见《Apache Struts 配置参数详解》):
cd /tmp && grep -oE '<result-type name="[^"]+" class="[^"]+"' struts-default.xml | head -12
实测回显:
<result-type name="chain" class="com.opensymphony.xwork2.ActionChainResult"
<result-type name="dispatcher" class="org.apache.struts2.result.ServletDispatcherResult"
<result-type name="freemarker" class="org.apache.struts2.views.freemarker.FreemarkerResult"
<result-type name="httpheader" class="org.apache.struts2.result.HttpHeaderResult"
<result-type name="redirect" class="org.apache.struts2.result.ServletRedirectResult"
<result-type name="redirectAction" class="org.apache.struts2.result.ServletActionRedirectResult"
<result-type name="stream" class="org.apache.struts2.result.StreamResult"
<result-type name="plainText" class="org.apache.struts2.result.PlainTextResult"
<result-type name="postback" class="org.apache.struts2.result.PostbackResult"
三种高频用法的 struts.xml 写法与实测效果:
<action name="goHello">
<result type="redirect">/hello.action?name=Redirected</result>
</action>
<action name="index">
<result type="redirectAction">
<param name="actionName">HelloWorld</param>
<param name="namespace">/example</param>
</result>
</action>
<action name="hello" class="com.example.struts.example.HelloAction">
<result name="success">/WEB-INF/example/Hello.jsp</result>
</action>
redirect 实测(302 到带参数 URL):
HTTP/1.1 302
Location: /struts2-demo/hello.action?name=Redirected
redirectAction 实测(跨 namespace 302 到 Action,未匹配请求经 default-action-ref 兜底触发):
HTTP/1.1 302
Location: /struts2-demo/example/HelloWorld.action
dispatcher(默认类型)实测(转发渲染 JSP,HTTP 200 且内容为页面 HTML):
curl -s 'http://localhost:8080/struts2-demo/example/HelloWorld.action' | grep -E '<h2>'
<h2>Struts is up and running...</h2>
八、参考链接
- Apache Struts 官网:https://struts.apache.org/
- Action / ActionSupport 文档:https://struts.apache.org/core-developers/actions
- 标签参考:https://struts.apache.org/tag-developers/tag-reference
- 文件上传:https://struts.apache.org/core-developers/file-upload
- 国际化(i18n):https://struts.apache.org/core-developers/localization
- config-browser 插件:https://struts.apache.org/plugins/config-browser