1. 程式人生 > 實用技巧 >JAVA框架-SpringMVC3(引數對映,檔案上傳,請求限制)

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"})