- 1.Servlet 概述
- 2.Servlet 实现方式
- 2.1 实现 Servlet 接口
- 2.2 继承 GenericServlet 抽象类
- 2.3 继承 HttpServlet 抽象类
- 3.Servlet 生命周期
- 4.Servlet 线程安全问题
- 5.Servlet 不同映射方式
- 6.Servlet 创建时机
- 7.默认 Servlet
Servlet 是运行在 Java 服务器端的程序,用于接收和响应来自客户端基于 HTTP 协议的请求。
如果想实现 Servlet 的功能,可以通过实现 javax.servlet.Servlet 接口或者继承它的实现类。
核心方法:service(),任何客户端的请求都会经过该方法。
2.Servlet 实现方式 2.1 实现 Servlet 接口实现所有的抽象方法。该方式支持最大程度的自定义。
TestServlet.java
package com.qdu;
import jakarta.servlet.*;
import java.io.IOException;
public class TestServlet implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("service方法执行了...");
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
web.xml
ServletFlag com.qdu.TestServlet ServletFlag /Servlet01
在浏览器中输入 localhost:8080/Servlet01,回车,在 IDEA 控制台输出 service方法执行了...。
执行流程:
2.2 继承 GenericServlet 抽象类必须重写 service 方法,其他方法可选择重写。该方式让我们开发 Servlet 变得简单。但是这种方式和 HTTP 协议无关。
TestServlet.java
package com.qdu;
import jakarta.servlet.GenericServlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import java.io.IOException;
public class TestServlet extends GenericServlet {
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("service方法执行了...");
}
}
web.xml
ServletFlag com.qdu.TestServlet ServletFlag /Servlet02
在浏览器中输入 localhost:8080/Servlet02,回车,在 IDEA 控制台输出 service方法执行了...。
执行过程:
2.3 继承 HttpServlet 抽象类需要重写 doGet 和 doPost 方法。该方式表示请求和响应都需要和 HTTP 协议相关。
TestServlet.java
package com.qdu;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("方法执行了...");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
web.xml
ServletFlag com.qdu.TestServlet ServletFlag /Servlet03
在浏览器中输入 localhost:8080/Servlet03,回车,在 IDEA 控制台输出 方法执行了...。
3.Servlet 生命周期对象的生命周期,就是对象从出生到死亡的过程。即出生 --> 活着 --> 死亡。官方说法是对象创建到销毁的过程!
出生:请求第一次到达 Servlet 时,对象就创建出来,并且初始化成功。只出生一次,将对象放到内存中。
活着:服务器提供服务的整个过程中,该对象一直存在,每次都是执行 service 方法。
死亡:当服务停止时或者服务器宕机时,对象死亡。
TestServlet.java
package com.qdu;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public class TestServlet extends HttpServlet {
@Override
public void init() throws ServletException {
System.out.println("对象创建并初始化成功...");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("接收到了客户端的请求...");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
@Override
public void destroy() {
System.out.println("对象销毁了...");
}
}
web.xml
ServletFlag com.qdu.TestServlet ServletFlag /Servlet03
结论:Servlet 对象只会创建一次,销毁一次。所以 Servlet 对象只有一个实例。如果一个对象实例在应用中是唯一的存在,那么我们就称它为单例模式。
4.Servlet 线程安全问题由于 Servlet 采用的是单例模式,也就是整个应用中只有一个实例对象。所以我们需要分析这个唯一的实例对象中的类成员是否线程安全。
package com.qdu;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class TestServlet extends HttpServlet {
private String username;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取用户名
username = req.getParameter("username");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 将用户名响应给浏览器
PrintWriter pw = resp.getWriter();
pw.println("welcome:" + username);
pw.close();
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
ServletFlag com.qdu.TestServlet ServletFlag /Servlet03
结论:一个浏览器代表一个线程,多个浏览器代表多个线程。按理说我们期望的应该是每个浏览器查看的都应该是自己的用户名。而现在的结果是浏览器中数据混乱。因此,我们可以认为 Servlet 是线程不安全的!
解决:定义类成员要谨慎。如果是共用的,并且只会在初始化时赋值,其他时间都是获取的话,那么是没问题的。如果不是共用的,或者每次使用都有可能对其赋值,那就要考虑线程安全问题了,可以将其定义到 doGet 或 doPost 方法内或者使用同步功能即可。
package com.qdu;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = null;
// 获取用户名
username = req.getParameter("username");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 将用户名响应给浏览器
PrintWriter pw = resp.getWriter();
pw.println("welcome:" + username);
pw.close();
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
package com.qdu;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class TestServlet extends HttpServlet {
private String username;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
synchronized (this) {
// 获取用户名
username = req.getParameter("username");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 将用户名响应给浏览器
PrintWriter pw = resp.getWriter();
pw.println("welcome:" + username);
pw.close();
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
5.Servlet 不同映射方式
第一种:具体名称的方式。访问的资源路径必须和映射配置完全相同。
第二种:/ 开头 + 通配符的方式。只要符合目录结构即可,不用考虑结尾是什么。
第三种:通配符 + 固定格式结尾的方式。只要符合固定结尾格式即可,不用考虑前面的路径。
注意:优先级问题。越是具体的优先级越高,越是模糊通用的优先级越低。第一种 --> 第二种 --> 第三种。
package com.qdu;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("TestServlet执行了...");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
ServletFlag com.qdu.TestServlet ServletFlag /Servlet03
ServletFlag com.qdu.TestServlet ServletFlag /servlet/*
ServletFlag com.qdu.TestServlet ServletFlag *.do
我们可以给一个 Servlet 配置多个访问映射,从而根据不同的请求路径来实现不同的功能。
示例:
- 如果访问的资源路径是 /vip,商品价格打 9 折。
- 如果访问的资源路径是 /vvip,商品价格打 5 折。
- 如果访问的资源路径是其他,商品价格不打折。
package com.qdu;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
int money = 1000;
// 获取访问的资源路径
String name = req.getRequestURI();
name = name.substring(name.lastIndexOf("/"));
if ("/vip".equals(name)) {
// 如果访问资源路径是/vip,商品价格为9折
System.out.println("商品原价为:" + money + "。优惠后是:" + (money*0.9));
} else if ("/vvip".equals(name)) {
// 如果访问资源路径是/vvip,商品价格为5折
System.out.println("商品原价为:" + money + "。优惠后是:" + (money*0.5));
} else {
// 如果访问资源路径是其他,商品价格原样显示
System.out.println("商品价格为:" + money);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
6.Servlet 创建时机ServletFlag com.qdu.TestServlet ServletFlag /qdu/*
(1) 第一次访问时创建
- 优势:减少对服务器内存的浪费。提高了服务器启动的效率。
- 弊端:如果有一些要在应用加载时就做的初始化操作,无法完成。
(2) 服务器加载时创建
- 优势:提前创建好对象,提高了首次执行的效率。可以完成一些应用加载时要做的初始化操作。
- 弊端:对服务器内存占用较多,影响了服务器启动的效率。
修改 Servlet 创建时机:在
正整数代表服务器加载时创建,值越小,优先级越高。负整数或不写代表第一次访问时创建。
7.默认 ServletservletDemo03 com.itheima.servlet.ServletDemo03 1 servletDemo03 /servletDemo03
默认 Servlet 是由服务器提供的一个 Servlet。它配置在 Tomcat 的 conf 目录中的 web.xml 中。
它的映射路径是



