Servlet

1 Servlet

1.1 UML 图

Servlet 是接口,我们常使用的 HttpServlet 是一个实现类,支持 Http 协议。创建我们自己的 Servlet 类继承 HttpServlet 时,必须重写 doPost 和 doGet 两个方法中的一个。

HttpServletUML

1.2 Servlet 生命周期

  1. Web 容器加载我们创建的 Servlet 类 class 到内存中
  2. 当第一次请求时先调用构造器构造一个 servlet 实例,Web 容器中一个 Servlet 类只有一个实例
  3. 第一次构造了一个 Servlet 实例后,调用 init 方法进行初始化,只会调用一次
  4. service 方法处理请求参数和响应,每个请求都会在单个线程中执行
  5. Web 容器停止或实例被回收,会调用 destroy 进行资源回收操作,只会调用一次

servlet 初始化参数配置中 load-on-startup 可以指定当前 Servlet 类在 Web 容器启动时而不是等到第一个请求到来时就构造 Servlet 实例并执行 init 方法。

1.3 搭建 Servlet 程序

1.3.1 目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
\apache-tomcat-9.0.54\webapps\
D:.
├─docs
├─examples
├─host-manager
├─ROOT

└─servlet

├─src
│ └─top
│ └─reajason
│ HelloServlet.java
└─WEB-INF
│ web.xml

├─classes
│ └─top
│ └─reajason
│ HelloServlet.class
└─lib
  1. 在 tomacat 主目录中 webapps 下新建一个 servlet 目录,作为 web 项目
  2. 在 src 下编写 Java 代码
  3. WEB-INF 中 web.xml 配置当前 web 项目
  4. WEB-INF 中 classes 用来接收 Java 代码生成的 class 文件
  5. WEB-INF 中 lib 用来放第三方库

1.3.2 代码编写

.\src\top\reajason\HelloServlet.java

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
package top.reajason;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class HelloServlet extends HttpServlet {

@Override
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<h1>Hello Servlet</h1>");

}

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

.\WEB-INF\web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>top.reajason.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>

1.3.3 编译运行

在 servlet 目录下打开终端运行如下命令:apache-tomcat-9.0.54\webapps\servlet

  • -d:指定编译后的 class 文件到 .\WEB-INF\classes\ 下
  • -classpath:指定类路径
1
javac -encoding utf-8 -d .\WEB-INF\classes\ -classpath "..\..\lib\servlet-api.jar;.;.\WEB-INF\classes\" .\src\top\reajason\*.java

进入 tomcat 主目录的 bin 下面运行 startup.bat,开启 tomcat 服务

浏览器输入:http://localhost:8080/servlet/hello

2 请求与响应

Servlet 绝大多数都在 service 方法中响应处理请求,HttpServlet 重写了 Servlet 接口的 service 方法以请求方式分成了 doGet、doPost、doHead……,当请求方式是哪一种就走哪一个方法。方法的参数类型也变成了 HttpServletRequest 和 HttpServletResponse。

2.1 请求

2.1.1 UML 图

HttpServletRequest 实现了 ServletRequest 接口,并定义了更多关于 Http 的方法。

HttpServletRequestUML

2.1.2 常用方法

获取请求参数:

1
2
3
4
5
6
7
// 获取请求方法
String getMethod();
// 获取指定 name 的请求的值,第二个方法返回数组,比如前端使用多选框带过来的值
String getParameter(String name);
String[] getParameterValues(String name);
// 获取请求参数名称列表
Enumeration<String> getParameterNames();

获取请求头:

1
2
3
4
5
6
7
8
// 获取指定 name 的请求头值
String getHeader(String name);
// 获取指定 name 的请求头值,返回枚举集合,例如 accept-encoding 就可能有很多值
Enumeration<String> getHeaders(String name);
// 返回 int 值,例如 Content-Length 返回一个整数
int getIntHeader(String name);
// 获取所有请求头
Enumeration<String> getHeaderNames();

会话相关:

