在 Java 中获取 Tomcat 的端口,最常用和最可靠的方法是通过 Tomcat 内置的 API,下面我将详细介绍几种方法,从推荐到不推荐,并解释其原理和适用场景。

使用 org.apache.catalina.Server (最推荐)
这是最标准、最可靠的方法,因为它直接与 Tomcat 的核心组件交互,适用于任何将 Tomcat 作为嵌入式服务器或独立服务器运行的情况。
原理:
Tomcat 的顶层组件是 Server,它包含一个或多个 Service,每个 Service 有一个或多个 Connector,而 Connector 就是负责处理网络连接(包括监听端口)的组件,通过获取 Server 实例,我们可以遍历其 Connector 来获取所有配置的端口。
代码示例:
这个方法在 Servlet 3.0+ 的环境中(Spring Boot, Spring MVC, 普通 WAR 部署)都适用。

import org.apache.catalina.Server;
import org.apache.catalina.Service;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.startup.Tomcat;
import javax.servlet.ServletContext;
import java.util.Arrays;
public class TomcatPortUtils {
/**
* 获取 Tomcat 的所有端口
* @return 端口数组
*/
public static int[] getTomcatPorts() {
// 方法1: 从 ServletContext 获取 (适用于 Web 应用)
Server server = getServerFromServletContext();
if (server != null) {
return getPortsFromServer(server);
}
// 方法2: 如果是嵌入式 Tomcat,可以直接传入 Tomcat 实例
// (例如在 Spring Boot 的 TomcatServletWebServerApplicationContext 中)
// Tomcat tomcat = getTomcatInstance(); // 你需要有自己的方式获取
// if (tomcat != null) {
// return getPortsFromServer(tomcat.getServer());
// }
// 如果都获取不到,返回空数组
return new int[0];
}
/**
* 从 ServletContext 获取 Tomcat 的 Server 实例
*/
private static Server getServerFromServletContext() {
try {
// 在 Tomcat 中,ServletContext 的getAttribute("org.apache.catalina.Server") 可以直接获取 Server 实例
ServletContext servletContext = getServletContext(); // 你需要有自己的方式获取 ServletContext
if (servletContext != null) {
return (Server) servletContext.getAttribute("org.apache.catalina.Server");
}
} catch (Exception e) {
// 忽略异常,可能不是 Tomcat 或 ServletContext 不可用
e.printStackTrace();
}
return null;
}
/**
* 从 Server 实例中解析出所有端口
*/
private static int[] getPortsFromServer(Server server) {
if (server == null) {
return new int[0];
}
return server.findServices().stream()
.flatMap(service -> Arrays.stream(service.findConnectors()))
.map(Connector::getPort)
.mapToInt(Integer::intValue)
.toArray();
}
// --- 以下是辅助方法,用于演示 ---
// 在实际应用中,你需要通过你的框架(如 Spring)来获取这些对象
private static ServletContext getServletContext() {
// 示例:在 Spring MVC Controller 中,可以直接注入或从 Request 获取
// return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getServletContext();
// 示例:在静态工具类中,可以通过 Spring 的 ApplicationContext 获取
// WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();
// return context.getServletContext();
System.out.println("请在你的应用中替换此方法以获取真实的 ServletContext");
return null;
}
public static void main(String[] args) {
// 模拟一个 Tomcat 环境
Tomcat tomcat = new Tomcat();
tomcat.setPort(8080);
tomcat.getConnector(); // 默认创建一个 AJP/1.3 Connector 在端口 8009
int[] ports = getPortsFromServer(tomcat.getServer());
System.out.println("Tomcat 端口: " + Arrays.toString(ports)); // 输出: [8080, 8009]
}
}
适用场景:
- Spring Boot 应用
- Spring MVC (WAR 部署) 应用
- 任何使用 Tomcat 作为嵌入式服务器的应用
- 任何部署在 Tomcat 上的传统 Web 应用
使用 org.apache.coyote.ajp.AjpProcessor (仅限 AJP 协议)
如果你的应用只关心 Tomcat 的 AJP 端口(通常用于与 Apache Nginx 等前端服务器集成),并且你确定 Tomcat 正在使用 AJP 协议,这个方法也可以工作。
原理:
AJP 协议的处理器 AjpProcessor 中包含了它所监听的 endpoint,而 endpoint 知道端口号。
代码示例:

