Servlet中监听器
Servlet规范中三种技术:Servlet、Filter、Listener
监听Servlet技术中三种数据范围对象的相关事件:ServletContext、Session、Request
1、创建和销毁的事件监听器2、属性的增加和删除的事件监听器
3、session域中对象状态
监听器在web.xml中的注册方法
<listenner> <listener-class>listenner的实现类</listenner-class> </listenner>
第一类 创建和销毁监听器
ServletContextListener 监听全局context对象创建和销毁 ---- 监听创建和销毁方法执行一次
应用场景:有一些程序想随服务器启动而执行1、创建全局应用数据对象
例如:pageContext.request.contextPath
2、加载框架配置文件
Spring框架org.springframework.web.context.ContextLoaderListener
3、创建数据库连接池
4、实现任务调度
Timer
TimerTask
定时器案例 :
编写程序,让程序10点执行 每隔10秒1次(如果想要每天执行即可设置24h一次) ----------- 定时器
定时器编写方法:
package cn.timertest;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Timer;
import java.util.TimerTask;
/**
* 编写定时器
*
* @author seawind
*
*/
public class TimerTest {
public static void main(String[] args) throws ParseException {
// 编写程序10点运行
final Timer timer = new Timer(); // 表示定时器
// 要用schedule方法 编写日程 三个参数:第一个参数 任务、 第二个参数 第一次执行时间、第三个参数 多长时间后重复执行
// 第一次时间 10点零5分 ,每隔10秒一次、执行5次停止
String s = "2012-03-07 10:08:00";
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
timer.schedule(new TimerTask() {
int i = 0;
@Override
public void run() {
System.out.println("xxxx");
i++;
if (i == 5) {
// System.exit(0);
timer.cancel();
}
}
}, dateFormat.parse(s), 10 * 1000);
}
}
HttpSessionListener 监听session的创建和销毁
session何时创建:当客户端访问服务器,服务器端还没有该会话的session对象时,request.getSession() ---- 创建session何时销毁: 三种 1、服务器停止 2、session.invalidate 3、session过期(session.setMaxInactiveInterval(单位是秒)、配置web.xml(单位分钟 默认30分钟))
案例 : 统计在线人数
原理统计session的数量 ---- session的数量保存在哪?context数据范围
package cn.listener.demo1;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
/**
* 统计在线人数
*
* @author seawind
*
*/
public class OnlineCountListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
// 每当方法执行一次,在线人数+1
// 1、获得当前的在线人数
ServletContext context = se.getSession().getServletContext();
int onlineNumber = (Integer) context.getAttribute("onlineNumber");
onlineNumber++;
// 保存回context范围
context.setAttribute("onlineNumber", onlineNumber);
System.out.println("当前" + onlineNumber + "人在线!");
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
// 每当方法执行一次,在线人数-1
// 1、获得当前的在线人数
ServletContext context = se.getSession().getServletContext();
int onlineNumber = (Integer) context.getAttribute("onlineNumber");
onlineNumber--;
// 保存回context范围
context.setAttribute("onlineNumber", onlineNumber);
System.out.println("有人离线了...");
System.out.println("当前" + onlineNumber + "人在线!");
}
}
案例: 定时session销毁扫描器
需求:定制定时器,每隔1分钟执行一次,扫描所有session、如果发现session已经有1分钟没有使用,那么销毁session对象先编写TimerContextListener.java监听器
package cn.listener.demo2;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpSession;
/**
* 在服务器启动时,启动一个session 定时扫描器
*
* @author seawind
*
*/
public class TimerContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
final List<HttpSession> sessionList = new ArrayList<HttpSession>();
// 将session集合保存到context数据范围
sce.getServletContext().setAttribute("sessionList", sessionList);
Timer timer = new Timer();// 定时器
// 每隔1分钟执行一次
timer.schedule(new TimerTask() {
@Override
public void run() {
// 扫描所有session、如果发现session已经有1分钟没有使用,那么销毁session对象
// 获得sessionlist 是否需要通过context.getAttribute获得
synchronized (sessionList) {
System.out.println("定时销毁程序执行...");
// 从列表移除session通过迭代器对象
Iterator<HttpSession> iterator = sessionList.iterator();
while (iterator.hasNext()) {
HttpSession session = iterator.next();
System.out.println("session:"
+ session.getId()
+ "未使用时间:"
+ (System.currentTimeMillis() - session
.getLastAccessedTime()));
if (System.currentTimeMillis()
- session.getLastAccessedTime() >= 1000 * 60) {
System.out.println("执行" + session.getId()
+ "的销毁操作...");
session.invalidate();
// 从集合移除
iterator.remove();
}
}
}
// 下列语句不可行 因为forEach方法不能对子项进行修改删除操作,所以要用迭代器
// for (HttpSession session : sessionList) {
// // 判断session是否一分钟没有使用
// if (System.currentTimeMillis()
// - session.getLastAccessedTime() >= 1000 * 60) {
// session.invalidate();
// sessionList.remove(session);
// }
// }
}
}, new Date(), 1000 * 20);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
再编写TimerHttpSessionListener.java 监听器
package cn.listener.demo2;
import java.util.List;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
public class TimerHttpSessionListener implements HttpSessionListener {
@Override
// 当有session创建时,保存session到sessionlist
public void sessionCreated(HttpSessionEvent se) {
HttpSession session = se.getSession();
System.out.println("session被创建了,id:" + session.getId());
// 获得sessionlist
List<HttpSession> sessionList = (List<HttpSession>) session
.getServletContext().getAttribute("sessionList");
synchronized (sessionList) {
// 添加session到集合
sessionList.add(session);
}
// 这里还需要执行 context的setAttribute吗?
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("session被销毁了,id:" + se.getSession().getId());
}
}
ServletRequestListener 监听请求对象创建和销毁
请求对象生命周期(什么时间创建,什么时间销毁)客户端发请求时创建、服务器响应结束时销毁
如果执行forward 、include、redirect 哪个会造成 请求对象再次创建?----------redirect
-----------------------------------------------------------------------------------
第二类 属性的增加和删除的事件监听器
ServletContextAttributeListener HttpSessionAttributeListener ServletRequestAttributeListener
attributeAdded 当数据范围 没有该对象,调用setAttribute 执行attributeRemoved 当数据范围 执行removeAttribute 调用
attributeReplaced 当调用setAttribute时,该属性范围已经存在相同名称对象,执行
----------------------------------------------------------------------------------
第三类 感知 Session 绑定的事件监听器
--------------不需要 web.xml 文件中进行注册
session中对象 四种状态: 绑定、解除绑定、钝化、活化HttpSessionBindingListener 感知当前对象被保存到session和从session移除 ------- 在线用户列表
HttpSessionActivationListener 感知对象被活化和钝化 ---- 容器来完成
活化:将硬盘上对象加载回内存
钝化:将内存中对象保存到硬盘上
主流应用:将session中不常用的对象,保存到硬盘上
如何将对象数据写到硬盘上:序列化
1、编写javabean 实现 serializable ------- javabean需要序列化2、javabean实现HttpSessionActivationListener
3、在容器中 配置session要钝化和活化
META-INFO/context.xml
<Context>
<Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1">
<Store className="org.apache.catalina.session.FileStore" directory="it315"/>
</Manager>
</Context>
4、钝化生成目录 在tomcat/work/工程目录
** j avabean 可序列化: 实现serializable 不然无法实现活化和钝化效果
序列化 作用?
将内存中java对象 ------ 字节流格式企业应用中:用于将对象数据持久化到硬盘、远程数据传输 (RMI、JavaEE技术、远程访问数据)
案例:显示登陆用户列表,并实现踢人功能。
分析:
显示用户列表,知道哪些用户在线,将在线用户保存集合中(登陆) ---- 用户列表集合(ServletContext)
1、用户列表存放在哪? --- ServletContext2、什么时候将用户存入列表?---- 登陆成功, 用户信息保存到session (监听器)
HttpSessionAttributeListener 监听到session中所有属性变化
HttpSessionBindingListener 感知自己被加入session
HttpSessionAttributeListener 和HttpSessionBindingListener 区别?
当session中有任何数据变化,执行HttpSessionAttributeListener 里面三个方法 add remove replace ----- 这个时候变化的数据不一定是用户数据
HttpSessionBindingListener 感知当前对象被加入session, 只有加入session对象是当前数据类型,才会执行valueBound (不用在web.xml中注册)
3、踢人是怎么样实现的?原理:销毁session
想实现踢人,在用户登陆时,保存用户与session 对应关系
Servlet中主要编写 向session添加对象,或从session移除对象操作
对Session列表集合操作 ----- 交给Listener来完成
代码实现:
在index.jsp中实现在线用户列表的显示
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>My JSP 'index.jsp' starting page</title>
</head>
<body>
<h1>打印在线用户列表</h1>
<c:forEach var="entry" items="${applicationScope.userList}">
<h4>${entry.key }<a href="kick?name=${entry.key }">踢人</a></h4>
</c:forEach>
</body>
</html>
编写登陆页面 login.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="login" method="post">
用户名<input type="text" name="username"/>
密码<input type="password" name="password"/>
<input type="submit" value="登陆" />
</form>
</body>
</html>
编写实体类User
包括id , username, password
要实现HttpSessionBindingListener监听器,并实现感知登陆与退出的方法(将user加入userList与移出userList)
package cn.vo;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
public class User implements HttpSessionBindingListener {
private int id;
private String username;
private String password;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
// 感知登陆
public void valueBound(HttpSessionBindingEvent event) {
// 获得user对象
User user = (User) event.getValue();
// 获得列表
ServletContext context = event.getSession().getServletContext();
Map<String, HttpSession> userList = (Map<String, HttpSession>) context
.getAttribute("userList");
// 将当前登陆用户 加入列表
userList.put(user.getUsername(), event.getSession());
System.out.println("添加后map:" + userList);
// 传引用,无需保存用户列表回context
}
@Override
// 感知退出
public void valueUnbound(HttpSessionBindingEvent event) {
// 获得列表
ServletContext context = event.getSession().getServletContext();
Map<String, HttpSession> userList = (Map<String, HttpSession>) context
.getAttribute("userList");
userList.remove(this.getUsername());
System.out.println("移除后 map:" + userList);
}
}
编写context监听器
初始化一个用户列表 放入context数据范围
package cn.web.listener;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpSession;
public class InitServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
// 初始化一个用户列表 放入context数据范围
// Set<String> nameList = new HashSet<String>();
// sce.getServletContext().setAttribute("userList", nameList);
// 踢人需要用map实现
Map<String, HttpSession> userList = new HashMap<String, HttpSession>();
sce.getServletContext().setAttribute("userList", userList);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
// TODO Auto-generated method stub
}
}
编写登陆Servlet
package cn.web.servlet;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import cn.itcast.vo.User;
public class LoginServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String username = request.getParameter("username");
String password = request.getParameter("password");
boolean loginSuccess = false;
User user = null;
// 到数据库查询 是否成功
String sql = "select * from user where username = ? and password=?";
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Connection conn = null;
try {
conn = DriverManager.getConnection("jdbc:mysql:///day19", "root",
"123");
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, username);
pstmt.setString(2, password);
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
loginSuccess = true;
user = new User();
user.setId(rs.getInt("id"));
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (loginSuccess && user != null) {
// 登陆成功
request.getSession().setAttribute("loginUser", user);
request.getRequestDispatcher("/index.jsp").forward(request,
response);
} else {
// 登陆失败
request.getRequestDispatcher("/login.jsp").forward(request,
response);
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
编写踢人Servlet
package cn.web.servlet;
import java.io.IOException;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class KickServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获得 踢出 用户名称
String name = request.getParameter("name");
// 怎么样实现踢人
Map<String, HttpSession> userList = (Map<String, HttpSession>) getServletContext()
.getAttribute("userList");
HttpSession session = userList.get(name); // 这个session就是要踢出session
session.invalidate();
request.getRequestDispatcher("/index.jsp").forward(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
即完成显示在线用户与踢人功能