背景
在java开发中spring的很多功能已经非常的完美了。但是再完美的工具也难以抵挡用户的要求。
在很多次的项目中,我都会设计到一些标准文档,里面有各种各样的参数,这些数据有各种各样的取值,有各种各样的要求。数据的传输采用采用json方式。收到数据之后需要对数据进行按照标准进行过滤。在博主所知的现有的工具中没有办法满足这些功能。
但是获取每一个对象来进行判断的工作量太大太耗时间。所以想到采用自定义注解的方式来实现一些需要的数据验证和过滤。
实现
1. 定义一个注解
我需要的功能有如下几点:
- 验证必填属性
- 数据可添加默认值
- 值域校验(正则表达式)
- 在对象中的某个属性不为空时,该值不可为空,实现值的关联性
所以设计了以下的自定义注解
/**
* @author yixiangxuehai
* 自定义注解实现
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({java.lang.annotation.ElementType.FIELD})
public @interface DefineAnnotation {
//是否必填
boolean isRequire() default false;
//默认值实现
String defaultValue() default "";
//值域正则表达式
String regular() default "";
//在本类的其他其他对象存在时不能为空,多个以“,”隔开
String notNullRegular() default "";
}
自定义注解必须要有默认值default。
关于元注解的解释(meta-annotation):
元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。Java5.0定义的元注解:
1.@Target,
2.@Retention,
3.@Documented,
4.@Inherited
这些类型和它们所支持的类在java.lang.annotation包中可以找到。下面我们看一下每个元注解的作用和相应分参数的使用说明。
@Target:
@Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。
作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)
取值(ElementType)有:
1.CONSTRUCTOR:用于描述构造器
2.FIELD:用于描述域
3.LOCAL_VARIABLE:用于描述局部变量
4.METHOD:用于描述方法
5.PACKAGE:用于描述包
6.PARAMETER:用于描述参数
7.TYPE:用于描述类、接口(包括注解类型) 或enum声明
注解Table 可以用于注解类、接口(包括注解类型) 或enum声明,而注解NoDBColumn仅可用于注解类的成员变量。
@Retention:
@Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。
作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)
取值(RetentionPoicy)有:
1.SOURCE:在源文件中有效(即源文件保留)
2.CLASS:在class文件中有效(即class保留)
3.RUNTIME:在运行时有效(即运行时保留)
Retention meta-annotation类型有唯一的value作为成员,它的取值来自java.lang.annotation.RetentionPolicy的枚举类型值
Column注解的的RetentionPolicy的属性值是RUTIME,这样注解处理器可以通过反射,获取到该注解的属性值,从而去做一些运行时的逻辑处理
2. 定义一个POJO对象
测试的javaBean设计如图:
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class AnnotionInfo {
@JsonProperty("Name")
@DefineAnnotation(isRequire = true)
private String name;
@JsonProperty("Age")
@DefineAnnotation(notNullRegular = "birthDay")
private Integer age;
@JsonProperty("Birthday")
@DefineAnnotation(regular = "^[0-9]{10}$", defaultValue = "1234567891")
private String birthDay;
@JsonProperty("Gender")
@DefineAnnotation(regular = "0|1|2")
private String gender;
@JsonProperty("DefaultString")
@DefineAnnotation(defaultValue = "ghdfagh")
private String defaultString;
}
定义好实体类对象,并在需要的属性上写上自己的注解。
3. 实现注解验证过滤的公共方法
/**
* 根据自定义注解检测数据
* @param obj 需要检测的数据
* @return 检测结果
*/
private String inspectAnnotion(Object obj){
StringBuilder stringBuilder = new StringBuilder();
if(obj == null){
return null;
}
Field[] fields = obj.getClass().getDeclaredFields();
for(Field field : fields){ //获取每一个属性
field.setAccessible(true); //设置属性的访问权限
DefineAnnotation defineAnnotation = field.getAnnotation(DefineAnnotation.class);
if(defineAnnotation != null ){
//包含自定义注解 DefineAnnotation
Type type = field.getType();
try {
Object value = field.get(obj); //获取对象属性值
if(value == null){
//属性没有值
if(defineAnnotation.isRequire()){
//必填
stringBuilder.append(field.getName() + "不能为空;");
}
//在某些属性不为空的情况下,属性值不为空
String notNullRegular = defineAnnotation.notNullRegular();
boolean fieldValue = getFieldValue(obj, notNullRegular);
if(!fieldValue){
stringBuilder.append(field.getName() + "在["+ notNullRegular + "]值不为空时,不能为空;");
}
//默认值设定
String defaultValue = defineAnnotation.defaultValue();
if(StringUtils.isNotEmpty(defaultValue)){
field.set(obj, changeValue(field, defaultValue));
}
} else {
//属性有值
String regular = defineAnnotation.regular();
//正则验证
if(StringUtils.isNotEmpty(regular)) {
Object fieldValue = field.get(obj);
Pattern pattern = Pattern.compile(regular);
Matcher matcher = pattern.matcher(getStringValue(field, fieldValue));
if (!matcher.matches()) {
stringBuilder.append(field.getName() + "不符合[" + regular + "]规则");
}
}
}
} catch (Exception e){
System.out.println(field.getName() + "获取不到值");
e.printStackTrace();
}
}
}
return stringBuilder.toString();
}
/**
* 根据属性名称获取属性值
* @param obj
* @param fieldName
* @return
*/
private boolean getFieldValue(Object obj, String fieldName){
Field field = null;
if(StringUtils.isEmpty(fieldName)){
return true;
}
for(String str : fieldName.split(",")) {
try {
field = obj.getClass().getDeclaredField(str);
field.setAccessible(true);
Object value = field.get(obj);
if (value == null) {
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
}
return false;
}
由于涉及到的数据有不同类型,但是注解中的正则表达式和默认值都是String,所以需要做对应数据的转换。
方法如下:
/**
* 基本数据类型转为String
* @param field
* @param fieldValue
* @return
*/
private String getStringValue(Field field, Object fieldValue){
Type type = field.getType();
String classNmae = ((Class) type).getName().substring(((Class) type).getName().indexOf('.') == -1 ? 0 : ((Class) type).getName().lastIndexOf('.') + 1);
if("Integer".equals(classNmae) || "int".equals(classNmae)){
return String.valueOf((Integer)fieldValue);
}
else if("Double".equals(classNmae) || "double".equals(classNmae)){
return String.valueOf((Double) fieldValue);
}
else if("Short".equals(classNmae) || "short".equals(classNmae)){
return String.valueOf((Short) fieldValue);
}
else if("Boolean".equals(classNmae) || "boolean".equals(classNmae)) {
if ((boolean)fieldValue){
return "1";
}else {
return "0";
}
}
else if("Date".equals(classNmae)){
return new SimpleDateFormat("yyyyMMddhhmmss").format((Date)fieldValue);
}
else if("String".equals(classNmae)){
return (String) fieldValue;
} else {
try {
throw new Exception("值域出错");
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
/**
* String 值转为基本数据类型
* @param field
* @param fieldValue
* @return
*/
private Object changeValue(Field field, String fieldValue){
Type type = field.getType();
String classNmae = ((Class) type).getName().substring(((Class) type).getName().indexOf('.') == -1 ? 0 : ((Class) type).getName().lastIndexOf('.') + 1);
if("Integer".equals(classNmae) || "int".equals(classNmae)){
return Integer.valueOf(fieldValue);
}
else if("Double".equals(classNmae) || "double".equals(classNmae)){
return Double.valueOf(fieldValue);
}
else if("Short".equals(classNmae) || "short".equals(classNmae)){
return Short.valueOf(fieldValue);
}
else if("Boolean".equals(classNmae) || "boolean".equals(classNmae)) {
if ("true".equals(fieldValue)){
return true;
}else {
return false;
}
}
else if("Date".equals(classNmae)){
try {
return new SimpleDateFormat("yyyyMMddhhmmss").parse(fieldValue);
} catch (ParseException e) {
e.printStackTrace();
}
}
else if("String".equals(classNmae)){
return (String) fieldValue;
} else {
try {
throw new Exception("值域出错");
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
这样就能实现一些简单的自定义数据过滤功能。
开始测试:
测试一:
@Test
public void test(){
AnnotionInfo annotionInfo = new AnnotionInfo("12233", 12, "1234567890", "5","");
/*annotionInfo.setDefaultString(null);
annotionInfo.setAge(null);
annotionInfo.setBirthDay(null);*/
String result = inspectAnnotion(annotionInfo);
System.out.println(annotionInfo.toString());
System.out.println(result);
}
得到结果为:
AnnotionInfo(name=12233, age=12, birthDay=1234567890, gender=5, defaultString=)
gender不符合[0|1|2]规则
测试二:
@Test
public void test(){
AnnotionInfo annotionInfo = new AnnotionInfo("12233", 12, "1234567890", "5","");
annotionInfo.setDefaultString(null);
/*annotionInfo.setAge(null);
annotionInfo.setBirthDay(null);*/
String result = inspectAnnotion(annotionInfo);
System.out.println(annotionInfo.toString());
System.out.println(result);
}
得到结果为:
AnnotionInfo(name=12233, age=12, birthDay=1234567890, gender=5, defaultString=ghdfagh)
gender不符合[0|1|2]规则
测试三:
@Test
public void test(){
AnnotionInfo annotionInfo = new AnnotionInfo("12233", 12, "1234567890", "5","");
annotionInfo.setDefaultString(null);
annotionInfo.setAge(null);
/*annotionInfo.setBirthDay(null);*/
String result = inspectAnnotion(annotionInfo);
System.out.println(annotionInfo.toString());
System.out.println(result);
}
得到结果为:
AnnotionInfo(name=12233, age=null, birthDay=1234567890, gender=5, defaultString=ghdfagh)
age在[birthDay]值不为空时,不能为空;gender不符合[0|1|2]规则
测试四:
@Test
public void test(){
AnnotionInfo annotionInfo = new AnnotionInfo("12233", 12, "1234567890", "5","");
annotionInfo.setDefaultString(null);
annotionInfo.setAge(null);
annotionInfo.setBirthDay(null);
String result = inspectAnnotion(annotionInfo);
System.out.println(annotionInfo.toString());
System.out.println(result);
}
得到结果为:
AnnotionInfo(name=12233, age=null, birthDay=1234567891, gender=5, defaultString=ghdfagh)
gender不符合[0|1|2]规则
至此结束,如有错误之处,请大家指正。如有更好的方法,请告知,谢谢。