注解对代码的语意没有直接影响, 他们只负责提供信息给相关的程序使用. 注解永远不会改变被注解代码的含义, 但可以通过工具对被注解的代码进行特殊处理.
注解 |
说明 |
|
重写 |
|
已过时 |
|
压制编辑器警告 |
|
修饰”堆污染”警告 |
|
Java8特有的函数式接口 |
value
特权value
成员变量指定值, 则使用注解时可以直接在该注解的括号中指定value值, 而无需使用name=value
的形式. 如@SuppressWarnings("unchecked")
(SuppressWarnings的各种参数@Override
注解: 如果在每个方法中使用Override
注解来声明要覆盖父类声明, 编译器就可以替你防止大量的错误.元Annotation
用于修饰其他的Annotation定义.
元注解 |
释义 |
|
注解保留策略 |
|
注解修饰目标 |
|
注解文档提取 |
|
注解继承声明 |
@Retention
注解的保留策略
1
2
3
4
5
6
|
@Documented
@Retention
(RetentionPolicy.RUNTIME)
@Target
(ElementType.ANNOTATION_TYPE)
public
@interface
Retention {
RetentionPolicy value();
}
|
value为SOURCE
, CLASS
, RUNTIME
三值之一:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public
enum
RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
|
@Target
指定Annotation可以放置的位置(被修饰的目标)
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
|
@Documented
@Retention
(RetentionPolicy.RUNTIME)
@Target
(ElementType.ANNOTATION_TYPE)
public
@interface
Target {
ElementType[] value();
}
public
enum
ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE
}
|
@Documented
指定被修饰的该Annotation可以被javadoc工具提取成文档.@Inherited
指定被修饰的Annotation将具有继承性@Xxx
注解(该Annotation
使用了@Inherited
修饰)修饰, 则其子类自动被@Xxx
注解修饰.Annotation
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
|
/**
* Created by jifang on 15/12/22.
*/
@Inherited
@Target
({ElementType.ANNOTATION_TYPE, ElementType.TYPE})
@Retention
(RetentionPolicy.RUNTIME)
public
@interface
Testable {
}
Client
public
class
Client {
@Test
public
void
client(){
new
SubClass();
}
}
@Testable
class
SupperClass{
}
class
SubClass
extends
SupperClass{
public
SubClass() {
for
(Annotation annotation : SubClass.
class
.getAnnotations()){
System.out.println(annotation);
}
}
}
|
Annotation
是否包含成员变量,可以把Annotation分为两类:
Annotation
: 没有成员变量的Annotation; 这种Annotation仅利用自身的存在与否来提供信息;Annotation
: 包含成员变量的Annotation; 它们可以接受(和提供)更多的元数据;@interface
关键字, 其定义过程与定义接口非常类似(见上面的@Testable
), 需要注意的是:Annotation的成员变量在Annotation定义中是以无参的方法形式来声明的, 其方法名
和返回值类型
定义了该成员变量的名字
和类型
, 而且我们还可以使用default
关键字为这个成员变量设定默认值.
1
2
3
4
5
6
7
8
|
@Inherited
@Retention
(RetentionPolicy.RUNTIME)
@Target
({ElementType.METHOD, ElementType.TYPE})
public
@interface
Tag {
String name()
default
"该叫啥才好呢?"
;
String description()
default
"这家伙很懒, 啥也没留下..."
;
}
|
Annotation
这个接口, 因此自定义注解中包含了Annotation
接口中所有的方法;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public
interface
Annotation {
/**
* @return true if the specified object represents an annotation
* that is logically equivalent to this one, otherwise false
*/
boolean
equals(Object obj);
/**
* @return the hash code of this annotation
*/
int
hashCode();
/**
* @return a string representation of this annotation
*/
String toString();
/**
* Returns the annotation type of this annotation.
*/
Class<?
extends
Annotation> annotationType();
}
|
Annotation
修饰了类/方法/成员变量等之后,这些Annotation不会自己生效,必须由这些注解的开发者提供相应的工具来提取并处理Annotation信息(当然,只有当定义Annotation时使用了@Retention(RetentionPolicy.RUNTIME)
修饰,JVM才会在装载class文件时提取保存在class文件中的Annotation,该Annotation才会在运行时可见,这样我们才能够解析).Annotation
接口来代表程序元素前面的注解, 用AnnotatedElement
接口代表程序中可以接受注解的程序元素.像Class
Constructor
FieldMethod
Package
这些类都实现了AnnotatedElement
接口.
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
|
public
final
class
Class<T>
implements
java.io.Serializable,
java.lang.reflect.GenericDeclaration,
java.lang.reflect.Type,
java.lang.reflect.AnnotatedElement {
...
}
public
interface
AnnotatedElement {
/**
* Returns true if an annotation for the specified type
* is present on this element, else false. This method
* is designed primarily for convenient access to marker annotations.
*/
boolean
isAnnotationPresent(Class<?
extends
Annotation> annotationClass);
/**
* Returns this element's annotation for the specified type if
* such an annotation is present, else null.
*/
<T
extends
Annotation> T getAnnotation(Class<T> annotationClass);
/**
* Returns all annotations present on this element.
*/
Annotation[] getAnnotations();
/**
* Returns all annotations that are directly present on this
* element. Unlike the other methods in this interface, this method
* ignores inherited annotations. (Returns an array of length zero if
* no annotations are directly present on this element.) The caller of
* this method is free to modify the returned array; it will have no
* effect on the arrays returned to other callers.
*/
Annotation[] getDeclaredAnnotations();
}
|
这样, 我们只需要获取到Class
Method
Filed
等这些实现了AnnotatedElement
接口的类实例, 就可以获取到我们想要的注解信息了.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
/**
* Created by jifang on 15/12/22.
*/
public
class
Client {
@Test
public
void
client()
throws
NoSuchMethodException {
Annotation[] annotations =
this
.getClass().getMethod(
"client"
).getAnnotations();
for
(Annotation annotation : annotations) {
System.out.println(annotation.annotationType().getName());
}
}
}
|
如果需要获取某个注解中的元数据,则需要强转成所需的注解类型,然后通过注解对象的抽象方法来访问这些元数据:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@Tag
(name =
"client"
)
public
class
Client {
@Test
public
void
client()
throws
NoSuchMethodException {
Annotation[] annotations =
this
.getClass().getAnnotations();
for
(Annotation annotation : annotations) {
if
(annotation
instanceof
Tag) {
Tag tag = (Tag) annotation;
System.out.println(
"name: "
+ tag.name());
System.out.println(
"description: "
+ tag.description());
}
}
}
}
|
我们用@Testable
标记哪些方法是可测试的, 只有被@Testable
修饰的方法才可以被执行.
1
2
3
4
5
6
7
8
|
/**
* Created by jifang on 15/12/27.
*/
@Inherited
@Target
(ElementType.METHOD)
@Retention
(RetentionPolicy.RUNTIME)
public
@interface
Testable {
}
|
如下定义TestCase
测试用例定义了6个方法, 其中有4个被@Testable
修饰了:
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
|
public
class
TestCase {
@Testable
public
void
test1() {
System.out.println(
"test1"
);
}
public
void
test2()
throws
IOException {
System.out.println(
"test2"
);
throw
new
IOException(
"我test2出错啦..."
);
}
@Testable
public
void
test3() {
System.out.println(
"test3"
);
throw
new
RuntimeException(
"我test3出错啦..."
);
}
public
void
test4() {
System.out.println(
"test4"
);
}
@Testable
public
void
test5() {
System.out.println(
"test5"
);
}
@Testable
public
void
test6() {
System.out.println(
"test6"
);
}
}
|
为了让程序中的这些注解起作用, 必须为这些注解提供一个注解处理工具.
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
|
/**
* Created by jifang on 15/12/27.
*/
public
class
TestableProcessor {
public
static
void
process(String clazz)
throws
ClassNotFoundException, IllegalAccessException, InstantiationException {
int
passed =
0
;
int
failed =
0
;
Object obj = Class.forName(clazz).newInstance();
for
(Method method : Class.forName(clazz).getMethods()) {
if
(method.isAnnotationPresent(Testable.
class
)) {
try
{
method.invoke(obj);
++passed;
}
catch
(IllegalAccessException | InvocationTargetException e) {
System.out.println(
"method "
+ method.getName() +
" execute error: < "
+ e.getCause() +
" >"
);
e.printStackTrace(System.out);
++failed;
}
}
}
System.out.println(
"共运行"
+ (failed + passed) +
"个方法, 成功"
+ passed +
"个, 失败"
+ failed +
"个"
);
}
public
static
void
main(String[] args)
throws
ClassNotFoundException, InstantiationException, IllegalAccessException {
TestableProcessor.process(
"com.feiqing.annotation.TestCase"
);
}
}
|
前面介绍的只是一个标记
Annotation
,程序通过判断Annotation是否存在来决定是否运行指定方法,现在我们要针对只在抛出特殊异常时才成功
添加支持,这样就用到了具有成员变量的注解了:
1
2
3
4
5
6
7
8
9
|
/**
* Created by jifang on 15/12/28.
*/
@Inherited
@Target
(ElementType.METHOD)
@Retention
(RetentionPolicy.RUNTIME)
public
@interface
TestableException {
Class<?
extends
Throwable>[] value();
}
|
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
|
/**
* Created by jifang on 15/12/27.
*/
public
class
TestCase {
public
void
test1() {
System.out.println(
"test1"
);
}
@TestableException
(ArithmeticException.
class
)
public
void
test2()
throws
IOException {
int
i =
1
/
0
;
System.out.println(i);
}
@TestableException
(ArithmeticException.
class
)
public
void
test3() {
System.out.println(
"test3"
);
throw
new
RuntimeException(
"我test3出错啦..."
);
}
public
void
test4() {
System.out.println(
"test4"
);
}
@TestableException
({ArithmeticException.
class
, IOException.
class
})
public
void
test5()
throws
FileNotFoundException {
FileInputStream stream =
new
FileInputStream(
"xxxx"
);
}
@Testable
public
void
test6() {
System.out.println(
"test6"
);
}
}
|
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
|
public
class
TestableExceptionProcessor {
public
static
void
process(String clazz)
throws
ClassNotFoundException, IllegalAccessException, InstantiationException {
int
passed =
0
;
int
failed =
0
;
Object obj = Class.forName(clazz).newInstance();
for
(Method method : Class.forName(clazz).getMethods()) {
if
(method.isAnnotationPresent(TestableException.
class
)) {
try
{
method.invoke(obj,
null
);
// 没有抛出异常(失败)
++failed;
}
catch
(InvocationTargetException e) {
// 获取异常的引发原因
Throwable cause = e.getCause();
int
oldPassed = passed;
for
(Class excType : method.getAnnotation(TestableException.
class
).value()) {
// 是我们期望的异常类型之一(成功)
if
(excType.isInstance(cause)) {
++passed;
break
;
}
}
// 并不是我们期望的异常类型(失败)
if
(oldPassed == passed) {
++failed;
System.out.printf(
"Test <%s> failed <%s> %n"
, method, e);
}
}
}
}
System.out.println(
"共运行"
+ (failed + passed) +
"个方法, 成功"
+ passed +
"个, 失败"
+ failed +
"个"
);
}
public
static
void
main(String[] args)
throws
IllegalAccessException, InstantiationException, ClassNotFoundException {
process(
"com.feiqing.annotation.TestCase"
);
}
}
|
下面通过使用Annotation简化事件编程
, 在传统的代码中总是需要通过addActionListener
方法来为事件源绑定事件监听器:
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
|
/**
* Created by jifang on 15/12/27.
*/
public
class
SwingPro {
private
JFrame mainWin =
new
JFrame(
"使用注解绑定事件监听器"
);
private
JButton ok =
new
JButton(
"确定"
);
private
JButton cancel =
new
JButton(
"取消"
);
public
void
init() {
JPanel jp =
new
JPanel();
// 为两个按钮设置监听事件
ok.addActionListener(
new
OkListener());
cancel.addActionListener(
new
CancelListener());
jp.add(ok);
jp.add(cancel);
mainWin.add(jp);
mainWin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainWin.pack();
mainWin.setVisible(
true
);
}
public
static
void
main(String[] args) {
new
SwingPro().init();
}
}
class
OkListener
implements
ActionListener {
@Override
public
void
actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(
null
,
"你点击了确认按钮!"
);
}
}
class
CancelListener
implements
ActionListener {
@Override
public
void
actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(
null
,
"你点击了取消按钮!"
);
}
}
|
下面我们该用注解绑定监听器:
1
2
3
4
5
6
7
8
9
|
/**
* Created by jifang on 15/12/27.
*/
@Inherited
@Target
(ElementType.FIELD)
@Retention
(RetentionPolicy.RUNTIME)
public
@interface
ActionListenerFor {
Class<?
extends
ActionListener> listener();
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
/**
* Created by jifang on 15/12/27.
*/
public
class
ActionListenerInstaller {
public
static
void
install(Object targetObject)
throws
IllegalAccessException, InstantiationException {
for
(Field field : targetObject.getClass().getDeclaredFields()) {
// 如果该成员变量被ActionListenerFor标记了
if
(field.isAnnotationPresent(ActionListenerFor.
class
)) {
// 设置访问权限
field.setAccessible(
true
);
// 获取到成员变量的值
AbstractButton targetButton = (AbstractButton) field.get(targetObject);
// 获取到注解中的Listener
Class<?
extends
ActionListener> listener = field.getAnnotation(ActionListenerFor.
class
).listener();
// 添加到成员变量中
targetButton.addActionListener(listener.newInstance());
}
}
}
}
|
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
|
public
class
SwingPro {
private
JFrame mainWin =
new
JFrame(
"使用注解绑定事件监听器"
);
/**
* 使用注解设置Listener
*/
@ActionListenerFor
(listener = OkListener.
class
)
private
JButton ok =
new
JButton(
"确定"
);
@ActionListenerFor
(listener = CancelListener.
class
)
private
JButton cancel =
new
JButton(
"取消"
);
public
SwingPro init() {
JPanel jp =
new
JPanel();
// 使得注解生效
try
{
ActionListenerInstaller.install(
this
);
}
catch
(IllegalAccessException | InstantiationException e) {
e.printStackTrace(System.out);
}
jp.add(ok);
jp.add(cancel);
mainWin.add(jp);
mainWin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainWin.pack();
mainWin.setVisible(
true
);
return
this
;
}
//下同
}
|
在Java5到Java7这段时间里, 同一个程序元素前只能使用一个相同类型的
Annotation
; 如果需要在同一个元素前使用多个相同的Annotation, 则必须使用Annotation容器(在Java8中, 对这种情况做了改善, 但其实也只是一种写法上的简化, 其本质还是一样的).由于在实际开发中,Java8还未大面积的使用, 因此在此只介绍Java7中重复注解定义与使用.
1
2
3
4
5
6
7
8
9
10
11
|
/**
* Created by jifang on 15/12/27.
*/
@Inherited
@Retention
(RetentionPolicy.RUNTIME)
public
@interface
Table {
String name()
default
"表名是啥?"
;
String description()
default
"这家伙很懒, 啥也没留下..."
;
}
|
1
2
3
4
5
6
|
@Inherited
@Retention
(RetentionPolicy.RUNTIME)
public
@interface
Tables {
Table[] value();
}
|
注意: 容器
注解的保留期必须比它所包含的注解的保留期更长, 否则JVM会丢弃容器
, 相应的注解也就丢失了.
Table
注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@Tables
({
@Table
(name =
"t_user"
, description =
"用户表"
),
@Table
(name =
"t_feed"
, description =
"动态表"
)
})
public
class
Client {
@Test
public
void
client() {
Tables tableArray =
this
.getClass().getAnnotation(Tables.
class
);
Table[] tables = tableArray.value();
for
(Table table : tables) {
System.out.println(table.name() +
" : "
+ table.description());
}
}
}
|
在Java8中, 可以直接使用
1
2
|
@Table
(name =
"t_user"
, description =
"用户表"
)
@Table
(name =
"t_feed"
, description =
"动态表"
)
|
的形式来注解Client
, 但@Tables
还是需要开发者来写的, 由此可以看出, 重复注解只是一种简化写法, 这种写法只是一种假象: 多个重复注解其实会被作为容器
注解的value成员.
参考:
Effective Java
疯狂Java讲义
Java核心技术