12月26日——培训第32天

来源:互联网 发布:广电网络股票吧 编辑:程序博客网 时间:2024/05/16 10:16

What do you know about the mutation? mutation, it is the key to our evolution, it is how we
have evolved from a single celled organism into the dominant species on the planet , this
process is slow , normally taking thousands and thousands of years , but every few hundred
millenia, evolution leaps forward . Is that amazing ? Are we now on the golden age where
the evolution leaps forward ? Crazy idea, isn't it ?

You believe in it ? The mutation? I believe most of you won't give a shit , but who will give
a shit except those hardworking,enthusiastic scientist? We live in reality , not fairyland.


-----------------------------------------------------------------------------------------------
上午课程开始:

过滤器:
Filter技术是servlet2.3新增加的功能,

javax.servlet.Filter

destroy();
doFilter(ServletRequest request,ServletResponse response,FilterChain chain)
init(FilterConfig filterConfig)
FilterChain中的doFilter方法会转向下一个过滤器或是目标资源
新建一个Filter:

比如登陆的校验功能可以放到过滤器中

new一个class,名字叫做LoginFilter

public class LoginFilter implements Filter
{
 public void destroy()
 {}
 public void init(FilterConfig arg0) throws ServletException
 {}
 
 public void doFilter(ServletRequest arg0,ServletResponse arg1,FilterChain chain)
  throws IOException,ServletException
 {
  HttpServletRequest request = (HttpServletRequest)arg0 ;
  HttpServletResponse response = (HttpServletResponse)arg1 ;
  HttpSession session = request.getSession() ;

  if(session.getAttribute("user")!=null)//证明已经登陆过了
  {
   chain.doFilter(request,response); //跳到下一个过滤器,如果没有下一个过滤器,就跳到目标资源去
  }
  else
  {
   request.getRequestDispatcher("/index.html").forward(request,response);
  }
 }
}

现在的问题是要映射地址,告诉服务器这个过滤器过滤的是谁,需要向容器进行注册!

web-app标签元素里面可以嵌套filter和filter-mapping元素

<filter>
 <filter-name>loginFilter</filter-name>
 <filter-class>cn.itcast.filter.LoginFilter</filter-class>
</filter>

<filter-mapping>
 <filter-name>loginFilter</filter-name>
 <url-pattern>/welcome.jsp</url-pattern> //说明要过滤哪个页面
</filter-mapping>

<filter-mapping>
 <filter-name>loginFilter</filter-name>
 <url-pattern>/b.jsp</url-pattern> //说明要过滤哪个页面
</filter-mapping>

<filter-mapping>
 <filter-name>loginFilter</filter-name>
 <url-pattern>/c.jsp</url-pattern> //说明要过滤哪个页面
</filter-mapping>
//可以通过设置很多的filter-mapping 来达到过滤多个页面的目的!

<filter-mapping>
 <filter-name>loginFilter</filter-name>
 <url-pattern>/limit/*</url-pattern> //说明要过滤工程文件夹下的limit文件夹下的所有资源!
</filter-mapping>
*/
---------------------------------------------------
我自己写的一个过滤器小例子:

登陆表单页面ip.jsp:


<%@ page language="java" import="java.util.*" pageEncoding="GBK"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <base href="<%=basePath%>">
   
    <title>My JSP 'ip.jsp' starting page</title>
   
    <meta http-equiv="pragma" content="no-cache">
    <meta http-equiv="cache-control" content="no-cache">
    <meta http-equiv="expires" content="0">
    <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
    <meta http-equiv="description" content="This is my page">
   
    <!--
    <link rel="stylesheet" type="text/css" href="styles.css">
    -->
  </head>
 
  <body>
     <form action="/ServletReload/servlet/MyServlet" method="post">
   用户名<input type="text" name="username"/><br>
   密  码<input type="password" name="password"/><br>
   <input type="submit" value="提交"/>
  </form>
  </body>
</html>


