问题背景:
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
通过web.xml配置,ContextLoaderListener通过初始化applicationContext.xml定义的spring配置文件。
<context-param>
<param-name>contextConfigLocationparam-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对象。
我的错误在于,我使用了上边第二种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);
// }
}