SpringMVC 之 @ModelAttribute&OOP处理

来源:互联网 发布:js正方形3d立体旋转 编辑:程序博客网 时间:2024/06/03 22:44

SpringMVC 之 @ModelAttribute&OOP处理

本节我们将学习一下SpringMVC中 @ModelAttribute 作用以及SpringMVC 对对象处理的方法和实现思路。


概念

在Spring mvc中,注解@ModelAttribute是一个非常常用的注解,其功能主要在两方面:

  • 运用在参数上,会将客户端传递过来的参数按名称注入到指定对象中,并且会将这个对象自动加入ModelMap中,便于View层使用同时也支持Controller层的调用。

  • 运用在方法上,会在每一个@RequestMapping标注的方法前执行,如果被调用的Controller方法有返回值,则自动将@ModelAttribute修饰方法中的对象加入到ModelMap中。


使用场景

假设现在我们需要处理这么一个问题:我们在实体层定义了一个Contract(合同实体),假设它拥有100个属性,现在用户需要在平台上修改他的合同,那么我们希望以下功能能被实现:

  • 该合同的某一些敏感属性不能被修改比如合同甲乙方,监管合同的Clean house等等。
  • 基于上述条件成立,我们只对用户提交的字段做修改处理,其他的字段一律不做修改。

如果只是为了实现以上功能不用Spring MVC 提供的任何功能使用纯JAVA EE 代码即可实现,但这样不高效,代码还会很臃肿。SpringMVC 为我们提供了这么一个简洁高效的处理POJO的方法那就是 @ModelAttribute


