Java 8的范例注解:东西和时机
副标题#e#
在以前的Java版本中,开拓者只能将注解(Annotation)写在声明中。对付Java 8,注解可以写在利用范例的任那里所,譬喻声明、泛型和强制范例转换等语句:
@Encrypted String data; List<@NonNull String> strings; myGraph = (@Immutable Graph) tmpGraph;
乍一看,范例注解并不是Java新版本最炫的特性。事实上,注解只是语法!东西抉择了注解的的语义(即,它们的寄义和行为)。本文先容新的注解语法和实用东西,以提跨越产力和构建更高质量的软件。
在金融行业,我们的市场颠簸和禁锢情况抉择了上市时间比以往任何时候都越发重要。但牺牲安详性或质量绝对不是一个可选项:简朴的百分点和基点杂乱就大概造成严重效果。这种环境同样存在于所有其它行业。
作为一名Java措施员,也许你已经回收注解来提高软件质量。想想早在Java 1.5中引入的@Override注解。在具有巨大担任条理布局的大型项目中,要跟踪系统运行时会执行要领的哪一种实现是很坚苦的。假如你不小心修改了某个要领的声明,大概会导致子类要领没有被挪用。这种方法打消了一个要领挪用,将会引入缺陷可能安详裂痕。为此,Java引入了@Override注解,开拓者可以用它来说明该要领包围了父类要领。假如措施没有匹配这种意图,Java编译器将利用这些注解来告诫开拓者。如此,注解饰演了呆板查抄文档的形式。
开拓者可以通过元编程(Metaprogramming)等技能提跨越产率,注解在个中饰演了焦点脚色。其思想是通过注解够汇报东西如何生成新代码、转换代码可能抉择运行期的行为。以Java Persistence API(JPA)为例,这也是Java 1.5引入的成果。它答允开拓者以声明的方法如@Entity,指定Java工具与数据库实体之间的干系。然后Hibernate这类东西就可以利用这些注解,在运行期生成映射文件和SQL查询。
在JPA和Hibernate的场景中,注解用于支持DRY(Don’t Repeat Yourself)原则。有趣的是,无论你在哪寻找支持最佳实践的开拓东西,都不难发明注解的存在。一些著名的例子包罗利用依赖注入(Dependency Injection)低落耦合,利用面向切面编程(Aspect Oriented Programming)疏散存眷点。
问题来了:假如注解已经被用于晋升质量和提跨越产率,为什么我们还需要范例注解?
这个问题的简朴答复是:范例注解提供更多的成果。它们辅佐自动检测更多的缺陷,为你提供出产力东西的更多节制。
范例注解的语法
在Java 8中,范例注解可以写在利用范例的任那里所,以下是一些例子:
@Encrypted String data List<@NonNull String> strings MyGraph = (@Immutable Graph) tmpGraph;
引入一个新的范例注解很是简朴,只要界说一个注解,而且其target为ElementType.TYPE_PARAMETER或ElementType.TYPE_USE,可能两个都包括:
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) public @interface Encrypted { }
ElementType.TYPE_PARAMETER暗示注解能写在范例变量的声明语句中(如:class MyClass
一旦源码中的范例有了注解,就像声明中的注解一样,它可以同时存在于类文件中并在运行时可以通过反射获取(界说注解时利用RetentionPolicy.CLASS或RetentionPolicy.RUNTIME计策)。范例注解与以前的注解有两个主要区别:首先,局域变量声明中的范例注解也可以保存在类文件中;其次,完整泛型被保存,而且在运行期可以会见。
尽量注解可以生存在类文件中,但它不影响措施的通例运行。譬喻,开拓人员大概在要领体中声明白两个File变量和一个Connection变量:
File file = ...; @Encrypted File encryptedFile = ...; @Open Connection connection = ...;
当措施运行时,通报个中任何一个文件给connection的send(…)要领,城市挪用同一个要领实现。
// 以下代码将挪用同一个要领 connection.send(file); connection.send(encryptedFile);
正如你预期的那样,运行期没有区别,也就是说,尽量参数的范例是有注解的,但要领不会基于注解的范例举办重载:
public class Connection{ void send(@Encrypted File file) { ... } // Impossible: // void send( File file) { ... } . . . }
#p#副标题#e#
这个限制的背后,直觉汇报我们,编译器完全无法知道有注解的范例和无注解的范例之间的干系,也不知道有差异注解的范例之间的干系。
可是,别急!变量encryptedFile的注解@Encrypted和要领声明中file参数的注解是相对应的;那么变量connection的注解@Open又与哪个要领声明中的注解对应呢?在挪用connection.send(…)中,变量connection是要领的“吸收者”。(术语“吸收者, Receiver”来历于工具间通报动静的面向工具的经典比喻。)Java 8为要领声明引入了新的语法,因此范例注解可以写在要领吸收者上:
void send(@Open Connection this, @Encrypted File file)
#p#分页标题#e#
同样,由于注解对措施执行没有影响,因此以新的吸收者参数语法声明的要领与利用传统语法声明的要领具有同样的行为。实际上,当前新语法的独一用处是范例注解可以写在吸收者的范例上。
范例注解语法,包罗多维数组语法的完整说明可以查察JSR (Java Specification Request) 308网站。
利用注解检测缺陷
在代码中写注解可用来强调有缺陷代码中的错误:
@Closed Connection connection = ...; File file = ...; … connection.send(file); // 错误!封锁的毗连而且未加密!
然而,上面的代码仍然可以或许编译、运行,然后瓦解,Java编译器并没有查抄用户界说的注解。相反,Java平台果真了两个API,Java Compiler Plug-in和Pluggable Annotations Processing API,第三方开拓商可以开拓本身的阐明器。
在前面的例子中,实际上注解用于限制变量的值。我们可以用其它方法来限制File范例:@Open File, @Localized File, @NonNull File。我们也可以用这些注解来限制其它范例,譬喻@Encrypted String。因为范例注解独立于Java范例系统,注解的观念可重用于多种范例。
可是这些注解如何可以或许自动查抄呢?直观地说,有些注解是另一些注解的子类,利用它们将可以或许举办范例查抄。思量一下SQL注入进攻的问题,如何防备数据库执行用户提供的(污染的)输入。我们也许会把数据分为@Untainted或@MaybeTainted,对应于数据是否担保没有用户输入:
@MaybeTainted String userInput; @Untainted String dbQuery;
注解@MaybeTainted可认为是注解@Untainted的父类。有两种方法可以用来思考这种干系。首先,大概污染的数据集必然是确定未污染数据的超集(确定未污染的数据可以是大概污染数据集的元素)。相反地,注解@Untainted提供了比@MaybeTainted更严格有力的担保。让我们看看在实际应用中子类是否有效:
userInput = dbQuery; // OK dbQuery = "SELECT FROM * WHERE " + userInput; // 范例错误!
第一行检测通过,假如我们假定未污染的值也属于污染值,这应该没问题。在第二行,我们的子类法则发明白一个bug:我们实验将父类赋值给更严格的子类。
Checker框架
Checker框架是查抄Java注解的框架。该框架首次宣布于2007年,是一个活泼的开源项目,由JSR308尺度的副主管Michael Ernst传授率领。Checker框架包括了大量的注解和查抄器,可以或许检测空指针间接引用、计量单元不匹配和安详裂痕缺陷,以及线程/并发错误等等。因为查抄器在引擎盖下利用范例查抄,因此其功效都是声音。查抄器不会遗漏任何潜在错误,而东西利用只是探索威力。在编译期间,框架利用编译器API执行查抄。作为一个框架,你能快速建设本身的注解查抄器去检测特定应用的缺陷。
该框架的方针是不需要写大量的注解就能检测缺陷。这主要依赖于两个特性:智能默认(smart default)和节制流敏感(control-flow sensitivity)。举例来说,检测空指针缺陷时,查抄器默认假定参数不能为空。查抄器也能利用条件语句抉择间接引用表达式是安详的。
void nullSafe(Object nonNullByDefault, @Nullable Object mightBeNull){ nonNullByDefault.hashCode(); // OK due to default mightBeNull.hashCode(); // Warning! if (mightBeNull != null){ mightBeBull.hashCode(); // OK due to check } }
实际上,默认和节制流敏感意味着你在要领体中险些不消写注解,查抄器能自动揣度和查抄注解。通过保持注解的语义与官方Java编译器疏散,Java团队确保了第三方东西设计者与用户可以或许抉择本身的设计。这样就可以定制错误查抄来满意项目标本性化需求。
自界说注解的这种本领,让你也许会思量规模特定(Domain-specific)范例查抄。譬喻在金融行业,利率利用百分比描写,而利率差额凡是利用基点(1%的百分之一)描写。利用Checker框架的单元查抄器(Unit Checker),你可以界说两个注解@Percent和@BasisPoints,确保你没有夹杂两者:
BigDecimal pct = returnsPct(...); // annotated to return @Percent requiresBps(pct); // error: @BasisPoints is required
#p#分页标题#e#
这儿,因为Checker框架是节制流敏感的,当挪用requiresBps(pct)时,它知道pct是@Percent BigDecimal,原因是:第一,returnsPct(…)的注解表白返回@Percent BigDecimal;其次,挪用requiresBps(pct)前,pct没有被从头赋值。凡是开拓者利用定名类型来只管制止这类缺陷。Checker框架为你确保不存在这些缺陷,纵然代码不绝增长和产生变革。
Checker框架已经查抄了数百万行代码,纵然在颠末精采测试的软件中,也袒露了数百个缺陷。也许这是我最喜欢的例子:当框架查抄风行的Google Collections类库(此刻叫Google Guava)时,它发明白一些空指针缺陷,这些甚至是大量测试和开导式的静态阐明东西所没有发明的。
要得到这类功效,并不需要打乱代码。实际上,利用Checker框架校验属性,每千行代码只需要2-3个注解!
假如你在利用Java 6可能7,同样可以利用Checker框架来提高代码质量。框架支持范例注解写成注释(譬喻:/*@NotNull*/ String)。其汗青原因是,从2006年开始,Checker框架与JSR 308(范例注解类型)一起配合开拓。
尽量Checker框架是可以或许操作错误查抄新语法优势的最佳框架,但此刻它不是独一的一个。Eclipse与IntelliJ都已经支持范例注解。
注解是声明式的类型:(1)东西如何生成代码或帮助文件;(2)东西该如何影响措施的运行时行为。这种利用注解的方法被称为元编程。一些框架,如Lombok,利用注解举办元编程到了极致,导致代码都不再像Java了。
让我们先看看面向切面编程(AOP)。AOP旨在疏散存眷,譬喻将日志和身份验证与措施的主业务逻辑疏散。通过AOP,编译时东西基于法则集将特别代码加到你的措施中。譬喻,我们界说一个法则,基于范例注解自动加上身份验证:
void showSecrets(@Authenticated User user){ // 利用AOP自动插入: if (!AuthHelper.EnsureAuth(user)) throw . . .; }
如前所述,注解限定了范例。然而,AOP框架不是用来在编译期间查抄注解,而是用来在运行期自动执行校验。这个例子展示了范例注解如作甚你提供更多的节制,抉择AOP框架何时以及如何修改措施。
Java 8还支持局域声明中利用范例注解,而且生存在类文件中。这开启了细粒度AOP的新时机。譬喻有纪律地添加跟踪代码:
// 跟踪ar工具的所有挪用 @Trace AuthorizationRequest ar = . . .;
同样,范例注解为利用AOP举办元编程提供了更多的节制。依赖注入也是同样的景象。利用Spring 4,你终于可以利用泛型作为限定词形式:
@Autowired private Store<Product> s1; @Autowired private Store<Service> s2;
利用泛型消除了引入类,如ProductStore和ServiceStore,可能利用懦弱的定名为基本的注入法则的须要性。
利用范例注解,不难想像(Spring中还未实现)利用注解进一步节制注入:
@Autowired private Store<@Grocery Product> s3;
这个例子演示了范例注解作为一个东西疏散存眷,使项目标范例层级保持整洁。这种疏散是可行的,因为范例注解独立于Java范例系统。
前方的路
我们已经看到新的范例注解如何用于检测/防备措施错误和提跨越产力。然而,范例注解的真正潜能是团结错误查抄和元编程,开发新的开拓模式。
其根基思想是构建运行时和类库,操作注解自动使措施更高效、并行可能更安详,而且自动强制开拓者正确地利用那些注解。
这种要领的一个很好的例子是Adrian Sampson的EnerJ框架,该框架通过近似计较举办高能效计较。EnerJ基于监督,有时候,譬喻在移动设备上处理惩罚图像时,为了节省能源,衡量图像精度是有意义的。开拓者利用EnerJ,对付非要害数据利用@Approx范例注解。基于这些注解,EnerJ运行时处理惩罚这些数据时会思量各类捷径。譬喻,它大概利用低能耗的近似计较硬件来生存数据或执行计较。可是,通过措施移动近似数据是危险的,作为开拓者,你不会但愿节制流受到近似数据的影响。因此,EnerJ利用Checker框架强制近似数据不能用于节制流(譬喻,在if语句中)。
这种要领的应用并不范围于移动设备。在金融规模,我们经常面临精度与速度之间的衡量。在这些环境下,可以留给运行时来节制蒙特卡洛路径或收敛尺度的数目,可能基于当前的需求和可用的资源,甚至大概在专用硬件上运行计较。
这种要领的巧妙之处在于,它将如何执行与焦点业务逻辑描写的执行什么计较举办了疏散。
总结
#p#分页标题#e#
在Java 8中,除了在声明中写注解,你还能在利用范例的任那里所写注解。注解自己对措施行为没有任何影响。然而,通过利用Checker框架这样的东西,你可以利用范例注解来自动查抄和确认不存在软件缺陷,并利用元编程提跨越产效率。尽量现有东西要完全操作范例注解的优势还需要必然的时间,但此刻是时候开始摸索范例注解如何可以或许晋升你的软件质量和出产效率了。