09Servlet的高级特性

1. Filter

1.1 什么是Filter

  • Filte(过滤器):位于客户端和处理程序之间,能够对请求和响应进行检查和修改,通常将请求拦截后进行一些通用的操作

在这里插入图片描述

  • 当客户端对服务器资源发出请求时,服务器会根据过滤规则进行检查如果请求满足过滤规则,则对请求进行拦截,对请求头和请求数据进行检查或修改,并依次通过过滤器链,最后把过滤后的请求交给处理程序

  • 不同的Web资源中的过滤操作可以放在同一个Filter中完成,减少重复代码,提高程序的性能

1.2 Fliter相关API

  • Filter接口

    /*
    	Filter的初始化方法,创建Filter实例后调用
    	filterConfig:读取Filter的初始化参数
    */
    void init(FilterConfig filterConfig)
    /*
    	完成实际的过滤操作,当请求满足过滤规则时,Servlet容器将调用doFilter()完成实际的过滤操作
    	ServletRequest request:请求对象
    	ServletResponse response:响应对象
    	FilterChain chain:代表当前Filter链的对象
    */
    void doFilter(request, response, chain)
    /*
    	释放被Filter对象打开的资源,再Web服务器释放Filter对象之前被调用
    */
    void destroy()
    
  • FilterConfig接口

    //返回Filter名称
    String getFilterName()
    //返回FilterConfig对象中封装的ServletContext对象
    ServletContext getServletContext()
    //返回指定参数名的初始化参数值
    String getInitParameter(String name)
    //返回Filter所有初始化参数名称的枚举
    Enumeration getInitParameterNames()
    
    
  • FilterChain接口

    /*
    	调用Filter链中的下一个过滤器
    	如果这个过滤器是链上最后一个,则将请求提交给处理程序或将响应发送给客户端
    */
    void doFilter(request, response)
    

1.3 Filter生命周期

  • 创建阶段:服务器启动时就会创建Filter对象,并调用init()方法,完成对象的初始化。在一次完整的请求中,对象只会被创建一次
  • 执行阶段:服务端请求目标资源时,服务器会筛选出符合隐射条件的Filter,并按照类名的先后顺序依次执行doFilter()。在一次完整请求中,doFillter()可执行多次
  • 销毁阶段:服务器关闭时,服务器调用destroy()方法销毁对象

1.4 实现Filter

@WebFilter(filterName = "MyFilter", urlPatterns = "/*")
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 {
        System.out.println("执行Filter");
        //用于拦截用户的请求,如果和当前过滤器的拦截路径匹配,该方法会被调用
        PrintWriter out = servletResponse.getWriter();
        out.write("Hello MyFilter");
    }

    @Override
    public void destroy() {
        //过滤器对象在销毁时自动调用,释放资源
    }
}
//指定过滤器的名称,默认是过滤器类的名称
String filterName
/*
	指定一组过滤器的URL匹配模式
		拦截具体的资源:/index.jsp,只有访问index.jsp时才会被拦截
		目录拦截:/user/*,访问/user下的所有资源,都会被拦截
		后缀名拦截:*.jsp,访问jsp文件时,都会被拦截
		拦截所有:/*,访问所有资源,都会被拦截
*/
String[] urlPatterns
//等价于urlPatterns属性,二者不能同时使用
String[] value
//指定过滤器将应用于哪些servlet,取值是@WebServlet中的name属性的值
String[] servletNames
/*
	指定过滤器的转发模式,具体取值包括:
		REQUEST:如果用户通过RequestDispatcher对象的include()或forward()访问目标资源,那么过滤器不会被调用。除此之外,该过滤器会被调用
		INCLUDE:如果用户通过RequestDispatcher对象的include()访问目标资源,那么过滤器被调用。除此之外,该过滤器不会被调用
		FORWORD:如果用户通过RequestDispatcher对象的forward()访问目标资源,那么过滤器被调用。除此之外,该过滤器不会被调用
		ERROR:如果通过声明式异常处理机制调用目标资源,那么过滤器被调用。除此之外,过滤器不会被调用
*/
DispatcherType dispatcherTypes
//指定过滤器的一组初始化参数
WebInitParam[] initParams

1.5 Filter链

  • 如果有多个Filter都对同一个URL的请求进行拦截,那么这些Filter就组成一个Filter链

  • Filter链使用FilterChain对象表示,FilterChain对象提供了一个doFilter(),该方法的作用是让Filter链上的当前过滤器放行,使请求进入下一个Filter

  • Filter链的拦截过程

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