实例

  • ExampleController.java

    package com.spring.sstps.controller;import org.springframework.web.bind.annotation.ModelAttribute;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.servlet.ModelAndView;import com.spring.sstps.pojo.Contract;import com.spring.sstps.pojo.TradePolicy;@RestController("/example")public class ExampleController {    @ModelAttribute("contract")    public Contract getContractInfo(@RequestParam(value = "id", required = false) Integer id) {        Contract contract = null;        if (id == null) {            System.out.println("Id is inavaliable-----");            try {                TradePolicy tradePolicy = new TradePolicy("EPGR", "epClear");                contract = new Contract("c:yujh4464564", "tx41574644", "2017-11-28 18:18:18", "NA", tradePolicy);            } catch (Exception e) {                e.printStackTrace();            }        } else {            System.out.println("Id is avaliable:" + id);        }        return contract;    }    @RequestMapping("/saveContract")    public ModelAndView setContract(@ModelAttribute("contract") Contract contract) throws Exception {        ModelAndView modelAndView = new ModelAndView();        // 每一次调用该方法都将强制修改 ContractId 属性的至        System.out.println("Contract:" + contract);        modelAndView.addObject("CONTRACT", contract);        modelAndView.setViewName("contract/saveContract");        return modelAndView;    }    @RequestMapping("/toSaveContract")    public String tosetContract() throws Exception {        return "contract/toSaveContract";    }}
  • Contract.java

    package com.spring.sstps.pojo;public class Contract {    private String contractId;    private String contractTxId;    private String createTime;    private String area;    private TradePolicy tradePolicy;    public Contract(String contractId, String contractTxId, String createTime, String area, TradePolicy tradePolicy) {        super();        this.contractId = contractId;        this.contractTxId = contractTxId;        this.createTime = createTime;        this.area = area;        this.tradePolicy = tradePolicy;    }    public String getContractId() {        return contractId;    }    public void setContractId(String contractId) {        this.contractId = contractId;    }    public String getContractTxId() {        return contractTxId;    }    public void setContractTxId(String contractTxId) {        this.contractTxId = contractTxId;    }    public String getCreateTime() {        return createTime;    }    public void setCreateTime(String createTime) {        this.createTime = createTime;    }    public String getArea() {        return area;    }    public void setArea(String area) {        this.area = area;    }    public TradePolicy getTradePolicy() {        return tradePolicy;    }    public void setTradePolicy(TradePolicy tradePolicy) {        this.tradePolicy = tradePolicy;    }    @Override    public String toString() {        return "Contract [contractId=" + contractId + ", contractTxId=" + contractTxId + ", createTime=" + createTime                + ", area=" + area + ", tradePolicy=" + tradePolicy + "]";    }}
  • TradePolicy.java

    package com.spring.sstps.pojo;public class TradePolicy {    private String clearHouse;    private String epType;    public TradePolicy(String clearHouse, String epType) {        this.clearHouse = clearHouse;        this.epType = epType;    }    public String getClearHouse() {        return clearHouse;    }    public void setClearHouse(String clearHouse) {        this.clearHouse = clearHouse;    }    public String getEpType() {        return epType;    }    public void setEpType(String epType) {        this.epType = epType;    }    @Override    public String toString() {        return "TradePolicy [clearHouse=" + clearHouse + ", epType=" + epType + "]";    }}
  • JSP

    <%@ page language="java" contentType="text/html; charset=UTF-8"    pageEncoding="UTF-8"%><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>toSaveContract</title></head><body>    <form action="saveContract" method="Post">        ContractId:<input  type="text" name="contractId"/>        <br>        ContractTxId:<input type="text" name="contractTxId"  />        <br>        Area:<input type="text" name="area"/>        <br>        epType:<input type="text" name="tradePolicy.epType"/>        <br>        <input type="Submit" name="Submit"/>        <br>    </form></body></html>
  • 代码解读:

    • 有@ModelAttribute修饰的方法,在每一个目标方法被调用之前都将会被SpringMVC调用。

    • @ModelAttribute注解可以用来修饰目标方法的POJO类的入参,其value值(@ModelAttribute(“contract”)的value值为contract)作用如下:

      • SpringMVC 会使用value的属性值在implicitModel(所有POJO的集合,Map《String,Object类型>)中查找对象,如存在则直接传入到将目标方法中。

      • SpringMVC 会以 该value(此处特指contract) 为key, POJO类型的对象(入参之后的POJO即修改了属性之后的,接下来我们将学习目标方法的POJO入参过程)为Value 存入值request中。

    • POJO 支持级联入参(本例子),入参的value 要匹配 POJO 类的属性名称。

  • 以上代码运行流程:

    • 执行 @ModelAttribute 注解修饰的方法: 从数据库中取出对象, 把对象放入到了 implicitModel 的 Map 中. 键为: contract

    • SpringMVC 从 Map 中取出 Contract对象, 并把表单的请求参数赋给该 Contract对象的对应属性.

    • SpringMVC 把上述对象传入目标方法的参数.

    • 方法调用完毕之后 SpringMVC 将会把 Contract对象返回给视图层,供视图层调用。

注意: 在 @ModelAttribute 修饰的方法中, 放入到 Map 时的键需要和目标方法入参类型的第一个字母小写的字符串一致!


POJO 类型入的过程

  • 确定一个 key

    • 若目标方法的 POJO 类型的参数木有使用 @ModelAttribute 作为修饰, 则 key 为 POJO 类名第一个字母的小写

    • 若使用了 @ModelAttribute 来修饰, 则 key 为 @ModelAttribute 注解的 value 属性值

  • 在 implicitModel 中查找 key 对应的对象, 若存在, 则作为入参传入

    • 若在 @ModelAttribute 标记的方法中在 Map 中保存过, 且 key 和 1 确定的 key 一致, 则会获取到
  • 若 implicitModel 中不存在 key 对应的对象, 则检查当前的 Handler 是否使用 @SessionAttributes 注解修饰,若使用了该注解, 且 @SessionAttributes 注解的 value 属性值中包含了 key, 则会从 HttpSession 中来获取 key 所对应的 value 值, 若存在则直接传入到目标方法的入参中. 若不存在则将抛出异常

  • 若 Handler 没有标识 @SessionAttributes 注解或 @SessionAttributes 注解的 value 值中不包含 key, 则会通过反射来创建 POJO 类型的参数, 传入为目标方法的参数

  • SpringMVC 会把 key 和 POJO 类型的对象保存到 implicitModel 中, 进而会保存到 request 中


整体源代码理解过程

由于需要大量截图和大量实例来说明,下面我们只讲 Fuck Iterms !!!

  • 调用 @ModelAttribute 注解修饰的方法. 实际上把 @ModelAttribute 方法中 Map 中的数据放在了 implicitModel 中

  • 解析请求处理器的目标参数, 实际上该目标参数来自于 WebDataBinder 对象的 target 属性

    • 创建 WebDataBinder 对象

      • 确定 objectName 属性: 若传入的 attrName 属性值为 “”, 则 objectName 为类名第一个字母小写注意: attrName. 若目标方法的 POJO 属性使用了 @ModelAttribute 来修饰, 则 attrName 值即为 @ModelAttribute的 value 属性值

      • 确定 target 属性

      • 在 implicitModel 中查找 attrName 对应的属性值. 若存在,则直接赋值,若不存在: 则验证当前 Handler 是否使用了 @SessionAttributes 进行修饰, 若使用了, 则尝试从 Session 中获取 attrName 所对应的属性值. 若 session 中没有对应的属性值, 则抛出了异常

      • 若 Handler 没有使用 @SessionAttributes 进行修饰, 或 @SessionAttributes 中没有使用 value 值指定的 key和 attrName 相匹配, 则通过反射创建了 POJO 对象

  • SpringMVC 把表单的请求参数赋给了 WebDataBinder 的 target 对应的属性

  • SpringMVC 会把 WebDataBinder 的 attrName 和 target 给到 implicitModel近而传到 request 域对象中

  • 把 WebDataBinder 的 target 作为参数传递给目标方法的入参


小结

  • 总的来说SpingMVC 提供的@ModelAttribute 是OOP 的一个重要体现。

  • 要了解@ModelAttribute的作用需要理解 POJO 构造初始化、POJO入参、POJO构造完成整个流程。算是SpringMVC中比较抽象同时又是 狠狠狠 重要的一个功能点。

原创粉丝点击