Java注解机制的理解和运用

Java注解机制的理解和运用

March 20th, 2018
标签(空格分隔): Java程序构造



声明

以下的所有代码案例 均来自于官方的规范手册 个别地方会是我自己的运用 我会给出注释

写在前面

最近使用了很多的Junit 在自定义的测试函数前要加上@Test 在注释spec中要写出@param @return 在程序的开头要声明作者@author@date 这些东西统称为注解(Annotation) 因为最近想要写一个专属于自己的jar包 想要使用这种方式来进行调用 比如所计算程序运行的效率 输出运行的时间和满意度

看到了很多解释这个注解的博文 但是都没有说道点上 自定义注解的实现不够清晰 于是自己写了这篇文章

什么是注解

屏幕快照 2018-03-20 下午10.05.16.png-143.1kB

简单地说 就是@somewords这种方式的语句 这个简单的语句可以告诉IDE 下面的method是我自己定义的注解机制的一部分 需要按照我指定的要求来进行运行 注释中的注解也一样 可以直接通过java
.doc来直接生成文档 方便编写和阅读 省时省力

那么 如果我们要自己定义一个注解机制 要怎么办

官方文档

其实在官方规范中指出了注解机制的使用方法

以下是总体方法 我会一一阐释

AnnotationTypeDeclaration:
    InterfaceModifiersopt @ interface Identifier AnnotationTypeBody

AnnotationTypeBody:
    { AnnotationTypeElementDeclarationsopt }

AnnotationTypeElementDeclarations:
    AnnotationTypeElementDeclaration
    AnnotationTypeElementDeclarations AnnotationTypeElementDeclaration

注解类型元素的声明方法

AnnotationTypeElementDeclaration:
    AbstractMethodModifiersopt Type Identifier ( ) Dimsopt DefaultValueopt ;
    ConstantDeclaration
    ClassDeclaration
    InterfaceDeclaration
    EnumDeclaration
    AnnotationTypeDeclaration
    ;

DefaultValue:
    default ElementValue

我们看这里的定义方法 首先 定义method要遵循这样的语法

@interface myAnnotation{}

注意 注解类型不能够在方法后跟参数 或者是throw一个异常处理 并且按照惯例 在@interface之前不写publish之类的声明

官方例1

这是一个有元素的Annotation

/**
 * Describes the "request-for-enhancement" (RFE)
 * that led to the presence of the annotated API element.
 */
@interface RequestForEnhancement {
    int    id();        // Unique ID number associated with RFE
    String synopsis();  // Synopsis of RFE
    String engineer();  // Name of engineer who implemented RFE
    String date();      // Date RFE was implemented
}

我们看到这里的一个Annotation类型 它之内有四个元素 这里切记每个元素之后加上括号

官方例2

这是一个没有元素的Annotation 我们也可以称这个为marker Annotation

/**
 * An annotation with this type indicates that the 
 * specification of the annotated API element is 
 * preliminary and subject to change.
 */
@interface Preliminary {}

以下的场景会发生编译错误

  • 如果在注释类型声明一个方法的返回类型是下列之一:简单类型 字符串类 任何参数调用的类 一个枚举类型 注释类型或数组类型元素类型是前述的类型
@interface Verboten {
    String[][] value();
}
  • 在一个注释类型中声明的任何方法都有一个签名 覆盖任何public的或protected的方法或对象或接口java.lang.annotation.annotation中给出的对象
  • 注释类型声明T直接或间接地包含类型为T的元素
//For example, this is illegal:
@interface SelfRef { SelfRef value(); }
//and so is this:
@interface Ping { Pong value(); }
@interface Pong { Ping value(); }

按照惯例 单个元素注释类型中(只有一个元素) 这个元素需命名为value()

以下的单个元素注解类型声明均合法

//The convention is illustrated in the following annotation type declaration:

/**
 * Associates a copyright notice with the annotated API element.
 */
@interface Copyright {
    String value();
}
//The following annotation type declaration defines a single-element annotation type whose sole element has an array type:

/**
 * Associates a list of endorsers with the annotated class.
 */
@interface Endorsers {
    String[] value();
}
//The following annotation type declaration shows a Class annotation whose value is restricted by a bounded wildcard:

interface Formatter {}

// Designates a formatter to pretty-print the annotated class
@interface PrettyPrinter {
    Class<? extends Formatter> value();
}
//Note that the grammar for annotation type declarations permits other element declarations besides method declarations. For example, one might choose to declare a nested enum for use in conjunction with an annotation type:

@interface Quality {
    enum Level { BAD, INDIFFERENT, GOOD }
    Level value();
}

除此之外 也有复杂的声明

/**
 * A person's name.  This annotation type is not designed
 * to be used directly to annotate program elements, but to
 * define elements of other annotation types.
 */
@interface Name {
    String first();
    String last();
}
/**
 * Indicates the author of the annotated program element.
 */