import org.apache.coyote.ajp.AjpProcessor;
import org.apache.coyote.ajp.AbstractAjpProtocol;
import org.apache.coyote.ajp.AjpMessage;
import org.apache.coyote.RequestInfo;
import javax.servlet.ServletContext;
import java.lang.reflect.Field;
public class AjpPortFinder {
public static int getAjpPort(ServletContext servletContext) {
try {
// 从 ServletContext 获取 CoyoteAdapter
Object coyoteAdapter = servletContext.getAttribute("org.apache.catalina.adapter CoyoteAdapter");
if (coyoteAdapter == null) {
// 尝试另一种方式获取
coyoteAdapter = servletContext.getAttribute("org.apache.catalina.core.StandardContext").getConnector().getCoyote();
}
// 从 CoyoteAdapter 获取 Processor (通过反射)
Field processorField = coyoteAdapter.getClass().getDeclaredField("processor");
processorField.setAccessible(true);
AjpProcessor processor = (AjpProcessor) processorField.get(coyoteAdapter);
// 从 Processor 获取 Protocol (通过反射)
Field protocolField = processor.getClass().getDeclaredField("protocol");
protocolField.setAccessible(true);
AbstractAjpProtocol<?> protocol = (AbstractAjpProtocol<?>) protocolField.get(processor);
// 从 Protocol 获取端口号
return protocol.getEndpoint().getPort();
} catch (Exception e) {
e.printStackTrace();
return -1; // 表示获取失败
}
}
// public static void main(String[] args) {
// // 你需要获取真实的 ServletContext 实例
// // ServletContext context = ...;
// // int ajpPort = getAjpPort(context);
// // System.out.println("AJP Port: " + ajpPort);
// }
}
缺点:
- 不通用:只适用于 AJP 协议,如果你的应用只使用 HTTP/HTTPS,此方法无效。
- 依赖反射:使用了大量反射,代码脆弱,一旦 Tomcat 内部类结构发生变化,代码就会崩溃。
- 获取复杂:需要从
ServletContext开始,通过多个内部类才能最终拿到端口。
解析 server.xml (不推荐)
理论上,你可以读取 Tomcat 的配置文件 conf/server.xml 并用 XML 解析器来查找 <Connector> 元素的 port 属性。
为什么不推荐?
- 硬编码路径:
server.xml的路径是固定的($CATALINA_HOME/conf/server.xml),但在不同的部署环境中(如 Docker、云服务器)可能难以确定。 - 权限问题:应用可能没有读取服务器文件系统的权限。
- 动态配置:Tomcat 的端口可以通过启动命令行参数(如
--port=9090)或环境变量来覆盖,server.xml中的配置是无效的,但此方法会读取到错误的值。 - 性能开销:读取和解析文件比内存中查询对象要慢。
代码示例 (仅作演示):
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
public class ServerXmlParser {
public static int[] getPortsFromServerXml(String tomcatHome) {
try {
File serverXmlFile = new File(tomcatHome, "conf/server.xml");
if (!serverXmlFile.exists()) {
System.err.println("server.xml not found at: " + serverXmlFile.getAbsolutePath());
return new int[0];
}
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(serverXmlFile);
NodeList connectorNodes = document.getElementsByTagName("Connector");
int[] ports = new int[connectorNodes.getLength()];
for (int i = 0; i < connectorNodes.getLength(); i++) {
Node node = connectorNodes.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
String portStr = element.getAttribute("port");
if (!portStr.isEmpty()) {
ports[i] = Integer.parseInt(portStr);
}
}
}
return ports;
} catch (Exception e) {
e.printStackTrace();
return new int[0];
}
}
// public static void main(String[] args) {
// // 需要传入 Tomcat 的安装目录
// String catalinaHome = System.getenv("CATALINA_HOME");
// if (catalinaHome == null) {
// System.out.println("Please set CATALINA_HOME environment variable.");
// return;
// }
// int[] ports = getPortsFromServerXml(catalinaHome);
// System.out.println("Ports from server.xml: " + Arrays.toString(ports));
// }
}
解析启动参数 (有限制)
当 Tomcat 启动时,它会将监听端口等信息作为参数传递给 JVM,你可以通过 sun.management.VMManagement 的 API 来读取这些启动参数。
原理:
Tomcat 的 Catalina 启动类会解析命令行参数,并将监听端口等设置到 SystemProperty 中。
代码示例:
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.util.List;
public class StartupArgParser {
public static int getPortFromStartupArgs() {
RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
List<String> arguments = runtimeMxBean.getInputArguments();
for (String arg : arguments) {
if (arg.startsWith("-Dport=")) {
return Integer.parseInt(arg.substring(7));
}
// Tomcat 的标准参数是 --port, 但通常会被转换为 -Dport
if (arg.startsWith("--port=")) {
return Integer.parseInt(arg.substring(7));
}
}
return -1; // 未找到
}
// public static void main(String[] args) {
// // 需要以类似下面的方式启动 Tomcat 才能生效
// // catalina.sh run -Dport=9090
// int port = getPortFromStartupArgs();
// System.out.println("Port from startup args: " + port);
// }
}
缺点:
- 非常有限:此方法高度依赖于 Tomcat 的启动方式,在大多数应用服务器(如 JBoss/WildFly)或 Spring Boot 内嵌 Tomcat 的情况下,启动参数可能不会以这种方式设置。
- 不可靠:不能保证一定能获取到端口信息。
总结与推荐
| 方法 | 优点 | 缺点 | 推荐度 |
|---|---|---|---|
org.apache.catalina.Server |
最标准、最可靠、最通用,直接与 Tomcat 内核交互,不依赖文件或启动参数。 | 需要获取 ServletContext 或 Tomcat 实例。 |
⭐⭐⭐⭐⭐ (首选) |
AjpProcessor (反射) |
可以获取 AJP 端口。 | 不通用(仅 AJP)、依赖反射、代码脆弱、获取复杂。 | ⭐☆☆☆☆ (仅在特定场景下考虑) |
解析 server.xml |
思路简单。 | 路径硬编码、权限问题、无法获取动态端口、性能差。 | ⭐☆☆☆☆ (强烈不推荐) |
| 解析启动参数 | 可以获取命令行指定的端口。 | 非常有限、不可靠、依赖特定启动方式。 | ⭐☆☆☆☆ (基本不适用) |
最终结论:
请始终优先使用方法一 (org.apache.catalina.Server)。 它是获取 Tomcat 端口最健壮、最正确的方式,在你的 Java 应用(特别是基于 Spring 的应用)中,通过 ServletContext 或应用上下文(ApplicationContext)获取到 Server 实例,然后从中提取端口信息,这是最佳实践。
