Java注解机制的理解和运用
March 20th, 2018
标签(空格分隔): Java程序构造
声明
以下的所有代码案例 均来自于官方的规范手册 个别地方会是我自己的运用 我会给出注释
写在前面
最近使用了很多的Junit 在自定义的测试函数前要加上@Test
在注释spec中要写出@param
@return
在程序的开头要声明作者@author
和@date
这些东西统称为注解(Annotation) 因为最近想要写一个专属于自己的jar包 想要使用这种方式来进行调用 比如所计算程序运行的效率 输出运行的时间和满意度
看到了很多解释这个注解的博文 但是都没有说道点上 自定义注解的实现不够清晰 于是自己写了这篇文章
什么是注解
简单地说 就是@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机制的重现