1. 程式人生 > >Servlet的監聽器Listener(流量統計)

Servlet的監聽器Listener(流量統計)

監聽器:

•監聽器-就是一個實現待定介面的普通Java程式,此程式專門用於監聽別一個類的方法呼叫。 •都是使用觀察者設計模式。 •什麼是觀察者模式: •定義物件間一對多的依賴關係,當一個物件的狀態發生改變時,所有依賴於它的物件都得到通知自動更新。 •示例: •GUI程式設計中的addXxxxListener都是觀察者模式。

觀察者模式的三個重要類:

觀察者設計模式示例:

開發步驟:
 * 第一步:實現一個需要被監聽的類Person.
 * 第二步:實現一個監聽介面PersonListener。
 * 第三步:在Person類中,提供一個方法用於註冊PersonListener類,即registerListener
 * 第四步:必須要在Person類中維護PersonListener類的例項。
 * 第五步:在呼叫person.eat方法是,判斷PersonListener是否為null,如果不為null則呼叫它的eating方法。
 * 第六步:在Main類中,例項化Person,並註冊一個監聽。

給觀察者模式-新增事件源:

開發步驟:
第一步:在前頁的基礎上繼續新增一個PersonEvent類(注意我說是類不是介面),代表事件對像。
第二步:給PersonEvent對像,新增一個Object屬性,用以標識事件源對像。
第三步:修改PersonListener介面的eating方法,讓它接收一個PersonEvent引數。
第四步:在Person類eat方法中,如果判斷PersonListener屬性不為空,則在呼叫eating方法,例項化PersonEvent並傳給eating方法。
第五步:在main方法中,通過PersonEvent的getSource方法測試是否是同一個對像。


監聽示例1-監聽ServletContext的建立和銷燬:

開發步驟:
第一步:實現ServletContextListener介面。
第二步:實現兩個方法。
contextInitialized
contextDestroyed
第三步:在web.xml中新增<listener/>節點。
這一點與swing中的新增監聽有所區別。
第四步:測試
1、釋出專案啟動。
2、通過Tomcat管理控制檯停止此專案。

監聽器2:-監聽ServletContext上的屬性變化:

ContextListener應用場景:

•記錄一個網站的重新整理量。 •當伺服器關閉時,必須要儲存到檔案中或是資料庫中去。
•當伺服器啟動時,先從檔案中讀取並放到ServletContext。 •在Filter中記錄訪問量,每次訪問都加1。 •好處:資訊不是太重要,沒有必要每次使用者訪問都訪問資料庫或是操作檔案。 •在為不影響使用者的速度感受,應該開始一個新的執行緒同去操作資料。  這樣即使在後臺使用同步技術,使用者也不會感覺到速度很慢。

canListenerWeb

先來認識一下,純java中的監聽器:

MyJFrame.java                  JFrame中,按鈕點選事件就是一個監聽器。

package demo.hello;

import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;

public class MyJFrame extends JFrame{
	private MyJFrame(){
		setBounds(200, 200, 200, 300);
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		getContentPane().setLayout(new FlowLayout());
		final JButton btn=new JButton("按鈕");
		getContentPane().add(btn);
		btn.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
//				if(e.getSource()==btn){
//					System.out.println("aaaaaaa");;
//				}
				if(e.getSource() instanceof JButton){
					System.out.println("aaaaaaa");;
				}
			}
		});
		setVisible(true);
	}
	public static void main(String[] args) {
		new MyJFrame();
	}
}


Person.java

package demo.listenerPattern.one;
/*
 * 被監聽者
 * run方法放開讓別人監聽(觀察)
 */
public class Person {
	private String name;
	private IPersonListener listener;//如果這裡允許新增多個監聽,則宣告成集合List<IPeronListener>
	public Person(String name) {
		this.name = name;
	}
	public void run(){
		if(listener!=null){
			listener.runBeforeReaction();
		}
		System.out.println(name+"is  running.....");
		System.out.println(name+"is  running.....");
		System.out.println(name+"is  running.....");
		if(listener!=null){
			listener.runAfterReaction();
		}
	}
	//給外面註冊監聽器
	public void addActionListener(IPersonListener listener){
		this.listener=listener;
	}
}

IPersonListener.java

package demo.listenerPattern.one;
/*
 * 監聽器
 * 實現該介面的類物件是監聽者
 */
public interface IPersonListener {
	public abstract void runBeforeReaction();
	public abstract void runAfterReaction();
}


Client.java

package demo.listenerPattern.one;

public class Client {
	public static void main(String[] args) {
		Person p=new Person("小天");
		p.run();
		System.out.println("---------------------");
		p.addActionListener(new IPersonListener() {
			@Override
			public void runBeforeReaction() {
				System.out.println("小天加油。。。快跑.....");
			}
			@Override
			public void runAfterReaction() {
				System.out.println("小天你竟然跑完了。。。厲害啊.....");
			}
		});
		p.run();
	}
}

註冊事件則監聽,否則不監聽

加入源事件:

Cat.java

package demo.listenerPattern.two;

public class Cat {
	private String name;
	private ICatListener listener;
	
	public Cat(String name) {
		this.name=name;
	}
	public void climb(){
		if(listener!=null){
			CatEvent e=new CatEvent(this);
			listener.climbReaction(e);
		}
		System.out.println("a  cat  is  climbing! his name is "+name);
	}
	public void addActionListener(ICatListener listener){
		this.listener=listener;
	}
	public String getName() {
		return name;
	}
	
}

ICatListener.java

package demo.listenerPattern.two;

public interface ICatListener {
	public void climbReaction(CatEvent e);
}
class CatEvent{
	private Cat cat;
	public CatEvent(Cat cat) {
		this.cat=cat;
	}
	public Object getSource(){
		return cat;
	}
	public String getName(){
		return cat.getName();
	}
}

Client.java

package demo.listenerPattern.two;

public class Client {
	public static void main(String[] args) {
		Cat cat=new Cat("ABC");
		cat.climb();
		System.out.println("---------------------");
		cat.addActionListener(new ICatListener() {
			@Override
			public void climbReaction(CatEvent e) {
				System.out.println("Look! there is a big fat cat!");
				System.out.println(e.getSource());
			}
		});
		cat.climb();
	}

}


在JavaWeb中做監聽器:

這裡監聽了ServletContext的建立和銷燬和ServletContext上的屬性變化,做了一個訪問流量統計的小應用,並且把訪問量寫到了本地硬碟的一個檔案中永久儲存,不會因專案停止,訪問量就歸零了。

穿越整個軟體的生命週期:Filter+ServletContextListener(訪問量)

index.jsp

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
  </head>
  
  <body>
  <h1>這是主頁</h1>
  <h2>等下做訪問量的應用</h2>
  訪問量:${count }<br/>
  <a href="<c:url value='/jsps/a.jsp' />">另一個介面</a>
   <%
	   	application.setAttribute("name", "Jack");
	   	application.setAttribute("age", "22");
    %>
  </body>
</html>


CountFilter.java

package cn.hncu.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

public class CountFilter implements Filter{

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		//這裡統計訪問量只是一個小小的應用,如果說在這裡做統計,在多執行緒的情況下,容易出錯,應再開一個執行緒專門用來統計訪問量
		final HttpServletRequest httpReq=(HttpServletRequest) request;
//		new MyThread(httpReq).start();錯誤的解決方法
		new Thread(){
			public  void run() {
				AddCount.addCount(httpReq);
			};
		}.start();
		
		chain.doFilter(request, response);
	}

	@Override
	public void destroy() {
	}
}
class AddCount{
	public synchronized static void addCount(HttpServletRequest req){
		Integer count=Integer.parseInt(""+req.getServletContext().getAttribute("count"));
		count++;
		req.getServletContext().setAttribute("count", count);
	}
}
/*
 * 反模式:雖然同樣加了鎖,但是並不是同一個物件,所以加鎖沒有成功
 */
