一、Servlet出现的背景:Web开发的“刚需”催生

要理解Servlet的出现,我们得先回到早期的Web开发场景中。
在互联网刚兴起时,Web页面大多是“静态”的——也就是纯HTML页面,内容固定不变,用户只能被动浏览。但随着需求发展,用户需要和服务器交互(比如登录、查询数据、提交表单),这就需要“动态”Web页面,即页面内容能根据用户请求动态生成。

早期实现动态Web的技术有CGI(公共网关接口)、PHP等,但都存在明显问题:

  • CGI的痛点:每处理一个用户请求,就会创建一个新的进程。如果同时有上千个用户访问,服务器会被大量进程占满资源,性能极差,而且CGI需要用C/C++等语言开发,难度高、跨平台性差。

  • PHP的局限:虽然开发简单,但在大型企业级应用中,性能和可维护性不如Java生态,且当时Java作为跨平台语言的优势已逐渐凸显。

此时,Java语言急需一种能高效处理Web请求、支持跨平台、可复用的技术,来满足企业级动态Web开发的需求。于是,Sun公司在1997年推出了Servlet技术,它本质上是运行在服务器端的Java程序,专门用于接收和处理客户端的HTTP请求,生成动态的Web响应。

简单说:Servlet的出现,就是为了解决Java语言在Web开发中的“空白”,替代低效的CGI等技术,提供一种高效、跨平台、面向对象的动态Web开发方案。

二、Servlet与其他类似技术的对比及优势

1. 与CGI对比:“单线程多任务”vs“多进程”

1.1 举例
  • CGI就像一家餐厅,每来一个顾客(用户请求),就必须新雇一个服务员(创建进程)专门服务这个顾客,顾客吃完(请求处理完),服务员就离职(进程销毁)。如果高峰期来了1000个顾客,就要雇1000个服务员,餐厅(服务器)根本扛不住,成本高、效率低。

  • Servlet就像这家餐厅优化后的模式:只雇几个固定的服务员(线程),顾客来了就由现有服务员轮流服务,一个服务员服务完一个顾客,再去服务下一个(线程复用)。哪怕来了1000个顾客,也不用新雇人,只是服务员多忙一会儿,餐厅资源消耗极少,效率大大提升。

1.2 核心优势
  • 性能更高:Servlet运行在Java虚拟机(JVM)中,采用“线程池”机制复用线程,避免了CGI频繁创建/销毁进程的开销。

  • 跨平台性好:依托Java语言,一次编写,可在Windows、Linux等任何支持JVM的服务器上运行,而CGI依赖具体操作系统。

  • 开发效率高:可直接使用Java的面向对象特性(封装、继承、多态)和丰富的类库,比CGI的C/C++开发更简洁。

2. 与JSP对比:“后台厨师”vs“前台摆盘”

Servlet和JSP是互补关系,不是替代关系。

2.1 举例
  • Servlet是餐厅的“后台厨师”:专门负责处理核心业务(比如根据顾客订单“加工食材”——查询数据库、处理业务逻辑),不负责直接和顾客交互。

  • JSP是餐厅的“前台摆盘员”:专门负责把厨师做好的“菜品”(数据)整理成美观的“摆盘”(HTML页面),展示给顾客。

本质上,JSP在运行时会被Web服务器(如Tomcat)编译成Servlet,也就是说,JSP的底层还是Servlet。

2.2 核心优势(Servlet vs JSP):

Servlet更适合处理复杂的业务逻辑,代码结构清晰,便于维护;而JSP更适合展示页面。如果用JSP写大量业务逻辑,会导致“代码混乱”(HTML和Java代码混在一起),就像摆盘员既要摆盘又要做饭,效率低还容易出错。

3. 与Spring MVC对比:“基础工具”vs“高级套装”

3.1 举例

Servlet是“基础螺丝刀”:是Java Web开发的“底层工具”,所有Web请求的处理最终都要依赖Servlet。

