• springMVC第二天——高级参数绑定与其它特性


      大纲摘要:

        1、高级参数绑定

          a) 数组类型的参数绑定

          b) List类型的绑定

        2、@RequestMapping注解的使用

        3、Controller方法返回值

        4、Springmvc中异常处理

        5、图片上传处理

        6、Json数据交互

        7、Springmvc实现Restful

        8、拦截器

    一、高级参数绑定

      (1)数组类型

      1.需求:

        在商品列表页面选中多个商品,然后删除。

      从需求可以看出,这需要用到数组类型的参数绑定

      正式开始之前复制昨天的ssm-01项目,这里再提一点之前提到的要改项目名(web project settings),改context root

      修改jsp页面,将id传递过去,使用的是checkbox,当选中时将value值传递过去

    <c:forEach items="${itemList }" var="item">
    <tr>
        <td><input type="checkbox" name="ids" value="${item.id }"/></td>
        <td>${item.name }</td>
        <td>${item.price }</td>
        <td><fmt:formatDate value="${item.createtime}" pattern="yyyy-MM-dd HH:mm:ss"/></td>
        <td>${item.detail }</td>
        
        <td><a href="${pageContext.request.contextPath }/itemEdit.action?id=${item.id}">修改</a></td>
    
    </tr>
    </c:forEach>
    View Code

      把from提交的action路径改一下

    <form action="${pageContext.request.contextPath }/delAll.action" method="post">

      这样ItemList.jsp就改完了,把上面delAll这个action完成一样:

    //批量删除的方法
        @RequestMapping("/delAll")
        public String delAll(QueryVo vo) throws Exception{
            //一般不会单独接收某一个参数,而是采用接收vo的形式,以便拓展等
            //类似这种批量删除一堆的,可以采用只用checkbox被选中才被传递进来,并在此处使用vo里的数组接收
            System.out.println(vo);
            return "";
        }
    View Code

      当然,这里可以用工具看一下生成的HTML代码,结构更加清晰:

    <table width="100%" border=1>
    <tr>
        <td>商品名称</td>
        <td>商品价格</td>
        <td>生产日期</td>
        <td>商品描述</td>
        <td>操作</td>
    </tr>
    <tr>
        <td><input name="ids" value="1" type="checkbox"></td>
        <td>台式机</td>
        <td>3000.0</td>
        <td>2016-02-03 13:22:53</td>
        <td></td>
        <td><a href="/springmvc-web/itemEdit.action?id=1">修改</a></td>
    </tr>
    <tr>
        <td><input name="ids" value="2" type="checkbox"></td>
        <td>笔记本</td>
        <td>6000.0</td>
        <td>2015-02-09 13:22:57</td>
        <td></td>
        <td><a href="/springmvc-web/itemEdit.action?id=2">修改</a></td>
    </tr>
    <tr>
        <td><input name="ids" value="3" type="checkbox"></td>
        <td>背包</td>
        <td>200.0</td>
        <td>2015-02-06 13:23:02</td>
        <td></td>
        <td><a href="/springmvc-web/itemEdit.action?id=3">修改</a></td>
    </tr>
    </table>
    View Code

      //这里就只采用debug方式看一下数据是否正确传递进来了,这里用到的是不直接接收ids,而是采用数组形式接收,数组封装在vo中:

    package cn.vo;
    
    import cn.pojo.Items;
    
    public class QueryVo {
    
        //商品对象
        private Items items;
        public Items getItems() {
            return items;
        }
        public void setItems(Items items) {
            this.items = items;
        }
        //订单对象 用户对象...
        //批量删除时传递过来的ids,采用封装到vo的方式
        private Integer[] ids;
        public Integer[] getIds() {
            return ids;
        }
        public void setIds(Integer[] ids) {
            this.ids = ids;
        }
        
    }
    View Code

      //其它的属性暂时不管,这里看vo封装的ids

       (2)集合list类型

      1.需求:

        实现商品数据的批量修改。

       可以在表单页面批量修改,然后批量提交修改后的数据

         这里我们还是把接收的类型放在vo中

    //批量修改时用来接收的list集合
        private List<Items> itemsList;
        public List<Items> getItemsList() {
            return itemsList;
        }
        public void setItemsList(List<Items> itemsList) {
            this.itemsList = itemsList;
        }

      然后,修改一下itemsList.jsp页面:

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt"  prefix="fmt"%>
    <!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 action="${pageContext.request.contextPath }/updateAll.action" method="post">
    查询条件:
    <table width="100%" border=1>
    <tr>
    <!-- 使用了包装类型vo后,将使用对象.xx的形式编写name属性值 -->
    商品名称:<td><input type="text" name="items.name"/></td>
    商品价格:<td><input type="text" name="items.price"/></td>
    <td><input type="submit" value="批量修改"/></td>
    </tr>
    </table>
    商品列表:
    <table width="100%" border=1>
    <tr>
        <td></td>
        <td>商品名称</td>
        <td>商品价格</td>
        <td>生产日期</td>
        <td>商品描述</td>
        <td>操作</td>
    </tr>
    <!-- 批量修改操作时,可以使用List<pojo>接收,list封装在vo中
        而input中属性值为vo的相关list属性名称和下标.list泛型属性名称
        也就是例如:itemsList[${status.index }].id 
        vo中集合为:private List<Items> itemsList;
     -->
    <c:forEach items="${itemList }" var="item" varStatus="status">
    <tr>
        <td>
            <input type="checkbox" name="ids" value="${item.id }"/>
            <input type="hidden" name="itemsList[${status.index }].id" value="${item.id }"/>
        </td>
        <td><input type="text" name="itemsList[${status.index }].name" value="${item.name }"/></td>
        <td><input type="text" name="itemsList[${status.index }].price" value="${item.price }"/></td>
        <td><input type="text" name="itemsList[${status.index }].createtime" value="<fmt:formatDate value='${item.createtime}' pattern='yyyy-MM-dd HH:mm:ss'/>"/></td>
        <td><input type="text" name="itemsList[${status.index }].detail" value="${item.detail }"/></td>
        
        <td><a href="${pageContext.request.contextPath }/itemEdit.action?id=${item.id}">修改</a></td>
    
    </tr>
    </c:forEach>
    
    </table>
    </form>
    </body>
    
    </html>
    View Code

      //这里注意的是:

        1.sumbit提交改成提交到updateAll

    <td><input type="submit" value="批量修改"/></td>

        2.注意list的写法(注意通过varStatus控制循环变量下标信息index):

    <td><input type="text" name="itemsList[${status.index }].name" value="${item.name }"/></td>

        3.通过隐藏域将id传过去:

    <input type="hidden" name="itemsList[${status.index }].id" value="${item.id }"/>

       再将action按前面的方式编写完成,这里也只做测试:

    //批量修改的方法
        @RequestMapping("/updateAll")
        public String updateAll(QueryVo vo) throws Exception{
            System.out.println(vo);
            return "";
        }
    View Code

       其它几个参数相关的注解,用法都是类同

     这里先介绍一下@RequestParam;主要作用是前面接收基本类型时,基本类型的变量名称就可以自定义,

      而不必与前台页面name属性值一致。还可以配置除了value外,还可以配置required defaultValue等属性

    public String updateitem(@RequestParam("id") Integer idd,
    String name,Float price,String detail) throws Exception{

      //当然,看得出这方式略显麻烦,企业中用的不多,作【了解】

      其它的兄弟包括有 @RequestHeader @CookieValue (@PathVariable) 等,

      分别请求请求头信息、cookie信息等,这里不再展开赘述,用法也都类同

    二、Requestmapping注解的使用

      1. URL路径映射

      @RequestMapping(value="/item")或@RequestMapping("/item

      value的值是数组,可以将多个url映射到同一个方法

      了解:可以支持ant风格的通配符的形式(/?,/*,/**)

      2.窄化请求映射

      class上添加@RequestMapping(url)指定通用请求前缀, 限制此类下的所有方法请求url必须以请求前缀开头,通过此方法对url进行分类管理。

      如下:

        @RequestMapping放在类名上边,设置请求前缀 

        @Controller

        @RequestMapping("/item")

      3.请求方法映射

      限定GET方法

        @RequestMapping(method = RequestMethod.GET)

      如果通过Post访问则报错:

        HTTP Status 405 - Request method 'POST' not supported

        例如:

          @RequestMapping(value="/editItem",method=RequestMethod.GET)

       限定POST方法

          @RequestMapping(method = RequestMethod.POST)

      如果通过Post访问则报错:

        HTTP Status 405 - Request method 'GET' not supported

       GETPOST都可以

          @RequestMapping(method={RequestMethod.GET,RequestMethod.POST})

    //回忆注解部分,只有只有value属性时才可以只写值不写value=

        RequestMapping里的value映射url,methiod映射方法,params映射参数(类似@RequestParam可以映射请求参数),

          当然由示例知道这种形式使用多

    三、controller方法返回值

      1.返回 ModelAndView

        controller方法中定义ModelAndView对象并返回,对象中可添加model数据、指定view(当然也可以在构造modelAndView时使用viewName作为构造器参数直接传入)。

    @Autowired
        private ItemsService service;
        @RequestMapping(value="/list",method=RequestMethod.GET)
        public ModelAndView itemsList() throws Exception{
            List<Items> list = service.list();
            //需要modelAndView
            ModelAndView modelAndView = new ModelAndView();
            //加模型
            modelAndView.addObject("itemList", list);
            //加要去的视图路径
            modelAndView.setViewName("itemList");
            return modelAndView;
        }
    View Code

        核心代码如下:

          //加模型
            modelAndView.addObject("itemList", list);
            //加要去的视图路径
            modelAndView.setViewName("itemList");

      更新【处理模型数据】:除了可以通过modelAndView添加数据外,还可以有以下几种形式:

        1.定义ModelAndView添加模型数据并返回,这里上文已作演示

        2.入参为Map(注意添加泛型)或Model使用map.put将数据放入,页面即可取到

    @RequestMapping("/map")
        public String testMap(Map<String,Object> map){
            map.put("names", Arrays.asList("tom","jack"));
            return "list";
        }
    View Code

          model类型上文有提到,这里贴出核心代码:

    model.addAttribute("item", items);

        3.使用@SessionAttributes,可以将某个属性暂存到session中,实现跨请求共享数据

          使用方式是在controller类上添加注解:

    @SessionAttributes({"items"})
    @Controller
    public class ItemsController {

          //参数value是String[]数组类型,type属性可以规定类型 value={},types={String.class}

          在方法中当map.put的key和注解的value相同时,不仅会放在request域中,还会放在session中:

    @RequestMapping("/session")
        public String testSessionAttr(Map<String,Object> map){
            Items items = new Items();
            map.put("items", items);
            return "";
        }
    View Code

       SessionAttribute引发的异常:session找不到属性(可见源码分析处),当@SessionAttribute中的value和ModelAttribute中的value一致时

    就会抛出异常,它会强制从session中查找。

         4.使用@ModelAndAttribute,可以有如下的用法:

          【更新】:更多此注解的用法,参见http://blog.csdn.net/xiejx618/article/details/43638537

          请注意示例典型用法:

        @ModelAttribute
        public OaNotify get(@RequestParam(required=false) String id) {
            OaNotify entity = null;
            if (StringUtils.isNotBlank(id)){
                entity = oaNotifyService.get(id);
            }
            if (entity == null){
                entity = new OaNotify();
            }
            return entity;
        }

        简要分析:

          当执行修改时,先执行此方法,按ID查出要改的对象,返回给指定的页面进行表单数据回显

         当执行新增时,同样先执行此方法,按ID查,查出来的必为null,然后如果直接返回Null,对象,进入add()方法的入参则为null(因为在两个方法之间无法正确的进行参数绑定),当手动new一个对象时,有对应的name(只是值为空而已,在走完此方法去add方法之间会进行表单数据的绑定)

          1.情景:例如完成修改操作,但是有一个字段不能被修改(例如录入时间等)

             我们之前的做法是new 一个对象,给它赋值(不能修改的字段不能赋值,就为空了),拿着这个有字段为空的对象来修改时不行的

          如果使用我们的思路,可以使用隐藏域,但这样如果是敏感信息就不能用了,如果更新之前先从数据库查,又比较麻烦,这里可以使用这个

          注解来更加优雅地解决

          我们需要的是有一个字段是从数据库获取的不能修改的,而其他的是外界传入的。图解如下:

      //我们要修改的这个对象呢,是从数据库拿的(不能修改的字段也已经有值了),而不是外界new的

         原始的方式就不做赘述:

    @RequestMapping("/model")
        public String testmodelAttr(Items items){
            //传入items对象进行调用Service Dao等进行修改
            return "";
        }
    View Code

        这里我们看方法上使用注解(也可以使用在入参上):

    @ModelAttribute
        public void testmodelAttr(@RequestParam(value="id",required=false) Integer id,
                    Map<String,Object> map) throws Exception{
            if(id != null){//id由页面从隐藏域传来
                //从数据库中findById获取一个对象
                Items items = service.findItemsById(id);
                map.put("items", items);
            }
        }
    View Code

      //这里只使用上面的一个注解,因为方法上使用注解,springMVC会在调用目标方法前,逐个调用加了@ModelAttribute注解的方法

    源码分析不展开:

    /**
         * 运行流程:
         * 1. 执行 @ModelAttribute 注解修饰的方法: 从数据库中取出对象, 把对象放入到了 Map 中. 键为: user
         * 2. SpringMVC 从 Map 中取出 User 对象, 并把表单的请求参数赋给该 User 对象的对应属性.
         * 3. SpringMVC 把上述对象传入目标方法的参数. 
         * 
         * 注意: 在 @ModelAttribute 修饰的方法中, 放入到 Map 时的键需要和目标方法入参类型的第一个字母小写的字符串一致!
         * 
         * SpringMVC 确定目标方法 POJO 类型入参的过程
         * 1. 确定一个 key:
         * 1). 若目标方法的 POJO 类型的参数木有使用 @ModelAttribute 作为修饰, 则 key 为 POJO 类名第一个字母的小写
         * 2). 若使用了  @ModelAttribute 来修饰, 则 key 为 @ModelAttribute 注解的 value 属性值. 
         * 2. 在 implicitModel 中查找 key 对应的对象, 若存在, 则作为入参传入
         * 1). 若在 @ModelAttribute 标记的方法中在 Map 中保存过, 且 key 和 1 确定的 key 一致, 则会获取到. 
         * 3. 若 implicitModel 中不存在 key 对应的对象, 则检查当前的 Handler 是否使用 @SessionAttributes 注解修饰, 
         * 若使用了该注解, 且 @SessionAttributes 注解的 value 属性值中包含了 key, 则会从 HttpSession 中来获取 key 所
         * 对应的 value 值, 若存在则直接传入到目标方法的入参中. 若不存在则将抛出异常. 
         * 4. 若 Handler 没有标识 @SessionAttributes 注解或 @SessionAttributes 注解的 value 值中不包含 key, 则
         * 会通过反射来创建 POJO 类型的参数, 传入为目标方法的参数
         * 5. SpringMVC 会把 key 和 POJO 类型的对象保存到 implicitModel 中, 进而会保存到 request 中. 
         * 
         * 源代码分析的流程
         * 1. 调用 @ModelAttribute 注解修饰的方法. 实际上把 @ModelAttribute 方法中 Map 中的数据放在了 implicitModel 中.
         * 2. 解析请求处理器的目标参数, 实际上该目标参数来自于 WebDataBinder 对象的 target 属性
         * 1). 创建 WebDataBinder 对象:
         * ①. 确定 objectName 属性: 若传入的 attrName 属性值为 "", 则 objectName 为类名第一个字母小写. 
         * *注意: attrName. 若目标方法的 POJO 属性使用了 @ModelAttribute 来修饰, 则 attrName 值即为 @ModelAttribute 
         * 的 value 属性值 
         * 
         * ②. 确定 target 属性:
         *     > 在 implicitModel 中查找 attrName 对应的属性值. 若存在, ok
         *     > *若不存在: 则验证当前 Handler 是否使用了 @SessionAttributes 进行修饰, 若使用了, 则尝试从 Session 中
         * 获取 attrName 所对应的属性值. 若 session 中没有对应的属性值, 则抛出了异常. 
         *     > 若 Handler 没有使用 @SessionAttributes 进行修饰, 或 @SessionAttributes 中没有使用 value 值指定的 key
         * 和 attrName 相匹配, 则通过反射创建了 POJO 对象
         * 
         * 2). SpringMVC 把表单的请求参数赋给了 WebDataBinder 的 target 对应的属性. 
         * 3). *SpringMVC 会把 WebDataBinder 的 attrName 和 target 给到 implicitModel. 
         * 近而传到 request 域对象中. 
         * 4). 把 WebDataBinder 的 target 作为参数传递给目标方法的入参. 
         */
    View Code

      一部分属性来自数据库取出的,一部分属性来自表单,springMVC从map中取出,这里map.put的key键值必须与方法的入参对应类名第一个字母小写

    这里就是 map.put(items,...),是由于POJO类名为Items,如果不一致呢,这里就可以使用此注解修饰方法的入参,若使用了  @ModelAttribute 来修饰, 则 key 为 @ModelAttribute 注解的 value 属性值,比如:@ModelAttribute("attrName") Items items,attrName即为map中的key

    public String updateitem(@ModelAttribute("attrName") Items items) throws Exception{

       2.返回String

        返回的是逻辑视图名去掉拓展名(当然还有配置指定的前后缀有相关的配置),返回页面的数据可以通过model或者参数类型等指定

        这里的返回String感觉是和strtus2非常类似,比较容易上手。带转发重定向的特殊操作见下文

    //点击修改到详情页
        @RequestMapping("/itemEdit")
        public String itemEdit(HttpServletRequest request,HttpServletResponse response,
                HttpSession session,Model model) throws Exception{
            //原始的通过request拿参数
            String id = request.getParameter("id");
            Items items = service.findItemsById(Integer.parseInt(id));
            //model叫做模型,模型中放入返回给页面的数据
            //底层用的就是request域来传递数据,但是进行了拓展
            model.addAttribute("item", items);
            //如果springMVC方法返回一个简单的string字符串
            //springMVC就是认为这就是页面的名称
            return "editItem";
        }
    View Code

       3.返回void

       返回页面的数据可以通过request.setAttribute()等方法设置返回页面的数据

      在controller方法形参上可以定义requestresponse,使用requestresponse指定响应结果:

        1、使用request转向页面,如下:

          request.getRequestDispatcher("页面路径").forward(request, response);

        2、也可以通过response页面重定向:

          response.sendRedirect("url")

        3、也可以通过response指定响应结果,例如响应json数据如下:

          response.setCharacterEncoding("utf-8");

          response.setContentType("application/json;charset=utf-8");

          response.getWriter().write("json串");

      //这里就和WEB的阶段的是基本一致,这里就不再举例了。这里不会走springMVC的组件,路径需要全路径!

      (似乎和springMVC联系就不大了,应该是只做了解演示)

      请求转发和重定向:

        这里暂时就不赘述转发和重定向的区别(见WEB部分)

        我们可以通过打断点,查看调用栈来看大致原理。

        场景是:一般更新后,一般可以走list的action去list的页面,这里有点像struts2里的重定向action

      controller方法执行后继续执行另一个controller方法,如下商品修改提交后转向到商品修改页面,修改商品的id参数可以带到商品修改方法中。

    //点击修改到详情页
        @RequestMapping("/itemEdit")
        public String itemEdit(HttpServletRequest request,HttpServletResponse response,
                HttpSession session,Model model) throws Exception{
            //原始的通过request拿参数
            String id = request.getParameter("id");
            Items items = service.findItemsById(Integer.parseInt(id));
            //model叫做模型,模型中放入返回给页面的数据
            //底层用的就是request域来传递数据,但是进行了拓展
            model.addAttribute("item", items);
            //如果springMVC方法返回一个简单的string字符串
            //springMVC就是认为这就是页面的名称
            return "editItem";
        }
        //修改页面数据的方法(记得配requestMapping)
        //public String updateitem(Integer id,
        //String name,Float price,String detail) throws Exception{
        @RequestMapping("/updateitem")
        public String updateitem(Items items, Model model) throws Exception{
            //由于字段限制非空,必须加时间(后面配置转换器后不需要了)
            /*items.setCreatetime(new Date());*/
            service.updateitem(items);
            model.addAttribute("id", items.getId());
            return "forward:itemEdit.action";
        }
    View Code

      //这里需要注意的是,转发时的逻辑的处理(Id的传递)

      这里还需要特别注意转发时路径的编写

    return "forward:itemEdit.action";

      //像这里就是不加 / 的相对路径(相对的是当前的类路径)

      如果要转到其他的controller(其他类的方法),需要使用绝对路径:

    return "forward:/items/itemEdit.action";

      //绝对路径是从项目名目录开始,这里假设此controller使用了items的窄化映射

      当然,有转发就有重定向,使用方法是类同:

    //重定向到queryItem.action地址,request无法带过去
    return "redirect:queryItem.action";

      //这里我们知道,重定向是两次请求,request域里的数据是只能保存一次请求的,如果直接使用request.setAttribute(),将会失效

      //之前说过的model对request进行了增强,它就可以保存重定向后的数据!

         model.addAttribute("id", items.getId());
            return "redirect:itemEdit.action";

      出现NumberFormatException原因就是:试图把null值进行转换,也就是传值时值丢失了。

      若希望直接响应JSP页面而不通过controller,(因为jsp在WEB-INF下,无法由客户端跳转),可以通过mvc:view-controller跳转:

    <!-- 配置直接响应的页面 -->
        <mvc:view-controller path="/success" view-name="success"/>

      //可以直接从外部访问WEB-INF,不用经过controller,直接在springMVC核心配置文件配置即可

      但是这样的话,之前的访问就会报404,需要配置i<mvc annotation-driven> (虽然我们通常会配)

     四、Springmvc中异常处理

       这里所说的异常呢,是springMVC的架构级别的异常,用于处理比方说一些全局的异常

      springmvc在处理请求过程中出现异常信息交由异常处理器进行处理,由HandlerExceptionResolver自定义异常处理器可以实现一个系统的异常处理逻辑。

      这里我们重点是看第一个实现类:

      1.异常处理思路

      系统中异常包括两类:预期异常和运行时异常RuntimeException,前者通过捕获异常从而获取异常信息,

        后者主要通过规范代码开发、测试通过手段减少运行时异常的发生。

      系统的dao、servicecontroller出现都通过throws Exception向上抛出,最后由springmvc前端控制器交由异常处理器进行异常处理

      Spring MVC处理异常有3种方式: 
      (1)使用Spring MVC提供的简单异常处理器SimpleMappingExceptionResolver; 
      (2)实现Spring的异常处理接口HandlerExceptionResolver 自定义自己的异常处理器; 
      (3)使用@ExceptionHandler注解实现异常处理;

     异常处理实战:

      方式1:在spring的配置文件中增加相关的配置(这里是在sprinMVC的配置文件中增加),主要是配置这个bean

    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">  
        <!-- 定义默认的异常处理页面,当该异常类型的注册时使用 -->  
        <property name="defaultErrorView" value="error"></property>  
        <!-- 定义异常处理页面用来获取异常信息的变量名,默认名为exception -->  
        <property name="exceptionAttribute" value="ex"></property>  
        <!-- 定义需要特殊处理的异常,用类名或完全路径名作为key,异常也页名作为值 -->  
        <property name="exceptionMappings">  
            <props>  
                <prop key="cn.basttg.core.exception.BusinessException">error-business</prop>  
                <prop key="cn.basttg.core.exception.ParameterException">error-parameter</prop>  
      
                <!-- 这里还可以继续扩展对不同异常类型的处理 -->  
            </props>  
        </property>  
    </bean> 
    View Code

      实际配置:

    <!-- 异常处理的bean -->
        <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
            <property name="exceptionMappings">
                <props>
                    <prop key="org.apache.shiro.authz.UnauthorizedException">error/403</prop>
                    <prop key="java.lang.Throwable">error/500</prop>
                </props>
            </property>
        </bean>

    //prop的值为error页的路径(注意实际路径,也就是配上springMVC视图解析器的前后缀的路径)

      404等错误的配置:

    我们可能不得不面对尴尬的404、500……等服务器内部错误提示页面。 
    我们需要一个全面而有效的异常处理机制。目前大多数服务器也都支持在Web.xml中通过<error-page>(Websphere/Weblogic)或者<error-code>(Tomcat)节点配置特定异常情况的显示页面。修改web.xml文件,增加以下内容:

    <error-page>
            <error-code>500</error-code>
            <location>/WEB-INF/views/error/500.jsp</location>
        </error-page>
        <error-page>
            <error-code>404</error-code>
            <location>/WEB-INF/views/error/404.jsp</location>
        </error-page>

     2.自定义异常类(可选)

      为了区别不同的异常通常根据异常类型自定义异常类,这里我们创建一个自定义系统异常,如果controllerservicedao抛出此类异常说明是系统预期处理的异常信息。

    public class CustomException extends Exception {
    
        /** serialVersionUID*/
        private static final long serialVersionUID = -5212079010855161498L;
        
        public CustomException(String message){
            super(message);
            this.message = message;
        }
    
        //异常信息
        private String message;
    
        public String getMessage() {
            return message;
        }
    
        public void setMessage(String message) {
            this.message = message;
        }
    }
    View Code

      //通过继承Exception来实现

      3.自定义异常处理器

    public class CustomExceptionResolver implements HandlerExceptionResolver {
    
        @Override
        public ModelAndView resolveException(HttpServletRequest request,
                HttpServletResponse response, Object handler, Exception ex) {
    
            ex.printStackTrace();
    
            CustomException customException = null;
            
            //如果抛出的是系统自定义异常则直接转换
            if(ex instanceof CustomException){
                customException = (CustomException)ex;
            }else{
                //如果抛出的不是系统自定义异常则重新构造一个系统错误异常。
                customException = new CustomException("系统错误,请与系统管理 员联系!");
            }
            
            ModelAndView modelAndView = new ModelAndView();
            modelAndView.addObject("message", customException.getMessage());
            modelAndView.setViewName("error");
    
            return modelAndView;
        }
    
    }
    
    取异常堆栈:
               try {
                
            } catch (Exception e) {
                StringWriter s = new StringWriter();
                PrintWriter printWriter = new PrintWriter(s);
                e.printStackTrace(printWriter);
                s.toString();
            }
    View Code

      4.错误页面 error.jsp

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt"  prefix="fmt"%> 
    <!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>
    您的操作出现错误如下:<br/>
    ${message }
    </body>
    
    </html>
    View Code

      5.在配置文件springMVC.xml中配置异常处理器

    <!-- 异常处理器 -->
        <bean id="handlerExceptionResolver" class="cn.itcast.ssm.controller.exceptionResolver.CustomExceptionResolver"/>
    //处理异常的handler
        @ExceptionHandler(ArithmeticException.class)
        public String testHandlerException(Exception e){
            System.out.println("出现算术异常!异常信息是:"+e);
            return "error";
        }
        @RequestMapping("/testExceptionHandlerExceptionResolver")
        public String testExceptionHandlerExceptionResolver(
                @RequestParam("i") Integer i){
            //将抛出异常
            System.out.println("result:"+i/0);
            return "error";
        }
    View Code

     //通过注解简化

        6.异常测试

       修改商品信息,id输入错误提示商品信息不存在。

      修改controller方法“editItem”,调用service查询商品信息,如果商品信息为空则抛出异常:

    // 调用service查询商品信息
            Items item = itemService.findItemById(id);
            
            if(item == null){
                throw new CustomException("商品信息不存在!");
            }

      //service中抛出异常方法同上。

    **这里暂时参见:http://blog.csdn.net/ufo2910628/article/details/40399539 (视频中断了,待补充)

     五、图片上传处理

      图片上传是通过 multipatrResolver(ctrl shift t查看)

      1.配置虚拟目录

          一般都需要一个专门的服务器之类的存放图片这种占地方的文件,这里我们使用tomcat直接进行配置:

      双进tomcat,点击添加模块:

      这里进行简单的配置(这里path就相当于一个项目名):

      配置好之后进行校验一下(因为此时图片服务器应该是对外可以访问的,我们这里输入访问路径,成功访问图片即为成功!):

        http://localhost:8080/pic/a.jpg

      当然,不在eclipse中也是可以手动配置的:

      在tomcat上配置图片虚拟目录,在tomcatconf/server.xml中添加:

      <Context docBase="F:developupload emp" path="/pic" reloadable="false"/>

      访问http://localhost:8080/pic即可访问F:developupload emp下的图片。

      2.配置好后我们把文件上传的经典包导一下:

     

    【更新】:maven的fileupload引入:

    <!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
    <dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
        <version>1.3.1</version>
    </dependency>
    View Code

      3.配置解析器

      在springMVC.xml中进行配置:——也就是配置MultipartResolver(也可以配置默认编码)

    //用了springIDE后,美滋滋

    <!-- 文件上传 -->
        <bean id="multipartResolver"
            class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
            <!-- 设置上传文件的最大尺寸为5MB -->
            <property name="maxUploadSize">
                <value>5242880</value>
            </property>
        </bean>
    View Code

      4.修改JSP页面

       把editItem.jsp打开,先把上传图片的注释放出来。

      然后,重要的一点,凡是上传图片的,一定要改表单的enctype!

    <form id="itemForm"    action="${pageContext.request.contextPath }/updateitem.action" 
            method="post" enctype="multipart/form-data">

      修改完后的jsp页面如下:

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt"  prefix="fmt"%>
    <!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> 
        <!-- 上传图片是需要指定属性 enctype="multipart/form-data" -->
        <!-- <form id="itemForm" action="" method="post" enctype="multipart/form-data"> -->
        <form id="itemForm"    action="${pageContext.request.contextPath }/updateitem.action" 
            method="post" enctype="multipart/form-data">
            <input type="hidden" name="id" value="${item.id }" /> 修改商品信息:
            <table width="100%" border=1>
                <tr>
                    <td>商品名称</td>
                    <td><input type="text" name="name" value="${item.name }" /></td>
                </tr>
                <tr>
                    <td>商品价格</td>
                    <td><input type="text" name="price" value="${item.price }" /></td>
                </tr>
                
                <tr>
                    <td>商品生产日期</td>
                    <td><input type="text" name="createtime"
                        value="<fmt:formatDate value="${item.createtime}" pattern="yyyy-MM-dd HH:mm:ss"/>" /></td>
                </tr>
                
                <tr>
                    <td>商品图片</td>
                    <td>
                        <c:if test="${item.pic !=null}">
                            <img src="/tomcat_pic/${item.pic}" width=100 height=100/>
                            <br/>
                        </c:if>
                        <input type="file"  name="pictureFile"/> 
                    </td>
                </tr>
                 
                <tr>
                    <td>商品简介</td>
                    <td><textarea rows="3" cols="30" name="detail">${item.detail }</textarea>
                    </td>
                </tr>
                <tr>
                    <td colspan="2" align="center"><input type="submit" value="提交" />
                    </td>
                </tr>
            </table>
    
        </form>
    </body>
    
    </html>
    View Code

      5.编写controller

    @RequestMapping("/updateitem")
        public String updateitem(MultipartFile pictureFile,Items items, Model model) throws Exception{
            //由于字段限制非空,必须加时间(后面配置转换器后不需要了)
            /*items.setCreatetime(new Date());*/
            service.updateitem(items);
            //获取图片完整名称
            String fileStr = pictureFile.getOriginalFilename();
            //使用随机生成的字符串加原图片名组成新图片的名称,进而防止文件重名(UUID)
            String uuid = UUID.randomUUID().toString();
            String newFilename = uuid + fileStr.substring(fileStr.lastIndexOf("."));
            //将图片保存到 硬盘
            pictureFile.transferTo(new File("F:/tomcat_pic/"+newFilename));
            //将图片名称保存到数据库
            items.setPic(newFilename);
            model.addAttribute("id", items.getId());
            return "redirect:itemEdit.action";
        }
    View Code

      //这里注意的是MultipartFile,参数名称就是表单中上传文件的name属性值(当然可以通过@RequestParam()进行自定义)。 @RequestParam(value = "file1", required = false) MultipartFile file1,

        这个接口中有很多方便的方法,我们可以在真正进行执行之前做必要的文件是否是空的判断:if(!ifle.isEmpty()){}

      //这里打开F://tomcat_pic文件夹可以看到成功上传的文件。

      多图片上传请参考:http://blog.csdn.net/luckey_zh/article/details/46867957

       可以由此拓展上传文件,在controller中入参 @RequestParam("file" MutipartFile file)...

     六、JSON数据交互

      ajax请求与springMVC的配合案例可参考(遍历返回的JSON请参考JSON随笔): 

        参考1:   http://blog.csdn.net/yixiaoping/article/details/45281721

        参考2:   http://blog.csdn.net/CarryBest/article/details/71214111

      准备工作:

        JSON是什么?是一种数据交互的格式

      回忆前面的部分,和JSON交互需要使用 jackson 的包

    //一个注解包、一个核心包、数据绑定包 【更新】:maven中只需定义第三个数据包,后两个依赖包将会自动载入

      在注解适配器中加入messageConverters

    <!--注解适配器 -->
        <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
            <property name="messageConverters">
            <list>
            <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"></bean>
            </list>
            </property>
        </bean>
    View Code

      注意:如果使用<mvc:annotation-driven /> 则不用定义上边的内容。

    这里在webContent下建立js的folder,(不在WEB-INF安全目录下),外界可以直接访问,然后导支持jQuery的核心包,并在页面导入

    <script type="text/javascript" src="${pageContext.request.contextPath }/js/jquery-1.4.4.min.js"></script>

      //这里注意引入的src处的路径的写法(使用标准的绝对路径)

      这里把前台的ajax完成一下(复习一下jQuery的ajax操作):

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt"  prefix="fmt"%>
    <!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>
    <script type="text/javascript" src="${pageContext.request.contextPath }/js/jquery-1.4.4.min.js"></script>
    </head>
    <body> 
    <script type="text/javascript">
        function sendJson(){
                $.ajax({
                    type:"post",
                    url:"${pageContext.request.contextPath }/sendJson.action",
                    contentType:"application/json;charset=utf-8",
                    data:'{"name":"测试商品","price":99.9}',
                    success:function(data){
                        alert(data);
                    }
                });
        }
    </script>
        <input type="button" value="sendJson" onclick="sendJson()"/>
        <form action="${pageContext.request.contextPath }/updateAll.action" method="post">
        查询条件:
        <table width="100%" border=1>
        <tr>
        <!-- 使用了包装类型vo后,将使用对象.xx的形式编写name属性值 -->
        商品名称:<td><input type="text" name="items.name"/></td>
        商品价格:<td><input type="text" name="items.price"/></td>
        <td><input type="submit" value="批量修改"/></td>
        </tr>
        </table>
        商品列表:
        <table width="100%" border=1>
        <tr>
            <td></td>
            <td>商品名称</td>
            <td>商品价格</td>
            <td>生产日期</td>
            <td>商品描述</td>
            <td>操作</td>
        </tr>
        <!-- 批量修改操作时,可以使用List<pojo>接收,list封装在vo中
            而input中属性值为vo的相关list属性名称和下标.list泛型属性名称
            也就是例如:itemsList[${status.index }].id 
            vo中集合为:private List<Items> itemsList;
         -->
        <c:forEach items="${itemList }" var="item" varStatus="status">
        <tr>
            <td>
                <input type="checkbox" name="ids" value="${item.id }"/>
                <input type="hidden" name="itemsList[${status.index }].id" value="${item.id }"/>
            </td>
            <td><input type="text" name="itemsList[${status.index }].name" value="${item.name }"/></td>
            <td><input type="text" name="itemsList[${status.index }].price" value="${item.price }"/></td>
            <td><input type="text" name="itemsList[${status.index }].createtime" value="<fmt:formatDate value='${item.createtime}' pattern='yyyy-MM-dd HH:mm:ss'/>"/></td>
            <td><input type="text" name="itemsList[${status.index }].detail" value="${item.detail }"/></td>
            <td><a href="${pageContext.request.contextPath }/itemEdit.action?id=${item.id}">修改</a></td>
        </tr>
        </c:forEach>
        </table>
        </form>
    </body>
    </html>
    View Code

     1.@RequestBody

     作用:

        @RequestBody注解用于读取http请求的内容(字符串),通过springmvc提供的HttpMessageConverter接口将读到的内容转换为jsonxml等格式的数据

        并绑定到controller方法的参数上。

      List.action?id=1&name=zhangsan&age=12

      本例子应用:

        @RequestBody注解实现接收http请求的json数据,将json数据转换为java对象

       可以采用打断点的形式测试以下controller是否正常接收了数据:

    //测试和JSON交互
        /*导入jackson包后,在controller方法中可以使用@RequestBody,
        让springMVC将json格式字符串自动转换成POJO中的属性*/
        @RequestMapping("/sendJson")
        public String sendJson(@RequestBody Items items) throws Exception{
            System.out.println(items);
            return "";
        }

      //能成功注入的关键是jar的支持和json的key与@RequestBody注解后POJO类的属性名一致

      2@ResponseBody
      作用:
        该注解用于将Controller的方法返回的对象,通过HttpMessageConverter接口转换为指定格式的数据如:json,xml等,

        通过Response响应给客户端

      @responsebody表示该方法的返回结果直接写入HTTP response body中
      一般在异步获取数据时使用,在使用@RequestMapping后,返回值通常解析为跳转路径,加上@responsebody后返回结果不会被解析为跳转路径,而是直接写入HTTP response body中。比如异步获取json数据,加上@responsebody后,会直接返回json数据。

    【更新】:等同于原生态Servlet中response.getWriter().print()方法

      本例子应用:
        @ResponseBody注解实现将controller方法返回对象转换为json响应给客户端

      这里我们将上面的例子稍加改变:

    // 商品修改提交json信息,响应json信息
        @RequestMapping("/editItemSubmit_RequestJson")
        public @ResponseBody Items editItemSubmit_RequestJson(@RequestBody Items items) throws Exception {
            System.out.println(items);
            //itemService.saveItem(items);
            return items;
    
        }

      //笔记中的写法:

    //测试和JSON交互
        /*导入jackson包后,在controller方法中可以使用@RequestBody,
        让springMVC将json格式字符串自动转换成POJO中的属性*/
        @RequestMapping("/sendJson")
        @ResponseBody
        public Items sendJson(@RequestBody Items items) throws Exception{
            System.out.println(items);
            //springMVC会自动将POJO转换成key-value形式的json格式
            return items;
        }

      //测试的时候呢,在syso出打个断点,在前台F12进行调试看响应即可!

      可以通过@ResponseEntity<T> (byte[]) 等实现下载的功能(响应文件)

    不使用注解也可以使用以下形式:

      当然也可以有其他形式:return new ResponseEntity<Course>(course,HttpStatus.ok);

     @RequestMapping("/handle")
     public ResponseEntity<String> handle() {
       HttpHeaders responseHeaders = new HttpHeaders();
       responseHeaders.set("MyResponseHeader", "MyValue");
       return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED);
     }

    七、springMVC的RESTful

      1.什么是RESTful?

      字面意思:资源表现层状态转化(execuse me?)——核心是资源

      Restful就是一个资源定位及资源操作的风格。不是标准也不是协议,只是一种风格,是对http协议的诠释。

      资源定位:互联网所有的事物都是资源,要求url中没有动词,只有名词。没有参数

      Url格式:http://blog.csdn.net/beat_the_world/article/details/45621673

      资源操作:使用 put、deletepost、get,使用不同方法对资源进行操作。分别对应添加、删除、修改、查询。一般使用时还是post和get。Put和Delete几乎不使用。(发送PUT DELETE请求需要配置HiddenHttpMethodFilter过滤器(web.xml中过滤器) url-pattern请 配置为/*

    <!-- 配置 HiddenHttpMethodFilter: 把 POST 请求转为 DELETE、PUT 请求 -->
        <filter>
            <filter-name>HiddenHttpMethodFilter</filter-name>
            <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
        </filter>
        
        <filter-mapping>
            <filter-name>HiddenHttpMethodFilter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    View Code

      发送相关请求的写法可以参见(核心就是所有这些写法都是RESTful风格!)

    <form action="springmvc/testRest/1" method="post">
            <input type="hidden" name="_method" value="PUT"/>
            <input type="submit" value="TestRest PUT"/>
        </form>
        <br><br>
        
        <form action="springmvc/testRest/1" method="post">
            <input type="hidden" name="_method" value="DELETE"/>
            <input type="submit" value="TestRest DELETE"/>
        </form>
        <br><br>
        
        <form action="springmvc/testRest" method="post">
            <input type="submit" value="TestRest POST"/>
        </form>
        <br><br>
        
        <a href="springmv
    View Code

     //打开源码可以看到需要配置 _method的隐藏域

       通俗理解:

        也就是我们的URL不写?传参,也不写.action这样的,要访问一个资源,以上面的URL为例,之前可以是这样

          http://blog.csdn.net/beat_the_world/article/details?id=45621673

        我们使用的是传参的形式,使用RESTful风格后就是上面的风格,只有名词,而没有动词参数之类的

      2.改造项目为RESTful风格——实战见SSM-CRUD项目

      1) 之前配置文件是在web.xml中配置的前端控制器,我们配置的是*.action

    <servlet-mapping>
          <servlet-name>springMVC</servlet-name>
          <url-pattern>*.action</url-pattern>
      </servlet-mapping>

      这里我们改造为 / ,使他符合RESTful风格:

    <url-pattern>/</url-pattern>

      //也就是有 / 的都拦截,拦截所有不包括.jsp,如果是 /* ,则.jsp也会拦截

      2) 接下来我们看页面 itemlist.jsp,发现修改的超链接这里,有这么一行 ? 传参了,这里需要改:

    <td><a href="${pageContext.request.contextPath }/itemEdit.action?id=${item.id}">修改</a></td>

      按照我们的原则,把.action以及传参的部分进行修改如下:

    <td><a href="${pageContext.request.contextPath }/itemEdit/${item.id}">修改</a></td>

      3) 然后,对controller进行改造:

    /*
         * 通过@PathVariable注解可以接收URL中传过来的参数
         * 其中@RequestMapping中的id和@PathVariable中的id需要一致
         * */
        //点击修改到详情页
        @RequestMapping("/itemEdit/{id}")
        public String itemEdit(@PathVariable("id") Integer id,HttpServletRequest request,HttpServletResponse response,
                HttpSession session,Model model) throws Exception{
            //原始的通过request拿参数
            //String id = request.getParameter("id");
            Items items = service.findItemsById(id);
            //model叫做模型,模型中放入返回给页面的数据
            //底层用的就是request域来传递数据,但是进行了拓展
            model.addAttribute("item", items);
            //如果springMVC方法返回一个简单的string字符串
            //springMVC就是认为这就是页面的名称
            return "editItem";
        }
    View Code

      //这里需要注意@PathVariable注解(接收占位符)接收参数的使用,以及@RequestMapping中URL的接收参数风格(使用{}进行接收)

        这是springMVC向RESTful风格发展的里程碑式意义的新功能,使用的时候和RequestMapping的一起使用完成此功能!

        (从字面意思也可以看出,Path:和路径有关,Variable变量,将路径中的变量映射过来),这样,就很容易实现RESTful风格了!

      我们测试一下改造是否成功:

        访问list页面,此时不需要加action(已经修改为/),再点击修改,我们可以看到页面的URL变为RESTful风格了

    http://localhost:8080/ssm-02/itemEdit/1

      打开controller的方法,可以找到一个action跳转的代码:

    return "redirect:itemEdit.action";

      按照RESTful的风格,我们把.action去掉

    return "redirect:itemEdit";

      直接这样操作发现会报404,并且地址栏会自动拼凑 ?id= ,原因是我们的controller里使用了model,只要用model,数据就会自动拼进去

      所以路径不匹配,报了404

      那么我们对返回url进行如下改造:

    return "redirect:itemEdit/"+items.getId();

     完整的controller如下:

    package cn.controller;
    
    import java.io.File;
    import java.util.List;
    import java.util.UUID;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    import org.springframework.web.multipart.MultipartFile;
    import org.springframework.web.servlet.ModelAndView;
    
    import cn.pojo.Items;
    import cn.service.ItemsService;
    import cn.vo.QueryVo;
    
    @Controller
    public class ItemsController {
    
        //注入service,使用的注解为resource;注意与autowired的区别
        /*@Resource("itemsService")* 
         * 此方法适合多实现时指定实现类,指定名称为实现类上@service的指定类名
         * AutoWired为唯一实现类的自动装配*/
        @Autowired
        private ItemsService service;
        @RequestMapping(value="/list")
        public ModelAndView itemsList() throws Exception{
            List<Items> list = service.list();
            //需要modelAndView
            ModelAndView modelAndView = new ModelAndView();
            //加模型
            modelAndView.addObject("itemList", list);
            //加要去的视图路径
            modelAndView.setViewName("itemList");
            return modelAndView;
        }
        /*
         * 通过@PathVariable注解可以接收URL中传过来的参数
         * 其中@RequestMapping中的id和@PathVariable中的id需要一致
         * */
        //点击修改到详情页
        @RequestMapping("/itemEdit/{id}")
        public String itemEdit(@PathVariable("id") Integer id,
                    HttpServletRequest request,HttpServletResponse response,
                        HttpSession session,Model model) throws Exception{
            //原始的通过request拿参数
            //String id = request.getParameter("id");
            Items items = service.findItemsById(id);
            //model叫做模型,模型中放入返回给页面的数据
            //底层用的就是request域来传递数据,但是进行了拓展
            model.addAttribute("item", items);
            //如果springMVC方法返回一个简单的string字符串
            //springMVC就是认为这就是页面的名称
            return "editItem";
        }
        //修改页面数据的方法(记得配requestMapping)
        //public String updateitem(Integer id,
        //String name,Float price,String detail) throws Exception{
        @RequestMapping("/updateitem")
        public String updateitem(MultipartFile pictureFile,Items items, Model model) throws Exception{
            //由于字段限制非空,必须加时间(后面配置转换器后不需要了)
            /*items.setCreatetime(new Date());*/
            service.updateitem(items);
            //获取图片完整名称
            String fileStr = pictureFile.getOriginalFilename();
            //使用随机生成的字符串加原图片名组成新图片的名称,进而防止文件重名(UUID)
            String uuid = UUID.randomUUID().toString();
            String newFilename = uuid + fileStr.substring(fileStr.lastIndexOf("."));
            //将图片保存到 硬盘
            pictureFile.transferTo(new File("F:/tomcat_pic/"+newFilename));
            //将图片名称保存到数据库
            items.setPic(newFilename);
            model.addAttribute("id", items.getId());
            return "redirect:itemEdit/"+items.getId();
        }
        //查询的方法,主要用于演示绑定参数为vo等包装类型的,为简便起见采用断点查看
        @RequestMapping("/serach")
        public String serach(QueryVo vo) throws Exception{
            System.out.println(vo);
            return "";
        }
        //批量删除的方法
        @RequestMapping("/delAll")
        public String delAll(QueryVo vo) throws Exception{
            //一般不会单独接收某一个参数,而是采用接收vo的形式,以便拓展等
            //类似这种批量删除一堆的,可以采用只用checkbox被选中才被传递进来,并在此处使用vo里的数组接收
            System.out.println(vo);
            return "";
        }
        //批量修改的方法
        @RequestMapping("/updateAll")
        public String updateAll(QueryVo vo) throws Exception{
            System.out.println(vo);
            return "";
        }
        //测试和JSON交互
        /*导入jackson包后,在controller方法中可以使用@RequestBody,
        让springMVC将json格式字符串自动转换成POJO中的属性*/
        @RequestMapping("/sendJson")
        @ResponseBody
        public Items sendJson(@RequestBody Items items) throws Exception{
            System.out.println(items);
            //springMVC会自动将POJO转换成key-value形式的json格式
            return items;
        }
    }
    View Code

       当然,我们也可以改造成接收多个参数的:

       jsp页面改为传两个参:

    <td><a href="${pageContext.request.contextPath }/itemEdit/${item.id}/zhangsan">修改</a></td>

      相应的,controller也进行相应的修改:

    //点击修改到详情页
        @RequestMapping("/itemEdit/{id}/{name}")
        public String itemEdit(@PathVariable("id") Integer id,@PathVariable("name") String name,
                    HttpServletRequest request,HttpServletResponse response,
                        HttpSession session,Model model) throws Exception{

       接下来对其他需要改造的地方进行改造,我们看itemlist.jsp中只有一个form提交处有URL,把.action去掉就可以了:

    <form action="${pageContext.request.contextPath }/updateAll" method="post">

      5.静态资源访问<mvc:resources>

      如果在DispatcherServlet中设置url-pattern/则必须对静态资源进行访问处理。

      spring mvc 的<mvc:resources mapping="" location="">实现对静态资源进行映射访问。

      如下是对js文件访问配置:

      <mvc:resources location="/js/" mapping="/js/**"/>

      静态资源访问day01亦有相关介绍

    八、拦截器 

      什么是拦截器?

      Spring Web MVC 的处理器拦截器类似于Servlet 开发中的过滤器Filter,用于对处理器进行预处理和后处理

      定义拦截器

      我们通过实现springMVC提供的 HandlerInterceptor 接口来定义拦截器

      (拦截器需实现三个方法,通过方法名也容易分辨三个方法的执行顺序)

      配置拦截器

      在springMVC.xml中进行配置

    <!-- 配置拦截器 -->
        <mvc:interceptors>
            <mvc:interceptor>
                <!-- 拦截器请求路径 ,要拦截所有必须配置成/**,直接/*是拦截一层目录,
                这里是拦截所有,故需要/**-->
                <mvc:mapping path="/**"/>
                <!-- 拦截器指定位置 -->
                <bean class="cn.interceptor.Interceptor01"></bean>
            </mvc:interceptor>
        </mvc:interceptors>
    View Code

      //具体格式不再赘述,见注释,需要注意的是path需要配置为 /**

      拦截器如下:

    package cn.interceptor;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;
    
    public class Interceptor01 implements HandlerInterceptor {
    
        //在拦截后执行
        //执行时机:controller方法已经执行,modelAndView已经返回
        //用于操作日志,用户登录IP和时间等
        @Override
        public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)
                throws Exception {
    
        }
    
        //在拦截时执行
        //执行时机:controller方法已经执行,modelAndView没有返回
        //用于全局的都需要的数据处理
        @Override
        public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)
                throws Exception {
    
        }
        //返回true则放行,返回false则拦截
        //执行时机:controller方法没有执行,modelAndView还没有返回
        //用于权限验证,在执行之前的一些验证等(例如验证session等)
        @Override
        public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2) throws Exception {
            return true;
        }
    
    }
    View Code

      //这里用法等见注释

      上面的是针对所有mapping,针对某种Mapping这里不展开,给出参考配置:

    <bean
        class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
        <property name="interceptors">
            <list>
                <ref bean="handlerInterceptor1"/>
                <ref bean="handlerInterceptor2"/>
            </list>
        </property>
    </bean>
        <bean id="handlerInterceptor1" class="springmvc.intercapter.HandlerInterceptor1"/>
        <bean id="handlerInterceptor2" class="springmvc.intercapter.HandlerInterceptor2"/>
    View Code

      *如果有多个拦截器,则会按照配置文件配置的顺序进行顺序执行

      执行顺序:(pre正序,后两个反序,打断点看循环的索引可以验证 i=0.i++,后面的i=length,i--)

    HandlerInterceptor1..preHandle..
    HandlerInterceptor2..preHandle..
    
    HandlerInterceptor2..postHandle..
    HandlerInterceptor1..postHandle..
    
    HandlerInterceptor2..afterCompletion..
    HandlerInterceptor1..afterCompletion..

      典型应用:登陆权限验证

      流程:

      1)编写登录的controller, 编写跳转到登录页面的方法, 编写登录验证方法
      2)编写登录页面
      3)编写拦截器
      运行过程:
        1)访问随意一个页面,拦截器会拦截请求,会验证session中是否有登录信息
          如果已登录,放行
          如果未登录,跳转到登录页面
        2)在登录页面中输入用户名,密码,点击登录按钮,拦截器会拦截请求,如果是登录路径放行
          在controller方法中判断用户名密码是否正确,如果正确则将登录信息放入session

      1.分析完成后我们来实现一下;没有登陆页面,我们先编写一个登陆页面:

      login.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>
        <form action="" method="POST">
            <table>
                <tr>
                    <td>
                        <input name="username" type="text"/>
                    </td>
                </tr>
                <tr>
                    <td>
                        <input name="password" type="password"/>
                    </td>
                </tr>
                <tr>
                    <td>
                        <input value="登陆" type="submit"/>
                    </td>
                </tr>
            </table>
        </form>
    </body>
    </html>
    View Code

      //这里的action,待后续补充

      2.我们再新建一个controller,(注意把注解等加上)

    package cn.controller;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpSession;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    @RequestMapping("/login")
    public class LoginController {
    
        @RequestMapping("/login")
        public String login() throws Exception{
            //跳转到登陆页面
            return "login";
        }
        //接收参数采用普通类型,和表单name属性一致即可
        //还可以接收默认类型,直接拿到session
        @RequestMapping("/sumbit")
        public String sumbit(String username,String password,HttpServletRequest request) throws Exception{
            //验证这里不调service dao ,实际时可以适当改动
            HttpSession session = request.getSession();
            if("admin".equals(username) && "123".equals(password)){
                session.setAttribute("username", username);
            }
            return "redirect:/list";
        }
    }
    View Code

      3.把拦截器LoginInterceptor完成

    package cn.interceptor;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;
    
    public class LoginInterceptor implements HandlerInterceptor {
    
        @Override
        public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)
                throws Exception {
            // TODO Auto-generated method stub
    
        }
    
        @Override
        public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)
                throws Exception {
            // TODO Auto-generated method stub
    
        }
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object arg2) throws Exception {
            //判断当然访问路径是否是登陆
            if(request.getRequestURI().indexOf("/login") > 0){
                return true;
            }
            //判断session中是否有登陆信息,有则放行
            if(request.getSession().getAttribute("username") != null){
                return true;
            }
            request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response);
            return false;
        }
    
    }
    View Code

      拦截器完之后将拦截器注册一下:

    <!-- 配置拦截器 -->
        <mvc:interceptors>
            <mvc:interceptor>
                <!-- 拦截器请求路径 ,要拦截所有必须配置成/**,直接/*是拦截一层目录,
                这里是拦截所有,故需要/**-->
                <mvc:mapping path="/**"/>
                <!-- 拦截器指定位置 -->
                <bean class="cn.interceptor.LoginInterceptor"></bean>
            </mvc:interceptor>
        </mvc:interceptors>
    View Code

      把前面Login页面的action可以完善了:

    <form action="${pageContext.request.contextPath }/login/sumbit" method="POST">

      测试时可以通过打断点进行测试

     小结:

    总结:
    ☆1.参数绑定:(从请求中接收参数)
        1)默认支持的类型:Request,Response,Session,Model
        2)基本数据类型(包含String)
        3)Pojo类型
        4)Vo类型
        5)Converter自定义转换器
        6)数组
        7)List
    
    ☆2.controller方法返回值(指定返回到哪个页面, 指定返回到页面的数据)
        1)ModelAndView 
            modelAndView.addObject("itemList", list); 指定返回页面的数据
            modelAndView.setViewName("itemList");      指定返回的页面
        2)String(推荐使用)
            返回普通字符串,就是页面去掉扩展名的名称, 返回给页面数据通过Model来完成
            返回的字符串以forward:开头为请求转发
            返回的字符串以redirect:开头为重定向
        3)返回void(使用它破坏了springMvc的结构,所以不建议使用)
            可以使用request.setAttribut 来给页面返回数据
            可以使用request.getRquestDispatcher().forward()来指定返回的页面
            如果controller返回值为void则不走springMvc的组件,所以要写页面的完整路径名称
    
        相对路径:相对于当前目录,也就是在当前类的目录下,这时候可以使用相对路径跳转
        绝对路径:从项目名后开始.
        在springMvc中不管是forward还是redirect后面凡是以/开头的为绝对路径,不以/开头的为相对路径
        例如:forward:/items/itemEdit.action 为绝对路径
            forward:itemEdit.action为相对路径
    
    
    3.架构级别异常处理:
        主要为了防止项目上线后给用户抛500等异常信息,所以需要在架构级别上整体处理.hold住异常
        首先自定义全局异常处理器实现HandlerExceptionResolver接口
        在spirngMvc.xml中配置生效
    4.上传图片:
        1)在tomcat中配置虚拟图片服务器
        2)导入fileupload的jar包
        3)在springMvc.xml中配置上传组件
        4)在页面上编写上传域,更改form标签的类型
        5)在controller方法中可以使用MultiPartFile接口接收上传的图片
        6)将文件名保存到数据库,将图片保存到磁盘中
    5.Json数据交互:
        需要加入jackson的jar包
        @Requestbody:将页面传到controller中的json格式字符串自动转换成java的pojo对象
        @ResponseBody:将java中pojo对象自动转换成json格式字符串返回给页面
    6.RestFul支持:
        就是对url的命名标准,要求url中只有能名词,没有动词(不严格要求),但是要求url中不能用问号?传参
        传参数:
            页面:${pageContext.request.contextPath }/items/itemEdit/${item.id}
            方法: @RquestMapping("/itemEdit/{id}")
            方法: @PathVariable("id") Integer idd
    7.拦截器:
        作用:拦截请求,一般做登录权限验证时用的比较多
        1)需要编写自定义拦截器类,实现HandlerInterceptor接口
        2)在spirngMvc.xml中配置拦截器生效
    
    8.登录权限验证:
        1)编写登录的controller, 编写跳转到登录页面的方法,  编写登录验证方法
        2)编写登录页面
        3)编写拦截器
    运行过程:
        1)访问随意一个页面,拦截器会拦截请求,会验证session中是否有登录信息
            如果已登录,放行
            如果未登录,跳转到登录页面
        2)在登录页面中输入用户名,密码,点击登录按钮,拦截器会拦截请求,如果是登录路径放行
            在controller方法中判断用户名密码是否正确,如果正确则将登录信息放入session
    View Code

       国际化需要国际化资源文件,配置文件配置,页面使用<fmt message key="">进行使用【具体待更新】

      springMVC常用注解http://www.cnblogs.com/leskang/p/5445698.html

  • 相关阅读:
    2016/05/16 thinkphp3.2.2 验证码使用
    2016/05/16 UEditor 文本编辑器 使用教程与使用方法
    2016/05/15 ThinkPHP3.2.2 表单自动验证实例 验证规则的数组 直接写在相应的控制器里
    DropzoneJS 使用指南
    MVC设计模式
    Smarty 配置文件的读取
    会话控制
    JS中的call和apply
    CSS选择器
    XML
  • 原文地址:https://www.cnblogs.com/jiangbei/p/6911426.html
Copyright © 2020-2023  润新知