专栏首页程序猿的大杂烩SpringMVC返回数据到视图

SpringMVC返回数据到视图

通过ModelAndView对象返回数据到视图

在SpringMVC中有一个ModelAndView对象,如其名,Model代表模型,View代表视图,这个名字就很好地解释了该类的作用——它用来存储模型数据以及显示该数据的视图名称。在控制器中调用完模型层处理完用户的请求后,我们可以把结果数据存储在该对象的model属性中,把要返回的视图信息存储在该对象的view属性中,然后让把ModelAndView对象返回给SpringMVC框架。框架则会通过调用Spring配置文件中定义的视图解析器,对该对象进行解析,最后把结果数据传递到指定的视图上,这样我们就可以在视图中获得结果数据并显示出来了。

Spring的配置文件内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>
    <context:component-scan base-package="org.zero01"/>

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
          p:prefix="/WEB-INF/pages/" p:suffix=".jsp"
    />

</beans>

下例将简单介绍如何使用ModelAndView来存储数据,控制器代码如下:

package org.zero01.test;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class Test {

    @RequestMapping("/test.do")
    // SpringMVC会自动把 ModelAndView 对象传递到方法参数上
    public ModelAndView testModelAndView(ModelAndView modelAndView){
        // 设置视图名称
        modelAndView.setViewName("index");
        // 添加数据
        modelAndView.addObject("name","Jon");
        modelAndView.addObject("age","15");
        modelAndView.addObject("address","USA");

        return modelAndView;
    }
}

SpringMVC最后会把ModelAndView里的数据拿出来存储到request对象中,所以在视图中我们可以通过EL表达式中直接获取数据,index.jsp内容如下:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Test</title>
</head>
<body>
<div>
    <p>name::
        <span>${requestScope.name}</span>
    </p>
    <p>age::
        <span>${requestScope.age}</span>
    </p>
    <p>address::
        <span>${requestScope.address}</span>
    </p>
</div>
</body>
</html>

浏览器访问结果如下:

如果不想在方法上声明ModelAndView参数,也可以自己new一个,并且可以直接在构造器中指定视图名称,示例:

package org.zero01.test;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class Test {

    @RequestMapping("/test.do")
    public ModelAndView testModelAndView(){
        // 构造器中可以设置视图名称
        ModelAndView modelAndView = new ModelAndView("index");

        // 添加数据
        modelAndView.addObject("name","Jon");
        modelAndView.addObject("age","15");
        modelAndView.addObject("address","USA");

        return modelAndView;
    }
}

以上只是使用到了其中一个构造器,ModelAndView总共提供了7个构造器,这些多样的构造器让ModelAndView使用起来更便利。

例如,如果当我们只需要返回一个模型数据时,可以使用以下这个构造器:

public class ModelAndView {
    ...
    public ModelAndView(String viewName, String modelName, Object modelObject) {
        this.view = viewName;
        this.addObject(modelName, modelObject);
    }
    ...
}

示例:

package org.zero01.test;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class Test {

    @RequestMapping("/test.do")
    public ModelAndView testModelAndView() {
        return new ModelAndView("index","name","Jon");
    }
}

如果模型层处理完数据之后,返回的是一个Map的实现类对象,例如HashMap集合等,就可以使用以下这个构造器:

public class ModelAndView {
    ...
    public ModelAndView(String viewName, Map<String, ?> model) {
        this.view = viewName;
        if (model != null) {
            this.getModelMap().addAllAttributes(model);
        }
    }
    ...
}

示例:

package org.zero01.test;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import java.util.Map;

@Controller
public class Test {

    @RequestMapping("/test.do")
    public ModelAndView testModelAndView() {
        // 模型层返回了一个Map的实现类对象
        Map<String, Object> dataMap = new TestModule().getModuleData();

        return new ModelAndView("index", dataMap);
    }
}

通过Model返回数据到视图

除了以上介绍的ModelAndView可以返回数据到视图之外,SpringMVC中的Model也可以返回数据到视图。虽然两者都可以完成返回数据到视图的任务,但是它们区别挺大的,ModelAndView是一个实体类,而Model则是一个接口,Model没有指定视图的功能,也就是不能像ModelAndView那样指定视图名称。

