CentOS 8 安装 Apache Wicket 指南

CentOS 8 从零集成 Apache Wicket:安装 OpenJDK 17 与 Maven,引入 wicket-core 打包 war,部署 Tomcat 10.1 运行,用 curl 验证页面、表单与登录,含堆设置与卸载。

Apache WicketApache Wicket2026/10/12

CentOS 8 安装 Apache Wicket 指南

Apache Wicket 是组件化的 Java Web 应用框架,以库的形式通过 Maven 引入工程,随 war 包一起部署到 Servlet 5 及以上兼容的运行环境中。本文以 CentOS 8 为例,完成从安装 JDK、Maven,到创建工程、引入 wicket-core 依赖、编写最小应用、打包 war、部署 Tomcat 并验证访问的完整流程,所有命令与回显均来自真实服务器实测。

一、环境信息

项目值
操作系统CentOS Linux 8(8.5.2111 软件源)
内核6.8.0-138-generic
JDKOpenJDK 17.0.1+12-LTS(Red Hat)
Maven3.9.16(官方二进制包)
Apache Wicket10.11.0(wicket-core,Maven Central 当前稳定版)
Tomcat10.1.61(官方二进制包)
应用上下文路径/wicket-demo
监听端口8080

二、前置条件

  1. 拥有 root 权限或可执行 sudo 的账号。
  2. 服务器可访问公网 Maven 仓库(repo.maven.apache.org 或 repo1.maven.org)与 Apache 官方下载站(dlcdn.apache.org)。
  3. 确认 8080 端口未被占用:
ss -lntp | grep 8080

无输出即表示端口空闲。

  1. CentOS 8 官方源已停止维护,需确认软件源指向可用的归档源后执行:
dnf makecache

三、版本选型说明

  1. Wicket 10.x 要求 Java 17 及以上、Jakarta Servlet 5.0 及以上,因此 JDK 选 17,Servlet 运行环境选 Tomcat 10.1(Tomcat 9 及以下基于 javax 命名空间,无法运行 Wicket 10 应用)。
  2. wicket-core 版本以 Maven Central 元数据为准,查询命令与实测回显:
curl -s https://repo1.maven.org/maven2/org/apache/wicket/wicket-core/maven-metadata.xml | grep -E "<latest>|<release>"

实测回显:

    <latest>10.11.0</latest>
    <release>10.11.0</release>
  1. CentOS 8 归档源中的 maven 包版本较旧且对 JDK 17 兼容性差,因此 Maven 采用 Apache 官方二进制包安装。

四、部署步骤

4.1 安装 OpenJDK 17

dnf install -y java-17-openjdk-devel curl

实测回显(结尾部分):

  xorg-x11-fonts-Type1-7.5-19.el8.noarch

Complete!

验证:

java -version

实测回显:

openjdk version "17.0.1" 2021-10-19 LTS
OpenJDK Runtime Environment 21.9 (build 17.0.1+12-LTS)
OpenJDK 64-Bit Server VM 21.9 (build 17.0.1+12-LTS, mixed mode, sharing)

4.2 安装 Maven 3.9.16

下载并解压官方二进制包,同时创建软链接便于全局调用:

curl -sL -o /tmp/maven.tgz https://dlcdn.apache.org/maven/maven-3/3.9.16/binaries/apache-maven-3.9.16-bin.tar.gz
tar -xzf /tmp/maven.tgz -C /opt/
ln -s /opt/apache-maven-3.9.16/bin/mvn /usr/local/bin/mvn

验证:

mvn -version

实测回显:

Apache Maven 3.9.16 (2bdd9fddda4b155ebf8000e807eb73fd829a51d5)
Maven home: /opt/apache-maven-3.9.16
Java version: 17.0.1, vendor: Red Hat, Inc., runtime: /usr/lib/jvm/java-17-openjdk-17.0.1.0.12-2.el8_5.x86_64
Default locale: en_US, platform encoding: ANSI_X3.4-1968

4.3 创建 Wicket 工程

使用官方 quickstart 脚手架生成工程骨架:

mkdir -p /root/lab && cd /root/lab
mvn archetype:generate -DarchetypeGroupId=org.apache.wicket -DarchetypeArtifactId=wicket-archetype-quickstart -DarchetypeVersion=10.11.0 -DgroupId=com.example -DartifactId=wicket-demo -Dversion=1.0.0 -DinteractiveMode=false

实测回显(节选,BUILD SUCCESS 表示成功):

[INFO] --- maven-archetype-plugin:3.4.1:generate (default-cli) @ standalone-pom ---
[INFO] Generating project in Batch mode
[INFO] Archetype repository not defined. Using the one from [org.apache.wicket:wicket-archetype-quickstart:10.11.0] found in catalog remote
[INFO] Using following parameters for creating project from Archetype: wicket-archetype-quickstart:10.11.0
[INFO] Project created from Archetype in dir: /root/lab/wicket-demo
[INFO] BUILD SUCCESS
ls wicket-demo

实测回显:

pom.xml
src

4.4 引入 wicket-core 依赖

将 pom.xml 的依赖与构建部分调整为以下内容(编码统一 UTF-8,固定 war 插件版本避免与 JDK 17 不兼容):

<properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
    <dependency>
        <groupId>org.apache.wicket</groupId>
        <artifactId>wicket-core</artifactId>
        <version>10.11.0</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <version>2.0.17</version>
    </dependency>
</dependencies>

<build>
    <finalName>wicket-demo</finalName>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
        </resource>
        <resource>
            <directory>src/main/java</directory>
            <excludes>
                <exclude>**/*.java</exclude>
            </excludes>
        </resource>
    </resources>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-war-plugin</artifactId>
            <version>3.5.1</version>
        </plugin>
    </plugins>
</build>

4.5 编写最小应用

在 src/main/java/com/example 下创建四个类。

WicketApplication.java(应用入口,声明首页、自定义会话、挂载 /login 路由):

package com.example;

import org.apache.wicket.Page;
import org.apache.wicket.Session;
import org.apache.wicket.protocol.http.WebApplication;
import org.apache.wicket.request.Request;
import org.apache.wicket.request.Response;

public class WicketApplication extends WebApplication {

    @Override
    public Class<? extends Page> getHomePage() {
        return HomePage.class;
    }

    @Override
    public Session newSession(Request request, Response response) {
        return new DemoSession(request);
    }

    @Override
    protected void init() {
        super.init();
        getMarkupSettings().setDefaultMarkupEncoding("UTF-8");
        getRequestCycleSettings().setResponseRequestEncoding("UTF-8");
        mountPage("/login", LoginPage.class);
    }
}

DemoSession.java(保存登录用户):

package com.example;

import org.apache.wicket.protocol.http.WebSession;
import org.apache.wicket.request.Request;

public class DemoSession extends WebSession {

    private static final long serialVersionUID = 1L;

    private String user;

    public DemoSession(Request request) {
        super(request);
    }

    public static DemoSession get() {
        return (DemoSession) WebSession.get();
    }

    public String getUser() {
        return user;
    }

    public void setUser(String user) {
        this.user = user;
    }
}

HomePage.java(首页,包含 Label、Link、Form 三类组件):

package com.example;

import java.text.MessageFormat;

import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;

public class HomePage extends WebPage {

    private static final long serialVersionUID = 1L;

    public HomePage() {
        String user = DemoSession.get().getUser();
        if (user == null) {
            user = getString("home.guest");
        }
        add(new Label("greeting", MessageFormat.format(getString("home.greeting"), user)));
        add(new Label("mode", getApplication().getConfigurationType().name()));

        add(new FeedbackPanel("feedback"));

        IModel<String> nameModel = Model.of("");
        Form<Void> echoForm = new Form<Void>("echoForm") {

            private static final long serialVersionUID = 1L;

            @Override
            protected void onSubmit() {
                info(MessageFormat.format(getString("home.echo"), nameModel.getObject()));
            }
        };
        echoForm.add(new TextField<String>("name", nameModel).setRequired(true));
        add(echoForm);

        add(new Link<Void>("loginLink") {

            private static final long serialVersionUID = 1L;

            @Override
            public void onClick() {
                setResponsePage(LoginPage.class);
            }
        });
    }
}

LoginPage.java(登录页,校验账号 admin、密码 q1w2e3r4):

package com.example;

import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.PasswordTextField;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
import org.apache.wicket.model.Model;

public class LoginPage extends WebPage {

    private static final long serialVersionUID = 1L;

    public LoginPage() {
        add(new FeedbackPanel("feedback"));
        add(new LoginForm("loginForm"));
    }

    private static final class LoginForm extends Form<Void> {

        private static final long serialVersionUID = 1L;

        private final TextField<String> username;
        private final PasswordTextField password;

        LoginForm(String id) {
            super(id);
            username = new TextField<String>("username", Model.of(""));
            password = new PasswordTextField("password", Model.of(""));
            add(username, password);
        }

        @Override
        protected void onSubmit() {
            if ("admin".equals(username.getModelObject())
                    && "q1w2e3r4".equals(password.getModelObject())) {
                DemoSession.get().setUser(username.getModelObject());
                setResponsePage(getApplication().getHomePage());
            } else {
                error(getString("login.failed"));
            }
        }
    }
}

在 src/main/resources/com/example 下创建两个 HTML 模板(与组件类同包同名)。HomePage.html:

<!DOCTYPE html>
<html xmlns:wicket="http://wicket.apache.org">
<head>
    <meta charset="utf-8"/>
    <title>Wicket Demo</title>
</head>
<body>
    <h1 wicket:id="greeting">greeting goes here</h1>
    <p>Runtime mode: <span wicket:id="mode">mode</span></p>
    <ul wicket:id="feedback">
        <li>feedback</li>
    </ul>
    <form wicket:id="echoForm">
        <input type="text" wicket:id="name" placeholder="your name"/>
        <input type="submit" value="Submit"/>
    </form>
    <a href="#" wicket:id="loginLink">Sign in</a>
</body>
</html>

LoginPage.html:

<!DOCTYPE html>
<html xmlns:wicket="http://wicket.apache.org">
<head>
    <meta charset="utf-8"/>
    <title>Wicket Demo Login</title>
</head>
<body>
    <h1>Sign in</h1>
    <ul wicket:id="feedback">
        <li>feedback</li>
    </ul>
    <form wicket:id="loginForm">
        <input type="text" wicket:id="username" placeholder="username"/>
        <input type="password" wicket:id="password" placeholder="password"/>
        <input type="submit" value="Login"/>
    </form>
</body>
</html>

本地化资源文件(中文内容使用 unicode 转义,避免编码问题)。HomePage.properties(默认英文):

home.greeting=Welcome, {0}!
home.guest=guest
home.echo=Hello, {0}. The form has been submitted.

HomePage_zh_CN.properties(中文,按 native2ascii 惯例转义):

home.greeting=\u6b22\u8fce\u56de\u6765\uff0c{0}\uff01
home.guest=\u8bbf\u5ba2
home.echo=\u4f60\u597d\uff0c{0}\uff0c\u8868\u5355\u5df2\u63d0\u4ea4\u3002

LoginPage.properties:

login.failed=Wrong username or password.

LoginPage_zh_CN.properties:

login.failed=\u7528\u6237\u540d\u6216\u5bc6\u7801\u9519\u8bef\u3002

最后创建 src/main/webapp/WEB-INF/web.xml(Jakarta Servlet 5.0 规范,deployment 模式):

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
                             https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0">
    <display-name>wicket-demo</display-name>
    <context-param>
        <param-name>configuration</param-name>
        <param-value>deployment</param-value>
    </context-param>
    <filter>
        <filter-name>wicket-demo</filter-name>
        <filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class>
        <init-param>
            <param-name>applicationClassName</param-name>
            <param-value>com.example.WicketApplication</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>wicket-demo</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>ERROR</dispatcher>
    </filter-mapping>
</web-app>

4.6 编译打包 war

cd /root/lab/wicket-demo && mvn package

实测回显(结尾部分):

[INFO] Building war: /root/lab/wicket-demo/target/wicket-demo.war
[INFO] WEB-INF/web.xml already added, skipping
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

确认产物:

ls -la target/wicket-demo.war

实测回显:

-rw-r--r-- 1 root root 6723319 Oct 12 09:48 target/wicket-demo.war

4.7 安装并配置 Tomcat 10.1

curl -sL -o /tmp/tomcat.tgz https://dlcdn.apache.org/tomcat/tomcat-10/v10.1.61/bin/apache-tomcat-10.1.61.tar.gz
tar -xzf /tmp/tomcat.tgz -C /opt/ && ln -s /opt/apache-tomcat-10.1.61 /opt/tomcat && chmod +x /opt/tomcat/bin/*.sh

验证版本:

/opt/tomcat/bin/version.sh | head -4

实测回显:

Server version: Apache Tomcat/10.1.61
Server built:   Oct 6 2026 22:56:25 UTC
Server number:  10.1.61.0
OS Name:        Linux

设置 JVM 堆内存(256 至 512MB 区间),创建 bin/setenv.sh:

printf "CATALINA_OPTS=\"-Xms256m -Xmx512m -Dfile.encoding=UTF-8\"\n" > /opt/tomcat/bin/setenv.sh
cat /opt/tomcat/bin/setenv.sh

实测回显:

CATALINA_OPTS="-Xms256m -Xmx512m -Dfile.encoding=UTF-8"

4.8 部署与启动

将 war 复制到部署目录,随后启动:

cp /root/lab/wicket-demo/target/wicket-demo.war /opt/tomcat/webapps/
/opt/tomcat/bin/startup.sh

实测回显:

Tomcat started.

确认部署完成:

ls /opt/tomcat/webapps/

实测回显:

ROOT  docs  examples  host-manager  manager  wicket-demo  wicket-demo.war

catalina.out 中的启动日志:

12-Oct-2026 09:48:47.205 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8080"]
12-Oct-2026 09:48:47.218 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [1174] milliseconds

4.9 验证页面与表单

访问首页(Wicket 默认渲染策略首跳返回 302,用 -L 跟随重定向):

rm -f /tmp/ck.txt && curl -s -c /tmp/ck.txt -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8080/wicket-demo/

实测回显:

302
curl -s -b /tmp/ck.txt -L http://127.0.0.1:8080/wicket-demo/ | grep -o "<h1[^>]*>[^<]*"

实测回显:

<h1>Welcome, guest!

确认运行模式为 DEPLOYMENT(来自 web.xml 的 context-param):

curl -s -b /tmp/ck.txt -L http://127.0.0.1:8080/wicket-demo/ | grep -o "DEPLOYMENT\|DEVELOPMENT"

实测回显:

DEPLOYMENT

表单提交往返(输入框渲染出的参数名以页面实际 HTML 为准):

curl -s -b /tmp/ck.txt -L http://127.0.0.1:8080/wicket-demo/ -o /tmp/t1.html
A=$(grep -o "action=\"[^\"]*\"" /tmp/t1.html | head -1 | sed "s/action=\"//;s/\"//") && curl -s -b /tmp/ck.txt -L --data-urlencode "p::name=Li Si" "http://127.0.0.1:8080/wicket-demo/${A#./}" | grep -o "Hello[^<]*"

实测回显:

Hello, Li Si. The form has been submitted.

登录往返(账号 admin,密码 q1w2e3r4):

curl -s -b /tmp/ck.txt -L http://127.0.0.1:8080/wicket-demo/login -o /tmp/t2.html
B=$(grep -o "action=\"[^\"]*\"" /tmp/t2.html | head -1 | sed "s/action=\"//;s/\"//") && curl -s -b /tmp/ck.txt -L --data-urlencode "username=admin" --data-urlencode "password=q1w2e3r4" "http://127.0.0.1:8080/wicket-demo/login${B#./login}" | grep -o "Welcome[^<]*" | head -1

实测回显:

Welcome, admin!

中文本地化验证(新会话并携带 Accept-Language 头):

rm -f /tmp/ckzh.txt && curl -s -c /tmp/ckzh.txt -o /dev/null -H "Accept-Language: zh-CN" http://127.0.0.1:8080/wicket-demo/
curl -s -b /tmp/ckzh.txt -L -H "Accept-Language: zh-CN" http://127.0.0.1:8080/wicket-demo/ | grep -o "<h1[^>]*>[^<]*"

实测回显:

<h1>欢迎回来,访客!

4.10 JVM 堆设置与验证

堆内存通过 bin/setenv.sh 中的 CATALINA_OPTS 传递(256MB 至 512MB),修改后重启 Tomcat 生效。用 jps -v 验证实际生效的参数:

jps -v | grep -v Jps

实测回显(节选):

677 Bootstrap -Djava.util.logging.config.file=/opt/tomcat/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djdk.tls.ephemeralDHKeySize=2048 -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -Dsun.io.useCanonCaches=false -Dorg.apache.catalina.security.SecurityListener.UMASK=0027 --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.rmi/sun.rmi.transport=ALL-UNNAMED -Xms256m -Xmx512m -Dfile.encoding=UTF-8 -Dcatalina.base=/opt/tomcat -Dcatalina.home=/opt/tomcat -Djava.io.tmpdir=/opt/tomcat/temp

ps -ef 同样可确认:

ps -ef | grep "Xmx512m" | grep -v grep | head -1

实测回显(节选):

/usr/bin/java -Djava.util.logging.config.file=/opt/tomcat/conf/logging.properties -Xms256m -Xmx512m -Dfile.encoding=UTF-8

五、配置说明

  1. 运行模式:web.xml 的 context-param configuration 控制 DEVELOPMENT 或 DEPLOYMENT,生产环境必须为 deployment,否则启动日志与页面会出现 DEVELOPMENT 警告且保留调试标记。
  2. 应用入口:WicketFilter 的 init-param applicationClassName 指向 WicketApplication 全限定名,改名后需同步修改。
  3. 上下文路径:默认取 war 文件名 wicket-demo,如需根路径访问可将文件重命名为 ROOT.war。
  4. 堆内存:修改 bin/setenv.sh 后执行 shutdown.sh 再执行 startup.sh 重新生效。
  5. 会话超时:在 web.xml 中追加 session-config 节点即可,例如将超时调整为 30 分钟:
<session-config>
    <session-timeout>30</session-timeout>
</session-config>

六、注意事项

  1. 不要使用 Tomcat 9 及以下版本部署 Wicket 10 应用,启动时会因 Servlet API 命名空间不匹配而失败。
  2. pom.xml 必须显式固定 maven-war-plugin 3.x 版本,否则在 JDK 17 下打包会报 Cannot access defaults field of Properties 错误。
  3. Wicket 默认渲染策略为 REDIRECT_TO_RENDER,首次 GET 与表单提交后均返回 302,脚本化验证必须用 curl 的 -L 跟随。
  4. 本地化资源文件默认按 ISO-8859-1 读取,中文内容务必采用 unicode 转义写法,直接写 UTF-8 中文会出现乱码。
  5. JVM 堆保持在 256 至 512MB 区间,应用较小不需要更大堆;调整后务必用 jps -v 复核。

七、常见问题

Q1:访问首页返回 302 而不是 200,是否异常?

正常。Wicket 默认采用 REDIRECT_TO_RENDER 策略,先 302 再渲染页面,浏览器与 curl -L 均能正常拿到 200 的最终页面。

Q2:表单提交后提示参数必填或无响应效果?

Wicket 10 会为表单输入生成带前缀的参数名(实测中首页回显输入框的参数名为 p::name),脚本化提交时必须以页面渲染出的 name 属性为准,可用 grep 从返回的 HTML 中提取。

Q3:登录成功后再次访问又变回未登录状态?

会话基于 Cookie 中的 JSESSIONID,curl 每次调用需使用同一份 cookie 文件(-c 保存、-b 携带);另外确认 web.xml 的 session-timeout 未设置过小。

Q4:启动 Tomcat 后 404?

查看 logs/catalina.out 是否有部署错误;确认 war 内 WEB-INF/web.xml 使用 jakarta 命名空间(web-app_5_0.xsd),applicationClassName 与实际包名类名一致。

八、卸载方式

停止 Tomcat:

/opt/tomcat/bin/shutdown.sh

移除应用与 Tomcat:

rm -rf /opt/tomcat/webapps/wicket-demo /opt/tomcat/webapps/wicket-demo.war
rm -rf /opt/tomcat /opt/apache-tomcat-10.1.61

移除 Maven:

rm -rf /opt/apache-maven-3.9.16 /usr/local/bin/mvn

移除 JDK 与本地仓库(如不再需要):

dnf remove -y java-17-openjdk-devel
rm -rf /root/.m2 /root/lab

九、参考链接

  1. Apache Wicket 官网:https://wicket.apache.org
  2. Wicket 快速开始:https://wicket.apache.org/start/quickstart.html
  3. Maven Central 上的 wicket-core:https://repo1.maven.org/maven2/org/apache/wicket/wicket-core/
  4. Apache Tomcat 10.1 文档:https://tomcat.apache.org/tomcat-10.1-doc/
  5. Maven 官方文档:https://maven.apache.org/guides/index.html