- 高级参数绑定(重点)
a) 数组类型的参数绑定
b) List类型的绑定
@RequestMapping注解的使用(重点)
Controller方法返回值(重点)
SpringMVC中异常处理
图片上传处理
Json数据交互
Springmvc实现Restful
拦截器
高级参数绑定(重点)
数组类型
数组类型的参数可以传递一批相同的数据到Controller的方法中。
需求
批量删除:在商品列表页面选中多个商品,然后删除。
需求分析
此功能要求商品列表页面中的每个商品前有一个checkbook,选中多个商品后点击删除按钮把商品id传递给Controller,根据商品id删除商品信息。
功能分解
前端:1)能选中多个商品;2)能提交选中的多个商品
后端:1)能接收到选中商品的id;2)进行删除处理
传参规范
总结上面两种规范:页面提交的控件name属性值必须等于Controller方法数组形参名或者形参对象中的数组属性名。
演示代码
- Jsp
可以重新创建一个专门演示批量删除的画面【itemListDelBatch.jsp】,利用原来的itemList.jsp拷贝一个,然后在表格的最前面增加一列checkbox。
【itemListDelBatch.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 }/delAll.action” method=“post”>
查询条件:
<table width=“100%” border=1>
<tr>
<td><input type=“submit” value=“批量删除”/></td>
</tr>
</table>
商品列表:
<table width=“100%” border=1>
<tr>
<td></td>
{width=”2.4256944444444444in” height=”1.163888888888889in”} <td>商品名称</td>
<td>商品价格</td>
<td>生产日期</td>
<td>商品描述</td>
<td>操作</td>
</tr>
<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 }/toEdit.action?id=${item.id}”>修改</a></td>
</tr>
</c:forEach>
</table>
</form>
</body>
</html>
提交相同名称的参数时,SpringMVC会自动把它们处理成数组。
- Controller
【ItemsController.java】先定义一个方法用于itemListDelBatch.jsp页面的显示:
@RequestMapping(“/listForDel”)
public ModelAndView getListForDel() throws Exception {
List<Items> itemsList = itemsService.getItemsList();
ModelAndView modelAndView = new ModelAndView();
// request.setAttribute(key, value)
// 底层仍然是把属性名和属性值放到request对象中
// jsp页面永远是从request对象中取得数据的
modelAndView.addObject(“itemsList”, itemsList);
modelAndView.setViewName(“items/itemListDelBatch”);
return modelAndView;
}
然后再定义一个执行删除的方法(这里主要是学习如何传参数,不做具体的删除操作)
方式一:直接传递数组参数
·传参规范:页面上input框的name属性值必须等于接收时数组参数的变量名称。
/**
* 演示接收数组(直接接收数组)
*/
@RequestMapping(“/delAll”)
public String delAll(Integer[] ids) throws Exception {
// 批量删除:可以循环这个ids数组,逐条删除即可。
return “success”;
}
方式二:在Vo中传递数组参数
【QueryVo.java】
package cn.itcast.pojo;
import java.util.List;
public class QueryVo {
private Integer[] ids;
// getter/setter方法。。。。。。
}
·传参规范:页面上input框的name属性值必须等于接收时Vo中数组类型属性的变量名称。
/**
* 演示接收数组(用Vo传递数组)
*/
@RequestMapping(“/delAll”)
public String delAll(QueryVo vo) throws Exception {
// 批量删除:可以循环这个Vo中的ids数组,逐条删除即可。
return “success”;
}
用@RequestParam注解
这种方式是对直接接收的另一种处理方式,可以解决HTTP参数与形参名称不一致时的参数接收:
/**
* 批量删除
* 演示:@RequestParam注解修饰形参,接收HTTP数组参数(直接传递数组类型)
*/
@RequestMapping(“/deleteitem”)
public String deleteItems(@RequestParam(value=”ids”) Integer[] itemsIds) throws Exception {
// 遍历数组,逐条进行删除….
return “common/success”;
}
List集合类型
可以利用List集合类型的参数传递多条数据进行批量处理。比如批量更新。
需求
批量更新:实现商品数据的批量修改。
需求分析
要想实现商品数据的批量修改,需要在商品列表中可以对商品信息进行修改,并且可以批量提交修改后的商品数据。提交的数据应该是一个List。
功能分解:
**前端:**1)列表改成input输入框;2)定义改好的input框的name属性;
**后端:**1)能接收到提交过来的更新数据;2)批量更新处理
演示代码
- 接收商品列表的pojo
注意:SpringMVC不能直接传递List集合类型的参数,必须包装在Vo中。这是SpringMVC框架的强制要求。
【QueryVo.java】
package cn.itcast.pojo;
import java.util.List;
public class QueryVo {
private List<Items> updateItemsList;
// setter/getter方法。。。。。。。
}
- Jsp
可以重新创建一个专门演示批量更新的画面【itemListUpdBatch.jsp】,利用原来的itemList.jsp拷贝一个,然后将表格中的项目都改成input输入框,可以直接修改数据。
【itemListUpdBatch.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>
<td><input type=“submit” value=“批量修改”/></td>
</tr>
</table>
商品列表:
<table width=“100%” border=1>
<tr>
<td>商品名称</td>
<td>商品价格</td>
<td>生产日期</td>
<td>商品描述</td>
<td>操作</td>
</tr>
<c:forEach items=”${itemList }” var=“item” varStatus=“status”> <!– status.index表示集合的下标,从0开始 –>
<tr>
<td><input type=“text” name=“updateItemsList[${status.index }].name” value=”${item.name }” /></td>
<td><input type=“text” name=“updateItemsList[${status.index }].price” value=”${item.price }” /></td>
<td><input type=“text” name=“updateItemsList[${status.index }].createtime” value=”<fmt:formatDate value=”${item.createtime}” pattern=“yyyy-MM-dd HH:mm:ss”/>” /></td>
<td><input type=“text” name=“updateItemsList[${status.index }].detail” value=”${item.detail }” /></td>
<td><input type=“hidden” name=“updateItemsList[${status.index }].id” value=”${item.id}” />
<a href=”${pageContext.request.contextPath }/toEdit.action?id=${item.id}”>修改</a>
</td>
</tr>
</c:forEach>
</table>
</form>
</body>
</html>
HTTP参数提交的样子:
{width=”7.268055555555556in”
height=”4.600694444444445in”}
HTTP参数传输的实际样子:
{width=”7.268055555555556in”
height=”3.713888888888889in”}
附:
varStatus属性常用参数总结下:
${status.index} 输出行号,从0开始。
${status.count} 输出行号,从1开始。
${status.current} 当前这次迭代的(集合中的)项
${status.first} 判断当前项是否为集合中的第一项,返回值为true或false
${status.last} 判断当前项是否为集合中的最后一项,返回值为true或false
begin属性、end属性、step属性分别表示:起始序号,结束序号,跳跃步伐。
其中[]被encode了,仔细辨认可以看出来。
提交名称开头相同,又带有下标和后缀名的参数,SpringMVC会根据形参类型反射得到的List<T>属性自动处理成同样的List<T>。
- Controller
【ItemsController.java】先定义一个方法用于itemListDelBatch.jsp页面的显示:
@RequestMapping(“/listForUpd”)
public ModelAndView getListForUpd() throws Exception {
List<Items> itemsList = itemsService.getItemsList();
ModelAndView modelAndView = new ModelAndView();
// request.setAttribute(key, value)
// 底层仍然是把属性名和属性值放到request对象中
// jsp页面永远是从request对象中取得数据的
modelAndView.addObject(“itemsList”, itemsList);
modelAndView.setViewName(“items/itemListUpdBatch”);
return modelAndView;
}
再定义一个方法,用来更新处理。这里不做具体的更新,我们主要学习如何接收List参数。
/**
* 演示接收List
*/
@RequestMapping(“/updateAll”)
public String updateAll(QueryVo vo) throws Exception {
System.out.println(vo);
// 批量修改:遍历List,逐个修改。
return “success”;
}
传参规范
SpringMVC不能直接传递List集合类型的参数,必须包装在java
bean中。这是SpringMVC框架的强制要求。页面上input框的name属性值必须等于Controller方法形参java
bean中List属性名 + [集合下标] + . + List泛型中的属性名。
例如:
<input type=“text” name=“itemsUpdLst[${status.index }].name”
value=”${item.name }” />
@RequestMapping(重点)
注解@RequestMapping控制着url到请求方法的映射,对url能访问到Controller中正确的响应方法起到了至关重要的作用。
使用在方法上
标记url到请求方法的映射,即通过最终的url找到Controller中对应的方法。这个在以前的示例中已经练习了。
使用在类上
官方的说法叫做窄化请求映射,其实就是为了防止你和你的同事起的url重名,在类上多给url加了一层目录。
【ItemsController.java】的修改:
@Controller
@RequestMapping(“/items”)
public class ItemsController {
。。。。。。
}
访问地址从【http://localhost:8080/ssm-1/list.action】变成了【http://localhost:8080/ssm-1/items/list.action】,多了一层【/items】目录。
注意:
此时SpringMVC.xml中的视图解析器的前缀的开头要加斜杠/WEB-INF/jsp,如果写成WEB-INF/jsp就会被SpringMVC认为是相对于【/items】下的相对路径,直接拼在【/items】的后面了。
限制请求类型
Http请求类型:post、get、put、delete等,不过put、delete现在已经很少使用了。
post与get:
必须明确指定是post时,才是post请求;否则默认是get请求。
在浏览器中输入url提交的请求是get请求。
@RequestMapping使用方式:
a. 默认方式:之前使用@RequestMapping都是它的默认使用方式,默认的@RequestMapping支持所有的Http请求类型。
b. 正常方式:@RequestMapping(value=”具体url路径”,
method=某一种http请求类型)
指定了Http请求类型就限制只能用指定的请求类型提交请求。
a. 多请求方式:@RequestMapping(value=”具体url路径”, method={请求类型1,
请求类型2,……})
- 示例:
◆限定只允许GET方法访问:
@RequestMapping(value=”/list”, method = RequestMethod.GET)
◆限定只允许POST方法访问:
@RequestMapping(value=”/list”, method = RequestMethod.POST)
◆GET和POST方法都可以:
@RequestMapping(value=”/list”, method = {RequestMethod.POST,
RequestMethod.GET })或
@RequestMapping(”/list”)
以商品列表画面的访问为例,是通过get方式访问的,因此可以在@RequestMapping的method属性中设置请求的类型是【RequestMethod.GET】:
@RequestMapping(value=”/list”, method=RequestMethod.GET)
public ModelAndView itemsList() throws Exception {
List<Items> itemsList = itemsService.findItemsList();
// 1. 设置返回页面需要的数据 2. 指定返回页面的地址
ModelAndView modelAndView = new ModelAndView();
// 1. 设置返回页面需要的数据
modelAndView.addObject(“itemList”, itemsList);
// 2. 逻辑视图名称的设置(就是视图文件的文件名)
modelAndView.setViewName(“itemList”);
return modelAndView;
}
尝试是否能访问成功。
然后把【RequestMethod.GET】改成【RequestMethod.POST】,再试一次,会报405错误:
HTTP Status 405 - Request method ‘GET’ not supported
这说明请求访问受限了。
同样如果用POST方法访问【RequestMethod.GET】修饰的URL也会报405错误:
HTTP Status 405 - Request method ‘POST’ not supported
用途
大家是不是有一个疑问,感觉这个功能多余,还不如不限制。是的,如果在传统web系统中这个限制功能使用的很少。但在RESTful的url中十分有用。
Controller方法的返回值(重点)
提到Controller方法的返回值主要指两方面内容:
怎样指定返回页面的路径?
怎样指定返回页面的数据?
ModelAndView模型和视图
可以调用里面的方法指定页面的地址;还可以调用里面的方法指定返回给页面的数据。
这个在第一天的内容中已经介绍过了。这里就不多说了。
返回普通的字符串
Controller方法如果返回的是普通字符串,那就是视图的逻辑视图名或物理视图名。这个在第一天的代码示例中已经介绍过了。
返回不普通的字符串
请求转发:【forward:】开头的字符串,后面跟转发的URL路径,URL如果有后缀(.action)要加上。
重定向:【redirect:】开头的字符串,后面跟重定向的URL路径,URL如果有后缀(.action)要加上。
注意:关键字后面的冒号是半角。
请求转发与重定向的区别(特征)
请求转发是后台程序方法之间跳转时使用的,由于是后台之间的跳转,因此浏览器中URL不发生改变,这也说明还是在同一个Request请求中,因此转发后的方法仍然可以接收转发前从浏览器提交上来的Request对象的HTTP参数。
重定向相当于再次从浏览器发起一个新的Request请求,由于是从前台重新发起的,因此浏览器中的URL发生改变,因不是同一个Request请求,因此重定向后的方法不能接收重定向前从浏览器提交上来的HTTP参数。
请求转发的试验
1. 试验目的:验证请求转发的特征,理解请求转发。
2.
试验方法:一览页面点击【修改】进入编辑页面,在编辑页面点击【保存】后再次回到本编辑页面。
3. 试验过程:
【itemList.jsp】页面提交:
{width=”7.268055555555556in”
height=”2.7055555555555557in”}
……
<c:forEach items=”${itemList }” var=“item”>
<tr>
<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 }/items/itemEdit.action?id=${item.id}”>修改</a></td>
</tr>
</c:forEach>
……
【editItem.jsp】页面,对应方法接收名为id的参数。
{width=”7.268055555555556in”
height=”2.5756944444444443in”}
/**
* 根据id查询商品的详细信息
*/
@RequestMapping(“/itemEdit”)
public String getItemById(HttpServletRequest request, Model model) throws Exception {
// 取得Request对象的HTTP参数id
Integer id = Integer.parseInt(String.valueOf(request.getParameter(“id”)));
Items items = itemsService.getItemById(id);
// 将商品详细信息返回给页面
model.addAttribute(“item”, items);
return “items/editItem”;
}
【保存】提交进行更新:提交上来的参数中包含名为id的商品全部信息:
{width=”4.796188757655293in”
height=”3.7820516185476816in”}
/**
* 演示请求转发:
* 转发前的更新处理
*/
@RequestMapping(“/updateitem”)
public String updateItemsById(Items items) throws Exception {
itemsService.updateItemById(items);
return “forward:itemEdit.action”;
}
然后请求转发回根据id查询商品详细信息的方法,重新查询并再次显示编辑页面:
因为请求转发的特征是同一个Request请求,也就是上面保存更新时候的Request请求对象,因此这个时候仍然可以取得保存更新时提交上来的参数,其中刚好也有名为id的参数,因此可以执行查询:
{width=”7.268055555555556in”
height=”5.625694444444444in”}
同时也看到浏览器的url没有发生变化,还是保存更新时候的url【/updateitem】,这样证明了没有惊动客户端,说明请求转发完全是后台的事情:
{width=”7.268055555555556in”
height=”2.4770833333333333in”}
重定向的验证
1. 试验目的:验证重定向的特征,理解重定向。
2. 试验方法:同请求转发。
3. 试验过程:
【itemList.jsp】页面提交:
{width=”7.268055555555556in”
height=”2.7055555555555557in”}
……
<c:forEach items=”${itemList }” var=“item”>
<tr>
<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 }/items/itemEdit.action?id=${item.id}”>修改</a></td>
</tr>
</c:forEach>
……
【editItem.jsp】页面,对应方法接收名为id的参数。
{width=”7.268055555555556in”
height=”2.5756944444444443in”}
/**
* 根据id查询商品的详细信息
*/
@RequestMapping(“/itemEdit”)
public String getItemById(HttpServletRequest request, Model model) throws Exception {
// 取得Request对象的HTTP参数id
Integer id = Integer.parseInt(String.valueOf(request.getParameter(“id”)));
Items items = itemsService.getItemById(id);
// 将商品详细信息返回给页面
model.addAttribute(“item”, items);
return “items/editItem”;
}
【保存】提交进行更新:提交上来的参数中包含名为id的商品全部信息:
{width=”5.482280183727034in”
height=”3.993589238845144in”}
/**
* 根据id更新商品信息
* 演示:SpringMVC的重定向
*/
@RequestMapping(“/updateitem”)
public String updateItemsById(Items items) throws Exception {
itemsService.updateItemById(items);
return “redirect:itemEdit.action”;
}
然后重定向回根据id查询商品详细信息的方法,重新查询并再次显示编辑页面:
因为重定向的特征是重新回到浏览器发起一个新的url
Request请求,但问题出现了,新的Request中没有保存更新时提交的HTTP参数:
{width=”5.467948381452318in”
height=”3.3650918635170606in”}
解决方法:利用SpringMVC的Model可以给重定向后的方法传递参数
/**
* 根据id更新商品信息
* 演示:SpringMVC的重定向
*/
@RequestMapping(“/updateitem”)
public String updateItemsById(Items items, Model model) throws Exception {
itemsService.updateItemById(items);
// 使用model对象传递id
model.addAttribute(“id”, items.getId());
return “redirect:itemEdit.action”;
}
再次启动运行:
{width=”5.512820428696413in”
height=”3.4016622922134734in”}
浏览器的url不再是【/updateitem】:说明重定向操作不是后台的跳转,而是由浏览器重新发起的请求。
{width=”7.268055555555556in”
height=”2.408333333333333in”}
为什么重定向可以使用Model传递参数?
因为重定向的字符串返回时,SpringMVC看到是redirect开头最终由视图解析器解析后会生成一个专门处理重定向的视图View对象:org.springframework.web.servlet.view.RedirectView,这个View会把前端控制器给它的Model数据放到新的Request对象的Parameter(HTTP参数)中,这样重定向后就会得到参数。
如果是非重定向的字符串返回(逻辑视图名或请求转发)时,视图解析器会生成普通的视图View对象:org.springframework.web.servlet.view.InternalResourceView,这个View会把前端控制器给它的Model数据放到同一个Request对象的Attribute(属性)中。
因此大家在使用SpringMVC时要善用Model(ModelAndView)。
Controller方法的返回jsp页面是请求转发还是重定向?
Jsp本身就是一个Servlet对象,Controller方法在向jsp页面跳转后URL并没有发生改变,仍然是提交给方法时候的URL,而且放置在Model(ModelAndView)参数中的数据会被SpringMVC放置到Request对象中,也都在Jsp页面通过EL表达式得到了,这说明它们是一个Request对象,EL表达式通过属性名从Request对象的Attribute中取得了结果值。
因此跳转到JSP页面是请求转发而不是重定向。
Request对象的Parameter和Attribute
Request对象的Parameter是指从请求发起端提交上来的参数,出于安全的考虑,这些参数在后台java程序中是无法篡改的。因此只有request.getParameter(“参数名”)方法,但没有setParamete()方法。
Request对象的Attribute是指后台方法与方法对象与对象之间请求转发时,传递数据的,这个可以在后台方法中随便进行set/getAttribute()。
因此,不要混淆这两个集合的用途和概念。
附:void {#附void .ListParagraph}
如果使用void为返回值,SpringMVC是处理不了的,只能采用java
web原生的HttpServletRequest,HttpServletResponse处理,这样就不走SpringMVC的视图解析器,就破坏了SpringMVC的体系结构,所以一般不要使用。这里只是提一下。
示例代码【ItemsController.java】
/**
* 演示void
*/
@RequestMapping(“/update”)
public void updateItems(Items items, HttpServletRequest request, HttpServletResponse response) throws Exception {
itemsService.updateItems(items);
request.setAttribute(“id”, items.getId());
request.getRequestDispatcher(“/WEB-INF/jsp/editItem.jsp”).forward(request, response);
response.setCharacterEncoding(“utf-8”);
response.setContentType(“application/json;charset=utf-8”);
response.getWriter().write(“json串”);
}
SpringMVC异常处理
异常分类
- 可预知异常:
Java编译时可检测异常,例如:IOException、SQLException等。
自定义异常(继承Exception父类的自定义类即为自定义异常)。
- 不可预知异常:
Java运行时异常,例如:NullPointerException、IndexOutOfBoundsException等。
SpringMVC异常处理
在JavaEE项目的开发中,不管是持久层的数据库操作过程,还是业务层的处理过程,还是控制层的处理过程,都不可避免的遇到各种可预知的、不可预知的异常需要处理。如果每个过程都单独处理异常,那么系统的代码冗余度会很高,工作量大且不好统一,维护的工作量也很大。
那么,能不能将所有类型的异常处理从各处理过程提取出来呢?如果能提取出来,那么既保证了各层程序的处理逻辑的功能较单一(只专注业务逻辑的实现),也实现了异常信息的统一处理和维护。答案是肯定的。下面将介绍使用Spring
MVC统一处理异常的解决和实现过程。
SpringMVC异常处理方式
SpringMVC异常处理的思路总的来说就是dao、service、controller层的程序出现异常都通过throws
Exception向外抛出,抛出的异常就会逐层向它的上层传递,最后异常有DispatcherServlet接收,它接到之后就会转给统一的异常处理组件HandlerExceptionResolver(处理器异常解析器)进行异常处理,如下图:
自定义异常解析器
因为HandlerExceptionResolver(处理器异常解析器)只是一个接口,SpringMVC不提供实现类进行异常处理,所以异常的具体处理需要由我们继承这个接口自己实现。
在实现自定义异常解析器之前要明确一点认识:
我们不能把404、500这样的错误异常信息展示给用户,也就一旦展示给用户会产生很不友好的印象。说的不好听点就是对外要掩饰错误,给出一些善意的托词,比如:系统繁忙,请稍后再试,或者一个可爱卖萌的动画图片等等。目的是求得用户暂时的理解。
创建package【cn.itcast.exception】在其中创建【CustomExceptionResolver.java】
package cn.itcast.exception;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
public class CustomExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2,
Exception exc) {
// 异常信息
String msg = “系统繁忙,请稍候再试”;
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject(“msg”, msg);
modelAndView.setViewName(“common/error”);
return modelAndView;
}
}
配置异常解析器
【SpringMVC.xml】
<!– 配置自定义异常解析器 –>
<bean class=“cn.itcast.exception.CustomExceptionResolver” />
只要在SpringMVC核心配置文件中把这个bean配置上就可以。由于它继承了HandlerExceptionResolver,所以SpringMVC可以自动加载这个自定义的组件。
错误页面
{width=”2.2424245406824146in”
height=”2.139154636920385in”}
<%@ 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>
${msg }
</body>
</html>
异常测试
特意把Controller中的一个方法改错,【ItemsController.java】:运行时异常
@RequestMapping(“/list”)
public ModelAndView itemsList() throws Exception {
// 程序错误,自动抛出异常
int i = 0 / 0;
List<Items> itemsList = itemsService.findItemsList();
// 1. 设置返回页面需要的数据 2. 指定返回页面的地址
ModelAndView modelAndView = new ModelAndView();
// 1. 设置返回页面需要的数据
modelAndView.addObject(“itemList”, itemsList);
// 2. 逻辑视图名称的设置(就是视图文件的文件名)
modelAndView.setViewName(“itemList”);
return modelAndView;
}
画面显示了【系统繁忙,请稍候再试】,而不是丑陋的500异常信息,就是因为有了整个系统的统一异常处理。
如果去掉这个统一的异常处理,比如讲SpringMVC.xml中的配置去掉,然后在请求这个页面就会出现丑陋的500:
{width=”7.268055555555556in”
height=”4.218055555555556in”}
SpringMVC异常处理方式的好处
各层都throws
Exception,最后由DispatcherServlet交给HandlerExceptionResolver的实现类来处理的好处:
异常信息统一处理,更易于维护。
避免将500、404这样的错误信息返回给用户。
可以判断自定义异常,用异常机制控制业务违规的限制。
自定义异常类
我们还可以自定义异常类,那自定义异常类究竟有什么作用呢?——自定义异常只是希望利用java异常机制做一些特殊业务的限制,这样的业务限制不是程序bug。比如秒杀活动中的限购提示或者取钱时余额不足时中断处理并提示余额不足等。这些并不是程序的bug,都是业务范畴的限制。我们就可以利用java的异常机制,自定义一种异常,一旦业务出现违规就抛出这个特殊的异常,当系统捕获到这个特殊异常时就做对应的业务违规提示。
{width=”1.9317705599300088in” height=”1.84in”}
自定义异常【CustomException.java】
package cn.itcast.exception;
/**
* 自定义异常类
* @author Derek Sun
*
*/
public class CustomException extends Exception {
private String message;
/**
* @return the message
*/
public String getMessage() {
return message;
}
/**
* @param message the message to set
*/
public void setMessage(String message) {
this.message = message;
}
}
在程序中造一个业务业务违规。由于是业务违规都是先进行判断,并在判断条件为true的逻辑中设置业务违规的具体信息,然后再抛出自定义异常。
【ItemsController.java】:做一个假的业务违规逻辑
@RequestMapping(“/list”)
public ModelAndView itemsList() throws Exception {
// 自定义异常
if (true) {
CustomException exc = new CustomException();
exc.setMessage(“请不要太贪心,您已经购买了一台!”);
throw exc;
}
List<Items> itemsList = itemsService.findItemsList();
// 1. 设置返回页面需要的数据 2. 指定返回页面的地址
ModelAndView modelAndView = new ModelAndView();
// 1. 设置返回页面需要的数据
modelAndView.addObject(“itemList”, itemsList);
// 2. 逻辑视图名称的设置(就是视图文件的文件名)
modelAndView.setViewName(“itemList”);
return modelAndView;
}
异常抛出后最终还是会由自定义的异常处理解析器捕获,因此需要在异常处理解析器中增加自定义异常处理的逻辑判断:【CustomExceptionResolver.java】
package cn.itcast.exception;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
public class CustomExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2,
Exception exc) {
// 异常信息
String msg = “”;
// 判断传入的异常种类
// 如果是自定义异常直接抛出对应的业务违规信息
// 如果是程序异常就提示:系统繁忙,请稍后再试
if (exc instanceof CustomException) {
// 自定义异常
msg = exc.getMessage();
} else {
// 运行时异常
msg = “系统繁忙,请稍候再试”;
}
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject(“msg”, msg);
modelAndView.setViewName(“error”);
return modelAndView;
}
}
再次运行tomcat测试,结果显示【请不要太贪心,您已经购买了一台!】
架构级别异常处理总结
SpringMVC的异常处理思想其实就是架构级别的异常处理思想,是从JavaEE架构整体的角度去统一异常处理。这是一个系统架构处理异常的最重要思想。
上传图片
本章所讲的图片上传方法是JavaWeb传统的上传方式,即前台页面提交一个可以包含图片的特殊form,后台处理需要具有处理特殊form的能力,将form中的图片提取出来交给后台程序处理。
服务器端配置文件访问服务
上传的图片应该在画面上显示出来,在web页面中访问一个图片是使用一个url的。Tomcat提供一种设置虚拟URL和实际图片保存的磁盘路径的映射关系,这样在web页面访问这个虚拟url就相当于访问实际磁盘的路径,就可以访问到指定的图片。
如何创建一个web虚拟url路径和一个磁盘物理路径的映射关系呢?——在web服务器中可以指定它们之间的映射关系,比如我们的tomcat就可以创建:
{width=”3.6979166666666665in”
height=”1.2083333333333333in”}
点击Modules
{width=”7.268055555555556in” height=”4.6375in”}
{width=”7.268055555555556in”
height=”1.5303029308836396in”}
{width=”2.386363735783027in”
height=”1.2752810586176728in”}
将上面指定的实际保存图片的物理路【C:\mydir\03_workspace\image】与这个虚拟url路径【/pic】关联到一起。这样当通过url:http://localhost:8080/pic/xxxx.jpg就可以访问的对应的图片了,并显示到浏览器中。就相当于访问C:\mydir\03_workspace\image\xxxx.jpg。
这里的物理路径:C:\mydir\03_workspace\image
映射后url路径:/pic
可以启动tomcat试一下:
先找一个图片放到C:\mydir\03_workspace\image目录下
然后启动tomcat
在浏览器访问http://localhost:8080/pic/xxx.jpg
注意:这个虚拟url路径是tomcat本身自己的配置,和任何web工程无关,所以任何web工程都可以使用这个虚拟路径。
这样在页面上就可以在回显的img的src中这样写:
<%@ 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=”${pageContext.request.contextPath }/items/it/${item.id }” method=“post” enctype=“multipart/form-data”>
<%– <form id=”itemForm” action=”${pageContext.request.contextPath }/items/update.action” method=”post”> –%>
<%– <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=“/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>
图片上传的过程
前台上传与图片显示
在jsp页面中,form的【enctype=”multipart/form-data”】属性,作用是表示该表单可以提交多媒体文件,比如图片
修改【editItem.jsp】,给form添加这个属性,使得它能够处理图片上传。
<%@ 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=”${pageContext.request.contextPath }/items/it/${item.id }” method=“post” enctype=“multipart/form-data”>
<%– <form id=”itemForm” action=”${pageContext.request.contextPath }/items/update.action” method=”post”> –%>
<%– <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=”${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>
上传过程只是强调一点:提交表单,前台将图片转换成二进制流并提交。
注意:图片上传必须通过post方式提交多媒体类型的form表单,其他方式,包括get都不允许提交多媒体的form,否则会报500错误(The
current request is not a multipart request)
多媒体解析器——配置
SpringMVC对上传的图片提供后台的解析支持,使用的解析器是:org.springframework.web.multipart.commons.CommonsMultipartResolver,但是解析器需要依赖commons-fileupload和commons-io两个第三方的jar包,因此需要导入它们:
{width=”2.122916666666667in” height=”0.5375in”}
然后SpringMVC需要配置一下这个解析器才能生效:
【SpringMVC.xml】
<!– 文件上传 –>
<bean id=“multipartResolver”
class=“org.springframework.web.multipart.commons.CommonsMultipartResolver”>
<!– 设置上传文件的最大尺寸为5MB –>
<property name=“maxUploadSize”>
<value>5242880</value>
</property>
</bean>
这里限制了文件上传的大小,不能太大,否则容易造成服务器的磁盘负担超大。
后台图片处理——编码
SpringMVC中配置了多媒体解析器后,Controller方法中就可以使用【MultipartFile】类型定义一个形参接收图片,并调用这个形参对象的方法处理图片。
·传参规范:页面上传控件的name属性值必须等于Controller方法中MultipartFile形参的变量名。
【ItemsController.java】:修改updateItems方法如下:
/**
* 演示图片上传的后台处理
*/
@RequestMapping(“/update”)
public String updateItems(MultipartFile pictureFile, Items items, Model model) throws Exception {
// 1. 获取图片原始的文件名
String fileName = pictureFile.getOriginalFilename();
// 2. 随机生成字符串 + 原文件的扩展名组成新的文件名称
String newFileName = UUID.randomUUID().toString() + fileName.substring(fileName.lastIndexOf(“.”));
// 3. 将图片保存到磁盘
pictureFile.transferTo(new File(“C:\\mydir\\03_workspace\\image\\” + newFileName));
// 4. 将新的图片名称保存到数据库
items.setPic(“http://localhost:8080/image/” + newFileName);
itemsService.updateItems(items);
// 在底层仍然是将model中设置的这个属性名和属性值设置到request对象中,所以无论是请求转发还是
// 重定向都可以将需要的数据通过model带到他们对应的request对象中,这样数据就被带到请求转发或
// 者重定向后的方法中去了。
model.addAttribute(“id”, items.getId());
return “redirect:toEdit.action”;
}
注意
在项目实际中这种传统的上传方式已经不适用了,作为SpringMVC的一个比较重要的插件,这里只是作为一个SpringMVC的知识点把SpringMVC对上传图片的支持介绍给大家,大家作为一个知识了解一下即可。
因为在当今实际项目中,都采用js端的上传插件,图片选择完成后直接上传,后台需要提前编写一个独立的Controller类并定义一个方法来处理上传,直接保存到文件服务器,然后返回对应的url给页面。这时在整个页面完整信息进行提交保存时,form表单中只包含图片的url字符串和其他业务信息,这个form就不需要指定多媒体类型的属性了,没有了多媒体类型的属性的form就可以不局限于只运行post提交了,这就给处理带来了便利。尤其是解决了RESTful的更新表单提交问题(这个在RESTful中再详细说明)。
json的数据交互
json的数据格式
1. JSON数据格式:键值对的形式承载数据,即{k1:v1,k2:v2,…}
2.
JSON的起源:源于JavaScript,JS对象的字符串表示形式,这种用字符串表示对象的方式叫做序列化。序列化的好处是便于对象的传输交互。
3. JSON的本质:就是一个字符串。
因此,JSON在JS代码中必须是一个字符串的形式:
(其中key名、字符串类型的value值都要用双引号括起来,包括大括号在内整体要包在一对单引号中)
例如:‘{“name”:”测试商品”, “price”:99.9}’
除此之外,大家经常在js中写的:{“name”:”测试商品”,
“price”:99.9},这种不是JSON,而是普通js对象。
大家为什么会误解普通js对象就是一个json?
之所以会误解普通js对象就是一个json的原因是浏览器惹的祸,因为在js代码以外的地方我们看到的json都是这种:{“name”:”测试商品”,
“price”:99.9},但注意这是在js代码以外的地方,才表示成这样的,在它的老家里它必须要表明它的本质——字符串。而我们有时候看json最多的地方就是浏览器的后台监视里面,而在浏览器的监视窗口中json确实就是这个样子:{“name”:”测试商品”,
“price”:99.9},因此许多人此时产生了错误的理解,即在js代码中{“name”:”测试商品”,
“price”:99.9}就是json。
json数据格式的好处
比xml更小、更高效,构上结和pojo类似,可以借助工具进行相互转换。
SpringMVC支持json所需要的jar包
在SpringMVC中要想使用json必须导入一下jar包:
{width=”2.04545384951881in”
height=”0.699760498687664in”}
因为注解驱动<mvc:annotation-driven />会自动加载解析json的转换器:
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter,而这个转换器就需要依赖这三个jar包,因此直接导入jar包后不需要任何配置。
SpringMVC中接收和返回json
接收和返回json需要两个注解:@RequestBody和@ResponseBody:
@RequestBody:接收json转化成java对象。要求:json的key名==java对象的属性名
@ResponseBody:将返回值处理成字符串返回,如果返回结果是String类型就直接返回,如果返回值是java对象就转化成json返回(因为json本质也是一个字符串)。
两个注解是通过SpringMVC提供的接口:
org.springframework.http.converter.HttpMessageConverter<T>来调用实际的实现类MappingJackson2HttpMessageConverter的。
前台ajax的回调函数是怎么触发的?
@ResponseBody注解对应的处理类会把返回的字符串写入Response对象的body区,并返回给浏览器,HTTP头信息中的状态会变成200(OK,即成功),ajax会监听这个HTTP头的状态,如果是200,就会调用success中定义的function函数,这样就激活了回调函数,然后js把Response对象body区中的字符串取出来,如果是普通字符串就直接传给回调函数的参数,如果是json字符串就转化成普通js对象然后传给回调函数的参数。由于采用了ajax异步提交并回调的方式,因此此时SpringMVC方法返回后是不会走视图解析器的处理流程的,直接回到了前台浏览器。
【代码示例】
- 随便在itemList.jsp页面上添加一个button,然后在jsp中用jquery定义一个js函数里面定义一个ajax作为客户端,点击添加的button进行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”>
<script type=“text/javascript” src=”${pageContext.request.contextPath }/js/jquery-1.4.4.min.js”></script>
<script type=“text/javascript”>
function sendJson() {
$.ajax({
type:”post”,
url:”${pageContext.request.contextPath }/items/sendJson.action”,
contentType:”application/json;charset=utf-8”, // 指定从页面传给Controller的数据格式是什么样的
//dataType:”“, // 从Controller返回给页面的数据格式是什么样的,一般可以不写,它可以自动jquery可以自动匹配
data:’{“name”:”测试商品”,”price”:99.9}’,
success:function(data){
alert(data);
}
});
}
</script>
<title>查询商品列表</title>
</head>
<body>
<input type=“button” value=“sendJson” οnclick=”sendJson()”>
<form action=”${pageContext.request.contextPath }/items/search.action” method=“post”>
查询条件:
<table width=“100%” border=1>
。。。。。。。。
</table>
</form>
</body>
</html>
- 在后台Controller中定义一个新方法来响应这个ajax提交:
【ItemsController.java】形式一:@ResponseBody放在了方法定义上面
/**
* json数据交互
*/
@RequestMapping(“/sendJson”)
@ResponseBody
public Items sendJson(@RequestBody Items items) throws Exception {
System.out.println(items);
items.setDetail(“aaaa”);
return items;
}
【ItemsController.java】形式二:@ResponseBody放在了方法返回类型前面
/**
* json数据交互
*/
@RequestMapping(“/sendJson”)
public @ResponseBody Items sendJson(@RequestBody Items items) throws Exception {
System.out.println(items);
items.setDetail(“aaaa”);
return items;
}
@RequestBody的限制
1.
如果方法参数上配置了@RequestBody,必须保证前台提交过来的必须是json字符串,即【data:’{“name”:”测试商品”,”price”:99.9}’】和【contentType:”application/json;charset=utf-8”】必须有。没有就不会被看作是一个json字符串。
2.
@RequestBody要求HTTP请求的类型不能是GET,那如果是在传统web请求中除了GET就只剩POST了。但在RESTful中还可以是PUT和DELETE。
如果不满足上面的两个要求,提交会报错。
附:不用注解驱动时json转换器的配置 {#附不用注解驱动时json转换器的配置 .ListParagraph}
<!–注解适配器 –>
<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>
如果不使用默认的转换器,也需要像上面这样配置。
RESTful支持
RESTful风格的URL
传统的URL没有严格的要求,但为了表明URL操作的目的一般都会加入一些动词,比如要取得一个商品就getXXX:http://shop.com/laptop/getlenovo.action。比如:添加一个商品就addXXX或insertXXX:http://shop.com/laptop/addlenovo.action。
RESTful风格URL的一个重要特征:
URL中只能包含名词。
URL只能包含名词意味着什么?
如果只有名词,那URL只能作为网络中某个文件、某些数据、某段视频、某个图片等等的定位地址了。
对URL地址定位的“东西”进行操作的目的该如何表达呢?
这就是RESTful风格URL的另一个重要特征:用HTTP请求的动词类型来区分操作的目的,即GET:查询、POST:新增、PUT:更新、DELETE:删除。
网络上的这些“东西”我们统称为资源。
根据上面两个RESTful特征,传统的URL就变成了下面的样子:
取得商品:HTTP GET—http://shop.com/laptop/lenovo.action。
添加商品:HTTP POST—http://shop.com/laptop/lenovo.action。
删除商品:HTTP DELETE—http://shop.com/laptop/lenovo.action。
修改商品:HTTP PUT—http://shop.com/laptop/lenovo.action。
RESTful风格URL的一个目的就是要让URL变得简单,因此RESTful风格URL的第三个重要特征:
URL不能有后缀名。
URL进一步变成了下面的样子:没有了后缀名,URL就变短了。
取得商品:HTTP
GET—http://shop.com/laptop/lenovo。
添加商品:HTTP
POST—http://shop.com/laptop/lenovo。
删除商品:HTTP
DELETE—http://shop.com/laptop/lenovo。
修改商品:HTTP
PUT—http://shop.com/laptop/lenovo。
GET需要传递的参数在RESTful风格中有什么特征呢?
传统Get请求URL后面附带的参数必须跟在RESTful风格URL后面用斜杠/分隔。
比如根据商品类别取得商品:HTTP
GET—http://shop.com/laptop/lenovo/E470。
如果传递多个参数就按照事先设计好的参数顺序排在URL的后面:
HTTP GET—http://shop.com/laptop/lenovo/E470/black/8G/500G。
RESTful风格URL特点
用名词组成的URL定位资源,用HTTP动词(GET:查询、POST:新增、PUT:更新、DELETE:删除)描述操作。
请求的URL,除了静态资源文件URL以外,不允许有后缀名。
GET请求URL后面附带的参数必须在URL后面用斜杠/分隔。
URL改成RESTful风格后变得更加简洁了。
RESTful并不是强制的
RESTful风格URL特点并不是强制要求,只是一些改进的建议,愿意遵守的就是RESTful风格,不愿意遵守就不是RESTful风格。
系统RESTful风格URL的改造
SpringMVC如何支持RESTful
想要SpringMVC支持RESTful,需要在web.xml中修改可以接收的URL:.action改成/,这样的配置会让DispatcherServlet拦截有所的url,只放行.jsp的URL。
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<!– DispatcherServlet拦截接收所有url请求,但只放行以.jsp为结尾的url,
其他资源文件后缀的url不放行 –>
<url-pattern>/</url-pattern>
</servlet-mapping>
因为除了.jsp的URL以外,其余的URL都会被拦截,那js、css等资源文件的URL怎么办?
配置放行的资源文件
【SpringMVC.xml】
<!– 配置放行的资源文件目录 –>
<!– 放行js资源文件的配置,也可以理解为为location对应的目录配置对应的url访问路径
location:表示js所在的相对目录(以web根目录为基准)
mapping:表示url中对应的路径名,**表示所有的js文件均被放行。
对css、jsp、pdf等,继续增加<mvc:resources>标签的配置项即可。
–>
<mvc:resources location=“/js/” mapping=“/js/**”/>
此处可以试一试:
启动tomcat后,直接访问一个js文件,应该是可以访问到的,但是如果把这个配置注视掉,再启动tomcat后,就访问不到了。
根据RESTful特征改造url
在我们现在的代码示例中,传统的URL:
http://localhost:8080/ssm-2/items/list.action(查询,GET)
http://localhost:8080/ssm-2/items/itemEdit.action?id=1(查询,GET)
http://localhost:8080/ssm-2/items/itemUpdate.action(更新,POST)
http://localhost:8080/ssm-2/items/sendJson.action(模拟删除,POST)
把上面url变成RESTful样式如下:
http://localhost:8080/ssm-2/items/list(查询,GET)
http://localhost:8080/ssm-2/items/detail(模拟删除,DELETE)
RESTful系统中@RequestMapping注解的作用非常大:
@RequestMapping(value=”url”, method=RequestMethod.POST/GET/DELETE/PUT)
对代码的改造
【itemList.jsp】:ajax支持四种HTTP动词,可以直接写:
<script type=“text/javascript”>
function sendJson() {
$.ajax({
type:’delete’,
url:’${pageContext.request.contextPath }/items/detail’,
contentType:’application/json;charset=utf-8’,
data:’{“name”:”测试商品”, “price”:99.9}’,
success:function(data) {
alert(data.name + ‘—’ + data.price);
}
});
}
</script>
<tr>
<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 }/items/detail/${item.id}”>修改</a></td>
</tr>
JSP表单提交只支持GET和POST,不支持DELETE和PUT,这是java
web的要求。所以想要能DELETE或PUT提交就必须将POST转换成PUT或者DELETE。
如何把POST改成PUT和DELETE?
1) 需要在【web.xml】中配置一个过滤器:这个配置用时拷贝即可。
<!– 将POST请求转化为DELETE或者是PUT,要在页面指定一个_method名称的hidden变量来指定真正的请求参数 –>
<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>
2) 在editItem.jsp中添加一个名为_method的hidden变量:
<body>
<!– 上传图片是需要指定属性 enctype=”multipart/form-data” –>
<form id=“itemForm” action=”${pageContext.request.contextPath }/items/detail” method=“post” enctype=“multipart/form-data”>
<input type=“hidden” name=“_method” value=“PUT” />
。。。。。。
</form>
注意:多媒体表单只能使用POST提交,所以这里将图片上传的功能暂时取消掉,恢复成正常的form。
RESTful的URL中用PUT表示更新,但是如果是多媒体表单提交即使你做了PUT的相关设置也是无效的,只要是多媒体表单,提交就只认POST类型。
【ItemsController.java】
在@RequestMapping的URL中如果有接收的参数,就用{}把参数名括起来,名称根据业务需求自定义;
在方法的形参前用@PathVariable注解限定形参与URL中参数的映射关系;
@RequestMapping(value=”/detail/{itemsId}”, method=RequestMethod.GET)
public String getItemsById(@PathVariable(“itemsId”) Integer id,
HttpServletRequest request, Model model) throws Exception {
// Integer id = Integer.valueOf(request.getParameter(“id”));
Items items = itemsService.getItemsById(id);
model.addAttribute(“item”, items);
return “items/editItem”;
}
或者:如果url中参数名==形参名,@PathVariable中的名字可以省略
@RequestMapping(value=”/detail/{id}”, method=RequestMethod.GET)
public String getItemsById(@PathVariable Integer id,
HttpServletRequest request, Model model) throws Exception {
// Integer id = Integer.valueOf(request.getParameter(“id”));
Items items = itemsService.getItemsById(id);
model.addAttribute(“item”, items);
return “items/editItem”;
}
如果想加多个参数:【http://localhost:8080/ssm-2/items/detail/1/123.1】,对应注解可以这样写:@RequestMapping(value=”/detail/{itemId}/{itemPrice}”, method=RequestMethod.GET)
然后用注解@PathVariable(“url中参数名”)限定URL参数与形参的映射,对号入座取来使用。
@RequestMapping(value=”/detail”, method=RequestMethod.PUT)
public String updateItemsById2(Items items) throws Exception {
itemsService.updateItemsById(items);
// model.addAttribute(“id”, items.getId());
return “redirect:/items/detail/” + items.getId();
}
注意:
在RESTful的url下请求转发和重定向的url要用绝对路径,否则路径可能会出现混乱,此时的id必须拼在URL上面,不能用model对象传递了,否则与上面方法的URL定义即不匹配了。
@RequestMapping(value=”/detail”, method=RequestMethod.DELETE)
@ResponseBody
public Items sendJsonTest(@RequestBody Items items) throws Exception {
items.setDetail(“json test”);
return items;
}
附1:为什么会出现REST这个概念 {#附1为什么会出现rest这个概念 .ListParagraph}
这跟我们软件系统的演变有关系:C/S单机结构 -> B/S网络结构 ->
C/S互联网结构
C/S互联网结构:
!C:\Users\Derek
Sun\Downloads\06ee404783540f0af299042057738a99_b.jpg{width=”5.034722222222222in”
height=”2.2824070428696412in”}
一个后台系统服务多种客户端,甚至还出现了一些面向大众的公共服务平台,比如Facebook
platform,微博开放平台,微信公共平台等,它们不需要有显式的前端,只有一套提供服务的接口,用户可以利用这些平台进行基于平台的应用开发。
这些新的互联网的演化,要求我们的服务端架构设计要进行调整,以适应各种不同的C(客户)。于是一哥们在他的毕业论文中提出了REST概念,即以网络资源(数据、文件、图片、视频、音频)为核心的一种思想。
!C:\Users\Derek
Sun\Downloads\11cdfc60bde58e8545bafe42f0af79ca_b.jpg{width=”4.568588145231846in”
height=”3.4294870953630796in”}
Roy Fielding的毕业论文。这哥们参与设计HTTP协议,也是Apache Web
Server项目的co-founder。PhD的毕业学校是 UC
Irvine,Irvine在加州,有着充裕的阳光和美丽的海滩,是著名的富人区。Oculus
VR 的总部就坐落于此(虚拟现实眼镜,被FB收购,CTO为Quake和Doom的作者 John
Carmack)。
附2:REST {#附2rest .ListParagraph}
- 全称:
Resource Representational State
Transfer(资源表现的状态转移),通俗讲就是资源在网络中以某种表现形式进行状态转移。它认为网络中的核心是资源。
- 解释:
Resource:资源,即数据,比如商品信息、用户信息、一个图片、一个视频等、一个pdf文件等。互联网中的一切都是资源。
Representational:某种表现形式,比如用JSON、XML、JPEG、PDF等;
State
Transfer:状态变化。通过HTTP动词(GET、POST、PUT、DELETE等)实现。即通过CRUD的动作对数据产生的变化。比如:苹果从青到红到烂,就是苹果的状态变化,是细菌和氧气对苹果的产生的动作作用的结果。同理通过HTTP协议中的动作对网络资源进行CRUD的操作,使得资源发生变化,即为状态变化。
- 怎样理解:
小的方面:就是围绕着网络资源的状态变化,通过某种表现形式表现出来。
大的方面:就是为了达到网络资源为核心的目的,并能更好的为各种客户端提供服务,需要对web系统架构进行重组,基于此大牛架构师先行者们提出了一些建议,使得REST成为一种如何组织web服务架构的建议的代名词,它不是强制性的标准,更不是一种新的技术,只是一种建议或者叫做风格。
附3:RESTful {#附3restful .ListParagraph}
从小的方面入手就是用URL定位资源,用HTTP动词(GET、POST、PUT、DELETE等)描述操作。
从大的方面入手就是形容web系统符合了REST风格就称为RESTful。
附4:RESTful的URL {#附4restful的url .ListParagraph}
大的方面需要多年的开发积累和自己的对系统架构的不断研究学习才能有所体会的。因此我们从小的方面讲RESTful,即解决如何使我们的url变得RESTful
先来看一个RESTful风格URL的例子:知乎的某问题的url:
http://www.zhihu.com/question/28557115。
根据用URL定位资源,用HTTP动词描述操作原则,组合如下:
创建:POST http://www.zhihu.com/question/28557115
删除:DELETE http://www.zhihu.com/question/28557115 (可以用POST代替)
更新:PUT http://www.zhihu.com/question/28557115 (可以用POST代替)
取得:GET http://www.zhihu.com/question/28557115
由上面的叙述可知:URL中只需要描述你需要访问的资源在哪,即:
http://www.jd.com/drinks/beers/qingdao/1903/1
如何使我们的URL变得RESTful?(两点)
RESTful的URL中使用名词而不是动词,且推荐用复数,不要有参数。
RESTful中的资源要分类分层次(什么分类下什么层次下的什么资源名中的具体哪个资源对象)
注意:
不要有参数即不要有Get请求中那样的参数:http://www.a.com/goods/list.action?id=aaa&name=bbb
RESTful中的参数全被视为资源定位的名词描述
URL示例:
Bad:
http://www.jd.com/beer/getqingdao/1903/1
http://www.a.com/toys/cars/list.action?name=bmw&color=red
Good:
http://www.jd.com/beers/qingdao/1903/1
http://www.a.com/toys/cars/list/bmw/red
附5:REST建议 {#附5rest建议 .ListParagraph}
- 使用客户/服务器模型:
客户和服务器之间通过一个统一的接口来互相通讯。
- 层次化的系统:
在一个REST系统中,客户端并不会固定地与一个服务器打交道。
- 无状态:
在一个REST系统中,服务端并不会保存有关客户的任何状态。也就是说,客户端自身负责用户状态的维持,并在每次发送请求时都需要提供足够的信息。
- 可缓存:
REST系统需要能够恰当地缓存请求,以尽量减少服务端和客户端之间的信息传输,以提高性能。
- 统一的接口:
一个REST系统需要使用一套统一的接口来完成子系统之间以及服务与用户之间的交互。这使得REST系统中的各个子系统可以独自完成演化。
参考网页:
https://www.zhihu.com/question/28557115
http://www.cnblogs.com/loveis715/p/4669091.html
http://www.cnblogs.com/rainy-shurun/p/5412162.html
拦截器
作用
拦截请求,类似于Servlet
开发中的过滤器Filter,用于对处理器进行预处理和后处理。一般在权限验证的时候使用较多。
SpringMVC第一天学习的转换器仅仅是处理参数的,拦截器的功能更加强大。
拦截器定义
自定义拦截器都要实现org.springframework.web.servlet.HandlerInterceptor接口:
在工程中创建连接器:
{width=”3.5in” height=”2.4166666666666665in”}
【Interceptor1.java】
package 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 Interceptor1 implements HandlerInterceptor {
/**
* 执行时机:页面渲染完毕后调用此方法。
* 应用场景:可以用来清除某些资源(类似java的finally)。
*/
@Override
public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3) throws Exception {
System.out.println(“======Interceptor1=============afterCompletion======”);
}
/**
* 执行时机:在调用Controller方法结束后、页面渲染之前调用此方法。
* 应用场景:把一些公共信息加入到Model中,返回给页面。
*/
@Override
public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3) throws Exception {
System.out.println(“======Interceptor1=============postHandle======”);
// Items items = (Items)arg3.getModel().get(“item”);
// System.out.println(items.getName());
}
/**
* 执行时机:在调用Controller方法前会调用此方法。
* 返回布尔类型的结果,返回true放行,返回false拦截后续所有的处理。
* 应用场景:检查用户是否登录。
*/
@Override
public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2) throws Exception {
System.out.println(“======Interceptor1=============preHandle======”);
return true;
}
}
配置拦截器
【SpringMVC.xml】
<!– 配置全局拦截器 –>
<mvc:interceptors>
<mvc:interceptor>
<!– 配置拦截器能够拦截的url –>
<!– /**表示拦截所有请求 –>
<mvc:mapping path=“/**”/>
<bean class=“interceptor.Interceptor1” />
</mvc:interceptor>
<mvc:interceptor>
<!– 配置拦截器能够拦截的url –>
<!– /**表示拦截所有请求 –>
<mvc:mapping path=“/**”/>
<bean class=“interceptor.Interceptor2” />
</mvc:interceptor>
</mvc:interceptors>
正常流程测试
这里了解一下单个拦截器中和多个拦截器并存时三个方法的执行顺序的规律,主要是想让大家把握住拦截器执行的详细顺序,尤其是多个拦截器共同工作的时候,以免使用时由于不清楚顺序而拦截失败或拦截了不该拦截的东西。
- 单个拦截器的执行顺序:
先定义一个拦截器:Interceptor1.java测试它里面三个方法的拦截顺序
======Interceptor1=============preHandle======
======Interceptor1=============postHandle======
======Interceptor1=============afterCompletion======
多个拦截器的执行顺序:
a. 两个拦截器中preHandle方法都返回true时:在配置文件中配置顺序是先1后2
preHandle:(配置的正序)
======Interceptor1=============preHandle======
======Interceptor2=============preHandle======
postHandle:(配置的反序)
======Interceptor2=============postHandle======
======Interceptor1=============postHandle======
afterCompletion:(配置的反序)
======Interceptor2=============afterCompletion======
======Interceptor1=============afterCompletion======
a. 两个拦截器中preHandle方法都返回true时:在配置文件中配置顺序是先2后1
preHandle:(配置的正序)
======Interceptor2=============preHandle======
======Interceptor1=============preHandle======
postHandle:(配置的反序)
======Interceptor1=============postHandle======
======Interceptor2=============postHandle======
afterCompletion:(配置的反序)
======Interceptor1=============afterCompletion======
======Interceptor2=============afterCompletion======
当都所有拦截器都返回true时,此时总的规律:先开始的后结束。
中断流程测试
- 让Interceptor2的preHandle方法返回false时:(配置顺序中不是第一个的拦截器)
======Interceptor1=============preHandle======
======Interceptor2=============preHandle======
======Interceptor1=============afterCompletion======
说明:
首先拦截器2的preHandle返回false,它自己的后续方法全部中断。
其次拦截器1的preHandle返回true,但是它的postHandle也没有执行,说明postHandle受到所有拦截器的preHandle方法返回值的影响
再次拦截器1的afterCompletion方法却执行了,说明afterCompletion不受其他拦截器的preHandle方法返回值的影响。
结论:
postHandle受所有拦截器的preHandle执行结果的影响,只有全部preHandle都返回true时才执行
afterCompletion只受它自己所属拦截器中preHandle的影响,preHandle返回true时执行。
- 让Interceptor1的preHandle方法返回false时:(配置顺序中的第一个拦截器)
======Interceptor1=============preHandle======
结论:
配置顺序第一个拦截器的preHandle返回了false,则中断所有后续处理。
拦截器应用
处理流程
有一个登录页面,需要写一个Controller访问登录页面
登录页面有一提交表单的动作。需要在Controller中处理。
a) 判断用户名密码是否正确(在控制台打印)
b) 如果正确,向session中写入用户信息(写入用户名username)
c) 跳转到商品列表
- 拦截器
a) 访问商品列表画面时,拦截用户请求,判断用户是否登录(登录请求不能拦截)
b) 如果用户已经登录。放行
c) 如果用户未登录,跳转到登录页面。
JSP页面
【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=”${pageContext.request.contextPath }/user/login.action”>
<label>用户名:</label>
<br>
<input type=“text” name=“username”>
<br>
<label>密码:</label>
<br>
<input type=“password” name=“password”>
<br>
<input type=“submit”>
</form>
</body>
</html>
用户登录Controller
【UserController.java】
package cn.itcast.controller;
import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping(“/user”)
public class UserController {
/**
* 跳转到登录页面
*/
@RequestMapping(“/toLogin”)
public String toLogin() {
return “items/login”;
}
/**
* 用户登录
*/
@RequestMapping(“/login”)
public String login(String username, String password, HttpSession session) {
// 校验用户登录
System.out.println(username);
System.out.println(password);
// 把用户名放到session中
if (username != null && !”“.equals(username)) {
session.setAttribute(session.getId(), username);
}
return “redirect:/items/list.action”;
}
}
编写拦截器
{width=”2.7083333333333335in”
height=”0.8333333333333334in”}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object arg2) throws Exception {
// 从request中获取session
HttpSession session = request.getSession();
// 从session中根据Session id取得用户登录信息
Object user = session.getAttribute(session.getId());
// 判断user是否为null
if (user != null) {
// 如果不为空则放行
return true;
} else {
// 如果为空则跳转到登录页面
response.sendRedirect(request.getContextPath() + “/user/toLogin.action”);
}
return false;
}
配置拦截器
拦截商品业务中的url
因为ItemController做了url窄化限定,
{width=”3.676388888888889in” height=”1.65625in”}
所以配置文件中如下配置:表明url以/items/开头的均被拦截。
<mvc:interceptor>
<!– 配置商品被拦截器拦截 –>
<mvc:mapping path=“/items/**”/>
<!– 配置具体的拦截器 –>
<bean class=“cn.itcast.interceptor.LoginHandlerInterceptor”/>
</mvc:interceptor>