仅仅是对学习资料的整理。
学习计划:
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/**"/>
处理静态资源被“/”所拦截的问题 还有别的解决办法:
方案一:激活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