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中比较抽象同时又是 狠狠狠 重要的一个功能点。