通俗讲清楚:Servlet、Servlet容器

1. Servlet 是什么?

Servlet = 运行在服务器上的Java小程序,用来接收、处理浏览器HTTP请求,并返回响应。

简单拆解:

  • 客户端(浏览器)发请求 http://xxx/hello
  • 服务器调用一段Java代码(这个类就是Servlet)
  • Servlet执行业务逻辑、拼接页面/JSON,返回数据给浏览器

原生Servlet本质就是一个Java类,实现规范接口:

  1. public class HelloServlet implements Servlet{
  2. // 处理请求的核心方法
  3. service(HttpServletRequest req, HttpServletResponse resp){
  4. }
  5. }

一句话:Servlet是处理网络请求的Java组件规范。

2. Servlet容器 是什么?

Servlet容器 = 能够运行、管理Servlet的服务器软件,也叫Web容器。

常见实现:Tomcat、Jetty、Undertow、JBoss

容器负责干什么(Servlet自己做不到)

  1. 监听端口(8080),接收浏览器TCP连接
  2. 把原始HTTP报文封装成 HttpServletRequest、HttpServletResponse 对象传给Servlet
  3. 创建Servlet实例、管理生命周期(init() → service() → destroy())
  4. 多线程:每个请求分配一条线程调用Servlet
  5. 处理并发、网络IO、资源回收

👉 Servlet只负责业务处理;网络通信、生命周期、线程管理全部交给容器!

3. 两者关系(重点)

  1. 浏览器 ←HTTP→ 【Servlet容器(Tomcat)】 ←调用→ Servlet(业务代码)
  • Servlet只是一套接口规范,本身不能独立运行,不能监听端口;
  • Tomcat实现了Servlet规范,提供运行环境,所以Tomcat就是Servlet容器;
  • 如果没有Tomcat,单独写一个Servlet类直接main运行,无法接收网页请求。

4. 容易混淆的概念区分

  1. Web容器 ≈ Servlet容器 Web容器主要实现Servlet规范,处理动态资源(Servlet);静态html也能托管。
  2. JavaEE服务器(JBoss/WildFly) 不仅包含Servlet容器,还实现EJB、JTA等全套JavaEE规范。

    Tomcat只是Web容器,不是完整JavaEE服务器。

5. 发展史小补充

早期:浏览器只能访问静态html; SUN推出Servlet规范,让Java可以在服务器动态生成网页; 各大厂商开发容器实现这套规范(Tomcat最流行)。

极简总结背诵版

  1. Servlet:处理HTTP请求的Java组件,一套规范接口,负责业务逻辑。
  2. Servlet容器:Servlet的运行环境(Tomcat),负责网络通信、管理Servlet生命周期。
  3. Servlet不能独立运行,必须跑在Servlet容器里面。

原生Servlet完整示例(Tomcat 9/10通用区分说明)

Tomcat10 注意:包名变更 Tomcat9及以下:javax.servlet.* Tomcat10+/Jakarta EE:jakarta.servlet.* 下面先演示Tomcat9 版本(javax)

1. 第一步:导入依赖Maven

  1. <!-- Servlet API 依赖 -->
  2. <dependency>
  3. <groupId>javax.servlet</groupId>
  4. <artifactId>javax.servlet-api</artifactId>
  5. <version>4.0.1</version>
  6. <!-- scope provided:tomcat自带,打包不要带入 -->
  7. <scope>provided</scope>
  8. </dependency>

2. 编写Servlet类 HelloServlet.java

  1. import javax.servlet.ServletException;
  2. import javax.servlet.http.HttpServlet;
  3. import javax.servlet.http.HttpServletRequest;
  4. import javax.servlet.http.HttpServletResponse;
  5. import java.io.IOException;
  6. import java.io.PrintWriter;
  7. /**
  8. * HttpServlet:Servlet规范的实现基类
  9. */
  10. public class HelloServlet extends HttpServlet {
  11. /**
  12. * 初始化:Servlet第一次创建时执行,只执行1次
  13. */
  14. @Override
  15. public void init() throws ServletException {
  16. System.out.println("HelloServlet 初始化 init()");
  17. }
  18. /**
  19. * 处理GET请求
  20. */
  21. @Override
  22. protected void doGet(HttpServletRequest request,
  23. HttpServletResponse response) throws ServletException, IOException {
  24. // 设置响应内容类型
  25. response.setContentType("text/html;charset=utf-8");
  26. // 获取输出流,向浏览器写数据
  27. PrintWriter out = response.getWriter();
  28. out.println("<h1>你好,Servlet!</h1>");
  29. out.println("<p>当前处理GET请求</p>");
  30. out.println("<p>访问路径:/hello</p>");
  31. }
  32. /**
  33. * 处理POST请求
  34. */
  35. @Override
  36. protected void doPost(HttpServletRequest request,
  37. HttpServletResponse response) throws ServletException, IOException {
  38. response.setContentType("text/html;charset=utf-8");
  39. PrintWriter out = response.getWriter();
  40. out.println("<h1>收到POST请求</h1>");
  41. }
  42. /**
  43. * 销毁:容器关闭/项目卸载时执行,只执行1次
  44. */
  45. @Override
  46. public void destroy() {
  47. System.out.println("HelloServlet 销毁 destroy()");
  48. }
  49. }

