监听器
- 监听器-就是一个实现待定接口的普通Java程序,此程序专门用于监听别一个类的方法调用。
- 都是使用观察者设计模式。
- 什么是观察者模式:
定义对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知自动更新。
监听者模式的三个重要类
一个小Demo来认识一下观察者模式
假设以下三个类时别人写的类:
被监听者
package patternDemo.Person;
import java.util.ArrayList;
import java.util.List;
/* 被观察(监听)者
*/
public class Person {
private String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
List<IPersonActionListener> listeners = new ArrayList<IPersonActionListener>();
public void addIPersonActionListener(IPersonActionListener l) {
listeners.add(l);
}
public void run() {
for (IPersonActionListener listener : listeners) {
PersonActionEvent e = new PersonActionEvent( this );
listener.runAction(e);
}
System.out.println( name +">>在跑步");
}
public void eat() {
for (IPersonActionListener listener : listeners) {
PersonActionEvent e = new PersonActionEvent( this );
listener.eatAction(e);
}
System.out.println( name +">>在吃饭");
}
public void program() {
System.out.println( name +">>在编程");
}
@Override
public String toString() {
return "Person [name=" + name + "]";
}
}
监听器
package patternDemo.Person;
/* 观察(监听)者的父类
*/
public interface IPersonActionListener {
public abstract void runAction( PersonActionEvent e );
public abstract void eatAction( PersonActionEvent e );
}
事件对象
package patternDemo.Person;
/*
* 事件源---封装事件源与其相关信息
*/
public class PersonActionEvent {
private Person p;
public PersonActionEvent(Person p) {
this.p = p;
}
public Object getSource() {
return p;
}
}
而我们在开发时使用对方开发的Person时,只要对方提供相应方法的监听器,我们实现了,就可以监听到Person所做的一些动作。
package patternDemo.test;
import patternDemo.Person.IPersonActionListener;
import patternDemo.Person.Person;
import patternDemo.Person.PersonActionEvent;
public class Test {
public static void main(String[] args) {
Person p = new Person("Tom"); //事件源
//注册一个监听器
p.addIPersonActionListener( new IPersonActionListener() {
@Override
public void runAction(PersonActionEvent e) {
System.out.println("我看到"+e.getSource()+"要开始跑步了");
}
@Override
public void eatAction(PersonActionEvent e) {
System.out.println("我看到"+e.getSource()+"要开吃饭了");
}
});
//当事件源做出一些动作时
p.run();
p.program(); //Person做这个动作时,不给外部提供监听
p.eat();
}
}
一个小案例---统计网站访问数量
需求:
-
记录一个网站的访问量;
-
当服务器关闭时,把访问数量的值保存到文件中或是数据库中去;
-
当服务器启动时,先从文件中读取并放到ServletContext;
解决方案:
- 在Filter中记录访问量,每次访问都加1;
- 在为不影响用户的速度感受,应该开始一个新的线程同去操作数据;
- 这样即使在后台使用同步技术,用户也不会感觉到速度很慢。
ServletContextListener接口的实现类
当项目启动或者关闭时的监听器,在启动后把存储的数据重新加载到内存中,在关闭前进行数据存储。
package cn.hncu.listener;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class MyServletContextListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent sce) {
System.out.println("ServletContextListener,初始化...");
//应该从本地磁盘读取出访问数量,继续记录
ServletContext sctx = sce.getServletContext();
String path = sctx.getRealPath("/WEB-INF/count.txt");
BufferedReader br = null;
try {
br = new BufferedReader( new FileReader(path));
String strCount = br.readLine();
Integer count =0;
try {
count = Integer.valueOf(strCount);
} catch (NumberFormatException e) {
System.err.println("谁改了/WEB-INF/count.txt文件!!!");
e.printStackTrace();
}
sctx.setAttribute("count", count);
} catch (FileNotFoundException e) {
//e.printStackTrace();
//如果出现异常说明项目是第一次初始化,即直接给ServletContext中放一个count等于0
sctx.setAttribute("count", 0);
} catch (IOException e) {
System.err.println("项目初始化异常...请联系维护人员!!!");
e.printStackTrace();
} finally {
if( br != null ) { //关流防止内存泄漏
try {
br.close();
} catch (IOException e) {
System.err.println("项目初始化异常...请联系维护人员!!!");
e.printStackTrace();
}
}
}
}
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("MyServletContextListener,销毁了...");
//项目关闭时应该 把 count 存到 '/WEB-INF/count.txt' 中
ServletContext sctx = sce.getServletContext();
String path = sctx.getRealPath("/WEB-INF/count.txt");
PrintWriter writer = null;
try {
writer = new PrintWriter( path );
writer.println("" + sctx.getAttribute("count") );
} catch (FileNotFoundException e) {
System.err.println("项目停止时存储'/WEB-INF/count.txt'数据出现异常...请联系维护人员!!!");
e.printStackTrace();
} finally {
if( writer != null ) { //关流防止内存泄漏
writer.close();
}
}
}
}
Filter接口的实现类
该过滤器用来统计当前项目的访问次数,为保证数据的安全性和效率,采用多线程+同步块的形式进行计数。
package cn.hncu.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class AccessCountFilter implements Filter {
public void init(FilterConfig fConfig) throws ServletException {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//这里声明成常量,因为匿名局部内部类只能访问外部类的成员变量或者外部方法域中的常量
final ServletContext sctx = request.getServletContext();
//这里 有个 知识点:多用户访问时,必须采用多线程+同步块的形式进行数据统计。
//多线程保证效率;同步块保证多用户共享一个数据时,数据的准确性。
new Thread() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+">>"+ sctx.hashCode() );
//静态方法,锁对象为 AccessCount类模板对象
AccessCount.add(sctx);
};
}.start();
chain.doFilter(request, response); //放行
}
public void destroy() {
}
/* //该方式是WA的
static class AccessCount{
//把AccessCount类模板对象当锁
public synchronized static void add( ServletContext sctx ) {
Object objCount = sctx.getAttribute("count");
Integer count = Integer.valueOf( objCount.toString() );
count++;
sctx.setAttribute("count", count);
}
}
*/
}
//注意 锁对象 不能是内部类 : 见测试类 TestSynchronized类
class AccessCount{
//把AccessCount类模板对象当锁
public synchronized static void add( ServletContext sctx ) {
Object objCount = sctx.getAttribute("count");
Integer count = Integer.valueOf( objCount.toString() );
count++;
sctx.setAttribute("count", count);
}
}
web.xml中过滤器和监听器的配置
<filter>
<filter-name>accessCountFilter</filter-name>
<filter-class>cn.hncu.filter.AccessCountFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>accessCountFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>cn.hncu.listener.MyServletContextListener</listener-class>
</listener>