重温Servlet,2020年了,它还有必要学吗?
1、本章前言
对呀!现在都2020年了,时间过得真快,本文来说说现在Servlet它还有必要学吗?因为Servlet已经是一个非常非常古老的技术了,而且在实际开发中几乎不会用到,在面试中也几乎不会问到Servlet相关的知识。所以我们不需要学习Servlet了吗?这样想就大错特错了。我们后面会学习到Struts2和SpringMVC框架,它两的底层都是跟Servlet有关,所以Servlet还是很有必要的学习的,最好不要跳过它。我们只有打下坚实的基础,后面的框架学习起来才能得心应手。
2、什么是Servlet
Servlet(Server Applet)是Java Servlet的简称,称为小服务程序或服务连接器,用Java编写的服务器端程序,主要功能在于交互式地浏览和修改数据,生成动态Web内容。这是百度百科上的一段话。说简单点Servlet就是对客户端发送过来的请求进行处理,并且作出相应的响应,其本质就是一个实现了Servlet接口的实现类。其过程如下:
- 客户端发送请求至Web服务器端。
- 服务器将请求信息发送至Servlet。
- Servlet 生成响应内容并将其传给服务器。响应内容动态生成,通常取决于客户端的请求。
- 服务器将响应返回给客户端。
注:servlet程序是由servlet容器(即tomcat服务器)进行管理,包括实例化、初始化、服务、销毁的过程都由tomca在指定时间内完成。
服务器的三大组件:
- servlet:用于处理请求和响应
- filter:用于过滤请求和响应
- listener:用于监听服务器的状态
3、创建Servlet程序
在创建Servlet之前需要提前配置好环境:1、安装好JDK;2、开发工具Eclipse或IDEA(推荐);3、安装Tomcat。这三个条件是必须的,具体怎么配置网上教程很多,这里不多BB。
创建Servlet程序的流程如下:
- 编写一个Java类,然后继承HttpServlet(或者继承GenericServlet,又或者直接实现Servlet接口)。
/**
* @author tanghaorong
* @date 2020-04-20
* @desc 创建一个Servlet程序
*/
public class MyServlet extends HttpServlet {
}
注意:我们一般都是继承HTTPServlet,因为HttpServlet是指能够处理HTTP协议请求的Servlet,它在原有Servlet接口上添加了一些与HTTP协议处理方法,它比Servlet接口的功能更为强大。因此开发人员在编写Servlet时,通常应继承这个类,而避免直接去实现Servlet接口和继承GenericServlet。而且HttpServlet在实现Servlet接口时,覆写了service方法,该方法体内的代码会自动判断用户的请求方式,如为GET请求,则调用HttpServlet的doGet方法,如为Post请求,则调用doPost方法。因此,开发人员在编写Servlet时,通常只需要覆写doGet或doPost方法,而不要去覆写service方法。
- 重写HttpServlet类中的doGet和doPost方法(IDEA快捷键Ctrl+O)。
/**
* @author tanghaorong
* @date 2020-04-20
* @desc 重写父类的doGet、doPost方法
*/
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("第一个Servlet程序");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
- 使用web.xml文件或者注解对servlet进行配置。推荐使用注解
前面两步没什么可说的,重点是在配置web.xml文件上,这一步决定了我们的请求和响应是哪个Servlet来完成的。上面说到配置Servlet有两种方式:一种是使用web.xml文件配置,另外一种就是使用注解配置,所以下面我们来详解介绍这两种配置方式:
3.1、使用web.xml文件配置
我们打开WEB-INF/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。
注:如果访问不了则在访问地址加上项目名: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
3.2、使用注解配置(推荐)
注:用了注解,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栗子:
①、修改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>
②、创建名为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();
}
}
执行结果:
- 登录页面:
- 提交登录结果信息:
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表示一个Servlet的配置信息,每个Servlet对象都有一个封装Servlet配置的ServletConfig对象。因此可通过此对象获取servlet相关信息,也可以用来读取web.xml中用<init-param>配置的Servlet初始化参数。
当我们的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对象获取初始化数据的简单举例:
①、在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>
②、获取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();
}
}
③、启动 Tomcat 服务器,在浏览器的地址栏中输入地址: http://localhost:8080/config 访问Servlet,结果如图所示。
从上图中可以看出,web.xml 文件中配置的信息全部被读取了出来。
6、ServletContext对象
ServletContext对象表示的当前整个上下文(应用程序),也就是整个Web应用。这个对象在Tomcat启动的时候,会创建一个唯一的ServletContext对象代表当前的整个Web应用,该对象封装了当前Web应用的所有信息。我们一般用来配置或者获取整个应用的初始化配置信息、读取资源文件、多个Servlet之间的通信等。所以ServletContext是相对于整个的应用,而ServletConfig是单个的应用。
下面对ServletContext对象获取不同的资源分别进行讲解。
6.1、获取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应用程序的初始化参数。
①、在项目的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>
②、使用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)
方法得到对应的参数值。
③、启动 Tomcat 服务器,在浏览器的地址栏中输入地址 http://localhost:8080/context 访问,浏览器的显示结果如图所示。
从图中可以看出,web.xml 文件中配置的信息被读取了出来。
6.2、读取 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 容器不能将虚拟路径转换为文 件系统的真实路径,则返回 nullURL getResource(String path)
:返回映射到某个资源文件的 URL 对象。参数 path 必须以正斜线(/)开始,/ 表示当前 Web 应用的根目录InputStream getResourceAsStream(String path)
:返回映射到某个资源文件的 InputStream 输入流对象。参数 path 的传递规则和 getResource() 方法完全一致
熟悉了下面的方法后,在通过使用 ServletContext 对象读取资源文件举例:
在项目的src目录下创建一个名称为userInfo.properties的文件,文件中的配置信息如下:
name=tanghaorong
password=654321
②、使用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);
}
}
③、启动 Tomcat 服务器,在浏览器的地址栏中输入地址 http://localhost:8080/context1 访问,浏览器的显示结果如图所示。
从图中可以看出,userInfo.properties 资源文件中的内容已经被读取了出来。
6.3、多个Servlet之间的通信
ServletContext代表了整个Web应用,并且数据是共享的,而一个Web应用可以有多个Servlet实例,也就意味着多个Servlet是可以实现通信的。下面使用ServletContext实现多个Servlet之间的通信,相关的方法如下。
- Object getAttribute(String var1):获取域对象中共享的数据
- void setAttribute(String var1, Object var2):向域对象中共享数据
- void removeAttribute(String var1):删除域对象中共享的数据
我们创建两个类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,GenericServlet和HTTPServlet
Servlet,GenericServlet和HTTPServlet三者之间的关系如下图。
①、Servlet接口是Servlet程序的根接口,里面定义了5个方法。
public interface Servlet {
//初始化
void init(ServletConfig var1) throws ServletException;
//获取ServletConfig对象
ServletConfig getServletConfig();
//用于处理请求和响应
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
//获取Servlet的相关信息
String getServletInfo();
//销毁
void destroy();
}
②、GenericServlet实现了Servlet接口和ServletConfig接口,它是一个抽象类,将Servlet接口中的init()、destroy()、getServletConfig()、getServletInfo()进行了重写,并且将service()设置为抽象方法,因此若创建的Servlet继承了GenericServlet,则只需要重写service()即可,但是在实际中一般不会使用它,因为它有一个子类HttpServlet,功能更加强大。
GenericServlet抽象类相比于直接实现Servlet接口,有以下几个好处:
- 为Servlet接口中的所有方法提供了默认的实现,则程序员需要什么就直接改什么,不再需要把所有的方法都自己实现了。
- 提供了一系列的方法,包括ServletConfig对象中的方法。
- 将init( )方法中的ServletConfig参数赋给了一个内部的ServletConfig引用从而来保存ServletConfig对象,不需要程序员自己去维护ServletConfig了。
package javax.servlet;
import java.io.IOException;
import java.io.Serializable;
import java.util.Enumeration;
import java.util.ResourceBundle;
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
private static final String LSTRING_FILE = "javax.servlet.LocalStrings";
private static ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.LocalStrings");
private transient ServletConfig config;
public GenericServlet() {
}
public void destroy() {
}
public String getInitParameter(String name) {
ServletConfig sc = this.getServletConfig();
if (sc == null) {
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
} else {
return sc.getInitParameter(name);
}
}
public Enumeration<String> getInitParameterNames() {
ServletConfig sc = this.getServletConfig();
if (sc == null) {
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
} else {
return sc.getInitParameterNames();
}
}
public ServletConfig getServletConfig() {
return this.config;
}
public ServletContext getServletContext() {
ServletConfig sc = this.getServletConfig();
if (sc == null) {
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
} else {
return sc.getServletContext();
}
}
public String getServletInfo() {
return "";
}
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
public void init() throws ServletException {
}
public void log(String msg) {
this.getServletContext().log(this.getServletName() + ": " + msg);
}
public void log(String message, Throwable t) {
this.getServletContext().log(this.getServletName() + ": " + message, t);
}
public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
public String getServletName() {
ServletConfig sc = this.getServletConfig();
if (sc == null) {
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
} else {
return sc.getServletName();
}
}
}
③、HttpServlet继承了GenericServlet,还记得在GenericServlet中的service()方法,将其定义为了一个抽象的方法,所以在HttpServlet中肯定会进行重写,然后来具体的看一看HttpServlet抽象类是如何实现自己的service方法吧。
首先来看GenericServlet抽象类中是如何定义service方法的:
public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
HttpServlet又是怎么重写这个service方法的:
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
if (req instanceof HttpServletRequest && res instanceof HttpServletResponse) {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
this.service(request, response);
} else {
throw new ServletException("non-HTTP request or response");
}
}
可以发现HttpServlet在重写GenericServlet中的service()时,将ServletRequest强制转成了HttpServletRequest,ServletResponse强制转成了HttpServletResponse,之所以将它两转为Http类型的因为它们的功能更加强大。然后又调用了一个拥有HttpServletRequest和HttpServletResponse为参数的service(),再来看看这个方法是如何实现的:
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取请求的类型;GET|POST|PUT|DELETE...
String method = req.getMethod();
long lastModified;
//是否为GET请求
if (method.equals("GET")) {
lastModified = this.getLastModified(req);
if (lastModified == -1L) {
this.doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader("If-Modified-Since");
if (ifModifiedSince < lastModified) {
this.maybeSetLastModified(resp, lastModified);
this.doGet(req, resp);
} else {
resp.setStatus(304);
}
}
//是否为HEAD请求
} else if (method.equals("HEAD")) {
lastModified = this.getLastModified(req);
this.maybeSetLastModified(resp, lastModified);
this.doHead(req, resp);
//是否为POST请求
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}
}
在这个方法中,先获取了当前请求的请求方式,通过判断请求的方式调用不同的方法,分别调用了doXXX()方法,例如:GET请求调用了doGet(),POST请求调用了doPost(),由于浏览器只能发送GET和POST请求,因此若Servlet继承了HttpServlet,只需要重写其中的doGet()和doPost()即可。
8、HttpServletRequest接口
HttpServletRequest表示Http环境中的Servlet请求。它是一个接口,继承自javax.servlet.ServletRequest接口,它封装了请求报文,因此可以通过此对象获取请求报文中的数据以及请求转发。HttpServletRequest在ServletRequest接口的基础上添加下面这几个方法:
String getContextPath()
:返回请求上下文的请求URI部分Cookie[] getCookies()
:返回一个cookie对象数组String getHeader(String var1)
:返回指定HTTP标题的值String getMethod()
:返回生成这个请求HTTP的方法名称String getQueryString()
:返回请求URL中的查询字符串HttpSession getSession()
:返回与这个请求相关的会话对象
8.1、HttpServletRequest内封装的请求
8.2、通过request获得请求行
获得请求行的相关方法如下:
- String getMethod():获取请求的方式(get/post)
- String getRequestURI():获取请求的URI地址
- StringBuffer getRequestURL():获取请求的URL地址
- String getContextPath():获取web应用的名称
- String getQueryString():获取get提交url地址后的参数字符串
request获得请求行示例代码:
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(req.getMethod());
System.out.println(req.getRequestURI());
System.out.println(req.getRequestURL());
System.out.println(req.getContextPath());
System.out.println(req.getQueryString());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
获取结果:
8.3、通过request获得请求头
获得请求头的相关方法如下:
- String getHeader(String name):根据请求头的key获取对应的value
- Enumeration getHeaderNames():获取请求头中所有的key
- Enumeration getHeaders(String name):根据请求头的key获取对应批量的value
- int getIntHeader(String name):根据请求头的key获取对应的value,它返回的是Int类型的值
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//根据请求头的key获取对应的value
System.out.println(req.getHeader("Host"));
//获取请求头中所有的key
Enumeration<String> names = req.getHeaderNames();
while (names.hasMoreElements()){
System.out.println(names.nextElement());
}
//根据请求头的key获取对应批量的value
Enumeration<String> accept = req.getHeaders("Accept");
while (accept.hasMoreElements()){
System.out.println(accept.nextElement());
}
//根据请求头的key获取对应的value,它返回的是Int类型的值
System.out.println(req.getIntHeader("Content-length"));
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
补充:HTTP请求中的常用消息头
- accept:浏览器通过这个头告诉服务器,它所支持的数据类型
- Accept-Charset: 浏览器通过这个头告诉服务器,它支持哪种字符集
- Accept-Encoding:浏览器通过这个头告诉服务器,支持的压缩格式
- Accept-Language:浏览器通过这个头告诉服务器,它的语言环境
- Host:浏览器通过这个头告诉服务器,想访问哪台主机
- If-Modified-Since: 浏览器通过这个头告诉服务器,缓存数据的时间
- Referer:浏览器通过这个头告诉服务器,客户机是哪个页面来的 防盗链
- Connection:浏览器通过这个头告诉服务器,请求完后是断开链接还是何持链接
8.4、通过request获得请求体
上面请求体中的内容是通过post提交的请求参数,格式是:
username=tanghaorong&password=123456
#对应的key-value格式为
username:tanghaorong
password:123456
以上面参数为例,通过一下方法获得请求参数:
- String getParameter(String name):根据请求体中的key获取value
- String[] getParameterValues(String name):根据请求体中的key获取批量value
- Enumeration getParameterNames():获取所有请求体中的key
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//根据请求体中的key获取value
System.out.println(req.getParameter("username"));
System.out.println(req.getParameter("password"));
//根据请求体中的key获取批量value
String[] usernames = req.getParameterValues("username");
for (String username : usernames) {
System.out.println(username);
}
//获取所有请求体中的key
Enumeration<String> names = req.getParameterNames();
while (names.hasMoreElements()){
System.out.println(names.nextElement());
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
8.5、Request乱码问题的解决方法
在service中使用的编码解码方式默认为:ISO-8859-1编码,但此编码并不支持中文,因此会出现乱码问题,所以我们需要手动修改编码方式为UTF-8编码,才能解决中文乱码问题,下面是发生乱码的具体细节:
乱码问题解决:
- 解决get提交的方式的乱码:
- 在tomcat的配置文件server.xml,在71行左右,改端口号的标签中,加入属性URIEncoding="UTF-8"
- 或者使用String parameter = new String(parameter.getbytes("iso8859-1"),"utf-8");
- 解决post提交方式的乱码:
- 在获取请求参数之前设置
request.setCharacterEncoding("UTF-8");
- 在获取请求参数之前设置
9、HttpServletResponse
HttpServletResponse也是一个接口,它继承自ServletResponse接口,专门用来封装HTTP响应报文,由于HTTP请求消息分为状态行,响应消息头,响应消息体三部分,因此,在HttpServletResponse接口中定义了向客户端发送响应状态码,响应消息头,响应消息体的方法。
9.1、HttpServletResponse内封装的响应
9.2、Response的乱码问题解决
//设置响应报文的编码格式
response.setCharacterEncoding("UTF-8");
//设置响应报文中响应体的内容格式以及浏览器的解码方式
response.setContentType("text/html;charset=UTF-8");
//向浏览器响应数据,就是将数据以响应体的方式响应到浏览器
PrintWriter writer = response.getWriter();
writer.print("helloworld<br>");
writer.write("你好");
10、Servlet的转发和重定向
转发:客户端向服务器端发送请求,服务器将请求转发到服务器内部,再响应给客户端。
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
//转发
request.getRequestDispatcher("success.jsp").forward(request,response);
}
访问后的地址:
重定向:客户端向服务器端发送请求,服务器告诉客户端你去重定向(状态码302,响应头location=客户端绝路路径),客户端继续向服务器发送请求(请求地址已经成重定向的地址),服务器端给客户端响应。
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
//重定向的两种方式
//方式一
//resp.sendRedirect(req.getContextPath()+"/success.html");
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对象则失去作用。
11、补充:web应用中的路径问题(重要)
路径分为相对路径和绝对路径:
- 相对路径:目标资源相当于当前位置的路径
- 绝对路径
- Static web:绝对路径指资源在磁盘上的完整路径
- Web Application:绝对路径指资源在服务器中的路径,以/开头的路径都是绝对路径
11.1、相对路径的缺点
1、对于相同的资源,在不同的页面中访问路径不同,即对于同一个资源没有统一的访问路径
2、若当前位置或目标资源的位置发生了变化,则相对路径有可能失效
3、由于转发浏览器发送一次请求,且在服务器的内部跳转到转发的地址,因此地址栏不变,即访问servlet的地址,因此造成了地址栏中的地址和页面中显示的内容不匹配,即当前位置发生了变化,就影响了页面中所有的相对路径
总结:相对路径不靠谱,推荐使用绝对路径
11.2、绝对路径
在web应用中,以 / 开头的路径都是绝对路径。绝对路径又分为由浏览器解析的绝对路径
和由服务器解析的绝对路径
:
- 由浏览器解析的绝对路径,/ 表示localhost:8080下访问
- 由浏览器解析的绝对路径的情况有:html标签中所设置的绝对路径(超链接、form标签中的action、img、link、script)、JavaScript中的location对象所设置的绝对路径、重定向中设置的绝对路径
<a href="/HelloServlet">HelloServlet</a> <--浏览器会将其解析的地址为:localhost:8080/HelloServlet,如果你有上下文路径的话则会报404错误!(上下文路径就是你的项目名称)-->
- 由服务器解析的绝对路径,/ 表示localhost:8080/上下文路径 下访问
- 由服务器解析的绝对路径的情况有:web.xml中url-pattern设置的绝对路径、转发中设置的绝对路径、jsp中jsp指令和jsp动作标签所设置的绝对路径
11.3、浏览器解析的绝对路径的解决方案
页面中所设置的绝对路径:手动添加上下文路径,例如:
<a href="${pageContext.request.contextPath}/success.jsp">success.jsp</a>
重定向中所设置的绝对路径:在绝对路径前通过request.getContextPath()拼接上下文路径,例如:
resp.sendRedirect(req.getContextPath() + "/success.jsp");
参考资料: