Filter&Listener&文件上传
内容介绍
web开发的三大组件(servlet,filter,listener)其中的两个
filter:过滤器 就在在请求到达资源之前对数据进行过滤,在响应信息到达客户端之前进行过滤
listener:监听器 主要用来监听三大域对象的创建,销毁及属性的变化操作
文件上传
一 Filter
1 概述
生活中的过滤器
地铁安检、净水器、空气净化器
javaweb中的过滤器
当用户访问服务器资源时,过滤器将请求拦截下来,完成一些特殊的功能.还可以对响应信息进行拦截过来(一般是用来做数据压缩的)
作用
一般用于完成通用的操作。如:登录验证、统一编码处理、敏感字符过滤
2 Filter(xml方式)快速入门
步骤分析
- 编写一个类,继承Filter接口
- 在web.xml中配置
- 告诉web服务器我是谁,我在哪
- 告诉web服务器我能处理什么路径
- 测试
代码实现
<!--告诉web服务器我是谁,我在哪-->
<filter>
<filter-name>A_HelloFilter</filter-name>
<filter-class>com.itheima.web.filter.A_HelloFilter</filter-class>
</filter>
<!--告诉web服务器我能处理什么路径-->
<filter-mapping>
<filter-name>A_HelloFilter</filter-name>
<url-pattern>/a/demo1</url-pattern>
</filter-mapping>
public class A_HelloFilter implements Filter {
public void destroy() {
}
//执行过滤操作的方法
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
System.out.println("过滤器收到了请求");
//放行
chain.doFilter(req, resp);
System.out.println("过滤器收到了响应");
}
public void init(FilterConfig config) throws ServletException {
}
}
编写一个servlet来测试过滤器
@WebServlet(value = "/a/demo1")
public class Demo1Servlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("demo1 servlet 收到了请求");
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TGCA8A5Z-1620011645236)(Filter&Listener&文件上传.assets/image-20210503103448449.png)]
3 生命周期(了解)
void init(FilterConfig config):初始化方法,在项目启动的时候就会执行,只执行一次
void destroy():销毁,在项目被移除的时候或者服务器关闭的时候就会执行,只执行一次
void doFilter(request,response,FilterChain chain):执行过滤方法,当请求匹配上的时候就会执行
filter是一个单实例的线程不安全的对象
服务器启动的时候,服务器会创建filter对象,且调用filter的init方法,实现初始化操作
当请求来的时候,若filter能匹配到此路径,服务器就会从线程池中获取一个线程,在线程中调用filter的doFilter方法,当dofilter方法执行了放行操作的时候才会到达下一个地方.
当服务器正常关闭的时候,服务器会调用filter的destroy方法,实现销毁操作.
和servlet不同的地方,初始化方法的执行时机
- servlet的init默认是第一次访问来的时候才执行,可以通过load-on-startup修改为服务器启动的时候执行
- filter直接就是服务器启动的时候就会执行init方法
public class B_LifeFilter implements Filter {
//销毁方法 在服务器关闭的时候或者项目被移除的时候 只执行一次
public void destroy() {
System.out.println(444);
}
//执行过滤的方法, 每次请求来的时候,若能匹配上请求路径就会执行一次
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
System.out.println("filter 收到了请求");
chain.doFilter(req, resp);
System.out.println("filter 收到了响应");
}
//初始化方法 项目启动的时候就会执行 只执行一次
public void init(FilterConfig config) throws ServletException {
System.out.println(555);
}
}
<filter>
<filter-name>B_LifeFilter</filter-name>
<filter-class>com.itheima.web.filter.B_LifeFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>B_LifeFilter</filter-name>
<url-pattern>/b/1.jsp</url-pattern>
</filter-mapping>
4 Filter配置
拦截路径配置
- 精确(完全)路径匹配 例如:/demo1 /a/demo1 /1.html /a/b/demo23
- 模糊路径匹配 例如:/a/b/* /a/* /* 注意:(/*在servlet中最好不用使用)
- 后缀名匹配 例如: *.jsp *.do *.action
在filter中一般使用的模糊路径匹配和后缀名匹配
<filter>
<filter-name>C_Url1Filter</filter-name>
<filter-class>com.itheima.web.filter.C_Url1Filter</filter-class>
</filter>
<filter-mapping>
<filter-name>C_Url1Filter</filter-name>
<url-pattern>/c/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>C_Url2Filter</filter-name>
<filter-class>com.itheima.web.filter.C_Url2Filter</filter-class>
</filter>
<filter-mapping>
<filter-name>C_Url2Filter</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>
filter代码都是一句输出 就省略
测试访问的路径
<a href="${pageContext.request.contextPath}/c/1.jsp">c_filter的路径配置_模糊路径 /c/*</a><br>
<a href="${pageContext.request.contextPath}/user/findAll.do">c_filter的路径配置_后缀名匹配 *.do</a>
一个路径只能有一个servlet执行-- 一个路径能对应上一个servlet,若一个路径能匹配上多个servlet的时候,路径存在优先级问题. 完全路径>模糊路径>后缀名>缺省配置(/)
但是,在filter中不存在一个路径只能有一个filter执行,一个请求的路径若能匹配上多个filter的时候,多个filter都会执行.
过滤器链
FilterChain:只有一个放行方法 doFilter(req,resp).
当一个请求可以匹配到多个过滤器的时候,请求就相当于将所有匹配到的过滤器串起来了,这个就是过滤器链.当前的过滤器若执行了放行操作,就会将请求放行到链中的下一个过滤器.若当前的过滤器是最后一个过滤器话,就可以执行目标资源(servlet,jsp,…)
过滤器链中的过滤器的执行顺序为:
- xml中,根据filter-mapping标签在web.xml中顺序决定的
- 注解中,根据FiIter的类名的字典(自然)顺序决定
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TdBrRYt8-1620011645248)(Filter&Listener&文件上传.assets/image-20210503103511539.png)]
拦截方式配置
通过filter-mapping标签中dispatcher标签来配置过滤拦截那种方式过来的请求,
- 默认值(REQUEST):只拦截从浏览器直接发送过来的请求.一旦显式的配置了其他方式,默认的就失效了.
- 若想过滤拦截转发过来的请求,只需要通过dispatcher标签配置FORWARD
- 若想过滤拦截转发或从浏览器直接发送过来的请求的话,就需要配置两个dispatcher即可.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LpRuIhUu-1620011645250)(Filter&Listener&文件上传.assets/image-20210503102644306.png)]
测试servlet
@WebServlet(value = "/d/demo3")
public class Demo3Servlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("demo3 servlet执行了,紧接这转发请求到/a/demo1上");
request.getRequestDispatcher("/a/demo1").forward(request,response);
}
}
5 Filter注解方式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qJfq8BeO-1620011645253)(Filter&Listener&文件上传.assets/image-20210503103528869.png)]
/*
filterName:配置filter的名字 对我们来说用途不大
urlPatterns和value:字符串数组 配置filter的过滤拦截路径
dispatcherTypes:DispatcherType注解数组类型 配置拦截方式
*/
@WebFilter(filterName = "D_AnnoFilter", value = "/e/*",dispatcherTypes = {DispatcherType.FORWARD,DispatcherType.REQUEST})
public class D_AnnoFilter implements Filter {
public void destroy() {
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
System.out.println("注解过滤器执行了");
chain.doFilter(req, resp);
}
public void init(FilterConfig config) throws ServletException {
}
}
二 Filter案例
1 解决全站乱码
需求
浏览器发出的任何请求,通过过滤器统一处理中文乱码 。
tomcat8.5处理了get请求的中文乱码,但是post的没有处理,对于post请求来说,我们之前的操作,是在获取参数之前执行 request.setCharacterEncoding(“utf-8”);
步骤分析
- 编写过滤器,过滤器的过滤器路径为/*
- 在过滤器的过滤方法中
- 获取请求方式
- 若是post请求,调用request.setCharacterEncoding(“utf-8”);
- 无论如何别忘记放行操作
- 测试
代码实现
@WebFilter(filterName = "EncodingFilter", value = "/*")
public class EncodingFilter implements Filter {
public void destroy() {
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
//强转
HttpServletRequest request = (HttpServletRequest) req;
//- 获取请求方式
String method = request.getMethod();
//- 若是post请求,调用request.setCharacterEncoding("utf-8");
if ("post".equalsIgnoreCase(method)) {
request.setCharacterEncoding("utf-8");
}
//- 无论如何别忘记放行操作
chain.doFilter(req, resp);
}
public void init(FilterConfig config) throws ServletException {
}
}
测试表单和servlet自己编写一下
2 登录案例
需求
实现一个基本权限验证的案例,对于一些资源(例如:购物车,订单列表等),如果用户已经登录了,才访问目标资源;如果用户没有登录过,就不能访问目标资源
步骤分析:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WTkkWZRF-1620011645256)(Filter&Listener&文件上传.assets/image-20210422101454015.png)]
- 完成登录案例(快速完成)
- 登录成功后需要将用户数据放入session
- 登录成功跳转首页
- 编写一个Filter
- 判断用户是否登录
- 如果登录成功, 放行
- 如果没有登录,跳转登录页面
代码实现
index.jsp
<a href="${pageContext.request.contextPath}/login.jsp">登陆页面</a><br>
<a href="${pageContext.request.contextPath}/cart/findAll">购物车详情页面</a><br>
<a href="${pageContext.request.contextPath}/order/mylist">我的订单页面</a><br>
<a href="${pageContext.request.contextPath}/product/list">商品列表页面</a><br>
login.jsp代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/login" method="post">
姓名:<input type="text" name="username"><br/>
密码:<input type="text" name="password"><br/>
<input type="submit" value="提交"><br/>
${msg}
</form>
</body>
</html>
filter代码
package cn.itcast.login.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebFilter(value = {"/cart/*","/order/*"})
public class LoginFilter implements Filter {
public void destroy() {
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
//1.强转
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
//2,判断用户是否登陆(session中有无用户)
Object loginUser = request.getSession().getAttribute("loginUser");
//3.若没有的话 跳转登录页
if (null == loginUser) {
request.setAttribute("msg","请先登陆");
request.getRequestDispatcher("/login.jsp").forward(request,response);
return;
}
chain.doFilter(req, resp);
}
public void init(FilterConfig config) throws ServletException {
}
}
LoginServlet
package cn.itcast.login.servlet;
import cn.itcast.login.domain.User;
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;
@WebServlet(value = "/login")
public class LoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
String username = request.getParameter("username");
String password = request.getParameter("password");
if ("tom".equals(username) && "123".equals(password)) {
//登陆成功
User user = new User();
user.setUsername(username);
user.setPassword(password);
//将用户存入session中
request.getSession().setAttribute("loginUser",user);
response.getWriter().print("登陆成功");
}else {
request.setAttribute("msg","用户名或密码错误");
request.getRequestDispatcher("/login.jsp").forward(request,response);
}
}
}
其他Servlet
@WebServlet(value = "/cart/findAll")
public class CartServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.getWriter().print("cart info");
}
}
@WebServlet(value = "/order/mylist")
public class OrderServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.getWriter().print("my order list");
}
}
@WebServlet(value = "/product/list")
public class ProductServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.getWriter().print("product list~~");
}
}
三 Listener
1 概述
生活中的监听器
我们很多商场有摄像头,监视着客户的一举一动。如果客户有违法行为,商场可以采取相应的措施。
javaweb中的监听器
在我们的java程序中,有时也需要监视某些事情,一旦被监视的对象发生相应的变化,我们应该采取相应的操作。
监听web三大域对象:ServletRequest,HttpSession,ServletContext
可以监听三大域对象的创建、销毁和属性变化等
监听机制:
- 事件源:发生事件的对象
- 事件:事情
- 监听器:监听某个事件源发生指定事件之后,所做的操作.
场景
系统启动时初始化配置信息、统计在线人数
2 xml方式快速入门
监听器在web开发中使用的比较少,见的机会就更少了,今天我们使用ServletContextListenner来带领大家学习下监听器,因为这个监听器是监听器中使用率最高的一个,且监听器的使用方式都差不多。
我们使用这个监听器可以在项目启动和销毁的时候做一些事情,例如,在项目启动的时候加载配置文件。
步骤分析
- 编写一个类,实现ServletContextListener接口
- 重写两个方法,项目启动和项目终止方法
- 在web.xml中配置
- 告诉web服务器我在哪里
代码实现
public class MyServletContextListener implements ServletContextListener {
/*
servletContext初始化后要做的事情(项目启动成功了)
方法中的参数就是事件对象,可以获取事件源
*/
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println("servletContext对象 创建了,项目启动了");
}
/*
servletContext销毁后要做的事情(项目终止前)
*/
public void contextDestroyed(ServletContextEvent servletContextEvent) {
System.out.println("servletContext对象 销毁了,项目马上要关闭了");
}
}
<listener>
<listener-class>com.itheima.web.listener.MyServletContextListener</listener-class>
</listener>
3 注解方式
只需要在类上添加一个注解即可 @WebListener
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qp3uibac-1620011645258)(Filter&Listener&文件上传.assets/image-20210503103553315.png)]
四 Listener案例:统计在线人数
1 需求
有用户使用网站,在线人数就+1;用户不再使用网站,在线人数就-1 ;访问一个servlet可以知道目前有几个用户在访问网站
监听session的创建和销毁
2 步骤分析
- 先创建一个CountServlet,来展示在线人数
- 获取ServletContext中人数
- 将人数输出到页面
- 再创建一个OnlineListener,实现HttpSessionListener
- 在创建session的方法中,每创建一个session,就把在线人数+1
- 在销毁session的方法中,每销毁一个session,就把在线人数-1
3 代码实现
@WebServlet(value = "/count")
public class CountServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//- 获取ServletContext中人数
Integer count = (Integer) getServletContext().getAttribute("count");
//- 将人数输出到页面
response.setContentType("text/html;charset=utf-8");
response.getWriter().print("当前在线人数为:" + (count==null?0:count));
}
}
@WebListener
public class OnlineListener implements HttpSessionListener {
//创建session之后的操作
public void sessionCreated(HttpSessionEvent httpSessionEvent) {
//获取serveltcontext对象,获取人数
HttpSession session = httpSessionEvent.getSession();
ServletContext context = session.getServletContext();
Integer count = (Integer) context.getAttribute("count");
//将人数+1
//将最新的人数放入域中
context.setAttribute("count",count==null?1:++count);
}
// 销毁session之后的操作
public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
//获取serveltcontext对象,获取人数
HttpSession session = httpSessionEvent.getSession();
ServletContext context = session.getServletContext();
Integer count = (Integer) context.getAttribute("count");
//将人数-1
count--;
//将最新的人数放入域中
context.setAttribute("count",count);
}
}
五 案例:文件上传
1 需求
在一个页面上,我们可以输入用户名,还可以选择要上传的文件.点击提交按钮,就可以将用户输入的内容和选择的文件保存到服务器上.
2 技术分析
文件上传:
将客户的数据通过网络复制到服务器上
文件上传对浏览器要求:
- 表单的提交方式必须是post
- 表单的enctype属性值必须是multipart/form-data(多部分表单数据)
- 在表单中提交文件选择框,且需要提供name属性
注意:一旦表单使用multipart/form-data属性值,后台使用原生的方式获取参数的方法就都失效了
<form action="${pageContext.request.contextPath}/upload" method="post" enctype="multipart/form-data">
用户名:<input type="text" name="username"><br>
文件:<input type="file" name="ff"><br>
<input type="submit" value="提交">
</form>
常见的文件上传技术:
- 使用apache提供的工具:commons-fileupload (代码写起来比较复杂)-代码不需要大家写
- 使用servlet3.0开始提供的文件上传方式
- 使用springmvc框架(非常简单,底层封装了commons-fileupload)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w2Gi7sAX-1620011645259)(Filter&Listener&文件上传.assets/image-20210503103613916.png)]
若表单提交的时候 enctype属性设置为multipart/form-data ,request原生的获取参数的方法都失效了.
建议使用工具类commons-fileupload(apache提供)来完成操作
3 步骤分析
使用commons-fileupload完成上传
- 导入jar包(昨天资料),commons-fileupload和commons-io
- 在servlet编写代码
- 创建磁盘文件项工厂对象
- 创建核心上传对象,使用工厂对象作为参数
- 使用核心上传对象 解析 请求,获取文件项集合(list)
- 遍历文件项集合,获取每个文件项
- 判断是否是普通上传项,若是:获取name属性值,获取用户输入的内容
- 若是文件上传项,获取name属性,获取文件名字,获取文件输入流
- 创建输出流,俩流拷贝,保存文件,关闭流,清理临时文件.
4 代码实现-复制或照抄
public class UUIDUtils { public static String getId(){ return UUID.randomUUID().toString(); }}
@WebServlet(value = "/upload")public class UploadServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { //1. 创建磁盘文件项工厂对象 DiskFileItemFactory factory = new DiskFileItemFactory(); //2. 创建核心上传对象,使用工厂对象作为参数 ServletFileUpload upload = new ServletFileUpload(factory); //3. 使用核心上传对象 解析 请求,获取文件项集合(list) List<FileItem> list = upload.parseRequest(request); //4. 遍历文件项集合,获取每个文件项 if (list!=null && list.size()>0) { for (FileItem fi : list) { if (fi.isFormField()) { //普通上传项, // 获取name属性值, String name = fi.getFieldName(); // 获取用户输入的内容 String value = fi.getString("utf-8"); System.out.println("普通上传项:"+name+"=="+value); }else { //文件上传项 // 获取name属性值, String name = fi.getFieldName(); // 获取文件名字 String filename = fi.getName(); //为了避免出现同名文件覆盖的问题,在原始的文件名前拼接uuid值 filename = UUIDUtils.getId()+"_"+filename; // 获取文件 InputStream is = fi.getInputStream(); // 获取上传文件 存放的目录 File dirFile = new File("e:/upload"); //判断目录是否存在,若不再则创建 if (!dirFile.exists()) { dirFile.mkdirs(); } // 创建输出流 FileOutputStream os = new FileOutputStream(new File(dirFile, filename)); // 拷贝流 IOUtils.copy(is,os); // 关闭流 os.close(); is.close(); //删除临时文件 fi.delete(); } } } } catch (FileUploadException e) { e.printStackTrace(); } }}
六 总结
Filter
过滤器
作用:在请求达到目标资源之前对请求进行过滤拦截,或者也可以在响应达到浏览器之前,对响应进行过滤拦截
实现步骤:
- 编写一个类,实现javax.servlet.Filter接口
- 重写所有方法,重点关注doFilter方法(过滤拦截),别忘记放行
- 在web.xml中进行配置
了解下filter的生命周期
掌握filter的url的配置,一般不使用精确路径
- 精确路径
- 模糊路径
- 后缀名
过滤器链:
- 若一个路径能匹配到多个过滤器的时候,多个过滤器执行是有顺序
- 若是xml方式,是按照web.xml中filter-mapping的顺序决定的
- 若是注解方式,是按照类名的自然(字典)顺序决定的(自己测试)
拦截方式配置
-
默认只过滤从浏览器直接发送过来的请求
-
一旦显式的声明了其他方式的,默认值就失效了
-
若想过滤器多种方式的请求,在filter-mappbing标签中配置多个dispatcher标签
REQUEST FORWORD
注解配置
- 在类上添加一个注解 @WebFilter(value=“路径配置”)
Listener
监听器
监听三大域对象的创建,销毁和属性变化等
记住:ServletContextListener,监听项目的启动和关闭,可以在项目启动的时候加载项目的配置文件(spring框架中)
文件上传
对浏览器的要求:
- 表单提交方式为post
- 表单enctype属性为 multipart/form-data
- 在表单中提供文件选择框
例子中我们使用的apache的commons-fileupload
扩展:uuid 随机唯一的字符串,在一台电脑上生成的字符串好多年不会出现重复的数据.