Servlet基础之ServletConfig与ServletContext接口详解

​ 在之前的博客中,我们讲到了Serlvet的创建和虚拟路径的映射,关于其中的知识相信大家也都已经掌握了,下面我们就要来学习下和Servlet息息相关的两个接口:ServletConfig和ServletContext。

ServletConfig描述和方法

​ 首先我们来看下源码中对两个接口的解释,首先是ServletConfig和其中定义的方法:

/**
 * A servlet configuration object used by a servlet container
 * to pass information to a servlet during initialization. 
 */
 public interface ServletConfig {
   /**
     * Returns the name of this servlet instance.
     * The name may be provided via server administration, assigned in the 
     * web application deployment descriptor, or for an unregistered (and thus
     * unnamed) servlet instance it will be the servlet's class name.
     *
     * @return	the name of the servlet instance
     */
    public String getServletName();

    /**
     * Returns a reference to the {@link ServletContext} in which the caller
     * is executing.
     *
     * @return	a {@link ServletContext} object, used
     * by the caller to interact with its servlet container
     * 
     * @see ServletContext
     */
    public ServletContext getServletContext();

    /**
     * Gets the value of the initialization parameter with the given name.
     *
     * @param name the name of the initialization parameter whose value to
     * get
     *
     * @return a <code>String</code> containing the value 
     * of the initialization parameter, or <code>null</code> if 
     * the initialization parameter does not exist
     */
    public String getInitParameter(String name);

    /**
     * Returns the names of the servlet's initialization parameters
     * as an <code>Enumeration</code> of <code>String</code> objects, 
     * or an empty <code>Enumeration</code> if the servlet has
     * no initialization parameters.
     *
     * @return an <code>Enumeration</code> of <code>String</code> 
     * objects containing the names of the servlet's 
     * initialization parameters
     */
    public Enumeration<String> getInitParameterNames();
 }

​ 上面是源码中的注释,我给大家解释下:ServletConfig是Servlet容器在创建servlet时根据web.xml中的配置信息为对应的Servlet对象创建的配置对象,在Serlvet初始化通过init方法将配置信息传递给Servlet。也就是说,每个Servlet都拥有自己的一个ServletConfig,并可以通过此获取web.xml中的配置信息,下面让我们看下其中的四个方法的功能描述:

  1. getServletName:返回Servlet实例的名称,即配置中的<servlet-name>或@WebServlet中的name属性,如果没有配置的话,则返回该实例的类名(可通过xxx.class.getName()获取);
  2. getServletContext:返回当前正在运行的web应用ServletContext的引用,在每个web应用中,ServletContext是全局唯一的,此对象的引用是Servlet容器创建Servlet时初始化的;
  3. getInitParameter:通过传入的参数名获取对应的初始化参数的值,即配置中的<init-param>或@WebServlet中的initParams属性中的一个元素;
  4. getInitParameterNames:以Enumeration<String>的形式返回对应Servlet的初始化参数的名称(其中的name属性),如果没有初始化参数,则返回一个空的Enumeration<String>

​ 其实上述的方法除了getServletContext(),其他三个方法我们在开发中使用的不多,初始化参数我们可以使用配置文件来进行配置,这样还可以通过给配置文件增加不同的后缀来在不同的环境中使用(测试、预发、生产等)。其中有个比较重要的应用,就是上篇文章中说到的DefaultServlet的配置,可以使用init-param来设置缺省Servlet的一些’行为’。

ServletContext描述与常用方法

​ 首先我们也来看下ServletContext的源码(其中定义的方法过多,因此只讲述常用的方法):

/**
 * Defines a set of methods that a servlet uses to communicate with its
 * servlet container, for example, to get the MIME type of a file, dispatch
 * requests, or write to a log file.
 *
 * <p>There is one context per "web application" per Java Virtual Machine.  (A
 * "web application" is a collection of servlets and content installed under a
 * specific subset of the server's URL namespace such as <code>/catalog</code>
 * and possibly installed via a <code>.war</code> file.) 
 *
 * <p>In the case of a web
 * application marked "distributed" in its deployment descriptor, there will
 * be one context instance for each virtual machine.  In this situation, the 
 * context cannot be used as a location to share global information (because
 * the information won't be truly global).  Use an external resource like 
 * a database instead.
 *
 * <p>The <code>ServletContext</code> object is contained within 
 * the {@link ServletConfig} object, which the Web server provides the
 * servlet when the servlet is initialized.
 *
 * @author 	Various
 *
 * @see 	Servlet#getServletConfig
 * @see 	ServletConfig#getServletContext
 */

