首页 > 技术文章 > SpringMVC实现数据绑定与传参​

it-bt 2021-10-03 09:50 原文

1.1 URL绑定

@ReqeustMapping(value="/test/info", method=GET) 用于绑定Controller中的方法与对应的URL,默认可接受Get /Post请求,也可以用method进行限定。但是有更针对性的 @GetMapping@PostMapping,因此 @ReqeustMapping主要用于在类层面添加注解,用于设置这个类下所有访问方法的URL前缀。

1.2 Controller接收请求参数

1.2.1原始的键值对直接传递,保证属性名相同即可

前端

<form action="/um/p" method="post">
  <input name = "username"></br>
  <input name = "password"></br>
	<input type = "submit" value="提交"></br>
</form>

后端

@RequestMapping("/um")
public clss URLMappingController {
		@PostMapping("/p")
		@ResponseBody
		public String postMapping(String username, Sring password){
		}
}

SpringMVC 还可以进行自动类型转换,比如前端传的密码是数字,后端可以用Long接收,可以支持自动类型转换,但是如果前端密码是 "abcd",后端 Long password 就会报 400 错误,类型转换异常。因此出现400转换异常通常就是 前端表单验证不严导致的类型参数传递类型转换异常。

1.2.2 @RequestParam的使用

​ 如果参数名无法保持一致,比如前端属性名是 user_name ,那么后端接收也要用同样的属性名,如此会违反驼峰命名变量名的规则,此时可以用 @RequestParam 使用别名来绑定。而且前端传来的复杂数据比如多选框的数据,后端如果用List和Map集合类型的属性,也必须用 @RequestParam 来修饰,这样SpringMVC才能正确识别并接收。此外@RquestParam 还可以设置默认值,即前端没有限制某个数据强制必填时或者表单匿名提交时,后端如何设置该参数的默认值(如下代码①位置所示)。

前端:复杂表单

<div class="container">
        <h2>学员调查问卷</h2>
        <form action="./apply" method="post">
        <h3>您的姓名</h3>
        <input name="name" class="text"  style="width: 150px">
        <h3>您正在学习的技术方向</h3>
        <select name="course" style="width: 150px">
            <option value="java">Java</option>
            <option value="h5">HTML5</option>
            <option value="python">Python</option>
            <option value="php">PHP</option>
        </select>
        <div>
            <h3>您的学习目的:</h3>
            <input type="checkbox" name="purpose" value="1">就业找工作
            <input type="checkbox" name="purpose" value="2">工作要求
            <input type="checkbox" name="purpose" value="3">兴趣爱好
            <input type="checkbox" name="purpose" value="4">其他
        </div>
        <div style="text-align: center;padding-top:10px" >
            <input type="submit" value="提交" style="width:100px">
        </div>
        </form>
    </div>

后端:

@Controller
public class FormController {
    // ① @RequestParam可以设置当前端没有传参时的后端接收的默认值,如下 name会默认为 "ANON"
//    @PostMapping("/apply")
    @ResponseBody
    public String apply(@RequestParam(value = "n",defaultValue = "ANON") String name, String course, Integer[] purpose){
        System.out.println(name);
        System.out.println(course);
        for (Integer p : purpose) {
            System.out.println(p);
        }
        return "SUCCESS";
    }

    // @RquestParam 修饰 List
//    @PostMapping("/apply")
    @ResponseBody
    public String apply(String name, String course, @RequestParam List<Integer> purpose){
        System.out.println(name);
        System.out.println(course);
        for (Integer p : purpose) {
            System.out.println(p);
        }
        return "SUCCESS";
    }

    //用实体类来接收(只有在Post请求才会生效)
//   ② @PostMapping("/apply")
    @ResponseBody
    public String apply(Form form){
        return "SUCCESS";
    }
    
    // @RquestParam 修饰 Map
//    @PostMapping("/apply")
    @ResponseBody
    public String apply(@RequestParam Map map){
        System.out.println(map);
        return "SUCCESS";
    }
}

​ 如果前端表单参数过多,比如有两个以上,可以用 Map来接收,但是会存在数据丢失的问题:在上面例子中,前端 name=purpose 多选框的数组数据 如果用Map接收只会保存选中的数组中的第一个数据,导致数据丢失。因此,这种结构化数据封装成实体类接收更为规范。但是用实体类接收,切记必须在Post请求下(如上代码②位置所示)。

1.2.3 @RequestBody

