注解在Java中是如何工作的?
来一点咖啡,准备好进入注解的世界。
注解一直是 java 的一个非常重要的部分,它从 j2se 5.0 开始就已经存在了。在我们的应用程序代码中,经常看到 @override
和 @deprecated
这样的注解。在本文中,我将讨论注解到底是什么,为什么引入注解,它们是如何工作的,如何编写自定义注解(有示例代码),注解的有效场景是什么,最后是注解和 adf。这将是一个很长的帖子,所以来一点咖啡,准备好进入注解的世界。
1. 注解是什么?
用一个词来可以解释:注解即是元数据。元数据是关于数据的数据。所以注解是代码的元数据。举个例子,查看下面的代码。
@override public string tostring() { return "this is string representation of current object."; }
我在以上代码中重写 tostring()
方法时使用了 @override
注解。即使我不使用 @override
,代码仍然能够正常工作,没有任何问题。那么,这个注解的优点是什么?又代表了什么?@override
告诉编译器,此方法是一个重写的方法(有关该方法的元数据),如果父类中不存在此类方法,则引发编译器错误(超类中没有被覆盖其的方法)。现在,如果我犯了一个排版错误,并使用方法名为 tostrring() {double r}
如果我没有使用 @override
,我的代码会成功地编译和执行,但是结果将会与预期不同。现在,我们理解了注解是什么,阅读正式的定义是很有用处的。
注解是一种特殊的 java 构造,用于修饰类、方法、字段、参数、变量、构造函数或包。这是 jsr-175 选择的提供元数据的工具。
2. 为什么引入注解?
在注解出现之前(甚至之后),xml 被广泛地用于元数据,但是,一部分特定的应用程序开发人员和架构师认为 xml 维护变得麻烦了。他们希望能够通过某种方式与代码紧密耦合,来代替 xml,因为 xml 与代码非常松散地耦合(在某些情况下,几乎是独立的)。如果你在谷歌上搜索 “xml vs annotations”,你会发现很多有趣的争论。有趣的一点是,xml配置是为了将配置从代码中分离出来而引入的。最后两种说法可能会在你的脑海中产生一些疑问,即这两种说法都在创造一个循环,但两者各有优缺点。让我们试着用一个例子来理解。
假设您希望设置一些应用程序范围的常量/参数。在这个场景中,xml将是一个更好的选择,因为这与任何特定的代码段无关。如果您希望将某些方法公开为服务,那么注解将是一个更好的选择,因为它需要与该方法紧密耦合,并且该方法的开发人员必须知道这一点。
另一个重要因素是注解定义了在代码中定义元数据的标准方法。在注解之前,人们还使用自己的方法来定义元数据。一些例子是使用标记接口、注解、临时关键字等。每个开发人员都需要根据自己的方式来决定元数据,但是注解是标准化的东西。
如今,大多数框架都将xml和注解结合起来,充分利用两者的优点。
3.注解如何工作以及如何编写自定义注解
在开始解释之前,我建议您下载这个注释(annotationssample.zip) 的示例代码,并在您常用的 ide 中保持打开,因为它将帮助您更好地理解下面的解释。
编写注解非常简单。您可以将注解定义与接口定义进行比较。让我们看两个例子 — 一个是标准 @override
第二个注解是自定义注解。@todo
:
@target(elementtype.method) @retention(retentionpolicy.source) public @interface override { }
似乎有些可疑之处 @override
它没有做任何事情 — 它只是检查是否在父类中定义了一个方法。别惊讶,我不是在开玩笑。重写注解的定义只有那么多代码。这是需要理解的最重要的部分,我重申一遍:注解只是元数据,不包含任何业务逻辑。很难理解但却是真的。如果注解不包含逻辑,那么一定有人在做一些事情,并且有人是这个注解元数据的消费者。注解只提供有关定义的属性(类/方法/包/字段)的信息。消费者是一段代码,它读取这些信息,然后执行必要的逻辑。
当我们谈论标准注解时,比如 @override
, jvm 是使用者,它是在字节码级别工作的。这是应用程序开发人员无法控制、也不能用于自定义注解的东西。因此,我们需要为我们自己的注解写一些消费实例。
让我们逐一理解用于编写注解的关键术语。在上面的例子中,您将看到注解如何被应用。
j2se 5.0 在 java.lang.annotation 包中提供了四个注解,它们仅在编写注解时使用:
@documented – 是否将注解放在 javadocs 中
@retention – 注解什么时候被保留
@target? – 注解可以使用的地方
@inherited – 子类是否可以继承注解。
@documented
- 一个简单的标记注解,它标识了是否将注解添加到 javadoc 中。
@retention
- 定义应保留注解的时间。
retentionpolicy.source
在编译期间丢弃。这些注解在编译完成后没有任何意义,因此它们不会被写入字节码。例子:@override
, @suppresswarnings
retentionpolicy.class
– 在类加载期间丢弃。应用在进行字节码级别的编译期间。有些令人惊讶的是,这是默认的。
retentionpolicy.runtime
– 不会丢弃。该注解可以在运行时进行反射。这是我们通常用于自定义注解的内容。
@target
- 注解可以使用的地方。如果不指定这一属性,注解可以应用在任何地方。以下是该注解的有效值。这里的一个要点,它只有包含的形式,这意味着如果您想要对7个属性进行注解,并且只想排除一个属性,这时需要在定义目标时包含所有7个属性。
elementtype.type (类,接口,枚举)
elementtype.field (实例变量)
elementtype.method
elementtype.parameter
elementtype.constructor
elementtype.local_variable
elementtype.annotation_type (用于其他注解)
elementtype.package (记住 package-info.java)
@inherited
- 控制注解是否应该影响子类。
现在,注解定义中包含了什么?注解只支持基本类型、字符串和枚举。注解的所有属性都定义为方法,并且还可以提供默认值。
@target(elementtype.method) @retention(retentionpolicy.runtime) @interface todo { public enum priority {low, medium, high} public enum status {started, not_started} string author() default "yash"; priority priority() default priority.low; status status() default status.not_started; }
以下是如何使用上述注解的示例:
@todo(priority = todo.priority.medium, author = "yashwant", status = todo.status.started) public void incompletemethod1() { //some business logic is written //but it’s not complete yet }
如果注解中只有一个属性,则应该将其命名为 “value”,并且可以在使用时不使用属性名称。
@interface author{ string value(); } @author("yashwant") public void somemethod() { }
目前为止一切顺利。我们已经定义了自定义注解,并将其应用于一些业务逻辑方法。现在,是时候写一个消费的实例了。为了完成这个目标,我们需要使用到反射。如果您熟悉反射代码,那么您就知道反射提供了类、方法和字段对象。所有这些对象都有一个 getannotation()
方法,该方法返回注解对象。我们需要将此对象转换为自定义注解(在使用 instanceof()
检查之后),然后,我们可以调用自定义注解中定义的方法。让我们看一下示例代码,它使用了上面的注解:
class businesslogicclass = businesslogic.class; for(method method : businesslogicclass.getmethods()) { todo todoannotation = (todo)method.getannotation(todo.class); if(todoannotation != null) { system.out.println(" method name : " + method.getname()); system.out.println(" author : " + todoannotation.author()); system.out.println(" priority : " + todoannotation.priority()); system.out.println(" status : " + todoannotation.status()); } }
4. 注解用例
注解非常的强大,spring 和 hibernate 等框架非常广泛地使用注解进行日志记录和验证。注解可以在使用标记接口的地方使用。标记接口用于整个类,但您可以定义可以用于单个方法的注解,例如,某个方法是否公开为服务方法。
在 servlet 3.0 规范中,引入了许多注解,特别是与 servlet 安全有关的注解。我们先来看看几个:
handlestypes
- 此注解用于声明传递给 servletcontainerinitializer
的应用程序类数组。
httpconstraint
- 此注解表示应用于所有具有http协议方法类型的请求的安全约束,在 servletsecurity
上没有相应的 httpmethodconstraint
注解时。
httpmethodconstraint
- 特定的安全约束可以应用于不同类型的请求,在 servletsecurity
上注解。
multipartconfig
- 此注解用于指示声明它的 servlet 将使用 multipart/form-data mime 类型发出请求。
servletsecurity
- 在 servlet 实现类上声明此注解,对http协议请求执行安全约束。
webfilter
- 用于声明 servlet 过滤器的注解。
webinitparam
- 用于在 servlet 或过滤器上声明初始化参数的注解,在 webfilter
或 webservlet
上注解。
weblistener
- 用于在给定的web应用程序上下文中声明各种类型事件的侦听器的注解。
webservlet
- 此注解用于声明 servlet 的配置。
5. 应用程序开发框架(adf,application development framework)和注解
现在,我们正在讨论的最后一部分:应用程序开发框架(adf)。adf 由 oracle 开发,用于构建 oracle 融合应用程序。我们已经看到了优点和缺点,并且知道如何编写自定义注解,但是在 adf 中我们可以在哪里使用自定义注解呢?adf 是否提供本地注解?
这些无疑是有趣的问题:但是是否存在某些限制来阻止在 adf 中大规模使用注解?前面提到的框架(如 spring 和 hibernate ) 使用的 aop (面向方面的编程)。在 aop 中,框架提供了为任何事件的预处理和后处理注入代码的机制。例如,您有一个钩子在方法执行之前和之后放置代码,因此您可以在这些地方编写自定义代码。adf 不使用 aop。如果我们有任何有效的注解用例,我们可能需要通过。
9月福利,关注公众号
后台回复:004,领取8月翻译集锦!
往期福利回复:001,002, 003即可领取!