Java Annotation

在Java中,Annotation(注解)是一种可添加到源码中的句法元数据(Java annotation),类、方法、变量、参数以及包都可以进行注解。

注解主要有如下几个用途:

  • 为编译器提供信息: 编译器使用annotation来检测错误或者消除警告;
  • 编译或者部署时处理: 可以利用注解信息产生代码,XML文件等;
  • 运行时处理: 可在运行时检查注解,产生相应的代码;

Java预定义注解类型

Java中java.lang内置有@Deprecated, @Override,以及@SuppressWarnings三个常用的注解:

  • @Deprecated: 用于标识一个元素弃用了,不再使用;

    1
    2
    3
    4
    5
        
    @Documented
    @Retention(value=RUNTIME)
    @Target(value={CONSTRUCTOR,FIELD,LOCAL_VARIABLE,METHOD,PACKAGE,PARAMETER,TYPE})
    public @interface Deprecated{}
  • @Override: 该注解用于标识当前方法重载了父类中的方法,如果一个标识了@Override的方法没有重载父类中的方法,编译器会产生错误;

1
2
3
4

@Target(value=METHOD)
@Retention(value=SOURCE)
public @interface Override{}
  • @SuppressWarnings:告知编译器忽略特定的警告,例如@SuppressWarnings("unchecked")让编译器忽略unchecked警告;@SuppressWarnings("deprecation")则用于忽略某个元素已经废弃的警告。

    1
    2
    3
    4
    5
    6
    7
        

    @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
    @Retention(RetentionPolicy.SOURCE)
    public @interface SuppressWarnings {
    String[] value();
    }

除了上述注解外, Java还提供了应用于其他注解的注解,被称之为元注解(meta-annotations),如@Rentention, @Documented, @Target,@Inherited:

Annotation interface Applicable to Purpose
Target annotations 指定注解使用的元素类型(见下)
Retention annotations 指定注解使用的方式,需要保留的时间,RetentionPolicy.SOURCERetentionPolicy.CLASSRetentionPolicy.RUNTIME
Documented annotations 使用了该注解的注解,都会用Javadoc工具进行处理,然后输出相应的文档
Inherited annotations 标识某个注解是否能被子类锁继承
Resource class or interface 声明单个资源
Resources class or interface 声明多个资源

Target中指定了注解的目标类型,java.lang.annotation中定义了枚举类型ElementType用于表示注解所使用的元素类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
    
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,

/** Method declaration */
METHOD,

/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,

/** Local variable declaration */
LOCAL_VARIABLE,

/** Annotation type declaration */
ANNOTATION_TYPE,

/** Package declaration */
PACKAGE,

/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,

/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}

如何自定义注解类型

Java Annotation使用关键字@interface来声明,自定义时需要注意几点:

  • 只允许使用publicdefault修饰符;
  • 对于注解的属性值,只能使用public或者default修饰符;
  • 注解的属性值类型,只能使用基本类型,StringClass,EnumAnnotation以及上述类型的一维数组;
  • 可以为属性值定义默认值;

这里,来看下Android中的一个注解示例@RequestPermission,当某个元素需要请求系统权限时,可以使用该注解标识:从定义可以看到,@RequestPermission只是在源码(不是运行时或者class文件中)中使用,可用于方法、构造函数、变量、参数以及注解等几种类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

@Retention(SOURCE)
@Target({ANNOTATION_TYPE,METHOD,CONSTRUCTOR,FIELD,PARAMETER})
public @interface RequiresPermission {
/**
* The name of the permission that is required, if precisely one permission
* is required. If more than one permission is required, specify either
* {@link #allOf()} or {@link #anyOf()} instead.
*/
String value() default "";

/**
* Specifies a list of permission names that are all required.
*/
String[] allOf() default {};

/**
* Specifies a list of permission names where at least one is required
*/
String[] anyOf() default {};

/**
* If true, the permission may not be required in all cases (e.g. it may only be
* enforced on certain platforms, or for certain call parameters, etc.
*/
boolean conditional() default false;

/**
* Specifies that the given permission is required for read operations.
*
* When specified on a parameter, the annotation indicates that the method requires
* a permission which depends on the value of the parameter (and typically
* the corresponding field passed in will be one of a set of constants which have
* been annotated with a <code>@RequiresPermission</code> annotation.)
*/
@Target({FIELD, METHOD, PARAMETER})
@interface Read {
RequiresPermission value() default @RequiresPermission;
}

/**
* Specifies that the given permission is required for write operations.
*
* When specified on a parameter, the annotation indicates that the method requires
* a permission which depends on the value of the parameter (and typically
* the corresponding field passed in will be one of a set of constants which have been annotated with a <code>@RequiresPermission</code> annotation.)
*/
@Target({FIELD, METHOD, PARAMETER})
@interface Write {
RequiresPermission value() default @RequiresPermission;
}
}

