在之前的博客中,我们讲到了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中的配置信息,下面让我们看下其中的四个方法的功能描述:
- getServletName:返回Servlet实例的名称,即配置中的
<servlet-name>
或@WebServlet中的name属性,如果没有配置的话,则返回该实例的类名(可通过xxx.class.getName()获取); - getServletContext:返回当前正在运行的web应用ServletContext的引用,在每个web应用中,ServletContext是全局唯一的,此对象的引用是Servlet容器创建Servlet时初始化的;
- getInitParameter:通过传入的参数名获取对应的初始化参数的值,即配置中的
<init-param>
或@WebServlet中的initParams属性中的一个元素; - 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中常用的一些方法:
- getAttribute(String name):根据传入的name返回Servlet容器属性(通过下面的setAttribute方法设置的),如果没有对应的属性,则返回一个null;
- setAttribute(String name, Object object):在serlvet容器中将给定的object绑定到对应的属性名name上(其形如key:value),如果serlvet容器中已经存在该name的属性,则将其原始值覆盖,如果传入的object为null,则和removeAttribute方法起到的效果一致(注意,这点和我们使用的Map不同);
- removeAttribute(String name):根据传入的name将对应的属性从Servlet容器中移除;
- getAttributeNames():以
Enumeration<String>
的形式返回对应Servlet容器中可用的属性名称(其中的name); - getResourcePaths(String path):返回一个包含目录列表的集合,如果web应用中所有资源的路径都不是以path开头,则返回一个空的集合。参数path是用与匹配资源的起始路径,且需要以"/"开头;
- getResource(String path):返回映射到给定路径的资源的URL对象,path需要以"/"开头;
- getResourceAsStream(String path):返回映射到给定路径的资源的输入流(InputStream),path需要以"/"开头,此方法无法获取到资源的类型和长度属性;
- getRealPath(String path):返回给定虚拟路径(相对于当前应用)的资源在主机上的真实路径,当servlet容器无法将给定的虚拟路径转换为真实路径时,返回null;
- getRequestDispatcher(String path):返回一个RequestDispatcher对象,该对象可用于将请求转发到资源(指定路径)或将资源包括在响应中,path需要以"/"开头;
需要注意的是前三个方法中,如果ServletContext中的属性发生了变化,如果有监听器监听ServletContext,Servlet容器会自动的通知对应的监听器。对于getResourcePaths、getResource等方法中的path,皆要求以"/"开头,说到这个,这里需要讲一下ServletContext这里使用的路径和我们在IDE中看到的区别,我们来通过两张图对比一下,首先看下Eclipse中项目的目录结构:
![资源分配图](https://img-blog.csdnimg.cn/20200226154247128.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM0NjY2ODU3,size_16,color_FFFFFF,t_70)
然后我们再看下FirstProject项目在tomcat安装目录下的webapps目录中的结构(为了方便显示层级关系,在命令行窗口来展示项目目录),截图如下:
![资源分配图](https://img-blog.csdnimg.cn/20200226154316423.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM0NjY2ODU3,size_16,color_FFFFFF,t_70)
对比两张截图,其中第一张图的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()
}
其对应的执行结果如下图:
![资源分配图](https://img-blog.csdnimg.cn/20200226154353784.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM0NjY2ODU3,size_16,color_FFFFFF,t_70)
下面我们新建一个AnswerServlet来测试ServletContext中的getAttribute方法、使用getResourceAsStream来加载配置文件、getRequestDispatcher的跳转,前期准备工作如下:
- 将HelloServlet中ddoGet方法的最后一行注释删掉,让其可以进行跳转;
- 创建AnswerServlet,配置选项全部默认即可;
- 在src下新建config.properties,src右击–>new–>other–>General–>File,点击next后输入文件名;
- 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,可以达到同样的效果。运行结果如下图所示:
![资源分配图](https://img-blog.csdnimg.cn/2020022615443647.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM0NjY2ODU3,size_16,color_FFFFFF,t_70)
可以看到,在浏览器上输入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这一专栏会是一个系列博客,喜欢的话可以持续关注,如果本文对你有所帮助,还请还请点赞、评论加关注。
有任何疑问,可以评论区留言。