MyServlet:

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class MyServlet extends HttpServlet {

 /**
  * The doGet method of the servlet. <br>
  *
  * This method is called when a form has its tag value method equals to get.
  *
  * @param request the request send by the client to the server
  * @param response the response send by the server to the client
  * @throws ServletException if an error occurred
  * @throws IOException if an error occurred
  */
 public void doPost(HttpServletRequest request, HttpServletResponse response)
   throws ServletException, IOException {
 
  response.setContentType("text/html;charset=GBK");
  PrintWriter out = response.getWriter();
  String username = request.getParameter("username");
  String password = request.getParameter("password");
  System.out.println(username);
  System.out.println(password);
  if("yuan".equals(username)&&"yuan".equals(password))
  {
   System.out.println("dfsjkalfjla;sfj");
   HttpSession session = request.getSession();
   session.setAttribute("user","true");
   //response.sendRedirect("/ServletReload/limit/Success.html");
   response.sendRedirect("../limit/Success.html"); //返回上一级目录
   return ;
  }
  response.sendRedirect("/ServletReload/ip.jsp") ;

  
  out.close();
  

 }

}


过滤器:

package itcast.cn;

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;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class LoginFilter implements Filter{

 public void destroy() {
  // TODO Auto-generated method stub
  
 }

 public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) throws IOException, ServletException {
  // TODO Auto-generated method stub
  HttpServletRequest request = (HttpServletRequest)arg0;
  HttpServletResponse response = (HttpServletResponse)arg1;
  
  HttpSession session = request.getSession();
  String result = (String)session.getAttribute("user");
  if("true".equals(result))
  {
   arg2.doFilter(request,response);
  }
  else
  {
   response.sendRedirect("/ServletReload/ip.jsp");
  }
 }

 public void init(FilterConfig arg0) throws ServletException {
  // TODO Auto-generated method stub
  
 }
 
}

 

我的web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
 xmlns="http://java.sun.com/xml/ns/j2ee"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
 http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
  <servlet>
    <description>This is the description of my J2EE component</description>
    <display-name>This is the display name of my J2EE component</display-name>
    <servlet-name>MyServlet</servlet-name>
    <servlet-class>MyServlet</servlet-class>
  </servlet>
  <servlet>
    <description>This is the description of my J2EE component</description>
    <display-name>This is the display name of my J2EE component</display-name>
    <servlet-name>LoginServlet</servlet-name>
    <servlet-class>LoginServlet</servlet-class>
  </servlet>
  <servlet>
    <description>This is the description of my J2EE component</description>
    <display-name>This is the display name of my J2EE component</display-name>
    <servlet-name>TestServlt</servlet-name>
    <servlet-class>TestServlt</servlet-class>
  </servlet>


  <filter>
 <filter-name>LoginFilter</filter-name>
 <filter-class>itcast.cn.LoginFilter</filter-class>
  </filter>
  <filter-mapping>
   <filter-name>LoginFilter</filter-name>
 <url-pattern>/limit/*</url-pattern>     */
  </filter-mapping>
 
  <servlet-mapping>
    <servlet-name>MyServlet</servlet-name>
    <url-pattern>/servlet/MyServlet</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>LoginServlet</servlet-name>
    <url-pattern>/servlet/LoginServlet</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>TestServlt</servlet-name>
    <url-pattern>/servlet/TestServlt</url-pattern>
  </servlet-mapping>

</web-app>


--------------------------------------------------------------------------------------
ServletResponse里面有一个getWriter还有一个getOutputStream,
二者的区别:前者发送字符,后者发送二进制码,两者都可以向客户端输出,但是你绝对不可以同时使用二者!!

JspWriter和PrintWriter有什么区别?JspWriter多了一个Buffering的功能,jsp中的out.println其实是
写在了缓存里面,直到缓存被刷新或者缓存满了才调用PrintWriter,或者页面结束后容器去调用PrintWriter将
缓存中的JspWriter中的内容刷到PrintWriter中,然后输出到页面

所以容器在页面的结尾肯定会调用response.getWriter()方法得到PrintWriter!

request.getRequestDispatcher("index.html").forward(request,response);

在服务器上访问html静态资源的时候,是有一个Servlet帮你做了映射,conf里面的web.xml中:
里面有一个叫做default的servlet,url-pattern是/ , 它用来处理没有映射到任何servlet mapping
的资源,它帮助读出指定的html静态资源,然后一行一行的用outputStream来写出去。

 

------------------------------------------
这里有一个问题你要特别的小心:
在jsp中如果使用request请求转发的话,一定记住在forward方法的下一行加入
if(true)
 return ;
否则会由于getWriter和getOutputStream方法出现冲突会出现错误!!!

还有在Filter中不可以使用PrintWriter,否则也可能会有冲突!!!