@interface Author {
    Name value();
}
/**
 * Indicates the reviewer of the annotated program element.
 */
@interface Reviewer {
    Name value();
}

注解类型的缺省值

注解类型有时候可能会需要一个缺省值 这个可以通过一个有default关键字的变量表确定 或者是为元素一一指定

当然默认值不相称时会出现编译时错误

@interface RequestForEnhancementDefault {
    int    id();       // No default - must be specified in 
                       // each annotation
    String synopsis(); // No default - must be specified in 
                       // each annotation
    String engineer()  default "[unassigned]";
    String date()      default "[unimplemented]";
}

另一种设置default的方式会在之后进行展示

预定义的注解类型

在JavaSE中 已经有几个注解类型被定义了 这些预定义有特殊的语义 这里不展示完整的API规范 只是展示一些实现

@Target

注释类型java.lang.annotation.target被用于表明注释类型适用于什么样的程序单元

java.lang.annotation.target有一个元素 为ElementType[]类(这是一个枚举类型)

如果一个枚举类型常量在一个注释对应类型为java.lang.annotation.target的注释类型中出现了不止一次 会发生编译错误

ElementType[]类接受的参数有以下这些:

ElementType.ANNOTATION_TYPE //注解类型声明
ElementType.CONSTRUCTOR     //构造器声明
ElementType.FIELD           //属性声明
ElementType.LOCAL_VARIABLE  //局部变量声明
ElementType.METHOD          //方法声明
ElementType.PACKAGE         //包声明
ElementType.PARAMETER       //普通变量声明
ElementType.TYPE            //泛型声明 可以是类 接口 或者枚举类型声明
ElementType.TYPE_PARAMETER  //Type的变量声明
ElementType.TYPE_USE        //Type的调用声明
@Retention

注释类型java.lang.annotation.retention预注解类型用于表明这个注解在代码的那个阶段生效

注意这个预注解类型仅仅在直接作为注解时才会生效 在另一个注解中使用这个注解会失效

同样的 这个注解类型可以接受一个枚举参数RetentionPolicy[]

RetentionPolicy.CLASS       //注解会被编译器保留在class文件中但是在vm运行时不会保留
RetentionPolicy.RUNTIME     //保留在class文件中并且vm运行时也会保留
RetentionPolicy.SOURCE      //不保留

在理解这两个最重要的注解类型之后 我们举个例子 测试某个函数的运行时间

/**
 * 自定义注解@TimeTest
 * 目标为方法
 * 执行时间为运行时
 */

package com.individual.george.TimeTest;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TimeTest {
}

因为我们是要获取注解的方法 所以这里暂时不在内部声明变量

在Junit内主要是使用Java invoke方法来获取方法 我们做适当的重试

关于映射的相关知识不在赘述 自行百度

package com.individual.george.TimeTest;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
 * timeTestFunc is used to calculate the time method spent when it was finished
 * @parm Class c  which the method belongs to.
 * @return void
 */
public class timeTestFunc {
    public static void run(Class c) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        Method[] methods = c.getDeclaredMethods();
        List<Method> testList = new ArrayList<>();
        for (Method method: methods){
            if(method.isAnnotationPresent(TimeTest.class)){
                testList.add(method);
            }
        }
        Object object = c.newInstance();
        for(Method method: testList){
            testWrapper(method,c);
        }
    }
    private static void testWrapper(Method method, Class c) throws InvocationTargetException, IllegalAccessException, InstantiationException{
        long startTime = 0;
        long endTime = 0;
        if(method!=null && c!=null){
            Object object = c.newInstance();
            startTime = System.currentTimeMillis();
            method.invoke(object);
            endTime = System.currentTimeMillis();
            System.out.println("Run time[" +  method  + "]:" + (endTime - startTime) + "ms");
        }
    }
}

这里简单的通过反射机制 把注解的方法反射运行 算出前后时间进行输出 相当于进行了一次时间测试

但是这里和Junit内部的实现不同 这里仅仅是简单的尝试 之后会进行重现

再写一个main 然后进行运行

package testExamples;

import com.individual.george.TimeTest.timeTestFunc;
import java.lang.reflect.InvocationTargetException;

public class Main {
    public static void main(String[] args) throws InstantiationException, IllegalAccessException, InvocationTargetException{
        timeTestFunc.run(Test.class);
    }
}

最后随意写一个类 进行注解

package testExamples;

import com.individual.george.TimeTest.TimeTest;

public class Test {

    @TimeTest
    public void sum(){
        int sumNum = 0;
        for(int i = 0; i < 2000; i++){
            sumNum += i;
        }
        System.out.println(sumNum);
    }
}

最后的输出

1999000
Run time[public void testExamples.Test.sum()]:0ms

简单的使用了注解机制 之后会更新Junit机制的重现

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值