本博文主要讲解的知识点如下:
在我们的Struts2中,我们是继承ActionSupport来实现校验的...它有两种方式来实现校验的功能
而SpringMVC使用JSR-303(javaEE6规范的一部分)校验规范, springmvc使用的是Hibernate Validator(和Hibernate的ORM无关)
导入jar包
配置校验器
<!-- 校验器 --> <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>classpath:CustomValidationMessages</value> </list> </property> <!-- 资源文件编码格式 --> <property name="fileEncodings" value="utf-8" /> <!-- 对资源文件内容缓存时间,单位秒 --> <property name="cacheSeconds" value="120" /> </bean>
添加到自定义参数绑定的WebBindingInitializer中
<!-- 自定义webBinder --> <bean id="customBinder" class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer"> <!-- 配置validator --> <property name="validator" ref="validator" /> </bean>
最终添加到适配器中
<!-- 注解适配器 --> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <!-- 在webBindingInitializer中注入自定义属性编辑器、自定义转换器 --> <property name="webBindingInitializer" ref="customBinder"></property> </bean>
创建CustomValidationMessages配置文件
定义规则
package entity; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import java.util.Date; 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; private String detail; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name == null ? null : name.trim(); } public Float getPrice() { return price; } public void setPrice(Float price) { this.price = price; } public String getPic() { return pic; } public void setPic(String pic) { this.pic = pic == null ? null : pic.trim(); } public Date getCreatetime() { return createtime; } public void setCreatetime(Date createtime) { this.createtime = createtime; } public String getDetail() { return detail; } public void setDetail(String detail) { this.detail = detail == null ? null : detail.trim(); } }
测试:
<%-- Created by IntelliJ IDEA. User: ozc Date: 2017/8/11 Time: 9:56 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>测试文件上传</title> </head> <body> <form action="${pageContext.request.contextPath}/validation.action" method="post" > 名称:<input type="text" name="name"> 日期:<input type="text" name="createtime"> <input type="submit" value="submit"> </form> </body> </html>
Controller需要在校验的参数上添加@Validation注解...拿到BindingResult对象...
@RequestMapping("/validation") public void validation(@Validated Items items, BindingResult bindingResult) { List<ObjectError> allErrors = bindingResult.getAllErrors(); for (ObjectError allError : allErrors) { System.out.println(allError.getDefaultMessage()); } }
由于我在测试的时候,已经把日期转换器关掉了,因此提示了字符串不能转换成日期,但是名称的校验已经是出来了...
分组校验其实就是为了我们的校验更加灵活,有的时候,我们 并不需要把我们当前配置的属性都进行校验,而需要的是当前的方法仅仅校验某些的属性 。那么此时,我们就可以用到分组校验了...
步骤:
在我们之前SSH,使用Struts2的时候也配置过统一处理异常...
当时候是这么干的:
详情可看: http://blog.csdn.net/hon_3y/article/details/72772559
那么我们这次的统一处理异常的方案是什么呢????
我们知道Java中的异常可以分为两类
对于运行期异常我们是无法掌控的,只能通过代码质量、在系统测试时详细测试等排除运行时异常
而对于编译时期的异常,我们可以在代码手动处理异常可以try/catch捕获,可以向上抛出。
我们可以换个思路,自定义一个模块化的异常信息,比如: 商品类别的异常
public class CustomException extends Exception { //异常信息 private String message; public CustomException(String message){ super(message); this.message = message; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }
我们在查看Spring源码的时候发现:前端控制器DispatcherServlet在进行HandlerMapping、调用HandlerAdapter执行Handler过程中,如果遇到异常, 在系统中自定义统一的异常处理器,写系统自己的异常处理代码。。
我们也可以学着点,定义一个统一的处理器类来处理异常...
public class CustomExceptionResolver implements HandlerExceptionResolver { //前端控制器DispatcherServlet在进行HandlerMapping、调用HandlerAdapter执行Handler过程中,如果遇到异常就会执行此方法 //handler最终要执行的Handler,它的真实身份是HandlerMethod //Exception ex就是接收到异常信息 @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 e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return new ModelAndView(); } }
<!-- 定义统一异常处理器 --> <bean class="cn.itcast.ssm.exception.CustomExceptionResolver"></bean>
我们在学习webservice的时候可能就听过RESTful这么一个名词,当时候与SOAP进行对比的...那么RESTful究竟是什么东东呢???
RESTful(Representational State Transfer)软件开发理念,RESTful对http进行非常好的诠释。
如果一个架构支持RESTful,那么就称它为RESTful架构...
以下的文章供我们了解:
http://www.ruanyifeng.com/blog/2011/09/restful
综合上面的解释,我们总结一下什么是RESTful架构:
关于RESTful幂等性的理解: http://www.oschina.net/translate/put-or-post
简单来说,如果对象在请求的过程中会发生变化(以Java为例子,属性被修改了),那么此是非幂等的。多次重复请求,结果还是不变的话,那么就是幂等的。
PUT用于幂等请求,因此在更新的时候把所有的属性都写完整,那么多次请求后,我们其他属性是不会变的
在上边的文章中,幂等被翻译成“状态统一性”。这就更好地理解了。
其实一般的架构并不能完全支持RESTful的,因此, 只要我们的系统支持RESTful的某些功能,我们一般就称作为支持RESTful架构 ...
非RESTful的http的url:http://localhost:8080/items/editItems.action?id=1&....
RESTful的url是简洁的:http:// localhost:8080/items/editItems/1
从上面我们可以发现,url并没有.action后缀的,因此我们要修改核心分配器的配置
<!-- 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名称+"-serlvet.xml"即:springmvc-serlvet.xml --> <param-value>classpath:spring/springmvc.xml</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>springmvc_rest</servlet-name> <!-- rest方式配置为/ --> <url-pattern>/</url-pattern> </servlet-mapping>
在Controller上使用PathVariable注解来绑定对应的参数
//根据商品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拦截/开头的所有请求,对静态资源的访问就报错:我们需要配置对静态资源的解析
<!-- 静态资源 解析 --> <mvc:resources location="/js/" mapping="/js/**" /> <mvc:resources location="/img/" mapping="/img/**" />
/
就表示不管有多少层,都对其进行解析, /*
代表的是当前层的所有资源..
在Struts2中拦截器就是我们当时的核心,原来在SpringMVC中也是有拦截器的
用户请求到DispatherServlet中,DispatherServlet调用HandlerMapping查找Handler,HandlerMapping返回一个拦截的链儿(多个拦截),springmvc中的拦截器是通过HandlerMapping发起的。
实现拦截器的接口:
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之后执行此方法 //作系统 统一异常处理,进行方法执行性能监控,在preHandle中设置一个时间点,在afterCompletion设置一个时间,两个时间点的差就是执行时长 //实现 系统 统一日志记录 @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("HandlerInterceptor1...afterCompletion"); } }
配置拦截器
<!--拦截器 --> <mvc:interceptors> <!--多个拦截器,顺序执行 --> <!-- <mvc:interceptor> <mvc:mapping path="/**" /> <bean class="cn.itcast.ssm.controller.interceptor.HandlerInterceptor1"></bean> </mvc:interceptor> <mvc:interceptor> <mvc:mapping path="/**" /> <bean class="cn.itcast.ssm.controller.interceptor.HandlerInterceptor2"></bean> </mvc:interceptor> --> <mvc:interceptor> <!-- /**可以拦截路径不管多少层 --> <mvc:mapping path="/**" /> <bean class="cn.itcast.ssm.controller.interceptor.LoginInterceptor"></bean> </mvc:interceptor> </mvc:interceptors>
如果两个拦截器都放行
测试结果: HandlerInterceptor1...preHandle HandlerInterceptor2...preHandle HandlerInterceptor2...postHandle HandlerInterceptor1...postHandle HandlerInterceptor2...afterCompletion HandlerInterceptor1...afterCompletion 总结: 执行preHandle是顺序执行。 执行postHandle、afterCompletion是倒序执行
1 号放行和2号不放行
测试结果: HandlerInterceptor1...preHandle HandlerInterceptor2...preHandle HandlerInterceptor1...afterCompletion 总结: 如果preHandle不放行,postHandle、afterCompletion都不执行。 只要有一个拦截器不放行,controller不能执行完成
1 号不放行和2号不放行
测试结果: HandlerInterceptor1...preHandle 总结: 只有前边的拦截器preHandle方法放行,下边的拦截器的preHandle才执行。
日志拦截器或异常拦截器要求
拦截器拦截
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("HandlerInterceptor1...postHandle"); } //执行handler之后执行此方法 //作系统 统一异常处理,进行方法执行性能监控,在preHandle中设置一个时间点,在afterCompletion设置一个时间,两个时间点的差就是执行时长 //实现 系统 统一日志记录 @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("HandlerInterceptor1...afterCompletion"); } }
Controller
@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"; } }