-------------------------------------------
张老师来为前些日子的课程向我们道歉,
为了了解我们的实际情况以及更好的展开课程,周四要进行考试,作一个论坛,作一天。

有注册界面

留言界面需要使用框架,用上下框架,下面是供用户输入标题和正文的,然后有一个标题按钮来提交,
上面的框架用来显示留言,

用户注册、用户登陆、用户发贴、查看帖子列表、查看帖子,
后台是有删除帖子、封帐号、(要求最好使用hibernate)

界面原型图、设计数据库的表、编写各个用例

其中有一些小细节的雕琢,会在做完之后统一点评。

这个论坛里面的实体:用户和文章留言,

张老师关于JSP的重点提炼:
1 jsp运行原理、9个内置对象以及作用、了解jsp表达式、jsp脚本片断、jsp声明、jap注释、el表达式简介
2 jsp中的作用、page指令以及其几个重要的属性:import、session、page、isErrorPage、contentType、pageEncoding、
isELIgnored、在web.xml文件中设置全局错误处理,jsp的include指令以及其典型应用
3 pageContext对象的详细讲解。

 

---------------------------------------------------------
事件监听器:提供对ServletContext、HttpSession以及ServletRequest三种对象生命周期的管理

1 监听的对象
3种作用域,
2 监听的事件
生命周期,属性变化

每个作用域对应一个监听器,每个监听器要监听两个事件

Session:每个用户都有一个session,当session太多的时候,也就是把内存占满了的时候,当再有人请求session的时候,
服务器会把一个最近最少使用(Least Use Recently)的Session序列化到外存(存到硬盘中去)中去,也就是钝化。其实
就是虚拟内存的概念,但是如果钝化的session用户又来了的话,还得再把外存中的这个session装载到内存中还原为对象,这叫做
激活

Passivate :钝化
Activate : 激活

如果被钝化的session长时间没有使用的话,是不可以长时间的驻留外存中的,它也有个失效的时间,到时也会被清除掉。
所以HttpSessionActivationListener就是用来监听是否被激活。

监听器工作原理:当发生了某件事情的时候,会调用监听器的相应方法。

服务器启动,web程序部署成功后ServletContext就存在了。
ServletContextListener :监听初始化和销毁事件
ServletContextAttributeListener :

下面手写一个监听器:

package cn.itcast.listener;

public class MyListener implements ServletContextListener //生命周期的监听
{

 public void contextDestroyed(ServletContextEvent arg1)
 {
  System.out.println("in listener");
  System.ou.println("__________________________________");
 }

 public void contextInitialized(ServletContextEvent arg0)
 {
  System.out.println("in listener");
  System.ou.println("__________________________________");
 }
}

在工程的web.xml中加上如下代码来注册监听器:
<listener>
 <listener-class>cn.itcast.listener.MyListener</listener-class>
</listener>

注意:对于一个作用域只能创建一个监听器,也就是如果创建一个session的监听器那么它将监听所有session的事件,不可能
单独针对一个session,所以不需要什么listener-mapping之类来映射。

当然我也可以在我写的监听器中同时继承多个作用域的监听器,实现定义一个监听器监听多个作用域
-------------------------------------------------
HttpSessionActivationListener中
sessionDidActivate方法在激活(调回资源)之后调用,sessionWillPassivate在钝化(释放资源)之前调用

HttpSessionBindingListener:其实是和ActivationListener一样的,只是为了兼容而存在。
valueBound和valueUnbound

--------------------------------------------------------

周四和周五两天将是张老师带着我们作一下简单的论坛,主要实现留言板的功能,田老师休息,周六周日周一周二四天元旦
休息,再加上明天周三,田老师将会一直休息一个礼拜……
-------------------------------------------------------
下午课程开始:
IllegalStateException:在同一个响应中,当getOutputStream和getWriter二者同时调用的时候会抛出这个异常!

try
{
 ostream = response.getOutputStream();

}
catch(IllegalStateException e)
{
 //if it fails,we try to get a Writer instead if we're trying to serve a text file
 if( (contentType==null)||(contentType.startsWith("text")) )
 {
  writer = response.getWriter();
 }
 else
 {
  throw e ;
 }
}