class MyThread extends Thread{
	private HttpServletRequest req;
	public MyThread(HttpServletRequest req){
		this.req=req;
	}
	@Override
	public void run() {
		synchronized (this) {
			Integer count=Integer.parseInt(""+req.getServletContext().getAttribute("count"));
			count++;
			req.getServletContext().setAttribute("count", count);
		}
	}
}

a.jsp

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
  </head>
  
  <body>
  <h1>這是另外一個網頁</h1>
  訪問量:${count }<br/>
   <%
	   	application.setAttribute("name", "Tom");//修改屬性
	   	application.removeAttribute("age");//刪除屬性
    %>
  </body>
</html>

MyContextListener.java

package cn.hncu.listener;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
/*
 * servlet監聽器開發步驟:
 * 1.寫一個類實現XXXListener介面(6個=3個容器+3個容器中的屬性操作)
 * 2.在web.xml中配置<listener>----規範:一般寫在<Filter>和<servlet>之間
 */

public class MyContextListener implements ServletContextListener{

	@Override
	public void contextInitialized(ServletContextEvent sce) {
		System.out.println("專案初始化,讀取訪問量.....");
		//count.text
		try {
			String fileName=sce.getServletContext().getRealPath("/count.txt");//專案根目錄的絕對路徑
			BufferedReader br=new BufferedReader(new FileReader(fileName));
			String srcCount=br.readLine();
			Integer count=Integer.valueOf(srcCount);
			
			sce.getServletContext().setAttribute("count", count);
		} catch (Exception e) {
			System.out.println("專案初始化讀取沒有點選量,出異常");
			sce.getServletContext().setAttribute("count", 0);
		}
	}

	@Override
	public void contextDestroyed(ServletContextEvent sce) {
		System.out.println("專案關閉了,儲存點選量......");
		String fileName=sce.getServletContext().getRealPath("/count.txt");//專案根目錄的絕對路徑
		try {
			//由於要把點選量原樣寫到本地檔案儲存起來,所以要用到PrintWriter,不然再次讀取時會掛掉(轉換不成整數了)
			PrintWriter pw=new PrintWriter(fileName);//IO中要用絕對路徑
			pw.println(sce.getServletContext().getAttribute("count"));
			pw.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}

MyContextAttributeListener.java

package cn.hncu.listener;

import javax.servlet.ServletContextAttributeEvent;
import javax.servlet.ServletContextAttributeListener;

public class MyContextAttributeListener implements ServletContextAttributeListener{
	@Override
	public void attributeAdded(ServletContextAttributeEvent scae) {
		System.out.println("這裡添加了一個屬性:"+scae.getName()+"--"+scae.getValue());
	}

	@Override
	public void attributeRemoved(ServletContextAttributeEvent scae) {
		System.out.println("這裡移除了一個屬性:"+scae.getName()+"--"+scae.getValue());
	}

	@Override
	public void attributeReplaced(ServletContextAttributeEvent scae) {
		System.out.println("這裡修改了一個屬性:"+scae.getName()+"--"+scae.getValue());
	}

}


web.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" 
	xmlns="http://java.sun.com/xml/ns/javaee" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
	http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
  <display-name></display-name>	
  <listener>
  	<listener-class>cn.hncu.listener.MyContextListener</listener-class>
  </listener>
  <listener>
  	<listener-class>cn.hncu.listener.MyContextAttributeListener</listener-class>
  </listener>
  <filter>
  	<filter-name>CountFilter</filter-name>
  	<filter-class>cn.hncu.filter.CountFilter</filter-class>
  </filter>
  <filter-mapping>
  	<filter-name>CountFilter</filter-name>
  	<url-pattern>/*</url-pattern>
  </filter-mapping>
  
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>

設計思想:一個專案已經完成了,寫死了,如果再想加程式碼進去,就用監聽者模式!