handler參數映射: 接下來就是Spring的各個處理細節了,無論框架如何瘋轉其實我們處理請求的流程是不變的,設計到的操作也是固定的,舉個例子,當我們要實現一個登陸功能時: 創建一個用於處理登錄請求的Servlet 實現doget等其他http方法(一些情況可能根據業務需要限制請求方法) 從re ...
handler參數映射:
接下來就是Spring的各個處理細節了,無論框架如何瘋轉其實我們處理請求的流程是不變的,設計到的操作也是固定的,舉個例子,當我們要實現一個登陸功能時:
創建一個用於處理登錄請求的Servlet
實現doget等其他http方法(一些情況可能根據業務需要限制請求方法)
從request對象中取出數據
處理編碼
驗證參數是否符合要求
對參數數據類型進行轉換(需要時)
開始業務邏輯處理(登錄)
可能需要操作session來完成
組織響應給數據,可能是html可能是json,
- 異常處理
Header與cookie的處理
整個SpringMVC其實就是幫我們對上面的操作進行封裝,當然了SpringMVC也提供了更多的功能,如國際化..
Handler方法特殊參數
在handler方法中我們可以添加一下參數,用於獲取一些特殊的對象:
- HttpServletRequest,HttpServletResponse
- HttpSession
- Model
- ModelMap
model 是框架幫我們創建好的的Model對象,若使用該參數作為返回的model,則需要修改方法返回值為String用於指定視圖名稱;
案例:
@RequestMapping("/courseList2.action")
public String courseList(Model model) {
model.addAttribute("courses", courseService.selectCourseList());
return "courses.jsp";
}
@RequestMapping("/courseList3.action")
public String courseList(ModelMap model, HttpSession session,HttpServletRequest request,HttpServletResponse response) {
System.out.println(request);
System.out.println(response);
System.out.println(session.getId());
model.addAttribute("courses", courseService.selectCourseList());
return "courses.jsp";
}
請求參數映射:
在使用Servlet開發的過程中我們會頻繁的調用request.getAttribute來獲取請求參數,參數較少時還沒什麼,一旦參數較多的時候就會產生大量的冗餘代碼,SpringMVC提供了多種以簡化獲取參數的過程的方法
映射到handler方法的參數
在handler方法中添加與前臺參數名稱和類型匹配的參數,框架會自動解析參數傳入handler方法中;
案例:
假設我們要修改課程信息的功能,首先要獲取原始信息
@RequestMapping("/edit.action")
public String edit(Model model,Integer id) {
Course course = courseService.selectCourseByID(id);
model.addAttribute("course",course);
return "edit.jsp";
}
支持的參數類型:
整形:Integer、int
字元串:String
單精度:Float、float
雙精度:Double、double
布爾型:Boolean、boolean
@RequestParam
當前後臺參數名稱不匹配時可以@RequestParam註解進行自定義映射;
註解參數:
- value,name 兩者都代表前臺參數名稱
- requerid 該參數是否必須
- defaultValue 參數預設值
案例:
@RequestMapping("/edit.action")
public String edit(Model model,@RequestParam("id") Integer iid) {
Course course = courseService.selectCourseByID(iid);
model.addAttribute("course",course);
return "edit.jsp";
}
註意:參數類型可以是基礎類型也可以是包裝類型,建議使用包裝類型,這樣可以保證為獲取到參數時不會因為null無法轉換為基礎類型而導致的異常;
edit.jsp:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head></head>
<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 type="submit">
</form>
<body>
</body>
</html>
映射到實體類
當參數個數非常多時上面的方法就顯得麻煩了,SpringMVC支持將參數映射到一個實體類;
在handler方法中添加任意類型實體類作為參數; 同樣的只有參數名稱和實體屬性一致時才能映射成功;
我們繼續完善修改功能,現在要獲取修改後的內容了:
@RequestMapping("/updateCourse.action")
public String update(Course course) {
courseService.updateCourse(course);
return "/courseList.action";
}
亂碼過濾器
上面的例子中出現了中文亂碼問題,請求方法為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"); //重新編碼
參數類型轉換
我們將edit.jsp中的開課標簽取消註釋,然後測試會發現系統給出了400異常,查看控制台可以看到以下信息:
意思是框架無法將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="com.kkb.converter.StringToDateConverter"/>
</set>
</property>
</bean>
<mvc:annotation-driven conversion-service="conversionService"/>
使用註解註冊轉換器
使用@DateTimeFormat可以實現上面xml相同的配置:
public class Course {
private Integer id;
private String name;
private String teachName;
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date startTime;
}
註意:註解作用在實體類對應的屬性上,且配置文件必須添加<mvc:annotation-driven>
包裝類型映射
當需要將參數映射到實體類的關聯對象中時,也稱為包裝類型;
例如:在課程對象中有一個用戶對象,表示這是某個用戶的課程;前臺需要同時傳遞課程對象的屬性,和用戶對象的屬性,後臺就需要要用一個包裝類型來接收,即一個包裝了用戶對象的課程對象; 再說的簡單點,即課程對象中包含一個用戶對象;
在前臺需要指出關聯對象的屬性名稱,如:用戶.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.修改頁面添加多選框:
<%@ 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>
</head>
<body>
<form action="deleteCourses">
<table border="1">
<tr>
<th>選擇</th>
<th>名稱</th>
<th>講師</th>
<th>開課日期</th>
<th>學分</th>
<th>課時</th>
<th>操作</th>
</tr>
<c:forEach items="${courses}" var="course">
<tr>
<td>
<input type="checkbox" name="ids" value="${course.id}">
</td>
<td>${course.name}</td>
<td>${course.teachName}</td>
<td>
<fmt:formatDate value="${course.startTime}" pattern="yyyy-MM-dd"/>
</td>
<td>${course.score}</td>
<td>${course.hours}</td>
<td>
<a href="editCourse?id=${course.id}">修改</a>
</td>
</tr>
</c:forEach>
</table>
<input type="submit" value="批量刪除">
</form>
</body>
</html>
2.handler方法:
@RequestMapping("/deleteCourses")
public String deleteCourses(Integer[] ids){
courseService.deleteCourses(ids);
return "/getCourses";
}
3.service方法:
public void deleteCourses(Integer[] ids) {
for (Integer id : ids){
courseMapper.deleteByPrimaryKey(id);
}
}
list映射
當請求參數包含多個對象的屬性數據,是需要使用list來接收,通常用在批量修改批量添加等;
list映射要求參數名稱為對象屬性[下標].屬性名稱
,同時handler中使要用包裝類型來接收;
以下是實現一個批量修改課程信息的功能;
1.修改頁面中的td,使得每一個td都可以編輯:
<%@ 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>
</head>
<body>
<form action="deleteCourses" id="fm">
<table border="1">
<tr>
<th>選擇</th><th>名稱</th><th>講師</th><th>開課日期</th><th>學分</th><th>課時</th><th>操作</th>
</tr>
<c:forEach items="${courses}" var="course" varStatus="status">
<tr>
<td><input type="checkbox" name="ids" value="${course.id}"></td>
<td><input value="${course.name}" name="courses[${status.index}].name"/></td>
<td><input value="${course.teachName}" name="courses[${status.index}].teachName"/></td>
<td><input value='<fmt:formatDate value="${course.startTime}" pattern="yyyy-MM-dd"/>' name="courses[${status.index}].startTime"/></td>
<td><input value="${course.score}" name="courses[${status.index}].score"/></td>
<td><input value="${course.hours}" name="courses[${status.index}].hours"/></td>
<input hidden="hidden" name="courses[${status.index}].id" value="${course.id}">
<td><a href="editCourse?id=${course.id}">修改</a></td>
</tr>
</c:forEach>
</table>
<input type="submit" value="批量刪除">
<input type="button" onclick='function updates() {
document.getElementById("fm").action = "updateCourses"
document.getElementById("fm").submit()
}
updates()' value="批量修改">
</form>
</body>
</html>
2.包裝類型:
public class RequestPack {
//用於接收參數列表的list
private List<Course> courses;
public List<Course> getCourses() {
return courses;
}
public void setCourses(List<Course> courses) {
this.courses = courses;
}
public RequestPack(List<Course> courses) {
this.courses = courses;
}
}
3.handler方法:
@RequestMapping("/updateCourses")
public String updateCourses(RequestPack data){
courseService.updateCourses(data.getCourses());
return "/getCourses";
}
4.service方法:
public void updateCourses(List<Course> courses) {
for (Course course:courses){
courseMapper.updateByPrimaryKey(course);
}
}
強調:list只能映射到包裝類型中,無法直接映射到handler參數上
錯誤案例:
@RequestMapping("/updateCourses")
public String updateCourses(RequestPack data, ArrayList<Course> courses){
courseService.updateCourses(data.getCourses());
return "/getCourses";
}
文件上傳
文件上傳是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文件,使之與資料庫欄位對應
1.頁面增加input 用於提交文件,並修改表單的enctype為multipart/form-data
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head></head>
<form action="updateCourse" method="post" enctype="multipart/form-data">
<input name="id" value="${course.id}" hidden="hidden"/>
名稱:<input name="name" value="${course.name}"/>
<br/>
講師:<input name="teachName" value="${course.teachName}"/>
<br/>
<input name="startTime" type="date" value='<fmt:formatDate value="${course.startTime}" pattern="yyyy-MM-dd"/>'/>
<br/>
學分:<input name="score" type="number" value="${course.score}"/>
<br/>
課時:<input name="hours" type="number" value="${course.hours}"/>
<br/>
<c:if test="${course.pic != null}">
<img src="${pageContext.servletContext.contextPath}${course.pic}" style="width: 100px;height: 100px">
</c:if>
圖片:<input name="picFile" type="file"/><br/> <!-- 新增input-->
<br/>
<input type="submit">
</form>
<body>
</body>
</html>
2.在mvc配置文件中添加multipart解析器,(頁面上傳文件都是以,multipart編碼方式)
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>
3.handler方法中添加MultipartFile類型的參數
@RequestMapping("/updateCourse")
public String updateCourse(Course course, MultipartFile picFile) throws IOException {
/***
* 1.獲得文件,取出文件尾碼
* 2.生成唯一標識,
* 3.寫入文件到指定路徑
* 4.存儲文件路徑到資料庫
*/
System.out.println(picFile.getName());
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(basepath+"../../images/"+fileName));
course.setPic("/images/"+fileName);
courseService.update(course);
return "/getCourses";
}
註意:實際開發中都是存儲到文件伺服器,不會放在項目里
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"})
為了簡化書寫,MVC還提供了集合路徑和方法限制的註解,包括常見的請求方法:
PostMapping
GetMapping
DeleteMapping
PutMapping
例:
@PostMapping("/editCourse")