【知识点】你真的了解断言吗?

一、前言

之前在使用gateway时,经常涉及到yml的配置,其中有一项和请求路径匹配相关的内容叫做断言(Predicate),在看到这个词时,对断言的概念充满疑惑,为什么叫做断言,之前一直没有找到合适时间整理,正好今天又在异常处理中看到了"断言"这个词,所以借着这个实际,简单的把断言相关内容整理,也补充一下自己的知识点;

:本文主要对工作中可能涉及到断言的技术点对它做一个简单的总结,主要涉及java中的assertjdk1.8的Predicategateway中的断言和全局异常处理中用到的断言;

二、断言应用

1. java断言(assert)

概念:Java 是从 JDK1.4 开始支持断言的,主要用于程序代码的调试或测试阶段,千万不能用在正式环境上,JVM默认关闭断言的,想要开启断言得向 JVM 新增一个参数-enableassertions才可以启用断言;

断言(Assertion)是一种程序调用方式:

  • 使用assert关键
  • 断言条件预期为true
  • 如果断言失败,抛出AssertionError异常

断言的特点:

  • 断言失败时会抛出AssertionError,导致程序结束退出
  • 不能用于可恢复的程序错误
  • 只应该用于开发和测试阶段

开启断言JVM配置:

  • 可以指定特定的启动断言,-ea:com.sk.NettyTestApplication
  • 可以指定特定的启动断言,-ea:com.sk

在这里插入图片描述
assert有两种语法,一种是直接抛出一个错误,另一种是可以抛出一个错误附带我们写的一个字符串作为提示。

1)assert condition

示例代码:

package com.sk;

import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class NettyTestApplication {
    public static void main(String[] args) {
        int a = 0;
        assert a > 10;
    }

}

执行结果:

Exception in thread "main" java.lang.AssertionError
	at com.sk.NettyTestApplication.main(NettyTestApplication.java:12)

2)assert condition : msg

示例代码

package com.sk;

import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class NettyTestApplication {
    public static void main(String[] args) {
        int a = 0;
        assert a > 10:"断言测试";
    }
}

执行结果:

Exception in thread "main" java.lang.AssertionError: 断言测试
	at com.sk.NettyTestApplication.main(NettyTestApplication.java:12)

2. jdk1.8的Predicate函数式接口

概念Predicate 是“断言”的意思。就是给你一段描述,判断这段描述是正确的还是错误的。这就像我们考试中做的判断题一样。

Predicate是一个函数式接口,它可以接受一个泛型 参数,返回值为布尔类型,Predicate 常用于数据过滤,如过滤出集合中符合某个条件的元素。

Java 8 中函数接口 Predicate源码如下:

package java.util.function;

import java.util.Objects;

@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }
  
    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

Stream 中的 filter() 方法是通过接收一个 Predicate 函数接口实现的;

/**
 * Returns a stream consisting of the elements of this stream that match
 * the given predicate.
 *
 * <p>This is an <a href="package-summary.html#StreamOps">intermediate
 * operation</a>.
 *
 * @param predicate a non-interfering stateless predicate to apply to each element to determine if it
 * should be included in the new returned stream.
 * @return the new stream
 */
Stream<T> filter(Predicate<? super T> predicate);

stream中的使用

List<String> list = new ArrayList<>();
        list.stream().filter(new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return false;
            }
        });

同时也可以转为Lambda 表达式

List<String> list = new ArrayList<>();
        list.stream().filter(s -> false);

:在使用stream中的方法时,为了方便理解,建议先创建函数接口,再转Lambda表达式

关于Predicate 函数接口更多介绍大家可以参考:Java 8 Predicate 函数接口

3. gateway中的断言Predicate

简介Predicate(断言, 谓词) 用于进行条件判断,只有断言都返回真,才会真正的执行路由。 断言就是说: 在什么条件下才能进行路由转发;

首先说一下Gateway三大概念

  • Route(路由):路由是构建网关的基本模块,它由 ID、目标 URI、一系列的断言和过滤器组成,如果断言为 true 则匹配该路由
  • Predicate(断言):参考的是 Java8 中的 java.util.function.Predicate。开发人员可以匹配
    HTTP 请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由
  • Filter(过滤):指的是 Spring 框架中 GatewayFilter
    的实例,使用过滤器,可以在请求被路由之前或之后对请求进行修改

为了方便使用SpringCloud Gateway包括许多内置的断言工厂,所有这些断言都与HTTP请求的不同属性匹配;

例如:
基于Host的断言工厂
HostRoutePredicateFactory:接收一个参数,主机名模式。判断请求的Host是否满足匹配规则。

-Host=**.testhost.org

关于更多的内在断言工厂的内容,大家可以参考:说说Gateway中的断言

在yml中配置示例:

gateway:
    discovery:
        locator:
            enabled: true
    routes:
        - id: product-route
        uri: lb://service-product
        predicates:
            - Path=/product-serv/**
            - Age=18,60 # 限制年龄只有在18到60岁之间的人能访问
        filters:
            - StripPrefix=1

4. 断言用于全局异常处理

在程序开发中我们经常使用try{...}catch(){...}捕获异常,但是如果过多的使用try{...}catch(){...}不仅有大量的冗余代码,而且还影响代码的可读性,所以为了优化异常处理,我们使用@ControllerAdvice实现全局的异常处理

关于全局异常处理的内容网上有很多,这里不再过多介绍,可以参考:SpringBoot 全局异常拦截器

为了进一步简化代码,我们使用异常处理类+断言

代码示例:

1)统一异常处理类:

/**
  * 统一异常处理
  */
 @Slf4j
 @ControllerAdvice
 public class GlobalExceptionHandler {
 
     @ExceptionHandler(value = AssertException.class)
     @ResponseBody
     public ResponseBean bootExceptionHandler(AssertException e) {
         ApiCode apiCode = ApiCode.getObjectByValue(e.getCode());
         log.error("business error : {}", e.getMessage(), e);
         if (e.getCode() == -1) {
             return ResponseBean.error(ApiCode.SERVICE_ERROR.getValue(), ApiCode.SERVICE_ERROR.getMessage());
         }
         return ResponseBean.error(apiCode.getValue(), e.getMessage());
     }
 
     @ExceptionHandler(value = com.alibaba.fastjson.JSONException.class)
     public ResponseBean alibabaJsonExceptionHandler(com.alibaba.fastjson.JSONException e) {
         ResponseBean response = new ResponseBean(false, ApiCode.PARAM_FORMAT_INCORR.getValue(), ApiCode.PARAM_FORMAT_INCORR.getMessage() + e.getMessage(), null);
         log.error("1102", e);
         return response;
     }
 
     @ExceptionHandler(value = JSONException.class)
     @ResponseBody
     public ResponseBean jsonExceptionHandler(JSONException e) {
         ResponseBean response = new ResponseBean(false, ApiCode.PARAM_FORMAT_INCORR.getValue(), ApiCode.PARAM_FORMAT_INCORR.getMessage() + e.getMessage(), null);
         log.error(ApiCode.PARAM_FORMAT_INCORR.getValue() + "", e);
         return response;
     }
 
     @ExceptionHandler(value = JsonParseException.class)
     @ResponseBody
     public ResponseBean jsonParseExceptionHandler(JsonParseException e) {
         ResponseBean response = new ResponseBean(false, ApiCode.PARAM_FORMAT_INCORR.getValue(), String.format(ApiCode.PARAM_FORMAT_INCORR.getMessage() + ":%s", e.getMessage()), null);
         log.error(ApiCode.PARAM_FORMAT_INCORR.getValue() + "", e);
         return response;
     }
 
     @ExceptionHandler(value = Exception.class)
     @ResponseBody
     public ResponseBean exceptionHandler(Exception e) {
         ResponseBean response = new ResponseBean(false, ApiCode.SERVICE_ERROR.getValue(), ApiCode.SERVICE_ERROR.getMessage(), null);
         log.error(ApiCode.SERVICE_ERROR.getValue() + "", e);
         return response;
     }
 
     @ExceptionHandler(value = MethodArgumentTypeMismatchException.class)
     @ResponseBody
     public ResponseBean exceptionHandle(MethodArgumentTypeMismatchException e) {
         ResponseBean response = new ResponseBean(false, ApiCode.PARAM_FORMAT_INCORR.getValue(), String.format(ApiCode.PARAM_FORMAT_INCORR.getMessage() + ":%s", e.getMessage()), null);
         log.error(ApiCode.PARAM_FORMAT_INCORR.getValue() + "", e);
         return response;
     }
 
     @ExceptionHandler(value = WebException.class)
     @ResponseBody
     public ResponseBean exceptionHandler(WebException e) {
         ResponseBean response = new ResponseBean(e.getIsSuccess(), e.getResponseCode(), e.getResponseMsg(), null);
         log.error(e.getResponseCode() + "", e);
         return response;
     }
 
     @ExceptionHandler(value = IllegalArgumentException.class)
     @ResponseBody
     public ResponseBean exceptionHandler(IllegalArgumentException e) {
         log.error("illegal request : {}", e.getMessage(), e);
         return ResponseBean.error(ApiCode.PARAM_INVALID.getValue(), ApiCode.PARAM_INVALID.getMessage());
     }
 
     @ExceptionHandler(value = ServiceInvokeException.class)
     @ResponseBody
     public ResponseBean exceptionHandler(ServiceInvokeException e) {
         log.error("serviceInvoke error request : {}", e.getMessage(), e);
         return ResponseBean.error(ApiCode.SERVICE_ERROR.getValue(), ApiCode.SERVICE_ERROR.getMessage());
     }
 
     @ExceptionHandler(value = BusinessException.class)
     @ResponseBody
     public ResponseBean businessExceptionHandler(BusinessException e) {
         log.info("business error : {}",e.getMessage(),e);
         if (e.getCode() == -1) {
             return ResponseBean.error(ApiCode.SERVICE_ERROR.getValue(), ApiCode.SERVICE_ERROR.getMessage());
         }
         return ResponseBean.error(e.getCode(), e.getMessage());
     }
 
     @ResponseBody
     @ExceptionHandler(MethodArgumentNotValidException.class)
     public ResponseBean  exceptionHandler(MethodArgumentNotValidException e) {
         log.info("req params error", e);
         String message = e.getBindingResult().getFieldError().getDefaultMessage();
         if (StringUtils.isNotBlank(message) && !"不能为空".equals(message)) {
             return ResponseBean.error(ApiCode.PARAM_INVALID.getValue(), message);
         }
         return ResponseBean.error(ApiCode.PARAM_INVALID.getValue(), ApiCode.PARAM_INVALID.getMessage());
     }
 
     @ExceptionHandler(value = TokenErrorException.class)
     @ResponseBody
     public ResponseBean tokenErrorExceptionHandler(TokenErrorException e) {
         log.info("登录失效 : {}",e.getMessage(),e);
         return ResponseBean.error(ApiCode.SERVICE_ERROR.getValue(), "登录已失效,请重新登录!");
     }
 
     @ExceptionHandler(value = ServiceException.class)
     @ResponseBody
     public ResponseBean businessExceptionHandler(ServiceException e) {
         log.info("service error : {}",e.getMessage(),e);
         return ResponseBean.error(ApiCode.SERVICE_ERROR.getValue(), e.getMessage());
     }
 }

2)增加异常类 BusinessException

/**
  * 业务异常,异常信息会返回到前端展示给用户
  *
  * @date 2020/12/15 14:18
  */
 public class BusinessException extends RuntimeException {
     private static final long serialVersionUID = -5770538329754222306L;
 
     private int code = 1;
     private Level level;
 
     public BusinessException(int code, String message, Throwable cause) {
         super(message, cause);
         this.code = code;
     }
 
     public BusinessException(String message) {
         super(message);
     }
 
     public BusinessException(Level level, String message) {
         super(message);
         this.level = level;
     }
 
     public BusinessException(Throwable cause) {
         super(cause);
     }
 
     public BusinessException(int code, String message) {
         super(message);
         this.code = code;
     }
 
     public int getCode() {
         return this.code;
     }
 
     public final Level getLevel() {
         return this.level;
     }
 }

3)增加断言工具类 AssertUtil

public class AssertUtil extends cn.com.bluemoon.common.web.exception.AssertUtil  {
     public AssertUtil() {
     }
 
     /**
      * 服务调用异常
      * @param expression
      * @param message
      */
     public static void isTrueServiceInvoke(boolean expression, String message) {
         if (!expression) {
             throw new ServiceInvokeException(message);
         }
     }
 
     /**
      * 抛出异常(默认错误1000)
      * @param message
      */
     public static void businessInvalid(String message) {
         throw new BusinessException(ApiCode.SERVICE_ERROR.getValue(), message);
     }
 
