如何编写Java代码在所有环境中正确获取Tomcat webapps路径
现有业务API需要在Tomcat的rules目录下创建新文件夹,将主规则目录下的文件复制到新目录中:例如创建名为abc的文件夹时,需要把tomcat/webapps/rules/masterRule路径下的规则文件复制到tomcat/webapps/rules/abc路径下。
原有路径获取逻辑如下:
Path path = Paths.get(System.getProperty("catalina.base") + File.separator + "webapps" + File.separator+ "rules" + File.separator + dc_name);
该逻辑在开发、测试环境运行正常,但生产环境完全失效:生产环境catalina.base对应路径/usr/local/tomcat下的webapps/rules是空目录,通过UI上传规则文件时,实际文件会写入EFS存储挂载的/efs-files/tomcat/webapp/rules路径下。
各环境实际规则存储路径汇总:
- 开发、测试环境路径:/opt/apache-tomcat-9.0.22/webapps/rules
- 生产环境
catalina.base默认对应路径(空目录,无业务文件):/usr/local/tomcat/webapps/rules - 生产环境UI上传文件实际存储路径:/efs-files/tomcat/webapp/rules
生产环境Tomcat做了外部存储挂载映射,UI上传逻辑没有使用Tomcat本地安装目录下的webapps路径,而是直接写入EFS共享存储路径。硬编码拼接catalina.base推导路径的逻辑,完全无法适配这种跨环境的部署架构差异,靠判断环境硬编码不同路径的方式也会给后续运维、环境迁移留下隐患。
方案一:标准Servlet上下文获取路径(优先推荐)
使用Java Web规范定义的ServletContext.getRealPath()方法获取Web应用的真实物理部署路径,该方法会自动适配所有路径映射、外部存储挂载的场景,不需要针对不同环境做特殊处理。
注意:该方法需要在Web应用上下文完全初始化后调用,不要在静态代码块、Servlet初始化前置阶段执行。
import javax.servlet.ServletContext; import java.nio.file.Path; import java.nio.file.Paths; // Spring环境可直接通过@Autowired注入ServletContext,原生Servlet环境可从request、ServletConfig对象中获取 public class RulePathUtil { private final ServletContext servletContext; // 构造注入ServletContext public RulePathUtil(ServletContext servletContext) { this.servletContext = servletContext; } /** * 获取规则目录下指定dc的目标路径 * @param dcName 目标dc名称 * @return 目标路径 */ public Path getTargetRulePath(String dcName) { // 获取Web应用根目录的真实物理路径,自动适配所有部署映射 String webappRealRoot = servletContext.getRealPath("/"); // 直接通过Paths.get多参数重载拼接路径,自动适配操作系统文件分隔符 return Paths.get(webappRealRoot, "rules", dcName); } /** * 获取主规则目录路径 * @return 主规则目录路径 */ public Path getMasterRulePath() { String webappRealRoot = servletContext.getRealPath("/"); return Paths.get(webappRealRoot, "rules", "masterRule"); } }
方案二:外部配置路径(架构限制无法获取ServletContext时的兜底方案)
将规则存储根路径作为可配置项,从配置文件、环境变量中读取,各环境部署时单独配置对应路径,不需要在代码中硬编码路径值。
以Spring场景为例:
- 各环境配置文件中添加配置项
- 开发、测试环境配置:
rule.storage.root=/opt/apache-tomcat-9.0.22/webapps/rules - 生产环境配置:
rule.storage.root=/efs-files/tomcat/webapp/rules
- 开发、测试环境配置:
- 代码实现
import java.nio.file.Path; import java.nio.file.Paths; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component public class RulePathService { // 读取配置的规则根路径,未配置时默认使用原有catalina.base拼接逻辑做向下兼容 @Value("${rule.storage.root:${catalina.base}/webapps/rules}") private String ruleStorageRoot; public Path getTargetRulePath(String dcName) { return Paths.get(ruleStorageRoot, dcName); } public Path getMasterRulePath() { return Paths.get(ruleStorageRoot, "masterRule"); } }
- 禁止通过判断环境标识硬编码切换路径,后续新增环境、调整存储挂载点时需要修改代码重新发版,维护成本极高
- 路径拼接统一使用
Paths.get()的多参数重载方法,不要手动拼接文件分隔符,避免Windows/Linux系统分隔符差异引发的路径错误 - 拿到路径后先做目录存在性校验,目录不存在时自动创建,避免文件复制操作抛出路径不存在异常
内容的提问来源于stack exchange,提问作者Ritwik1711

