J2EE 标准的组件:监听器 - Listener
- 理解监听器的作用
- 理解六种监听器接口作用
- 掌握项目中监听器应用场景
“监听器” 对某个事物进行实时监听. J2EE Servlet模块下的组件,Listener的作用对Web应用对象的行为进行监控.通过Listener监听自动触发指定的功能代码.
监听三种对象
- ServletContext - 对全局ServletContext及其属性进行监听
- HttpSession - 对用户会话及其属性操作进行监听
- ServletRequest(顶级接口) - 对请求及属性操作进行监听
过滤器与监听器区别:
- 过滤器Filter的指责对URL进行过滤拦截,是主动执行的.
- 监听器Listener的职责是对Web对象进行监听,是被动触发.
监听器的使用
开发监听器的三要素
- 实现XxxListener接口,不同接口对应不同监听对象
- 实现每个接口中独有的方法,实现触发监听器的后续操作
- 在web.xml (或通过@WebListener) 中配置
使监听器生效
比如:下面的代码,实现监听ServletContext的创建和销毁
public class FirstListener implements ServletContextListener{
@Override
public void contextDestroyed(ServletContextEvent sce) {
// TODO Auto-generated method stub
System.out.println("ServletContext 销毁");
}
@Override
public void contextInitialized(ServletContextEvent sce) {
// TODO Auto-generated method stub
System.out.println("ServletContext 初始化");
}
}
然后在web.xml中进行配置
<listener>
<listener-class>com.jakeprom.listener.WebListener</listener-class>
</listener>
也可以使用注解进行配置,推荐在web.xml进行配置,注解配置只需要在类的上面添加@WebListener 即可.
@WebListener
启动tomcat 会打印出:ServletContext 初始化
当tomcat重启或停止时打印:ServletContext 销毁
下面我们来了解具体的监听器,只要有六种监听器.同时又分为:内置对象监听器 和 属性监听接口
内置对象监听器
- ServletContextListener - 监听ServletContext对象创建、销毁等操作
- HttpSessionListener - 监听HttpSession对象创建、销毁等操作
- ServletRequestListener - 监听HttpServletRequest对象创建、销毁等
这三个接口,其实就是对应了Web应用的生命周期.
监听代码如下: 主要监听了ServletContext的创建和销毁、Session对象的创建和销毁、HttpServletRequest对象创建和销毁
public class WebListener implements ServletContextListener,HttpSessionListener,ServletRequestListener{
@Override
public void requestDestroyed(ServletRequestEvent sre) {
// TODO Auto-generated method stub
System.out.println("HttpServletRequest 已销毁");
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
HttpServletRequest servletRequest = (HttpServletRequest)sre.getServletRequest();
System.out.println("HttpServletRequest 已创建 URI:"+servletRequest.getRequestURI());
}
@Override
public void contextDestroyed(ServletContextEvent arg0) {
System.out.println("ServletContext 已销毁");
}
@Override
public void contextInitialized(ServletContextEvent arg0) {
// TODO Auto-generated method stub
System.out.println("ServletContext 已初始化");
}
@Override
public void sessionCreated(HttpSessionEvent se) {
System.out.println("Session 被创建,SessionId:"+se.getSession().getId());
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
// TODO Auto-generated method stub
System.out.println("session 已销毁");
}
}
Servlet代码如下:
@WebServlet("/hello")
public class HelloServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.getServletContext().setAttribute("sc-attr1", "value1");
req.getSession().setAttribute("session-attr1", "session-value1");
req.setAttribute("request-attr1", "request-attr1-value1");
}
}
然后我们,启动tomcat访问HelloServlet
打印如下:
ServletContext 已初始化
再次访问HelloServlet
关闭窗口再次访问HelloServlet
刷新重启tomcat
从上述打印中,可以看出整个Web应用的生命周期:
Request 在请求完毕后或者说使用完毕后就会销毁掉
Session 在使用时被创建,再次访问Session不会被重新创建,当关闭窗口再次访问时才会被重新创建
ServletContext 在应用启动时就会被创建,在应用重启和停止时被销毁
属性监听接口
属性监听接口其实就是监听了上述的三个内置对象的属性变化:添加、更新、删除操作
- ServletContextAttributeListener - 监听ServletContext属性操作
- HttpSessionAttributeListener - 监听HttpSession属性操作
- ServletRequestAttributeListener - 监听HttpServletRequest 属性操作
这几个监听很容易理解我们通过示例代码来进行讲解,如下:
创建监听类,进行打印:
属性监听一般在项目中不会用到,大致了解即可.
public class WebAttributeListener implements ServletContextAttributeListener, HttpSessionAttributeListener, ServletRequestAttributeListener {
/**
* Default constructor.
*/
public WebAttributeListener() {
// TODO Auto-generated constructor stub
}
/**
* @see ServletContextAttributeListener#attributeAdded(ServletContextAttributeEvent)
*/
public void attributeAdded(ServletContextAttributeEvent arg0) {
System.out.println("ServletContext 新增属性:"+arg0.getName()+":"+arg0.getValue());
}
/**
* @see ServletContextAttributeListener#attributeRemoved(ServletContextAttributeEvent)
*/
public void attributeRemoved(ServletContextAttributeEvent arg0) {
// TODO Auto-generated method stub
System.out.println("ServletContext 移除属性:"+arg0.getName()+":"+arg0.getValue());
}
/**
* @see ServletContextAttributeListener#attributeReplaced(ServletContextAttributeEvent)
*/
public void attributeReplaced(ServletContextAttributeEvent arg0) {
// TODO Auto-generated method stub
System.out.println("ServletContext 更新属性:"+arg0.getName()+":"+arg0.getValue());
}
/**
* @see ServletRequestAttributeListener#attributeRemoved(ServletRequestAttributeEvent)
*/
public void attributeRemoved(ServletRequestAttributeEvent arg0) {
// TODO Auto-generated method stub]
System.out.println("HttpServletRequest 移除属性:"+arg0.getName()+":"+arg0.getValue());
}
/**
* @see ServletRequestAttributeListener#attributeAdded(ServletRequestAttributeEvent)
*/
public void attributeAdded(ServletRequestAttributeEvent arg0) {
System.out.println("HttpServletRequest 新增属性:"+arg0.getName()+":"+arg0.getValue());
}
/**
* @see ServletRequestAttributeListener#attributeReplaced(ServletRequestAttributeEvent)
*/
public void attributeReplaced(ServletRequestAttributeEvent arg0) {
// TODO Auto-generated method stub
System.out.println("HttpServletRequest 更新属性:"+arg0.getName()+":"+arg0.getValue());
}
/**
* @see HttpSessionAttributeListener#attributeAdded(HttpSessionBindingEvent)
*/
public void attributeAdded(HttpSessionBindingEvent arg0) {
System.out.println("HttpSession 新增属性:"+arg0.getName()+":"+arg0.getValue());
}
/**
* @see HttpSessionAttributeListener#attributeRemoved(HttpSessionBindingEvent)
*/
public void attributeRemoved(HttpSessionBindingEvent arg0) {
// TODO Auto-generated method stub
System.out.println("HttpSession 移除属性:"+arg0.getName()+":"+arg0.getValue());
}
/**
* @see HttpSessionAttributeListener#attributeReplaced(HttpSessionBindingEvent)
*/
public void attributeReplaced(HttpSessionBindingEvent arg0) {
// TODO Auto-generated method stub
System.out.println("HttpSession 更新属性:"+arg0.getName()+":"+arg0.getValue());
}
}
实战:请求流量分析统计
监听器常见的应用:对请求流量的分析,实现效果如下: 利用监听器来统计访问量
代码实现地址:https://github.com/JakePrim/Awesome-Java-Notebook/tree/master/JavaWeb/listener
分析: 要实现统计访问量,那我们肯定需要一个存储的地方,由于我们还没有学习数据库,这里将数据存储在ServletContext中, 那么我们需要监听两个监听器:ServletContextListener、ServletRequestListener.ServletContextListener在tomcat启动的时候创建两个list集合分别存储时间和访问量,存储到ServletContext的setAttribute()属性中,通过ServletRequestListener 获取请求的URI进行筛选和统计,获取ServletContext存储的list进行赋值,然后我们还需要一个servlet来返回json数据,需要一个html来展示数据. (注意JSON的转换使用的是:FastJson 需要导入jar包) OK,通过以上分析我们先创建监听器:
如下代码,监听器的实现逻辑也非常简单,在requestInitialized 获取URI和当前的时间,如果当前的时间不存在timelist中,则添加到结尾,同时数量valuelist中默认添加1,如果当前的时间在timelist中存在,这改变valuelist中的值加1,将数据保存在ServletContext中,注意我们需要拦截写返回结果的json的servlet,这个URL是不用统计的.(将监听器配置到web.xml中)
public class RequestTotalListener implements ServletContextListener,ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent sre) {
// TODO Auto-generated method stub
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
HttpServletRequest servletRequest = (HttpServletRequest)sre.getServletRequest();
String uri = servletRequest.getRequestURI();
//排除servlet
if (uri.endsWith("/rt")) {
return;
}
// 获取访问量
// 10:02 10:04 10:05
List<String> timeList = (List) sre.getServletContext().getAttribute("timeList");
// 5 9 10
List<Integer> valueList = (List) sre.getServletContext().getAttribute("valueList");
Date date = new Date();
SimpleDateFormat format = new SimpleDateFormat("HH:mm");
String time=format.format(date);
if (timeList.indexOf(time) == -1) {
timeList.add(time);//list中不存在当前的时间则存储当前的时间
valueList.add(1);//当前的时间访问量 1
sre.getServletContext().setAttribute("timeList", timeList);
sre.getServletContext().setAttribute("valueList", valueList);
}else {//时间点已经存在 找到对应的value +1
int index = timeList.indexOf(time);
Integer value = valueList.get(index);
if (value != null) {
value = value + 1;
valueList.set(index, value);
}else {
valueList.set(index, 1);
}
sre.getServletContext().setAttribute("valueList", valueList);
}
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
// TODO Auto-generated method stub
}
@Override
public void contextInitialized(ServletContextEvent sce) {
// TODO Auto-generated method stub
//存储时间点
List timeList = new ArrayList();
//存储流量值
List valueList = new ArrayList();
sce.getServletContext().setAttribute("timeList", timeList);
sce.getServletContext().setAttribute("valueList", valueList);
}
}
servlet实现如下:
主要是进行获取数据,转换为json然后响应返回json.
@WebServlet("/rt")
public class RequestTotalServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public RequestTotalServlet() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//输出当前的统计结果
ServletContext context = request.getServletContext();
List<String> timeList = (List)context.getAttribute("timeList");
List<Integer> valueList = (List)context.getAttribute("valueList");
response.setContentType("text/html;charset=utf-8");
Map<String, Object> resultMap = new HashMap<String, Object>();
resultMap.put("timeList", timeList);
resultMap.put("valueList", valueList);
//将统计的信息转换称json
String json = JSON.toJSONString(resultMap);
//响应返回json
response.getWriter().println(json);
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
动态图表的实现,这里使用的ECharts 实现的,ECharts是非常简单的有不懂的大家可以自行搜索,5分钟就可以学会了,自主学习才可以更快的进步.代码如下:非常简单通过ajax请求servlet,获取json然后显示得到的数据.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<!-- 引入 ECharts 文件 -->
<script src="js/echarts.min.js"></script>
<script src="js/jquery.min.js"></script>
</head>
<body>
<div id="main" style="width: 600px; height: 400px;"></div>
<script type="text/javascript">
function showChart() {
//ajax 请求
$
.ajax({
url : "./rt",
type : "get",
dataType : "json",
success : function(json) {
// 基于准备好的dom,初始化echarts实例
var myChart = echarts.init(document
.getElementById('main'));
// 指定图表的配置项和数据
var option = {
title : {
text : '请求流量统计分析'
},
tooltip : {},
legend : {
data : [ '访问量' ]
},
xAxis : {
data : json.timeList
},
yAxis : {},
series : [ {
name : '访问量',
type : 'line',
data : json.valueList
} ]
};
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
}
});
}
window.setInterval("showChart()",1000);
</script>
</body>
</html>
常见的场景:静态数据预处理
在应用程序启动的时候,进行加载,存储到ServletContext中,直接拿来就用就可以了.比如一些网站的导航,其实都是静态数据,不用每次进行请求. 实现代码如下:
public class StaticDataListener implements ServletContextListener{
@Override
public void contextDestroyed(ServletContextEvent sce) {
// TODO Auto-generated method stub
}
@Override
public void contextInitialized(ServletContextEvent sce) {
// 初始化静态数据
//模拟查询数据库数据
List<Channel> channels = new ArrayList<Channel>();
channels.add(new Channel("免费课程", "http://www.baidu.com"));
channels.add(new Channel("实战课程", "http://www.baidu.com"));
channels.add(new Channel("就业课程", "http://www.baidu.com"));
//直接提取数据
sce.getServletContext().setAttribute("channels", channels);
}
}
我们可以通过jsp和JSTL 快速的获取静态数据而不用在从网络中获取.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<c:forEach items="${applicationScope.channels }" var="c">
<a href="${c.url }">${c.channelName }</a>
</c:forEach>
<hr>
</body>
</html>
合理的利用监听器,可以帮助我们更好的关注业务逻辑,也可以省去很多代码.大家加油!