首页 > 代码库 > 扯淡过滤器之乱码篇

扯淡过滤器之乱码篇

  在JavaWeb开发中,Servlet过滤器可以很方便地帮助开发者做很多重复的事情,比如说这里要和大家分享的乱码问题。其实说起乱码自己也没有什么经验可谈,只是东拼西凑来出来的一些代码,这里说过滤器是一方面,另一方面还有其中用到的一些思想上的东西。
  乱码产生的原因说来说去就一句话,编码和解码用的码表不同造成。但是要弄清楚这其中的原理,怕是自己也不清楚,只好扬长避短。Web开发中的乱码就发生在服务器和浏览器之间,这样根据乱码的作用者可以分为请求参数乱码和响应内容乱码,响应乱码容易解决,只要response.setContentType("text/html;charset=utf-8"),就可以简单搞定;但是对于请求参数乱码又分了post提交和get提交参数的乱码处理,这两种提交方式的乱码处理也不一样,当然还有其他提交方式,只是不常用就没有考虑。
  对于post提交,使用request.setCharacterEncoding("utf-8")也可以简单搞定,但是get方式提交的中文数据就不能这样来处理。首先搞清楚产生的原因很有必要:浏览器会以服务器response中设置的编码发送请求参数到服务器,但是服务器并不知道以什么编码来解码,默认情况下会使用ISO-8859-1字符集对参数解码,而ISO-8859-1中没有中文,所以这种情况下查出来的基本上都会是"?"来显示,可以肯定的是,服务器接收到的编码本身是没有问题,只是他自作主张查了错误的编码表,所以我们就可以用ISO-8859-1编码表再查回去,然后再重新以正确的编码表查出来就解决了get方式提交的参数乱码问题,在代码上的体现也可以很直观:
    request.getParameter("nickname") -- 查错码表的数据
    request.getParameter("nickname").getBytes("ISO-8859-1") -- 使用错误的码表再错误一次
    new String(request.getParameter("nickname").getBytes("ISO-8859-1"),"utf-8") -- 正确的表得正确
  这样我们的get提交的中文乱码也可以解决了,所以通过以上剖析可以开发自己的全站乱码过滤器了,还等什么呢?代码奉上。

package com.hitech.filter;

import java.io.IOException;
import java.util.Map;

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;

public class ApplicationEncodingFilter implements Filter {

    private String encoding = null;
    public void init(FilterConfig filterConfig) throws ServletException {
        this.encoding = filterConfig.getInitParameter("encoding");
    }

    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        // 解决响应中文乱码
        response.setContentType("text/html;charset=" + encoding);
        // 解决请求中文乱码
        chain.doFilter(new _HttpServletRequest((HttpServletRequest) request), response);
    }

    public void destroy() {

    }

    class _HttpServletRequest extends HttpServletRequestWrapper {

        private HttpServletRequest request = null;
        // 由于Map的缓存,所以需要控制只对请求参数做一次编解码
        private boolean flag = true;
        // 父类没有提供无参构造,所以这里也只能有无参构造
        public _HttpServletRequest(HttpServletRequest request) {
            super(request);
            this.request = request;
        }

        @SuppressWarnings("unchecked")
        @Override
        public Map<String, String[]> getParameterMap() {
            try {
                if ("post".equals(request.getMethod().toLowerCase())) {
                    // 如果是post提交的参数,只需要指定编码
                    request.setCharacterEncoding(encoding);
                    return request.getParameterMap();
                }else if ("get".equals(request.getMethod().toLowerCase())) {
                    Map<String, String[]> parameterMap = request.getParameterMap();
                    // 对于get提交的参数,系统默认用ISO-8859-1解码,所以要先用ISO-8859-1先编码回去,再按指定编码解码
                    if (flag) {// 由于Map会对提交过来的数据进行缓存,所以只需要编码一次以防止正确的编码再次被编码
                        // 遍历Map,对String[]中的每一项值进行重新编解码
                        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
                            String[] values = entry.getValue(); 
                            for (int i = 0; i < values.length; i++) {
                                values[i] = new String(values[i].getBytes("ISO-8859-1"),encoding);
                            }
                        }
                        // 编码一次后,不再需要编码
                        flag = false;
                    }
                    return parameterMap;
                }else {
                    // 其他方式提交的数据,不作处理,直接返回
                    return super.getParameterMap();
                }
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }
        @Override
        public String[] getParameterValues(String name) {
            // 从getParameterMap中的取出已经修改好的Values[]
            return getParameterMap().get(name);
        }

        @Override
        public String getParameter(String name) {
            // 需要对NullPointerException做处理
            String[] parameterValues = getParameterValues(name);
            return parameterValues == null ? null : parameterValues[0];
        }
    }
}

  有了filter,再在web.xml中配置filter便可以使用。

  <filter>
      <filter-name>CodecFilter</filter-name>
      <filter-class>com.hitech.filter.ApplicationEncodingFilter</filter-class>
      <init-param>
          <param-name>encoding</param-name>
          <param-value>utf-8</param-value>
      </init-param>
  </filter>
    
  <filter-mapping>
      <filter-name>CodecFilter</filter-name>
      <url-pattern>/*</url-pattern>
  </filter-mapping>    

  完成了filter之后,再看看其中用到的一种设计模式--包装模式,谈到包装模式先说说对于一个对象上的方法进行重写,可以有三种方式完成。
  1、使用继承
      一个类继承另一个类后,可以对父类中已经的方法进行重写,以完成子类特有的功能。但是在这里似乎不太合适,原因是无法改变ServletRequest的继承结构。
  2、使用包装设计模式
     显然这个方式在这里是可行的,也就是使用一个类实现和要包装的类相同的接口,构造方法中接受一个需要包装的对象,然后就可以重新定义其中的方法。
  3、动态代理
   使用java中的反射机制也可以完成这样的功能,但是看起来似乎稍微有些难以理解,上来一个demo吧。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class DynamicProxy {
    public static void main(String[] args) {
        final ProxyDemo demo = new ProxyDemo();
        Proxy proxy = (Proxy)java.lang.reflect.Proxy.newProxyInstance(ProxyDemo.class.getClassLoader(), ProxyDemo.class.getInterfaces(), new InvocationHandler() {
            
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                // 如果只想重新定义function方法可以这样
                if ("function".equals(method.getName())) {
                    // 这里重新定义function方法的功能
                    System.out.println("This is a new function");
                    return null;
                }else {
                    return method.invoke(demo, args);
                }
            }
        });
        proxy.method();
        proxy.function();
    }
}
class ProxyDemo implements Proxy{

    public ProxyDemo(){}
    
    public void method() {
        System.out.println("This is a method.");
    }

    public void function() {
        System.out.println("This is a function.");
    }
}
interface Proxy{
    public abstract void method();
    public abstract void function();
}

  代码看起来似乎比干巴巴的说一大堆效果好,别人的思想,自己理解接受了,就是自己的,加油。
  为了心中的美好,不妥协直到变老。