JavaWeb笔记(3)-Servlet的介绍与使用

Servlet

简介

前面我们看到的页面都是静态的,任何用户访问到的内容都是相同的,但是实际场景中更多的动态的内容。而Servlet则是JavaWeb中最为核心的内容,它是Java提供的一门动态web资源开发技术。使用Servlet就可以完成动态的效果。

Servlet是JavaEE规范之一,也就是一个接口。我们使用Servlet,就是需要定义类来实现Servlet接口,并由Web服务器来运行Servlet。下面我们通过一个案例来快速入门Servlet。

首先,我们需要创建Maven Web项目,在项目中导入Servlet依赖api:

1
2
3
4
5
6
7
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>

之后,我们新建一个类,实现Servlet接口,并实现接口中的所有方法。其中,运行的核心方法是service方法。之后在类上使用注解来指定对应访问URL,实现如下:

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
@WebServlet("/hello")
public class ServletHelloWorld implements Servlet {

@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("Servlet Hello World~");
}

@Override
public void init(ServletConfig servletConfig) throws ServletException {

}

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


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

@Override
public void destroy() {

}
}

然后我们就可以启动Tomcat服务,通过浏览器访问对应的路径http://localhost:8080/LearningWeb_war/hello,发现在IDEA控制台上输出了对应的内容,"Servlet Hello World~"。

通过上面的小案例,我们可以对Servlet的执行流程进行一个学习。

首先浏览器发出http://localhost:8080/LearningWeb_war/hello的请求,从请求中可以解析出三部分内容,分别是:

  • localhost:8080:找到对应的服务器应用
  • LearningWeb_war:找到Tomcat上对应的项目
  • hello:找到需要访问的项目中对应的Servlet类

找到对应的类之后,Tomcat服务器就会为这个类创建一个实例对象,然后调用对象中的service方法。service方法中有ServletRequest和ServletResponse两个参数,ServletRequest封装的是请求数据,ServletResponse封装的是响应数据,后期我们可以通过这两个参数实现前后端的数据交互。

生命周期与方法介绍

对象的生命周期指的是一个对象从被创建到被销毁的整个过程。Servlet运行在Tomact服务器中,它的对象生命周期主要分为四个阶段:加载和实例化、初始化、请求处理、服务终止。

  1. 加载和实例化:默认情况下,当Servlet对象被第一次访问的时候,由容器来创建Servlet对象
  2. 初始化:在Servlet实例化之后,容器调用它的init()方法来进行对象的初始化,完成一些相关工作,如加载配置文件、创建连接等。该方法只会调用一次
  3. 请求处理:每次请求Servlet的时候,容器都会调用该对象的service()方法,对请求进行处理
  4. 服务终止:当需要释放内存或者关闭容器,容器就会调用Servlet实例的destroy()方法完成资源的释放。调用之后,容器释放Servlet实例,由Java的垃圾回收器进行回收

默认情况下,Servlet对象会在第一次访问的时候被容器创建。不过有时候一些Servlet的创建比较耗时,我们可以将创建过程提前到服务器启动的时候,配置方式如下:

1
@WebServlet(urlPatterns = "/hello", loadOnStartup = 1)

在注解中增加参数loadOnStratup,它的取值有下面两种情况:

  • 负整数:表示第一次访问时创建Servlet对象
  • 0或者正整数:服务器启动的时候创建Servlet对象,数字越小优先级越高

在Servlet的生命周期中,涉及到了Servlet接口中的init、service、destroy方法,而接口中需要实现的一共有五个方法,这里一并进行说明:

  • void init(ServletConfig config):初始化方法,在Servlet被创建的时候执行,只会执行一次
  • void service(ServletRequest req, ServletResponse res):提供服务方法,每次Servlet被访问,都会调用该方法
  • void destroy():在Servlet被销毁的时候执行该方法,完成一些资源释放的工作
  • String getServletInfo():用来返回Servlet的相关信息,返回一个String字符串
  • ServletConfig getServletConfig():用来返回ServletConfig对象

