1.springmvc框架基础回顾
2.包装类型pojo参数绑定
2.1实现方法
2.2页面参数和controller方法形参定义
3.集合类型绑定
3.1数组绑定
<input type="button" value="批量删除" onclick="deleteItems()"/>
function deleteItems() {
document.itemsForm.action="${pageContext.request.contextPath }/items/deleteItems.action";
document.itemsForm.submit();
}
<td><input type="checkbox" value="${item.id}" name="items_id"/></td>
上面的name的值需要和控制器的参数名一样才行
@RequestMapping("/deleteItems")
public String deleteItems(Integer[] items_id) throws Exception{
for(int i=0;i<items_id.length;i++){
itemsService.deleteById(items_id[i]);
}
return "success";
}
3.2list绑定
使用List接收页面提交的批量数据,通过包装pojo接收,在包装pojo中定义list属性。
3.3map绑定
4.服务端校验
4.1校验理解
4.2springmvc校验
springmvc使用hibernate的校验框架validation(和hibernate没雨关系)
校验思路:
页面提交请求的参数,请求到controller方法中,使用validation进行校验,如果校验出错,将错误信息展示到页面。
具体需求:
商品修改,添加校验(校验商品名称长度,生产日期的非空校验),如果校验出错,在商品修改页面显示错误信息。
4.3环境准备
4.4配置校验器
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<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>CustomValidationMessages</value>
</list>
</property>
<!--资源编码格式-->
<property name="fileEncodings" value="utf-8" />
<!--对资源文件内容缓存时间,单位秒-->
<property name="cacheSeconds" value="120" />
</bean>
4.5校验器注入到处理器适配器中
配置方式1
<mvc:annotation-driven conversion-service="conversionService" validator="validator"></mvc:annotation-driven>
配置方式2
4.6在pojo中添加校验规则
4.7创建CustomValidationMessages.properties
#校验提示信息:items.name.length.error要写在java代码中
items.name.length.error=商品名称的长度请限制在1到30个字符
items.createtime.is.notnull=请输入商品的生产日期
4.8捕获校验错误信息
//在每个@Validated后面都需要一个BindingResult来接收对于的错误信息,是一对一配对关系的,并且顺序固定
@RequestMapping("/editItemsSubmit")
public ModelAndView editItemsSubmit(Integer id, @Validated ItemsCustom itemsCustom, BindingResult bindingResult) throws Exception{
4.9在页面显示校验错误信息
@NotNull 和 @NotEmpty 和@NotBlank 区别
@NotEmpty 用在集合类上面
@NotBlank 用在String上面
@NotNull 用在基本类型上
如果在基本类型上面用NotEmpty或者NotBlank 会出现上面的错
5.数据回显
5.1什么是数据回显
提交后,如果出现错误,将刚才提交的数据回显到刚才的提交页面
5.2pojo数据回显方法
-
springmvc默认对pojo数据进行回显
pojo数据传入controller方法后,springmvc自动将pojo数据放到request域,key等于pojo类型(首字母小写)
使用@ModelAttribute指定pojo回显到页面在request中的key
-
@ModelAttribute还可以将方法的返回值传到页面
//单独将商品类型的方法提取出来,将方法返回值填充到request域,在页面提示 @ModelAttribute("itemsType") public Map<String,String> getItemsType() throws Exception{ HashMap<String,String> itemsType=new HashMap<>(); itemsType.put("001","data type"); itemsType.put("002","clothes"); return itemsType; }
然后我们在itemsList.jsp页面中加入如下标签进行页面的展示:
<td> <select> <c:forEach items="${itemsType}" var="item"> <option value="${item.key}">${item.value}</option> </c:forEach> </select> </td>
成功使用@ModleAttribute注解将方法返回值返回到页面。其实这种注解的方式也是将数据填入到request域中,然后在页面中通过el表达式取出,同model.addAttribute()方法和modelAndView.addObject()方法。
6.异常处理
6.1常见异常类型
系统中异常类型有哪些?
包括预期可能发生的异常、运行时异常(RuntimeException),运行时异常不是预期会发生的。
针对预期可能发生的异常,在代码手动处理异常可以try/catch捕获,可以向上抛出。
针对运行时异常,只能通过规范代码质量、在系统测试时详细测试等排除运行时异常。
系统的dao、service、controller出现都通过throws Exception 向上抛出,最后由springmvc前端控制器交由异常处理器进行异常处理
6.2自定义异常类
对不同的异常类型定义异常类,继承Exception
public class CustomException extends Exception {
//异常信息
public String message;
public CustomException(String message){
super(message);
this.message=message;
}
@Override
public String getMessage() {
return super.getMessage();
}
public void setMessage(String message) {
this.message = message;
}
}
6.3全局异常处理器
思路:
系统遇到异常,在程序中手动抛出,dao抛给service,service给controller,controller抛给前端控制器,前端控制器调用全局异常处理器。
全局异常处理器处理思路:
解析出异常类型
如果该 异常类型是系统 自定义的异常,直接取出异常信息,在错误页面展示
如果该 异常类型不是系统 自定义的异常,构造一个自定义的异常类型(信息为“未知错误”)
springmvc提供一个HandlerExceptionResolver接口
public class CustomExceptionResolver implements HandlerExceptionResolver{
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, javax.servlet.http.HttpServletResponse httpServletResponse, Object o, Exception ex) {
//输出异常
ex.printStackTrace();
//统一异常处理代码
//针对系统自定义的CustomException异常,就可以直接从异常中获取异常信息,将异常处理在错误页面展示
//异常信息
String message=null;
CustomException customException=null;
//如果ex是系统自定义的异常,我们就直接取出异常信息
if (ex instanceof CustomException)
{
customException= (CustomException) ex;
}else {
customException=new CustomException("未知错误");
}
//错误信息
message=customException.getMessage();
ModelAndView modelAndView=new ModelAndView();
modelAndView.addObject("message",message);
modelAndView.setViewName("Name");
return modelAndView;
}
}
6.4 在springmvc中配置异常处理器
<!--全局异常处理器配置-->
<bean class="exception.CustomExceptionResolver"></bean>
6.5异常测试
if(itemsCustom==null){
throw new CustomException("商品信息不存在");
}
如果与业务功能相关的异常,建议在service中抛出异常。
与业务功能没有关系的异常,建议在controller中抛出。
7. 上传图片
7.1 springmvc中对多部件类型解析
springmvc使用commons-fileupload进行图片上传。
需要导入的jar包为:commons-fileupload-1.2.2.jar和依赖包commons-io-2.4.jar。
在页面form中提交enctype=“multipart/form-data"的数据时,需要springmvc对multipart类型的数据进行解析
在springmvc.xml中配置multipart类型的解析器:
<!-- 文件上传
CommonsMultipartResolver依赖我们传入的fileupload jar包-->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设置上传文件的最大尺寸为5MB -->
<property name="maxUploadSize">
<value>5242880</value>
</property>
</bean>
7.2配置虚拟目录
切记:不要把图片上传到工程目录 ,不方便进行工程维护。实际电商项目中使用专门图片服务器(http,比如apache、tomcat)。本教程使用图片虚拟目录,通过虚拟目录访问硬盘上存储的图片目录。
-
打开tomcat的虚拟目录
-
点击下边的+号,选择External source
-
选择你的虚拟目录即可
并在Application context中添加一个在服务器上访问的路径别名,我这里设置的为/pic
。若想访问该目录下的图片,则在浏览器网站上输入:http://localhost:8080/pic/图片名称
,(不需要写项目名称),即可在浏览器中成功访问到该图片。
**注意:**图片目录中尽量进行目录分级存储,提高访问速度(提高i/o),一般我们采用日期(年、月、日)进行分级创建
7.3编写上传图片的页面
<tr>
<td>商品图片</td>
<td>
<c:if test="${itemsCustom.pic!=null}">
<img src="/pic/${itemsCustom.pic}" width="100" height="100">
<br/>
</c:if>
<input type="file" name="pictureFile">
</td>
</tr>
7.4编写controller
if (pictureFile!=null)
{
//图片上传成功后,将图片的地址写到数据库
String filePath="/Users/chenzeyuan/Downloads/images";//它的值要同你设置虚拟目录时涉及的目录路径一致,
String originalFilename=pictureFile.getOriginalFilename();
String newFileName= UUID.randomUUID()+originalFilename.substring(originalFilename.lastIndexOf("."));
//新文件
File file=new File(filePath+newFileName);
//将内存中的文件写入磁盘
pictureFile.transferTo(file);
//图片上传成功
itemsCustom.setPic(newFileName);
}
8.json数据交互
8.1为什么要进行json数据交互
json数据格式是比较简单容易理解,json数据格式常用于远程接口传输,http传输json数据,非常方便页面进行提交/请求结果解析,对json数据的解析。
8.2springmvc进行json交互
@RequestBody:将请求的json数据转成java对象。
@ResponseBody:将java对象转成json数据输出。
- 请求json、输出json,要求请求的是json串,所以在前端页面中需要将请求的内容转成json,不太方便。
- 请求key/value、输出json。此方法比较常用。
8.3springmvc解析json需要加入的json解析包
Springmvc默认用MappingJacksonHttpMessageConverter对json数据进行转换,需要加入jackson的包:jackson-annotations-2.7.4.jar,jackson-core-2.7.4.jar,jackson-databind-2.7.4.jar
在springmvc下配置:
<mvc:annotation-driven conversion-service="conversionService">
<mvc:message-converters>
<bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>
8.4json交互测试
以下分别为请求的是json数据,响应的也是json数据和请求key/value数据,响应的json数据的情况
<script type="text/javascript" src="${pageContext.request.contextPath}/js/jquery-1.4.4.min.js"></script>
<script type="text/javascript">
function requestJson(){
$.ajax({
url:"${pageContext.request.contextPath }/json/requestJson.action",
type:"post",
contentType:"application/json;charset=utf-8",
//请求json数据,使用json表示商品信息
data:'{"name":"手机","price":1999}',
success:function(data){
alert(data.name);
}
});
}
function responseJson() {
$.ajax({
url:"${pageContext.request.contextPath }/json/responseJson.action",
type:"post",
//contentType:"application/json;charset=utf-8",
//不需要指定contentType,因为默认是key/value型
//请求key/value数据
data:"name=手机&price=1999",
success:function(data){
alert(data.name);
}
});
}
</script>
@Controller
@RequestMapping("/json")
public class jsonTest {
@RequestMapping("requestJson")
public @ResponseBody ItemsCustom requestJson(@RequestBody ItemsCustom itemsCustom) throws Exception{
return itemsCustom;
}
//请求key/value(在页面中通过ajax写入用户想要请求的key/value信息,不需要加@RequestBody注解),响应json(由于action返回的是itemsCustom对象,所以需要加入@ResponseBody注解将pojo对象转换为json格式响应给用户)
@RequestMapping("/responseJson")
public @ResponseBody ItemsCustom responseJson(ItemsCustom itemsCustom) throws Exception{
return itemsCustom;
}
}
9.restful
9.1什么是restful
RESTful是一种软件开发理念,RESTful对http进行非常好的诠释。RESTful即Representational State Transfer的缩写。
1、 对url进行规范,写restful格式的url
非RESTful的http的url:http://localhost:8080/items/editItems.action?id=1&....
RESTful的url是简洁的:http://localhost:8080/items/editItems/1
2、 http的方法规范
不管是删除、添加、更新。使用url是一致的,如果进行删除,需要设置http的方法为delete,同理添加。。
后台controller方法:判断http方法,如果是delete执行删除,如果是post执行添加
3、 对http的contentType规范
请求时指定contentType,要json数据,设置成json格式的type
9.2需求
根据id查看商品信息,商品信息查看的连接使用RESTful方式实现,商品信息以json返回。
9.3Controller
@RequestMapping("/itemsView/{id}")
public @ResponseBody ItemsCustom itemsView(@PathVariable("id")Integer id) throws Exception{
ItemsCustom itemsCustom=itemsService.findItemById(id);
return itemsCustom;
}
9.4rest前端控制器配置
<!--RESTful的配置-->
<servlet>
<servlet-name>springmvc_rest</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--加载springmvc配置文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<!--配置文件的地址
如果不配置contextConfigLocation,默认查找的配置文件名称是classpath下的:servlet名称+"-servlet.xml"即springmvc-servlet.xml-->
<param-value>classpath:springmvc/springmvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springmvc_rest</servlet-name>
<!--可以配置/:此工程所有的请求全部由springmvc解析,此种方式可以实现RESTful方式,需要特殊处理对静态文件的解析不能由springmvc解析
可以配置*.do或者*.action,所有请求的url扩展名为.do或.action由springmvc解析,此中方法常用
不可以配置/*,如果配置/*,返回jsp也由springmvc解析,这是不对的-->
<!--rest方式配置为/-->
<url-pattern>/</url-pattern>
</servlet-mapping>
9.5对静态资源的解析
我们在web.xml中将访问所有路径的方式设置为:<url-pattern>/</url-pattern>
即访问只要是该路径下的资源时都会经过前端控制器,这是不对的。当我们访问web包下的静态资源时也会经过前端控制器由springmvc解析,这当然是不正确的。所以我们需要设置静态资源的解析,在springmvc.xml中添加如下配置信息:
<mvc:resources location="/js/" mapping="/js/**"/>
表示访问web/js/包下的静态资源时不会被前端控制器拦截也就不会被springmvc.xml解析。此时便可以正常访问静态资源。
10.拦截器
10.1拦截器的定义
定义拦截器
public class HandlerInterceptor1 implements HandlerInterceptor{
//在执行handler之前来执行的
//用于用户认证校验、用户权限校验
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
//返回false表示进行拦截,不继续执行handler。返回true表示不进行拦截,继续执行handler
return false;
}
//在执行handerl但是返回modelandview之前来执行
//如果需要向页面提供一些公用的数据(菜单的导航)或配置一些视图信息,使用此方法实现从modelAndView入手
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
//执行handler之后执行此方法
//做系统统一异常处理,进行方法执行性能监控,在prehandler中设置一个时间点,在afterCompletion设置一个时间点,两个时间点的差就是执行时长
//实现系统统一日志记录
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
10.2拦截器配置
- 针对HandlerMapping进行拦截设置
springmvc拦截器对HandlerMapping进行拦截设置。
如果在某个HandlerMapping中配置拦截,经过该HandlerMapping映射成功的handler最终使用该拦截器。
- 类似全局的拦截器
springmvc配置类似全局的拦截器,springmvc框架将配置的类似全局的拦截器注入到每个HandlerMapping中
<!--拦截器 -->
<mvc:interceptors>
<!--多个拦截器,顺序执行 -->
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="interceptor.HandlerInterceptor1"></bean>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="interceptor.HandlerInterceptor2"></bean>
</mvc:interceptor>
</mvc:interceptors>
10.3拦截测试
再定义一个拦截器HandlerInterceptor2.java,代码如下:
public class HandlerInterceptor2 implements HandlerInterceptor{
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
System.out.println("HandlerInterceptor2....postHandle");
return false;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
System.out.println("HandlerInterceptor2....postHandler");
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
System.out.println("HandlerInterceptor2....afterCompletion");
}
}
- 两个拦截器都放行
测试结果为:
HandlerInterceptor1...preHandle
HandlerInterceptor2...preHandle
HandlerInterceptor2...postHandle
HandlerInterceptor1...postHandle
HandlerInterceptor2...afterCompletion
HandlerInterceptor1...afterCompletion
总结:执行preHandle是顺序执行。执行postHandle、afterCompletion是倒序执行。
- 拦截器1放行,2不放行
此时访问界面,界面是空白的但是不会出现报错信息,控制台打印测试结果:
HandlerInterceptor1...preHandle
HandlerInterceptor2...preHandle
HandlerInterceptor1...afterCompletion
总结:
拦截器1放行,拦截器2才会执行preHandler
如果preHandle不放行,postHandle、afterCompletion都不执行。只要有一个拦截器不放行,postHandler不会执行。
-
拦截器1和2都不放行
测试结果:
HandlerInterceptor1...preHandle
10.4拦截器应用(实现登陆认证)
10.4.1需求
-
用户请求url
-
拦截器进行拦截校验
如果请求的url是公开地址(无需登录即可访问的url),让放行
如果用户session不存在跳转到登录页面
如果用户session存在放行,继续操作。
10.4.2登录的controller方法
@Controller
public class LoginController {
//用户登陆提交方法
@RequestMapping("/login")
public String login(HttpSession session, String usercode, String password) throws Exception
{
//调用service校验用户帐号和密码的正确性
//这个东西我们讲shiro的时候再写
//如果service校验通过,将用户身份记录到session
session.setAttribute("usercode",usercode);
//重定向到商品查询页面
return "redirect:/items/queryItems.action";
}
//用户退出
@RequestMapping("/logout")
public String logout(HttpSession session) throws Exception
{
//session失效
session.invalidate();
//重定向到商品查询页面
return "redirect:/items/queryItems.action";
}
}