JAVAWEB开发之SpringMVC详解(二)——高级开发、数据回显、参数绑定集合、图片上传、json交互、validation校验、异常处理、RESTful支持、拦截器

知识回顾

springmvc框架
用户请求url到DispatcherServlet前端控制器,相当于中央调度器,降低系统各组件之间的耦合度。
DispatcherServlet前端控制器通过HandlerMapping处理器映射器根据url找到Handler。
DispatcherServlet前端控制器通过HandlerAdapter处理器适配器执行Handler。
DispatcherServlet前端控制器得到Handler返回的ModelAndView通过视图解析器ViewResolver进行视图解析。
视图解析:将程序中写的逻辑视图名,转成真正的视图(springmvc通过view表示各个类型不同的视图)。DispatcherServlet前端控制器调用View的渲染方法进行视图渲染(将ModelAndView中的model放到request域)

重点:SpringMVC的注解开发,企业中常用springmvc的注解开发
使用专门注解处理器映射器(RequestMappingHandlerMapping)和处理器适配器(RequestMappingHandlerAdapter)
注意:使用可以替代上边的注解处理器映射器和注解处理器适配器的配置。
在Handler(controller)中定义很多的方法,一个方法通过@RequestMapping对url进行映射。
方法返回值:ModelAndView、String(jsp的逻辑视图名)、void(通过response将数据输出成json)。
方法输入参数(形参):springmvc需要将请求的key/value(串,id=001&id=002)、解析绑定到Handler(controller)中方法的形参上。
springmvc默认支持多类型的参数绑定。
默认支持的类型:HttpServletRequest、HttpServletResponse、HttpSession、Model(用于将数据填充到request域)。
@RequestParam注解:用于绑定单个请求参数,常用于简单类型参数(Integer、String、Float......)绑定。
不使用@ReuestParam要求请求参数的名称和方法形参名一致方可进行绑定。
对于简单类型参数中的日期型,建议使用自定义参数绑定,对日期类型数据个性化定义日期的格式。
自定义参数绑定:建议使用Converter进行参数绑定。
还可以绑定pojo、包装的pojo.

高级知识清单

注解开发:
  数据回显:表单提交错误,重新回到表单,用户重新填写数据,刚才提交的参数在页面上回显。
  集合类型(String[]、List<>、Map)的参数绑定
  springmvc上传图片(重点)
  json数据交互(提交json数据、响应json数据)(重点)
  Validation(springmvc使用校验方式: 使用Hibernate Validator(和Hibernate的ORM没有任何关系) )
拦截器(用于权限控制)

数据回显

需求  

表单提交出现错误,重新回到表单,用户重新填写数据,刚才提交的数据在页面上显示。

对简单类型的数据回显

对商品修改数据回显:
注意在进入修改页面的controller方法中和提交修改商品信息方法model.addAttribute方法设置key一致。

修改商品显示方法:
 @RequestMapping(value="/editItems",method={RequestMethod.GET}) 
	  public String editItems(Model model,Integer id) throws Exception{
	  
	 // 将id传递到页面
	  model.addAttribute("id",id);
		  
	  // 调用Service查询商品信息 
	  ItemsCustom itemsCustom = itemsService.findItemsById(id); //将模型数据传到jsp 
	  model.addAttribute("item",itemsCustom);
	  
	  //return "editItem_2";
	  return "editItem"; 
	  }
修改页面
修改商品信息: ......修改商品提交方法
@RequestMapping("/editItemSubmit")
	public String editItemSubmit(Model model,Integer id, ItemsCustom itemsCustom) throws Exception {

		// 调用Service接口更新商品信息
		itemsService.updateItems(id, itemsCustom);

		// 进行数据回显
		model.addAttribute("id", id);
		
		// 提交后回到修改页面
		return "editItem";
	}
提交后查看网页源码
JAVAWEB开发之SpringMVC详解(二)——高级开发、数据回显、参数绑定集合、图片上传、json交互、validation校验、异常处理、RESTful支持、拦截器_第1张图片
可以发现只有id回显到了修改页面
其过程原理是这样的:
第一次点击修改跳转到修改页面,从request域取值为name的id的属性赋值。
点击提交后将name的值(请求参数)提交到提交方法中为形参id赋值。
然后在提交方法内将id存入request域,再跳转到编辑页面,进行回显。

pojo数据类型回显

回显方法1:

使用model.addAttribute方法进行数据回显
@RequestMapping("/editItemSubmit")
	public String editItemSubmit(Model model,Integer id, ItemsCustom itemsCustom) throws Exception {

		// 调用Service接口更新商品信息
		itemsService.updateItems(id, itemsCustom);

		// 进行数据回显
		model.addAttribute("id", id);
		model.addAttribute("item",itemsCustom);
		
		// 提交后回到修改页面
		return "editItem";
}
过程就是:提交后表单数据传入到提交方法的形参id和itemsCustom中。
然后将形参存入到request域,属性命名保持与编辑页面取数据的名称一致。
跳转到编辑页面即可从request取值 进行回显。
JAVAWEB开发之SpringMVC详解(二)——高级开发、数据回显、参数绑定集合、图片上传、json交互、validation校验、异常处理、RESTful支持、拦截器_第2张图片

回显方法2:

