- Servlet
- 1. HTTP协议
- 2. Servlet API
- 3. Java Web应用的生命周期
- 4. Servlet生命周期
- 5. ServletContext与Web应用范围
- 6. Servlet.service()的异常
- 7. 防止页面被客户端缓存
- 8. Annotation标注配置Servlet
- 9. 中文字符编码
- 10. 下载文件
- 11. 上传文件
- 12. 动态生成图像
- 13. Cookie
- 14. 访问Web应用的工作目录
- 15. 转发和包含
- 16. 重定向
- 17. 访问Servlet容器内其他Web应用
- 18. 避免并发问题
- 19. 对请求的异步处理
- 20. 服务器端推送
- 21. Session
- 22. 域对象总结
- 23. web.xml
- 24. URL总结
- cookie保存密码免登录的例子
[toc]
Servlet
1. HTTP协议
- 超文本传输协议,客户端请求和响应的标准协议。
- URL:协议+地址+端口+资源路径+参数,
http://localhost:8080/myweb/sr01?name=zhangsan
- 特点:
- C/S模式
- 简单快速
- 灵活:允许传输任意类型数据,传输类型用
Content-Type
标记 - 无连接、1.1版本支持有连接甚至流水线
- 无状态:协议对事务没有记忆能力,后续处理用到之前数据要重传
- 请求:请求行(一行)+请求头(请求状态,多行)+请求正文(仅POST请求有)
- 响应:状态行(一行)+响应头(响应状态,多行)+响应正文
- 报头域:键值对,中以冒号+空格间隔,键名大小写无关
2. Servlet API
一个Servlet样例
1 | "/location") ( |
-
代码结构:
- WebServlet标注,内容为本Servlet的各种设置
- 继承HttpServlet类
- 复写service方法(内含doGet和doPost方法,自动处理)
-
工作流程:
- 通过请求头确定要访问的主机
- 通过请求行确定要访问的应用
- 通过请求行的路径确定要访问的资源
- 通过4中资源路径,匹配项目的真实路径
- 若首次访问Servlet对象,服务器会创建实例,并init方法
- 调用service方法处理请求
- 方法完毕,服务器将response缓冲区数据取出,以HTTP响应的格式发送给浏览器
2.1 Servlet接口
本接口是API的核心,所有Servlet类都需实现,包含5个方法
init(ServletConfig config)
:容器在创建好Servlet对象后调用本方法service(ServletRequest req, ServletResponse res)
:容器接收到客户端访问特定Servlet对象的请求时调用destroy()
:释放Servlet对象占用资源,结束生命周期时调用- 以上方法均由容器自行调用
getSerletConfig()
:返回ServletConfig对象,包含了初始化参数信息getServletInfo()
:返回一个包含了Servlet创建者、版本、版权等信息的字符串
2.2 GenericServlet抽象类
- 本抽象类实现了Servlet、ServletConfig和Serializable接口,且HttpServlet抽象类是本类的子类。
- 本类实现了init方法,传入的ServletConfig对象,由私有变量config引用,此时GenericServlet对象即于ServletConfig对象关联。
- 同时本类还定义了不带参数的
init()
方法,带参init会调用不带参init,故若要覆盖父类的初始化,则可覆盖父类的不带参init,或覆盖带参init(此时若需关联config,则要super.init(config)
先用父类init关联)。 - 本类未实现service方法,这是唯一抽象方法。其他方法虽不是抽象,但多数无实际意义。
- destory方法中,可以释放所占资源,如文件流关闭,数据库关闭等。
- 本类实现了ServletConfig的所有方法,可直接调用。
2.3 HttpServlet抽象类
-
是GenericServlet的抽象子类,本类为Servlet接口提供了与HTTP协议相关的实现,即本类适合运行在与客户端采用HTTP协议通信的servlet容器或web服务器中。JavaWeb开发时定义类一般扩展本类。
-
本类为HTTP的每种请求方式提供了响应的服务方法,如
doGet()
、doPost()
、doPut()
、doDelete()
等。 -
本类实现了
service(ServletRequest req, ServletResponse res)
方法,内部将ServletRequest和ServletResponse强转为HttpServletRequest和HttpServletResponse,之后调用重载方法service(HttpServletRequest req, HttpServletResponse resp)
。重载方法中通过res.getMethod()
获取请求方式,然后调用不同请求方式对应的不同do方法。 -
本类的各种默认实现的do方法,都会客户端返回一个错误:
- 若HTTP1.1通信,那么返回
HttpServletResponse.SC_METHOD_NOT_ALLOWED
即错误405 - 若不是HTTP1.1,则返回``HttpServletResponse.SC_BAD_REQUEST`即错误400
故子类中应覆盖各种do方法。?
- 若HTTP1.1通信,那么返回
2.4 ServletRequest接口
-
Servlet接口的service方法中,有此类型的参数。容器接收到访问Servlet请求时,将原始请求数据包装成一个ServletRequest对象,容器调用service方法时就传入该对象。
-
本接口提供了一系列获取请求数据的方法:
getContentLength()
返回请求正文长度/-1getContentType()
获取请求正文的MIME类型/nullgetInputStream()
返回读取请求正文的输入流getLocalAddr()
服务器端IPgetLocalName()
服务器端主机名getLocalPort()
服务器端FTP端口号getParameter(String name)
根据请求参数名获取参数值getProtocol()
通信协议名称和版本号getReader()
用于读取字符串形式的请求正文的BufferedReader对象getRemoteAddr()
客户端IPgetRemoteHost()
客户端主机名getRemotePort()
客户端FTP端口号
以下是定义了一组用于在请求范围内存取共享数据的方法
setAttribute(String name, Object)
在请求范围内保存一个属性(即要共享的数据),参数是属性名与值getAttribute(String name)
根据name获取属性值removeAttribute(String name)
在请求范围中删除一个属性
2.5 HttpServletRequest接口
-
是2.4中ServletRequest的子接口
-
本接口提供了读取HTTP请求中相关信息的方法
getContextPath()
客户端请求访问的Web应用的URL入口,如客户端访问http://localhost:8080/helloapp/info
,则本方法返回/helloapp
,即Web应用名字getCookies()
返回HTTP请求中的所有CookiegetHearder(String name)
返回HTTP请求头部的特定项getHearNames()
返回一个Enumeration对象,包含HTTP请求头部的所有项目名getMethod()
Http请求方式getRequestURI()
HTTP请求头部第一行的URI,请求行中的资源名称部分(项目名称开始)getQueryString()
HTTP请求中的查询字符串,即?
后的内容,参数部分
-
getRequestURL()
完整URLgetProtocol()
HTTP版本号getParameter(name)
获取指定名称的参数值(参数均为键值对)getParameterValues(name)
获取指定名称参数的所有值
由此可见,Servlet容器已经将HTTP请求进行了解析,只需get方法获取即可。
2.6 ServletResponse接口
- Servlet通过本接口对象生成响应结果。当容器接收到访问Servlet请求时,创建一个本接口对象,作为参数传给service方法。
- 定义了一系列与生成响应结果相关的方法:
setHeader("content-type", "text/html");
设置响应MIME类型set/getCharacterEncoding(String charser)
响应正文的字符编码,默认编码为ISO-8859-1不支持中文,GB2312支持简中,其扩展GBK支持简繁中setcontentLength()
响应正文长度set/getContenttype(String type)
响应正文的MIME类型(ServletResponse中默认MIME是text/plain
纯文本,而HttpServletResponse中默认MIME是text/html
HTML文档)set/getBufferSize(int size)
存放响应正文数据的缓冲区大小reset()
清空缓冲区内正文数据,并清空响应状态代码和响应头resetBuffer()
仅清空缓冲区正文数据flushBuffer()
强制把缓冲区响应正文数据发送到客户端isCommitted()
若true,表示缓冲区数据已发送到客户端getOutputStream().write(string.getBytes())
返回ServletOutputStream对象,用于输出二进制正文数据,字节流,可响应一切数据getWriter().write(string)
返回PrintWriter对象,用于输出字符串形式正文数据,字符流,响应字符- 响应回的数据到客户端被解析
- 两种输出流不可同时使用
- 本对象主要用于产生HTTP相应结果正文部分。
- 缓冲区数据发送到客户端的时机:
- 缓冲区已满,自动发送,并清空缓冲区
- 调用本对象的
flushBuffer()
方法 - 调用ServletOutputStream对象或PrintWriter对象的flush或close方法
- 为确保缓冲区数据完全发送,应输出数据完毕后调用ServletOutputStream对象或PrintWriter对象的
close()
方法。Tomcat中,若service未执行close,则自动调用。 - MIME和字符编码的设置(其set方法),要先于输出(get输出方法)。
2.7 HttpServletResponse接口
- 是2.6的ServletResponse接口的子接口,提供了与HTTP协议相关的一些方法,可用于设置响应头或向客户端写Cookie:
addHeader(String name, String value)
向响应头加入内容sendError(int sc)
向客户端发送HTTP错误状态响应码sendError(int sc, String msg)
向客户端发送响应码和具体信息setHeader(String name, String value)
设置响应头的一项内容,会覆盖已存在项setStatus(int sc)
设置响应状态码addCookie(Cookie cookie)
向相应中加入CookiesendRedirect(String path)
重定向
- 本接口中定义了一些响应码静态常量
HTTPServletResponse.SC_BAD_REQUEST
400HTTPServletResponse.SC_FOUND
302HTTPServletResponse.SC_METHOD_NOT_ALLOWED
405HTTPServletResponse.SC_NON_AUTHORITATIVE_INFORMATION
203HTTPServletResponse.SC_FORBIDDEN
403HTTPServletResponse.SC_NOT_FOUND
404HTTPServletResponse.SC_OK
200
2.8 ServletConfig接口
- Servlet接口的init方法接收此参数。当Servlet容器初始化一个Servlet对象时,对为其创建一个ServletConfig对象,其中包含了Servlet初始化参数信息。init中会将Servlet对象与ServletConfig对象关联。
- 此外,ServletConfig对象还与当前web应用的ServletContext对象关联。
- 本接口有以下方法:
getInitParameter(String name)
根据参数名获取参数值getInitParameterNames
返回一个Enumeration对象,包含所有参数名getServletContext()
返回一个ServletContext对象getServletName()
即web.xml中,<servlet-name>
的值,若未指定则返回类名
- 每个初始化参数包括一对名和值,在web.xml中配置Servlet时,可以通过
<servlet>
内的<init_param>
来设置初始化参数,有<param_name>
和<param_value>
两个子元素。 - GenericServlet类实现了本接口,HttpServlet类继承GenericServlet类,故在其及其子类中可调用本接口方法。
2.9 ServletContext接口
- ServletContext是Servlet和容器通信的接口。容器在启动一个web应用时,会为其创建一个ServletContext对象,即web应用有一个专属对象。本web应用下的所有Servlet对象都共享这个“总管家”,通过它访问容器的资源:
- 在web应用范围内存取共享数据:
set/getAttribute(String name, Object)
将一个对象与name绑定,存入ServletContextgetAttributeNames()
返回Enumeration对象,包含所有存放于ServletContext的属性名removeAttribute(String name)
删除属性
- 访问当前web应用的资源:
getContextPath()
当前web应用的URL入口getInitParameter(String name)
根据参数名,返回web应用范围内的匹配的初始化参数值。在web.xml中,在<web-app>
根元素下定义的<context_param
表示应用范围内的初始化参数getInitParameterNames()
Enumeration对象,包含web应用范围内的所有初始化参数名getServletContextName()
web应用名字,即web.xml中<display-name
的值getRequestDispathcer(String path)
返回一个用于向其他web组件转发请求的RequestDispathcer对象
- 访问Servlet容器中的其他web应用
getContext(String uripath)
根据uri,返回当前容器中其他web应用的ServletContext对象
- 访问容器相关信息
getMajorVersion()
容器支持的Java Servlet API的主版本号getMinorVersion()
容器支持的Java Servlet API的次版本号getServerInfo()
容器的名字和版本
- 访问服务器端文件系统资源
getRealPath(String path)
根据参数指定的虚拟路径,返回文件真实路径getResource(String path)
返回一个映射到参数指定路径的URLgetResourceAsStream(String path)
返回一个用于读取指定文件的输入流,默认从当前Web应用根目录下取,故path也如此getMimeType(String file)
返回参数指定文件的MIME类型
- 输出日志
log(String msg)
向Servlet日志文件写日志log(String msg, Throwable)
写错误日志及异常堆栈信息- 默认日志输出到
<CATALINA_HOME>/logs/localhost.YYYY-MM-DD.log
文件
- ServletConfig接口中定义了
getServletContext()
方法,故其实现类、继承类均可直接调用本方法获得ServletContext对象。
3. Java Web应用的生命周期
Java Web生命周期由Servlet容器控制,共三阶段:
- 启动:加载Web应用数据,创建ServletContext对象,对filter和一些Servlet初始化
- 运行时:提供服务
- 终止:释放Web应用占用资源
3.1 启动阶段
- web.xml加载到内存
- 为Java Web应用创建一个ServletContext对象(此时有ServletContextListener监听,并调用方法)
- 对所有Filter初始化
- 对启动阶段需要初始化的Servlet初始化
3.2 运行时阶段
此时,所有Servlet待命,随时响应请求,提供服务。若请求的Servlet还不存在,容器就先加载并初始化Servlet,再调用service方法。
3.3 终止阶段
- 销毁应用中所有运行时状态的Servlet
- 销毁应用中所有运行时状态的Filter
- 销毁所有与应用相关的对象,如ServletContext,并释放占用资源(此时有ServletContextListener监听,并调用方法)
3.4 Tomcat管理平台管理应用生命周期
<CATALINA_HOME>/conf/tomcat-users.xml
中加入<user username="name" password="password" roles="manager-gui"/>
- 启动Tomcat,访问
http://localhost:8080/manager/html
,即可进入管理平台(也是一个web应用,位于webapps/manager
) - 在将web应用发布到Tomcat时,可为其配置
<Context>
元素,内有reloadable属性,若true,则Tomcat运行时监视应用的WEB-INF/classes
和WEB-INF/lib
目录下类文件的改动,若检测到更新,会自动重启应用。默认false,开发阶段可设为true。
4. Servlet生命周期
也由容器控制,分为初始化、运行时、销毁阶段,Servlet接口的init、service。destroy方法分别在三个状态调用。
此处说明一点,为Servlet命名时,可以有多个名字,则代表有多个Servlet对象(使用同一类)。
4.1 初始化阶段
- 初始化步骤:
- 容器加载Servlet类,把clss文件读入内存
- 创建ServletConfig对象,包含特定Servlet的初始化配置信息,如初始化参数;并且容器将ServletConfig对象与当前web应用的ServletContext对象关联
- 容器创建Servlet对象
- 容器调用Servlet对象的
init(ServletConfig)
方法,GenericServlet会将Servlet与ServletConfig关联
- 以上初始化结束后,Servlet对象可通过
getServletContext()
直接得到应用的ServletContext对象 - 以下情况,Servlet会进入初始化阶段:
- web应用运行时阶段,特定Servlet被首次请求访问
- web.xml中为某个Servlet设置了
<load-on-startup> num </load-on-startup>
元素,则容器启动应用时就初始化该Servlet,num表示在此阶段Servlet被初始化的顺序(0开始)(初始化:实例化并调用其init()方法,并非service方法) - 当web应用重启时,所有Servlet都会在特定时间被重新初始化
4.2 运行时阶段
此时Servlet随时响应请求。
- 容器接收到访问请求,为Servlet创建ServletRequest和ServletResponse对象,调用相应Servlet对象的service方法
- 当容器将结果发送给客户端,就会销毁ServletRequest和ServletResponse对象
4.3 销毁阶段
当应用终止,容器先调用应用中所有Servlet对象的destroy方法,然后再销毁这些对象。在destroy方法中释放占用资源。
此外容器还会销毁与Servlet对象关联的ServletConfig对象,ServletRequest和ServletResponse不会销毁。
5. ServletContext与Web应用范围
- ServletContext与Web应用有同样的生命周期
- 范围:时间段,与可共享数据的Web组件集合
- Web应用范围:Wen应用生命周期构成的时间段 与 Web应用生命周期内所有Web组件的集合
- 存放在Web应用范围内的共享数据:共享数据的生命周期位于Web生命周期的一段内 且 共享数据可被Web应用中的所有组件共享
- 由于ServletContext与Web应用生命周期相同,故用其存放Web应用范围的共享数据
- 由于Web终止时,销毁ServletContext对象,故此时存中的共享数据/属性一并销毁
5.1 ServletContextListener
-
Servlet API中有此接口,能够监听ServletContext对象的生命周期,即Web应用的生命周期
-
当容器启动或终止Web应用时,会触发ServletContextEvent事件,由ServletContextListener处理。接口中定义了处理事件的两个方法:
contextInitialized(ServletContextEvent sce)
当容器启动应用时调用此方法,之后再对Filter初始化,初始化必要ServletcontextDestroyed(ServletContextEvent sce)
当容器终止应用时调用,先销毁Servlet和Filter,再调用此法(此时甚至不能调用ServletContext中的共享数据)
-
自定义的监听器也需web.xml中注册
1
2
3<Listener>
<listener-class>MyServletContextListener</listener-class>
</Listener>也可用标注配置
@WebListener
6. Servlet.service()的异常
public void service(ServletRequest req, res) throws ServletException, java.io.IOException
ServletException表示常规操作时异常,IOException表示进行IO时异常。
- ServletException有子类UnavailableException,表示无法访问当前Servlet的异常。若Servlet由于一些系统级别的原因(内存不足、无法访问第三方服务器如数据库服务器等)而不能响应请求,就可抛出此异常。
- UnavailableException的两个构造方法:
UnavailableException(String msg)
创建一个表示Servlet永远不能被访问的异常,此后除非重启Web应用,否则再也无法通过浏览器访问此ServletUnavailableException(String msg, int seconds)
创建一个表示Servlet暂时不能被访问的异常,seconds表示暂时不能访问的时间,若为0或负数表示无法估计暂且不能被访问的时间
- service方法抛出的异常由容器捕获,并向客户端发送错误信息。
7. 防止页面被客户端缓存
-
浏览器为了能够快速响应请求,会把来自服务端的页面存放至缓存。
-
但缓存技术适用于静态网页,及不包含敏感数据的网页
-
可通过一下方式禁止缓存:
1
2
3resp.addHeader("Pragma", "no-cache"); // HTTP/1.0
resp.setHeader("Cache-Control", "no-cache"); // HTTP/1.1
resp.setHeader("Expires", "0"); // 两版本协议均支持,表示网页过期时间,0即立即过期,需要从服务器获取最新数据
8. Annotation标注配置Servlet
从Servlet3开始,为简化开发,可不必在web.xml配置,直接在类中Annotation标注配置。
8.1 Servlet的标注配置
-
各属性用法:
属性 类型 描述 name String 指定Servlet名字, <servlet-name>
,未指定则默认类的全限定名urlPatterns String[] 指定一组URL匹配模式, <url-pattern>
loadOnStartup int 指定Servlet启动时加载顺序, <load-on-startup>
initParams WebInitParam[] 指定一组初始化参数, <init-param>
asyncSupported boolean 声明Servlet是否支持异步处理模式, <async-supported>
desciption String 指定Servlet描述信息, <description>
displayName String 指定Servlet显示名,通常配合工具使用, <display-name>
-
eg:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<servlet>
<servlet-name>Font</servlet-name>
<servlet-class>mypack.FontServlet</servlet-class>
<init-param>
<param-name>color</param-name>
<param-value>blue</param-value>
</init-param>
<init-param>
<param-name>size</param-name>
<param-value>15</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>Font</servlet-name>
<url-pattren>/font</url-pattren>
</servlet-mapping>1
2
3
4
5
6
7"Font", (name=
urlPatterns={"\font"},
initParams={
"color", value="blue"), (name=
"size", value="15") (name=
})
public class FontServlet extends HttpServlet {...} -
仅指定一个urlPatterns时,可直接
@WebServlet("/hello")
-
配置方式二选一:若配置固定,可标注形式,否则web.xml更集中管理方便
8.2 ServletContextListener的标注配置
1 | <Listener> |
1 |
|
9. 中文字符编码
请求和响应的编码设定,要在获取输入输出流之前
9.1 HTTP请求
接收客户端参数并解析时,部分浏览器默认使用ISO-8859-1编码,不支持中文,会乱码.
- 单独对参数更改编码:
username = new String(username.getBytes(ISO8859-1), "GBK");
,或UTF-8 - POST:接收数据前
req.setCharacterEncoding("UTF-8");
,或GBK - ServletContext也有
set/getRequestCharacterEncoding
方法,对整个Web应用修改,从Servlet 4开始 - 以上方法都是对请求正文的设置编码,而GET的参数位于请求头。Tomcat8起,GET方式不会乱码
- 在Tomcat的server.xml中对HTTP连接器更改配置,自动把URI中的请求参数转换编码,一劳永逸
<Connection ... URIEncoding="UTF-8" />
- 不同浏览器传递的请求参数采用字符编码不同,常用的四种GBK、GB2312、ISO-8859-1、UTF-8,可通过
str.equals(new String(str.getBytes(encodingType), encodingType))
判断str是否是encodingType类型编码
9.2 HTTP响应
resp.setCharacterEncoding("GBK");
resp.setContentType("text/html;charset=GBK");
10. 下载文件
-
IDEA中,项目根目录下不可新增文件夹,要在“web”下新建“store”目录存放上传下载文件
-
前台超链接方法:前台a标签跳转时,若资源无法被浏览器识别则下载,可识别资源也可以通过设置download属性实现下载。该属性为空使用原文件名,指定属性即为下载的文件名。
<a href="download/avatar.jpg" download="dragon.jpg">头像图片</a>
-
后台代码方法:
-
需要通过
response.setContentType
方法设置Content-type头字段的值为浏览器无法使用某种方式或激活某个程序来处理的MIME类型,例如application/octet-stream
、application/x-msdownload
、application/force-download
等。 -
需要通过
response.setHeader
方法设置Content-Disposition
头的值为attachment;filename=文件名
-
读取下载文件,调用
response.getOutputStream
方法向客户端写入附件内容。
1 | public class DownloadServlet extends HttpServlet { |
11. 上传文件
11.1 前台
前台:表单、post、指定的enctype数据类型multipart/form-data
,表示复杂的包括多个子部分的复合表单。
1 | <form method="post" enctype="multipart/form-data" action="UpLoad"> |
此请求发至后台,请求会被以下两方法看作多个子部分:HTTP请求头不被考虑进内,主要是正文被分为多个子部分:
- 普通表单域:
- 此子部分的请求头
Content-Disposition:
form-data;
name="uname"
前端<input>
标签指定的name
- 此子部分正文:
<input>
输入内容
- 此子部分的请求头
- 文件域:
- 子部分请求头
Content-Disposition:
form-data;
name="myfile"
前端<input>
标签指定的namefilename="C:\...\file1.rar"
此处完整路径名
Content-Type:application/octet-stream
此文件MIME类型根据文件不同自动形成
- 子部分正文:即上传文件的内容
- 子部分请求头
11.2 后台
容器会将HTTP请求包装为HttpServletRequest对象,当请求正文为multipart/form-data
类型时,Servlet从HttpServletRequest中解析复合表单子部分仍然十分繁琐,为了简化,可使用Apache开源类库或Servlet API提供的Part接口(Servlet 3.0出现)。
11.2.1 Apache
-
Apache开源类库:提供了两个与上传文件相关的包:
两个jar文件放于
app/WEB-INF/lib
下,Servlet利用fileupload包的类和接口完成上传,而此包依赖于I/O包。 -
fileupload包中主要类和接口:
- FileItem接口
- FileItemFactory接口,依赖于FileItem接口,是创建FileItem对象的工厂
- DiskFileItem类,实现了FileItem接口,表示基于硬盘的FileItem,能够把客户端上传的文件数据保存到硬盘
- DiskFileItemFactory类,实现了FileItemFactory接口,依赖于DiskFileItem类,是创建DiskFileItem对象的工厂
- ServletFileUpload类,与FileItemFactory接口关联,是文件上传处理器
-
fileupload软件包把
multipart/form-data
类型HTTP请求正文中,复合表单包含的每个子部分看作是一个FileItem对象,此类对象有两种类型:formFiled普通表单域如文本域及提交按钮,非formFiled即上传文件类型 -
包实现中,为提高效率,会使用缓存和临时目录存放临时数据,而ServletFileUpload作为文件上传处理器,与工厂接口关联,能够解析请求对象,返回一组FileItem对象的List集合
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
32
33
34
35
36
37
38
39
40// 创建工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
// 设置缓冲区,此处4k
factory.setSizeThreshold(4 * 1024);
// 设置临时目录
factory.setRepository(new File(tempFilePath));
// 创建文件上传处理器
ServletFileUpload upload = new ServletFileUpload(factory);
// 设置允许上传的文件最大尺寸,此处4MB
upload.setSizeMax(4 * 1024 * 1024);
// 解析HTTPServletRequest请求
List<FileItem> items = upload.parseRequest(request);
// 遍历集合,分别处理
for (FileItem item : items) {
if (item.isFormFiled())
processFormFile(item, out); // 处理普通表单域
else
processUploadedFile(item, out); // 处理上传文件
}
// processFormFile()
void processFormFile(FileItem item, PrintWriter out) {
String name = item.getFieldName();
String value = item.getString();
out.println(...);
}
// processUploadedFile()
void processUploadedFile(FileItem item, PrintWriter out) throws Exception {
String fileName = item.getName();
int idx = fileName.lastIndexOf("\\");
fileName = fileName.substring(idx+1, fileName.length());
long fileSize = item.getSize();
if (fileName.equals("") && fileSize == 0)
return;
File uploadFile = new File(filePath + "/" + fileName);
item.write(uploadFile);
out.println(fileName + " is saved. Size is " + fileSize);
} -
因要用到上传文件存储目录和临时目录,故可在web.xml或标注中,将两路径作为参数,Servlet.init()中直接从ServletConfig中获取即可
1
2
3
4
5
6
7
8
9
10<servlet>
<init-param>
<param-name>filePath</param-name>
<param-value>store</param-value>
</init-param>
<init-param>
<param-name>tempFilePath</param-name>
<param-value>temp</param-value>
</init-param>
</servlet>1
2
3
4
5
6
7
8
9
10
11
12public class UploadServlet extends HttpServlet {
private String filePath;
private String tempFilePath;
public void init(ServletConfig config) throws ServletException {
super.init(config);
filePath = config.getInitParameter("filePath");
filePath = getServletContext().getRealPath(filePath);
tempFilePath = config.getInitParameter("tempFilePath");
tempFilePath = getServletContext.getRealPath(tempFilePath);
}
}
11.2.2 Part
Servlet将multipart/form-data
的POST请求封装成Part,通过Part对上传的文件操作。
- 注解
@MultipartConfig
将一个Servlet标识为支持文件上传 - 会将复合表单的每个子部分看作一个Part对象。
HttpServletRequest.getParts()
方法返回Part对象集合,每个对象代表请求中复合表单的一个子部分 - Part对象方法:
getHeader(String name)
读取子部分请求头中特定选项值getContentType()
读取子部分请求正文的数据类型getSize()
读取子部分的请求正文长度getName()
读取子部分名字,与HTML表单中<input>
元素的name值对应write(String filename)
把子部分请求正文写到参数指定文件中
1 | "/UpLoad") ( |
12. 动态生成图像
-
所需类:
java.awt.image.BufferedImage
位于缓存中的图像java.awt.Graphics
画笔javax.imageio.ImageIO
把原始图像转换为特定格式,利用外界提供输出流来输出
-
ImageIO类以
write()
静态方法输出,会产生临时文件,需在Tomcat根目录下事先创建temp目录public static boolean write (RenderedImage im, String formatName, File output) throws IOException
-
绘图实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14resp.setContentType("image/jpeg");
var out = resp.getOutputStream();
// 创建一个缓存中的图像,长高11*16
var image = new BufferedImage(11, 16, BufferedImage.TYPE_INT_RGB);
// 画笔
var g = image.getGraphics();
g.setColor(Color.white);
var font = new Font("Courier", Font.BOLD, 12);
g.setFont(font);
g.drawString("..");
g.drawLine(...);
// 输出JPEG
ImageIO.write(iamge, "jpeg", out);
out.close(); -
前端
<img>
标签:<img src='image?param=12'/>
image表示自定义绘图ImageServlet,param=12表示传给该Servlet的参数(动态生成img,相当于此元素需要再次发送请求给后台)
-
是客户端访问服务器时,服务器存于客户端硬盘的信息,服务器可以根据Cookie跟踪客户状态,尤其便于区别客户端。但保存在客户端,安全性差。
-
Cookie建立过程:
- 客户端首次请求访问服务器时,发送一个不含Cookie的请求
- 服务器的响应中包含一个含有客户端信息的Cookie(Cookie位于协议头)
- 客户端解析响应,把Cookie保存到本地硬盘
- 之后的请求中,包含Cookie
- 浏览器收到含有Cookie的请求,就可获取客户端信息
-
原始Cookie数据的解析也由Servlet容器完成。
javax.servlet.http.Cookie
类可操作Cookie。每个Cookie对象包含一个Cookie名和值。 -
服务器端创建与发送:
1 | Cookie cookie01 = new Cookie("name", "admin"); |
- 服务器端获取:仅有
getCookies
一个方法,返回数组,需要遍历并getName() getValue()
,若无Cookie则返回null
1 | Cookie[] cookies = req.getCookies(); |
-
到期时间:默认关闭浏览器即失效。
setMaxAge(int)
设置到期时间,秒 -
-1(或负数):默认就是-1,表示不存储,只在内存存活,关闭浏览器就失效
-
正整数:存储到磁盘
-
0:删除cookie
-
注意点:
-
保存在当前浏览器,换电脑或浏览器均不能使用
-
中文:默认不能存储中文,需要
URLEncoder.encode()
编码存储,decode()
解码读取 -
发送重名cookie会覆盖
-
有数量上限
-
路径:决定能否读取到cookie
-
cookie.setPath("/")
即为服务器根路径,表示当前服务器下所有项目都可访问 -
cookie.setPath("/pro1")
当前项目下可访问,默认为此 -
cookie.setPath("/pro2")
仅项目pro2下可访问,即使是pro1项目创建的cookie -
cookie.setPath("/pro1/cook")
仅项目pro1下的cook目录下可访问 -
当设置了路径且访问路径在设置的路径下,cookie就会加载到req对象中
-
cookie.setDomain(".cat.tom")
表示可使其他服务器www.cat.tom
的所有应用访问本Cookie
14. 访问Web应用的工作目录
- 每个Web应用下都有一个工作目录,容器将应用相关临时文件存于此。Tomcat默认工作目录是
<CATALINA_HOME>/work/[enginename]/[hostname]/[contextpath]
- 显式指定:配置Web应用的
<Context>
元素时,指定workDir
属性 - 访问:除容器,Servlet也可访问工作目录。Servlet规范规定容器初始化应用时,要向创建的ServletContext设置
javax.servlet.context.tempdir
的属性,属性值是一个java.io.File
对象,代表当前应用的工作目录。故Servlet获取应用工作目录File workDir = (File)context.getAttribute("javax.servlet.context.tempdir")
15. 转发和包含
- 从Servlet2.1开始,Servlet内无法获取其他Servlet的引用,也就无法调用其他Servlet,为完成组件协作,Servlet规范提供了两种途径:请求转发(源组件处理一部分后将请求转发给目标组件)和包含(源组件把其他组件/目标组件生成的响应结果包含到自身的响应结果中)
- 二者特点:
- 源组件与目标组件,处理的是同一请求,共享一份ServletRequest和ServletResponse对象
- 目标组件可以是Servlet、JSP、HTML
- 都依赖
javax.servlet.RequestDispatcher
接口 - 地址栏的URL不变,整个过程仅一个请求
- RequestDispatcher请求分发器,有两个方法:
public void forward(ServletRequest request, ServletResponse response) throws ServletException, java.io.IOException
把请求转发给目标组件public void include(ServletRequest request, ServletResponse response) throws ServletException, java.io.IOException
包含目标组件响应结果
- 获取RequestDispatcher对象:
servletContext.getRequestDispatcher(String path)
目标组件绝对路径servletRequest.getRequestDispatcher(String path)
绝对/相对路径- url起始不加/
15.1 请求转发
- 流程:清空存放响应正文的缓冲区;若目标组件是Servlet或JSP,就调用其service方法,把该方法结果发送到客户端;若目标组件是HTML,就把文档发送到客户端
- 特点:
- 源组件不能提交响应结果,否则forward()时会报错,仅目标组件可响应,数据通信用
req.setAttribute()
- 源组件转发后,后面的代码仍会执行
- 源组件不能提交响应结果,否则forward()时会报错,仅目标组件可响应,数据通信用
15.2 包含
- 流程:目标组件Servlet或JSP,就将其service产生的响应正文添加到源组件相应结果中;若HTML就直接把文档内容添加到源组件响应结果
- 特点:目标组件中对响应状态码和响应头所做的修改都会被忽略
15.3 请求范围
- Web应用范围/ServletContext:整个Web应用的生命周期
- 请求范围/ServletRequest:服务器响应一次客户请求的过程,从接收到返回;容器接收到请求,创建req和resp对象传给相应Servlet,待容器返回给客户结果后,俩对象销毁
16. 重定向
- 流程:
- 客户端输入URl访问服务器某个组件
- 服务器端组件返回302结果和另一个组件的URL,表示让浏览器在请求访问该组件。该组件不一定在同一服务器上
- 浏览器接收到响应结果后立即自动请求访问另一组件
- 浏览器接收到来自另一组件的响应结果
- 方法:
HttpServletRequest.sendRedirect(String location)
- 特点:
- 源组件响应结果不会发送到客户端
- 源组件重定向前已提交响应结果会抛出异常
- 源组件与目标组件不共享ServletRequest对象,不能共享请求范围内的数据
- 参数location若以
/
开头,表示相对于当前服务器根路径的URL,要/webapp/...
,若以http://
开头表示完整URL - 目标组件不必是同一服务器的同一Web应用的组件,可以是网络上任一有效网页
17. 访问Servlet容器内其他Web应用
- 一个容器进程内可以同时运行多个Web应用,各应用之间通过ServletContext通信
ServletContext.getContext(String uripath)
用于获取其他Web应用的ServletContext对象,参数指定其他应用的URL入口,如/app1
- 为了安全起见,多数容器允许用户设置是否允许应用得到其他应用的ServletContext对象。Tomcat中,
<Context>
元素的crossContext属性用于设置本项。默认false,调用上述方法返回null
18. 避免并发问题
- 并发访问同一Servlet,容器通常会为每个请求分配一个工作线程,线程们并发执行同一个Servlet.service()方法
- 但多线程引入线程并发问题,处理原则:
- 根据实际需求,决定Servlet中变量作用域,是实例变量还是局部变量
- 多线程同时访问共享数据而导致并发问题时,采用Java同步机制对线程同步
javax.servlet.SingleThreadModel
接口已废弃
- 合理决定变量作用域:
- 不同变量作用域:
- 局部变量:方法中定义,执行方法时线程堆栈中创建局部变量,线程执行完方法时局部变量销毁。多线程时,每个线程有独立的局部变量
- 实例变量:即类的成员,每个对象(类的实例)有独立的实例变量。实例销毁,实例变量才销毁。若多线程执行同一实例的方法,则访问到同一组实例变量
- 由于每个请求对应一个线程,故如需每个请求对应单独变量时,使用局部变量;而需每个请求都对应同一变量时,使用实例变量
- 不同变量作用域:
- Java同步机制
- 当需每个请求都对应同一变量,使用实例变量时,要使用同步机制对多线程同步
- 可使用锁对象或synchronized关键字,锁方法或代码块
- 废弃的SingleThreadModel接口:
- 如Servlet实现此接口,可采用以下方式之一运行Servlet:
- 任一时刻仅允许一个工作线程执行service方法,其余请求放入等待队列,依次响应,这实际上禁止了多客户端对同一Servlet的并发访问
- 容器为Servlet创建对象池,其中存放多个对象,每个请求的线程使用一个实例对象。此方法一来使得不同请求访问不同实例对象,二来请求过多时要创建过多实例对象,消耗内存资源
- 从Servlet API 2.4开始废弃,Tomcat采用上述方法2
- 如Servlet实现此接口,可采用以下方式之一运行Servlet:
19. 对请求的异步处理
19.1 Servlet中的异步
- Servlet API 3.0之前,容器对请求分配各自线程,这些线程来自主线程池的空闲线程,但请求过多,每个线程耗时过长时,会极大降低并发访问性能,故3.0开始引入异步处理机制,3.1加入非阻塞IO进一步增强异步处理的性能
- Servlet异步处理机制:Servlet从HttpServletRequest对象中获得一个AsyncContext对象,表示异步处理的上下文。它把响应当前请求的任务传给另一个新线程,新线程完成处理请求并向客户端返回结果的任务。而最初由容器分配给请求的线程就可及时释放回主线程池,从而及时处理更多请求。故Servlet异步处理机制,就是把响应请求的任务传递给另一个线程
- AsyncContext接口的方法:
setTimeout(long timeout)
设置异步线程处理请求任务的超时时间(毫秒),异步线程必须在参数时间内完成任务start(Runnable run)
启动一个异步线程,执行参数run指定的任务addListener(AsyncListener listener)
添加一个异步监听器complete()
通知容器任务完成,返回响应结果dispatch(String path)
把请求派发给参数指定的Web组件getRequest()
获取当前上下文中的req对象getResponse()
19.2 异步流程
-
开启asyncSupport属性,使Servlet支持异步处理
1
2
3"...", (name=
urlPatterns="/...",
asyncSupported=true)1
2
3
4
5<servlet>
<servlet-name>...</servlet-name>
<servlet-class>...</servlet-class>
<async-supported>true</async-supported>
</servlet> -
service方法中,获得AsyncContext对象
1
AsyncContext asyncContext = req.startAsync();
-
调用AsyncContext对象的setTimeout方法,设置处理请求的异步线程超时时间(非必须)
-
启动异步线程执行处理请求的任务,启动方法有三种:
-
aysncContext.start(Runnable)
,参数可自定义一个实现了Runnable接口的新类,为的是将AsyncContext对象传入该任务类的实例变量,能够在run方法中执行complete方法。AsyncContext对象的start方法的实现取决于具体容器,有的容器额外维护一个线程池,从中取出线程用于响应,有的容器从主线程池中取线程;后者并未改进性能 -
new Thread(Runnable).start()
此法亲自创建新线程,并发访问量大时导致创建大量新线程,降低运行性能 -
利用Java API的线程池ThreadPoolExecutor类创建一个线程池,所有异步线程都放在此池中。此法可灵活设定池。
1
2
3
4
5
6
7
8
9
10
11
private static ThreadPoolExecutor executor = new ThreadPoolExecutor(100, 200, 50000L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(100));
// 代表池中最少100线程,最多200,允许的线程空闲时间,空闲时间单位,池使用的缓冲队列
service() {
...
executor.execute(Runnable); // 从池中取出一个空闲线程执行任务
}
destory() {
executor.shutdownNow(); // 需显式关闭
}
- 调用AsyncContext对象的complete方法,通知容器已完成任务,或dispatch方法,把请求转发给其他Web组件
19.3 异步监听器AsyncListener
-
可用AsyncListener捕获并处理异步线程运行中的特定事件
onStartAsync(AsyncEvent event)
异步线程开始时调用onError(event)
异步线程出错时调用onTimeout(event)
异步线程执行超时时调用onComplete(event)
异步线程执行完毕时调用
-
如例
1
2
3
4
5
6AsyncContext asyncContext = req.getAsyncContext();
asyncContext.setTimeout(60 * 1000);
asyncContext.addListener(new AsyncListener() {
// 复写四个监听方法
});
asyncContext.start(Runnable);
19.4 非阻塞I/O
-
阻塞I/O:
- 线程通过输入流执行读操作时,若输入流的可读数据还未准备好,则当前线程进入阻塞状态,当读到数据或到达数据末尾,线程才从读方法退出。如上传大文件,网络传输耗时,后台读数据阻塞
- 线程通过输出流写操作时,若因为某些原因,暂时不能向目的地写数据,就阻塞,只有完成了写数据的操作才从写方法中退出。如发送大文件。
-
非阻塞I/O:
- 线程输入流读操作,数据未准备好,则线程立即退出读方法。只有输入流中有可读数据才进行读操作
- 线程输出流写操作,暂时不能写数据时立即退出写方法。只有可以写时才进行写操作
-
Java中传统读写都是阻塞I/O。当异步线程用阻塞I/O读写时,会使异步线程阻塞,还是削弱了并发性能。故从Servlet API 3.1开始,引入非阻塞I/O机制,建立在异步基础上
-
实现方法:引入两个监听器
- ReadListener接口:监听ServletInputStream输入流行为
onDataAvailable()
输入流中有可读数据时触发此方法onAllDataRead()
输入流中所有数据读完时触发此方法onError(Throwable)
输入操作出现错误时触发此方法
- WriteListener接口:监听ServletOutputStream输出流行为
onWritePossible()
可以向输出流写数据时触发此方法onError(Throwable)
输出操作出现错误时触发此方法
- ReadListener接口:监听ServletInputStream输入流行为
-
操作步骤:在支持异步处理的Servlet类中进行非阻塞I/O
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
32
33
34
35
36
37
38
39
40
41
42
43// 得到输入输出流
ServletInputStream input = req.getInputStream();
ServletOutputStream output = resp.getOutputStream();
// 注册监听器
input.setReadListener(new MyReadListener(input, asyncContext));
output.setWriteListener(new MyWriteListener(output, asyncContext));
// MyReadListener.class
public class MyReadListener implements ReadListener {
private ServletInputStream imput;
private AsyncContext asyncContext;
private StringBUilder builder = new StringBuilder();
public MyReadListener (ServletInputStream input, AsyncContext asyncContext) {
this.input = input;
this.asyncContext = asyncContext;
}
public void onDataAvailable() {
try {
int len = -1;
byte[] buff = new byte[1024];
// 读取客户端提交的数据
while (input.isReady() && (len = input.read(buff)) > 0) {
String data = new String(buff, 0, len);
builder.append(data);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
public void onAllDataRead() {
// 将数据设为req范围属性,并派发给负责输出的Servlet组件
asyncContext.getRequest.setAttribute("msg", builder.toString());
asyncContext.dispatch("/output");
}
public void OnError (Throwable t) {
t.printStackTrace();
}
} -
当主线程使用监听器时,service方法会先于读取结束,可见容器会提供一个异步线程来执行监听器中的非阻塞I/O操作
20. 服务器端推送
- 如最新消息推送、原请求包含其他链接时的主动发送等,都需主动推送。
- HTTP/2引入服务器推送概念,Servlet API 4开始提供服务器推送支持,有PushBuilder接口实现
path(String path)
指定待推送资源的URLpush()
把path指定资源推送到客户端
21. Session
- 介绍:
- HttpSession对象是同名接口的实例,无父接口。session本身就是http协议的内容。
- session标识一次会话/确认一个用户(一个用户的多次请求),并在会话期间共享数据(更长的生命周期)
- 对于服务器,每个连接来的客户端都是一个session。Servlet容器用此接口创建客户端和服务器的会话,保留指定时长,跨多个连接或请求。一个会话通常对应一个用户,可用session查看和操作会话属性。
- session保留在浏览器,切换浏览器无法取得之前的会话。
req.getSession()
获取session对象。- JSession标识符:
- 会话的唯一标志sessionId
- 名为JSESSIONID的cookie,请求中如果访问了session,则服务器会创建一个名为JSESSIONID,值为获取到的session(无论是获取到的还是新创建的)的sessionId的cookie对象,并添加到response对象中,响应给客户端,有效时间为关闭浏览器。所以Session的底层依赖Cookie来实现。
- 每当一次请求到达服务器,如果开启了会话(访问了session),服务器第一步会查看是否从客户端回传一个名为JSESSIONID的cookie,如果没有则认为这是一次新的会话,会创建一个新的session对象,并用唯一的sessionId为此次会话做一个标志。如果有JESSIONID 这个cookie回传,服务器则会根据JSESSIONID这个值去查看是否含有id为JSESSION值的session对象,如果没有则认为是一个新的会话,重新创建一个新的 session对象,并标志此次会话;如果找到了相应的session对象,则认为是之前标志过的一次会话,返回该session对象,数据达到共享。
- session域对象:
session.setAttribute(key, value)
req.getAttribute(key)
session.removeAttribute(key)
。数据存储在session域对象中。 - 生命周期:
- 默认时长:当客户端第一次请求 servlet 并且操作session时,session 对象生成,Tomcat 中 session 默认的存活时间为30min,即你不操作界面的时间,一旦有操作,session 会重新计时。
session.setMaxInactiveInterval(int)
按秒设置最大不活动时间或get。session.invalidate()
立刻失效- 关闭浏览器:jsession基于cookie,若cookie有效期默认,关闭浏览器后session也结束。
- 关闭服务器:session销毁。
22. 域对象总结
- request域对象:一次请求中有效。请求转发有效,重定向无效。
- session域对象:一次会话中有效。请求转发和重定向都有想,session销毁无效。
- ServletContext域对象:整个应用程序有效,服务器关闭失效。
23. web.xml
为Servlet映射URL既可以通过xml,也可以用标注@WebServlet(/serlvetName)
1 | <servlet> |
-
servlet为Servlet指定名字
servlet-name
为Servlet指定名字servlet-class
指定具体类的完整路径/类名init-param
定义初始化参数(参数名和值),可有多个本元素load-on-startup
指定Servlet容器启动web时加载Servlet的次序,小数字优先级高,0或未指定则表示访问时才加载
-
servlet-mapping
为其映射一个URLurl-pattern
指定相对URL路径,可一个Servlet映射多个URL
-
welcome-file-list
welcome-file>
用于为web设置默认主页
24. URL总结
- Servlet前的
@WebServlet("/servletName")
,此为Servlet的名字,并且在地址栏中显示。 - HTML中的
action="servletName"
,同1中URL,但不加/
,表示要调用的Servlet。 - 请求转发时,url要加
/
。
1 | /** |
1 | /** |