而且执行到AnnotationMethodHandlerAdapter类中的invokeHandlerMethod方法时,Model中的数据最终还是会被存储到ModelAndView里。而作为存储模型数据以及视图名称的ModelAndView对象会在DispatcherServlet中被取出,然后DispatcherServlet会先把模型数据存储在request对象中,接着通过视图解析器转发到具体的视图上。

虽然Model是个接口,不过我们并不需要去实现Model接口,只需要在方法参数上进行声明,SpringMVC就会自动帮我们把Model对象传递过来,然后调用相应的方法存储数据即可。

代码示例:

package org.zero01.test;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class Test {

    @RequestMapping("/test.do")
    public String testModel(Model model) {
        model.addAttribute("name","Jon");
        model.addAttribute("age","15");
        model.addAttribute("address","USA");

        return "index";
    }
}

我们来看一下详细的执行过程,看看模型数据最后是否真的会被存储在request对象中。在以上代码中的 return "index"; 那一行打个断点,然后通过debug运行。如下,我们可以看到,在DispatcherServlet的doDispatch方法中视图名称以及模型数据是存储在ModelAndView对象中的,而不是Model中:

而ModelAndView对象中的模型数据则会被存储在HttpServletRequest对象中,所以我们才可以在视图上直接获取结果数据。这一点我们也可以通过debug来看到:

1.首先ModelAndView对象被拿出来之后,就会调用processDispatchResult方法,将ModelAndView对象传递到该方法中进行处理:

2.在processDispatchResult方法中,如果ModelAndView对象不为空的话,就会调用render方法,并把ModelAndView对象传递过去:

3.在render方法中,会把ModelAndView对象中的模型数据拿出来,传递到View对象中的render方法中(这个View的实现类是AbstractView):

4.在view对象中的render方法中,会把模型数据传递到createMergedOutputModel方法中进行合并:

5.在createMergedOutputModel方法中会把几个数据合并到一个集合里,但是这里除了model之外其他都为空,所以只合并了model数据:

在控制台中可以看到mergedModel对象里的数据如下:

6.得到mergedModel对象后,继续往下执行,接着就会调用renderMergedOutputModel方法,把mergedModel、request以及response对象都传递过去:

7.但是renderMergedOutputModel是一个抽象方法,所以该方法的调用被传递到了它的一个子类中(该子类是InternalResourceView),这个子类实现的renderMergedOutputModel方法中调用了exposeModelAsRequestAttributes方法并把模型数据和request对象传递了过去:

8.而exposeModelAsRequestAttributes方法没有被子类重写,所以调用的是父类的,也就是AbstractView类的,所以调用被传递到了AbstractView类的exposeModelAsRequestAttributes方法中。就是在这个方法中,模型数据被一个一个的放入到了HttpServletRequest对象中:

我们可以来看看将模型数据添加到request对象中的具体过程: 第一个数据:

控制台:

第二个数据:

控制台:

第三个数据:

控制台:

以上的一系列复杂的流程走完之后,我们在视图中,才可以直接使用EL表达式进行拿值:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Test</title>
</head>
<body>
<div>
    <p>name::
        <span>${requestScope.name}</span>
    </p>
    <p>age::
        <span>${requestScope.age}</span>
    </p>
    <p>address::
        <span>${requestScope.address}</span>
    </p>
</div>
</body>
</html>

浏览器访问结果:


通过Map返回数据到视图

使用Map返回数据与使用Model类似,也是只需要在方法上声明Map参数,然后添加数据即可。SpringMVC会自动把对象传递进来,而且返回的数据也是一样会存储到request对象中,示例:

package org.zero01.test;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.Map;

@Controller
public class Test {

    @RequestMapping("/test.do")
    public String testMap(Map map) {
        map.put("name","Jon");
        map.put("age","15");
        map.put("address","USA");

        return "index";
    }
}

jsp代码和之前一样,略。浏览器访问结果如下:


@SessionAttributes注解

