软件开发工程师(JAVA)中级考试大纲之----五 J2EE WEB高级组件开发(二)Web过滤器组件技术、Web监听器组件技术;

时间:2022-12-20 15:32:14
一、什么是Servlet 过滤器?

      Servlet过滤器是小型的 Web 组件,它们拦截请求和响应,以便查看、提取或以某种方式操作正在客户机和服务器之间交换的数据。过滤器是通常封装了一些功能的 Web 组件,这些功能虽然很重要,但是对于处理客户机请求或发送响应来说不是决定性的。典型的例子包括记录关于请求和响应的数据、处理安全协议、管理会话属性等。过滤器提供一种面向对象的模块化机制,用以将公共任务封装到可插入的组件中,这些组件通过一个配置文件来声明,并动态地处理。

      Servlet过滤器中结合了许多元素,从而使得过滤器成为独特、强大和模块化的 Web 组件。Servlet过滤器具有如下特点:
  1、声明式的:过滤器通过 Web 部署描述符(web.xml)中的 XML 标签来声明。这样允许添加和删除过滤器,而无需改动任何应用程序代码或 JSP 页面。
  2、动态的:过滤器在运行时由 Servlet 容器调用来拦截和处理请求和响应。
  3、灵活的:过滤器在 Web 处理环境中的应用很广泛,涵盖诸如日志记录和安全等许多最公共的辅助任务。过滤器还是灵活的,因为它们可用于对来自客户机的直接调用执行预处理和后期处理,以及处理在防火墙之后的 Web 组件之间调度的请求。最后,可以将过滤器链接起来以提供必需的功能。
  4、模块化的:通过把应用程序处理逻辑封装到单个类文件中,过滤器从而定义了可容易地从请求/响应链中添加或删除的模块化单元。

  5、可移植的:与 Java 平台的其他许多方面一样,Servlet过滤器是跨平台和跨容器可移植的,从而进一步支持了 Servler 过滤器的模块化和可重用本质。
  6、可重用的:归功于过滤器实现类的模块化设计,以及声明式的过滤器配置方式,过滤器可以容易地跨越不同的项目和应用程序使用。
  7、透明的:在请求/响应链中包括过滤器,这种设计是为了补充(而不是以任何方式替代)servlet 或 JSP 页面提供的核心处理。因而,过滤器可以根据需要添加或删除,而不会破坏 servlet 或 JSP 页面。

所以 Servlet过滤器是通过一个配置文件来灵活声明的模块化可重用组件。过滤器动态地处理传入的请求和传出的响应,并且无需修改应用程序代码就可以透明地添加或删除它们。最后,过滤器独立于任何平台或者 Servlet 容器,从而允许将它们容易地部署到任何相容的 J2EE 环境中。

二 Servlet 过滤器体系结构
    正如其名称所暗示的,Servlet过滤器用于拦截传入的请求和/或传出的响应,并监视、修改或以某种方式处理正在通过的数据流。过滤器是自包含、模块化的组件,可以将它们添加到请求/响应链中,或者在无需影响应用程序中其他 Web 组件的情况下删除它们。过滤器仅只是改动请求和响应的运行时处理,因而不应该将它们直接嵌入Web应用程序框架,除非是通过 Servlet API 中良好定义的标准接口来实现。

    Web 资源可以配置为没有过滤器与之关联(这是默认情况)、与单个过滤器关联(这是典型情况),甚至是与一个过滤器链相关联。那么过滤器究竟做什么呢? 像 servlet 一样,它接受请求并响应对象。然后过滤器会检查请求对象,并决定将该请求转发给链中的下一个组件,或者中止该请求并直接向客户机发回一个响应。如果请求被转发了,它将被传递给链中的下一个资源(另一个过滤器、servlet 或 JSP 页面)。在这个请求设法通过过滤器链并被服务器处理之后,一个响应将以相反的顺序通过该链发送回去。这样就给每个过滤器都提供了根据需要处理响应对象的机会。


    当过滤器在 Servlet 2.3 规范中首次引入时,它们只能过滤 Web 客户机和客户机所访问的指定 Web 资源之间的内容。如果该资源然后将请求调度给其他 Web 资源,那就不能向幕后委托的任何请求应用过滤器。2.4 规范消除了这个限制。Servlet过滤器现在可以应用于 J2EE Web 环境中存在请求和响应对象的任何地方。因此,Servlet过滤器可以应用在客户机和 servlet 之间、servlet 和 servlet 或 JSP 页面之间,以及所包括的每个 JSP 页面之间。

