Java三大器的实现原理

在这里插入图片描述

一、过滤器工作原理及代码演示

1、过滤器概念
  Filter也称之为过滤器,它是Servlet技术中最激动人心的技术之一,WEB开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp,Servlet, 静态图片文件或静态html文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。
  Servlet API中提供了一个Filter接口,开发web应用时,如果编写的Java类实现了这个接口,则把这个java类称之为过滤器Filter。通过Filter技术,开发人员可以实现用户在访问某个目标资源之前,对访问的请求和响应进行拦截,Filter接口源代码:

public abstract interface Filter{  
    public abstract void init(FilterConfig paramFilterConfig) throws ServletException;  
    public abstract void doFilter(ServletRequest paramServletRequest, ServletResponse paramServletResponse, FilterChain   
        paramFilterChain) throws IOException, ServletException;  
    public abstract void destroy();  
}  

2、过滤器的工作原理

  Filter接口中有一个doFilter方法,当我们编写好Filter,并配置对哪个web资源进行拦截后,WEB服务器每次在调用web资源的service方法之前,都会先调用一下filter的doFilter方法,因此,在该方法内编写代码可达到如下目的:

  • 调用目标资源之前,让一段代码执行。
  • 是否调用目标资源(即是否让用户访问web资源)。
  • 调用目标资源之后,让一段代码执行。

  web服务器在调用doFilter方法时,会传递一个filterChain对象进来,filterChain对象是filter接口中最重要的一个对象,它也提供了一个doFilter方法,开发人员可以根据需求决定是否调用此方法,调用该方法,则web服务器就会调用web资源的service方法,即web资源就会被访问,否则web资源不会被访问。

3、自定义过滤器步骤

3.1、Filter开发步骤
Filter开发分为2步:
第一步:编写java类实现Filter接口,并实现其doFilter方法。
第二步:在web.xml 文件中使用和元素对编写的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;  
/** 
 * @author yangcq 
 * @description 过滤器Filter的工作原理 
 */  
public class FilterTest implements Filter{  
  
    public void destroy() {  
        System.out.println("----Filter销毁----");  
    }  
  
    public void doFilter(ServletRequest request, ServletResponse response,FilterChain filterChain) throws IOException, ServletException {  
        // 对request、response进行一些预处理  
        request.setCharacterEncoding("UTF-8");  
        response.setCharacterEncoding("UTF-8");  
        response.setContentType("text/html;charset=UTF-8");  
        System.out.println("----调用service之前执行一段代码----");  
        filterChain.doFilter(request, response); // 执行目标资源,放行  
        System.out.println("----调用service之后执行一段代码----");  
    }  
  
    public void init(FilterConfig arg0) throws ServletException {  
        System.out.println("----Filter初始化----");  
    }  
}  

