Filter过滤器 和 Listener
目录
Filter过滤器
什么是过滤器
简而言之,就是一个门,要想过去必须通过这道门的考验。
过滤器会对浏览器所有的请求进行过滤,让满足条件的请求通行,起到一个拦截的作用
过滤器的作用
- 对一些敏感词汇进行过滤
- 统一设置编码,提高了代码的复用性
- 自动登录
过滤器的使用
- 实现 Filter类,(javax.servlet.* 包下面的)
public class MyLoginFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
//项目启动的时候就会调用该方法,与ServletContext类似
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//具体过滤的内容和操作,每次过滤都会调用这个方法
//过滤完之后,要放行,使用该方法,如果不使用,就会一直拦截
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
//当销毁时候调用,服务器关闭的时候销毁
}
}
- 注册过滤器
在web.xml中注册,与Servlet类似
<filter>
<filter-name>AutoLoginFilter</filter-name>
<filter-class>com.filter.AutoLoginFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>AutoLoginFilter</filter-name>
<url-pattern>/*</url-pattern>
<!--表示拦截所有的请求-->
</filter-mapping>
Filter的生命周期
- Filter的创建
当项目启动的时候就会创建,并且调用init(FilterConfig filterConfig) 方法
- Filter的销毁
当服务器关闭的时候销毁
Filter的执行顺序
当有多个过滤器时候,执行顺序与 web.xml文件中<Filter-mapping>标签的顺序有关
<filter>
<filter-name>AutoLoginFilter01</filter-name>
<filter-class>com.filter.AutoLoginFilter</filter-class>
</filter>
<filter>
<filter-name>AutoLoginFilter02</filter-name>
<filter-class>com.filter.AutoLoginFilter</filter-class>
</filter>
<filter>
<filter-name>AutoLoginFilter03</filter-name>
<filter-class>com.filter.AutoLoginFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>AutoLoginFilter01</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>AutoLoginFilter03</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>AutoLoginFilter02</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
顺序为 1---->3----->2
Filter细节
- Filter类中doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)方法
chain.doFilter(req,resp); //放行的方法
-
init方法的参数 FilterConfig , 可以用于获取filter在注册的名字 以及初始化参数。 其实这里的设计的初衷与ServletConfig是一样的。
-
<url-pattern>/*</url-pattern> 写法格式与servlet一样。
- 全路径匹配
/LoginServlet- 以 / 开头,以 * 号结尾
/LoginServlet/*- 以 * 号开始,以后缀名结尾
*.do *.jsp *.html
4.dispatch 标签的用法
<filter-mapping>
<filter-name>AutoLoginFilter02</filter-name>
<url-pattern>/*</url-pattern>
<dispatch> REQUEST </dispatch>
</filter-mapping>
REQUEST : 只要是请求过来,都拦截,默认就是REQUEST
FORWARD : 只要是转发都拦截。
ERROR : 页面出错发生跳转
INCLUDE : 包含页面的时候就拦截。
自动登录案例
案例分析
环境搭建
- 数据库环境搭建
建立一张用户表user,存放用户名,密码,等各种用户信息 - jar包的导入,使用数据库连接池,beanutils工具类,jstl标签库,dbutils简化crud操作
代码实现
- 登录页面 login.jsp 搭建
<html>
<head>
<title>登录页面</title>
</head>
<body>
<form action="LoginServlet" method="post">
<table>
<tr>
<td>用户名:</td>
<td><input type="text" name="username" ></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="password" name="password" ></td>
</tr>
<tr>
<td>日期:</td>
<td><input type="date" name="date" ></td>
</tr>
<tr>
<td colspan="2"><input type="checkbox" name="auto" >自动登录</td>
</tr>
<tr>
<td colspan="2"><input type="submit" name="登录" ></td>
</tr>
</table>
</form>
</body>
</html>
- LoginServlet.java 类,判断登录成功或失败,是否选择了自动登录,选择了进行向客户端添加cookie
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
request.setCharacterEncoding("utf-8");
//1、使用 beanutils 来获取参数,并封装在 user类中。封装在javaBean中
//注意,只能序列化字符串,如果是日期类型的话,提供了一个接口,先注册自己的接口
// Converter myDateConverter = new MyDateConverter();
ConvertUtils.register(new MyDateConverter(), Date.class);
Map map = request.getParameterMap();
User user = new User();
//将数据封装到User中去
BeanUtils.populate( user,map);
//2、查询数据库,判断是否用户名密码是否正确
UserDao userDao = new UserDaoImpl();
UserBean userBean = userDao.login(user); //如果数据库中有该用户,返回该用户的所有信息,封装成UserBean
if (userBean != null){
//判断要不要自动登录
String auto = request.getParameter("auto");
System.out.println(auto);
if(auto != null){ //如果是要自动登录,向客户端存cookie
//登陆成功,将用户名密码存储到cookie中
System.out.println(user.getUsername()+"#"+user.getPassword());
Cookie cookie = new Cookie("user", user.getUsername()+"#"+user.getPassword());
cookie.setMaxAge(60*60*24*7); //设置生存周期
response.addCookie(cookie);
}
//3.将用户信息UserBean保存在作用域中,用于前端页面的判断
HttpSession session = request.getSession();
System.out.println(userBean.toString());
session.setAttribute("user",userBean);
//4、跳转页面重定向
response.sendRedirect("index.jsp");
}else {
response.getWriter().write("用户名或密码错误");
}
} catch (Exception e) {
e.printStackTrace();
}
}
- 登录首页 index.jsp,根据seeion作用域中是否有UserBean进行判断
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>首页</title>
</head>
<body>
<c:if test="${not empty user }">
您好,欢迎您,${user.username}
</c:if>
<c:if test="${empty user }">
您好,请登录。
</c:if>
</body>
</html>
- 过滤器AutoLoginFilter 过滤器的使用
过滤器的核心不是说拦截不给,还是说放行显示。核心是在放行之前,帮用户完成自动登录的功能
- 过滤器实现的思路
- 先根据作用域中是否有UserBean,有,就不用取cookie了, 直接放行
- 如果session失效了,那么就取 cookie。
- 判断有无cookie,没有,直接放行
- 有,读取cookie,用户名和密码,进而实现登录的功能
然后再将UserBean保存在session中,以至于方便下一次没过期前还能使用
//过滤时候调用这个方法
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
try {
//实现自动登录的功能
//1、获取cookie
HttpServletRequest request = (HttpServletRequest) req;
Cookie[] cookies = request.getCookies();
//自定义一个工具类,cookie有好多,要得到想要的,参数是cookies数组,还有想要的cookie名字,得到我们想要的cookie
Cookie cookie = CookieUtil.getCookie(cookies, "user");
User user = new User();
HttpSession session = request.getSession();
UserBean userBean = (UserBean)session.getAttribute("user");
//判断作用域中是否还有userbean,浏览器不关闭的情况下直接访问首页,如果不判断会一直重新登录一遍,
//没有必要,因为还在一个会话中,当前会话还没失效,直接放行就可以
if(userBean != null){
//当前会话还有效,浏览器没有关闭直接放行,不用再登录一遍了
chain.doFilter(req,resp);
}else {
//当前会话失效,浏览器关闭了,
//进而再根据cookie判断是否已经登录过
if (cookie != null) { //说明不是第一次登录,执行自动登录的功能,
//判断还要不要登录
String value = cookie.getValue();
String[] split = value.split("#");
user.setUsername(split[0]);
user.setPassword(split[1]);
UserDao userDao = new UserDaoImpl();
userBean = userDao.login(user);
//将用户信息存到session中
session.setAttribute("user",userBean);
//放行
chain.doFilter(req,resp);
}else{
//第一次登录,也放行
chain.doFilter(req,resp);
}
}
} catch (SQLException e) {
e.printStackTrace();
chain.doFilter(req,resp);
}
}
BeanUtils的使用
BeanUtils作用就是讲在登录提交过来的信息封装到类中,简化了代码
BeanUtils的使用
BeanUtils.populate(bean, map);
注册自己的日期转换器
ConvertUtils.register(new MyDateConverter(), Date.class);
//转化数据
Map map = request.getParameterMap();
UserBean bean = new UserBean();转化map中的数据,放置到bean对象身上
BeanUtils.populate(bean, map);
request.setCharacterEncoding("utf-8");
//1、使用 beanutils 来获取参数,并封装在 user类中。封装在javaBean中
//注意,只能序列化字符串,如果是日期类型的话,提供了一个接口,先注册自己的接口
// Converter myDateConverter = new MyDateConverter();
ConvertUtils.register(new MyDateConverter(), Date.class);
//获得map集合
Map map = request.getParameterMap();
User user = new User();
//将数据封装到User中去
BeanUtils.populate( user,map);
注意:这里是只能快速的封装字符串数据,像日期的类型不可以直接封装,
但是BeanUtils提供了向外的接口,可以实现自定义的类去封装自己相封装的类型
public class MyDateConverter implements Converter {
@Override
// 将value 转换 c 对应类型
// 存在Class参数目的编写通用转换器,如果转换目标类型是确定的,可以不使用c 参数
public Object convert(Class c, Object value) {
String strVal = (String) value;
// 将String转换为Date --- 需要使用日期格式化
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
try {
Date date = dateFormat.parse(strVal);
return date;
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
}
Listener监听器
监听器的作用
就是监听某一事件的发生,状态的改变
监听器的原理(接口回调技术)
在没考虑接口回调的时候,如果A类中想要调用B类的方法,首先new B() 的对象,然后再调用B类的方法,或者直接将该方法定义成静态的,用类名.方法名调用。
前提:你要知道B类的名字,B类必须存在
如果A类在1998年写的,当时还不知道B类,B类在2020年写的,这种情况下没办法调用。 所以A类在建立的时候提供一个接口,并作为方法的参数。
A在执行循环,当循环到5的时候, 通知B。
事先先把一个对象传递给 A , 当A 执行到5的时候,通过这个对象,来调用B中的方法。 但是注意,不是直接传递B的实例,而是传递一个接口的实例过去。
体现了态的思想
八大类监听器
三大作用域监听器
- ServletRequestListener 监听器
监听请求,浏览器发出的任何请求都会监听到
访问html,访问jsp,访问Servlet都会监听
- request的创建:就会加载监听器的实现类,并且调用requestDestroyed()方法
- 服务器对请求作出响应之后销毁,并调用监听器的实现类。并且调用requestInitialized()方法
- 使用–> 在web.xml文件中注册
<listener>
<listener-class>com.listener.MyListener</listener-class>
</listener>
- 自定义实现类去实现该接口ServletRequestListener
public class MyListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
//在发生请求的时候调用该方法
}
@Override
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
//服务器对请求做出相应之后就会销毁,代用该方法
}
}
- HttpSessionListener 监听器
会话监听器,只要会话的建立就可以监听----> 监听在线人数
html 不会监听、jsp会、Servlet会
- 创建HttpSession对象,getSession(),调用监听器的实现类,并且运行sessionCreated()方法
- session的销毁
会话超时,会销毁session。
服务器的非正常关闭,销毁session
注意,服务器的正常关闭是使session钝化。
- 在web.xml文件中注册
<listener>
<listener-class>com.listener.MyListener</listener-class>
</listener>
- 自定义实现类去实现该接口ServletRequestListener
public class MyListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent httpSessionEvent) {
//在创建会话(getSession())时候调用该方法
}
@Override
public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
//session销毁的时候调用
//1.会话超时 2.非正常话关闭服务器
}
}
- ServletContextListener 监听器
监听ServletContext类创建的时候,以及ServletContext类销毁的时候
1.ServletContext 创建:当服务器启动的时候
2. ServletContext 销毁:关闭服务器时候、项目移除的时候
- 在web.xml中注册
<listener>
<listener-class>com.listener.MyListener</listener-class>
</listener>
- 自定义实现类去实现该接口ServletRequestListener
public class MyListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
//ServletContext对象创建的时候调用该方法
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
//ServletContext对象销毁的时候调用该方法
}
}
三大作用域中属性的添加、删除、替换状态的监听器
使用过程和前三个一样
- ServleRequestAttributeListener
在request作用域中添加、删除、修改属性,分别调用监听器的三个方法
- HttpSessionAttributeListener
在session作用域中添加、删除、修改属性,分别调用监听器的三个方法
- ServletContextAttributeListener
在ServletContext作用域中添加、删除、修改属性,分别调用监听器的三个方法
两大作用域 绑定和解绑 and 钝化和活化
这两个作用域不用注册,直接使用JavaBean去实现他就可以
- HttpSessionActivationListener 钝化,活化
- 用于监听现在session的值是钝话,还是活化,作用是监听值的钝化或者活化,并不是让session的值钝化或者活化。那怎么样让session的值钝化或者活化呢?
- 当前的会话中session的值会很多,如果一直不用会在内存中,消耗内存,这时候考虑把他存在硬盘上,就是钝化(序列化),等到再用的时候,再从硬盘中读取,就是活化(反序列化)
怎么样时session中的值钝化或者活化呢??
- 正常关闭服务器
- 配置文件
- 在Tomcat里面的 conf/context.xml 里面配置,对所有的项目生效
- 在conf/Catalina/localhost/context.xml 配置,对 localhost生效。 localhost:8080
- 在自己的web工程项目中的 META-INF/context.xml,只对当前的工程生效。
maxIdleSwap : 1分钟不用就钝化
directory : 钝化后的那个文件存放的目录位置。
<Context>
<Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1">
<Store className="org.apache.catalina.session.FileStore" directory="hou"/>
</Manager>
</Context>
- HttpSessionBindingListener监听器
- 使用JavaBean去实现接口
- 什么时候绑定,什么时候解绑
//绑定
User user = new User();
user.setName("da");
session.setAttribute("a",User);
//解绑
session.removeAttribute("a");
案例:监听在线人数
- 需求
当一个用户登录成功之后,显示人登录的名称,和当前在线的人数,以及在线人数的所有人的名字。
- 分析
- 第一步:首先实现用户登录的功能
- 输入用户名和密码之后,查询数据库,看是否有该用户,没有返回null,提示用户名密码错误,有返回该用户的所有信息,进入登录页面
- 第二步:显示登录用户的名称
- 该名称都在javaBean对象中,怎么在这个页面将该对象中的数据读取出来?
将在数据库查询的用户信息保存在作用域中,用el表达式读取数据。
- 第三步:显示在线人数,和在线人数的所有的名字
- 显示在线人数和所有用户,只要把所有的用户(javaBean)数据传过来,然后进行遍历每一个用户,再分别输出每一个用户的名字即可。
- 怎么样把所有用户传过来呢,显然要用一个集合,存储所有的用户(javaBean),在登录页面直接遍历该集合,然后使用size()函数获得集合中的有几个数据,进而就能求出所有在线用户的人数。
- 第四步:每添加一个用户,就把用户存到list集合中,然后再把集合传到登录页面,怎么实现呢?
- 使用监听器-------》三大作用域中值的状态监听 HttpSessionAttributeListener
- 为什么选中它呢?根据需求而做出的选择。
当一个用户登录,他会打开浏览器,进行登录,也就是说一个浏览器窗口也只是提供给一个用户用,如果用户在在没退出的情况下再进入登录页面登录,就要替换之前登陆的用户。
只有在不同的会话中(不同的会话值指的是不同的浏览器打开,或者同一浏览器关闭再打开,如果一个浏览器打开没关闭再接着打开统一浏览器,也是同一会话),才表示是另一个人在用,而不是同一个人有两个用户。
显然,不同的会话表示不同的用户,同一会话两次登录会替换前一个用户,必须保证一个会话一个用户,而一个会话一个用户使用HttpSessionListener监听器是不合适的,如果一个用户打开了一个会话,直接输入登录页面地址,也会jsp会自动建立一个session,就算没登录也多了一个人,还有就是用户登录成功,点击退出登录,没办法立即减去一个用户,只能等到30分钟之后session自动销毁。
所以要用HttpSessionAttributeListener
- 第五步:根据在session作用域中值的状态向list集合中添加用户,删除用户,和更改用户 。 可以在登录页面直接使用EL表达式获取
- list集合中的数据,是针对不同的会话,多以要把list结合存入比session更大的作用域中才可以
- 使用监听器,当向session中存用户,就在监听器中向list集合添加添加用户。
- list集合怎么来,在 ServletContext作用域中取。
如果没有,就是第一次添加用户。创建集合 new ArrayList() 添加用户add(),
如果有,直接在其基础上再添加用户- 删除集合元素使用remove(),
- 替换集合中的元素,是先删除原来的元素,再添加新的元素,没有直接的替换方法
在监听器中替换的方法se.getValue();是获得原来的旧值,获取新的值要使用session.getAttribute()获取- 修改完之后,再向servletContext作用域中重新存值
- 第六步:就是第五步做法的第二种方法
将List集合封装在自定义UserList类中,向外提供添加。替换、删除的方法。
注意,要把属性和方法定义成静态的才可以,这样数据才在该类的所有实现类中才可以共享,
数据在内存中才会只有一份,这样的话就不用存在作用域中了,在登录页面只要导入这个类,
然后使用(类名.属性) 调用里面的方法或者属性就可以了。
- 效果
- 代码实现
MyServletListener.java 监听器
@WebListener
public class MyListener implements HttpSessionAttributeListener {
@Override
public void attributeAdded(HttpSessionBindingEvent se) {
System.out.println("session添加值了");
HttpSession session = se.getSession();
ServletContext servletContext = session.getServletContext();
List list = (List)servletContext.getAttribute("list");
System.out.println(list);
if(list != null){
//list集合中有没有人,有,继续添加
//向集合中添加一个用户
list.add((User)se.getValue());
servletContext.setAttribute("list",list);
}else{
//没有,添加第一个
List<User> list1 = new ArrayList<>();
boolean add = list1.add((User) se.getValue());
servletContext.setAttribute("list",list1);
}
}
@Override
public void attributeRemoved(HttpSessionBindingEvent se) {
System.out.println("sessionti移除值了");
HttpSession session = se.getSession();
ServletContext servletContext = session.getServletContext();
List list = (List)servletContext.getAttribute("list");
list.remove((User)se.getValue());
servletContext.setAttribute("list",list);
}
@Override
public void attributeReplaced(HttpSessionBindingEvent se) {
System.out.println("session替换值了");
//在同一个会话中,切换用户,当前用户就会被新用户替换
HttpSession session = se.getSession();
ServletContext servletContext = session.getServletContext();
List list = (List)servletContext.getAttribute("list");
//获得session中的旧对象
User user = (User) se.getValue();
//删除旧对象
list.remove(user);
//获得session中的新对象
User user1 = (User)session.getAttribute("user");
//向集合中添加新对象
list.add(user1);
servletContext.setAttribute("list",list);
}
}
LoginServlet.java 登录
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
request.setCharacterEncoding("utf-8");
String name = request.getParameter("username");
// System.out.println(name); //没填的时候返回空字符串,不是返回null
if(name != null && name != ""){
//有用户在线
HttpSession session = request.getSession();
User user = new User();
//将数据封装到User类中
user.setName(name);
//讲User保存到作用域中,在这里存值,会调用监听器中的attributeAdd()方法
session.setAttribute("user", user);
//跳转页面
response.sendRedirect("index01.jsp");
}else{
response.getWriter().write("请输入用户名");
}
}
ExitServlet.java 退出登录
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.getSession().removeAttribute("username");//从session中移除对象
response.sendRedirect("login.jsp");//重定向到用户登录页面
}
index01.jsp 登录首页
<body>
<%
List alist = (List) application.getAttribute("list");
%>
<c:if test="${not empty user }">
欢迎您,${user.name }
<a href="ExitServlet">退出登录</a><br>
当前在线人数:<%=alist.size() %> 人<br>
在线用户名单:<br>
<select multiple="multiple" name="list" style="width:200px;height:250px">
<c:forEach items="${list}" var="user1">
<option>${user1.name }</option>
</c:forEach>
</select>
</c:if>
<c:if test="${empty user}">
<a href="login.jsp">请输入用户名登录</a>
</c:if>
</body>
login.jsp登录页面
<form action="loginServlet" method="post">
<table>
<tr>
<td>用户名</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="登录"></td>
</tr>
</table>
</form>