首页 > 代码库 > 扯淡过滤器之乱码篇
扯淡过滤器之乱码篇
在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(); }
代码看起来似乎比干巴巴的说一大堆效果好,别人的思想,自己理解接受了,就是自己的,加油。
为了心中的美好,不妥协直到变老。