源码: /android/frameworks/base/core/java/android/annotation/RequiresPermission.java

应用示例

那么,如何使用annotation?使用过JUnit测试框架的应该了解,JUnit就是使用注解来创建测试用例,并执行这些用例的。现在,我们就来模仿JUnit来写一个自定义的测试注解@Test。假定有一个类MathsOperation, 该类支持add以及divide两个数学操作,对于除法操作,如果除数为零,则抛出一个自定义的InvalidParamterException的异常, 因此如果任何除数为零的操作,都会抛出异常,此时可通过检测该异常来判断测试用例是否通过。

  • 定义@Test注解Test.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

package com.examples.jason.javecore.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;

@Documented
@Inherited
@Target (value=METHOD)
@Retention (RetentionPolicy.RUNTIME)
public @interface Test {

/**
* This annotation attribute is used whether method throw assigned custom *exception
* or not. If it throws custom exception, it means test case pass
* @return
*/
public Class<? extends Exception> expected() default java.lang.Exception.class;
}
  • InvalidParameterException.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

package com.examples.jason.javecore.annotation;

/**
* The class <code>InvalidParameterException</code> is a subclass of <code>Exception</code> class;
* This class is used to throw exception when method arguments are invalid
* @author Jason
*
*/
@SuppressWarnings("serial")
public class InvalidParameterException extends Exception{

private String message = null;

public InvalidParameterException(){
super();
}

public InvalidParameterException(String msg){
this.message = msg;
}

public InvalidParameterException(Throwable t){
super(t);
}

@Override
public String toString(){
return this.message;
}

@Override
public String getMessage(){
return this.message;
}
}
  • MathsOperation.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23


package com.examples.jason.javecore.annotation;

/**
* math operation class, which support two math operations
* @author Jason
*
*/
public class MathsOperation {

public int sum(int a, int b){
return a + b;
}

public float divide(int a, int b) throws InvalidParameterException{
if(b == 0){
throw new InvalidParameterException();
}

return (float)(a/b);
}
}
  • MathUnitTests.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

package com.examples.jason.javecore.annotation;

/**
* All test cases are written here with @Test annotation
*
*/
public class MathUnitTests {

@Test(expected = InvalidParameterException.class)
public void testDivide() throws InvalidParameterException{
MathsOperation mp = new MathsOperation();
mp.divide(10, 0);
}

@Test
public void testSum(){
MathsOperation mp = new MathsOperation();
int s = mp.sum(10, 3);
if(s == 13){
System.out.println("Sum Test Case Pass, output value is " + s);
}
}
}
  • UnitTestRunner.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

package com.examples.jason.javecore.annotation;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
* This class is test suite runner, which will runs all test cases
* provided in given test class. This class particularly check custom exception thrown by method.
* If thrown exception match with expected exception mentioned on attributes of @Test annotation, that means
* test case is pass. This same scenario is used in JUnit as well.
*
*/
public class UnitTestRunner {

public void runUnitTests(String className){
try{
Class<?> testClass = Class.forName(className);
Object obj = testClass.newInstance();
Method[] methods = testClass.getMethods();

for(Method method : methods){
if(method.isAnnotationPresent(com.examples.jason.javecore.annotation.Test.class)){
Test annotation = method.getAnnotation(Test.class);
Class<? extends Exception> expectedClass = annotation.expected();
if(expectedClass != null){
try{
method.invoke(obj);
} catch(InvocationTargetException e){
if(e.getTargetException().getClass() == expectedClass){
System.out.println("Test Case Pass with InvalidParameterException");
} else{
System.out.println("Test Case Fail with an exception: " + e.getMessage());
}
} catch(Exception e){
System.out.println("Test Case Fail with an exception: " + e.getMessage());
}
}
}
}
} catch (ClassNotFoundException cnfe){
cnfe.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}

public static void main(String[] args){
UnitTestRunner utRunner = new UnitTestRunner();
utRunner.runUnitTests("com.examples.jason.javecore.annotation.MathUnitTests");
}

}

参考文献