public interface ServletContext {
  //...
}

​ 从上述描述,我们可以知道,每个Java虚拟机上运行的每个web应用都有一个ServletContext(上下文环境),这个web应用是一组Servlet的集合。ServletContext中定义了一组方法用于Servlet实例和Servlet容器来进行通信,比如获取文件的MIME类型(比如text/html、image/jpeg、application/json等),调度请求和写入日志文件等。

​ 需要注意的是,在上段中我们讲到了,每个Java虚拟机上运行的每个web应用都有一个ServletContext,因此,当应用是分布式的情况下,比如我们将xxx项目部署在两台主机上形成了两个应用,这两个应用就会分别拥有一个独立的ServletContext,这两个应用之间的Serlvet是无法共享全局信息的(这也是为何Session在分布式的系统中无法直接共享的原因),这个时候就需要中间的一个服务来保证两个分布式的服务的通信,比如数据库、MQ等。

​ 并且每个Servlet在创建的时候,web应用会在ServletConfig对象中初始化ServletContext的引用,也就是说我们可以通过ServletConfig获取当前应用的ServletContext,并使用ServletContext来帮助我们完成某些操作。

​ 下面我们来讲解ServletContext中常用的一些方法:

  1. getAttribute(String name):根据传入的name返回Servlet容器属性(通过下面的setAttribute方法设置的),如果没有对应的属性,则返回一个null;
  2. setAttribute(String name, Object object):在serlvet容器中将给定的object绑定到对应的属性名name上(其形如key:value),如果serlvet容器中已经存在该name的属性,则将其原始值覆盖,如果传入的object为null,则和removeAttribute方法起到的效果一致(注意,这点和我们使用的Map不同);
  3. removeAttribute(String name):根据传入的name将对应的属性从Servlet容器中移除;
  4. getAttributeNames():以Enumeration<String>的形式返回对应Servlet容器中可用的属性名称(其中的name);
  5. getResourcePaths(String path):返回一个包含目录列表的集合,如果web应用中所有资源的路径都不是以path开头,则返回一个空的集合。参数path是用与匹配资源的起始路径,且需要以"/"开头;
  6. getResource(String path):返回映射到给定路径的资源的URL对象,path需要以"/"开头;
  7. getResourceAsStream(String path):返回映射到给定路径的资源的输入流(InputStream),path需要以"/"开头,此方法无法获取到资源的类型和长度属性;
  8. getRealPath(String path):返回给定虚拟路径(相对于当前应用)的资源在主机上的真实路径,当servlet容器无法将给定的虚拟路径转换为真实路径时,返回null;
  9. getRequestDispatcher(String path):返回一个RequestDispatcher对象,该对象可用于将请求转发到资源(指定路径)或将资源包括在响应中,path需要以"/"开头;

​ 需要注意的是前三个方法中,如果ServletContext中的属性发生了变化,如果有监听器监听ServletContext,Servlet容器会自动的通知对应的监听器。对于getResourcePaths、getResource等方法中的path,皆要求以"/"开头,说到这个,这里需要讲一下ServletContext这里使用的路径和我们在IDE中看到的区别,我们来通过两张图对比一下,首先看下Eclipse中项目的目录结构:

资源分配图

​ 然后我们再看下FirstProject项目在tomcat安装目录下的webapps目录中的结构(为了方便显示层级关系,在命令行窗口来展示项目目录),截图如下:

资源分配图

​ 对比两张截图,其中第一张图的src(1标注)也就是存放Java代码的目录会转成第二张图的WEB-INF(1标注)下的classes(3标注),第一张图中的index.jsp、welcom.html(3标注)会直接到根目录下(2标注),和WEB-INF平级,如果有静态资源放入在文件夹下(方便管理),也是一样,不过相对的也要增加一级目录。因此在Servlet中获取资源文件是使用的"/“就会对应到"FirstProject/”(项目名,这里和在浏览器上使用时不同,我们后面在讨论)。

