J2EE 标准的组件:监听器 - Listener

  • 理解监听器的作用
  • 理解六种监听器接口作用
  • 掌握项目中监听器应用场景

“监听器” 对某个事物进行实时监听. J2EE Servlet模块下的组件,Listener的作用对Web应用对象的行为进行监控.通过Listener监听自动触发指定的功能代码.

监听三种对象

  • ServletContext - 对全局ServletContext及其属性进行监听
  • HttpSession - 对用户会话及其属性操作进行监听
  • ServletRequest(顶级接口) - 对请求及属性操作进行监听

过滤器与监听器区别:

  • 过滤器Filter的指责对URL进行过滤拦截,是主动执行的.
  • 监听器Listener的职责是对Web对象进行监听,是被动触发.

监听器的使用

开发监听器的三要素

  • 实现XxxListener接口,不同接口对应不同监听对象
  • 实现每个接口中独有的方法,实现触发监听器的后续操作
  • 在web.xml (或通过@WebListener) 中配置使监听器生效

比如:下面的代码,实现监听ServletContext的创建和销毁

  1. public class FirstListener implements ServletContextListener{
  2. @Override
  3. public void contextDestroyed(ServletContextEvent sce) {
  4. // TODO Auto-generated method stub
  5. System.out.println("ServletContext 销毁");
  6. }
  7. @Override
  8. public void contextInitialized(ServletContextEvent sce) {
  9. // TODO Auto-generated method stub
  10. System.out.println("ServletContext 初始化");
  11. }
  12. }

然后在web.xml中进行配置

  1. <listener>
  2. <listener-class>com.jakeprom.listener.WebListener</listener-class>
  3. </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 已初始化
image.png
再次访问HelloServlet
image.png
关闭窗口再次访问HelloServlet
image.png
刷新重启tomcat
image.png

从上述打印中,可以看出整个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());
    }    
}

实战:请求流量分析统计

监听器常见的应用:对请求流量的分析,实现效果如下: 利用监听器来统计访问量

image.png

代码实现地址: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>

合理的利用监听器,可以帮助我们更好的关注业务逻辑,也可以省去很多代码.大家加油!