使用@ModelAttribute, 作用于将请求pojo数据放到Model中回显到页面
	public String editItemSubmit(Model model,Integer id, @ModelAttribute(value="item")ItemsCustom itemsCustom) throws Exception {
在@ModelAttribute方法指定的名称就是要填充到Model中的key,在页面上就要通过key取数据。

@ModelAttribute将方法返回值传到页面

需求:商品类别信息在商品信息页面展示
// 单独将商品类型的方法提出来,将方法返回值填充到request,在页面显示
	@ModelAttribute("itemsType")
	public Map getItemsType() throws Exception{
		HashMap itemsType = new HashMap();
		itemsType.put("001", "数码");
		itemsType.put("002", "服装");
		return itemsType;
	}
页面
商品类别:
	
使用@ModelAttribute将公用的取数据的方法返回值传递到页面,不用在controller的每一个方法中通过Model将数据传到页面。

参数绑定集合类型

绑定数组

需求:在商品查询列表页面,用户选择要删除的商品,批量删除商品。
在controller方法中如何将批量提交的数据绑定成数组类型。

页面定义

  

controller方法定义

// 删除商品
	@RequestMapping("/deleteItems")
	public String deleteItems(Integer[] delete_id) throws Exception{
		System.out.println(delete_id);
		return "success";
	}
运行结果:
JAVAWEB开发之SpringMVC详解(二)——高级开发、数据回显、参数绑定集合、图片上传、json交互、validation校验、异常处理、RESTful支持、拦截器_第3张图片JAVAWEB开发之SpringMVC详解(二)——高级开发、数据回显、参数绑定集合、图片上传、json交互、validation校验、异常处理、RESTful支持、拦截器_第4张图片

绑定List 需求:批量修改商品信息提交
先进入批量修改商品页面,填写信息,点击提交

页面定义


...... 注释:name值中的itemList
itemsList:controller方法形参包装类型中list的属性名
itemsList[0]或itemsList[0]......[]中是序号,从0开始
itemsList[index].name就是controller方法形参包装类型中list中pojo的属性名。

Controller方法定义

使用包装类型接收页面批量提交的数据,绑定成list
public class ItemsQueryVo {

	// 商品信息
	private ItemsCustom itemsCustom;

	// 定义一个List
	private List itemsList;
        ......
}
使用包装类型作为形参 接收参数
// 批量修改商品提交
	@RequestMapping("/editItemsListSubmit")
    public String editItemsListSubmit(ItemsQueryVo itemsQueryVo){
		return "success";
	}
JAVAWEB开发之SpringMVC详解(二)——高级开发、数据回显、参数绑定集合、图片上传、json交互、validation校验、异常处理、RESTful支持、拦截器_第5张图片 JAVAWEB开发之SpringMVC详解(二)——高级开发、数据回显、参数绑定集合、图片上传、json交互、validation校验、异常处理、RESTful支持、拦截器_第6张图片

springmvc和struts的区别

SpringMVC是通过方法的形参接受参数,在使用时可以以单例方式使用,建议使用单例。
Struts是通过成员变量接收参数,在使用时必须以多例方式使用。

SpringMVC是基于方法开发,Struts是基于类开发。
SpringMVC将一个请求的Method和Handler进行关联绑定,一个method对应一个Handler。
SpringMVC开发是以方法为单位进行开发,方法更贴近Service(业务方法)。
经过实际测试,发现Struts标签解析速度比较慢,建议在实际开发中使用jstl。

商品图片上传

需求

在商品修改页面,增加图片上传的功能。
操作流程:
    用户进入商品修改页面
    上传图片
    点击提交(提交的是图片和商品的信息)
    再次进入修改页面,图片在商品修改页面展示

图片存储的问题

切记:不要将图片上传到工程目录,不方便进行工程维护。
实际电商项目中使用专门的图片服务器(比如Apache、Tomcat)
在Tomcat中进行虚拟目录的设置
设置方法如下:
在Tomcat下,找到conf文件下的server.xml,打开,在之间加上如下代码,然后就部署完成,重启服务器,浏  览器可以访问:
 
 虚拟目录名:浏览器访问的地址
 目标目录位置:项目所在目录的绝对路径

 reloadable="true" :服务器配置动态加载

测试:拖进目录中一张图片,启动服务器进行测试:

JAVAWEB开发之SpringMVC详解(二)——高级开发、数据回显、参数绑定集合、图片上传、json交互、validation校验、异常处理、RESTful支持、拦截器_第7张图片
注意:图片目录中尽量进行目录分级存储,提高IO的访问速度。

配置图片上传解析器

springmvc使用commons-fileupload进行图片上传
commons-fileupload对应的springmvc的图片上传解析器:org.springframework.web.multipart.commons.CommonsMultipartResolver
在springmvc.xml中配置如下:

	
		
		
			5242880
		
	
加入commons-fileupload的jar包

编写上传图片的页面

表单属性的enctype要设置为multipart/form-data
添加图片上传组件 其中name的属性值要与控制器中接收图片的方法中的形参一致。

编写控制器方法

@RequestMapping("/editItemSubmit")
	// public String editItemSubmit(Integer id,ItemsCustom
	// itemsCustom,ItemsQueryVo itemsQueryVo) throws Exception{
	public String editItemSubmit(Model model,Integer id, 
			@ModelAttribute(value="item")ItemsCustom itemsCustom,
			// 上传图片
			MultipartFile pictureFile
			) throws Exception {
		// 进行数据回显
		model.addAttribute("id", id);
		//model.addAttribute("item",itemsCustom);
		
		//进行图片上传
		if (pictureFile!=null && pictureFile.getOriginalFilename()!=null && pictureFile.getOriginalFilename().trim().length()>0) {
			// 图片上传成功后,将图片的地址写到数据库
			String filePath = "/Users/liuxun/Desktop/pictures";
			// 上传文件原始名称
			String originFileName = pictureFile.getOriginalFilename();
			// 新的图片的名称
			String newFileName = UUID.randomUUID()+originFileName.substring(originFileName.lastIndexOf("."));
			// 新文件
			File file = new File(filePath+File.separator+newFileName);
			
			// 将内存中的文件写入磁盘
			pictureFile.transferTo(file);
			
			// 图片上传成功,将图片地址写入数据库
			itemsCustom.setPic(newFileName);
		}
		
		// 调用Service接口更新商品信息
		itemsService.updateItems(id, itemsCustom);
		
		// 提交后回到修改页面
		return "editItem";
		// 请求重定向
		//return "redirect:queryItems.action";
		// 转发
		// return "forward:queryItems.action";
	}
提交后页面效果如下
JAVAWEB开发之SpringMVC详解(二)——高级开发、数据回显、参数绑定集合、图片上传、json交互、validation校验、异常处理、RESTful支持、拦截器_第8张图片

json数据交互

需求

json数据格式是比较简单和容易理解的,json数据格式常用于远程接口传输,http传输json数据,非常方便页面进行 提交/请求结果解析,对json数据的解析。

SpringMVC解析json加入json解析包

springmvc默认采用MappingJacksonHttpMessageConverter对json数据进行转换,需要加入jackson的包如下所示:

在处理器适配器中注入MappingJacksonHttpMessageConverter

让处理器适配器支持json数据解析,需要注入MappingJacksonHttpMessageConverter
 
    
    	
    	
    	
    	
    		
    			
    		
    	
    
需要将支持json数据解析的消息转换器注入到注解适配器中。

@RequestBody和@RespoonseBody

@RequestBody:将请求的json数据转成Java对象
@ResponseBody:将Java对象转成json数据输出。
JAVAWEB开发之SpringMVC详解(二)——高级开发、数据回显、参数绑定集合、图片上传、json交互、validation校验、异常处理、RESTful支持、拦截器_第9张图片

请求json响应json

controller方法
//请求的json响应json,请求商品信息,商品信息使用json格式,输出商品信息
	@RequestMapping("/requestJson")
	public @ResponseBody ItemsCustom requestJson(@RequestBody ItemsCustom itemsCustom) throws Exception{
		return itemsCustom;
	}
页面(需要引入jQuery)
// 请求json响应json
	function requestJson(){
		$.ajax({
			url:"${pageContext.request.contextPath}/requestJson.action",
			type:"post",
			contentType:"application/json;charset=utf-8",
			//请求json数据 使用json表示商品信息
			data:'{"name":"手机","price":1999}',
			success:function(data){
				alert(data.name);
			}
		});
	}
测试:
JAVAWEB开发之SpringMVC详解(二)——高级开发、数据回显、参数绑定集合、图片上传、json交互、validation校验、异常处理、RESTful支持、拦截器_第10张图片JAVAWEB开发之SpringMVC详解(二)——高级开发、数据回显、参数绑定集合、图片上传、json交互、validation校验、异常处理、RESTful支持、拦截器_第11张图片

请求key/value响应json

controller方法
@RequestMapping("/responseJson")
	public @ResponseBody ItemsCustom responseJson(ItemsCustom itemsCustom) throws Exception{
		return itemsCustom;
	}
页面
// 请求key/value响应json
	function responseJson(){
		$.ajax({
			url:"${pageContext.request.contextPath}/responseJson.action",
			type:"post",
			//contentType:"application/json;charset=utf-8",
			//请求key/value数据 使用地址拼接表示商品信息
			data:"name=手机&price=1999",
			success:function(data){
				alert(data.name);
			}
		});
	}
测试:
JAVAWEB开发之SpringMVC详解(二)——高级开发、数据回显、参数绑定集合、图片上传、json交互、validation校验、异常处理、RESTful支持、拦截器_第12张图片JAVAWEB开发之SpringMVC详解(二)——高级开发、数据回显、参数绑定集合、图片上传、json交互、validation校验、异常处理、RESTful支持、拦截器_第13张图片

总结:

如果前端处理没有特殊要求建议使用第二种,请求key/value, 返回json, 方便客户端解析请求结果。

validation校验

对前台的校验大多数通过js在页面校验,这种方法比较简单,如果出于安全性考虑,还需要在后台进行校验。
SpringMVC使用JSR-303(JavaEE规范第一部分)校验规范,SpringMVC使用的是Hibernate Validator

加入Hibernate Validator的jar


在处理器适配器中配置校验器

在注解适配器中注入自定义的webBinder
  
    
    	
    	
    	
    	
    		
    			
    		
    	
    
在webBinder中注入自定义的校验器

	
		
		
		
		
	
配置自定义校验器

	
		
		
		
		
	
	
	
		
		
			
				classpath:CustomValidationMessages
			
		
		
		
		
		
	

创建CustomValidationMessages

在classpath下新建CustomValidationMessages.properties

校验规则

需求:编辑商品信息提交时校验,商品生产日期不能为空,商品名称在1-30个字符之间
public class Items {
    private Integer id;

    //商品名称的长度限制在1到30个字符
    @Size(min=1,max=30,message="{items.name.length.error}")
    private String name;

    private Float price;

    private String pic;

    //请输入商品生产日期
    @NotNull(message="{items.createtime.is.notnull}")
    private Date createtime;
    ......
}

捕获错误

需要修改controller方法,在需要校验的形参pojo前边加@Validated, 在pojo的后边加上参数BindingResult用来接收错误信息
@RequestMapping("/editItemSubmit")
    //注意:每个校验pojo的前边必须加@Validated, 每个校验的pojo后边必须加BindingResult接收错误信息
	public String editItemSubmit(Model model,Integer id, 
			@Validated @ModelAttribute(value="item")ItemsCustom itemsCustom,
			BindingResult bindingResult,
			// 上传图片
			MultipartFile pictureFile
			) throws Exception {
错误信息输出
// 输出错误信息
		// 如果参数绑定时有错误
		if (bindingResult.hasErrors()) {
			// 获取错误
			List errors = bindingResult.getAllErrors();
			// 准备在页面输出errors,页面使用jstl遍历
			model.addAttribute("errors",errors);
			for (ObjectError error : errors) {
				// 输出错误信息
				System.out.println(error.getDefaultMessage());
			}
			// 如果校验错误,回到商品修改页面
			return "editItem";
		}

在页面上展示错误


${error.defaultMessage }
效果如下:
JAVAWEB开发之SpringMVC详解(二)——高级开发、数据回显、参数绑定集合、图片上传、json交互、validation校验、异常处理、RESTful支持、拦截器_第14张图片

分组校验

需求:针对不同的controller方法通过分组校验达到个性化校验的目的,修改商品的修改功能,只校验商品的生产日期不能为空。
第一步:创建分组接口
package liuxun.ssm.controller.validation;
/**
 * 校验分组: 用于商品修改的校验分组
 * @author liuxun
 *
 */
public interface ValidGroup1 {
	//接口不定义方法,就是只标识 哪些校验规则属于ValidGroup1分组
}
第二步:定义校验规则属于哪个分组
 //请输入商品生产日期
    //通过groups指定此校验属于哪个分组,可以指定多个分组 之间用逗号隔开groups={ValidGroup1.class,ValidGroup2.class}
    @NotNull(message="{items.createtime.is.notnull}",groups={ValidGroup1.class})
    private Date createtime;
第三步:在controller方法中使用定义的校验分组
public String editItemSubmit(Model model,Integer id, 
			@Validated(value={ValidGroup1.class}) @ModelAttribute(value="item")ItemsCustom itemsCustom,
			BindingResult bindingResult,
			// 上传图片
			MultipartFile pictureFile
			) throws Exception {
运行如下:
JAVAWEB开发之SpringMVC详解(二)——高级开发、数据回显、参数绑定集合、图片上传、json交互、validation校验、异常处理、RESTful支持、拦截器_第15张图片

统一异常处理

需求

一般项目中都需要做异常处理,基于系统架构的设计考虑,使用统一的异常处理方法。
系统中的异常类型:
包括预期可能发生的异常、运行时异常(RuntimeException),运行时异常不是预期会发生的。
针对预期可能发生的异常,在代码中手动处理异常可以try/catch捕获,可以向上抛出。
针对运行时异常,只能通过规范代码质量、在系统测试时详细测试等排除运行时异常。

统一异常处理解决方案

自定义异常

针对预期可能发生的异常,定义很多异常类型,这些异常类型通常继承于Exception。
这里定义一个系统自定义异常类。
CustomException:用于测试。
public class CustomException extends Exception {
	
	//异常信息
	private String message;

	public CustomException(String message) {
		super();
		this.message = message;
	}

	public String getMessage() {
		return message;
	}

	public void setMessage(String message) {
		this.message = message;
	}
	
}

异常处理

要在一个统一异常处理的类中要处理系统抛出的所有异常,根据异常类型处理。
统一异常处理类是什么?
前端控制器DispatcherServlet在进行HandlerMapping、调用HandlerAdapter执行Handler的过程中,如果遇到异常进行异常处理

在系统中自定义统一的异常处理器,写系统自己的异常处理代码。

自定义异常处理器类

统一异常处理器实现HandlerExceptionResolver接口
public class CustomExceptionResolver implements HandlerExceptionResolver {

	//前端控制器DispatcherServlet在进行HandlerMapping、调用HandlerAdapter执行Handler过程中,如果遇到异常就会执行此方法
	@Override
	public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
			Exception ex) {
		// 统一处理异常代码
		// ......
		return null;
	}

}

配置统一异常处理类


	

异常处理逻辑

根据不同的异常类型进行异常处理。
系统自定义的异常类是CustomException,在Controller方法中、Service方法中手动抛出此类异常。
针对系统自定义的CustomException异常,就可以直接从异常类中获取异常信息,将异常处理在错误页面展示。
针对非CustomException异常,对这类异常重新构造成一个CustomException,异常信息为"未知错误", 此类错误需要在系统测试阶段进行排除。
在统一异常处理器CustomExceptionResolver中实现上边逻辑。
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
			Exception ex) {
		//输出异常
		ex.printStackTrace();
		
		//统一处理异常代码
		//针对系统自定义的CustomException异常,就可以直接从异常类中获取异常信息,将异常处理在错误页面进行展示
		//异常信息
		String message = null;
		CustomException customException = null;
		//如果ex是自定义异常信息,直接取出异常信息
		if (ex instanceof CustomException) {
			customException = (CustomException) ex;
		} else {
			//针对非CustomException异常,对这类重新构成一个CustomException,异常信息为"未知错误"
			customException = new CustomException("未知错误");
		}
		
		//错误信息
		message = customException.getMessage();
		request.setAttribute("message", message);
		
		try {
			//转向到错误页面
			request.getRequestDispatcher("/WEB-INF/jsp/error.jsp").forward(request, response);
		} catch (ServletException | IOException e) {
			e.printStackTrace();
		}
		
		return new ModelAndView();
	}
展示错误信息页面

${message }

测试抛出异常由统一异常处理器捕获

可以在controller方法、service方法、dao实现类中抛出异常,要求dao、service、controller遇到异常全部向上抛出异常,方法向上抛出异常throws Exception
public ItemsCustom findItemsById(int id) throws Exception {
		Items items = itemsMapper.selectByPrimaryKey(id);
		//如果查询的商品信息为空,抛出系统自定义异常
		if (items == null) {
			throw new CustomException("修改商品信息不存在");
		}
		// 在这里随着需求的变量,需要查询商品的其他相关信息,返回到controller

		ItemsCustom itemsCustom = new ItemsCustom();
		// 将items的属性拷贝到itemsCustom
		BeanUtils.copyProperties(items, itemsCustom);

		return itemsCustom;
	}
测试修改方法 参数改成一个不存在的值
JAVAWEB开发之SpringMVC详解(二)——高级开发、数据回显、参数绑定集合、图片上传、json交互、validation校验、异常处理、RESTful支持、拦截器_第16张图片
流程图解如下:
JAVAWEB开发之SpringMVC详解(二)——高级开发、数据回显、参数绑定集合、图片上传、json交互、validation校验、异常处理、RESTful支持、拦截器_第17张图片

RESTful支持

什么是RESTful

一、REST,即Representational State Transfer的缩写。我对这个词组的翻译是"表现层状态转化"。
如果一个架构符合REST原则,就称它为RESTful架构。
要理解RESTful架构,最好的方法就是去理解Representational State Transfer这个词组到底是什么意思,它的每一个词代表了什么涵义。如果你把这个名称搞懂了,也就不难体会REST是一种什么样的设计。
二、资源(Resources)
REST的名称"表现层状态转化"中,省略了主语。"表现层"其实指的是"资源"(Resources)的"表现层"。
所谓"资源",就是网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的实在。你可以用一个URI(统一资源定位符)指向它,每种资源对应一个特定的URI。要获取这个资源,访问它的URI就可以,因此URI就成了每一个资源的地址或独一无二的识别符。
所谓"上网",就是与互联网上一系列的"资源"互动,调用它的URI。
三、表现层(Representation)
"资源"是一种信息实体,它可以有多种外在表现形式。我们把"资源"具体呈现出来的形式,叫做它的"表现层"(Representation)
比如,文本可以用txt格式表现,也可以用HTML格式、XML格式、JSON格式表现,甚至可以采用二进制格式;图片可以用JPG格式表现,也可以用PNG格式表现。
URI只代表资源的实体,不代表它的形式。严格地说,有些网址最后的".html"后缀名是不必要的,因为这个后缀名表示格式,属于"表现层"范畴,而URI应该只代表"资源"的位置。它的具体表现形式,应该在HTTP请求的头信息中用Accept和Content-Type字段指定,这两个字段才是对"表现层"的描述。
四、状态转化(State Transfer)
访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,势必涉及到数据和状态的变化。
互联网通信协议HTTP协议,是一个无状态协议。这意味着,所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生"状态转化"(State Transfer)。而这种转化是建立在表现层之上的,所以就是"表现层状态转化"
客户端用到的手段,只能是HTTP协议。具体来说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源
五、综述
综合上面的解释,我们总结一下什么是RESTful架构:
  (1)每一个URI代表一种资源;
  (2)客户端和服务器之间,传递这种资源的某种表现层;
  (3)客户端通过四个HTTP动词,对服务器端资源进行操作,实现"表现层状态转化"。

六、误区
RESTful架构有一些典型的设计误区。
最常见的一种设计错误,就是URI包含动词。因为"资源"表示一种实体,所以应该是名词,URI不应该有动词,动词应该放在HTTP协议中。
举例来说,某个URI是/posts/show/1,其中show是动词,这个URI就设计错了,正确的写法应该是/posts/1,然后用GET方法表示show。
如果某些动作是HTTP动词表示不了的,你就应该把动作做成一种资源。比如网上汇款,从账户1向账户2汇款500元,错误的URI是:
  POST /accounts/1/transfer/500/to/2
正确的写法是把动词transfer改成名词transaction,资源不能是动词,但是可以是一种服务:
  POST /transaction HTTP/1.1
  Host: 127.0.0.1
  
  from=1&to=2&amount=500.00
另一个设计误区,就是在URI中加入版本号:
  http://www.example.com/app/1.0/foo
  http://www.example.com/app/1.1/foo
  http://www.example.com/app/2.0/foo
因为不同的版本,可以理解成同一种资源的不同表现形式,所以应该采用同一个URI。版本号可以在HTTP请求头信息的Accept字段中进行区分
  Accept: vnd.example-com.foo+json; version=1.0
  Accept: vnd.example-com.foo+json; version=1.1
  Accept: vnd.example-com.foo+json; version=2.0

url的RESTful实现

非RESTful的http的url:http://localhost:8080/items/editItems.action?id=1&....
RESTful的url是简洁的:http:// localhost:8080/items/editItems/1
参数通过url传递,rest接口返回json数据

需求

根据id查看商品信息,商品信息查看的连接使用RESTful方式实现,商品信息以json方式返回。

第一步更改DispatcherServlet配置

在web.xml中添加如下配置

	
		springmvc_rest
		org.springframework.web.servlet.DispatcherServlet
		
		
			contextConfigLocation
			
			classpath:spring/springmvc.xml
		
	
	
		springmvc_rest
		
		/
	

第二步参数通过url传递

//根据id查看商品信息rest接口
	 //@RequestMapping中指定restful方式的url参数,参数要使用{}包起来
	 //@PathVariable将url中的{}包起来的参数和形参进行绑定
	  @RequestMapping("/viewItems/{id}")
	  public @ResponseBody ItemsCustom viewItems(@PathVariable("id") Integer id) throws Exception{
		  //调用Service查询商品信息
		  ItemsCustom itemsCustom = itemsService.findItemsById(id);
		  return itemsCustom;
	  }

第三步设置静态资源解析

当DispatcherServlet拦截/开头的所有请求,对静态资源的访问就会报错
JAVAWEB开发之SpringMVC详解(二)——高级开发、数据回显、参数绑定集合、图片上传、json交互、validation校验、异常处理、RESTful支持、拦截器_第18张图片
需要在springmvc.xml中通过设置对静态资源解析

	
	
访问/js/**的url从工程下/js/下解析
测试结果:
JAVAWEB开发之SpringMVC详解(二)——高级开发、数据回显、参数绑定集合、图片上传、json交互、validation校验、异常处理、RESTful支持、拦截器_第19张图片
注意:使用可以替代注解映射器和注解适配器的配置,而且原先需要向注解适配器中注入webBinder(包含validator校验器以及converter参数转换器或者属性编辑器) 使用替代后不用webBinder 可以直接设置到标签的节点属性中,如下所示:
JAVAWEB开发之SpringMVC详解(二)——高级开发、数据回显、参数绑定集合、图片上传、json交互、validation校验、异常处理、RESTful支持、拦截器_第20张图片

springmvc拦截器

拦截器的通常场合

用户请求到DIspatcherServlet中,DispatcherServlet调用HandlerMapping查找Handler,HandlerMapping返回一个拦截器的链,springmvc的拦截器是通过HandlerMapping发起的。
在企业开发中,使用拦截器实现用户认证(用户登陆后进行身份校验拦截器),用户权限拦截。

springmvc拦截器方法

自定义拦截器,需要实现HandlerInterceptor接口
public class HandlerInterceptor1 implements HandlerInterceptor{
	//在执行handler之前执行的
	//用于用户认证校验、用户权限校验
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        
		System.out.println("HandlerInterceptor1...preHandle");
		
		//如果返回false表示拦截器不继续执行handler,如果返回true表示放行
		return false;
	}

	//在执行handler返回modelAndView之前执行
	//如果需要向页面提供一些公用的数据或配置一些视图信息,使用此方法实现 从modelAndView入手
	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
			throws Exception {
		System.out.println("HandlerInterceptor1...postHandle");
	}

	//执行handler之后执行此方法
	//作为系统统一异常处理,进行方法执行性能监控,在preHandler中设置一个时间点 在afterCompletion设置一个时间点 二者时间差就是执行时长
	//实现系统,统一日志记录
	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception modelAndView)
			throws Exception {
		System.out.println("HandlerInterceptor1...afterCompletion");
	}

}

测试拦截器

定义两个拦截器


配置拦截器

配置全局拦截器,DispatcherServlet将配置的全局拦截器加载到所有的HandlerMapping映射器。
在springmvc.xml中配置:

	
		
		
			
			
		
		
			
			
		
	

测试1:1号和2号都放行

测试结果
HandlerInterceptor1...preHandle
HandlerInterceptor2...preHandle

HandlerInterceptor2...postHandle
HandlerInterceptor1...postHandle

HandlerInterceptor2...afterCompletion
HandlerInterceptor1...afterCompletion
总结:
执行preHandler是顺序执行的
执行postHandler、afterHandler是倒序执行的

测试2:1号放行,2号不放行

HandlerInterceptor1...preHandle
HandlerInterceptor2...preHandle
HandlerInterceptor1...afterCompletion
总结:
如果当前拦截器的preHandler不放行,postHandler、afterHandler都不放行。
拦截器链中只要有一个拦截器不放行,所有拦截器的postHandler都不会执行,controller就不能执行完成

测试3:1号和2号都不放行

测试结果:
HandlerInterceptor1...preHandle
总结:
只有前边的拦截器preHandler方法放行,下边的拦截器的preHandler才会执行

日志拦截器或异常拦截器要求

将日志拦截器或异常拦截器放在拦截器链中的第一个位置,且preHandler方法放行。

拦截器应用(用户认证拦截器)

需求

用户访问系统的资源(url),如果用户没有进行身份认证,进行拦截,系统跳转登录页面,如果用户已经认证通过,用户可以访问系统资源。

用户登录及退出功能开发

@Controller
public class LoginController {

	//用户登录提交方法
	@RequestMapping("/login")
	public String login(HttpSession session,String usercode,String password) throws Exception{
		//调用service校验用户账号和密码的正确性
		//...
		
		//如果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";
	}
}

用户身份认证拦校验截器

拦截器实现思路:http请求URL,如果URL是公开地址(不需要认证即可访问的URL) 放行,如果用户在Session中存在 放行,如果用户在Session中不存在,跳转到登录页面。

自定义登录拦截器

@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        
		//得到请求的url
		String url = request.getRequestURI();
		
		//判断是否是公开地址
		//实际开发中需要将公开地址配置在配置文件中
		//...
		if (url.indexOf("login.action")>=0) {
			//如果是公开地址 则放行
			return true;
		}
		
		//判断用户身份在Session中是否存在
		HttpSession session = request.getSession();
		String usercode = (String) session.getAttribute("usercode");
		//如果用户身份在session中存在则放行
		if (usercode!=null) {
			return true;
		}
		//执行到这里拦截,跳转到登录页面,用户进行身份认证
		request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response);
		
		//如果返回false表示拦截器不继续执行handler,如果返回true表示放行
		return false;
	}
在springmvc.xml中配置拦截器
    
	    
		   	
		   	
		   	
	    
	
效果如下:
JAVAWEB开发之SpringMVC详解(二)——高级开发、数据回显、参数绑定集合、图片上传、json交互、validation校验、异常处理、RESTful支持、拦截器_第21张图片

实例源代码如下

源码已经上传GitHub   https://github.com/LX1993728/springmvc_mybatis_1
工程目录结构如下:
JAVAWEB开发之SpringMVC详解(二)——高级开发、数据回显、参数绑定集合、图片上传、json交互、validation校验、异常处理、RESTful支持、拦截器_第22张图片JAVAWEB开发之SpringMVC详解(二)——高级开发、数据回显、参数绑定集合、图片上传、json交互、validation校验、异常处理、RESTful支持、拦截器_第23张图片

JAVAWEB开发之SpringMVC详解(二)——高级开发、数据回显、参数绑定集合、图片上传、json交互、validation校验、异常处理、RESTful支持、拦截器_第24张图片

web.xml


	springmvc_mybatis_1

	
	
		contextConfigLocation
		classpath:spring/applicationContext-*.xml 
	
	
		org.springframework.web.context.ContextLoaderListener
	

	
	
		springmvc
		org.springframework.web.servlet.DispatcherServlet
		
		
			contextConfigLocation
			
			classpath:spring/springmvc.xml
		
	
	
		springmvc
		
		*.action
	
	
	
	
		springmvc_rest
		org.springframework.web.servlet.DispatcherServlet
		
		
			contextConfigLocation
			
			classpath:spring/springmvc.xml
		
	
	
		springmvc_rest
		
		/
	
	
	
	
		CharacterEncodingFilter
		org.springframework.web.filter.CharacterEncodingFilter
		
			encoding
			utf-8
		
	
	
		CharacterEncodingFilter
		/*
	


	
		index.html
		index.htm
		index.jsp
		default.html
		default.htm
		default.jsp
	
SqlMapConfig.xml




	
	
	 	
		
	

	
applicationContext-dao.xml



	
	
	
	
      
      
      
      
      
      
	
	
	
    
	    
	    
	    
	    
    
    
    
     
	     
	     
	     
	     
     
applicationContext-service.xml


      
   
   
applicationContext-transaction.xml



	

	
	
		
	

	
	
		
			
			
			
			
			
			
			
		
	

	
	
		
	
springmvc.xml


	
	
	
	
	
	
	
	
	

	
	

    
    
    	
    	
    	
    	
    		
    			
    		
    	
    

    
	
	
		
		
	
	
	
	
		
		
		
		
		
	
		
		
	
	
	
	

	
	
		
			
				
				
			
		
	
	
	
	
		
   	
		
	    
		   	
		   	
		   	
	    
	
	
	
	
		
		
			5242880
		
	
	
	
	
	
	
	
		
		
		
		
	
	
	
		
		
			
				classpath:CustomValidationMessages
			
		
		
		
		
		
	
	
CustomValidationMessages.properties

db.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=root
log4j.properties
# Global logging configuration\uff0c\u5efa\u8bae\u5f00\u53d1\u73af\u5883\u4e2d\u8981\u7528debug
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
ItemsController.java
package liuxun.ssm.controller;

import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;

import liuxun.ssm.controller.validation.ValidGroup1;
import liuxun.ssm.po.ItemsCustom;
import liuxun.ssm.po.ItemsQueryVo;
import liuxun.ssm.service.ItemsService;

/**
 * 商品管理
 * 
 * @author liuxun
 *
 */
@Controller
// 定义url的根路径,访问时 根路径+方法的url
@RequestMapping("/items")
public class ItemsController {

	// 注入Service
	@Autowired
	private ItemsService itemsService;

	// 单独将商品类型的方法提出来,将方法返回值填充到request,在页面显示
	@ModelAttribute("itemsType")
	public Map getItemsType() throws Exception{
		HashMap itemsType = new HashMap();
		itemsType.put("001", "数码");
		itemsType.put("002", "服装");
		return itemsType;
	}
	
	// 查询商品信息方法
	@RequestMapping("/queryItems")
	public ModelAndView queryItems(HttpServletRequest request) throws Exception {

		// 调用Service查询商品列表
		List itemsList = itemsService.findItemsList(null);

		ModelAndView modelAndView = new ModelAndView();
		modelAndView.addObject("itemsList", itemsList);
		// 指定逻辑视图名
		modelAndView.setViewName("itemsList");

		return modelAndView;
	}

	// 批量修改商品查询
	@RequestMapping("/editItemsList")
	public ModelAndView editItemsList(HttpServletRequest request) throws Exception {
		
		// 调用Service查询商品列表
		List itemsList = itemsService.findItemsList(null);
		
		ModelAndView modelAndView = new ModelAndView();
		modelAndView.addObject("itemsList", itemsList);
		// 指定逻辑视图名
		modelAndView.setViewName("editItemsList");
		
		return modelAndView;
	}
	// 批量修改商品提交
	@RequestMapping("/editItemsListSubmit")
    public String editItemsListSubmit(ItemsQueryVo itemsQueryVo){
		return "success";
	}
	// 商品修改页面
	// 使用method=RequestMethod.GET限制使用get方法
	/*
	  @RequestMapping(value="/editItems",method={RequestMethod.GET}) public
	  ModelAndView editItems() throws Exception{
	  
	  ModelAndView modelAndView = new ModelAndView();
	  
	  // 调用Service查询商品信息 ItemsCustom itemsCustom =
	  itemsService.findItemsById(1); //将模型数据传到jsp
	  modelAndView.addObject("item", itemsCustom); // 指定逻辑视图名
	  modelAndView.setViewName("editItem");
	  
	  return modelAndView; }
	 */

	// 方法返回字符串,字符串就是逻辑视图名,Model作用就是将数据填充到request域,在页面展示
	  @RequestMapping(value="/editItems",method={RequestMethod.GET}) 
	  public String editItems(Model model,Integer id) throws Exception{
	  
	 // 将id传递到页面
	  model.addAttribute("id",id);
		  
	  // 调用Service查询商品信息 
	  ItemsCustom itemsCustom = itemsService.findItemsById(id); //将模型数据传到jsp 
	  model.addAttribute("item",itemsCustom);
	  
	  //return "editItem_2";
	  return "editItem"; 
	 }
	 
	 //根据id查看商品信息rest接口
	 //@RequestMapping中指定restful方式的url参数,参数要使用{}包起来
	 //@PathVariable将url中的{}包起来的参数和形参进行绑定
	  @RequestMapping("/viewItems/{id}")
	  public @ResponseBody ItemsCustom viewItems(@PathVariable("id") Integer id) throws Exception{
		  //调用Service查询商品信息
		  ItemsCustom itemsCustom = itemsService.findItemsById(id);
		  return itemsCustom;
	  }

	// 方法返回void
	/*
	@RequestMapping(value = "/editItems", method = { RequestMethod.GET })
	public void editItems(HttpServletRequest request, HttpServletResponse response,
			// @RequestParam(value="item_id",required=false,defaultValue="1")Integer id
			Integer id) throws Exception {

		// 调用Service查询商品信息
		ItemsCustom itemsCustom = itemsService.findItemsById(id);
		request.setAttribute("item", itemsCustom);
		// 注意如果使用request转向页面,这里指定页面的完整路径
		request.getRequestDispatcher("/WEB-INF/jsp/editItem.jsp").forward(request, response);
	}
	*/

	// 商品修改提交
	// itemsQueryVo是包装类型的pojo
	@RequestMapping("/editItemSubmit")
	// public String editItemSubmit(Integer id,ItemsCustom
	// itemsCustom,ItemsQueryVo itemsQueryVo) throws Exception{
    //注意:每个校验pojo的前边必须加@Validated, 每个校验的pojo后边必须加BindingResult接收错误信息
	public String editItemSubmit(Model model,Integer id, 
			@Validated(value={ValidGroup1.class}) @ModelAttribute(value="item")ItemsCustom itemsCustom,
			BindingResult bindingResult,
			// 上传图片
			MultipartFile pictureFile
			) throws Exception {
		// 输出错误信息
		// 如果参数绑定时有错误
		if (bindingResult.hasErrors()) {
			// 获取错误
			List errors = bindingResult.getAllErrors();
			// 准备在页面输出errors,页面使用jstl遍历
			model.addAttribute("errors",errors);
			for (ObjectError error : errors) {
				// 输出错误信息
				System.out.println(error.getDefaultMessage());
			}
			// 如果校验错误,回到商品修改页面
			return "editItem";
		}
		
		// 进行数据回显
		model.addAttribute("id", id);
		//model.addAttribute("item",itemsCustom);
		
		//进行图片上传
		if (pictureFile!=null && pictureFile.getOriginalFilename()!=null && pictureFile.getOriginalFilename().trim().length()>0) {
			// 图片上传成功后,将图片的地址写到数据库
			String filePath = "/Users/liuxun/Desktop/pictures";
			// 上传文件原始名称
			String originFileName = pictureFile.getOriginalFilename();
			// 新的图片的名称
			String newFileName = UUID.randomUUID()+originFileName.substring(originFileName.lastIndexOf("."));
			// 新文件
			File file = new File(filePath+File.separator+newFileName);
			
			// 将内存中的文件写入磁盘
			pictureFile.transferTo(file);
			
			// 图片上传成功,将图片地址写入数据库
			itemsCustom.setPic(newFileName);
		}
		
		// 调用Service接口更新商品信息
		itemsService.updateItems(id, itemsCustom);
		
		// 提交后回到修改页面
		return "editItem";
		// 请求重定向
		//return "redirect:queryItems.action";
		// 转发
		// return "forward:queryItems.action";
	}
	
	// 删除商品
	@RequestMapping("/deleteItems")
	public String deleteItems(Integer[] delete_id) throws Exception{
		System.out.println(delete_id);
		return "success";
	}

	//自定义属性编辑器
	/*
	@InitBinder
	public void initBinder(WebDataBinder binder) throws Exception {
		// Date.class必须是与controler方法形参pojo属性一致的date类型,这里是java.util.Date
		binder.registerCustomEditor(Date.class, new CustomDateEditor(
				new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"), true));
	}
	*/

}
JsonTest.java
package liuxun.ssm.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import liuxun.ssm.po.ItemsCustom;

/**
 * json测试
 * @author liuxun
 *
 */
@Controller
public class JsonTest {
	//请求的json响应json,请求商品信息,商品信息使用json格式,输出商品信息
	@RequestMapping("/requestJson")
	public @ResponseBody ItemsCustom requestJson(@RequestBody ItemsCustom itemsCustom) throws Exception{
		return itemsCustom;
	}
	
	@RequestMapping("/responseJson")
	public @ResponseBody ItemsCustom responseJson(ItemsCustom itemsCustom) throws Exception{
		return itemsCustom;
	}
}
LoginController.java
package liuxun.ssm.controller;

import javax.servlet.http.HttpSession;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * 登录和退出
 * @author liuxun
 *
 */
@Controller
public class LoginController {

	//用户登录提交方法
	@RequestMapping("/login")
	public String login(HttpSession session,String usercode,String password) throws Exception{
		//调用service校验用户账号和密码的正确性
		//...
		
		//如果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";
	}
}
CustomDateConverter.java
package liuxun.ssm.controller.converter;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.springframework.core.convert.converter.Converter;

/**
 * 自定义日期转换器
 * @author liuxun
 *
 */
public class CustomDateConverter implements Converter {

	@Override
	public Date convert(String source) {
		if (source!=null&&source.trim().length()>0) {
			// 进行日期转换
			try {
				return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(source);
			} catch (ParseException e) {
				e.printStackTrace();
			}
		}
		
		return null;
	}

}
StringTrimConverter.java
package liuxun.ssm.controller.converter;

import org.springframework.core.convert.converter.Converter;
/**
 * 自定义去除字符串前后空格的转换器
 * @author liuxun
 *
 */
public class StringTrimConverter implements Converter{

	@Override
	public String convert(String source) {
		
		//去掉字符串两边空格,如果去除后为空设置为null
		if (source!=null) {
			source = source.trim();
			if (source.equals("")) {
				return null;
			}
		}
		
		return source;
	}

}
LoginInterceptor.java
package liuxun.ssm.controller.interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

/**
 * 测试拦截器1
 * @author liuxun
 *
 */
public class LoginInterceptor implements HandlerInterceptor{
	//在执行handler之前执行的
	//用于用户认证校验、用户权限校验
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        
		//得到请求的url
		String url = request.getRequestURI();
		
		//判断是否是公开地址
		//实际开发中需要将公开地址配置在配置文件中
		//...
		if (url.indexOf("login.action")>=0) {
			//如果是公开地址 则放行
			return true;
		}
		
		//判断用户身份在Session中是否存在
		HttpSession session = request.getSession();
		String usercode = (String) session.getAttribute("usercode");
		//如果用户身份在session中存在则放行
		if (usercode!=null) {
			return true;
		}
		//执行到这里拦截,跳转到登录页面,用户进行身份认证
		request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response);
		
		//如果返回false表示拦截器不继续执行handler,如果返回true表示放行
		return false;
	}

	//在执行handler返回modelAndView之前执行
	//如果需要向页面提供一些公用的数据或配置一些视图信息,使用此方法实现 从modelAndView入手
	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
			throws Exception {
		System.out.println("HandlerInterceptor2...postHandle");
	}

	//执行handler之后执行此方法
	//作为系统统一异常处理,进行方法执行性能监控,在preHandler中设置一个时间点 在afterCompletion设置一个时间点 二者时间差就是执行时长
	//实现系统,统一日志记录
	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception modelAndView)
			throws Exception {
		System.out.println("HandlerInterceptor2...afterCompletion");
	}

}
CustomPropertyEditor.java
package liuxun.ssm.controller.propertyeditor;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.springframework.beans.PropertyEditorRegistrar;
import org.springframework.beans.PropertyEditorRegistry;
import org.springframework.beans.propertyeditors.CustomDateEditor;