Spring MVC是“多功能工具箱”:它基于Servlet封装而来,提供了更强大的功能(如请求映射、参数绑定、视图解析、拦截器等),就像在螺丝刀的基础上,增加了扳手、钳子等工具,让开发更高效。

3.2 核心优势(Servlet的不可替代性):

Spring MVC是“站在Servlet的肩膀上”实现的,没有Servlet,就没有Spring MVC。学习Servlet能帮你理解Web开发的底层原理,比如“请求如何从客户端传到服务器”“服务器如何处理请求并响应”,这是掌握Spring MVC等框架的基础。

4. 与Spring、Spring MVC的关系及对SSM框架的影响

很多后端开发者入门就接触SSM(Spring + Spring MVC + MyBatis)框架,容易忽略Servlet与它们的底层关联。其实Servlet是整个Java Web框架生态的“地基”,Spring和Spring MVC都是在它的基础上构建的。

4.1 Servlet与Spring的关系:底层无关,上层互补

首先要明确:Spring核心与Servlet没有直接依赖。Spring的核心是IoC(控制反转)容器和AOP(面向切面编程),它的设计目标是“简化企业级应用开发”,不仅适用于Web场景,还能用于桌面应用、分布式应用等非Web场景。

但在Web开发中,Spring与Servlet形成了“互补协作”的关系:Servlet负责处理HTTP请求的接收与响应(Web层的底层交互),而Spring负责管理业务层、数据层的Bean(如Service、Dao对象),并通过依赖注入(DI)将这些Bean注入到Servlet中,简化Servlet的开发(无需手动创建Service实例)。

生活类比:如果把Web应用看作“一栋大楼”,Servlet就是“大楼的地基和入口门岗”(负责承接外部访客、引导流向),Spring就是“大楼的物业管理系统”(负责管理内部资源、协调各部门工作),两者分工不同但协同运作。

4.2 Servlet与Spring MVC的关系:Spring MVC是Servlet的“高级封装”

Spring MVC是Spring框架的Web模块,专门用于解决Web开发中Servlet的“痛点”,它的底层完全依赖Servlet,核心组件DispatcherServlet本质上就是一个Servlet(继承自HttpServlet)。

4.2.1 传统Servlet开发的痛点
  • 每个功能都要编写独立的Servlet类(如用户登录一个Servlet、用户查询一个Servlet),导致类数量激增,维护成本高;

  • 需要手动处理请求参数封装(如将请求参数转为Java对象)、视图跳转(如转发、重定向)等重复工作;

  • 全局功能(如登录拦截、异常处理)需要在每个Servlet中重复实现,代码冗余。

Spring MVC的解决方案(基于Servlet封装):

Spring MVC通过“前端控制器模式”,用一个核心的DispatcherServlet接收所有客户端请求,再将请求分发给对应的处理器(Handler,即我们编写的Controller类),同时封装了参数绑定、视图解析、拦截器、异常处理等功能,彻底解决了传统Servlet开发的冗余问题。

4.2.2 核心流程拆解
  1. 客户端发送HTTP请求,请求先到达Web容器(Tomcat);

  2. Web容器将请求转发给Spring MVC的核心DispatcherServlet(本质是Servlet,需在web.xml或通过注解配置);

  3. DispatcherServlet根据请求路径,通过处理器映射器(HandlerMapping)找到对应的Controller方法;

  4. Controller方法处理业务逻辑(依赖Spring管理的Service Bean),返回视图名称或数据;

  5. DispatcherServlet通过视图解析器(ViewResolver)解析视图名称,生成视图对象,最终通过响应流返回给客户端。

关键结论:没有Servlet,就没有Spring MVC。DispatcherServlet是Spring MVC与Servlet的桥梁,所有Spring MVC的请求处理流程,最终都会通过Servlet的service()方法完成底层的HTTP交互。
4.2.3 Servlet对SSM框架出现的影响:奠定基础,催生高效开发生态