在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>      
  <welcome-file-list>  
    <welcome-file>index.jsp</welcome-file>  
  </welcome-file-list>  
  <!--配置过滤器-->  
  <filter>  
      <filter-name>FilterTest</filter-name>  
      <filter-class>com.yangcq.filter.FilterTest</filter-class>  
  </filter>  
  <!--映射过滤器-->  
  <filter-mapping>  
      <filter-name>FilterTest</filter-name>  
      <!--“/*”表示拦截所有的请求 -->  
      <url-pattern>/*</url-pattern>  
  </filter-mapping>  
</web-app>  

3.2、Filter链
  在一个web应用中,可以开发编写多个Filter,这些Filter组合起来称之为一个Filter链。web服务器根据Filter在web.xml文件中的注册顺序,决定先调用哪个Filter,当第一个Filter的doFilter方法被调用时,web服务器会创建一个代表Filter链的FilterChain对象传递给该方法。在doFilter方法中,开发人员如果调用了FilterChain对象的doFilter方法,则web服务器会检查FilterChain对象中是否还有filter,如果有,则调用第2个filter,如果没有,则调用目标资源。

4、Spring框架下,过滤器的配置
  如果项目中使用了Spring框架,那么,很多过滤器都不用自己来写了,Spring为我们写好了一些常用的过滤器。下面我们就以字符编码的过滤器CharacterEncodingFilter为例,来看一下Spring框架下,如果配置过滤器。

<filter>  
    <filter-name>encodingFilter</filter-name>  
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>  
    <init-param>  
        <param-name>encoding</param-name>  
        <param-value>UTF-8</param-value>  
    </init-param>  
    <init-param>  
        <param-name>forceEncoding</param-name>  
        <param-value>true</param-value>  
    </init-param>  
</filter>  
  
<filter-mapping>  
    <filter-name>encodingFilter</filter-name>  
    <url-pattern>/*</url-pattern>  
</filter-mapping> 

  很简单吧,这样几行配置代码,就完成了从全局控制字符编码的功能。接下来,我们看一下CharacterEncodingFilter这个过滤器的关键代码,感受一下,大师的风采,如果我们写过滤器的话,可以以此为范例。

package org.springframework.web.filter;  
import java.io.IOException;  
import javax.servlet.FilterChain;  
import javax.servlet.ServletException;  
import javax.servlet.http.HttpServletRequest;  
import javax.servlet.http.HttpServletResponse;  
import org.springframework.util.ClassUtils;  
public class CharacterEncodingFilter extends OncePerRequestFilter{  
    private static final boolean responseSetCharacterEncodingAvailable = ClassUtils.hasMethod(  
        class$javax$servlet$http$HttpServletResponse, "setCharacterEncoding", new Class[] { String.class });  
    // 需要设置的编码方式,为了支持可配置,Spring把编码方式设置成了一个变量  
    private String encoding;  
    // 是否强制使用统一编码,也是为了支持可配置  
    private boolean forceEncoding;  
    // 构造器,在这里,Spring把forceEncoding的值默认设置成了false  
    public CharacterEncodingFilter(){  
        this.forceEncoding = false;  
    }  
    // encoding/forceEncoding的setter方法  
    public void setEncoding(String encoding){  
        this.encoding = encoding;  
    }  
    public void setForceEncoding(boolean forceEncoding){  
        this.forceEncoding = forceEncoding;  
    }  
    // Spring通过GenericFilterBean抽象类,对Filter接口进行了整合,  
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)  
        throws ServletException, IOException{  
        if ((this.encoding != null) && (((this.forceEncoding) || (request.getCharacterEncoding() == null)))) {  
            request.setCharacterEncoding(this.encoding);  
            if ((this.forceEncoding) && (responseSetCharacterEncodingAvailable)) {  
                response.setCharacterEncoding(this.encoding);  
            }  
        }  
        filterChain.doFilter(request, response);  
    }  
}  

GenericFilterBean类:
public abstract class GenericFilterBean implements Filter, BeanNameAware, ServletContextAware, InitializingBean, DisposableBean

实例一:防止脚本攻击的过滤器:InvilidCharacterFilter

import java.io.IOException;  
import java.util.Enumeration;  
import javax.servlet.FilterChain;  
import javax.servlet.RequestDispatcher;  
import javax.servlet.ServletException;  
import javax.servlet.http.HttpServletRequest;  
import javax.servlet.http.HttpServletResponse;  
import org.apache.commons.lang.StringUtils;  
import org.springframework.web.filter.CharacterEncodingFilter;  
/* 
 * InvalidCharacterFilter:过滤request请求中的非法字符,防止脚本攻击 
 * InvalidCharacterFilter继承了Spring框架的CharacterEncodingFilter过滤器,当然,我们也可以自己实现这样一个过滤器 
 */  
public class InvalidCharacterFilter extends CharacterEncodingFilter{  
    // 需要过滤的非法字符  
    private static String[] invalidCharacter = new String[]{  
        "script","select","insert","document","window","function",  
        "delete","update","prompt","alert","create","alter",  
        "drop","iframe","link","where","replace","function","onabort",  
        "onactivate","onafterprint","onafterupdate","onbeforeactivate",  
        "onbeforecopy","onbeforecut","onbeforedeactivateonfocus",  
        "onkeydown","onkeypress","onkeyup","onload",  
        "expression","applet","layer","ilayeditfocus","onbeforepaste",  
        "onbeforeprint","onbeforeunload","onbeforeupdate",  
        "onblur","onbounce","oncellchange","oncontextmenu",  
        "oncontrolselect","oncopy","oncut","ondataavailable",  
        "ondatasetchanged","ondatasetcomplete","ondeactivate",  
        "ondrag","ondrop","onerror","onfilterchange","onfinish","onhelp",  
        "onlayoutcomplete","onlosecapture","onmouse","ote",  
        "onpropertychange","onreadystatechange","onreset","onresize",  
        "onresizeend","onresizestart","onrow","onscroll",  
        "onselect","onstaronsubmit","onunload","IMgsrc","infarction"  
    };  
  
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)  
            throws ServletException, IOException{     
        String parameterName = null;  
        String parameterValue = null;  
        // 获取请求的参数  
        @SuppressWarnings("unchecked")  
        Enumeration<String> allParameter = request.getParameterNames();  
        while(allParameter.hasMoreElements()){  
            parameterName = allParameter.nextElement();  
            parameterValue = request.getParameter(parameterName);  
            if(null != parameterValue){  
                for(String str : invalidCharacter){  
                    if (StringUtils.containsIgnoreCase(parameterValue, str)){  
                        request.setAttribute("errorMessage", "非法字符:" + str);  
                        RequestDispatcher requestDispatcher = request.getRequestDispatcher("/error.jsp");  
                        requestDispatcher.forward(request, response);  
                        return;  
                    }  
                }  
            }  
        }  
        super.doFilterInternal(request, response, filterChain);  
    }  
}  

