SpringMVC注解之@ResponseBody

@kuitos 2015-08-23 09:29:20发表于 kuitos/kuitos.github.io JavaSpringMVC

SpringMVC注解之@responsebody

原文写于 2013-04-18

web项目中会大量用到ajax请求实现前后台交互,以前处理后台返回给前台的集合数据的方式是这样的:

@RequestMapping("loadConfigUsers")
public void loadConfigUsers(String domain, HttpServletResponse response) {
    response.setCharacterEncoding("UTF-8");
    response.setContentType("text/html;charset=utf-8");
    List<Map<String, Object>> list = userConfigService.loadConfigUsers(domain);
    PrintWriter out = null;
    try {
        out = response.getWriter();
        out.print(JackSonMapper.toJsonString(list));
    } catch (IOException e) {
        logger.error("I/O出错", e);
    } finally {
        try {
            out.close();
        } catch (Exception e) {
            logger.error("关闭流出错", e);
        }
    }
}

也就是使用jackson将List<Map<String,Object>>对象转换为json格式的数组,如[{"a":"b"},{"c","d"}]。
有了@responsebody之后我们的代码就简单多了

@ResponseBody
@RequestMapping("loadConfigUsers")
public List<Map<String,String>> loadConfigUsers(String domain, HttpServletResponse response) {
    return userConfigService.loadConfigUsers(domain);
}

前台接收到的即为json格式数组,如[{"a":"b"},{"c","d"}]。SpringMVC底层会使用jackson将带有@responsebody的方法体的返回值转成标准的json格式。
想返回Map<String,String>格式的也一样

@ResponseBody
@RequestMapping("loadConfigUsers")
public Map<String,String> loadConfigUsers(String domain, HttpServletResponse response) {
    return userConfigService.loadConfigUsers(domain);
}

返回的json格式为 {"a":"b","c":"d"}。
也可以直接向前台返回String

@ResponseBody
@RequestMapping("loadConfigUsers")
public String loadConfigUsers(String domain, HttpServletResponse response) {
    return "success";
}

前台接收到的为 "success"。
但是在实际开发中碰到一个问题,返回List,Map,即前台接收到的为json格式字符串的时候中文字符都正常,但是直接返回String却会出现中文乱码问题。google一下发现SpringMVC是这样实现的。
SpringMVC对于注有@responsebody注解的方法返回值有自己的一系列转换器,当发现返回值为List,Map等集合类型时SpringMVC使用的是MappingJacksonHttpMessageConverter转换器,改转换器字符集设置的为UTF-8,附部分代码

public class MappingJacksonHttpMessageConverter extends AbstractHttpMessageConverter<Object> {
    // 设置默认字符集
    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
    private ObjectMapper objectMapper = new ObjectMapper();
    private boolean prefixJson = false;
    /**
     * Construct a new {@code BindingJacksonHttpMessageConverter}.
     */
    public MappingJacksonHttpMessageConverter() {
        super(new MediaType("application", "json", DEFAULT_CHARSET));
    }
}

而对于返回值为String时使用的转换器则为StringHttpMessageConverter

public class StringHttpMessageConverter extends AbstractHttpMessageConverter<String> {
    // 设置默认字符集
    public static final Charset DEFAULT_CHARSET = Charset.forName("ISO-8859-1");
    private final List<Charset> availableCharsets;
    private boolean writeAcceptCharset = true;
    public StringHttpMessageConverter() {
        super(new MediaType("text", "plain", DEFAULT_CHARSET), MediaType.ALL);
        this.availableCharsets = new ArrayList<Charset>(Charset.availableCharsets().values());
    }
}

可以发现,两个转换器用的字符集竟然不一样,这个实在是难以理解,为毛用于处理同一个注解的两个转换器要用两种字符集??
经过一番google及测试,发现了有一种方式是可以解决StringHttpMessageConverter字符集的问题,即修改我们的springmvc-servlet.xml,在<mvc:annotation-driven />前加上这样一段配置

<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="messageConverters">
        <list>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <value>text/plain;charset=UTF-8</value>
                    </list>
                </property>
            </bean>
            <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />
        </list>
    </property>
</bean>

<mvc:annotation-driven />

即设置StringHttpMessageConverter可支持的媒体类型仅只有"text/plain;charset=UTF-8"一种。
另外还有一种是配置AnnotationMethodHandlerAdapter的messageConverters,即

<bean class="org.springframework.web.servlet.mvc.method.annotation.AnnotationMethodHandlerAdapter">       
    <property name="messageConverters">
            <list>
                <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                    <property name="supportedMediaTypes">
                        <list>
                            <value>text/plain;charset=UTF-8</value>
                        </list>
                    </property>
                </bean>
                <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />
            </list>
     </property>
</bean>

这种方式是将spring所有的messageConverters改为两种StringHttpMessageConverter、MappingJacksonHttpMessageConverter
使用这种配置就不能再用mvc:annotation-driven了,官方文档是这样写的。也就是说它会覆盖之前的配置

The above registers a RequestMappingHandlerMapping, a RequestMappingHandlerAdapter, and an ExceptionHandlerExceptionResolver (among others) in support of processing requests with annotated controller methods using annotations such as @RequestMapping , @ExceptionHandler, and others
This is the complete list of HttpMessageConverters set up by mvc:annotation-driven:
ByteArrayHttpMessageConverter converts byte arrays.
StringHttpMessageConverter converts strings.
ResourceHttpMessageConverter converts to/from org.springframework.core.io.Resource for all media types.
SourceHttpMessageConverter converts to/from a javax.xml.transform.Source.
FormHttpMessageConverter converts form data to/from a MultiValueMap<String, String>.
Jaxb2RootElementHttpMessageConverter converts Java objects to/from XML — added if JAXB2 is present on the classpath.
MappingJackson2HttpMessageConverter (or MappingJacksonHttpMessageConverter) converts to/from JSON — added if Jackson 2 (or Jackson) is present on the classpath.
AtomFeedHttpMessageConverter converts Atom feeds — added if Rome is present on the classpath.
RssChannelHttpMessageConverter converts RSS feeds — added if Rome is present on the classpath.

至于spring是如何选择可用的converter的,这里有一篇文章,有兴趣可以看下:这里