Dubbo
从低版本升级到2.6.5
版本后,启动失败,报错如下:
05-Mar-2019 16:02:25.204 ?? [RMI TCP Connection(2)-127.0.0.1] org.apache.catalina.core.StandardContext.listenerStart Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener
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:296)
at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:107)
at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4727)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5189)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
上终极方案:使用2.6.2
以下版本或者2.7.0
以上版本的dubbo
;
具体解决方式需要根据项目的情况解决,提供一些其他方案:
web.xml
的纯xml工程;web.xml
的Spring Boot
工程;ContextLoaderListener
删除 web.xml
中如下的配置:
org.springframework.web.context.ContextLoaderListener
注意:如果有自定义的
Listener
继承自ContextLoaderListener
也需要删除;这么做的目的是不需要自己去配置初始化
Spring
框架,Dubbo
在2.6.3
之后可以“自动”
初始化Spring
框架;
Servlet 3.0
的可插性功能web.xml
的xsd
升级到3.0
;metadata-complete
;Dubbo
的ContextInitializer
添加到描述文件中;
contextInitializerClasses
org.apache.dubbo.config.spring.initializer.DubboApplicationContextInitializer
Spring Boot
工程没有特别好的解决方案,提供两个解决思路:
web.xml
文件并按照传统配置web.xml
Spring Boot
工程改造下,创建webapp/WEB-INF
目录并创建web.xml
文件;方案2
改造工程;Spring Boot
自动做的需要我们手动在web.xml
中配置;NOTE:如果使用此方案来改造,需要注意自己的
Spring Boot
项目是否还有其他依赖Servelt 3.0
特性的地方,并手动配置到web.xml
中;
dubbo
的Listener
运行这个方案也没有绕过添加web.xml
的命运,做法如下:
webapp/WEB-INF
目录并创建web.xml
;web.xml
中指定absolute-ordering
,仅允许Spring Web
的配置生效;
risk-etl
spring_web
观察报错日志,报错位置很明显是Spring
框架初始化时的报错,重点是:there is already a root application
。
这个错误抛出位置位于:Spring-web
包的ContextLoader
类的initWebApplicationContext
方法。
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
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!");
}
…………
}
原因很明显,ContextLoader
被调用了至少两遍,第二遍报错导致项目初始化失败,其主要的“罪魁祸首”是dubbo
包下面的web-fragment.xml
。
Servlet 3.0
是随着Java EE 6
规范发布的,主要新增特性:
Servlet
可以支持异步;Servlet
/Filter
/Listener
;web-fragment.xml
将一个web.xml
拆分到多个包中配置;支持Servlet 3.0
规范的容器,在启动后会扫描工程的jar
包,找到符合规范的添加了相关注解的类
和web-fragment.xml
然后跟web.xml
的配置合并作为整个项目的初始化配置。
上述问题的发生原因很明显了:
dubbo
在2.6.3
版本为了实现优雅关机
(实际上并不好用)引入了web-fragment.xml
注册自己的ContextInitializer
;DubboApplicationContextInitializer
通过Spring
的消息广播机制
在Context
加载完成后调用addShutdownHook()
向JVM
注册一个钩子函数,以便JVM
关闭时可以释放一些资源防止内存泄露;dubbo
为了保证自己的ContextInitializer
被用到(利用的Spring
的机制)在自己的web-fragment.xml
顺手配置了一个listener
;web.xml
中配置了ContextLoaderListener
(或其子类),我们的Listener
一般会被优先调用,完成第一次的Spring Context
初始化;Spring Boot
项目,容器会先调用SpringServletContainerInitializer
类的onStartup
方法,这个方法内部会初始化Spring Context
;Spring Boot
的Listener
调用完成后会调用dubbo
的listener
,这个时候ContextLoader
类会检测到已经初始化了一个Context
从而报错,引发项目启动失败;<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-fragmentname>
<ordering>
<before>
<others/>
before>
ordering>
<context-param>
<param-name>contextInitializerClassesparam-name>
<param-value>org.apache.dubbo.config.spring.initializer.DubboApplicationContextInitializerparam-value>
context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>
web-fragment>
Dubbo在 github 上说明了他为什么要这么做:https://github.com/apache/incubator-dubbo/pull/2126
这个是Servlet 3.0
提供的一个属性,等同一个开关,设置为true
则表示web.xml
已经提供了全部的配置信息,不需要容器再去各个jar
包找配置了,换句话就是:关闭可插特性
;
这个属性是SpringServletContainerInitializer
注释里面提供的解决思路。这个属性可以理解为指定web-fragment.xml
的加载顺序,和ordering
标签的区别是,absolute-ordering
仅仅针对我们指定的web-fragment.xml
做排序。
轻易升级一个基础框架不是一个好的做法,升级基础框架还是应该关注下当前版本和目标升级版本,框架作者做了些什么事情,出现过什么BUG。
当前的Spring Boot
的解决方案并不让人满意,毕竟Spring Boot
的无Xml的感觉还是很爽的,为了这个升级引入了web.xml
会有一点点不爽。