接下来需要在web.xml中进行配置:

<filter>  
    <filter-name>InvalidCharacterFilter</filter-name>  
    <filter-class>com.yangcq.filter.InvalidCharacterFilter</filter-class>  
</filter>  
<filter-mapping>  
    <filter-name>InvalidCharacterFilter</filter-name>  
    <url-pattern>/*</url-pattern>  
</filter-mapping>  

实例二:如果我们不使用Spring的CharacterEncodingFilter类,可以自己来写。

import java.io.IOException;  
import java.util.Enumeration;  
import javax.servlet.Filter;  
import javax.servlet.FilterChain;  
import javax.servlet.FilterConfig;  
import javax.servlet.RequestDispatcher;  
import javax.servlet.ServletException;  
import javax.servlet.ServletRequest;  
import javax.servlet.ServletResponse;  
import org.apache.commons.lang.StringUtils;  
/** 
 * SelfDefineInvalidCharacterFilter:过滤request请求中的非法字符,防止脚本攻击 
 */  
public class SelfDefineInvalidCharacterFilter implements Filter{  
  
    public void destroy() {  
          
    }  
  
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {  
        String parameterName = null;  
        String parameterValue = null;  
        // 获取请求的参数  
        @SuppressWarnings("unchecked")  
        Enumeration<String> allParameter = request.getParameterNames();  
        while(allParameter.hasMoreElements()){  
            parameterName = allParameter.nextElement();  
            parameterValue = request.getParameter(parameterName);  
            if(null != parameterValue){  
                for(String str : invalidCharacter){  
                    if (StringUtils.containsIgnoreCase(parameterValue, str)){  
                        request.setAttribute("errorMessage", "非法字符:" + str);  
                        RequestDispatcher requestDispatcher = request.getRequestDispatcher("/error.jsp");  
                        requestDispatcher.forward(request, response);  
                        return;  
                    }  
                }  
            }  
        }  
        filterChain.doFilter(request, response); // 执行目标资源,放行  
    }  
  
    public void init(FilterConfig filterConfig) throws ServletException {  
          
    }  
    // 需要过滤的非法字符  
    private static String[] invalidCharacter = new String[]{  
        "script","select","insert","document","window","function",  
        "delete","update","prompt","alert","create","alter",  
        "drop","iframe","link","where","replace","function","onabort",  
        "onactivate","onafterprint","onafterupdate","onbeforeactivate",  
        "onbeforecopy","onbeforecut","onbeforedeactivateonfocus",  
        "onkeydown","onkeypress","onkeyup","onload",  
        "expression","applet","layer","ilayeditfocus","onbeforepaste",  
        "onbeforeprint","onbeforeunload","onbeforeupdate",  
        "onblur","onbounce","oncellchange","oncontextmenu",  
        "oncontrolselect","oncopy","oncut","ondataavailable",  
        "ondatasetchanged","ondatasetcomplete","ondeactivate",  
        "ondrag","ondrop","onerror","onfilterchange","onfinish","onhelp",  
        "onlayoutcomplete","onlosecapture","onmouse","ote",  
        "onpropertychange","onreadystatechange","onreset","onresize",  
        "onresizeend","onresizestart","onrow","onscroll",  
        "onselect","onstaronsubmit","onunload","IMgsrc","infarction"  
    };  
  
}  

接下来需要在web.xml中进行配置:

<filter>  
    <filter-name>SelfDefineInvalidCharacterFilter</filter-name>  
    <filter-class>com.yangcq.filter.SelfDefineInvalidCharacterFilter</filter-class>  