Servlet继承体系与HttpServlet

上面的Servlet类的实现,都是通过实现Servlet接口来完成的,我们需要实现接口中的五个方法,虽然能够完成要求,但是编写起来还是比较繁琐。在实际项目中,我们集中关注的更多是service方法,因此可以使用更加简单的方式来进行Servlet对象的完成。

在Servlet继承体系中,Servlet是顶层的根接口,GenericServlet实现了Servlet,是它的抽象实现类,而HttpServlet又继承了GenericServlet,是一种对HTTP协议封装的Servlet实现类。由于我们对Web进行开发,都是针对HTTP协议的,因此我们可以通过继承HttpServlet来更加方便的实现相关类的编写。我们需要做的就是继承HttpServlet,覆盖其中相应的方法即可,常用的方法是doGet方法以及doPost方法,分别处理不同的请求。

1
2
3
4
5
6
7
8
9
10
11
12
@WebServlet(urlPatterns = "/httpdemo")
public class HttpServletDemo extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("get...");
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("post...");
}
}

之后可以访问localhost:8080/LearningWeb_war/httpdemo,这是get请求,也可以使用网页中使用下面的表单进行post请求,表单部分内容如下:

1
2
3
4
<form action="httpdemo" method="post">
<input name="username">
<input type="submit">
</form>
  • 这里的action可以写httpdemo,也可以写全,为/LearningWeb_war/httpdemo

urlPatterns配置

前面我们一直在使用WebServlet注解来配置我们的Servlet类,其中一个非常重要的参数就是urlPatterns,它决定了通过何种URL能够访问到对应的Servlet类。一个Servlet可以配置多个urlPattern,它以字符串数组的形式传入。

1
@WebServlet(urlPattern = {"/demo1", "/demo2"})

urlPatterns配置规则主要分为四种,分别是精确匹配、目录匹配、拓展名匹配以及任意匹配

精确匹配:直接在urlPattern中写好唯一的URL,对应访问路径。

目录匹配:针对目录的访问都可以访问到该Servlet。例如下面的路径,只要是针对user目录的访问都可以访问到该Servlet,例如/user/aaa/user/bbb

1
@WebServlet(urlPattern = "/user/*")

扩展名匹配:利用扩展名来匹配。注意这里不能以/开头。下面的路径,只要是最后扩展名为.me的访问都可以访问到该Servle,例如/aa.me/bb.me

1
@WebServlet(urlPattern = "*.me")

任意匹配:任意的访问都可以匹配。主要方式有如下两种:

1
2
@WebServlet(urlPattern = "/")
@WebServlet(urlPattern = "/*")

