首先说明一点,以下的测试方法只有一个HttpServletRequest.forward,但是基于原理上的讲解,其他乱码问题应该也可以从中得到一些启示。不敢保证百分百正确,但能提供一个大致的方向。
下面为测试入口servlet的代码,其中的getWriter被注释掉,后面讲其作用。
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
//PrintWriter out=resp.getWriter();
resp.setHeader("Expires","0");
resp.setHeader("Cache-Control","no-cache,no-store");
resp.setHeader("Pragma","no-cache");
req.getRequestDispatcher("/WEB-INF/jsp/in2.jsp").forward(req,resp);
}
测试流程就是很简单的从一个servlet转到另一个jsp或者html上。
一.从servlet转到jsp页面
<1>直接转到一个jsp
首先我们应该知道,jsp页面实际上会转换成一个servlet,至于存在那个地方,可以自行百度,不同的系统以及集成开发环境会使之存在不同的地方。下面为测试的第一个jsp文件in2.jsp,很简单,直接输出一段话。
<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8"%>
<html>
<head>
<title>Title</title>
</head>
<body>
<%
out.print("我是in2.jsp文件的内容");
//out.print("参数为:"+request.getParameter("param"));
%>
</body>
</html>
其中的page里面设置的几个UTF-8编码有必要讲下它的作用范围:
- charset="UTF-8":它是设置的response的输出编码,其实设置的就是Content-type头字段里面的编码,是告诉浏览器以什么方式解码的。
- pageEncoding="UTF-8":这个是指用什么编码格式打开这个文件,而不是以什么格式保存这个文件,虽然想要得到正确的内容,打开文件的格式和文件编码的格式必须一致,但这个逻辑关系必须理清楚。
- 其实还有另外一个属性可以设置,那就是<head>标签下的<meta>标签可以设置文档的编码格式,它作用的是html文档的内容,告诉浏览器以什么编码格式来解析文档。对于jsp而言,它会被content-type的charset给覆盖掉。如果没有设置content-type,指的是<%@ page%>里设置的,生成的servlet会默认使用"text/html;charset=ISO-8859-1",<meta>标签里设置的无效,在jsp里设置和不设置影响不到浏览器以什么格式解码。但在HTML里就不一样,因为HTML不生成servlet,所以可以指定浏览器以什么格式解码。但有一点及其重要,即便HTML以UTF-8格式保存,标签告诉浏览器以UTF-8格式解码,但也可能产生乱码,那是因为,服务器在写入HTML时,是以默认的格式写入的,它没有pageEncoding指定打开写入的格式,而默认格式为ANSI,不一定为UTF-8,所以就产生了乱码!!!
下面的jsp转换成一个servlet的流程必须清楚:
- 将指定的jsp文件以pageEncoding指定的编码格式打开,转换成一个.java文件
- 类装载器以utf-8的格式读取.java文件,转换成一个.class文件,在将这个.class文件以Unicode编码格式载入虚拟机,现在就相当于一个servlet
上述in2.jsp文件是UTF-8格式保存的,接下来打开它转换成的servlet,片段代码如下,可以看到没有乱码
out.write("
");
out.write("
");
out.write("<html>
");
out.write("<head>
");
out.write(" <title>Title</title>
");
out.write("</head>
");
out.write("<body>
");
out.print("我是in2.jsp文件的内容");
//out.print("参数为:"+request.getParameter("param"));
out.write("
");
out.write("</body>
");
out.write("</html>
");
浏览器端的输出也是正常的
现在我们改动pageEncoding=ISO-8859-1
再次查看jsp转换成的servlet
out.write("
");
out.write("
");
out.write("<html>
");
out.write("<head>
");
out.write(" <title>Title</title>
");
out.write("</head>
");
out.write("<body>
");
out.print("ææ¯in2.jspæ件çå容");
//out.print("åæ°ä¸º:"+request.getParameter("param"));
out.write("
");
out.write("</body>
");
out.write("</html>
");
可以看到已经乱码了,这就是以ISO-8859-1编码打开UTF-8编码文件时产生的乱码,网页输出端也是如此
因此,可以总结出,如果要保证不乱码,必须以文件编码的格式打开文件,而且还得指定浏览器以同样的编码格式解码。这儿并未测试response设置编码格式,但得注意这点!
<2>现在改变一下测试方式,从一个servlet转到test.jsp,然后test.jsp转到in2.jsp
test.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>测试jsp</title>
</head>
<body>
<jsp:forward page="in2.jsp">
<jsp:param name="param" value="data数据"/>
</jsp:forward>
</body>
</html>
in2.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="ISO-8859-1"%>
<html>
<head>
<title>Title</title>
</head>
<body>
<%
out.print("我是in2.jsp文件的内容");
out.print("参数为:"+request.getParameter("param"));
%>
</body>
</html>
入口servlet
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// resp.setContentType("text/html;charset=utf-8");
// PrintWriter out=resp.getWriter();
resp.setHeader("Expires","0");
resp.setHeader("Cache-Control","no-cache,no-store");
resp.setHeader("Pragma","no-cache");
req.getRequestDispatcher("/WEB-INF/jsp/test.jsp").forward(req,resp);
}
网页端得输出为以下情况:
in2.jsp文件的内容没问题,test.jsp中设置的参数汉字乱码,这个也可以从test_jsp.java里面看到原因,是因为这个"data数据"是更据request的编码格式来写入的,默认为ISO-8859-1,将其改变为utf-8则正常显示。在设置参数之前改变编码,不要在乱码之后改变编码,那时没用了,可以在jsp里面改变,也可以在入口servlet改变,如req.setCharacterEncoding("utf-8");。
二.从一个servlet转到一个静态HTML
静态HTML文件如下in1.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html" charset="UTF-8">
<title>Title</title>
</head>
<body>
这是in1.html里面的内容
</body>
</html>
入口servlet的代码如下:
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
PrintWriter out=resp.getWriter();
resp.setHeader("Expires","0");
resp.setHeader("Cache-Control","no-cache,no-store");
resp.setHeader("Pragma","no-cache");
req.setCharacterEncoding("utf-8");
req.getRequestDispatcher("/WEB-INF/jsp/in1.html").forward(req,resp);
}
结果为如下,乱码了,归根揭底其实也是pageEncoding的原因
因为HTML文件无法设置pageEncoding,也不会生成servlet,所以我也不能确定是不是pageEncoding的原因,因此作出假设,转发到HTML后,HTML的打开方式为以下2种情况。
- 是以UTF-8的格式打开
- 不是以UTF-8的格式打开,这个格式我等下说明
以notepad++打开这个in1.html,并且将编码格式设置为ANSI,则结果如下,刚好和浏览器输出一致
因此我猜测是以ANSI的格式打开HTML,这个格式会因操作系统和地区而已,中国WINDOWS为GBK格式
下面我另存为一个in2.html格式保存为ANSI
并将servlet的转发到in2.html,输出正常
因此,我认为一个HTML文件在集成开发创建时是UTF-8的格式,这个格式在IDE上应该可以设置,但在打开时是更具默认编码格式打开的(即ANSI),因此会产生乱码,当然,这只是找到了原因。而解决的办法就是将这个HTML以我们想要的格式打开,即UTF-8,但又不能设置pageEncoding,因此我们可以将HTML当作JSP处理,静态HTML是可以转成JSP的。
这种途径可以配置XML文件,添加如下XML语句
<jsp-config>
<jsp-property-group>
<url-pattern>*.html</url-pattern>
<page-encoding>UTF-8</page-encoding>
</jsp-property-group>
</jsp-config>
意思是,任何以html结尾的URL请求的资源,都已UTF-8格式打开,因此在次转到in1.html则正常了。如果配置了XML就不能在访问in2.html,会乱码,因为in2.html的编码格式为ANSI,以UTF-8的格式打开会乱码。
关于最开始说的PrintWriter out=resp.getWriter的作用关于另一个知识点,对于一切没有在XML中配置的servlet,都是采用缺省servlet访问,关于缺省servlet,可以百度查看更多。如上面测试时,如果在转发之前没有使用getWriter,则缺省servlet使用的是字节流输出,如果使用了getwriter则使用字符流输出,字节流同理。关于content-type头字段的charset编码格式和字节流字符流之间又会产生多种情况,如使用字符流,但不指定charset,则默认的格式为ISO-8859-1,用来输出中文则会乱码;使用字节流时,不论是否设置为utf-8,都不会产生乱码,按理字节流使用utf-8输出中文会乱码,我没有去看这个缺省servlet的源码,只能猜测使用字节流时都采用ISO-8859-1来输出。