顾名思义,该对象应包含两部分内容:
由标记了@RequestMapping的Handler method返回,交ViewResolver解析,生成HTML内容
演示:查看源代码
public ModelAndView Single() { ModelAndView mv = new ModelAndView(); mv.setViewName("article/single"); mv.addObject("title", "源栈课堂 之 SpringMVC入门"); return mv; }
Model中存的数据,最终都会存放到HttpServletRequest对象中,页面上可以通过HttpServletRequest对象获取数据。
在模板中就可以直接使用:
<h1>${title}</h1>
Article ariticle = new Article(); ariticle.setTitle("SpringMVC入门(1)"); article.setBody("ModelAndView\\r\\n顾名思义,该对象应包含两部分内容: ……"); mv.addObject("article",ariticle);
注意:这个Article对象必须是public的!
package models; public class Article { private String title; private String body;
在freemarker的模板中,可以用点(.)引出对象属性:
<h3>${article.title}</h3> <article>${article.body}</article>
在handler method的参数中添加org.springframework.ui.Model
public String Single(Model model) { model.addAttribute("title", "SpringMVC入门(1)"); return "article/single"; }
还可以是:
理解:(堆栈调用代码演示)
复习:分层架构 / entity&Repository / AOP&IoC
我们开始逐步引入架构Service层。
首先要(通过和BLL层开发团队等协商)定义一个接口,我们把所有的Service层接口专门放一个项目ServiceInterface中:
public interface IArticleService { ArticleModel GetById(int id);
ArticleModel属于MVC中的Model,也存放在单独的项目ViewModel中
public class ArticleModel { private String title;因为分属于不同的两个项目,这就要在ServiceInterface中添加对ViewModel的引用:项目上Alter+Enter,进入其properties,在Java Build Path的projects选项下Add
此后其他项目的添加引用以此为例,不再赘述。
然后在MockService中模拟实现:
public class ArticleService implements IArticleService { @Override public ArticleModel GetById(int id) { ArticleModel model = new ArticleModel(); if (id==1) { model.setTitle("SpringMVC:Dispatch / @Controller / @RequestMapping "); model.setBody("……"); }else if (id==2) { model.setTitle(" JSP页面:变量赋值 / 表达式 / 分支循环 / include / useBean / 底层servlet"); model.setBody("……"); } return model; }
最后在ArticleController中调用MockService的方法GetById()方法:
public String Single(Model model, @PathVariable int id) { model.addAttribute("article", service.GetById(id));
为了:
我们声明一个字段,并在构造函数中实例化:
public class ArticleController { private IArticleService service; public ArticleController() { service = new ArticleService();
PS:为了代码文件夹的条理性,我把所有Service层的项目放到了SRV文件夹下:
Hander method中的model参数,除了可以响应时向模板“传递”数据,也可以在接收HTTP请求时,将“绑定(bind)”请求中的数据。
<label>用户名:</label> <input type="text" name="username" /><br /> <label>密码:</label> <input type="password" name="password" /><br /> <label>自我介绍:</label><br /> <textarea name="selfDescription"></textarea><br />
不要忘了还有隐藏文本域:
<input type="hidden" value="fg" name="teacher" />
对应字符串
public class RegisterModel { private String username; private String password; private String selfDescription; private String teacher;
演示:控制台输出:
@RequestMapping public ModelAndView input(RegisterModel model) { System.out.println(model.getUsername()); System.out.println(model.getPassword()); System.out.println(model.getSelfDescription()); System.out.println(model.getTeacher());
比如:radio、checkbox、select等表单元素:
<label>性别:</label> <label> <input type="radio" name="gender" value="0" checked /> 男</label> <input type="radio" name="gender" value="1" />女 <br /> <label> <label>年龄:</label> <select name="age"> <option value="1">one</option> <option value="2" selected>two</option> <option value="3">three</option> </select><br /> <label>爱好:</label> <label> <input type="checkbox" name="hobbies" value="table-tenis" />乒乓球</label> <label> <input type="checkbox" name="hobbies" value="chess" />象棋</label> <label> <input type="checkbox" name="hobbies" value="LOL" />LOL</label>SpringMVC会在绑定时自动的进行类型转换,比如上述表单元素,对应的Model就可以是:
public class RegisterModel { private Boolean gender; private Integer age; private String[] hobbies;演示:控制台输出
System.out.println(model.isGender()); System.out.println(model.getAge()); for (int i = 0; i < model.getHobby().length; i++) { System.out.println(model.getHobby()[i]); }
这种类型转换是比较灵活的,比如:
引入:GET和POST Handler的区分
@RequestMapping(method = RequestMethod.GET) public ModelAndView input() { @RequestMapping(method = RequestMethod.POST) public ModelAndView input(RegisterModel model) {
实际开发中,我们通常使用更高效的标签库(tag lib),比如freemarker中可以这样写:
<#import "spring.ftl" as spring> <form method="post"> <@spring.formInput "command.age"/>
但无论如何,我们首先要明白:所有这些标签库最终都是为了解析成和Model相呼应的HTML标签。其核心问题是获得Model的字段名……
演示:在spring-webmvc-xx.jar中,包org.springframework.web.servlet.view.freemarker下有一个内置的spring.ftl文件
spring.ftl中定义了大量的宏(macro),在我们的form表单页直接引入(import)这个文件,
<#import "spring.ftl" as spring>
总是建议使用标准的spring命名空间(别名)。
然后就可以使用这个宏里面的内容了。
PS:根据版本不同,可能需要在springmvc-servlet.xml的FreeMarkerViewResolver节点下添加
<property name="exposeSpringMacroHelpers" value="true"/>
显式的暴露这个spring.ftl宏
<@spring.formInput "command.age">就开始调用formInput了,传递的参数被称之为path,前缀
实际上是handler method中添加到model中的一个键名,存储的是一个POJO对象:
public ModelAndView input(Model model) { model.addAttribute("command", new RegisterModel());或者:
public ModelAndView input(@ModelAttribute("command")RegisterModel register, Model model) {
而command.后面对应的是model的字段名。
演示因为不匹配而报运行时错误:
<#macro formInput path attributes="" fieldType="text"> <@bind path/> <input type="${fieldType}" id="${status.expression?replace('[','')?replace(']','')}" name="${status.expression}" value="<#if fieldType!="password">${stringStatusValue}</#if>" ${attributes?no_esc}<@closeTag/> </#macro>
其中,status来源于@bind,
<#macro bind path> <#if htmlEscape?exists> <#assign status = springMacroRequestContext.getBindStatus(path, htmlEscape)>而@bind中最有价值的是:springMacroRequestContext
这是SpirngWeb为没有内置的request等对象的模板(比如FreeMarker),专门准备的一个RequestContext对象。
演示:
public static final String SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE = "springMacroRequestContext";
model.put(SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE, new RequestContext(request, response, getServletContext(), model));
在freemarker模板中,直接使用springMacroRequestContext
${springMacroRequestContext.getQueryString()}
而getBindStatus()方法就根据path取相应model对象字段的:
<@spring.formInput "command.age" "style='background-color: blue;' disabled" "password" />但formInput独有的第三个参数是fieldType,可以指定其type
<@spring.formTextarea "command.username" "" />另外5个是选择型表单标签:
formCheckbox(注意单复数)是单个的复选框:
<@spring.formCheckbox "command.gender" />传入的path一般来说都是Boolean值,有趣的是生成的HTML元素:
<input type="hidden" name="_gender" value="on"/> <input type="checkbox" id="gender" name="gender" />@想一想@:这是为什么?
其他4个标签,除了path参数以外,他们还需要options,比如command.hobbies:
<@spring.formSingleSelect "command.hobby" command.hobbies />options是键为String类型的Map(Map<String, T>),一般都由model传递:
RegisterModel register = new RegisterModel(); Map<String, String> hobbies = new HashMap<>(); hobbies.put("1", "乒乓球"); hobbies.put("2", "读书"); hobbies.put("3", "LOL"); register.setHobbies(hobbies); model.addAttribute("command", register);
Map中的String用于POST(给value赋值),值用于HTML呈现
<select id="hobby" name="hobby" > <option value="1" >乒乓球</option> <option value="2" >读书</option> <option value="3" >LOL</option> </select>
formRadioButtons和formCheckboxes还:
<@spring.formRadioButtons "command.hobby" command.hobbies "" "class='radio-inline'" />
生成的HTML如下所示:
<input type="radio" id="hobby0" name="hobby" value="1" > <label for="hobby0">乒乓球</label> <input type="radio" id="hobby1" name="hobby" value="2" > <label for="hobby1">读书</label> <input type="radio" id="hobby2" name="hobby" value="3" > <label for="hobby2">LOL</label>
注意:checkboxes后台绑定的最好是数组集合,否则就是逗号分割的字符串……
邀请人为例,在RegisterModel中再包含一个UserModel
public class RegisterModel { //避免null值报异常 private UserModel invitedBy = new UserModel();然后我们就可以在freemarker中这样写:
<label>邀请人姓名:</label> <@spring.formInput "command.invitedBy.username" />演示:一样可以在后台实现model绑定
System.out.println(model.getInvitedBy().getUsername());原理:生成的HTML代码name值为:invitedBy.username
<input type="text" id="invitedBy.username" name="invitedBy.username" value="">SpringMVC会根据invitedBy.username自动查找model中的invitedBy属性,并为其(invitedBy属性对象)username赋值。
之前讲的formRadioButtons和formCheckboxes并不能满足所有需求。
比如我的消息页面,checkboxes是彼此隔离的(或者说label是非常复杂的);且这时候model再用Map就不合适了,通常会使用List。
public class MineModel { private List<MessageModel> messages;
public class MessageModel { private int id; private String content; private LocalDateTime created;通过MockService返回
public class MessageService { public MineModel Get() { MineModel model = new MineModel(); List<MessageModel> messages = new ArrayList<>(); MessageModel m1 = new MessageModel(); m1.setId(1); m1.setContent("飞哥好帅……"); m1.setCreated(LocalDateTime.now()); MessageModel m2 = new MessageModel(); m2.setId(2); m2.setContent("飞哥好帅帅……"); m2.setCreated(LocalDateTime.now()); MessageModel m3 = new MessageModel(); m3.setId(3); m3.setContent("飞哥好帅帅帅……"); m3.setCreated(LocalDateTime.now()); messages.add(m1); messages.add(m2); messages.add(m3); model.setMessages(messages); return model;注意因为使用的是(注意单数),所以不能再像一样使用数组/集合啥的装用户选中的
handler method中检查model绑定:
for (MessageModel message : model.getMessages()) { System.out.println(message.getId() + ":" + message.getSelected()); }
想一想:为什么取不到id?
如果需要在页面加载时就设置一个默认的选中值,只需要在Controller中指定:
//"2"是(对应value的)键值 register.setHobby("2");
model中声明一个LocalDate字段enroll
private LocalDate enroll;在Controller中赋值
register.setEnroll(LocalDate.now());对比演示:
<@spring.formInput "command.enroll" /><br /> ${command.enroll}ʅ(‾◡◝)ʃ
解决办法:在enroll上添加注解
@DateTimeFormat(pattern = "yyyy-MM-dd")pattern可以指定日期格式……
按我们之前的代码,提交之后,进入POST的handler method,最后页面就会报错:
Caused by: java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'command' available as request attribute
@想一想@:为什么?因为没有给model添加command嘛!
#体会#:HTTP的无状态。
因为RegisterModel已经命名为model,不便再引入springframework.ui.Model,所以我们可以用注解的方式暂时解决这个问题:
public ModelAndView input(@ModelAttribute("command")RegisterModel model) {
多快好省!前端后端,线上线下,名师精讲
更多了解 加: