SpringMVC学习笔记(二)

仅仅是对学习资料的整理。

学习计划:

1、高级参数绑定
  a) 数组类型的参数绑定
  b) List类型的绑定
2、@RequestMapping注解的使用
3、Controller方法返回值
4、Springmvc中异常处理
5、图片上传处理
6、Json数据交互
7、Springmvc实现Restful
8、拦截器

高级参数绑定

绑定数组

需求:在商品列表页面选中多个商品,然后删除。
需求分析:此功能要求商品列表页面中的每个商品前有一个checkbook,选中多个商品后点击删除按钮把商品id传递给Controller,根据商品id删除商品信息。
jsp实现name="ids"

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

...

<c:forEach items="${itemList }" var="item">
<tr>
    <!-- name属性名称要等于vo中的接收的属性名 -->
    <td><input name="ids" value="${item.id}" type="checkbox"></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>

解析后的html代码
页面选中多个checkbox向controller方法传递

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

Controller:Controller方法中可以用String[] ( 或者Integer[],看实际 ) 接收,或者pojo ( vo ) 的String[]属性接收。两种方式任选其一即可。
vo如下 :关键:private String[] ids;

public class QueryVo {
    private Items items;
    //批量删除使用
    private String[] ids;
    set/get...
}

Controller定义如下:

//如果批量删除,一堆input复选框,那么可以提交数组.(只有input复选框被选中的时候才能提交)
@RequestMapping("/delAll")
    public String delAll(QueryVo vo) throws Exception{
        System.out.println(queryVo.getIds());
        return null;
    }

或者:

@RequestMapping("/delAll")
    public String delAll(String[] ids) throws Exception{
        System.out.println(ids.toString());
        return null;
    }

为了查看方便,我们将两种方法写一起,并进行断点查看。

@RequestMapping("/delAll")
    public String delAll(String[] ids) throws Exception{
        System.out.println(queryVo.getIds());
        System.out.println(ids.toString());
        return null;
    }

查看结果
这里写图片描述

绑定List集合

需求:实现商品数据的批量修改。
需求分析:要想实现商品数据的批量修改,需要在商品列表中可以对商品信息进行修改,并且可以批量提交修改后的商品数据。
接收商品列表的pojo/vo:关键:private List<Items> itemsList;

public class QueryVo {
    //商品对象
    private Items items;
    //订单对象...
    //用户对象....

    //批量删除使用
    private Integer[] ids;
    //批量修改使用
    private List<Items> itemsList;

    public Items getItems() {
        return items;
    }

    public void setItems(Items items) {
        this.items = items;
    }

    public Integer[] getIds() {
        return ids;
    }

    public void setIds(Integer[] ids) {
        this.ids = ids;
    }

    public List<Items> getItemsList() {
        return itemsList;
    }

    public void setItemsList(List<Items> itemsList) {
        this.itemsList = itemsList;
    }


}

jsp代码1)隐藏域2)varStatus="status" 3) name="itemsList[${status.index }].name"

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

...

<c:forEach items="${itemList }" var="item" varStatus="status">
<tr>
    <!-- 如果批量删除,可以用List<pojo>来接收,页面上input框的name属性值= vo中接收的集合属性名称+[list的下标]+.+list泛型的属性名称 -->
    <td>
        <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 }/items/itemEdit/${item.id}">修改</a></td>

</tr>
</c:forEach>

Name属性必须是包装pojo的list属性+下标+元素属性。
varStatus属性常用参数总结下:
${status.index} 输出行号,从0开始。
${status.count} 输出行号,从1开始。
${status.current} 当前这次迭代的(集合中的)项
${status.first} 判断当前项是否为集合中的第一项,返回值为true或false
${status.last} 判断当前项是否为集合中的最后一项,返回值为true或false
begin、end、step分别表示:起始序号,结束序号,跳跃步伐。

生成的html代码

<tr>
<td>
<input type="text" name=" itemsList[0].id" value="${item.id}"/>
</td>
<td>
<input type="text" name=" itemsList[0].name" value="${item.name }"/>
</td>
<td>
<input type="text" name=" itemsList[0].price" value="${item.price}"/>
</td>
</tr>
<tr>
<td>
<input type="text" name=" itemsList[1].id" value="${item.id}"/>
</td>
<td>
<input type="text" name=" itemsList[1].name" value="${item.name }"/>
</td>
<td>
<input type="text" name=" itemsList[1].price" value="${item.price}"/>
</td>
</tr>

controller

@RequestMapping("/updateAll")
    public String updateAll(QueryVo vo) throws Exception{
        System.out.println(vo);
        return "";
    }

