1、前言
对呀!现在都2020年了,我还深刻的记得在读初中的时候提到过2020年,因为初中历史书中有段是这么写的:“确保2020年中国全面实现小康社会”。那个时候的我以为2020年离我还很遥远,没想到就一眨眼的事,时间过的是真的快。也不知道大家有没有步入小康社会呢,反正我整天只能吃土为生。
上面扯远了,我们来说说Servlet它还有必要学吗?Servlet已经是一个非常非常古老的技术了,而且在实际开发中几乎不会用到,在面试中也几乎不会问Servlet的知识。所以我们不需要学习Servlet了吗?错错错。我们后面会学习到Struts2和SpringMVC框架,而它两的底层都是跟Servlet有关,所以Servlet还是很有必要的学习的,最好不要跳过它。我们只有打下坚实的基础,后面的框架学习起来才能得心应手。
2、什么是Servlet
Servlet(Server Applet)是Java Servlet的简称,称为小服务程序或服务连接器,用Java编写的服务器端程序,主要功能在于交互式地浏览和修改数据,生成动态Web内容。这是百度百科上的一段话。说简单点就是对客户端发送过来的请求进行处理,并且作出相应的响应。其过程如下:
- 客户端发送请求至Web服务器端。
- 服务器将请求信息发送至Servlet。
- Servlet 生成响应内容并将其传给服务器。响应内容动态生成,通常取决于客户端的请求。
- 服务器将响应返回给客户端。
3、创建Servlet程序
我们在创建Servlet之前需要提前配置好环境:1、安装好JDK;2、开发工具Eclipse或IDEA(推荐);3、安装Tomcat。这三个条件是必须的,具体怎么配置网上教程很多,这里不多BB。
创建Servlet程序的流程如下:
- 编写一个Java类(类名必须是:XxxServlet),然后继承HttpServlet,或者继承GenericServlet,又或者直接实现Servlet接口。
- 重写HttpServlet类中的doGet和doPost方法。(IDEA快捷键Ctrl+O)
- 配置web.xml文件,或者使用注解对servlet进行配置。推荐使用注解
注意:我们一般都是继承HTTPServlet,因为HttpServlet是指能够处理HTTP协议请求的Servlet,它在原有Servlet接口上添加了一些与HTTP协议处理方法,它比Servlet接口的功能更为强大。因此开发人员在编写Servlet时,通常应继承这个类,而避免直接去实现Servlet接口和继承GenericServlet。而且HttpServlet在实现Servlet接口时,覆写了service方法,该方法体内的代码会自动判断用户的请求方式,如为GET请求,则调用HttpServlet的doGet方法,如为Post请求,则调用doPost方法。因此,开发人员在编写Servlet时,通常只需要覆写doGet或doPost方法,而不要去覆写service方法。
所以第一步和第二步没什么可说的,重点是在配置web.xml文件上,这一步决定了我们的请求和响应是哪个Servlet来完成的。上面说到配置Servlet有两种方式:一种是使用web.xml文件配置,另外一种就是使用注解配置,所以下面我们来详解介绍这两种配置方式:
- 使用web.xml文件配置
我们打开web.xml文件,在<web-app>元素中编写一个<servlet>元素用于配置一个Servlet,它包含有两个主要的子元素:<servlet-name>和<servlet-class>,分别用于设置Servlet的名称和Servlet的完整类名。另外一个<servlet-mapping>元素用于映射一个已注册的Servlet的一个对外访问路径,它包含有两个子元素:<servlet-name>和<url-pattern>,分别用于指定映射到哪个Servlet和Servlet的对外访问路径。配置详细信息如下:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!--配置一个Servlet--> <servlet> <!--Servlet名称,最好见名知意--> <servlet-name>HelloServlet</servlet-name> <!--Servlet的全类名,即包+类--> <servlet-class>com.thr.MyServlet</servlet-class> </servlet> <!--配置Servlet的映射--> <servlet-mapping> <!--映射到哪个Servlet,注意一点要与上面有的名称一样--> <servlet-name>HelloServlet</servlet-name> <!-- Servlet的对外访问路径 --> <url-pattern>/HelloServlet</url-pattern> </servlet-mapping> </web-app>
完成上面的web.xml配置后,当服务器运行之后,我们的Servlet程序就可以被外界访问了,打开页面访问 http://localhost:8080/HelloServlet。
注意:同一个Servlet可以被映射到多个URL上,即多个<servlet-mapping>元素的<servlet-name>子元素的设置值可以是同一个Servlet的名称。 例如:
<!--配置一个Servlet--> <servlet> <!--Servlet名称,最好见名知意--> <servlet-name>HelloServlet</servlet-name> <!--Servlet的全类名,即包+类--> <servlet-class>com.thr.MyServlet</servlet-class> </servlet> <!--配置Servlet的映射--> <servlet-mapping> <!--映射到哪个Servlet,注意一点要与上面有的名称一样--> <servlet-name>HelloServlet</servlet-name> <!-- Servlet的对外访问路径 --> <url-pattern>/HelloServlet</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>HelloServlet</servlet-name> <url-pattern>/HelloServlet/HelloServlet</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>HelloServlet</servlet-name> <url-pattern>/HelloServlet/HelloServlet/HelloServlet</url-pattern> </servlet-mapping>
通过上面的配置,当我们想访问名称是MyServlet的Servlet,可以使用如下的几个地址去访问,但结果都是访问的同一个Servlet:
http://localhost:8080/HelloServlet
http://localhost:8080/HelloServlet/HelloServlet
http://localhost:8080/HelloServlet/HelloServlet/HelloServlet
- 使用注解配置(注:用了注解,web.xml中就不能再配置该Servlet了)
我们都知道使用web.xml文件来配置是很头痛的事情,随着系统的开发,配置文件肯定会越来越多,里面的文件也会看的眼花缭乱。所以Servlet3.0之后提供了注解(annotation),使得不再需要在web.xml文件中进行Servlet的配置,而是使用注解@WebServlet代替了web.xml,从而简化开发流程。
下面是注解@WebServlet源码中的属性列表:
属性名 | 类型 | 描述 |
name | String | 指定Servlet 的 name 属性,等价于<servlet-name>。如果没有显式指定,则该 Servlet 的取值即为类的全限定名。 |
value | String[] | 该属性等价于下面urlPatterns属性。这两个属性不能同时使用。 |
urlPatterns | String[] | 指定一组Servlet的URL匹配模式,等价于<url-pattern>标签。 |
loadOnStartup | int | 指定Servlet的加载顺序,等价于<load-on-startup>标签。 |
initParams | WebInitParam[] | 指定一组Servlet初始化参数,等价于<init-param>标签 |
asyncSupported | boolean | 声明Servlet是否支持异步操作,等价于<async-supported>标签。 |
smallIcon | String | 此Servlet的小图标。 |
largeIcon | String | 此Servlet的大图标。 |
description | String | 该Servlet的描述信息,等价于<description>标签。 |
displayName | String | 该Servlet的显示名,通常配合工具使用,等价于<display-name>标签。 |
使用注解配置Servlet的示例如下:
package com.thr; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author tanghaorong * @date 2020-04-20 * @desc 使用注解@WebServlet配置Servlet */ //name = "MyServlet":servlet名称,相当于web.xml中的<servlet-name> //urlPatterns = "/HelloServlet":servlet的访问路径,相当于<url-pattern> @WebServlet(name = "MyServlet",value = "/HelloServlet") public class MyServlet extends HttpServlet { public MyServlet() { super(); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException { System.out.println("get 请求执行"); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException { System.out.println("post 请求执行"); } }
上面实现的效果和web.xml中是一模一样的,是不是这样太爽了,所以一般推荐使用注解进行开发,现在无论任何系统开发都基本上摒弃了XML开发,因为开发效率不高,而且排错也很麻烦。
使用 * 通配符模糊匹配映射Servlet程序,在前面的所有例子中我们映射的URL都是精确匹配,而在Servlet映射到的URL中也是可以使用 * 通配符进行模糊匹配的。
但是只能有两种固定的格式:一种格式是"*.扩展名"(例如:*.do *.action),另一种格式是以正斜杠(/)开头并以"/*"结尾。
它们的匹配规则如下:
- /*:匹配任何路径映射到servlet。
- /abc/*:匹配/abc/下的任意路径映射到servlet。
- /abc/def:只匹配/abc/def路径下的servlet。
- *.do:匹配 任意名称.do 的路径映射到servlet。
其中例如:/abc/*.do、/*.do、abc*.do 这些都是非法的,启动时候会报错,我亲自去试了一下,反正 * . 后缀名 这种格式前面是不能加正斜杠(/)的。
还有要注意的是,可能会出现这样的情况,例如:我请求的URL为:/abc/edf,而这个路径有两个Servlet匹配(/* 和 /abc/*),那么它会选择哪一个呢?
答:会选择/abc/edf 的Servlet,因为匹配的原则是"谁长得更像就找谁"。
举一个完整的Servlet栗子:
(1)、修改index.jsp页面。
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>登录</title> </head> <body> <h1 align="center" style="color: red;">欢迎您登录系统</h1><hr/> <div align="center"> <form method="post" action="/login"> <table> <tr> <td>Username:</td> <td><input type="text" name="username"/></td> </tr> <tr> <td>Password:</td> <td><input type="password" name="password"/></td> </tr> <tr> <td></td> <td> <input type="submit" value="登录"/> <input type="reset" value="重置"/> </td> </tr> </table> </form> </div> </body> </html>
(2)、创建名为LoginServlet的Servlet类。并用@WebServlet注释。
package com.thr; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * @author Administrator * @date 2020-04-20 * @desc Servlet登录的例子 */ @WebServlet(name = "loginServlet",value = "/login") public class LoginServlet extends HttpServlet { //因为设置了为Post请求,使用doGet方法就不写了 @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置request的编码 request.setCharacterEncoding("UTF-8"); // 获取信息 String name = request.getParameter("username"); String password = request.getParameter("password"); System.out.println(name+":"+password); // 设置response的编码 response.setCharacterEncoding("UTF-8"); response.setContentType("text/html"); // 获取PrintWriter对象 PrintWriter out = response.getWriter(); // 输出信息 out.println("<HTML>"); out.println("<HEAD><TITLE>登录信息</TITLE></HEAD>"); out.println("<BODY>"); out.println("姓名:" + name + "<br>"); out.println("密码:" + password + "<br>"); out.println("</BODY>"); out.println("</HTML>"); // 释放PrintWriter对象 out.flush(); out.close(); } }
执行结果:
(1)登录页面
(2)提交登录结果信息
4、Servlet生命周期
Servlet接口中定义了五个方法,我们看一看Servlet接口中方法:
package javax.servlet; import java.io.IOException; public interface Servlet { void init(ServletConfig var1) throws ServletException; ServletConfig getServletConfig(); void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException; String getServletInfo(); void destroy(); }
其中有三个为生命周期方法:init(),service(),destory():
- init()方法用于初始化该Servlet。当Servlet第一次被加载时,Servlet引擎调用这个Servlet的init()方法,而且只调用一次。
- service()方法用于处理请求。这是Servlet最重要的方法,是真正处理请求的地方。对于每个请求,Servlet引擎都会调用Servlet的service方法,并把Servlet请求对象和Servlet响应对象最为参数传递给它,并且判断Servlet调用的是doGet方法还是doPost方法。
- destory()方法用于销毁该Servlet。这是相对于init的可选方法,当Servlet即将被卸载时由Servlet引擎来调用,这个方法用来清除并释放在init方法中所分配的资源。
此外,还有两个非生命周期方法。
- getServletInfo( 方法用于返回Servlet的一段描述,可以返回一段字符串。
- getServletConfig( )方法用于返回由Servlet容器传给init( )方法的ServletConfig对象。
下面来编写一个简单的Servlet来验证一下它的生命周期:
package com.thr; import javax.servlet.*; import javax.servlet.annotation.WebServlet; import java.io.IOException; /** * @author Administrator * @date 2020-04-22 * @desc Servlet生命周期 */ @WebServlet(value = "/HelloServlet1") public class MyServlet1 implements Servlet { @Override public void init(ServletConfig servletConfig) throws ServletException { System.out.println("Servlet完成初始化--init()"); } @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { System.out.println("Servlet正在执行操作--service()"); } @Override public String getServletInfo() { return null; } @Override public void destroy() { System.out.println("Servlet已经销毁--destroy()"); } }
服务器运行后我们在浏览器访问:http://localhost:8080/HelloServlet1,控制台输出了如下信息:
然后,我们在浏览器中刷新3遍:
接下来,我们关闭Servlet容器:
以上就是一个Servlet的整个生命周期了。可以发现,在Servlet的整个生命周期内,Servlet的init方法只被调用一次。也就是说当客户端多次Servlet请求时,服务器只会创建一个Servlet实例对象,而且Servlet实例对象一旦创建,它就会驻留在内存中,为后续的其它请求服务,直至web容器退出,Servlet实例对象才会销毁。而对一个Servlet的每次访问请求都导致Servlet引擎调用一次servlet的service方法。对于每次访问请求,Servlet引擎都会创建一个新的HttpServletRequest请求对象和一个新的HttpServletResponse响应对象,然后将这两个对象作为参数传递给它调用的Servlet的service()方法,service方法再根据请求方式分别调用doXXX方法。
上面说当客户端在第一次访问Servlet的时候会才创建Servlet实例对象,那如果这个Servlet程序要处理的信息很多,那就会造成第一次访问的Servlet加载时间较长。所以为了解决这样的问题Servlet提供了自动加载机制,就是在启动服务器的时候就将Servlet加载起来,它的操作很简单。我们可以在web.xml中配置也可以在注解中配置。
- 在web.xml中进行配置:
<servlet> <servlet-name>HelloServlet</servlet-name> <servlet-class>com.thr.MyServlet</servlet-class> <!-- 让servlet对象自动加载 --> <load-on-startup>1</load-on-startup> </servlet>
注意:<load-on-startup></load-on-startup>中的整数值越大,创建优先级越低!
- 在注解中进行配置:
@WebServlet(name = "MyServlet",value = "/HelloServlet",loadOnStartup = 1) public class MyServlet extends HttpServlet {
通过上面的实例,可以看到Servlet在创建时只会执行一次init()方法,后面每次点击都只调用 service() 方法。那么Servlet的一次执行过程是什么样的呢?
上面这幅图可以这样理解:
1、客户端向 Web 服务器发送请求,服务器查询 web.xml 文件配置。根据请求信息找到对应的 Servlet。
2、Servlet 引擎检查是否已经装载并创建了该 Servlet 的实例对象,如果有,则直接执行第4步,否则执行第3步,
3、Web 服务器加载 Servlet,并调用 Servlet 构造器(只会调用一次),创建 Servlet 的实例对象。并调用 init() 方法,完成 Servlet 实例对象的初始化(只会调用一次)。
4、Web 服务器把接收到的 http 请求封装成 ServletRequest 对象,并创建一个 响应消息的 ServletResponse 对象,作为 service() 方法的参数传入。(每一次访问都会调用一次该方法)
5、执行 service() 方法,并将处理信息封装到 ServletResponse 对象中返回
6、浏览器拆除 ServletResponse 对象,形成 http 响应格式,返回给客户端。
7、Web 应用程序停止或者重新启动之前,Servlet 引擎将卸载 Servlet实例,并在卸载之前调用 destory() 方法
5、ServletConfig对象
ServletConfig对象是用来读取web.xml中用<init-param>配置的初始化参数。
当我们的Servlet配置了初始化参数后,启动服务器,web容器在创建Servlet实例对象后,接着ServletConfig对象就会被创建,而且会自动将初始化参数封装到ServletConfig对象中,并在调用Servlet的init方法时,将ServletConfig对象传递给创建好的Servlet。进而,我们通过ServletConfig对象就可以得到当前Servlet的初始化参数信息。
ServletConfig中有四个方法如下:
- String getServletName():获取当前Servlet的名称,即:<servlet-name>中的内容。
- ServletContext getServletContext():获取当前当前Web的应用上下文,即整个Servlet。
- String getInitParameter(String var1):通过名称获取指定初始化参数的值。
- Enumeration<String> getInitParameterNames():获取所有初始化参数的名称。
使用ServletConfig对象获取初始化数据的简单举例:
1)在Servlet的配置文件web.xml中,可以使用一个或多个<init-param>标签为servlet配置一些初始化参数。
<!--配置一个Servlet--> <servlet> <servlet-name>config</servlet-name> <servlet-class>com.thr.ServletConfigDemo</servlet-class> <!-- 初始参数:这些参数会在加载web应用的时候,封装到ServletConfig对象中 --> <init-param> <param-name>name</param-name> <param-value>tanghaorong</param-value> </init-param> <init-param> <param-name>password</param-name> <param-value>123456</param-value> </init-param> <!-- 让servlet对象自动加载,注意:init-param要在自动加载之前 --> <load-on-startup>1</load-on-startup> </servlet> <!--配置Servlet的映射--> <servlet-mapping> <servlet-name>config</servlet-name> <!-- Servlet的对外访问路径 --> <url-pattern>/config</url-pattern> </servlet-mapping>
2)获取web.xml中<init-param>标签初始化参数,代码如下:
package com.thr; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.Enumeration; /** * @author tanghaorong * @date 2020-04-22 * @desc 使用ServletConfig对象获取初始化参数 */ public class ServletConfigDemo extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request,response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //第一种方式——getInitParameter ServletConfig config = this.getServletConfig(); String name = config.getInitParameter("name"); String password = config.getInitParameter("password"); System.out.println(name+":"+password); // 设置response的编码 response.setCharacterEncoding("UTF-8"); response.setContentType("text/html"); // 获取PrintWriter对象 PrintWriter out = response.getWriter(); // 输出信息 out.println("<HEAD><TITLE>初始化信息</TITLE></HEAD>"); out.println("姓名:" + name + "<br>"); out.println("密码:" + password + "<br><hr>"); //第二种方式——getInitParameterNames Enumeration<String> initParameterNames = this.getServletConfig().getInitParameterNames(); while (initParameterNames.hasMoreElements()) { String names = initParameterNames.nextElement(); String value = config.getInitParameter(names); System.out.println(value); response.setCharacterEncoding("UTF-8"); response.setContentType("text/html"); response.getWriter().print(names + "=" + value + "<br/>"); } // 释放PrintWriter对象 out.flush(); out.close(); } }
3)启动 Tomcat 服务器,在浏览器的地址栏中输入地址: http://localhost:8080/config 访问Servlet,结果如图所示。
从上图中可以看出,web.xml 文件中配置的信息全部被读取了出来。
6、ServletContext对象
ServletContext对象表示的当前整个Web应用。这个对象在Tomcat启动的时候,会创建一个唯一的ServletContext对象代表当前的整个Web应用,该对象封装了当前Web应用的所有信息。我们一般用来获取整个应用的初始化配置信息、读取资源文件、多个Servlet之间的通信等。所以ServletContext是相对于整个的应用,而ServletConfig是单个的应用。
下面对ServletContext对象获取不同的资源分别进行讲解。
①、获取Web应用程序的初始化参数
我们在web.xml文件中,不仅可以配置Servlet的映射信息和初始化信息,也可以配置整个Web应用的初始化信息。Web应用初始化参数的配置方式具体如下所示:
<!--配置整个Web应用的初始化信息格式--> <context-param> <param-name>AAA</param-name> <param-value>BBB</param-value> </context-param> <context-param> <param-name>CCC</param-name> <param-value>DDD</param-value> </context-param>
注意:在上面的配置文件中,<context-param> 元素位于根元素 <web-app> 中,它的子元素 <param-name> 和 <param-value> 分别用于指定参数的名字和参数值。要想获取这些参数名和参数值的信息,可以使用 ServletContext对象中定义的 getInitParameterNames() 和 getInitParameter(String name)方法分别获取。
下面通过案例演示如何使用 ServletContext对象获取Web应用程序的初始化参数。
1)在项目的web.xml文件中配置初始化参数信息和Servlet信息,其代码如下所示:
<!--配置整个Web应用的初始化信息--> <context-param> <param-name>name</param-name> <param-value>tanghaorong</param-value> </context-param> <context-param> <param-name>password</param-name> <param-value>123456</param-value> </context-param> <!--配置一个Servlet--> <servlet> <servlet-name>context</servlet-name> <servlet-class>com.thr.ServletContextDemo</servlet-class> <!-- 让servlet对象自动加载,注意:init-param要在自动加载之前 --> <load-on-startup>1</load-on-startup> </servlet> <!--配置Servlet的映射--> <servlet-mapping> <servlet-name>context</servlet-name> <!-- Servlet的对外访问路径 --> <url-pattern>/context</url-pattern> </servlet-mapping>
2)使用ServletContext对象获取web.xml中配置的信息,代码如下所示。
package com.thr; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Enumeration; /** * @author tanghaorong * @date 2020-04-23 * @desc 使用ServletContext对象获取整个Web应用的配置信息 */ public class ServletContextDemo extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request,response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //获取ServletContext对象 ServletContext context = this.getServletContext(); Enumeration<String> names = context.getInitParameterNames(); while (names.hasMoreElements()) { //获取配置信息 String name = names.nextElement(); String value = context.getInitParameter(name); //打印 System.out.println(name+":"+value); response.setCharacterEncoding("UTF-8"); response.setContentType("text/html"); response.getWriter().println("<head><title>获取整个Web应用初始化信息</title></head>"); response.getWriter().println(name + "=" + value + "<br/>"); //释放流资源 response.getWriter().close(); } } }
上述代码中,当通过 this.getServletContext() 方法获取到 ServletContext 对象后,首先调用 getInitParameterNames() 方法,获取到包含所有初始化参数名的 Enumeration 对象,然后遍历 Enumeration 对象,根据获取到的参数名,通过 getInitParamter(String name)方法得到对应的参数值。
3)启动 Tomcat 服务器,在浏览器的地址栏中输入地址 http://localhost:8080/context访问,浏览器的显示结果如图所示。
从图中可以看出,web.xml 文件中配置的信息被读取了出来。
②、读取 Web 应用下的资源文件
我们在实际开发过程中,不仅需要从web.xml文件中配置信息,有时候也会会需要读取 Web 应用中的一些资源文件,如配置文件和日志文件等。为此,在 ServletContext 接口中定义了一些读取 Web 资源的方法,这些方法是依靠 Servlet 容器实现的。Servlet 容器根据资源文件相对于 Web 应用的路径,返回关联资源文件的 I/O 流或资源文件在系统的绝对路径等。
ServletContext对象中用于获取资源路径的相关方法。
- Set getResourcePaths(String path)返回一个 Set 集合,集合中包含资源目录中子目录和文件的路径名 称。参数 path 必须以正斜线(/)开始,指定匹配资源的部分路径
- String getRealPath(String path) 返回资源文件在服务器文件系统上的真实路径(文件的绝对路径)。参数 path 代表资源文件的虚拟路径,它应该以正斜线(/)开始,/ 表示当前 Web 应用的根目录,如果 Servlet 容器不能将虚拟路径转换为文 件系统的真实路径,则返回 null
- URL getResource(String path)返回映射到某个资源文件的 URL 对象。参数 path 必须以正斜线(/)开始,/ 表示当前 Web 应用的根目录
- InputStream getResourceAsStream(String path)返回映射到某个资源文件的 InputStream 输入流对象。参数 path 的传递规则和 getResource() 方法完全一致
熟悉了下面的方法后,在通过使用 ServletContext 对象读取资源文件举例:
1)在项目的src目录下创建一个名称为userInfo.properties的文件,文件中的配置信息如下:
name=tanghaorong password=654321
2)使用ServletContext对象获取userInfo.properties中的资源文件配置信息,代码如下所示:
package com.thr; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.util.Properties; /** * @author tanghaorong * @date 2020-04-23 * @desc 使用ServletContext对象获取整个Web应用的配置信息 */ public class ServletContextDemo1 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request,response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //第一种方式:使用ServletContext对象读取资源文件 ServletContext context = this.getServletContext(); //获取userInfo.properties文件 InputStream is = context.getResourceAsStream("/WEB-INF/classes/userInfo.properties"); //创建Properties并载入数据 Properties prop = new Properties(); prop.load(is); //打印在网页上 response.setCharacterEncoding("UTF-8"); response.setContentType("text/html"); response.getWriter().println("<head><title>使用ServletContext对象读取资源文件</title></head>"); response.getWriter().println("姓名"+":"+prop.getProperty("name")+ "<br/>"); response.getWriter().println("密码"+":"+prop.getProperty("password")+ "<br/>"); //第二种方式:使用类装载器读取资源文件 ClassLoader loader = ServletContextDemo1.class.getClassLoader(); InputStream resourceAsStream = loader.getResourceAsStream("userInfo.properties"); Properties properties = new Properties(); properties.load(resourceAsStream); String name = properties.getProperty("name"); String password = properties.getProperty("password"); //在控制台打印 System.out.println("姓名:"+name+",密码:"+password); } }
3)启动 Tomcat 服务器,在浏览器的地址栏中输入地址 http://localhost:8080/context1访问,浏览器的显示结果如图所示。
从图中可以看出,userInfo.properties 资源文件中的内容已经被读取了出来。
③、多个Servlet之间的通信
即然ServletContext代表了整个Web应用,而一个Web应用可以有多个Servlet实例,也就意味着多个Servlet是可以实现通信的。
下面使用ServletContext实现多个Servlet之间的通信。
我们创建两个类ServletContextDemo2和ServletContextDemo3通过ServletContext对象实现通信。
ServletContextDemo2代码如下:
package com.thr; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.util.Properties; /** * @author tanghaorong * @date 2020-04-23 * @desc 使用ServletContext对象实现多个Servlet通信,放入 */ public class ServletContextDemo2 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request,response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String name="tanghaorong"; String password="123456"; ServletContext context = this.getServletContext(); context.setAttribute("name",name); context.setAttribute("password",password); } }
ServletContextDemo3代码如下:
package com.thr; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.util.Properties; /** * @author tanghaorong * @date 2020-04-23 * @desc 使用ServletContext对象实现多个Servlet通信,获取 */ public class ServletContextDemo3 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request,response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ServletContext context = this.getServletContext(); response.setCharacterEncoding("UTF-8"); response.setContentType("text/html"); response.getWriter().println("姓名"+":"+context.getAttribute("name")+ "<br/>"); response.getWriter().println("密码"+":"+context.getAttribute("password")+ "<br/>"); } }
先运行ServletContextDemo2,并且访问该Servlet,将数据name和password数据存储到ServletContext对象中。然后运行访问ServletContextDemo3,就可以从ServletContext对象中取出数据了,这样就实现了多个Servlet的通信,运行结果如下图所示:
7、Servlet的转发和重定向
转发:客户端向服务器端发送请求,服务器将请求转发到服务器内部,再响应给客户端。
@Override protected void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException { //转发 request.getRequestDispatcher("index.jsp").forward(request,response); }
访问后的地址:
重定向:客户端向服务器端发送请求,服务器告诉客户端你去重定向(状态码302,响应头location=客户端绝路路径),客户端继续向服务器发送请求(请求地址已经成重定向的地址),服务器端给客户端响应。
@Override protected void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException { //重定向的两种方式 //方式一 response.sendRedirect("https://www.baidu.com/"); //方式二 //response.setStatus(302); //response.setHeader("location","https://www.baidu.com/"); }
访问后的地址:
转发和重定向的区别:
请求次数:转发只发出了一次请求,而重定向发出了两次请求。
地址栏变化:转发不会改变地址栏中的URL,而重定向则会改变URL。
项目名称:转发不用写项目名称(http://localhost:8080/项目名称/),重定向需要编写项目名称(http://localhost:8080/)。
跳转范围:转发只能访问到当前web应用中的内容,而重定向则可以访问到任意web应用中的内容 。
request对象作用范围:转发后,在转发后的页面中仍然可以使用原来的request对象,而重定向,原来的request对象则失去作用。
参考资料: