使用拦截器处理繁琐的前置条件判定
在开发过程中,为了提高程序的健壮性,对参数的校验是必不可少的,然而使用传统的方式进行参数校验时,导致程序中存在了if xxx return xxx;处理不够优雅。虽然jfinal提供了Validator,但是使用过于繁琐,对前后端分离不友好。 在guaua工具包中的Preconditions启发下,本人利用拦截器和自定义异常实现了一个较为优雅的参数校验方法。(注:未经全面测试,仅供参考)
1. ParaExceptionInterceptor 自定义异常拦截器(仅拦截返回值为Ret的方法)
public class ParaExceptionInterceptor implements Interceptor { @SuppressWarnings("unchecked") private static final List<Class<? extends Exception>> DEFAULT_EXCPTIONS = Lists.newArrayList(ParaExcetion.class); private static List<Class<? extends Exception>> getConfigWithExceptionConfig(Invocation inv) { ExceptionConfig config = inv.getMethod().getAnnotation(ExceptionConfig.class); if (config == null) config = inv.getTarget().getClass().getAnnotation(ExceptionConfig.class); if (config != null) { Class<? extends Exception>[] value = config.value(); return Arrays.asList(value); } return DEFAULT_EXCPTIONS; } /** * (non-Javadoc) * Title: intercept * Description:对指定异常进行拦截并封装为错误信息返回 * @param inv * @see com.jfinal.aop.Interceptor#intercept(com.jfinal.aop.Invocation) */ @Override public void intercept(Invocation inv) { try { inv.invoke(); } catch (Exception e) { // 若返回值类型不是Ret则将异常继续往上抛 Class<?> returnType = inv.getMethod().getReturnType(); if (!(returnType.equals(Ret.class))) { throw e; } List<Class<? extends Exception>> exceptionClasses = getConfigWithExceptionConfig(inv); for (Class<? extends Exception> exceptionClass : exceptionClasses) { if (Objects.equals(e.getClass(), exceptionClass) || Objects.equals(e.getClass().getSuperclass(), exceptionClass)) { inv.setReturnValue(MRetKit.buildFail(e.getMessage())); return; } } throw e; } } }
2. ParaExcetion 自定义异常
public class ParaExcetion extends com.iipcloud.api.exception.ParaExcetion { /** serialVersionUID */ private static final long serialVersionUID = 4888200095167386189L; /** * <p>Title: </p> * <p>Description: </p> */ public ParaExcetion() { super(); } /** * <p>Title: </p> * <p>Description: </p> * @param message * @param cause * @param enableSuppression * @param writableStackTrace */ public ParaExcetion(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } /** * <p>Title: </p> * <p>Description: </p> * @param message * @param cause */ public ParaExcetion(String message, Throwable cause) { super(message, cause); } /** * <p>Title: </p> * <p>Description: </p> * @param message */ public ParaExcetion(String message) { super(message); } /** * <p>Title: </p> * <p>Description: </p> * @param cause */ public ParaExcetion(Throwable cause) { super(cause); } }
3. ExceptionConfig 异常注解
@Retention(RUNTIME) @Target({ TYPE, METHOD }) public @interface ExceptionConfig { Class<? extends Exception>[] value(); }
4. ParaCheckKit 配套工具类
public class ParaCheckKit { private ParaCheckKit() { super(); } public static void requireFalse(boolean expression, String errMsgFormat, Object... args) { requireTrue(!expression, String.format(errMsgFormat, args)); } /** * Title: checkPara * Description: 检查表达式是否满足,若不满足则抛出异常 * Date: 2020年2月25日 * @param expression * @param errMsg * @throws IllegalParaException */ public static void requireTrue(boolean expression, String errMsg) { if (!expression) { throw new IllegalParaException(errMsg); } } public static void requireTrue(boolean expression, String errMsgFormat, Object... args) { if (!expression) { throw new IllegalParaException(String.format(errMsgFormat, args)); } } /** * Title: requireNotNull * Description: 判断指定对象是否为空,为空则抛出异常 * Date: 2020年2月25日 * @param obj * @param errMsg * @throws IllegalParaException */ public static void requireNotNull(Object obj, String errMsg, Object... args) { requireTrue(Objects.nonNull(obj), errMsg, args); } public static void requireNull(Object obj, String errMsg, Object... args) { requireTrue(Objects.isNull(obj), errMsg, args); } /** * Title: requireNotBlank * Description:判断指定字符串是否为空,为空则抛出异常 * Date: 2020年2月25日 * @param str * @param errMsg * @throws com.iipmes.exception.IllegalParaException */ public static void requireNotBlank(String str, String errMsg, Object... args) { requireTrue(StrKit.notBlank(str), errMsg, args); } public static void requireBlank(String str, String errMsg, Object... args) { requireTrue(StrKit.isBlank(str), errMsg, args); } /** * Title: requireNotEmpty * Description: * Date: 2020年2月25日 * @param obj * @param errMsg * @throws com.iipmes.exception.IllegalParaException */ public static void requireNotEmpty(Object obj, String errMsg, Object... args) { requireTrue(IIPUtil.notEmpty(obj), errMsg, args); } public static void requireEmpty(Object obj, String errMsg, Object... args) { requireTrue(IIPUtil.isEmpty(obj), errMsg, args); } /** * Title: checkModel * Description: 检查model中的指定字段是否为空,为空则抛出异常 * Date: 2020年2月25日 * @param model * @param fields */ public static void checkModel(Model<? extends Model<?>> model, List<String> fields, String errMsg, Object... args) { requireTrue(fieldNotEmpty(model, fields), errMsg, args); } public static boolean fieldNotEmpty(Model<? extends Model<?>> model, String... fields) { return fieldNotEmpty(model, Arrays.asList(fields)); } /** * Title: fieldNotEmpty * Description: 检查Model中指定的字段 * Date: 2020年2月25日 * @param model * @param fields * @return */ public static boolean fieldNotEmpty(Model<? extends Model<?>> model, List<String> fields) { for (String field : fields) { if (ObjectKit.isEmpty(model.get(field))) { return false; } } return true; } }
5. 使用demo
@Before(ParaExceptionInterceptor.class) public class DemoService { public Ret doSomeThing(Record record, Model<? extends Model<?>> model) { ParaCheckKit.requireNotNull(record.getStr("id"), "id can not be null"); ParaCheckKit.checkModel(model, Lists.newArrayList("id,name"), "id required, name required"); // do Something return Ret.ok(); } }
注意事项
1. 配合静态方法导入更为舒爽
2. 以上提供的只是一个简单的demo,适用于servce层,Controller层使用需要改动(默认无返回值,可自定义一个拦截器处理返回值返回给前端页面,下面是一个简单示例)。
public class ActionInterceptor implements Interceptor { /** * (non-Javadoc) * <p>Title: intercept</p> * <p>Description: </p> * @param inv * @see com.jfinal.aop.Interceptor#intercept(com.jfinal.aop.Invocation) */ @Override public void intercept(Invocation inv) { inv.invoke(); Class<?> returnType = inv.getMethod().getReturnType(); if (returnType.equals(Void.class)) { return; } Object returnValue = inv.getReturnValue(); if (returnType.equals(String.class)) { inv.getController().render((String) returnValue); } else if (returnType.equals(Ret.class)) { inv.getController().renderJson((Ret) returnValue); } else { inv.getController().renderJson(RetKit.buildOk(returnValue)); } } }
早期的 jfinal 版本对前后分离的 validate 支持不太好,所以高版本的 jfinal 提供了相关 API,在本站 jfinal.com 中的一个用法如下:
/**
* ajax 登录参数验证
*/
public class LoginValidator extends Validator {
protected void validate(Controller c) {
setShortCircuit(true);
setRet(Ret.fail()); // Ret.fail() 将设置 state : "fail" 值
validateRequired("userName", "msg", "邮箱不能为空");
validateEmail("userName", "msg", "邮箱格式不正确");
validateRequired("password", "msg", "密码不能为空");
validateCaptcha("captcha", "msg", "验证码不正确");
}
protected void handleError(Controller c) {
// getRet() 与 setRet(...) 配合使用
c.renderJson(getRet());
}
}
以上的核心在于 validate 中的 setRet(Ret.fail()) 以及 handleError 中的 c.renderJson(getRet()),这样处理以后的好处是对前后分离也一并支持了,controller 中的 renderJson(Ret) 与 validate 中的 c.renderJson(Ret) 实现了统一