Java之Listener--时刻监控着Servlet 的一举一动

一、背景

每次启动tomcat 时,console 控制台会输出各种信息,其中看到这两条信息

[xxx 2018-06-04 15:52:13,772](DEBUG) - xxx.xxx.basic.listener.StartUpListener - (StartUpListener.java:53)XXX启动初始化开始 
[xxx 2018-06-04 15:52:13,783](DEBUG) - xxx.xxx.basic.listener.StartUpListener - (StartUpListener.java:57)XXX启动初始化结束

为什么在每次启动tomcat 时会打印这样的两条信息呢,为了一探究竟,我们进入到这个类里面

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

@WebListener("startUpListener")

public class StartUpListener implements ServletContextListener {

    /*

     * log4j日志记录

     */

    protected final Logger LOG = Logger.getLogger(this.getClass());

 

    /**

     * 监听项目启动,进行初始化

     * @param sce  ServletContextEvent对象

     */

    @Override

    public void contextInitialized(ServletContextEvent sce) {

        // TODO Auto-generated method stub

        LOG.debug("XXX启动初始化开始");

        Const.CONTEXT = WebApplicationContextUtils.getWebApplicationContext(sce.getServletContext());

        Const.PROJECT_PATH = sce.getServletContext().getRealPath(Const.SEPARATOR);

        Const.BASE = sce.getServletContext().getContextPath();

        LOG.debug("XXX启动初始化结束");

    }

 

    /**

     * 监听项目终止,进行销毁

     * @param sce ServletContextEvent对象

     */

    @Override

    public void contextDestroyed(ServletContextEvent sce) {

        // TODO Auto-generated method stub

    }

}

在这个类中看到如下字样“监听项目启动,进行初始化”和“监听项目终止,进行销毁”,同时在contextInitialized 方法中找到了之前的日志信息。再看到这个类继承了ServletContextListener, 由此我们不难得出结论,该方法的作用是在tomcat 启动时对项目的启动过程进行监听记录。那么这个方法为什么能够实现监听呢?

二、Listener 简介

Listener 即监听器,是servlet 的监听器。随web应用的启动而启动,只初始化一次,随web应用的停止而销毁。当被监视的对象发生情况时,立即采取相应的行动(观察者模式)。主要作用是:做一些初始化的内容添加工作、设置一些基本的内容、比如一些参数或者是一些固定的对象,通过监听器,可以自动激发一些操作。比如:监听在线用户数量等等。

在 Servlet API 中有一个 ServletContextListener 接口,它能够监听 ServletContext 对象的生命周期,实际上就是监听 Web 应用的生命周期。当Servlet 容器启动或终止Web 应用时,会触发ServletContextEvent 事件,该事件由ServletContextListener 来处理。在 ServletContextListener 接口中定义了处理ServletContextEvent 事件的两个方法:

1

2

3

4

5

6

public interface ServletContextListener extends EventListener {

    //当Servlet 容器启动Web 应用时调用该方法。在调用完该方法之后,容器再对Filter 初始化, 并且对那些在Web 应用启动时就需要被初始化的Servlet 进行初始化。

    public void contextInitialized(ServletContextEvent sce);

    //当Servlet 容器终止Web 应用时调用该方法。在调用该方法之前,容器会先销毁所有的Servlet 和Filter 过滤器。

    public void contextDestroyed(ServletContextEvent sce);

}

三、案例分析

再回到最开始的疑问,StartUpListener 类实现了ServletContextListener 接口,通过配置@WebListener("startUpListener") 监听注解的方式,在项目启动时,调用contextInitialized 方法打印了项目启动的日志以及初始化一些参数。另外如果不使用注解的方式实现监听,也可以再web.xml 中配置该监听类

1

2

3

4

5

6

<!-- spring 监听项目启动过程,初始化参数 -->

    <listener>

         <listener-class>

          xxx.xxx.basic.listener.StartUpListener

         </listener-class>

    </listener> 

