“一个人最好的状态:梦想藏在心里,行动落于腿脚。”
目录
1、前言
上一篇我们通过SpringMVC框架的快速入门已经了解了它的工作流程和各个组件,知道了引入SpringMVC框架后需要我们程序员手动开发的其实就是Handler 处理器和 View 视图,这一篇我们就从Handler 处理器(Controller层)开始入手。
2、参数绑定
当项目中引入springmvc框架后,所有的请求流转将由springmvc进行控制,当客户端发送的请求中包含数据(也就是请求参数)时,那么该如何在controller层获取这些参数呢?
在 SpringMVC 中,提交请求的数据是通过方法形参来接收的。
也就是说从客户端请求的 key/value 数据会自动的和方法的参数进行匹配,然后将 key/value 数据绑定到 Controller 的形参上,我们就可以在Controller层使用这些请求中的参数,这就叫做参数绑定
在参数绑定过程中,会涉及到参数绑定组件,可以理解为将请求的数据类型转换成我们需要的数据类型,也叫做参数绑定转换器。在SprinifMVC框架中内置了很多参数转换器,只有在需要的情况下才需要我们自定义参数转换器,这个后面会介绍到
2.1 默认支持的参数类型
SpringMVC有默认支持的参数类型,我们可以直接在Controller层形参上给出这些默认对象的声明,就可以直接使用这些对象
对象 | 说明 |
---|---|
HttpServletRequest | 通过request对象获取请求信息 例如url、ip、请求方式、请求参数和给未来页面传递数据 |
HttpServletResponse | 通过response对象获取请求信息 例如设置响应类型、生成验证码、Cookie和返回json格式数据 |
HttpSession | 通过session对象获取session里存储的信息 例如对会话级别的数据进行读写 |
Model | 通过model对象将数据填充到request域中 向页面传递数据 |
接下来我介绍一下其他常用类型的参数绑定
为此我们先创建一个简单的表达页面用于提交数据
编辑index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>参数绑定</title>
</head>
<body>
<form action="/param" method="post">
用户名:<input type="text" name="username"><br/>
密码:<input type="text" name="password"><br/>
<input type="submit" value="提 交">
</form>
</body>
</html>
2.2 简单数据类型参数绑定
基本数据类型
基本数据类型包括byte、short、int、long、float、double、boolean、char八种,接下来我们以 int 为例
-
Controller控制层代码
@Controller
public class ParamController {
/**
* 简单类型参数
* @return
*/
@RequestMapping("/param")
public ModelAndView param(int username, int password) {
System.out.println(username + " : " + password);
return null;
}
}
-
测试,输出结果
控制台打印出来了我们提交的表单数据
注意:表单中input的name值是和控制层的参数变量名保持一致,这样才能 完成参数绑定。如果不一致的情况下就需要我们引入一个注解
@RequestParam
使用这个注解,控制层可以使用任意形参,但是必须要保证注解的value值和表单的input的name值保持一致
@RequestMapping("/param")
public ModelAndView param(@RequestParam("username") int name, int password) {
System.out.println("用户名:" + name);
System.out.println("密码:" + password);
return null;
}
控制台成功打印出我们的提交数据
@RequestParam 注解细节
该注解有三个变量:value、required、defaultValue
-
value : 指定name属性的值是什么,一般情况下value属性可以省略不写,直接写值即可
-
required : 参数是否必须传递,可以是true或false
-
defaultValue : 设置默认值
注意:
我们这里的参数是基本数据类型,如果从表单传递过来的参数是""或者null的情况下,就会出现数据转换异常
所以在我们开发的过程中,为了避免传递过来的数据可能为空的情况下,最好将参数类型定义为包装类型,我们往下看
包装数据类型
包装数据类型包括Integer、Long、Byte、Double、Float、Short,(String 类型在这也是适用的),接下来我们以 Integer和String 为例
-
Controller控制层代码
@RequestMapping("/param")
public ModelAndView param(String username, Integer password) {
System.out.println("用户名:" + username);
System.out.println("密码:" + password);
return null;
}
和基本数据类型基本一样,如果我们在表单处没有提交任何一个参数,那么String类型的数据就会显示为"",Integer类型的数据就会显示为null
3、SpringMVC编码过滤器
思考:
之前我们测试的参数绑定中,在Controller控制层没有对编码进行任何的操作,如果我们接收的是中文字符会是什么样子的呢?
唷~乱码了!
我们想到之前学过Servlet,可以设置request对象编码
request.setCharacterEncoding("utf-8”);
测试
唷~还是乱码!
因为SpringMVC框架接收参数是通过控制器中的无参构造方法,再经过具体业务方法的Object对象来得到具体的参数类型的
解决方法:SpringMVC已经给我们准备了一个编码过滤器,该过滤器只针对POST方法有效,只需要在web.xml文件中进行相关配置就可以
<!-- 编码过滤器 -->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<!-- 设置编码格式 -->
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
这样就可以正常提交中文字码了
3.1 POJO实体类型参数绑定
当需要接收客户端发送过来的多个数据时,我们在控制层通过声明方法参数一个一个的去接收就变的非常麻烦
我们是否可以在方法上声明对象类型的参数,通过对这些数据进行统一的接收,SpringMVC框架会自动的将接收过来的数据封装到对象中
普通POJO类型
-
我们先定义一个User类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private String username;
private Integer password;
}
注意:表单中input中的name属性只要和POJO实体类中的属性保持一致就可以映射成功
-
编辑Controllerl类
/**
* pojo实体类型参数
* @return
*/
@RequestMapping("/param")
public ModelAndView param(User user) {
System.out.println(user);
return null;
}
-
测试,输出结果
-
接下来我们在User实体中新添加一个成员变量(Date类型)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private String username ;
private String password;
private Date birthday;
}
-
index.jsp表单提交页面中也添加一个input标签
生日:<input type="text" name="birthday">
-
测试,输出结果
可以看到我们在表单提交页面输入的日期类型为 yyyy/MM/dd 类型,控制层可以正常接收,这是因为SpringMVC默认的日期格式就是这种,如果我们输入的是 yyyy-MM-dd 类型呢?
-
测试,输出结果
结果出问题了,SpringMVC不支持这种类型的数据
解决方法:SpirngMVC提供了一个@InitBinder注解,用于指定自定义的日期转换格式,因此,我们只需要在Controller类中添加下面的代码即可,在接受日期类型的参数时,会自动按照自定义的日期格式进行转换。
/* 自定义日期转换格式 */
@InitBinder
public void InitBinder (ServletRequestDataBinder binder){
binder.registerCustomEditor(Date.class,
new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), true)
);
}
-
再次测试,输出结果
上述我们字符串转日期格式的方式叫WebDataBinder方式,但这种方式有一个弊端是只能针对当前的Controller使用,如果有多个Controller需要进行格式转换的话就需要用到日期格式转换器
4、SpringMVC日期格式转换器
-
自定义转换器
import org.springframework.core.convert.converter.Converter;
/**
* 日期格式转换器
* 需要实现Converter接口,将String类型转换成Date类型
*/
public class DateConverter implements Converter<String,Date> {
@Override
public Date convert(String date) {
//实现将字符串转成日期类型(格式是yyyy-MM-dd HH:mm:ss)
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
try {
return dateFormat.parse(date);
} catch (ParseException e) {
e.printStackTrace();
}
//参数绑定失败返回null
return null;
}
}
-
配置dispatcher-servlet.xml
<context:component-scan base-package="com.cn"/>
<!-- 配置注解驱动,用于识别注解(比如@Controller) -->
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
<!--自定义参数绑定-->
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<list>
<!--自定义转换器的类路径-->
<bean class="com.cn.util.DateConverter"></bean>
</list>
</property>
</bean>
-
再次测试,输出结果
4.1 包装POJO类型
包装POJO类型与普通POJO类型的区别是,包装类型POJO里有的成员变量是另一个POJO的属性,也就是POJO套POJO
这种组合的设计方法对于后期程序的扩展很有用,比如复杂的查询条件就需要包装到这种包装类型中
-
新编辑一个实体类UserInfo
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserInfo {
private String address;
}
-
在User实体中增加一个属性
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private String username;
private String password;
private Date birthday;
private UserInfo userinfo;
}
-
index.jsp表单提交页面中也添加一个input标签
地址:<input type="text" name="userinfo.address">
注意:User对象中有userinfo属性,在表单提交代码中要使用 "属性名(对象类型的属性).属性名 "来定义input标签中的name值
-
测试代码
/**
* 包装POJO类型参数绑定
* @param user
* @return
*/
@RequestMapping("/param")
public ModelAndView getParam(User user){
System.out.println(user.getUserinfo().getAddress());
return null;
}
-
输出结果
4.2 集合类型参数绑定
数组类型
数组的绑定指的是从前台传过来同一类型的数据,我们在Conreoller层使用数组形参来接收数据
-
编辑index.jsp表单提交页面(模拟选中多个复选框)
<input type="checkbox" name="hobbies" value="篮球">篮球
<input type="checkbox" name="hobbies" value="乒乓球">乒乓球
<input type="checkbox" name="hobbies" value="羽毛球">羽毛球
-
编辑Controller层
@RequestMapping("/param")
public ModelAndView param(String[] hobbies) {
for (String hobby : hobbies) {
System.out.println(hobby);
}
return null;
}
Controller层的方法形参值和表单提交页面input标签中的name值保持一致
-
测试,输出结果
List类型
通常我们需要批量提交数据的时候(例如批量删除,修改等业务),往往将数据绑定到list再提交给Controller层
-
创建一个UserList类,封装 List<User> 属性
public class UserList {
private List<User> list;
}
-
index.jsp表单提交页面模拟提交多条用户信息
用户名:<input type="text" name="list[0].username"><br/>
密码:<input type="text" name="list[0].password"><br/>
用户名:<input type="text" name="list[1].username"><br/>
密码:<input type="text" name="list[1].password"><br/>
-
编辑Controller层
@RequestMapping("/param")
public ModelAndView param(UserList userList) {
List<User> users = userList.getList();
for (User user : users) {
System.out.println(user);
}
return null;
}
-
测试,输出结果
Map类型
Map类型参数绑定和List的方法一样,只不过是绑定的类型不一致而已
-
创建一个UserMap类,封装 Map<String,User> 属性
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserMap {
private Map<String,User> map;
}
-
index.jsp表单提交页面模拟提交多条用户信息
用户名:<input type="text" name="map['one'].username"><br/>
密码:<input type="text" name="list['one'].password"><br/>
用户名:<input type="text" name="list['two'].username"><br/>
密码:<input type="text" name="list['two'].password"><br/>
-
编辑Controller层
@RequestMapping("/param")
public ModelAndView param(UserMap userMap) {
Map<String, User> map = userMap.getMap();
for (Map.Entry<String, User> entry : map.entrySet()) {
System.out.println(entry);
}
return null;
}
-
测试,输出结果
到这里我们关于SpringMVC的参数绑定基本常用的就总结完毕了,其实原理都差不了多少,只是针对页面提交过来的类型,绑定的方式有些区别而已,多加练习基本就能掌握这些。
接下来我们思考控制层接收参数处理完业务后数据是怎么响应到页面上的?
5、数据回显
其实数据回显我们之前在其他文章中就已经使用过EL表达式在页面上过数据,其实这就叫做数据回显,本质其实就是获取request的值
在SpringMVC中,我们一般是将数据添加到Model,然后使用model把数据绑定到reques中
5.1 Model的使用
Model其实就是一个Map对象,我们把数据通过addAttribute方法添加到Model中后会自动保存在request域中,再通过转发将属性数据带到响应的页面中,然后通过 ${ } 来读取数据
-
Model中添加属性
/**
* 测试Model对象
*/
@RequestMapping("/testModel")
public String testModel(Model model){
/* 往Model添加属性 */
model.addAttribute("name", "桔子");
model.addAttribute("age", 18);
return "home";
}
-
编辑home.jsp页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
用户名:${ name } <br/>
年龄:${ age }
</body>
</html>
-
测试,输出结果
6、转发和重定向
在前面Servlet的request对象的学习中,我们通过request对象可是实现转发和重定向
同样SpringMVC中也提供了请求和重定向的方法,并且非常简单,我们一一介绍
转发
-
编辑测试请求转发方法
/**
* 测试请求转发(forward)
*/
@RequestMapping("/testForward")
public String testForward(){
System.out.println("测试请求转发(forward)...");
return "forward:/hello";
}
@RequestMapping("/hello")
public String hello() {
return "hello";
}
-
访问测试,输出结果
其实我们添加的forword就相当于之前学过的下面的代码
request.getRequestDispatcher("url").forward(req, resp);
特点:
-
转发是一次请求,一次响应
-
转发后地址栏没有发生变化,还是访问的/testForword地址
-
转发前后的request和response对象也是同一个
重定向
-
编辑测试重定向方法
/*
* 测试请求重定向(redirect)
*/
@RequestMapping("/testRedirect")
public String testRedirect(){
System.out.println("测试请求重定向(redirect)...");
return "redirect:/hello";
}
-
访问测试,输出结果
其实我们添加的redirect就相当于之前学过的下面的代码
response.sendRedirect(url)
特点:
-
重定向是两次请求,两次响应
-
重定向后地址栏发生了变化,之前访问的/testRedirect地址,变成了/hello地址
-
重定向前后的request和response对象不是同一个
7、返回JSON文本
JSON(JavaScript Object Notation)是一种JS提供的轻量级的数据交换格式。
JSON在项目开发中是一种非常流行的数据交换格式。
我们如果想要返回给页面一个JSON文本,SpringMVC给我们提供了一个非常简便的注解——@ResponseBody
-
首先我们需要添加两个JSON的jar包
<!-- https://mvnrepository.com/artifact/org.codehaus.jackson/jackson-core-asl -->
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
<version>1.9.13</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.codehaus.jackson/jackson-mapper-asl -->
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.13</version>
</dependency>
-
编辑测试方法
@RequestMapping("/testJson")
@ResponseBody
public List<User> testJson(){
//模拟查询所有用户,将所有用户信息封装到List<User>集合中
List<User> list = new ArrayList();
list.add( new User("张三", 123) );
list.add( new User("李四", 222) );
//将所有用户的List<User>集合以JSON格式响应
return list;
}
-
输出结果
8、文件上传
文件的上传和下载基本上是web项目中经常中会用到的技术,比如在一些社交网站上上传一些图片和视频等
springmvc中由MultipartFile接口来实现文件上传。
-
首先我们需要添加两个文件上传的jar包
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
-
配置文件上传解析器
<!--文件上传-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设置上传文件的最大尺寸为5MB -->
<property name="maxUploadSize" value="5242880"></property>
</bean>
-
编辑前端upload.jsp页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>文件上传</title>
</head>
<body>
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="picture">
<input type="submit" value="图片上传">
</form>
</body>
</html>
-
处理层Controller代码
@Controller
public class UploadController {
@RequestMapping("/upload")
//MultipartFile该对象就是封装了图片文件
public void upload(MultipartFile picture) {
System.out.println(picture.getOriginalFilename());
}
@RequestMapping("/testUpload")
public ModelAndView testUpload() {
return new ModelAndView("upload");
}
}
-
访问测试,输出结果
下一篇我们将继续介绍SpringMVC的统一异常管理、RESTful、拦截器等相关的知识,敬请期待。