菜鸟学java web(四)---------filter详解

这篇文章中,我们将学习过滤器技术。具体围绕filter的使用场景,如何创建filter,以及filter的使用示例三个方面展开。

一.什么是filter?

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

  • 声明式的:过滤器通过 Web 部署描述符(web.xml)中的 XML 标签来声明。这样允许添加和删除过滤器,而无需改动任何应用程序代码或 JSP 页面。
  • 动态的:过滤器在运行时由 Servlet 容器调用来拦截和处理请求和响应。
  • 灵活的:过滤器在 Web 处理环境中的应用很广泛,涵盖诸如日志记录和安全等许多最公共的辅助任务。过滤器还是灵活的,因为它们可用于对来自客户机的直接调用执行预处理和后期处理,以及处理在防火墙之后的 Web 组件之间调度的请求。最后,可以将过滤器链接起来以提供必需的功能。
  • 模块化的:通过把应用程序处理逻辑封装到单个类文件中,过滤器从而定义了可容易地从请求/响应链中添加或删除的模块化单元。
  • 可移植的:与 Java 平台的其他许多方面一样,Servlet 过滤器是跨平台和跨容器可移植的,从而进一步支持了 Servler 过滤器的模块化和可重用本质。
  • 可重用的:归功于过滤器实现类的模块化设计,以及声明式的过滤器配置方式,过滤器可以容易地跨越不同的项目和应用程序使用。
  • 透明的:在请求/响应链中包括过滤器,这种设计是为了补充(而不是以任何方式替代)servlet 或 JSP 页面提供的核心处理。因而,过滤器可以根据需要添加或删除,而不会破坏 servlet 或 JSP 页面。

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

过滤器原理浅析:

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

二.为什么提供filter技术:
filter是一个可插的java 组件,我们可以使用filter在请求传递给servlet之前对其进行拦截,还可以在servlet处理之后拦截响应信息。
filter的一些常用使用使用场景如下:
1、打印请求参数到日志文件中。
2、对请求的资源进行认证处理(即权限控制)。
3、格式化请求头或者请求体(过滤敏感词,解决乱码)。
4、压缩响应信息。
5、在响应信息上加上cookie或者header等信息。
正如我之前说的,filter是可插拔的,它们被配置在web.xml文件中,我们可以随时更改web.xml文件以决定filter是否被使用。对同一个资源我们可以有多个filter。

三、filter接口简介:
filter接口中提供一系列filter生命周期方法,它被servlet容器所管理。
其方法如下:
void init(FilterConfig paramFilterConfig)
当容器初始化filter时,会调用此方法。这个方法在filter生命周期中仅被调用一次,我们应该在此方法中初始化一些资源。可以通过FilterConfig对象获取初始化参数(init parameter)或者是上下文对象(servletContext).
doFilter(ServletRequest paramServletRequest, ServletResponse paramServletResponse, FilterChain paramFilterChain)
当将一个filter应用到一个资源上时,每次调用资源时都会调用这个方法。这个方法中有一个请求对象和一个响应对象,还有一个FilterChain对象。使用request和response两个对象可以对请求和响应进行一些处理。FilterChain通常是用来调用下一个filter(如果有的话)或者是放行。(责任链模式)
void destroy()
当容器卸载filter实例,将调用此方法。这个方法可以用来关闭资源。这个方法在filter生命周期中仅被调用一次。

四.关于WebFilter注解
这个注解是servlet3.0中用来声明filter的,加上这个注解我们便不需在web.xml中配置filter了。
这个方法可以定义filter初始化参数,filter名,url pattern等。如果你的filter经常会改变,那么建议使用web.xml,因为那样不需要你编译filter。在文章最后的实例中我们使用了这个注解。
五、如何在web.xml中配置过滤器
声明:
<filter>
  <filter-name>RequestLoggingFilter</filter-name> <!-- mandatory -->
  <filter-class>cn.edu.chd.filter.RequestLoggingFilter</filter-class> <!--mandatory-->
  <init-param> <!-- optional -->
  <param-name>test</param-name>
  <param-value>testValue</param-value>
  </init-param>
</filter>
映射:
<filter-mapping>
  <filter-name>RequestLoggingFilter</filter-name> <!-- mandatory -->
  <url-pattern>/*</url-pattern> 
  <servlet-name>LoginServlet</servlet-name>
  <dispatcher>REQUEST</dispatcher>
</filter-mapping>
这里需要注意一下顺序,必须先有filter节点再有filter-mapping节点。
filter通常只有在客户端有请求时才执行,但是有时我们希望执行RequestDispatcher时也执行filter,这时候我们便需要配置dispatcher节点,值可以为:REQUEST,FORWARD,INCLUDE,ERROR,ASYNC.默认是request。
六、实例
1.解决全站乱码问题:
package 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;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;

/**
 * @author Rowand jj
 *
 *过滤乱码的Filter
 */
public class EncodingFilter implements Filter
{
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException
    {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        
        req.setCharacterEncoding("utf-8");
        resp.setCharacterEncoding("utf-8");
        resp.setContentType("text/html;charset=utf-8");
        chain.doFilter(new MyRequest(req), resp);
    }

    @Override
    public void destroy()
    {
        // TODO Auto-generated method stub
        
    }
    @Override
    public void init(FilterConfig filterConfig) throws ServletException
    {
        // TODO Auto-generated method stub
        
    }
    
    class MyRequest extends HttpServletRequestWrapper
    {
        private HttpServletRequest request = null;
        public MyRequest(HttpServletRequest request)
        {
            super(request);
            this.request = request;
        }
        