</filter>  
<filter-mapping>  
    <filter-name>SelfDefineInvalidCharacterFilter</filter-name>  
    <url-pattern>/*</url-pattern>  
</filter-mapping>

5、Filter的生命周期
5.1、Filter的创建
  Filter的创建和销毁由web服务器负责。 web应用程序启动时,web服务器将创建Filter的实例对象,并调用其init方法,完成对象的初始化
功能,从而为后续的用户请求作好拦截的准备工作,filter对象只会创建一次,init方法也只会执行一次。通过init方法的参数,可获得代表当前
filter配置信息的FilterConfig对象。

5.2、Filter的销毁
  web容器调用destroy方法销毁Filter。destroy方法在Filter的生命周期中仅执行一次。在destroy方法中,可以释放过滤器使用的资源。

5.3、FilterConfig接口
  用户在配置filter时,可以使用为filter配置一些初始化参数,当web容器实例化Filter对象,调用其init方法时,会把封装了
filter初始化参数的filterConfig对象传递进来。因此开发人员在编写filter时,通过filterConfig对象的方法,就可获得:
  String getFilterName():得到filter的名称。
  String getInitParameter(String name): 返回在部署描述中指定名称的初始化参数的值。如果不存在返回null.
  Enumeration getInitParameterNames():返回过滤器的所有初始化参数的名字的枚举集合。
  public ServletContext getServletContext():返回Servlet上下文对象的引用。

示例:利用FilterConfig得到filter配置信息

package com.yangcq.filter;  
import java.io.IOException;  
import java.util.Enumeration;  
import javax.servlet.Filter;  
import javax.servlet.FilterChain;  
import javax.servlet.FilterConfig;  
import javax.servlet.ServletException;  
import javax.servlet.ServletRequest;  
import javax.servlet.ServletResponse;  
public class FilterTest implements Filter {  
  
    /* 过滤器初始化 
     * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) 
     */  
    @Override  
    public void init(FilterConfig filterConfig) throws ServletException {  
        System.out.println("----过滤器初始化----");  
        /** 
         *  <filter> 
                  <filter-name>FilterTest</filter-name> 
                  <filter-class>com.yangcq.filter.FilterTest</filter-class> 
                  <!--配置FilterTest过滤器的初始化参数--> 
                  <init-param> 
                      <description>FilterTest</description> 
                      <param-name>name</param-name> 
                      <param-value>gacl</param-value> 
                  </init-param> 
                  <init-param> 
                      <description>配置FilterTest过滤器的初始化参数</description> 
                      <param-name>like</param-name> 
                      <param-value>java</param-value> 
                  </init-param> 
            </filter> 
             
             <filter-mapping> 
                  <filter-name>FilterDemo02</filter-name> 
                  <!--“/*”表示拦截所有的请求 --> 
                  <url-pattern>/*</url-pattern> 
             </filter-mapping> 
         */  
        //得到过滤器的名字  
        String filterName = filterConfig.getFilterName();  
        //得到在web.xml文件中配置的初始化参数  
        String initParam1 = filterConfig.getInitParameter("name");  
        String initParam2 = filterConfig.getInitParameter("like");  
        //返回过滤器的所有初始化参数的名字的枚举集合。  
        Enumeration<String> initParameterNames = filterConfig.getInitParameterNames();  
          
        System.out.println(filterName);  
        System.out.println(initParam1);  
        System.out.println(initParam2);  
        while (initParameterNames.hasMoreElements()) {  
            String paramName = (String) initParameterNames.nextElement();  
            System.out.println(paramName);  
        }  
    }  
  
    @Override  
    public void doFilter(ServletRequest request, ServletResponse response,  
            FilterChain chain) throws IOException, ServletException {  
        System.out.println("FilterDemo02执行前!!!");  
        chain.doFilter(request, response);  //让目标资源执行,放行  
        System.out.println("FilterDemo02执行后!!!");  
    }  
  
    @Override  
    public void destroy() {  
        System.out.println("----过滤器销毁----");  
    }  
}  

6、Filter的部署时的一些参数的含义

Filter的部署分为两个步骤:
  1、注册Filter
  2、映射Filter

6.1、注册Filter
  开发好Filter之后,需要在web.xml文件中进行注册,这样才能够被web服务器调用。在web.xml文件中注册Filter范例:

<filter>  
    <description>过滤器名称</description>  
    <filter-name>自定义的名字</filter-name>  
    <filter-class>com.yangcq.filter.FilterTest</filter-class>  
    <!--配置FilterTest过滤器的初始化参数-->  
    <init-param>  
        <description>配置过滤器的初始化参数</description>  
        <param-name>name</param-name>  
        <param-value>gacl</param-value>  
    </init-param>  
    <init-param>  
        <description>配置FilterTest过滤器的初始化参数</description>  
        <param-name>like</param-name>  
        <param-value>java</param-value>  
    </init-param>  
</filter>  

<description>用于添加描述信息,该元素的内容可为空,<description>可以不配置。
<filter-name>用于为过滤器指定一个名字,该元素的内容不能为空。
<filter-class>元素用于指定过滤器的完整的限定类名。
<init-param>元素用于为过滤器指定初始化参数,它的子元素<param-name>指定参数的名字,<param-value>指定参数的值。在过滤器中,可以使用FilterConfig接口对象来访问初始化参数。如果过滤器不需要指定初始化参数,那么<init-param>元素可以不配置。

