Spring MVC(2)Spring MVC 組件開發

  1、控制器接收各種請求參數javascript

  代碼測試環境:html

  接收各種參數的控制器--ParamsControllerjava

package com.ssm.chapter15.controller;

@Controller
@RequestMapping("/params")
public class ParamsController {
    // 各類控制器方法
}

  先看一下目錄結構:mysql

  

  這裏須要知道的知識點是,WebContent文件夾下的.jsp文件均可以經過http://localhost:8080/工程名/文件名.jsp直接訪問。jquery

  而WEB-INF裏面的文件,必須經過Spring MVC 中的Controller控制器產生映射才能訪問。web

 

  1.接收普通請求參數spring

  params.jsp文件的內容以下,其中action="./params/commonParams.do"表示提交按鈕按下後,跳轉到action指定的頁面。sql

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
    "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>參數</title>
<body>
    <!-- 根據你的須要改變請求url -->
    <form id="form" action="./params/commonParams.do">
        <table>
            <tr>
                <td>角色名稱</td>
                <td><input id="roleName" name="roleName" value="" /></td>
            </tr>
            <tr>
                <td>備註</td>
                <td><input id="note" name="note" /></td>
            </tr>
            <tr>
                <td></td>
                <td align="right"><input type="submit" value="提交" /></td>
            </tr>
        </table>
    </form>
</body>
</html>

  而對應的控制器方法:commonParams方法數據庫

@Controller
@RequestMapping("/params")
public class ParamsController {
    
    @RequestMapping("/commonParams")
    public ModelAndView commonParams(String roleName, String note) {
        System.out.println("roleName =>" + roleName);
        System.out.println("note =>" + note);
        ModelAndView mv = new ModelAndView();
        mv.setViewName("index");
        return mv;
    }

  這裏由於當前Spring MVC 比較智能化,若是傳遞進來的參數名稱和HTTP的保存一致,意思就是傳遞進來的參數名稱爲roleName和note,而params.jsp中<input id="roleName" name="roleName" value="" />和<input id="note" name="note" />兩個參數名稱都和roleName和note一致,所以,能夠獲取到params.js中提交的參數。apache

  測試:首先輸入訪問表單,輸入任意參數,並提交

  

  而後,正確跳轉到./params/commonParams.do?roleName=mingcheng&note=beizhu,這一URL,說明參數傳遞成功。

  

   可是,在參數不少的狀況下,再使用這樣的方式,顯然所寫方法的參數就會很是多,這是應該考慮到使用一個POJO來管理這些參數。在沒有任何註解的狀況下,Spring MVC 也有映射POJO的能力。

  新建一個角色參數類,將兩個參數封裝到類中:

package com.ssm.chapter14.pojo;

public class RoleParams {
    private String roleName;
    private String note;

  /*getter and setter*/
}

  而後增長控制器方法:因爲上面的POJO的屬性和HTTP參數(jsp文件中的參數)一一對應了,而後在commonParamPojo方法中將POJO類對象RoleParams roleParams當成方法的參數,也能夠在沒有任何註解的狀況下實現參數的有效傳遞。

    @RequestMapping("/commonParamPojo")
    public ModelAndView commonParamPojo(RoleParams roleParams) {
        System.out.println("roleName =>" + roleParams.getRoleName());
        System.out.println("note =>" + roleParams.getNote());
        ModelAndView mv = new ModelAndView();
        mv.setViewName("index");
        return mv;
    }

  另外,還須要修改jsp中的action成<form id="form" action="./params/commonParamPojo.do">才能進行測試。

 

  2.使用@RequestParam註解獲取參數

  前面的兩種狀況,僅僅在參數名稱和jsp文件中的參數名稱一一對應時纔有效。那麼,若是修改jsp中的參數名稱,例如,<td><input id="role_name" name="role_name" value="" /></td>將roleName修改爲role_Name,此時因爲參數不一致,就沒法再進行自動對應傳遞了。

  能夠用@RequestParam註解獲取參數的方式解決這個問題:使用@RequestParam("role_name")來說HTTP的參數名稱(即jsp文件中的參數名稱)和傳遞進去的roleName參數一一對應。

    @RequestMapping("/requestParam")
    //使用@RequestParam("role_name")指定映射HTTP參數名稱
    public ModelAndView requestParam(@RequestParam("role_name") String roleName, String note) {
        System.out.println("roleName =>" + roleName);
        System.out.println("note =>" + note);
        ModelAndView mv = new ModelAndView();
        mv.setViewName("index");
        return mv;
    }

  一樣,修改action,而後也能夠獲得正確的測試結果:

  

 

  3.使用URL傳遞參數

  一些網站使用URL的形式傳遞參數,對於一些業務比較簡單的應用是很是常見的,若是想把得到數據庫中id爲1的role的信息,那麼就寫成/params/getRole/1,這裏的1就表明角色編號,只不過是在URL中傳遞。Spring MVC 也提供了支持。

  須要經過@RequestMapping註解和@PathVariable註解協做完成。其中,

  @RequestMapping("/getRole/{id}")中的{id}表示處理器須要接受一個由URL組成的參數,且參數名稱爲id

  @PathVariable("id")的意思是獲取定義在@RequestMapping中參數名稱爲id的參數,這樣就能夠在方法內獲取這個參數了

  而後經過劫色服務類獲取角色對象,並將其綁定到視圖中,將視圖設置爲JSON視圖。

    //注入角色服務對象
    @Autowired
    RoleService roleService;

    //{id}表明接收一個參數
    @RequestMapping("/getRole/{id}")
    //註解@PathVariable表示從URL的請求地址中獲取參數
    public ModelAndView pathVariable(@PathVariable("id") Long id)  {
        Role role = roleService.getRole(id);
        ModelAndView mv = new ModelAndView();
        //綁定數據模型
        mv.addObject(role);
        //設置爲JSON視圖
        mv.setView(new MappingJackson2JsonView());
        return mv;
    }

  測試結果:

  

  

  4.傳遞JSON參數

  假如要傳遞更多的參數。例如,對於查詢參數,假設還有開始行start和限制返回大小的limit,那麼加上roleName和note,就有了4個參數。

  首先定義分頁參數POJO類PageParams類:

package com.ssm.chapter14.pojo;

public class PageParams {
    private int start;
    private int limit;

  /*getter and setter*/
}

  而後,在原來的RoleParams類中增長一個PageParams類的對象,即:

package com.ssm.chapter14.pojo;

public class RoleParams {
    private String roleName;
    private String note;

    private PageParams pageParams = null;// 分頁參數

  /*getter and setter*/
}

  這樣,查詢參數和分頁參數就均可以被傳遞了。這時,爲了模擬傳遞過程,在params.jsp中增長JavaScript腳本代碼:

    /** 傳遞JSON**/
    $(document).ready(function() {
        //JSON參數和類RoleParams一一對應
        var data = {
            //角色查詢參數
            roleName : 'role',
            note : 'note',
            //分頁參數
            pageParams : {
                start : 0,
                limit : 4
            }
        }
        //Jquery的post請求
        $.post({
            url : "./params/findRoles.do",
            //此處須要告知傳遞參數類型爲JSON,不能缺乏
            contentType : "application/json",
            //將JSON轉化爲字符串傳遞
            data : JSON.stringify(data),
            //成功後的方法
            success : function(result) {
            }
        });
    });

  與之對應的findRoles方法:首先傳遞的JSON數據須要和對應參數的POJO保持一致。其次,在請求的時候須要告知請求的參數類型爲JSON。最後,傳遞的參數是一個字符串,而不是一個JSON,因此須要將JSON轉換成字符串。而後,經過@RequestBody註解,就能夠將和JavaScript代碼中對應的POJO類對象roleParams傳遞進去。

    @RequestMapping("/findRoles")
    public ModelAndView findRoles(@RequestBody RoleParams roleParams) {
        List<Role> roleList = roleService.findRoles(roleParams);
        ModelAndView mv = new ModelAndView();
        //綁定模型
        //mv.addObject(roleList);
        //設置爲JSON視圖
        //mv.setView(new MappingJackson2JsonView());
        return mv;
    }

  與之對應的Mapper中的配置:

    <select id="findRoles" parameterType="com.ssm.chapter15.pojo.RoleParams"
        resultType="com.ssm.chapter14.pojo.Role">
        select id, role_name as roleName, note from t_role
        <where>
            <if test="roleName != null">
                and role_name like concat('%', #{roleName}, '%')
            </if>
            <if test="note != null">
                and note like concat('%', #{note}, '%')
            </if>
        </where>
        limit #{pageParams.start}, #{pageParams.limit}
    </select>

  另外,還須要將原來的params.jsp文件中的action配置刪除才能進行正確的測試:

 

  5.接收列表數據和表單序列化

  (1)傳遞數組給控制器,進行一次性刪除多個角色的操做

  查看刪除前的數據庫:

mysql> select * from t_role;
+----+-------------+--------+
| id | role_name   | note   |
+----+-------------+--------+
|  1 | role_name_1 | note_1 |
|  2 | role_name_2 | note_2 |
|  3 | role_name_3 | note_3 |
|  4 | role_name_4 | note_4 |
|  5 | role_name_5 | note_5 |
+----+-------------+--------+
5 rows in set (0.00 sec)

  jsp中對應的應該添加的JavaScript代碼:

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
    "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>參數</title>

<!-- 加載Query文件-->
<script type="text/javascript"
    src="https://code.jquery.com/jquery-3.2.0.js"></script>

<script type="text/javascript">
    /**傳遞數組**/
    $(document).ready(function() {
        //刪除角色數組
        var idList = [ 1, 2, 3 ];
        //jQuery的post請求
        $.post({
            url : "./params/deleteRoles.do",
            //將JSON轉化爲字符串傳遞
            data : JSON.stringify(idList),
            //指定傳遞數據類型,不可缺乏
            contentType : "application/json",
            //成功後的方法
            success : function(result) {
            }
        });
    });
</script>
</head>
</html>

  與之對應的deleteRoles方法:這裏的@RequestBody List<Long> idList表示要求Spring MVC 將傳遞過來的JSON數組數據,轉換爲對應的Java集合類型。

    @RequestMapping("/deleteRoles")
    public ModelAndView deleteRoles(@RequestBody List<Long> idList) {
        ModelAndView mv = new ModelAndView();
        //刪除角色
        int total = roleService.deleteRoles(idList);
        System.out.println(total);
        //綁定視圖
//        mv.addObject("total", total);
        //JSON視圖
//        mv.setView(new MappingJackson2JsonView());
        return mv;
    }

  roleServicedeleteRoles方法的實現:

    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
    public int deleteRoles(List<Long> idList) {
        int count = 0;
        for (Long id : idList) {
            count += roleDao.deleteRole(id);
        }
        return count;
    }

  對應映射器Mapper中deleteRoles的配置:

    <delete id="deleteRole" parameterType="long">
        delete from t_role where
        id = #{id}
    </delete>

  執行http://localhost:8080/Chapter14/deleteRoles.jsp後,控制檯輸出:3,而且數據庫的結果爲:

mysql> select * from t_role;
+----+-------------+--------+
| id | role_name   | note   |
+----+-------------+--------+
|  4 | role_name_4 | note_4 |
|  5 | role_name_5 | note_5 |
+----+-------------+--------+
2 rows in set (0.00 sec)

  (2)新增多個角色

  同理addRole.jsp的內容是:

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
    "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>參數</title>

<!-- 加載Query文件-->
<script type="text/javascript"
    src="https://code.jquery.com/jquery-3.2.0.js"></script>

<script type="text/javascript">
    $(document).ready(function () {
        //新增角色數組
        var roleList = [
            {roleName: 'role_name_1', note: 'note_1'},
            {roleName: 'role_name_2', note: 'note_2'},
            {roleName: 'role_name_3', note: 'note_3'}
        ];
        //jQuery的post請求
        $.post({
            url: "./params/addRoles.do",
            //將JSON轉化爲字符串傳遞
            data: JSON.stringify(roleList),
            contentType: "application/json",
            //成功後的方法
            success: function (result) {
            }
        });
    });
</script>
</head>
</html>

  與之對應的視圖處理器中的addRoles方法:經過@RequestBody註解來獲取對應的角色列表參數,這樣就能夠在控制器中經過@ResponseBody將對應的JSON數據轉換成對象列表。

    @RequestMapping("/addRoles")
    public ModelAndView addRoles(@RequestBody List<Role> roleList) {
        ModelAndView mv = new ModelAndView();
        // 新增
        int total = roleService.insertRoles(roleList);
        System.out.println(total);
        //綁定視圖
//        mv.addObject("total", total);
        //JSON視圖
//        mv.setView(new MappingJackson2JsonView());
        return mv;
    }

  roleServiceinsertRoles方法:

    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
    public int insertRoles(List<Role> roleList) {
        int count = 0;
        for (Role role : roleList) {
            count += roleDao.insertRole(role);
        }
        return count;
    }

  MyBatis映射器Mapper中的insertRoles配置:

    <insert id="insertRole"
        parameterType="com.ssm.chapter14.pojo.Role" keyProperty="id"
        useGeneratedKeys="true">
        insert into t_role (role_name, note) value(#{roleName}, #{note})
    </insert>

  在瀏覽器中輸入http://localhost:8080/Chapter14/addRoles.jsp而後查看數據庫的結果爲:

mysql> select * from t_role;
+----+-------------+--------+
| id | role_name   | note   |
+----+-------------+--------+
|  4 | role_name_4 | note_4 |
|  5 | role_name_5 | note_5 |
|  6 | role_name_1 | note_1 |
|  7 | role_name_2 | note_2 |
|  8 | role_name_3 | note_3 |
+----+-------------+--------+
5 rows in set (0.00 sec)

  (3)經過表單序列化也能夠將表單數據轉換爲字符串傳遞到後臺,由於一些隱藏表單須要必定的計算,因此咱們也須要在用戶點擊提交按鈕後,經過序列化去提交表單。

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>參數</title>
<!-- 加載Query文件-->
<script type="text/javascript"
    src="https://code.jquery.com/jquery-3.2.0.js">  
</script>
<script type="text/javascript">
    $(document).ready(function() {
        $("#commit").click(function() {
            var str = $("form").serialize();
            //提交表單
            $.post({
                url : "./params/commonParamPojo2.do",
                //將form數據序列化,傳遞給後臺,則將數據以roleName=xxx&&note=xxx傳遞
                data : $("form").serialize(),
                //成功後的方法
                success : function(result) {
                }
            });
        });
    });
</script>
</head>
<body>
    <form id="form">
        <table>
            <tr>
                <td>角色名稱</td>
                <td><input id="roleName" name="roleName" value="" /></td>
            </tr>
            <tr>
                <td>備註</td>
                <td><input id="note" name="note" /></td>
            </tr>
            <tr>
                <td></td>
                <td align="right"><input id="commit" type="button" value="提交" /></td>
            </tr>
        </table>
    </form>
</body>
</html>

  對應的commonParamPojo2方法:從表單獲取數據後,點擊提交按鈕,就會把數據在後臺打印出來。

    @RequestMapping("/commonParamPojo2")
    public ModelAndView commonParamPojo2(String roleName, String note) {
        System.out.println("roleName =>" + roleName);
        System.out.println("note =>" + note);
        ModelAndView mv = new ModelAndView();
        // mv.setViewName("index");
        return mv;
    }

   這裏須要說明的是,jquery中的$.post方法中,在執行完方法後,是沒法跳轉到Spring MVC 返回的ModelAndView類型的mv頁面的。具體能夠了解Ajax的內容。

 

  2、重定向

  經過以前的例子,咱們知道,能夠showRoleJsonInfo方法能夠接收三個參數,而後就能夠將這些參數轉化爲視圖,經過JSON的形式展現在頁面上。

    @RequestMapping("/showRoleJsonInfo")
    public ModelAndView showRoleJsonInfo(Long id, String roleName, String note) {
        ModelAndView mv = new ModelAndView();
        mv.setView(new MappingJackson2JsonView());
        mv.addObject("id", id);
        mv.addObject("roleName", roleName);
        mv.addObject("note", note);
        return mv;
    }

  例如:瀏覽器中輸入參數,就能夠將參數信息轉化成視圖,而後展現出來。

  

  可是,若是想要實現:每當新增一一個角色信息時,須要其將數據以JSON視圖的形式展現給請求者。

  實現方法是:在數據保存到數據庫後,由數據庫返回角色編號,再將角色信息傳遞showRoleJsonInfo方法,就能夠展現JSON視圖給請求者了。

  1.在視圖控制器中新增重定向功能的方法:這裏須要注意的是,在執行完roleService.insertRole(role);語句後,插入數據庫的id會回填到role對象中。

  而後經過返回字符串類型的"redirect:./showRoleJsonInfo.do"來進行重定向,其中若是字符串中帶有「redirect」,那麼就會認爲是一個重定向。

    @RequestMapping("/addRole")
    //Model爲重定向數據模型,Spring MVC會自動初始化它
    public String addRole(Model model, String roleName, String note) {
        Role role = new Role();
        role.setRoleName(roleName);
        role.setNote(note);
        //插入角色後,會回填角色編號
        roleService.insertRole(role);
        //綁定重定向數據模型
        model.addAttribute("roleName", roleName);
        model.addAttribute("note", note);
        model.addAttribute("id", role.getId());
        return "redirect:./showRoleJsonInfo.do";
    }

  只須要指定roleName和note參數便可,

  

  2.不只能夠經過字符串來實現重定向,還能夠經過返回視圖來實現重定向

    @RequestMapping("/addRole2")
    //ModelAndView對象Spring MVC會自定初始化它
    public ModelAndView addRole2(ModelAndView mv, String roleName, String note) {
        Role role = new Role();
        role.setRoleName(roleName);
        role.setNote(note);
        //插入角色後,會回填角色編號
        roleService.insertRole(role);
        //綁定重定向數據模型
        mv.addObject("roleName", roleName);
        mv.addObject("note", note);
        mv.addObject("id", role.getId());
        mv.setViewName("redirect:./showRoleJsonInfo.do");
        return mv;
    }

  3.上面的例子都是傳遞簡單的String類型的參數,有些時候須要傳遞角色POJO,而不是一個個字段的傳遞。

  修改showRoleJsonInfo方法成showRoleJsonInfo2,能夠以JSON的形式展現role對象

    @RequestMapping("/showRoleJsonInfo2")
    public ModelAndView showRoleJsonInfo(Role role) {
        ModelAndView mv = new ModelAndView();
        mv.setView(new MappingJackson2JsonView());
        mv.addObject("role", role);
        return mv;
    }

  可是,在URL重定向的過程當中,並不能有效傳遞對象,由於HTTP的重定向參數是以字符串傳遞的。爲了解決這個問題,可使用Spring MVC 的flash屬性,即RedirectAttributes參數,

    @RequestMapping("/addRole3")
    //RedirectAttribute對象Spring MVC會自動初始化它
    public String addRole3(RedirectAttributes ra, Role role) {
        //插入角色後,會回填角色編號
        roleService.insertRole(role);
        //綁定重定向數據模型
        ra.addFlashAttribute("role", role);
        return "redirect:./showRoleJsonInfo2.do";
    }

  這樣就能傳遞POJO對象到下一個地址了,Spring MVC 的實現方式是:將數據保存在Session中,重定向後就會將其消除,這樣就能傳遞數據給下一個地址了。

  

 

  3、保存並獲取屬性參數(request、session、cookie和HTTP header)

  Spring MVC 能夠經過一些註解從HTTP的request對象或者Session對象中獲取數據。

  1.@RequestAttribute註解能夠從HTTP的request對象中取出請求屬性,只是範圍是在一次請求中存在。

  request.jsp的內容以下,

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
    "http://www.w3.org/TR/html4/loose.dtd">
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Insert title here</title>
    </head>
    <body>
       <%
       //設置請求屬性
       request.setAttribute("id", 11L);
       //轉發給控制器
       request.getRequestDispatcher("./attribute/requestAttribute.do").forward(request, response);
       %>
    </body>
</html>

  對應的控制器中的sessionAttribute方法:jsp文件中設置request的屬性id爲1,而後進行了轉發控制器,這樣將有對應的控制器去處理業務邏輯,而後由AttributeController控制器去處理它,經過(@RequestAttribute("id") Long id)將jsp中設置的request屬性獲取到。

  @RequestAttribute和@RequestParam註解同樣,默認是不能爲空的,不然系統會拋出異常。可是,它們都有一個required配置項,只要配置成false,參數就能夠爲空了。

package com.ssm.chapter14.controller;

@Controller
@RequestMapping("/attribute")
public class AttributeController {
    
    @Autowired
    private RoleService roleService = null;
    
    @RequestMapping("/requestAttribute")
    public ModelAndView reqAttr(@RequestAttribute("id") Long id) {
        ModelAndView mv = new ModelAndView();
        Role role = roleService.getRole(id);
        mv.addObject("role", role);
        mv.setView(new MappingJackson2JsonView());
        return mv;
    }
}

  在瀏覽器中輸入http://localhost:8080/Chapter14/request.jsp,就能夠獲取jsp中定義的request屬性值,並跳轉到指定的界面。

  

 

  2.@SessionAttribute和@SessionAttributes

  這兩個註解都和HTTP的會話對象有關,在瀏覽器和服務器保持聯繫的時候HTTP會建立一個會話對象,這樣可讓咱們在和服務器會話期間經過它讀/寫會話對象的屬性,緩存必定的數據信息。

  (1)經過@SessionAttributes設置會話屬性

  @SessionAttributes註解只能對類進行標註,不能對方法或者參數註解。它能夠配置屬性名稱或者屬性類型。它的做用是當這個類被註解後,Spring MVC 執行完控制器的邏輯後,將數據模型中對應的屬性名稱或者屬性類型保存到HTTP的Session對象中。  

  sessionAttrs方法中,經過首先根據傳遞進來的id,經過查詢獲得role對象,而因爲AttributeController類中經過@SessionAttributes設置了名稱和類型,所以,id和role對象都會保存到Session對象中。

  (2)經過@SessionAttribute獲取會話屬性

  當瀏覽器中輸入/attribute/sessionAttributes.do?id=1時,id和role就會被保存到Session對象中,能夠在sessionAttribute.jsp中經過session.getAttribute("role")和session.getAttribute("id")兩個方法獲取到以前保存進去的role和id。

package com.ssm.chapter14.controller;

@Controller
@RequestMapping("/attribute")
// 能夠配置數據模型的名稱和類型,二者取或關係
@SessionAttributes(names ={"id"}, types = { Role.class })
public class AttributeController {
    
    @Autowired
    private RoleService roleService = null;
   
    @RequestMapping("/sessionAttributes")
    public ModelAndView sessionAttrs(Long id) {
        ModelAndView mv = new ModelAndView();
        Role role = roleService.getRole(id);
        //根據類型,session將會保存角色信息
        mv.addObject("role", role); 
        //根據名稱,session將會保存id
        mv.addObject("id", id);
        //視圖名稱,定義跳轉到一個JSP文件上
        mv.setViewName("sessionAttribute");
        return mv;
    }
    
    @RequestMapping("/sessionAttribute")
    public ModelAndView sessionAttr(@SessionAttribute("id") Long id) {
        ModelAndView mv = new ModelAndView();
        Role role = roleService.getRole(id);
        mv.addObject("role", role);
        mv.setView(new MappingJackson2JsonView());
        return mv;
    }
}

  和以前的設置request屬性值的jsp腳本相似:編寫設置Session屬性的jsp腳本,而後就會跳轉到sessionAttribute控制器去處理。

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
    "http://www.w3.org/TR/html4/loose.dtd">
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>session</title>
    </head>
    <body>
        <%
            //設置Session屬性
            session.setAttribute("id", 7L);
            //執行跳轉
            response.sendRedirect("./attribute/sessionAttribute.do");
        %>
    </body>
</html>

 

  3.@CookieValue和@RequestHeader

  能夠經過@CookieValue和@RequestHeader註解分別從Cookie和HTTP Header中讀取信息。

    @RequestMapping("/getHeaderAndCookie")
    public String testHeaderAndCookie(
        @RequestHeader(value="User-Agent", required = false, defaultValue = "attribute")
             String userAgent,
        @CookieValue(value = "JSESSIONID", required = true, defaultValue = "MyJsessionId") 
             String jsessionId) {
        System.out.println("User-Agent:" + userAgent);
        System.out.println("JSESSIONID:" + jsessionId);
        return "index";
    }

 

  4、攔截器

  攔截器是Spring MVC 中強大的組件,它能夠在進入處理器以前作一些操做,或者在處理器完成後進行操做,甚至是在渲染視圖後進行操做。

  回顧Spring MVC 執行流程:Spring MVC 會在啓動期間就經過@RequestMapping的註解解析URL和處理器的對應關係,在運行的時候經過請求找到對應的HandlerMapping,而後構建一個執行的責任鏈對象即HandlerExecutionChain對象,而HandlerExecutionChain對象中包含了handler對象,這個對象指向了控制器所對應的方法和攔截器。

  1.定義攔截器

  Spring 要求處理器的攔截器都要實現接口org.springframework.web.servlet.HandlerInterceptor,這個接口定義了3個方法:

  • preHandle方法:在處理器以前執行的前置方法,這樣 Spring MVC 能夠在進入處理器前處理一些方法。它將返回一個boolean值,會影響到後面 Spring MVC 的流程。
  • postHandle方法:在處理器以後執行的後置方法,處理器的邏輯完成後運行它。
  • afterCompletion方法:不管是否產生異常都會在渲染視圖後執行的方法。

  2.攔截器的執行流程:

  

  須要注意的是,當前置方法返回false時,就不會再執行後面的邏輯了。

  3.開發攔截器

  Spring MVC 中攔截器的設計:

  

  其中,Spring MVC 還提供了公共攔截器HandlerInterceptorAdapter,當只想實現3個攔截器方法中的一到兩個時,能夠繼承這個公共攔截器,而後按照須要重寫須要的方法就能夠了。

  建立角色攔截器RoleInterceptor類,其繼承了HandlerInterceptorAdapter公共攔截器類:

package com.ssm.chapter15.interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

public class RoleInterceptor extends HandlerInterceptorAdapter {
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        System.err.println("preHandle");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        System.err.println("postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, 
             Object handler, Exception ex) throws Exception {
        System.err.println("afterCompletion");
    }

}

  而後還須要在Spring MVC 的配置文件dispatcher-servlet.xml中進行配置:須要聲明RoleInterceptor所在的包和類的全限定名

    <mvc:interceptors>
         
        <mvc:interceptor>
            <mvc:mapping path="/role/*.do" />
            <bean class="com.ssm.chapter14.interceptor.RoleInterceptor" />
        </mvc:interceptor>

    </mvc:interceptors>

  4.多個攔截器執行的順序

  假設如今有3個攔截器,在各自的方法中,分別打印攔截器方法名+編號(1,2,3)

    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/role/*.do" />
            <bean class="com.ssm.chapter14.interceptor.RoleInterceptor1" />
        </mvc:interceptor>
        <mvc:interceptor>
            <mvc:mapping path="/role/*.do" />
            <bean class="com.ssm.chapter14.interceptor.RoleInterceptor2" />
        </mvc:interceptor>
        <mvc:interceptor>
            <mvc:mapping path="/role/*.do" />
            <bean class="com.ssm.chapter14.interceptor.RoleInterceptor3" />
        </mvc:interceptor>
    </mvc:interceptors>

  (1)加入三個攔截器的preHandle方法的返回值都是true,那麼會先從第一個攔截器開始進入前置方法,前置方法是順序執行的,然後置和完成方法則是逆序運行的,這裏參考攔截器執行流程。

preHandle1
preHandle2
preHandle3
業務邏輯
postHandle3
postHandle2
postHandle1
afterCompletion3
afterCompletion2
afterCompletion1

  (2)加入第二個攔截器的preHandle方法的返回值爲false,那麼後面的攔截器的preHandle方法都不會運行了,即後面的全部攔截器都不起做用。

preHandle1
preHandle2
afterCompletion1

 

  5、驗證表單

  在實際工做中,獲得數據後的第一步就是檢驗數據的正確性,若是存在錄入上的問題,通常會經過註解校驗,發現錯誤後返回給用戶,可是對於一些邏輯上的錯誤,好比購買金額=購滿數量×單價,這樣的規則就很難使用註解方式進行驗證了,這個時候可使用驗證器(Validator)規則去驗證。

  1.使用JSR 303註解驗證輸入內容

  Spring提供了對Bean的功能校驗,經過註解代表哪一個Bean須要進行驗證以及驗證內容:

  

  首先,新建一個POJO類,而且根據實際須要在字段上分別進行標註:

package com.ssm.chapter14.pojo;public class Transaction {
    // 產品編號
    @NotNull // 不能爲空
    private Long productId;

    // 用戶編號
    @NotNull // 不能爲空
    private Long userId;

    // 交易日期
    @Future // 只能是未來的日期
    @DateTimeFormat(pattern = "yyyy-MM-dd") // 日期格式化轉換
    @NotNull // 不能爲空
    private Date date;

    // 價格
    @NotNull // 不能爲空
    @DecimalMin(value = "0.1") // 最小值0.1元
    private Double price;

    // 數量
    @Min(1) // 最小值爲1
    @Max(100) // 最大值
    @NotNull // 不能爲空
    private Integer quantity;

    // 交易金額
    @NotNull // 不能爲空
    @DecimalMax("500000.00") // 最大金額爲5萬元
    @DecimalMin("1.00") // 最小交易金額1元
    private Double amount;

    // 郵件
    @Pattern(// 正則式
            regexp = "^([a-zA-Z0-9]*[-_]?[a-zA-Z0-9]+)*@"
                    + "([a-zA-Z0-9]*[-_]?[a-zA-Z0-9]+)+[\\.][A-Za-z]{2,3}([\\.][A-Za-z]{2})?$",
            // 自定義消息提示
            message = "不符合郵件格式")
    private String email;

    // 備註
    @Size(min = 0, max = 256) // 0到255個字符
    private String note;

  /**************************getter and setter*****************************************/
}

  而後建立一個表單,其中action指定了提交事後應該調用的控制器視圖:

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
    "http://www.w3.org/TR/html4/loose.dtd">
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>validate</title>
    </head>
    <body>
        <form action = "./validate/annotation.do"> 
            <table>
                <tr>
                    <td>產品編號:</td>
                    <td><input name="productId" id="productId"/></td>
                </tr>
          ...
                <tr><td colspan="2" align="right"> <input type="submit" value="提交"/> </tr>
            </table>
        <form>
    </body>
</html>

  而後定義一個當Bean的檢驗失敗後的處理器,@Valid Transaction trans中使用@Valid註解代表這個Bean將會被檢驗,而另一個Errors的參數則是用於保存是否存在錯誤信息的。

package com.ssm.chapter14.controller;

import

@Controller
@RequestMapping("/validate")
public class ValidateController {

    @RequestMapping("/annotation")
    public ModelAndView annotationValidate(@Valid Transaction trans, Errors errors) {
        // 是否存在錯誤
        if (errors.hasErrors()) {
            // 獲取錯誤信息
            List<FieldError> errorList = errors.getFieldErrors();
            for (FieldError error : errorList) {
                // 打印字段錯誤信息
                System.err.println("fied :" + error.getField() + "\t" + "msg:" + error.getDefaultMessage());
            }
        }
        ModelAndView mv = new ModelAndView();
        mv.setViewName("index");
        return mv;
    }
}

  測試結果:

   

  打印結果:

fied :quantity    msg:最大不能超過100
fied :productId    msg:不能爲null
fied :date    msg:須要是一個未來的時間
fied :userId    msg:不能爲null
fied :email    msg:不符合郵件格式
fied :price    msg:必須大於或等於0.1

 

  2.使用驗證器Validator規則驗證輸入內容

   有時候除了簡單的輸入格式、非空型等校驗,也須要必定的業務檢驗,Spring 提供了Validator接口來實現校驗,它將在進入控制器邏輯以前對參數的合法性進行檢驗。

  首先定義驗證器,必須實現Validator接口:首先驗證是不是Transaction對象,若是是,就驗證交易金額是否等於單價×數量

package com.ssm.chapter14.validator;

import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

import com.ssm.chapter14.pojo.Transaction;

public class TransactionValidator implements Validator {
    @Override
    public boolean supports(Class<?> clazz) {
        //判斷驗證是否爲Transaction,若是是則進行驗證
        return Transaction.class.equals(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        Transaction trans = (Transaction) target;
        //求交易金額和價格×數量的差額
        double dis = trans.getAmount() - (trans.getPrice() * trans.getQuantity());
        //若是差額大於0.01,則認爲業務錯誤
        if (Math.abs(dis) > 0.01) {
            //加入錯誤信息
            errors.rejectValue("amount", null, "交易金額和購買數量與價格不匹配");
        }
    }
}

  而後,還須要將驗證器TransactionValidator和控制器綁定起來,Spring MVC 提供了@InitBinder註解,經過這個註解就能夠將驗證器和控制器綁定到一塊兒了:

package com.ssm.chapter14.controller;

import
@Controller
@RequestMapping("/validate")
public class ValidateController {

    @InitBinder
    public void initBinder(DataBinder binder) {
        // 數據綁定器加入驗證器
        binder.setValidator(new TransactionValidator());
    }

    @RequestMapping("/validator")
    public ModelAndView validator(@Valid Transaction trans, Errors errors) {
        // 是否存在錯誤
        if (errors.hasErrors()) {
            // 獲取錯誤信息
            List<FieldError> errorList = errors.getFieldErrors();
            for (FieldError error : errorList) {
                // 打印字段錯誤信息
                System.err.println("fied :" + error.getField() + "\t" + "msg:" + error.getDefaultMessage());
            }
        }
        ModelAndView mv = new ModelAndView();
        mv.setViewName("index");
        return mv;
    }
}

  最後,還須要修改表單的action項爲新的當前的控制器<form action = "./validate/validator.do">,而後進行測試:在控制檯打印出:

fied :amount    msg:交易金額和購買數量與價格不匹配

  還須要注意的是,JSR 303註解方式和驗證器方式不能同時使用,不過能夠在使用JSR 303註解額方式獲得基本的檢驗信息後,再使用本身的方法進行驗證。

 

  6、數據模型

  視圖是業務處理後展示給用戶的內容,不過通常伴隨着業務處理返回的數據,用來給用戶查看。Spring MVC 的流程是從控制器獲取數據後,會裝載數據到數據模型和視圖中,而後將視圖名稱轉發到視圖解析器中,經過解析器解析後獲得最終視圖,最後將數據模型渲染到視圖中,展現最終的結果給用戶。

  以前一直使用ModelAndView來定義視圖類型,包括JSON視圖,也用它來加載數據模型。ModelAndView有一個類型爲ModelMap的屬性model,而ModelMap繼承了LinkedHashMap<String, Object>,所以它能夠存放各類鍵值對,爲了進一步定義數據模型功能,Spring 還建立了類ExtendedModelMap,這個類實現了數據模型定義的Model 接口,而且還在此基礎上派生了關於數據綁定的類---BindAwareModelMap:

  

  在控制器的方法中,能夠把ModelAndView、Model、ModelMap做爲參數。在Spring MVC 運行的時候,會自動初始化它們,所以能夠選擇 ModelMap 或者 Model 做爲數據模型。

    @RequestMapping(value = "/getRoleByModelMap", method = RequestMethod.GET)
    public ModelAndView getRoleByModelMap(@RequestParam("id") Long id, ModelMap modelMap) {
        Role role = roleService.getRole(id);
        ModelAndView mv = new ModelAndView();
        mv.setViewName("roleDetails");
        modelMap.addAttribute("role", role);
        return mv;
    }

    @RequestMapping(value = "/getRoleByModel", method = RequestMethod.GET)
    public ModelAndView getRoleByModel(@RequestParam("id") Long id, Model model) {
        Role role = roleService.getRole(id);
        ModelAndView mv = new ModelAndView();
        mv.setViewName("roleDetails");
        model.addAttribute("role", role);
        return mv;
    }

    @RequestMapping(value = "/getRoleByMv", method = RequestMethod.GET)
    public ModelAndView getRoleByMv(@RequestParam("id") Long id, ModelAndView mv) {
        Role role = roleService.getRole(id);
        mv.setViewName("roleDetails");
        mv.addObject("role", role);
        return mv;
    }

  在瀏覽器直接輸入http://localhost:8080/Chapter14/role/getRoleByModel.do?id=8也會獲得正確的跳轉視圖。

  事實上,不管是 Model 仍是 ModelMap,Spring MVC 建立的是一個BindingAwareModelMap 實例,而 BindingAwareModelMap 是一個繼承了 ModelMap 實現了 Model 接口的類,因此就有了相互轉換的功能。

 

  7、視圖和視圖解析器

  視圖是展現給用戶的內容,而在此以前,要經過控制器獲得對應的數據模型,若是是非邏輯視圖,則不會通過視圖解析器定位視圖,而是直接將數據模型渲染便結束了;而邏輯視圖則是要對其進一步解析,以定位真實視圖,這就是視圖解析器的做用了。而視圖則是把從控制器查詢回來的數據模型進行渲染,以展現給請求者查看。

  1.視圖  

  在請求以後,Spring MVC 控制器獲取了對應的數據,綁定到數據模型中,那麼視圖就能夠展現數據模型的信息了。

  視圖又分爲邏輯視圖和非邏輯視圖,好比MappingJackson2JsonView是一個非邏輯視圖,它的目的就是將數據模型轉換爲一個JSON視圖,展示給用戶,無須對視圖名字再進行下一步的解析。這其中,因爲非邏輯視圖在沒有視圖解析器的狀況下就能夠進行渲染,最終將其綁定的數據模型轉換爲JSON數據。

  

  Spring MVC 中定義了多種視圖,它們都須要實現視圖接口--View,而View接口中主要有方法getContentType和render,其中getContentType方法表示返回一個字符串,代表給用戶什麼類型的文件響應,可使HTML、JSON、PDF等,而render方法則是一個渲染視圖的方法,其參數包括其數據模型Model,HTTP請求對象和HTTP響應對象。當控制器返回ModelAndView 的時候,視圖解析器就會解析它,而後將數據模型傳遞給 render 方法,這樣就能將數據模型渲染成各類視圖,而後經過HTTP請求兌現和HTTP響應對象展現給用戶了。

public interface View {
    ...
    String getContentType();
    void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throw Exception;  
}

  而InternalResourceView是一個邏輯視圖,對於邏輯視圖而言它須要一個視圖解析器,視圖解析器的做用也就是,經過前綴和後綴加上視圖名稱就可以找到對應的JSP文件,而後把數據模型渲染到JSP文件中,這樣便能展示視圖給用戶了。

    <!-- 定義視圖解析器 -->
    <!-- 找到Web工程/WEB-INF/JSP文件夾,且文件結尾爲jsp的文件做爲映射 -->
    <bean id="viewResolver"
        class="org.springframework.web.servlet.view.InternalResourceViewResolver"
        p:prefix="/WEB-INF/jsp/" p:suffix=".jsp" />

 

  2.視圖解析器

  對於邏輯視圖而言,把視圖名稱轉換爲邏輯視圖是一個必備的過程,InternalResourceView會加載到 Spring MVC 的視圖解析器列表中去,當返回ModelAndView的時候,Spring MVC 就會在視圖解析器列表中遍歷,找到對應的視圖解析器去解析視圖。

  

  視圖解析器的定義以下,其中viewName表示傳遞進來的視圖名稱,而Locale類型的locale是國際化對象。

public interface ViewResolver {
   View resolveViewName(String viewName, Local locale) throws Exception;
}

  有時候在控制器中並無返回一個 ModelAndView, 而是隻返回了一個字符串,它也可以渲染視圖,由於視圖解析器生成了對應的視圖:

    @RequestMapping(value = "/index", method = RequestMethod.GET)
    public String index(@RequestParam("id") Long id, ModelMap model) {
        Role role = roleService.getRole(id);
        model.addAttribute("role", role);
        return "roleDetails";
    }

 

  3.實例:Excel視圖的使用

  需求:用戶經過輸入URL,而後經過GET請求下載到保存了數據庫中全部的查詢記錄的Excel表

  (1)選擇視圖類

  對於Excel而言,Spring MVC 所推薦的是使用AbstractXlsView,根據視圖類的關係能夠看到,AbstractXlsView繼承了AbstractView類,而AbstractView類又實現了View接口。

  因爲AbstractXlsView是抽象類,所以須要實現其中的抽象方法buildExcelDocument:

protected abstract void buildExcelDocument(Map<String, Object> model, Workbook workbook, HttpServletRequest request, HttpServletResponse response) throws Exception);

  其中,model表示數據模型,workbook表示POI workbook對象,這個方法的主要任務是建立一個Workbook,它要用到POI的API。

  (2)自定義導出接口

  目前的需求是導出全部就是信息,可是未來或許還須要增長其餘的導出功能,所以,先定義一個接口,這個接口的功能是讓開發者自定義生成Excel的規則。

package com.ssm.chapter14.view;

import java.util.Map;
import org.apache.poi.ss.usermodel.Workbook;
public interface ExcelExportService {
    
    /***
     *  生成exel文件規則
     * @param model 數據模型
     * @param workbook excel workbook
     */
    public void makeWorkBook(Map<String, Object> model, Workbook workbook);

}

  (3)定義Excel視圖

  即Excel視圖類:對於導出來講,還須要一個下載文件名稱,因此還須要定義一個fileName屬性。因爲該視圖不是一個邏輯視圖,因此無需視圖解析器也能夠運行它。

  對於buildExcelDocument方法來講,其最後一行纔是關鍵:excelExpService.makeWorkBook(model, workbook);,意思就是調用ExcelExportService接口的makeWorkBook方法使用自定義的規則進行Excel建立。便可以根據須要進行自定義生成Excel的規則。

package com.ssm.chapter14.view;
import
public class ExcelView extends AbstractXlsView { // 文件名 private String fileName = null; // 導出視圖自定義接口 private ExcelExportService excelExpService = null; // 構造方法1 public ExcelView(ExcelExportService excelExpService) { this.excelExpService = excelExpService; } // 構造方法2 public ExcelView(String viewName, ExcelExportService excelExpService) { this.setBeanName(viewName); }
  /******** getter and setter **********/
@Override protected void buildExcelDocument(Map<String, Object> model, Workbook workbook, HttpServletRequest request, HttpServletResponse response) throws Exception { // 沒有自定義接口 if (excelExpService == null) { throw new RuntimeException("導出服務接口不能爲null!!"); } // 文件名不爲空,爲空則使用請求路徑中的字符串做爲文件名 if (!StringUtils.isEmpty(fileName)) { // 進行字符轉換 String reqCharset = request.getCharacterEncoding(); reqCharset = reqCharset == null ? "UTF-8" : reqCharset; fileName = new String(fileName.getBytes(reqCharset), "ISO8859-1"); // 設置下面文件名 response.setHeader("Content-disposition", "attachment;filename=" + fileName); } // 回調接口方法,使用自定義生成Excel文檔 excelExpService.makeWorkBook(model, workbook); } }

  (4)定義控制器

  其中ExcelView ev = new ExcelView(exportService());將獲得ExcelView類型的視圖ev,而後mv.addObject("roleList", roleList);加入數據模型,最後mv.setView(ev);將視圖設置爲ev。  

  ExcelView ev = new ExcelView(exportService());exportService()方法就是(5)中定義的ExcelExportService接口的實現類的方法。

    @RequestMapping(value = "/export", method = RequestMethod.GET)
    public ModelAndView export() {
        //模型和視圖
        ModelAndView mv = new ModelAndView();
        //Excel視圖,並設置自定義導出接口
        ExcelView ev = new ExcelView(exportService());    
        //文件名
        ev.setFileName("全部角色.xlsx");
        //設置SQL後臺參數
        RoleParams roleParams = new RoleParams();
        //限制1萬條
        PageParams page = new PageParams();
        page.setStart(0);
        page.setLimit(10000);
        roleParams.setPageParams(page);
        //查詢
        List<Role> roleList = roleService.findRoles(roleParams); 
        //加入數據模型
        mv.addObject("roleList", roleList);
        mv.setView(ev);
        return mv;
    }

  (5)定義ExcelExportService接口的實現:這裏是使用了Lambda表達式實現的,看起來比較高級。

    @SuppressWarnings({ "unchecked"})
    private ExcelExportService exportService() {
        //使用Lambda表達式自定義導出excel規則
        return (Map<String, Object> model, Workbook workbook) -> {
            //獲取用戶列表
            List<Role> roleList = (List<Role>) model.get("roleList");
            //生成Sheet
            Sheet sheet= workbook.createSheet("全部角色");
            //加載標題
            Row title = sheet.createRow(0);
            title.createCell(0).setCellValue("編號");
            title.createCell(1).setCellValue("名稱");
            title.createCell(2).setCellValue("備註");
            //便利角色列表,生成一行行的數據
            for (int i=0; i<roleList.size(); i++) {
                Role role = roleList.get(i);
                int rowIdx = i + 1;
                Row row = sheet.createRow(rowIdx);
                row.createCell(0).setCellValue(role.getId());
                row.createCell(1).setCellValue(role.getRoleName());
                row.createCell(2).setCellValue(role.getNote());
            }
        };
    }

  (6)測試:在瀏覽器中輸入:http://localhost:8080/Chapter14/role/export.do就能夠下載到一個名爲「全部角色.xlsx」的Excel文件,打開該文件,能夠看到正確顯示了數據庫中的記錄:

  

 

  8、上傳文件

  Spring MVC 爲上傳文件提供了良好的支持。首先 Spring MVC 的文件上傳是經過MultipartResolver處理的,對於MultipartResolver而言它只是一個接口,它有兩個實現類:CommonMultipartResovler和StandardMultipartResolver。其中,StandardMultipartResolver不須要引入任何第三方包便可實現。不管使用哪一個類,都須要配置一個MultipartResolver

  1.MultipartResolver配置

  (1)經過註解配置StandardMultipartResolver  

  對於StandardMultipartResolver來講,其構造方法沒有參數,所以很容易對其進行初始化。

    @Bean(name = "multipartResolver")
    public MultipartResolver initMultipartResolver() {
        return new StandardServletMultipartResolver();
    }

  可是,僅僅這樣配置是不夠的,還須要對上傳文件進行配置,例如限制單個文件的大小,設置上傳路徑等,爲了進行設置,能夠在Spring MVC 初始化的時候對MultipartResolver進行配置:

  若是須要經過Java配置 Spring MVC 的初始化,只須要繼承AbstractAnnotationConfigDispatcherServletInitializer 類就能夠了,經過繼承它就能夠進行註解配置了,這個類中能夠覆蓋customizeRegistration方法,它是一個用於初始化DispatcherServlet設置的方法。

package com.ssm.chapter15.config;

import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletRegistration.Dynamic;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    //Spring IoC容器配置
    @Override
    protected Class<?>[] getRootConfigClasses() {
        //能夠返回Spring的Java配置文件數組
        return new Class<?>[] {};
    }

    //DispatcherServlet的URI映射關係配置
    @Override
    protected Class<?>[] getServletConfigClasses() {
        //能夠返回Spring的Java配置文件數組
        return new Class<?>[] { WebConfig.class };
    }

    //DispatchServlet[修改成:DispatcherServlet]攔截請求匹配
    @Override
    protected String[] getServletMappings() {
        return new String[] { "*.do" };
    }
    
    /**
     * @param dynamic Servlet動態加載配置
     */
    @Override
    protected void customizeRegistration(Dynamic dynamic) {
        //文件上傳路徑
        String filepath = "d:/mvc/upload";
        //5MB
        Long singleMax = (long) (5*Math.pow(2, 20));
        //10MB
        Long totalMax = (long) (10*Math.pow(2, 20));
        //配置MultipartResolver,限制請求,單個文件5MB,總共文件10MB
        dynamic.setMultipartConfig(new MultipartConfigElement(filepath, singleMax, totalMax, 0));
    }

}

  其中,customizeRegistration方法中設置了MultipartResolver的屬性,包括文件路徑、單個文件大小、所有文件大小。

  (2)經過XML配置StandardMultipartResolver  

  還能夠經過在Web.xml中實現對MultipartResolver的初始化,而後經過XML或者註解生成一個AbstractAnnotationConfigDispatcherServletInitializer便可。

        <!--MultipartResolver參數 -->
        <multipart-config>
            <location>e:/mvc/uploads/</location>
            <max-file-size>5242880</max-file-size>
            <max-request-size>10485760</max-request-size>
            <file-size-threshold>0</file-size-threshold>
        </multipart-config>

  (3)經過註解配置CommonMultipartResovler,可是這種方式須要依賴於第三方的包

    @Bean(name = "multipartResolver")
    public MultipartResolver initCommonsMultipartResolver() {
        //文件上傳路徑
        String filepath = "d:/mvc/uploads";
        //5MB
        Long singleMax = (long) (5 * Math.pow(2, 20));
        //10MB
        Long totalMax = (long) (10 * Math.pow(2, 20));
        CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
        multipartResolver.setMaxUploadSizePerFile(singleMax);
        multipartResolver.setMaxUploadSize(totalMax);
        try {
            multipartResolver.setUploadTempDir(new FileSystemResource(filepath));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return multipartResolver;
    }

  2.提交上傳文件表單

  須要將enctype="multipart/form-data設置成這樣,不然 Spring MVC 會解析失敗。

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
"http://www.w3.org/TR/html4/loose.dtd">
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>文件上傳</title>
    </head>
    <body>
        <form method="post" action="./file/upload.do" enctype="multipart/form-data" >
            <input type="file" name="file" value="請選擇上傳的文件"/>
            <input type="submit" value="提交"/>     
        </form>
    </body>
</html>

  3.與之對應,須要有一個upload控制器方法

package com.ssm.chapter15.controller;

import 

@Controller
@RequestMapping("/file")
public class FileController implements ApplicationContextAware  {

    @RequestMapping("/upload")
    public ModelAndView upload(HttpServletRequest request) {
        // 進行轉換
        MultipartHttpServletRequest mhsr = (MultipartHttpServletRequest) request;
        // 得到請求上傳的文件
        MultipartFile file = mhsr.getFile("file");
        // 設置視圖爲JSON視圖
        ModelAndView mv = new ModelAndView();
        mv.setView(new MappingJackson2JsonView());
        // 獲取原始文件名
        String fileName = file.getOriginalFilename();
        // 目標文件
        File dest = new File(fileName);
        try {
            // 保存文件
            file.transferTo(dest);
            // 保存成功
            mv.addObject("success", true);
            mv.addObject("msg", "上傳文件成功");
        } catch (IllegalStateException | IOException e) {
            // 保存失敗
            mv.addObject("success", false);
            mv.addObject("msg", "上傳文件失敗");
            e.printStackTrace();
        }
        return mv;
    }
}

  4.測試

  項目目錄爲:

  

  其中,採用1(1)中的配置,然而首先須要建立目錄,d:/mvc/upload,而後在瀏覽器中輸入:http://localhost:8080/Chapter15.2/file.jsp

  選擇文件後,點擊提交,跳轉到下面的頁面,同時,d:/mvc/upload下也發現了以前選擇的文件:

  

  5.問題分析

  這裏會有一個問題,就是控制器中的upload方法的參數是HttpServletRequest request,這樣會形成API侵入,即調用了Servlet的API

    @RequestMapping("/upload")
    public ModelAndView upload(HttpServletRequest request) {....}

  解決方法是,將參數修改爲MultipartFile或者Part類對象,這樣作的好處是把代碼從Servlet API 中解放出來,這體現了Spring 的思惟,即高度的解耦合性。

  下面兩個方法都經過了測試。

    // 使用MultipartFile
    @RequestMapping("/uploadMultipartFile")
    public ModelAndView uploadMultipartFile(MultipartFile file) {
        // 定義JSON視圖
        ModelAndView mv = new ModelAndView();
        mv.setView(new MappingJackson2JsonView());
        // 獲取原始文件名
        String fileName = file.getOriginalFilename();
        file.getContentType();
        // 目標文件
        File dest = new File(fileName);
        try {
            // 保存文件
            file.transferTo(dest);
            mv.addObject("success", true);
            mv.addObject("msg", "上傳文件成功");
        } catch (IllegalStateException | IOException e) {
            mv.addObject("success", false);
            mv.addObject("msg", "上傳文件失敗");
            e.printStackTrace();
        }
        return mv;
    }

    // 使用Part
    @RequestMapping(value="/uploadPart", method=RequestMethod.POST)
    public ModelAndView uploadPart(Part file) {
        ModelAndView mv = new ModelAndView();
        mv.setView(new MappingJackson2JsonView());
        // 獲取原始文件名
        String fileName = file.getSubmittedFileName();
        try {
            // 保存文件
            file.write("d:/mvc/upload/" + fileName);
            mv.addObject("success", true);
            mv.addObject("msg", "上傳文件成功");
        } catch (IllegalStateException | IOException e) {
            mv.addObject("success", false);
            mv.addObject("msg", "上傳文件失敗");
            e.printStackTrace();
        }
        return mv;
    }
相關文章
相關標籤/搜索