在之前的博客中,我们讲到了Serlvet的创建和虚拟路径的映射,关于其中的知识相信大家也都已经掌握了,下面我们就要来学习下和Servlet息息相关的两个接口:ServletConfig和ServletContext。
首先我们来看下源码中对两个接口的解释,首先是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 String
containing the value
* of the initialization parameter, or null
if
* the initialization parameter does not exist
*/
public String getInitParameter(String name);
/**
* Returns the names of the servlet's initialization parameters
* as an Enumeration
of String
objects,
* or an empty Enumeration
if the servlet has
* no initialization parameters.
*
* @return an Enumeration
of String
* 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中的配置信息,下面让我们看下其中的四个方法的功能描述:
或@WebServlet中的name属性,如果没有配置的话,则返回该实例的类名(可通过xxx.class.getName()获取);
或@WebServlet中的initParams属性中的一个元素;Enumeration
的形式返回对应Servlet的初始化参数的名称(其中的name属性),如果没有初始化参数,则返回一个空的Enumeration
。 其实上述的方法除了getServletContext(),其他三个方法我们在开发中使用的不多,初始化参数我们可以使用配置文件来进行配置,这样还可以通过给配置文件增加不同的后缀来在不同的环境中使用(测试、预发、生产等)。其中有个比较重要的应用,就是上篇文章中说到的DefaultServlet的配置,可以使用init-param来设置缺省Servlet的一些’行为’。
首先我们也来看下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.
*
* 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 /catalog
* and possibly installed via a .war
file.)
*
*
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.
*
*
The ServletContext
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中常用的一些方法:
Enumeration
的形式返回对应Servlet容器中可用的属性名称(其中的name); 需要注意的是前三个方法中,如果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>BusinessNameparam-name>
<param-value>ZZXYparam-value>
context-param>
<context-param>
<param-name>CourseNameparam-name>
<param-value>JAVA WEBparam-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的
out.print("3.获取Servlet的:" );
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的跳转,前期准备工作如下:
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这一专栏会是一个系列博客,喜欢的话可以持续关注,如果本文对你有所帮助,还请还请点赞、评论加关注。
有任何疑问,可以评论区留言。