这里需要注意//*的区别:

  1. 当我们的项目中有Servlet配置了/,它会覆盖掉Tomcat中的DefalutServlet,当其他的urlPattern都匹配不上,就会匹配这个Servlet
  2. Tomcat的DefalutServlet是用来处理静态资源的。如果我们配置了/,它会把默认的DefalutServlet覆盖掉,这样就在请求静态资源的时候就没有走Tomcat默认的Servlet,而是走了我们自定义的Servlet,最终就会导致静态资源无法访问
  3. 当我们的项目中有Servlet配置了/*,它表示匹配任意访问路径

urlPattern在匹配的时候有优先级之分,越精确的匹配优先级越高,大体优先级如下:精确匹配 > 目录匹配 > 扩展名匹配 > /* > /

  • 补充:XML配置Servlet

上面我们都是使用注解的方式来配置Servlet,但是这是从Servlet 3.0版本之后才支持的,在此之前Servlet的配置只支持XML方式。

利用XML配置Servlet分为两步,第一步是正常编写Servlet类,当然这里不需要无法进行注解配置,第二步是在web.xml中配置该Servlet,配置方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- Servlet 全类名 -->
<servlet>
<!-- servlet的名称,名字任意-->
<servlet-name>test</servlet-name>
<!--servlet的类全名-->
<servlet-class>com.web.test.MyHttpServletDemo</servlet-class>
</servlet>

<!-- Servlet 访问路径 -->
<servlet-mapping>
<!-- servlet的名称,要和上面的名称一致-->
<servlet-name>test</servlet-name>
<!-- servlet的访问路径-->
<url-pattern>/test</url-pattern>
</servlet-mapping>

在前面的service方法中,参数列表还提供了两个参数,分别是ServletRequest对象以及ServletResponse对象,分别对应请求对象以及响应对象,这两个对象分别可以实现对浏览器响应的以及回复的处理,接下来就来进行相关的介绍。

Request

简介

Request允许我们获取请求数据。浏览器发送的HTTP请求到达后台服务器Tomcat,Tomcat则会将请求中的数据进行解析,包括请求行、请求头和请求体。解析的结果存入一个对象中,即Requset对象,而我们就可以从Request对象中获取请求的相关参数。

Requset的继承体系如下,其中我们需要重点关注也是平常使用最多的是其中的HttpServletRequest接口。

请求解析

根据HTTP协议,请求数据总共分为三个部分,分别是请求行,请求头和请求体。对于这三部分的内容,HttpRequest中都提供了相关的方法来进行获取。

请求行数据获取:请求行包含三个内容,分别是请求方式、请求资源路径、HTTP协议以及版本,相关的API如下:(举例:get http://localhost:8080/LearningWeb_war/httpdemo

  • String getMethod():获取请求方式(GET
  • String getContextPath():获取虚拟目录,即项目访问路径(/LearningWeb_war
  • StringBuffer getRequestURL():获取URL,即统一资源定位符(http://localhost:8080/LearningWeb_war/httpdemo
  • String getRequestURI():获取URI,就统一资源标识符(/LearningWeb_war/httpdemo
  • String getQueryString():获取请求参数,在GET方式下拼接在请求链接中的参数(username=123&password=345

请求头数据获取:请求头中的数据都是以键值对的形式存在的

  • String getHeader(String name):传入请求头中的key,返回对应的value,例如我们可以通过req.getHeader('user-agent')来获取浏览器的版本信息

请求体数据获取:在使用get方式的时候,请求体是没有数据的,而post请求会将自己的参数存放在请求体中。对于请求体的数据,Request对象提供了两种方式进行获取,分别对应字节输入流和字符输入流

  • ServletInputStream getInputStream():获取字节输入流
  • BufferedReader getReader():获取字符输入流

这里的输入流对象是通过Request对象来获取的,当请求完成之后,Request对象也会被销毁,之后输入流对象也会自动关闭,也不需要我们手动关闭流

参数获取

在解析HTTP请求的时候,最主要的一个工作就是解析其中的相关请求参数。

在实际场景中,我们面对最多的就是get请求和post请求。我们知道,get请求的请求参数被拼接在URL中,我们通过getQueryString()方法可以获取到拼接之后的字符串;post请求的请求参数在请求体中,我们通过getReader()获取字符输入流,也可以得到请求参数。对于这两种情况,我们都可以获得到所有请求参数的原始字符串,经过例如字符串split等处理,就可以得到所需的请求参数。这种方式虽然能够解决需求,但是还是比较麻烦,而Request对象也提供了一种比较方便的方式来获取请求参数,并且统一了get与post请求的参数获取。

前端传来的参数可以看成组成了一个Map,Request对象为我们提供了如下方法:

  • Map<String, String[]> getParameterMap():获取所有参数组成的Map集合
  • String[] getParameterValues(String name):根据名称获取参数值数组
  • String getParameter(String name):根据名称获取单个参数值

上面的方法可以解析get的参数,以及post请求体中类似于username=123&password=456这样的参数,但是无法解析json等的数据。

参数中文乱码问题:如果参数中有中文,可能出现中文乱码问题。乱码问题出现的根本原因在于编码与解码所用的字符集不一致。Tomcat 8以后使用UTF-8来进行编解码,对于post请求出现的中文问题,只需要在请求前设置编码格式相符合即可,而对于get请求的中文问题,Tomcat 8之后已经得到解决。

1
req.setCharacterEncoding("UTF-8");

请求转发

请求转发(forward)是一种在服务器内部的资源跳转方式。举例来说,浏览器发送一个请求给服务器,服务器中的资源A接收到请求,资源A处理完请求后将请求发送给资源B,资源B处理完成之后再将结果响应给浏览器。其中请求从资源A到资源B的过程就叫做请求转发。

请求转发有如下特点:

  1. 浏览器地址栏的路径不发生变化
  2. 只能转发到当前服务器的内部资源,不能从一个服务器通过请求转发到另一个服务器
  3. 一次请求,可以在转发资源之间使用Request对象共享数据

在Servlet中,可以用下面的方式实现请求转发:

1
req.getRequestDispatcher("目标资源路径").forward(req, resp)

在请求转发的过程中,资源之间可以使用Request对象完成数据共享,需要使用到Request对象提供的三个方法:

  • void setAttribute(String name, Object o):将键值对数据存储到Request对象域中
  • Object getAttribute(String name):根据key来获取值
  • void removeAttribute(String name):根据key来删除键值对

Response

简介

Response允许我们设置响应数据。在我们接受到HTTP请求后,完成相关的业务处理,后台就需要将结果即业务数据返回给前端。我们将响应数据封装到Response对象中,后台服务器Tomcat就会解析Response对象,按照响应行+响应头+响应体的格式拼接出最终响应,浏览器解析结果,得到展示内容。

与Request相同,Response也有类似的继承体系,我们也是重点关注其中的HttpServletResponse接口。

响应设置

HTTP的响应数据同样分成了三部分,分别是响应行、响应头和响应体。对于这三部分的数据,Response都提供了对应的方法来进行设置。

在响应行中,比较常用的就是设置响应状态码,对应的方法如下:

  • void setStatus(int sc):设置响应状态码

Response的响应行也是由键值对构成的,对应的设置方法如下:

  • void setHeader(String name, String value):设置响应头键值对

响应体中的内容,则是通过字符或者字节输入流的方式进行写入,相关方法如下。获取到输入流之后,利用write方法进行写入,这里同样不需要手动关闭流。

  • PrintWriter getWriter():获取字符输出流
  • ServletOutputStream getOutputStream():获取字节输出流

写入内容除了可以普通的纯文本,也可以是html代码。但是返回html的话,要想在浏览器中显示,需要注意设置响应的数据格式以及数据的编码。

1
resp.setContentType("text/html;charset=utf-8");

请求重定向

Response重定向(redirect),是一种资源跳转方式。举例来说,浏览器发送请求给服务器之后,服务器中对应的资源A接收到请求。此时资源A无法处理该请求,则会给浏览器响应一个302的状态码以及一个访问其他资源的路径location,浏览器接收到响应状态302,就会重新发送请求到location对应的访问地址去访问资源B,资源B接收到请求之后进行处理,并最终给浏览器响应结果。

重定向具有如下特点:

  1. 浏览器地址栏路径发生变化
  2. 可以重定向到任何位置的资源,包括服务器内部以及外部的资源
  3. 两次请求之间不能使用Resquest对象来进行资源共享

在Servlet中,可以用下面的方式来实现重定向,只需要返回302状态码以及对应location位置即可

1
2
3
4
resp.setStatus(302);
resp.setHeader("location", "目标资源的访问路径");
// 上面的两行代码也可以进行简化
resp.sendRedirect("目标资源的访问路径");

JavaWeb笔记(3)-Servlet的介绍与使用
http://example.com/2022/10/06/JavaWeb笔记-3-Servlet的介绍与使用/
作者
EverNorif
发布于
2022年10月6日
许可协议