本文主要是介绍SpringMvc 视图和视图解析器 CURD案例 转换器 mvc:annotation-driven mvc:default-servlet-handler @InitBinder,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
1、视图简介
(1)视图的作用是渲染模型数据,将模型里的数据以某种形式呈现给用户。
(2)为了实现视图模型和具体实现技术的解耦,Spring在org.springframework.web.servlet 包中定义了一个高度抽象的 View接口:
(3)视图对象由视图解析器负责实例化。由于视图是无状态的,所以他们不会有线程安全的问题
2、视图解析器简介
(1)SpringMVC 为逻辑视图名的解析提供了不同的策略,可以在 Spring WEB 上下文中配置一种或多种解析策略,并指定他们之间的先后顺序。每一种映射策略对应一个具体的视图解析器实现类。
(2)视图解析器的作用比较单一:将逻辑视图解析为一个具体视图对象。
(3)所有的视图解析器都必须实现 ViewResolver 接口:
说明:每个视图解析器都实现了 Ordered 接口并开放出一个 order 属性,可以通过 order 属性指定解析器的优先顺序,order 越小优先级越高。但是注意,默认order属性值为max(最大数字),所以只要定义了order属性,就会比默认优先级高。SpringMVC 会按视图解析器顺序的优先顺序对逻辑视图名进行解析,直到解析成功并返回视图对象,否则将抛出 ServletException 异常
3、解析过程简介
请求处理方法执行完成后返回的String类型或者View类型,都会被SpringMVC转换成ModelAndView类型对象,它包含了逻辑名和模型对象的视图
SpringMVC通过视图解析器(ViewResolver),得到真正的物理视图对象View(最终视图可以是JSP,也可以是Excel、JFreeChart等等各种),最终通过该对象的render方法,得到显示结果。目前常用的是InternalResourceView(视频老师说的,八成过时了)
关于重定向和转发(复习Servlet的转发与重定向)
一般情况下,控制器方法返回字符串类型的值会被当成逻辑视图名处理。
如果返回的字符串中带 forward: 或 redirect: 前缀时,SpringMVC 会对他们进行特殊处理:将 forward: 和redirect: 当成指示符,其后的字符串作为 URL 来处理
redirect:success.jsp:会完成一个到 success.jsp 的重定向的操作
forward:success.jsp:会完成一个到 success.jsp 的转发操作
package com.atguigu.springmvc.handlers@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest{private static final String SUCCESS="success";@RequestMapping("/testRedirect")public String testRedirect(){System.out.println("testRedirect");return "redirect:/index.jsp"; //重定向,又返回主页}
}
前端index.jsp页面
<body><a href="springmvc/testRedirect">Test Redirect</a>
</body>
4、解析器配置
4.1 InternalResourceViewResolver解析器解析URL类型视图
在Springmvc入门案例中配置过,回忆下。
package com.atguigu.springmvc.handlers@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest{private static final String SUCCESS="success";@RequestMapping("/testViewAndViewResolver")public String testViewAndViewResolver(){System.out.println("testViewAndViewResolver");return SUCCESS;}
}
<body><a href="springmvc/testViewAndViewResolver">Test ViewAndViewResolver</a>
</body>
4.2 InternalResourceViewResolver解析JstlView类型视图
若JSP页面中使用了JSTL国际化标签功能,则 SpringMVC 会自动把视图由InternalResourceView 转为 JstlView。由于已经过时,这个配置又很麻烦,了解一下就是了。就不专门演示了
4.3 不经过handler方法,直接渲染页面
若希望直接响应通过 SpringMVC 渲染的页面(地址输入path后,直接到xxx/success.jsp),可以使用 mvc:view-controller 标签实现
<mvc:view-controller path="springmvc/testJstlView" view-name="success">
但是注意:此方法会导致handler中的视图失效
解决办法:使用<mvc:annotation-driven></mvc:annotation-driven>
5、自定义视图
此时需要使用前面讲的
创建一个com.atguigu.springmvc.views包,在该包下建一个视图(实现View接口),并将其放入IOC容器中
package com.atguigu.springmvc.views;//将视图放入IOC容器,才能被解析
@Component
public class HelloView implements View{@Overridepublic String getContentType(){return "text/html";}@Overridepublic void render(Map<String,?> model,HttpServletRequest request,HttpServletResponse response)throws Exception{reponse.getWriter().print("hello view,time:"+new Date());}
}
配置视图解析器,此时用BeanNameViewResolver视图解析器
说明:BeanNameViewResolver使用视图的名字解析视图。配置order属性后,就会优先用这个解析器解析视图
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver"><property name="order" value="100"></property>
</bean><!--原视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"><property name="prefix" value="/WEB-INF/views/"></property><property name="suffix" value=".jsp"></property>
</bean>
此时有两个视图解析器。前面讲过,可以通过视图解析器的order属性来定义视图解析器的优先级,数字越小优先级越高。
回到SpringMVCTest类
package com.atguigu.springmvc.handlers@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest{private static final String SUCCESS="success";@RequestMapping("/testView")public String testView(){System.out.println("testView");return "helloView"; //springmvc里面有helloView视图对象}
}
前端页面
<body><a href="springmvc/testView">Test View</a>
</body>
6、RESTful CRUD 代码演示
6.1 Restful风格 (参考前面讲过的)
6.2 需求
需求1:显示所有员工的信息,URI:emps,请求方式:GET 显示效果如下,
需求2:添加员工信息
(1)显示添加页面,URI:emp 请求方式GET 显示效果
(2)添加员工信息,URI:emp 请求方式POST 显示效果:完成添加,重定向到list页面
需求3:删除操作
URL:emp/{id} 这个id是占位符
请求方式:DELETE
删除后效果:对应记录从数据表中删除
需求4:修改操作:不修改lastName,其他都修改
① 显示修改页面
URI:emp
请求方式:GET
显示效果:回显表单
② 修改员工信息:
URI:emp
请求方式:PUT
显示效果:完成修改,重定向到list页面
完成以上需求相关的类和页面
(1)相关的类
实体类:Employee、Department
Handler:EmployeeHandler (重点)
Dao:EmployeeDao、DepartmentDao (这个案例不连接数据库,只是静态注入页面)
(2)相关的页面
list.jsp
input.jsp
edit.jsp
6.3 代码演示
新建工程springmvc-2 ,将springmvc的jar包全部复制大lib下(后面实际都是用maven),
6.3.1 实现显式需求
(1)在web.xml中配置DispatcherServlet,名称空间选择bean,context和mvc。以下将配置文件位置修改为类路径下的springmvc.xml
<!--配置DispatcherServlet-->
<Servlet><Servlet-name>springDispatcherServlet</Servlet-name><Servlet-class>org.springframework.web.servlet.DispatcherServlet</Servlet-class><!--配置DiapatcherServlet的一个初始化参数:SpringMvc配置文件的位置和名称 --><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:springmvc.xml</param-value></init-param><load-on-startup>1</load-on-startup>
</Servlet><servlet-mapping><Servlet-name>springDispatcherServlet</Servlet-name><url-pattern>/</url-pattern>
</servlet-mapping>
由于REST风格还有DELETE 和 PUT请求,而Tomcat还不支持,因此需要继续在配置文件中配置web.xml,配置 HiddenHttpMethodFilter过滤器 ,目标是通过该过滤器+前端特别标签,就可以把POST请求转为DELETE、PUT请求
<!--filter 标签用于配置一个Filter 过滤器-->
<filter><!--给filter 起一个别名--><filter-name>HiddenHttpMethodFilter</filter-name><!--配置filter 的全类名--><filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter><filter-mapping><filter-name>HiddenHttpMethodFilter</filter-name><url-pattern>/*</url-pattern>
</filter-mapping>
(2)进入SpringMVC.xml配置
step1:配置要扫描的包,将所有要扫描的包都放在com.atguigu.springmvc.crud目录下
step2:配置视图解析器
<!--配置自动扫描的包-->
<context:component-scan base-package="com.atguigu.springmvc"></context:component-scan><!--配置视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"><property name="prefix" value="/WEB-INF/views/"></property><property name="suffix" value=".jsp"></property>
</bean>
step3:在WebContent目录下创建views文件夹用于保存最终视图,并在views文件夹下先创建list.jsp(用于显示所有员工信息)
step4:新建一个index.jsp 主页面
step5:创建好实体类Department.java 和 Employee.java(包com.atguigu.springmvc.crud.entities)和DAO DepartmentDao.java 和 EmployeeDao.java(包com.atguigu.springmvc.crud.dao)
创建好后的架构如下:
这章重点是介绍SpringMvc,重点是handlers,其他代码就不演示了
进入com.atguigu.springmvc.crud.handlers包 创建EmployeeHandler类
package com.atguigu.springmvc.crud.handlers;@Controller
public class EmployeeHandler{@Autowiredprivate EmployeeDao employeeDao;//这个类的静态代码块已经注入数据了@RequestMapping("/emps")public String list(Map<String,Object> map){ //添加到模型中,不懂看上一节map.put("employees",employeeDao.getAll());//获取静态“数据库”,并添加到模型(请求域)return "list"; //根据配置显示views目录的list.jsp}
}
前端 index.jsp
<body><a href="emps">List All Employees</a>
</body>
让list.jsp显示所有数据,springmvc没有准备遍历标签,所有需要用jsp原生的JSTL标签(后面的SpringBoot准备了的),这个参考就好
头文件引入JSTL遍历标签 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
用JSTL标签遍历 从request域中获取到的employees对象
<body><c:if test="${empty requestScope.employees }">没有任何员工信息.</c:if><c:if test="${!empty requestScope.employees }"><table border="1" cellpadding="10" cellspacing="0"><tr><th>ID</th><th>LastName</th><th>Email</th><th>Gender</th><th>Department</th><th>Edit</th><th>Delete</th></tr><c:forEach items="${requestScope.employees }" var="emp"><tr><td>${emp.id }</td><td>${emp.lastName }</td><td>${emp.email }</td><td>${emp.gender == 0 ? 'Female' : 'Male' }</td><td>${emp.department.departmentName }</td><td><a href="emp/${emp.id}">Edit</a></td><td><a class="delete" href="emp/${emp.id}">Delete</a></td></tr></c:forEach></table></c:if><br><br>
</body>
6.3.2 添加员工信息操作
两项工作,1、显示添加页面 ,2、点击提交按钮 完成添加操作
注意:虽然两个的URI都是 emp,但是请求方式不同,一个是GET,另一个是POST
由于里面的部门Department 数据来源于DAO,所以不是纯粹前端页面直接写的,就必须要有handler方法。添加成功后,重定向的list页面
step1:回到list.jsp ,接前一节的显示代码
tip:超链接标签 默认请求方式是 GET 请求
<body><!--接着上一段的显示代码--><a href="emp">Add New Employee</a>
</body>
step2 回到上文的handler(核心)
package com.atguigu.springmvc.crud.handlers;@Controller
public class EmployeeHandler{@Autowiredprivate EmployeeDao employeeDao;//这个类的静态代码块已经注入数据了@Autowiredprivate DepartmentDao departmentDao;//完成添加操作,重定向到emps去完成添加@RequestMapping(value="/emp",method=RequestMethod.POST)public String save(Employee employee){employeeDao.save(employee);//添加id并保存,sava()是自己编写的添加id方法return "redirect:/emps"; //重定向到这个地址去请求添加}@RequestMapping(value="emp",method=RequestMethod.GET)public String input(Map<String,Object> map){map.put("departments",departmentDao.getDepartments());//获取到的数据,添加到模型中map.put("employee",new Employee());//表单需要绑定一个域中的一个bean,但我们不需要回显,就new一个就是了return "input";}@RequestMapping("/emps")public String list(Map<String,Object> map){ //添加到模型中,不懂看上一节map.put("employees",employeeDao.getAll());//获取静态“数据库”,并添加到模型(请求域)return "list"; //去list.jsp页面查看最终效果}
}
step3 去input.jsp
插入知识点1:SpringMVC表单标签
通过 SpringMVC 的表单标签可以实现将模型数据中的属性和 HTML 表单元素相绑定,以实现表单数据更便捷编辑和表单值的回显
SpringMVC 提供了多个表单组件标签,如<form:input/>、<form:select/> 等,用以绑定表单字段的属性值,它们的共有属性如下:
path:表单字段,对应 html 元素的 name 属性,支持级联属性 等等等等(非常多,这个查字典吧,这里就不写完了)
其他本次会用的标签:form:radiobuttons:单选框组标签,用于构造多个单选框。
常用属性:
items:可以是一个 List、String[] 或 Map
itemValue:指定 radio 的 value 值。可以是集合中 bean 的一个属性值
itemLabel:指定 radio 的 label 值,就是客户能够看到的属性
delimiter:多个单选框可以通过 delimiter 指定分隔符
form:checkboxs:用于构造多个复选框。使用方式和上面的意义
引入标签代码,jsp文件头部:<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<body>
<!--
1. WHY 使用 form 标签呢 ?
可以更快速的开发出表单页面, 而且可以更方便的进行表单值的回显
2. 注意:springmvc认为表单一定要回显,所以需要绑定一个bean来执行
可以通过 modelAttribute 属性指定绑定的模型属性,bean的属性必须和表单页面对应
若没有指定该属性,则默认从 request 域对象中读取 command 的表单 bean
如果该属性值也不存在,则会发生错误。
-->
<br><br>
<form:form action="${pageContext.request.contextPath }/emp" method="POST" modelAttribute="employee">
<!--path属性对应html的name属性值-->
LastName: <form:input path="lastName"/>
<br>
Email: <form:input path="email"/>
<br><%
Map<String, String> genders = new HashMap();
genders.put("1", "Male");
genders.put("0", "Female");
<!--加入域对象 对比@RequestMapping标记的函数,用了Map接口,自动进域对象-->
request.setAttribute("genders", genders);
%>
Gender:
<br>
<form:radiobuttons path="gender" items="${genders }" delimiter="<br>"/>
<br>Department: <form:select path="department.id"
<!--departments是一个Map,值就是Bean组件department对象,该对象有属性departmentName和 id >
items="${departments }" itemLabel="departmentName" itemValue="id"></form:select>
<br>
<input type="submit" value="Submit"/>
</form:form>
</body>
6.3.3 删除操作
(1)要求
URL:emp/{id} 这个id是占位符
请求方式:DELETE
删除后效果:对应记录从数据表中删除,重定向到list页面查看效果
(2)操作步骤
step1: 在list.jsp中(最终展示界面),添加删除按钮(超链接)
............前后代码暂时忽略
<td><a class="delete" href="emp/${emp.id}">Delete</a></td>
step2:回到Handler中,编写链接过来的delete。上一节讲了关于@PathVariable 和占位符知识,忘了回去看
@RequestMapping(value="/emp/{id}",method=RequestMethod.DELETE)
public Strign delete(@PathVariable("id") Integer id){
employeeDao.delete(id);
return "redirect:/emps";
}
由于这里用了DELETE请求,但是前段只有POST和DELETE需要转换,虽然已经配置好了过滤器,但是还需要用JS处理下,才能正常使用。但是由于第一步就配置了springDispatcherServlet,而且路径是/,即拦截所有请求去springmvc.xml的配置文件中一趟,自然js也不能幸免,同时,我们还没有在springmvc.xml中映射js,就会导致在即使在前端页面用了js标签,也没法使用js
插入知识点2:SpringMVC处理静态资源(比如标签连接到Js,Css等静态资源)
优雅的 REST 风格的资源URL 不希望带 .html 或 .do 等后缀。但是我们前面第一步一般都会把 DispatcherServlet 请求映射路径配置为 /,即让 Spring MVC 将捕获WEB 容器的所有请求,包括静态资源的请求,因此遇到静态请求, SpringMVC 也会将他们当成一个普通请求处理,这样就会导致找不到对应处理器(并没有为静态请求专门编写handler)将导致错误。
静态资源处理办法(解决办法):springmvc.xml中使用 <mvc:default-servlet-handler/>标签
<mvc:default-servlet-handler/>
<mvc:annotation-driven></mvc:annotation-driven>
注意:需要搭配第二个标签,才能正常使用,原因,后面补充
解释:
(1)<mvc:default-servlet-handler/> 将在 SpringMVC 上下文中定义一个DefaultServletHttpRequestHandler
说明:该标签用了后,就会用一个springmvc自己写的handler,它会对进入 DispatcherServlet 的请求进行筛查,如果发现是没有经过映射的请求,就将该请求交由 WEB 应用服务器默认的 Servlet 处理(serlvet可以处理静态请求),如果不是静态资源的请求,才由DispatcherServlet 继续处理。
(2)一般 WEB 应用服务器默认的 Servlet 的名称都是 default。若所使用的WEB 服务器的默认 Servlet 名称不是 default,则需要通过 default-servlet-name 属性显式指定。
继续step3 回到list.jsp页面,补充js标签(这里使用了jQuery),用js完成DELETE请求转换POST,提交后再转换回DELETE给服务端
<head>
<script type="text/javascript" src="scripts/jquery-1.9.1.min.js"></script>
<script type="text/javascript">
$(function(){
$(".delete").click(function(){
var herf=$(this).attr("href");
$("form").attr("action",href).submit(); //$(selector).attr(attribute,value),设置属性和值:将form的action属性值设置为href的值然后提交
return false;
})
})
</script>
</head>
............前后代码暂时忽略
<body>
<form action=''" method="POST"> //使用form的POST提交,然而标签内部已经转换成了DELETE
<input type="hidden" name="_method" value="DELETE">
</form>
.....
<td><a class="delete" href="emp/${emp.id}">Delete</a></td>
....
</body>
说明:超链接并没在表单里面,而在格式要求的表格里面。所以,那么麻烦。如果在表单里面,就简单了,不用js去转了。但如果都在一个表单里面,请求又各不相同(PUT,DELETE,Updata都有),那还是得转换
package com.atguigu.springmvc.crud.handlers;@Controller
public class EmployeeHandler{@Autowiredprivate EmployeeDao employeeDao;//这个类的静态代码块已经注入数据了@Autowiredprivate DepartmentDao departmentDao;@RequestMapping(value="/emp/{id}",method=RequestMethod.DELETE)public Strign delete(@PathVariable("id") Integer id){employeeDao.delete(id);return "redirect:/emps";}//完成添加操作,重定向到emps去完成添加@RequestMapping(value="/emp",method=RequestMethod.POST)public String save(Employee employee){employeeDao.save(employee);//添加id并保存,sava()是自己编写的添加id方法return "redirect:/emps"; //重定向到这个地址去请求添加}@RequestMapping(value="emp",method=RequestMethod.GET)public String input(Map<String,Object> map){map.put("departments",departmentDao.getDepartments());//获取到的数据,添加到模型中map.put("employee",new Employee());//表单需要绑定一个域中的一个bean,但我们不需要回显,就new一个就是了return "input";}@RequestMapping("/emps")public String list(Map<String,Object> map){ //添加到模型中,不懂看上一节map.put("employees",employeeDao.getAll());//获取静态“数据库”,并添加到模型(请求域)return "list"; //去list.jsp页面查看最终效果}
}
6.3.4 修改操作(lastName不可修改)
要求一:显示修改页面
显示修改页面:
URI:emp/{id} 占位符,不是问号
请求方式:GET
显示效果:在相应行点了Edit,就回显以下表单给客户进行修改
要求二:修改员工信息
URI:emp
请求方式:PUT
显示效果:完成修改,重定向到 list 页面
step 1:回到list.jsp页面。
............前后代码暂时忽略
<td><a href="emp/${emp.id}">Edit</a></td>
进入Employeehandler.java, 重新写一个input方法,因为要传{id}入参
@RequestMapping(value="/emp/{id}",method=RequestMethod.GET)public String input(@PathVariable("id") Integer id,Map<String,Object> map){map.put("employee",employeeDao.get(id));//表单里面就是绑定这个beanemployee,获取所有的employee并放入域中,准备回显employeeDao.get(id)map.put("departments",departmentDao.getDepartments());//表单需要绑定一个域中的一个bean,但我们不需要回显,就new一个就是了return "input";}
完成后 自动回到 input.jsp页面,和添加时的界面相似,需要略修改一下。
修改要求如下:lastName标签不能显示(要求不修改lastName)
进入input.jsp
导入JSTL标签库,因为要使用if标签
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<form:form action="${pageContext.request.contextPath }/emp" method="POST" //路径改成了绝对路径,上面还用的相对路径,相对路径会出问题
modelAttribute="employee">
<c:if test="${employee.id == null }"> //如果不是修改操作,而是显示结果,就可以显示lastName
LastName: <form:input path="lastName"/> //这会是添加,添加需要写lastName
</c:if>
<c:if test="${employee.id != null }">//这会判定是修改,就不能显示lastName
<form:hidden path="id"/>
<input type="hidden" name="_method" value="PUT"/>
<%-- 对于 _method 不能使用 form:hidden 标签, 因为 modelAttribute 对应的 bean (即employee)中没有 _method 这个属性 --%>
</c:if>
回到Handler页面
@RequestMapping(value="/emp", method=RequestMethod.PUT)public String update(Employee employee){employeeDao.save(employee);return "redirect:/emps";}
上节课@ModelAttribute注解专门讲了,如何修改这种。先读取全部数据,再修改要修改的数据,@ModelAttribute注解可以让程序优先执行它标记的方法。
@ModelAttributepublic void getEmployee(@RequestParam(value="id",required=false) Integer id,Map<String, Object> map){if(id != null){map.put("employee", employeeDao.get(id));}}
7、数据转换&数据格式化&数据校验
问题引入:如果想要给输入的表单加一个字段,生日。该如何处理
常规做法
在input.jsp中加入
Birth: <form:input path="birth"/>
接着进入目标实体类 Employee.java 新加入
package com.atguigu.springmvc.crud.handlers;public class Employee {private Date birth;//以及birth的get,set方法,更新构造器,toString方法
}
遇到的问题:
① 数据类型转换的问题(Date转换成字符串传输的问题),数据类型格式化的问题(要求只能输入生日)
② 数据校验
7.1 数据绑定流程(框架已经自动绑定好了,了解下过程即可)
① Spring MVC 主框架将 ServletRequest 对象及目标方法的入参实例传递给 WebDataBinderFactory 实例,以创建 DataBinder 实例对象
② DataBinder 调用装配在 Spring MVC 上下文中的ConversionService 组件进行数据类型转换、数据格式化工作。将 Servlet 中的请求信息填充到入参对象中
③ 调用 Validator 组件对已经绑定了请求消息的入参对象进行数据合法性校验,并最终生成数据绑定结果BindingData 对象
④ Spring MVC 抽取 BindingResult 中的入参对象和校验
错误对象,将它们赋给处理方法的响应入参
7.2 数据转换
绑定好以后,可以开始数据转换。
Spring MVC 上下文中内建了很多转换器,可完成大多数 Java 类型的转换工作。 但是还是有个别需要自己定义的,这个用的非常少,了解即可
7.2.1自定义类型转换器(很少用,了解即可)
(1)编写好input.jsp,很容易看到这里通过input文本框里传递到服务端的是string类型
<body><form action="testConversionServiceConverer" method="POST"><!-- 期望的格式lastname-email-gender-department.id 例如: GG-gg@atguigu.com-0-105 -->Employee: <input type="text" name="employee"/><input type="submit" value="Submit"/></form><br><br>
</body>
(2)编写服务端,测试转换器
step1 在com.atguigu.springmvc.test包下创建SpringMVCTest.java类
此时,接收的前端传来的@RequestParam("employee"是一个字符串,但是入参用的是Employee对象,显然不匹配,此时需要编写转换器
package com.atguigu.springmvc.test;@Controller
public class SpringMVCTest { @Autowiredprivate EmployeeDao employeeDao;@RequestMapping("/testConversionServiceConverer")public String testConverter(@RequestParam("employee") Employee employee){System.out.println("save: " + employee);employeeDao.save(employee);return "redirect:/emps";}}
插入知识点3 Spring支持的转换器
Spring 定义了 3 种类型的转换器接口,实现任意一个转换器接口都可以作为自定义转换器注册到ConversionServiceFactroyBean 中:
最常用的是:Converter<S,T>:将 S 类型对象转为 T 类型对象
step2 再建一个com.atguigu.springmvc.converters包,下面创建EmployeeConverter类,实现Converter<S,T>接口(将String转换成Employee)。 该类就是转换器,自定义转换器,就要自己写代码了
前面写input.jsp的时候已经说过了,期望的格式lastname-email-gender-department.id,因此前端传过来的是类似: GG-gg@atguigu.com-0-105这种
package com.atguigu.springmvc.converters;@Compoment //将这个类加入spring容器
public class EmployeeConverter implements Converter<String,Employee>{@Overridepublic Employee convert(String source) {if(source != null){String [] vals = source.split("-");//前端传来字符串:GG-gg@atguigu.com-0-105if(vals != null && vals.length == 4){String lastName = vals[0];String email = vals[1];Integer gender = Integer.parseInt(vals[2]);Department department = new Department();department.setId(Integer.parseInt(vals[3]));Employee employee = new Employee(null, lastName, email, gender, department);System.out.println(source + "--convert--" + employee);return employee;}}return null;}}
上面写的只是通用方法,并且加入了容器。但这是框架,我们并不会直接传实参给这个转换器转换,所以还需要进一步配置,让spring框架完全和这个转换器拟合。因此接着需要做的事,用配置文件将该类的对象employeeconverter注入ConversionService类对象conversionService属性中。并且还要通过<mvc:annotation-driven conversion-service=“conversionService”/> 标签,将自定义的 ConversionService 注册到Spring MVC 的上下文中,这样才会在关键时刻,框架调用我们的自定义的转换器。
特别说明:<mvc:annotation-driven conversion-service=“conversionService”/> 会将自定义的 ConversionService 注册到Spring MVC 的上下文中
<mvc:annotation-driven conversion-service=“conversionService”/><!--配置ConversionService-->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"><!--这个converters属性是一个set集合,在集合里添加我们自己写的employeeConverter对象--><property name="converters"><set><ref bean="employeeConverter"/></set></property>
<>
完成以后。前端页面输入字符串,就能被自动转换成对象了
插入知识点4:mvc:annotation-driven
先说结论:开发的时候一定要加这个标签。
源码没法看,很难。官方文档翻译一部分就是:
(1)<mvc:annotation-driven />标签会自动注册RequestMappingHandlerMapping、RequestMappingHandlerAdapter 与ExceptionHandlerExceptionResolver 三个bean。(写了标签后自动操作注册)
(2)还将提供以下支持:(以下需要程序员手动完成部分内容)
① 支持使用 ConversionService 实例对表单参数进行类型转换
② 支持使用 @NumberFormatannotation、@DateTimeFormat 注解完成数据类型的格式化
③ 支持使用 @Valid 注解对 JavaBean 实例进行 JSR 303 验证
④ 支持使用 @RequestBody 和 @ResponseBody 注解
插入知识点5:@InitBinder注解
由 @InitBinder 标识的方法,可以对 WebDataBinder 对象进行初始化。WebDataBinder 是 DataBinder 的子类,用于完成由表单字段到 JavaBean 属性的绑定(数据绑定和数据转换前面都讲了)
@InitBinder方法不能有返回值,它必须声明为void。
@InitBinder方法的参数通常是是 WebDataBinder
实验:回到EmployeeHandler.java类
package com.atguigu.springmvc.crud.handlers;@Controller
public class EmployeeHandler {//这样就可以不对lastName这个属性进行赋值@InitBinderpublic void initBinder(WebDataBinder binder){binder.setDisallowedFields("lastName");}}
7.3数据类型格式化
(1)对属性对象的输入/输出进行格式化,从其本质上讲依然属于 “类型转换” 的范畴。
(2)Spring 在格式化模块中定义了一个实现 ConversionService 接口的FormattingConversionService 实现类,该实现类扩展了GenericConversionService,因此它既具有类型转换的功能,又具有格式化的功能,FormattingConversionService 拥有一个FormattingConversionServiceFactroyBean 工厂类,该类内部已经注册了 :
① NumberFormatAnnotationFormatterFactroy:支持对数字类型的属性—— 使用 @NumberFormat 注解
② JodaDateTimeFormatAnnotationFormatterFactroy:支持对日期类型的属性——使用 @DateTimeFormat 注解
演示:
回到7开始的问题引入,我们已经重写了jsp,添加了birth。在handler.java也添加了birth属性,可以正常显示,但是提交就玩完。因为这里提交是字符串,还不知道是什么格式显示日期。
解决办法
首先,还是必须要在springmvc.xml配置文件中,写上上一节讲的<mvc:annotation-driven />标签(以后开发,二话不说,先写上这个,养成习惯)
<mvc:annotation-driven />
接着回到Employee类,在目标属性假设@DateTimeFormat(pattern="yyyy-MM-dd")注解,即可
package com.atguigu.springmvc.crud.entities;public class Employee {@DateTimeFormat(pattern="yyyy-MM-dd")private Date birth;//以及birth的get,set方法,更新构造器,toString方法
}
继续测试格式转换,再添加薪资salary(float类型),看下前端发来的String能否顺利转float类型的salary属性
<body><form:form action="${pageContext.request.contextPath }/emp" method="POST" modelAttribute="employee">Birth: <form:input path="birth"/><br>Salary: <form:input path="salary"/><br><input type="submit" value="Submit"/></form:form></body>
同样的道理,需要添加注解。 记得添加新属性后更新get,set,空参,有参,tostring方法
package com.atguigu.springmvc.crud.entities;public class Employee {@DateTimeFormat(pattern="yyyy-MM-dd")private Date birth;@NumberFormat(pattern="#,###,###.#")private Float salary;//以及salary的get,set方法,更新构造器,toString方法
}
7.3 数据校验(简单的说,就是excel里面的数据有效性)
JSR 303 是 Java 为 Bean 数据合法性校验提供的标准框架,JSR 303 通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,并通过标准的验证接口对 Bean 进行验证
Hibernate Validator 是 JSR 303 的一个参考实现,除支持所有标准的校验注解外,它还支持以下的扩展注解
7.3.1 校验方法
①使用JSR303 验证标准,就是导入相关jar包
②加入hibernate validator验证框架,就是导入相关jar包
③在SpringMVC配置文件中添加<mvc:annotation-driven />(做开发就先加上这个注解)
④在目标方法(handler类的标记了@RequestMapping的方法)bean类型的前面添加@Valid注解。在表单/命令• 对象类的属性中标注校验注解,在处理方法对应的入参前添加 @Valid,Spring MVC 就会实施校验并将校验结果保存在被校验入参对象之后的 BindingResult 或Errors 入参中。
操作演示:
step1:回到employee.java实体类
package com.atguigu.springmvc.crud.entities;public class Employee {private Integer id;@NotEmptyprivate String lastName;@Emailprivate String email;//1 male, 0 femaleprivate Integer gender;private Department department;@Past@DateTimeFormat(pattern="yyyy-MM-dd")private Date birth;@NumberFormat(pattern="#,###,###.#")private Float salary;public Employee() {// TODO Auto-generated constructor stub}
}
step2:再去handler类,在入参employee前面加@valid注解,这样这个对象里面的属性注解都激活了
校验结果(不管是对或者不对)都会保存在DateBinder类的BindingResult属性里
特别注意:需校验的 Bean 对象和其绑定的保存该Bean验证错误内容的对象是成对出现的,它们之间不允许声明其他的入参。(bean和验证后接收结果的对象必须放在一起,中间不能有其他入参)
比如:以下两对,都是挨着的
7.3.2 验证出错转向到哪一个页面
前面说了校验结果(不管是对或者不对)都会保存在DateBinder类的BindingResult属性里,因此保存校验结果的入参必须是BindingResult 或
Errors 类型(BindingResult实现类Errors接口)
package com.atguigu.springmvc.crud.handlers;@Controller
public class EmployeeHandler {@RequestMapping(value="/emp", method=RequestMethod.POST)public String save(@Valid Employee employee, Errors result,Map<String, Object> map){System.out.println("save: " + employee);if(result.getErrorCount() > 0){System.out.println("出错了!");for(FieldError error:result.getFieldErrors()){System.out.println(error.getField() + ":" + error.getDefaultMessage());}//若验证出错, 则转向定制的页面map.put("departments", departmentDao.getDepartments());return "input";}employeeDao.save(employee);return "redirect:/emps";}}
7.3.3 错误信息?如何显示,如何把错误消息进行国际化
Spring MVC 除了会将表单/命令对象的校验结果保存到对应的 BindingResult 或 Errors 对象中外,还会将所有校验结果保存到 “隐含模型”即使处理方法的签名中没有对应于表单/命令对象的结果入参,校验结果也会保存在 “隐含对象” 中。隐含模型中的所有数据最终将通过 HttpServletRequest 的属性列表暴露给 JSP 视图对象,因此在 JSP 中可以获取错误信息,在 JSP 页面上可通过 <form:errors path=“userName”> 显示错误消息
代码演示,回到input.jsp页面,用标签<form:errors path=“userName”>,显示错误捕捉到的错误信息<form:errors path=“*”>说明显示所有错误
<body><form:form action="${pageContext.request.contextPath }/emp" method="POST" modelAttribute="employee"><form:errors path="*"></form:errors><br><c:if test="${employee.id == null }"><!-- path 属性对应 html 表单标签的 name 属性值 -->LastName: <form:input path="lastName"/><form:errors path="lastName"></form:errors></c:if><c:if test="${employee.id != null }"><form:hidden path="id"/><input type="hidden" name="_method" value="PUT"/></c:if><br>Email: <form:input path="email"/><form:errors path="email"></form:errors><br></form:form></body>
国际化:
(1)校验时发生错误,回显错误信息的国际化
每个属性在数据绑定和数据校验发生错误时,都会生成一个对应的 FieldError 对象。当一个属性校验失败后,校验框架会为该属性生成 4 个消息代码,这些代码以校验注解类名为前缀,结合modleAttribute、属性名及属性类型名生成多个对应的消息代码:例如 User 类中的 password 属性标记了一个 @Pattern 注解,当该属性值不满足 @Pattern 所定义的规则时, 就会产生以下 4个错误代码:
① Pattern.user.password
② Pattern.password
③ Pattern.java.lang.String
④ Pattern
当使用 Spring MVC 标签显示错误消息时, Spring MVC 会查看WEB 上下文是否装配了对应的国际化消息,如果没有,则显示默认的错误消息,否则使用国际化消息。
(2)数据类型转换或数据格式转换时发生错误,或该有的参数不存在,或调用处理方法时发生错误。回显错误信息的国际化
若数据类型转换或数据格式转换时发生错误,或该有的参数不存在,或调用处理方法时发生错误,都会在隐含模型中创建错误消息。其错误代码前缀说明如下:
required:必要的参数不存在。如 @RequiredParam(“param1”) 标注了一个入参,但是该参数不存在
typeMismatch:在数据绑定时,发生数据类型不匹配的问题
methodInvocation:Spring MVC 在调用处理方法时发生了错误
step1:直接在项目src目录下新建一个国际化资源配置文件,命名i18n.properties
编写配置文件:
//键为 注解名.类型.属性名 值会被自动填充为看不懂的符号
NotEmpty.employee.lastName=^^LastName\u4E0D\u80FD\u4E3A\u7A7A.
Email.employee.email=Email\u5730\u5740\u4E0D\u5408\u6CD5
Past.employee.birth=Birth\u4E0D\u80FD\u662F\u4E00\u4E2A\u5C06\u6765\u7684\u65F6\u95F4.//当数据类型转换或数据格式转换时发生错误,或该有的参数不存在,或调用处理方法时发生错误
typeMismatch.employee.birth=Birth\u4E0D\u662F\u4E00\u4E2A\u65E5\u671F.
step2:进入springmvc.xml配置文件,注册国际化资源文件
<!-- 配置国际化资源文件 -->
<bean id="messageSource"class="org.springframework.context.support.ResourceBundleMessageSource"><property name="basename" value="i18n"></property>
</bean>
最终显示效果如下:
这篇关于SpringMvc 视图和视图解析器 CURD案例 转换器 mvc:annotation-driven mvc:default-servlet-handler @InitBinder的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!