一.简介
Servlet监听器 是Servlet规范中定义的一种特殊类 ,或者说一种高级特性.常用于监听request,session,context的创建,销毁或属性改变
二.监听器分类及作用(Servlet3.0规范)
eclipse里创建listener所提供的监听器接口
1.ServletContextListener , SerlvetContextAttributeListener 用于监听应用程序环境对象(ServletContext)
在web.xml中配置
<context-param>
<param-name>begincode</param-name>
<param-value>code</param-value>
</context-param>
//在实现ServletContextListener接口中
public void contextInitialized(ServletContextEvent arg0) {
String value = arg0.getServletContext().getInitParameter("begincode");
System.out.println(value); //code
}
当 Servlet 容器启动后,会部署和加载所有 web 应用。当web 应用被加载,Servlet 容器会创建一次 ServletContext,然后将其保存在服务器的内存中。web 应用的 web.xml 被解析,找到其中所有 servlet、filter 和 Listener 或 @WebServlet、@WebFilter 和 @WebListener 注解的内容,创建一次并保存到服务器的内存中。ServletContext 与 web 应用存活时间一样长。它被所有 session 中的所有请求共享。
主要用途:作为定时器、加载全局属性对象、创建全局数据库连接、加载缓存信息等
2.ServeltRequestListener接口 监听HttpServletRequest的创建 销毁
public void requestInitialized(ServletRequestEvent sre)//request创建时调用
public void requestDestroyed(ServletRequestEvent sre)//request销毁时调用
ServletRequestAttributeListener接口 request域对象值改变
public void attributeAdded(ServletRequestAttributeEvent arg0) {
}
@Override
public void attributeRemoved(ServletRequestAttributeEvent arg0) {
}
@Override
public void attributeReplaced(ServletRequestAttributeEvent arg0) {
}
主要用途:读取request参数,记录访问历史
3.HttpSessionListener,HttpSessionAttributeListener 用于监听HttpSession的创建 销毁 或者属性改变
public void sessionCreated(HttpSessionEvent se)//session创建时调用
public void sessionDestroyed(HttpSessionEvent se)//session销毁时调用
public void attributeAdded(HttpSessionBindingEvent arg0) {
}
public void attributeRemoved(HttpSessionBindingEvent arg0) {
}
public void attributeReplaced(HttpSessionBindingEvent arg0) {
}
主要用途:统计在线人数、记录访问日志等
最后再来讲讲 HttpSessionAcivationListener,HttpSessionBindingListener :
监听绑定到HttpSeesion域中的某个对象的状态的事件监听器(创建普通JavaBean)
HttpSessionBingingListener:当实现此接口的对象被加入HttpSession或从中移除时,就会调用对应的valueBound()与valueUnbound()方法,并传入HttpSessionBindingEvent对象,可以通过该对象的getSession()取得HttpSession对象。
HttpSessionAcivationListener: 可以实现对象的钝化和活化 >具体的可以自己百度,因为用的比较少,这里不再详细介绍.
钝化: 将session对象持久化到存储设备上
活化: 将session对象从存储设备上进行恢复
ps:使用注解 @WebListener无法去定义监听器的执行顺序
三. Spring 监听器源码简单分析
在使用spirng,springmvc的web项目中 我们常用的监听器有两个
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:spring/applicationContext-*.xml
classpath*:spring/dataSource.xml
</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
ContextLoaderListener 能够监听ServletContext对象的生命周期,实际上就是监听Web应用的生命周期。当Servlet容器启动或终止Web应用时,会触发ServletContextEvent事件,该事件由ServletContextListener来处理。在ServletContextListener接口中定义了处理ServletContextEvent 事件的两个方法contextInitialized()和contextDestroyed()。
ContextLoaderListener监听器的作用就是启动Web容器时,自动装配ApplicationContext的配置信息。因为它实现了ServletContextListener这个接口,在web.xml配置了这个监听器,启动容器时,就会默认执行它实现的方法。由于在ContextLoaderListener中关联了ContextLoader这个类,所以整个加载配置过程由ContextLoader来完成。
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
接下来看父类
//看名字就知道是拿到当前的上下文对象
private static volatile WebApplicationContext currentContext;
//初始化保存的context对象
private WebApplicationContext context;
//静态代码块 加载配置文件里的信息
static {
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
}
}
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext");
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
if (this.context == null) {
//通过反射创建上下文实例
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
//配置 并刷新WebApplicationContext
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
return this.context;
}
catch (RuntimeException ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
catch (Error err) {
logger.error("Context initialization failed", err);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
throw err;
}
}
这段代码最主要的是在这里
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
key为 :String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
value为: this.context
在程序启动的时候 我们debug看看里面保存了什么 发现了一个很熟悉的名字 beanFactory ,这不就是spring的bean工厂类嘛!!
我们可以实现一个接口 用来手动拿取spring容器里面的bean
@Component
public class SpringApplicationContextHolder implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
SpringApplicationContextHolder.context = context;
}
public static Object getSpringBean(String beanName) {
Validate.notEmpty(beanName, "bean name is required");
return context==null?null:context.getBean(beanName);
}
public static String[] getBeanDefinitionNames() {
return context.getBeanDefinitionNames();
}
}
具体可以应用在多线程中,spring为了安全 不允许在多线程中用注解来注入类,我们可以用上面的代码来实现注入.