ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

SpringMVC Model&@ModelAttribute解析

2022-07-24 15:33:38  阅读:142  来源: 互联网

标签:String Person SpringMVC person ModelAttribute Model public


为甚么需要Model

先忘掉前后端分离的基于API的开发方式。

在传统的MVC模式开发中,一个Controller的典型职责是:

  1. 通过模型层来获得一些数据
  2. 选择要渲染的视图,并将模型层获得的数据放到视图中

如果不使用SpringMVC,那我们必须要面对的一个问题就是如何将模型层的数据放到视图中。如果我们使用JSP技术,那么request对象的属性是一个放置模型层数据的好地方,而且我们在开发中也都是这么做的。那如果使用别的视图技术呢?如果你使用Velocity呢?如果你不是forward到一个JSP而是重定向到一个JSP呢?这时request对象的属性还可用吗?你可能需要以别的方式传递数据到视图中。

上面的问题不应该是Controller的职责,因为View层才最知道如何将数据绑定到页面中。所以,SpringMVC提供模型数据的统一表示,也就是一个MapDispatcherServlet将会在渲染视图时将这个Map传递到View中,View如何处理这个数据的绑定就是它自己的事了。

Spring中的模型使用ModelModelMapMap来表示,最后,它们都会以Map形式交付到View中进行渲染

所以Model存在的意义就是在要渲染到View中的数据和实际使用的View技术解耦

InternalResourceView处理模型数据的示例

比如在下面的InternalResourceView的渲染方法中,它就将Model数据保存到了request的Attribute中,所以也就有了Model数据会被保存在请求属性中这一说法,但实际这个说法是狭隘的,也许在别的视图技术中就不这样处理数据了呢!

img

所以,我们可以在JSP中使用request.getAttribute中获取模型数据,下面这个方法设置了一个模型属性customAttr,然后它会把我们带向modelTestPage2,这是一个JSP页面,使用InternalResourceView进行渲染:

@Controller
@RequestMapping("/model")
public class ModelTest {
    @GetMapping("/testModel")
    public String testModel(Model model) {
        model.addAttribute("customAttr", "customValue");
        return "modelTestPage2";
    }
}

JSP页面中会分别尝试使用el直接获取和使用requestScope.get来获取,实际上,它们都是在获取request域的属性,只不过前一种写法更像Model对象用了什么魔法将一个普通变量暴露到JSP中一样:

<p>Directly get use el: ${customAttr}</p>
<p>Using request attribute: ${requestScope.get("customAttr")}</p>

结果:

img

使用RedirectView时

RedirectView是把模型数据添加到URL参数中

img

所以,如果在Redirect时Model中有数据,那么它们就都会被拼接到URL上。

img

RedirectView可以通过设置exposeModelAttributes属性,不将Model中的数据放到URL参数中。而且,它只会将Model中的简单对象放到URL中(BeanUtils.isSimpleValueType返回true的),像Pojo这种对象它不会动的

@ModelAttribute

修饰方法参数

如果@ModelAttribute用在参数上,那么它将从Model中获取值并设置给属性:

@GetMapping("/testModelAttribute")
public String testModelAttribute(@ModelAttribute Person person) {
    System.out.println(person);
    return "modelTestPage2";
}

可是,Model从哪来?我们通过Controller使用Model,目的是将模型层的数据放进去然后传递给View层,所以Controller才是Model的填充者,现在它却要从Model中获取一个值,谁来给它填充?

把这个问题先放一放

修饰方法

@ModelAttribute也可以修饰方法,该方法的作用就是在为具体URL服务的方法被调用前,向Model中填充一些东西。

所以,上面的例子可以这样写:

// 在服务方法被调用前,向Model中填充一些内容
@ModelAttribute
public Person person() {
    return new Person("Yudoge", 21, 100.0, 100.0, "翻斗大街", new Date());
}

// 使用@ModelAttribute获取Model中的属性
@GetMapping("/testModelAttribute")
public String testModelAttribute(@ModelAttribute(name = "person") Person person) {
    System.out.println(person);
    return "modelTestPage2";
}

@ModelAttribute修饰的方法中可以使用和Handler方法一样的参数,并且它们都会正确的被注入。

需要注意的是,如果你通过@ModelAttribute方法返回的是简单属性,并且还redirect了,那么这个Model中的数据自然会被放到转发的url querystring中

应用场景?

假如我们的系统中用Person表示一个用户,当然,很难想象这是什么脑瘫想出来的idea。

现在,我们希望在Controller的每个方法中都通过请求头中的Token来获取当前登录的Person对象,那在每个请求方法中都要写上具体的逻辑,为了避免这种重复代码,这时就可以使用@ModelAttribute