6.2、映射Filter
  在web.xml文件中注册了Filter之后,还要在web.xml文件中映射Filter

<!--映射过滤器-->
<filter-mapping>
    <filter-name>FilterTest</filter-name>
    <!--“/*”表示拦截所有的请求 -->
    <url-pattern>/*</url-pattern>
</filter-mapping>

<filter-mapping>元素用于设置一个 Filter 所负责拦截的资源。一个Filter拦截的资源可通过两种方式来指定:Servlet 名称和资源访问的请求路径
<filter-name>子元素用于设置filter的注册名称。该值必须是在元素中声明过的过滤器的名字
<url-pattern>设置 filter 所拦截的请求路径(过滤器关联的URL样式)
<servlet-name>指定过滤器所拦截的Servlet名称。
<dispatcher>指定过滤器所拦截的资源被 Servlet 容器调用的方式,可以REQUEST,INCLUDE,FORWARD和ERROR之一,默认REQUEST。用户可以设置多个<dispatcher> 子元素用来指定 Filter 对资源的多种调用方式进行拦截。如下:

<filter-mapping>
    <filter-name>testFilter</filter-name>
    <url-pattern>/index.jsp</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
</filter-mapping>

<dispatcher> 子元素可以设置的值及其意义:
REQUEST:当用户直接访问页面时,Web容器将会调用过滤器。如果目标资源是通过RequestDispatcher的include()或forward()方法访问
时,那么该过滤器就不会被调用。
INCLUDE:如果目标资源是通过RequestDispatcher的include()方法访问时,那么该过滤器将被调用。除此之外,该过滤器不会被调用。
FORWARD:如果目标资源是通过RequestDispatcher的forward()方法访问时,那么该过滤器将被调用,除此之外,该过滤器不会被调用。
ERROR:如果目标资源是通过声明式异常处理机制调用时,那么该过滤器将被调用。除此之外,过滤器不会被调用。

二、监听器工作原理及代码演示

1、监听器概念
  Servlet的监听器Listener,它是实现了javax.servlet.ServletContextListener 接口的服务器端程序,它也是随web应用的启动而启动,只初始化一次,随web应用的停止而销毁。主要作用是:做一些初始化的内容添加工作、设置一些基本的内容、比如一些参数或者是一些固定的对象等等。首先来看一下ServletContextListener接口的源代码:

public abstract interface ServletContextListener extends EventListener{  
    public abstract void contextInitialized(ServletContextEvent paramServletContextEvent);  
    public abstract void contextDestroyed(ServletContextEvent paramServletContextEvent);  
} 

下面利用监听器对数据库连接池DataSource的初始化演示它的使用:ListenerTest.java

import javax.servlet.ServletContext;     
import javax.servlet.ServletContextEvent;     
import javax.servlet.ServletContextListener;     
import org.apache.commons.dbcp.BasicDataSource;         
/** 
 * 现在来说说Servlet的监听器Listener,它是实现了javax.servlet.ServletContextListener 接口的 
 * 服务器端程序,它也是随web应用的启动而启动,只初始化一次,随web应用的停止而销毁。主要作用是:做一些初始化 
 * 的内容添加工作、设置一些基本的内容、比如一些参数或者是一些固定的对象等等。 
 *  
 * 示例代码:使用监听器对数据库连接池DataSource进行初始化 
 */   
public class ListenerTest implements ServletContextListener{       
   // 应用监听器的销毁方法     
   public void contextDestroyed(ServletContextEvent servletContextEvent) {     
        ServletContext servletContext = servletContextEvent.getServletContext();  
        // 在整个web应用销毁之前调用,将所有应用空间所设置的内容清空  
        servletContext.removeAttribute("dataSource");  
        System.out.println("销毁工作完成...");    
   }     
    // 应用监听器的初始化方法     
    public void contextInitialized(ServletContextEvent servletContextEvent) {     
        // 通过这个事件可以获取整个应用的空间     
        // 在整个web应用下面启动的时候做一些初始化的内容添加工作     
        ServletContext servletContext = servletContextEvent.getServletContext();    
        // 设置一些基本的内容;比如一些参数或者是一些固定的对象     
        // 创建DataSource对象,连接池技术 dbcp     
        BasicDataSource basicDataSource = new BasicDataSource();   
        basicDataSource.setDriverClassName("com.jdbc.Driver");   
        basicDataSource.setUrl("jdbc:mysqlocalhost:3306/");   
        basicDataSource.setUsername("root");     
        basicDataSource.setPassword("root");     
        basicDataSource.setMaxActive(10);//最大连接数     
        basicDataSource.setMaxIdle(5);//最大管理数     
        //bds.setMaxWait(maxWait); 最大等待时间     
        // 把 DataSource 放入ServletContext空间中,     
        // 供整个web应用的使用(获取数据库连接)  
        servletContext.setAttribute("dataSource", basicDataSource);     
        System.out.println("应用监听器初始化工作完成...");     
        System.out.println("已经创建DataSource...");    
    }     
}  

