沉淀再出发:java中注解的本质和使用
沉淀再出发:java中注解的本质和使用
一、前言
以前XML是各大框架的青睐者,它以松耦合的方式完成了框架中几乎所有的配置,但是随着项目越来越庞大,XML的内容也越来越复杂,维护成本变高。于是就有人提出来一种标记式高耦合的配置方式——注解。方法上可以进行注解,类上也可以注解,字段属性上也可以注解,反正几乎需要配置的地方都可以进行注解。关于注解和XML两种不同的配置模式,争论了好多年了,各有各的优劣,注解可以提供更大的便捷性,易于维护修改,但耦合度高,而XML相对于注解则是相反的。追求低耦合就要抛弃高效率,追求效率必然会遇到耦合。
二、注解的本质
1 /* 2 * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. 3 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15 * 16 * 17 * 18 * 19 * 20 * 21 * 22 * 23 * 24 */ 25 26 package java.lang.annotation; 27 28 /** 29 * The common interface extended by all annotation types. Note that an 30 * interface that manually extends this one does <i>not</i> define 31 * an annotation type. Also note that this interface does not itself 32 * define an annotation type. 33 * 34 * More information about annotation types can be found in section 9.6 of 35 * <cite>The Java™ Language Specification</cite>. 36 * 37 * The {@link java.lang.reflect.AnnotatedElement} interface discusses 38 * compatibility concerns when evolving an annotation type from being 39 * non-repeatable to being repeatable. 40 * 41 * @author Josh Bloch 42 * @since 1.5 43 */ 44 public interface Annotation { 45 /** 46 * Returns true if the specified object represents an annotation 47 * that is logically equivalent to this one. In other words, 48 * returns true if the specified object is an instance of the same 49 * annotation type as this instance, all of whose members are equal 50 * to the corresponding member of this annotation, as defined below: 51 * <ul> 52 * <li>Two corresponding primitive typed members whose values are 53 * <tt>x</tt> and <tt>y</tt> are considered equal if <tt>x == y</tt>, 54 * unless their type is <tt>float</tt> or <tt>double</tt>. 55 * 56 * <li>Two corresponding <tt>float</tt> members whose values 57 * are <tt>x</tt> and <tt>y</tt> are considered equal if 58 * <tt>Float.valueOf(x).equals(Float.valueOf(y))</tt>. 59 * (Unlike the <tt>==</tt> operator, NaN is considered equal 60 * to itself, and <tt>0.0f</tt> unequal to <tt>-0.0f</tt>.) 61 * 62 * <li>Two corresponding <tt>double</tt> members whose values 63 * are <tt>x</tt> and <tt>y</tt> are considered equal if 64 * <tt>Double.valueOf(x).equals(Double.valueOf(y))</tt>. 65 * (Unlike the <tt>==</tt> operator, NaN is considered equal 66 * to itself, and <tt>0.0</tt> unequal to <tt>-0.0</tt>.) 67 * 68 * <li>Two corresponding <tt>String</tt>, <tt>Class</tt>, enum, or 69 * annotation typed members whose values are <tt>x</tt> and <tt>y</tt> 70 * are considered equal if <tt>x.equals(y)</tt>. (Note that this 71 * definition is recursive for annotation typed members.) 72 * 73 * <li>Two corresponding array typed members <tt>x</tt> and <tt>y</tt> 74 * are considered equal if <tt>Arrays.equals(x, y)</tt>, for the 75 * appropriate overloading of {@link java.util.Arrays#equals}. 76 * </ul> 77 * 78 * @return true if the specified object represents an annotation 79 * that is logically equivalent to this one, otherwise false 80 */ 81 boolean equals(Object obj); 82 83 /** 84 * Returns the hash code of this annotation, as defined below: 85 * 86 * <p>The hash code of an annotation is the sum of the hash codes 87 * of its members (including those with default values), as defined 88 * below: 89 * 90 * The hash code of an annotation member is (127 times the hash code 91 * of the member-name as computed by {@link String#hashCode()}) XOR 92 * the hash code of the member-value, as defined below: 93 * 94 * <p>The hash code of a member-value depends on its type: 95 * <ul> 96 * <li>The hash code of a primitive value <tt><i>v</i></tt> is equal to 97 * <tt><i>WrapperType</i>.valueOf(<i>v</i>).hashCode()</tt>, where 98 * <tt><i>WrapperType</i></tt> is the wrapper type corresponding 99 * to the primitive type of <tt><i>v</i></tt> ({@link Byte}, 100 * {@link Character}, {@link Double}, {@link Float}, {@link Integer}, 101 * {@link Long}, {@link Short}, or {@link Boolean}). 102 * 103 * <li>The hash code of a string, enum, class, or annotation member-value 104 I <tt><i>v</i></tt> is computed as by calling 105 * <tt><i>v</i>.hashCode()</tt>. (In the case of annotation 106 * member values, this is a recursive definition.) 107 * 108 * <li>The hash code of an array member-value is computed by calling 109 * the appropriate overloading of 110 * {@link java.util.Arrays#hashCode(long[]) Arrays.hashCode} 111 * on the value. (There is one overloading for each primitive 112 * type, and one for object reference types.) 113 * </ul> 114 * 115 * @return the hash code of this annotation 116 */ 117 int hashCode(); 118 119 /** 120 * Returns a string representation of this annotation. The details 121 * of the representation are implementation-dependent, but the following 122 * may be regarded as typical: 123 * <pre> 124 * @com.acme.util.Name(first=Alfred, middle=E., last=Neuman) 125 * </pre> 126 * 127 * @return a string representation of this annotation 128 */ 129 String toString(); 130 131 /** 132 * Returns the annotation type of this annotation. 133 * @return the annotation type of this annotation 134 */ 135 Class<? extends Annotation> annotationType(); 136 }
2.1、什么是注解?
所有的注解类型都继承自Annotation这个普通的接口。我们看一个 JDK 内置注解的定义:
1 @Target(ElementType.METHOD) 2 @Retention(RetentionPolicy.SOURCE) 3 public @interface Override { 4 5 }
这是注解 @Override 的定义,本质上就是:
1 public interface Override extends Annotation{ 2 3 }
注解的本质就是一个继承了 Annotation 接口的接口。一个注解准确意义上来说,只不过是一种特殊的注释而已,如果没有解析它的代码,它可能连注释都不如。而解析一个类或者方法的注解往往有两种形式,一种是编译期直接的扫描,一种是运行期反射。编译器的扫描指的是编译器在对 java 代码编译字节码的过程中会检测到某个类或者方法被一些注解修饰,这时它就会对于这些注解进行某些处理。典型的就是注解 @Override,一旦编译器检测到某个方法被修饰了 @Override 注解,编译器就会检查当前方法的方法签名是否真正重写了父类的某个方法,也就是比较父类中是否具有一个同样的方法签名。这一种情况只适用于那些编译器已经熟知的注解类,比如 JDK 内置的几个注解,而自定义的注解,编译器是不知道这个注解的作用的,当然也不知道该如何处理,往往只是会根据该注解的作用范围来选择是否编译进字节码文件,仅此而已。没有元素的注解称为标记注解。元注解是用于修饰注解的注解,通常用在注解的定义上,如上面的 @Override 注解的定义,可以看到其中的 @Target,@Retention 两个注解就是我们所谓的元注解,元注解一般用于指定某个注解生命周期以及作用目标等信息。
JAVA 中有以下几个元注解:
1 @Target:注解的作用目标 2 @Retention:注解的生命周期 3 @Documented:注解是否应当被包含在 JavaDoc 文档中 4 @Inherited:是否允许子类继承该注解
其中 @Target 用于指明被修饰的注解最终可以作用的目标是谁,也就是指明注解到底是用来修饰方法、类,还是修饰字段属性的。
@Target 的定义
我们可以通过以下的方式来为这个 value 传值:@Target(value = {ElementType.FIELD})
被这个 @Target 注解修饰的注解将只能作用在成员字段上,不能用于修饰方法或者类。其中,ElementType 是一个枚举类型,有以下一些值:
1 ElementType.TYPE:允许被修饰的注解作用在类、接口和枚举上 2 ElementType.FIELD:允许作用在属性字段上 3 ElementType.METHOD:允许作用在方法上 4 ElementType.PARAMETER:允许作用在方法参数上 5 ElementType.CONSTRUCTOR:允许作用在构造器上 6 ElementType.LOCAL_VARIABLE:允许作用在本地局部变量上 7 ElementType.ANNOTATION_TYPE:允许作用在注解上 8 ElementType.PACKAGE:允许作用在包上
1 package java.lang.annotation; 2 3 /** 4 * The constants of this enumerated type provide a simple classification of the 5 * syntactic locations where annotations may appear in a Java program. These 6 * constants are used in {@link Target java.lang.annotation.Target} 7 * meta-annotations to specify where it is legal to write annotations of a 8 * given type. 9 * 10 * <p>The syntactic locations where annotations may appear are split into 11 * <em>declaration contexts</em> , where annotations apply to declarations, and 12 * <em>type contexts</em> , where annotations apply to types used in 13 * declarations and expressions. 14 * 15 * <p>The constants {@link #ANNOTATION_TYPE} , {@link #CONSTRUCTOR} , {@link 16 * #FIELD} , {@link #LOCAL_VARIABLE} , {@link #METHOD} , {@link #PACKAGE} , 17 * {@link #PARAMETER} , {@link #TYPE} , and {@link #TYPE_PARAMETER} correspond 18 * to the declaration contexts in JLS 9.6.4.1. 19 * 20 * <p>For example, an annotation whose type is meta-annotated with 21 * {@code @Target(ElementType.FIELD)} may only be written as a modifier for a 22 * field declaration. 23 * 24 * <p>The constant {@link #TYPE_USE} corresponds to the 15 type contexts in JLS 25 * 4.11, as well as to two declaration contexts: type declarations (including 26 * annotation type declarations) and type parameter declarations. 27 * 28 * <p>For example, an annotation whose type is meta-annotated with 29 * {@code @Target(ElementType.TYPE_USE)} may be written on the type of a field 30 * (or within the type of the field, if it is a nested, parameterized, or array 31 * type), and may also appear as a modifier for, say, a class declaration. 32 * 33 * <p>The {@code TYPE_USE} constant includes type declarations and type 34 * parameter declarations as a convenience for designers of type checkers which 35 * give semantics to annotation types. For example, if the annotation type 36 * {@code NonNull} is meta-annotated with 37 * {@code @Target(ElementType.TYPE_USE)}, then {@code @NonNull} 38 * {@code class C {...}} could be treated by a type checker as indicating that 39 * all variables of class {@code C} are non-null, while still allowing 40 * variables of other classes to be non-null or not non-null based on whether 41 * {@code @NonNull} appears at the variable's declaration. 42 * 43 * @author Joshua Bloch 44 * @since 1.5 45 * @jls 9.6.4.1 @Target 46 * @jls 4.1 The Kinds of Types and Values 47 */ 48 public enum ElementType { 49 /** Class, interface (including annotation type), or enum declaration */ 50 TYPE, 51 52 /** Field declaration (includes enum constants) */ 53 FIELD, 54 55 /** Method declaration */ 56 METHOD, 57 58 /** Formal parameter declaration */ 59 PARAMETER, 60 61 /** Constructor declaration */ 62 CONSTRUCTOR, 63 64 /** Local variable declaration */ 65 LOCAL_VARIABLE, 66 67 /** Annotation type declaration */ 68 ANNOTATION_TYPE, 69 70 /** Package declaration */ 71 PACKAGE, 72 73 /** 74 * Type parameter declaration 75 * 76 * @since 1.8 77 */ 78 TYPE_PARAMETER, 79 80 /** 81 * Use of a type 82 * 83 * @since 1.8 84 */ 85 TYPE_USE 86 }
@Retention 用于指明当前注解的生命周期
也有一个 value 属性:@Retention(value = RetentionPolicy.RUNTIME
这里的 RetentionPolicy 依然是一个枚举类型,它有以下几个枚举值可取:
1 RetentionPolicy.SOURCE:当前注解编译期可见,不会写入 class 文件 2 RetentionPolicy.CLASS:类加载阶段丢弃,会写入 class 文件 3 RetentionPolicy.RUNTIME:永久保存,可以反射获取
@Retention 注解指定了被修饰的注解的生命周期,一种是只能在编译期可见,编译后会被丢弃,一种会被编译器编译进 class 文件中,无论是类或是方法,乃至字段,他们都是有属性表的,而 JAVA 虚拟机也定义了几种注解属性表用于存储注解信息,但是这种可见性不能带到方法区,类加载时会予以丢弃,最后一种则是永久存在的可见性。
剩下两种类型的注解我们日常用的不多,也比较简单,这里不再详细的进行介绍了,只需要知道他们各自的作用即可。@Documented 注解修饰的注解,当我们执行 JavaDoc 文档打包时会被保存进 doc 文档,反之将在打包时丢弃。@Inherited 注解修饰的注解是具有可继承性的,也就说我们的注解修饰了一个类,而该类的子类将自动继承父类的该注解。
JAVA 的内置三大注解
除了上述四种元注解外,JDK 还为我们预定义了另外三种注解,它们是:
1 @Override,它没有任何的属性,所以并不能存储任何其他信息。它只能作用于方法之上,编译结束后将被丢弃。它就是一种典型的标记式注解,仅被编译器可知,编译器在对 java 文件进行编译成字节码的过程中,一旦检测到某个方法上被修饰了该注解,就会去匹对父类中是否具有一个同样方法签名的函数,如果不是,自然不能通过编译。 2 @Deprecated,依然是一种标记式注解,永久存在,可以修饰所有的类型,作用是标记当前的类或者方法或者字段等已经不再被推荐使用了,可能下一次的 JDK 版本就会删除。当然,编译器并不会强制要求我们做什么,只是告诉你我们JDK 已经不再推荐使用当前的方法或者类了,建议使用某个替代者。 3 @SuppressWarnings,主要用来压制 java 的警告,有一个 value 属性需要主动的传值,这个 value 代表的就是需要被压制的警告类型。而如果我们不希望程序启动时,编译器检查代码中过时的方法,就可以使用 @SuppressWarnings 注解并给它的 value 属性传入一个参数值来压制编译器的检查。这样编译器不再检查 main 方法下是否有过时的方法调用,也就压制了编译器对于这种警告的检查。当然,JAVA 中还有很多的警告类型,都会对应一个字符串,通过设置 value 属性的值即可压制对于这一类警告类型的检查。
2.2、自定义注解的使用
步骤主要分为三部分:定义注解、使用注解、解析注解。
定义注解:
1 package com.annotation.test; 2 3 import java.lang.annotation.ElementType; 4 import java.lang.annotation.Retention; 5 import java.lang.annotation.RetentionPolicy; 6 import java.lang.annotation.Target; 7 8 9 @Target(ElementType.METHOD) 10 //作用在方法上 11 @Retention(RetentionPolicy.RUNTIME) 12 //生命期编码、字节码到运行一直存在 13 14 public @interface ZyrAnnotation { 15 //定义注解的属性 16 String name(); //必选注解 17 int value() default 20;//可选属性 18 String desc() default "这是ZyrAnnotation注解";//可选属性 19 }
使用注解:
1 package com.annotation.test; 2 3 //使用注解 4 public class AddZyrAnnotation { 5 6 @ZyrAnnotation(name = "zyr",desc="正在使用show()...") 7 public void show(String str){ 8 System.out.println(str); 9 } 10 11 @ZyrAnnotation(name = "lsx",value=30) 12 public void print(String str){ 13 System.out.println(str); 14 } 15 }
解析注解:
1 package com.annotation.test; 2 3 4 import java.lang.reflect.InvocationTargetException; 5 import java.lang.reflect.Method; 6 7 public class ParseZyrAnnotation { 8 9 public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { 10 //获取字节码对象 11 Class myclass = AddZyrAnnotation.class; 12 Method method1 = myclass.getMethod("show", String.class); 13 Method method2 = myclass.getMethod("print", String.class); 14 //获取方法上面的注解 15 ZyrAnnotation annotation1 = method1.getAnnotation(ZyrAnnotation.class); 16 ZyrAnnotation annotation2 = method2.getAnnotation(ZyrAnnotation.class); 17 //获取注解属性值并根据业务处理数据 18 //激活方法,也就是让方法执行 19 System.out.println(annotation1.name()+"\t"+annotation1.value()+"\t"+annotation1.desc()); 20 method1.invoke(new AddZyrAnnotation(), "输出:zyr"); 21 System.out.println(annotation2.name()+"\t"+annotation2.value()+"\t"+annotation2.desc()); 22 method2.invoke(new AddZyrAnnotation(), "输出:lsx"); 23 } 24 }
运行结果:
由此我们也理解在一些框架中,比如springmvc里面的注解,其实都是被通过反射机制进行相应的处理之后产生作用的了,而这些注解的本质是继承了Annotation接口,这就是注解的本质。
三、总结
通过对注解的解析,我们更加的理解了一些框架背后做的事情,同样的培养了我们的框架思维,注解和xml两种方式的选择,在很多地方是必不可少的,我们把一些需要框架处理的定义成注解,把一些我们自己配置的定义成xml里面的bean,这样的一种折中非常的有用。