访问MyServlet类,MyFilter01和MyFilter02的执行结果

在这里插入图片描述

  • 注意:当使用注解配置多个Filter时,用户无法控制它们的执行顺序,Filter的执行顺序是按照Filter的类名排序控制的

2. Listener

2.1 Listener概述

  • 监听过程的几个重要组成部分

    • 事件:用户的一个操作,例如点击按钮,调用方法等
    • 事件源:产生事件的对象
    • 事件监听器:负责监听发生在事件源上的事件
    • 事件处理器:监听器的成员方法,当事件发生时会触发对应的处理器(成员方法)
  • 当用户执行一个操作触发事件源上的事件时,该事件会被事件监听器听到,当监听器听到事件发生时,相应的事件处理器就会对发生的事件进行处理

  • 事件监听器工作过程:

    • 将监听器绑定到事件源,也就是注册监听器
    • 监听器监听到事件发生时,会调用监听器的成员方法,将事件对象传递给事件处理器,即触发事件监听器
    • 事件处理器通过事件对象获得事件源,并对事件源进行处理

2.2 Listener的API

  • Listener接口

    //监听ServletContext对象的创建与销毁
    ServletContextListener
    //监听ServletContext对象中的属性变更
    ServletContextAttributeListener
    //监听HttpSession对象的创建和销毁过程
    HttpSessionListener
    //监听HttpSession对象中属性的变更
    HttpSessionAttributeListener
    //监听HttpSession中对象的活化和钝化的过程
    HttpSessionActionListener
    //监听JavaBean对象绑定到HttpSession对象和从HttpSession对象解绑的事件
    HttpSessionBindingListener
    //监听ServletRequest对象的创建和销毁过程
    ServletRequestListener
    //监听ServletRequest对象中属性的变更
    ServletRequestAttributeListener
    
    /*ServletContextListener接口中有以下两个方法*/
    //ServletContext对象被创建了会自动执行的方法
    void contextInitialized(ServletContextEvent sce);
    //ServletContext对象被销毁了会自动执行的方法
    void contextDestroyed(ServletContextEvent sce);
    
  • Web容器会为每次请求都创建一个新的ServletRequest对象,而对于同一个浏览器在会话期间只会创建一个HttpSession对象

3. Servlet3.0新特性

3.1 注解

//修饰Servlet类,用于部署Servlet类
@WebServlet
//修饰Filter类,用于部署Filter类
@WebFilter
//修饰Listener类,用于部署Listener类
@WebListener
//与@WebServlet或@WebFilter连用,为@WebServlet或@WebFilter注解配置参数
@WebInitParam
//修饰Servlet类,指定Servlet类负责处理multipart/form-data类型的请求(重要用于处理上传文件)
@MultipartConfig
//修饰Servlet类,是与JAAS(Java验证和授权API)有关的注解
@ServletSecurity

3.2 异步处理支持

  • Servlet3.0之前,Servlet的工作流程如下:
    • Servlet接收到请求后,对请求携带的数据进行一些预处理
    • 调用业务接口的某些方法,完成业务处理
    • 根据处理的结果提交响应,Servlet线程结束

注意:步骤2是最耗时的,主要体现在数据库操作和其他跨网络调用等方面,此过程中,Servlet线程一直处于阻塞状态。通常采用提前结束Servlet线程的方式,及时释放资源

  • Servlet3.0通过异步处理的工作流程
    • Servlet接收到请求之后,首先对请求携带的数据进行一些预处理
    • Servlet线程将请求转交给一个异步线程执行业务处理
    • 线程本身返回至Web容器,此时Servlet还没有生成响应数据
    • 异步线程处理完业务以后,可以直接生成响应数据,或者将请求继续转发给其他Servlet

注意:Servlet线程不再是一直处于阻塞状态等待业务逻辑处理完成,而是启动异步线程之后可以立即返回

  • 异步处理特性可以应用于Servlet和过滤器两个组件,由于异步处理的工作模式和普通工作模式在实现上有着本质的区别,因此默认情况下,Servlet和过滤器并没有开启异步处理特性,如果希望使用该特性,可以通过web.xml配置注解两种方式实现

    <servlet>
    	<servlet-name>MyServlet</servlet-name>
        <servlet-class>com.why.filter.MyServlet</servlet-class>
        <async-supported>true</async-supported>
    </servlet>
    
    @WebFilter(filterName="MyFilter", urlPatterns="/MyServlet", asyncSupported="true")
    