web.xml中配置如下,很简单:

<!-- 配置应用监听器  -->     
<listener>     
    <listener-class>com.ycq.ListenerTest</listener-class>     
</listener>

这样配置好了之后,以后在web应用中就可以通过ServletContext取得BasicDataSource对象,从而获取与数据库的连接,提高性能,方便使用。

2、监听器在实际项目中的应用
监听器在java web中应用的较多,比如:统计当前在线人数、自定义session扫描器。

应用一:统计当前在线人数

import javax.servlet.ServletContext;  
import javax.servlet.http.HttpSessionEvent;  
import javax.servlet.http.HttpSessionListener;  
/** 
 * @description HttpSessionListener监听器实现统计网站在线人数的功能 
 */  
public class SessionListener implements HttpSessionListener{  
  
    public static int TOTAL_ONLINE_USERS = 0;  
    public void sessionCreated(HttpSessionEvent httpSessionEvent) {  
        ServletContext servletContext = httpSessionEvent.getSession().getServletContext();  
        TOTAL_ONLINE_USERS = (Integer) servletContext.getAttribute("TOTAL_ONLINE_USERS");  
        // 如果用户退出,TOTAL_ONLINE_USERS自减1  
        if(TOTAL_ONLINE_USERS == 0){  
            servletContext.setAttribute("TOTAL_ONLINE_USERS", 1);  
        }  
        else{  
            TOTAL_ONLINE_USERS--;  
            servletContext.setAttribute("TOTAL_ONLINE_USERS", TOTAL_ONLINE_USERS);  
        }  
    }  
  
    public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {  
        ServletContext servletContext = httpSessionEvent.getSession().getServletContext();  
        TOTAL_ONLINE_USERS = (Integer) servletContext.getAttribute("TOTAL_ONLINE_USERS");  
        // 如果用户登录,TOTAL_ONLINE_USERS自增1  
        if(TOTAL_ONLINE_USERS == 0){  
            servletContext.setAttribute("TOTAL_ONLINE_USERS", 1);  
        }  
        else{  
            TOTAL_ONLINE_USERS++;  
            servletContext.setAttribute("TOTAL_ONLINE_USERS", TOTAL_ONLINE_USERS);  
        }  
    }  
}  

应用二:自定义session扫描器

import java.util.LinkedList;  
import java.util.List;  
import java.util.Timer;  
import javax.servlet.ServletContextEvent;  
import javax.servlet.ServletContextListener;  
import javax.servlet.http.HttpSession;  
import javax.servlet.http.HttpSessionEvent;  
import javax.servlet.http.HttpSessionListener;  
import jeus.util.concurrent50.Collections;  
/** 
 * @description 当网站用户量增加时,session占用的内存会越来越大,这时session的管理,将会是一项很大的 
 * 系统开销,为了高效的管理session,我们可以写一个监听器,定期清理掉过期的session 
 */  
public class SessionScanerListener implements HttpSessionListener,ServletContextListener{  
    // 创建一个线程安全的集合,用来存储session  
    @SuppressWarnings("unchecked")  
    List<HttpSession> sessionList = Collections.synchronizedList(new LinkedList<HttpSession>());  
    private Object lock = new Object();  
      
    public void sessionCreated(HttpSessionEvent httpSessionEvent) {  
        System.out.println("session 创建成功...");  
        HttpSession httpSession = httpSessionEvent.getSession();  
        synchronized (lock){  
            sessionList.add(httpSession);  
        }  
    }  
  
    public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {  
        System.out.println("session 销毁成功...");  
    }  
    // web应用关闭时触发contextDestroyed事件  
    public void contextDestroyed(ServletContextEvent servletContextEvent) {  
        System.out.println("web应用关闭...");  
    }  
  
    // web应用启动时触发contextInitialized事件  
    public void contextInitialized(ServletContextEvent servletContextEvent) {  
        System.out.println("web应用初始化...");  
        // 创建定时器  
        Timer timer = new Timer();  
        // 每隔30秒就定时执行任务  
        timer.schedule(new MyTask(sessionList,lock), 0, 1000*30);  
    }  
}  

三、拦截器工作原理及代码演示

