【知识点】你真的了解断言吗?
一、前言
之前在使用gateway
时,经常涉及到yml的配置,其中有一项和请求路径匹配相关的内容叫做断言(Predicate
),在看到这个词时,对断言的概念充满疑惑,为什么叫做断言,之前一直没有找到合适时间整理,正好今天又在异常处理中看到了"断言
"这个词,所以借着这个实际,简单的把断言相关内容整理,也补充一下自己的知识点;
注
:本文主要对工作中可能涉及到断言的技术点对它做一个简单的总结,主要涉及java中的assert
、jdk1.8的Predicate
、gateway
中的断言和全局异常处理
中用到的断言;
二、断言应用
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();
}
三、总结
今天的总结说两句激励自己的话吧!!!
千淘万浪虽辛苦,吹尽黄沙始到金