说明
日常开发中少不了会抛出各种各样的业务异常,而在抛出异常之前又要写一些代码对日志进行处理,有没有一种更为优雅简洁并且能处理大部分应用场景的优化方式来解决这一问题呢,今天接着从json-script-rule上面扒下来一些优秀的代码稍微修改一下,采用一种特别的设计来优雅的解决这个问题,看代码
package edi.rule.work.custom;
import edi.rule.extend.interfaces.IJSRuleHttpResponse;
import edi.rule.work.constant.JSRuleJsonSign;
import java.io.Serial;
/**
* @apiNote Spring @Transactional 默认情况下只会回滚RuntimeException运行时异常,throw new Exception()默认不会回滚
* @apiNote 此时必须使用 rollbackFor 属性指定回滚的异常,JSRuleException继承的是RuntimeException运行时异常,因此不会出现此问题
* */
public final class JSRuleException extends RuntimeException{
@Serial
private static final long serialVersionUID = 1L;
/**
* <p>其子类为返回给调用端的消息对象</>
* */
public IJSRuleHttpResponse status;
/**
* <p>写入到本地日志的消息,如xxx:{}</>
* */
public String log;
/**
* <p>写入到本地日志的参数,如log.error("xxx:{}",params)</>
* */
public Object[] params;
public JSRuleException() {super();}
/**
* <p>只支持后端做日志,调用端返回默认消息对象</>
* */
public JSRuleException(Throwable cause) {
super(cause);
}
/**
* <p>支持后端做日志并且支持打印log参数行为,调用端返回默认消息对象</>
* <p>使用该方法时需注意该异常是否在其它地方被捕获,这样有可能因为处理该异常的地方没有处理参数,进而导致参数为空{}</>
* @param params 参考{@link JSRuleException#params}说明
* */
public JSRuleException(String log,Object...params){
super(log);
this.log = log;
this.params = params;
}
/**
* <p>支持后端做日志,同时支持自定义返回调用端日志对象</>
* */
public JSRuleException(Throwable cause,IJSRuleHttpResponse status) {
this(cause);
this.status = status;
}
/**
* <p>只支持调用端做日志,后端日志默认使用调用端日志</>
* */
public JSRuleException(IJSRuleHttpResponse status) {
this(status,status.getMessage());
}
/**
* <p>支持后端做日志并且支持打印log参数行为,同时支持自定义返回调用端日志对象</>
* <p>使用该方法时需注意该异常是否在其它地方被捕获,这样有可能因为处理该异常的地方没有处理参数,进而导致参数为空{}</>
* @param params 参考{@link JSRuleException#params}说明
* */
public JSRuleException(IJSRuleHttpResponse status,String log,Object...params) {
this(log,params);
this.status = status;
}
}
上面是自定义一个异常类,可以直接copy代码复制到自己的项目中,可以把类名字改一改,接下来是如何处理这个异常类,代码如下
@Order(Ordered.HIGHEST_PRECEDENCE)
@Slf4j
@ControllerAdvice
public final class JSRuleExceptionHandler {
/**
* <p>用于全局捕获漏掉的JSRuleException异常,通常在未使用内置{@link JSRuleController#start()}方法时会产生遗漏
* <p>此外若外部项目使用同样的手法对该异常进行捕获,则该方法有可能被截断而无法正常执行
* */
@ExceptionHandler(value = JSRuleException.class)
@ResponseBody
public <R extends JSResult> IJSRuleHttpResponse JSRuleExceptionProcess(JSRuleException e) {
/*处理本地log日志*/
if (ZSString.isBlank(e.log)){
log.error(e.getMessage(),e);
}else{
log.error(e.log,e.params);
}
/*调用端响应对象*/
if (e.status==null){
R result = JSResult.fail();
result.setLog(e.getMessage());
return result;
}else{
return e.status;
}
}
}
上面是定义一个全局异常捕获的处理类,可以直接copy代码到自己的项目中,到这一步其实看着非常的简单,其实这里的设计才是最为关键的
注意:R result = JSResult.fail();result.setLog(e.getMessage());这行代码可以替换成项目中默认的实现了IJSRuleHttpResponse 这个接口的响应对象
接下来补充一个代码后进行使用说明
public interface IJSRuleHttpResponse {
/**
* <p>状态码
* */
Integer getCode();
/**
* <p>状态码对应的消息内容
* */
String getMessage();
}
上面定义一个接口,规定使用者必须实现getCode()和getMessage()方法,通常在类中或者实现了该接口的enum中定义两个字段并在类或者enum上加注@Data或者@Getter注解就可以了,如下
@Data
public class JSResult implements IJSRuleHttpResponse {
/**
* <p>状态码
* */
public Integer code;
/**
* <p>系统消息
* */
public String message;
/**
* <p>系统返回的结果
* */
public LinkedHashMap<String,Object> result;
/**
* <p>系统返回的日志
* */
public String log;
public static <R extends JSResult> R instance(){
return ZSRule.createModel(IJSRuleCustom.ExtendType.result);
}
public static <R extends JSResult> R success(){
R result = instance();
result.setCode(HttpStatusEnum.SUCCESS.getCode());
result.setMessage(HttpStatusEnum.SUCCESS.getMessage());
return result;
}
public static <R extends JSResult> R fail(){
R result = instance();
result.setCode(HttpStatusEnum.FAIL.getCode());
result.setMessage(HttpStatusEnum.FAIL.getMessage());
return result;
}
@Getter
@AllArgsConstructor
protected enum HttpStatusEnum implements IJSRuleHttpResponse {
SUCCESS(200, "operation successfully"),
FAIL(500,"operation failure");
private final Integer code;
private final String message;
}
}
上面的代码使用了泛型,项目中可以根据自己的实际情况改成对应的代码即可
使用
在任意代码处只需要throw new JSRuleException(),框架便会根据其中的构造参数进行相应的处理,而无需开发者再去关注如何构建日志的问题,接下来只对常用的构造参数进行说明
- throw new JSRuleException(String log,Object...params):它等同于log.error("xxx:{}",params),于此同时框架会对调用端(前端或其它第三方)返回的对象采用默认的
- throw new JSRuleException(IJSRuleHttpResponse status,String log,Object...params):这个构造参数比上面的多了一个IJSRuleHttpResponse类型的参数,它支持自定义前端返回对象,此处扔进去一个要返回的响应对象就可以了,值得注意的是,返回的响应消息和后台的日志消息完全分离,可以分别指定不同的消息响应
以此类推,上面的自定义异常类足够支撑绝大部分业务场景了,当需要抛出Throwable cause时也可以通过对应的构造函数来实现。
其实代码非常的简单,但是这里如何用最少的代码实现最为复杂的功能,这里面的设计其实才是最重要的,对于开发者而言,在做异常处理的时候只需要throw一个异常就可以了,这样既保证了代码的统一,又减少了开发者的工作