在软件开发中,通常需要对参数进行校验,比如某些参数不能为空,而且不光前端要校验,后端也要校验。那么后端在哪里校验参数?是在控制层还是service层?参数校验有多种实现方式,采用哪一种校验方式呢? 一般来说,把无业务语义的校验放在action层,用validation做校验,比如数据类型,是否为空,数据长度、格式等,有业务语义的校验放在service层。当然实际中往往并没有那么清晰,当校验足够简单时,可能只在前端校验就行了,或者简单的在service层校验一下,而当校验较复杂时,则可能需要多层校验。
Spring3支持JSR-303验证框架,JSR-303是JAVA EE6中的一项子规范,叫做BeanValidation,官方参考实现是hibernate Validator(与Hibernate ORM没有关系),主要用于对Java Bean中的字段值就行校验。
通过BeanValidation配合拦截器可以实现对参数的校验。如在Java Bean中属性上添加@NotNull或@NotBlank注解,表示该属性不能为空串或者null。然后在rest接口上添加注解:@BeanValid。接着实现一个拦截器,在拦截器的preHandle方法中,获取注解对应的方法及参数,进行validatation。
当然也可以不用拦截器,而直接用aop切面,在注解的方法前面进行切入,通过BeanValidation进行校验。实际上拦截器也是利用的切面原理实现的。
这里先以aop校验方式为例,直接看代码。
自定义BeanValid注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface BeanValid {
/* 是否必须校验 */
boolean required() default true;
}
创建aop切面(注解式声明):
@Component
@Aspect
public class BeanAspect {
@Before("@annotation({包名}.BeanValid)")
public void checkBeanValid(JoinPoint joinPoint) throws Exception{
List
相关类:
public class DataValidationUtil {
private static final Logger LOG = LoggerFactory.getLogger(DataValidationUtil.class);
private static ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
private static Validator validator = factory.getValidator();
public static void validate(T bean){
StringBuffer sb = new StringBuffer();
try {
if (null == bean) {
sb.append("校验的对象不能为null");
} else {
Set> constraintViolations = validator.validate(bean);
for (ConstraintViolation violation : constraintViolations) {
sb.append(violation.getPropertyPath()).append(":").append(violation.getMessage()).append(" ,");
}
}
} catch (RuntimeException e) {
LOG.error("数据校验发生异常", e);
sb.append("数据校验发生异常,请检查字段注解配置是否合法");
}
if(StringUtils.isNotEmpty(sb.toString())){
LOG.info("校验异常入参:{}", JsonUtil.toString(bean));
throw new ValidException(sb.toString());
}
}
}
public class ValidException extends RuntimeException {
private static final long serialVersionUID = 1L;
public ValidException(String message){
super(message);
}
public ValidException() {
super();
}
public ValidException(String message, Throwable cause) {
super(message, cause);
}
public ValidException(Throwable cause) {
super(cause);
}
}
其中BusinessException为自定义业务异常,JsonUtil为自定义json转换工具,ErrorCodeDefinition为自定义错误码枚举类。
前面说到可以以拦截器的方式实现参数校验。具体实现方式是:定义一个拦截器,需实现HandlerInterceptor接口。拦截器接口中有三个方法,preHandle,postHandle和afterCompletion。其中preHandle方法是在控制器rest接口之前被调用,该方法的第三个参数handle为接口方法句柄,通过接口方法句柄可以获得接口方法相关的信息,以此实现对参数的校验。
说到拦截器,不得不说一下过滤器,这两个概念很容易混淆。过滤器是在Servlet规范中声明的,是Servlet容器支持的,一般在web.xml中进行配置,关键字是filter。而拦截器是spring的组件,配置在Spring文件中,能使用Spring里的资源。如果使用springMVC拦截器,则声明方式如下:
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean id="customInterceptor" class="xx.framework.interceptor.FrameworkInterceptor">
bean>
mvc:interceptor>
mvc:interceptors>
过滤器是在servlet容器启动后,请求到达springMVC之前进行过滤,一般用于登录权限验证、资源访问权限控制、敏感词汇过滤,字符转换编码等操作。自定义过滤器要实现Filter接口并重写doFilter方法。过滤器在web.xml中进行配置,配置顺序和执行顺序是一致的。
拦截器是在请求到达springMVC的前端控制器DispatcherServlet之后,映射到具体的rest接口处理方法之前起作用的。
下面将以一个完整的例子来说明如何使用拦截器:
先注册一个拦截器:
/*
* 1.interceptors根目录下的拦截器对所有请求都起作用
* 2.此处注册的实际上是一个拦截器链,其中只有一个子拦截器
*/
<mvc:interceptors>
<bean id="customInterceptor" class="cn.tonghao.framework.interceptor.FrameworkInterceptor">
<property name="actions">
<list>
<bean class="cn.tonghao.framework.interceptor.validation.PropertyAnalyticsInterceptor"/>
list>
property>
bean>
mvc:interceptors>
下面是拦截器类:
public class FrameworkInterceptor implements HandlerInterceptor{
/**
* 处理动作集
*/
private List actions;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handle) throws Exception {
String requestUrl = request.getRequestURI().toString();
if (handle instanceof HandlerMethod){
HandlerMethod handlerMethod = (HandlerMethod) handle;
//获得方法返回类型
Class> methodReturnType = handlerMethod.getMethod().getReturnType();
if (methodReturnType == null || methodReturnType.equals(String.class)) {
return true;
}
//构建调用上下文
InvokeContext context = new InvokeContext(request,response,handlerMethod);
context.setJsonParameter(getJsonParameter(request));
//将子拦截器(通过bean注入到actions属性中)设置到上下文调用对象中
context.setActions(actions);
context.setRequestUrl(requestUrl);
//开始调用子拦截器
context.startInvoke();
return context.getExecuteResult();
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object o, Exception e) throws Exception {
}
/**
* 获取HttpServletRequest参数体
* @param request
* @return
*/
private String getJsonParameter(HttpServletRequest request) throws IOException{
String method = request.getMethod();
if (method.equals("GET") || method.equals("DELETE")){
return request.getQueryString();
}
StringBuilder buffer = new StringBuilder();
String line;
BufferedReader reader = request.getReader();
while ((line = reader.readLine()) != null){
buffer.append(line);
}
return buffer.toString();
}
public List getActions() {
return actions;
}
public void setActions(List actions) {
this.actions = actions;
}
}
下面是调用上下文实现:
public class InvokeContext {
private static Logger logger = LoggerFactory.getLogger(InvokeContext.class);
/**
* 请求
*/
private HttpServletRequest request;
/**
* 响应
*/
private HttpServletResponse response;
/**
* 方法句柄
*/
private HandlerMethod handlerMethod;
/**
* 任务执行下标
*/
private int index;
/**
* 任务链
*/
private List actions;
/**
* 请求参数
*/
private String JsonParameter;
private String requestUrl;
/**
* 任务执行结果
*/
private List executeResult = new ArrayList();
public InvokeContext(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) {
super();
this.request = request;
this.response = response;
this.handlerMethod = handlerMethod;
}
/**
* 启动任务链
*/
public void startInvoke(){
reset();
invokeNext();
}
/**
* 重置链
*/
public void reset() {
this.index = 0;
}
/**
* 获取各个链的执行结果,不能有错误
*/
public synchronized boolean getExecuteResult() {
for (boolean bool : executeResult) {
if (!bool) {
return bool;
}
}
return true;
}
public synchronized void invokeNext() {
if (actions != null && !actions.isEmpty() && getExecuteResult()) {
try {
if (index <= actions.size() - 1) {
FilterAction action = actions.get(index);
index++;
action.invoke(this);
executeResult.add(Boolean.TRUE);
}
}catch (Exception e) {
logger.error("执行interceptor时出错");
executeResult.add(Boolean.FALSE);
}
}
}
public HttpServletRequest getRequest() {
return request;
}
public void setRequest(HttpServletRequest request) {
this.request = request;
}
public HttpServletResponse getResponse() {
return response;
}
public void setResponse(HttpServletResponse response) {
this.response = response;
}
public HandlerMethod getHandlerMethod() {
return handlerMethod;
}
public void setHandlerMethod(HandlerMethod handlerMethod) {
this.handlerMethod = handlerMethod;
}
public String getJsonParameter() {
return JsonParameter;
}
public void setJsonParameter(String jsonParameter) {
JsonParameter = jsonParameter;
}
public String getRequestUrl() {
return requestUrl;
}
public void setRequestUrl(String requestUrl) {
this.requestUrl = requestUrl;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public List getActions() {
return actions;
}
public void setActions(List actions) {
this.actions = actions;
}
public void setExecuteResult(List executeResult) {
this.executeResult = executeResult;
}
}
调用上下文中通过action.invoke(this)来调用子拦截器,子拦截器实现如下:
public class PropertyAnalyticsInterceptor implements FilterAction{
private static final Logger logger = LoggerFactory.getLogger(Logger.class);
@Resource
private Validator validator;
/**
* Jackson对象映射器
*/
private ObjectMapper objectMapper;
{
//初始化
objectMapper = new ObjectMapper();
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
}
@Override
public void invoke(InvokeContext invokeContext) throws Exception {
//解析请求
String jsonData = invokeContext.getJsonParameter();
//参数转化及根据JSR303规范进行属性验证
BeanValid beanValid = invokeContext.getHandlerMethod().getMethodAnnotation(BeanValid.class);
try{
if (StringUtils.isNoneBlank(jsonData) && !"{}".equals(jsonData)) {
if (beanValid != null) {
//获取方法参数
MethodParameter[] methodParameters = invokeContext.getHandlerMethod().getMethodParameters();
for (MethodParameter methodParameter : methodParameters){
if (!methodParameter.hasParameterAnnotation(RequestBody.class)) {
//如果参数上没有请求参数绑定注解,则跳过当前参数
continue;
}
//过滤掉不需要校验的参数
if (filterParameter(methodParameter)) {
continue;
}
Object validObject = resolveArgument(jsonData, invokeContext.getRequest(), methodParameter);
validateObject(validObject);
}
}
}else{
ObjectNotNull notNullAnno = null;
MethodParameter[] methodParameters = invokeContext.getHandlerMethod().getMethodParameters();
boolean forceRequired = true;
for (MethodParameter methodParameter : methodParameters) {
notNullAnno = methodParameter.getParameterType().getAnnotation(ObjectNotNull.class);
if (notNullAnno != null) {
forceRequired = beanValid.required();
if (forceRequired) {
// 异常封装,参数基础验证失败
throw new BusinessException(ErrorDefinition.INVALID_PARAMETER, "参数不合法,参数不能为空");
}
}
}
}
}catch (Exception e) {
HttpServletRequest request = invokeContext.getRequest();
String requestUri = request.getRequestURI();
logger.error("property analytics error,request url:{}, data:{}, e:{}", requestUri, jsonData, e.getMessage());
throw e;
}
invokeContext.invokeNext();
}
/**
* 参数解析
* @param jsonData json格式的参数
* @param request 请求
* @param parameter 方法参数描述
* @return 参数对象
*/
private Object resolveArgument(String jsonData, HttpServletRequest request, MethodParameter parameter)
throws IOException{
try{
JsonNode node = objectMapper.readTree(jsonData);
String path = parameter.getParameterName();
if (node.has(path)) {
return objectMapper.readValue(node.path(path).traverse(),parameter.getParameterType());
}else{
return objectMapper.readValue(jsonData,parameter.getParameterType());
}
}catch (Exception e) {
throw new BusinessException(ErrorDefinition.INVALID_PARAMETER,"解析json对象出错:"+e.getMessage());
}
}
/**
* 参数校验过滤器
*/
private boolean filterParameter(MethodParameter methodParameter) {
boolean filtered = false;
if (methodParameter != null && (methodParameter.getParameterAnnotation(RequestBody.class) == null)
&& (methodParameter.getParameterAnnotation(PathVariable.class) != null)) {
filtered = true;
}
return filtered;
}
private void validateObject(Object validObject) {
Validate.notNull(validObject);
Set> violations = validator.validate(validObject);
if (!violations.isEmpty()) {
StringBuilder buffer = new StringBuilder();
for (ConstraintViolation
相关类及变量说明:
FilterAction接口
public interface FilterAction {
/**
* 任务调用
*
* @param invokeContext 调用上下文
* @throws Exception 任务执行异常
*/
void invoke(InvokeContext invokeContext) throws Exception;
}
验证bean:validator在springMVC配置文件中配置(注意:spring容器可以调用springMVC子容器中的资源,但反之不行)
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
其他相关类BusinessException为自定义业务异常,JsonUtil为自定义json转换工具,ErrorCodeDefinition为自定义错误码枚举类。
两种参数验证实现方式的比较:
aop方式较为简洁;拦截器方式实际上展示了一个拦截器系统的实现方式,通常情况下拦截器是以拦截器链的方式出现的。