上篇我们学习了注解的简单实用方法,结合反射我们可以通过注解来做好多的事情的。
注意到:注解里面配置的信息其实呢都是可以配置在 xml文件中 的
因此使用注解的地方我们都可以通过配置xml文件来完成.
不过使用注解来解析明显还是方便一些。
下面介绍一个编程任务: 我们常常会遇到 接口要进行数据校验或者是导入的文件进行数据校验的动作。
完成这个动作我们可以一个接口一个类然后每个类都写一个校验方法完成动作,可以完成编程任务,但是我们不能进行复用.
遇到这个我们想到了 struts中的那个校验的文件,里面配置一些规则就可以完成校验功能。
规则我们只写一套,不同的校验只是这些规则的不同输入罢了。
struts那个校验做的不错。不过我们可以借鉴一下,我们使用注解来完成.
此次我们的编程任务: 提供一个服务接口,接收另外一个系统的调用参数,然后对参数进行校验,校验失败反馈给该系统校验失败信息
思路:
1 将校验规则信息进行抽象,分类,我们将这些校验的角度 可以看成是 注解里面的参数属性名
2. 做成一个注解表述这些校验的角度和信息
3. 做一个解析注解的方法
4. 做一套校验的策略类
5.使用反射获取到vo中被该注解标记的字段然后调用校验的策略类进行校验
实现:
1. 校验的规则 大致有 : 必填 , 条件必填 , 依赖校验 , 长度校验
2. 可以使用上篇中的
FieldInfo 注解来描述
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import cn.lambdafk.common.constant.CheckStrategyEnum;
/**
* bean的注解,描述字段的汉字名,必填,条件必填,长度校验等信息
*
* @author kaifeng1
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FieldInfo {
/**
* 字段名称
*/
String name() default "";
/**
* 标记一个字段是否是必填项目
*/
boolean required() default false;
/**
* 依赖于其他项目字段条件必填
*/
boolean dependsRequired() default false;
/**
* dependsRequired为true的时候,标记一个字段条件必填的依赖字段
*/
String[] dependsField() default {};
/**
* dependsRequired为true的时候,条件必填时候的策略
*/
CheckStrategyEnum checkStrategy() default CheckStrategyEnum.NULL;
/**
* 是否进行长度校验
*/
boolean lengthCheck() default false;
/**
* 默认字段值长度
*/
int length() default 0;
}
3. 4 ,5的实现
/**
* 检验被FieldInfo注解标记的bean的校验是否合法
*
* @param bean
* 被检验的bean
* @param strategy
* 校验策略
* @return 校验结果信息 判断result字段 false:校验OK ,true:校验失败
* @throws InstantiationException
* @throws IllegalAccessException
*/
public static <V> Map<String, String> checkFieldInfo(V bean, CheckStrategy strategy) throws InstantiationException,
IllegalAccessException {
Class<?> claz = bean.getClass();
Field[] fields = claz.getDeclaredFields();
// 保存校验结果的信息
Map<String, String> errorInfo = new HashMap<String, String>();
// 如果有条件必填项目,保存条件项目和值
Map<String, Object> dependsCondition = new HashMap<String, Object>();
// 当前该字段的值
Object fValue = null;
// 当前该字段的名字
String fName = null;
// 当前字段的汉字名
String chName = null;
// 当前该字段的字符串值
String strFValue = null;
// 当前处理的FieldInfo
FieldInfo fdInfoAnno = null;
for (Field f : fields) {
if (f.isAnnotationPresent(FieldInfo.class)) {
try {
dependsCondition.clear();
f.setAccessible(true);
fValue = f.get(bean);
fName = f.getName();
// 解析该字段的字符串值
if (fValue != null && (fValue instanceof String)) {
strFValue = (String) fValue;
}
fdInfoAnno = f.getAnnotation(FieldInfo.class);
chName = fdInfoAnno.name();
logger.info("当前校验字段" + chName + "开始...");
// 必填项目校验
boolean required = fdInfoAnno.required();
if (required) {
if (isBlank(fValue)) {
Object[] params = {chName};
errorInfo.put("【必填】" + fName, formatMsg(MessageId.APPLY_MUST_01, params));
}
}
logger.info(fName + ":[required校验完成]");
// 条件必填项目校验
boolean dependsRequired = fdInfoAnno.dependsRequired();
if (dependsRequired) {
String[] dependsFields = fdInfoAnno.dependsField();
for (String strField : dependsFields) {
Field itemField = claz.getDeclaredField(strField);
itemField.setAccessible(true);
dependsCondition.put(strField, itemField.get(bean));
itemField.setAccessible(false);
}
CheckStrategyEnum checkEnum = fdInfoAnno.checkStrategy();
// 进行条件依赖校验
String errorMsg = strategy.checkFieldInfo(dependsCondition, checkEnum, fValue);
// 如果校验失败
if (!Constants.STR_TRUE.equals(errorMsg)) {
errorInfo.put("【条件必填】" + fName, errorMsg);
}
logger.info(fName + ":[dependsRequired校验完成]");
}
// 长度check
boolean lengthCheck = fdInfoAnno.lengthCheck();
if (lengthCheck) {
int length = fdInfoAnno.length();
int strLength = strFValue == null ? 0 : strFValue.length();
if (length < strLength) {
Object[] params = {chName, length};
errorInfo.put("【长度】" + fName, formatMsg(MessageId.APPLY_LENGTH_01, params));
}
}
logger.info(fName + ":[lengthCheck校验完成]");
logger.info("当前校验字段" + chName + "结束.");
} catch (Exception e) {
logger.error("执行出错", e);
errorInfo.put("【异常】" + fName, chName + "的校验出现异常!");
continue;
} finally {
f.setAccessible(false);
}
}
}
// 默认为校验结果OK,ERROR_RESULT设置为false
boolean empty = errorInfo.isEmpty();
// 如果校验出错
if (!empty) {
// ERROR_RESULT设置为true
errorInfo.put(Constants.ERROR_RESULT, Constants.STR_TRUE);
} else {
errorInfo.put(Constants.ERROR_RESULT, Constants.STR_FALSE);
}
return errorInfo;
}
使用代码片段
为了所有的接口的实现都可以使用,我们可以在实现类和接口中铺一层抽象类
然后再抽象类中 定义一个校验的方法来完成
1 定义接口来描述 所有服务接口的功能 ZmaCommonApplyService 接口
public interface ZmaCommonApplyService<T> {
/**
* 解析请求信息,生成输入信息bean
*
* @param json
* SIA传送的JSON信息
* @return 输入bean对象
*/
public T parseApplyJson(String json);
/**
* 申请处理逻辑
*
* @param input
* 输入bean
* @return 申请响应信息
* @throws ApplicationException
*/
public Map<String, Object> executApply(T input) throws ApplicationException;
2.定义抽象类完成其中公共的功能 ZmaCommonApplyServiceAbstract
public abstract class ZmaCommonApplyServiceAbstract<IN> implements ZmaCommonApplyService<IN>
然后改方法的实现体中将会调用我们的校验方法 doCheckField
3. doCheckField的方法的定义
/**
* 字段校验模板方法
*
* @param input
* 输入bean
* @param errorInfo
* 校验结果信息存储
* @return 校验结果 true:校验OK ,false:校验失败
*/
protected boolean doCheckField(IN input, Map<String, Object> errorInfo) {
// 如果是处理集合的场景
if (input instanceof Collection) {
boolean resultFlag = true;
@SuppressWarnings("rawtypes")
Iterator<?> it = ((Collection) input).iterator();
List<Map<String, Object>> errorList = new LinkedList<Map<String, Object>>();
int index = -1;
while (it.hasNext()) {
Object obj = it.next();
Map<String, Object> map = new HashMap<String, Object>();
++index;
try {
Map<String, String> chkInfo = ComUtil.checkFieldInfo(obj, checkStrategy);
String result = chkInfo.get(Constants.ERROR_RESULT);
ComUtil.pushAllToObject(chkInfo, map);
// 将当前的输入bean存入map
map.put(Constants.CURRENT_INPUT_BEAN, obj);
// ERROR_RESULT为false的时候校验成功
if (Constants.STR_FALSE.equals(result)) {
// 校验成功
continue;
} else {
// 校验失败
resultFlag = false;
}
} catch (InstantiationException | IllegalAccessException e) {
logger.error("当前对象doCheckField方法执行时字段注解处理出错", e);
resultFlag = false;
map.put("[" + index + "]", "当前对象doCheckField方法执行时字段注解处理出错");
map.remove(Constants.ERROR_RESULT);
map.put(Constants.ERROR_RESULT, Constants.STR_TRUE);
continue;
}
errorList.add(map);
}
// 批处理的情形的key是ERROR_RESULT_LIST,里面每一条记录的信息是ERROR_RESULT为key的
errorInfo.put(Constants.ERROR_RESULT_LIST, errorList);
return resultFlag;
} else {
// 单个处理场景
try {
// 字段校验
Map<String, String> chkInfo = ComUtil.checkFieldInfo(input, checkStrategy);
String result = chkInfo.get(Constants.ERROR_RESULT);
ComUtil.pushAllToObject(chkInfo, errorInfo);
// ERROR_RESULT为false的时候校验成功
if (Constants.STR_FALSE.equals(result)) {
// 校验成功
return true;
} else {
// 校验失败
return false;
}
} catch (InstantiationException | IllegalAccessException e) {
logger.error("doCheckField方法执行时字段注解处理出错");
throw new ApplicationException(e);
}
}
}
4.校验扩展