1
2
3
4
5
// 获取请求 cookie
Cookie[] getCookies();
// 获取 session 对象
HttpSession getSession();
HttpSession getSession(boolean b);

获取请求输入流:

1
2
3
4
5
// 获取请求字节输入流,可获取用户上传的文件
ServletInputStream getInputStream();

// 获取请求字符缓冲流
BufferedReader getReader();

获取地址相关:

1
2
3
4
5
6
7
8
// 获取客户端地址,Servlet 编写于服务端,因此对于服务端 remote 就是指的客户端
String getRemoteAddr();
String getRemoteHost();
int getRemotePort();

// 获取服务端
String getServerName();
int getServerPort();

请求转发:

1
2
3
4
// 获取请求转发对象
RequestDispatcher getRequestDispatcher(String name);
// include() 请求转发之后,又回到当前 Servlet 类继续处理
// forward() 请求转发,直接将请求交给其他 Servlet 类处理,地址栏不发生变化,区别于重定向

属性相关:

1
2
3
4
5
6
7
// 设置属性,可以将自定义类实例设为属性值
setAttribute(String name, Object obj);
// 获取属性
Object getAttribute(String name);
Enumeration<String> getAttributeNames();
// 删除属性
void removeAttribute(String name);

其他:

1
2
3
4
5
6
7
// 获取单个文件
Part getPart(String name);
// 获取多个文件
Collection<Part> getParts();
// 获取 ServletContext
ServletContext getServletContext();
// ......

2.2 响应

2.2.1 UML 图

HttpServletResponse 实现了 ServletResponse 接口,并定义了更多关于 Http 的方法。

HttpServletResponse

2.2.2 常用方法

设置响应头:

1
2
3
4
5
6
7
// 设置响应头
void setHeader(String head, String value);
void setIntHeader(String head, int value);
// 设置响应状态,200,404...
void setStatus(int);
// 设置响应类型,常见的有:text/html、image/jpeg、application/pdf...
void setContentType();

获取输出流:

1
2
3
4
// 获取字符输出流
PrintWriter getWriter();
// 获取字节输出流
ServletOutputStream getOutputStream();

重定向:

1
void sendRedirect(String url);

3 ServletConfig

每一个 Servlet 类都有一个 ServletConfig,不能共享。Web 容器启动后 Servlet 类初始化后 ServletConfig 中的值就不变了。

使用方法:

在 web.xml 中配置 ServletConfig 初始化参数,在 servlet 标签中使用 init-param 标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<servlet>
<servlet-name>rq</servlet-name>
<servlet-class>top.reajason.HelloServlet</servlet-class>
<init-param>
<param-name>username</param-name>
<param-value>ReaJason</param-value>
</init-param>
<init-param>
<param-name>gender</param-name>
<param-value>male</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>

在 Servlet 类中获取:

1
2
getServletConfig().getInitParameter("username");
getServletConfig().getInitParameter("gender");

4 ServletContext

每一个 Web 应用有一个 ServletContext(每个 JVM 有一个 ServletContext,分布式应用时会有不同的),该应用类所有 Servlet 类都可以访问

初始化参数使用方法:

在 web.xml 中配置 ServletContext 初始化参数,使用 context-param 标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>

<context-param>
<param-name>username</param-name>
<param-value>ReaJason</param-value>
</context-param>

<servlet>
<servlet-name>rq</servlet-name>
<servlet-class>top.reajason.HelloServlet</servlet-class>
<init-param>
<param-name>username</param-name>
<param-value>ReaJason</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>rq</servlet-name>
<url-pattern>/rq</url-pattern>
</servlet-mapping>
</web-app>

在 Servlet 类中调用获取参数

1
getServletContext().getInitParameter("username");

ServletContext 也有属性相关的方法,设置之后可以 Web 应用全局访问,用于数据共享,可配合 ServletContextListener 监听器在创建时初始化属性。

5 HttpSession

HttpSession 对象在与一个特定客户的整个会话期间都存在,对于会话期间客户做的所有请求,从中得到的所有信息都可以用 HttpSession 对象保存。

