请求响应编码问题一文全解决

1 编码介绍

编码分为:

  • ISO-8859-1:用于欧洲国家的编码
  • 中文:如果想支持中文,可以使用UTF-9、GB2312、GBK等,其中UTF-8是国际化的,哪个国家都支持,所以推荐使用UTF-8。

2 Servlet获取数据时中文乱码问题

servlet中涉及的编码问题已过时,因为开发中不使用servlet了。

乱码问题分为:

  • 请求乱码:浏览器发给服务器的数据,服务器收到解析出现乱码。我们应该告诉服务器我使用的请求编码格式
  • 响应乱码:服务器发给浏览器的数据,浏览器收到解析出现乱码
2.1 请求编码:POST方式

乱码原因:

* post方式:会乱码
浏览器默认的数据编码格式是ISO-8859-1,浏览器发的是ios8859-1,tomcat解析是用给定utf-8,会乱码 。

对于POST方式,它采用的编码是由页面来决定的即ContentType("text/html; charset=GBK")。当通过点击页面的submit按钮来提交表单时,浏览器首先会根据ContentType的charset编码格式来对POST表单的参数进行编码然后提交给服务器,在服务器端同样也是用ContentType中设置的字符集来进行解码,这就是通过POST表单提交的参数一般而言都不会出现乱码问题。
    
当然这个字符集编码我们是可以自己设定的,通过:
    request.setCharacterEncoding(charset)
    设置编码,然后通过request.getParameter获得正确的数据。

解决方案:

方法1:在 service 方法中使用:

req.setCharacterEncoding(“utf-8”);

方法2:(实际更常用)

springMVC已经提供了轮子给我们使用,在web.xml添加post乱码filter:
在web.xml中加入:

<!-- 字符编码过滤器 -->
<filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <!-- 设置请求编码 -->
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
    <!-- 设置响应编码格式为也是encoding -->
    <init-param>
        <param-name>forceEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern><!-- 拦截所有用户请求 -->
</filter-mapping>
2.2 请求编码:GET方式

乱码原因

get请求方式的参数是放到了URL后面。这时设置content-type是没用的。
tomcat收到这个请求就会根据设置的调用默认的URIEncoding来进行解码,默认为ISO8859-1,并封装成request对象。
Tomcat8及以上版本不存在乱码问题,请求URL默认的编码是UTF-8,但是请求体还是使用的ISO-8859-1
Tomcat7及之前的服务器使用的是ISO8859-1编码,不支持中文

解决方案:

方法一(String编码,不推荐):

new String(request.getParameter("name").getBytes("iso-8859-1"),"utf-8");
//原来以为的解析方式是8859的,所以我们先用8859解出来字节,再用utf8重新编码字符

方法二(server设置):

解决方案是通知设置tomcat编码URL的格式与请求体一致,或单独设置URL编码格式
在 tomcat 服务器目录下的 conf 文件下找到 server.xml 文件,打开进行如下配置:
<Connector port="8080" protocol="HTTP/1.1"  maxThreads="150" 
           connectionTimeout="20000"
           redirectPort="8443"
           URIEncoding="utf-8"/> (单独设置了URL编码格式)
---------------或 -------------------------
<Connector port="8080" protocol="HTTO/1.1"
    connectionTimeout="20000"
    redirection="8443" 
    useBodyEncodingForURI="true">(设置URL的编码格式与请求体一致)
然后再使用req.setCharacterEncoding("");//设置请求体编码格式
----------------------------------------
(默认是iso-8859-1)。
2.3 响应编码
设置响应编码格式:告诉浏览器接收的格式
response直接使用ISO-8859-1编码然后交给浏览器
    3.1 在使用response对象之前设置编码格式
    (不用)response.setCharacterEncoding("UTF-8");//通知服务器用什么编码集将字符转为字节//只能让字符串不乱吗,浏览器还是不知道如何解析页面字符串
    3.2 在使用response对象之前设置一个响应头,通知浏览器以什么编码打开,告诉response对象如何读取字符串
response.setContentType("text/html;charset=Utf-8");
//或resp.setHeader("content-type","text/html;charset=utf-8");//等价
//只要设置过ContentType,服务器自动识别编码会自动设置response.setCharacterEncoding("")

https://blog.csdn.net/qq_38409944/article/details/80637980

2.4 SpringMVC中的编码Filter
springMVC已经提供了轮子给我们使用,在web.xml添加post乱码filter:
在web.xml中加入:

<!-- 字符编码过滤器 -->
<filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <!-- 设置请求编码 -->
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
    <!-- 设置响应编码格式为也是encoding -->
    <init-param>
        <param-name>forceEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern><!-- 拦截所有用户请求 -->
</filter-mapping>

服务器启动的时候就会创建Filter,将init-param中的参数加载,注入到CharacterEncodingFilter 类中,浏览器每次发送请求都会经过这个过滤器,然后调用doFilterInternal

在这里插入图片描述

可以看到CharacterEncodingFilter继承OncePerRequestFilter,OncePerRequestFilter是个抽象类,其中的抽象方法doFilterInternal被CharacterEncodingFilter实现,同时OncePerRequestFilter实现了doFilter方法,调用了doFilterInternal方法。

@Override
protected void doFilterInternal(
    HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
    throws ServletException, IOException {

    if (this.encoding != null && (this.forceEncoding || request.getCharacterEncoding() == null)) {//条件为设置的encoding不为空 且 (传入了forceEncoding或CharacterEncoding为空)
        request.setCharacterEncoding(this.encoding);//关键语句//设置请求编码格式
        if (this.forceEncoding) {// 关键语句
            response.setCharacterEncoding(this.encoding);//设置响应编码格式
        }
    }
    filterChain.doFilter(request, response);
}

3 JSP中的编码原理

流程:JSP源文件首先被web容器转换为java源文件,接着JSP编译器对此进行自动编译,产生临时的Servlet类的Class类文件。客户请求到来时,web容器调用JVM来运行Class类文件,JVM获取表单提交的参数或从后台数据库读取数据,把数据写入数据库或者把输出发送给浏览器。

JSP的三个阶段:

第一阶段:转译(.jsp -> .java;pageEncoding -> UTF-8)。

(第一次访问时)web容器将jsp编译成Servlet(.java)文件,它会根据pageEncoding的设定读取jsp。在编译过程中,根据pageEncoding=“XXX”的指示,找到编码的规则为“XXX”,然后服务器将JSP文件编译成.java文件时会根据pageEncoding的设定读取jsp,结果是由指定的编码方案翻译成统一的UTF-8编码的JAVA源码(即.java,也就是Servlet)。

第二阶段:编译(.java -> .class;UTF-8 -> UTF-8)。

从Servlet文件(.java)到Java字节码文件(.class),从UTF-8到UTF-8。在这一阶段中,不论JSP编写时候用的是什么编码方案,经过这个阶段的结果全部是UTF-8的encoding的java源码(JVM读取java会默认认为java的编码格式是utf8)。JAVAC用UTF-8的encoding读取java源码,编译成UTF-8编码的二进制码(即.class),这是JVM对常数字串在二进制码(Java encoding)内表达的规范。这一过程是由JVM的内在规范决定的,不受外界控制。

第三阶段:编译(UTF-8 -> contentType)。

从服务器到浏览器输出的结果,也就是在客户端见到的,这时隐藏在阶段一和阶段二的参数contentType就发挥了功效。这一过程中用到的指令是contentType。Tomcat(或其的application container)服务器载入和执行由第二阶段生成出来JAVA二进制码,输出的结果,也就是在客户端可见到的结果,在这次输出过程中,由contentType属性中的charset来指定,将UTF8形式的二进制码以charset的编码形式来输出。如果没有人为设定,则默认的是ISO-8859-1的形式。

下面内容涉及字符编码转换:
  • 1 JSP–>java转译过程中的转码(JSP–>Servlet(java)编码)
  • 2 JVM对客户端表单提交的参数转码(请求编码)
  • 3 JVM运行历史Servlet类文件后产生输出的转码(响应编码)
  • 4 JSP源文件在数据库的入口和出口涉及字符编码的转换(数据库编码)

4 JSP中可以设置编码的地方:

4.1 pageEncoding=”UTF-8”

<%@ page pageEncoding="UTF-8"%>

只是指明了 JSP 页面本身的编码格式,跟页面显示的编码没有关系。pageEncoding=”UTF-8” 的作用是设置JSP编译成Servlet时使用的编码。jsp页面中有中文的时候,保存的时候会报错(此时报错是保存JSP文件时报错,而不是浏览器访问时的报错),因为默认的是ISO-8859-1。例如,你的 JSP文件中含有中文字符,而在JSP中却指定pageEncoding=”iso-8859-1”,就会导致中文字符显示异常。

  • 如果pageEncoding属性存在,那么JSP页面的字符编码方式就由pageEncoding决定,
  • pageEncoding属性不存在,就由contentType属性中的charset决定,如果charset也不存在,JSP页面的字符编码方式就采用默认的ISO-8859-1。

可以在Eclipse中设置新建JSP的格式,设置完后就会发现JSP中的pageEncoding部分改变了。

例子:

<%@ page language="java" pageEncoding="iso-8859-1" import="java.util.*" %>
<html>
  <head>
    <title>哈哈</title>
  </head>
  <body>
     中文 <br>
  </body>
</html>

在其编译为Servlet后,其源码(片段)如下所示:

public void _jspService(HttpServletRequest request, HttpServletResponse response)
        throws java.io.IOException, ServletException {
    
	// ...
  out.write("<html>\r\n");
  out.write("  <head>\r\n");
  out.write("    <title>哈哈</title>\r\n");
  out.write("  </head>\r\n");
  out.write("  <body>\r\n");
  out.write("   \t 中文 <br>\r\n");
  out.write("  </body>\r\n");
  out.write("</html>\r\n");

  // ...
}

访问该页面,页面显示如下:

我们可以看到,由于pageEncoding被指定为”iso-8859-1”,导致其在由服务器将JSP文件编译成.java文件过程中,在使用 “iso-8859-1” 读取jsp并翻译成统一的UTF-8编码的JAVA源码时,所有的中文字符被转成乱码,并使得其呈现给用户的响应也包含乱码。特别地,该属性还有一个功能,就是在JSP中不指定contentType参数,也不使用response.setCharacterEncoding方法时,指定对服务器响应的内容进行编码。

4.2 contentType=”text/html;charset=UTF-8”

<%@ page contentType="text/html; charset=UTF-8"%>

contextType指定了两个内容:

  • MIME类型的默认值是“text/html”;
  • 字符编码方式的默认值是“ISO-8859-1”

contentType=”text/html;charset=UTF-8” 的作用是将上述第二阶段所生成的UTF8形式的二进制码以charset的编码形式来输出到客户端,如果设置不当的话,会出现乱码。看下面的例子:

<%@ page language="java" contentType="text/html;iso-8859-1" import="java.util.*" 
    pageEncoding="utf-8"%>
<html>
  <head>
    <title>哇哈哈</title>
  </head>

  <body>
     哇哈哈 <br>
  </body>
</html>

在其编译为Servlet后,其源码(片段)如下所示:

public void _jspService(HttpServletRequest request, HttpServletResponse response)
        throws java.io.IOException, ServletException {
	// ...

  out.write("<html>\r\n");
  out.write("  <head>\r\n");
  out.write("    <title>哇哈哈</title>\r\n");
  out.write("  </head>\r\n");
  out.write("  <body>\r\n");
  out.write("   \t 哇哈哈 <br>\r\n");
  out.write("  </body>\r\n");
  out.write("</html>\r\n");

  // ...
}

访问该页面,页面显示如下:原因是没告诉浏览器的接收方式

pageEncoding和contentType的关系:pageEncoding的内容只是用于jsp输出时的编码,不会作为header发出去的; contentType是告诉web Server的jsp页面按照什么编码输出,即web服务器输出的响应流的编码;

4.3 request.setCharacterEncoding(“UTF-8”)

在JSP中java代码中写  request.setCharacterEncoding(“UTF-8”)

用来指定对浏览器发送来的数据以特定的字符集进行重新编码,常用于对 POST 请求参数进行解码。具体见https://blog.csdn.net/justloveyou_/article/details/55827718

request.setCharacterEncoding()设置请求编码,
response.setCharacterEncoding() 设置响应编码

4.4 response.setCharacterEncoding(“UTF-8”)

response.setCharacterEncoding(“UTF-8”)的作用是:在服务器将响应返回到浏览器前,对响应使用指定字符集进行重新编码。一旦使用了该种方式,即使该响应页面指定了具体的 contentType,也将失效。看下面的例子:

<%@ page language="java" contentType="text/html;iso-8859-1" import="java.util.*" 
    pageEncoding="utf-8"%>

<html>
  <head>
    <title>哇哈哈</title>
  </head>

  <body>
     哇哈哈 <br>
  </body>
</html>

在其编译为Servlet后,其源码(片段)如下所示:

public void _jspService(HttpServletRequest request, HttpServletResponse response)
        throws java.io.IOException, ServletException {
// ...

  out.write("<html>\r\n");
  out.write("  <head>\r\n");
  out.write("    <title>哇哈哈</title>\r\n");
  out.write("  </head>\r\n");
  out.write("  <body>\r\n");
  out.write("   \t 哇哈哈 <br>\r\n");
  out.write("  </body>\r\n");
  out.write("</html>\r\n");

  // ...
}

访问该页面,页面显示如下:

4.5 JSP中html页面charset:(无需设置)

<META http-equiv="Content-Type" content="text/html; charset=UTF-8"> 
web页面输入编码: 输入框输入的字体编码

5 四种编码设定方式之间的相互影响以及优先级

根据上文内容,我们得出以下三点:

  • 在指定JSP编译成Servlet时使用的编码时,优先级为: pageEncoding=”UTF-8” > contentType=”text/html;charset=UTF-8”

  • 在指定服务器对响应内容的编码时,优先级为:response.setCharacterEncoding(“UTF-8”)> contentType=”text/html;charset=UTF-8” > pageEncoding=”UTF-8”

  • request.setCharacterEncoding(“UTF-8”)只用来指定对浏览器发送来的请求数据的解码方式。

假如html后的排序为:

和contentType效果一样的设置方式还有 
- html页面charset, 
- response.setCharacterEncoding(),
- response.setContentType(),
- response.setHeader(); 
其中,
优先级对最高的是:response.setContentType(),response.setHeader();
其次是response.setCharacterEncoding();
再者是 <%@page contentType="text/html; chareset=gbk"%>,
最后是<meta http-equiv="content-type" content="text/html; charset=gb2312" />
前面的会覆盖后面的

推荐阅读:

  • https://blog.csdn.net/justloveyou_/article/details/58048879
  • https://blog.csdn.net/justloveyou_/article/details/55827718
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值