// 获取当前登录的Person对象并添加到Model
@ModelAttribute
public Person getLoginPerson(WebRequest request) {
    String token = request.getHeader("token");
    String id = TokenUtils.dec(token);
    Person p = personRepository.findPersonById(id);
    return p;
}

// 直接从model中拿到当前登录的Person对象
@GetMapping("/testModelAttribute")
public String testModelAttribute(@ModelAttribute Person person) {
    System.out.println(person);
    return "modelTestPage2";
}

回到修饰方法参数的情况

刚刚我们在说修饰方法参数时被打断了,现在我们回来。

我们通过将@ModelAttribute放在方法上,向每个Handler方法预填充Model,以让它们可以从Model中获取到值。那么如果没人给你填充,Model里没有你想要的对象咋办?

这种情况下,这个对象会被新建并且自动添加到Model中,你可以理解为这种情况下的@ModelAttribute注解是将一个对象快速添加到Model中而不需要显式操作Model对象的方法。

@GetMapping("/testModelAttribute")
public String testModelAttribute(@ModelAttribute(name = "person") Person person) {
    person.setName("Yudoge");
    person.setAddress("翻斗大街");
    person.setAge(10);
    return "modelTestPage3";
}

页面可以读取到值:

img

@ModelAttribute的数据绑定

@ModelAttribute标注的参数对象中的属性还可以被PathVariable中的属性覆盖,这被称为数据绑定(Databinding):

@GetMapping("/testModelAttribute/{weight}/{height}")
public String testModelAttribute(@ModelAttribute(name = "person") Person person) {
    person.setName("Yudoge");
    person.setAddress("翻斗大街");
    person.setAge(10);
    return "modelTestPage3";
}

页面中,weightheight被成功设置:

img

数据绑定过程可能失败,可以采用BindingResult来监测失败:

@GetMapping("/testModelAttribute/{weight}/{height}")
public String testModelAttribute(@ModelAttribute(name = "person") Person person, BindingResult bindingResult) {
    if (bindingResult.hasErrors()) {
        bindingResult.getAllErrors().forEach(System.out::println);
    }
    person.setName("Yudoge");
    person.setAddress("翻斗大街");
    person.setAge(10);
    return "modelTestPage3";
}

这时如果你传递错误格式的height

img

可以通过@ModelAttribute(binding=false)来取消数据绑定过程

@ModelAttribute注入的全部方式

实际上,@ModelAttribute放在方法参数上,SpringMVC并非只尝试从Model中获取,下面的是这种参数的全部获取方式:

  1. 尝试从Model中获取,也就是刚刚说的情况
  2. 如果类使用了@SessionAttributes注解,那么从Session获取
  3. 从通过Converter传递的URI的PathVariable中
  4. 调用默认的构造方法,也就是刚刚的Model中没有的情况
  5. 调用与Serlvet请求参数匹配的主构造函数创建

通过Converter的PathVarible注入

下面的代码通过@ModelAttribute尝试从PathVarible中获取Person对象:

@GetMapping("/testModelAttributeAndConverter/{person}")
public String testModelAttributeAndConverter(@ModelAttribute(name = "person") Person person) {
    return "modelTestPage3";
}

PathVarible是字符串,我们需要一个Converter将以某种格式编排的字符串转换成Person:

public class PersonConverter implements Converter<String, Person> {
    private final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
    @Override
    public Person convert(String source) {
        try {
            String[] sarr = source.split(":");
            Person person = new Person();
            person.setName(sarr[0]);
            person.setAge(Integer.parseInt(sarr[1]));
            person.setWeight(Double.parseDouble(sarr[2]));
            person.setHeight(Double.parseDouble(sarr[3]));
            person.setAddress(sarr[4]);
            person.setBirthday(format.parse(sarr[5]));
            return person;
        } catch (Exception e) {
            throw new IllegalArgumentException(e);
        }
    }
}

这里我们的编排格式是:

名字:年龄:体重:身高:地址:生日

它简单的使用split方法来把字符串分割成数组,如果在这过程中发生任何异常,比如数组下标越界,比如parse格式失败,都会遵循Converter接口的规范,重新抛出一个IllegalArgumentException表示转换失败。

注册该Converter:

@Configuration
@ComponentScan(basePackages = {"top.yudoge.controller"})
@EnableWebMvc
public class AppConfig implements WebMvcConfigurer {

    // ...

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new PersonConverter());
    }
}

结果:

img

ModelAttribute小总结

  1. @ModelAttribute放在方法上时,该方法会在所有Handler请求方法之前调用,用于向Model中预填充值
  2. @ModelAttribute放在方法参数上时,若Model中有该参数,那么获取到这个参数
  3. 否则,新建该对象,并将该对象放在Model中

标签:String,Person,SpringMVC,person,ModelAttribute,Model,public
来源: https://www.cnblogs.com/lilpig/p/16514562.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有