Spring-boot Unable to start EmbeddedWebApplicationContext 分析与解决方法

异常触发背景

项目中依赖了第三方的dubbo调用依赖。

    <dependency>
      <groupId>xxx.xxx.com</groupId>
      <artifactId>dubbo-test-client</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>

随后启动spring-boot application后产生异常

org.springframework.context.ApplicationContextException: Unable to start EmbeddedWebApplicationContext due to missing EmbeddedServletContainerFactory bean.

Unable to start EmbeddedWebApplicationContext due to missing EmbeddedServletContainerFactory bean.

异常产生原因

在启动时Spring-boot会检查当前环境,org.springframework.boot.SpringApplication内部维护 webEnvironment成员,记录当前是否是web环境。

private boolean webEnvironment;

在启动时会沿如下的调用路径通过deduceWebEnvironment方法判断当前环境,并给webEnvironment变量赋值。

this.webEnvironment = deduceWebEnvironment();

      at org.springframework.boot.SpringApplication.deduceWebEnvironment(SpringApplication.java:256)
      at org.springframework.boot.SpringApplication.initialize(SpringApplication.java:248)
      at org.springframework.boot.SpringApplication.<init>(SpringApplication.java:225)
      at org.springframework.boot.SpringApplication.run(SpringApplication.java:1118)
      at org.springframework.boot.SpringApplication.run(SpringApplication.java:1107)
      at springboot.commandline.StartupRunner.main(StartupRunner.java:36)

deduceWebEnvironment,通过尝试加载WEB_ENVIRONMENT_CLASSES数组中的下面两个类,如果全部加载成功那么推断当前属于web环境。

  • javax.servlet.Servlet(servelet-api jar)
  • org.springframework.web.context.ConfigurableWebApplicationContext (spring-web jar)
    private boolean deduceWebEnvironment() {
        for (String className : WEB_ENVIRONMENT_CLASSES) {
            if (!ClassUtils.isPresent(className, null)) {
                return false;
            }
        }
        return true;
    }
    private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
            "org.springframework.web.context.ConfigurableWebApplicationContext" };

由于在maven中添加的依赖同时引入WEB_ENVIRONMENT_CLASSES中的类,导致spring-boot判断当前为web环境。

当判断为web环境,会在springboot.run方法中通过createApplicationContext()方法创建EmbeddedWebApplicationContext,最终会沿如下的调用链调用createEmbeddedServletContainer()方法去创建servlet容器。

      at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.createEmbeddedServletContainer(EmbeddedWebApplicationContext.java:159)
      at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.onRefresh(EmbeddedWebApplicationContext.java:134)
      at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:537)
      - locked <0xbc0> (a java.lang.Object)
      at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:122)
      at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:693)
      at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:360)
      at org.springframework.boot.SpringApplication.run(SpringApplication.java:303)
      at org.springframework.boot.SpringApplication.run(SpringApplication.java:1118)
      at org.springframework.boot.SpringApplication.run(SpringApplication.java:1107)
      at springboot.commandline.StartupRunner.main(StartupRunner.java:36)

createEmbeddedServletContainer中会通过getEmbeddedServletContainerFactory()方法来获取容器工厂对象。

    private void createEmbeddedServletContainer() {
        EmbeddedServletContainer localContainer = this.embeddedServletContainer;
        ServletContext localServletContext = getServletContext();
        if (localContainer == null && localServletContext == null) {
            EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
            this.embeddedServletContainer = containerFactory
                    .getEmbeddedServletContainer(getSelfInitializer());
        }
        else if (localServletContext != null) {
            try {
                getSelfInitializer().onStartup(localServletContext);
            }
            catch (ServletException ex) {
                throw new ApplicationContextException("Cannot initialize servlet context",
                        ex);
            }
        }
        initPropertySources();
    }

getEmbeddedServletContainerFactory 方法被调用时会尝试获取工厂bean名称,如果获取不到,则会抛出Unable to start EmbeddedWebApplicationContext due to missing的异常。

    protected EmbeddedServletContainerFactory getEmbeddedServletContainerFactory() {
        // Use bean names so that we don't consider the hierarchy
        String[] beanNames = getBeanFactory()
                .getBeanNamesForType(EmbeddedServletContainerFactory.class);
        if (beanNames.length == 0) {
            throw new ApplicationContextException(
                    "Unable to start EmbeddedWebApplicationContext due to missing "
                            + "EmbeddedServletContainerFactory bean.");
        }
        if (beanNames.length > 1) {
            throw new ApplicationContextException(
                    "Unable to start EmbeddedWebApplicationContext due to multiple "
                            + "EmbeddedServletContainerFactory beans : "
                            + StringUtils.arrayToCommaDelimitedString(beanNames));
        }
        return getBeanFactory().getBean(beanNames[0],
                EmbeddedServletContainerFactory.class);
    }

总结:通过对异常产生的原因分析,可以知道是因为引入了spring-web-xx.jar(xx为版本号),导致判断当前环境为web,最终尝试创建相关的Servlet容器工厂失败而导致。

最后分析maven依赖关系发现依赖的包dubbo-test-client存在对spring-web的间接依赖。

《Spring-boot Unable to start EmbeddedWebApplicationContext 分析与解决方法》

解决方法

1.在dubbo-test-client中排除对spring-web jar的依赖。

    <dependency>
      <groupId>xxx.xxx.xx</groupId>
      <artifactId>dubbo-test-client</artifactId>
      <version>1.0-SNAPSHOT</version>
      <exclusions>
        <exclusion>
          <groupId>org.springframework</groupId>
          <artifactId>spring-web</artifactId>
        </exclusion>
      </exclusions>
    </dependency>

2.引入spring-boot-starter-web,引入创建web容器所需要的相关依赖。

      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
      </dependency>

参考

spring-boot 1.5.6 源码

    原文作者:Spring Boot
    原文地址: https://blog.csdn.net/Revivedsun/article/details/76762115
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