如何禁用 Servlet 3.0 扫描和自动加载组件

How to disable Servlet 3.0 scanning and auto loading of components

我们有一个应用程序不断从我们的第 3 方库中加载 ServletContainerInitializer 的实例。

一个实例是 JerseyServletContainerInitializer,另一个是 SpringServletContainerInitializer。这些来自 Jersey 的 类 和 Spring 似乎 "take over" 我们的 servlet 上下文扰乱了我们的映射和过滤器等。

我们确实需要显式配置我们的 servlet 容器 web.xml,这种自动扫描快把我们逼疯了。通过简单地在我们的 pom.xml 中引入依赖项,我们的运行时 ServletContext 配置(例如 Servlets/Filters/ContextListeners 会发生变化,因为 servlet 容器会在类路径中找到这些库。

有没有办法使用 Servlet 3 但禁用其烦人的自动类路径扫描"feature"?

来自https://wiki.apache.org/tomcat/HowTo/FasterStartUp

There are two options that can be specified in your WEB-INF/web.xml file:

  • Set metadata-complete="true" attribute on the <web-app> element.
  • Add an empty <absolute-ordering /> element.

Setting metadata-complete="true" disables scanning your web application and its libraries for classes that use annotations to define components of a web application (Servlets etc.). The metadata-complete option is not enough to disable all of annotation scanning. If there is a SCI with a @HandlesTypes annotation, Tomcat has to scan your application for classes that use annotations or interfaces specified in that annotation.

The <absolute-ordering> element specifies which web fragment JARs (according to the names in their WEB-INF/web-fragment.xml files) have to be scanned for SCIs, fragments and annotations. An empty element configures that none are to be scanned.

In Tomcat 7 the absolute-ordering option affects discovery both of SCIs provided by web application and ones provided by the container (i.e. by the libraries in $CATALINA_HOME/lib). In Tomcat 8 the option affects the web application ones only, while the container-provided SCIs are always discovered, regardless of absolute-ordering. In such case the absolute-ordering option alone does not prevent scanning for annotations, but the list of JARs to be scanned will be empty, and thus the scanning will complete quickly. The classes in WEB-INF/classes are always scanned regardless of absolute-ordering.

Scanning for web application resources and TLD scanning are not affected by these options.

根据 Servlet 3.1 规范,关于绝对排序的规范声明可在第 8.2.2 节 "Ordering of web.xml and web-fragment.xml",部分 1.d.:

中找到

The element may contain zero or one element. The required action for this element is described below. If the element does not contain an element, any web-fragment not specifically mentioned within elements MUST be ignored. Excluded jars are not scanned for annotated servlets, filters or listeners. However, if a servlet, filter or listener from an excluded jar is listed in web.xml or a non-excluded web-fragment.xml, then it's annotations will apply unless otherwise excluded by metadata-complete.

一个关键的收获是,使用空的绝对排序元素不仅会关闭对 servlet 容器初始化程序的扫描。扫描所有组件定义注释(例如 @WebServlet)已关闭。仅当您真的想关闭 Web 应用程序中 JAR 文件的所有注释处理时,才建议使用空的绝对排序元素。

扫描可以更精细地调整:使用绝对排序元素,并使用名称元素包含要扫描的 JAR 的名称,并省略您要扫描的 JAR希望跳过。 不要使用其他元素,因为这会将所有未明确列出的 JAR 重新放入排序中,并将重新扫描这些 JAR。

作为一般做法,我们发现关闭所有注释处理是危险的,因为启用了注释扫描的 Web 应用程序有一些必须扫描的 类,这意味着关闭扫描完全会破坏 Web 应用程序。

我发现给定的答案均无效。

从类路径中实际过滤掉第 3 方 SCI 的唯一方法原来是 Context container(在 context.xml 中)的 containerSciFilter 属性,它被定义为

The regular expression that specifies which container provided SCIs should be filtered out and not used for this context. Matching uses java.util.regex.Matcher.find() so the regular expression only has to match a sub-string of the fully qualified class name of the container provided SCI for it to be filtered out. If not specified, no filtering will be applied.

在我的例子中,我想过滤掉像 org.eclipse.jetty.apache.jsp.JettyJasperInitializer 这样的东西,所以我使用了 <Context ... containerSciFilter="jetty" ...>

JIK 这是我的情况: 我必须添加另一个项目作为我项目的依赖项,但该项目包含自己的 WebInit(即 AbstractAnnotationConfigDispatcherServletInitializer 的子 class),所以当我的 war 部署两个 WebApplicationInitializer 被称为(我和另一个)。

所以为了避免这种情况,我

  • 添加一个 ${projectRoot}/src/main/webapp/WEB-INF/web.xml<absolute-ordering />
  • 添加${projectRoot}/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer文本文件(文件名必须准确为“javax.servlet.ServletContainerInitializer”)一行my.custom.package.MyCustomeServletContainerInitializer
  • 在包中添加一个 class MyCustomeServletContainerInitializer my.custom.package 在我的例子中它是一个 kotlin class 像这样
class MyCustomeServletContainerInitializer: ServletContainerInitializer {
    override fun onStartup(webAppInitializerClasses: MutableSet<Class<*>>?, servletContext: ServletContext) = ServletInitializer().onStartup(servletContext)
}

其中 ServletInitializer 是我自己实现的 WebApplicationInitializer 或者 SpringBootServletInitializer