β

JSP 中的字符集编码与乱码问题

Just_do_挨踢 50 阅读

<p> 在说完了网页中的编码与乱码( <a rel="nofollow" target="_blank"> 一 </a> 、 <a rel="nofollow" target="_blank"> 二 </a> 、 <a rel="nofollow" target="_blank"> 三 </a> 、 <a rel="nofollow" target="_blank"> 四 </a> 、 <a rel="nofollow" target="_blank"> 五 </a> ), <a rel="nofollow" target="_blank"> servlet 中的编码问题 </a> 后,这次来探讨一下 JSP 中的编码与乱码问题。 </p> <p> 在之前,曾谈到过 <a rel="nofollow" target="_blank"> JSP 与 HTML 间的关系 </a> ,JSP 本质上是一个 HTML 的模板,用于在服务端动态生成 HTML,这点跟 servlet 是类似。 </p> <blockquote> <p> 其实 JSP 本质就是 servlet,一个 JSP 页面它会被编译成一个 java 文件,实际上就是一个 servlet 类(或其子类,在文章的后面会具体讨论这个问题)。 </p> </blockquote> <h2> 一个具有多个编码的 JSP 页面 </h2> <p> 关于 JSP 中的编码设置,有好几处的地方值得注意。用一个例子来说吧,比如下面这个叫 testEncoding.jsp 的页面: </p> <p> <img alt="jsp contentType pageEncoding meta charset diff value" height="288" src="/imgr?src=https%3A%2F%2Fstatic.oschina.net%2Fuploads%2Fimg%2F201711%2F08085346_1xMZ.png&r=https%3A%2F%2Fmy.oschina.net%2Fgoldenshaw%2Fblog%2F1568035" title="jsp contentType pageEncoding meta charset diff value" width="637"/> </p> <p> 有三处地方出现了编码相关的信息(contentType,pageEncoding,meta charset),而且还是矛盾的,有 utf-8,gbk 和 iso-8859-1。(正常情况下你应该保持三者一致)那么现在问题来了: </p> <ol> <li> 这个源文件到底是什么编码? </li> <li> 最终生成并发送到浏览器的字节流又是什么编码? </li> <li> 会不会出现乱码?(包括用编辑器打开以及在浏览器中打开时) </li> </ol> <p> 这三个矛盾的编码可能还是会让很多人困惑的,可能会觉得被搞晕了,下面将一一分析以上问题。 </p> <h2> 源文件的编码与 pageEncoding </h2> <p> 自然,需要明白 <a class="referer" href="https://my.oschina.net/u/937418" target="_blank"> @page </a> 指令中各个属性的含义,比如 contentType,有了前面的 <a rel="nofollow" target="_blank"> 网页中的编码与乱码(3) </a> 的基础等,应该不难猜出它就是指定了 response header 中的 content-type 字段的值。 </p> <p> 那么 pageEncoding 又是什么呢?其实从字面上也不难猜出,它就是页面的编码,也就是这个 JSP 源文件本身的编码。 </p> <p> 在 eclipse 中,在文件上“右键--属性(properties)”,在弹出的属性框中可以发现文件的编码本身就是 gbk: </p> <p> <img alt="jsp file properties text file encoding determined from content" height="368" src="/imgr?src=https%3A%2F%2Fstatic.oschina.net%2Fuploads%2Fimg%2F201711%2F08085346_a1z3.png&r=https%3A%2F%2Fmy.oschina.net%2Fgoldenshaw%2Fblog%2F1568035" title="jsp file properties text file encoding determined from content" width="683"/> </p> <p> 而且它还加了一句话(determined from content:GBK),也就是从内容中推断出来的。而页面的内容中不就是 pageEncoding 那里才有 gbk 吗,所以这个属性就是源文件编码本身。如果你调整了它的值,推断出的值也会跟着改变,你可以自行试试。 </p> <blockquote> <p> 注意:只有智能的编辑器(比如专门的 JSP 编辑器)才能做这些推断,普通的编辑器是不行的。在前面的 <a rel="nofollow" target="_blank"> 网页中的编码与乱码(2) </a> 中曾经谈过这个问题。 </p> </blockquote> <h2> JSP 页面编码的官方参考 </h2> <p> 关于 JSP 页面编码的官方参考见这里: <a href="https://docs.oracle.com/cd/E19316-01/819-3669/bnayf/index.html" rel="nofollow"> https://docs.oracle.com/cd/E19316-01/819-3669/bnayf/index.html </a> </p> <p> 在此官方文档里它提到: </p> <blockquote> <p> For JSP pages, the <b> page encoding </b> is the character encoding in which the file is encoded. </p> <p> For JSP pages in standard syntax, the page encoding is determined from the following sources: </p> <ul> <li> <p> The page encoding value of a JSP property group (see <a href="https://docs.oracle.com/cd/E19316-01/819-3669/bnajg/index.html" rel="nofollow"> Setting Properties for Groups of JSP Pages </a> ) whose URL pattern matches the page. </p> </li> <li> <p> The pageEncoding attribute of the page directive of the page. It is a translation-time error to name different encodings in the pageEncoding attribute of the page directive of a JSP page and in a JSP property group. </p> </li> <li> <p> The CHARSET value of the contentType attribute of the page directive. </p> </li> </ul> <p> If none of these is provided, ISO-8859-1 is used as the default page encoding. </p> </blockquote> <p> 简单翻译一下,JSP 页面编码的确定有四个途径(越前面的优先级越高): </p> <ol> <li> JSP 属性组(JSP property group)中的配置。 </li> <li> page 指令中的 pageEncoding 属性。 </li> <li> page 指令中的 contentType 属性下的 charset 的值。 </li> <li> 以上均无提供,使用缺省值,具体为 ISO-8859-1。 </li> </ol> <p> 由上可见,pageEncoding 属性它的优先级还不是最高的,最高的是 JSP 属性组中的配置,这个到底是什么呢? </p> <h2> JSP 属性组中的 page-encoding </h2> <p> 其实它就是 web.xml 中的一个配置项,比如下面就配置了一个全局的 JSP 文件编码,值为 utf-8: </p> <p> <img alt="web.xml jsp-config jsp-property-group page-encoding config" height="336" src="/imgr?src=https%3A%2F%2Fstatic.oschina.net%2Fuploads%2Fimg%2F201711%2F08085346_4GlX.png&r=https%3A%2F%2Fmy.oschina.net%2Fgoldenshaw%2Fblog%2F1568035" title="web.xml jsp-config jsp-property-group page-encoding config" width="463"/> </p> <p> 具体为 jsp-config 标签下的 jsp-property-group 标签下的 page-encoding 标签所指定的值,再结合 url-pattern 标签指定一个适配范围。 </p> <p> 假如增加以上配置,那么它这里配置的 utf-8 实际上与现在这个文件中的 pageEncoding=”gbk”是冲突的,这种不一致会导致 servlet 容器(这里也就是 tomcat)报错: </p> <p> <img alt="page-encoding specified in jsp-property-group is different from that specified in page directive. http status 500" height="373" src="/imgr?src=https%3A%2F%2Fstatic.oschina.net%2Fuploads%2Fimg%2F201711%2F08085346_2xkg.png&r=https%3A%2F%2Fmy.oschina.net%2Fgoldenshaw%2Fblog%2F1568035" title="page-encoding specified in jsp-property-group is different from that specified in page directive. http status 500" width="670"/> </p> <p> 既然你在 web.xml中配置了 page-encoding,tomcat 遵循 JSP 规范会优先采纳它。 </p> <blockquote> <p> 这也很好理解,既然你都专门配置了来告诉我文件的编码,tomcat 都不需要自己去检测了,它何乐而不为呢? </p> </blockquote> <p> 但它接着又发现文件里面还指定了 pageEncoding,值与配置的还不一致,于是它就被搞糊涂了,抛出了以上异常,抱怨两者不一致。 </p> <blockquote> <p> 其实是 JSP 中的规定,说不一致就抛异常,tomcat 作为一个 servelt 容器只是遵循了这些规范。另:手动测试时,当在 web.xml 中增加配置后,你可能需要清理一下 tomcat 的缓存,以确保 JSP 文件被重新读取编译等。 </p> </blockquote> <p> 所以,配置这种全局属性要小心,要么不要采用它;要么可以通过 url-patterm 缩小匹配的范围,确保它只应用在正确的文件或文件夹下。 </p> <blockquote> <p> 另一方面要保证保存文件的真实编码确实使用了配置的值。这点 <a rel="nofollow" target="_blank"> 在前面 </a> 也一再强调过。 </p> </blockquote> <p> 由前面的优先级还可以知道,假如没有 JSP 属性组的配置,也没有 pageEncoding 属性,则会用 contentType 中的 charset 值作为页面的编码。 </p> <blockquote> <p> 尽管这个值实际是给响应流用的。在前面的 <a rel="nofollow" target="_blank"> 网页中的编码与乱码(3) </a> 篇章中曾详细讨论过这个问题。 </p> </blockquote> <p> 那么现在清楚了,这个 JSP 文件本身的编码是 gbk,但它也可能受一些全局性配置的影响。 </p> <p> 智能的 JSP 文本编辑器会采纳 pageEncoding 的值来保存,当然,如果不是智能的编辑器,情况就不好说了,你完全可以在保存时自己指定一个编码。 </p> <h2> 响应流中的编码 </h2> <p> 以上就是第一个问题,接下来讨论第二个问题:最终生成并发送到浏览器的字节流又是什么编码? </p> <p> 是 pageEncoding 中指定的 GBK 呢?还是 contentType 中指定 UTF-8 呢?亦或是 meta charset 中的 ISO-8859-1 呢? </p> <p> 如果在浏览器中访问这个页面,中文是能正确显示的: </p> <p> <img alt="jsp encoding test page" height="122" src="/imgr?src=https%3A%2F%2Fstatic.oschina.net%2Fuploads%2Fimg%2F201711%2F08085347_FoX4.png&r=https%3A%2F%2Fmy.oschina.net%2Fgoldenshaw%2Fblog%2F1568035" title="jsp encoding test page" width="549"/> </p> <p> 这说明肯定不是 ISO-8859-1 了。再看 response 中的具体内容,也就是最终生成的 html,gbk 什么的也全部消失了,只有 meta charset 还在: </p> <p> <img alt="jsp response content" height="276" src="/imgr?src=https%3A%2F%2Fstatic.oschina.net%2Fuploads%2Fimg%2F201711%2F08085347_IE4J.png&r=https%3A%2F%2Fmy.oschina.net%2Fgoldenshaw%2Fblog%2F1568035" title="jsp response content" width="352"/> </p> <p> 再看 response header 中的 Content-Type,charset 是 utf-8: </p> <p> <img alt="Response header content-type charset utf-8" height="214" src="/imgr?src=https%3A%2F%2Fstatic.oschina.net%2Fuploads%2Fimg%2F201711%2F08085347_XW4i.png&r=https%3A%2F%2Fmy.oschina.net%2Fgoldenshaw%2Fblog%2F1568035" title="Response header content-type charset utf-8" width="352"/> </p> <p> 所以,响应流的编码实际就是 utf-8。 </p> <h2> 响应流编码的官方参考 </h2> <p> 关于这个响应流的编码,同样有官方文档: <a href="https://docs.oracle.com/cd/E19316-01/819-3669/bnayg/index.html" rel="nofollow"> https://docs.oracle.com/cd/E19316-01/819-3669/bnayg/index.html </a> </p> <p> 具体来说是这样的: </p> <blockquote> <p> The <b> response encoding </b> is the character encoding of the textual response generated by a web component. The response encoding must be set appropriately so that the characters are rendered correctly for a given locale. A web container sets an initial response encoding for a JSP page from the following sources: </p> <ul> <li> <p> The CHARSET value of the contentType attribute of the page directive </p> </li> <li> <p> The encoding specified by the pageEncoding attribute of the page directive </p> </li> <li> <p> The page encoding value of a JSP property group whose URL pattern matches the page </p> </li> </ul> <p> If none of these is provided, ISO-8859-1 is used as the default response encoding. </p> </blockquote> <p> 简单翻译一下,它由以下几个步骤来确定响应流的编码(越前面的优先级越高): </p> <ol> <li> page 指令中的 contentType 属性下的 charset 的值。 </li> <li> page 指令中的 pageEncoding 属性。 </li> <li> JSP 属性组(JSP property group)中的配置。 </li> <li> 以上均无提供,使用缺省值,具体为 ISO-8859-1。 </li> </ol> <p> 发现什么规律没有呢?前面三项与之前的确定页面编码的顺序恰好是相反的,也就是 contentType 中的值这时反而是优先级最高的;没有 contentType,才会看 pageEncoding。 </p> <p> 所以在这个例子中,gbk 只是作为文件编码,tomcat 用它来把源文件正确读取上来,之后就没有它什么事了。 </p> <p> 而假如前两者都不存在,就会采用 JSP 属性组中的值,就是前面介绍的那个 web.xml 中的 page-encoding 配置(假如有的话)。 </p> <p> 最后,假如以上手段都不能确定,就用缺省值 ISO-8859-1。 </p> <h2> 从 JSP 到 Servlet 再到 HTML </h2> <p> 其实 JSP 文件会被转换成一个 Java 文件,在这个例子中,具体在我的电脑上,可以去到 D:\dev\wp\neon3\workspace.metadata.plugins\org.eclipse.wst.server.core\tmp0\work\Catalina\localhost\jcc-web\org\apache\jsp\demo\encoding\page 下, </p> <blockquote> <p> 这个文件夹的前面部分就是上一篇 <a rel="nofollow" target="_blank"> Java servlet 使用 PrintWriter 时的编码与乱码 </a> 介绍的 tomcat 启动命令行中的 –Dcatalina.base 中的值: </p> <p> <img alt="tomcat server command line catalina.base" height="214" src="/imgr?src=https%3A%2F%2Fstatic.oschina.net%2Fuploads%2Fimg%2F201711%2F08085347_Dkcs.png&r=https%3A%2F%2Fmy.oschina.net%2Fgoldenshaw%2Fblog%2F1568035" title="tomcat server command line catalina.base" width="426"/> </p> <p> 不同的部署方式具体情况可能会有所差别。不同的 server 甚至不同的 IDE 工具,请自行查阅资料了解具体部署到的地方。 </p> </blockquote> <p> 可以发现一个 testEncoding_jsp.java 和 testEncoding_jsp.class 的文件: </p> <p> <img alt="jsp servlet work location" height="117" src="/imgr?src=https%3A%2F%2Fstatic.oschina.net%2Fuploads%2Fimg%2F201711%2F08085347_kfeD.png&r=https%3A%2F%2Fmy.oschina.net%2Fgoldenshaw%2Fblog%2F1568035" title="jsp servlet work location" width="571"/> </p> <p> 与 testEncoding.jsp 是对应的(从名字与所处位置都不难推测出来)。它的内容如下: </p> <p> <img alt="jsp 生成的 servlet 类示例" height="187" src="/imgr?src=https%3A%2F%2Fstatic.oschina.net%2Fuploads%2Fimg%2F201711%2F08085347_JgWO.png&r=https%3A%2F%2Fmy.oschina.net%2Fgoldenshaw%2Fblog%2F1568035" title="jsp 生成的 servlet 类示例" width="830"/> </p> <p> 从开头的注释可以看出它是由 tomcat 的 Jasper 组件生成的,继承自 org.apache.jasper.runtime.HttpJspBase 这个类。 </p> <p> 把这个文件导入某个 web 工程中,查看其类型层级(在 eclipse 中,具体操作为:菜单--Navigate—Open Type Hierarchy): </p> <p> <img alt="jsp 生成的 servlet 类的类型层级" height="347" src="/imgr?src=https%3A%2F%2Fstatic.oschina.net%2Fuploads%2Fimg%2F201711%2F08085347_oriQ.png&r=https%3A%2F%2Fmy.oschina.net%2Fgoldenshaw%2Fblog%2F1568035" title="jsp 生成的 servlet 类的类型层级" width="656"/> </p> <p> 在左侧的继承树中可以看到它继承了 HttpJspBase,而后者又继承了 HttpServlet ,所以它实际上就是一个 servlet。在右侧的方法和属性列表中还可以看到它有个 _jspService 方法,有 request 和 response 两个参数,实际上就类似于 servlet 中的 service 方法。 </p> <p> 去到这个 _jspService 方法中看一下,将其中部分内容(只截取了关键部分,因为整个方法比较长)与原来的 JSP 页面及最终的 html 输出对比的话: </p> <p> <img alt="jsp to servlet to html" height="616" src="/imgr?src=https%3A%2F%2Fstatic.oschina.net%2Fuploads%2Fimg%2F201711%2F08085347_wNm9.png&r=https%3A%2F%2Fmy.oschina.net%2Fgoldenshaw%2Fblog%2F1568035" title="jsp to servlet to html" width="1080"/> </p> <p> 不难看出,jsp 中 page 指令中的 contentType 的值就成了 servlet 中的 response.setContentType 的值,最终成为前面提到的浏览器中 header 响应中的 Content-Type: </p> <p> <img alt="Response header content-type charset utf-8" height="214" src="/imgr?src=https%3A%2F%2Fstatic.oschina.net%2Fuploads%2Fimg%2F201711%2F08085347_XW4i.png&r=https%3A%2F%2Fmy.oschina.net%2Fgoldenshaw%2Fblog%2F1568035" title="Response header content-type charset utf-8" width="352"/> </p> <p> 而 jsp 中的 java 脚本(&lt;%%&gt; 部分的代码)就直接转换成了代码;其它 jsp 中跟 html 一样的标签在 servlet 中就直接用 out.write 输出了,最终 out 输出的结果就是在浏览器端看到的 response 的内容,也就是最终的 html 页面。 </p> <p> pageEncoding 在这里已经不存在了。那么,至此,第二个问题答案也清楚了,响应流用 utf-8 编码。 </p> <h2> 会不会乱码? </h2> <p> 最后一个问题,会不会出现乱码?(包括用编辑器打开以及在浏览器中打开时)其实答案也很清楚了。 </p> <p> 前面 <a rel="nofollow" target="_blank"> 网页中的编码与乱码(3) </a> 提到,response header 中的 Content-Type 下的 charset 编码具有比 html 页面中 meta charset 声明的值更高的优先级,所以浏览器会选用 utf-8 而不是 ISO-8859-1 来解析,所以页面显示也是正常的。 </p> <p> 而编辑 jsp 文件时,智能的编辑器会检测到正确的编码,是可以正常打开这个文件的。而普通的编辑器就不好说了,会受很多因素影响,比如所在系统环境的缺省编码设置,以及编辑器本身是否具有一定的编码探测能力等等,也可能会正常打开,也可能在打开时乱码。 </p> <h2> 示例代码(gitee) </h2> <p> 最后,文中示例的代码可见我的码云: <a href="https://gitee.com/goldenshaw/java_code_complete/commit/33f03921214a31b052a0e968805652dd0d46f849" rel="nofollow"> https://gitee.com/goldenshaw/java_code_complete/commit/33f03921214a31b052a0e968805652dd0d46f849 </a> </p> <p> 好了,关于 JSP 中的字符集编码与乱码问题的探讨就到这里,在正常的开发活动中,你应该始终注意保持几处编码的一致,比如始终在各处统一使用 utf-8 编码,这样就能避免绝大多数问题。 </p>

作者:Just_do_挨踢
国栋的博客
原文地址:JSP 中的字符集编码与乱码问题, 感谢原作者分享。