通俗讲清楚:Servlet、Servlet容器
1. Servlet 是什么?
Servlet = 运行在服务器上的Java小程序,用来接收、处理浏览器HTTP请求,并返回响应。
简单拆解:
- 客户端(浏览器)发请求
http://xxx/hello - 服务器调用一段Java代码(这个类就是Servlet)
- Servlet执行业务逻辑、拼接页面/JSON,返回数据给浏览器
原生Servlet本质就是一个Java类,实现规范接口:
public class HelloServlet implements Servlet{// 处理请求的核心方法service(HttpServletRequest req, HttpServletResponse resp){}}
一句话:Servlet是处理网络请求的Java组件规范。
2. Servlet容器 是什么?
Servlet容器 = 能够运行、管理Servlet的服务器软件,也叫Web容器。
常见实现:Tomcat、Jetty、Undertow、JBoss
容器负责干什么(Servlet自己做不到)
- 监听端口(8080),接收浏览器TCP连接
- 把原始HTTP报文封装成
HttpServletRequest、HttpServletResponse对象传给Servlet - 创建Servlet实例、管理生命周期(
init()→service()→destroy()) - 多线程:每个请求分配一条线程调用Servlet
- 处理并发、网络IO、资源回收
👉 Servlet只负责业务处理;网络通信、生命周期、线程管理全部交给容器!
3. 两者关系(重点)
浏览器 ←HTTP→ 【Servlet容器(Tomcat)】 ←调用→ Servlet(业务代码)
- Servlet只是一套接口规范,本身不能独立运行,不能监听端口;
- Tomcat实现了Servlet规范,提供运行环境,所以Tomcat就是Servlet容器;
- 如果没有Tomcat,单独写一个Servlet类直接main运行,无法接收网页请求。
4. 容易混淆的概念区分
- Web容器 ≈ Servlet容器 Web容器主要实现Servlet规范,处理动态资源(Servlet);静态html也能托管。
- JavaEE服务器(JBoss/WildFly)
不仅包含Servlet容器,还实现EJB、JTA等全套JavaEE规范。
Tomcat只是Web容器,不是完整JavaEE服务器。
5. 发展史小补充
早期:浏览器只能访问静态html; SUN推出Servlet规范,让Java可以在服务器动态生成网页; 各大厂商开发容器实现这套规范(Tomcat最流行)。
极简总结背诵版
- Servlet:处理HTTP请求的Java组件,一套规范接口,负责业务逻辑。
- Servlet容器:Servlet的运行环境(Tomcat),负责网络通信、管理Servlet生命周期。
- Servlet不能独立运行,必须跑在Servlet容器里面。
原生Servlet完整示例(Tomcat 9/10通用区分说明)
Tomcat10 注意:包名变更 Tomcat9及以下:
javax.servlet.*Tomcat10+/Jakarta EE:jakarta.servlet.*下面先演示Tomcat9 版本(javax)
1. 第一步:导入依赖Maven
<!-- Servlet API 依赖 --><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.1</version><!-- scope provided:tomcat自带,打包不要带入 --><scope>provided</scope></dependency>
2. 编写Servlet类 HelloServlet.java
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;/*** HttpServlet:Servlet规范的实现基类*/public class HelloServlet extends HttpServlet {/*** 初始化:Servlet第一次创建时执行,只执行1次*/@Overridepublic void init() throws ServletException {System.out.println("HelloServlet 初始化 init()");}/*** 处理GET请求*/@Overrideprotected void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {// 设置响应内容类型response.setContentType("text/html;charset=utf-8");// 获取输出流,向浏览器写数据PrintWriter out = response.getWriter();out.println("<h1>你好,Servlet!</h1>");out.println("<p>当前处理GET请求</p>");out.println("<p>访问路径:/hello</p>");}/*** 处理POST请求*/@Overrideprotected void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {response.setContentType("text/html;charset=utf-8");PrintWriter out = response.getWriter();out.println("<h1>收到POST请求</h1>");}/*** 销毁:容器关闭/项目卸载时执行,只执行1次*/@Overridepublic void destroy() {System.out.println("HelloServlet 销毁 destroy()");}}
方式A:传统配置 web.xml(老式配置方式)
路径:WEB-INF/web.xml
<?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/javaeehttp://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"version="4.0"><!-- 注册Servlet --><servlet><servlet-name>helloServlet</servlet-name><servlet-class>HelloServlet</servlet-class></servlet><!-- 映射访问地址 --><servlet-mapping><servlet-name>helloServlet</servlet-name><url-pattern>/hello</url-pattern></servlet-mapping></web-app>
方式B:注解方式(推荐,Servlet3.0+ 不需要web.xml)
修改Servlet代码,增加注解,可以删掉web.xml
import javax.servlet.annotation.WebServlet;// 访问地址:/hello@WebServlet("/hello")public class HelloServlet extends HttpServlet {// 里面doGet、doPost代码不变}
运行流程完整梳理(重点!面试常考)
- 启动Tomcat(Servlet容器),监听8080端口
- 浏览器发起请求:
http://localhost:8080/项目名/hello - Tomcat解析URL,匹配
@WebServlet("/hello") - 判断HelloServlet是否实例化:
- 第一次访问:执行
init()→ 创建对象
- 第一次访问:执行
- 创建
request、response对象(容器封装HTTP报文) - 调用
service(),根据请求方式分发到doGet()/doPost() - Servlet通过response输出内容给浏览器
- 请求结束,线程回收;Servlet对象保留,等待下一次请求
- 关闭Tomcat时执行
destroy()销毁Servlet
重要知识点
- Servlet是单实例多线程 整个容器中一个Servlet只会创建一个对象,多个请求共用这个实例,并发访问要注意线程安全!
- 生命周期顺序
init()(一次) → service()(每次请求) → destroy()(一次) - HttpServlet只是实现Servlet接口的工具类
原始顶层接口:
javax.servlet.Servletpublic interface Servlet {void init(ServletConfig config);ServletConfig getServletConfig();void service(ServletRequest req, ServletResponse res);String getServletInfo();void destroy();}
拓展:和SpringMVC关联(后面你会学到)
SpringMVC底层核心 DispatcherServlet 本身就是一个Servlet!
SpringBoot内置Tomcat(内嵌Servlet容器)。
如果你需要,我再给你演示:原生Servlet获取请求参数、转发、重定向的例子。
Servlet 常用功能示例:获取参数、转发、重定向
沿用上面 @WebServlet("/hello") 的代码,整合接收参数、请求转发、重定向、域对象存数据,直接复制就能测试。
Tomcat9:
javax.servletTomcat10+:全部替换成jakarta.servlet
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;import java.util.Enumeration;@WebServlet("/hello")public class HelloServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {// 1. 设置编码(防止中文乱码!重中之重)request.setCharacterEncoding("utf-8");response.setContentType("text/html;charset=utf-8");PrintWriter out = response.getWriter();// ===================== 一、获取请求参数 =====================// 地址示例:http://localhost:8080/demo/hello?name=张三&age=22&hobby=篮球&hobby=游戏String name = request.getParameter("name"); // 获取单个参数String age = request.getParameter("age");String[] hobbies = request.getParameterValues("hobby"); // 获取多值(复选框)out.println("<h3>接收参数</h3>");out.println("姓名:" + name + "<br/>");out.println("年龄:" + age + "<br/>");out.print("爱好:");if(hobbies != null){for (String hobby : hobbies) {out.print(hobby + " ");}}out.println("<hr/>");// 遍历所有参数out.println("<h3>遍历全部参数</h3>");Enumeration<String> paramNames = request.getParameterNames();while (paramNames.hasMoreElements()){String key = paramNames.nextElement();out.println(key + " = " + request.getParameter(key) + "<br/>");}out.println("<hr/>");// ===================== 二、request域对象(请求域) =====================// 往request存放数据,用于【请求转发】传递数据request.setAttribute("msg","这是存在request中的消息");request.setAttribute("username","李四");// ===================== 三、两种跳转【重点区分面试必考】 =====================/*// 方式1:请求转发(服务器内部跳转,浏览器地址栏不变)// 特点:一次请求,可以共享request域数据request.getRequestDispatcher("/target").forward(request,response);return;*//*// 方式2:重定向(通知浏览器发起新请求,地址栏URL改变)// 特点:两次请求,不能共享request数据response.sendRedirect("/demo/target");return;*/}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {// POST请求直接调用doGet,代码复用doGet(request,response);}}
再加一个目标Servlet用来测试跳转 TargetServlet.java
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;@WebServlet("/target")public class TargetServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {response.setContentType("text/html;charset=utf-8");PrintWriter out = response.getWriter();// 获取转发带过来的数据String msg = (String) request.getAttribute("msg");out.println("<h3>到达Target页面</h3>");out.println("request域消息:" + msg);}}
📌 请求转发 VS 重定向(必须背下来)
| 对比 | 请求转发 forward | 重定向 sendRedirect |
|---|---|---|
| 发起方 | 服务器内部 | 通知浏览器重新访问 |
| 请求次数 | 1次请求 | 2次独立请求 |
| 地址栏 | URL不变 | URL改变 |
| request域 | 数据共享 | 数据丢失 |
| 访问范围 | 只能当前项目内资源 | 可以跳转外部网址(百度等) |
| 写法 | request.getRequestDispatcher("/xxx").forward(req,resp) |
response.sendRedirect("/项目名/xxx") |
易错坑: 转发路径:
/target不需要项目名 重定向路径:必须加项目上下文路径,推荐动态获取:
String ctx = request.getContextPath();response.sendRedirect(ctx + "/target");
三个域对象简单认识(Servlet四大作用域先掌握3个)
- request:一次请求有效,转发间共享,请求结束销毁
- session:一次用户会话(浏览器不关闭),每个用户独立
// 使用示例request.getSession().setAttribute("loginUser","admin");
- application(ServletContext):整个项目全局,所有用户共享,服务器启动创建,关闭销毁
getServletContext().setAttribute("count",100);
额外补充常见API
// 获取客户端IPrequest.getRemoteAddr();// 获取请求方式 GET/POSTrequest.getMethod();// 获取项目访问根路径request.getContextPath();// 获取Cookierequest.getCookies();// 获取Sessionrequest.getSession();
测试建议
- 先注释转发、重定向代码,访问
http://localhost:8080/demo/hello?name=张三&age=20&hobby=足球&hobby=读书查看参数打印 - 放开转发代码测试,观察地址栏不变,能拿到msg
- 注释转发,放开重定向测试,地址栏变化,获取不到request中的msg
如果你想,我接下来可以整理: Servlet生命周期完整执行流程 + 面试常见问答题库。
