JAVA框架-SpringMVC3(参数映射,文件上传,请求限制)
参数映射
接下来就是Spring的各个处理细节了,无论框架如何疯转其实我们处理请求的流程是不变的,设计到的操作也是固定的,举个例子,当我们要实现一个登陆功能时:
- 创建一个用于处理登录请求的Servlet
- 实现doget等其他http方法(一些情况可能根据业务需要限制请求方法)
- 从request对象中取出数据
- 处理编码
- 验证参数是否符合要求
- 对参数数据类型进行转换(需要时)
- 开始业务逻辑处理(登录)
- 可能需要操作session来完成
- 组织响应给数据,可能是html可能是json,
- 异常处理
- Header与cookie的处理
整个SpringMVC其实就是帮我们对上面的操作进行封装,当然了SpringMVC也提供了更多的功能,如国际化..
handle方法获取特殊参数
在handler方法中我们可以添加一下参数,用于获取一些特殊的对象:
- HttpServletRequest,HttpServletResponse
- HttpSession
- Model
- ModelMap
model 是框架帮我们创建好的的Model对象,若使用该参数作为返回的model,则需要修改方法返回值为String用于指定视图名称;
我们在继续上个博客的程序,在控制类中添加一个方法:
package controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
/**
* Created by Jeason Luna on 2020/7/4 22:54
*/
@Controller
public class AnnotionController {
@RequestMapping("/UserInfo")
public ModelAndView UserInfo(){
\\...
}
@RequestMapping("/UserLogin")
public ModelAndView UserLogin(){
\\...
}
@RequestMapping("/test")
//当操作request、response、session的时候,只需要在handle方法上加上对应的参数即可
public void test(HttpServletRequest request, HttpServletResponse response, HttpSession session) throws IOException {
System.out.println("test is working!!!!!!" );
System.out.println( "request name: " + request.getParameter("name") );
System.out.println("Session ID: "+ session.getId());
PrintWriter writer = response.getWriter();
writer.println("Hello World!!!");
}
}
请求参数映射
在使用Servlet开发的过程中我们会频繁的调用request.getAttribute来获取请求参数,参数较少时还没什么,一旦参数较多的时候就会产生大量的冗余代码,SpringMVC提供了多种以简化获取参数的过程的方法
映射到handle方法的参数
在handler方法中添加与前台参数名称和类型匹配的参数,框架会自动解析参数传入handler方法中;
支持参数类型:
整形:Integer、int
字符串:String
单精度:Float、float
双精度:Double、double
布尔型:Boolean、boolean
@RequestParam
当前后台参数名称不匹配时可以@RequestParam注解进行自定义映射;
注解参数:
- value,name 两者都代表前台参数名称
- requerid 该参数是否必须
- defaultValue 参数默认值
/**
* Created by Jeason Luna on 2020/7/6 19:55
*/
@Controller
public class UserController {
@Autowired
private UserService service;
@RequestMapping("/getOneUser")
public ModelAndView getOneUser( @RequestParam("id") Integer iid ){
System.out.println("working..........");
System.out.println( " 接受到的参数为: "+ iid);
ModelAndView t = new ModelAndView();
Kuser kuser = service.selectUserByID(iid);
System.out.println(kuser);
t.addObject("user", kuser);
t.setViewName("User.jsp");
return t;
}
}
注意:参数类型可以是基础类型也可以是包装类型,建议使用包装类型,这样可以保证为获取到参数时不会因为null无法转换为基础类型而导致的异常;
映射到实体类
当参数个数非常多时上面的方法就显得麻烦了,SpringMVC支持将参数映射到一个实体类;
在handler方法中添加任意类型实体类(其实就是Bean对象)作为参数; 同样的只有参数名称和实体属性一致时才能映射成功;
我们继续完善修改功能,现在要获取修改后的内容了:
更新用户信息的小案例:
假设我们要实现修改用户信息的功能,首先要获取原始信息:
先写一个能够提交信息的页面edit.jsp
<%--
Created by IntelliJ IDEA.
User: 17390
Date: 2020/7/12
Time: 20:50
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<html>
<head>
<title>获取用户并且修改用户信息</title>
</head>
<body>
<form action="update.do" method="post">
<input name="id" value="${user.id}" hidden="hidden"/>
<input name="username" value="${user.username}"/>
<input name="birthday" type="date" value='<fmt:formatDate value="${user.birthday}" pattern="yyyy-MM-dd"/>'/>
<input name="sex" type="number" value="${user.sex}"/>
<input name="address" value="${user.address}"/>
<input type="submit">
</form>
</body>
</html>
Controller层中添加如下两个方法(一个用于显示,一个用于更新,更新后还要调回显示页面给用户看)
package Controller;
import Bean.Kuser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import service.UserService;
import java.util.Arrays;
/**
* Created by Jeason Luna on 2020/7/6 19:55
*/
@Controller
public class UserController {
@Autowired
private UserService service;
@RequestMapping("/showOneUser.do")
public ModelAndView updataOneUser( Integer id ){
ModelAndView t = new ModelAndView();
Kuser kuser = service.selectUserByID(id);
System.out.println(kuser);
t.addObject("user", kuser);
t.setViewName("edit.jsp");
return t;
}
@RequestMapping("/update.do")
public String update(Kuser user) {
System.out.println( "updata is working!!!!");
service.updatUser(user);
return "/showOneUser.do";
}
}
理论上,这样我们就实现了用户数据的修改功能:
但是我们还会有两个如下的小问题
乱码过滤器
上面的例子中出现了中文乱码问题,请求方法为post, 只需要在request中设置编码方式即可,但是此时参数已经被框架解析了,我们在handler中通过request设置以及不生效了,所以我们需要在请求到达SpringMVC之前就进行处理,这就用到了以前学过的过滤器了;
好消息是SpringMVC以及提供了过滤器,我们只需要配置到web.xml中即可
<!-- 编码过滤器-->
<filter>
<filter-name>encodingFilter</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>
<!-- 是否对响应设置编码 -->
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 编码过滤器 END-->
需要注意的是,该过滤器只对post生效,如果是get乱码则还是需要修改tomcat的server.xml或是通过代码从ISO-8859-重新编码为UTF-8
String username = request.getParameter("username");
username = new String(username.getBytes("iso8859-1"), "utf-8"); //重新编码
参数类型转换
在我们的案例中还有有这样一个报错:
Field error in object 'rejected value [2020-06-11]; codes [typeMismatch.kuser.birthday,typeMismatch.birthday,typeMismatch.java.util.Date,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvab
意思是框架无法将String类型的请求参数转换为需要的Date类型,这就需要,这是因为日期格式多种多样,每个地区不同,所以这需要我们自己来实现转换;
编写转换器
实现convert接口即可作为转换器,该接口的两个两个泛型表示输入源类型和输出目标类型;
public class StringToDateConverter implements Converter<String, Date> {
@Override
public Date convert(String s) {
SimpleDateFormat sm = new SimpleDateFormat("yyyy-MM-dd");
try {
return sm.parse(s);
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
}
mvc配置文件
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="converter.StringToDateConverter"/>
</set>
</property>
</bean>
<mvc:annotation-driven conversion-service="conversionService"/>
使用注解注册转换器
使用@DateTimeFormat可以实现上面xml相同的配置:
public class Kuser {
private Integer id;
private String username;
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
包装类型映射
当需要将参数映射到实体类的关联对象中时,也称为包装类型;
例如:在课程对象中有一个用户对象,表示这是某个用户的课程;前台需要同时传递课程对象的属性,和用户对象的属性,后台就需要要用一个包装类型来接收,即一个包装了用户对象的课程对象; 再说的简单点,即课程对象中包含一个用户对象;
在前台需要指出关联对象的属性名称,如:用户.name
实体:
public class Course {
private Integer id;
private String name;
private String teachName;
private Date startTime;
private Integer score;
private Integer hours;
private User user;//新添加的User类属性
set/get....
handler:
@RequestMapping("/updateCourse.action")
public String update(Course course) {
courseService.updateCourse(course);
return "/courseList.action";
}
jsp:
<form action="updateCourse.action" method="post">
<input name="id" value="${course.id}" hidden="hidden"/>
<input name="name" value="${course.name}"/>
<input name="teachName" value="${course.teachName}"/>
<input name="startTime" type="date" value='<fmt:formatDate value="${course.startTime}" pattern="yyyy-MM-dd"/>'/>
<input name="score" type="number" value="${course.score}"/>
<input name="hours" type="number" value="${course.hours}"/>
<input name="user.username"/> <!-- 新添加的参数-->
<input type="submit">
</form>
数组参数映射
一些情况下,某一参数可能会有多个值,例如要进行批量删除操作,要删除的id会有多个,那就需要将参数映射到一个数组中;HttPServletRequest原本就支持获取数组参数,SpringMVC仅是帮我们做了一个类型转换;
1.修改页面添加多选框
<%--
Created by IntelliJ IDEA.
User: 17390
Date: 2020/7/12
Time: 20:50
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<html>
<head>
<title>批量删除用户信息</title>
</head>
<body>
<form action="deleteUsers.do">
<table border="1">
<tr>
<th>选择</th>
<th>姓名</th>
<th>生日</th>
<th>性别</th>
<th>地址</th>
<th>操作</th>
</tr>
<c:forEach items="${users}" var="user">
<tr>
<td>
<input type="checkbox" name="ids" value="${user.id}">
</td>
<td>${user.username}</td>
<td>
<fmt:formatDate value="${user.birthday}" pattern="yyyy-MM-dd"/>
</td>
<td>${user.sex}</td>
<td>${user.address}</td>
<td>
<a href="/update.do?id=${user.id}">修改</a>
</td>
</tr>
</c:forEach>
</table>
<input type="submit" value="批量删除">
</form>
</body>
</html>
随后我们添加方法
@RequestMapping("/getUsers.do")
public ModelAndView getUsers(Integer[] ids){
List<Kuser> allUsers = service.getAllUsers();
ModelAndView t = new ModelAndView();
System.out.println(allUsers);
t.addObject("users", allUsers);
t.setViewName("delete.jsp");
return t;
}
@RequestMapping("/deleteUsers.do")
public String deleteCourses(Integer[] ids){
service.deleteUsers(ids);
return "/getUsers.do";
}
在service和dao层去实现的部分略.....详细可参见Mybatis博客
list映射
当请求参数包含多个对象的属性数据,是需要使用list来接收,通常用在批量修改批量添加等;
list映射要求参数名称为对象属性[下标].属性名称
,同时handler中使要用包装类型来接收;
以下是实现一个批量修改课程信息的功能;
1.修改页面中的td,使得每一个td都可以编辑:
<%--
Created by IntelliJ IDEA.
User: 17390
Date: 2020/7/12
Time: 20:50
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<html>
<head>
<title>批量删除用户信息</title>
</head>
<body>
<form action="deleteUsers.do" id="fm">
<table border="1">
<tr>
<th>选择</th>
<th>姓名</th>
<th>生日</th>
<th>性别</th>
<th>地址</th>
<th>操作</th>
</tr>
<c:forEach items="${users}" var="user" varStatus="status" >
<tr>
<td>
<input type="checkbox" name="users[${status.index}].id" value="${user.id}">
</td>
<td><input value="${user.username}" name="users[${status.index}].username"/></td>
<td><input value='<fmt:formatDate value="${user.birthday}" pattern="yyyy-MM-dd"/>' name="users[${status.index}].birthday"/></td>
<td><input value="${user.sex}" name="users[${status.index}].sex"/></td>
<td><input value="${user.address}" name="users[${status.index}].address"/></td>
<td>
<a href="/update.do?id=${user.id}">修改</a>
</td>
</tr>
</c:forEach>
</table>
<input type="submit" value="批量删除">
<input type="button" onclick='function updateUsers() {
document.getElementById("fm").action = "updateUsers.do"
document.getElementById("fm").submit()
}
updateUsers()' value="批量修改">
</form>
</body>
</html>
2.包装类型
package RequestPack;
import Bean.Kuser;
import java.util.List;
/**
* Created by Jeason Luna on 2020/7/12 23:37
*/
public class RequestPack {
private List<Kuser> users;
public List<Kuser> getCourses() {
return users;
}
public void setCourses(List<Kuser> users) {
this.users = users;
}
public RequestPack(List<Kuser> users) {
this.users = users;
}
}
强调:list只能映射到包装类型中,无法直接映射到handler参数上
3.handler方法:
@RequestMapping("/updateUsers.do")
public String updateUsers(RequestPack data){
System.out.println("updateUsers.do is working");
service.updateUsers( data.getUsers() );
return "/getUsers.do";
}
4.service方法:
@Override
public void updateUsers(List<Kuser> users){
for (Kuser user:users){
mapper.updateByPrimaryKey(user);
}
}
文件上传
文件上传是web项目中非常常见的需求,SpringMVC使用了apache开源的两个库用于处理文件上传,所以在编写代码前我们需要先导入下面两个依赖包:
<dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.4</version>
</dependency>
假设需要实现一个上传图片的功能,需要现在数据库中添加一个字段用于存储图片的路径,同时不要忘记修改pojo以及mapper文件,使之与数据库字段对应
实际上我们的MVC在收到图片之后会放到一个临时的文件夹里面,我们所需要做的就是把这个图片从临时文件夹里面放到我们指定的地方
1.页面增加input 用于提交文件,并修改表单的enctype为multipart/form-data
<%--
Created by IntelliJ IDEA.
User: 17390
Date: 2020/7/12
Time: 20:50
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<html>
<head>
<title>获取用户并且修改用户信息</title>
</head>
<body>
<form action="userInfo.do" method="post" enctype="multipart/form-data">
<input name="id" value="${user.id}" hidden="hidden"/>
<input name="username" value="${user.username}"/>
<input name="birthday" type="date" value='<fmt:formatDate value="${user.birthday}" pattern="yyyy-MM-dd"/>'/>
<input name="sex" type="number" value="${user.sex}"/>
<input name="address" value="${user.address}"/>
<input type="submit">
<img src="${user.pic}" alt="图片....">
<br>图片:<input name="picFile" type="file"/><br/> <!-- 新增input-->
</form>
</body>
</html>
2.在mvc配置文件中添加multipart解析器,(页面上传文件都是以,multipart编码方式)
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>
3.handler方法中添加MultipartFile类型的参数
@RequestMapping("/getOneUser2.do")
public ModelAndView getOneUser2( @RequestParam("id") Integer iid ){
System.out.println("working..........");
System.out.println( " 接受到的参数为: "+ iid);
ModelAndView t = new ModelAndView();
Kuser kuser = service.selectUserByID(iid);
System.out.println(kuser);
t.addObject("user", kuser);
t.setViewName("UserInfo.jsp");
return t;
}
@RequestMapping("/userInfo.do")
public String updateCourse(Kuser user, MultipartFile picFile) throws IOException {
/***
* 1.获得文件,取出文件后缀
* 2.生成唯一标识,
* 3.写入文件到指定路径
* 4.存储文件路径到数据库
*/
System.out.println("文件原始名称:"+picFile.getOriginalFilename());
String suffix = picFile.getOriginalFilename().substring(picFile.getOriginalFilename().lastIndexOf("."));
String fileName = UUID.randomUUID() + suffix;
String basepath = getClass().getClassLoader().getResource(".").getPath();
System.out.println(basepath);
picFile.transferTo(new File("C:/Users/17390/Desktop/img/"+fileName));
user.setPic("C:/Users/17390/Desktop/img/"+fileName);
service.updatUser(user);
return "/getOneUser2.do?id=2";
}
注意:实际开发中都是存储到文件服务器,不会放在项目里
4.静态资源处
若web.xml中DispatcherServlet的URLmapping 为/ 则还需要在SpringMVC中添加静态资源配置
<mvc:resources mapping="/images/**" location="/images/"/>
<!--当请求地址为/images/开头时(无论后面有多少层目录),作为静态资源 到/images/下查找文件-->
若URLMapping为*.action 或类似其他的时则无需处理,因为Tomcat会直接查找webapp下的资源,不会交给DispatcherServlet
请求限制
一些情况下我们可能需要对请求进行限制,比如仅允许POST,GET等...
RequestMapping注解中提供了多个参数用于添加请求的限制条件
- value 请求地址
- path 请求地址
- method 请求方法
- headers 请求头中必须包含指定字段
- params 必须包含某个请求参数
- consumes 接受的数据媒体类型 (与请求中的contentType匹配才处理)
- produce 返回的媒体类型 (与请求中的accept匹配才处理)
@RequestMapping(value = "/editCourse",method = RequestMethod.POST,headers = {"id"},params = {"name"},consumes = {"text/plain"})