注意:接收List类型的数据必须是pojo的属性,方法的形参为List类型无法正确接收到数据。这里与数组类型不同,单独的数组类型也可以接受页面传递拖来的数组类型参数。

RequestMapping

@RequestParam

在接受基本数据类型时候,要求controller中方法的参数名称和jsp页面中标签的name属性是一致的;在使用若不一致的时候可以使用@RequestParam来说明。
例如:

@RequestMapping("/updateitem")
    public String update(Integer id, String name, Float price, String detail) throws Exception{
        ...
    }
@RequestMapping("/updateitem")
    public String update( @RequestParam("id") Integer idxxx, String name, Float price, String detail) throws Exception{
        ...
    }

@RequestParam常用于处理简单类型的绑定

value:参数名字,即入参的请求参数名字,如value=“id”表示请求的参数区中的名字为item_id的参数的值将传入;
required:是否必须,默认是true,表示请求中一定要有相应的参数,否则将报;
TTP Status 400 - Required Integer parameter ‘XXXX’ is not present

defaultValue:默认值,表示如果请求中没有同名参数时的默认值

定义如下:

public String editItem(@RequestParam(value="id",required=true) String myId) {

}

形参名称为id,但是这里使用value=id限定请求的参数名为item_id,所以页面传递参数的名必须为id。
注意:如果请求参数中没有item_id将抛出异常:
HTTP Status 500 - Required Integer parameter ‘item_id’ is not present

这里通过required=true限定id参数为必需传递,如果不传递则报400错误,可以使用defaultvalue设置默认值,即使required=true也可以不传id参数值

URL路径映射