------------------------------------------------------------------------------------------
作在线分析,看都有哪些IP地址访问了本网站,而不是只访问了本网站中的某一个网页!而且还要记录访问的次数和浏览的次数
而且如何去封杀一个IP,比如一秒内访问网站1000次的ip是肯定的恶意攻击,必须封杀掉。
还有如何去判断一个IP地址是否在线呢??(这个只能通过session是否有效来判断,你根本没办法判断用户是否就真的在网上在线浏览
,因为BS模式实质上所有人都是离线的,只有发送请求的时候才连接)

有的网站有个1秒内的最大访问量问题,压力测试就是在1秒内可以最大经受的访问量
可以用过滤器,过滤所有网站上的网页,只要有人访问任何一个网页,那么过滤器就记录其ip……
但是优化是个问题,如果每个网页都这么记录ip的话可就麻烦了,如果用户太多很快就撑不住了。
封杀ip:尽可能让恶意ip少占用我的资源,软件封杀ip肯定比不上硬件防火墙。
而且软件封杀ip可能还得在数据库中保存……这样就比较麻烦了

主要使用过滤器和监听器完成这件事情。

新建一个工程叫做ip,首先在数据库中建表ips
ip:varchar(15)
visitTime: DATETIME
visitTimes: integer(访问次数)
id:integer(主键、自增)
times: integer(浏览次数)
online: tinyint(是否在线)   1代表在线(默认值为1)
permit: tinyint(是否被封杀) 1代表不被封杀(默认值为1)
country: varchar(45)
language:varchar(45)
notes: varchar(45) 备注

这里注意!visitTimes是session的个数,就是说,我使用同样的ip在这个网站先后有两个会话,
我的visitTimes就是2,但是times是请求的次数!也就是visitTimes记录的是session的个数!

注意将country和language的字段编码改为gbk

代码如下:

首先,cn.itcast.util包里面建一个class
public final class Constants
{
 public static final String FORBIDDEN_IP = "TITAN_FORBIDDEN_IP" ;
 public static final String COUNTER_ATTR_NAME = "TITAN_COUNTER_ATTR_NAME";
}//专门定义常量的类,不允许别人扩展继承

public class Counter implements java.io.Serializable   //计数器类
{
 private int count = 1; //初值为1,第一次访问的时候访问次数就是1!
 private String ip;
 //ip的getter和setter方法这里省略!
 
 public Counter(String ip)
 {
  this.ip = ip ;
 }

 public void increase()
 {
  count ++ ;
 }

 public void decrease()
 {
  count -- ;
 }

 public int getCount()
 {
  return count ;
 }
}

 

新建一个cn.itcast.filter包,类叫做IpFilter

public class IpFilter implements Filter
{
 private FilterConfig config ;

 public void init(FilterConfig arg0)
 {
  config = arg0;
 }