三、步骤

经历三个步骤 :
1、编写过滤器实现类的程序
2、把该过滤器添加到Web应用程序中(通过在Web部署描述符/web.xml中声明它)
3、把过滤器与应用程序一起打包并部署它

3个接口:Filter FilterChainFilterConfig
从编程的角度看,过滤器类将实现Filter接口,然后使用FilterChain和FilterConfig接口。该过滤器类的一个引用将传递给FilterChain对象,以允许过滤器把控制权传递给链中的下一个资源。FilterConfig对象将由容器提供给过滤器,以允许访问该过滤器的初始化数据。

四、过滤器生命周期方法
1、init() :这个方法在容器实例化过滤器时被调用,它主要设计用于使过滤器为处理做准备。该方法接受一个 FilterConfig类型的对象作为输入。    

2、doFilter() :与servlet拥有一个service()方法(这个方法又调用doPost()或者doGet())来处理请求一样,过滤器拥有单个用于处理请求和响应的方法---doFilter()。这个方法接受三个输入参数:一个ServletRequest 、ServletResponse和一个FilterChain对象。    

3、destroy():正如想像的那样,这个方法执行任何清理操作,这些操作可能需要在自动垃圾收集之前进行。

以下是一个完成字符集转换的过滤器实例。 首先完成过滤器代码,在init方法中读取配置参数,在doFilter中完成字符集的转换。

   package myproj.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;
public class SetCharacterEncodingFilter implements Filter{
//定义替换后的字符集,从过滤器的配置参数中读取
String newCharSet;
public void destroy(){}
public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException  {  
//处理请求字符集  
request.setCharacterEncoding(newCharSet);  
//处理响应字符集  
response.setContentType("text/html;charset="+newCharSet);
//传递给下一个过滤器,如果没有filter那就是请求的资源
chain.doFilter(request,response);
  }
public void init(FilterConfig filterConfig) throws ServletException {  
//从过滤器的配置中获得初始化参数,如果没有就使用默认值   
if(filterConfig.getInitParamter("newcharset")!=null){   
newCharSet = filterConfig.getInitParamter("newcharset");
}else{
    newCharSet = "GB2312";
}
  }
}
  初始化: 当容器第一次加载该过滤器时, init() 方法将被调用。该类在这个方法中包含了一个指向 FilterConfig 对象的引用。通过FilterConfig对象获得配置文件中设定的字符集。
  过滤: 过滤器的大多数时间都消耗在这里。 doFilter() 方法被容器调用,同时传入分别指向这个请求/响应链中的 ServletRequest 、 ServletResponse 和 FilterChain 对象的引用。在这里完成响应页面字符集的设定。
  析构: 容器紧跟在垃圾收集之前调用 destroy() 方法,以便能够执行任何必需的清理代码。

部署Servlet过滤器,在web.xml文件中配置过滤器

  <!--过滤器配置-->
<filter>  
<filter-name>Encoder</filter-name>  
<filter-class>myproj.filter.SetCharacterEncodingFilter</filter-class>  
<!--初始化参数-->  
<init-param>    
<param-name>newcharset</param-name>    
<param-value>gb2312</param-value>
   </init-param>