4. 文件的上传和下载

4.1 文件上传原理

  • 要实现Web开发中的文件上传功能,通常需要完成两步操作:

    • 在Web项目的页面中添加上传输入项
    • 在Servlet中读取上传文件的数据,并保存到目标路径中
  <!--指定表单数据的enctype属性以及提交方式-->
  <form enctype="multipart/form-data" method="post">
      <!--指定标记的类型和文件域的名称-->
      <input type="file" name="myfile"/>
  </form>
  
  <!--
  	注意:
  		必须要设置input输入项的name属性,否则浏览器将不会发送上传文件的数据
  		必须将表单页面的method设置为post,enctype设置为multipart/form-data类型
  -->
  • 当浏览器通过表单上传文件时,文件数据都附带在HTTP请求消息体中,并且采用MIME类型进行描述,在后台可以使用request对象提供的**getInputStream()**方法读取客户端提交过来的数据

4.2 认识Commons-FileUpload组件

  • Commons-FileUpload组件实现文件上传的工作流程

在这里插入图片描述

  • 使用Commons-FileUpload组件需要导入的jar包

    <dependency>
    	<groupId>commons-io</groupId>
    	<artifactId>commons-io</artifactId>
    	<version>2.6</version>
    </dependency>
    <dependency>
    	<groupId>commons-fileupload</groupId>
    	<artifactId>commons-fileupload</artifactId>
    	<version>1.3.3</version>
    </dependency>
    
  • FileItem接口:封装单个表单字段元素的数据,将每一个表单域(包括普通的文本表单域和文件域)封装在一个FileItem对象中

    //判断FileItem类对象封装的数据是一个普通文本表单字段(true)还是一个文件表单字段(false)
    boolean isFormField()
    //获取文件上传字段中的文件名;普通文本返回null,否则,返回文件的字段信息,例如:D:\sun.jpg
    String getName()
    //获取表单标签name属性的值
    String getFieldName()
    //将FileItem对象中保存的主体内容保存到某个指定的文件中
    void write(File file)
    //将FileItem对象中保存的数据流内容以一个字符串形式返回
    String getString()//默认的字符串编码集
    String getString(String encoding)//指定的字符串编码集
    //获取上传文件的类型,即"Content-Type"的值,如image/jpeg,如果是普通表单字段,返回null
    String getContentType()
    
    
  • DiskFileItemFactory类:将请求消息实体中的每一个文件封装成单独的FileItem对象如果上传文件比较小,直接保存在内存中,否则以临时文件的形式保存在磁盘的临时文件夹中。默认文件存储临界值是10240字节(10KB)

    //采用默认临界值和系统临时文件夹构造文件项工厂对象
    DiskFileItemFactory()
    //采用参数指定临界值和系统临时文件夹构造文件项工厂对象
    DiskFileItemFactory(int sizeThreshold, File repository)
    
    
  • SevletFileUpload类:通过调用parseRequest(HttpServletRequest)方法可以将HTML中每个表单提交的数据封装成一个FileItem对象,然后以List列表的形式返回

    //构造一个未初始化的SevletFileUpload实例对象
    SevletFileUpload()
    //根据参数指定的FileItemFactory对象创建一个SevletFileUpload实例对象
    SevletFileUpload(FileItemFactory fileItemFactory)
    
    

4.3 实现文件上传

try {
    //设置ContentType的值
    response.setContentType("text/html;charset=UTF-8");
    //创建DiskFileItemFactory工厂对象
    DiskFileItemFactory factory = new DiskFileItemFactory();
    //设置文件缓存目录,如果该目录不存在,则新创建一个
    File f = new File("D:\\why6\\01");
    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];
            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);
}

4.4 文件下载原理

  • 直接使用Servlet类和输入/输出流实现,需要指定文件的路径和在HTTP协议中设置两个响应消息头

    //设定接收程序处理数据的方式
    Content-Disposition:attachment;
    //设定实体内容的MIME类型
    filename= Content-Type:application/x-msdownload
    
    

4.5 实现文件下载

//设置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);
//通过文件流读取文件
InputStream in = getServletContext().getResourceAsStream(folder + filename);
//获取resopnse对象的输出流
OutputStream out = response.getOutputStream();
byte[] buffer = new byte[1024];
int len;
//循环取出流中的数据
while ((len = in.read(buffer)) != -1) {
	out.write(buffer, 0, len);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CL3

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值