JAVA自定义注解的使用的一个简单例子
作为一个 JAVA 开发者,对注解这一概念一定是不陌生的。像我们平时常用的就有 @Controller, @Service,@Test,@Override 等等好多个,正确的使用注解确实可以方便我们的开发,以@Controller 为例,加上该注解后,框架层面为我们节省了一大堆需要在 Servlet 层面写的通用代码,大大减少了实际开发时的重复代码量。
除了使用这些框架提供的注解外,我们也可以为我们的应用自定义注解方便自己的开发,下面我们来看一下如何自定义和使用注解,并在提供一个实际使用自定义注解的例子。
注解的概念
从JDK 1.5开始, Java增加了对元数据(MetaData)的支持,也就是 Annotation(注解)。 注解其实就是代码里的特殊标记,它用于替代配置文件:传统方式通过配置文件告诉类如何运行,有了注解技术后,开发人员可以通过注解告诉类如何运行。在Java技术里注解的典型应用是:可以通过反射技术去得到类里面的注解,以决定怎么去运行类。
对于注解,官方的说法是:注解是一种能被添加到java代码中的元数据,类、方法、变量、参数和包都可以用注解来修饰。注解对于它所修饰的代码并没有直接的影响。
注解实际上就是一种数据结构,为我们的类、属性、方法等添加附加信息,我们可以利用这些附加信息对宿主进行一些逻辑判断。
注解的生命周期有三个阶段:1、Java源文件阶段;2、编译到class文件阶段;3、运行期阶段。
我们可以通过元注解来配置我们自定义注解的生命周期,在实际生产中,用的最多的是在运行期阶段使用注解,所以一般情况下我们会将自定义注解的生命周期配置为运行期生效。
元注解
上面说过,我们可以通过元注解来配置注解的生命周期,同样的我们可以使用元注解配置注解的作用对象等等基本属性。元注解是一种用于修饰注解的注解。
在JDK 1.5中提供了4个标准的用来对注解类型进行注解的注解类,我们称之为 meta-annotation(元注解),他们分别是:
@Target、@Retention、@Documented、@Inherited
我们可以使用这4个元注解来对我们自定义的注解类型进行注解,接下来,我们挨个对这4个元注解的作用进行介绍。
1、Target注解:该注解的作用是:描述注解的使用范围(即:被修饰的注解可以用在什么地方) 。Target注解用来说明那些被它所注解的注解类可修饰的对象范围:注解可以用于修饰 packages、types(类、接口、枚举、注解类)、类成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数),在定义注解类时使用了@Target 能够更加清晰的知道它能够被用来修饰哪些对象,它的取值范围定义在ElementType 枚举中。
2、Reteniton注解:该注解的作用是:描述注解保留的时间范围(即:被描述的注解在它所修饰的类中可以被保留到何时) 。Reteniton注解用来限定那些被它所注解的注解类在注解到其他类上以后,可被保留到何时,一共有三种策略,定义在RetentionPolicy枚举中。
3、Documented注解:该注解的作用是:描述在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息。
4、Inherited注解:该注解的作用是:使被它修饰的注解具有继承性(如果某个类使用了被@Inherited修饰的注解,则其子类将自动具有该注解)。
其中 Target 和 Reteniton 是我们最常用的。
例子
注解的基础知识比较少,我们直接上手一个实际生产的例子。
在生产中,遇到了一个用于描述评分信息的类,类的属性是评分时的各个评分项。评分项非常多(getter/setter 全部略过节约篇幅):
public class SysMark extends BaseEntity {
private String markReasons;
private String fj1ReasonSelf;
private Double fj1MarkSelf;
private String fj2ReasonSelf;
private Double fj2MarkSelf;
private String fj3ReasonSelf;
private Double fj3MarkSelf;
private String fj4ReasonSelf;
private Double fj4MarkSelf;
private String fj5ReasonSelf;
private Double fj5MarkSelf;
private String jf1ReasonSelf;
private Double jf1MarkSelf;
private String jf2ReasonSelf;
private Double jf2MarkSelf;
private String jf3ReasonSelf;
private Double jf3MarkSelf;
private String jf4ReasonSelf;
private Double jf4MarkSelf;
private String jf5ReasonSelf;
private Double jf5MarkSelf;
private String jf6ReasonSelf;
private Double jf6MarkSelf;
private String llxz1ReasonSelf;
private Double llxz1MarkSelf;
private String llxz2ReasonSelf;
private Double llxz2MarkSelf;
private String llxz3ReasonSelf;
private Double llxz3MarkSelf;
private String llxz4ReasonSelf;
private Double llxz4MarkSelf;
private String zygw1ReasonSelf;
private Double zygw1MarkSelf;
private String zygw2ReasonSelf;
private Double zygw2MarkSelf;
private String zygw3ReasonSelf;
private Double zygw3MarkSelf;
private String jsyw1ReasonSelf;
private Double jsyw1MarkSelf;
private String jsyw2ReasonSelf;
private Double jsyw2MarkSelf;
private String jsyw3ReasonSelf;
private Double jsyw3MarkSelf;
private String jsyw4ReasonSelf;
private Double jsyw4MarkSelf;
private String zgsx1ReasonSelf;
private Double zgsx1MarkSelf;
private String zgsx2ReasonSelf;
private Double zgsx2MarkSelf;
private String zgsx3ReasonSelf;
private Double zgsx3MarkSelf;
private String zgsx4ReasonSelf;private Double zgsx4MarkSelf;
private java.sql.Timestamp markTime;
private java.sql.Timestamp updateTime;
private String startTime;
private String endTime;
private String markId;
private Double markMark;
private String markMonth;
private String markYear;
private String markJiDu;
private long uid;
private String deptId;
}
目前接到的需求是,将评分项汇总,组成一个 “评分项1:得分1;评分项2:得分2...” 的字符串。其中每个评分项得分在实际语义中包含三种情况:加分项,减分项,否决项(总分直接判0)。
在通常情况下,我们一般便会通过 if - else 逐个判断每个属性是否有打分,判断该评分项的类型来决定得分是加分、减分还是直接判 0 。
但这样做会带来很多不方便,几十个 if 判断不说,万一我们要新增或减少一个评分项或者配错/漏配了一个评分项,我们需要那 if 的代码逐行的与类中的属性比对,非常的繁琐。这时候我们可以考虑自定义一个注解用于修饰这些属性,在定义属性时便为其附加 评分项名称 以及加减分/否决的属性,然后通过反射对所有属性统一进行处理。
我们来定义出这个注解:
/**
* @Author Nxy
* @Date 2020/2/15 13:17
* @Description 自定义评分原因注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value = ElementType.FIELD)
public @interface MarkReason {
//评分项目名称
public String reasonName();
//是否减分项
public boolean isSubtraction() default true;
//是否否决项
public boolean isFouJue() default false;
}
其中两个元注解的含义便是:@Retention(RetentionPolicy.RUNTIME):该注解在运行期生效;@Target(value = ElementType.FIELD):该注解作用于属性。
我们为注解定义了三个属性 resonName、isSubtraction、isFouJue,被 MarkReason 注解修饰的属性可以拥有这三个属性。
我们用该注解对上述评分类进行修饰后评分类变成这样:
public class SysMark extends BaseEntity { private String markReasons; private String fj1ReasonSelf; @MarkReason(reasonName = "责任区党员群众发生违法行为", isFouJue = true) private Double fj1MarkSelf; private String fj2ReasonSelf; @MarkReason(reasonName = "责任区党员群众发生撞“红线”及以上问题", isFouJue = true) private Double fj2MarkSelf; private String fj3ReasonSelf; @MarkReason(reasonName = "责任区党员群众参与群体上访、越级上访", isFouJue = true) private Double fj3MarkSelf; private String fj4ReasonSelf; @MarkReason(reasonName = "责任区发生打架斗殴等不良行为", isFouJue = true) private Double fj4MarkSelf; private String fj5ReasonSelf; @MarkReason(reasonName = "经研究其他否决项目的问题", isFouJue = true) private Double fj5MarkSelf; private String jf1ReasonSelf; @MarkReason(reasonName = "责任区党员群众发现安全重大隐患、防止安全事故、受到段级及以上表扬表彰或通报嘉奖", isSubtraction = false) private Double jf1MarkSelf; private String jf2ReasonSelf; @MarkReason(reasonName = "责任区党员群众参加技术比武获得名次", isSubtraction = false) private Double jf2MarkSelf; private String jf3ReasonSelf; @MarkReason(reasonName = "积极组织责任区党员群众 围绕安全、运输和技术难题立项攻关取得实效,受到总公司、 集团公司、段表彰", isSubtraction = false) private Double jf3MarkSelf; private String jf4ReasonSelf; @MarkReason(reasonName = "责任区党员群众完成急难险重任务成绩突出", isSubtraction = false) private Double jf4MarkSelf; private String jf5ReasonSelf; @MarkReason(reasonName = "责任区党员群众做好人好事、见义勇为事迹受到表彰奖励或媒体表扬", isSubtraction = false) private Double jf5MarkSelf; private String jf6ReasonSelf; @MarkReason(reasonName = "其他受到集团公司及以上表彰奖励", isSubtraction = false) private Double jf6MarkSelf; private String llxz1ReasonSelf; @MarkReason(reasonName = "责任区党员群众无故不参加上级组织的集体活动") private Double llxz1MarkSelf; private String llxz2ReasonSelf; @MarkReason(reasonName = "责任区内环境卫生差、备品摆放不整齐") private Double llxz2MarkSelf; private String llxz3ReasonSelf; @MarkReason(reasonName = "班组标准化验收不达标") private Double llxz3MarkSelf; private String llxz4ReasonSelf; @MarkReason(reasonName = "班组未完成生产任务,运输组织工作,旅客、货主等服务工作受到上级批评") private Double llxz4MarkSelf; private String zygw1ReasonSelf; @MarkReason(reasonName = "责任区党员群众发生迟到、早退") private Double zygw1MarkSelf; private String zygw2ReasonSelf; @MarkReason(reasonName = "责任区党员群众发生严重“两违”问题") private Double zygw2MarkSelf; private String zygw3ReasonSelf; @MarkReason(reasonName = "作业提醒不到位") private Double zygw3MarkSelf; private String jsyw1ReasonSelf; @MarkReason(reasonName = "不参加月度业务考试、模拟演练") private Double jsyw1MarkSelf; private String jsyw2ReasonSelf; @MarkReason(reasonName = "责任区党员群众月度考试或抽考成绩不达标") private Double jsyw2MarkSelf; private String jsyw3ReasonSelf; @MarkReason(reasonName = "应知应会考试、专业技能考核不达标") private Double jsyw3MarkSelf; private String jsyw4ReasonSelf; @MarkReason(reasonName = "责任区党员群众技术业务帮带效果不明显") private Double jsyw4MarkSelf; private String zgsx1ReasonSelf; @MarkReason(reasonName = "对责任区内职工思想动态不掌握、不熟悉、不了解,不能及时做思想工作") private Double zgsx1MarkSelf; private String zgsx2ReasonSelf; @MarkReason(reasonName = "未及时与发生“两违”问题的党员群众谈心谈") private Double zgsx2MarkSelf; private String zgsx3ReasonSelf; @MarkReason(reasonName = "未及时与困难党员群众谈心谈话") private Double zgsx3MarkSelf; private String zgsx4ReasonSelf; @MarkReason(reasonName = "未及时化解矛盾造成不良影响") private Double zgsx4MarkSelf; private java.sql.Timestamp markTime; private java.sql.Timestamp updateTime; private String startTime; private String endTime; private String markId; private Double markMark; private String markMonth; private String markYear; private String markJiDu; private long uid; private String deptId;
}
这样一来,在每个属性被定义时,它的语义便被一同写进了其注解中。我们通过反射获取每个属性的注解,对所有属性进行统一的处理:
/**
* @Author Nxy
* @Date 2020/2/15 14:14
* @Description 汇总加减分原因
*/
public static void setMarkReasons(BaseEntity markBean) throws IllegalAccessException, NoSuchFieldException {
Class beanClass = markBean.getClass();
Field[] fields = beanClass.getDeclaredFields();
if (fields == null || fields.length == 0) {
throw new RuntimeException(markBean + " has no field");
}
Field targetField = beanClass.getDeclaredField("markReasons");
StringBuilder reasonsSb = new StringBuilder();
//遍历属性
for (Field field : fields) {
//判断该属性是否被 MarkReason 注解修饰
if (field.isAnnotationPresent(MarkReason.class)) {
//允许私有属性访问
field.setAccessible(true);
String isSubtraction = "-";
MarkReason reasonAnno = field.getAnnotation(MarkReason.class);
Object markMark = field.get(markBean);
//判断该项是否已评分
if(markMark==null){
continue;
}
//判断是否是减分项
if (!reasonAnno.isSubtraction()) {
isSubtraction = "+";
}
String project="";
//判断是否否决项
if(reasonAnno.isFouJue()){
project = "-100";
}else {
project = isSubtraction + (double) markMark;
}
//拼装加减分原因
reasonsSb.append(reasonAnno.reasonName() + ":" + project + ";");
}
}
//汇总后将结果写入对象
targetField.setAccessible(true);
targetField.set(markBean, reasonsSb.toString());
}
通过上面的方法,我们只需要在定义一个新的评分项时将其用 @MarkReason 注解修饰即可,汇总评分原因的代码不需要做任何改变。而使用传统的 if 判断的方法时,新增/删除/改变一个评分项,相应的 if 节点的代码都需要做出对应的改变。并且相较 if 判断,该方法的代码量也减少了非常多。