在web应用中,请求处理时,出现异常是很是常见的。因此当应用出现各种异常时,进行异常的捕获或者二次处理(好比sql异常正常是不能外抛)是很是必要的,好比在开发对外api服务时,约定了响应的参数格式,如
respCode
、respMsg
,调用方根据错误码进行本身的业务逻辑。本章节就重点讲解下统一异常和数据校验处理。前端
springboot
中,默认在发送异常时,会跳转值/error
请求进行错误的展示,根据不一样的Content-Type
展示不一样的错误结果,如json请求时,直接返回json格式参数。 浏览器访问异常时:java
使用postman
访问时:git
显然,默认的异常页是对用户或者调用者而言都是不友好的,因此通常上咱们都会进行实现本身业务的异常提示信息。github
利用
@ControllerAdvice
和@ExceptionHandler
定义一个统一异常处理类web
@ControllerAdvice:控制器加强,使@ExceptionHandler、@InitBinder、@ModelAttribute注解的方法应用到全部的 @RequestMapping注解的方法。正则表达式
@ExceptionHandler:异常处理器,此注解的做用是当出现其定义的异常时进行处理的方法spring
建立异常类:CommonExceptionHandlersql
@ControllerAdvice public class CommonExceptionHandler { /** * 拦截Exception类的异常 * @param e * @return */ @ExceptionHandler(Exception.class) @ResponseBody public Map<String,Object> exceptionHandler(Exception e){ Map<String,Object> result = new HashMap<String,Object>(); result.put("respCode", "9999"); result.put("respMsg", e.getMessage()); //正常开发中,可建立一个统一响应实体,如CommonResp return result; } }
多余不一样异常(如自定义异常),须要进行不一样的异常处理时,可编写多个exceptionHandler方法,注解ExceptionHandler
指定处理的异常类,如编程
/** * 拦截 CommonException 的异常 * @param ex * @return */ @ExceptionHandler(CommonException.class) @ResponseBody public Map<String,Object> exceptionHandler(CommonException ex){ log.info("CommonException:{}({})",ex.getMsg(), ex.getCode()); Map<String,Object> result = new HashMap<String,Object>(); result.put("respCode", ex.getCode()); result.put("respMsg", ex.getMsg()); return result; }
因为加入了@ResponseBody
,因此返回的是json
格式,json
说明异常已经被拦截了。 可拦截不一样的异常,进行不一样的异常提示,好比NoHandlerFoundException
、HttpMediaTypeNotSupportedException
、AsyncRequestTimeoutException
等等,这里就不列举了,读者可本身加入后实际操做下。
对于返回页面时,返回ModelAndView
便可,如
@ExceptionHandler(value = Exception.class) public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception { ModelAndView mav = new ModelAndView(); mav.addObject("exception", e); mav.addObject("url", req.getRequestURL()); mav.setViewName(DEFAULT_ERROR_VIEW); return mav; }
因为工做中都是才有先后端分离开发模式,因此通常上都没有直接返回资源页的需求了,通常上都是返回固定的响应格式,如respCode
、respMsg
、data
,前端经过判断respCode
的值进行业务判断,是弹窗仍是跳转页面。
在web开发时,对于请求参数,通常上都须要进行参数合法性校验的,原先的写法时一个个字段一个个去判断,这种方式太不通用了,因此java的
JSR 303: Bean Validation
规范就是解决这个问题的。
JSR 303
只是个规范,并无具体的实现,目前一般都是才有hibernate-validator
进行统一参数校验。
JSR303定义的校验类型
Constraint | 详细信息 |
---|---|
@Null |
被注释的元素必须为 null |
@NotNull |
被注释的元素必须不为 null |
@AssertTrue |
被注释的元素必须为 true |
@AssertFalse |
被注释的元素必须为 false |
@Min(value) |
被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@Max(value) |
被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@DecimalMin(value) |
被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@DecimalMax(value) |
被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@Size(max, min) |
被注释的元素的大小必须在指定的范围内 |
@Digits (integer, fraction) |
被注释的元素必须是一个数字,其值必须在可接受的范围内 |
@Past |
被注释的元素必须是一个过去的日期 |
@Future |
被注释的元素必须是一个未来的日期 |
@Pattern(value) |
被注释的元素必须符合指定的正则表达式 |
Hibernate Validator 附加的 constraint
Constraint | 详细信息 |
---|---|
@Email |
被注释的元素必须是电子邮箱地址 |
@Length |
被注释的字符串的大小必须在指定的范围内 |
@NotEmpty |
被注释的字符串的必须非空 |
@Range |
被注释的元素必须在合适的范围内 |
建立实体类
@Data @NoArgsConstructor @AllArgsConstructor public class DemoReq { @NotBlank(message="code不能为空") String code; @Length(max=10,message="name长度不能超过10") String name; }
而后在控制层方法里,加入@Valid
便可,这样在访问前,会对请求参数进行检验。
@GetMapping("/demo/valid") public String demoValid(@Valid DemoReq req) { return req.getCode() + "," + req.getName(); }
启动,后访问http://127.0.0.1:8080/demo/valid
加上正确参数后,http://127.0.0.1:8080/demo/valid?code=3&name=s
这样数据的统一校验就完成了,对于其余注解的使用,你们可自行谷歌下,基本上都很简单的,对于已有的注解没法知足校验须要时,也可进行自定义注解的开发,一下简单讲解下,自定义注解的编写
不使用@valid的状况下,也可利用编程的方式编写一个工具类,进行实体参数校验
public class ValidatorUtil { private static Validator validator = ((HibernateValidatorConfiguration) Validation .byProvider(HibernateValidator.class).configure()).failFast(true).buildValidatorFactory().getValidator(); /** * 实体校验 * * @param obj * @throws CommonException */ public static <T> void validate(T obj) throws CommonException { Set<ConstraintViolation<T>> constraintViolations = validator.validate(obj, new Class[0]); if (constraintViolations.size() > 0) { ConstraintViolation<T> validateInfo = (ConstraintViolation<T>) constraintViolations.iterator().next(); // validateInfo.getMessage() 校验不经过时的信息,即message对应的值 throw new CommonException("0001", validateInfo.getMessage()); } } }
使用
@GetMapping("/demo/valid") public String demoValid(@Valid DemoReq req) { //手动校验 ValidatorUtil.validate(req); return req.getCode() + "," + req.getName(); }
自定义注解,主要时实现
ConstraintValidator
的处理类便可,这里已编写一个校验常量的注解为例:参数值只能为特定的值。
自定义注解
@Documented //指定注解的处理类 @Constraint(validatedBy = {ConstantValidatorHandler.class }) @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) @Retention(RUNTIME) public @interface Constant { String message() default "{constraint.default.const.message}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; String value(); }
注解处理类
public class ConstantValidatorHandler implements ConstraintValidator<Constant, String> { private String constant; @Override public void initialize(Constant constraintAnnotation) { //获取设置的字段值 this.constant = constraintAnnotation.value(); } @Override public boolean isValid(String value, ConstraintValidatorContext context) { //判断参数是否等于设置的字段值,返回结果 return constant.equals(value); } }
使用
@Constant(message = "verson只能为1.0",value="1.0") String version;
运行:
此时,自定义注解已生效。你们可根据实际需求进行开发。
你们看到在校验不经过时,返回的异常信息是不友好的,此时可利用统一异常处理,对校验异常进行特殊处理,特别说明下,对于异常处理类 共有如下几种状况(被@RequestBody和@RequestParam注解的请求实体,校验异常类是不一样的)
@ExceptionHandler(MethodArgumentNotValidException.class) public Map<String,Object> handleBindException(MethodArgumentNotValidException ex) { FieldError fieldError = ex.getBindingResult().getFieldError(); log.info("参数校验异常:{}({})", fieldError.getDefaultMessage(),fieldError.getField()); Map<String,Object> result = new HashMap<String,Object>(); result.put("respCode", "01002"); result.put("respMsg", fieldError.getDefaultMessage()); return result; } @ExceptionHandler(BindException.class) public Map<String,Object> handleBindException(BindException ex) { //校验 除了 requestbody 注解方式的参数校验 对应的 bindingresult 为 BeanPropertyBindingResult FieldError fieldError = ex.getBindingResult().getFieldError(); log.info("必填校验异常:{}({})", fieldError.getDefaultMessage(),fieldError.getField()); Map<String,Object> result = new HashMap<String,Object>(); result.put("respCode", "01002"); result.put("respMsg", fieldError.getDefaultMessage()); return result; }
启动后,提示就友好了
因此统一异常仍是颇有必要的。
本章节主要是阐述了统一异常处理和数据的合法性校验,同时简单实现了一个自定义的注解类,你们在遇见已有注解没法解决时,可经过自定义的形式进行,固然对于通用而已,利用
@Pattern(正则表达式)
基本上都是能够实现的。
目前互联网上不少大佬都有
SpringBoot
系列教程,若有雷同,请多多包涵了。本文是做者在电脑前一字一句敲的,每一步都是实践的。若文中有所错误之处,还望提出,谢谢。
499452441
lqdevOps
完整示例:chapter-8
原文地址:http://blog.lqdev.cn/2018/07/20/springboot/chapter-eight/