一. 乱码产生原因
浏览器在向服务器发送请求时会对携带的请求参数进行编码(UTF-8格式),服务器在接收到请求参数时会对其进行解码,由于浏览器与服务器编码格式不同产生乱码。不同服务器默认编码格式不同,Tomcat默认ISO-8859-1。
二. get、post请求方式乱码解决方案——简单版
1. get请求
get请求有三种解决方案。
a. 通过先编码再解码方式。服务器以不同编码格式解码后导致乱码,此时可以通过先以与服务器相同的编码格式将字符串编码成原始的字节流,再通过String类的构造方法解码生成正确的字符串。代码如下:
String newJoo = new String(joo.getBytes("ISO-8859-1"), "UTF-8"); //joo为字符串
String newJoo = new String(joo.getBytes("ISO-8859-1"), "UTF-8"); //joo为字符串
b. 修改tomcat服务器配置文件,设置url编码格式。tomcat安装目录下conf文件夹内server.xml配置文件,在<Connector connectionTimeout="20000" port="8090" protocol="HTTP/1.1" redirectPort="8443" />中添加 URIEncoding="UTF-8"属性。该方法对post乱码无作用,因为它只能设置url中携带的请求参数,而post请求参数存放在请求体中。
c. 在页面中用js函数如:encoding(),进行编码,后台用java代码解码。这种方法麻烦,就不多说了。
三种方法各有各的缺陷,使用a方法需要在每个servlet类中都添加那段代码,造成代码冗余,可维护性差;使用b方法可以达到一劳永逸的效果,但是如果服务器中有其他项目需要用不同的url编码格式怎么办?所以这样不灵活,不友好,而且对post请求乱码无效;c方法的缺陷同a方法。
2. post请求
post请求乱码解决方式很简单,只需要一行代码。request对象中有个设置编码格式的方法:
request.setCharacterEncoding("utf-8")
request.setCharacterEncoding("utf-8")
同样的,这种方法也有缺陷。首先,它造成代码冗余,使可维护性变差,其次,它只对请求体中的参数有作用,对get请求方式乱码无效。
三. post get乱码解决方案——Filter版
1. 如何做
在Servlet中获取请求参数有三种方法:getParameter(),getParameterValues(),getParameterMap(),getParameter()返回String类型,用于获取单个请求参数,getParameterValues()返回String[]类型,也用于获取单个请求参数,只不过这个请求参数包含多个值,如复选框,getParameterMap()返回值为Map<String, String[]>,用于获取所有请求参数。
ServletRequest接口中定义了这三个方法,tomcat实现这三个方法,但实现源码并没有做编码相关的处理,如果我们自己定义一个类实现ServletRequest接口然后重写这三个方法,并在其中解决post和get乱码不就既可以一劳永逸解决问题,避免代码冗余,同时还保证了灵活性,确保不会影响同服务器中的其它项目。但是实现ServletRequest接口就必须实现其中的所有方法,工作量太大!
sun公司早已考虑到这个问题,很体贴的设计了一个包装类实现ServletRequest接口,我们只需要定义一个类继承HttpServletRequestWrapper类,并重写三个方法就能达到目的。先来看看HttpServletRequestWrapper类的继承关系:
public class HttpServletRequestWrapper extends ServletRequestWrapper implements HttpServletRequest 可以看到它继承了ServletRequestWrapper类,实现了HttpServletRequest接口,所以HttpServletRequestWrapper实现了ServletRequest接口的所有方法。
我们的目的是要让所有Servlet都使用重写的方法,上面我们解决了怎么重写三个获取请求参数的方法,但是还有一个问题——如何才能让所有Servlet都使用我们重写的方法,即在哪里重写这三个方法。解决这个问题首先要明白Servlet的生命周期,这里简单的介绍下,tomcat启动时实例化Servlet对象并执行其中的init()方法进行初始化操作,当tomcat接收到请求时会创建HttpServletRequest和HttpServletResponse对象,然后执行Servlet对象中的Service()方法判断请求方式并调用doGet()或者doPost()方法,这里会将两对象当作参数传递给doGet()和doPost()方法,当tomcat正常stop时执行Servlet类中的destory()方法进行销毁操作。知道了Servlet的生命周期我们知道,如果要让所有Servlet类都使用重写的方法我们可以修改tomcat的源代码,使它在创建HttpServletRequest和HttpServletResponse对象的过程中创建的是我们自定义对象,但这样工作量很大,而且还会影响其他项目,不灵活也不友好。到这里java web三大组件之一Filter就浮出水面了。
Filter——过滤器,可以先于所有Servlet执行,我们在自定义的Filter中定义个类继承HttpServletRequestWrapper,重写三个获取请求参数方法,再将它传递给Servlet就可以达到我们的目的。
2. 代码实现
web.xml配置
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>ff.charset.EncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
自定义Filter类
public class EncodingFilter implements Filter{
private String encoding; // 编码格式
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 从web.xml中获取编码格式
this.encoding = filterConfig.getInitParameter("encoding");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 向下转型成HttpServletRequest
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
// 设置数据响应编码格式
response.setContentType("text/html;charset=" + encoding);
// 创建装饰类
MyHttpRequest myhr = new MyHttpRequest(req, encoding);
// 放行
chain.doFilter(myhr, res);
}
@Override
public void destroy() {
// TODO Auto-generated method stub
}
}
public class EncodingFilter implements Filter{
private String encoding; // 编码格式
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 从web.xml中获取编码格式
this.encoding = filterConfig.getInitParameter("encoding");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 向下转型成HttpServletRequest
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
// 设置数据响应编码格式
response.setContentType("text/html;charset=" + encoding);
// 创建装饰类
MyHttpRequest myhr = new MyHttpRequest(req, encoding);
// 放行
chain.doFilter(myhr, res);
}
@Override
public void destroy() {
// TODO Auto-generated method stub
}
}
自定义类继承HttpServletRequestWrapper
上面提到了getParameterMap()用于获取所有请求参数,那它也可以获取单个请求参数,所有我们只需要重写这个方法,然后其他两方法调用此方法就可以。
public class MyHttpRequest extends HttpServletRequestWrapper {
private HttpServletRequest req; // 原始请求对象
private String encoding; // 编码格式
private boolean flag = true; // 开关
public MyHttpRequest(HttpServletRequest request, String encoding) {
super(request);
this.req = request;
this.encoding = encoding;
}
@Override
public String getParameter(String name) {
Map<String, String[]> map = getParameterMap();
String[] values = map.get(name);
if (null == values) {
return null;
}
return values[0];
}
@Override
public String[] getParameterValues(String name) {
Map<String, String[]> map = getParameterMap();
return map.get(name);
}
@Override
public Map<String, String[]> getParameterMap() {
String method = req.getMethod();
if (method.equalsIgnoreCase("post")) {
try {
req.setCharacterEncoding(encoding);
return req.getParameterMap();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
} else if (method.equalsIgnoreCase("get") && true == flag) {
// 通过flag使这段代码一次请求只执行一次,因为执行多次相当于解码再编码多次,造成乱码
Map<String, String[]> map = req.getParameterMap(); // 对象赋值传递的是地址,map指向req地址,修改map值相当于修改req中属性值
Set<String> set = map.keySet();
// 迭代遍历map中所有值,通过解码再编码解决get乱码
Iterator<String> iterator = set.iterator();
while (iterator.hasNext()) {
String[] values = map.get(iterator.next());
if (null != values) {
for (int i = 0; i < values.length; i++) {
try {
values[i] = new String(values[i].getBytes("ISO-8859-1"), encoding);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
}
flag = false;
return map;
}
// 其它请求执行原始获取参数方法,除了get post外还有其他请求方式
return super.getParameterMap();
}
}
public class MyHttpRequest extends HttpServletRequestWrapper {
private HttpServletRequest req; // 原始请求对象
private String encoding; // 编码格式
private boolean flag = true; // 开关
public MyHttpRequest(HttpServletRequest request, String encoding) {
super(request);
this.req = request;
this.encoding = encoding;
}
@Override
public String getParameter(String name) {
Map<String, String[]> map = getParameterMap();
String[] values = map.get(name);
if (null == values) {
return null;
}
return values[0];
}
@Override
public String[] getParameterValues(String name) {
Map<String, String[]> map = getParameterMap();
return map.get(name);
}
@Override
public Map<String, String[]> getParameterMap() {
String method = req.getMethod();
if (method.equalsIgnoreCase("post")) {
try {
req.setCharacterEncoding(encoding);
return req.getParameterMap();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
} else if (method.equalsIgnoreCase("get") && true == flag) {
// 通过flag使这段代码一次请求只执行一次,因为执行多次相当于解码再编码多次,造成乱码
Map<String, String[]> map = req.getParameterMap(); // 对象赋值传递的是地址,map指向req地址,修改map值相当于修改req中属性值
Set<String> set = map.keySet();
// 迭代遍历map中所有值,通过解码再编码解决get乱码
Iterator<String> iterator = set.iterator();
while (iterator.hasNext()) {
String[] values = map.get(iterator.next());
if (null != values) {
for (int i = 0; i < values.length; i++) {
try {
values[i] = new String(values[i].getBytes("ISO-8859-1"), encoding);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
}
flag = false;
return map;
}
// 其它请求执行原始获取参数方法,除了get post外还有其他请求方式
return super.getParameterMap();
}
}