​ 因此我们在项目中想要获取对应的资源文件,就需要使用图二中的文件目录结构,path开头的"/",也就是直接定位到webapps/FirstProject下

使用实例

​ 我们通过例子,来简单的介绍下ServletConfig、ServletContext中方法的使用。我们首先在web.xml中增加Web应用的初始化信息,配置如下:

<context-param>
  <param-name>BusinessName</param-name>
  <param-value>ZZXY</param-value>
</context-param>
<context-param>
  <param-name>CourseName</param-name>
  <param-value>JAVA WEB</param-value>
</context-param>

​ 我们将HelloServlet修改如下:

package com.zzxy.web.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.URL;
import java.util.Date;
import java.util.Enumeration;
import java.util.Set;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.sun.org.apache.bcel.internal.generic.NEW;

/**
 * Servlet implementation class HelloServlet
 */
@WebServlet(
		description = "My First Servlet", 
		urlPatterns = { "/HelloServlet", "/StillMe" }, 
		initParams = { 
				@WebInitParam(name = "name", value = "lizishu"),
				@WebInitParam(name = "password", value = "123456")
		})
public class HelloServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
  
  //...
  
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//设置返回客户端的contentType
		//text/plain :纯文本格式  设置为text/html println的换行会失效
		response.setContentType("text/plain;charset=utf-8");
		//response.setCharacterEncoding("utf-8"); 
		PrintWriter out = response.getWriter();
		//Servlet容器的上下文对应的相对的根目录
		out.print("1.Servlet容器的上下文对应的相对的根目录:");
		out.println("Served at: " + request.getContextPath());
		ServletConfig config = this.getServletConfig();
		//获取Servlet的初始化参数
		out.print("2.获取Servlet的初始化参数:");
		out.println("name: " + this.getInitParameter("name"));
		//获取Servlet的<servlet-name>
		out.print("3.获取Servlet的<servlet-name>:");
		out.println("访问的Servle名为:" + config.getServletName());
		
		// 得到包含所有初始化参数名的Enumeration对象
		Enumeration<String> paramNames = config.getInitParameterNames();
		//遍历所有的初始化参数名,得到相应的参数值
		out.println("4.遍历所有的初始化参数名,得到相应的参数值:");
		// 遍历所有的初始化参数名,得到相应的参数值并打印
		while (paramNames.hasMoreElements()) {
			String name = paramNames.nextElement();
			String value = config.getInitParameter(name);
			out.println(name + ": " + value);
		}
		//得到ServletContext对象
		ServletContext context = this.getServletContext();
		//获取应用程序的初始化参数(全局的,所有的Servlet可共享)
		out.print("5.获取应用程序的初始化参数:");
		out.println("name's value: " + context.getInitParameter("BusinessName"));
		
		//一次获取所有的应用程序的初始化参数的name
		Enumeration<String> attributeNames = context.getInitParameterNames();
		out.println("6.遍历所有的应用程序的初始化参数:");
		while (attributeNames.hasMoreElements()) {
			String attributeName = attributeNames.nextElement();
			String value = context.getInitParameter(attributeName);
			out.println(attributeName + ": " + value);
		}
		//setAttribute,设置serlvet容器的属性,在另一个Servlet实例中获取
		context.setAttribute("setTime", new Date());
		//获取Servlet容器的上下文对应的相对的根目录下的文件和目录的集合,也对应path开头的"/"
		Set<String> pathSet = context.getResourcePaths("/");
		out.println("7.获取Servlet容器的上下文对应的相对的根目录下的文件和目录的集合:");
		for(String path: pathSet) {
			out.println(path);
		}
		//getResource方法,URL对象中包含资源文件的许多属性,比如文件类型、文件长度等
		out.println("8.获取对应资源的URL对象:");
		URL url = context.getResource("/index.jsp");
		out.println(url.toString());
		//getRealPath 获取对应资源虚拟路径的真实路径
		out.println("9.获取对应资源虚拟路径的真实路径:");
		out.println(context.getRealPath("/index.jsp"));
		//getRequestDispatcher实现转发
		//context.getRequestDispatcher("/AnswerServlet").forward(request, response);
	}
  
	//doPost()
}

​ 其对应的执行结果如下图:

资源分配图

