重温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 + "="