一、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

 package com.servletContext.demo;

 import java.io.IOException;

 import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; public class CounterServlet extends HttpServlet { @Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
} @Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
} @Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 获得ServletContext的引用
ServletContext context = getServletContext(); // 从ServletContext读取count属性
Integer count = (Integer) context.getAttribute("count"); // 如果没有读到count属性,那么创建count属性,并设置初始值为0
if(count == null) {
System.out.println("context中还没有count属性呢");
count = new Integer(0);
context.setAttribute("count", count);
}
count = count + 1;
// count增加之后还要写回去,引用为什么还要重新存回去
context.setAttribute("count", count);
System.out.println("您是第" + count + "个访问的!"); } @Override
public void destroy() {
super.destroy();
} }

从上述代码中可见通过getServletContext()方法可以直接获得ServletContext的引用。

二、Spring和ServletContext的关系

缘何这两货会扯上关系呢?

在使用Spring的时候想必对如下代码肯定熟悉:

 // 获取Spring容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml"); // 从Spring容器中根据id获得对象的引用
User user = (User) ctx.getBean("user"); // 调用对象的方法
user.add();

这样做是最低级的,也就是通过加载配置文件来获得Spring容器,再来获取对象的应用,在Web项目中,每次都通过加载配置文件显得效率低下,而且繁琐,这里介绍一种另外的方法。想在Web项目启动的时候就把Spring容器也给启动了,不用每次都手动去启动。

这里就用到了上面介绍的ServletContext了,每次Web项目启动的时候都会创建ServletContext对象,而该对象又有一个ServletContextListener的接口,监视ServletContext的创建,这样就可以调用这个接口的回调方法来启动Spring容器了。(但是这里我有个疑问,随着项目启动的不止有ServletContext啊,过滤器好像也随着项目启动,为啥不在过滤器的init()方法里面启动Spring容器呢?)

先来看看这个接口是啥定义:

 package javax.servlet;

 import java.util.EventListener;

 /**
* Implementations of this interface receive notifications about changes to the
* servlet context of the web application they are part of. To receive
* notification events, the implementation class must be configured in the
* deployment descriptor for the web application.
*/ public interface ServletContextListener extends EventListener { /**
** Notification that the web application initialization process is starting.
* All ServletContextListeners are notified of context initialization before
* any filter or servlet in the web application is initialized.
*/
public void contextInitialized(ServletContextEvent sce); /**
** Notification that the servlet context is about to be shut down. All
* servlets and filters have been destroy()ed before any
* ServletContextListeners are notified of context destruction.
*/
public void contextDestroyed(ServletContextEvent sce);
}

第一段注释描述的是:这个接口的实现接受和Web应用关联的servlet context的变更的通知。为了接受通知事件,这个类的实现必须在web应用的部署描述符配置。

第二段注释的描述是:通知是在Web应用初始化的时候开始的。所有的ServletContextListeners都会在web应用中任何的filter和servlet初始话之前接收到context初始化的时候通知。

第三段注释的描述是:servlet context将要被关闭的时候的通知。所有的filter和servlet会在任何ServletContextListeners收到context销毁的通知之前就被销毁了。

另外再来看看ServeletContextEvent.java

 package javax.servlet;

 /**
* This is the event class for notifications about changes to the servlet
* context of a web application.
*
* @see ServletContextListener
* @since v 2.3
*/
public class ServletContextEvent extends java.util.EventObject { private static final long serialVersionUID = 1L; /**
* Construct a ServletContextEvent from the given context.
*
* @param source
* - the ServletContext that is sending the event.
*/
public ServletContextEvent(ServletContext source) {
super(source);
} /**
* Return the ServletContext that changed.
*
* @return the ServletContext that sent the event.
*/
public ServletContext getServletContext() {
return (ServletContext) super.getSource();
}
}

public ServletContextEvent(ServletContext source);这个方法是从一个给定的ServletContext构建一个ServletContextEvent。而public ServletContext getServletContext();则是返回已经改变的ServletContext,暂时不知道有啥用,是不是给监听器塞ServletContext用的啊?

想自己也写一个ServletContextListener呢!

 package com.servletContext.demo;

 import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener; public class MyServletContextListener implements ServletContextListener { @Override
public void contextInitialized(ServletContextEvent sce) { // 从web.xml中拿出添加的参数
ServletContext ctx = sce.getServletContext();
String initParam = ctx.getInitParameter("myContextListener");
System.out.println("我配置的初始化参数为:" + initParam); // 利用初始化参数找到配置文件机型初始化
System.out.println("context初始化了咯");
System.out.println("这里假装初始化Spring容器....."); } @Override
public void contextDestroyed(ServletContextEvent sce) { // 在销毁之前获得ServletContext
ServletContext ctx = sce.getServletContext(); // 正好刚刚存了一个值进去了,销毁之前拿出来瞅瞅
Integer count = (Integer) ctx.getAttribute("count"); System.out.println("在销毁之前,count的值为:" + count); } }

这他喵的居然真的可以!

web.xml为:

 <?xml version="1.0" encoding="UTF-8"?>
<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">
<display-name>ServletContext</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list> <!-- 假装为Spring监听器提供启动参数,其实是给ServletContext提供的 -->
<context-param>
<param-name>myContextListener</param-name>
<!-- 这里如果bean.xml在包cn.ssh下,那么就应该写为:cn/ssh/bean.xml -->
<param-value>这是我设置的值</param-value>
</context-param> <!-- 配置Spring的监听器 -->
<listener>
<listener-class>com.servletContext.demo.MyServletContextListener</listener-class>
</listener> <servlet>
<servlet-name>count</servlet-name>
<servlet-class>com.servletContext.demo.CounterServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>count</servlet-name>
<url-pattern>/counter</url-pattern>
</servlet-mapping> </web-app>

测试结果为:

看来真的是可以了,这里关闭服务器的时候Console中的内容也被清除了,暂时没有看到ServletContext销毁时的消息。

Spring提供的是ContextLoaderListener,这个监听器实现了ServletContextListener接口,可以作为Listener使用,它会在创建的时候自动查找WEB-INF/下的applicationContext.xml文件,因此,如果只有一个配置文件,并且文件名为applicationContext.xml,则只需要在web.xml中加入对Listener的配置就可以。

如果有多个配置文件需要加载,则要考虑使用<context-param.../>元素来确定配置文件的文件名。ContextLoaderListener加载的时候,会查找名为contextConfigLocation的初始化参数。因此<context-param.../>时应该指定参数名为contextConfigLocation。

 <!-- 为Spring监听器提供启动参数 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<!-- 这里如果bean.xml在包cn.ssh下,那么就应该写为:cn/ssh/bean.xml -->
<param-value>classpath:bean.xml</param-value>
</context-param> <!-- 配置Spring的监听器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

如果没有使用contextConfigLocation指定配置文件,则Spring自动查找applicationContext.xml配置文件;如果有contextConfigLocation,则利用该参数确定配置文件。如果无法找到适合的配置文件,Spring将无法初始化。

Spring根据指定的文件创建WebApplicationContext对象,并将其保存在Web应用的ServletContext中。大部分情况下,应用中的Bean无需感受到ApplicationContext的存在,只要用ApplicationContext中的IoC即可。

这个监听器所在的jar包为:

如果需要利用ApplicationContext的实例,可以通过如下代码获取:

 package com.ssh.domain;

 import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest; import org.apache.struts2.ServletActionContext;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils; import com.opensymphony.xwork2.ActionSupport;
import com.ssh.test.TestAdd; public class TestAction extends ActionSupport { @Override
public String execute() throws Exception { HttpServletRequest request = ServletActionContext.getRequest();
ServletContext servletContext = request.getServletContext();
// 这里不是通过依赖注入,而是直接从容器中拿
WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext); // 也可以是下面这样的
WebApplicationContext ctx1 = (WebApplicationContext)
servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); if(ctx == ctx1) {
System.out.println("两次获得对象是一样的");
} TestAdd testAdd = (TestAdd) ctx.getBean("testAdd");
testAdd.add(); return NONE;
}
}

TestAdd.java

 package com.ssh.test;

 public class TestAdd {

     public void add( ) {
System.out.println("通过WebContext获得的而打印....");
} }

测试结果为:http://localhost:8080/spring_struts2/testAction

三、瞧瞧Spring提供的ContextLoaderListener代码是咋写的

