如何同时使用SessionManager与AbstractAnnotationConfigDispatcherServletInitializer避免多ContextLoader*定义?
现有以下两段代码:
public class SpringMVCWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class[] { LoginApplicationConfig.class }; } @Override protected Class<?>[] getServletConfigClasses() { return null; } @Override protected String[] getServletMappings() { return new String[] { "/" }; } } public class Initializer extends AbstractHttpSessionApplicationInitializer { public Initializer() { super(Config.class); } }
请问如何在不产生多个ContextLoader*定义的前提下,同时使用SessionManager与AbstractAnnotationConfigDispatcherServletInitializer?
核心问题在于AbstractAnnotationConfigDispatcherServletInitializer和AbstractHttpSessionApplicationInitializer都会触发Spring根上下文的初始化,分开使用会导致多个根上下文被创建,进而出现ContextLoader*重复定义的冲突。我们可以通过合并初始化逻辑来解决这个问题,这里提供两种常用且可靠的方式:
方式一:让MVC初始化器继承AbstractHttpSessionApplicationInitializer
直接修改你的SpringMVCWebAppInitializer,让它同时承担Session管理器的初始化职责,这样所有上下文初始化逻辑都在一个类中完成,从根源避免重复创建上下文:
public class SpringMVCWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { // 构造方法传入Session相关配置类,替代原有的Initializer类 public SpringMVCWebAppInitializer() { super(Config.class); } @Override protected Class<?>[] getRootConfigClasses() { // 把LoginApplicationConfig和Session的Config合并到根上下文配置中 return new Class[] { LoginApplicationConfig.class, Config.class }; } @Override protected Class<?>[] getServletConfigClasses() { return null; // 若需要Servlet级别(比如MVC专属)的配置,可在此指定,否则保持null } @Override protected String[] getServletMappings() { return new String[] { "/" }; } }
之后你可以删除原来独立的Initializer类,这样整个应用只会初始化一个根上下文,自然不会出现ContextLoader*重复定义的问题。
方式二:手动注册Session配置,避免自动上下文重复创建
如果不想修改现有初始化器的继承结构,可以在SpringMVCWebAppInitializer的onStartup方法中手动注册Session相关配置,绕过AbstractHttpSessionApplicationInitializer的自动上下文创建逻辑:
public class SpringMVCWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override public void onStartup(ServletContext servletContext) throws ServletException { // 先执行父类的onStartup,完成MVC上下文的初始化 super.onStartup(servletContext); // 手动初始化Session相关的根上下文,避免重复创建 AnnotationConfigWebApplicationContext sessionContext = new AnnotationConfigWebApplicationContext(); sessionContext.register(Config.class); // 检查是否已有ContextLoaderListener,防止重复注册 if (servletContext.getAttribute(ContextLoader.CONTEXT_INITIALIZER_CLASSES) == null) { servletContext.addListener(new ContextLoaderListener(sessionContext)); } } @Override protected Class<?>[] getRootConfigClasses() { return new Class[] { LoginApplicationConfig.class }; } @Override protected Class<?>[] getServletConfigClasses() { return null; } @Override protected String[] getServletMappings() { return new String[] { "/" }; } }
不过这种方式需要额外做重复检查,逻辑相对繁琐,更推荐第一种方式——它更符合Spring的设计习惯,代码结构也更清晰。
最后要确认你的Config.class中已经正确配置了Session管理器,比如使用@EnableSpringHttpSession注解,或者指定了合适的Session存储实现,这样才能和MVC上下文无缝整合。
内容的提问来源于stack exchange,提问作者Jim Krikris

