Servlet监听
在《Servlet和Jsp》中我们使用了ServletConfig获取Servlet的初始配置,用ServletContext来获取整个Web应用的初始配置,但如果需要在所有的Servlet之前初始化资源怎么办呢?比如DataSource、Log4j等,可惜Servlet没有main方法,它是靠Web容器(如Tomcat)来加载的。
幸运的是Servlet提供了一个类javax.servlet.ServletContextListener,它能够监听ServletContext一生中的两个关键事件:初始化(创建)和撤销。
小示例
新建net.oseye.web.listener.MyTestContextListener类实现javax.servlet.ServletContextListener:
public class MyTestContextListener implements ServletContextListener { public void contextInitialized(ServletContextEvent arg0) { //通过ServletContext传递资源 arg0.getServletContext().setAttribute("name", "kevin"); } public void contextDestroyed(ServletContextEvent arg0) { // 不需要销毁资源 } }
在DD文件中配置Context监听:
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> <listener> <listener-class>net.oseye.web.listener.MyTestContextListener</listener-class> </listener> <servlet> <servlet-name>HelloOther</servlet-name> <display-name>HelloOther</display-name> <description></description> <servlet-class>net.oseye.web.HelloOther</servlet-class> </servlet> <servlet-mapping> <servlet-name>HelloOther</servlet-name> <url-pattern>/HelloOther</url-pattern> </servlet-mapping> </web-app>
在Servlet中获取Context值并使用:
String name=(String) getServletContext().getAttribute("name"); response.getWriter().println("name:"+name);
Servlet中使用Log4J
Log4j的配置文件在项目中的位置如下图:
log4j.properties内容
log4j.logger.net.oseye=INFO,WebFile log4j.appender.WebFile=org.apache.log4j.DailyRollingFileAppender log4j.appender.WebFile.File=d:/log/testweb/web.log log4j.appender.WebFile.DatePattern=yyyy-MM-dd-HH'.log' log4j.appender.WebFile.layout=org.apache.log4j.PatternLayout log4j.appender.WebFile.layout.ConversionPattern=%d{HH:mm:ss,SSS} %M %m%n
在pom.xml中添加slf4j依赖
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.6</version> </dependency>
web.xml配置监听
<listener> <listener-class> net.oseye.web.listener.MyTestContextListener </listener-class> </listener>
net.oseye.web.listener.MyTestContextListener监听类代码
public class MyTestContextListener implements ServletContextListener { public void contextInitialized(ServletContextEvent arg0) { //通过ServletContext传递资源 arg0.getServletContext().setAttribute("name", "kevin"); PropertyConfigurator.configure(arg0.getServletContext().getRealPath("/config/log4j.properties")); } public void contextDestroyed(ServletContextEvent arg0) { // 不需要销毁资源 } }
在Servlet中记录日志
Logger logger=LoggerFactory.getLogger(HelloOther.class); logger.info("测试Log4j");
监听器
只要是在生命周期里的重要时刻,总会有一个监听器在监听,下面是8个监听器
场景 | 监听器接口 | 时间类型 |
你想知道一个web应用上下文中是否增加、删除或替换了一个属性 |
javax.servlet.ServletContextAttributeListener |
ServletContextAttributeEvent |
你想知道有多少个并发用户,也就是说,你想跟踪活动的会话 |
javax.servlet.http.HttpSessionListener |
HttpSessionEvent |
每次请求到来时你都想知道,以便建立日志记录 |
javax.servlet.ServletRequestListener |
ServletRequestEvent |
增加、删除或替换一个请求属性时你希望能够知道 |
javax.servlet.ServletRequestAttributeListener |
ServletRequestAttributeListener |
你有一个属性类,而且你希望这个类型的对象在绑定到一个会话或从会话中或从会话删除时得到通知 |
javax.servlet.HttpSessoinBindingListener |
HttpSessionBindingEvent |
增加、删除或添加一个会话属性时你希望能够知道 |
javax.servlet.HttpSessionAttributeListener |
HttpSessionBindingEvent |
你想知道是否创建或撤销了一个上下文 |
javax.servlet.ServletContextListener |
ServletContextEvent |
你有一个属性类,而且希望这个类型的对象在其绑定的会话迁移到另一个JVM时得到通知 |
javax.servlet.http.HttpSessionActivationListener |
HttpSessionEvent |
HttpSessionListener和HttpSessionActivationListener共用HttpSessionEvent事件;ServletSessionBindingListener和ServletSessionAttributeListener共用HttpSessionBindingEvent事件。
如在部署描述文件web.xml添加监听
<listener> <listener-class>net.oseye.web.listener.ContextAttributeListener</listener-class> </listener> <listener> <listener-class>net.oseye.web.listener.SessionLinstener</listener-class> </listener>
net.oseye.web.listener.ContextAttributeListener
public class ContextAttributeListener implements ServletContextAttributeListener { private static Logger logger=LoggerFactory.getLogger(ContextAttributeListener.class); public void attributeAdded(ServletContextAttributeEvent arg0) { logger.info("name:{} val:{}",arg0.getName(),arg0.getValue().toString()); } public void attributeRemoved(ServletContextAttributeEvent arg0) { // TODO Auto-generated method stub } public void attributeReplaced(ServletContextAttributeEvent arg0) { // TODO Auto-generated method stub } }
net.oseye.web.listener.SessionLinstener
public class SessionLinstener implements HttpSessionListener { private static Logger log=LoggerFactory.getLogger(SessionLinstener.class); public void sessionCreated(HttpSessionEvent arg0) { log.info("创建-"+arg0.getSession().getId()); } public void sessionDestroyed(HttpSessionEvent arg0) { log.info("销毁-"+arg0.getSession().getId()); } }
对于ServletContextAttributeListener我的测试只能监听ServletContext()操作的属性,而对于在DD文件中的却没能监听到,如web.xml配置:
<context-param> <param-name>email</param-name> <param-value>kevin@oseye.net</param-value> </context-param>
PS:这里是我理解错误,把属性和上下文参数搞混了,在web.xml中配置的是上下文参数。
Context属性和会话属性的多线程
Context属性和会话属性因为作用域很大,所以他们不是线程安全的,我们一般这样保证线程安全
synchronized (getServletContext()) { getServletContext().setAttribute("name", "kevin"); getServletContext().setAttribute("age", "22"); } synchronized (request.getSession()) { request.getSession().setAttribute("name", "kevin"); request.getSession().setAttribute("age", "22"); }
PS:每个Servlet只有一个实例对象(Instance),但可以被多个线程访问;这里强调这点并不是为了解释上面的属性线程不安全,因为即使是多个实例对象,也会有多个Servlet造成属性线程不安全的。