SSM框架的出现,本质是Java Web开发从“底层原生开发”向“高效封装开发”的演进,而Servlet正是这一演进的“基础前提”,其影响主要体现在3个方面:

  • 奠定Web层交互基础:Servlet定义了Java Web中HTTP请求与响应的核心规范,为SSM框架提供了底层的交互标准。MyBatis负责数据持久化、Spring负责Bean管理、Spring MVC负责Web层调度,这三者的协同都建立在Servlet的HTTP交互规范之上。

  • 暴露原生开发痛点,推动框架封装:传统Servlet开发的冗余问题(类繁多、重复代码多、维护成本高),催生了Spring MVC的出现。SSM框架的核心价值之一,就是通过Spring MVC封装Servlet,简化Web层开发,同时结合Spring的IoC/AOP和MyBatis的ORM能力,形成“一站式”的企业级开发解决方案。

  • 保障生态兼容性:Servlet是Java EE的标准规范,所有主流Web容器(Tomcat、Jetty)都支持Servlet。SSM框架基于Servlet构建,确保了其能在所有标准Web容器中运行,具备良好的跨容器兼容性,这也是SSM能成为企业级开发主流框架的重要原因之一。

总结:Servlet是Java Web开发的“根”,Spring MVC是对Servlet的“优化封装”,而SSM框架则是在Servlet奠定的基础上,整合了Spring的Bean管理、MyBatis的数据访问能力,形成的高效开发生态。学习Servlet,就是掌握所有Java Web框架的“底层逻辑”,这也是为什么很多大厂面试都会考察Servlet原理的核心原因。

三、Servlet的日常使用和示例:手把手写一个简单Servlet

1. 要使用Servlet,需要先准备环境:

  • 开发工具:IDEA/Eclipse

  • Web服务器:Tomcat 8+(Servlet的运行依赖Web服务器,Tomcat是最常用的开源服务器)

  • 依赖:Servlet API(可通过Maven引入,或直接添加Tomcat的lib目录下的servlet-api.jar)

2. 核心步骤(以IDEA+Maven+Tomcat为例)

2.1 步骤1:创建Maven Web项目

在IDEA中新建Maven项目,选择“webapp”模板,填写项目名称、groupId等信息,完成创建。

2.2 步骤2:引入Servlet依赖

在pom.xml中添加Servlet API的依赖(注意:scope设为provided,因为Tomcat已自带该jar包,避免冲突):

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
</dependency>
2.3 步骤3:编写Servlet类

创建一个Java类,继承HttpServlet,重写doGet()或doPost()方法(doGet处理GET请求,doPost处理POST请求):

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;

// 注解方式配置Servlet:urlPatterns指定访问路径(/hello)
@WebServlet(urlPatterns = "/hello")
public class HelloServlet extends HttpServlet {

    // 重写doGet方法,处理GET请求
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 1. 设置响应的编码格式(避免中文乱码)
        resp.setContentType("text/html;charset=UTF-8");
        
        // 2. 获取响应流,向客户端输出内容
        PrintWriter writer = resp.getWriter();
        writer.write("<h1>Hello Servlet!</h1>");
        writer.write("<p>这是我的第一个Servlet程序</p>");
        
        // 3. 关闭流
        writer.close();
    }
}
2.4 步骤4:配置Tomcat并运行

在IDEA中配置Tomcat,指定项目的部署路径,启动Tomcat后,在浏览器中访问:http://localhost:8080/项目名称/hello

如果看到页面显示“Hello Servlet!这是我的第一个Servlet程序”,说明Servlet运行成功!

3. 核心API说明

  • HttpServlet:所有Servlet的基类,提供了doGet()、doPost()等处理HTTP请求的方法。

  • HttpServletRequest:封装了客户端的请求信息(如请求参数、请求头、Session等),比如通过req.getParameter(“username”)可获取前端传递的参数。

  • HttpServletResponse:封装了服务器对客户端的响应信息(如响应码、响应头、响应内容),比如通过resp.getWriter()获取输出流,向客户端输出HTML或JSON。

  • @WebServlet:Servlet 3.0及以上版本支持的注解,用于替代传统的web.xml配置,指定Servlet的访问路径(urlPatterns)、名称(name)等。