打开源码,就蛋疼了,有封装了一下:

 package org.springframework.web.context;

 import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener; /**
* Bootstrap listener to start up and shut down Spring's root {@link WebApplicationContext}.
* Simply delegates to {@link ContextLoader} as well as to {@link ContextCleanupListener}.
*
* <p>This listener should be registered after {@link org.springframework.web.util.Log4jConfigListener}
* in {@code web.xml}, if the latter is used.
*
* <p>As of Spring 3.1, {@code ContextLoaderListener} supports injecting the root web
* application context via the {@link #ContextLoaderListener(WebApplicationContext)}
* constructor, allowing for programmatic configuration in Servlet 3.0+ environments.
* See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
*
* @author Juergen Hoeller
* @author Chris Beams
* @since 17.02.2003
* @see #setContextInitializers
* @see org.springframework.web.WebApplicationInitializer
* @see org.springframework.web.util.Log4jConfigListener
*/
public class ContextLoaderListener extends ContextLoader implements ServletContextListener { /**
* Create a new {@code ContextLoaderListener} that will create a web application
* context based on the "contextClass" and "contextConfigLocation" servlet
* context-params. See {@link ContextLoader} superclass documentation for details on
* default values for each.
* <p>This constructor is typically used when declaring {@code ContextLoaderListener}
* as a {@code <listener>} within {@code web.xml}, where a no-arg constructor is
* required.
* <p>The created application context will be registered into the ServletContext under
* the attribute name {@link WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE}
* and the Spring application context will be closed when the {@link #contextDestroyed}
* lifecycle method is invoked on this listener.
* @see ContextLoader
* @see #ContextLoaderListener(WebApplicationContext)
* @see #contextInitialized(ServletContextEvent)
* @see #contextDestroyed(ServletContextEvent)
*/
public ContextLoaderListener() {
} /**
* Create a new {@code ContextLoaderListener} with the given application context. This
* constructor is useful in Servlet 3.0+ environments where instance-based
* registration of listeners is possible through the {@link javax.servlet.ServletContext#addListener}
* API.
* <p>The context may or may not yet be {@linkplain
* org.springframework.context.ConfigurableApplicationContext#refresh() refreshed}. If it
* (a) is an implementation of {@link ConfigurableWebApplicationContext} and
* (b) has <strong>not</strong> already been refreshed (the recommended approach),
* then the following will occur:
* <ul>
* <li>If the given context has not already been assigned an {@linkplain
* org.springframework.context.ConfigurableApplicationContext#setId id}, one will be assigned to it</li>
* <li>{@code ServletContext} and {@code ServletConfig} objects will be delegated to
* the application context</li>
* <li>{@link #customizeContext} will be called</li>
* <li>Any {@link org.springframework.context.ApplicationContextInitializer ApplicationContextInitializer}s
* specified through the "contextInitializerClasses" init-param will be applied.</li>
* <li>{@link org.springframework.context.ConfigurableApplicationContext#refresh refresh()} will be called</li>
* </ul>
* If the context has already been refreshed or does not implement
* {@code ConfigurableWebApplicationContext}, none of the above will occur under the
* assumption that the user has performed these actions (or not) per his or her
* specific needs.
* <p>See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
* <p>In any case, the given application context will be registered into the
* ServletContext under the attribute name {@link
* WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE} and the Spring
* application context will be closed when the {@link #contextDestroyed} lifecycle
* method is invoked on this listener.
* @param context the application context to manage
* @see #contextInitialized(ServletContextEvent)
* @see #contextDestroyed(ServletContextEvent)
*/
public ContextLoaderListener(WebApplicationContext context) {
super(context);
} /**
* Initialize the root web application context.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
} /**
* Close the root web application context.
*/
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
} }

源码果然是个好东西,平时敲代码那会注意到这么多细节

这个类不复杂,两个构造方法,外加一个初始化的时候创建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....

来看这段代码:

 /**
* Initialize the root web application context.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}

这个initWebApplicationContext方法是ContextLoad.java这个类里面的方法。

 /**
* Initialize Spring's web application context for the given servlet context,
* using the application context provided at construction time, or creating a new one
* according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and
* "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.
* @param servletContext current servlet context
* @return the new WebApplicationContext
* @see #ContextLoader(WebApplicationContext)
* @see #CONTEXT_CLASS_PARAM
* @see #CONFIG_LOCATION_PARAM
*/
public WebApplicationContext initWebApplicationContext(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(); try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
} if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
} return this.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;
}
}

ContextLoad.initWebApplicationContext是为给定的servlet context来初始化web应用的上下文的。

业务逻辑解读:

首先从ServletContext中看看有没有Spring创建的这个容器;

然后为ContextLoader存一份实例变量,使得在ServletContext关闭之后仍可以访问;

 this.context = createWebApplicationContext(servletContext);

这句就是创建一个WebApplicationContext相当于我们自己加载配置文件的那个类。

 configureAndRefreshWebApplicationContext(cwac, servletContext);

这句话也很明显,就是配置并且刷新WebAppCtx的。

 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

