Description
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的,这里有一篇文章,有兴趣可以看下:这里