4. Servlet生命周期详解

Servlet的生命周期完全由Web容器(如Tomcat、Jetty)管理,从实例创建到资源销毁的全流程遵循固定规范,核心分为实例创建、初始化、服务、销毁四个核心阶段,每个阶段都有明确的触发条件、执行逻辑和方法约束。理解生命周期不仅能掌握Servlet的运行本质,还能帮你规避线程安全、资源泄漏等关键问题。

生活类比强化:把Web容器看作“物业公司”,Servlet看作“小区里的便民服务站”——物业公司负责服务站的搭建(实例创建)、设备调试(初始化)、日常运营(处理居民请求),最后在小区翻新时拆除服务站(销毁),全程无需服务站自身干预。

4.1 阶段1:实例创建阶段(Web容器主导的“出生”)

触发时机(2种核心场景):

  • 默认场景:Servlet第一次被客户端访问时,Web容器会通过反射机制创建Servlet实例(懒加载模式)。比如用户第一次访问/hello路径时,Tomcat才会创建HelloServlet的对象。

  • 预加载场景:若通过load-on-startup配置(注解或web.xml),Web容器启动时就会主动创建Servlet实例。配置方式:@WebServlet(urlPatterns = “/hello”, loadOnStartup = 1),其中loadOnStartup值为非负整数,数值越小,实例创建优先级越高。

核心机制
  • 单实例特性:同一Servlet类在整个Web应用中仅创建一个实例(Web容器集群部署除外),所有客户端请求共享该实例。这是Servlet性能高效的关键,但也是线程安全问题的根源。

  • 反射创建流程:Web容器读取Servlet配置 → 加载Servlet类到JVM(类加载器) → 通过Class.forName(类名).newInstance()(Servlet 3.0+为getDeclaredConstructor().newInstance())创建实例 → 实例存储在容器的Servlet实例池中管理。

注意点:Servlet的构造方法由Web容器调用,开发者无需手动创建实例;若自定义构造方法,必须保留无参构造(否则反射创建时会抛出NoSuchMethodException)。

4.2 阶段2:初始化阶段(init() - 初始化资源的“准备工作”)

触发时机:Servlet实例创建完成后立即执行,且全局仅执行1次。若初始化失败(方法抛出ServletException),Web容器会直接销毁该实例,且后续不再为该Servlet分配任何请求。

核心方法与参数

初始化阶段的核心方法是init(ServletConfig config),HttpServlet父类已实现该方法,并重载了无参init()方法(供开发者重写)。关键说明:

  • ServletConfig参数:封装了Servlet的配置信息(如初始化参数、Servlet名称、ServletContext对象),可通过config.getInitParameter(“参数名”)获取配置的初始化参数。

  • 开发者重写建议:优先重写无参init()方法(代码更简洁),若需要获取配置信息,可通过getServletConfig()方法获取ServletConfig对象。

典型应用场景:执行耗时但仅需一次的初始化操作
  • 加载配置文件(如数据库连接信息、接口地址配置);

  • 创建数据库连接池(避免每次请求都创建新连接,提升性能);

  • 初始化缓存数据(如加载常用的字典数据到内存)。

4.3 阶段3:服务阶段(service() - 处理请求的“核心工作”)

触发时机:每次有客户端请求匹配当前Servlet的访问路径时,Web容器会从线程池中分配一个线程,由该线程调用Servlet的service()方法,每次请求对应一次调用,并发请求对应多个线程同时调用。

核心逻辑(HttpServlet底层实现):

HttpServlet的service()方法已封装了“请求方式判断与分发”的核心逻辑,开发者无需重写,仅需根据业务需求重写对应的doXxx()方法(如doGet、doPost)。底层核心代码拆解:

@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    // 1. 获取HTTP请求方式(GET/POST/PUT/DELETE等)
    String method = req.getMethod();
    // 2. 根据请求方式分发到对应的doXxx()方法
    if (method.equals(METHOD_GET)) {
        // 处理GET请求的预处理(如缓存控制)
        long lastModified = getLastModified(req);
        if (lastModified == -1) {
            doGet(req, resp);
        } else {
            long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
            if (ifModifiedSince < lastModified) {
                // 资源已更新,执行doGet
                doGet(req, resp);
            } else {
                // 资源未更新,返回304 Not Modified
                resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
            }
        }
    } else if (method.equals(METHOD_POST)) {
        doPost(req, resp); // 分发到POST处理方法
    } else if (method.equals(METHOD_PUT)) {
        doPut(req, resp);
    } else if (method.equals(METHOD_DELETE)) {
        doDelete(req, resp);
    } else {
        // 其他不支持的请求方式,返回405错误
        String errMsg = lStrings.getString("http.method_not_supported");
        Object[] errArgs = new Object[1];
        errArgs[0] = method;
        errMsg = MessageFormat.format(errMsg, errArgs);
        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, errMsg);
    }
}

关键特点:

  • 多线程并发:每个请求对应独立线程,线程共享Servlet实例的成员变量(若定义),因此严禁在Servlet中定义非线程安全的成员变量(如ArrayList、普通对象),否则会出现数据错乱。

  • 请求/响应封装:HttpServletRequest封装了客户端的所有请求信息(请求行、请求头、请求参数、Cookie、Session等);HttpServletResponse封装了服务器的响应信息(响应行、响应头、响应体、Cookie等),开发者通过这两个对象完成数据交互。

4.4 阶段4:销毁阶段(destroy() - 释放资源的“收尾工作”)
触发时机

Web容器关闭时(如Tomcat停止运行)、Web应用卸载时(如从Tomcat的webapps目录删除项目),Web容器会调用Servlet的destroy()方法,全局仅执行1次。

核心作用:释放Servlet占用的资源,避免内存泄漏,典型操作包括:
  • 关闭数据库连接池、Redis连接等资源;

  • 停止后台线程(如Servlet中启动的定时任务线程);

  • 清理缓存数据、释放文件句柄等。

关键注意点:
  • destroy()方法仅释放资源,不负责销毁Servlet实例本身,实例的销毁由Java垃圾回收机制(GC)在资源释放后自动完成;

  • destroy()方法执行时,Web容器会确保所有正在执行service()方法的线程先完成工作,再调用destroy(),避免资源释放时出现线程安全问题。

4.5 生命周期完整演示代码
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
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;

// 配置:访问路径/ lifecycle,预加载(loadOnStartup=1),添加初始化参数
@WebServlet(
        urlPatterns = "/lifecycle",
        loadOnStartup = 1,
        initParams = {@WebInitParam(name = "appName", value = "ServletDemo")}
)
public class LifecycleServlet extends HttpServlet {

    // 1. 构造方法(Web容器创建实例时调用)
    public LifecycleServlet() {
        super();
        System.out.println("【1.实例创建阶段】LifecycleServlet构造方法执行,实例创建完成");
    }

    // 2. 初始化方法(实例创建后执行,仅1次)
    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        // 获取初始化参数
        String appName = config.getInitParameter("appName");
        System.out.println("【2.初始化阶段】init()方法执行,初始化参数appName:" + appName);
        // 模拟初始化:加载配置文件、创建连接池等操作
        System.out.println("【2.初始化阶段】模拟加载数据库连接池完成");
    }

    // 3. 服务方法(每次请求执行,分发到doGet/doPost)
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("【3.服务阶段】service()方法执行,开始分发请求");
        super.service(req, resp); // 调用父类方法完成分发
    }

    // 4. 处理GET请求(service()分发后执行)
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("【3.服务阶段】doGet()方法执行,处理GET请求");
        // 向客户端响应内容
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter writer = resp.getWriter();
        writer.write("<h1>Servlet生命周期演示</h1>");
        writer.write("<p>请查看服务器控制台日志,观察生命周期各阶段执行顺序</p>");
        writer.close();
    }

    // 5. 销毁方法(Web容器关闭时执行,仅1次)
    @Override
    public void destroy() {
        System.out.println("【4.销毁阶段】destroy()方法执行,开始释放资源");
        // 模拟释放资源:关闭数据库连接池
        System.out.println("【4.销毁阶段】模拟关闭数据库连接池完成");
        super.destroy();
    }
}
预期效果
  1. 配置Tomcat并启动,查看控制台日志,会立即输出(预加载生效):
    【1.实例创建阶段】LifecycleServlet构造方法执行,实例创建完成
    【2.初始化阶段】init()方法执行,初始化参数appName:ServletDemo
    【2.初始化阶段】模拟加载数据库连接池完成

  2. 浏览器访问http://localhost:8080/项目名/lifecycle,控制台输出:
    【3.服务阶段】service()方法执行,开始分发请求
    【3.服务阶段】doGet()方法执行,处理GET请求

  3. 刷新浏览器(多次请求),会重复输出服务阶段日志(证明service()每次请求都执行);

  4. 停止Tomcat,控制台输出:
    【4.销毁阶段】destroy()方法执行,开始释放资源
    【4.销毁阶段】模拟关闭数据库连接池完成