@RequestMapping(value="/item")或@RequestMapping("/item)
value的值是数组,可以将多个url映射到同一个方法

窄化请求路径

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

如下:
@RequestMapping放在类名上边,设置请求前缀
@Controller
@RequestMapping("/item")

方法名上边设置请求映射url:
@RequestMapping放在方法名上边,如下:
@RequestMapping("/queryItem ")

访问地址为:/item/queryItem

@Controller
//窄化请求映射:为防止你和你的队友在conroller方法起名的时候重名,所以相当于在url中多加了一层目录,防止重名
//例如:当前list的访问路径   localhost:8081/ssm0523-1/items/list.action
@RequestMapping("/items")
public class ItemsController {

    @Autowired
    private ItemsService itmesService;

    //@RequestMapping(value="/list", method=RequestMethod.GET)
    @RequestMapping("/list")
    public ModelAndView itemsList() throws Exception{
    }
    ...
}

请求方法限定

限定GET方法
@RequestMapping(method = RequestMethod.GET)

如果通过Post访问则报错:
HTTP Status 405 - Request method ‘POST’ not supported

例如:

@Controller
//窄化请求映射:为防止你和你的队友在conroller方法起名的时候重名,所以相当于在url中多加了一层目录,防止重名
//例如:当前list的访问路径   localhost:8081/ssm0523-1/items/list.action
@RequestMapping("/items")
public class ItemsController {

    @Autowired
    private ItemsService itmesService;

    @RequestMapping(value="/list", method=RequestMethod.GET)
    public ModelAndView itemsList() throws Exception{
    }
    ...
}

限定POST方法

@RequestMapping(method = RequestMethod.POST)

如果通过Post访问则报错:
HTTP Status 405 - Request method ‘GET’ not supported

GET和POST都可以
@RequestMapping(method={RequestMethod.GET,RequestMethod.POST})

controller方法返回值

方法返回值 ( 指定返回到哪个页面或者那个controller, 指定返回到页面的数据 )
controller指向结束后,可以将产生的数据返回给页面,也可以继续将请求,发送给另一个controller。无论是返回给页面还是将请求发给另外一个controller,都可以以重定向转发两种形式,默认是转发。
普通资源的转发和重定向
转发:
  请求资源路径(相对或者绝对)可以用视图解析器来配置统一的前缀和后缀简化返回路径的书写;
  forward:请求资源路径
重定向:"redirect:请求资源路径"

转发和重定向到controller,必须带字符串中必须包含,forward: 和 redirect:, 转发和重定向到普通资源时候,带不带特殊资源都可以。

返回ModelAndView

controller方法中定义ModelAndView对象并返回,对象中可添加model数据、指定view。
modelAndView.addObject(“itemList”, list); 指定返回页面的数据
modelAndView.setViewName(“itemList”); 指定返回的页面
配置视图解析器,并指定前缀后后缀名称:

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!-- 真正的页面路径 =  前缀 + 去掉后缀名的页面名称 + 后缀 -->
        <!-- 前缀 -->
        <property name="prefix" value="/WEB-INF/jsp/"></property>
        <!-- 后缀 -->
        <property name="suffix" value=".jsp"></property>
    </bean>
@RequestMapping("/list")
    public ModelAndView itemsList() throws Exception {
        List<Items> list = itmesService.list();
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("itemList", list);
        //转发到指定的逻辑视图页面
        modelAndView.setViewName("itemList");//默认是转发
        return modelAndView;
    }

取消视图解析器的前缀后后缀设置:

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!-- 真正的页面路径 =  前缀 + 去掉后缀名的页面名称 + 后缀 -->
        <!-- 前缀 -->
        <!--<property name="prefix" value="/WEB-INF/jsp/"></property>-->
        <!-- 后缀 -->
        <!--<property name="suffix" value=".jsp"></property>-->
    </bean>

根目录下建一个测试也test.jsp;在modelAndView中也可以设置转发和重定向资源。

@RequestMapping("/forward3")
    public ModelAndView testForward3() throws Exception {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("forward:/test.jsp");//默认是转发
        return modelAndView;
    }
 @RequestMapping("/redirect2")
    public ModelAndView testRedirect2() throws Exception {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("redirect:/test.jsp");//重定向
        return modelAndView;
    }

返回void

使用它破坏了springMvc的结构,所以不建议使用,若返回值为void,我们怎么将数据和返回路径返回给页面呢?
在controller方法形参上可以定义request和response,使用request或response指定响应结果:
如果controller返回值为void则不走springMvc的组件,所以要写页面的完整路径名称;
1、可以使用request.setAttribut 来给页面返回数据
2、使用request转向页面,如下:

//如果controller方法返回值为void,则不走springMvc组件,所以要写页面的完整路径名称
request.getRequestDispatcher("/WEB-INF/jsp/success.jsp").forward(request, response);
//request.getRequestDispatcher("页面路径").forward(request, response);

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

response.sendRedirect("url")

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

response.setCharacterEncoding("utf-8");
response.setContentType("application/json;charset=utf-8");
response.getWriter().write("json串");

返回字符串

逻辑视图名

视图解析器,会根据返回的逻辑视图名称,解析成真正的物理页面地址,然后进行数据显示。
controller方法返回字符串可以指定逻辑视图名,通过视图解析器解析为物理视图地址。

//指定逻辑视图名,经过视图解析器解析为jsp物理路径:
/WEB-INF/jsp/item/editItem.jsp
return "item/editItem";
 @RequestMapping("/itemEdit")
    public String itemEdit(HttpServletRequest request,
                           Model model) throws Exception {

        String id = request.getParameter("id");
        Items items = itmesService.findItemsById(Integer.parseInt(id));

        //Model模型:模型中放入了返回给页面的数据
        //model底层其实就是用的request域来传递数据,但是对request域进行了扩展.
        model.addAttribute("item", items);

        //如果springMvc方法返回一个简单的string字符串,那么springMvc就会认为这个字符串就是页面的名称
        return "/WEB-INF/jsp/editItem.jsp";//转发到editItem.jsp页面
    }

转发和重定向

model是对request的包装,大家都知道在使用request域对象的时候,凡是重定向,域对象中的数据就会丢失,然而model中的数据在重定向的时候任然保留,不会丢失。

普通资源

   //转发
   @RequestMapping("/forward1")
    public String testForward1() {
        return "/test.jsp";
    }

   //转发
    @RequestMapping("/forward2")
    public String testForward2() {
        return "forward:/test.jsp";
    }

   //转发
    @RequestMapping("/forward3")
    public ModelAndView testForward3() throws Exception {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("forward:/test.jsp");//默认是转发
        return modelAndView;
    }
    //重定向
    @RequestMapping("/redirect1")
    public String testRedirect1() {
        return "redirect:/test.jsp";
    }

    //重定向
    @RequestMapping("/redirect2")
    public ModelAndView testRedirect2() throws Exception {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("redirect:/test.jsp");//默认是转发
        return modelAndView;
    }

controller

转发和重定向到controller,必须带字符串中必须包含,forward: 和 redirect:, 转发和重定向到普通资源时候,带不带特殊资源都可以。
forward:controller的访问url
redirect:controller的访问url

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

//结果转发到editItem.action,request可以带过去
return "forward:editItem.action";

例子:updateitem(controller)——>itemEdit(controller)——>editItem.jsp

@RequestMapping("/itemEdit")
    public String itemEdit(HttpServletRequest request,
                           Model model) throws Exception {

        String id = request.getParameter("id");

        Items items = itmesService.findItemsById(Integer.parseInt(id));

        //Model模型:模型中放入了返回给页面的数据
        //model底层其实就是用的request域来传递数据,但是对request域进行了扩展.
        model.addAttribute("item", items);

        //如果springMvc方法返回一个简单的string字符串,那么springMvc就会认为这个字符串就是页面的名称
        return "/WEB-INF/jsp/editItem.jsp";//转发
    }
 @RequestMapping("/updateitem")
    public String update(Items items) throws Exception{
        itmesService.updateItems(items);
        model.addAttribute("id", items.getId());

        //在springMvc中凡是以redirect:字符串开头的都为转发
        return "forward:itemEdit";
        //重定向的时候model会将id按照问号传参的形式拼接在字符串后。
       //return "redirect:itemEdit";

    }

forward方式相当于“request.getRequestDispatcher().forward(request,response)”,转发后浏览器地址栏还是原来的地址。转发并没有执行新的request和response,而是和转发前的请求共用一个request和response。所以转发前请求的参数在转发后仍然可以读取到。

重定向

Contrller方法返回结果重定向到一个url地址,如下商品修改提交后重定向到商品查询方法,参数无法带到商品查询方法中。

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

redirect方式相当于“response.sendRedirect()”,转发后浏览器的地址栏变为转发后的地址,因为转发即执行了一个新的request和response。
由于新发起一个request原来的参数在转发时就不能传递到下一个url,如果要传参数可以/item/queryItem.action后边加参数,如下:
/item/queryItem?…&…..

model中的数据在重定向的时候任然保留,不会丢失。model会将按照问号传参的形式进行参数传递。

 @RequestMapping("/updateitem")
    public String update(Items items) throws Exception{
        itmesService.updateItems(items);
        //model中的数据在重定向的时候任然保留,不会丢失。
        model.addAttribute("id", items.getId());

        //在springMvc中凡是以redirect:字符串开头的都为重定向
        return "redirect:itemEdit";

    }
@RequestMapping("/itemEdit")
    public String itemEdit(HttpServletRequest request,
                           Model model) throws Exception {

        String id = request.getParameter("id");

        Items items = itmesService.findItemsById(Integer.parseInt(id));

        //Model模型:模型中放入了返回给页面的数据
        //model底层其实就是用的request域来传递数据,但是对request域进行了扩展.
        model.addAttribute("item", items);

        //如果springMvc方法返回一个简单的string字符串,那么springMvc就会认为这个字符串就是页面的名称
        return "/WEB-INF/jsp/editItem.jsp";//转发
    }

相对路径,绝对路径

forward:itemEdit.action表示相对路径,相对路径就是相对于当前目录,当前为类上面指定的items目录.在当前目录下可以使用相对路径随意跳转到当前类的某个方法中

forward:/itemEdit.action路径中以斜杠开头的为绝对路径,绝对路径从项目名后面开始算

异常处理

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

异常处理思路

系统中异常包括两类:

  • 预期异常
  • 运行时异常RuntimeException

前者通过捕获异常从而获取异常信息,后者主要通过规范代码开发、测试手段减少运行时异常的发生。

系统的dao、service、controller出现都通过throws Exception向上抛出,最后由springmvc前端控制器交由异常处理器进行异常处理。如下图:
这里写图片描述

自定义异常类

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

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

自定义异常处理器

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

}