 public void doFilter(ServletRequest arg0,ServletResponse arg1,FilterChain chain)
  throws ServletException,IOException
 {
  HttpServletRequest request = (HttpServletRequest)arg0 ;
  HttpServletResponse response = (HttpServletResponse)arg1 ;

  Connection conn = null ;
  PreparedStatement pstmt = null ;
  ResultSet rs = null ;
  
  HttpSession session = request.getSession();//到session中来找counter,看该ip是否来过
  Counter counter = (Counter)session.getAttribute(Constants.COUNTER_ATTR_NAME);
  if(counter!=null)//如果不是第一次访问的话,counter绝对不是null,这样我只更新计数器就可以,最后在session销毁的时候我再一并更新数据库!
  {
   counter.increase(); //如果该ip确实来过的话,那么肯定session里面有了计数器值
   chain.doFilter(request,response); //上面已经子增了一次了
   return ; //不加这句话的话仍然会继续执行下面的代码!!!
  }
  
  String ipAddr = request.getRemoteAddr();//取出请求的IP,取出IP后立刻得去数据库中的permit字段里面看是不是为0,为0的话
            //就封杀了,被封杀的ip应该在程序启动的时候加载到ServletContext的作用域中,这可以
            //通过ServletContext的listener来实现,在启动服务器的时候,监听事件就把数据库中
            //记录的封杀ip给拿出来存到application作用域里面
  ServletContext context = config.getServletContext();
  
  ArrayList forbiddenIps = (ArrayList)context.getAttribute(Constants.FORBIDDEN_IP);
  if(forbiddenIps.contains(ipAddr))//证明请求的ip是被封杀的ip
  {//contains方法用于判断集合中是否存在某指定的对象,有的话就返回true!
   response.sendError(HttpServletResponse.SC_FORBIDDEN,"你的IP已经被封杀了……!");
  //sendError(int sc,String msg) sc是状态码,比如404、500等(400到499是客户端错误,500到599是服务器错误)
   return; //下边就什么都不用处理了,直接return就可以了。
  }
  try //如果不是封杀的ip访问的话,执行下面的代码:
  {
   Class.forName("com.mysql.jdbc.Driver");
   conn = DriverManager.getConnection("jdbc:mysql:///j2ee?useUnicode=true&characterEncoding=gbk","root","root");
  //为了解决国家和语言字段的中文问题,必须这么写url
   pstmt = conn.prepareStatement("select * from ips where ip=?"); //找这个ip在数据库中是否存在!
   pstmt.setString(1,ipAddr);

   rs = pstmt.executeQuery();
   if(rs.next())//有的话就更新,没有的话就插入!
   {
    pstmt = conn.prepareStatement("update ips set times=times+1,visitTimes=visitTimes+1,online=1"+
     "visitTime=?,country=?,language=? where ip=?");
   }
   else
   {
    pstmt = conn.prepareStatement("insert into ips(times,visitTimes,online,visitTime,country,language,ip)"+
     "values(1,1,1,?,?,?,?)");
   }
   pstmt.setTimestamp(1,new Timestamp(System.currentTimeMillis()));
   pstmt.setString(2,request.getLocale().getDisplayCountry());
   pstmt.setString(3,request.getLocale().getDisplayLanguage());
   pstmt.setString(4,ipAddr);

   if(pstmt.executeUpdate()!=1)//上述无论是更新还是插入都会影响一行,如果不能影响一行或是影响了多行的话,肯定是出错了!
   {
    throw new SQLException("not only 1 row infected");
   }
   session.setAttribute(Constants.COUNTER_ATTR_NAME,new Counter(IpAddr)) ;
   session.setMaxInactiveInterval(10); //把session的失效时间设的小一点
  }
  catch(Exception e)
  {
   e.printStackTrace();
  }
  finally 
  {
   if(conn!=null)
   {
    conn.close();
    conn = null ;
   }
  }
  chain.doFilter(request,response) ;
 }

}


监听器:
package cn.itcast.listener;

public class IpServletContextListener implements ServletContextListener
{
 //除了这个方法外还可以继承一个销毁ServletContext的方法,这里省略了!
 public void contextInitialized(ServletContextEvent arg0)
 {
  Connection conn = null ;
  PreparedStatement pstmt = null ;
  ResultSet rs = null ;

  
  try
  {
   Class.forName("com.mysql.jdbc.Driver");
   conn = DriverManager.getConnection("jdbc:mysql:///j2ee?useUnicode=true&characterEncoding=gbk","root","root");
  //为了解决国家和语言字段的中文问题,必须这么写url
   pstmt = conn.prepareStatemnet("select ip from ips where permit=0");
   rs = pstmt.executeQuery();

   ArrayList a = new ArrayList();
   while(rs.next())
   {
    a.add(rs.getString(1));
   }
   ServletContext context = arg0.getServletContext();
   context.setAttribute(Constants.FORBIDDEN_IP,a); //将被封杀IP的集合给塞到应用程序作用域的对应属性中!
  }
  catch(Exception e)
  {
   e.printStackTrace();
  }
  finally 
  {
   if(conn!=null)
   {
    conn.close();
    conn = null ;
   }
  }
 }
}

我还需要一个Session的监听器!用于在session被销毁前把该ip的所有数据写回到数据库中!主要就是更新访问的次数!

public class CounterSessionListener implements HttpSessionListener,HttpSessionActivationListener
{
 public void sessionDestroyed(HttpSessionEvent arg0)
 {
  HttpSession session = arg0.getSession(); //找到监听的对象
  Counter counter = (Counter)session.getAttribute(Contants.COUNTER_ATTR_NAME);

  Connection conn = null ;
  PreparedStatement pstmt = null ;
  ResultSet rs = null ;

  
  try
  {
   Class.forName("com.mysql.jdbc.Driver");
   conn = DriverManager.getConnection("jdbc:mysql:///j2ee?useUnicode=true&characterEncoding=gbk","root","root");
  
   pstmt = conn.prepareStatemnet("update ips set times=times+? where ip=?");
   pstmt.setInt(1,counter.getCount()); //从计数器中得到访问次数!
   pstmt.setString(2,counter.getIp()); //从计数器中得到IP字符串!
   pstmt.executepdate();   
  }
  catch(Exception e)
  {
   e.printStackTrace();
  }
  finally 
  {
   if(conn!=null)
   {
    conn.close();
    conn = null ;
   }
  }
 }
}

 