/**
 * 自定义属性编辑器
 * @author liuxun
 *
 */
public class CustomPropertyEditor implements PropertyEditorRegistrar {

	public void registerCustomEditors(PropertyEditorRegistry binder) {
		binder.registerCustomEditor(Date.class,
				new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"), true));
	}

}
ValidGroup1.java
package liuxun.ssm.controller.validation;
/**
 * 校验分组: 用于商品修改的校验分组
 * @author liuxun
 *
 */
public interface ValidGroup1 {
	//接口不定义方法,就是只标识 哪些校验规则属于ValidGroup1分组
}
CustomException.java
package liuxun.ssm.exception;

public class CustomException extends Exception {
	
	//异常信息
	private String message;

	public CustomException(String message) {
		super();
		this.message = message;
	}

	public String getMessage() {
		return message;
	}

	public void setMessage(String message) {
		this.message = message;
	}
	
}
CustomExceptionResolver.java
package liuxun.ssm.exception;

import java.io.IOException;

import javax.servlet.ServletException;
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 {

	//前端控制器DispatcherServlet在进行HandlerMapping、调用HandlerAdapter执行Handler过程中,如果遇到异常就会执行此方法
	@Override
	public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
			Exception ex) {
		//输出异常
		ex.printStackTrace();
		
		//统一处理异常代码
		//针对系统自定义的CustomException异常,就可以直接从异常类中获取异常信息,将异常处理在错误页面进行展示
		//异常信息
		String message = null;
		CustomException customException = null;
		//如果ex是自定义异常信息,直接取出异常信息
		if (ex instanceof CustomException) {
			customException = (CustomException) ex;
		} else {
			//针对非CustomException异常,对这类重新构成一个CustomException,异常信息为"未知错误"
			customException = new CustomException("未知错误");
		}
		
		//错误信息
		message = customException.getMessage();
		request.setAttribute("message", message);
		
		try {
			//转向到错误页面
			request.getRequestDispatcher("/WEB-INF/jsp/error.jsp").forward(request, response);
		} catch (ServletException | IOException e) {
			e.printStackTrace();
		}
		
		return new ModelAndView();
	}

}
ItemsMapperCustom.java
package liuxun.ssm.mapper;