生命周期流程总结(完整时序):

Web容器启动 → 反射创建Servlet实例(构造方法) → init()初始化 → 等待客户端请求 → 客户端请求到达 → 分配线程执行service() → 分发到对应doXxx()处理 → 响应客户端 → 重复处理请求 → Web容器关闭 → destroy()释放资源 → GC回收实例。

5. Servlet的生命周期是指从Servlet被创建到销毁的整个过程,由Web服务器(如Tomcat)全程管理,核心分为4个阶段,每个阶段对应特定的方法调用,理解生命周期能帮你更深入掌握Servlet的运行机制。

用生活例子类比:Servlet的生命周期就像“员工从入职到离职”的过程,Web服务器是“公司”,负责招聘(创建)、安排工作(初始化与服务)、最终辞退(销毁)。

5.1 阶段1:初始化阶段(init() - 入职培训)
  • 触发时机:Servlet第一次被访问时,Web服务器会创建Servlet实例,然后调用init()方法(仅调用1次);若配置了load-on-startup(如@WebServlet(loadOnStartup = 1)),则Web服务器启动时就会创建实例并执行init()。

  • 核心作用:完成Servlet的初始化工作,比如加载配置文件、创建数据库连接池、初始化缓存等,避免每次处理请求都重复执行这些耗时操作。

  • 注意事项:init()方法执行失败时,Servlet无法正常使用,Web服务器会直接销毁该实例,不再为其分配请求。

5.2 阶段2:服务阶段(service() - 日常工作)
  • 触发时机:每次有客户端请求访问该Servlet时,Web服务器会为当前请求分配一个线程,线程调用service()方法(可调用多次,每次请求对应一次调用)。

  • 核心作用:接收客户端请求,判断请求方式(GET/POST/PUT/DELETE等),并分发到对应的doXxx()方法(如doGet()、doPost())处理。

  • 底层逻辑:service()方法是HttpServlet的核心方法,源码中已实现请求方式的判断逻辑,无需手动重写,只需根据需求重写对应的doXxx()方法即可。示例逻辑如下:

@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String method = req.getMethod(); // 获取请求方式
    if (method.equals("GET")) {
        doGet(req, resp); // 分发到doGet处理
    } else if (method.equals("POST")) {
        doPost(req, resp); // 分发到doPost处理
    }
    // 其他请求方式的判断...
}
5.3 阶段3:销毁阶段(destroy() - 办理离职)
  • 触发时机:Web服务器关闭时,或Servlet被移除(如项目卸载)时,Web服务器会调用destroy()方法(仅调用1次)。

  • 核心作用:释放Servlet占用的资源,比如关闭数据库连接、销毁线程池、清理缓存数据等,避免资源泄漏。

  • 注意事项:destroy()方法仅释放资源,不会销毁Servlet实例本身,Servlet实例的销毁由Java垃圾回收机制(GC)负责。

