SpringMVC返回JSON方案
SpringMVC已经大行其道。一般的,都是返回JSP视图。如果需要返回JSON格式,我们大都掌握了一些方法。
在ContentNegotiatingViewResolver之前,一般使用XmlViewResolver的location属性,手工编写一个视图专门处理Json类型,就是将返回的数据封装成Json输出。下面介绍该方案具体代码。
exam-servlet.xml:
<bean id="xmlViewResolver" class="org.springframework.web.servlet.view.XmlViewResolver"> <property name="order" value="1"/> <property name="location" value="/WEB-INF/views.xml"/> </bean> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="order" value="2"></property> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> </bean>
views.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="json" class="cc.monggo.web.views.JsonView"></bean> </beans>
JsonView.java
/** * */ package cc.monggo.web.views; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import net.sf.json.JSON; import net.sf.json.JSONArray; import net.sf.json.JSONSerializer; import net.sf.json.JsonConfig; import net.sf.json.filters.OrPropertyFilter; import net.sf.json.util.PropertyFilter; import org.springframework.web.servlet.view.AbstractView; import cc.monggo.web.form.BaseForm; /** * @author fangjinsong * */ public class JsonView extends AbstractView { private static final String DEFAULT_JSON_CONTENT_TYPE = "application/json;charset=UTF-8"; private boolean forceTopLevelArray = false; private boolean skipBindingResult = true; private JsonConfig jsonConfig = new JsonConfig(); public JsonView() { super(); setContentType(DEFAULT_JSON_CONTENT_TYPE); } public boolean isForceTopLevelArray() { return forceTopLevelArray; } public void setForceTopLevelArray(boolean forceTopLevelArray) { this.forceTopLevelArray = forceTopLevelArray; } public boolean isSkipBindingResult() { return skipBindingResult; } public void setSkipBindingResult(boolean skipBindingResult) { this.skipBindingResult = skipBindingResult; } public JsonConfig getJsonConfig() { return jsonConfig; } public void setJsonConfig(JsonConfig jsonConfig) { this.jsonConfig = jsonConfig != null ? jsonConfig : new JsonConfig(); if (skipBindingResult) { PropertyFilter jsonPropertyFilter = this.jsonConfig.getJavaPropertyFilter(); if (jsonPropertyFilter == null) { this.jsonConfig.setJsonPropertyFilter(new BindingResultPropertyFilter()); } else { this.jsonConfig.setJsonPropertyFilter(new OrPropertyFilter(new BindingResultPropertyFilter(), jsonPropertyFilter)); } } } public static String getDefaultJsonContentType() { return DEFAULT_JSON_CONTENT_TYPE; } public boolean isIgnoreDefaultExcludes() { return getJsonConfig().isIgnoreDefaultExcludes(); } public void setExcludedProperties(String[] excludedProperties) { jsonConfig.setExcludes(excludedProperties); } public void setIgnoreDefaultExcludes(boolean ignoreDefaultExcludes) { jsonConfig.setIgnoreDefaultExcludes(ignoreDefaultExcludes); } protected String[] getExcludedProperties() { return jsonConfig.getExcludes(); } @Override protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { response.setContentType(getContentType()); Map newModel = new HashMap(); for (Iterator it = model.keySet().iterator(); it.hasNext();) { Object key = it.next(); Object value = model.get(key); if (!(value instanceof BaseForm)) { newModel.put(key, value); } } writeJSON(newModel, request, response); } protected void writeJSON(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { JSON json = createJSON(model, request, response); if (forceTopLevelArray) { json = new JSONArray().element(json); } json.write(response.getWriter()); } protected final JSON defaultCreateJSON(Map model) { if (skipBindingResult && jsonConfig.getJavaPropertyFilter() == null) { jsonConfig.setJsonPropertyFilter(new BindingResultPropertyFilter()); } return JSONSerializer.toJSON(model, jsonConfig); } private JSON createJSON(Map model, HttpServletRequest request, HttpServletResponse response) { return defaultCreateJSON(model); } private static class BindingResultPropertyFilter implements PropertyFilter { @Override public boolean apply(Object source, String name, Object value) { return name.startsWith("org.springframework.validation.BindingResult"); } } }
这种方案会增加一个views.xml文件。另一个方案使用ContentNegotiatingViewResolver内容协商解析器。该解析器可以解析多种Controller返回结果,方法是分派给其他的解析器解析。这样就可以返回JSP视图,Xml视图,Json视图。其中Json视图由MappingJacksonJsonView视图表示。ContentNegotiatingViewResolver的一般配置如下:
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"> <property name="order" value="1" /> <property name="ignoreAcceptHeader" value="true" /> <property name="favorParameter" value="false" /> <property name="defaultContentType" value="text/html" /> <property name="mediaTypes"> <map> <entry key="json" value="application/json" /> </map> </property> <property name="viewResolvers"> <list> <bean class="org.springframework.web.servlet.view.BeanNameViewResolver" /> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> <property name="prefix" value="/WEB-INF/jsp/" /> <property name="suffix" value=".jsp"></property> </bean> </list> </property> <property name="defaultViews"> <list> <bean id="json" class="com.mogee.web.views.MogeeMappingJacksonJsonView" /> </list> </property> </bean>
要返回Json格式数据,Controller中一般使用JsonResult,@responseBody等技术。例如:
@RequestMapping("/json3.json") public JsonResult testJson3(@RequestBody User u){ log.info("handle json output from ContentNegotiatingViewResolver"); return new JsonResult(true,"return ok"); }
这样一来,有一个问题:Controller总是返回Object对象,不能返回ModelAndview类型。按笔者的经验,经常有这样的需求:一个请求,如果返回了正确的结果,返回正常的JSP;如果返回了错误的结果,我们希望返回Json类型数据。为了满足这样要求,一般使用response.getWriter.print("")技术。笔者并不喜欢这样写法,不像Spring的风格。所以下面笔者将提出第二种方案。两者都能兼顾。Contoller代码如下。
@RequestMapping(value = "/test", method = RequestMethod.GET) public ModelAndView jsonView(HttpServletRequest request, HttpServletResponse response, HelloForm form) throws Exception { Map<String, Object> map = new HashMap<String, Object>(); map.put("hello", "Hello"); ModelAndView mv = new ModelAndView(new MappingJacksonJsonView(), map); return mv; }
通过这样的代码,可以在Controller中灵活控制,返回JSON或者JSP都可以。看似完美的方案,也有一个问题,返回的时候会将Form也打包到Json中去。如下:
原因是因为MappingJacksonJsonView代码中将数据map,mv.add(),还有form,还有错误提示等四项数据都加入Json,原程序只是过滤了错误提示,所以就留下了form数据。
为此我们需要重写MappingJacksonJsonView,将剩余三种数据中的form也过滤掉。一般的,form都会继承BaseForm,所以只要使用instanceof判断即可。这样的改造简洁明了,符合Spring编程的特点。具体代码如下:
public class MogeeMappingJacksonJsonView extends MappingJacksonJsonView { @Override protected Object filterModel(Map<String, Object> model) { Map<String, Object> result = new HashMap<String, Object>(model.size()); Set<String> renderedAttributes = !CollectionUtils.isEmpty(super.getRenderedAttributes()) ? super .getRenderedAttributes() : model.keySet(); for (Map.Entry<String, Object> entry : model.entrySet()) { if (!(entry.getValue() instanceof BindingResult) && renderedAttributes.contains(entry.getKey())) { if (!(entry.getValue() instanceof BaseForm)) { result.put(entry.getKey(), entry.getValue()); } } } return result; } }
相应的,exam-servlet.xml中也要修改,代码如下:
如此,就完成了输出Json的方案。
方劲松 东莞虎门信丰物流
2015.3.20