     /**
      * 表达式为真即抛出异常(默认错误1000)
      *
      * @param expression
      * @param message
      */
     public static void businessInvalid(boolean expression, String message) {
         if (expression) {
             throw new BusinessException(ApiCode.SERVICE_ERROR.getValue(), message);
         }
     }
 
     /**
      * 表达式为真即抛出异常
      *
      * @param expression
      * @param message
      */
     public static void businessInvalid(boolean expression, int code, String message) {
         if (expression) {
             throw new BusinessException(code, message);
         }
     }
 }

4)异常情况枚举,仅作参考:

public enum ErrorCodeEnum implements EnumBase{
 
     FAIL(-1, "网络异常,请稍后再试"),
 
     SUCCESS(0, "请求成功"),
 
     MAX_UPLOAD_SIZE_ERROR(1000, "上传文件不能超过20M"),
 
     SERVICE_BUSY_ERROR(1000, "服务器正在繁忙,请稍后再试哦~"),
 
     REQUEST_PARAMS_FAIL(1001, "参数错误"),
 
     USER_NOT_LOGIN(1002, "用户未登录,请重新登录"),
 
     USER_HAS_EXIST_LOGIN(1007, "用户已经存在,请检查!"),
 
     USER_CODE_NOT_EXIST(1008, "用户编码不存在,请检查!"),
 
     REQUEST_PARAMS_FORMAT_ERROR(1102, "请求参数格式异常"),
 
     PASSWORD_SAFETY_ERROE(2204, "密码不符合安全规则,请通过忘记密码重新设置8-18位数字+字母组合密码"),
 
     TOKEN_EXPIRED(2301, "token过期"),
 
     TOKEN_ERROR(2302, "token验证失败"),
 
     INTERFACE_ERROR(10000, "接口服务器异常");
 
     private final int code;
     private final String msg;
 
     ErrorCodeEnum(int code, String msg) {
         this.code = code;
         this.msg = msg;
     }
 
     @Override
     public int getCode() {
         return this.code;
     }
 
     @Override
     public String getMsg() {
         return this.msg;
     }
 }

5)业务层应用:

/**
  * 取消服务单
  */
 public ApiResult cancelService(@PathVariable Long serviceOrderId){
     ServiceOrder serviceOrder = serviceOrderMapper.selectByPrimaryKey(serviceOrderId);
     AssertUtil.businessInvalid(ObjectUtil.isNull(serviceOrder),"查无此服务单");
     AssertUtil.businessInvalid(serviceOrder.getOrderStatus().equals(cancelOrderStatus),"查无此服务单");
     AssertUtil.businessInvalid(serviceOrder.getSortOrderId() != null,"查无此服务单");
     // ...other check
 
     // ...do something
     return ApiResult.success();
 }

三、总结

今天的总结说两句激励自己的话吧!!!

千淘万浪虽辛苦,吹尽黄沙始到金

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dylan~~~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值