Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 313|回复: 0

SpringBoot

[复制链接]
  • TA的每日心情
    奋斗
    2024-4-6 11:05
  • 签到天数: 748 天

    [LV.9]以坛为家II

    2034

    主题

    2092

    帖子

    70万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    705612
    发表于 2021-5-13 12:36:36 | 显示全部楼层 |阅读模式

    前言

    在web应用中,请求处理时,出现异常是非常常见的。所以当应用出现各类异常时,进行异常的捕获或者二次处理(比如sql异常正常是不能外抛)是非常必要的,比如在开发对外api服务时,约定了响应的参数格式,如respCoderespMsg,调用方根据错误码进行自己的业务逻辑。本章节就重点讲解下统一异常和数据校验处理。

    springboot中,默认在发送异常时,会跳转值/error请求进行错误的展现,根据不同的Content-Type展现不同的错误结果,如json请求时,直接返回json格式参数。
    浏览器访问异常时:

    浏览器

    使用postman访问时:

    postman

    统一异常处理

    显然,默认的异常页是对用户或者调用者而言都是不友好的,所以一般上我们都会进行实现自己业务的异常提示信息。

    创建全局的统一异常处理类

    利用@ControllerAdvice@ExceptionHandler定义一个统一异常处理类

    • @ControllerAdvice:控制器增强,使@ExceptionHandler、@InitBinder、@ModelAttribute注解的方法应用到所有的 @RequestMapping注解的方法。

    • @ExceptionHandler:异常处理器,此注解的作用是当出现其定义的异常时进行处理的方法

    创建异常类:CommonExceptionHandler

    @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格式

    说明异常已经被拦截了。
    可拦截不同的异常,进行不同的异常提示,比如NoHandlerFoundExceptionHttpMediaTypeNotSupportedExceptionAsyncRequestTimeoutException等等,这里就不列举了,读者可自己加入后实际操作下。

    对于返回页面时,返回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;
        }
    

    由于工作中都是才有前后端分离开发模式,所以一般上都没有直接返回资源页的需求了,一般上都是返回固定的响应格式,如respCoderespMsgdata,前端通过判断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系列教程,如有雷同,请多多包涵了。本文是作者在电脑前一字一句敲的,每一步都是实践的。若文中有所错误之处,还望提出,谢谢。

    老生常谈

    • 个人QQ:499452441
    • 微信公众号:lqdevOps

    公众号

    个人博客:https://blog.lqdev.cn

    完整示例:chapter-8

    原文地址:http://blog.lqdev.cn/2018/07/20/springboot/chapter-eight/

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-5-20 09:36 , Processed in 0.063405 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表