SpringMVC返回数据到视图

时间:2022-07-25
本文章向大家介绍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注解配置的方法,则是会按从上至下的顺序进行执行。