Java注解实践--annotation学习三
注解对代码的语意没有直接影响, 他们只负责提供信息给相关的程序使用. 注解永远不会改变被注解代码的含义, 但可以通过工具对被注解的代码进行特殊处理.
JDK 基本Annotation
注解 |
说明 |
|
重写 |
|
已过时 |
|
压制编辑器警告 |
|
修饰”堆污染”警告 |
|
Java8特有的函数式接口 |
value
特权
如果使用注解时只需要为value
成员变量指定值, 则使用注解时可以直接在该注解的括号中指定value值, 而无需使用name=value
的形式. 如@SuppressWarnings("unchecked")
(SuppressWarnings的各种参数
请参考解析 @SuppressWarnings的各种参数)- 请坚持使用
@Override
注解: 如果在每个方法中使用Override
注解来声明要覆盖父类声明, 编译器就可以替你防止大量的错误.
JDK 元Annotation
元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
这个接口, 因此自定义注解中包含了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信息(当然,只有当定义Annotation时使用了@Retention(RetentionPolicy.RUNTIME)
修饰,JVM才会在装载class文件时提取保存在class文件中的Annotation,该Annotation才会在运行时可见,这样我们才能够解析). - Java使用
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()); } } } } |
模拟Junit框架
我们用@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(); } |
- TestCase
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中重复注解定义与使用.
- Table Annotation定义(代表数据库表)
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 "这家伙很懒, 啥也没留下..." ; } |
- Table 容器
1
2
3
4
5
6
|
@Inherited @Retention (RetentionPolicy.RUNTIME) public @interface Tables { Table[] value(); } |
注意: 容器
注解的保留期必须比它所包含的注解的保留期更长, 否则JVM会丢弃容器
, 相应的注解也就丢失了.
- Client
使用时需要用Table容器来盛装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成员.
参考:
Java注解实践--annotation学习三的更多相关文章
- Java注解处理器--annotation学习四
Java中的注解(Annotation)是一个很神奇的东西,特别现在有很多Android库都是使用注解的方式来实现的.一直想详细了解一下其中的原理.很有幸阅读到一篇详细解释编写注解处理器的文章.本文的 ...
- Java注解实践
Java注解实践 标签 : Java基础 注解对代码的语意没有直接影响, 他们只负责提供信息给相关的程序使用. 注解永远不会改变被注解代码的含义, 但可以通过工具对被注解的代码进行特殊处理. JDK ...
- (转)秒懂,Java 注解 (Annotation)你可以这样学
转自:秒懂,Java 注解 (Annotation)你可以这样学 注解如同标签 回到博文开始的地方,之前某新闻客户端的评论有盖楼的习惯,于是 “乔布斯重新定义了手机.罗永浩重新定义了傻X” 就经常极为 ...
- (转)深入理解Java注解类型(@Annotation)
背景:在面试时候问过关于注解的问题,工作中也用到过该java的特性,但是也没有深入的了解. 秒懂,Java 注解 (Annotation)你可以这样学 ps:注解最通俗易懂的解释 注解是一系列元数据, ...
- 秒懂,Java 注解 (Annotation)你可以这样学
转自: https://blog.csdn.net/briblue/article/details/73824058 文章开头先引入一处图片. 这处图片引自老罗的博客.为了避免不必要的麻烦,首先声明我 ...
- Java 注解(Annotation)秒懂,你可以这样学,
文章开头先引入一处图片. 这处图片引自老罗的博客.为了避免不必要的麻烦,首先声明我个人比较尊敬老罗的.至于为什么放这张图,自然是为本篇博文服务,接下来我自会说明.好了,可以开始今天的博文了. Anno ...
- 自定义Java注解(annotation)
https://www.imooc.com/learn/456 笔记 Java从1.5开始引进注解. 首先解决一个问题,为什么要学习Java注解? 1.看懂别人写的代码,尤其是框架的代码 2.可以是 ...
- (转)秒懂,Java 注解 (Annotation)你可以这样学 ---- 重要 注解定义与反射解析
转:http://blog.csdn.net/briblue/article/details/73824058 文章开头先引入一处图片. 这处图片引自老罗的博客.为了避免不必要的麻烦,首先声明我个人 ...
- 10分钟学会JAVA注解(annotation)
(原) 先认识注解(Annotation) 定义类用class,定义接口用interface,定义注解用@interface 如public @interface AnnotationTest{} 所 ...
随机推荐
- 打开eclipse报错:发现了以元素 'd:skin' 开头的无效内容。此处不应含有子元素。
[错误] 打开eclipse报错:发现了以元素 ‘d:skin’ 开头的无效内容.此处不应含有子元素. [具体报错信息] Error parsing D:\Android-sdks\system-im ...
- js toggle事件
参数:even (Function): 第奇数次点击时要执行的函数. odd (Function): 第偶数次点击时要执行的函数. 示例:$("p").toggle(functio ...
- MyISAM 和InnoDB区别
MyISAM 和InnoDB 讲解 InnoDB和MyISAM是许多人在使用MySQL时最常用的两个表类型,这两个表类型各有优劣,视具体应用而定.基本的差别为:MyISAM类型不支持事务处理等高级处理 ...
- 怎么提高OCR文字识别软件的识别正确率
在OCR文字识别软件当中,ABBYY FineReader是比较好用的程序之一,但再好的识别软件也不能保证100%的识别正确率,用户都喜欢软件的正确率高一些,以减轻识别后修正的负担,很多用户也都提过这 ...
- SQL Server取系统当前时间【转】
getdate //获得系统当前日期 datepart //获取日期指定部分(年月日时分表) getdate()函数:取得系统当前的日期和时间.返回值为datetime类型的. 用法:getdate( ...
- OpenJudge计算概论-鸡尾酒疗法
/*===================================== 鸡尾酒疗法 总时间限制: 1000ms 内存限制: 65536kB 描述 鸡尾酒疗法,原指“高效抗逆转录病毒治疗”(HA ...
- Ubuntu上安装mono并进行C#代码测试
微软的.NET框架与Linux开发和管理,是Buider AU和一个更广泛行业的两个最流行的主题. 大多数时候,这两个主题往往会产生冲突,很少有开发者需要同时了解这两个工具.但是,许多人都没意识到, ...
- Linux下升级python
本文的Linux系统为CentOS 7 64 在Linux系统的下载文件夹中邮件打开终端,输入命令: wget http://www.python.org/ftp/python/3.4.4/Pytho ...
- PCA的数学原理
PCA(Principal Component Analysis)是一种常用的数据分析方法.PCA通过线性变换将原始数据变换为一组各维度线性无关的表示,可用于提取数据的主要特征分量,常用于高维 数据的 ...
- Pop Sequence
题目来源:PTA02-线性结构3 Pop Sequence (25分) Question:Given a stack which can keep M numbers at most. Push ...