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 判断,该方法的代码量也减少了非常多。

 

posted @ 2020-02-15 17:40  牛有肉  阅读(2226)  评论(0编辑  收藏  举报