spring启动的时候报错:because there is already a root application context present

问题背景:
spring mvc 整合spring security的时候出错

28-Apr-2018 10:20:22.518 严重 [localhost-startStop-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:262)
    at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:103)
    at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4743)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5207)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:752)
    at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:728)
    at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:734)
    at org.apache.catalina.startup.HostConfig.deployDescriptor(HostConfig.java:596)
    at org.apache.catalina.startup.HostConfig$DeployDescriptor.run(HostConfig.java:1805)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    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)

28-Apr-2018 10:20:22.526 信息 [localhost-startStop-1] org.apache.catalina.core.ApplicationContext.log Closing Spring root WebApplicationContext
28-Apr-2018 10:20:22.532 信息 [localhost-startStop-1] org.apache.catalina.core.ApplicationContext.log Closing Spring root WebApplicationContext

初始化spring 根root context的两种方法:

第一种:

通过web.xml配置,ContextLoaderListener通过初始化applicationContext.xml定义的spring配置文件。

    <context-param>  
        <param-name>contextConfigLocation</param-name>  
        <param-value>classpath*:applicationContext.xml  
        </param-value>  
    </context-param>  
    <listener>  
        <listener-class>org.springframework.web.context.ContextLoaderListener  
        </listener-class>  
    </listener> 
第二种:

通过继承AbstractAnnotationConfigDispatcherServletInitializer 来初始化AbstractContextLoaderInitializer,也就初始化了WebApplicationInitializer

public class MvcWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Nullable
    @Override
    protected Class<?>[] getRootConfigClasses() {

        return new Class[] {RootConfig.class};//初始化spring的根容器
        //RootConfig.class相当于平常配置的applicationContext.xml文件
    }

    @Nullable
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]
                {SpringMvcConfig.class};//初始化springMVC的子容器
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};//springMVC匹配的路径
    }
}

RootConfig.java

//spring 的applicationContext.xml的配置类,等同。
@Configuration
@ComponentScan(basePackages = {"com.unicom.si"},
        excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = EnableWebMvc.class)})//排除Controller类的扫描
public class RootConfig {

}

这两种方式,有一种就可以。如果两个都写上了,需要根据自己的情况,去掉一个,只保留一种方式。因为最终初始化根容器的ContextLoader中initWebApplicationContext方法初始化根上下文

 public WebApplicationContext initWebApplicationContext(ServletContext 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!");  
        }  

        Log logger = LogFactory.getLog(ContextLoader.class);  
        servletContext.log("Initializing Spring root WebApplicationContext");  
        if (logger.isInfoEnabled()) {  
            logger.info("Root WebApplicationContext: initialization started");  
        }  
        long startTime = System.currentTimeMillis();  
      //.....云 云 下边还有很多
  }

其中servletContext就是servlet中三大对象request、session、application中的最大的application对象。

整合spring security时同样的错误

我的错误在于,我使用了上边第二种AbstractAnnotationConfigDispatcherServletInitializer ,初始化了spring 的根容器。然后又在SecurityWebApplicationInitializer中又配置了初始化。
错误代码:

public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {

    //如果没有使用spring,没有spring MVC,只使用spring security
    //这个是需要的,因为super(WebSecurityConfig.class);也会初始化spring的根容器
    public SecurityWebApplicationInitializer() {
        super(WebSecurityConfig.class);
    }
}

翻看源代码你会发现super(WebSecurityConfig.class);其实调用的是父类的带参构造函数

//调用的是这个构造函数
protected AbstractSecurityWebApplicationInitializer(Class... configurationClasses) {
        this.configurationClasses = configurationClasses;
    }

    //这个是web应用初始化顶级接口WebApplicationInitializer中唯一的方法
    public final void onStartup(ServletContext servletContext) throws ServletException {
        this.beforeSpringSecurityFilterChain(servletContext);
        if (this.configurationClasses != null) {
            AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
            //可以看到,如果configurationClasses 不为空就会初始化一个rootAppContext。configurationClasses 就是上边super传进来的
            rootAppContext.register(this.configurationClasses);
            servletContext.addListener(new ContextLoaderListener(rootAppContext));
        }

        if (this.enableHttpSessionEventPublisher()) {
            servletContext.addListener("org.springframework.security.web.session.HttpSessionEventPublisher");
        }

        servletContext.setSessionTrackingModes(this.getSessionTrackingModes());
        this.insertSpringSecurityFilterChain(servletContext);
        this.afterSpringSecurityFilterChain(servletContext);
    }

补充一个AbstractAnnotationConfigDispatcherServletInitializer也就是上边初始化spring根容器的类,有个方法

 @Nullable
    protected WebApplicationContext createRootApplicationContext() {
        Class<?>[] configClasses = this.getRootConfigClasses();
        if (!ObjectUtils.isEmpty(configClasses)) {
            AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
            //同样的一句,也是注册根容器
            context.register(configClasses);
            return context;
        } else {
            return null;
        }
    }

从上边的分析可以看出,这就是注册重复了根容器。解决办法就是SecurityWebApplicationInitializer注解掉super构造函数

public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {

//或者干脆都删掉,留一个干净清爽的类
//    public SecurityWebApplicationInitializer() {
//        super(WebSecurityConfig.class);
//    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值