四、Servlet Listener接口和事件(Event)对象

按监听的对象划分:servlet2.4规范定义的事件有三种:

1.用于监听应用程序环境对象(ServletContext)的事件监听器

2.用于监听用户会话对象(HttpSession)的事件监听器

3.用于监听请求消息对象(ServletRequest)的事件监听器

1

2

3

4

5

6

7

8

9

10

Servlet API提供了以下监听器接口:

javax.servlet.AsyncListener - 如果在添加了侦听器的ServletRequest上启动的异步操作已完成,超时或导致错误,将会通知侦听器。

javax.servlet.ServletContextListener - 用于接收关于ServletContext生命周期更改的通知事件的接口。

javax.servlet.ServletContextAttributeListener - 接收关于ServletContext属性更改的通知事件的接口。

javax.servlet.ServletRequestListener - 用于接收关于进入和超出Web应用程序范围的请求的通知事件的接口。

javax.servlet.ServletRequestAttributeListener - 接收关于ServletRequest属性更改的通知事件的接口。

javax.servlet.http.HttpSessionListener - 接收关于HttpSession生命周期更改的通知事件的接口。

javax.servlet.http.HttpSessionBindingListener - 使对象从会话绑定到绑定或从其绑定时被通知。

javax.servlet.http.HttpSessionAttributeListener - 用于接收关于HttpSession属性更改的通知事件的接口。

javax.servlet.http.HttpSessionActivationListener - 绑定到会话的对象可能会侦听容器事件,通知他们会话将被钝化,该会话将被激活。需要在VM或持久化会话之间迁移会话的容器来通知绑定到实现HttpSessionActivationListener的会话的所有属性。

按监听的事件类项划分

1.用于监听域对象自身的创建和销毁的事件监听器

2.用于监听域对象中的属性的增加和删除的事件监听器

3.用于监听绑定到HttpSession域中的某个对象的状态的事件监听器

1

2

3

4

5

6

7

Servlet API提供以下事件对象:

javax.servlet.AsyncEvent - 在ServletRequest(通过调用ServletRequest#startAsync或ServletRequest#startAsync(ServletRequest,ServletResponse))启动的异步操作已完成,超时或产生错误时触发的事件。

javax.servlet.http.HttpSessionBindingEvent - 将此类型的事件发送到实现HttpSessionBindingListener的对象,当该对象从会话绑定或解除绑定时,或者发送到在web.xml中配置的HttpSessionAttributeListener,当绑定任何属性时,在会话中取消绑定或替换。会话通过对HttpSession.setAttribute的调用来绑定对象,并通过调用HttpSession.removeAttribute解除对象的绑定。当对象从会话中删除时,我们可以使用此事件进行清理活动。

javax.servlet.http.HttpSessionEvent - 这是表示Web应用程序中会话更改的事件通知的类。

javax.servlet.ServletContextAttributeEvent - 关于对Web应用程序的ServletContext的属性进行更改的通知的事件类。

javax.servlet.ServletContextEvent - 这是关于Web应用程序的servlet上下文更改的通知的事件类。

javax.servlet.ServletRequestEvent - 此类事件表示ServletRequest的生命周期事件。事件的源代码是这个Web应用程序的ServletContext。<br>javax.servlet.ServletRequestAttributeEvent - 这是事件类,用于对应用程序中servlet请求的属性进行更改的通知。<br><br>在一个web应用程序的整个运行周期内,web容器会创建和销毁三个重要的对象,ServletContext,HttpSession,ServletRequest。

 五、简单实例

自定义session扫描器

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

package com.github.listener;

 

import java.util.Collections;

import java.util.LinkedList;

import java.util.List;

import java.util.Timer;

 

import javax.servlet.ServletContextEvent;

import javax.servlet.http.HttpSession;

import javax.servlet.http.HttpSessionEvent;

import javax.servlet.http.HttpSessionListener;

 

/**

 * 监听器:当网站用户量增加时,session占用的内存会越来越大,这时session的管理,将会是一项很大的系统开销,为了高效的管理session,

 * 我们可以写一个监听器,定期清理掉过期的session

 * @date 2018年6月4日 下午5:20:31

 */

public class SessionScanerListener implements HttpSessionListener {

    // 创建一个线程安全的集合,用来存储session

    List<HttpSession> sessionList = Collections.synchronizedList(new LinkedList<HttpSession>());

 

    private Object lock = new Object();

 

    @Override

    public void sessionCreated(HttpSessionEvent se) {

        System.out.println("session 创建成功...");

        HttpSession httpSession = se.getSession();

        synchronized (lock) {

            sessionList.add(httpSession);

        }

    }

 

    @Override

    public void sessionDestroyed(HttpSessionEvent se) {

        System.out.println("session 销毁成功...");

    }

 

    // web应用关闭时触发contextDestroyed事件

    public void contextDestroyed(ServletContextEvent servletContextEvent) {

        System.out.println("web应用关闭...");

    }

 

    // web应用启动时触发contextInitialized事件

    public void contextInitialized(ServletContextEvent servletContextEvent) {

        System.out.println("web应用初始化...");

        // 创建定时器

        Timer timer = new Timer();

        // 每隔30秒就定时执行任务

        timer.schedule(new MyTask(sessionList, lock), 01000 30);

    }

 

}

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

package com.github.listener;

 

import java.util.List;

import java.util.ListIterator;

import java.util.TimerTask;

 

import javax.servlet.http.HttpSession;

 

/**

 * 定时器,定义定时任务的具体内容

 * @date 2018年6月4日 下午5:52:24

 */

public class MyTask extends TimerTask {

    private List<HttpSession> list;

    // 存储传递过来的锁

    private Object lock;

 

    // 构造方法

    MyTask(List<HttpSession> list, Object lock) {

        this.list = list;

        this.lock = lock;

    }

 

    @Override

    public void run() {

        // 考虑到多线程的情况,这里必须要同步

        synchronized (lock) {

            System.err.println("定时器开始执行");

            ListIterator<HttpSession> listIterator = list.listIterator();

            while (listIterator.hasNext()) {

                HttpSession httpSession = listIterator.next();

                // httpSession.getLastAccessedTime() = session的最后访问时间

                if (System.currentTimeMillis() - httpSession.getLastAccessedTime() >= 1000 30) {

                    // 手动销毁session

                    httpSession.invalidate();

                    // 从集合中移除已经被销毁的session

                    listIterator.remove();

                }

            }

        }

    }

}

在web.xml 中添加上下文配置

1

2

3

4

<!--spring 监听器的配置,用于监听session 内存的情况 -->

    <listener>

        <listener-class>com.github.listener.SessionScanerListener</listener-class>

    </listener>

servlet 中常用的listener:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

<!-- 此监听器主要用于解决java.beans.Introspector导致的内存泄漏的问题,有些框架(Structs,Quartz) 不会清理Introspector。(应该放在开头部分,至少也得在ContextLoaderListener

        之前) -->

    <listener>

        <listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>

    </listener>

    <!--spring 监听器的配置,用于在启动 Web 容器时,自动装配 ApplicationContext 的配置信息 -->

    <listener>

        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

    </listener>

    <!--spring 监听器的配置,用于监听session 内存的情况 -->

    <listener>

        <listener-class>com.github.listener.SessionScanerListener</listener-class>

    </listener>

    <!--spring log4j 监听器 -->

    <listener>

        <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>

    </listener>

六、总结

至此,通过对servlet 的listener 案例分析以及简单样例,可以知道listener 和filter 有个共同点,都是由容器进行调度。我们只需要编写自己的listener 去实现我们关心的监听器接口并注册,剩下来的工作就是在我们自己的listener 里面编写业务逻辑。

 

参考:https://www.cnblogs.com/EasonJim/p/7100750.html 和 https://blog.csdn.net/reggergdsg/article/details/52891311

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值