​ 下面我们新建一个AnswerServlet来测试ServletContext中的getAttribute方法、使用getResourceAsStream来加载配置文件、getRequestDispatcher的跳转,前期准备工作如下:

  1. 将HelloServlet中ddoGet方法的最后一行注释删掉,让其可以进行跳转;
  2. 创建AnswerServlet,配置选项全部默认即可;
  3. 在src下新建config.properties,src右击–>new–>other–>General–>File,点击next后输入文件名;
  4. config.properties文件中增加两行配置,配置内容如下:
BusinessName = Java Web learning
weapon = Work Hard

下面让我们一起来看下AnswerServlet,代码如下:

package com.zzxy.web.servlet;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Properties;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class AnswerServlet
 */
@WebServlet("/AnswerServlet")
public class AnswerServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
  
  //...

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//设置返回客户端的contentType
		//text/plain :纯文本格式  设置为text/html println的换行会失效
		response.setContentType("text/plain;charset=utf-8");
		//response.setCharacterEncoding("utf-8"); 
		PrintWriter out = response.getWriter();
		
		//获取ServletContext,可以直接通过this指针
		//调用GenericServlet提供的直接获取ServletContext的方法
		ServletContext context = this.getServletContext();
		
		out.println("获取Servlet容器的属性的值为:" + context.getAttribute("setTime"));
		
		//获取资源文件的输入流InputStream对象
		InputStream inStream = context.getResourceAsStream("/WEB-INF/classes/config.properties");
		//创建一个Properties对象,用于加载配置
		Properties properties = new Properties();
		//从输入流中读取参数列表
		properties.load(inStream);
		out.println("从配置文件中读取信的BusinessName:" + properties.getProperty("BusinessName"));
		out.println("从配置文件中读取信的weapon:" + properties.getProperty("weapon"));
		//Properties对象同样提供一个获取所有属性name的方法,propertyNames
		
    	//通过getResource获取URL对象,获取输入流
    	URL url = context.getResource("/WEB-INF/classes/config.properties");
		properties.clear();
		properties.load(url.openStream());
		out.println("从配置文件中读取信的BusinessName:" + properties.getProperty("BusinessName"));
		out.println("从配置文件中读取信的weapon:" + properties.getProperty("weapon"));
	}

	//doPost

}

​ 在AnswerServlet中的doGet方法里,我们获取了在HelloServlet设置的setTime属性,实现了应用内的Servlet共享(此方法用处不大,单应用的数据完全可以放在内存中来实现共享);还通过getResourceAsStream来获取资源文件的输入流,可以看到,使用的路径为我们在上面讲的打包后运行在tomcat服务器上的路径,也可以通过getResource方法获取资源文件的URL对象,在获取InputStream,可以达到同样的效果。运行结果如下图所示:

资源分配图

​ 可以看到,在浏览器上输入http://localhost:8080/FirstProject/HelloServlet,页面上显示的内容为AnswerServlet输出到屏幕的内容,这个就是RequestDispatcher对象的转发功能了(后面再具体讨论转发和重定向)。

总结

​ 本文主要讲了ServletConfig和ServletContext两个接口的定义,首先ServletConfig是相对于每个Servlet实例的,是根据web.xml中的配置或者@WebServlet注解来生成的;ServletContext是相对于web 应用的,在一个web应用(一个虚拟机中运行的一个web应用)中是全局唯一的,ServletContext中封装了许多功能强大的方法,其中比较重要的就是对资源文件的使用,通过Servlet容器可以方便的使用web应用中的资源,这也需要我们多练习,熟练掌握并能应用。

​ 还有一个比较重要的知识点就是Java web项目的打包路径和IDE中的开发路径的不同,大家要能熟练地掌握IDE中的路径的"转换",可以准确的通过path找到你所需要的资源。


​ 又到了分隔线以下,本文到此就结束了,本文内容全部都是由博主自己进行整理并结合自身的理解进行总结,如果有什么错误,还请批评指正。

​ Java web这一专栏会是一个系列博客,喜欢的话可以持续关注,如果本文对你有所帮助,还请还请点赞、评论加关注。

​ 有任何疑问,可以评论区留言。

发布了43 篇原创文章 · 获赞 323 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/qq_34666857/article/details/104517970