Spring MVC(2)Spring MVC 组件开发

  一、控制器接收各类请求参数

  代码测试环境:

  接收各类参数的控制器--ParamsController

package com.ssm.chapter15.controller;

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

  先看一下目录结构:

  

  这里需要知道的知识点是,WebContent文件夹下的.jsp文件都可以通过http://localhost:8080/工程名/文件名.jsp直接访问。

  而WEB-INF里面的文件,必须通过Spring MVC 中的Controller控制器产生映射才能访问。

  1.接收普通请求参数

  params.jsp文件的内容如下,其中action="./params/commonParams.do"表示提交按钮按下后,跳转到action指定的页面。

<%@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中提交的参数。

  测试:首先输入访问表单,输入任意参数,并提交

  

  然后,正确跳转到./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的内容。

  二、重定向

  通过之前的例子,我们知道,可以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中,重定向后就会将其消除,这样就能传递数据给下一个地址了。

  

  三、保存并获取属性参数(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";
    }

  四、拦截器

  拦截器是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

  五、验证表单

  在实际工作中,得到数据后的第一步就是检验数据的正确性,如果存在录入上的问题,一般会通过注解校验,发现错误后返回给用户,但是对于一些逻辑上的错误,比如购买金额=购满数量×单价,这样的规则就很难使用注解方式进行验证了,这个时候可以使用验证器(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() + "	" + "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() + "	" + "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注解额方式得到基本的检验信息后,再使用自己的方法进行验证。

  六、数据模型

  视图是业务处理后展现给用户的内容,不过一般伴随着业务处理返回的数据,用来给用户查看。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 接口的类,所以就有了相互转换的功能。

  七、视图和视图解析器

  视图是展示给用户的内容,而在此之前,要通过控制器得到对应的数据模型,如果是非逻辑视图,则不会经过视图解析器定位视图,而是直接将数据模型渲染便结束了;而逻辑视图则是要对其进一步解析,以定位真实视图,这就是视图解析器的作用了。而视图则是把从控制器查询回来的数据模型进行渲染,以展示给请求者查看。

  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文件,打开该文件,可以看到正确显示了数据库中的记录:

  

  八、上传文件

  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;
    }

  

  

原文地址:https://www.cnblogs.com/BigJunOba/p/9774056.html