</filter>
<!--过滤器与URL关联-->
<filter-mapping>   
<filter-name>Encoder</filter-name>   
<url-pattern>/*</url-pattern>
</filter-mapping>
  编译SetCharacterEncodingFilter类,编译时,需要将Java Servlet API的包servlet-api.jar放到类路径中,编译后的类放到Web应用的WEB-INF\classes目录下,并且目录结构要与包的结构一致。

五、过滤器的应用

1、设置字符编码的过滤器:通过配置参数encoding指明使用何种字符编码,以处理页面的中文问题。
2、使用Filter实现URL级别的权限认证:在实际开发中我们经常把一些执行敏感操作的servlet映射到一些特殊目录中,并用filter把这些特殊目录保护起来,限制只能拥有相应访问权根的用户才能访问这些目录下的资源。
3、实现用户自动登陆的过滤器:在用户登陆成功后,发送一个名为user的cookie给客户端,cookie的值为用户名和加密后的密码。可以通过过滤器检查请求是否带有名称为user的cookie。如果有,则进行验证。
4、实现应用访问的日志:对于到达系统的所有请求,过滤器收集诸如浏览器类型、一天中的时间、转发 URL等相关信息,并对它们进行日志记录。
5、XSLT 转换:不管是使用移动客户端还是使用基于XML的Web服务,无需把逻辑嵌入应用程序就在XML语法之间执行转换的能力都绝对是无价的。


、Servlet监听器用于监听一些重要事件的发生,监听器对象可以在事情发生前、发生后可以做一些必要的处理。

接口: 目前Servlet2.4和JSP2.0总共有8个监听器接口和6个Event类

Listener与对应的Event如下所示:

  1、ServletContextListener---ServletContextEvent

  2、ServletContextAttributeListener---ServletContextAttributeEvent

  3、HttpSessionListener---HttpSessionEvent

  4、HttpSessionActivationListener---HttpSessionEvent

  5、HttpSessionAttributeListener---HttpSessionBindingEvent

  6、HttpSessionBindingListener---HttpSessionBindingEvent

  7、ServletRequestListener--- ServletRequestEvent

  8、ServletRequestAttributeListener---ServletRequestAttributeEvent

(一)ServletContext相关监听接口   

  通过ServletContext 的实例可以存取应用程序的全局对象以及初始化阶段的变量。 在JSP文件中,application 是 ServletContext 的实例,由JSP容器默认创建。Servlet 中调用 getServletContext()方法得到 ServletContext 的实例。 注意:全局对象即Application范围对象,初始化阶段的变量指在web.xml中,经由<context-param>元素所设定的变量,它的范围也是Application范围,例如:

<context-param>   

  <param-name>Name</param-name>   

  <param-value>browser</param-value>

</context-param>

当容器启动时,会建立一个Application范围的对象,若要在JSP网页中取得此变量时: String name = (String)application.getInitParameter("Name");

或者使用EL时: ${initPara.name} 若是在Servlet中,取得Name的值方法: String name = (String)ServletContext.getInitParameter("Name");

1、ServletContextListener: 用于监听WEB 应用启动和销毁的事件,监听器类需要实现javax.servlet.ServletContext
2、Listener 接口。ServletContextListener 是 ServletContext 的监听者,如果 ServletContext 发生变化,如服务器启动时 ServletContext 被创建,服务器关闭时 ServletContext 将要被销毁。

ServletContextListener接口的方法:
void contextInitialized(ServletContextEvent sce) 通知正在接受的对象,应用程序已经被加载及初始化。
void contextDestroyed(ServletContextEvent sce) 通知正在接受的对象,应用程序已经被载出。

ServletContextEvent中的方法:
ServletContext getServletContext() 取得ServletContext对象


ServletContextAttributeListener:用于监听WEB应用属性改变的事件,包括:增加属性、删除属性、修改属性,监听器类需要实现javax.servlet.ServletContextAttributeListener接口。

ServletContextAttributeListener接口方法:
void attributeAdded(ServletContextAttributeEvent scab) 若有对象加入Application的范围,通知正在收听的对象
void attributeRemoved(ServletContextAttributeEvent scab) 若有对象从Application的范围移除,通知正在收听的对象
void attributeReplaced(ServletContextAttributeEvent scab) 

ServletContextAttributeEvent中的方法:
java.lang.String getName() 回传属性的名称
java.lang.Object getValue() 回传属性的值


二 、HttpSession相关监听接口
    通过HttpSession的实例可以实现会话跟踪,HttpSession相关监听接口定义了与会话相关的处理。

1、HttpSessionBindingListener接口 

注意:HttpSessionBindingListener接口是唯一不需要在web.xml中设定的Listener 当我们的类实现了HttpSessionBindingListener接口后,只要对象加入Session范围(即调用HttpSession对象的setAttribute方法的时候)或从Session范围中移出(即调用HttpSession对象的removeAttribute方法的时候或Session Time out的时候)时,容器分别会自动调用下列两个方法:

void valueBound(HttpSessionBindingEvent event)

void valueUnbound(HttpSessionBindingEvent event)

2、HttpSessionAttributeListener接口

HttpSessionAttributeListener监听HttpSession中的属性的操作。 当在Session增加一个属性时,激发attributeAdded(HttpSessionBindingEvent se) 方法;当在Session删除一个属性时,激发attributeRemoved(HttpSessionBindingEvent se)方法;当在Session属性被重新设置时,激发attributeReplaced(HttpSessionBindingEvent se) 方法。这和ServletContextAttributeListener比较类似。
3、HttpSessionListener接口

HttpSessionListener监听HttpSession的操作。当创建一个Session时,激发session Created(HttpSessionEvent se)方法;当销毁一个Session时,激发sessionDestroyed (HttpSessionEvent se)方法。4.HttpSessionActivationListener接口 主要用于同一个Session转移至不同的JVM的情形。
三、ServletRequest监听接口

1、ServletRequestListener接口 和ServletContextListener接口类似,该接口定义ServletRequest对象相关的操作。

2、ServletRequestAttributeListener接口 和ServletContextAttributeListener接口类似,只是该接口监听ServletRequest的属性变化。
以下实例MySessionListener用来监控会话(session)的变化。

package sample;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import javax.servlet.http.HttpSessionActivationListener;
import javax.servlet.http.HttpSessionBindingListener;
import java.io.PrintWriter;
import java.io.FileOutputStream;
public final class MySessionListener
implements HttpSessionActivationListener ,HttpSessionBindingListener ,
HttpSessionAttributeListener, HttpSessionListener,ServletContextListener {
ServletContext context;
int users=0;
//HttpSessionActivationListener
public void sessionDidActivate(HttpSessionEvent se)
{
logout("sessionDidActivate("+se.getSession().getId()+")");
}
  public void sessionWillPassivate(HttpSessionEvent se)
   {
        logout("sessionWillPassivate("+se.getSession().getId()+")");
   }//HttpSessionActivationListener
   //HttpSessionBindingListener
   public void valueBound(HttpSessionBindingEvent event)
   {
               logout("valueBound("+event.getSession().getId()+event.getValue()+")");
   }
   public void valueUnbound(HttpSessionBindingEvent event)
   {
                   logout("valueUnbound("+event.getSession().getId()+event.getValue()+")");
   }
   //HttpSessionAttributeListener
    public void attributeAdded(HttpSessionBindingEvent event) {
        logout("attributeAdded('" + event.getSession().getId() + "', '" +
            event.getName() + "', '" + event.getValue() + "')");
    }
  public void attributeRemoved(HttpSessionBindingEvent event)
{
  logout("attributeRemoved('" + event.getSession().getId() + "', '" +event.getName() + "', '" + event.getValue() + "')");
}
  public void attributeReplaced(HttpSessionBindingEvent se)
{           
logout("attributeReplaced('"+se.getSession().getId()+",'"+se.getName()+"','"+se.getValue()+"')");
}//HttpSessionAttributeListener
 //HttpSessionListener
  public void sessionCreated(HttpSessionEvent event)
{
  users++;
  logout("sessionCreated('" + event.getSession().getId() + "'),目前有"+users+"个用户");
  context.setAttribute("users",new Integer(users));
}

public void sessionDestroyed(HttpSessionEvent event) {
        users--;
        logout("sessionDestroyed('" + event.getSession().getId() + "'),目前有"+users+"个用户");
        context.setAttribute("users",new Integer(users));
  }//HttpSessionListener
    //ServletContextListener
  public void contextDestroyed(ServletContextEvent sce) {
        logout("contextDestroyed()-->ServletContext被销毁");
        this.context = null;
    }
  public void contextInitialized(ServletContextEvent sce) {
        this.context = sce.getServletContext();
        logout("contextInitialized()-->ServletContext初始化了");
  }//ServletContextListener
  private void logout(String message) {
    PrintWriter out=null;
    try{
       out=new PrintWriter(new       FileOutputStream("d:\\temp\\session.txt",true));
       out.println(new java.util.Date().toLocaleString()+"::Form MySessionListener: " + message);
       out.close();
     } catch(Exception e) {
       out.close();
       e.printStackTrace();
     }
}
}