这句将创建的Spring的context作为属性放到servletContext中。

 return this.context;

然后就返回了Spring的容器了.....是不是简洁(装逼装不下去了),调用链好长。

暂时只能分析到这里!

ServletContext与Web应用以及Spring容器启动的更多相关文章

  1. spring容器启动扩展

    我们可以在spring容器启动时添加自己的定制化初始化工作. 1)具体方法为:在web.xml中配置初始化属性contextInitializerClasses的值例如: <context-pa ...

  2. spring容器启动

    1 主要类 ContextLoaderListener:注册在web.xml中,web应用启动时,会创建它,并回调它的initWebApplicationContext()方法,从而创建并启动spri ...

  3. Web环境中Spring的启动过程

    1.spring不但可以在JavaSE环境中应用,在Web环境中也可以广泛应用,Spring在web环境中应用时,需要在应用的web.xml文件中添加如下的配置: …… <context-par ...

  4. spring容器启动的加载过程(一)

    使用spring,我们在web.xml都会配置ContextLoaderListener <listener> <listener-class> org.springframe ...

  5. Spring容器启动源码解析

    1. 前言 最近搭建的工程都是基于SpringBoot,简化配置的感觉真爽.但有个以前的项目还是用SpringMvc写的,看到满满的配置xml文件,却有一种想去深入了解的冲动.折腾了好几天,决心去写这 ...

  6. Spring容器启动源码分析

    1. 前言 最近搭建的工程都是基于SpringBoot,简化配置的感觉真爽.但有个以前的项目还是用SpringMvc写的,看到满满的配置xml文件,却有一种想去深入了解的冲动.折腾了好几天,决心去写这 ...

  7. 一则spring容器启动死锁问题(DefaultListableBeanFactory/DefaultSingletonBeanRegistry)

    线上发现一个问题,应用在启动时会卡死,log上并没有什么异常输出,初判应该是死锁问题. 抓现场的thread dump文件, 确实是有两个线程有deadlock问题. 线程一 "HSFBiz ...

  8. spring容器启动原理分析1

    在项目的web.xml中配置 <listener> <listener-class>org.springframework.web.context.ContextLoaderL ...

  9. spring容器启动的三种方式

    一.在Web项目中,启动Spring容器的方式有三种,ContextLoaderListener.ContextLoadServlet.ContextLoaderPlugin. 1.1.监听器方式: ...

随机推荐

  1. URL 和URI的区别

    1.统一资源标志符URI就是在某一规则下能把一个资源独一无二地标识出来. URL:(Uniform/Universal Resource Locator 的缩写,统一资源定位符). URI:(Unif ...

  2. Nginx实现图片防盗链(referer指令)

    什么是图片盗链   每张图片在浏览器中都有对应的图片地址,在浏览器中输入这个地址是可以直接拿到图片. 图片盗链,就是盗用者在他的站上需要显示我们的图片,他没有把图片拿下来,放到他的服务器上, 而是直接 ...

  3. Redis编程实践【pub/sub】

    原文:http://shift-alt-ctrl.iteye.com/blog/1867454 Redis或许已经在很多企业开始推广并试水,本文也根据个人的实践,简单描述一下Redis在实际开发过程中 ...

  4. C#调用页面中的窗体中的方法,获取窗体的元素。

    页面中的窗体 <div class="div_width" style="width: 100%; height: 95%;"> <ifram ...

  5. 腾讯云会话服务器node+nginx

    1.除了一个正常的服务器还需要一个会话服务器(websocket),利用node加socket.io来做 2.正常安装Nginx yum install nginx 3.Nginx的配置内容略微不同( ...

  6. android 用 XML 自定义View边框个数,只有一边或两边

    <?xml version="1.0" encoding="UTF-8"?> <layer-list xmlns:android=" ...

  7. ChannelHandlerContext writeAndFlush(firstMessage)

  8. [Angular] Angular Custom Change Detection with ChangeDetectorRef

    Each component has its own ChangeDetectorRef, and we can inject ChangeDetectorRef into constructor: ...

  9. 编程算法 - 和为s的两个数字 代码(C)

    和为s的两个数字 代码(C) 本文地址: http://blog.csdn.net/caroline_wendy 题目: 输入一个递增排序的数组和一个数字s, 在数组中查找两个数, 使得它们的和正好是 ...

  10. 有关于apktool的使用的一些心得

    <span style="font-family: Arial, Helvetica, sans-serif;">1.配置Java的环境</span> 1) ...