content-type是一个非常重要的HTTP Header,设置错误的话,浏览器端是没有办法正常显示的。作为API容器,需要根据format来定义content-type,并且强制使用UTF-8。

package me.arganzheng.study.api.container.controller;

@Controller
public class ApiDispatcher {

    private static final Logger logger         = Logger.getLogger(ApiDispatcher.class);

    private static final String FORMAT_DEFAULT = "xml";

    @RequestMapping(value = "{moduleName}/{apiName}.xhtml")
    public void dispatch(@PathVariable String moduleName,//
                         @PathVariable String apiName, //
                         HttpServletRequest request,//
                         HttpServletResponse response) {
        Map<String, String> parameterMap = getParameterMap(request.getParameterMap());
        String format = parameterMap.remove("format");
        if (StringUtils.isEmpty(format)) {
            format = FORMAT_DEFAULT;
        }
        String out = StringUtils.EMPTY;
        String apiName4Idl = StringUtils.EMPTY;
        try {
            OpenApiBridge bridge = new OpenApiBridgeImpl();
            apiName4Idl = getApiName4Idl(apiName);
            Service service = getService(apiName4Idl);
            out = bridge.invoke(parameterMap, apiName4Idl, service, format);
        } catch (Exception ex) {
            logger.error("dispatch api failed! moduleName: " + moduleName + ", apiName:" + apiName4Idl, ex);
        }
        
        String contentTypeAndCharset = getContentType(format) + ";charset=UTF-8";
        response.addHeader("Content-Type", contentTypeAndCharset);
        
        try {
            response.getWriter().write(out);
        } catch (IOException e) {
            logger.error("IO Exception thrown when trying to write response", e);
        }
    }

    private String getContentType(String format) {
        if (StringUtils.equals(format, "json")) {
            return "application/json";
        } else {
            return "application/xml";
        }
    }
}

Tips 这里直接使用了HttpServletResponse,虽然难看了点,但是控制力强,实现简单。这里如果要使用@ResponseBody,需要自定义一个。注意,不能两者同时使用。

使用@ResponseBody的编码方式

@ResponseBody其实是调用对应的MessageConverter来实现消息转换和输入输出的,这里也会有字节流到字符串的问题,需要指定charset。注意,在HTTP中默认的编码不是UTF-8,而是ISO-8859-1。比如StringHttpMessageConverter。如果你数据库和JVM编码都是UTF-8,但是使用了@ReponseBody 返回了String对象,那么是一定会有乱码问题的。

package org.springframework.http.converter;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;

import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.util.StreamUtils;

public class StringHttpMessageConverter extends AbstractHttpMessageConverter<String> {

    public static final Charset DEFAULT_CHARSET = Charset.forName("ISO-8859-1");

    private final Charset defaultCharset;

	private final List<Charset> availableCharsets;

	private boolean writeAcceptCharset = true;


	/**
	 * A default constructor that uses {@code "ISO-8859-1"} as the default charset.
	 * @see #StringHttpMessageConverter(Charset)
	 */
	public StringHttpMessageConverter() {
		this(DEFAULT_CHARSET);
	}

	/**
	 * A constructor accepting a default charset to use if the requested content
	 * type does not specify one.
	 */
	public StringHttpMessageConverter(Charset defaultCharset) {
		super(new MediaType("text", "plain", defaultCharset), MediaType.ALL);
		this.defaultCharset = defaultCharset;
		this.availableCharsets = new ArrayList<Charset>(Charset.availableCharsets().values());
	}

	private Charset getContentTypeCharset(MediaType contentType) {
		if (contentType != null && contentType.getCharSet() != null) {
			return contentType.getCharSet();
		}
		else {
			return this.defaultCharset;
		}
	}
}

关于如何让@ReponseBody相应的MessageConveter正确的设置charset参数,参见这篇帖子:Who sets response content-type in Spring MVC (@ResponseBody) 写的非常好。