5.4 阶段4:实例创建与销毁(对象生命周期)
  • 补充说明:Servlet是单实例多线程的:

  • 单实例:整个Web应用中,同一个Servlet类仅创建一个实例(除非Web服务器集群部署),避免实例过多占用资源。

  • 多线程:每次请求由独立线程处理,线程安全问题需要特别注意(比如避免在Servlet中定义成员变量,若必须定义需加锁保护)。

生命周期整体流程总结:Web服务器启动/首次请求 → 创建Servlet实例 → init()初始化 → 每次请求触发service()→分发到doXxx()处理 → Web服务器关闭 → destroy()销毁资源 → GC回收实例。

四、Servlet开发常见问题踩坑指南

4.1 坑1:中文乱码问题

  • 现象:前端传递的中文参数,在Servlet中获取后是“??? ”;或Servlet输出的中文,在浏览器中显示乱码。

  • 原因:请求/响应的编码格式不统一(比如前端用UTF-8,后端用ISO-8859-1)。

  • 解决方案:

    • 处理请求乱码(GET/POST):
    1. // POST请求:在获取参数前设置req.setCharacterEncoding("UTF-8");
    2. // GET请求(Tomcat 8以下):需要修改Tomcat的server.xml,在Connector标签添加URIEncoding="UTF-8"
    3. // Tomcat 8及以上默认UTF-8,无需修改
    • 处理响应乱码:
    1. // 在获取输出流前设置resp.setContentType("text/html;charset=UTF-8"); // 或分开设置 resp.setCharacterEncoding("UTF-8"); resp.setHeader("Content-Type", "text/html;charset=UTF-8");

4.2 坑2:404错误(资源未找到)

  • 现象:浏览器访问Servlet时,显示“404 Not Found”。

  • 常见原因及解决方案:

  • 访问路径错误:检查浏览器地址是否正确(比如项目名称、Servlet的urlPatterns是否写错)。

  • Servlet未正确配置:

    • 使用注解@WebServlet时,确保注解格式正确(如urlPatterns不能少“/”,写成“hello”会报错,需写成“/hello”)。

    • 使用web.xml配置时,确保servlet-name和servlet-mapping的name一致,url-pattern配置正确。

  • 项目未正确部署:检查Tomcat的webapps目录下是否有项目的部署文件,或IDEA中Tomcat的部署配置是否正确。

4.3 坑3:500错误(服务器内部错误)

  • 现象:浏览器显示“500 Internal Server Error”。

  • 常见原因及解决方案:

  • Servlet代码错误:比如空指针异常、数组越界等,查看Tomcat的日志(logs/catalina.out),找到具体的错误行,修复代码。

  • Servlet API依赖冲突:比如项目中引入的servlet-api.jar和Tomcat自带的版本冲突,需将依赖的scope设为provided(避免打包时将该jar包放入WEB-INF/lib)。

4.4 坑4:doGet/doPost方法未重写或写错

  • 现象:访问Servlet时,浏览器无响应或显示错误,Tomcat日志提示“HTTP method GET is not supported by this URL”。

  • 原因:

    • 未重写对应的doGet()或doPost()方法:比如前端用POST请求提交表单,但Servlet只重写了doGet(),未重写doPost()。
    • 方法名写错:比如把doGet()写成doGET()、doPost()写成doPOST()(Java是大小写敏感的)。
  • 解决方案:

    • 根据前端请求方式,重写对应的doGet()或doPost()方法。
    • 如果需要同时支持GET和POST,可在其中一个方法中调用另一个方法,比如:

五、总结

Servlet作为Java Web开发的基石,虽然现在直接开发Servlet的场景减少了(大多用Spring MVC等框架),但它的核心原理是理解所有Java Web框架的关键。

  • Servlet的出现是为了解决早期动态Web开发的性能和跨平台问题,替代低效的CGI。

  • Servlet相比CGI性能更高,相比JSP更适合处理业务逻辑,是Spring MVC等框架的底层基础。

  • 开发Servlet的核心是继承HttpServlet,重写doGet/doPost方法,通过HttpServletRequest和HttpServletResponse处理请求和响应。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