1、拦截器的概念
  java里的拦截器是动态拦截Action调用的对象,它提供了一种机制可以使开发者在一个Action执行的前后执行一段代码,也可以在一个Action执行前阻止其执行,同时也提供了一种可以提取Action中可重用部分代码的方式。在AOP中,拦截器用于在某个方法或者字段被访问之前,进行拦截然后再之前或者之后加入某些操作。目前,我们需要掌握的主要是Spring的拦截器,Struts2的拦截器不用深究,知道即可。

2、拦截器的原理
  大部分时候,拦截器方法都是通过代理的方式来调用的。Struts2的拦截器实现相对简单。当请求到达Struts2的ServletDispatcher时,Struts2会查找配置文件,并根据配置实例化相对的拦截器对象,然后串成一个列表(List),最后一个一个的调用列表中的拦截器。Struts2的拦截器是可插拔的,拦截器是AOP的一个实现。Struts2拦截器栈就是将拦截器按一定的顺序连接成一条链。在访问被拦截的方法或者字段时,Struts2拦截器链中的拦截器就会按照之前定义的顺序进行调用。

3、自定义拦截器的步骤
第一步:自定义一个实现了Interceptor接口的类,或者继承抽象类AbstractInterceptor。
第二步:在配置文件中注册定义的拦截器。
第三步:在需要使用Action中引用上述定义的拦截器,为了方便也可以将拦截器定义为默认的拦截器,这样在不加特殊说明的情况下,所有的Action都被这个拦截器拦截。

4、过滤器与拦截器的区别
  过滤器可以简单的理解为“取你所想取”,过滤器关注的是web请求;拦截器可以简单的理解为“拒你所想拒”,拦截器关注的是方法调用,比如拦截敏感词汇。

4.1、拦截器是基于java反射机制来实现的,而过滤器是基于函数回调来实现的。(有人说,拦截器是基于动态代理来实现的)
4.2、拦截器不依赖servlet容器,过滤器依赖于servlet容器。
4.3、拦截器只对Action起作用,过滤器可以对所有请求起作用。
4.4、拦截器可以访问Action上下文和值栈中的对象,过滤器不能。
4.5、在Action的生命周期中,拦截器可以多次调用,而过滤器只能在容器初始化时调用一次。

5、Spring拦截器
5.1、抽象类HandlerInterceptorAdapter
  我们如果在项目中使用了Spring框架,那么我们可以直接继承HandlerInterceptorAdapter.java这个抽象类来实现我们自己的拦截器。Spring框架,对 java 的拦截器概念进行了包装,这一点和Struts2很类似。HandlerInterceptorAdapter继承了抽象接口HandlerInterceptor。

package org.springframework.web.servlet.handler;  
import javax.servlet.http.HttpServletRequest;  
import javax.servlet.http.HttpServletResponse;  
import org.springframework.web.servlet.HandlerInterceptor;  
import org.springframework.web.servlet.ModelAndView;  
public abstract class HandlerInterceptorAdapter implements HandlerInterceptor{  
    // 在业务处理器处理请求之前被调用  
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{  
        return true;  
    }  
    // 在业务处理器处理请求完成之后,生成视图之前执行  
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception{  
    }  
    // 在DispatcherServlet完全处理完请求之后被调用,可用于清理资源  
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception{  
    }  
}  

  接下来我们看一下Spring框架实现的一个简单的拦截器UserRoleAuthorizationInterceptor,UserRoleAuthorizationInterceptor继承了抽象类HandlerInterceptorAdapter,实现了用户登录认证的拦截功能,如果当前用户没有通过认证,会报403错误。

package org.springframework.web.servlet.handler;  
import java.io.IOException;  
import javax.servlet.ServletException;  
import javax.servlet.http.HttpServletRequest;  
import javax.servlet.http.HttpServletResponse;  
public class UserRoleAuthorizationInterceptor extends HandlerInterceptorAdapter{  
    // 字符串数组,用来存放用户角色信息  
    private String[] authorizedRoles;  
    
    public final void setAuthorizedRoles(String[] authorizedRoles){  
        this.authorizedRoles = authorizedRoles;  
    }  
    
    public final boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException{  
        if (this.authorizedRoles != null) {  
            for (int i = 0; i < this.authorizedRoles.length; ++i) {  
                if (request.isUserInRole(this.authorizedRoles[i])) {  
                    return true;  
                }  
            }  
        }  
        handleNotAuthorized(request, response, handler);  
        return false;  
    }  
    
    protected void handleNotAuthorized(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException{  
          // 403表示资源不可用。服务器理解用户的请求,但是拒绝处理它,通常是由于权限的问题  
          response.sendError(403);  
    }  
}  

