Java 基础之认识 Annotation
Java 基础之认识 Annotation
从 JDK 1.5 版本开始,Java 语言提供了通用的 Annotation 功能,允许开发者定义和使用自己的 Annotation 类型。Annotation 功能包括了关于定义 Annotation 类型的语法,对声明式进行注解的语法,读取 Annotation 的 API,Annotation 在 class 文件中的表现,以及 Annotation 处理工具(APT)。
Annotation 并不直接对程序的语法产生作用,但是会提供一些程序之外的数据或者信息,影响工具或者类库对程序的处理或者调用的方式,从而最终影响程序运行时的行为。
Annotation 功能现在已经广泛应用于各种基于 Java 的应用系统的开发。Java API 本身就预定义了一系列的 Annotation 类型,例如 @Deprecated, @Override, @SuppressWarnings 等等。在流行的框架中也经常会用到各种 Annotation, 如 Spring 中的 @Required,@Autowired,@Aspect,@Pointcut 等等。
因此对于 Java 开发者来说,很可能已经通过使用 Annotation 来对代码和软件质量进行了提升。以 @Override 标签为例,在具有复杂继承结构的大型项目中,对于父类的某一个方法,开发者很难直观地通过代码判断出是哪一个子类的具体实现在运行时被调用。而一旦开发者改变了父类的方法名或其参数,而忽视了对子类作出同样的修改,子类中重写的方法将不会再被调用,从而导致系统无法呈现出预期的行为或结果。通过引入 @Override 标签,Java 编译器在对相关代码进行编译时会提示开发者对所有相关的子类中的重写方法进行同步修改,从而避免这一问题的产生。
为了更好地帮助开发者提升代码的质量和可读性,以及自动化代码分析的准确性,Java 8 对 Annotation 引入了两项重要的改变:Type Annotation 和 Repeating Annotation。
Java 8 Annotation 新特性初体验
特性一:Type Annotation
在 Java 8 之前的版本中,只能允许在声明式前使用 Annotation。而在 Java 8 版本中,Annotation 可以被用在任何使用 Type 的地方,例如:初始化对象时 (new),对象类型转化时,使用 implements 表达式时,或者使用 throws 表达式时。
清单 1. Type Annotation 使用示例
//初始化对象时
String myString = new @NotNull String();
//对象类型转化时
myString = (@NonNull String) str;
//使用 implements 表达式时
class MyList<T> implements @ReadOnly List<@ReadOnly T>{
...
}
//使用 throws 表达式时
public void validateValues() throws @Critical ValidationFailedException{
...
}
定义一个 Type Annotation 的方法与普通的 Annotation 类似,只需要指定 Target 为 ElementType.TYPE_PARAMETER 或者 ElementType.TYPE_USE,或者同时指定这两个 Target。
清单 2. 定义 Type Annotation 示例
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
public @interface MyAnnotation {
}
ElementType.TYPE_PARAMETER 表示这个 Annotation 可以用在 Type 的声明式前,而 ElementType.TYPE_USE 表示这个 Annotation 可以用在所有使用 Type 的地方(如:泛型,类型转换等)
与 Java 8 之前的 Annotation 类似的是,Type Annotation 也可以通过设置 Retention 在编译后保留在 class 文件中(RetentionPolicy.CLASS)或者运行时可访问(RetentionPolicy.RUNTIME)。但是与之前不同的是,Type Annotation 有两个新的特性:在本地变量上的 Annotation 可以保留在 class 文件中,以及泛型类型可以被保留甚至在运行时被访问。
虽然 Type Annotation 可以保留在 class 文件中,但是它并不会改变程序代码本身的行为。例如在一个方法前加上 Annotation,调用此方法返回的结果和不加 Annotation 的时候一致。
Java 8 通过引入 Type Annotation,使得开发者可以在更多的地方使用 Annotation,从而能够更全面地对代码进行分析以及进行更强的类型检查。
特性二:Repeating Annotation
在实际应用中,可能会出现需要对同一个声明式或者类型加上相同的 Annotation(包含不同的属性值)的情况。
例如系统中除了管理员之外,还添加了超级管理员这一权限,对于某些只能由这两种角色调用的特定方法,可以使用 Repeating Annotation。
清单 3. Repeating Annotation 使用示例-1
@Access(role="SuperAdministrator")
@Access(role="Administrator")
public void doCheck() {
......
}
上面的示例是针对方法使用 Annotation, 开发者也可以根据产品中的具体需求在其他地方使用 Repeating Annotation。例如某个类专门提供管理员相关的功能,可以直接在这个类上标注同样的 Annotation。
清单 4. Repeating Annotation 使用示例-2
@Access(role="SuperAdministrator")
@Access(role="Administrator")
public class AdminServices{
}
之前版本的 JDK 并不允许开发者在同一个声明式前加注同样的 Annotation,(即使属性值不同)这样的代码在编译过程中会提示错误。而 Java 8 解除了这一限制,开发者可以根据各自系统中的实际需求在所有可以使用 Annotation 的地方使用 Repeating Annotation。
由于兼容性的缘故,Repeating Annotation 并不是所有新定义的 Annotation 的默认特性,需要开发者根据自己的需求决定新定义的 Annotation 是否可以重复标注。Java 编译器会自动把 Repeating Annotation 储存到指定的 Container Annotation 中。而为了触发编译器进行这一操作,开发者需要进行以下的定义:
首先,在需要重复标注特性的 Annotation 前加上 @Repeatable 标签,示例如下:
清单 5. 定义 Repeating Annotation 示例
@Repeatable(AccessContainer.class)
public @interface Access {
String role();
}
@Repeatable 标签后括号中的值即为指定的 Container Annotation 的类型。在这个例子中,Container Annotation 的类型是 AccessContainer,Java 编译器会把重复的 Access 对象保存在 AccessContainer 中。
AccessContainer 中必须定义返回数组类型的 value 方法。数组中元素的类型必须为对应的 Repeating Annotation 类型。具体示例如下:
清单 6. 定义 Container Annotation 示例
public @interface AccessContainer {
Access[] value();
}
可以通过 Java 的反射机制获取注解的 Annotation。一种方式是通过 AnnotatedElement 接口的 getAnnotationByType(Class<T>) 首先获得 Container Annotation,然后再通过 Container Annotation 的 value 方法获得 Repeating Annotation。另一种方式是用过 AnnotatedElement 接口的 getAnnotations(Class<T>) 方法一次性返回 Repeating Annotation。
Repeating Annotation 使得开发者可以根据具体的需求对同一个声明式或者类型加上同一类型的注解,从而增加代码的灵活性和可读性。
结合 Checker Framework 进行代码分析
Java 8 通过引入 Annotation 的新特性以支持对 Java 程序代码进行更全面和深入的分析和校验。然而 Java 8 本身并没有提供校验框架,要针对 Annotation 进行代码分析,开发者可以选择一些第三方工具,例如:Checker Framework, FindBugs, Eclipse, IntelliJ 等开源工具,以及一些其他的商业分析工具。这些工具可以在 IDE, Maven/Ant 或者持续集成平台中使用。
相比于其他的开源工具,Checker Framework 对 Java 8 的兼容性最好,包括支持注释中的 Annotation。它本身内置了针对一系列常见错误类型的校验类,如空指针,字符串格式不匹配,度量单位不匹配,安全漏洞,并发错误等。同时开发者也可以自定义校验类型。本文中选择使用 Checker Framework 对代码进行基于 Annotation 的分析和校验。
安装和配置 Checker Framework
如果开发者使用支持 Java 8 的 Eclipse 版本(如 4.4.0)进行开发,可以直接在 Eclipse 中安装 Checker Framework 插件。步骤如下:
- 打开 Eclipse, 选择菜单:帮助 -> 安装新软件;
- 点击添加;
- 输入名称为 Checker Framework,地址为http://types.cs.washington.edu/checker-framework/eclipse;
- 点击下一步直至结束进行安装;
- 安装完成选择重启 Eclipse。
使用内置的校验类
Checker Framework 提供了一系列内置的校验类,具体如下:
表 1. Checker Framework 内置校验类
| 名称 | 功能 |
|---|---|
| Nullness Checker | 检测和避免系统出现空指针异常。 |
| Javari Checker | 检测和避免只读对象的值被修改。 |
| Interning Checker | 保证引用比较操作符“==”的正确使用,即是,“==”没有用在本应该使用 equals() 方法的地方。 |
| Fenum Checker | 全名为 Fake Enum Checker,用来保证使用正确的 Fake Enum 常量。 Fake Enum: 也就是用来替代 Enum 使用的 int 或者 String 常量定义, 例如 public static final @Fenum("A") int ACONST1 = 1;。 |
| Format String Checker | 检测和避免在格式化方法(例如 System.out.printf()或者 String.format())中使用不正确格式的 String。 |
| Linear Checker | 保证线性类型系统,避免别名的使用,即一个对象只能同时存在一个引用。 |
| Lock Checker | 检测和保证线程在访问被标注的对象或方法时持有合适的锁。 |
| Regex Checker | 检测和保证 String 符合正则表达式。 |
| Tainting Checker | 检测数据源参数是否可能为脏数据。 |
| I18n Checker | 检测代码中的国际化是否正确使用,即字符串或对象是否对应国际化文件中的 key 或者是否与选定的 locale 一致。 |
以 Nullness Checker 为例,要使用 Checker Framework 对代码进行分析,首先在代码中需要做空指针校验的对象或方法上标注相应的 Annotation, 如以下示例:
清单 7. Nullness Checker 示例-1
class NullnessExample {
@Nullable Object o1;
@NonNull Object o2;
@EnsuresNonNullIf(expression="getComponentType()", result=true)
public native boolean isArray();
public native @Nullable Class<?> getComponentType();
}
要执行相应的校验插件,只需要在用 Javac 进行正常编译时,加上 -processor plugin_class 参数。具体示例如下:
清单 8. Nullness Checker 示例-2
javac -processor org.checkerframework.checker.nullness.NullnessChecker
NullnessExample.java
在 Eclipse 里使用 Checker Framework 插件则可以直接在需要做校验的类或者项目的右键选项中选择 Checker Framework -> Run Built-in Checker -> Nullness Checker。
编译完成后即可以在日志中找到相应的提示信息。例如:
清单 9. Nullness Checker 日志
Description: dereference of possibly-null reference o1
Resource: NullnessExample.java
Path: /Mysamples/src/checkerFramework/samples
Location: 26
Type:Checker Framework Problem
开发者可以通过这些信息快速准确地定位代码中潜在的缺陷,从而避免程序运行时出现异常。
Annotation 校验使用场景
Type Annotation 为 Java 程序中的校验提供了更多的可能性,然而,相对于业务方面的校验,它更适用于计算机科学相关的一般性的格式校验。例如,空指针校验,数值范围校验,字符串格式校验等,都可以很方便地通过使用 Type Annotation 结合第三方的校验工具,如 Checker Framework 进行校验,避免程序在运行时出现此类错误或异常。
然而一些特定的业务逻辑方面的校验可能并不适合通过 Type Annotation 进行实现。例如在做转账交易时,需要检查用户权限以及账户余额等,这类的校验需要涉及各种不同的业务数据,很难通过一般性的校验框架执行校验。
结束语
在 Java 8 中,Annotation 得到了很好的扩展,可以用在任何使用 Type 的地方,而不仅限于声明式前。Annotation 本身并不会改变程序的运行行为,但是可以通过使用第三方工具,如 Checker Framework, 开发者可以利用 Annotation 去自动化地对程序中的潜在缺陷进行检测,提升软件质量,避免重复开发,提高开发效率。
Java 基础之认识 Annotation的更多相关文章
- Java基础之理解Annotation(与@有关,即是注释)
Java基础之理解Annotation 一.概念 Annontation是Java5开始引入的新特征.中文名称一般叫注解.它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata) ...
- Java基础之理解Annotation
一.概念 Annontation是Java5开始引入的新特征.中文名称一般叫注解.它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类.方法.成员变量等)进行关 ...
- JAVA基础知识之Annotation
基本Annotation Annotation必须使用工具(APT, Annotation tool)才能处理,Annotation可以在编译,类加载,运行时被读取,并执行相应处理. 下面介绍一些常用 ...
- 【JAVA - 基础】之Annotation注解浅析
注解在JAVA中,尤其是一些ORM框架(如Hibernate等)中是比较常用的一种机制. 注解是JAVA 1.5之后引入的新功能,正确来说是反射的一部分,没有反射,注解也就无法正常使用.注解可以理解成 ...
- java基础篇---注解(Annotation)
一.概念 Annontation是Java5开始引入的新特征.中文名称一般叫注解.它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类.方法.成员变量等)进行关 ...
- Java 基础之--注解Annotation详解
自定义注解入门: public @interface Annotation01 { //set default value ""; String value() default & ...
- Java基础笔记 – Annotation注解的介绍和使用 自定义注解
Java基础笔记 – Annotation注解的介绍和使用 自定义注解 本文由arthinking发表于5年前 | Java基础 | 评论数 7 | 被围观 25,969 views+ 1.Anno ...
- Java基础加强总结(一)——注解(Annotation)
一.认识注解 注解(Annotation)很重要,未来的开发模式都是基于注解的,JPA是基于注解的,Spring2.5以上都是基于注解的,Hibernate3.x以后也是基于注解的,现在的Struts ...
- Java基础知识【上】(转载)
http://blog.csdn.net/silentbalanceyh/article/details/4608272 (最终还是决定重新写一份Java基础相关的内容,原来因为在写这一个章节的时候没 ...
随机推荐
- startDiscovery() and startLeScan().
You have to start a scan for Classic Bluetooth devices with startDiscovery() and a scan for Bluetoot ...
- js之正则表达式(上)
1.正则表达式的创建方式 两种方式创建:通过new修饰符创建和字面量的方式创建 1>new修饰符方式创建 var b2=new RegExp('Box','ig'); //第二个参数是 模式字符 ...
- 笔记本电脑连接wifi有时候会自动断网提示有限的访问权限解决办法
解决办法如下: [设备管理器],找到[网络适配器]第一项,右键属性
- mysql中实现oracle中的rowid功能
mysql中没有函数实现,只能自己手动添加变量递增 := 就是赋值,只看红色字体就行 select @rownum:=@rownum+1,img.img_path,sku.sku_name from ...
- [转载]AFX_MANAGE_STATE关于资源切换
应用程序进程本身及其调用的每个DLL模块都具有一个全局唯一的HINSTANCE句柄,它们代表了DLL或EXE模块在进程虚拟空间中的起始地址.进程本身的模块句柄一般为0x400000,而DLL模块的缺省 ...
- POJ 3277 City Horizon(叶子节点为[a,a+1)的线段树+离散化)
网上还有用unique函数和lowerbound函数离散的方法,可以百度搜下题解就有. 这里给出介绍unique函数的链接:http://www.cnblogs.com/zhangshu/archiv ...
- @QueryParam和@PathParam比较
来源:http://jackyrong.iteye.com/blog/1128364 1 先来看@queryparam Path("/users") public class Us ...
- lintcode:Add Binary 二进制求和
题目: 二进制求和 给定两个二进制字符串,返回他们的和(用二进制表示). 样例 a = 11 b = 1 返回 100 解题: 和求两个链表的和很类似 考虑进位,考虑最后一项的进位 0+0 = 0 不 ...
- Spring transaction事务之roll back回滚
转载自:http://blog.csdn.net/lovejavaydj/article/details/7635848 试验方法: 写一个单元测试,调用一个service层方法(发生对数据库进行写操 ...
- Linux基础---开关机与帮助
1.X window与文字模式的切换 通常我们也称文字模式为 终端机接口, terminal或console!Linux预设的情况下, 会提供六个Terminal来让使用者登入,切换的方式为: [Ct ...