Apache Struts 常用工具与 API 详解

Struts 6.12.0 常用工具与 API 详解:archetype/依赖树/jetty:run,config-browser 与 Manager 文本接口,诊断命令,ActionSupport/标签库/上传 API,附实测回显。

Apache StrutsApache Struts2026/10/12

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 探测
核心 APIActionSupport 与结果常量Action 基类、SUCCESS/INPUT 等返回值
国际化 APIgetText + 资源文件文案渲染与 request_locale 切换
标签库 APIs:property / s:iterator / s:form 等值栈取值与表单渲染
上传 APIFile/uploadContentType/uploadFileName 三件套multipart 文件接收

二、Maven 工具链

2.1 archetype:generate(工程生成)

常用参数(均为实测命令中出现的形式):

参数实测取值说明
-B—批处理模式,关闭交互
-DgroupIdcom.example.struts工程 groupId
-DartifactIdstruts2-demo工程名(生成同名目录)
-Dversion1.0.0工程初始版本
-DarchetypeGroupIdorg.apache.struts模板组织
-DarchetypeArtifactIdstruts2-archetype-blank空白工程模板
-DarchetypeVersion6.0.0模板版本(当前 archetype 最新发布版)
-DinteractiveModefalse非交互模式

实测命令与回显(节选):

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>&iexcl;Struts est&aacute; 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 属性类型注入内容
uploadjava.io.File上传的临时文件
uploadContentTypeString内容类型(Content-Type)
uploadFileNameString原始文件名

核心处理逻辑(复制到目标目录):

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>

八、参考链接