方式A:传统配置 web.xml(老式配置方式)

路径:WEB-INF/web.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
  5. http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
  6. version="4.0">
  7. <!-- 注册Servlet -->
  8. <servlet>
  9. <servlet-name>helloServlet</servlet-name>
  10. <servlet-class>HelloServlet</servlet-class>
  11. </servlet>
  12. <!-- 映射访问地址 -->
  13. <servlet-mapping>
  14. <servlet-name>helloServlet</servlet-name>
  15. <url-pattern>/hello</url-pattern>
  16. </servlet-mapping>
  17. </web-app>

方式B:注解方式(推荐,Servlet3.0+ 不需要web.xml)

修改Servlet代码,增加注解,可以删掉web.xml

  1. import javax.servlet.annotation.WebServlet;
  2. // 访问地址:/hello
  3. @WebServlet("/hello")
  4. public class HelloServlet extends HttpServlet {
  5. // 里面doGet、doPost代码不变
  6. }

运行流程完整梳理(重点!面试常考)

  1. 启动Tomcat(Servlet容器),监听8080端口
  2. 浏览器发起请求:http://localhost:8080/项目名/hello
  3. Tomcat解析URL,匹配 @WebServlet("/hello")
  4. 判断HelloServlet是否实例化:
    • 第一次访问:执行init() → 创建对象
  5. 创建request、response对象(容器封装HTTP报文)
  6. 调用service(),根据请求方式分发到doGet()/doPost()
  7. Servlet通过response输出内容给浏览器
  8. 请求结束,线程回收;Servlet对象保留,等待下一次请求
  9. 关闭Tomcat时执行destroy()销毁Servlet

重要知识点

  1. Servlet是单实例多线程 整个容器中一个Servlet只会创建一个对象,多个请求共用这个实例,并发访问要注意线程安全!
  2. 生命周期顺序 init()(一次) → service()(每次请求) → destroy()(一次)
  3. HttpServlet只是实现Servlet接口的工具类 原始顶层接口:javax.servlet.Servlet
    1. public interface Servlet {
    2. void init(ServletConfig config);
    3. ServletConfig getServletConfig();
    4. void service(ServletRequest req, ServletResponse res);
    5. String getServletInfo();
    6. void destroy();
    7. }

拓展:和SpringMVC关联(后面你会学到)

SpringMVC底层核心 DispatcherServlet 本身就是一个Servlet! SpringBoot内置Tomcat(内嵌Servlet容器)。

如果你需要,我再给你演示:原生Servlet获取请求参数、转发、重定向的例子。

Servlet 常用功能示例:获取参数、转发、重定向

沿用上面 @WebServlet("/hello") 的代码,整合接收参数、请求转发、重定向、域对象存数据,直接复制就能测试。

