0 业务问题引入
0.1 提两个问题
1.大家在访问某个网站的时候,往往都会看到网站的首页面显示您是第几位浏览者(网站计数器),这是怎么实现的?
2.我们在访问某个bbs网站的时候,往往会显示有多少人在线,这是怎么实现的?
可能我们会想到的常规实现思路:数据库或者文件。这种做法比较简单,但是却会对数据库或者文件访问过于频繁,开销比较大。
解决之道是用ServletContext
0.2 要理解ServletContext就必须和Cookie、Session做一个对比
- 可以把它想象成一个公用的空间,可以被所有的客户访问,也就是说A客户端可以访问D,B客户端可以访问D,C客户端也可以访问D。
- WEB容器在启动时,它会为每个Web应用程序都创建一个对应的ServletContext,它代表当前Web应用。并且它被所有客户端共享。
- ServletContext对象可以通过ServletConfig.getServletContext()方法获得对ServletContext对象的引用,也可以通过this.getServletContext()方法获得其对象的引用。
- 由于一个WEB应用中的所有Servlet共享同一个ServletContext对象,因此Servlet对象之间可以通过ServletContext对象来实现通讯。ServletContext对象通常也被称之为context域对象。公共聊天室就会用到它。
- 当web应用关闭、Tomcat关闭或者Web应用reload的时候,ServletContext对象会被销毁
1 ServletContext接口
该接口提供了一个全局的储存信息的空间,服务器开始,其就存在,服务器关闭,其才释放。
- 对一个用户而言,一个用户只有一个ServletContext对象;【全部应用程序范围,最大】
- 对一个用户而言,一个用户只有一个session对象;【该用户的会话范围,适中】
- 对一个用户而言,一个用户可以拥有多个request对象;【用户一次会话中的一个请求的范围,最小】
- 所以,为了节省空间,提高效率,ServletContext中,要放必须的、重要的、所有用户需要共享的线程又是安全的一些信息。
2 理解ServletContext对象
- 换一种方式说吧,运行在JAVA虚拟机中的每一个Web应用程序都有一个与之相关的Servlet上下文。
- 站在服务器端的角度来看,是对应的一个应用程序,也就是初学者眼中的一个web项目。
- ServletContext对象是Web服务器中的一个已知路径的根,Servlet上下文被定位于
http://localhost:8080/项目名.
- 以 /项目名 请求路径(称为上下文路径)开始的所有请求被发送到与此ServletContext关联的Web应用程序。
- 一个ServletContext对象表示了一个Web应用程序的上下文。
3 Servlet上下文
- Servlet上下文提供对应用程序中所有Servlet所共有的各种资源和功能的访问。
- Servlet上下文API用于设置应用程序中所有Servlet共有的信息。
- Servlet可能需要共享他们之间的共有信息。
运行于同一服务器的Servlet有时会共享资源,如JSP页面、文件和其他Servlet。
4 业务举例
比如,做一个购物类的网站,要从数据库中提取物品信息,如果用session保存这些物品信息,每个用户都访问一便数据库,效率就太低了;所以要用来Servlet上下文来保存,在服务器开始时,就访问数据库,将物品信息存入Servlet上下文中,这样,每个用户只用从上下文中读入物品信息就行了。这肯定比从缓存或是库中提取对应的数据效率要高一些。
5 ServletContext接口详解
5.1 ServletContext接口简介
ServletContext接口定义了运行servlet的web应用的servlet视图。容器供应商负责提供servlet容器内ServletContext接口的实现。使用ServletContext对象,servlet可以记录事件日志,获取资源的URL地址,并且设置和保存上下文内可以访问的其他servlet的属性。
ServletContext以web的已知路径为根路径。比如,假定一个servlet上下文位于http://www.mycorp.com/catalog。以/catalog请求路径开头的所有请求,已知为上下文路径,被路由到和该ServletContext关联的web应用。
5.2 ServletContext接口作用域
容器中部署的每一个web应用都有一个ServletContext接口的实例对象与之关联。如果容器被分布在多个虚拟机上,一个web应用将在每一个VM中有一个ServletContext实例。
不作为web应用一部分部署的容器中的servlet默认是“默认”web应用的一部分,有一个默认的ServletContext。在分布式容器中。默认ServletContext是非分布式的,并且必须只存在于一个VM中。
5.3 初始化参数
ServletContext接口的初始化参数允许servlet访问与web应用相关的上下文初始化参数,这些由应用开发人员在部署描述符中指定:
getInitParameter
getInitParameterNames
应用开发人员利用初始化参数传送配置信息。典型的例子是web管理员的e-mail地址或者一个持有关键数据的系统名称。
5.4 上下文属性
servlet可以通过名称将对象属性绑定到上下文。任何绑定到上下文的属性可以被同一个web应用的其他servlet使用。ServletContext接口的下列方法允许访问这种功能:
setAttribute
getAttribute
getAttributeNames
removeAttribute
5.4.1 分布式容器中的上下文属性
上下文属性对于创建它们的VM来说是本地的。这防止ServletContext属性存储于分布式容器的共享内存中。当信息需要在运行于分布式环境中的servlet之间共享时,信息被放入会话中(参见第7章“会话”),存储于数据库中,或者存储于EJB组件中。
5.5 资源
ServletContext接口通过下列方法提供对web应用组成的静态内容文档层级的直接访问,包括HTML,GIF和JPEG文件:
getResource
getResourceAsStream
getResource和getResourceAsStream方法以“/”开头的字符串为参数,它指定上下文根路径的资源相对路径。文档的层级可能存在于服务器的文件系统,war文件,远程服务器或者在一些其它位置中。
这些方法不用来获取动态内容。比如,在一个支持JSP规范1的容器中,getResource("/index.jsp")这种形式的方法调用将返回JSP源代码,而不是处理后的输出。关于访问动态内容的更多信息参见第8章“转发请求”。
Web应用资源的完整列表可以使用getResourcePaths(String path)方法访问。该方法语义的完整信息可以在本规范的API文档中找到。
5.6 多个主机和ServletContext
Web服务器可能支持一个服务器上多个逻辑主机共享一个IP地址。这功能有时被称为“虚拟主机”。这种情况下,每一个逻辑主机必须有它自己的servlet上下文或者servlet上下文组。Servlet上下文不可以被多个虚拟主机共享。
5.7 重载考虑
尽管容器供应商因为对于易于开发而实现的类加载不做要求,但是任何那样的实现必须确保所有它们可能使用2的所有servlet和类,被加载在单个类加载器作用域内。必须保证应用应该如开发人员预想的那样运转。作为开发辅助,绑定监听器的会话通知的完整语义应当由容器支持,在类加载上会话终止的监听上使用。
上一代的容器创建新的类加载器以加载servlet,这和用来加载servlet上下文中使用的其他servlet或者类的类加载器不同。这可能造成servlet上下文内的对象引用指向一个意想不到的类或对象,造成意想不到的行为。需要阻止由新一代类加载器所引发的问题。
5.7.1 临时工作目录
每一个servlet上下文都需要一个临时存储目录。Servlet容器必须为每一个servlet上下文提供一个私有的临时目录,并且使它可以通过javax.servlet.context.tempdir上下文属性可用。这些属性关联的对象必须是java.io.File类型。
这项需求认可了很多servlet引擎实现中提供的常见便利。容器不需要在servlet重启时维持临时目录的内容,但是需要确保一个servlet上下文的临时目录的内容对于该servlet容器上运行的其他web应用的servlet上下文不可见。
6 使用ServletContext
6.1 怎么使用ServletContext
使用ServletContext:
(1) 如何得到ServletContext对象
this.getServletContext();
this.getServletConfig().getServletContext();
(2) 你可以把它想象成一张表,这个和Session非常相似:每一行就是一个属性
keyNameString:valueObject
- 添加属性:setAttribute(String name, Object obj);
- 得到值:getAttribute(String name),这个方法返回Object
- 删除属性:removeAttribute(String name)
(3) 生命周期
ServletContext中的属性的生命周期从创建开始,到服务器关闭结束。
6.2 一个具体的实例
6.2.1 创建Servlet1和Servlet2
Servlet1用于在ServletContext中创建属性,Servlet2用于从ServletContext读取属性:
Servlet1的doGet方法为:
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
// 获取ServletContext对象的引用
// 第一种方法
ServletContext servletContext = this.getServletContext();
// 第二种方法
// ServletContext servletContext2 = this.getServletConfig().getServletContext();
servletContext.setAttribute("name", "小明");
out.println("将 name=小明 写入了ServletContext");
}
Servlet2的doGet方法为:
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
// 取出ServletContext的某个属性
//1.首先获取到ServletContext
ServletContext servletContext = this.getServletContext();
//2.取出属性
String name = (String)servletContext.getAttribute("name");
out.println("name="+name);
}
6.2.2 先后访问这两个servlet
以此访问Servlet1,我们可以分别看到输出如下:
以此访问Servlet2,我们可以分别看到输出如下:
6.2.3 访问结果读解
- 运行结果似乎和Session,Cookie的应用没什么不同。
- 其实看似相同,实则完全不一样。
- 只要我们不关闭Tomcat或者reload该应用,这时候我们关闭当前的浏览器,或者是换一个浏览器,假设我们从Chrome换到IE再次访问Servlet2,依然可以看到结果!
- 这就是它们最大的不同了,因为ServletContext是存在于服务器内存中的一个公共空间,它可以供所有的用户客户端访问。
7 Servlet应用详解
7.1 多个Servlet通过ServletContext对象实现数据共享
这个很好理解,类似于Session,我们也可以通过ServletContext对象来共享数据,但要注意的是,Session只能在一个客户端共享数据,它独占一个客户端。而ServletContext中的数据是可以供所有客户端共享的。
7.2 实现Servlet的请求转发
之前我们学过的请求转发是通过request对象的:
request.getRequestDispatcher("/url").forward(request, response);
这里要说明的是,ServletContext也可以实现请求转发:
this.getServletContext().getRequestDispatcher("/url").forward(request, response);
这两个转发效果是一样的。
7.3 获取Web应用的初始化参数
在【Servlet——开发细节+ServletConfig对象】中,我们介绍过在Servlet部署的时候,我们可以使用一个或多个标签为servlet配置一些初始化参数,然后我们通过ServletConfig对象获取这些参数,假如有如下的MyServlet,它的配置为:
<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-class>com.gavin.servlet.MyServlet</servlet-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</servlet>
可以看到它配置了一个初始化参数:encoding=utf-8,那么我们在MyServlet的源代码中需要这样去得到这个参数:
String encoding = this.getServletConfig().getInitParameter("encoding");
上述的参数配置方法只针对一个特定的Servlet有效,现在我们可以通过ServletContext来获取全局的、整个Web应用的初始化参数,全局的初始化参数是这样配置在web.xml文件中的:
<!-- 如果希望所有的Servlet都可以使用该配置,则必须这么做 -->
<context-param>
<param-name>name</param-name>
<param-value>gavin</param-value>
</context-param>
然后我们可以在任意一个Servlet中使用ServletContext获取这个参数:
String name = this.getServletContext().getInitParameter("name");
7.4 利用ServletContext对象读取资源文件(比如properties文件)
读取资源文件要根据资源文件所在的位置分为两种情况:
(1)文件在WebRoot文件夹下,即我们的Web应用的根目录下。这时候我们可以使用ServletContext来读取该资源文件。
假设我们Web根目录下有一个配置数据库信息的dbinfo.properties文件,里面配置了name和password属性,这时候可以通过ServletContext去读取这个文件:
// 这种方法的默认读取路径就是Web应用的根目录
InputStream stream = this.getServletContext().getResourceAsStream("dbinfo.properties");
// 创建属性对象
Properties properties = new Properties();
properties.load(stream);
String name = properties.getProperty("name");
String password = properties.getProperty("password");
out.println("name="+name+";password="+password);
(2)但是如果这个文件放在了src目录下,通过ServletContext是读不到的,必须要使用类加载器去读取。
// 类加载器的默认读取路径是src根目录
InputStream stream = MyServlet.class.getClassLoader().getResourceAsStream("dbinfo.properties")
但是如果这个文件此时还没有直接在src目录下,而是在src目录下的某个包下,比如在com.gavin包下,此时类加载器要加上包的路径,如下:
InputStream stream = MyServlet.class.getClassLoader().getResourceAsStream("cn/edu/cuit/dbinfo.properties")
另外,补充一点,ServletContext可以获取文件的全路径,当然这个也是在Web应用根目录下的文件。比如我们在WebRoot文件夹下有一个images文件夹,images文件夹下有一个Servlet.jpg图片,为了得到这个图片的全路径,如下:
// 如何读取到一个文件的全路径,这里会得到在Tomcat的全路径
String path = this.getServletContext().getRealPath("/images/Servlet.jpg");
在网站开发中,有很多功能要使用ServletContext,比如
- 网站计数器
- 网站的在线用户显示
- 简单的聊天系统
总之,如果是涉及到不同用户共享数据,而这些数据量不大,同时又不希望写入数据库中,我们就可以考虑使用ServletContext实现。