什么是Servlet

运行在服务器端的程序

servlet是一个接口,定义了Java类被浏览器访问到的规则(接口)

Servlet是用java编写的服务器端的程序,主要是交互式的浏览和修改数据,生成动态Web内容,在JavaEE平台上,处理TCP连接,解析HTTP协议这些底层工作统统扔给现成的Web服务器去做,我们只需要把自己的应用程序跑在Web服务器上

Servlet运行于支持Java的应用服务器中。从实现上讲,Servlet可以响应任何类型的请求,但绝大多数情况下Servlet只用来扩展基于HTTP协议Web服务器

Struts2的核心用的是Filter(过滤器),而SpringMVC的核心用的就是Servlet。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// WebServlet注解表示这是一个Servlet,并映射到地址/:
@WebServlet(urlPatterns = "/")
public class HelloServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 设置响应类型:
resp.setContentType("text/html");
// 获取输出流:
PrintWriter pw = resp.getWriter();
// 写入响应:
pw.write("<h1>Hello, world!</h1>");
// 最后不要忘记flush强制输出:
pw.flush();
}
}

一个Servlet总是继承自HttpServlet,然后重写doGet()doPost()方法。注意到doGet()方法传入了HttpServletRequestHttpServletResponse两个对象,分别代表HTTP请求和响应。我们使用Servlet API时,并不直接与底层TCP交互,也不需要解析HTTP协议,因为HttpServletRequestHttpServletResponse就已经封装好了请求和响应

工作模式

  • 客户端发送请求至服务器
  • 服务器启动并调用Servlet,Servlet根据客户端请求生成响应内容并将其传给服务器
  • 服务器将响应返回客户端

API预览

Servlet API 包含以下4个Java包:

1.javax.servlet 其中包含定义servlet和servlet容器之间契约的类和接口。

2.javax.servlet.http 其中包含定义HTTP Servlet 和Servlet容器之间的关系。

3.javax.servlet.annotation 其中包含标注servlet,Filter,Listener的标注。它还为被标注元件定义元数据。

4.javax.servlet.descriptor,其中包含提供程序化登录Web应用程序的配置信息的类型。

使用Servlet

Servlet技术的核心是Servlet,它是所有Servlet类必须直接或者间接实现的一个接口。在编写实现Servlet的Servlet类时,直接实现它。在扩展实现这个这个接口的类时,间接实现它。

工作原理

​ Servlet接口定义了Servletservlet容器之间的契约:Servlet容器将Servlet类载入内存,并产生Servlet实例和调用它具体的方法。

​ 但是要注意的是,在一个应用程序中,每种Servlet类型只能有一个实例

​ 用户请求致使Servlet容器调用Servlet的Service()方法,并传入一个ServletRequest对象和一个ServletResponse对象。 ServletRequest中封装了当前的Http请求,ServletResponse表示当前用户的Http响应.

​ 对于每一个应用程序,Servlet容器还会创建一个ServletContext对象。这个对象中封装了上下文(应用程序)的环境详情。每个应用程序只有一个ServletContext。每个Servlet对象也都有一个封装Servlet配置的ServletConfig对象。

接口中定义的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface Servlet {
// 这是 Servlet 的初始化方法,在 Servlet 实例被创建之后,容器会调用此方法对 Servlet 进行初始化操作。
void init(ServletConfig var1) throws ServletException;

// 用于获取 Servlet 的配置信息,会返回由Servlet容器传给init( )方法的ServletConfig对象
ServletConfig getServletConfig();

// 用来处理客户端的请求并生成响应。当有客户端请求到达时,Servlet 容器会调用这个方法。
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

// 用于返回 Servlet 的描述信息
String getServletInfo();

// 这是 Servlet 的销毁方法,在 Servlet 实例被销毁之前,容器会调用此方法,用于释放 Servlet 占用的资源
void destroy();
}

执行原理

当服务器接收到客户端浏览器的请求后,会解析请求URL路径,获取访问的Servlet的资源路径

查找Web.xml文件,是否有对应的标签体的内容,如果有,则在对应的全类名

tomcat会将其字节码加载进内存然后构建对象,调用其方法

Servlet的生命周期

上述接口中定义的方法内,init( ),service( ),destroy( )是Servlet生命周期的方法。

Servlet容器(例如TomCat)会根据下面的规则来调用这三个方法:

  • 当Servlet第一次被请求时,Servlet容器就会开始调用 init() 方法,初始化一个Servlet对象出来,这个方法在后续请求中不会在被Servlet容器调用。调用这个方法时,Servlet容器会传入一个ServletConfig对象进来从而对Servlet对象进行初始化。

  • service( )方法,每当请求Servlet时,Servlet容器就会调用这个方法,第一次请求时,Servlet容器会先调用init( )方法初始化一个Servlet对象出来,然后会调用它的service( )方法进行工作,但在后续的请求中,Servlet容器只会调用service方法了。

  • destory,当要销毁Servlet时,Servlet容器就会调用这个方法,一般在这个方法中会写一些清除代码,只有服务器正常关闭的时候,才会执行destory方法

    演示代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
    System.out.println("Servlet正在初始化");
    }

    @Override
    public ServletConfig getServletConfig() {
    return null;
    }

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
    //专门向客服端提供响应的方法
    System.out.println("Servlet正在提供服务");

    }

    @Override
    public String getServletInfo() {
    return null;
    }

    @Override
    public void destroy() {
    System.out.println("Servlet正在销毁");
    }

其中servlet的创建时期是可以被指定的

1
2
<!-- 指定servlet的创建时期,负数为第一次访问时期,0或正数为服务器启动时期-->
<load-on-startup>5</load-on-startup>

Servlet中的 init 方法只执行一次,说明一个Servlet中只存在一个对象,是单例的,多个用户同时访问可能存在线程安全的隐患,所以尽量不要再其中定义成员变量,也不要对其赋值

Tomcat部分

普通的Java程序是通过启动JVM,然后执行main()方法开始运行。但是Web应用程序有所不同,我们无法直接运行war文件,必须先启动Web服务器,再由Web服务器加载我们编写的HelloServlet,这样就可以让HelloServlet处理浏览器发送的请求。

Tomcat本身的目录层次结构

img

Tomcat提供了一个部署其服务器在你本地电脑上的功能

​ 实际上,类似Tomcat这样的服务器也是Java编写的,启动Tomcat服务器实际上是启动Java虚拟机

​ 执行Tomcat的main()方法,然后由Tomcat负责加载我们的.war文件,并创建一个HelloServlet实例,最后以多线程的模式来处理HTTP请求。

​ 如果Tomcat服务器收到的请求路径是/(假定部署文件为ROOT.war),就转发到HelloServlet并传入HttpServletRequestHttpServletResponse两个对象。

配置Servlet

web.xml配置

web.xml 文件是用于配置 Web 应用程序的核心文件,它能对 Servlet、过滤器、监听器等组件进行配置

Servlet 定义

1
2
3
4
<servlet>
<servlet-name>ServletExpDemo</servlet-name>
<servlet-class>org.ergoutree.servletexpdemo.demo1.FastGo</servlet-class>
</servlet>
  • 标签:该标签的作用是定义一个 Servlet。在一个 web.xml 文件中,可以定义多个 Servlet。
  • 标签:这里为 Servlet 指定一个名称,也就是 ServletExpDemo。这个名称属于逻辑名称,主要用于在 web.xml 文件的其他部分引用该 Servlet。
  • 标签:它指定了 Servlet 类的全限定名,即 org.ergoutree.servletexpdemo.demo1.FastGo。当 Web 容器启动时,会依据这个全限定名来加载并实例化该 Servlet 类。

Servlet 映射

1
2
3
4
<servlet-mapping>
<servlet-name>ServletExpDemo</servlet-name>
<url-pattern>/demo1</url-pattern>
</servlet-mapping>
  • 标签:此标签的功能是将 Servlet 与一个或多个 URL 模式进行映射。当客户端发送请求时,Web 容器会根据请求的 URL 找到对应的 Servlet。
  • 标签:这里引用了之前定义的 Servlet 的名称,也就是 ServletExpDemo
  • 标签:它指定了与该 Servlet 关联的 URL 模式,即 /demo1。当客户端请求的 URL 匹配这个模式时,Web 容器就会将请求转发给 ServletExpDemo 所对应的 Servlet 类(也就是org.ergoutree.servletexpdemo.demo1.FastGo)进行处理。

原理及其示例

原理说明

当客户端向 Web 应用程序发送 HTTP 请求时,Web 容器(像 Tomcat 这类)会按照以下步骤处理请求:

  1. 解析请求 URL:Web 容器对客户端请求的 URL 进行解析,从中提取出请求的路径部分。
  2. 查找 Servlet 映射:Web 容器在 web.xml 文件里查找与请求路径相匹配的 ``。
  3. 定位 Servlet:若找到匹配的 ,Web 容器会根据对应的 找到之前定义的 Servlet。
  4. 实例化并调用 Servlet:Web 容器加载并实例化该 Servlet 类,然后调用其 service() 方法来处理请求。
  5. 返回响应:Servlet 处理完请求后,将响应返回给客户端。

示例

假设客户端发送的请求 URL 为 http://localhost:8080/yourApp/demo1,Web 容器会进行如下操作:

  1. 解析出请求路径为 /demo1
  2. web.xml 文件中找到 为 `/demo1` 的
  3. 根据 `ServletExpDemo,找到对应的 Servlet 类org.ergoutree.servletexpdemo.demo1.FastGo`。
  4. 加载并实例化 FastGo 类,调用其 service() 方法处理请求。
  5. 将处理结果返回给客户端。

Serlvet3.0的注解配置

@WebServlet 的属性列表:

image-20250412202207184

核心注解@WebServlet

取代 ,直接绑定 URL 模式与 Servlet 类。