配置异常处理器

在springmvc.xml中添加:

<!-- 配置全局异常处理器 -->
<bean id="handlerExceptionResolver" class="cn.ssm.controller.exceptionResolver.CustomExceptionResolver"/>

异常测试

error页面:

<%@ 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>

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

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

  @RequestMapping("/itemEdit")
  public String itemEdit(HttpServletRequest request,
                          Model model) throws Exception {

       String id = request.getParameter("id");

       Items item = itmesService.findItemsById(Integer.parseInt(id));

    if(item == null){
        throw new CustomException("商品信息不存在!");
    }

       //Model模型:模型中放入了返回给页面的数据
       //model底层其实就是用的request域来传递数据,但是对request域进行了扩展.且在重定向的时候model中的数据是不会丢失的。
       model.addAttribute("item", item );

       //如果springMvc方法返回一个简单的string字符串,那么springMvc就会认为这个字符串就是页面的逻辑名称
       return "editItem";//转发
   }

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

上传图片

配置虚拟目录

一般都会都单独的文件/图片服务器,来物理保存上传的数据文件/图片。

下面来简单模拟一下:

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

<Context docBase="D:\temp" path="/pic" reloadable="false"/>

访问 http://localhost:8080/pic/fileName 即可访问 D:\temp下的图片。
/pic 相当于一个单独的项目