会话死亡的三种情况:

  1. 超时
  2. 调用 invalidate() 方法
  3. 应用结束

在分布式应用中 ServletContext、ServletConfig 每个 VM 都会有一个备份,但是 HttpSession 只会有一个,相同的 HttpSession 不会出现在两个 VM 中。Web 容器会操作 HttpSession 对象在多个 VM 中进行移动完成请求,这个过程中 HttpSessionAttributeListener 监听器发挥作用。

5.1 获取 HttpSession 对象

1
2
3
4
5
6
// 从请求中获取会话对象,没有则创建,有则直接拿
request.getSession();
// 从请求中获取会话对象,没有则返回 null,有则直接拿
request.getSession(false);
// 每次获取都是新的
request.getSession(true);

5.2 HttpSession 常用方法

1
2
3
4
5
6
7
8
9
10
11
12
// 判断当前 HttpSession 是否是新创建的
boolean isNew();
// 获取创建时间
long getCreationTime();
// 获取最近一次获取时间
long getLastAccessedTime();
// 设置用户请求最大间隔时间,超过时间未请求会话撤销(秒)
void setMaxInactiveInterval(int seconds);
// 获取用户请求最大间隔时间(秒)
int getMaxInactiveInterval();
// 结束会话
coid invalidate();

属性相关:

1
2
3
4
5
6
7
// 设置属性
void setAttribute(String name, Object obj);
// 获取属性
Object getAttribute(String name);
Enumeration<String> getAttributeNames();
// 删除属性
void removeAttribute(String name);

当客户端不支持 Cookie 时使用 URL 重写的方法

1
2
3
// encodeURL 会将 session 里面的属性拼接在 URL 后当作请求参数
response.encodeURL("/login");
response.sendRedirect(response.encodeRedirectURL("/login"));

向响应中添加 Cookie 的步骤

1
2
3
4
5
6
// 1、创建一个新的 Cookie 对象
Cookie cookie = new Cookie("username", "ReaJason");
// 2、设置 cookie 的存活时间(秒)
cookie.setMaxAge(30*60);
// 3、添加到响应中
response.addCookie(cookie);

从请求中获取特定 cookie 的步骤

1
2
3
4
5
6
7
8
9
10
// 1、获取 cookie 数组
Cookie[] cookies = request.getCookies();
// 2、遍历获取
for(int i = 0; i < cookies.length; i++){
Cookie cookie = cookie[i];
if("username".equals(cookie.getName())){
out.println(cookie.getValue());
break;
}
}

6 监听器

6.1 ServletContextListener

ServletContext 监听器,监听 ServletContext 初始化时和销毁时执行对应方法

1
2
3
4
public interface ServletContextListener extends EventListener {
default public void contextInitialized(ServletContextEvent sce) {}
default public void contextDestroyed(ServletContextEvent sce) {}
}

使用方法:

  1. 编写自定义类实现 ServletContextListener,并实现两个方法

  2. 在 web.xml 中注册监听器

    1
    2
    3
    4
    5
    6
    7
    <web-app>

    <listener>
    <listener-class>top.reajason.listener.MyServletContextListener</listener-class>
    </listener>

    </web-app>

6.2 ServletContextAttributeListener

ServletContext 属性监听器,监听 ServletContext 添加、删除、修改属性时调用对应方法

1
2
3
4
5
public interface ServletContextAttributeListener extends EventListener {
default public void attributeAdded(ServletContextAttributeEvent event) {}
default public void attributeRemoved(ServletContextAttributeEvent event) {}
default public void attributeReplaced(ServletContextAttributeEvent event) {}
}

使用方法同 ServletContextListener

6.3 ServletRequestListener

ServletRequest 监听器,监听请求初始化和销毁的时候调用对应方法,比如记录请求日志

1
2
3
4
public interface ServletRequestListener extends EventListener {
default public void requestDestroyed(ServletRequestEvent sre) {}
default public void requestInitialized(ServletRequestEvent sre) {}
}

使用方法同 ServletContextListener

6.4 ServletRequestAttributeListener

ServletRequest 属性监听器,监听 ServletRequest 添加、删除、修改属性时调用对应方法

1
2
3
4
5
public interface ServletRequestAttributeListener extends EventListener {
default public void attributeAdded(ServletRequestAttributeEvent srae) {}
default public void attributeRemoved(ServletRequestAttributeEvent srae) {}
default public void attributeReplaced(ServletRequestAttributeEvent srae) {}
}

使用方法同 ServletContextListener

6.5 HttpSessionListener

HttpSession 监听器,监听 HttpSession 创建和销毁时调用对应方法

1
2
3
4
public interface HttpSessionListener extends EventListener {
default public void sessionCreated(HttpSessionEvent se) {}
default public void sessionDestroyed(HttpSessionEvent se) {}
}

使用方法同 ServletContextListener

6.6 HttpSessionBindingListener

HttpSession 绑定监听器,监听一个自定类与 HttpSession 绑定状态调用对应方法

1
2
3
4
public interface HttpSessionBindingListener extends EventListener {
default public void valueBound(HttpSessionBindingEvent event) {}
default public void valueUnbound(HttpSessionBindingEvent event) {}
}

使用方法,在属性类中实现 HttpSessionBindingListener 接口中对应方法,无需在 web.xml 中注册。

1
2
3
4
5
6
7
8
9
10
11
import javax.servlet.http.*;

public class Dog implements HttpSessionBindingListener{
...;
public void valueBound(HttpSessionBindingEvent event){
// 当前类实例添加到会话时会触发当前方法
}
public void valueUnbound(HttpSessionBindingEvent event){
// 当前类实例从会话中移除时会触发当前方法
}
}

6.7 HttpSessionAttributeListener

HttpSession 属性监听器,监听 HttpSession 添加、删除、修改属性时调用对应方法

1
2
3
4
5
6
public interface HttpSessionAttributeListener extends EventListener {
default public void attributeAdded(HttpSessionBindingEvent event) {}
default public void attributeRemoved(HttpSessionBindingEvent event) {}
default public void attributeReplaced(HttpSessionBindingEvent event) {}

}

使用方法同 ServletContextListener

6.8 HttpSessionActivationListener

HttpSession 迁移监听器,监听绑定在 HttpSession 的自定义类从一个 JVM 迁移时调用对应方法。

HttpSession 迁移时只会迁移 Serialization 属性,因此非 Serializable 属性可以通过该监听器解决。

1
2
3
4
public interface HttpSessionActivationListener extends EventListener { 
default public void sessionWillPassivate(HttpSessionEvent se) {}
default public void sessionDidActivate(HttpSessionEvent se) {}
}

使用方法:

  1. 在自定义类中实现当前监听器接口
  2. 在 web.xml 注册当前监听器

7 过滤器

FileChain 过滤器链,使得在拦截一个请求时可以经过多个过滤器进行操作。

可以使用包装类进行请求和响应的定制操作:

  • ServletRequestWrapper
  • HttpServletRequestWrapper
  • ServletRequestWrapper
  • HtpServletResponseWrapper

定义过滤器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package top.reajason;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class MyFilter implements Filter{

private FilterConfig fc;

public void init(FilterConfig config) throws ServletException{
this.fc = config;
}

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws ServletException, IOException{
HttpServletRequest httpReq = (HttpServletRequest)req;
fc.getServletContext().log("进入过滤器了");
chain.doFilter(req, resp);
}

public void destroy(){}
}

web.xml 配置过滤器解析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<filter>
<filter-name>LRequest</filter-name>
<filter-class>top.reajason.MyFilter</filter-class>
<init-param>
<param-name>LogFileName</param-name>
<param-value>UserLog.txt</param-value>
</init-param>
</filter>

<filter-mapping>
<filter-name>LRequest</filter-name>
<!-- <url-pattern>/hello</url-pattern> -->
<servlet-name>hello</servlet-name>
</filter-mapping>

Servlet
https://reajason.vercel.app/2021/10/28/JavaWebServlet/
作者
ReaJason
发布于
2021年10月28日
许可协议