Tomcat9:javax.servlet Tomcat10+:全部替换成 jakarta.servlet

  1. import javax.servlet.ServletException;
  2. import javax.servlet.annotation.WebServlet;
  3. import javax.servlet.http.HttpServlet;
  4. import javax.servlet.http.HttpServletRequest;
  5. import javax.servlet.http.HttpServletResponse;
  6. import java.io.IOException;
  7. import java.io.PrintWriter;
  8. import java.util.Enumeration;
  9. @WebServlet("/hello")
  10. public class HelloServlet extends HttpServlet {
  11. @Override
  12. protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  13. // 1. 设置编码(防止中文乱码!重中之重)
  14. request.setCharacterEncoding("utf-8");
  15. response.setContentType("text/html;charset=utf-8");
  16. PrintWriter out = response.getWriter();
  17. // ===================== 一、获取请求参数 =====================
  18. // 地址示例:http://localhost:8080/demo/hello?name=张三&age=22&hobby=篮球&hobby=游戏
  19. String name = request.getParameter("name"); // 获取单个参数
  20. String age = request.getParameter("age");
  21. String[] hobbies = request.getParameterValues("hobby"); // 获取多值(复选框)
  22. out.println("<h3>接收参数</h3>");
  23. out.println("姓名:" + name + "<br/>");
  24. out.println("年龄:" + age + "<br/>");
  25. out.print("爱好:");
  26. if(hobbies != null){
  27. for (String hobby : hobbies) {
  28. out.print(hobby + " ");
  29. }
  30. }
  31. out.println("<hr/>");
  32. // 遍历所有参数
  33. out.println("<h3>遍历全部参数</h3>");
  34. Enumeration<String> paramNames = request.getParameterNames();
  35. while (paramNames.hasMoreElements()){
  36. String key = paramNames.nextElement();
  37. out.println(key + " = " + request.getParameter(key) + "<br/>");
  38. }
  39. out.println("<hr/>");
  40. // ===================== 二、request域对象(请求域) =====================
  41. // 往request存放数据,用于【请求转发】传递数据
  42. request.setAttribute("msg","这是存在request中的消息");
  43. request.setAttribute("username","李四");
  44. // ===================== 三、两种跳转【重点区分面试必考】 =====================
  45. /*
  46. // 方式1:请求转发(服务器内部跳转,浏览器地址栏不变)
  47. // 特点:一次请求,可以共享request域数据
  48. request.getRequestDispatcher("/target").forward(request,response);
  49. return;
  50. */
  51. /*
  52. // 方式2:重定向(通知浏览器发起新请求,地址栏URL改变)
  53. // 特点:两次请求,不能共享request数据
  54. response.sendRedirect("/demo/target");
  55. return;
  56. */
  57. }
  58. @Override
  59. protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  60. // POST请求直接调用doGet,代码复用
  61. doGet(request,response);
  62. }
  63. }

再加一个目标Servlet用来测试跳转 TargetServlet.java

  1. import javax.servlet.ServletException;
  2. import javax.servlet.annotation.WebServlet;
  3. import javax.servlet.http.HttpServlet;
  4. import javax.servlet.http.HttpServletRequest;
  5. import javax.servlet.http.HttpServletResponse;
  6. import java.io.IOException;
  7. import java.io.PrintWriter;
  8. @WebServlet("/target")
  9. public class TargetServlet extends HttpServlet {
  10. @Override
  11. protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  12. response.setContentType("text/html;charset=utf-8");
  13. PrintWriter out = response.getWriter();
  14. // 获取转发带过来的数据
  15. String msg = (String) request.getAttribute("msg");
  16. out.println("<h3>到达Target页面</h3>");
  17. out.println("request域消息:" + msg);
  18. }
  19. }

📌 请求转发 VS 重定向(必须背下来)

对比 请求转发 forward 重定向 sendRedirect
发起方 服务器内部 通知浏览器重新访问
请求次数 1次请求 2次独立请求
地址栏 URL不变 URL改变
request域 数据共享 数据丢失
访问范围 只能当前项目内资源 可以跳转外部网址(百度等)
写法 request.getRequestDispatcher("/xxx").forward(req,resp) response.sendRedirect("/项目名/xxx")

易错坑: 转发路径:/target 不需要项目名 重定向路径:必须加项目上下文路径,推荐动态获取:

  1. String ctx = request.getContextPath();
  2. response.sendRedirect(ctx + "/target");

三个域对象简单认识(Servlet四大作用域先掌握3个)

  1. request:一次请求有效,转发间共享,请求结束销毁
  2. session:一次用户会话(浏览器不关闭),每个用户独立
    1. // 使用示例
    2. request.getSession().setAttribute("loginUser","admin");
  3. application(ServletContext):整个项目全局,所有用户共享,服务器启动创建,关闭销毁
    1. getServletContext().setAttribute("count",100);

额外补充常见API

  1. // 获取客户端IP
  2. request.getRemoteAddr();
  3. // 获取请求方式 GET/POST
  4. request.getMethod();
  5. // 获取项目访问根路径
  6. request.getContextPath();
  7. // 获取Cookie
  8. request.getCookies();
  9. // 获取Session
  10. request.getSession();

测试建议

  1. 先注释转发、重定向代码,访问 http://localhost:8080/demo/hello?name=张三&age=20&hobby=足球&hobby=读书 查看参数打印
  2. 放开转发代码测试,观察地址栏不变,能拿到msg
  3. 注释转发,放开重定向测试,地址栏变化,获取不到request中的msg

如果你想,我接下来可以整理: Servlet生命周期完整执行流程 + 面试常见问答题库。