也可以通过eclipse配置:
这里写图片描述

jar包

文件图片上传的时候表单会添加一个enctype="multipart/form-data"属性,添加该属性之后,数据便会以二进制流的方式进行传输。在没有改属性的时候是按照字符串的方式进行传输。

使用enctype属性之后,在接受数据解析的时候会比较复杂,springMVC提供了解析接口,所以这里配合fileupload组件可以很方便的帮我们完成解析工作。

CommonsMultipartResolver解析器 (springMVC提供) 依赖commons-fileupload和commons-io,加入如下jar包:
这里写图片描述

配置解析器

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

图片上传实现

controller
file的name与controller形参一致
MultipartFile类型用来接收文件,该类型将文件名称,路径等很多信息进行了封装。

    @RequestMapping("/updateitem")
    public String update(MultipartFile pictureFile,Items items, Model model, HttpServletRequest request) throws Exception{
        //1. 获取图片完整名称
        String fileStr = pictureFile.getOriginalFilename();
        //2. 使用随机生成的字符串+源图片扩展名组成新的图片名称,防止图片重名
        String newfileName = UUID.randomUUID().toString() + fileStr.substring(fileStr.lastIndexOf("."));
        //3. 将图片保存到硬盘
        pictureFile.transferTo(new File("E:\\image\\" + newfileName));
        //4.将图片名称保存到数据库
        items.setPic(newfileName);
        itmesService.updateItems(items);

        return "editItem";

    }

页面
form添加enctype=”multipart/form-data”:

<form id="itemForm"
action="${pageContext.request.contextPath }/item/editItemSubmit.action"
        method="post" enctype="multipart/form-data">
        <input type="hidden" name="pic" value="${item.pic }" />

file的name与controller形参一致:

<tr>
    <td>商品图片</td>
    <td><c:if test="${item.pic !=null}">
            <img src="/pic/${item.pic}" width=100 height=100 />
            <br />
        </c:if> <input type="file" name="pictureFile" /></td>
</tr>

json数据交互

一般json数据交互的流程是这样的:
客户端ajax请求,发送json数据包——> 服务端接受请求解析数据json数据包(有很多的第三方解析工具包)——> 服务端通过response向客户端写回json数据包。
springMVC提供了json的解析接口,但是没有具体实现,需要我们提供解析包(有很多优秀的第三方解析包),通过@RequestBody@ResponseBody(具体使用稍后介绍),来实现json数据格式和pojo类的相互转换。

@RequestBody

作用:
@RequestBody注解用于读取http请求的内容(字符串),通过springmvc提供的HttpMessageConverter接口将读到的json、xml等格式的数据内容转换为java对象并绑定controller方法的参数上。
List.action?id=1&name=zhangsan&age=12

接下来的例子中:
@RequestBody注解实现接收http请求的json数据,将json数据转换为java对象

ResponseBody

作用:
该注解用于将Controller的方法返回的对象,通过HttpMessageConverter接口转换为指定格式的数据如:json,xml等,通过Response响应给客户端

接下来的例子中:
@ResponseBody注解实现将controller方法返回对象转换为json响应给客户端

例子:请求json,响应json实现

Springmvc默认用MappingJackson2HttpMessageConverter对json数据进行转换,需要加入jackson的包,如下:
这里写图片描述

配置json转换器

在注解适配器中加入messageConverters

<!-- 在注解适配器中加入messageConverters -->
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <property name="messageConverters">
            <list>
                <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"></bean>
            </list>
        </property>
    </bean>

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

controller编写

@RequestBody注解实现接收http请求的json数据,将json数据转换为java对象
@ResponseBody注解实现将controller方法返回对象转换为json响应给客户端

//导入jackson的jar包在 controller的方法中可以使用@RequestBody,让spirngMvc将json格式字符串自动转换成java中的pojo
//页面json的key要等于java中pojo的属性名称
//controller方法返回pojo类型的对象,并且用@ResponseBody注解,springMvc会自动将pojo对象转换成json格式字符串,并且使用response相应给客户端。
@RequestMapping("/sendJson")
@ResponseBody
public Items json(@RequestBody Items items) throws Exception{
    System.out.println(items);
    return items;
}

页面js

引入 js:

<script type="text/javascript" 
src="${pageContext.request.contextPath }/js/jquery-1.4.4.min.js"></script>
//请求json响应json
function request_json(){
    $.ajax({
        type:"post",
        url:"${pageContext.request.contextPath }/item/editItemSubmit_RequestJson.action",
        contentType:"application/json;charset=utf-8",
        data:'{"name":"测试商品","price":99.9}',
        success:function(data){
            alert(data);
        }
    });
}

测试结果

这里写图片描述
从上图可以看出请求的数据是json格式。

RESTful支持

什么是RESTful

Restful就是一个资源定位及资源操作的风格(url的命名风格)。不是标准也不是协议,只是一种风格,是对http协议的诠释。
资源定位:互联网所有的事物都是资源,Restful格式要求url中没有动词,只有名词。没有参数(通俗解释,没有问号传参,没有.action后缀)
Url格式:http://blog.csdn.net/beat_the_world/article/details/45621673
以前:http://blog.csdn.net/beat_the_world/article/details?id=45621673
资源操作:使用put、delete、post、get,使用不同方法对资源进行操作。分别对应添加、删除、修改、查询。一般使用时还是post和get。Put和Delete几乎不使用。

添加DispatcherServlet的rest配置

修改配置为<url-pattern>/</url-pattern>

  <!-- springmvc前端控制器 -->
  <servlet>
    <servlet-name>springMvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:SpringMvc.xml</param-value>
    </init-param>
    <!-- 在tomcat启动的时候就加载这个servlet -->
    <load-on-startup>2</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>springMvc</servlet-name>
    <!-- 
    *.action    代表拦截后缀名为.action结尾的
    /           拦截所有但是不包括.jsp
    /*          拦截所有包括.jsp
     -->
    <url-pattern>/</url-pattern>
  </servlet-mapping>

URL 模板模式映射

@RequestMapping(value="/ viewItems/{id}"){×××}占位符,请求的URL可以是“/viewItems/1”或“/viewItems/2”,通过在方法中使用@PathVariable获取{×××}中的×××变量

@PathVariable用于将请求URL中的模板变量映射到功能处理方法(controller 的方法)的参数上。

     /*
     * 通过@PathVariable可以接收url中传入的参数
     * @RequestMapping("/itemEdit/{id}")中接收参数使用大括号中加上变量名称,
     * @PathVariable中的变量名称要和@RequestMapping中的变量名称相同
     */
    @RequestMapping("/itemEdit/{id}")
    public String itemEdit(@PathVariable("id") Integer id, HttpServletRequest reuqest, 
             Model model) throws Exception{

        Items items = itmesService.findItemsById(id);
        model.addAttribute("item", items);

        return "editItem";
    }

如果RequestMapping中表示为”/itemEdit/{id}”,id和形参名称一致,@PathVariable不用指定名称。
商品查询的controller方法也改为rest实现:

@RequestMapping("/updateitem")
    public String update(MultipartFile pictureFile,Items items, Model model, HttpServletRequest request) throws Exception{

        String fileStr = pictureFile.getOriginalFilename();
        String newfileName = UUID.randomUUID().toString() + fileStr.substring(fileStr.lastIndexOf("."));
        pictureFile.transferTo(new File("E:\\image\\" + newfileName));
        items.setPic(newfileName);
        itmesService.updateItems(items);

        //重定向:浏览器中url发生改变,request域中的数据不可以带到重定向后的方法中,items.getId()不是request域对象,可以携带到重定向的方法中。
        return "redirect:itemEdit/"+items.getId();

        //model.addAttribute("id", items.getId());
        //model中的数据虽然可以重定向之后带到后续参数中共,但是这里是按照问号传参数形式:
        //http://localhost:8080/items/itemEdit?id=1
        //return "redirect:itemEdit"
    }

静态资源访问<mvc:resources>

如果在DispatcherServlet中设置url-pattern为 <url-pattern>/</url-pattern>,虽然不会拦截jsp文件,但是会拦截所有的静态文件,因此必须对静态资源进行访问处理

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

如下在springMVC核心配置文件中对 js,css,img 文件的访问配置:

<!-- 静态资源访问 -->
<!-- /**代表不下的所有文件,包括子文件夹下的文件,这里与web.xml总的DispatcherServlet中<url-parttern>的配置规则不一样  -->  
 <mvc:resources location="/img/" mapping="/img/**"/>   
 <mvc:resources location="/js/" mapping="/js/**"/>    
 <mvc:resources location="/css/" mapping="/css/**"/>  

处理静态资源被“/”所拦截的问题 还有别的解决办法:

SPRING-MVC访问静态文件,如jpg,js,css

方案一:激活Tomcat的defaultServlet来处理静态文件

<servlet-mapping>       
    <servlet-name>default</servlet-name>      
    <url-pattern>*.jpg</url-pattern>     
    </servlet-mapping>    <servlet-mapping>           
    <servlet-name>default</servlet-name>        
    <url-pattern>*.js</url-pattern>    
    </servlet-mapping>    <servlet-mapping>            
    <servlet-name>default</servlet-name>           
    <url-pattern>*.css</url-pattern>      
</servlet-mapping>   

要配置多个,每种文件配置一个
要写在DispatcherServlet的前面, 让defaultServlet先拦截,这个就不会进入Spring了,我想性能是最好的吧。
Tomcat, Jetty, JBoss, and GlassFish 默认 Servlet的名字 – “default”
Google App Engine 默认 Servlet的名字 – “_ah_default”
Resin 默认 Servlet的名字 – “resin-file”
WebLogic 默认 Servlet的名字 – “FileServlet”
WebSphere 默认 Servlet的名字 – “SimpleFileServlet”

方案二: 在spring3.0.4以后版本提供了<mvc:resources location="" mapping=""/>

方案三 ,使用<mvc:default-servlet-handler/>

拦截器

概述

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

拦截器定义

实现HandlerInterceptor接口,该接口提供了三个方法,分别在controller运行的不同时机执行。各个方法的执行顺序如下。

Public class HandlerInterceptor1 implements HandlerInterceptor{

    /**
     * 执行时机:controller方法没有被执行,且视图(如:modelAndview)没有被返回
     * 返回布尔值:返回true表示继续执行,返回false中止执行
     * 使用场景:这里可以加入登录校验、权限拦截等
     */
    @Override
    Public boolean preHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler) throws Exception {
        // TODO Auto-generated method stub
        Return false;
    }
    /**
     * 执行时机:Controller方法已经执行,且视图(如:modelAndview)没有返回
     * 使用场景:这里可在返回用户前对模型数据进行加工处理,可以在此方法中设置全局的数据处理业务,比如这里加入公用信息(如天气状况)以便页面显示
     */
    @Override
    Public void postHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        // TODO Auto-generated method stub

    }
    /**
     * 执行时机:controller已经执行,且视图(如:modelAndview)已经返回后调用此方法
     * 使用场景: 记录操作日志,记录登录用户的ip,时间等..
     *          这里可得到执行controller时的异常信息
     *          这里可记录操作日志,资源清理等
     */
    @Override
    Public void afterCompletion(HttpServletRequest request,
            HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        // TODO Auto-generated method stub

    }

}

拦截器配置

概述

