SpringMVC - 数据绑定

1. 概述

在执行程序时,Spring MVC会根据客户端请求参数的不同,将请求消息中的信息以一定的方式转换并绑定到控制器类的方法参数中。这种将请求消息数据与后台方法参数建立连接的过程就是Spring MVC中的数据绑定

在早期springMVC使用PropertyEditor,后期使用convertor进行任意类型的转换,springMVC提供了很多convertor(转换器),在特使情况下(日期数据的绑定)需要自定义convertor

数据绑定的流程
SpringMVC 有支持的默认参数类型,我们直接在形参上给出这些默认类型的声明,就能直接使用了

//HttpServletRequest 对象

//HttpServletResponse 对象

//HttpSession 对象

//Model/ModelMap 对象 

@RequestMapping("/defaultParameter")
public ModelAndView defaultParameter(HttpServletRequest request,HttpServletResponse response,HttpSession session,Model model,ModelMap modelMap) throws Exception{
    request.setAttribute("requestParameter", "request类型");
    response.getWriter().write("response");
    session.setAttribute("sessionParameter", "session类型");
    //ModelMap是Model接口的一个实现类,作用是将Model数据填充到request域
    //即使使用Model接口,其内部绑定还是由ModelMap来实现
    model.addAttribute("modelParameter", "model类型");
    modelMap.addAttribute("modelMapParameter", "modelMap类型");
     
    ModelAndView mv = new ModelAndView();
    mv.setViewName("view/success.jsp");
    return mv;
}

而对于其他类型的参数,SpringMVC通过反射机制对目标处理方法进行解析,将请求消息绑定到处理方法的入参中,数据绑定的核心部件是DataBinder
在这里插入图片描述

① SpringMVC主框架将ServletRequest对象以及目标法方法的入参传递给WebDataBinderFactory实例,以创建DataBinder(数据绑定器)实例对象

DataBinder调用装配在SpringMVC上下文的ConversionService组件进行数据类型转换,数据格式化工作,将Servlet中的请求信息填充到入参对象中

③ 调用Validator组件对已经绑定了请求参数的入参对象进行数据合法性校验,并最终生成绑定结果BindingData对象,如果在校验过程中出现错误,结果会放到BindingResult对象中

④ 校验完成后会生成数据绑定结果BindingResult对象,Spring MVC会将BindingResult对象中的内容赋给处理方法的相应参数

2. 数据绑定

① 基本类型参数

包括基本类型和 String 类型
使用要求:参数名称必须和控制器中方法的形参名称保持一致。 (严格区分大小写)

index.jsp

<form action="basicData" method="post">
    <input name="username" value="10" type="text"/>
    <input type="submit" value="提交">
</form>

Controller 代码:

@RequestMapping("/basicData")
public void basicData(int username){
    System.out.println(username);//10
}

我们这里的参数是基本数据类型,如果从前台页面传递的值为 null 或者 “”的话,那么会出现数据转换的异常,就是必须保证表单传递过来的数据不能为null或”",所以,在开发过程中,对可能为空的数据,最好将参数数据类型定义成包装类型

index.jsp

<form action="${pageContext.request.contextPath}/user/testParam" method="post">
	用户名: <input type="text" name="username"> <br>
	年龄: <input type="text" name="age"> <br>
	性别: <input type="text" name="sex"> <br>
    <input type="submit" value="提交">
</form>

UserController.java

@Controller
@RequestMapping("/user")
public class UserController2 {

    @RequestMapping(value = "/testParam",method = RequestMethod.POST)
    public String testParam(String username,Integer age,Character sex){
        System.out.println(username + "--" + age + "--" + sex);
        return "show";
    }
}

表单传递过来的数据可以为null或”",以上面代码为例,如果表单中num为”"或者表单中无num这个input,那么,Controller方法参数中的num值则为null

② POJO类型参数
  • 要求集合类型的请求参数必须在 POJO 中。在表单中请求参数名称要和 POJO 中集合属性名称相同。

index.jsp

<form action="${pageContext.request.contextPath}/user/saveUser" method="post">
	用户名: <input type="text" name="username"> <br>
    年龄: <input type="text" name="age"> <br>
    性别: <input type="text" name="sex"> <br>
    一个角色对象:<!--role为 POJO类型参数-->
    id:<input type="text" name="role.id">
    roleName:<input type="text" name="role.roleName"><br>
    <input type="submit" value="提交">
</form>

Role.java

public class Role {
    private Integer id;
    private String roleName;
    //此处省略 getter and setter toString
}

User.java

