Tomcat 如果 spring-boot 上下文部署失败,多上下文部署将停止整个过程

Tomcat multi-context deployment stops whole process if spring-boot context fails to deploy

我有一个非常具体的 Tomcat 配置,我不想更改,我正在为 这个具体的 配置寻找解决方案。

我已经在 conf/server.xml 文件中声明了多个上下文 (<Context>) 用于部署 Tomcat,不推荐我知道的东西。我的问题是,如果其中一个部署失败,整个 Tomcat 进程就会停止,从而终止已成功启动的其余应用程序。这种行为是预期的吗?是否有属性或方法来防止这种情况并模拟类似于 context.xml 文件的用法的不同上下文声明的行为?

我得到的错误如下:

ConfigServletWebServerApplicationContext : Exception encountered during context initialization

使用此堆栈跟踪(针对 Tomcat 版本 8.5.24):

SEVERE [Catalina-startStop-1] org.apache.catalina.core.ContainerBase.startInternal A child container failed during start
 java.util.concurrent.ExecutionException: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[/auth-service]]
        at java.util.concurrent.FutureTask.report(FutureTask.java:122)
        at java.util.concurrent.FutureTask.get(FutureTask.java:192)
        at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:939)
        at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:872)
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
        at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1419)
        at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1409)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
Caused by: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[/auth-service]]
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:167)
        ... 6 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'authenticationController': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'app.client-html.context' in value "${app.client-html.context}"
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:405)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1415)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:608)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:531)
        at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean[=12=](AbstractBeanFactory.java:335)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:944)
        at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:925)
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:588)
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:144)
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:767)
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:759)
        at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:426)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:326)
        at org.springframework.boot.web.servlet.support.SpringBootServletInitializer.run(SpringBootServletInitializer.java:173)
        at org.springframework.boot.web.servlet.support.SpringBootServletInitializer.createRootApplicationContext(SpringBootServletInitializer.java:153)
        at org.springframework.boot.web.servlet.support.SpringBootServletInitializer.onStartup(SpringBootServletInitializer.java:95)
        at org.springframework.web.SpringServletContainerInitializer.onStartup(SpringServletContainerInitializer.java:174)
        at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5196)
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
        ... 6 more
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'app.client-html.context' in value "${app.client-html.context}"
        at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:178)
        at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:124)
        at org.springframework.core.env.AbstractPropertyResolver.doResolvePlaceholders(AbstractPropertyResolver.java:239)
        at org.springframework.core.env.AbstractPropertyResolver.resolveRequiredPlaceholders(AbstractPropertyResolver.java:210)
        at org.springframework.context.support.PropertySourcesPlaceholderConfigurer.lambda$processProperties[=12=](PropertySourcesPlaceholderConfigurer.java:175)
        at org.springframework.beans.factory.support.AbstractBeanFactory.resolveEmbeddedValue(AbstractBeanFactory.java:931)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1308)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1287)
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:640)
        at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:119)
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:399)
        ... 27 more

示例配置:

<Host name="localhost" unpackWARs="true" autoDeploy="true">
    <Context docBase="../../app-that-works" path="/itworks"/>
    <Context docBase="../../app-that-fails" path="/itfails"/>
</Host>

重要提示:失败 应用程序是一个 spring-boot 应用程序,它被打包为 .war 文件和 运行 在香草 tomcat, 而非 嵌入式 tomcat 方式。这很重要,因为这些应用程序似乎具有这种行为...

免责声明:我指的是 vanilla Tomcat、not spring boot 或 embedded tomcat 等。我不是在寻找这样的东西解决方案,只是针对这个特定问题的解决方案。

基本上:

  • server.xml 中配置的 任何 组件的 start 阶段(参见 LifeCycle)异常导致服务器退出。然而,一些异常(如失败的 ServletContextListener)不会传播。
  • 在运行时添加的组件错误,不会导致服务器退出。

如果您不希望整个服务器停止,当上下文无法启动时,使用自动部署:为 server.xml 中定义的每个上下文创建一个包含内容的文件 $CATALINA_BASE/conf/<engine_name>/<host_name>/<context_path>.xml :

<Context docBase="/path/to/application" />

在您的情况下,您需要创建 $CATALINA_BASE/conf/Catalina/localhost/itworks.xml$CATALINA_BASE/conf/Catalina/localhost/itfails.xml。有关 Tomcat 对 XML 描述符的命名约定,请参见 Naming

编辑:您的堆栈跟踪表明我们可能正在处理 Tomcat 或 Spring 错误:

  • 一方面,StandardContext 期望 ServletContainerInitializer 仅抛出 ServletExceptions(参见 source code) and let's all unchecked exceptions through. Remark that at the same time all exceptions from ServletContextListeners are caught and just logged (cf. source code)。

  • 另一方面Spring的SpringServletContainerInitializer 应该将所有未经检查的异常包装到 ServletException.

在您的情况下发生的情况是未经检查的异常一直到 StandardContext 并作为 LifecycleException 停止服务器重新抛出。无论如何,设置:

<Context throwOnFailure="false" ... />

应该阻止 Tomcat 传播异常。