01.Spring Boot + Dubbo 打成 war 包报错

1. Spring Boot 项目打成 war 包

  • 网上提供的思路一般都是如下方式,然后就可以成功启动了
1.1 修改 pom.xml
1. 修改 packaging
<packaging>war</packaging>
2. 修改 dependency
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <artifactId>spring-boot-starter-tomcat</artifactId>
                <groupId>org.springframework.boot</groupId>
            </exclusion>
        </exclusions>
    </dependency>
    <!-- 依赖改为 provided -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <scope>provided</scope>
    </dependency>
    <!-- 依赖改为 provided -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <scope>provided</scope>
    </dependency>
</dependencies>
1.2 修改启动类
@SpringBootApplication
public class HelloWorldMainApplication extends SpringBootServletInitializer {

    /*public static void main(String[] args) {
        SpringApplication.run(HelloWorldMainApplication.class, args);
    }*/

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(HelloWorldMainApplication.class);
    }

}

2. 报错详解

  • 但是对于 Spring Boot + Dubbo 的项目,这样配置还不行,会报错
2.1 报错信息
java.lang.IllegalStateException: Cannot initialize context because there is already a root application context present - check whether you have multiple ContextLoader* definitions in your web.xml!
	at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:262)
	at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:103)
	at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4745)
	at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5207)
	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
2.2 servlet 3.0 的可插拔特性
1. spring 的扩展特性
  • 在 spring 的 spring-web-xxx.RELEASE.jar 中有一个 META-INF/services/javax.servlet.ServletContainerInitializer 文件,文件内容是 org.springframework.web.SpringServletContainerInitializer,在 servlet 容器启动时候会去寻找 ServletContainerInitializer 实例,并调用 onStartup 方法,此方法最终会调用 org.springframework.context.support.AbstractApplicationContext#refresh 方法来初始化 spring 容器和 spring-mvc 子容器

  • 调用栈关系如下:

onRefresh:152, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:540, AbstractApplicationContext (org.springframework.context.support)
refresh:142, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:775, SpringApplication (org.springframework.boot)
refreshContext:397, SpringApplication (org.springframework.boot)
run:316, SpringApplication (org.springframework.boot)
run:157, SpringBootServletInitializer (org.springframework.boot.web.servlet.support)
createRootApplicationContext:137, SpringBootServletInitializer (org.springframework.boot.web.servlet.support)
onStartup:91, SpringBootServletInitializer (org.springframework.boot.web.servlet.support)
onStartup:171, SpringServletContainerInitializer (org.springframework.web)
2. dubbo 的扩展特性
  • 在 dubbo 的 META-INF 目录下有一个 web-fragment.xml 文件,文件内容是:

    <web-fragment version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-fragment_3_0.xsd">
    
        <name>dubbo-fragment</name>
    
        <ordering>
            <before>
                <others/>
            </before>
        </ordering>
    
        <context-param>
            <param-name>contextInitializerClasses</param-name>
            <param-value>org.apache.dubbo.config.spring.initializer.DubboApplicationContextInitializer</param-value>
        </context-param>
    
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
    
    </web-fragment>
    
  • 可以看到此处给容器中添加了 spring 的监听器 ContextLoaderListener,根据 servlet 的监听器原理,会在 servlet 容器启动之后调用 contextInitialized 方法,此方法调用了 initWebApplicationContext(event.getServletContext()) 方法,从字面意思上看也知道是在初始化 spring-mvc 容器

    public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
        // 判断 servletContext 是否有此属性,如果有表示已经初始化过 web 容器
    	if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
    		throw new IllegalStateException(
    				"Cannot initialize context because there is already a root application context present - " +
    				"check whether you have multiple ContextLoader* definitions in your web.xml!");
    	}
    
    	......
    	
    }
    

3. 解决方法

  • 既然知道是基于 servlet 新特性做出的扩展,只要禁用 web-fragment.xml 即可

  • 在项目中新建 webapp/WEB-INF/web.xml 文件,添加属性 metadata-complete="true" 即可

<?xml version="1.0" encoding="UTF-8" ?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.4"
         xmlns="http://java.sun.com/xml/ns/j2ee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee  http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
         metadata-complete="true">

</web-app>
发布了37 篇原创文章 · 获赞 3 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/masteryourself/article/details/100114835
今日推荐