JavaWeb(二) Servlet
本文最后更新于:2023年2月28日 下午
Java Servlet (Server Applet) 是运行在 Web 服务器或应用服务器上的程序,实现动态资源文件。
其作为 Web 浏览器或其他 HTTP 客户端和 HTTP 服务器上的数据库或应用程序之间的中间层,扩展服务器功能并响应传入请求。
Servlet接口实现类的开发步骤:
- 创建一个继承
HttpServlet
父类的 Java类,实现Servlet
接口 - 重写
HttpServlet
父类方法doGet() / doPost()
- 在
web.xml
中注册类信息,完成路由分发
1. Servlet容器
Servlet容器,提供Servlet功能的服务器。实现Servlet接口规范,为部署在容器内的应用提供支持,而无需关心接口被应用如何使用。
Servlet应用,使用Servlet接口规范,完成具体业务逻辑,而无需关心规范被容器如何实现
Tomcat
轻量级Web服务器。
下载
下载文件夹压缩包,免去安装。
目录结构
1 |
|
使用Maven Tomcat插件
注意!!! 插件是Tomcat7,不支持Servlet3.0,互搓的概率非常大,还是下载Tomcat后配入IDEA吧
pom.xml
配置:
1 |
|
2. Servlet路由配置
由于Servlet的java文件路经过深,我们将Servlet类文件映射至一个别名,实现路由分发。
具体有两种实现方式,Servlet3.0及以上版本均支持注解实现。
web.xml
web.xml主要用来配置Servlet三大组件Filter、Listener、Servlet,Servlet3.0后非web工程必须文件。本节学习记录Servleti相关标签。
根元素
根元素
<web-app>
,其属性配置模式文件、指定路由配置是否全部依赖于web.xml
(metadata-complete="true"
)等
1 |
|
servlet元素
<servlet></servlet>
用来声明一个servlet的数据,主要有以下子元素:
<servlet-name></servlet-name>
指定servlet的名称<servlet-class></servlet-class>
指定servlet的类名称<jsp-file></jsp-file>
指定web站台中的某个JSP网页的完整路径<init-param></init-param>
用来定义参数,可有多个init-param。在servlet类中通过getInitParamenter(String name)方法访问初始化参数<load-on-startup></load-on-startup>
指定当Web应用启动时,装载Servlet的次序。当值为正数或零时:Servlet容器先加载数值小的servlet,再依次加载其他数值大的servlet。当值为负或未定义:Servlet容器将在Web客户首次访问这个servlet时加载它。
<servlet-mapping></servlet-mapping>
用来定义servlet所对应的URL 路由
,包含两个子元素:
<servlet-name></servlet-name>
指定servlet的名称<url-pattern></url-pattern>
指定servlet所对应的URL
1 |
|
默认资源文件
用于配置启动项目时(输入项目URL)直接跳转的页面。
1 |
|
注解
@WebServlet
为每个Servlet类配置一个@WebServlet
注解,有以下参数:
属性 | 类型 | 意义 |
---|---|---|
name | String | 指定 Servlet 的 name 属性,等价于 <servlet-name> 。如果没有显式指定,则该 Servlet 的取值即为类的全限定名。 |
value/urlPatterns | String[] | 指定一组 Servlet 的 URL 匹配模式。等价于<url-pattern> 标签。 |
loadOnStartup | int | 指定 Servlet 的加载顺序,等价于 <load-on-startup> 标签。 |
initParams | WebInitParam[] | 指定一组 Servlet 初始化参数,等价于<init-param> 标签。 |
asyncSupported | boolean | 声明 Servlet 是否支持异步操作模式,等价于<async-supported> 标签。 |
description | String | 该 Servlet 的描述信息,等价于 <description> 标签。 |
displayName | String | 该 Servlet 的显示名,通常配合工具使用,等价于 <display-name> 标签。 |
示例
1 |
|
3. Servlet结构
Servlet生命周期
Servlet 由其容器实例化,每个Servlet实例化时都会经历生命周期。
init()
初始化service()
处理客户端的请求destroy()
销毁
结构详解
1 |
|
因此,Servlet实现业务逻辑的模式为(Java设计模式中的模板模式):
graph LR;
Tomcat新建LoginServlet实例 --执行Servlet生命周期--> 调用service方法 --判断Get/Post请求类型--> 调用LoginServlet中重写的doGet/doPost方法
4. Servle处理Request
在客户端发出每个Http请求时,服务器都会创建一个对应的HttpServletRequest对象,并把请求数据封装到该对象中。根据上文对Servlet结构的拆解,我们发现HttpServletRequest对象将作为service()
方法和对应请求类型的服务方法doGet()/doPost()
的参数传入。
HttpServletResponse对象的实现结构如下:
graph BT;
RequestFacade类 --implements--> HttpServletRequest接口 --extends--> ServletRequest接口
HttpServletRequest对象封装了请求信息(请求行、请求头、请求体),在实现业务逻辑时通过其封装方法解读请求数据:
1 |
|
5. Servlet处理Response
在客户端发出每个Http请求时,服务器创建一个对应的HttpServletRequest对象的同时,也会创建一个HttpServletResponse对象。HttpServletResponse对象同样将将作为service()
方法和对应请求类型的服务方法doGet()/doPost()
的参数传入。
HttpServletResponse对象的实现结构如下:
graph BT;
ResponseFacade类 --implements--> HttpServletResponse接口 --extends--> ServletResponse接口
HttpServletResponse对象封装了响应信息(状态行、响应头、响应体),在实现业务逻辑时通过其封装方法获得文本输出流,或返回响应数据:
1 |
|
6. Redirect与RequestDispatcher
Redirect | RequestDispatcher | |
---|---|---|
功能 | 重定向 | 转发 |
对象 | 响应对象的执行方法 resp.sendRedirect(<path>) |
请求对象的执行方法 req.getRequestDispatcher(<path>).forward(req, resp) |
实现逻辑 | 服务器返回客户端响应消息中状态为 302 重定向;location 写入重定向地址客户端发起新的Get请求 地址栏变为重定向地址 产生新request/response对象 |
服务器内部处理转发,与客户端无关 地址栏不变 转发原request/response对象 |
传参方式 | 因发起新Get请求 仅支持URL地址传递参数 |
因服务器内部转发 可直接获取使用request/response对象 |
跳转范围 | 支持重定向到服务器外部资源 不支持重定向至项目安全目录 |
不支持转发至外部资源 仅支持转发至服务器内部资源 |
相对位置 | / 服务器根路径通常需要 getContextPath() + <path> 拼接跳转路径 |
/ 项目根路径 |
运用场景 | 用于处理Post请求的刷新重复提交。因此,无论Post请求逻辑处理成功/失败/异常,均需执行重定向 | 用于处理Get请求转发相应资源(JSP/HTML/图片/文档等),仅对外暴露请求地址,真实资源由服务器端转发响应。因此,客户端将不再直接访问文档等资源,而是仅能访问资源映射的地址 |
以某登录界面 /login
为例:
1 |
|
7. 多个Servlet数据共享
Cookie
会话是从浏览器访问服务器开始,到访问服务器结束,浏览器关闭为止的这段时间内容产生的多次请求和响应的总和。
Cookie是客户端会话技术:
- 用户通过浏览器第一次向某网站发送请求,并由
<OneServlet>
处理业务逻辑时。<OneServlet>
在运行期间创建一个 cookie 存储与当前用户相关数据。 <OneServlet>
工作完毕后,将cookie写入响应头交返回浏览器。浏览器收到响应包之后,将 cookie 存储在浏览器的缓存中。- 一段时间之后,用户通过同一个浏览器再次向某网站发送请求,并由申请
<TwoServlet>
处理业务逻辑时。浏览器需要无条件的将该网站已有的 cookie 写入请求头发送给服务器。此时<TwoServlet>
通过读取请求头中cookie中信息,得到<OneServlet>
提供的共享数据。
Cookie的写入
1 |
|
Cookie的读出
1 |
|
Cookie的有效时间
会话Cookie & 持久性Cookie
如果 cookie 不包含到期时间,则可视为会话 cookie。存储在内存中,不会写入磁盘。当浏览器关闭时,cookie 将永久丢失。
如果 cookie 包含到期时间,则可视为持久性 cookie。在指定的到期时间,cookie 将从磁盘中删除。
设置到期时间
cookie.setMaxAge(<正数/负数/0>);
- 如果设置为正数,则cookie存储到客户端电脑,不论关闭浏览器或关闭电脑,直到时间(此cookie从创建到过期所能存在的时间,以秒为单位)到才会过期;
- 如果设置为负数,则cookie在内存中保存,关闭浏览器即失效;
- 如果设置为0,则立即删除该cookie。
Servlet域对象
对于web应用程序,在不同的Servlet实现类之间传递数据是至关重要的。Servlt有三个不同的作用域: request scope, session scope , application scope 。
域对象的作用:保存数据、获取数据、共享数据。
域对象的名称 | 域对象的类型 |
---|---|
application | ServletContext (javax.servlet.http.HttpServletRequest) |
request | HttpServletRequest (javax.servlet.http.HttpServletRequest) |
session | HttpSession (javax.servlet.http.HttpSession) |
全局作用域对象(ServletContext)
服务器启动时,为每个Web应用创建一个ServletContext对象,保存在服务器内存里。所有Servlet实现类均可直接由request对象获得。
还是不明白ServletContext域对象的实现结构。既然作为全局作用域,所有Servlet实现类共享的变量仓库,就应该存在于服务器端,为何是从HttpServletRequest对象中拿到(
req.getServletContext()
),并且发现GenericServlet抽象类中重写了ServletConfig接口的方法getServletContext()
,他是怎么跑到HttpServletRequest对象里去的呢?除非HttpServletRequest对象封装请求数据的同时还封装了Web容器的初始数据。那么它是怎么做到的呢?由于HttpServletRequest是接口,无法实例化,他应该是借由其实现类Request类/RequestFacade类等通过向上转型实现实例化的。难道玄机在这些类中?时间精力有限,希望之后能把Tomat的源码掰开读读,就能解惑了。
ServletContext域对象写入
1 |
|
Attribute以键值对的形式存储数据
ServletContext域对象读出
1 |
|
请求作用域对象(HttpServletRequest)
在同一Web项目下,两个Servlet实现类间通过请求转发的同时共享请求对象共享数据。
HttpServletRequest域对象写入
1 |
|
HttpServletRequest域对象读出
1 |
|
会话作用域对象(Session)
Session是服务器端会话技术:
- 每个浏览器访问服务器Servlet,jsp等动态资源文件时,就会在服务器中自动创建相应Session对象,并分配SessionID作为标识。SessionID将以
JSESIONID
存储为cookie。 - 浏览器每次发起请求,请求头中都携带cookie:
JSESIONID
,凭此在服务器中获取其Session对象中存储的数据。 - 服务器将相应数据写入响应体中返回浏览器客户端。
Session域对象写入
1 |
|
Session域对象读出
1 |
|
Session的有效时间
session是必须存在到期时间的,过期后服务器中存储的Session对象销毁。但session中的有效时间并不是从Session对象创建时开始计时,而是从其停止活动时计时(因此,若sessiob一直被使用,则一直重新计时而session不会过期)。
无法保证session数据的存储方式。它可能存储在文件中,在服务器重新启动时不可用,或者它可能存储在数据库中,因此在服务器重新启动时可用。
但sesion不一定在销毁时失效。由于Session对象由SeesionID唯一标识,通常存储于 cookie中。当cookie销毁时(如会话cookie在关闭浏览器时销毁),SessionID同时被销毁无法找回Session对象,Session失效。
设置到期时间
-
web.xml
文件中配置单位分,默认为30分钟
1
2
3<session-config>
<session-timeout>30</session-timeout>
</session-config> -
session.setMaxInactiveInterval();
设置Session对象的到期时间
Session对象的销毁 session.invalidate();
作用范围与生命周期
cookie | session | request | application | |
---|---|---|---|---|
创建 | 在Servlet实现类中实例化Cookie对象 | 浏览器访问服务器Servlet,jsp等动态资源文件时就会自动创建Session对象,保存在服务器内存里可直接由request对象获得 | 客户端向服务器发送一次请求,服务器就会创建Request对象 | 服务器启动时,为每个Web应用创建一个ServletContext对象,保存在服务器内存里可直接由request对象获得 |
销毁 | 1. 会话cookie:关闭浏览器 2. 持久性cookie:过期 3. 手动销毁 |
1. session过期 2. 非正常关闭服务器(正常关闭session会序列化,再次启动服务器session会被反序列化) 3. 手动销毁 |
服务器对这次请求作出响应后就会销毁Request对象 | 服务器关闭或者项目从服务器中移除 |
有效 | 会话cookie在一次会话期间有效 (打开浏览器会话开始,直到关闭浏览器会话结束) 持久性cookie在有效时间内有效 |
一次会话期间 (打开浏览器会话开始,直到关闭浏览器会话结束) | 当前请求中 | 服务器运行中 |
关于parameter & attribute & cokkie & session 的一些总结:
- 每个作用域接口均包含
setAttribute()
/getAttribute()
方法,作用域对象均从request对象中获取 - parameter & cookie只能是字符串类型,attribute & session可以是任意类型
8 Servlet监听器
监听器可以用来检测网站的在线人数,统计网站的访问量等,由于目前未涉及到这样规模的项目,暂不深究。
9. Servlet过滤器
Servlet规范中提供Filter接口,实现该接口可以对访问指定资源文件(静态/动态)的请求和响应进行拦截。Filter对用户请求进行预处理,接着将请求交给Servlet进行业务逻辑处理并生成响应,最后Filter再对服务器响应进行后处理。
Filter主要有:
- 用户授权的Filter:Filter负责检查用户请求,根据请求过滤用户非法请求。
- 日志Filter:详细记录某些特殊的用户请求。
- 负责解码的Filter:包括对非标准编码的请求解码。
- 能改变XML内容的XSLT Filter 等。
路由配置
web.xml
在web.xml
中,与Servlet类似
1 |
|
注解
1 |
|
路径格式:
-
精确匹配,仅过滤指定路径地址:
/user/LoginServlet
-
通配符过滤指定路径下的所有下级路径:
/admin/*
-
通配符过滤指定文件类型:
*.action
-
不支持既指定路径,又指定扩展名的规则:
/admin/*.action
❌
生命周期
-
init()
,首次创建过滤器后,回调 -
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
,过滤功能的具体实现方法,每当请求匹配过滤器的过滤规则时,均回调传入参数包括拦截的请求与响应
request
,response
;当前 Filter链上的对象chain
,其方法chain.doFilter(req, res);
代表过滤器放行,请求与响应将进入下一个Filter或Servlet -
destroy()
,过滤器对象销毁前,回调
例子:判断用户是否登陆的过滤器
为防止用户未登录即访问服务器资源吗,过滤器通过Session信息识别用户是否已登录。已登录用户在服务器中有对应的session对象,以此为是否登陆的表示进行过滤。
1 |
|
参考资料
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!