import java.util.List;

import liuxun.ssm.po.ItemsCustom;
import liuxun.ssm.po.ItemsQueryVo;

/**
 * 商品自定义Mapper
 * @author liuxun
 *
 */

public interface ItemsMapperCustom {
  // 商品查询列表
	public List findItemsList(ItemsQueryVo itemsQueryVo) throws Exception;
}
ItemsMapperCustom.xml



  
   
     
     	
     	 and name like '%${itemsCustom.name.trim()}%'
     	
     	
     		and id = #{itemsCustom.id}
     	
     
   
   
   
    
ItemsCustom.java
package liuxun.ssm.po;
/**
 * 商品信息的扩展类
 * @author liuxun
 *
 */
public class ItemsCustom extends Items {

}
ItemsQueryVo.java
/**
 * 商品的包装类
 * @author liuxun
 *
 */
public class ItemsQueryVo {

	// 商品信息
	private ItemsCustom itemsCustom;

	// 定义一个List
	private List itemsList;
	
	public ItemsCustom getItemsCustom() {
		return itemsCustom;
	}

	public void setItemsCustom(ItemsCustom itemsCustom) {
		this.itemsCustom = itemsCustom;
	}

	public List getItemsList() {
		return itemsList;
	}

	public void setItemsList(List itemsList) {
		this.itemsList = itemsList;
	}
	
}
修改自动生成的Items 添加校验
public class Items {
    private Integer id;