        /* 拦截getParameter方法,解决get请求乱码问题*/
        @Override
        public String getParameter(String name)
        {
            String value = this.request.getParameter(name);
            try
            {
                if(this.request.getMethod().equalsIgnoreCase("get"))
                {
                    value = new String(value.getBytes("iso8859-1"),this.request.getCharacterEncoding());
                }
                return value;
            } catch (Exception e)
            {
                throw new RuntimeException(e);
            }
        }
        
    }
}
web.xml配置:
 <filter>
      <filter-name>EncodingFilter</filter-name>
      <filter-class>cn.edu.chd.web.filter.EncodingFilter</filter-class>
  </filter>
  
  <filter-mapping>
  <filter-name>EncodingFilter</filter-name>
     <url-pattern>/*</url-pattern>
  </filter-mapping>
以上方式有个缺点就是使用了硬编码,假如我们需要更换码表,则需要改动源码,很不方便。所以我们应该将编码格式放在web.xml的context-param中(使用了动态代理和上面介绍的注解技术):
package cn.edu.chd.filter;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
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.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebFilter("/*")
public class CharacterEncodingFilter implements Filter
{
    private String characterEncoding = "iso8859-1";//默认编码
    public void destroy()
    {
    }
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException
    {
        final HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        
//        设置post请求的编码
        req.setCharacterEncoding(characterEncoding);
        
//        设置响应的编码
        resp.setCharacterEncoding(characterEncoding);
        resp.setContentType("text/html;charset="+characterEncoding);
        
//        解决get请求乱码
        chain.doFilter((ServletRequest) Proxy.newProxyInstance(CharacterEncodingFilter.class.getClassLoader(),req.getClass().getInterfaces(),new InvocationHandler()
        {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable
            {
                if(!method.getName().equals("getParameter"))//拦截getParameter方法
                {
                    return method.invoke(req, args);
                }
                if(!req.getMethod().equalsIgnoreCase("get"))//拦截get请求
                {
                    return method.invoke(req, args);
                }
                String value = (String) method.invoke(req, args);
                if(value == null)
                    return null;
                return new String(value.getBytes("iso8859-1"),characterEncoding);
            }
        }), resp);
    }
    public void init(FilterConfig fConfig) throws ServletException
    {
//        使用web.xml文件中配置的编码
        characterEncoding = fConfig.getServletContext().getInitParameter("CharacterEncoding");
    }
}
web.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" 
    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_2_5.xsd">
    
<context-param>
    <param-name>CharacterEncoding</param-name>
    <param-value>utf-8</param-value>
</context-param>
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>
2.解决全站压缩问题:
原理是在过滤器中增强response对象,将增强后的response对象返回给servlet,servlet操作的response对象其实是增强后的response,当调用这个response的getOutputStream/getWriter等写方法时,会将写出的数据全部存到byteArrayOutputStream的内部缓冲区中,而不是直接返回给浏览器。然后过滤器再将缓冲区中的数据压缩,统一打给浏览器,这样就做到了压缩数据的功能。
package cn.edu.chd.filter;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.zip.GZIPOutputStream;

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

public class GzipFilter implements Filter
{
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException
    {
        HttpServletRequest req = (HttpServletRequest) request;
        final HttpServletResponse resp = (HttpServletResponse) response;
        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
        
        OutputStreamWriter osw = new OutputStreamWriter(baos,"utf-8");//硬编码...
        final PrintWriter pw = new PrintWriter(osw);//别忘了这里要设置编码
//        拦截getWriter和getOutputStream方法
        chain.doFilter(req, (ServletResponse) Proxy.newProxyInstance(GzipFilter.class.getClassLoader(),resp.getClass().getInterfaces(),new InvocationHandler()
        {
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable
            {
                if(method.getName().equals("getWriter"))
                {
                    return pw;
                }else if(method.getName().equals("getOutputStream"))
                {
                    return new MyServletOutputStream(baos);
                }else
                {
                    return method.invoke(resp, args);
                }
            }
        }));
        
        pw.close();
        byte[] data = baos.toByteArray();//获取缓冲区中的数据
        System.out.println("[压缩前]:"+data.length);
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        GZIPOutputStream gout = new GZIPOutputStream(bout);
        gout.write(data);
        gout.close();
        data = bout.toByteArray();
        System.out.println("[压缩后]:"+data.length);
        resp.setHeader("Content-Encoding","gzip");
        resp.setContentLength(data.length);
        resp.getOutputStream().write(data);
    }

    public void init(FilterConfig filterConfig) throws ServletException
    {
    }
    
    public void destroy()
    {
    }
}
class MyServletOutputStream extends ServletOutputStream
{
    ByteArrayOutputStream baos = null;
    public MyServletOutputStream(ByteArrayOutputStream baos)
    {
        this.baos = baos;
    }
    @Override
    public void write(int b) throws IOException
    {
        baos.write(b);
    }
}
web.xml文件:
<filter>
      <filter-name>GzipFilter</filter-name>
    <filter-class>cn.edu.chd.filter.GzipFilter</filter-class>
  </filter>
  <filter-mapping>
      <filter-name>GzipFilter</filter-name>
      <url-pattern>/*</url-pattern>
  </filter-mapping>









菜鸟学java web(四)---------filter详解

上一篇:extjs 数字校园-云资源平台 2013.10.28--网盘


下一篇:php zend Framework 2 入门