java注解
注解的本质是什么呢?要探究注解的本质,就得先看它是怎样定义的。定义注解的关键字是@Interface,Interface是接口的意思,我们可以猜想注解和接口有关系。实际上,注解就是一个接口。官方说明文档中说注解默认继承java.lang.Annotation接口,什么东西可以继承接口呢,无非是抽象类和接口,所以说注解实际上是一个接口。
注解相当于一个说明、备注、通知,用以告诉系统、编译器或者JVM在特定的时候去做一些事情。如@Author用以在生成javadoc时告诉系统类的作者是谁,@Overwrite用以在编译时告诉编译器此方法必须覆盖父类的一个方法,否则要报出错误信息,@Component用以告诉JVM在服务启动时要创建此类的一个实例。这几个例子总结一下,就是注解的三个作用,分别是生成文档、编译时检查、替代配置文件。
注解不会影响代码的语义,比如用@Autowired注解一个属性,这个属性的访问修辞符没有变、属性类型没有变、属性名称没有变,即此注解对这行代码的语义没有任何影响,不会增加其语义、不会减少其语义、也不会改变其语义。
Annotation可以从源文件即.java文件、class文件或者在运行时通过反射机制多种方式被读取。
--上面这句话其实是再说注解的生命周期,可以这样解释:存在于源文件中但在编译时不起作用、存在于源文件中且在编译时起作用、存在于class文件中并在运行期间起作用。
--反射机制可以获取一个类的所有信息,包括访问修辞符、属性类型、属性名称、方法返回值类型、方法名称、方法参数信息,当然还包括属性的注解和方法的注解。
------------------------>>元注解:
我们听过元数据,元数据就是描述数据的数据,描述了数据的特征,比如索引就是一种元数据,它描述了数据的位置。类似的,元注解就是描述注解的注解,描述了注解的生命周期、作用的位置和继承性。
1.描述注解的生命周期的注解:@Retention
注解对于程序来说,并不是一直都有存在的价值。程序在特定的阶段才会使用到某种类型的注解,过了这个阶段,这种类型的注解就不会再被使用了,因此也就失去了存在的价值,所以系统就不会再保留它,将它丢弃。注解被保留的时间段称为生命周期,用元注解@Retention来描述。
@Retention定义了保留策略,可取的值有三个,分别为RetentionPolicy.SOURCE、RetentionPolicy.CLASS、RetentionPolicy.RUNNING。
RetentionPolicy.SOURCE指注解将保留直到源文件阶段结束,即注解只存在于.java文件中,不存在于编译后的文件中,如@Overwrite。
RetentionPolicy.CLASS指注解将保留直到.class文件阶段结束,不存在于字节码文件中。
RetentionPolicy.RUNTIME将注解将一直保留直到程序运行结束,如spring的注解@Component。
默认保留策略为RetentionPolicy.CLASS
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention { /** * Returns the retention policy. * @return the retention policy */ RetentionPolicy value(); }
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 }
2.描述注解的作用目标的注解:@Target
我们知道,不同的注解作用的目标不同,拿spring的注解来举个例子吧。@Bean只能作用于方法,@Component只能作用于类,@Autowired既可以作用于属性,又可以作用于方法。注解的作用目标用@Target来描述。
@Target定义了作用目标,可取的值有10个,分别为
ElementType.PACKAGE、
ElementType.TYPE如@Component、
ElementType.FIELD如@Autowired、
ElementType.METHOD如@Bean、
ElementType.CONSTRUCTOR如@Autowired、
ElementType.PARAMETER如@RequestParam、
ElementType.LOCAL_VARIABLE、
ElementType.ANNOTATION_TYPE。
为什么要用ElementType.TYPE,而不用ElementType.CLASS呢?因为CLASS关键字已经被@Retention占用了。
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target { /** * Returns an array of the kinds of elements an annotation type * can be applied to. * @return an array of the kinds of elements an annotation type * can be applied to */ ElementType[] value(); }
public enum ElementType { /** Class, interface (including annotation type), or enum declaration */ TYPE, /** Field declaration (includes enum constants) */ FIELD, /** Method declaration */ METHOD, /** Formal parameter declaration */ PARAMETER, /** Constructor declaration */ CONSTRUCTOR, /** Local variable declaration */ LOCAL_VARIABLE, /** Annotation type declaration */ ANNOTATION_TYPE, /** Package declaration */ PACKAGE, /** * Type parameter declaration * * @since 1.8 */ TYPE_PARAMETER, /** * Use of a type * * @since 1.8 */ TYPE_USE }
3.描述注解是否出现在java文档中的注解:@Documented
@Documented是一个标记注解,它没有可选的值,加上@Documented就表示java文档要包含此目标注解,否则就表示不包含目标注解。
我们知道,我们的项目是可以生成java文档的,java文档主要是对类的说明。注解与类有关系,但是关系又不是很强,所以java文档中是否需要包含注解,要视情况而定,所以就有两个选择,@Documented就是用来描述这两种选择的。
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Documented { }
4.描述注解是否对子类起作用的注解:@Inherited
@Inherited是一个标记注解,它没有可选的值,加上@Inherited就表示目标注解将会对子类起作用,尽管子类没有显式地使用该注解;否则将不对子类起作用。
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Inherited { }
以上4个注解的作用目标是ElementType.ANNOTATION_TYPE,因为它们都是元注解,元注解是用来描述注解的注解。
------------------------>>内置注解:
1.重写:@Override
注意单词是Override,而非Overwrite。override的意思为“推翻”,Overwrite的意思是“重新书写”。Overwrite是重新书写文字或者文章,和编写代码没有关系。
override的作用有两个。第一个是断言,我们在编写子类的时候,如果打算重写父类的一个方法,可以加上@Override,来判断是否达到了重写的格式要求,如果没有,编译会报错误。第二个是便于阅读代码,当我们在读子类的代码时,看到了@Override,就知道这个方法重写了父类的某个方法,就知道了这个类有一个父类,并且这个类还有其他兄弟类等信息。
2.声明不赞成:@Deprecated
Deprecate的意思是“对某某不赞成”,@Deprecated就是不赞成使用某方法或者属性,这个方法或者属性将来可能会被废除。如果方法被@Deprecated注解了,那么我们如果调用这个方法,就会发现方法名上有一条横线,比如java.util.Date类的getDate()方法。
3.消除警告:@SuppressWarnings
单词suppress的意思是“镇压、消除”。我们在编码时应该都会对一种情况不爽,那就是各种警告信息,总觉得看着别扭,@SuppressWarnings就是用来消除警告信息的。事实上,消除我们的不爽只是表面上的作用,它还有更深层的意义。
编译器报出一些警告信息,是为了让我们关注可能出错的地方。但是如果一个类中有大量的警告信息,并且其中很多警告有可能对我们来说并不重要,那么我们也许对所有的警告信息都采取视而不见的态度了,这种结果和编译器报警告的目的南辕北辙了,所以java提供了@SuppressWarnings。
如果某些警告对我们不重要,那么就可以用@SuppressWarnings消除它。如果我们已经确认了一个警告,确认它不会出现问题,则可以用@SuppressWarnings消除它。这样我们就会去关注每一个警告信息,从而达到编译器报警告的目的。
总的来说,@SuppressWarnings间接地提高了代码的安全性。
---------------------自定义注解
注解没有行为,只有变量,注解通过方法的形式声明变量。
注解其实起到了约定的作用,使用者一旦使用了注解,则系统会帮助使用者做一些事情。JVM通过反射机制获取注解信息,然后做一些对应的约定的工作。其实注解真的没什么神秘的,就像声明一个int类型的变量,则JVM就会申请4个字节的空间一样,对一个元素使用注解,则JVM也会做一些对应的事情。示例如下:
自定义一个注解,代码为:
package com.zaoren.myAnnotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(value = RetentionPolicy.RUNTIME) @Target(value = { ElementType.METHOD }) @Documented @Inherited public @interface TestAnnotation { String value(); }
枚举:
package com.zaoren.myEnum; public enum TestAnnotationEnum { PRINT_BEFORELOG("beforeLog", "执行方法前打印日志"), PRINT_AFTERLOG("afterLog", "执行方法后打印日志"); private String code; private String name; private TestAnnotationEnum(String code, String name) { this.code = code; this.name = name; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
main方法:
package com.zaoren.controller; import java.lang.reflect.Method; import com.zaoren.myAnnotation.TestAnnotation; import com.zaoren.myEnum.TestAnnotationEnum; public class AnnotationController { @TestAnnotation("beforeLog") public static void buyThings() { System.out.println("buy a computer."); } public static void printBeforeLog() { System.out.println("Know about the price before you buy a computer."); } @SuppressWarnings({ "rawtypes", "unchecked"}) public static void main(String[] args) { Class cla = AnnotationController.class; try { Method method = cla.getMethod("buyThings", new Class[] {}); boolean isExistAnnotation = method.isAnnotationPresent(TestAnnotation.class); System.out.println("isExistAnnotation ="+isExistAnnotation); TestAnnotation annotation = method.getAnnotation(TestAnnotation.class); String value = annotation.value(); System.out.println("value = "+value); if(value.equals(TestAnnotationEnum.PRINT_BEFORELOG.getCode())) { printBeforeLog(); } method.invoke(cla); } catch (Exception e) { e.printStackTrace(); } } }
运行结果为:
如上所示,在方法buyThings上使用了注解@TestAnnotation,则main方法做了额外的事情,即在买电脑前了解了电脑的价格情况。JVM就是这样处理注解的。
注解要想起到实际作用,必须要被解析,在java里的体现就是代理类。比如使用AOP技术来处理日志,AOP注解被JVM解析后,肯定生成了代理类,方法的调用也发生了改变,比如源代码中调用的是A类的方法,但是字节码中调用的却是A类的代理类的方法。
注解不能临时起作用,所有的注解在系统初始化的时候都必须要得到解析,根据解析结果来变换相关的代码,在正式运行的时候,执行的代码实际上是经过变换之后的代码。spring框架的注解也是在容器初始化的时候统一进行解析并处理,并不是在运行的时候处理的。
附:
在写测试代码的过程中,遇到了一个有趣的问题:明明对一个方法使用了注解,但是通过反射机制却获取不到这个注解,令我很疑惑。后来发现,这个注解的RetentionPolicy是RetentionPolicy.SOURCE,即只在源文件中起作用,在运行时已经被丢弃了,所以无法获取到。
思考这个问题的正确过程应该是这样的:首先应该发散联想类似的情况。如果没有类似的情况,就继续联想相关的熟悉的知识。如果还无法解决问题,就应该继续结合注解本身的知识来思考问题。整个过程是一个由远及近的发散联想的过程。
----------------------
第二个问题:我在练习组合注解的时候,自定义了三个注解A、B、C,我用A注解去注解C的时候,编译一直报错,报错信息为 The annotation @TestAnnotation2 is disallowed for this location 。我百思不得其解。最后发觉是A注解的作用目标中没有ElementType.ANNOTATION_TYPE,所以才报错。
这个问题的正确的思考过程,也应该和第一个问题一样,由远及近发散联想。
这里重申一下,发散联想是一切思考的基石和核心。