    //商品名称的长度限制在1到30个字符
    @Size(min=1,max=30,message="{items.name.length.error}")
    private String name;

    private Float price;

    private String pic;

    //请输入商品生产日期
    //通过groups指定此校验属于哪个分组,可以指定多个分组 之间用逗号隔开groups={ValidGroup1.class,ValidGroup2.class}
    @NotNull(message="{items.createtime.is.notnull}",groups={ValidGroup1.class})
    private Date createtime;
    ....
    ....
}
ItemsService
package liuxun.ssm.service;

import java.util.List;

import liuxun.ssm.po.ItemsCustom;
import liuxun.ssm.po.ItemsQueryVo;

/**
 * 商品Service接口
 * 
 * @author liuxun
 *
 */
public interface ItemsService {
	// 商品查询列表
	public List findItemsList(ItemsQueryVo itemsQueryVo) throws Exception;

	// 根据商品id查询商品信息
	public ItemsCustom findItemsById(int id) throws Exception;
	
	// 更新商品信息
	/**
	 * 定义Service接口,遵循单一职责,将业务参数细化(不要使用包装类型,比如Map)
	 * @param id  修改商品的id
	 * @param itemsCustom  修改商品的信息
	 * @throws Exception
	 */
	public void updateItems(Integer id,ItemsCustom itemsCustom) throws Exception;

}
ItemsServiceImpl.java
package liuxun.ssm.service.impl;

