开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,点击查看活动详情
Servlet技术
一.Servlet
1.概述
-
Servlet是运行在web服务器端的应用程序,使用Java语言编写
-
Servlet对象主要封装了对HTTP请求的处理,它的运行需要Servlet容器(如Tomcat)的支持
-
Servlet由Servlet容器进行管理,Servlet容器将Servlet动态加载到服务器上,与HTTP协议相关的Servlet使用HTTP请求和HTTP响应与客户端进行交互
-
如下图,Servlet的请求首先会被HTTP服务器(如Apache)接收,HTTP服务器只负责静态HTML界面的解析,而Servlet的请求则转交给Servlet容器,Servlet容器会根据请求路径以及Servlet之间的映射关系,调用相应的Servlet,Servlet将处理的结果返回给Servlet容器,并通过HTTP服务器将响应传输给客户端
2.Servlet接口
-
Servlet就是实现了Servlet接口的类,它由Web服务器创建并调用,用于接收和响应用户的请求,在Servlet接口中定义了五个抽象方法
方法声明 说明 void init(ServletConfig config) Servlet实例化后,Servlet容器调用该方法完成Servlet的初始化工作 ServletConfig getServletConfig() 获取Servlet对象的配置信息 String getServletInfo() 返回包含Servlet信息的字符串,如作者,版权等 void service(ServletRequest req,ServletResponse resp) 负责响应用户的请求,当容器接收到客户端访问Servlet对象的请求时就会调用此方法。容器会构造一个表示客户端请求信息的ServletRequest对象和一个用于响应客户端的ServletResponse对象作为参数传递给service() 方法,在service()方法中可以通过ServletRequest对象得到客户端的相关信息和请求信息,在对请求进行处理后,调用ServletResponse对象的方法设置相应信息 void destroy() 负责释放Servlet对象占用的资源,当服务器关闭或者Servlet对象被移除时会销毁Servlet对象,此时会调用此方法 -
编写的Servlet类必须实现Servlet接口或者继承Servlet接口的实现类,如GenericServlet和HttpServlet,这两个类都是抽象类并且HttpServlet继承了GenericServlet,其中GenericServlet有一个抽象方法service() ,而HttpServlet没有抽象方法
-
总结三种方式编写Servlet类
-
实现Servlet接口,需要重写Servlet接口里边的所有方法
-
继承GenericServlet抽象类,必须重写service() 方法,其他方法也可以重写
-
继承HttpServlet抽象类,不需要重写service() 方法,因为HttpServlet在service() 方法中调用了另一个重载的service() 方法,这个重载的方法里边会根据参数选择相应的方法,如doGet() 方法或doPost() 方法,所以我们编写时可以重写doGet()方法和doPost()方法来实现我们想要的功能
-
通常情况下使用继承HttpServlet抽象类的方式编写Servlet
-
实例
public class ServletDemo02 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("方法执行了"); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req,resp); } }
-
-
Servlet线程安全问题:在实现Servlet的类中如果存在自定义的成员变量,并且在service()方法中操作这个变量时可能会出现线程安全问题,解决的办法是将成员变量改为定义在方法内部的局部变量,或者使用sychronized锁
-
HttpServlet常用方法
方法 说明 protected void doGet(HttpServletRequest req,HttpServletResponse resp) 用户处理GETl类型的HTTP请求 protected void doPost(HttpServletRequest req,HttpServletResponse resp) 用于处理POST类型的HTTP请求 protected void doPut(HttpServletRequest req,HttpServletResponse resp) 用于处理PUT类型的HTTP请求 -
关系视图
3.Servlet生命周期
-
生命周期图示
-
Servlet生命周期大致分为三个阶段,分别是初始化阶段、运行阶段、销毁阶段
-
初始化阶段:
当客户端向Servlet容器发出HTTP请求访问Servlet时,Servlet容器首先解析请求,检查内存中是否已经有了该Servlet对象,如果有,直接使用该Servlet对象;如果没有,就创建Servlet实例对象,然后通过调用init()方法完成Servlet初始化。需要注意,在Servlet整个生命周期内,它的init()方法只被调用一次
-
运行阶段:
Servlet容器会为客户端请求创建代表HTTP请求的ServletRequest对象和代表HTTP响应的ServletResponse对象,然后将它们作为参数传递给Servlet的service()方法。service()方法从ServletRequest对象中获取客户端请求并处理该请求,通过ServletResponse对象生成响应结果。在Servlet整个生命周期内,对于Servlet的每一次访问请求,Servlet容器都会调用一次Servlet的service()方法,并且创建新的ServletRequest和ServletResponse对象。即service()方法在Servlet整个生命周期中会被调用多次
-
销毁阶段
当服务器关闭或者Web应用被移除出容器时,Servlet随着Web应用的销毁而销毁。在销毁Servlet之前,Servlet容器会调用Servlet的destroy()方法,以便让Servlet对象释放它所占用的资源。在Servlet的整个生命周期中,destroy()方法也只被调用一次。注意,Servlet对象一旦创建就会驻留在内存中等待客户端的访问,直到服务器关闭或Web应用被移除出容器时Servlet对象才会销毁
-
4.Servlet配置
-
使用web.xml配置Servlet
-
在web.xml文件中,通过
<servlet></servlet>
标签进行注册,标签下包含若干个子元素,功能如下元素 类型 描述 String 指定Servlet名称,一般与Servlet类同名,要求唯一 String 指定Servlet位置,一般是全类名 String 指定Servlet的描述信息 String 指定Servlet的显示名 -
把Servlet映射到URL地址,使用
<servlet-mapping></servlet-mapping>
进行映射,在元素下使用<servlet-name></servlet-name>
指出Servlet的名称,需要和之前在标签下注册的相同;使用<url-pattern></url-pattern>
映射URL地址,地址前必须添加 / -
实例
<servlet> <!--指出Servlet名称--> <servlet-name>ServletDemo01</servlet-name> <!--指出Servlet全类名--> <servlet-class>com.demo.ServletDemo01</servlet-class> </servlet> <servlet-mapping> <!--和servlet标签下的名称一致--> <servlet-name>ServletDemo01</servlet-name> <!--映射地址,必须加斜杠--> <url-pattern>/ServletDemo01</url-pattern> </servlet-mapping>
-
-
使用注解配置Servlet
-
@WebServlet注解用于代替web.xml文件中的
<servlet>
、<servlet-mapping>
等标签,该注解会在项目部署时被容器处理,容器会根据具体的属性配置将相应的类部署为Servlet,@WebServlet注解常用属性如下 -
常用属性
属性声明 功能 String name 指定Servlet的name属性,等价于,如果没有显式指定则取值为类的全限定名 String[] value 等价于 String[] urlPatterns 和value功能一样,value和urlPatterns不能同时使用 int loadOnStartup 指定Servlet的加载时机,等价于 WebInitParam[] initParams 指定一组Servlet初始化参数,等价于,其中WebInitParam也是一种注解 -
实例
@WebServlet(name = "ServletDemo01",urlPatterns = "/ServletDemo01",initParams = {@WebInitParam(name = "encoding",value = "UTF-8"),@WebInitParam(name = "desc",value = "this is ServletDemo01")}) public class ServletDemo01 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("ServletDemo01运行了"); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req,resp); } }
-
5.Servlet创建时机
-
第一次访问时创建
优势:减少服务器内存的浪费
弊端:如果有一些需要在应用加载时就做的初始化操作,无法完成
-
服务器加载时创建
优势:提前创建好对象,提高首次执行时的效率,可以完成一些应用加载时要做的初始化工作
弊端:对服务器内存占用较多,影响服务器启动效率
-
修改Servlet创建时机:在web.xml文件的标签中添加
<load-on-startup></load-on-startup>
标签,标签中间填写的是整数,正整数表示在服务器加载时创建,值越小优先级越高,负整数或者不填表示第一次访问时创建
6.默认Servlet
- 默认Servlet是服务器提供的一个Servlet,它配置在Tomcat的conf目录中的web.xml文件中
- 它的映射路径是
<url-pattern>/</url-pattern>
,在接收请求时,首先会在项目中的web.xml文件查找映射配置,找到则执行,找不到时再去找默认的Servlet,由默认Servlet处理
二.ServletConfig
1.概述
-
ServletConfig是一个接口
-
当Tomcat初始化一个Servlet时,会将该Servlet的配置信息封装到一个ServletConfig对象中,通过调用init(ServletConfig config)方法将ServletConfig对象传递给Servlet,ServletConfig的生命周期与Servlet一样
-
ServletConfig封装的配置信息是键值对的形式
-
常用方法
方法 说明 String getInitParameter(String name) 根据参数名返回对应的参数值 Enumeration getInitParameterNames() 返回一个Enumeration对象,其中包含了所有的参数名 ServletContext getServletContext() 返回代表当前web应用的ServletContext对象 String getServletName() 返回Servlet的名称
2.ServletConfig配置
-
配置ServletConfig也是在web.xml文件中的
<servlet></servlet>
标签下进行的,需要使用<init-param></init-param>
标签将参数名和参数值包住,表示一个键值对,其中使用<param-name></para-name>
表示参数名,<param-value></param-value>
表示参数值 -
一个
<init-param></init-param>
只能表示一个键值对 -
范例
<servlet> <servlet-name>ServletConfigDemo</servlet-name> <servlet-class>com.liaoxiangqian.ServletConfigDemo</servlet-class> <!--配置encoding参数,使用一个init-param标签--> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <!--配置desc参数,使用一个init-param标签--> <init-param> <param-name>desc</param-name> <param-value>this is ServletConfigDemo</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>ServletConfigDemo</servlet-name> <url-pattern>/ServletConfigDemo</url-pattern> </servlet-mapping>
三.ServletContext
1.概述
- Servlet容器启动时会为每个Web应用创建一个唯一的ServletContext对象代表当前Web应用,ServletContext对象封装了当前Web应用的所有信息
- ServletContext可以配置和获得应用的全局初始化参数,可以实现多个Servlet之间的数据共享
- 生命周期:应用加载则创建,应用停止则销毁
2.ServletContext配置
-
在web.xml文件的
<web-app>
标签中,通过<context-param>
标签来配置,其中他有两个子标签 -
子标签
<param-name>
表示全局初始化参数的key -
子标签
<param-value>
表示全局初始化参数的value -
范例
<context-param> <param-name>globalEncoding</param-name> <param-value>UTF-8</param-value> </context-param> <context-param> <param-name>globaleDesc</param-name> <param-value>This is SevletContext</param-value> </context-param>
3.ServletContext的三个作用
-
获取Web应用程序的初始化参数
Enumeration getInitParameterName()
方法用于返回包含所有参数名的Enumeration对象String getInitParameter(String name)
方法用于根据参数名获取参数值
-
实现多个Servlet之间的数据共享
方法名 说明 Enumeration getAttributeNames() 返回包含所有域属性名的Enumeration对象 Object getAttribute(String name) 根据域属性名返回域属性值 void removeAttribute(String name) 根据域属性名从ServletContext中删除对应的域属性 void setAttribute(String name,Object obj) 设置ServletContext的域属性,其中name是域属性名,obj是域属性值 注意:域属性指的就是可以共享的数据,即可以被多个Servlet访问到
-
读取Web应用下的资源文件
-
实际开发中有时会需要读取Web应用中的一些资源文件,为此ServletContext接口定义了一些读取Web资源的方法,这些方法由依靠Servlet容器实现,可以根据资源文件相对Web应用的路径,返回关联资源文件的IO流、资源文件的绝对路径等
-
相关方法
方法名 说明 Set getResourcePaths(String path) 返回一个Set集合,集合包含path指定路径下的子目录名称和文件名称但是不包含子目录下的文件名称,path以斜杠 / 开头表示Web应用的根目录 String getRealPath(String path) 返回资源文件在服务器文件系统上的真实路径,参数path表示资源文件相对Web应用的路径,以斜杠 / 开头表示Web应用的根目录,如果不能将虚拟路径转为真实路径则返回null URL getResource(String path) 返回映射到某个资源文件的URL对象,参数必须以斜杠 / 开头表示Web应用的根目录 InputStream getResourceAsStream(String path) 返回映射到某个资源文件的InputStream输入流对象,参数path的传递规则与getResource()一致 总结:这四个方法中的path其实就是相对于Web应用的路径,以斜杠 / 开头
-
四.HttpServletRequest
1.概述
- HttpServletRequest接口继承自ServletRequest接口
- HttpServletRequest专门用于封装HTTP请求消息
- HttpServletRequest提供了一些用于访问请求消息的方法,如获取请求行、获取请求头等
2.相关方法
-
获取请求行信息
String getMethod()
,该方法用于获取HTTP请求消息中的请求方式,如GET或PostString getRequestURI()
,该方法用于获取请求行中资源名称部分,即URL中主机名端口号之后和参数部分之前的部分String getQueryString()
,该方法用于获取请求行中的参数部分,即资源路径 ? 号后边的全部内容String getProtocol()
,该方法用于获取请求行中的协议名和版本,如HTTP/1.0或HTTP/1.1String getContextPath()
,该方法用于获取URL中Web应用程序的路径,以 / 开头,结尾没有斜杠String getServletPath()
,该方法用于获取Servlet映射的路径或者Servlet的名称String getRemoteAddr()
,该方法用于获取发送请求的客户端的IP地址String getRemoteHost()
,该方法用于获取发送请求的客户端的完整主机名,如果解析不到则返回IP地址int getRemotePort()
,该方法用于获取发送请求的客户端的端口号String getLocalAddr()
,该方法用于获取Web服务器上接收当前请求的网络的IP地址String getLocalName()
,该方法用于获取Web服务器上接收当前请求的网络的IP地址对应的主机名int getLocalPort()
,该方法用于获取web服务器上接收当前网络连接的端口号String getServerName()
,该方法用于获取当前请求所指向的主机名,即HTTP请求消息中Host字段所对应的主机名部分String getServerPort()
,该方法用于获取当前请求所连接的服务器端口号,即HTTP请求消息中Host字段所对应的端口号部分String getSchema()
,该方法用于获取请求的协议名,如HTTP或HTTPSStringBuffer getRequestURL()
,该方法用于获取客户端发出请求时的完整URL,包括协议、服务器名、端口号、资源路径等,但不包括后面的查询参数部分,StringBuffer类型方便修改
范例:
@WebServlet("/HttpServletRequestDemo") public class HttpServletRequestDemo extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); System.out.println(method); String requestURI = req.getRequestURI(); System.out.println(requestURI); String queryString = req.getQueryString(); System.out.println(queryString); String protocol = req.getProtocol(); System.out.println(protocol); String contextPath = req.getContextPath(); System.out.println(contextPath); String servletPath = req.getServletPath(); System.out.println(servletPath); String remoteAddr = req.getRemoteAddr(); System.out.println(remoteAddr); String remoteHost = req.getRemoteHost(); System.out.println(remoteHost); int remotePort = req.getRemotePort(); System.out.println(remotePort); String localAddr = req.getLocalAddr(); System.out.println(localAddr); String localName = req.getLocalName(); System.out.println(localName); int localPort = req.getLocalPort(); System.out.println(localPort); String serverName = req.getServerName(); System.out.println(serverName); int serverPort = req.getServerPort(); System.out.println(serverPort); String scheme = req.getScheme(); System.out.println(scheme); StringBuffer requestURL = req.getRequestURL(); System.out.println(requestURL); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req,resp); } }
输出结果:
-
获取请求头信息相关方法
String getHeader(String name)
,该方法作用是获取请求头中指定名称字段的值,如果没有该字段则返回null,如果有多个该指定名称的字段,则返回第一个字段的值Enumeration getHeaders(String name)
,返回一个Enumeration对象,该对象包含所有请求头中指定名称字段的值Enumeration getHeaderName()
,返回一个包含所有请求头字段的Enumeration对象int getIntHeader(String name)
,该方法获取一个指定请求头字段的值,并将该字段的值转为int类型再返回,如果该字段不存在则返回-1,如果该字段的值不能转为int类型则抛出NumberFormatException异常long getDateHeaders(String name)
,该方法获取一个指定请求头字段的值,并将其转为一个代表日期/时间的长整数,这个长整数是1970年1月1日0时0分0秒算起到现在的毫秒值String getContentType()
,获取Content-Type字段的值int getContentLength()
,获取Content-Length字段的值String getCharacterEncoding()
,获取请求消息的实体部分的字符集编码,通常从Content-Type字段中截取
范例:
@WebServlet("/HttpServletRequestDemo") public class HttpServletRequestDemo extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Enumeration<String> headerNames = req.getHeaderNames(); while(headerNames.hasMoreElements()){ String s = headerNames.nextElement(); System.out.println(s+":"+req.getHeader(s)); } } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req,resp); } }
输出结果:
-
获取请求参数相关方法
String getParameter(String name)
,该方法获取指定名称参数的参数值,如果没有该参数则返回null,如果该参数没有设置值则返回空串,如果该参数有多个值则返回第一个String getParameterValues(String name)
,该方法用于获取同一个参数名称的所有值Enumeration getParameterNames()
,该方法用于返回包含所有参数名的Enumeration对象Map getParameterMap()
,将参数名和参数值装入一个Map对象中返回
范例:
@WebServlet("/HttpServletRequestDemo") public class HttpServletRequestDemo extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Enumeration<String> parameterNames = req.getParameterNames(); while (parameterNames.hasMoreElements()) { String s = parameterNames.nextElement(); String[] parameterValues = req.getParameterValues(s); System.out.println(s+":"+Arrays.toString(parameterValues)); } } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req,resp); } }
输出结果:
3.HttpServletRequest共享数据
-
HttpServletRequest不仅可以获取一些列数据,还能通过属性共享、传递一些数据,比如运用在请求转发和请求包含中
-
相关方法
void setAttribute(String name,Object obj)
,用于定义一个属性,其中name是属性的名称,obj是属性值,如果原先已经存在相同名称的属性,则会将原来的属性删除再添加该属性,如果obj为null则会删除删除指定名称的属性Object getAttribute(String name)
,根据属性名获取属性值void removeAttribute(String name)
,删除指定名称的属性Enumeration getAttributeNames()
,返回一个包含所有属性名的Enumeration对象
-
需要注意同一个请求中的数据才能共享、传递
4.请求转发和请求包含
-
请求转发
-
Servlet之间可以相互跳转,如果某个Servlet的功能不能处理客户端的请求则可以使用跳转的方式将请求传递给另一个Servlet,由另一个Servlet完成并响应给客户端
-
相关方法
RequestDispatcher getRequestDispatcher(String path)
,该方法由HttpServletRequest提供,用于获取一个封装了某条路径所指定资源的RequestDispatcher对象,路径必须以斜杠 / 开头,即该路径就是需要转发到的某个Servlet的路径void forward(ServletRequest req,ServletResponse resp)
,该方法由RequestDispatcher提供,用于将请求转发给另一个Web资源,如某个Servlet
-
-
请求包含
-
某个Servlet不能完全处理一个请求时可以将另一个Servlet“包含”进来,实现功能合并然后响应给客户端,这时浏览器的地址栏不会发送改变,被包含的Servlet的响应头会丢失,因为是由第一个Servlet做出响应的
-
相关方法
RequestDispatcher getRequestDispatcher(String path)
,该方法根请求转发的相同,路径是需要包含的哪个Servlet的路径void include(ServletRequest req,ServletResponse resp)
,该方法由RequestDispatcher提供,用于实现包含另一个Servlet的功能
-
5.流对象获取数据
BufferedReader getReader()
,该方法获取一个字符输入缓冲流ServletInputStream getInputStream()
,该方法获取一个字节输入流- 注意这两个方法读取的数据来自请求体,所以使用GET方式获取不到数据,而POST方法可以
6.中文乱码问题
- 在网页的表单中如果需要提交的数据存在中文时可能会出现乱码的情况
- HttpServletRequest提供了
void setCharacterEncoding(String name)
方法用于设置Request对象的解码方式 - GET方式提交的请求不会出现乱码的问题,但是POST方式存在乱码问题
五.HttpServletResponse
1.概述
- HttpServletResponse接口继承自ServletResponse接口
- 用于封装HTTP响应消息
- HTTP响应消息分为响应行,响应头,响应体三部分,所以HttpServletResponse中定义了向客户端发送响应状态码、响应消息头、响应消息体等方法
2.常见状态码
状态码 | 说明 |
---|---|
200 | 成功 |
302 | 重定向 |
304 | 请求资源未改变,使用缓存 |
400 | 请求错误,常见于请求参数错误 |
404 | 请求资源未找到 |
405 | 请求方式不支持 |
500 | 服务器错误 |
3.发送状态码的方法
void setStatus(int status)
,设置响应消息的状态码,并生成响应状态行void sendError(int sc)
,发送表示错误信息的状态码void sendError(int sc,String message)
,设置表示错误的状态码外还向客户端发送一条错误信息,其中包含message的内容
4.发送响应消息头的方法
void addHeader(String name,String value)
,设置响应头指定名称字段的值,会覆盖掉原来该名称字段的值void setHeader(String name,String value)
,与addHeader()方法功能相同,但是不会覆盖原来的值而是添加一个值void setContentLength(int len)
,设置响应头的实体内容的大小,单位是字节void setContentType(String type)
,设置响应内容的类型,即Content-Type的值,并且响应内容为文本类型时还能指定字符集编码,如text/html;charset=UTF-8
5.发送响应消息体的方法
ServletOutputStream getOutputStream()
,用于获取字节输出流对象PrintWriter getWriter()
,用于获取输出流对象- 注意:在使用这些输出流时可能会出现乱码的情况,所以在使用输出流时可以使用
resp.setContentType("text/html;UTF-8");
来设置字符集编码,用来解决乱码问题
6.请求重定向
- 请求重定向是指服务器接收到客户端请求后,可能由于某些条件限制,不能访问请求指定的资源,让客户端去访问另一个指定的资源
- HttpServletResponse定义了一个sendRedirect()方法,用于生成304响应状态码和Location响应头,从而通知客户端重新访问Location响应头中指定的URL
- 方法:
void sendRedirect(String location)
- location可以使用相对路径重定向到同一个Web服务器的其他Servlet,也可以绝对路径重定向到其他Web服务器
- 注意:重定向时会重新生成另一个HttpServletRequest对象,所以不能实现Servlet的共享数据,如果需要共享数据那么可以使用请求转发
7.设置缓存时间
- 对于不经常变化的数据,可以设置缓存时间减少频繁访问服务器,提高效率
- 使用
void setDateHeader(String name,long time)
方法来设置响应头 - 范例:
resp.setDateHeader("Expires",System.currentTimeMills+1*60*60*1000)
,设置缓存时间为1个小时
8.设置定时刷新
- 定时刷新是指设置经过某个时间后自动跳转到某个页面
- 使用
void setHeader(String name,String value)
方法 - 范例:
resp.setHeader("Refresh","3;URL=/虚拟目录/demo.html");
8.文件下载
-
实现在浏览器中下载文件的功能,步骤如下:
- 创建字节输入流,关联读取的文件
- 设置响应消息头支持的类型
- 设置响应消息头以下载方式打开资源
- 通过响应对象获得字节输出流对象
- 循环读写
- 释放资源
-
范例
@WebServlet("/ServletDownloadDemo") public class ServletDownloadDemo extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //1. 创建字节输入流,关联读取的文件 String realPath = getServletContext().getRealPath("/img/ServletLive.png"); BufferedInputStream bis = new BufferedInputStream(new FileInputStream(realPath)); //2. 设置响应消息头支持的类型 //Content-Type是消息头名称,表示支持的类型 //application/octet-stream是消息头参数,表示字节流 resp.setHeader("Content-Type","application/octet-stream"); //3. 设置响应消息头以下载方式打开资源 //Content-Disposition是消息头名称,表示处理形式 //attachment;filename=ServletLive.png是消息头参数,表示附件形式处理,filename表示文件名称 resp.setHeader("Content-Disposition","attachment;filename=ServletLive.png"); //4. 通过响应对象获得字节输出流对象 ServletOutputStream outputStream = resp.getOutputStream(); //5. 循环读写 byte[] bytes = new byte[1024]; int len; while((len = bis.read(bytes)) != -1){ outputStream.write(bytes,0,len); } //6. 释放资源 bis.close(); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } }