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

      

      

  • 相关阅读:
    事件使用(转 )
    使用Dotfuscator 进行.Net代码混淆 代码加密的方法
    Git分布式版本控制系统学习笔记
    免费SVN服务器笔记
    如何设置mysql远程访问及防火墙设置
    c# GridControl怎么换行
    模拟Post登陆带验证码的网站
    c#控制打印机杂项
    ssh 自动登录
    mysql 使用记号
  • 原文地址:https://www.cnblogs.com/BigJunOba/p/9774056.html
Copyright © 2020-2023  润新知