​ 这里背景知识涉及到 Content-type 的类型,可以参考Conten-TypeMIME 以及 Content-Type几种主要类型
简而言之: Content-type 就是 请求和响应头中表明 信息主体以哪种格式编码的,主要的有:

  1. application/x-www-form-urlencoded:早些年原始Html表单提交 Post请求时默认使用的 Content-type,提交的数据按照 key1=val1&key2=val2 的方式进行编码,key和val都进行了URL转码;
  2. multipart/form-data:表单中存在上传的文件时 form 指定的 enctype;
  3. application/json:ajax以及后续的vue.js(axios) ,越来越多使用 application/json来编码消息;
  4. text/xml:一种使用 HTTP 作为传输协议,XML 作为编码方式的远程调用规范。

​ 首先明确一点,一开始还是 Html简单页面的时候 表单发送Post请求,则请求的 content-type默认是application/www-urlencode。如果 SpringMVC后端使用某个类来接收,则后端传参时不需要添加 @RequestBody。但是如果 content-type=application/json 或者有文件上传的表单时,则必须使用 @RequestBody来修饰接收类。而现在的Vue.js前端框架搭配 axios(ajax变体) 实现表单传参都默认用 application/json的方式,所以Java后端实体类获取参数都必须前面加上 @RequestBody。 上述的演化历程可以参考 vue的axios使用时,Content-Type 引发的参数接收不到的问题回顾

1.2.4 关联对象赋值

当前端表单如下:

<!--原始-->
<div>
  <input name="username">
  <input name="password">
  <!---------------------->
  <input name="name">
  <input name="idno">
  <input name="expire">
</div>

<!--与后端类匹配之后,进行针对性修改-->
<div>
  <input name="username">
  <input name="password">
  <!---------------------->
  <input name="idcard.name">
  <input name="idcard.idno">
  <input name="idcard.expire">
</div>

按照面向对象的涉及原则,在设计接收的实体类时,应该如下:

## User.java
public class User {
    private String userName;
    private String password;
    private IDcard idcard = new IDcard();
}

## IDcard.java
public class IDcard {
    private String name;
    private String idno;
    private Date expire;
}

​ 由此可以将前端的表单的值准确的传递到User类的普通属性以及关联对象中。

1.2.5 @PathVariable注解

@PathVariable 用于获取 url中的路径变量

@GetMapping("/api/employees/{id}")
@ResponseBody
public String getEmployeesById(@PathVariable String id) {
    return "ID: " + id;
}

## 直接指定,类似与 @RequestParam使用别名
@GetMapping("/api/employeeswithvariable/{id}")
@ResponseBody
public String getEmployeesByIdWithVariableName(@PathVariable("id") String employeeId) {
    return "ID: " + employeeId;
}

## 多个变量
@GetMapping("/api/employees/{id}/{name}")
@ResponseBody
public String getEmployeesByIdAndName(@PathVariable String id, @PathVariable String name) {
    return "ID: " + id + ", name: " + name;
}

1.2.6 时间类型参数的单个和全局转换

单个转换

​ @DateTimeFormat可以接收前端指定格式的表示时间的字符串格式的参数,并在后端解析成 Date类型或者LocalDate类型对应的属性。

## 解析前端传来的 "2021-10-01", 为Date类型
@PostMapping("/p1")
@ResponseBody
public String postMapping1(User user , String username ,@DateTimeFormat(pattern = "yyyy-MM-dd") Date createTime){
    System.out.println(user.getUsername() + ":" + user.getPassword());
    return "<h1>这是Post响应</h1>";
}

## 实体类接收,对应属性田间 @DateTimeFormat注解
public class User {
    private String username;
    private Long password;
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date createTime;
}

(BTW: 后端往前端转可以添加 @JsonFormat来实现自动转)

全局设置

​ 如果要全局设置,需要自己写一个自定义转换器类:

public class MyDateConverter implements Converter<String, Date> {
    public Date convert(String s) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        try {
            Date d = sdf.parse(s);
            return d;
        } catch (ParseException e) {
            return null;
        }
    }
}

​ 在applicationContext.html中添加以下代码添加到容器中

 <mvc:annotation-driven conversion-service="conversionService">
 </mvc:annotation-driven>

<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="com.imooc.springmvc.converter.MyDateConverter"/>
            </set>
        </property>
</bean>

​ 此时,后端获取到前端的表示时间的格式为 "yyyy-MM-dd" 的字符串就会自动转换为Date类型。单个与全局的设置,应该是互相搭配和补充。

推荐阅读