Servlet高级---Filter、Listener、 Servlet 3.0新特性 注解、文件的上传和下载
Servlet高级Servlet规范有三个高级特性,分别是Filter、Listener和文件的上传下载。Filter用于修改request、response对象,Listener用于监听context、session、request事件。善用Servlet规范中的这三个高级特性能够轻松地解决一些特殊问题。01. Filter在Servlet高级特性中,Filter被称为过滤器,Filter基
Servlet高级
Servlet规范有三个高级特性,分别是Filter、Listener和文件的上传下载。Filter用于修改request、response对象,Listener用于监听context、session、request事件。善用Servlet规范中的这三个高级特性能够轻松地解决一些特殊问题。
01. Filter
在Servlet高级特性中,Filter被称为过滤器,Filter基本功能就是对Servlet容器调用Servlet的过程进行拦截,它位于客户端和处理程序之间,能够对请求和响应进行检查和修改。Filter就好比现实中的污水净化设备,专门用于过滤污水杂质。Filter在Web应用中的拦截过程如下图所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bbQN9W4h-1648631727897)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1648449671063.png)]
上图中展示了Filter在Web应用中的拦截过程,当客户端对服务器资源发出请求时,服务器会根据过滤规则进行检查,如果客户的请求满足过滤规则,则对客户请求进行拦截,对请求头和请求数据进行检查或修改,并依次通过过滤器链,最后把过滤之后的请求交给处理程序。请求信息在过滤器链中可以被修改,也可以根据客户端的请求条件不将请求发往处理程序。
Filter除了可以实现拦截功能,还可以提高程序的性能,在Web开发时,不同的Web资源中的过滤操作可以放在同一个Filter中完成,这样可以不用多次编写重复代码,从而提高了程序的性能。
Filter中包含了3个接口,分别是Filter接口、FilterConfig接口和FilterChain接口,它们都位于javax.servlet包中。
01. Filter接口
方法声明 | 功能描述 |
---|---|
init(FilterConfig filterConfig) | init()方法是Filter的初始化方法,创建Filter实例后将调用init()方法。该方法的参数filterConfig用于读取Filter的初始化参数。 |
doFilter(ServletRequest request,ServletResponse response,FilterChain chain) | doFilter()方法完成实际的过滤操作,当客户的请求满足过滤规则时,Servlet容器将调用过滤器的doFilter()方法完成实际的过滤操作。doFilter()方法有多个参数,其中,参数request和response为Web服务器或Filter链中的上一个Filter传递过来的请求和响应对象;参数chain代表当前Filter链的对象。 |
destroy() | 该方法用于释放被Filter对象打开的资源,例如关闭数据库和 IO流。destroy()方法在Web服务器释放Filter对象之前被调用。 |
02. FilterConfig接口
FilterConfig接口用于封装Filter的配置信息,在Filter初始化时,服务器将FilterConfig对象作为参数传递给Filter对象的初始化方法。
方法声明 | 功能描述 |
---|---|
String getFilterName() | 返回Filter的名称 |
ServletContext getServletContext() | 返回FilterConfig对象中封装的ServletContext对象 |
String getInitParameter(String name) | 返回名为name的初始化参数值 |
Enumeration getInitParameterNames() | 返回Filter所有初始化参数的枚举 |
03. FilterChain接口
FilterChain接口的doFilter()方法用于调用过滤器链中的下一个过滤器,如果这个过滤器是链上的最后一个过滤器,则将请求提交给处理程序或将响应发给客户端。
04. Filter的生命周期
Filter的生命周期指的是一个Filter对象从创建到执行再到销毁的过程。Filter接口中的三个方法就是管理Filter对象生命周期的方法。Filter的生命周期可分为创建、执行、销毁三个阶段。
Web服务器启动的时候会创建Filter实例对象,并调用init()方法,完成对象的初始化。需要注意的是,在一次完整的请求当中,Filter对象只会创建一次,init()方法也只会执行一次。
当客户端请求目标资源时,服务器会筛选出符合映射条件的Filter,并按照类名的的先后顺序依次执行doFilter() 方法。例如MyFilter01优先MyFilter02执行。在一次完整的请求当中,doFilter()方法可以执行多次。
服务器关闭时,Web服务器调用destroy()方法销毁Filter对象。
首先创建一个Servlet
package com.miao.filter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* \* Created with IntelliJ IDEA.
* \* User: maomao
* \* Date: 2022/3/28
* \* Time: 15:21
* \* Description:
* \
*/
@WebServlet(name = "MyServlet", value = "/MyServlet")
public class MyServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
resp.getWriter().write("Hello World");
}
}
实现第一个Filter
package com.miao.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
import java.io.PrintWriter;
/**
* \* Created with IntelliJ IDEA.
* \* User: maomao
* \* Date: 2022/3/28
* \* Time: 15:26
* \* Description:
* \
*/
@WebFilter(filterName = "MyFilter" , urlPatterns = "/MyServlet")
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 过滤器对象在初始化时调用,可以配置一些初始化参数
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 用于拦截用户的请求,如果和当前过滤器的拦截路径匹配,该方法会被调用
PrintWriter out=servletResponse.getWriter();
out.write("Hello MyFilter");
}
@Override
public void destroy() {
// 过滤器对象在销毁时自动调用,释放资源
}
}
@WebFilter注解的属性filterName用于设置Filter的名称,urlPattern属性用于匹配用户请求的URL,例如“/MyServlet”,表示过滤器MyFilter会拦截发送到/MyServlet资源的请求。这个URL还可以使用通配符“”表示,例如,“.do”匹配所有以“.do”结尾的Servlet路径。
05. @WebFilter注解的常用属性
属性名 | 类型 | 描述 |
---|---|---|
filterName | String | 指定过滤器的名称。默认是过滤器类的名称。 |
urlPatterns | String[] | 指定一组过滤器的URL匹配模式。 |
value | String[] | 该属性等价于urlPatterns属性。urlPatterns和value属性不能同时使用。 |
servletNames | String[] | 指定过滤器将应用于哪些Servlet。取值是 @WebServlet 中的 name 属性的取值。 |
dispatcherTypes | DispatcherType | 指定过滤器的转发模式。具体取值包括:ERROR、FORWARD、INCLUDE、REQUEST。 |
initParams | WebInitParam[] | 指定过滤器的一组初始化参数。 |
06. Filter映射
Filter拦截的资源需要在Filter实现类中使用注解@WebFilter进行配置,这些配置信息就是Filter映射。Filter的映射方式可分为两种:使用通配符“*”拦截用户所有请求和拦截不同访问方式的请求。
拦截不同访问方式的请求:
@WebFilter注解有一个特殊的属性dispatcherTypes,它可以指定过滤器的转发模式,dispatcherTypes属性有4个常用
值,REQUEST、INCLUDE、FORWARD和ERROR。
REQUEST
过滤器设置dispatcherTypes属性值为REQUEST时,如果用户通过RequestDispatcher对象的include()方法或forward()
方法访问目标资源,那么过滤器不会被调用。除此之外,该过滤器会被调用。
INCLUDE
过滤器设置dispatcherTypes属性值为INCLUDE时,如果用户通过RequestDispatcher对象的include()方法访问目标资
源,那么过滤器将被调用。除此之外,该过滤器不会被调用。
FORWARD
过滤器设置dispatcherTypes属性值为FORWARD时,如果通过RequestDispatcher对象的forward()方法访问目标资源,那
么过滤器将被调用。除此之外,该过滤器不会被调用。
ERROR
过滤器设置dispatcherTypes属性值为ERROR时,如果通过声明式异常处理机制调用目标资源,那么过滤器将被调用。除此
之外,过滤器不会被调用。
package com.miao.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
/**
* \* Created with IntelliJ IDEA.
* \* User: maomao
* \* Date: 2022/3/28
* \* Time: 15:38
* \* Description:
* \
*/
@WebFilter(filterName = "ForwardFilter", urlPatterns = "/index.jsp" , dispatcherTypes = DispatcherType.FORWARD)
public class ForwardFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
servletResponse.getWriter().write("Hello Filter");
}
@Override
public void destroy() {
}
}
package com.miao.filter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* \* Created with IntelliJ IDEA.
* \* User: maomao
* \* Date: 2022/3/28
* \* Time: 15:37
* \* Description:
* \
*/
@WebServlet(name = "ForwardServlet", value = "/ForwardServlet")
public class ForwardServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.getRequestDispatcher("/index.jsp").forward(req,resp);
}
}
<%--
Created by IntelliJ IDEA.
User: Administrator
Date: 2022/3/16
Time: 0:50
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
$END$
</body>
</html>
07. Filter链
在一个Web应用程序中可以注册多个Filter,每个Filter都可以针对某一个URL的请求进行拦截。如果多个Filter都对同一个URL的请求进行拦截,那么这些Filter就组成一个Filter链。Filter链使用FilterChain对象表示,FilterChain对象提供了一个doFilter()方法,该方法的作用是让Filter链上的当前过滤器放行,使请求进入下一个Filter。
当浏览器访问Web服务器中的资源时需要经过两个过滤器Filter1和Filter2,首先Filter1会对这个请求进行拦截,在Filter1过滤器中处理好请求后,通过调用Filter1的doFilter()方法将请求传递给Filter2,Filter2将用户请求处理后同样调用doFilter()方法,最终将请求发送给目标资源。当Web服务器对这个请求做出响应时,响应结果也会被过滤器拦截,拦截顺序与之前相反,最终响应结果被发送给客户端。
package com.miao.filter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* \* Created with IntelliJ IDEA.
* \* User: maomao
* \* Date: 2022/3/28
* \* Time: 15:21
* \* Description:
* \
*/
@WebServlet(name = "MyServlet", value = "/MyServlet")
public class MyServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
resp.getWriter().println("Hello World");
}
}
package com.miao.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
import java.io.PrintWriter;
/**
* \* Created with IntelliJ IDEA.
* \* User: maomao
* \* Date: 2022/3/28
* \* Time: 15:26
* \* Description:
* \
*/
@WebFilter(filterName = "MyFilter" , urlPatterns = "/MyServlet")
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 过滤器对象在初始化时调用,可以配置一些初始化参数
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 用于拦截用户的请求,如果和当前过滤器的拦截路径匹配,该方法会被调用
PrintWriter out=servletResponse.getWriter();
out.println("Hello MyFilter");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
// 过滤器对象在销毁时自动调用,释放资源
}
}
package com.miao.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
/**
* \* Created with IntelliJ IDEA.
* \* User: maomao
* \* Date: 2022/3/28
* \* Time: 15:59
* \* Description:
* \
*/
@WebFilter(filterName = "MyFilter01", value = "/MyServlet")
public class MyFilter01 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
servletResponse.getWriter().println("MyFilter01");
filterChain.doFilter(servletRequest, servletResponse);
servletResponse.getWriter().println("MyFilter01");
}
@Override
public void destroy() {
}
}
需要注意的是,Servlet3.0新增了@WebFilter注解,当使用注解配置多个Filter时,用户无法控制它们的执行顺序,Filter的执行顺序是按照Filter的类名控制的,按自然排序的规则。
02. Listener
在Web程序开发中,经常需要对某些事件进行监听,以便及时作出处理,如监听鼠标单击事件、监听键盘按下事件等。为此,Servlet提供了监听器(Listener),专门用于监听Servlet事件。
Listener在监听过程中会涉及几个重要组成部分,具体如下。
(1)事件:用户的一个操作,如单击一个按钮、调用一个方法、创建一个对象等。
(2)事件源:产生事件的对象。
(3)事件监听器:负责监听发生在事件源上的事件。
(4)事件处理器:监听器的成员方法,当事件发生的时候会触发对应的处理器(成员方法)。
注意:当用户执行一个操作触发事件源上的事件时,该事件会被事件监听器监听到,当监听器监听到事件发生时,相应的事件处理器就会对发生的事件进行处理。
事件监听器的工作过程可分为以下几个步骤。
(1)将监听器绑定到事件源,也就是注册监听器。
(2)监听器监听到事件发生时,会调用监听器的成员方法,将事件对象传递给事件处理器,即触发事件处理器。
(3)事件处理器通过事件对象获得事件源,并对事件源进行处理。
01. Listener事件监听器
Web应用中的Listener就是一个实现了特定接口的Java程序,专门用于监听Web应用程序中ServletContext、HttpSession和ServletRequest等域对象的创建和销毁过程,以及这些域对象属性的修改,并且感知绑定到HttpSession域中某个对象的状态变化。
类型 | 描述 |
---|---|
ServletContextListener | 用于监听ServletContext对象的创建与销毁过程 |
HttpSessionListener | 用于监听HttpSession对象的创建和销毁过程 |
ServletRequestListener | 用于监听ServletRequest对象的创建和销毁过程 |
ServletContextAttributeListener | 用于监听ServletContext对象中的属性变更 |
HttpSessionAttributeListener | 用于监听HttpSession对象中的属性变更 |
ServletRequestAttributeListener | 用于监听ServletRequest对象中的属性变更 |
HttpSessionBindingListener | 用于监听JavaBean对象绑定到HttpSession对象和从HttpSession对象解绑的事件 |
HttpSessionActivationListener | 用于监听HttpSession中对象活化和钝化的过程 |
Listener中的8种Servlet事件监听器可以分为三类,具体如下。
(1)用于监听域对象创建和销毁的监听器:ServletContextListener接口、HttpSessionListener接口和
ServletRequestListener接口。
(2)用于监听域对象属性增加和删除的监听器:ServletContextAttributeListener接口、
HttpSessionAttributeListener接口和ServletRequestAttributeListener接口。
(3)用于监听绑定到HttpSession域中某个对象状态的事件监听器:HttpSessionBindingListener接口和
HttpSessionActivationListener接口。
在Servlet规范中,这三类事件监听器都定义了相应的接口,在编写监听器程序时只需实现对应的接口就可以。Web服务器
会根据监听器所实现的接口,把它注册到被监听的对象上,当被监听的对象触发了监听器的事件处理器时,Web服务器将会
调用监听器相关的方法对事件进行处理。
任务:监听域对象的生命周期
要想对Servlet域对象的生命周期进行监听,首先需要实现域对象的ServletContextListener、HttpSessionListener和ServletRequestListener接口,这些接口中的方法和执行过程非常类似。可以为每一个监听器编写一个单独的类,也可以用一个类实现这三个接口,从而让这个类具有三个事件监听器的功能。
本案例要求编写一个Listener监听ServletContext、HttpSession和ServletRequest域对象的生命周期。
package com.miao.listener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
@WebListener
public class MyListener implements
ServletContextListener, HttpSessionListener, ServletRequestListener {
public void contextInitialized(ServletContextEvent arg0) {
System.out.println("ServletContext对象被创建了");
}
public void contextDestroyed(ServletContextEvent arg0) {
System.out.println("ServletContext对象被销毁了");
}
@Override
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
}
@Override
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
}
@Override
public void sessionCreated(HttpSessionEvent httpSessionEvent) {
}
@Override
public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
}
}
由图中所示的控制台窗口可知,ServletContext对象被创建了,这是因为Web服务器在启动时会自动加载这个Web项目,并创建其对应的ServletContext对象。而服务器之所以会自动加载项目,是因为在MyListenser类上添加的@WebListener注解开启了Listener。Web服务器创建ServletContext对象后就调用MyListener类中的contextInitialized()方法,输出“ServletContext对象被创建了”这行信息。
在web.xml中添加
<session-config>
<session-timeout>2</session-timeout>
</session-config>
<session-timeout>标签指定的超时必须为一个整数,若这个整数为0或负整数,则session永远不会超时,如果这个数是
正整数,那么项目中的session将在指定分钟后超时。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eT3UgRiC-1648631727905)(Servlet高级.assets/1648624284711.png)]
<%--
Created by IntelliJ IDEA.
User: Administrator
Date: 2022/3/30
Time: 15:06
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
这是一个测试监听器的页面
</body>
</html>
package com.miao.listener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
@WebListener
public class MyListener implements
ServletContextListener, HttpSessionListener, ServletRequestListener {
public void contextInitialized(ServletContextEvent arg0) {
System.out.println("ServletContext对象被创建了");
}
public void contextDestroyed(ServletContextEvent arg0) {
System.out.println("ServletContext对象被销毁了");
}
@Override
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
System.out.println("ServletRequestt对象被销毁了");
}
@Override
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
System.out.println("ServletRequest对象被创建了");
}
@Override
public void sessionCreated(HttpSessionEvent httpSessionEvent) {
System.out.println("HttpSession对象被创建了");
}
@Override
public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
System.out.println("HttpSession对象被销毁了");
}
}
03. Servlet 3.0新特性
Servlet 3.0 作为Java EE 6规范体系中一员,随着Java EE 6规范一起发布。Servlet 3.0在前一版本(Servlet
2.5)的基础上提供了很多新特性以简化Web应用的开发和部署。在前面的章节中其实已经接触了Servlet 3.0的新特性,例
如,已经使用过的@WebServlet注解、@WebFilter注解,注解就是Servlet 3.0的新特性之一,通过使用注解的方式简化
了Servlet的配置。
几乎所有基于Java的web框架都建立在servlet之上,在Servlet 3.0之前,web框架需要在web.xml中配置。在
Servlet 3.0之后,可以用注解的方式配置web框架,简化了web框架的开发。
Servlet 3.0常见的注解主要有以下几个。
@WebServlet :修饰Servlet类,用于部署Servlet类。
@WebFilter:修饰Filter类,用于部署Filter类
@WebListener:修饰Listener类,用于部署Listener类。
@WebInitParam:与@WebServlet或@WebFilter注解连用,为@WebServlet或@WebFilter注解配置参数。
@MultipartConfig:修饰Servlet类,指定Servlet类负责处理multipart/form-data类型的请求(主要用于处理上
传文件)
@ServletSecurity:修饰Servlet类,与JAAS(Java验证和授权API)有关的注解。
Servlet 3.0的异步处理特性可以提高Web程序的接口处理速度。在Servlet 3.0之前,一个普通Servlet的工作流程大致
如下。
(1)Servlet接收到请求之后,对请求携带的数据进行一些预处理。
(2)调用业务接口的某些方法,完成业务处理。
(3)最后根据处理的结果提交响应,Servlet 线程结束。
在上述的Servlet工作流程的第(2)步是业务处理通常是最耗时的,这主要体现在数据库操作,以及其他的跨网络调
用等。在此过程中,Servlet线程一直处于阻塞状态,直到业务方法执行完毕。在处理业务的过程中,Servlet资源一直被
占用而得不到释放,对于并发较大的应用,可能造成性能瓶颈。对于这个问题,Servlet 3.0之前,通常是采用提前结束
Servlet线程的方式,及时释放资源。
Servlet 3.0通过异步处理,将之前的Servlet工作流程进行了调整,具体如下。
(1)Servlet接收到请求之后,首先对请求携带的数据进行一些预处理。
(2)Servlet线程将请求转交给一个异步线程执行业务处理。
(3)线程本身返回至Web容器,此时Servlet还没有生成响应数据。
(4)异步线程处理完业务以后,可以直接生成响应数据(异步线程拥有ServletRequest 和ServletResponse对象
的引用),或者将请求继续转发给其他Servlet。
注意:如此一来,Servlet线程不再是一直处于阻塞状态等待业务逻辑处理完成,而是启动异步线程之后可以立即返回。
异步处理特性可以应用于Servlet和过滤器两个组件,由于异步处理的工作模式和普通工作模式在实现上有着本质的区别,因此默认情况下,Servlet和过滤器并没有开启异步处理特性,如果希望使用该特性,可以通过web.xml配置与注解两种方式实现。
对于使用web.xml文件配置Servlet和过滤器的情况,Servlet 3.0在Servlet标签中增加了async-supported子标签,该标签的默认取值为false,要启用异步处理支持,将其设置为 true 即可以的MyServlet.java为例,如果开启异步处理,web.xml文件的配置方式如下所示。
<servlet>
<servlet-name>XXXXX</servlet-name>
<servlet-class>XXXX</servlet-class>
<async-supported>true</async-supported>
</servlet>
对于使用Servlet 3.0提供的@WebServlet和@WebFilter注解对 Servlet 或过滤器进行配置的情况,由于这两个注
解都提供了asyncSupported属性,因此可以通过设置asyncSupported属性值开启异步处理。asyncSupported默认值为
false,要启用异步处理支持,只需将该属性设置为true即可。以@WebFilter注解为例,其配置方式如下所示。
@WebFilter(filterName = "MyFilter",urlPatterns = "/MyServlet",asyncSupported = true)
04. 文件的上传和下载
01. 文件上传的原理
要实现Web开发中的文件上传功能,通常需完成两步操作:一是在Web项目的页面中添加上传输入项,二是在Servlet
中读取上传文件的数据,并保存到目标路径中。
由于大多数文件的上传都是通过表单的形式提交给服务器的,因此,要想在程序中实现文件上传功能,首先要创建一个
用于提交上传文件的表单页面。在表单页面中,需要使用<input type="file">标签在jsp页面中添加文件上传输入项。
<input type="file">标签的使用需要注意以下两点:
必须要设置input输入项的name属性,否则浏览器将不会发送上传文件的数据。
必须把将表单页面的method属性设置为post方式,enctype属性设置为“multipart/form-data”类型。
示例代码如下:
<%--指定表单数据的enctype属性以及提交方式--%>
<form enctype="multipart/form-data" method="post">
<%--指定标记的类型和文件域的名称--%>
选择上传文件:<input type="file" name="myfile"/><br />
</form>
当浏览器通过表单提交上传文件时,文件数据都附带在HTTP请求消息体中,并且采用MIME类型(多用途互联网邮件扩
展类型)进行描述,在后台可以使用request对象提供的getInputStream()方法读取客户端提交过来的数据。但由于用户
可能会同时上传多个文件,而在Servlet端直接读取上传数据,并分别解析出相应的文件数据是一项非常麻烦的工作。为了
方便处理用户上传的数据,Apache组织提供了一个开源组件Commons- FileUpload,该组件可以方便地
将“multipart/form-data”类型请求中的各种表单域解析出来,并实现一个或多个文件的上传,同时也可以限制上传文件
的大小等。Commons-FileUpload组件性能十分优异,并且使用非常简单。
需要注意的是,在使用Commons-FileUpload组件时,需要导入commons-fileupload.jar和commons-io.jar两个JAR包,这两个JAR包可以在Apache官网下载.
Commons- FileUpload组件的工作流程:
02. FileItem接口
FileItem接口主要用于封装单个表单字段元素的数据,一个表单字段元素对应一个FileItem对象。Commons-
FileUpload组件在处理文件上传的过程中,将每一个表单域(包括普通的文本表单域和文件域)封装在一个FileItem对象
中。
为了便于讲解,在此将FileItem接口的实现类称为FileItem类,FileItem类实现了序列化接口Serializable,因
此,FileItem类支持序列化操作。
方法声明 | 功能描述 |
---|---|
boolean isFormField() | isFormField()方法用于判断FileItem类对象封装的数据是一个普通文本表单字段,还是一个文件表单字段,如果是普通文本表单字段则返回true,否则返回false。 |
String getName() | getName()方法用于获取文件上传字段中的文件名。如果FileItem类对象对应的是普通文本表单字段,getName()方法将返回null,否则,只要浏览器将文件的字段信息传递给服务器,getName()方法就会返回一个字符串类型的结果,如C:\Sunset.jpg。 |
String getFieldName() | getFieldName()方法用于获取表单字段元素描述头的name属性值,也是表单标签name属性的值。例如“name=file1”中的“file1”。 |
void write(File file) | write()方法用于将FileItem对象中保存的主体内容保存到某个指定的文件中。如果FileItem对象中的主体内容是保存在某个临时文件中,那么该方法顺利完成后,临时文件有可能会被清除。另外,该方法也可将普通表单字段内容写入到一个文件中,但它主要用于将上传的文件内容保存到本地文件系统中。 |
String getString() | getString()方法用于将FileItem对象中保存的数据流内容以一个字符串形式返回。它有两个重载的定义形式:①public String getString()②public String getString(java.lang.String encoding)前者使用默认的字符集编码将主体内容转换成字符串,后者使用参数指定的字符集编码将主体内容转换成字符串。 |
String getContentType() | getContentType()方法用于获得上传文件的类型,即表单字段元素描述头属性“Content-Type”的值,如“image/jpeg”。如果FileItem类对象对应的是普通表单字段,该方法将返回null。 |
DiskFileItemFactory类用于将请求消息实体中的每一个文件封装成单独的FileItem对象。如果上传的文件比较小
,将直接保存在内存中,如果上传的文件比较大,则会以临时文件的形式,保存在磁盘的临时文件夹中。默认情况下,不管
文件保存在内存还是磁盘临时文件夹,文件存储的临界值是10240字节,即10KB。
方法声明 | 功能描述 |
---|---|
DiskFileItemFactory() | 采用默认临界值和系统临时文件夹构造文件项工厂对象 |
DiskFileItemFactory(int sizeThreshold,File repository) | 采用参数指定临界值和系统临时文件夹构造文件项工厂对象 |
上表中列举了DiskFileItemFactory类的两个构造方法,其中,第二个构造方法需要传递两个参数,第一个参数
sizeThreshold表示文件保存在内存还是磁盘临时文件夹中的临界值,第二个参数repository表示临时文件的存储路径。
ServletFileUpload类是Apache组件处理文件上传的核心高级类,通过调用parseRequest(HttpServletRequest)
方法可以将HTML中每个表单提交的数据封装成一个FileItem对象,然后以List列表的形式返回。
方法声明 | 功能描述 |
---|---|
ServletFileUpload() | 构造一个未初始化的ServletFileUpload实例对象 |
ServletFileUpload(FileItemFactory fileItemFactory) | 根据参数指定的FileItemFactory 对象创建一个ServletFileUpload对象 |
上表中列举了ServletFileUpload类的两个构造方法。在文件上传过程中,在使用第一个构造方法创建
ServletFileUpload对象时,需要在解析请求之前调用setFileItemFactory()方法设置fileItemFactory属性。
03. Commons-FileUpload组件的下载
下面介绍Commons-FileUpload组件的下载以及commons-fileupload.jar和commons-io.jar两个JAR包的导入。
FileUpload – Home (apache.org)
完成Commons-FileUpload组件的jar包下载后,还需要下载Commons IO组件的jar包,因为Commons-FileUpload组件需要Commons IO的支持。
Commons IO组件:
Commons IO – Commons IO Overview (apache.org)
04. 实现文件上传
要实现web项目中的文件上传功能,首先需要使用到Commons-FileUpload组件,另外需要将jsp页面form表单的
enctype属性值设置为“multipart/form-data”,再在Servlet中使用IO流实现文件的上传。
package com.miao.upload;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.List;
import java.util.UUID;
/**
* \* Created with IntelliJ IDEA.
* \* User: maomao
* \* Date: 2022/3/30
* \* Time: 15:53
* \* Description:
* \
*/
//上传文件的Servlet类
@WebServlet(name = "UploadServlet",urlPatterns = "/UploadServlet")
//该注解用于标注文件上传的Servlet
@MultipartConfig
public class UploadServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public void doGet(HttpServletRequest request,
HttpServletResponse response)throws ServletException, IOException {
try {
//设置ContentType字段值
response.setContentType("text/html;charset=utf-8");
// 创建DiskFileItemFactory工厂对象
DiskFileItemFactory factory = new DiskFileItemFactory();
//设置文件缓存目录,如果该目录不存在则新创建一个
File f = new File("E:\\TempFolder");
if (!f.exists()) {
f.mkdirs();
}
// 设置文件的缓存路径
factory.setRepository(f);
// 创建 ServletFileUpload对象
ServletFileUpload fileupload = new ServletFileUpload(factory);
//设置字符编码
fileupload.setHeaderEncoding("utf-8");
// 解析 request,得到上传文件的FileItem对象
List<FileItem> fileitems = fileupload.parseRequest(request);
//获取字符流
PrintWriter writer = response.getWriter();
// 遍历集合
for (FileItem fileitem : fileitems) {
// 判断是否为普通字段
if (fileitem.isFormField()) {
// 获得字段名和字段值
String name = fileitem.getFieldName();
if(name.equals("name")){
//如果文件不为空,将其保存在value中
if(!fileitem.getString().equals("")){
String value = fileitem.getString("utf-8");
writer.print("上传者:" + value + "<br />");
}
}
} else {
// 获取上传的文件名
String filename = fileitem.getName();
//处理上传文件
if(filename != null && !filename.equals("")){
writer.print("上传的文件名称是:" + filename + "<br />");
// 截取出文件名
filename = filename.substring(filename.lastIndexOf("\\") + 1);
// 文件名需要唯一
filename = UUID.randomUUID().toString() + "_" + filename;
// 在服务器创建同名文件
String webPath = "/upload/";
//将服务器中文件夹路径与文件名组合成完整的服务器端路径
String filepath = getServletContext()
.getRealPath(webPath + filename);
// 创建文件
File file = new File(filepath);
file.getParentFile().mkdirs();
file.createNewFile();
// 获得上传文件流
InputStream in = fileitem.getInputStream();
// 使用FileOutputStream打开服务器端的上传文件
FileOutputStream out = new FileOutputStream(file);
// 流的拷贝
byte[] buffer = new byte[1024];//每次读取1个字节
int len;
//开始读取上传文件的字节,并将其输出到服务端的上传文件输出流中
while ((len = in.read(buffer)) > 0)
out.write(buffer, 0, len);
// 关闭流
in.close();
out.close();
// 删除临时文件
fileitem.delete();
writer.print("上传文件成功!<br />");
}
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void doPost(HttpServletRequest request,
HttpServletResponse response)throws ServletException, IOException {
doGet(request, response);
}
}
05. 文件下载原理
实现文件下载功能比较简单,文件下载一般不需要使用第三方组件实现,而是直接使用Servlet类和输入/输出流实
现。与访问服务器文件不同的是,要实现文件的下载,不仅需要指定文件的路径,还需要在HTTP协议中设置两个响应消息
头,具体如下:
// 设定接收程序处理数据的方式
Content-Disposition: attachment;
// 设定实体内容的MIME类型
filename = Content-Type:application/x-msdownload
浏览器通常会直接处理响应的实体内容,需要在HTTP响应消息中设置两个响应消息头字段,用来指定接收程序处理数
据内容的方式为下载。当单击“下载”超链接时,系统将请求提交到对应的Servlet。在Servlet中,首先获取下载文件的地
址,并根据文件下载地址创建文件字节输入流,然后通过输入流读取要下载的文件内容,最后将读取的内容通过输出流写到
目标文件中。
package com.miao.download;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* \* Created with IntelliJ IDEA.
* \* User: maomao
* \* Date: 2022/3/30
* \* Time: 16:17
* \* Description:
* \
*/
@WebServlet(name = "DownloadServlet",urlPatterns = "/DownloadServlet")
public class DownloadServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public void doGet(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
//设置ContentType字段值
response.setContentType("text/html;charset=utf-8");
//获取所要下载的文件名称
String filename = request.getParameter("filename");
//下载文件所在目录
String folder = "/download/";
// 通知浏览器以下载的方式打开
response.addHeader("Content-Type", "application/octet-stream");
response.addHeader("Content-Disposition",
"attachment;filename="+filename);
folder=folder+filename;
// 通过文件流读取文件
InputStream in = this.getServletContext().getResourceAsStream(folder);
// 获取response对象的输出流
OutputStream out = response.getOutputStream();
byte[] buffer = new byte[1024];
int len;
//循环取出流中的数据
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
}
public void doPost(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
doGet(request, response);
}
}
更多推荐
所有评论(0)