在web容器中获取spring容器中的bean对象

最近在做一个插件开发,即动态的上传插件之后,插件能够解决我们的需求,但是具体的处理方法处理的过程中需要对数据库进行操作,由于使用的是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";
}

猜你喜欢

转载自hbiao68.iteye.com/blog/2028169
今日推荐