注意这里涉及到一个Session的钝化问题!


别忘了过滤器和监听器都需要配置!还要导包!
在web.xml中加入以下代码:

<listener>
 <listener-class>cn.itcast.listener.IpServletContextListener</listener-class>
</listener>
<listener>
 <listener-class>cn.itcast.listener.CounterSessionListener</listener-class>
</listener>

<filter>
 <filter-name>ipFilter</filter-name>
 <filter-class>cn.itcast.filter.IpFilter</filter-class>
</filter>

<filter-mapping>
 <filter-name>ipFilter</filter-name>
 <url-pattern>/*</url-pattern>                                                             */
</filter-mapping>


其实可以在销毁session时写数据库,也可以在计数器的计数到达100的时候写一次数据库,也可以使用Timer定时器
来定时写数据库。

是否在线主要是session销毁或者钝化时都是不在线了,我如果要执行封杀动作的话
首先要把permit置为0,然后作用域中的集合里面加上一个ip!

如果要执行解封的话,就是相反的过程,先把permit置为1,然后集合里面remove掉它!

如果要作柱状图的话,就是表格里面图画比例的控制问题了!

晚上主要考虑一下封杀、解封动作以及在线和不在线的人的显示(表格的底色显示明暗相间),还可以加入查询功能,比如查询所有在线的人、
查询所有不在线的人等等(使用下拉列表框)


=====================================================================================================================
=====================================================================================================================
=====================================================================================================================
=====================================================================================================================
=====================================================================================================================
=====================================================================================================================
=====================================================================================================================
上面的代码是我在课堂上随着老师一起记录的,由于完全是手写,所以难免会有错误,但是还是有保留价值,毕竟里面的注释还是很有意义的,
但是完整的源代码也很重要,下面附上老师的源代码以供以后参考:
------------------------------------------------
web.xml配置信息如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
 xmlns="http://java.sun.com/xml/ns/j2ee"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
 http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

 <listener>
   <listener-class>cn.itcast.listener.IpServletContextListener</listener-class>
 </listener>
 <listener>
   <listener-class>cn.itcast.listener.CounterSessionListener</listener-class>
 </listener>
 
 <filter>
   <filter-name>ipFilter</filter-name>
   <filter-class>cn.itcast.filter.IpFilter</filter-class>
 </filter>
 
 <filter-mapping>
   <filter-name>ipFilter</filter-name>
   <url-pattern>/*</url-pattern>
 </filter-mapping>
</web-app>
------------------------------------------------*/
老师定义了两个辅助类:

//该类作用是定义常量字符串,用于给Session中的属性命名。
package cn.itcast.util;

public final class Constants {
 public static final String FORBIDDEN_IP = "TITAN_FORBIDDEN_IP";
 public static final String COUNTER_ATTR_NAME = "TITAN_COUNTER_ATTR_NAME";
}


//该类是个计数器,里面不但计数还封装了ip字符串的信息,之所以这么作是为了在Session销毁的时候
//可以根据字符串的值来更新对应的浏览次数,再有注意继承了序列化接口,是因为Session可能会有
//钝化(序列化到外存)的情况,所以要继承这个接口。
package cn.itcast.util;

import java.io.Serializable;

public class Counter implements Serializable {
 private int count = 1;

 private String ip;
 
 public Counter(String ip) {
  this.ip = ip;
 }

 public String getIp() {
  return ip;
 }

 public void increase() {
  count++;
 }

 public void decrease() {
  count--;
 }

 public int getCount() {
  return count;
 }
}

--------------------------------------------------------------
老师定义的监听器有两个,一个是Session的,一个是ServletContext的

Session的监听器:

package cn.itcast.listener;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionActivationListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

import cn.itcast.util.Constants;
import cn.itcast.util.Counter;

