第一节:定义注解
定义新的Annotation类型使用@interface关键字(在原有interface关键字前增加@符号)。定义一个新的Annotation类型与定义一个接口很像,例如:
public @interface Test{
}
定义完该Annotation后,就可以在程序中使用该Annotation。使用Annotation,非常类似于public、final这样的修饰符,通常,会把Annotation另放一行,并且放在所有修饰符之前。例如:
@Test
public class MyClass{
....
}
根据注解是否包含成员变量,可以把注解分为如下两类:
标记注解:没有成员变量的Annotation被称为标记。这种Annotation仅用自身的存在与否来为我们提供信息,例如@override等。
元数据注解:包含成员变量的Annotation。因为它们可以接受更多的元数据,因此被称为元数据Annotation。 成员以无参数的方法的形式被声明,其方法名和返回值定义了该成员变量的名字和类型。
成员变量
Annotation只有成员变量,没有方法。Annotation的成员变量在Annotation定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。例如:
public @interface MyTag{
string name();
int age();
}
示例中定义了2个成员变量,这2个成员变量以方法的形式来定义。
一旦在Annotation里定义了成员变量后,使用该Annotation时就应该为该Annotation的成员变量指定值。例如:
public class Test{
@MyTag(name="红薯",age=30)
public void info(){
......
}
}
也可以在定义Annotation的成员变量时,为其指定默认值,指定成员变量默认值使用default关键字。示例:
public @interface MyTag{
string name() default "我兰";
int age() default 18;
}
如果Annotation的成员变量已经指定了默认值,使用该Annotation时可以不为这些成员变量指定值,而是直接使用默认值。例如:
public class Test{
@MyTag
public void info(){
......
}
}
如果注解只有一个成员变量,则建议取名为value,在使用时可用忽略成员名和赋值符=
注解的语法与定义形式
(1)以@interface关键字定义
(2)注解需要标明注解的生命周期,注解的修饰目标等信息,这些信息是通过元注解实现。上面的语法不容易理解,下面通过例子来说明一下,这个例子就是Target注解的源码,
源码分析如下:第一:元注解@Retention,成员value的值为RetentionPolicy.RUNTIME。第二:元注解@Target,成员value是个数组,用{}形式赋值,值为ElementType.ANNOTATION_TYPE第三:成员名称为value,类型为ElementType[]另外,需要注意一下,如果成员名称是value,在赋值过程中可以简写。如果成员类型为数组,但是只赋值一个元素,则也可以简写。如上面的简写形式为:@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.ANNOTATION_TYPE)
(3)注解中可以包含枚举
package com.annotation.test;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FruitColor {
enum Color{RED,YELLOW,WHITE}
Color fruitColor() default Color.RED;
}
注解
修饰了类/方法/成员变量等之后,这些注解不会自己生效,必须由这些注解的开发者提供相应的工具来提取并处理注解信息(当然,只有当定义注解时使用了@Retention(RetentionPolicy.RUNTIME)
修饰,JVM才会在装载class文件时提取保存在class文件中的注解,该注解才会在运行时可见,这样我们才能够解析).Java使用Annotation
接口来代表程序元素前面的注解,该接口是所有注解的父接口。AnnotatedElement
接口代表程序中可以接受注解的程序元素.AnnotatedElement
接口的实现类有:Class(类元素)、Field(类的成员变量元素)、Method(类的方法元素)、Package(包元素),每一个实现类代表了一个可以接受注解的程序元素类型。这样, 我们只需要获取到Class、
Method、
Filed
等这些实现了AnnotatedElement
接口的类的实例,通过该实例对象调用该类中的方法(AnnotatedElement接口中抽象方法的重写) 就可以获取到我们想要的注解信息了。
获得Class类的实例有三种方法:
(1)利用对象调用getClass()方法获得Class实例
(2)利用Class类的静态的forName()方法,使用类名获得Class实例
(3)运用.class的方式获得Class实例,如:类名.class
AnnotatedElement接口提供的抽象方法(在该接口的实现类中重写了这些方法):
1.T getAnnotation(Class annotationClass)
为泛型参数声明,表明A的类型只能是Annotation类型或者是Annotation的子类。
功能:返回该程序元素上存在的、指定类型的注解,如果该类型的注解不存在,则返回null
2. Annotation[] getAnnotations()
功能:返回此元素上存在的所有注解,包括没有显示定义在该元素上的注解(继承得到的)。(如果此元素没有注释,则返回长度为零的数组。)
3.
功能:这是Java8新增的方法,该方法返回直接修饰该程序元素、指定类型的注解(忽略继承的注解)。如果该类型的注解不存在,返回null.
4. Annotation[] getDeclaredAnnotations()
功能:返回直接存在于此元素上的所有注解,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)
5. boolean isAnnotationPresent(Class extends Annotation> annotationClass)
功能:判断该程序元素上是否存在指定类型的注解,如果存在则返回true,否则返回false。
6.
功能: 因为java8增加了重复注解功能,因此需要使用该方法获得修饰该程序元素、指定类型的多个注解。
7.
功能: 因为java8增加了重复注解功能,因此需要使用该方法获得直接修饰该程序元素、指定类型的多个注解。
Class提供了getMethod()、getField()以及getConstructor()方法(还有其他方法),这些方法分别获取与方法、域变量以及构造函数相关的信息,这些方法返回Method、Field 以及Constructor类型的对象。
@Target(ElementType.Method)
@Retention(RetentionPopicy.RUNTIME)
public @interface MyTag
{
string name() default "yeeku";
int age() default 32;
}
public class Test
{
@MyTag
public void info()
{
}
}
获取Test类的info方法里的所有注解,并打印这些注解
Annotation [] aArray=Class.forName("Test").getMethod("info").getAnnotations(); //获取Class实例的方法1
for(Annotation an : aArray)
{
system.out.println(an);
}
如果需要获取某个注解里的元数据则可以将注解强制类型转换,转换成所需的注解类型,然后通过注解对象的抽象方法来访问这些元数据
获取tt对象的info方法所包含的所有注解
Annotation [] annotation=tt.getClass().getMethod("info").getAnnotations(); //获取Class实例的方法2
for(Annotation tag : annotation)
{
//如果tag注解是MyTag类型
system.out.println("tag.name(): "+((MyTag)tag).name());
system.out.println("tag.age(): "+((MyTag)tag).age());
}
我们用@Testable
标记哪些方法是可测试的, 只有被@Testable
修饰的方法才可以被执行.
1
2
3
4
5
6
7
8
|
@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
|
public
class
TestableProcessor {
public
static
void
process(String clazz)
throws
ClassNotFoundException, IllegalAccessException, InstantiationException {
int
passed =
0
;
int
failed =
0
;
for
(Method method : Class.forName(clazz).getMethods()) {
if
(method.isAnnotationPresent(Testable.
class
)) { //获取Class实例的方法3
try
{
method.invoke(null);
++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
|
@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
|
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
;
}
//下同
}
|