(1) 拦截器拦截的请求是建立在前端控制器配置之下的,若DispatcherServlet拦截的是*.action,则拦截器即使配置 /**,则拦截器拦截的也只是所有 *.action的请求。若DispatcherServlet拦截的是/,则拦截器配置 /**才是拦截所有资源。

(2) 前端控制器可以配置多个,名字不相同即可,配置*.action是正常的使用,配置/是springmvc restful风格的使用方式,不过当前端控制器有配置/ 时,必须再使用一个<mvc resource:>标签对静态资源进行排除,否则springmvc的会将静态资源也会当成 Handler的url去找对应的Handler ,这样静态资源就无法被正常的访问了。

(3) 配置拦截器是针对处理器映射器进行配置的,有如下两种方式:
  struts中是有一个大的拦截器链,它是一个共用的东西,可以把它添加到任何的action链接,都让它拦截。但是spring的拦截器不是全局的

方式一:针对某种处理器映射器配置拦截器

  springmvc拦截器针对HandlerMapping进行拦截设置,如果在某个HandlerMapping中设置拦截,经过该HandlerMapping映射成功的handler最终使用该拦截器。一般不推荐使用
这里写图片描述

方式二:针对所有mapping配置全局拦截器:

springmvc中没有全局拦截器概念,但是springmvc可以配置类似全局的拦截器,使用这种配置,springmvc将配置的拦截器分别注入到多个处理器映射器HandlerMapping中。同样也需要放行静态资源
这里写图片描述

多个拦截器的执行顺序:

若配置多个拦截器,多个拦截器的执行顺序为:按照在springMVC核心配置文件中的配置顺序来执行拦截器。

<!--拦截器 -->
<mvc:interceptors>
    <!-- 
        /** 代表不下的所有文件,包括子文件夹下的文件,这里与web.xml总的DispatcherServlet中<url-parttern>的配置规则不一样  
    -->  
    <!-- 多个拦截器的执行顺序等于springMvc.xml中的配置顺序 -->
    <mvc:interceptor>
        <!-- 拦截请求的路径    要拦截所有必需配置成/** -->
        <mvc:mapping path="/**"/>
        <!-- 指定拦截器的位置 -->
        <bean class="cn.springmvc.filter.HandlerInterceptor1"></bean>
    </mvc:interceptor>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean class="cn.springmvc.filter.HandlerInterceptor2"></bean>
    </mvc:interceptor>
</mvc:interceptors>

这里写图片描述
这里写图片描述
正常流程测试:
定义两个拦截器分别为:HandlerInterceptor1和HandlerInteptor2,每个拦截器的preHandler方法都返回true。

public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2) throws Exception {
        System.out.println("HandlerInterceptor1...preHandle");
        return true;
    }

运行流程:

HandlerInterceptor1..preHandle..
HandlerInterceptor2..preHandle..

HandlerInterceptor2..postHandle..
HandlerInterceptor1..postHandle..

HandlerInterceptor2..afterCompletion..
HandlerInterceptor1..afterCompletion..

中断流程测试:

定义两个拦截器分别为:HandlerInterceptor1和HandlerInteptor2。HandlerInterceptor1的preHandler方法返回false,HandlerInterceptor2返回true.
运行流程如下:

HandlerInterceptor1..preHandle..

从日志看出第一个拦截器的preHandler方法返回false后第一个拦截器只执行了preHandler方法,其它两个方法没有执行,第二个拦截器的所有方法不执行,且controller也不执行了。

HandlerInterceptor1的preHandler方法返回true,HandlerInterceptor2返回false,
运行流程如下

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

从日志看出第二个拦截器的preHandler方法返回false后第一个拦截器的postHandler没有执行,第二个拦截器的postHandler和afterCompletion没有执行,且controller也不执行了。

总结:
preHandle按拦截器定义顺序调用
postHandler按拦截器定义逆序调用
afterCompletion按拦截器定义逆序调用

postHandler在拦截器链内所有拦截器返成功调用
afterCompletion只有preHandle返回true才调用

参考文章:
https://www.cnblogs.com/gongchengshixiaobai/p/8039067.html

拦截器应用

登录权限验证

处理流程
1、有一个登录页面,需要写一个controller访问页面
2、登录页面有一提交表单的动作。需要在controller中处理。
  a) 判断用户名密码是否正确
  b) 如果正确 想session中写入用户信息
  c) 返回登录成功,或者跳转到商品列表
3、拦截器。
  a) 拦截用户请求,判断用户是否登录,若是放行,若不是进行下两步:
    a.b) 如果用户已经登录。放行
    a.c) 如果用户未登录,跳转到登录页面。

拦截器

Public class LoginInterceptor implements HandlerInterceptor{

    @Override
    Public boolean preHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler) throws Exception {

        //如果是登录页面则放行
        if(request.getRequestURI().indexOf("login.action")>=0){
            return true;
        }
        HttpSession session = request.getSession();
        //如果用户已登录也放行
        if(session.getAttribute("user")!=null){
            return true;
        }
        //用户没有登录挑战到登录页面
        request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response);

        return false;
    }
}

用户登录controller

@Controller
@RequestMapping("/login")
public class LoginController {

    //跳转到登录页面
    @RequestMapping("/login")
    public String login() throws Exception{
        return "login";
    }

    @RequestMapping("/submit")
    public String submit(String username, String pwd ,HttpServletRequest request) throws Exception{

        HttpSession session = request.getSession();
        //判断用户名密码的正确性,如果正确则将登录信息放入session中
        //这里简写,真正项目中需要去数据库中校验用户名和密码
        if(username != null){
            session.setAttribute("username", username);
        }

        //跳转到列表页
        return "redirect:/items/list";
    }

    //退出
    @RequestMapping("/logout")
    public String logout(HttpSession session)throws Exception{

        //session过期
        session.invalidate();

        return "redirect:items/list";
    }

}

配置拦截器
在springMVC核心配置文件中配置拦截器。

    <!-- 配置拦截器 -->
    <!--<mvc:interceptors>-->
        <mvc:interceptor>
            <!-- 拦截请求的路径    要拦截所有必需配置成/** -->
            <mvc:mapping path="/**"/>
            <!-- 指定拦截器的位置 -->
            <bean class="cn.itheima.interceptor.LoginInterceptor"></bean>
        </mvc:interceptor>
    </mvc:interceptors>

总结:

☆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

猜你喜欢

转载自blog.csdn.net/u010758410/article/details/80060439