public class CounterSessionListener implements HttpSessionListener, HttpSessionActivationListener{
//之所以继承了两个接口,是因为Session销毁时和session钝化时都要去更新数据库!所以在钝化和销毁的监听事件里面
//都会调用相同的业务逻辑
 public void sessionDidActivate(HttpSessionEvent arg0) {
  // TODO Auto-generated method stub
  
 }

 public void sessionWillPassivate(HttpSessionEvent arg0) {
  // TODO Auto-generated method stub
  flush(arg0);
 }

 public void sessionCreated(HttpSessionEvent arg0) {
  // TODO Auto-generated method stub
  
 }

 public void sessionDestroyed(HttpSessionEvent arg0) {
  // TODO Auto-generated method stub
  flush(arg0);
 }
 
 protected void flush(HttpSessionEvent arg0) {
  HttpSession session = arg0.getSession();
  Counter counter = (Counter)session.getAttribute(Constants.COUNTER_ATTR_NAME);
  
  Connection conn = null;
  PreparedStatement pstmt = null;
  ResultSet rs = null;
  
  try {
   Class.forName("com.mysql.jdbc.Driver");
   conn = DriverManager.getConnection("jdbc:mysql:///j2ee?useUnicode=true&characterEncoding=gbk", "root", "root");
   pstmt = conn.prepareStatement("update ips set browseTimes=browseTimes+? where ip=?");
   pstmt.setInt(1, counter.getCount());
   pstmt.setString(2, counter.getIp());
   
   if(pstmt.executeUpdate()!=1) {
    throw new SQLException("not only 1 row infected");
   }
   
   
  } catch (ClassNotFoundException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (SQLException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } finally {
   try {
    if(conn!=null) {
     conn.close();
     conn = null;
    }
   } catch (SQLException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }
  }
 }

}

-----------------------------------------------------
ServletContext的监听器:
//它的作用很明显,每一次应用程序启动就把所有被封杀的ip全部拿出来,在后面如果有封杀或是解封动作,
//则都要对这个拿出来的ip集合进行操作!
package cn.itcast.listener;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import cn.itcast.util.Constants;

public class IpServletContextListener implements ServletContextListener {

 public void contextDestroyed(ServletContextEvent arg0) {
  // TODO Auto-generated method stub
  
 }

 public void contextInitialized(ServletContextEvent arg0) {
  // TODO Auto-generated method stub
  Connection conn = null;
  PreparedStatement pstmt = null;
  ResultSet rs = null;
  
  try {
   Class.forName("com.mysql.jdbc.Driver");
   conn = DriverManager.getConnection("jdbc:mysql:///j2ee?useUnicode=true&characterEncoding=gbk", "root", "root");
   pstmt = conn.prepareStatement("select ip from ips where permit=0");
   rs = pstmt.executeQuery();
   
   ArrayList a = new ArrayList();
   while(rs.next()) {
    a.add(rs.getString(1));
   }
   ServletContext context = arg0.getServletContext();
   context.setAttribute(Constants.FORBIDDEN_IP, a);
   
  } catch (ClassNotFoundException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (SQLException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } finally {
   try {
    if(conn!=null) {
     conn.close();
     conn = null;
    }
   } catch (SQLException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }
  }
 }

}

----------------------------------------------------------------
最后是那个过滤器,用于处理主要业务:
主要作用如下:首先要明确的一点是访问次数统计的是会话的次数,而不是点击页面的次数(浏览次数),访问次数也就是你用同一个ip
一共在我这个服务器上面留下了多少个不同的会话,当每一个会话销毁或是钝化的时候访问次数就会加1。

这里首先看这个ip是不是被封杀的,如果是的话就直接干掉;否则看一下它当前有没有一个session与之绑定,也就是它当前有没有会话,
如果没有会话的话,那必然是开始了一个新的会话,也就是说访问的次数应该加上1,但是还得确定一点,没有会话并不意味着这个ip从来
没有访问过这个网站,完全可能我以前访问过这个网站,但是会话已经失效了,这样虽然我这个ip没有绑定session,但是数据库中仍然
有我的痕迹留下。所以根据是否有这个ip来决定是插入还是更新,这里有一个小技巧就是,插入语句和更新语句最好是设置成一样顺序的
参数,这样方便统一设置pstmt语句。

如果我这个ip有了个session与之相关联,那么就没有必要去更新数据库了,因为既然现在已经有了session,之后的每一次访问需要更新
的无非只是浏览的次数(点击次数),这样一来我只需要交给一个计数器去处理就可以了,而这个计数器放到session中是再合适不过的了,
它会随着第一次更新(或插入)数据库时创建session时被创建,也会自然而然的随着session被销毁或是被钝化而被消灭(在临被消灭前
通过session的监听方法来根据自己的计数器的真实值来更新数据库中的浏览次数即可)。
package cn.itcast.filter;

import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;

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;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import cn.itcast.util.Constants;
import cn.itcast.util.Counter;

public class IpFilter implements Filter {

 private FilterConfig config;
 
 public void destroy() {
  // TODO Auto-generated method stub
  
 }

 public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain chain) throws IOException, ServletException {
  // TODO Auto-generated method stub
  HttpServletRequest request = (HttpServletRequest)arg0;
  HttpServletResponse response = (HttpServletResponse)arg1;
  
  //获取IP和应用程序作用域对象
  String ipAddr = request.getRemoteAddr();
  ServletContext context = config.getServletContext();
  
  //从应用程序作用域中查找被封杀IP的列表
  ArrayList forbiddenIps = (ArrayList)context.getAttribute(Constants.FORBIDDEN_IP);
  //如果当前访问用户的IP地址包含在被封杀的IP列表中,则返回错误
  if(forbiddenIps.contains(ipAddr)) {
   response.sendError(HttpServletResponse.SC_FORBIDDEN, "你的IP已经被封杀!");
   return;
  }
  
  //看用户是否是第一次访问
  //如果是第一次访问则将用户的IP信息写入到数据库,之后的访问则只更新计数器
  HttpSession session = request.getSession();
  Counter counter = (Counter) session.getAttribute(Constants.COUNTER_ATTR_NAME);
  if(counter!=null) {
   counter.increase();
   chain.doFilter(request, response);
   return;
  }
  
  Connection conn = null;
  PreparedStatement pstmt = null;
  ResultSet rs = null;
  
  try {
   Class.forName("com.mysql.jdbc.Driver");
   conn = DriverManager.getConnection("jdbc:mysql:///j2ee?useUnicode=true&characterEncoding=gbk", "root", "root");
   pstmt = conn.prepareStatement("select * from ips where ip=?");
   pstmt.setString(1, ipAddr);
   
   
   
   rs = pstmt.executeQuery();
   if(rs.next()) {
    pstmt = conn.prepareStatement("update ips set browseTimes=browseTimes+1, visitTimes=visitTimes+1, " +
      "online=1,visitTime=?, country=?, language=? where ip=?");
   } else {
    pstmt = conn.prepareStatement("insert into ips(browseTimes, visitTimes, online, visitTime, country, language, ip) " +
      "values(1, 1, 1, ?, ?, ?, ?)");
   }
   pstmt.setTimestamp(1, new Timestamp(System.currentTimeMillis()));
   pstmt.setString(2, request.getLocale().getDisplayCountry());
   pstmt.setString(3, request.getLocale().getDisplayLanguage());
   pstmt.setString(4, ipAddr);
   
   if(pstmt.executeUpdate()!=1) {
    throw new SQLException("not only 1 row infected");
   }
   
   session.setAttribute(Constants.COUNTER_ATTR_NAME, new Counter(ipAddr));
   session.setMaxInactiveInterval(60);
  } catch (ClassNotFoundException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (SQLException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } finally {
   try {
    if(conn!=null) {
     conn.close();
     conn = null;
    }
   } catch (SQLException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }
  }
  
  chain.doFilter(request, response);
 }

 public void init(FilterConfig arg0) throws ServletException {
  // TODO Auto-generated method stub
  config = arg0;
 }

}

=======================================================
=======================================================
=======================================================
=======================================================
=======================================================
今天下午的代码还是很有难度的,需要花时间好好看看,而且最要命的是这些代码实现的功能并不全,以后还要继续扩充……
再加上还有张老师布置的后天的论坛的代码,确实有点……小麻烦

所以今后两天比较重要的任务就是这两个代码(一个是ip的访问次数,统计次数,以及封杀功能,另外一个是bbs论坛功能)
的熟练掌握,先主要关心那个论坛的,这个统计次数的可以在元旦的时候好好看看。

 

 

 

原创粉丝点击