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注解的常用属性
属性名类型描述
filterNameString指定过滤器的名称。默认是过滤器类的名称。
urlPatternsString[]指定一组过滤器的URL匹配模式。
valueString[]该属性等价于urlPatterns属性。urlPatterns和value属性不能同时使用。
servletNamesString[]指定过滤器将应用于哪些Servlet。取值是 @WebServlet 中的 name 属性的取值。
dispatcherTypesDispatcherType指定过滤器的转发模式。具体取值包括:ERROR、FORWARD、INCLUDE、REQUEST。
initParamsWebInitParam[]指定过滤器的一组初始化参数。
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);
    }
}

在这里插入图片描述

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