1
2
3
4
5
6
7
8
9
10
@WebServlet(
name = "userServlet", // Servlet 名称(可选)
urlPatterns = {"/user", "/api/user"}, // 支持多 URL 模式
loadOnStartup = 1, // 容器启动时立即加载(替代 <load-on-startup>)
initParams = {
@WebInitParam(name = "encoding", value = "UTF-8") // 初始化参数
},
asyncSupported = true // 启用异步支持(默认 false)
)
public class UserServlet extends HttpServlet { ... }
  • urlPatterns:支持精确匹配 (/user)、通配符 (/api/*)、后缀匹配 (*.do)。

  • loadOnStartup:值越小优先级越高,控制 Servlet 初始化顺序。

  • asyncSupported:异步处理开关,需配合 AsyncContext 使用(后文详解)。

@WebFilter:声明过滤器

替代 ,定义请求预处理和后处理逻辑。

1
2
3
4
5
6
7
@WebFilter(
filterName = "auditFilter",
urlPatterns = "/*", // 过滤所有请求
servletNames = {"userServlet"}, // 针对特定 Servlet
dispatcherTypes = {DispatcherType.REQUEST, DispatcherType.ASYNC}
)
public class AuditFilter implements Filter { ... }
  • dispatcherTypes:控制过滤器作用的请求类型(如 FORWARDERROR)。
  • 执行顺序问题:注解无法直接指定顺序,需通过类名自然排序或结合 web.xml 的 ``。

@WebListener:事件监听器 简化监听器的声明,覆盖 Servlet 上下文、会话、请求等生命周期事件。

1
2
3
4
5
6
7
@WebListener
public class AppContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
// 应用启动逻辑
}
}

异步处理

@WebServlet@WebFilter 中设置 asyncSupported = true

流程:

  1. 开启异步上下文:AsyncContext asyncContext = request.startAsync()
  2. 提交耗时任务到其他线程(如数据库查询、远程调用)。
  3. 任务完成时,通过 asyncContext.complete() 或返回响应。
1
2
3
4
5
6
7
8
9
10
11
@WebServlet(urlPatterns = "/async", asyncSupported = true)
public class AsyncServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
AsyncContext ctx = req.startAsync();
CompletableFuture.runAsync(() -> {
// 模拟耗时操作
ctx.getResponse().getWriter().write("Async Response");
ctx.complete();
});
}
}

模块化部署介绍

Servlet 3.0 模块化特性

  • 允许将 Web 组件(Servlet、Filter、Listener)打包为 JAR 文件,置于 WEB-INF/lib 目录。
  • 容器自动扫描 JAR 中的 META-INF/web-fragment.xml 和注解,实现“即插即用”

注意情况

如果使用@WebServlet Annotation(注解)来配置Servlet,需要注意:

① 不要在 web.xml 文件的根元素(<web-app—/>)中指定 metadata-complete=“true”; ② 不要在 web.xml 文件中再次配置该 Servlet 相关属性

ServletRequestServletResponseServlet 提供了处理客户端请求和发送响应的能力;ServletConfigServlet 提供了配置信息;ServletContextServlet 提供了整个 Web 应用程序的上下文环境。它们共同协作,使得 Servlet 能够正常运行并处理客户端的请求。

ServletRequset接口

Servlet容器对于接受到的每一个Http请求,都会创建一个ServletRequest对象,并把这个对象传递给Servlet的Sevice( )方法。其中,ServletRequest对象内封装了关于这个请求的许多详细信息。

ServletRequest接口的部分内容:

1
2
3
4
5
public interface ServletRequest {
int getContentLength();//返回请求主体的字节数
String getContentType();//返回主体的MIME类型
String getParameter(String var1);//返回请求参数的值
}

ServletResponse接口

javax.servlet.ServletResponse接口表示一个Servlet响应,在调用Servlet的Service( )方法前,Servlet容器会先创建一个ServletResponse对象,并把它作为第二个参数传给Service( )方法。

ServletResponse隐藏了向浏览器发送响应的复杂过程。在向客户端发送响应时,大多数都是使用该对象向客户端发送HTML。

ServletResponse内部定义的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public interface ServletResponse {
String getCharacterEncoding();
// 在发送任何HTML之前,应该先调用setContentType()方法,设置响应的内容类型,加上“charset=UTF-8”改变响应的编码方式以防止发生中文乱码现象。
String getContentType();
// 发送二进制数据的
ServletOutputStream getOutputStream() throws IOException;
// PrintWriter对象使用ISO-8859-1编码(该编码在输入中文时会发生乱码)。
PrintWriter getWriter() throws IOException;

void setCharacterEncoding(String var1);

void setContentLength(int var1);

void setContentType(String var1);

void setBufferSize(int var1);

int getBufferSize();

void flushBuffer() throws IOException;

void resetBuffer();

boolean isCommitted();

void reset();

void setLocale(Locale var1);

Locale getLocale();
}

ServletConfig接口

当Servlet容器初始化Servlet时,Servlet容器会给Servlet的init( )方式传入一个ServletConfig对象。

其中几个方法如下:

img

ServletContext对象

ServletContext对象表示Servlet应用程序,是Servlet的上下文对象。每个Web应用程序都只有一个ServletContext对象。

在将一个应用程序同时部署到多个容器的分布式环境中,每台Java虚拟机上的Web应用都会有一个ServletContext对象。

通过在ServletConfig中调用getServletContext方法,也可以获得ServletContext对象。

有了ServletContext对象,就可以共享从应用程序中的所有资料处访问到的信息,并且可以动态注册Web对象。前者将对象保存在ServletContext中的一个内部Map中

ServletContext中的下列方法负责处理属性:

1
2
3
4
5
6
7
8
Object getAttribute(String var1);  // 获取 ServletContext 中指定名称的属性值

// 用于获取 ServletContext 中所有属性的名称。返回的是一个 Enumeration 对象,通过它可以遍历所有属性的名称。
Enumeration<String> getAttributeNames();

void setAttribute(String var1, Object var2); // 向 ServletContext 中设置一个属性

void removeAttribute(String var1); // 从 ServletContext 中移除指定名称的属性

Servlet是否为线程安全

线程安全问题指的是多线程在并发执行时会不会出现问题。由于Web容器只会创建一个Servlet实例,所以多个用户发起请求时,会有多个线程处理Servlet代码,因此Servlet是线程不安全的。

考虑以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@WebServlet(name = "ThreadSafeServlet", urlPatterns = "/ThreadSafeServlet")
public class ThreadSafeServlet extends HttpServlet {
private String name;
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
name = request.getParameter("name");
try {
Thread.sleep(10000);//使线程沉睡10秒
} catch (Exception e) {
e.printStackTrace();
}
response.getWriter().println("name:" + name);
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}

10秒内在两个不同的浏览器窗口中的表单输入name并提交,假如在A浏览器中输入111,B浏览器中输入222,最后会发现A和B浏览器显示的name都是222。这是因为在第一个线程睡眠时,第二个线程修改了name的值,所有最后显示都是222,那么就产生了线程不安全问题。

实际上Servlet,Context上下文作用域,HttpSession都是线程不安全的,只有request请求和局部变量是线程安全的。

关于Servlet与http相关的应用,下一部分是这个

Servlet与http

关于Web基础部分

JavaWeb-http

引用文章

JavaWeb——HTTP详解

JavaWeb——Servlet

菜鸟教程Servlet部分