springmvc入门3
回顾:
一、什么是springmvc?
一个类似struts2的前端web框架。
二、Springmvc的入门程序
1、实现controller接口
2、实用性HttpRequestHandler接口
3、使用注解形式开发controller
a) 在Controller上加上@Controller注解
b) 方法上加上@RequestMapping注解
c) 需要在springmvc的配置文件中添加一个包扫描器。
三、Springmvc的框架结构
请求先到前端控制器DispatcherServlet,到处理器映射器中找url对应的handler,返回HandlerExecutionChain,执行链中包含拦截器和handler。执行handler使用处理器适配器执行,执行完handler返回ModelAndView对象,需要视图解析器将逻辑视图转换成物理视图,渲染视图。
四、springmvc整合mybatis
整合的思路是spring容器关联mapper对象、server、Controller。
事务配置在service层由spring容器关联
五、参数映射
a) 简单数据类型,需要页面中input的name属性和方法的形参名称一致。
不一致,使用@Requestparam注解转换
b) Pojo类型name属性和pojo中的属性名称一致。
c) 默认支持的参数类型
- HttpServletRequest
- httpservletResponse
- HttpSession
- Model/ModelMap
d) 自定义参数绑定
1、实现converter接口,需要泛型一个是Source、Target
2、配置到springmvc.xml中
3、配置的converterService配置到Annotation-driven标签中。
1、参数绑定的高级应用
a) 绑定包装的pojo类型
需求
商品查询controller方法中实现商品查询条件传入。
实现方法
第一种方法:在形参中 添加HttpServletRequest request参数,通过request接收查询条件参数。
第二种方法:在形参中让包装类型的pojo接收查询条件参数。
分析:
页面传参数的特点:复杂,多样性。条件包括 :用户账号、商品编号、订单信息。。。
如果将用户账号、商品编号、订单信息等放在简单pojo(属性是简单类型)中,pojo类属性比较多,比较乱。
建议使用包装类型的pojo,pojo中属性是pojo。
页面参数和controller方法形参定义
页面参数:
商品名称:<input name="itemsCustom.name" />
注意:itemsCustom和包装pojo中的属性一致即可。
controller方法形参:
public ModelAndView queryItems(HttpServletRequest request,ItemsQueryVo itemsQueryVo) throws Exception
1.1.1 实现步骤
1.1.1.1 创建一个QueryVo
package cn.itcast.ssm.po; public class ItemsCustom extends Items { //添加商品信息的扩展属性 }
ItemsQueryVo.java
package cn.itcast.ssm.po; import java.util.List; public class ItemsQueryVo { //商品信息 private Items items; //为了系统 可扩展性,对原始生成的po进行扩展 private ItemsCustom itemsCustom; //批量商品信息 private List<ItemsCustom> itemsList; //用户信息 //private UserCustom userCustom; public Items getItems() { return items; } public void setItems(Items items) { this.items = items; } public ItemsCustom getItemsCustom() { return itemsCustom; } public void setItemsCustom(ItemsCustom itemsCustom) { this.itemsCustom = itemsCustom; } public List<ItemsCustom> getItemsList() { return itemsList; } public void setItemsList(List<ItemsCustom> itemsList) { this.itemsList = itemsList; } }
1.1.1.2 修改Controller
// 商品查询 @RequestMapping("/queryItems") public ModelAndView queryItems(HttpServletRequest request, ItemsQueryVo itemsQueryVo) throws Exception { // 测试forward后request是否可以共享 System.out.println(request.getParameter("id")); // 调用service查找 数据库,查询商品列表 List<ItemsCustom> itemsList = itemsService.findItemsList(itemsQueryVo); // 返回ModelAndView ModelAndView modelAndView = new ModelAndView(); // 相当 于request的setAttribut,在jsp页面中通过itemsList取数据 modelAndView.addObject("itemsList", itemsList); // 指定视图 // 下边的路径,如果在视图解析器中配置jsp路径的前缀和jsp路径的后缀,修改为 // modelAndView.setViewName("/WEB-INF/jsp/items/itemsList.jsp"); // 上边的路径配置可以不在程序中指定jsp路径的前缀和jsp路径的后缀 modelAndView.setViewName("items/itemsList"); return modelAndView; }
ItemsServiceImpl.java
public class ItemsServiceImpl implements ItemsService{ @Autowired private ItemsMapperCustom itemsMapperCustom; @Autowired private ItemsMapper itemsMapper; @Override public List<ItemsCustom> findItemsList(ItemsQueryVo itemsQueryVo) throws Exception { //通过ItemsMapperCustom查询数据库 return itemsMapperCustom.findItemsList(itemsQueryVo); }
}
ItemsMapperCustom.java
public interface ItemsMapperCustom { //商品查询列表 public List<ItemsCustom> findItemsList(ItemsQueryVo itemsQueryVo)throws Exception; }
ItemsMapperCustom.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="cn.itcast.ssm.mapper.ItemsMapperCustom" > <!-- 定义商品查询的sql片段,就是商品查询条件 --> <sql id="query_items_where"> <!-- 使用动态sql,通过if判断,满足条件进行sql拼接 --> <!-- 商品查询条件通过ItemsQueryVo包装对象 中itemsCustom属性传递 --> <if test="itemsCustom!=null"> <if test="itemsCustom.name!=null and itemsCustom.name!=''"> items.name LIKE '%${itemsCustom.name}%' </if> </if> </sql> <!-- 商品列表查询 --> <!-- parameterType传入包装对象(包装了查询条件) resultType建议使用扩展对象 --> <select id="findItemsList" parameterType="cn.itcast.ssm.po.ItemsQueryVo" resultType="cn.itcast.ssm.po.ItemsCustom"> SELECT items.* FROM items <where> <include refid="query_items_where"></include> </where> </select> </mapper>
1.1.1.3 修改jsp
传入数据:商品名称:<input name="itemsCustom.name" />,注意:itemsCustom和包装pojo中的属性一致即可。
b) 集合类型
数组绑定
需求
商品批量删除,用户在页面选择多个商品,批量删除。
表现层实现
关键:将页面选择(多选)的商品id,传到controller方法的形参,方法形参使用数组接收页面请求的多个商品id。
controller方法定义:
页面定义:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>查询商品列表</title> <script type="text/javascript"> function deleteItems(){ //提交form document.itemsForm.action="${pageContext.request.contextPath }/items/deleteItems.action"; document.itemsForm.submit(); } function queryItems(){ //提交form document.itemsForm.action="${pageContext.request.contextPath }/items/queryItems.action"; document.itemsForm.submit(); } </script> </head> <body> 当前用户:${username }, <c:if test="${username!=null }"> <a href="${pageContext.request.contextPath }/logout.action">退出</a> </c:if> <form name="itemsForm" action="${pageContext.request.contextPath }/items/queryItems.action" method="post"> 查询条件: <table width="100%" border=1> <tr> <td> 商品名称:<input name="itemsCustom.name" /> 商品类型: <select name="itemtype"> <c:forEach items="${itemtypes }" var="itemtype"> <option value="${itemtype.key }">${itemtype.value }</option> </c:forEach> </select> </td> <td><input type="button" value="查询" onclick="queryItems()"/> <input type="button" value="批量删除" onclick="deleteItems()"/> </td> </tr> </table> 商品列表: <table width="100%" border=1> <tr> <td>选择</td> <td>商品名称</td> <td>商品价格</td> <td>生产日期</td> <td>商品描述</td> <td>操作</td> </tr> <c:forEach items="${itemsList }" var="item"> <tr> <td><input type="checkbox" name="items_id" 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 }/items/editItems.action?id=${item.id}">修改</a></td> </tr> </c:forEach> </table> </form> </body> </html>
list绑定
需求
通常在需要批量提交数据时,将提交的数据绑定到list<pojo>中,比如:成绩录入(录入多门课成绩,批量提交),
本例子需求:批量商品修改,在页面输入多个商品信息,将多个商品信息提交到controller方法中。
表现层实现
controller方法定义:
1、进入批量商品修改页面(页面样式参考商品列表实现)
2、批量修改商品提交
使用List接收页面提交的批量数据,通过包装pojo接收,在包装pojo中定义list<pojo>属性
页面定义:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>查询商品列表</title> <script type="text/javascript"> function editItemsAllSubmit(){ //提交form document.itemsForm.action="${pageContext.request.contextPath }/items/editItemsAllSubmit.action"; document.itemsForm.submit(); } function queryItems(){ //提交form document.itemsForm.action="${pageContext.request.contextPath }/items/queryItems.action"; document.itemsForm.submit(); } </script> </head> <body> <form name="itemsForm" action="${pageContext.request.contextPath }/items/queryItems.action" method="post"> 查询条件: <table width="100%" border=1> <tr> <td> 商品名称:<input name="itemsCustom.name" /> </td> <td><input type="button" value="查询" onclick="queryItems()"/> <input type="button" value="批量修改提交" onclick="editItemsAllSubmit()"/> </td> </tr> </table> 商品列表: <table width="100%" border=1> <tr> <td>商品名称</td> <td>商品价格</td> <td>生产日期</td> <td>商品描述</td> <td>操作</td> </tr> <c:forEach items="${itemsList }" var="item" varStatus="status"> <tr> <td><input name="itemsList[${status.index }].name" value="${item.name }"/></td> <td><input name="itemsList[${status.index }].price" value="${item.price }"/></td> <td><input name="itemsList[${status.index }].createtime" value="<fmt:formatDate value="${item.createtime}" pattern="yyyy-MM-dd HH:mm:ss"/>"/></td> <td><input name="itemsList[${status.index }].detail" value="${item.detail }"/></td> </tr> </c:forEach> </table> </form> </body> </html>
map绑定
也通过在包装pojo中定义map类型属性。
在包装类中定义Map对象,并添加get/set方法,action使用包装对象接收。
包装类中定义Map对象如下:
Public class QueryVo { private Map<String, Object> itemInfo = new HashMap<String, Object>(); //get/set方法.. }
页面定义如下:
<tr> <td>学生信息:</td> <td> 姓名:<inputtype="text"name="itemInfo['name']"/> 年龄:<inputtype="text"name="itemInfo['price']"/> .. .. .. </td> </tr>
Contrller方法定义如下
public String useraddsubmit(Model model,QueryVo queryVo)throws Exception{ System.out.println(queryVo.getStudentinfo()); }
2、Handler的返回值
3、有效性验证validation(了解)
校验理解
项目中,通常使用较多是前端的校验,比如页面中js校验。对于安全要求较高点建议在服务端进行校验。
服务端校验:
控制层conroller:校验页面请求的参数的合法性。在服务端控制层conroller校验,不区分客户端类型(浏览器、手机客户端、远程调用)
业务层service(使用较多):主要校验关键业务参数,仅限于service接口中使用的参数。
持久层dao:一般是不校验的
springmvc校验需求
springmvc使用hibernate的校验框架validation(和hibernate没有任何关系)。
校验思路:
页面提交请求的参数,请求到controller方法中,使用validation进行校验。如果校验出错,将错误信息展示到页面。
具体需求:
商品修改,添加校验(校验商品名称长度,生产日期的非空校验),如果校验出错,在商品修改页面显示错误信息。
环境准备
hibernate的校验框架validation所需要jar包:
SSM整合时,使用校验器hibernate-validator时报错
<!-- hibernate的校验框架validation所需要jar包 --> <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>1.0.0.GA</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.1.0.Final</version> </dependency>
配置校验器(springmvc.xml中)
<mvc:annotation-driven conversion-service="conversionService"
validator="validator"></mvc:annotation-driven>
<!-- 校验器 --> <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"> <!-- hibernate校验器--> <property name="providerClass" value="org.hibernate.validator.HibernateValidator" /> <!-- 指定校验使用的资源文件,在文件中配置校验错误信息,如果不指定则默认使用classpath下的ValidationMessages.properties --> <property name="validationMessageSource" ref="messageSource" /> </bean> <!-- 校验错误信息配置文件 --> <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <!-- 资源文件名--> <property name="basenames"> <list> <value>classpath:CustomValidationMessages</value> </list> </property> <!-- 资源文件编码格式 --> <property name="fileEncodings" value="utf-8" /> <!-- 对资源文件内容缓存时间,单位秒 --> <property name="cacheSeconds" value="120" /> </bean>
在CustomValidationMessages.properties配置校验错误信息:
在pojo中添加校验规则
在ItemsCustom.java中添加校验规则:
捕获校验错误信息
//在需要校验的pojo前边添加@Validated,在需要校验的pojo后边添加BindingResult bindingResult接收校验出错信息
//注意:@Validated和BindingResult bindingResult是配对出现,并且形参顺序是固定的(一前一后)。
在页面显示校验错误信息
在controller中将错误信息传到页面即可。
参考:https://blog.csdn.net/opopopwqwqwq/article/details/51926617
@RequestMapping("/register") public ModelAndView register( @Validated @ModelAttribute("user") User user,BindingResult bindingResult, Model model,HttpServletRequest request){ ModelAndView mav=new ModelAndView(); if(bindingResult.hasErrors()){ mav.setViewName("register"); }else{ User sessionUser = (User) request.getSession().getAttribute("sessionUser"); if(sessionUser!=null){ mav.addObject("errors", "目前已经是登录状态,请先退出"); mav.addObject("user", user); mav.setViewName("login"); }else{ if(!userService.insert(user)){ mav.addObject("errors", "用户名已存在"); mav.addObject("user", user); mav.setViewName("register"); }else{ mav.setViewName("login"); } } } return mav; }
页面显示错误信息:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<spring:hasBindErrors name="user"> <c:forEach items="${errors.allErrors}" var="error" > ${error.defaultMessage } </c:forEach> </spring:hasBindErrors>
或
if(bindingResult.hasErrors()){ mav.addObject("bindingResult",bindingResult); mav.setViewName("register"); }
页面
<c:forEach items="${bindingResult.allErrors}" var="error">
${error.defaultMessage }
</c:forEach>
检验错误信息在字段后面出现:http://blog.51cto.com/983836259/1768033
https://www.yiibai.com/spring_mvc/springmvc_hibernate_validator.html
注意:
分组校验
需求
在pojo中定义校验规则,而pojo是被多个 controller所共用,当不同的controller方法对同一个pojo进行校验,但是每个controller方法需要不同的校验。
解决方法:
定义多个校验分组(其实是一个java接口),分组中定义有哪些规则
每个controller方法使用不同的校验分组
校验分组
在校验规则中添加分组
在controller方法使用指定分组的校验
校验规则说明
@Null 被注释的元素必须为 null @NotNull 被注释的元素必须不为 null @AssertTrue 被注释的元素必须为 true @AssertFalse 被注释的元素必须为 false @Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 @Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 @DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 @DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 @Size(max=, min=) 被注释的元素的大小必须在指定的范围内 @Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内 @Past 被注释的元素必须是一个过去的日期 @Future 被注释的元素必须是一个将来的日期 @Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式 Hibernate Validator 附加的 constraint @NotBlank(message =) 验证字符串非null,且长度必须大于0 @Email 被注释的元素必须是电子邮箱地址 @Length(min=,max=) 被注释的字符串的大小必须在指定的范围内 @NotEmpty 被注释的字符串的必须非空 @Range(min=,max=,message=) 被注释的元素必须在合适的范围内
4、数据回显(掌握)
什么数据回显
提交后,如果出现错误,将刚才提交的数据回显到刚才的提交页面。
pojo数据回显方法
1、springmvc默认对pojo数据进行回显。
pojo数据传入controller方法后,springmvc自动将pojo数据放到request域,key等于pojo类型(首字母小写)
使用@ModelAttribute指定pojo回显到页面在request中的key
2、@ModelAttribute还可以将方法的返回值传到页面
在商品查询列表页面,通过商品类型查询商品信息。
在controller中定义商品类型查询方法,最终将商品类型传到页面。
页面上可以得到itemTypes数据。
3、使用最简单方法使用model,可以不用@ModelAttribute
简单类型数据回显
使用最简单方法使用model。
model.addAttribute("id", id);
5、异常处理,架构级别的异常处理(了解)
异常处理思路
系统中异常包括两类:预期异常和运行时异常RuntimeException,前者通过捕获异常从而获取异常信息,后者主要通过规范代码开发、测试通过手段减少运行时异常的发生。
系统的dao、service、controller出现都通过throws Exception向上抛出,最后由springmvc前端控制器交由异常处理器进行异常处理,如下图:
springmvc提供全局异常处理器(一个系统只有一个异常处理器)进行统一异常处理。
自定义异常类
对不同的异常类型定义异常类,继承Exception。
package cn.itcast.ssm.exception; public class CustomException extends Exception { //异常信息 public String message; public CustomException(String message){ super(message); this.message = message; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }
全局异常处理器
思路:
系统遇到异常,在程序中手动抛出,dao抛给service、service给controller、controller抛给前端控制器,前端控制器调用全局异常处理器。
全局异常处理器处理思路:
解析出异常类型
如果该 异常类型是系统 自定义的异常,直接取出异常信息,在错误页面展示
如果该 异常类型不是系统 自定义的异常,构造一个自定义的异常类型(信息为“未知错误”)
springmvc提供一个HandlerExceptionResolver接口
package cn.itcast.ssm.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 { /** * (非 Javadoc) * <p>Title: resolveException</p> * <p>Description: </p> * @param request * @param response * @param handler * @param ex 系统 抛出的异常 * @return * @see org.springframework.web.servlet.HandlerExceptionResolver#resolveException(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object, java.lang.Exception) */ @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { //handler就是处理器适配器要执行Handler对象(只有method) // 解析出异常类型 // 如果该 异常类型是系统 自定义的异常,直接取出异常信息,在错误页面展示 // String message = null; // if(ex instanceof CustomException){ // message = ((CustomException)ex).getMessage(); // }else{ //// 如果该 异常类型不是系统 自定义的异常,构造一个自定义的异常类型(信息为“未知错误”) // message="未知错误"; // } //上边代码变为 CustomException customException = null; if(ex instanceof CustomException){ customException = (CustomException)ex; }else{ customException = new CustomException("未知错误"); } //错误信息 String message = customException.getMessage(); ModelAndView modelAndView = new ModelAndView(); //将错误信息传到页面 modelAndView.addObject("message", message); //指向错误页面 modelAndView.setViewName("error"); return modelAndView; } }
在springmvc.xml配置全局异常处理器
异常测试
在controller、service、dao中任意一处需要手动抛出异常。
如果是程序中手动抛出的异常,在错误页面中显示自定义的异常信息,如果不是手动抛出异常说明是一个运行时异常,在错误页面只显示“未知错误”。
在商品修改的controller方法中抛出异常 .
在service接口中抛出异常:
如果与业务功能相关的异常,建议在service中抛出异常。
与业务功能没有关系的异常,建议在controller中抛出。
6、上传图片(掌握)
需求
在修改商品页面,添加上传商品图片功能。
springmvc中对多部件类型解析
在 页面form中提交enctype="multipart/form-data"的数据时,需要springmvc对multipart类型的数据进行解析。
在springmvc.xml中配置multipart类型解析器
<!-- 文件上传 -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设置上传文件的最大尺寸为5MB -->
<property name="maxUploadSize">
<value>5242880</value>
</property>
</bean>
加入上传图片的jar
上边的解析内部使用下边的jar进行图片上传。
创建图片虚拟 目录 存储图片
通过图形界面配置:
也可以直接修改tomcat的配置:
在conf/server.xml文件,添加虚拟 目录 :
注意:在图片虚拟目录 中,一定将图片目录分级创建(提高i/o性能),一般我们采用按日期(年、月、日)进行分级创建。
上传图片代码
controller方法
// 商品信息修改提交 // 在需要校验的pojo前边添加@Validated,在需要校验的pojo后边添加BindingResult // bindingResult接收校验出错信息 // 注意:@Validated和BindingResult bindingResult是配对出现,并且形参顺序是固定的(一前一后)。 // value={ValidGroup1.class}指定使用ValidGroup1分组的 校验 // @ModelAttribute可以指定pojo回显到页面在request中的key @RequestMapping("/editItemsSubmit") public String editItemsSubmit( Model model, HttpServletRequest request, Integer id, @ModelAttribute("items") @Validated(value = { ValidGroup1.class }) ItemsCustom itemsCustom, BindingResult bindingResult, MultipartFile items_pic//接收商品图片 ) throws Exception { // 获取校验错误信息 if (bindingResult.hasErrors()) { // 输出错误信息 List<ObjectError> allErrors = bindingResult.getAllErrors(); for (ObjectError objectError : allErrors) { // 输出错误信息 System.out.println(objectError.getDefaultMessage()); } // 将错误信息传到页面 model.addAttribute("allErrors", allErrors); //可以直接使用model将提交pojo回显到页面 model.addAttribute("items", itemsCustom); // 出错重新到商品修改页面 return "items/editItems"; }
//原始名称 String originalFilename = items_pic.getOriginalFilename(); //上传图片 if(items_pic!=null && originalFilename!=null && originalFilename.length()>0){ //存储图片的物理路径 String pic_path = "F:\\develop\\upload\\temp\\"; //新的图片名称 String newFileName = UUID.randomUUID() + originalFilename.substring(originalFilename.lastIndexOf(".")); //新图片 File newFile = new File(pic_path+newFileName); //将内存中的数据写入磁盘 items_pic.transferTo(newFile); //将新图片名称写到itemsCustom中 itemsCustom.setPic(newFileName); } // 调用service更新商品信息,页面需要将商品信息传到此方法 itemsService.updateItems(id, itemsCustom); // 重定向到商品查询列表 // return "redirect:queryItems.action"; // 页面转发 // return "forward:queryItems.action"; return "success"; }
service
@Override public void updateItems(Integer id, ItemsCustom itemsCustom) throws Exception { //添加业务校验,通常在service接口对关键参数进行校验 //校验 id是否为空,如果为空抛出异常 //更新商品信息使用updateByPrimaryKeyWithBLOBs根据id更新items表中所有字段,包括 大文本类型字段 //updateByPrimaryKeyWithBLOBs要求必须转入id itemsCustom.setId(id); itemsMapper.updateByPrimaryKeyWithBLOBs(itemsCustom); }
7、Json数据交换(掌握)
为什么要进行json数据交互
json数据格式在接口调用中、html页面中较常用,json格式比较简单,解析还比较方便。
比如:webservice接口,传输json数据.
springmvc进行json交互
1、请求json、输出json,要求请求的是json串,所以在前端页面中需要将请求的内容转成json,不太方便。
2、请求key/value、输出json。此方法比较常用。
1.1 环境准备
1.1.1 加载json转的jar包
springmvc中使用jackson的包进行json转换(@requestBody和@responseBody使用下边的包进行json转),如下:
1.1.2 配置json转换器
在注解适配器中加入messageConverters
<!--注解适配器 --> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <property name="messageConverters"> <list> <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"></bean> </list> </property> </bean>
注意:如果使用<mvc:annotation-driven /> 则不用定义上边的内容。
1.2 json交互测试
1.2.1 输入json串,输出是json串
1.2.1.1 jsp页面
使用jquery的ajax提交json串,对输出的json结果进行解析。
<%@ 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>json交互测试</title> <script type="text/javascript" src="${pageContext.request.contextPath }/js/jquery-1.4.4.min.js"></script> <script type="text/javascript"> //请求json,输出是json function requestJson(){ $.ajax({ type:'post', url:'${pageContext.request.contextPath }/requestJson.action', contentType:'application/json;charset=utf-8', //数据格式是json串,商品信息 data:'{"name":"手机","price":999}', success:function(data){//返回json结果 alert(data); } }); }</script> </head> <body> <input type="button" onclick="requestJson()" value="请求json,输出是json"/> </body> </html>
1.2.1.2 controller
//请求json串(商品信息),输出json(商品信息) //@RequestBody将请求的商品信息的json串转成itemsCustom对象 //@ResponseBody将itemsCustom转成json输出 @RequestMapping("/requestJson") public @ResponseBody ItemsCustom requestJson(@RequestBody ItemsCustom itemsCustom){ //@ResponseBody将itemsCustom转成json输出 return itemsCustom; }
1.2.1.3 测试结果
1.2.2 输入key/value,输出是json串
1.2.2.1 jsp页面
使用jquery的ajax提交key/value串,对输出的json结果进行解析。
<%@ 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>json交互测试</title> <script type="text/javascript" src="${pageContext.request.contextPath }/js/jquery-1.4.4.min.js"></script> <script type="text/javascript">//请求key/value,输出是json function responseJson(){ $.ajax({ type:'post', url:'${pageContext.request.contextPath }/responseJson.action', //请求是key/value这里不需要指定contentType,因为默认就 是key/value类型 //contentType:'application/json;charset=utf-8', //数据格式是json串,商品信息 data:'name=手机&price=999', success:function(data){//返回json结果 alert(data.name); } }); } </script> </head> <body> <input type="button" onclick="responseJson()" value="请求key/value,输出是json"/> </body> </html>
1.2.2.2 controller
//请求key/value,输出json @RequestMapping("/responseJson") public @ResponseBody ItemsCustom responseJson(ItemsCustom itemsCustom){ //@ResponseBody将itemsCustom转成json输出 return itemsCustom; }
1.2.2.3 测试
8、Restful风格的实现,url模板映射(了解)
1.1 什么是RESTful
RESTful架构,就是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。
RESTful(即Representational State Transfer的缩写)其实是一个开发理念,是对http的很好的诠释。
1、对url进行规范,写RESTful格式的url
非REST的url:http://...../queryItems.action?id=001&type=T01
REST的url风格:http://..../items/001
特点:url简洁,将参数通过url传到服务端
2、http的方法规范
不管是删除、添加、更新。。使用url是一致的,如果进行删除,需要设置http的方法为delete,同理添加。。。
后台controller方法:判断http方法,如果是delete执行删除,如果是post执行添加。
3、对http的contentType规范
请求时指定contentType,要json数据,设置成json格式的type。。
1.2 REST的例子
1.2.1 需求
查询商品信息,返回json数据。
1.2.2 controller
定义方法,进行url映射使用REST风格的url,将查询商品信息的id传入controller .
输出json使用@ResponseBody将java对象输出json。
@RequestMapping(value="/ itemsView/{id}"):{×××}占位符,请求的URL可以是“/viewItems/1”或“/viewItems/2”,通过在方法中使用@PathVariable获取{×××}中的×××变量。
@PathVariable用于将请求URL中的模板变量映射到功能处理方法的参数上。
如果RequestMapping中表示为"/ itemsView /{id}",id和形参名称一致,@PathVariable不用指定名称。
1.2.3 REST方法的前端控制器配置
在web.xml配置:
1.3 对静态资源的解析
配置前端控制器的url-parttern中指定/,对静态资源的解析出现问题:
在springmvc.xml中添加静态资源解析方法。
9、拦截器(掌握)
1.1 拦截定义
定义拦截器,实现HandlerInterceptor接口。接口中提供三个方法。
public class HandlerInterceptor1 implements HandlerInterceptor { //进入 Handler方法之前执行 //用于身份认证、身份授权 //比如身份认证,如果认证通过表示当前用户没有登陆,需要此方法拦截不再向下执行 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //return false表示拦截,不向下执行 //return true表示放行 return false; } //进入Handler方法之后,返回modelAndView之前执行 //应用场景从modelAndView出发:将公用的模型数据(比如菜单导航)在这里传到视图,也可以在这里统一指定视图 @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } //执行Handler完成执行此方法 //应用场景:统一异常处理,统一日志处理 @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }
1.2 拦截器配置
1.2.1 针对HandlerMapping配置
springmvc拦截器针对HandlerMapping进行拦截设置,如果在某个HandlerMapping中配置拦截,经过该 HandlerMapping映射成功的handler最终使用该 拦截器。
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"> <property name="interceptors"> <list> <ref bean="handlerInterceptor1"/> <ref bean="handlerInterceptor2"/> </list> </property> </bean> <bean id="handlerInterceptor1" class="springmvc.intercapter.HandlerInterceptor1"/> <bean id="handlerInterceptor2" class="springmvc.intercapter.HandlerInterceptor2"/>
一般不推荐使用。
1.2.2 类似全局的拦截器
springmvc配置类似全局的拦截器,springmvc框架将配置的类似全局的拦截器注入到每个HandlerMapping中。
1.3 拦截测试
1.3.1 测试需求
测试多个拦截器各各方法执行时机。
1.3.2 编写两个拦截
1.3.3 两个拦截器都放行
HandlerInterceptor1...preHandle
HandlerInterceptor2...preHandle
HandlerInterceptor2...postHandle
HandlerInterceptor1...postHandle
HandlerInterceptor2...afterCompletion
HandlerInterceptor1...afterCompletion
总结:
preHandle方法按顺序执行,
postHandle和afterCompletion按拦截器配置的逆向顺序执行。
1.3.4 拦截器1放行,拦截器2不放行
HandlerInterceptor1...preHandle
HandlerInterceptor2...preHandle
HandlerInterceptor1...afterCompletion
总结:
拦截器1放行,拦截器2 preHandle才会执行。
拦截器2 preHandle不放行,拦截器2 postHandle和afterCompletion不会执行。
只要有一个拦截器不放行,postHandle不会执行。
1.3.1 拦截器1不放行,拦截器2不放行
HandlerInterceptor1...preHandle
拦截器1 preHandle不放行,postHandle和afterCompletion不会执行。
拦截器1 preHandle不放行,拦截器2不执行。
1.3.2 小结
根据测试结果,对拦截器应用。
比如:统一日志处理拦截器,需要该 拦截器preHandle一定要放行,且将它放在拦截器链接中第一个位置。
比如:登陆认证拦截器,放在拦截器链接中第一个位置。权限校验拦截器,放在登陆认证拦截器之后。(因为登陆通过后才校验权限)
1.4 拦截器应用(实现登陆认证)
1.4.1 需求
1、用户请求url
2、拦截器进行拦截校验
如果请求的url是公开地址(无需登陆即可访问的url),让放行
如果用户session 不存在跳转到登陆页面
如果用户session存在放行,继续操作。
1.4.2 登陆controller方法
@Controller public class LoginController { // 登陆 @RequestMapping("/login") public String login(HttpSession session, String username, String password) throws Exception { // 调用service进行用户身份验证 // ... // 在session中保存用户身份信息 session.setAttribute("username", username); // 重定向到商品列表页面 return "redirect:/items/queryItems.action"; } // 退出 @RequestMapping("/logout") public String logout(HttpSession session) throws Exception { // 清除session session.invalidate(); // 重定向到商品列表页面 return "redirect:/items/queryItems.action"; } }
1.4.3 登陆认证拦截实现
1.4.3.1 代码实现
public class LoginInterceptor implements HandlerInterceptor { //进入 Handler方法之前执行 //用于身份认证、身份授权 //比如身份认证,如果认证通过表示当前用户没有登陆,需要此方法拦截不再向下执行 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //获取请求的url String url = request.getRequestURI(); //判断url是否是公开 地址(实际使用时将公开 地址配置配置文件中) //这里公开地址是登陆提交的地址 if(url.indexOf("login.action")>=0){ //如果进行登陆提交,放行 return true; } //判断session HttpSession session = request.getSession(); //从session中取出用户身份信息 String username = (String) session.getAttribute("username"); if(username != null){ //身份存在,放行 return true; } //执行这里表示用户身份需要认证,跳转登陆页面 request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response); //return false表示拦截,不向下执行 //return true表示放行 return false; }
1.4.3.2 拦截器配置