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);
}