  下面,我们利用Spring框架提供的HandlerInterceptorAdapter抽过类,来实现一个自定义的拦截器。我们这个拦截器叫做 UserLoginInterceptorBySpring,进行登录拦截控制。工作流程是这样的:如果当前用户没有登录,则跳转到登录页面;登录成功后,跳转到之前访问的URL页面。

import java.util.HashMap;  
import java.util.Map;  
import javax.servlet.http.HttpServletRequest;  
import javax.servlet.http.HttpServletResponse;  
import org.springframework.web.servlet.ModelAndView;  
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;  
/** 
 * @description 利用spring框架提供的HandlerInterceptorAdapter,实现自定义拦截器 
 */  
public class UserLoginInterceptorBySpring extends HandlerInterceptorAdapter{  
    // 在业务处理器处理请求之前被调用  
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{  
        // equalsIgnoreCase 与 equals的区别?  
        if("GET".equalsIgnoreCase(request.getMethod())){  
            //RequestUtil.saveRequest();  
        }  
        System.out.println("preHandle...");  
        String requestUri = request.getRequestURI();  
        String contextPath = request.getContextPath();  
        String url = requestUri.substring(contextPath.length());  
        System.out.println("requestUri" + requestUri);  
        System.out.println("contextPath" + contextPath);  
        System.out.println("url" + url);  
        String username = (String) request.getSession().getAttribute("username");  
        if(null == username){  
            // 跳转到登录页面  
            request.getRequestDispatcher("/WEB-INF/login.jsp").forward(request, response);  
            return false;  
        }  
        else{  
            return true;  
        }  
    }  
    // 在业务处理器处理请求完成之后,生成视图之前执行  
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception{  
        System.out.println("postHandle...");  
        if(modelAndView != null){  
            Map<String, String> map = new HashMap<String, String>();  
            modelAndView.addAllObjects(map);  
        }  
    }  
    // 在DispatcherServlet完全处理完请求之后被调用,可用于清理资源  
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception{  
        System.out.println("afterCompletion...");  
    }  
}  

  拦截器是依赖Java反射机制来实现的。拦截器的实现,用到的是JDK实现的动态代理,我们都知道,JDK
实现的动态代理,需要依赖接口。拦截器是在面向切面编程中应用的,就是在你的service或者一个方法前调用一个方法,或者在方法后调用一个方法。拦截器不是在web.xml,比如struts在
struts.xml中配置。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {    
    Object result = null;    
    System.out.println("方法调用前,可以执行一段代码" + method.getName());    
    result = method.invoke(this.targetObj, args);    
    System.out.println("方法调用后,可以执行一段代码 " + method.getName());    
    return result;    
}

总结:
1、过滤器(Filter):所谓过滤器顾名思义是用来过滤的,Java的过滤器能够为我们提供系统级别的过滤,也就是说,能过滤所有的web请求,这一点,是拦截器无法做到的。在Java Web中,你传入的request,response提前过滤掉一些信息,或者提前设置一些参数,然后再传入servlet或者struts的action进行业务逻辑,比如过滤掉非法url(不是login.do的地址请求,如果用户没有登陆都过滤掉),或者在传入servlet或者struts的action前统一设置字符集,或者去除掉一些非法字符(聊天室经常用到的,一些骂人的话)。filter 流程是线性的,url传来之后,检查之后,可保持原来的流程继续向下执行,被下一个filter, servlet接收。
2、监听器(Listener):Java的监听器,也是系统级别的监听。监听器随web应用的启动而启动。Java的监听器在c/s模式里面经常用到,它会对特定的事件产生产生一个处理。监听在很多模式下用到,比如说观察者模式,就是一个使用监听器来实现的,在比如统计网站的在线人数。又比如struts2可以用监听来启动。Servlet监听器用于监听一些重要事件的发生,监听器对象可以在事情发生前、发生后可以做一些必要的处理。
3、拦截器(Interceptor):java里的拦截器提供的是非系统级别的拦截,也就是说,就覆盖面来说,拦截器不如过滤器强大,但是更有针对性。Java中的拦截器是基于Java反射机制实现的,更准确的划分,应该是基于JDK实现的动态代理。它依赖于具体的接口,在运行期间动态生成字节码。
拦截器是动态拦截Action调用的对象,它提供了一种机制可以使开发者在一个Action执行的前后执行一段代码,也可以在一个Action执行前阻止其执行,同时也提供了一种可以提取Action中可重用部分代码的方式。在AOP中,拦截器用于在某个方法或者字段被访问之前,进行拦截然后再之前或
者之后加入某些操作。java的拦截器主要是用在插件上,扩展件上比如 Hibernate Spring Struts2等,有点类似面向切片的技术,在用之前先要在配置文件即xml,文件里声明一段的那个东西。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值