请求参数校验是我们编程经常需要做的事情,但是每次都用if语句去判空会使代码显得很难看,有的小伙伴就使用javax.validation中的注解去校验参数,这样会使代码变得优雅。但是遇到一个问题就是每个接口中去校验参数又会出现很多的代码重复,能不能用一个好点的方法来避免这种重复的代码呢?本文给出了一种比较简洁,并且适用于spring框架的解决方案。
本文只简单描述实现思路及编码,具体原理请参见Spring官方文档。
思路:
① javax.validation工具包+@Valid注解可以帮我们校验参数,对于不符合预期的参数会抛出MethodArgumentNotValidException异常。( javax.validation使用方法可以参见:https://www.cnblogs.com/xiaogangfan/p/5987659.html)
② 使用 springboot 全局异常-ExceptionHandler来处理由①抛出的异常并封装成视图返回。若项目不是使用springboot框架页可以实现spring的HandlerExceptionResolver接口来实现对Exception的抓取处理。
源码:
pom文件:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.juneyaoair.qinweipeng</groupId>
<artifactId>springboottest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>springboottest</name>
<url>http://maven.apache.org</url>
<!-- springboot项目的parent -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.4.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!-- springboot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 校验所需API -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<!-- hibernate对validation的实现 ,网上有人说javax.validation无法生效引入此依赖可以解决,但是本人亲测的时候不引此依赖也没有问题-->
<!--<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.2.1.Final</version>
</dependency>-->
<!-- 自动生成getter、setter、constructor等方法 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.12</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
App: springboot 项目启动main class
package com.juneyaoair.qinweipeng.springboottest;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
GlobleExceptionResolver:全局异常处理类
package com.juneyaoair.qinweipeng.springboottest.exception;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @ClassName GlobleExceptionResolver
* @Description
* @Author qinweipeng
* @Date 2019/4/3 15:44
**/
@ControllerAdvice
public class GlobalExceptionResolver implements HandlerExceptionResolver {
private Log logger = LogFactory.getLog(GlobalExceptionResolver.class);
@ResponseBody
@ExceptionHandler(value = Exception.class)
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
logger.error("系统发生异常",ex);
ModelAndView mv = new ModelAndView();
MappingJackson2JsonView view = new MappingJackson2JsonView();
//保存错误提示
StringBuilder errorMessage = new StringBuilder();
//如果是校验报出的异常需要从异常中取出参数校验不通过的信息
if (ex instanceof MethodArgumentNotValidException) {
BindingResult bindingResult = ((MethodArgumentNotValidException) ex).getBindingResult();
List<FieldError> errors = bindingResult.getFieldErrors();
errors.forEach(error ->
errorMessage.append(error.getDefaultMessage()).append(";")
);
} else {
//其他异常直接取异常信息,此处实际需要另外处理系统预期以外的异常,不在本文讨论范围内,请自行更改
errorMessage.append(ex.getMessage());
}
Map<String, Object> attributes = new HashMap<>();
attributes.put("errorMessage", errorMessage);
view.setAttributesMap(attributes);
mv.setView(view);
return mv;
}
}
代码非常简单,以上这些代码就能解决我们的需求了。然后我们来写一下测试接口验证一下功能。
测试请求参数MyValidatableRequest:
package com.juneyaoair.qinweipeng.springboottest.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
/**
* @ClassName MyValidatableRequest
* @Description
* @Author qinweipeng
* @Date 2019/4/3 13:25
**/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MyValidatableRequest {
@NotNull(message = "Id不能为空")
private String id;
@Min(value = 0, message = "number必须大于0")
@Max(value = 9, message = "number必须小于9")
private int number = -1;
}
测试controller:HelloWorldController:
package com.juneyaoair.qinweipeng.springboottest.controller;
import com.juneyaoair.qinweipeng.springboottest.bean.MyValidatableRequest;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
@RestController
@RequestMapping("/springBoot")
public class HelloWorldController {
@RequestMapping(value = "/validate/validateRequest", method = RequestMethod.POST, produces = {"application/json;charset=UTF-8"})
public String validateRequest(@Valid @RequestBody MyValidatableRequest validatableRequest, HttpServletRequest request){
return validatableRequest.toString();
}
}
开始测试:
使用postman调用接口:
后台日志:
2019-04-03 18:06:20.418 ERROR 3116 --- [nio-8080-exec-8] c.j.q.s.e.GlobalExceptionResolver : 系统发生异常
org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument at index 0 in method: public java.lang.String com.juneyaoair.qinweipeng.springboottest.controller.HelloWorldController.validateRequest(com.juneyaoair.qinweipeng.springboottest.bean.MyValidatableRequest,javax.servlet.http.HttpServletRequest), with 2 error(s): [Field error in object 'myValidatableRequest' on field 'id': rejected value [null]; codes [NotNull.myValidatableRequest.id,NotNull.id,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [myValidatableRequest.id,id]; arguments []; default message [id]]; default message [Id不能为空]] [Field error in object 'myValidatableRequest' on field 'number': rejected value [-1]; codes [Min.myValidatableRequest.number,Min.number,Min.int,Min]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [myValidatableRequest.number,number]; arguments []; default message [number],0]; default message [number必须大于0]]
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:106)
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:77)
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:162)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:129)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:776)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:705)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:959)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:966)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:868)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:648)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:842)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:85)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:518)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1091)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:668)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1521)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1478)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
有什么做的不对或者做的不好的地方欢迎指正。