之前看介绍Spring框架的时候,提到了Spring中内置了几种过滤器,其中有一种是CharacterEncodingFilter的过滤器,是将数据以某种格式编码的方式输出的。这个特性让我很兴奋,因为在WEB系统下,字符编码的格式是一定要面对的。所以我尝试了一下,结果很失望。
在web.xml中添加了如下内容
<!-- 配置请求过滤器,编码格式设为UTF-8,避免中文乱码-->
<filter>
<filter-name>springUtf8Encoding</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>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>springUtf8Encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
于是在浏览器中打开网页,在chrome中显示乱码,在opera中显示正常编码,查看了一下返回数据,发现HTTP报头中并没有指明编码类型,也就是说这时候浏览器并不知道消息体中是何种编码,于是它会按照自己默认编码去解析。这是不可忍受的。
于是我查看了CharacterEncodingFilter的源码,感谢博客
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.filter;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet 2.3/2.4 Filter that allows one to specify a character encoding for
* requests. This is useful because current browsers typically do not set a
* character encoding even if specified in the HTML page or form.
*
* <p>This filter can either apply its encoding if the request does not
* already specify an encoding, or enforce this filter's encoding in any case
* ("forceEncoding"="true"). In the latter case, the encoding will also be
* applied as default response encoding on Servlet 2.4+ containers (although
* this will usually be overridden by a full content type set in the view).
*
* @author Juergen Hoeller
* @since 15.03.2004
* @see #setEncoding
* @see #setForceEncoding
* @see javax.servlet.http.HttpServletRequest#setCharacterEncoding
* @see javax.servlet.http.HttpServletResponse#setCharacterEncoding
*/
public class CharacterEncodingFilter extends OncePerRequestFilter {
private String encoding;
private boolean forceEncoding = false;
/**
* Set the encoding to use for requests. This encoding will be passed into a
* {@link javax.servlet.http.HttpServletRequest#setCharacterEncoding} call.
* <p>Whether this encoding will override existing request encodings
* (and whether it will be applied as default response encoding as well)
* depends on the {@link #setForceEncoding "forceEncoding"} flag.
*/
public void setEncoding(String encoding) {
this.encoding = encoding;
}
/**
* Set whether the configured {@link #setEncoding encoding} of this filter
* is supposed to override existing request and response encodings.
* <p>Default is "false", i.e. do not modify the encoding if
* {@link javax.servlet.http.HttpServletRequest#getCharacterEncoding()}
* returns a non-null value. Switch this to "true" to enforce the specified
* encoding in any case, applying it as default response encoding as well.
* <p>Note that the response encoding will only be set on Servlet 2.4+
* containers, since Servlet 2.3 did not provide a facility for setting
* a default response encoding.
*/
public void setForceEncoding(boolean forceEncoding) {
this.forceEncoding = forceEncoding;
}
@Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (this.encoding != null && (this.forceEncoding || request.getCharacterEncoding() == null)) {
request.setCharacterEncoding(this.encoding);
if (this.forceEncoding) {
response.setCharacterEncoding(this.encoding);
}
}
filterChain.doFilter(request, response);
}
}
这个filter可能与我们自己写的Filter不太相同,它继承的是OncePerRequestFilter,看看OncePerRequestFilter的源码,可以发现,OncePerRequestFilter的过滤器会负责调用doFilterInternal()方法,因此,我们只关注这个方法即可。
这个方法很简单,就是看看是否设置了encoding,如果设置了encoding,且请求中没有设置characterEncoding的话,它会负责设置characterEncoding,并且如果forceEncoding设置为true的话,在responce中也会设置characterEncoding。
如何设置encoding和forceEncoding呢?这就是我们在配置文件中写的里面的数据,spring会负责将这些数据注入。我们可以不用管了。现在我们把这个方法简化一下,就是两个请求。
request.setCharacterEncoding(encoding);
response.setCharacterEncoding(encoding);
OK,现在我们来看一下setCharacterEncoding()干了点啥?我没找到setCharacterEncoding()的源码,只看看它的文档说明吧,
public void setCharacterEncoding(java.lang.String env)
throws java.io.UnsupportedEncodingException
Overrides the name of the character encoding used in the body of this request. This method must be called prior to reading request parameters or reading input using getReader().
Parameters:
env - a String containing the name of the character encoding.
Throws:
java.io.UnsupportedEncodingException - if this is not a valid encoding
看到这个说明的时候,我觉得这个作用就是更改HTTP报头中charset的内容,仅此而已,不会更改HTTPbody中的编码格式。所以我想到使用setContentType()来显式的设置输出的报文信息
setContentType("text/json";charst=utf-8);
这样就OK了,在Chrome和Opera中已经可以正常显示了。
我们在看看setContentType的说明
public void setContentType(java.lang.String type)
Sets the content type of the response being sent to the client, if the response has not been committed yet. The given content type may include a character encoding specification, for example, text/html;charset=UTF-8. **The response's character encoding is only set from the given content type** if this method is called before getWriter is called.
This method may be called repeatedly to change content type and character encoding. This method has no effect if called after the response has been committed. It does not set the response's character encoding if it is called after getWriter has been called or after the response has been committed.
Containers must communicate the content type and the character encoding used for the servlet response's writer to the client if the protocol provides a way for doing so. In the case of HTTP, the Content-Type header is used.
Parameters:
type - a String specifying the MIME type of the content
See Also:
setLocale(java.util.Locale), setCharacterEncoding(java.lang.String), getOutputStream(), getWriter()
这里面很明显的说明了:输出的字符编码只有在给定的content type下面才有效。也即必须在有setContentType的前提下setCharacterEncoding()才有效,但是问题是setContentType()完全可以同时设置charset,那何必在setCharacterEncoding呢?