最近在做一个插件开发,即动态的上传插件之后,插件能够解决我们的需求,但是具体的处理方法处理的过程中需要对数据库进行操作,由于使用的是springMVC,在service层中配置了相关的dao层,因此只需要能够获取service层对象即可,但是这个对象必须由spring来提供,否则service对象里面的属性需要逐一实例化。
首先,我们需要了解ContextLoaderListener与DispatcherServlet所加载的applicationContext的区别?
参考:http://blog.163.com/sir_876/blog/static/11705223201111544523333/
spring通过在web.xml 中配置ContextLoaderListener 来加载context配置文件,在DispatcherServlet中也可以来加载spring context配置文件,那么这两个有什么区别呢?
ContextLoaderListener中加载的context成功后,spring 将 applicationContext存放在ServletContext中key值为"org.springframework.web.context.WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE"的attribute 中。
下面是我看ContextLoader类的源代码的解析:
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { // 在系统加载的时候servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)的值为null 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(org / springframework / web / context / ContextLoader); servletContext.log("Initializing Spring root WebApplicationContext"); if (logger.isInfoEnabled()) logger.info("Root WebApplicationContext: initialization started"); long startTime = System.currentTimeMillis(); try { // private WebApplicationContext context; // public interface WebApplicationContext extends ApplicationContext // WebApplicationContext是ApplicationContext的子类 if (context == null) // servletContext这个是web.xml配置文件中的信息,其中就包含是否配置了contextConfigLocation参数 // 如果有则读取指定位置的配置文件,如果没有则从默认的位置找配置文件,如果没有则会报错 /* * <context-param> * <param-name>contextConfigLocation</param-name> <param-value> * /WEB-INF/config/spring/spring_application_*.xml, * /WEB-INF/myspring-servlet.xml </param-value> </context-param> */ // 现在的context对象已经有了ApplicationContext对象的功能,即可以生成spring容器中定义的bean对象 context = createWebApplicationContext(servletContext); if (context instanceof ConfigurableWebApplicationContext) configureAndRefreshWebApplicationContext((ConfigurableWebApplicationContext) context, servletContext); // 将已经有了ApplicationContext功能的对象存放在servletContext对象中,key的值就是WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE // 这个对象在整个应用系统的任何时刻任何地方都可以访问,相当于一个共享的资源池 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, context); ClassLoader ccl = Thread.currentThread().getContextClassLoader(); if (ccl == org / springframework / web / context / ContextLoader.getClassLoader()) currentContext = context; else if (ccl != null) currentContextPerThread.put(ccl, context); if (logger.isDebugEnabled()) logger.debug((new StringBuilder("Published root WebApplicationContext as ServletContext attribute with name [")).append(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) .append("]").toString()); if (logger.isInfoEnabled()) { long elapsedTime = System.currentTimeMillis() - startTime; logger.info((new StringBuilder("Root WebApplicationContext: initialization completed in ")).append(elapsedTime).append(" ms").toString()); } return context; } catch (RuntimeException ex) { logger.error("Context initialization failed", ex); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex); throw ex; } catch (Error err) { logger.error("Context initialization failed", err); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err); throw err; } }
因此有如下三种方法来获取ApplicationContext对象
1、servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
2、WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext)
3、WebApplicationContextUtils.getWebApplicationContext(servletContext)
HttpServlet --> HttpServletBean --> FrameworkServlet --> DispatcherServlet
在web.xml文件中配置了如下代码:
<!-- 使用spring 的MVC来管理 --> <servlet> <servlet-name>myspring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>myspring</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping>
也就是说会执行servlet的init()方法,而该方法在HttpServletBean类中,在init()该方法中有一个initServletBean()方法,这个initServletBean()方法被FrameworkServlet类重写,在initServletBean()中会调用initWebApplicationContext()方法,下面看看initWebApplicationContext()里面的详细内容:
protected WebApplicationContext initWebApplicationContext() {
//得到web.xml中的上下文
//这里是为了兼容ContextLoaderListener类已经获取到spring的applicationContext了,也就没有必要再重新加载一次spring的配置文件了。
WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null; if (webApplicationContext != null) { wac = webApplicationContext; if (wac instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac; if (!cwac.isActive()) { if (cwac.getParent() == null) cwac.setParent(rootContext); configureAndRefreshWebApplicationContext(cwac); } } } // 如果没有配置ContextLoaderListener类,则需要自己加载spring的配置文件了 if (wac == null) // 查看能否找到applicationContext对象 wac = findWebApplicationContext(); if (wac == null) // 找不到ApplicationContext对象就自己创建一个ApplicationContext对象 wac = createWebApplicationContext(rootContext); if (!refreshEventReceived) onRefresh(wac); if (publishContext) { // 这里得到的值是org.springframework.web.servlet.FrameworkServlet.CONTEXT.{web.xml配置文件中DispatcherServlet类servlet的名字} // 因此,在我的实例中的名字是org.springframework.web.servlet.FrameworkServlet.CONTEXT.myspring String attrName = getServletContextAttributeName(); // 将ApplicationContext对象存储在ServletContext上下文中,key的值为attrName getServletContext().setAttribute(attrName, wac); if (logger.isDebugEnabled()) logger.debug((new StringBuilder("Published WebApplicationContext of servlet '")).append(getServletName()).append("' as ServletContext attribute with name [").append(attrName) .append("]").toString()); } return wac;
在每次request请求时,DispatcherServlet会将此applicationContext存放在request中attribute 值为 org.springframework.web.servlet.DispatcherServlet.CONTEXT中 (request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE,getWebApplicationContext());)。
可以通过如下两种方式来获取ApplicationContext对象
1、 RequestContextUtils.getWebApplicationContext(ServletRequest request)
2、 WebApplicationContextUtils.getWebApplicationContext(servletContext,attrname)
备注:attrname实际上就是org.springframework.web.servlet.FrameworkServlet.CONTEXT.{web.xml配置文件中DispatcherServlet类servlet的名字}
从上面的分析可以看出,DispatcherServlet所加载的applicationContext可以认为是mvc私有的context,由于保存在servletContext中的key值与通过ContextLoaderListener加载进来的applicationContext使用的 key值不相同,因此如果只使用DispatcherServlet加载context的话,如果程序中有地方使用 WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext) 来试图获取applicationContext时,就会抛出"No WebApplicationContext found: no ContextLoaderListener registered?"的exception。
上面提到了很多ServletContext类,那么下面就解释一下他的功能:
ServletContext : 每一个web应用都有一个 ServletContext与之相关联。 ServletContext对象在应用启动的被创建,在应用关闭的时候被销毁。 ServletContext在全局范围内有效,类似于应用中的一个全局变量。
ServletContextListener: 使用listener接口,开发者能够在为客户端请求提供服务之前向ServletContext中添加任意的对象。这个对象在ServletContext启动的时候被初始化,然后在ServletContext整个运行期间都是可见的。
在 Servlet API 中有一个 ServletContextListener 接口,它能够监听 ServletContext 对象的生命周期,实际上就是监听 Web 应用的生命周期。
当Servlet 容器启动或终止Web 应用时,会触发ServletContextEvent 事件,该事件由ServletContextListener 来处理。在 ServletContextListener 接口中定义了处理ServletContextEvent 事件的两个方法。
解决办法:
在web容器中添加一个继承ServletContextListener接口的对象,然后给这个类添加一个ServletContext属性,在系统启动的时候就赋值给它。
import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import org.springframework.context.ApplicationContext; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; public class SpringInit implements ServletContextListener { private static WebApplicationContext springContext; public SpringInit() { super(); } public void contextInitialized(ServletContextEvent event) { springContext = WebApplicationContextUtils.getWebApplicationContext(event.getServletContext()); } public void contextDestroyed(ServletContextEvent event) { } public static ApplicationContext getApplicationContext() { return springContext; } }
备注:之所以将 ServletContext 属性设置为 static 是因为在不同的 servlet 对象中获取 MessageInteceptorFilter 对象的属性是获取不到的,因此将其设置为类的属性,如果MessageInteceptorFilter类存在就能够找到这个属性的具体值。
下面这段代码是在网上找的
import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import org.springframework.context.ApplicationContext; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; public class SpringInit implements ServletContextListener { public SpringInit() { super(); } //获取spring的applicationContext public void contextInitialized(ServletContextEvent event) { springContext = WebApplicationContextUtils.getWebApplicationContext(event.getServletContext()); //ServletContext对象在应用启动的被创建,在应用关闭的时候被销毁。 ServletContext在全局范围内有效,类似于应用中的一个全局变量。 event.getServletContext().setAttribute("springContext",springContext); } public void contextDestroyed(ServletContextEvent event) { } } 获取spring容器中的对象 @RequestMapping(value = "/sendMessageFromToCont") public String sendMessageFromToCont(HttpServletRequest request, HttpServletResponse response) { request.getSession().getServletContext().getAttribute("springContext"); return "adtec_message/imjwchat-testim"; }