Apache Struts 构建并运行示例应用指南
Apache Struts 6.12.0 构建并运行示例应用指南:archetype 生成工程,自定义 Action 与 JSP,war 打包排障,部署 Tomcat 9,登录校验与文件上传实测,内嵌 Jetty 运行与日志排查,全程实测。
Apache Struts 构建并运行示例应用指南
目标:从零构建一个 Apache Struts 6.12.0 示例 Web 应用并在 Tomcat 9 上运行验证。示例应用由两部分组成:archetype 自带的 HelloWorld/Login 示例,以及本文新增的自定义 Action(hello 问候、upload 文件上传、goHello 重定向)。覆盖工程生成、代码编写、war 打包与排障、两种部署方式(复制 WAR 与 Manager 接口)、页面功能实测、内嵌 Jetty 运行与日志排查。所有命令与回显均在 Ubuntu 22.04.5 + Tomcat 9.0.58 环境实测通过。
一、环境信息
- 操作系统:Ubuntu 22.04.5 LTS(jammy)
- Java:OpenJDK 17.0.20.1
- 构建工具:Apache Maven 3.6.3
- Servlet 引擎:Apache Tomcat 9.0.58(systemd 托管,端口 8080,JVM 堆 -Xms256m -Xmx512m -Xmn128m)
- Struts 版本:6.12.0
- 工程目录:
/root/struts2-demo - Tomcat Manager 账号:admin / q1w2e3r4(5.2 节配置)
示例应用组成:
| 模块 | 来源 | 说明 |
|---|---|---|
| HelloWorld / Login / Menu / Register / Missing | archetype 自带 | 登录校验、通配符、i18n 演示 |
| hello(HelloAction + Hello.jsp) | 本文新增 | 参数注入与 s:property / s:iterator 标签 |
| upload(UploadAction + Upload.jsp) | 本文新增 | multipart 文件上传 |
| goHello | 本文新增 | result type="redirect" 重定向演示 |
二、生成示例工程
2.1 生成命令
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 工程文件清单
cd /root/struts2-demo && find . -type f | sort
实测回显:
./pom.xml
./src/main/java/com/example/struts/example/ExampleSupport.java
./src/main/java/com/example/struts/example/HelloWorld.java
./src/main/java/com/example/struts/example/Login.java
./src/main/resources/com/example/struts/example/Login-validation.xml
./src/main/resources/com/example/struts/example/package.properties
./src/main/resources/com/example/struts/example/package_es.properties
./src/main/resources/example.xml
./src/main/resources/log4j2.xml
./src/main/resources/struts.xml
./src/main/webapp/WEB-INF/example/HelloWorld.jsp
./src/main/webapp/WEB-INF/example/Login.jsp
./src/main/webapp/WEB-INF/example/Menu.jsp
./src/main/webapp/WEB-INF/example/Missing.jsp
./src/main/webapp/WEB-INF/example/Register.jsp
./src/main/webapp/WEB-INF/example/Welcome.jsp
./src/main/webapp/WEB-INF/web.xml
./src/main/webapp/index.html
./src/test/java/com/example/struts/example/ConfigTest.java
./src/test/java/com/example/struts/example/HelloWorldTest.java
./src/test/java/com/example/struts/example/LoginTest.java
2.3 升级 Struts 版本到 6.12.0
archetype 生成的 pom 中 struts2.version 为 6.1.1,手工改为 6.12.0。修改后实测内容:
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<struts2.version>6.12.0</struts2.version>
<log4j2.version>2.19.0</log4j2.version>
</properties>
2.4 archetype 自带示例说明
自带示例的核心配置在 src/main/resources/example.xml(原样全文):
<struts>
<package name="example" namespace="/example" extends="struts-default">
<default-action-ref name="HelloWorld" />
<action name="HelloWorld" class="com.example.struts.example.HelloWorld">
<result>/WEB-INF/example/HelloWorld.jsp</result>
</action>
<action name="Login_*" method="{1}" class="com.example.struts.example.Login">
<result name="input">/WEB-INF/example/Login.jsp</result>
<result type="redirectAction">Menu</result>
</action>
<action name="*" class="com.example.struts.example.ExampleSupport">
<result>/WEB-INF/example/{1}.jsp</result>
</action>
</package>
</struts>
要点:
Login_*通配符:Login_input.action映射到Login.input()、Login_execute.action映射到execute();Login-validation.xml对 username/password 做 requiredstring 校验(校验失败回到input);*兜底动作把未知请求映射到同名 JSP(如 Menu → Menu.jsp)。
自带国际化资源 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.
对应西班牙语资源 package_es.properties(原样节选):
HelloWorld.message = \u00A1Struts est\u00E1 bien\!...
password = Contrase\u00F1a
username = Nombre de Usuario
自带 Action 与基类(节选):
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 ExampleSupport extends ActionSupport {
}
public class Login extends ExampleSupport {
public String execute() throws Exception {
if (isInvalid(getUsername())) return INPUT;
if (isInvalid(getPassword())) return INPUT;
return SUCCESS;
}
}
三、新增自定义 Action 与页面
3.1 HelloAction.java
路径:src/main/java/com/example/struts/example/HelloAction.java(全文):
package com.example.struts.example;
import com.opensymphony.xwork2.ActionSupport;
import java.util.ArrayList;
import java.util.List;
public class HelloAction extends ActionSupport {
private String name;
private List<String> features = new ArrayList<>();
@Override
public String execute() throws Exception {
if (name == null || name.trim().isEmpty()) {
name = "World";
}
features.add("Apache Struts 6.12.0");
features.add("Apache Tomcat 9.0.58");
features.add("OpenJDK 17.0.20.1");
return SUCCESS;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getFeatures() {
return features;
}
}
3.2 Hello.jsp
路径:src/main/webapp/WEB-INF/example/Hello.jsp(全文):
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
<title>Hello Struts</title>
</head>
<body>
<h2>Hello, <s:property value="name"/>! This page is rendered by Struts tags.</h2>
<h3>Environment features</h3>
<ul>
<s:iterator value="features">
<li><s:property/></li>
</s:iterator>
</ul>
</body>
</html>
3.3 UploadAction.java
路径:src/main/java/com/example/struts/example/UploadAction.java(全文):
package com.example.struts.example;
import com.opensymphony.xwork2.ActionSupport;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class UploadAction extends ActionSupport {
private File upload;
private String uploadContentType;
private String uploadFileName;
private String message;
@Override
public String execute() throws Exception {
if (upload == null) {
return INPUT;
}
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;
return SUCCESS;
}
public File getUpload() { return upload; }
public void setUpload(File upload) { this.upload = upload; }
public String getUploadContentType() { return uploadContentType; }
public void setUploadContentType(String uploadContentType) { this.uploadContentType = uploadContentType; }
public String getUploadFileName() { return uploadFileName; }
public void setUploadFileName(String uploadFileName) { this.uploadFileName = uploadFileName; }
public String getMessage() { return message; }
}
3.4 Upload.jsp
路径:src/main/webapp/WEB-INF/example/Upload.jsp(全文):
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head><title>Upload Demo</title></head>
<body>
<h2>Struts multipart upload demo</h2>
<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>
</body>
</html>
3.5 struts.xml 最终版
路径:src/main/resources/struts.xml(全文,实测最终版):
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"http://struts.apache.org/dtds/struts-2.5.dtd">
<struts>
<constant name="struts.enable.DynamicMethodInvocation" value="false"/>
<constant name="struts.devMode" value="true"/>
<constant name="struts.action.extension" value="do,action,"/>
<constant name="struts.multipart.maxSize" value="10485760"/>
<include file="example.xml"/>
<package name="default" namespace="/" extends="struts-default">
<default-action-ref name="index"/>
<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>
<action name="upload" class="com.example.struts.example.UploadAction">
<result name="success">/WEB-INF/example/Upload.jsp</result>
<result name="input">/WEB-INF/example/Upload.jsp</result>
</action>
<action name="goHello">
<result type="redirect">/hello.action?name=Redirected</result>
</action>
</package>
</struts>
常量说明:
struts.devMode=true:开发模式,便于排查(生产必须关闭);struts.action.extension=do,action,:同时支持.do、.action与无后缀(末尾空项含义与实测见《Apache Struts 配置参数详解》);struts.multipart.maxSize=10485760:上传上限 10MB(默认仅 2MB,第七节先按默认值实测超限,再调整到此值重新部署验证);struts.enable.DynamicMethodInvocation=false:关闭动态方法调用(Login!method语法),安全默认。
四、构建打包
4.1 首次打包报错与排障
cd /root/struts2-demo && mvn package
实测报错原文:
[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
⚠️ 排障结论:archetype 模板未固定 maven-war-plugin 版本,Maven 3.6.3 默认解析到古老的 2.2 版,其 API 与 JDK 17 不兼容(
Cannot access defaults field of Properties)。修复方式是在 pom.xml 的build/plugins首位显式固定新版 maven-war-plugin。
在 pom.xml 的 <build><plugins> 中加入:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.4.0</version>
</plugin>
4.2 修复后重新打包
mvn package
实测回显(测试与打包结果):
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.207 sec
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.11 sec
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.763 sec
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
五、部署到 Tomcat 9
5.1 方式一:复制 WAR 自动部署
cp /root/struts2-demo/target/struts2-demo-1.0.0.war /var/lib/tomcat9/webapps/struts2-demo.war
sleep 12 && ls /var/lib/tomcat9/webapps/
实测回显(war 已自动解压为同名目录):
ROOT
struts2-demo
struts2-demo.war
验证应用响应:
curl -s -o /dev/null -w 'HTTP %{http_code}\n' http://localhost:8080/struts2-demo/
实测回显:
HTTP 200
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.2 方式二:Manager 文本接口部署
先配置 Manager 账号,在 /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>
重启 Tomcat 并用文本接口查看应用列表:
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
卸载旧应用后再用 deploy 接口上传新 war(update=true):
curl -s -u admin:q1w2e3r4 'http://localhost:8080/manager/text/undeploy?path=/struts2-demo'
实测回显:
OK - Undeployed application at context path [/struts2-demo]
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
⚠️ 安全提示:manager 账号密码务必避免弱口令,生产环境还应通过网络策略或反向代理限制
/manager的访问来源。
六、页面功能验证
6.1 首页与跳转链
应用首页 index.html 通过 meta refresh 进入,index action 配置了 redirectAction 到 /example 命名空间的 HelloWorld。实测任意 .action 后缀的未匹配请求(如 foo.action)会被 default-action-ref index 兜底重定向:
curl -sI 'http://localhost:8080/struts2-demo/foo.action' | grep -i '^HTTP\|^Location'
实测回显:
HTTP/1.1 302
Location: /struts2-demo/example/HelloWorld.action
HelloWorld 页面渲染:
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>
6.2 自定义 hello(参数注入与标签渲染)
带 name 参数访问:
curl -s 'http://localhost:8080/struts2-demo/hello.action?name=Struts' | sed -n '1,20p'
实测回显:
<html>
<head>
<title>Hello Struts</title>
</head>
<body>
<h2>Hello, Struts! This page is rendered by Struts tags.</h2>
<h3>Environment features</h3>
<ul>
<li>Apache Struts 6.12.0</li>
<li>Apache Tomcat 9.0.58</li>
<li>OpenJDK 17.0.20.1</li>
</ul>
不带参数时 execute() 中兜底为 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.3 goHello 重定向
curl -sI 'http://localhost:8080/struts2-demo/goHello.action' | grep -i '^HTTP\|^Location'
实测回显:
HTTP/1.1 302
Location: /struts2-demo/hello.action?name=Redirected
6.4 登录与校验流程
访问登录表单(Login_input.action 经通配符映射到 input() 方法,s:form 标签自动生成表单 HTML):
curl -s 'http://localhost:8080/struts2-demo/example/Login_input.action' | grep -E '<form|<input|label' | head -8
实测回显:
<form id="Login" name="Login" action="/struts2-demo/example/Login.action;jsessionid=09C9821A0C08019E1C349EFF0B298FFE" method="post">
<td class="tdLabel"><label for="Login_username" class="label">User Name:</label></td>
><input type="text" name="username" value="" id="Login_username"/></td>
<td class="tdLabel"><label for="Login_password" class="label">Password:</label></td>
><input type="password" name="password" id="Login_password"/></td>
<td colspan="2"><div class="formButton"><input type="submit" value="Submit" id="Login_0"/>
空表单提交触发 requiredstring 校验,回到 input 页并显示错误:
curl -s -X POST 'http://localhost:8080/struts2-demo/example/Login_execute.action' | grep -E 'errorField|errorMessage|<form' | head -6
实测回显:
<form id="Login" name="Login" action="/struts2-demo/example/Login.action;jsessionid=B32202CFBDE532DA2D25BA410C134C7D" method="post">
<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>
提交合法账号(admin / q1w2e3r4)后校验通过,302 跳转 Menu:
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
Menu 页面(Menu.jsp 内嵌 Missing.jsp,i18n 文案渲染):
curl -s 'http://localhost:8080/struts2-demo/example/Menu.action' | head -20
实测回显:
<html>
<head><title>Missing Feature</title></head>
<body>
<p>
This feature is under construction. Please try again in the next iteration.
</p>
</body>
</html>
6.5 国际化切换
通过 request_locale=es 请求参数切换语言(I18nInterceptor 默认支持):
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>
切回默认英文:
curl -s 'http://localhost:8080/struts2-demo/example/HelloWorld.action' | grep -E '<h2>'
实测回显:
<h2>Struts is up and running...</h2>
6.6 动态方法调用(DMI)默认关闭
struts.enable.DynamicMethodInvocation=false 时,Login!method 感叹号语法直接 404:
curl -s -o /dev/null -w 'HTTP %{http_code}\n' 'http://localhost:8080/struts2-demo/example/Login!execute.action'
实测回显:
HTTP 404
七、文件上传实测
7.1 默认上限(2MB)下的行为
先按 struts.xml 未设置 struts.multipart.maxSize 的默认情况验证。构造 1MB 文件上传,成功保存到 /tmp:
dd if=/dev/zero of=/tmp/small.bin bs=1M count=1
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>
构造 3MB 文件上传,触发默认上限 2097152(2MB),action 捕获错误并回显:
dd if=/dev/zero of=/tmp/big.bin bs=1M count=3
curl -s -F 'upload=@/tmp/big.bin;type=application/octet-stream' 'http://localhost:8080/struts2-demo/upload.action' | grep -A2 'errorMessage'
实测回显:
<ul class="errorMessage">
<li><span>Request exceeded allowed size limit! Max size allowed is: 2,097,152 but request was: 3,145,929!</span></li> </ul>
7.2 提高上限到 10MB
在 struts.xml 加入 <constant name="struts.multipart.maxSize" value="10485760"/> 后重新 mvn package 并按第五节重新部署,3MB 上传成功(此处同时已启用 .do 后缀,用 upload.do 访问验证 extension 常量生效):
curl -s -F 'upload=@/tmp/big.bin;type=application/octet-stream' 'http://localhost:8080/struts2-demo/upload.do' | grep -E 'Saved'
实测回显:
<p>Saved big.bin (3145728 bytes, content-type=application/octet-stream) to /tmp/big.bin</p>
7.3 临时目录行为
未设置 struts.multipart.saveDir 时,日志会提示回退到 Web 应用临时目录:
2026-10-12 05:21:37,936 INFO [http-nio-8080-exec-9] dispatcher.Dispatcher (Dispatcher.java:1027) - Unable to find 'struts.multipart.saveDir' property setting. Defaulting to javax.servlet.context.tempdir
该提示为 INFO 级,不影响功能;如需固定上传临时目录,可在 struts.xml 中显式设置 struts.multipart.saveDir。
八、devMode 与日志排查
8.1 devMode 生效确认
struts.xml 设置 struts.devMode=true 后,服务日志会出现 devMode 告警(journalctl 查看):
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!
8.2 部署日志
Tomcat 自身日志(按日滚动)记录 war 部署过程:
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
8.3 日志文件位置
| 日志 | 路径 / 命令 | 内容 |
|---|---|---|
| Tomcat 运行日志 | /var/log/tomcat9/catalina.2026-10-12.log | 启动、部署、停机(按日滚动) |
| 应用级日志 | /var/log/tomcat9/localhost.2026-10-12.log | Web 应用异常 |
| 访问日志 | /var/log/tomcat9/localhost_access_log.2026-10-12.txt | HTTP 访问记录 |
| 服务单元日志 | journalctl -u tomcat9 | 应用 stdout 与 log4j2 输出(Ubuntu 的 tomcat9 无 catalina.out) |
九、内嵌 Jetty 运行(开发期免部署)
pom 已内置 jetty-maven-plugin(contextPath /struts2-demo),开发期无需部署 war,直接内嵌运行(指定 8081 端口避免与 Tomcat 冲突):
cd /root/struts2-demo && nohup mvn jetty:run -Djetty.http.port=8081 > /tmp/jetty.log 2>&1 &
首次运行需下载 Jetty 插件依赖,实测约 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
验证(注意 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>
十、常见问题
10.1 打包报 Cannot access defaults field of Properties
maven-war-plugin 2.2 与 JDK 17 不兼容,按 4.1 节在 pom 中固定 maven-war-plugin 3.4.0 后重新打包即可。判断依据:报错目标为 maven-war-plugin:2.2:war。
10.2 上传报 Request exceeded allowed size limit
默认上传上限为 2097152 字节(2MB),来自 struts2-core 内置默认值。按 7.2 节在 struts.xml 设置 struts.multipart.maxSize(如 10485760)后重新打包部署生效。
10.3 日志出现 Unable to find 'struts.multipart.saveDir'
INFO 级提示,表示未配置上传临时目录、已回退到 Web 应用临时目录,功能不受影响。需要固定目录时在 struts.xml 设置 struts.multipart.saveDir。
10.4 Action!method 形式的 URL 返回 404
Struts 6.x 默认关闭动态方法调用(struts.enable.DynamicMethodInvocation=false),感叹号语法被拒绝,属安全默认行为。如需按方法分发,使用通配符(如本示例的 Login_*)或 DMR 方式,不建议打开 DMI。
十一、清理与卸载
通过 Manager 卸载应用并删除产物(工程目录可保留供重复实验):
curl -s -u admin:q1w2e3r4 'http://localhost:8080/manager/text/undeploy?path=/struts2-demo'
rm -f /var/lib/tomcat9/webapps/struts2-demo.war
rm -rf /var/lib/tomcat9/webapps/struts2-demo
rm -rf /root/struts2-demo/target
停止内嵌 Jetty:
pkill -f 'jetty:run'
十二、参考链接
- Apache Struts 官网:https://struts.apache.org/
- Getting Started(官方入门示例):https://struts.apache.org/getting-started/
- struts2-archetype-blank 说明:https://struts.apache.org/maven-archetypes/
- 配置参考(常量速查):https://struts.apache.org/core-developers/configuration-files
- Tomcat 9 Manager App HOW-TO:https://tomcat.apache.org/tomcat-9.0-doc/manager-howto.html