SpringMVC【校验器、统一处理异常、RESTful、拦截器】

前言

本博文主要讲解的知识点以下:html

  • 校验器
  • 统一处理异常
  • RESTful
  • 拦截器

Validation

在咱们的Struts2中,咱们是继承ActionSupport来实现校验的...它有两种方式来实现校验的功能前端

  • 手写代码
  • XML配置
    • 这两种方式也是能够特定处理方法或者整个Action的

而SpringMVC使用JSR-303(javaEE6规范的一部分)校验规范,springmvc使用的是Hibernate Validator(和Hibernate的ORM无关)java

快速入门

导入jar包web

 

这里写图片描述

 

配置校验器spring

<!-- 校验器 -->
	<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配置文件restful

 

这里写图片描述

 

定义规则session

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());
        }

    }

因为我在测试的时候,已经把日期转换器关掉了,所以提示了字符串不能转换成日期,可是名称的校验已是出来了...

 

这里写图片描述

 

分组校验

分组校验其实就是为了咱们的校验更加灵活,有的时候,咱们并不须要把咱们当前配置的属性都进行校验,而须要的是当前的方法仅仅校验某些的属性。那么此时,咱们就能够用到分组校验了...

步骤:

  • 定义分组的接口【主要是标识】
  • 定于校验规则属于哪一各组
  • 在Controller方法中定义使用校验分组

 

这里写图片描述

 

 

这里写图片描述

 

 

这里写图片描述

 

统一异常处理

在咱们以前SSH,使用Struts2的时候也配置过统一处理异常...

当时候是这么干的:

  • 在service层中自定义异常
  • 在action层也自定义异常
  • 对于Dao层的异常咱们先无论【由于咱们管不着,dao层的异常太致命了】
  • service层抛出异常,Action把service层的异常接住,经过service抛出的异常来判断是否让请求经过
  • 若是不经过,那么接着抛出Action异常
  • 在Struts的配置文件中定义全局视图,页面显示错误信息

详情可看:blog.csdn.net/hon_3y/arti…

那么咱们此次的统一处理异常的方案是什么呢????

咱们知道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>

 

这里写图片描述

 

RESTful支持

咱们在学习webservice的时候可能就听过RESTful这么一个名词,当时候与SOAP进行对比的...那么RESTful到底是什么东东呢???

RESTful(Representational State Transfer)软件开发理念,RESTful对http进行很是好的诠释

若是一个架构支持RESTful,那么就称它为RESTful架构...

如下的文章供咱们了解:

www.ruanyifeng.com/blog/2011/0…

综合上面的解释,咱们总结一下什么是RESTful架构:

  •   (1)每个URI表明一种资源;
  •   (2)客户端和服务器之间,传递这种资源的某种表现层;
  •   (3)客户端经过四个HTTP动词,对服务器端资源进行操做,实现"表现层状态转化"

关于RESTful幂等性的理解:www.oschina.net/translate/p…

简单来讲,若是对象在请求的过程当中会发生变化(以Java为例子,属性被修改了),那么此是非幂等的。屡次重复请求,结果仍是不变的话,那么就是幂等的。

PUT用于幂等请求,所以在更新的时候把全部的属性都写完整,那么屡次请求后,咱们其余属性是不会变的

在上边的文章中,幂等被翻译成“状态统一性”。这就更好地理解了。

其实通常的架构并不能彻底支持RESTful的,所以,只要咱们的系统支持RESTful的某些功能,咱们通常就称做为支持RESTful架构...

url的RESTful实现

非RESTful的http的url:http://localhost:8080/items/editItems.action?id=1&....

RESTful的url是简洁的:http:// localhost:8080/items/editItems/1

更改DispatcherServlet的配置

从上面咱们能够发现,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/**" />

/**就表示无论有多少层,都对其进行解析,/*表明的是当前层的全部资源..

SpringMVC拦截器

在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才执行。

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

  • 将日志拦截器或异常拦截器放在拦截器链儿中第一个位置,且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";
		
	}
	

}

总结

  • 使用Spring的校验方式就是将要校验的属性前边加上注解声明。
  • 在Controller中的方法参数上加上@Validation注解。那么SpringMVC内部就会帮咱们对其进行处理(建立对应的bean,加载配置文件)
  • BindingResult能够拿到咱们校验错误的提示
  • 分组校验就是将让咱们的校验更加灵活:某方法须要校验这个属性,而某方法不用校验该属性。咱们就可使用分组校验了。
  • 对于处理异常,SpringMVC是用一个统一的异常处理器类的。实现了HandlerExceptionResolver接口。
  • 对模块细分多个异常类,都交由咱们的统一异常处理器类进行处理。
  • 对于RESTful规范,咱们可使用SpringMVC简单地支持的。将SpringMVC的拦截.action改为是任意的。同时,若是是静态的资源文件,咱们应该设置不拦截。
  • 对于url上的参数,咱们可使用@PathVariable将url中的{}包起参数和形参进行绑定
  • SpringMVC的拦截器和Struts2的拦截器差很少。不过SpringMVC的拦截器配置起来比Struts2的要简单。
    • 至于他们的拦截器链的调用顺序,和Filter的是没有差异的。

若是文章有错的地方欢迎指正,你们互相交流。习惯在微信看技术文章,想要获取更多的Java资源的同窗,能够关注微信公众号:Java3y

相关文章
相关标签/搜索