public class User {
    private Integer id;
    private String username;
    private Character sex;
    private Integer age;

    private  Role role; //POJO 类型参数
    //此处省略 getter and setter toString
 }

UserController.java

@RequestMapping(value = "/saveUser",method = RequestMethod.POST)
public String saveUser(User user){
	System.out.println(user);
    return "show";
}
③ 集杂数据类型
数组

表单中name属性相同,value不同;形参是数组,且名称相同

集合
  • 要求集合类型的请求参数必须在 POJO 中。在表单中请求参数名称要和 POJO 中集合属性名称相同

  • 给 List 集合中的元素赋值,使用下标

  • 给 Map 集合中的元素赋值,使用键值对

index.jsp

   <form action="${pageContext.request.contextPath}/user/saveUser" method="post">
       用户名: <input type="text" name="username"> <br>
       年龄: <input type="text" name="ag"> <br>
        性别: <input type="text" name="sex"> <br>
        list集合参数: <input type="text" name="list[0]">
                       <input type="text" name="list[1]"><br>
        一个角色对象:<input type="text" name="role.id">
                    <input type="text" name="role.roleName"><br>
        多个角色对象:<input type="text" name="roleList[0].id">
                    <input type="text" name="roleList[0].roleName"><br>
                    <input type="text" name="roleList[1].id">
                    <input type="text" name="roleList[1].roleName"><br>
        map集合:<input type="text" name="map[one]"><br>
                <input type="text" name="map[two]"><br>
                <input type="text" name="map[three]"><br>

        <input type="submit" value="提交">
    </form>

Role.java

public class Role {

    private Integer id;
    private String roleName;
    //此处省略 getter and setter toString
}

User.java

public class User {
    private Integer id;
    private String username;
    private Character sex;
    private Integer age;
    
    private List<String> list;
    private  Role role;
    private List<Role> roleList;
    private Map<String ,Object> map;
    //此处省略 getter and setter toString
 }

UserController.java

    @RequestMapping(value = "/saveUser",method = RequestMethod.POST)
    public String saveUser(User user){
        System.out.println(user);
        return "show";
    }

后台结果

User {
	id = null,
	username = 'zhangsan',
	sex =,
	age = 30,
	list = [hello, world],
	role = Role {
		id = 21,
		roleName = 'cook'
	},
	roleList = [Role {
			id = 23,
			roleName = 'teacher'
		}, Role {
			id = 25,
			roleName = 'student'
		}
	],
	map = {
		one = little,
		three = huge,
		two = big
	}
}

3. 自定义类型转换器

  • 表单提交的任何数据类型全部都是字符串类型,但是后台定义Integer类型,数据也可以封装上,说明Spring框架内部会默认进行数据类型转换。

  • 如果想自定义数据类型转换,可以实现Converter的接口

  • 注册自定义类型转换器,在springmvc.xml配置文件中编写配置

  • 还是可以使用Formatter进行类型转换

当我们在后端对日期类型的参数进行准换,默认只转换2000/1/1类型的日期,如果输入的是2000-1-1转换就会出错

ConversionService是Spring类型转换体系的核心接口

可以利用ConversionServiceFactoryBean在Spring的IOC容器中定义一个ConversionService,Spring将自动识别出IOC容器中的ConversionService,并在Bean属性配置及Spring MVC处理方法入参绑定等场合使用它进行数据的转换

可以通过ConversionServiceFactoryBeanconverters属性注册自定义的类型转换器

<form action="${pageContext.request.contextPath}/user/testDate">
    <input type="date" name="birthday">
    <input type="submit" value="提交">
</form>

@RequestMapping("/testDate")
public String testDate(Data birthday){
    System.out.println(birthday);
    return "show";
}

自定义String–>Date类型转换器

public class StringToDateConverter implements Converter<String ,Date>{
    //把 String 类型转成日期类型
    
    @Override
    public Date convert(String source) {
        //日期类型的转换对象
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        Date date = null;
        try {
            date = sdf.parse(source);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return date;
    }
}

在 spring 配置文件中配置类型转换器

<!-- 配置类型转换器工厂 -->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<!-- 给工厂注入一个新的类型转换器 -->
    <property name="converters">
        <set>
       		 <!-- 配置自定义类型转换器 -->
            <bean class="com.testfan.converter.StringToDateConverter"></bean>
        </set>
    </property>
</bean>

在 annotation-driven 标签中引用配置的类型转换服务

<!-- 引用自定义类型转换器 -->
<!--注解驱动: 关联类型转换工厂-->
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>

4. 数据格式化

数据格式化
Converter可以将一种类型转换成另一种类型,是任意Object之间的类型转换

Formatter则只能进行String与任意Object对象的转换,它提供 解析格式化 两种功能
其中:解析是将String类型字符串转换为任意Object对象,格式化是将任意Object对象转换为字符串进行格式化显示

① 实现Formatter<T> 接口

DateFormatter.java

//实现Formatter<T> 接口
public class DateFormatter implements Formatter<Date>{
    // 日期类型模板:如yyyy-MM-dd
    private String datePattern;
    // 日期格式化对象
    private SimpleDateFormat dateFormat;
 
    // 构造器,通过依赖注入的日期类型创建日期格式化对象
    public DateFormatter(String datePattern) {
        this.datePattern = datePattern;
        this.dateFormat = new SimpleDateFormat(datePattern);
    }
    // 显示Formatter<T>的T类型对象
    @Override
    public String print(Date date, Locale locale) {
        return dateFormat.format(date);
    }
    // 解析文本字符串返回一个Formatter<T>的T类型对象。
    @Override
    public Date parse(String source, Locale locale) throws ParseException {
        try {
            return dateFormat.parse(source);
        } catch (Exception e) {
            throw new IllegalArgumentException();
        }
    }
 
}

装配自定义格式化转换器

<!-- 装配自定义格式化转换器-->
<mvc:annotation-driven conversion-service="conversionService"/>
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="formatters">
            <list>
                <bean class="com.formatter.DataFormatter" c:_0="yyyy-MM-dd"></bean>
            </list>
    </property>
</bean>

② 使用注解

// 域对象,实现序列化接口
public class User implements Serializable{
 
    // 日期类型
    @DateTimeFormat(pattern="yyyy-MM-dd")
    private Date birthday;
    // 正常数字类型
    @NumberFormat(style=Style.NUMBER, pattern="#,###")
    private int total;
    // 百分数类型
    @NumberFormat(style=Style.PERCENT)
    private double discount;
    // 货币类型
    @NumberFormat(style=Style.CURRENCY)
    private double money;
    ...
}

5. 数据校验

数据校验
JSR 303 是Java为Bean数据合法性校验提供的标准框架,其已经包含在JavaEE 6.0中;JSR 303 通过在Bean属性上标注类似于@NotNull、@Max等标准的注解指定校验规则,并通过标准的验证接口对Bean进行验证

Hibernate Validator 是 JSR 303 的一个参考实现,除支持所有标准的校验注解外,还支持一些扩展注解

① 关于所需要的jar包
Spring4.0拥有独立的数据校验框架,同时支持 JSR 303 标准的校验框架;但其本身并没有提供 JSR 303 的实现,故必须将 JSR 303 的实现者的jar包放到类路径下。

② 关于LocalValidatorFactoryBean工厂类
该工厂类既实现了Spring的Validator接口,也实现了 JSR 303 的Validator接口;故需要在 Spring 容器中定义LocalValidatorFactoryBean,即可将其注入到需要数据校验的Bean中。

③ 关于@Valid注解
<mvc:annotation-driven/>会默认装配LocalValidatorFactoryBean,通过在处理方法的入参上标注@Valid注解即可让SpringMVC在进行数据绑定时,同时调用校验框架完成数据校验工作。

④ 关于校验结果
前一个表单/命令对象的校验结果保存到随后处理方法的入参中,该入参必须是BindingResultErrors类型;

需注意,需校验的Bean对象和其绑定结果对象或错误对象是成对出现的,其之间不允许声明其他的入参

在需要校验的JavaBean属性上添加相应的校验注解

public class Employee { 
    @NotNull
    private String name;
    @Email
    private String email;
    @Past
    @DateTimeFormat(pattern="yyyy-MM-dd")
    private Date birth; 
    // ……
}

在处理器目标方法的Bean类型的入参前添加@Valid注解,并添加保存校验结果的对象(BindingResult )

@RequestMapping(value="/emp", method=RequestMethod.POST)
public String save(@Valid Employee employee, BindingResult result, Map<String, Object> map) {
    System.out.println(employee);
 	//BindingResult
    if(result.getErrorCount() > 0) {
        for(FieldError error : result.getFieldErrors()) {
            System.out.println(error.getField() + " : " + error.getDefaultMessage());
        }
 
        // 指定校验错误时所转向的定制页面
        map.put("departments", departmentDao.getDepartments());
        return "emp-edit";
    }
 
    employeeDao.save(employee);
    return "redirect:/emps";
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值