
1. 问题现象与根源剖析如果你在启动 Tomcat 服务器时控制台日志里反复出现类似“At least one JAR was scanned for TLDs yet contained no TLDs”的警告信息并且后面跟着一长串 JAR 文件路径那么你肯定对这条“刷屏”的日志不陌生。它本身不会导致应用崩溃但每次启动都输出几十甚至上百行不仅污染了日志让排查真正的问题变得困难还会给人一种“项目不干净”的直觉对于有强迫症的开发者来说更是难以忍受。这个警告的本质是 Tomcat 在启动过程中执行的一项名为“TLD扫描”的检查工作出了点“小误会”。TLD 全称是 Tag Library Descriptor即标签库描述符它是 JSP 自定义标签的“说明书”告诉 Tomcat 这个标签怎么用、有哪些属性。为了支持 JSP 页面Tomcat 需要在启动时找到所有可能包含 TLD 文件的 JAR 包并读取其中的信息进行缓存这样在运行 JSP 时才能正确识别和处理自定义标签。那么Tomcat 怎么知道去哪里找这些 TLD 文件呢它的策略是“宁可错杀不可放过”。默认情况下它会扫描WEB-INF/lib目录下的每一个 JAR 包以及一些已知的、第三方库如 Spring、Struts通常存放 TLD 的特定路径。问题就出在这里我们项目依赖的绝大多数 JAR 包比如各种工具包、数据库驱动、业务框架等根本就不包含任何 TLD 文件。Tomcat 兴冲冲地打开这些 JAR 包翻了个底朝天结果一无所获于是它只好悻悻地在日志里记录一句“我扫描了这个 JAR但里面没找到 TLD 哦。” 这就是那条警告的由来。所以这个警告的核心矛盾在于扫描范围Scope过于宽泛。Tomcat 默认的扫描行为没有对 JAR 包进行区分对所有WEB-INF/lib下的 JAR 都执行了扫描操作导致了大量无谓的、耗时的检查并输出了冗余的日志信息。在大型项目中依赖的 JAR 包可能多达上百个这会使启动速度变慢日志文件臃肿。我们的目标就是通过精准配置告诉 Tomcat“这些包你不用查了里面肯定没有你要的东西”从而“彻底解决”这个烦人的提示。2. 解决方案全景与选型思路面对这个警告网络上流传着多种解决方案从“眼不见为净”的屏蔽法到“精准外科手术”式的配置法各有优劣。我们需要根据项目实际情况、部署环境和个人偏好来选择最合适的一种。下面我将这些方案分为几个层次进行解析帮助你理解背后的逻辑并做出选择。2.1 方案一全局日志级别屏蔽治标不治本这是最简单粗暴的方法即修改 Tomcat 的日志配置文件将输出该警告的日志记录器级别调高使其不输出WARNING级别的信息。操作修改conf/logging.properties文件找到org.apache.jasper.servlet.TldScanner.level这一行如果没有就添加将其值从FINE或WARNING改为SEVERE。优点操作极其简单一键“静音”。缺点治标不治本扫描行为依然发生启动时的性能开销I/O 操作并没有减少。可能掩盖真正问题如果未来某个真正包含 TLD 的 JAR 包因为路径错误等原因无法被正确读取由于警告被屏蔽你将无法收到任何提示可能导致 JSP 页面功能异常。影响范围广它屏蔽了整个TldScanner组件的警告可能会错过其他相关的、有价值的问题信息。注意我个人不推荐在生产环境中使用此方法它更像是一种临时性的“掩耳盗铃”。在开发环境为了日志清爽可以临时使用但务必清楚其潜在风险。2.2 方案二在应用内配置 TLD 扫描跳过推荐精准控制这是最主流和推荐的解决方案。其核心思想是在 Web 应用内部通过配置文件明确指定哪些 JAR 包需要被扫描或者哪些应该被跳过。Tomcat 提供了两种实现方式本质相同只是配置位置不同。方式 A在context.xml中配置这是最常用和灵活的方式。你可以在以下两个位置之一创建或修改context.xml文件META-INF/context.xml(位于你的 Web 应用的WEB-INF/classes同级目录): 这个配置只对当前应用生效是项目级配置便于随项目代码一起管理。$CATALINA_BASE/conf/context.xml: 这个配置对部署在该 Tomcat 实例下的所有应用生效是服务器级全局配置。配置内容如下Context JarScanner scanClassPathfalse/ /Context将scanClassPath设置为false可以禁止 Tomcat 扫描整个类路径ClassPath上的 JAR 包。但这可能过于绝对如果你有少数 JAR 确实包含 TLD比如 JSTL 的javax.servlet.jsp.jstl.jar它们也会被忽略。因此更精准的做法是使用JarScanFilterContext JarScanner JarScanFilter defaultTldScantrue tldScan*.jar tldSkip*.jar/ /JarScanner /Context看起来有点矛盾别急这里的关键是tldSkip属性。我们可以利用它来设置一个“黑名单”。但更高效的是设置“白名单”。方式 B在catalina.properties中配置这是 Tomcat 的全局属性文件位于$CATALINA_BASE/conf/catalina.properties。你可以找到tomcat.util.scan.StandardJarScanFilter.jarsToSkip这个属性。它本身已经包含了一个很长的列表用于跳过扫描已知不包含 TLD 的常见 JAR如log4j*.jar,slf4j*.jar等。我们只需要将我们项目中特有的、不含 TLD 的 JAR 包名支持通配符追加到这个列表后面即可。例如tomcat.util.scan.StandardJarScanFilter.jarsToSkip\ bootstrap.jar,commons-*.jar,log4j-*.jar,slf4j-*.jar,\ # 以下是你的自定义添加 my-utils.jar,some-framework-*.jar,activemq-*.jar优点真正避免了无谓的扫描提升了应用启动速度。配置精准不影响真正需要 TLD 的 JAR。缺点需要维护这个跳过列表。当项目引入新依赖时如果该依赖不含 TLD可能需要更新此列表。实操心得对于 Maven 项目一个技巧是观察启动日志把那些被警告的、你确定不含 TLD 的 JAR 文件名如fastjson-1.2.83.jar添加到跳过列表中。通常除了jstl*.jar,spring-webmvc*.jar(可能包含Spring表单标签TLD) 等少数几个其他大部分工具类、驱动类 JAR 都可以跳过。2.3 方案三彻底禁用 JSP激进方案如果你的应用是一个纯 RESTful API 后端服务或者是一个前后端分离的项目完全不需要 JSP 支持那么你可以考虑从根本上解决问题禁用 Tomcat 的 JSP 解析功能。操作从你的 Web 应用WAR 包中移除WEB-INF/lib下的jasper.jar和jsp-api.jar注意它们可能由 Tomcat 本身提供而非应用包内。或者在context.xml中配置不加载 JSP Servlet。优点一劳永逸彻底移除与 JSP 相关的所有开销。缺点方案非常激进。一旦未来需要 JSP改动会很大。而且许多框架即使是 Spring Boot 内嵌 Tomcat的某些功能可能间接依赖于 JSP 环境。个人建议除非你 200% 确定当前及可预见的未来都不会使用任何 JSP、JSTL 技术否则不要采用此方案。它带来的风险远大于解决一个警告日志的收益。选型思路总结 对于绝大多数项目方案二应用内配置 TLD 扫描跳过是最佳实践。它平衡了效果、安全性和可维护性。我建议在项目的META-INF/context.xml中进行配置这样配置能跟随项目代码走与部署环境解耦。3. 基于context.xml的详细配置实战让我们深入最推荐的方案手把手完成配置。我们将采用“白名单”模式即默认跳过所有只显式指定需要扫描的少数 JAR。这种方式最清晰也最安全。3.1 创建并配置META-INF/context.xml定位目录在你的 Web 项目源代码目录中找到src/main/webapp标准 Maven Web 项目结构或WebContentEclipse 动态 Web 项目目录。创建文件夹和文件在该目录下创建META-INF文件夹如果不存在然后在该文件夹内创建context.xml文件。注意META-INF目录通常与WEB-INF目录同级。在最终打出的 WAR 包中它也将位于根目录下。编写配置内容将以下配置写入context.xml文件。?xml version1.0 encodingUTF-8? Context !-- 禁用默认的Banner可选使日志更简洁 -- WatchedResource${catalina.base}/conf/web.xml/WatchedResource !-- 配置Jar扫描过滤器 -- JarScanner JarScanFilter defaultTldScanfalse defaultPluggabilityScantrue tldScanspring-webmvc*.jar, jstl*.jar, taglibs-standard-impl*.jar tldSkip*.jar pluggabilityScan*.jar pluggabilitySkip/ /JarScanner /Context3.2 配置参数深度解析让我们拆解上面配置中每个参数的含义和设置理由defaultTldScanfalse这是关键。它表示默认情况下不扫描任何 JAR 包中的 TLD。这相当于建立了一个“全部跳过”的基线。tldScanspring-webmvc*.jar, jstl*.jar, taglibs-standard-impl*.jar这是我们的“白名单”。只有匹配这些模式支持通配符*的 JAR 包才会被扫描 TLD。spring-webmvc*.jarSpring MVC 框架的 JAR内部包含了 Spring 的表单标签库 TLD 文件。jstl*.jar和taglibs-standard-impl*.jar这是 JSTLJSP 标准标签库的实现包必须被扫描。你需要根据自己项目的实际情况调整这个列表。如果你使用了 Apache Tiles、Struts Tags 或者其他自定义标签库需要将对应的 JAR 名加入此处。tldSkip*.jar这个属性与tldScan共同作用。由于我们已经设置了defaultTldScanfalse并指定了tldScan白名单这里的tldSkip设置为*.jar是安全的它重申了“跳过所有”的规则但实际生效的是白名单逻辑。defaultPluggabilityScantrue和pluggabilityScan这部分与“可插拔性扫描”相关主要涉及Servlet 3.0的WebServlet、WebFilter、WebListener等注解的扫描。通常我们不需要修改它保持默认的扫描行为即可以确保这些注解能被正确识别。因此我们设置了pluggabilityScan*.jar进行全扫描。pluggabilitySkip留空表示不跳过任何 JAR 的可插拔性扫描。3.3 验证配置效果重新打包部署将修改后的项目重新打包成 WAR 文件并部署到 Tomcat或者如果你使用的是 IDE如 IntelliJ IDEA、Eclipse直接运行确保新的context.xml文件被正确包含在发布内容中。观察启动日志重启 Tomcat 并仔细观察控制台输出。成功标志之前刷屏的“At least one JAR was scanned for TLDs...”警告信息应该大量减少甚至完全消失。你只会看到针对tldScan列表中明确指定的那几个 JAR 的扫描信息可能以INFO级别显示或者完全不显示。检查功能访问你应用中使用了 JSTL 或 Spring 表单标签的 JSP 页面确保所有功能正常没有出现标签无法解析的错误。4. 进阶排查与常见问题实录即使按照上述步骤配置你可能还是会遇到一些意外情况。下面是我在实际工作中遇到的一些典型问题及其解决方法。4.1 警告仍未完全消失现象配置了context.xml后大部分警告没了但仍有少数几个顽固的 JAR 包警告出现。排查思路检查 JAR 包的真实名称警告信息中的 JAR 文件名可能包含版本号如commons-lang3-3.12.0.jar。你的tldSkip模式是否匹配确保你的模式能覆盖它例如使用commons-*.jar。检查配置位置和生效范围确认你的context.xml文件放在了正确的位置META-INF/并且已被成功打包到 WAR 的根目录。你可以解压 WAR 包进行验证。同时检查 Tomcat 的全局conf/context.xml是否覆盖了你的配置通常不会应用级配置优先级更高。检查是否有其他配置冲突如果你同时在catalina.properties的jarsToSkip里做了配置需要确认两者没有冲突。建议优先使用应用内的context.xml进行管理。查看完整类路径有些 JAR 可能不是放在WEB-INF/lib下而是通过shared.loader或common.loader在 Tomcat 的共享类加载器中加载的。JarScanFilter默认主要扫描WEB-INF/lib和WEB-INF/classes。对于其他位置的 JAR可能需要额外配置。你可以通过查看警告日志中 JAR 的完整路径来判断其来源。4.2 配置后 JSP 标签库失效现象警告消失了但 JSP 页面中使用 JSTL 或 Spring 标签的地方报错例如The absolute uri: [http://java.sun.com/jsp/jstl/core] cannot be resolved...。原因与解决 这几乎可以肯定是因为tldScan白名单配置有误漏掉了包含必要 TLD 的 JAR 包。确认 JSTL 实现包现代项目通常使用taglibs-standard-impl作为 JSTL 实现。请检查你的pom.xml或lib目录确认 JSTL JAR 的确切名称并将其加入tldScan列表。例如tldScanspring-webmvc*.jar, taglibs-standard-impl-*.jar, javax.servlet.jsp.jstl-*.jar。检查 TLD 文件位置极少数情况下TLD 文件可能被打包在非标准的路径下。你可以使用解压工具打开疑似包含 TLD 的 JAR 包检查其内部是否有.tld文件通常位于META-INF目录或其子目录下。临时调试作为调试手段你可以先将defaultTldScan改回true确认功能恢复然后逐一从tldScan列表中移除 JAR直到问题复现从而定位出是哪个必需的 JAR 被漏掉了。4.3 内嵌式 Tomcat如 Spring Boot的配置差异现象我的项目是 Spring Boot使用内嵌的 Tomcat 服务器上述配置不生效。解决方案 Spring Boot 通过application.properties或application.yml以及编程方式配置内嵌容器。对于 TLD 扫描问题最优雅的方式是通过TomcatServletWebServerFactory进行自定义配置。方法一通过application.properties(有限支持)Spring Boot 提供了一些属性但可能无法精细控制到JarScanFilter。你可以尝试设置通用日志级别来屏蔽 Jasper 的警告# 提高Tomcat的TLD扫描日志级别使其不输出WARNING logging.level.org.apache.jasper.servlet.TldScannerWARN # 或者直接关闭Jasper的调试日志 logging.level.org.apache.jasperINFO但这同样是“屏蔽法”并未优化扫描过程。方法二通过 Java 配置类推荐创建一个配置类自定义TomcatServletWebServerFactoryimport org.apache.tomcat.util.scan.StandardJarScanFilter; import org.apache.tomcat.util.scan.StandardJarScanner; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.server.WebServerFactoryCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class TomcatConfiguration { Bean public WebServerFactoryCustomizerTomcatServletWebServerFactory tomcatCustomizer() { return factory - factory.addContextCustomizers(context - { StandardJarScanner jarScanner (StandardJarScanner) context.getJarScanner(); StandardJarScanFilter filter new StandardJarScanFilter(); // 设置白名单只扫描这些JAR filter.setTldScan(spring-webmvc*.jar, taglibs-standard-impl*.jar, jstl*.jar); // 默认跳过所有TLD扫描 filter.setTldSkip(*.jar); // 可插拔性扫描保持默认扫描所有 filter.setPluggabilityScan(*.jar); jarScanner.setJarScanFilter(filter); }); } }这种方法与在context.xml中配置的原理和效果完全一致是 Spring Boot 环境下的最佳实践。4.4 性能影响实测与建议为了让你更直观地理解优化效果我曾在两个中型项目依赖 JAR 数量约 80 个中做过简单对比测试场景启动时间 (平均)控制台警告行数未做任何优化约 28 秒75 行配置context.xml白名单约 25 秒0 行 (仅INFO日志)仅设置日志级别为 SEVERE约 28 秒0 行 (但扫描仍在进行)可以看到正确的配置不仅消除了视觉干扰还带来了约 10% 的启动时间优化。对于需要频繁重启的本地开发环境或追求极致启动速度的云原生场景这点优化积少成多不容小觑。最终建议将优化 TLD 扫描作为项目的一项标准配置。在新项目初始化时就根据技术栈如 Spring MVC JSTL预先配置好context.xml或 Spring Boot 配置类。对于存量老项目可以花一点时间清理这个“历史遗留问题”它能提升开发体验也让你的应用日志更加专业和清晰。记住关键永远是使用“白名单”思维进行精准控制而不是简单地全局屏蔽。