一、ServletContext对象获取Demo
Servlet容器在启动时会加载Web应用,并为每个Web应用创建唯一的ServletContext对象。
可以把ServletContext看作一个Web应用的服务器端组件的共享内存。在ServletContext中可以存放共享数据,有4个读取或者设置共享数据的方法:
方法名 | 描述 |
setAttribute(String name, Object object) | 把一个对象和属性名绑定,并将这个对象存放在ServletContext中 |
getAttribute(String name) | 根据给定的属性名返回所绑定的对象 |
removeAttribute(String name) | 根据给定的属性名从ServletContext中删除相应的属性 |
getAttributeNames() | 返回一个Enumeration对象,包含了存储在ServletContext对象中所有的属性名 |
CounterServlet.java
1 package com.servletContext.demo; 2 3 import java.io.IOException; 4 5 import javax.servlet.ServletConfig; 6 import javax.servlet.ServletContext; 7 import javax.servlet.ServletException; 8 import javax.servlet.http.HttpServlet; 9 import javax.servlet.http.HttpServletRequest; 10 import javax.servlet.http.HttpServletResponse; 11 12 public class CounterServlet extends HttpServlet { 13 14 @Override 15 public void init(ServletConfig config) throws ServletException { 16 super.init(config); 17 } 18 19 @Override 20 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 21 doPost(req, resp); 22 } 23 24 @Override 25 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 26 27 // 获得ServletContext的引用 28 ServletContext context = getServletContext(); 29 30 // 从ServletContext读取count属性 31 Integer count = (Integer) context.getAttribute("count"); 32 33 34 // 如果没有读到count属性,那么创建count属性,并设置初始值为0 35 if(count == null) { 36 System.out.println("context中还没有count属性呢"); 37 count = new Integer(0); 38 context.setAttribute("count", count); 39 } 40 count = count + 1; 41 // count增加之后还要写回去,引用为什么还要重新存回去 42 context.setAttribute("count", count); 43 System.out.println("您是第" + count + "个访问的!"); 44 45 } 46 47 @Override 48 public void destroy() { 49 super.destroy(); 50 } 51 52 }
从上述代码中可见通过getServletContext()方法可以直接获得ServletContext的引用。
二、Spring和ServletContext的关系
缘何这两货会扯上关系呢?
在使用Spring的时候想必对如下代码肯定熟悉:
1 // 获取Spring容器 2 ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml"); 3 4 // 从Spring容器中根据id获得对象的引用 5 User user = (User) ctx.getBean("user"); 6 7 // 调用对象的方法 8 user.add();
这样做是最低级的,也就是通过加载配置文件来获得Spring容器,再来获取对象的应用,在Web项目中,每次都通过加载配置文件显得效率低下,而且繁琐,这里介绍一种另外的方法。想在Web项目启动的时候就把Spring容器也给启动了,不用每次都手动去启动。
这里就用到了上面介绍的ServletContext了,每次Web项目启动的时候都会创建ServletContext对象,而该对象又有一个ServletContextListener的接口,监视ServletContext的创建,这样就可以调用这个接口的回调方法来启动Spring容器了。(但是这里我有个疑问,随着项目启动的不止有ServletContext啊,过滤器好像也随着项目启动,为啥不在过滤器的init()方法里面启动Spring容器呢?)
先来看看这个接口是啥定义:
1 package javax.servlet; 2 3 import java.util.EventListener; 4 5 /** 6 * Implementations of this interface receive notifications about changes to the 7 * servlet context of the web application they are part of. To receive 8 * notification events, the implementation class must be configured in the 9 * deployment descriptor for the web application. 10 */ 11 12 public interface ServletContextListener extends EventListener { 13 14 /** 15 ** Notification that the web application initialization process is starting. 16 * All ServletContextListeners are notified of context initialization before 17 * any filter or servlet in the web application is initialized. 18 */ 19 public void contextInitialized(ServletContextEvent sce); 20 21 /** 22 ** Notification that the servlet context is about to be shut down. All 23 * servlets and filters have been destroy()ed before any 24 * ServletContextListeners are notified of context destruction. 25 */ 26 public void contextDestroyed(ServletContextEvent sce); 27 } 28
第一段注释描述的是:这个接口的实现接受和Web应用关联的servlet context的变更的通知。为了接受通知事件,这个类的实现必须在web应用的部署描述符配置。
第二段注释的描述是:通知是在Web应用初始化的时候开始的。所有的ServletContextListeners都会在web应用中任何的filter和servlet初始话之前接收到context初始化的时候通知。
第三段注释的描述是:servlet context将要被关闭的时候的通知。所有的filter和servlet会在任何ServletContextListeners收到context销毁的通知之前就被销毁了。
另外再来看看ServeletContextEvent.java
1 package javax.servlet; 2 3 /** 4 * This is the event class for notifications about changes to the servlet 5 * context of a web application. 6 * 7 * @see ServletContextListener 8 * @since v 2.3 9 */ 10 public class ServletContextEvent extends java.util.EventObject { 11 12 private static final long serialVersionUID = 1L; 13 14 /** 15 * Construct a ServletContextEvent from the given context. 16 * 17 * @param source 18 * - the ServletContext that is sending the event. 19 */ 20 public ServletContextEvent(ServletContext source) { 21 super(source); 22 } 23 24 /** 25 * Return the ServletContext that changed. 26 * 27 * @return the ServletContext that sent the event. 28 */ 29 public ServletContext getServletContext() { 30 return (ServletContext) super.getSource(); 31 } 32 }
public ServletContextEvent(ServletContext source);这个方法是从一个给定的ServletContext构建一个ServletContextEvent。而public ServletContext getServletContext();则是返回已经改变的ServletContext,暂时不知道有啥用,是不是给监听器塞ServletContext用的啊?
想自己也写一个ServletContextListener呢!
1 package com.servletContext.demo; 2 3 import javax.servlet.ServletContext; 4 import javax.servlet.ServletContextEvent; 5 import javax.servlet.ServletContextListener; 6 7 public class MyServletContextListener implements ServletContextListener { 8 9 @Override 10 public void contextInitialized(ServletContextEvent sce) { 11 12 // 从web.xml中拿出添加的参数 13 ServletContext ctx = sce.getServletContext(); 14 String initParam = ctx.getInitParameter("myContextListener"); 15 System.out.println("我配置的初始化参数为:" + initParam); 16 17 // 利用初始化参数找到配置文件机型初始化 18 System.out.println("context初始化了咯"); 19 System.out.println("这里假装初始化Spring容器....."); 20 21 } 22 23 @Override 24 public void contextDestroyed(ServletContextEvent sce) { 25 26 // 在销毁之前获得ServletContext 27 ServletContext ctx = sce.getServletContext(); 28 29 // 正好刚刚存了一个值进去了,销毁之前拿出来瞅瞅 30 Integer count = (Integer) ctx.getAttribute("count"); 31 32 System.out.println("在销毁之前,count的值为:" + count); 33 34 } 35 36 } 37
这他喵的居然真的可以!
web.xml为:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> 3 <display-name>ServletContext</display-name> 4 <welcome-file-list> 5 <welcome-file>index.html</welcome-file> 6 <welcome-file>index.htm</welcome-file> 7 <welcome-file>index.jsp</welcome-file> 8 <welcome-file>default.html</welcome-file> 9 <welcome-file>default.htm</welcome-file> 10 <welcome-file>default.jsp</welcome-file> 11 </welcome-file-list> 12 13 <!-- 假装为Spring监听器提供启动参数,其实是给ServletContext提供的 --> 14 <context-param> 15 <param-name>myContextListener</param-name> 16 <!-- 这里如果bean.xml在包cn.ssh下,那么就应该写为:cn/ssh/bean.xml --> 17 <param-value>这是我设置的值</param-value> 18 </context-param> 19 20 <!-- 配置Spring的监听器 --> 21 <listener> 22 <listener-class>com.servletContext.demo.MyServletContextListener</listener-class> 23 </listener> 24 25 <servlet> 26 <servlet-name>count</servlet-name> 27 <servlet-class>com.servletContext.demo.CounterServlet</servlet-class> 28 </servlet> 29 <servlet-mapping> 30 <servlet-name>count</servlet-name> 31 <url-pattern>/counter</url-pattern> 32 </servlet-mapping> 33 34 </web-app>
测试结果为:
看来真的是可以了,这里关闭服务器的时候Console中的内容也被清除了,暂时没有看到ServletContext销毁时的消息。
Spring提供的是ContextLoaderListener,这个监听器实现了ServletContextListener接口,可以作为Listener使用,它会在创建的时候自动查找WEB-INF/下的applicationContext.xml文件,因此,如果只有一个配置文件,并且文件名为applicationContext.xml,则只需要在web.xml中加入对Listener的配置就可以。
如果有多个配置文件需要加载,则要考虑使用<context-param.../>元素来确定配置文件的文件名。ContextLoaderListener加载的时候,会查找名为contextConfigLocation的初始化参数。因此<context-param.../>时应该指定参数名为contextConfigLocation。
1 <!-- 为Spring监听器提供启动参数 --> 2 <context-param> 3 <param-name>contextConfigLocation</param-name> 4 <!-- 这里如果bean.xml在包cn.ssh下,那么就应该写为:cn/ssh/bean.xml --> 5 <param-value>classpath:bean.xml</param-value> 6 </context-param> 7 8 <!-- 配置Spring的监听器 --> 9 <listener> 10 <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 11 </listener>
如果没有使用contextConfigLocation指定配置文件,则Spring自动查找applicationContext.xml配置文件;如果有contextConfigLocation,则利用该参数确定配置文件。如果无法找到适合的配置文件,Spring将无法初始化。
Spring根据指定的文件创建WebApplicationContext对象,并将其保存在Web应用的ServletContext中。大部分情况下,应用中的Bean无需感受到ApplicationContext的存在,只要用ApplicationContext中的IoC即可。
这个监听器所在的jar包为:
如果需要利用ApplicationContext的实例,可以通过如下代码获取:
1 package com.ssh.domain; 2 3 import javax.servlet.ServletContext; 4 import javax.servlet.http.HttpServletRequest; 5 6 import org.apache.struts2.ServletActionContext; 7 import org.springframework.web.context.WebApplicationContext; 8 import org.springframework.web.context.support.WebApplicationContextUtils; 9 10 import com.opensymphony.xwork2.ActionSupport; 11 import com.ssh.test.TestAdd; 12 13 public class TestAction extends ActionSupport { 14 15 @Override 16 public String execute() throws Exception { 17 18 HttpServletRequest request = ServletActionContext.getRequest(); 19 ServletContext servletContext = request.getServletContext(); 20 // 这里不是通过依赖注入,而是直接从容器中拿 21 WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext); 22 23 // 也可以是下面这样的 24 WebApplicationContext ctx1 = (WebApplicationContext) 25 servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); 26 27 if(ctx == ctx1) { 28 System.out.println("两次获得对象是一样的"); 29 } 30 31 TestAdd testAdd = (TestAdd) ctx.getBean("testAdd"); 32 testAdd.add(); 33 34 return NONE; 35 } 36 }
TestAdd.java
1 package com.ssh.test; 2 3 public class TestAdd { 4 5 public void add( ) { 6 System.out.println("通过WebContext获得的而打印...."); 7 } 8 9 }
测试结果为:http://localhost:8080/spring_struts2/testAction
三、瞧瞧Spring提供的ContextLoaderListener代码是咋写的
打开源码,就蛋疼了,有封装了一下:
1 package org.springframework.web.context; 2 3 import javax.servlet.ServletContextEvent; 4 import javax.servlet.ServletContextListener; 5 6 /** 7 * Bootstrap listener to start up and shut down Spring's root {@link WebApplicationContext}. 8 * Simply delegates to {@link ContextLoader} as well as to {@link ContextCleanupListener}. 9 * 10 * <p>This listener should be registered after {@link org.springframework.web.util.Log4jConfigListener} 11 * in {@code web.xml}, if the latter is used. 12 * 13 * <p>As of Spring 3.1, {@code ContextLoaderListener} supports injecting the root web 14 * application context via the {@link #ContextLoaderListener(WebApplicationContext)} 15 * constructor, allowing for programmatic configuration in Servlet 3.0+ environments. 16 * See {@link org.springframework.web.WebApplicationInitializer} for usage examples. 17 * 18 * @author Juergen Hoeller 19 * @author Chris Beams 20 * @since 17.02.2003 21 * @see #setContextInitializers 22 * @see org.springframework.web.WebApplicationInitializer 23 * @see org.springframework.web.util.Log4jConfigListener 24 */ 25 public class ContextLoaderListener extends ContextLoader implements ServletContextListener { 26 27 /** 28 * Create a new {@code ContextLoaderListener} that will create a web application 29 * context based on the "contextClass" and "contextConfigLocation" servlet 30 * context-params. See {@link ContextLoader} superclass documentation for details on 31 * default values for each. 32 * <p>This constructor is typically used when declaring {@code ContextLoaderListener} 33 * as a {@code <listener>} within {@code web.xml}, where a no-arg constructor is 34 * required. 35 * <p>The created application context will be registered into the ServletContext under 36 * the attribute name {@link WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE} 37 * and the Spring application context will be closed when the {@link #contextDestroyed} 38 * lifecycle method is invoked on this listener. 39 * @see ContextLoader 40 * @see #ContextLoaderListener(WebApplicationContext) 41 * @see #contextInitialized(ServletContextEvent) 42 * @see #contextDestroyed(ServletContextEvent) 43 */ 44 public ContextLoaderListener() { 45 } 46 47 /** 48 * Create a new {@code ContextLoaderListener} with the given application context. This 49 * constructor is useful in Servlet 3.0+ environments where instance-based 50 * registration of listeners is possible through the {@link javax.servlet.ServletContext#addListener} 51 * API. 52 * <p>The context may or may not yet be {@linkplain 53 * org.springframework.context.ConfigurableApplicationContext#refresh() refreshed}. If it 54 * (a) is an implementation of {@link ConfigurableWebApplicationContext} and 55 * (b) has <strong>not</strong> already been refreshed (the recommended approach), 56 * then the following will occur: 57 * <ul> 58 * <li>If the given context has not already been assigned an {@linkplain 59 * org.springframework.context.ConfigurableApplicationContext#setId id}, one will be assigned to it</li> 60 * <li>{@code ServletContext} and {@code ServletConfig} objects will be delegated to 61 * the application context</li> 62 * <li>{@link #customizeContext} will be called</li> 63 * <li>Any {@link org.springframework.context.ApplicationContextInitializer ApplicationContextInitializer}s 64 * specified through the "contextInitializerClasses" init-param will be applied.</li> 65 * <li>{@link org.springframework.context.ConfigurableApplicationContext#refresh refresh()} will be called</li> 66 * </ul> 67 * If the context has already been refreshed or does not implement 68 * {@code ConfigurableWebApplicationContext}, none of the above will occur under the 69 * assumption that the user has performed these actions (or not) per his or her 70 * specific needs. 71 * <p>See {@link org.springframework.web.WebApplicationInitializer} for usage examples. 72 * <p>In any case, the given application context will be registered into the 73 * ServletContext under the attribute name {@link 74 * WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE} and the Spring 75 * application context will be closed when the {@link #contextDestroyed} lifecycle 76 * method is invoked on this listener. 77 * @param context the application context to manage 78 * @see #contextInitialized(ServletContextEvent) 79 * @see #contextDestroyed(ServletContextEvent) 80 */ 81 public ContextLoaderListener(WebApplicationContext context) { 82 super(context); 83 } 84 85 86 /** 87 * Initialize the root web application context. 88 */ 89 @Override 90 public void contextInitialized(ServletContextEvent event) { 91 initWebApplicationContext(event.getServletContext()); 92 } 93 94 95 /** 96 * Close the root web application context. 97 */ 98 @Override 99 public void contextDestroyed(ServletContextEvent event) { 100 closeWebApplicationContext(event.getServletContext()); 101 ContextCleanupListener.cleanupAttributes(event.getServletContext()); 102 } 103 104 }
源码果然是个好东西,平时敲代码那会注意到这么多细节
这个类不复杂,两个构造方法,外加一个初始化的时候创建Spring容器和服务关闭的时候对容器的清理,封装了之后还要看其他的类,哎。
首先第一段注释是对这个类的描述:
这个启动监听器是用开启和关闭Spring的root的,这里他用了root而不是容器。简单的代理给了ContextLoader和ContextCleanupListener这两个类来处理。
如果这个org.springframework.web.util.Log4jConfigListener被用到了,那么ContextLoaderListener应该在它之后注册。
在Spring3.1中,ContextLoaderListener支持通过ContextLoaderListener(WebApplicationContext)这个构造方法向应用上下文中注入root(也就是Spring的容器),这样可以以编程的方式来配置Servlet 3.0+的环境。
第二段注释是,新建一个ContextLoaderListener的类将会基于Servlet的"contextClass"和"contextCofigLocation"这两个参数来创建web应用的上下文。
翻译的好累啊,反正意思差不多就是这样5555....
来看这段代码:
1 /** 2 * Initialize the root web application context. 3 */ 4 @Override 5 public void contextInitialized(ServletContextEvent event) { 6 initWebApplicationContext(event.getServletContext()); 7 }
这个initWebApplicationContext方法是ContextLoad.java这个类里面的方法。
1 /** 2 * Initialize Spring's web application context for the given servlet context, 3 * using the application context provided at construction time, or creating a new one 4 * according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and 5 * "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params. 6 * @param servletContext current servlet context 7 * @return the new WebApplicationContext 8 * @see #ContextLoader(WebApplicationContext) 9 * @see #CONTEXT_CLASS_PARAM 10 * @see #CONFIG_LOCATION_PARAM 11 */ 12 public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { 13 if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) { 14 throw new IllegalStateException( 15 "Cannot initialize context because there is already a root application context present - " + 16 "check whether you have multiple ContextLoader* definitions in your web.xml!"); 17 } 18 19 Log logger = LogFactory.getLog(ContextLoader.class); 20 servletContext.log("Initializing Spring root WebApplicationContext"); 21 if (logger.isInfoEnabled()) { 22 logger.info("Root WebApplicationContext: initialization started"); 23 } 24 long startTime = System.currentTimeMillis(); 25 26 try { 27 // Store context in local instance variable, to guarantee that 28 // it is available on ServletContext shutdown. 29 if (this.context == null) { 30 this.context = createWebApplicationContext(servletContext); 31 } 32 if (this.context instanceof ConfigurableWebApplicationContext) { 33 ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context; 34 if (!cwac.isActive()) { 35 // The context has not yet been refreshed -> provide services such as 36 // setting the parent context, setting the application context id, etc 37 if (cwac.getParent() == null) { 38 // The context instance was injected without an explicit parent -> 39 // determine parent for root web application context, if any. 40 ApplicationContext parent = loadParentContext(servletContext); 41 cwac.setParent(parent); 42 } 43 configureAndRefreshWebApplicationContext(cwac, servletContext); 44 } 45 } 46 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); 47 48 ClassLoader ccl = Thread.currentThread().getContextClassLoader(); 49 if (ccl == ContextLoader.class.getClassLoader()) { 50 currentContext = this.context; 51 } 52 else if (ccl != null) { 53 currentContextPerThread.put(ccl, this.context); 54 } 55 56 if (logger.isDebugEnabled()) { 57 logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" + 58 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]"); 59 } 60 if (logger.isInfoEnabled()) { 61 long elapsedTime = System.currentTimeMillis() - startTime; 62 logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms"); 63 } 64 65 return this.context; 66 } 67 catch (RuntimeException ex) { 68 logger.error("Context initialization failed", ex); 69 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex); 70 throw ex; 71 } 72 catch (Error err) { 73 logger.error("Context initialization failed", err); 74 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err); 75 throw err; 76 } 77 }
ContextLoad.initWebApplicationContext是为给定的servlet context来初始化web应用的上下文的。
业务逻辑解读:
首先从ServletContext中看看有没有Spring创建的这个容器;
然后为ContextLoader存一份实例变量,使得在ServletContext关闭之后仍可以访问;
1 this.context = createWebApplicationContext(servletContext);
这句就是创建一个WebApplicationContext相当于我们自己加载配置文件的那个类。
1 configureAndRefreshWebApplicationContext(cwac, servletContext);
这句话也很明显,就是配置并且刷新WebAppCtx的。
1 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
这句将创建的Spring的context作为属性放到servletContext中。
1 return this.context;
然后就返回了Spring的容器了.....是不是简洁(装逼装不下去了),调用链好长。
暂时只能分析到这里!