从以上的实验中,我们可以得知,默认情况下SpringMVC会将模型中的数据存储到request对象中。而request对象里存储的数据是一次性的,当一个请求结束后,数据就失效了,如果要跨页面使用,那么就需要使用到session了。@SessionAttributes注解就是用来将模型中的数据存储一份到session对象中,这个注解是写在类上的。

这个注解中有两个属性:names和types,names属性用于指定哪些名称的数据需要存储到session对象中,如下示例:

package org.zero01.test;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;

@SessionAttributes(names = {"name", "age", "address"})
@Controller
public class Test {

    @RequestMapping("/test.do")
    public String testModel(Model model) {
        model.addAttribute("name", "Max");
        model.addAttribute("age", "20");
        model.addAttribute("address", "北京");

        return "index";
    }
}

index.jsp内容如下:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Test</title>
</head>
<body>
<div>
    <p>request_name::
        <span>${requestScope.name}</span>
    </p>
    <p>request_age::
        <span>${requestScope.age}</span>
    </p>
    <p>request_address::
        <span>${requestScope.address}</span>
    </p>
    <hr>
    <p>session_name::
        <span>${sessionScope.name}</span>
    </p>
    <p>session_age::
        <span>${sessionScope.age}</span>
    </p>
    <p>session_address::
        <span>${sessionScope.address}</span>
    </p>
</div>
</body>
</html>

浏览器访问结果如下:

types属性则是指定哪些类型的数据需要存储到session对象中,如下示例:

package org.zero01.test;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;

// 只要是Student类型的数据就存储一份到session中
@SessionAttributes(types = Student.class)
@Controller
public class Test {

    @RequestMapping("/test.do")
    public String testModel(Model model) {
        Student student = new Student();
        student.setSname("Max");
        student.setAge(20);
        student.setAddress("北京");

        model.addAttribute("student", student);

        return "index";
    }
}

index.jsp内容如下:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Test</title>
</head>
<body>
<div>
    <p>request_name::
        <span>${requestScope.student.sname}</span>
    </p>
    <p>request_age::
        <span>${requestScope.student.age}</span>
    </p>
    <p>request_address::
        <span>${requestScope.student.address}</span>
    </p>
    <hr>
    <p>session_name::
        <span>${sessionScope.student.sname}</span>
    </p>
    <p>session_age::
        <span>${sessionScope.student.age}</span>
    </p>
    <p>session_address::
        <span>${sessionScope.student.address}</span>
    </p>
</div>
</body>
</html>

浏览器访问结果和之前一样,略。


@SessionAttribute注解

这个@SessionAttribute注解与上面介绍的@SessionAttributes注解名字相似但作用相反,它用于在session对象中取值,并且是写在方法参数上的,如下示例:

package org.zero01.test;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttribute;
import org.springframework.web.bind.annotation.SessionAttributes;

@SessionAttributes(types = Student.class)
@Controller
public class Test {

    @RequestMapping("/test.do")
    public String testModel(Model model) {
        Student student = new Student();
        student.setSname("Max");
        student.setAge(20);
        student.setAddress("北京");

        model.addAttribute("student", student);

        return "redirect:/test2.do";
    }

    @RequestMapping("/test2.do")
    public String testModel2(@SessionAttribute Student student) {

        System.out.println(student.getSname());
        System.out.println(student.getAge());
        System.out.println(student.getAddress());

        return "index";
    }
}

先访问/test.do,控制台打印结果如下:

Max
20
北京

@RequestAttribute注解

@RequestAttribute注解使用在方法的参数上,该注解可以从request对象中拿取预先存在的数据,然后绑定到配置该注解的参数上。

我们需要一个过滤器事先在request对象中存储一些数据,过滤器代码如下:

package org.zero01.test;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter("/test.do")
public class TestFilter implements Filter {
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        request.setAttribute("name", "zero");
        request.setAttribute("age", 15);
        chain.doFilter(request, response);
    }

    public void destroy() {

    }
}

控制器代码如下:

package org.zero01.test;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestAttribute;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class Test {

    @RequestMapping("/test.do")
    public String testModel(
        @RequestAttribute("name") String name, 
        @RequestAttribute("age") int age
    ) {
        System.out.println("name is: " + name);
        System.out.println("age is: " + age);

        return "index";
    }
}

控制台打印结果如下:

name is: zero
age is: 15

注意:这个注解在Spring MVC5版本以上才支持,5以下的版本是不支持的,例如4版本虽然也有这个注解,但却是无效的,无法获取到request对象中的数据。


@ModelAttribute注解

这个@ModelAttribute注解可以写在方法上或参数上,当该注解写在方法上时,那么配置了该注解的方法就会比配置@RequestMapping注解的方法要先执行。所以我们通过这个注解的特性可以事前配置一些公共的数据,或补全一些数据参数什么的。如果该注解是写在方法参数上,则是从Model对象中取出预先存在的数据绑定对应的参数上。示例:

package org.zero01.test;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestAttribute;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;

@Controller
public class Test {

    @ModelAttribute
    public void beforeTestModel(HttpServletRequest request, Model model) {
        System.out.println("beforeTestModel方法执行了...");
        request.setAttribute("name", "zero");
        model.addAttribute("age", 15);
    }

    @RequestMapping("/test.do")
    public String testModel(@RequestAttribute("name") String name, @ModelAttribute("age") String age) {
        System.out.println("testModel方法执行了...");
        System.out.println("reques --- name is: " + name);
        System.out.println("model --- age is: " + age);

        return "index";
    }
}

控制台打印结果如下:

beforeTestModel方法执行了...
testModel方法执行了...
reques --- name is: zero
model --- age is: 15

如上,从控制台打印结果的结果,可以看到,@ModelAttribute注解配置的方法的确是先执行的。如果存在多个@ModelAttribute注解配置的方法,则是会按从上至下的顺序进行执行。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • SpringMVC中用于绑定请求数据的注解以及配置视图解析器

    在上一篇文章中我们简单介绍了@RequestMapping与@RequestParam注解,知道了如何去配置地址映射,本篇则介绍一些用于处理request数据的...

    端碗吹水
  • Mybatis-Generator插件的使用与Spring集成Mybatis的配置

    Mybatis-Generator是一个用于自动生成dao层接口、pojo以及mapper xml的一个Mybatis插件,该插件有三种用法:命令行运行、Ecl...

    端碗吹水
  • Spring Cloud Hystrix - 服务容错

    在微服务架构中,由于某个服务的不可用导致一系列的服务崩溃,被称之为雪崩效应。所以防御服务的雪崩效应是必不可少的,在Spring Cloud中防雪崩的利器就是Hy...

    端碗吹水
  • SpringBoot实战(三):整合Mybatis配置多数据源

    最近接到一个新需求,经过分析后做了相应的设计;其中需要在一个项目中操做不同的数据源;于是进行了相关验证;在此记录一下验证过程。

    用户2781897
  • 利用Spring MVC搭建REST Service

    之前写过一篇 利用JAX-RS快速开发RESTful 服务 今天来看下spring-mvc框架如何实现类似的功能:  一、pom.xml 1 <?xml v...

    菩提树下的杨过
  • SpringBoot基础(三、整合Mybatis、Redis)

    我们以前使用SSM的时候,使用Mybatis是需要各种配置文件、实体类、Dao层的各种映射关系,虽然可以使用注解减少这些配置信息,但还是有好多东西需要配置,自从...

    营琪
  • springboot gradle mybatis mysql配置(注解)

    蓝色人
  • SpringBoot系列之@PropertySource读取yaml文件

    最近在做实验,想通过@PropertySource注解读取配置文件的属性,进行映射,习惯上用properties都是测试没问题的,偶然换成yaml文件,发现都读...

    SmileNicky
  • shiro整合springmvc

    代码:https://github.com/jxq0816/springmvc_framework

    week
  • POI系列之根据样式识别word内容和标题

    ps:本博客内容比较简单,只是自己做下记录,有时间再探讨一下实现,网上实现的很多都是付费的,不建议用本博客的方法,本博客只是自己做下笔记

    SmileNicky

扫码关注云+社区

领取腾讯云代金券