JavaWeb笔记(3)-Servlet的介绍与使用
Servlet
简介
前面我们看到的页面都是静态的,任何用户访问到的内容都是相同的,但是实际场景中更多的动态的内容。而Servlet则是JavaWeb中最为核心的内容,它是Java提供的一门动态web资源开发技术。使用Servlet就可以完成动态的效果。
Servlet是JavaEE规范之一,也就是一个接口。我们使用Servlet,就是需要定义类来实现Servlet接口,并由Web服务器来运行Servlet。下面我们通过一个案例来快速入门Servlet。
首先,我们需要创建Maven Web项目,在项目中导入Servlet依赖api:
1 |
|
之后,我们新建一个类,实现Servlet接口,并实现接口中的所有方法。其中,运行的核心方法是service方法。之后在类上使用注解来指定对应访问URL,实现如下:
1 |
|
然后我们就可以启动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服务器中,它的对象生命周期主要分为四个阶段:加载和实例化、初始化、请求处理、服务终止。
- 加载和实例化:默认情况下,当Servlet对象被第一次访问的时候,由容器来创建Servlet对象
- 初始化:在Servlet实例化之后,容器调用它的
init()
方法来进行对象的初始化,完成一些相关工作,如加载配置文件、创建连接等。该方法只会调用一次 - 请求处理:每次请求Servlet的时候,容器都会调用该对象的
service()
方法,对请求进行处理 - 服务终止:当需要释放内存或者关闭容器,容器就会调用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 |
|
之后可以访问localhost:8080/LearningWeb_war/httpdemo
,这是get请求,也可以使用网页中使用下面的表单进行post请求,表单部分内容如下:
1 |
|
- 这里的action可以写
httpdemo
,也可以写全,为/LearningWeb_war/httpdemo
urlPatterns配置
前面我们一直在使用WebServlet
注解来配置我们的Servlet类,其中一个非常重要的参数就是urlPatterns
,它决定了通过何种URL能够访问到对应的Servlet类。一个Servlet可以配置多个urlPattern,它以字符串数组的形式传入。
1 |
|
urlPatterns配置规则主要分为四种,分别是精确匹配、目录匹配、拓展名匹配以及任意匹配
精确匹配:直接在urlPattern中写好唯一的URL,对应访问路径。
目录匹配:针对目录的访问都可以访问到该Servlet。例如下面的路径,只要是针对user目录的访问都可以访问到该Servlet,例如/user/aaa
,/user/bbb
1 |
|
扩展名匹配:利用扩展名来匹配。注意这里不能以/
开头。下面的路径,只要是最后扩展名为.me
的访问都可以访问到该Servle,例如/aa.me
,/bb.me
1 |
|
任意匹配:任意的访问都可以匹配。主要方式有如下两种:
1 |
|
这里需要注意/
与/*
的区别:
- 当我们的项目中有Servlet配置了
/
,它会覆盖掉Tomcat中的DefalutServlet,当其他的urlPattern都匹配不上,就会匹配这个Servlet - Tomcat的DefalutServlet是用来处理静态资源的。如果我们配置了
/
,它会把默认的DefalutServlet覆盖掉,这样就在请求静态资源的时候就没有走Tomcat默认的Servlet,而是走了我们自定义的Servlet,最终就会导致静态资源无法访问 - 当我们的项目中有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的过程就叫做请求转发。
请求转发有如下特点:
- 浏览器地址栏的路径不发生变化
- 只能转发到当前服务器的内部资源,不能从一个服务器通过请求转发到另一个服务器
- 一次请求,可以在转发资源之间使用Request对象共享数据
在Servlet中,可以用下面的方式实现请求转发:
1 |
|
在请求转发的过程中,资源之间可以使用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接收到请求之后进行处理,并最终给浏览器响应结果。
重定向具有如下特点:
- 浏览器地址栏路径发生变化
- 可以重定向到任何位置的资源,包括服务器内部以及外部的资源
- 两次请求之间不能使用Resquest对象来进行资源共享
在Servlet中,可以用下面的方式来实现重定向,只需要返回302状态码以及对应location位置即可
1 |
|