Spring MVC中直接用JSON对象作为ViewModel的可行性问询
直接将JSON对象作为Spring MVC ModelAndView的ViewModel并在JSP中自然访问的方案
完全可行,下面提供几种实用方案,既能跳过POJO转换步骤,又能在JSP中实现像访问普通Java对象一样的自然语法:
方案一:基于Gson的JSON对象包装类
通过包装Gson的JsonObject/JsonArray,让其实现Java集合接口(Map/List),这样EL表达式会自动识别并支持.和[]的访问语法。
实现包装类
// JsonObject包装类,实现Map接口以支持EL属性访问 public class GsonJsonObjectWrapper implements Map<String, Object> { private final JsonObject jsonObject; private final Gson gson = new Gson(); public GsonJsonObjectWrapper(JsonObject jsonObject) { this.jsonObject = jsonObject; } @Override public Object get(Object key) { if (!(key instanceof String)) return null; JsonElement element = jsonObject.get((String) key); if (element == null) return null; if (element.isJsonObject()) { return new GsonJsonObjectWrapper(element.getAsJsonObject()); } else if (element.isJsonArray()) { return new GsonJsonArrayWrapper(element.getAsJsonArray()); } else if (element.isJsonPrimitive()) { JsonPrimitive primitive = element.getAsJsonPrimitive(); if (primitive.isBoolean()) return primitive.getAsBoolean(); if (primitive.isNumber()) return primitive.getAsNumber(); return primitive.getAsString(); } return null; } // 以下为Map接口的默认/占位实现,仅保留EL需要的核心方法 @Override public int size() { return jsonObject.size(); } @Override public boolean isEmpty() { return jsonObject.size() == 0; } @Override public boolean containsKey(Object key) { return jsonObject.has((String) key); } @Override public boolean containsValue(Object value) { throw new UnsupportedOperationException(); } @Override public Object put(String key, Object value) { throw new UnsupportedOperationException(); } @Override public Object remove(Object key) { throw new UnsupportedOperationException(); } @Override public void putAll(Map<? extends String, ?> m) { throw new UnsupportedOperationException(); } @Override public void clear() { throw new UnsupportedOperationException(); } @Override public Set<String> keySet() { return jsonObject.keySet(); } @Override public Collection<Object> values() { throw new UnsupportedOperationException(); } @Override public Set<Entry<String, Object>> entrySet() { throw new UnsupportedOperationException(); } } // JsonArray包装类,实现List接口以支持EL索引访问 public class GsonJsonArrayWrapper implements List<Object> { private final JsonArray jsonArray; public GsonJsonArrayWrapper(JsonArray jsonArray) { this.jsonArray = jsonArray; } @Override public Object get(int index) { JsonElement element = jsonArray.get(index); if (element.isJsonObject()) return new GsonJsonObjectWrapper(element.getAsJsonObject()); if (element.isJsonArray()) return new GsonJsonArrayWrapper(element.getAsJsonArray()); JsonPrimitive primitive = element.getAsJsonPrimitive(); if (primitive.isBoolean()) return primitive.getAsBoolean(); if (primitive.isNumber()) return primitive.getAsNumber(); return primitive.getAsString(); } // 以下为List接口的默认/占位实现,仅保留EL需要的核心方法 @Override public int size() { return jsonArray.size(); } @Override public boolean isEmpty() { return jsonArray.size() == 0; } @Override public boolean contains(Object o) { throw new UnsupportedOperationException(); } @Override public Iterator<Object> iterator() { throw new UnsupportedOperationException(); } @Override public Object[] toArray() { throw new UnsupportedOperationException(); } @Override public <T> T[] toArray(T[] a) { throw new UnsupportedOperationException(); } @Override public boolean add(Object o) { throw new UnsupportedOperationException(); } @Override public boolean remove(Object o) { throw new UnsupportedOperationException(); } @Override public boolean containsAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection<? extends Object> c) { throw new UnsupportedOperationException(); } @Override public boolean addAll(int index, Collection<? extends Object> c) { throw new UnsupportedOperationException(); } @Override public boolean removeAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public boolean retainAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public void clear() { throw new UnsupportedOperationException(); } @Override public Object set(int index, Object element) { throw new UnsupportedOperationException(); } @Override public void add(int index, Object element) { throw new UnsupportedOperationException(); } @Override public Object remove(int index) { throw new UnsupportedOperationException(); } @Override public int indexOf(Object o) { throw new UnsupportedOperationException(); } @Override public int lastIndexOf(Object o) { throw new UnsupportedOperationException(); } @Override public ListIterator<Object> listIterator() { throw new UnsupportedOperationException(); } @Override public ListIterator<Object> listIterator(int index) { throw new UnsupportedOperationException(); } @Override public List<Object> subList(int fromIndex, int toIndex) { throw new UnsupportedOperationException(); } }
控制器中使用
@RequestMapping(value = {SOME_VALUE}) public ModelAndView myFunction() { ModelAndView modelAndView = new ModelAndView("myViewName"); JsonObject jsonObject = getAPIResponseASJsonObject(); // 用包装类包裹原始JSON对象 modelAndView.addObject("viewModel", new GsonJsonObjectWrapper(jsonObject)); return modelAndView; }
JSP中访问
<!-- 直接访问属性 --> <c:set var="name" value="${viewModel.name}"/> <c:set var="age" value="${viewModel.age}"/> <!-- 嵌套对象访问 --> ${viewModel.address.city} <!-- 数组/列表索引访问 --> ${viewModel.hobbies[0]}
方案二:自定义EL解析器
通过扩展Spring MVC的EL解析器,让系统自动识别JSON对象,并将.语法映射为JSON的get方法,无需修改控制器代码。
实现自定义EL解析器
public class JsonObjectELResolver extends ELResolver { @Override public Object getValue(ELContext context, Object base, Object property) { // 处理Gson JsonObject if (base instanceof JsonObject) { JsonObject jsonObject = (JsonObject) base; String propName = property.toString(); JsonElement element = jsonObject.get(propName); if (element != null) { context.setPropertyResolved(true); return convertJsonElement(element); } } // 处理org.json JSONObject else if (base instanceof JSONObject) { JSONObject jsonObject = (JSONObject) base; String propName = property.toString(); try { Object value = jsonObject.get(propName); context.setPropertyResolved(true); return value; } catch (JSONException ignored) {} } return null; } private Object convertJsonElement(JsonElement element) { if (element.isJsonObject()) return element.getAsJsonObject(); if (element.isJsonArray()) return element.getAsJsonArray(); JsonPrimitive primitive = element.getAsJsonPrimitive(); if (primitive.isBoolean()) return primitive.getAsBoolean(); if (primitive.isNumber()) return primitive.getAsNumber(); return primitive.getAsString(); } // 以下为ELResolver接口的默认实现 @Override public Class<?> getType(ELContext context, Object base, Object property) { return Object.class; } @Override public void setValue(ELContext context, Object base, Object property, Object value) {} @Override public boolean isReadOnly(ELContext context, Object base, Object property) { return true; } @Override public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) { return null; } @Override public Class<?> getCommonPropertyType(ELContext context, Object base) { return String.class; } }
配置Spring MVC注册解析器
XML配置方式
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/"/> <property name="suffix" value=".jsp"/> <property name="elResolvers"> <list> <bean class="com.yourpackage.JsonObjectELResolver"/> </list> </property> </bean>
Java配置方式
@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Bean public InternalResourceViewResolver viewResolver() { InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix("/WEB-INF/views/"); resolver.setSuffix(".jsp"); resolver.setElResolvers(new JsonObjectELResolver()); return resolver; } }
JSP中访问
直接传入原始JSON对象即可,JSP访问语法和POJO完全一致:
${viewModel.name} ${viewModel.age} ${viewModel.address.street}
方案三:自定义JSP标签库
编写专用JSP标签,简化JSON属性的访问流程,避免手动调用get方法和转义。
实现标签类
public class JsonGetTag extends TagSupport { private Object json; private String key; private String var; @Override public int doStartTag() throws JspException { Object value = null; if (json instanceof JsonObject) { JsonElement element = ((JsonObject) json).get(key); if (element != null) value = convertJsonElement(element); } else if (json instanceof JSONObject) { try { value = ((JSONObject) json).get(key); } catch (JSONException ignored) {} } if (var != null) { pageContext.setAttribute(var, value); } else { try { pageContext.getOut().write(value != null ? value.toString() : ""); } catch (IOException e) { throw new JspException(e); } } return SKIP_BODY; } private Object convertJsonElement(JsonElement element) { if (element.isJsonObject()) return element.getAsJsonObject(); if (element.isJsonArray()) return element.getAsJsonArray(); JsonPrimitive primitive = element.getAsJsonPrimitive(); if (primitive.isBoolean()) return primitive.getAsBoolean(); if (primitive.isNumber()) return primitive.getAsNumber(); return primitive.getAsString(); } // setter方法 public void setJson(Object json) { this.json = json; } public void setKey(String key) { this.key = key; } public void setVar(String var) { this.var = var; } }
定义标签库描述文件
在WEB-INF/tlds/下创建json.tld:
<?xml version="1.0" encoding="UTF-8"?> <taglib xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd" version="2.1"> <tlib-version>1.0</tlib-version> <short-name>json</short-name> <uri>http://yourdomain.com/tags/json</uri> <tag> <name>get</name> <tag-class>com.yourpackage.JsonGetTag</tag-class> <body-content>empty</body-content> <attribute> <name>json</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <name>key</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <name>var</name> <required>false</required> <rtexprvalue>false</rtexprvalue> </attribute> </tag> </taglib>
JSP中使用
<%@ taglib prefix="json" uri="http://yourdomain.com/tags/json" %> <!-- 直接输出属性值 --> <json:get json="${viewModel}" key="name"/> <!-- 将属性值存入变量 --> <json:get json="${viewModel}" key="age" var="age"/> <c:out value="${age}"/> <!-- 嵌套访问 --> <json:get json="${viewModel.address}" key="city" var="city"/>
方案对比
- 方案一:无需修改Spring配置,兼容性强,但需要手动包装JSON对象。
- 方案二:配置一次后无需改动后端代码,对JSP完全透明,体验最接近POJO。
- 方案三:适合不想修改后端逻辑的场景,通过标签简化访问语法。
内容的提问来源于stack exchange,提问作者BiaoGe
相关产品推荐
相关产品推荐

