You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 20:57:09