import java.util.List;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;

import liuxun.ssm.exception.CustomException;
import liuxun.ssm.mapper.ItemsMapper;
import liuxun.ssm.mapper.ItemsMapperCustom;
import liuxun.ssm.po.Items;
import liuxun.ssm.po.ItemsCustom;
import liuxun.ssm.po.ItemsQueryVo;
import liuxun.ssm.service.ItemsService;

public class ItemsServiceImpl implements ItemsService {

	// 注入mapper
	@Autowired
	private ItemsMapperCustom itemsMapperCustom;

	@Autowired
	private ItemsMapper itemsMapper;

	// 商品查询列表
	public List findItemsList(ItemsQueryVo itemsQueryVo) throws Exception {
		return itemsMapperCustom.findItemsList(itemsQueryVo);
	}

	public ItemsCustom findItemsById(int id) throws Exception {
		Items items = itemsMapper.selectByPrimaryKey(id);
		//如果查询的商品信息为空,抛出系统自定义异常
		if (items == null) {
			throw new CustomException("修改商品信息不存在");
		}
		// 在这里随着需求的变量,需要查询商品的其他相关信息,返回到controller

		ItemsCustom itemsCustom = new ItemsCustom();
		// 将items的属性拷贝到itemsCustom
		BeanUtils.copyProperties(items, itemsCustom);

		return itemsCustom;
	}

	public void updateItems(Integer id, ItemsCustom itemsCustom) throws Exception {
		// 写业务代码
		
		// 对于关键业务数据的非空校验
		if (id == null) {
			// 抛出异常,提示调用接口的用户,id不能为空
			// ...
		}
		
		itemsMapper.updateByPrimaryKeySelective(itemsCustom);
	}

}
itemsList.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"%>




查询商品列表


 
当前用户:${usercode }

	退出



查询条件:
商品名称
商品图片
商品类别:
商品列表:
选择 商品名称 商品价格 生产日期 商品描述 操作
${item.name } ${item.price } ${item.detail } 修改
editItem.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"%>




修改商品信息


 

${error.defaultMessage }
修改商品信息:
商品名称
商品价格
商品生产日期 "/>
商品图片
商品简介
editItemsList.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"%>




查询商品列表


 
查询条件:
商品类别:
商品列表:
商品名称 商品价格 生产日期 商品描述 操作
${item.detail } 修改
jsontest.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>




json测试




	
	



你可能感兴趣的:(JAVA,WEB基础知识,JAVAWEB开发)