一篇搞懂Java Servlet
一、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 核心流程拆解
-
客户端发送HTTP请求,请求先到达Web容器(Tomcat);
-
Web容器将请求转发给Spring MVC的核心DispatcherServlet(本质是Servlet,需在web.xml或通过注解配置);
-
DispatcherServlet根据请求路径,通过处理器映射器(HandlerMapping)找到对应的Controller方法;
-
Controller方法处理业务逻辑(依赖Spring管理的Service Bean),返回视图名称或数据;
-
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();
}
}
预期效果
-
配置Tomcat并启动,查看控制台日志,会立即输出(预加载生效):
【1.实例创建阶段】LifecycleServlet构造方法执行,实例创建完成
【2.初始化阶段】init()方法执行,初始化参数appName:ServletDemo
【2.初始化阶段】模拟加载数据库连接池完成 -
浏览器访问http://localhost:8080/项目名/lifecycle,控制台输出:
【3.服务阶段】service()方法执行,开始分发请求
【3.服务阶段】doGet()方法执行,处理GET请求 -
刷新浏览器(多次请求),会重复输出服务阶段日志(证明service()每次请求都执行);
-
停止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):
// POST请求:在获取参数前设置req.setCharacterEncoding("UTF-8");// GET请求(Tomcat 8以下):需要修改Tomcat的server.xml,在Connector标签添加URIEncoding="UTF-8"// Tomcat 8及以上默认UTF-8,无需修改
- 处理响应乱码:
// 在获取输出流前设置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处理请求和响应。
更多推荐




所有评论(0)