沉淀再出发:java中注解的本质和使用

时间:2023-03-08 15:42:12
沉淀再出发:java中注解的本质和使用

沉淀再出发:java中注解的本质和使用

一、前言

以前XML是各大框架的青睐者,它以松耦合的方式完成了框架中几乎所有的配置,但是随着项目越来越庞大,XML的内容也越来越复杂,维护成本变高。于是就有人提出来一种标记式高耦合的配置方式——注解。方法上可以进行注解,类上也可以注解,字段属性上也可以注解,反正几乎需要配置的地方都可以进行注解。关于注解和XML两种不同的配置模式,争论了好多年了,各有各的优劣,注解可以提供更大的便捷性,易于维护修改,但耦合度高,而XML相对于注解则是相反的。追求低耦合就要抛弃高效率,追求效率必然会遇到耦合。

二、注解的本质

沉淀再出发:java中注解的本质和使用

 /*
* Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/ package java.lang.annotation; /**
* The common interface extended by all annotation types. Note that an
* interface that manually extends this one does <i>not</i> define
* an annotation type. Also note that this interface does not itself
* define an annotation type.
*
* More information about annotation types can be found in section 9.6 of
* <cite>The Java&trade; Language Specification</cite>.
*
* The {@link java.lang.reflect.AnnotatedElement} interface discusses
* compatibility concerns when evolving an annotation type from being
* non-repeatable to being repeatable.
*
* @author Josh Bloch
* @since 1.5
*/
public interface Annotation {
/**
* Returns true if the specified object represents an annotation
* that is logically equivalent to this one. In other words,
* returns true if the specified object is an instance of the same
* annotation type as this instance, all of whose members are equal
* to the corresponding member of this annotation, as defined below:
* <ul>
* <li>Two corresponding primitive typed members whose values are
* <tt>x</tt> and <tt>y</tt> are considered equal if <tt>x == y</tt>,
* unless their type is <tt>float</tt> or <tt>double</tt>.
*
* <li>Two corresponding <tt>float</tt> members whose values
* are <tt>x</tt> and <tt>y</tt> are considered equal if
* <tt>Float.valueOf(x).equals(Float.valueOf(y))</tt>.
* (Unlike the <tt>==</tt> operator, NaN is considered equal
* to itself, and <tt>0.0f</tt> unequal to <tt>-0.0f</tt>.)
*
* <li>Two corresponding <tt>double</tt> members whose values
* are <tt>x</tt> and <tt>y</tt> are considered equal if
* <tt>Double.valueOf(x).equals(Double.valueOf(y))</tt>.
* (Unlike the <tt>==</tt> operator, NaN is considered equal
* to itself, and <tt>0.0</tt> unequal to <tt>-0.0</tt>.)
*
* <li>Two corresponding <tt>String</tt>, <tt>Class</tt>, enum, or
* annotation typed members whose values are <tt>x</tt> and <tt>y</tt>
* are considered equal if <tt>x.equals(y)</tt>. (Note that this
* definition is recursive for annotation typed members.)
*
* <li>Two corresponding array typed members <tt>x</tt> and <tt>y</tt>
* are considered equal if <tt>Arrays.equals(x, y)</tt>, for the
* appropriate overloading of {@link java.util.Arrays#equals}.
* </ul>
*
* @return true if the specified object represents an annotation
* that is logically equivalent to this one, otherwise false
*/
boolean equals(Object obj); /**
* Returns the hash code of this annotation, as defined below:
*
* <p>The hash code of an annotation is the sum of the hash codes
* of its members (including those with default values), as defined
* below:
*
* The hash code of an annotation member is (127 times the hash code
* of the member-name as computed by {@link String#hashCode()}) XOR
* the hash code of the member-value, as defined below:
*
* <p>The hash code of a member-value depends on its type:
* <ul>
* <li>The hash code of a primitive value <tt><i>v</i></tt> is equal to
* <tt><i>WrapperType</i>.valueOf(<i>v</i>).hashCode()</tt>, where
* <tt><i>WrapperType</i></tt> is the wrapper type corresponding
* to the primitive type of <tt><i>v</i></tt> ({@link Byte},
* {@link Character}, {@link Double}, {@link Float}, {@link Integer},
* {@link Long}, {@link Short}, or {@link Boolean}).
*
* <li>The hash code of a string, enum, class, or annotation member-value
I <tt><i>v</i></tt> is computed as by calling
* <tt><i>v</i>.hashCode()</tt>. (In the case of annotation
* member values, this is a recursive definition.)
*
* <li>The hash code of an array member-value is computed by calling
* the appropriate overloading of
* {@link java.util.Arrays#hashCode(long[]) Arrays.hashCode}
* on the value. (There is one overloading for each primitive
* type, and one for object reference types.)
* </ul>
*
* @return the hash code of this annotation
*/
int hashCode(); /**
* Returns a string representation of this annotation. The details
* of the representation are implementation-dependent, but the following
* may be regarded as typical:
* <pre>
* @com.acme.util.Name(first=Alfred, middle=E., last=Neuman)
* </pre>
*
* @return a string representation of this annotation
*/
String toString(); /**
* Returns the annotation type of this annotation.
* @return the annotation type of this annotation
*/
Class<? extends Annotation> annotationType();
}

Annotation的定义

2.1、什么是注解?

所有的注解类型都继承自Annotation这个普通的接口。我们看一个 JDK 内置注解的定义:

 @Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override { }

这是注解 @Override 的定义,本质上就是

 public interface Override extends Annotation{

 }

注解的本质就是一个继承了 Annotation 接口的接口。一个注解准确意义上来说,只不过是一种特殊的注释而已,如果没有解析它的代码,它可能连注释都不如。而解析一个类或者方法的注解往往有两种形式,一种是编译期直接的扫描,一种是运行期反射。编译器的扫描指的是编译器在对 java 代码编译字节码的过程中会检测到某个类或者方法被一些注解修饰,这时它就会对于这些注解进行某些处理。典型的就是注解 @Override,一旦编译器检测到某个方法被修饰了 @Override 注解,编译器就会检查当前方法的方法签名是否真正重写了父类的某个方法,也就是比较父类中是否具有一个同样的方法签名。这一种情况只适用于那些编译器已经熟知的注解类,比如 JDK 内置的几个注解,而自定义的注解,编译器是不知道这个注解的作用的,当然也不知道该如何处理,往往只是会根据该注解的作用范围来选择是否编译进字节码文件,仅此而已。没有元素的注解称为标记注解。元注解是用于修饰注解的注解,通常用在注解的定义上,如上面的 @Override 注解的定义,可以看到其中的 @Target,@Retention 两个注解就是我们所谓的元注解,元注解一般用于指定某个注解生命周期以及作用目标等信息。
    JAVA 中有以下几个元注解:

     @Target:注解的作用目标
@Retention:注解的生命周期
@Documented:注解是否应当被包含在 JavaDoc 文档中
@Inherited:是否允许子类继承该注解

其中 @Target 用于指明被修饰的注解最终可以作用的目标是谁,也就是指明注解到底是用来修饰方法、类,还是修饰字段属性的。

@Target 的定义

沉淀再出发:java中注解的本质和使用

我们可以通过以下的方式来为这个 value 传值:@Target(value = {ElementType.FIELD})
   被这个 @Target 注解修饰的注解将只能作用在成员字段上,不能用于修饰方法或者类。其中,ElementType 是一个枚举类型,有以下一些值:

     ElementType.TYPE:允许被修饰的注解作用在类、接口和枚举上
ElementType.FIELD:允许作用在属性字段上
ElementType.METHOD:允许作用在方法上
ElementType.PARAMETER:允许作用在方法参数上
ElementType.CONSTRUCTOR:允许作用在构造器上
ElementType.LOCAL_VARIABLE:允许作用在本地局部变量上
ElementType.ANNOTATION_TYPE:允许作用在注解上
ElementType.PACKAGE:允许作用在包上
 package java.lang.annotation;

 /**
* The constants of this enumerated type provide a simple classification of the
* syntactic locations where annotations may appear in a Java program. These
* constants are used in {@link Target java.lang.annotation.Target}
* meta-annotations to specify where it is legal to write annotations of a
* given type.
*
* <p>The syntactic locations where annotations may appear are split into
* <em>declaration contexts</em> , where annotations apply to declarations, and
* <em>type contexts</em> , where annotations apply to types used in
* declarations and expressions.
*
* <p>The constants {@link #ANNOTATION_TYPE} , {@link #CONSTRUCTOR} , {@link
* #FIELD} , {@link #LOCAL_VARIABLE} , {@link #METHOD} , {@link #PACKAGE} ,
* {@link #PARAMETER} , {@link #TYPE} , and {@link #TYPE_PARAMETER} correspond
* to the declaration contexts in JLS 9.6.4.1.
*
* <p>For example, an annotation whose type is meta-annotated with
* {@code @Target(ElementType.FIELD)} may only be written as a modifier for a
* field declaration.
*
* <p>The constant {@link #TYPE_USE} corresponds to the 15 type contexts in JLS
* 4.11, as well as to two declaration contexts: type declarations (including
* annotation type declarations) and type parameter declarations.
*
* <p>For example, an annotation whose type is meta-annotated with
* {@code @Target(ElementType.TYPE_USE)} may be written on the type of a field
* (or within the type of the field, if it is a nested, parameterized, or array
* type), and may also appear as a modifier for, say, a class declaration.
*
* <p>The {@code TYPE_USE} constant includes type declarations and type
* parameter declarations as a convenience for designers of type checkers which
* give semantics to annotation types. For example, if the annotation type
* {@code NonNull} is meta-annotated with
* {@code @Target(ElementType.TYPE_USE)}, then {@code @NonNull}
* {@code class C {...}} could be treated by a type checker as indicating that
* all variables of class {@code C} are non-null, while still allowing
* variables of other classes to be non-null or not non-null based on whether
* {@code @NonNull} appears at the variable's declaration.
*
* @author Joshua Bloch
* @since 1.5
* @jls 9.6.4.1 @Target
* @jls 4.1 The Kinds of Types and Values
*/
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
}

ElementType 定义

@Retention 用于指明当前注解的生命周期

也有一个 value 属性:@Retention(value = RetentionPolicy.RUNTIME
   这里的 RetentionPolicy 依然是一个枚举类型,它有以下几个枚举值可取:

     RetentionPolicy.SOURCE:当前注解编译期可见,不会写入 class 文件
RetentionPolicy.CLASS:类加载阶段丢弃,会写入 class 文件
RetentionPolicy.RUNTIME:永久保存,可以反射获取

沉淀再出发:java中注解的本质和使用

沉淀再出发:java中注解的本质和使用

@Retention 注解指定了被修饰的注解的生命周期,一种是只能在编译期可见,编译后会被丢弃,一种会被编译器编译进 class 文件中,无论是类或是方法,乃至字段,他们都是有属性表的,而 JAVA 虚拟机也定义了几种注解属性表用于存储注解信息,但是这种可见性不能带到方法区,类加载时会予以丢弃,最后一种则是永久存在的可见性。

沉淀再出发:java中注解的本质和使用
    剩下两种类型的注解我们日常用的不多,也比较简单,这里不再详细的进行介绍了,只需要知道他们各自的作用即可。@Documented 注解修饰的注解,当我们执行 JavaDoc 文档打包时会被保存进 doc 文档,反之将在打包时丢弃。@Inherited 注解修饰的注解是具有可继承性的,也就说我们的注解修饰了一个类,而该类的子类将自动继承父类的该注解。

JAVA 的内置三大注解

除了上述四种元注解外,JDK 还为我们预定义了另外三种注解,它们是:

     @Override,它没有任何的属性,所以并不能存储任何其他信息。它只能作用于方法之上,编译结束后将被丢弃。它就是一种典型的标记式注解,仅被编译器可知,编译器在对 java 文件进行编译成字节码的过程中,一旦检测到某个方法上被修饰了该注解,就会去匹对父类中是否具有一个同样方法签名的函数,如果不是,自然不能通过编译。
@Deprecated,依然是一种标记式注解,永久存在,可以修饰所有的类型,作用是标记当前的类或者方法或者字段等已经不再被推荐使用了,可能下一次的 JDK 版本就会删除。当然,编译器并不会强制要求我们做什么,只是告诉你我们JDK 已经不再推荐使用当前的方法或者类了,建议使用某个替代者。
@SuppressWarnings,主要用来压制 java 的警告,有一个 value 属性需要主动的传值,这个 value 代表的就是需要被压制的警告类型。而如果我们不希望程序启动时,编译器检查代码中过时的方法,就可以使用 @SuppressWarnings 注解并给它的 value 属性传入一个参数值来压制编译器的检查。这样编译器不再检查 main 方法下是否有过时的方法调用,也就压制了编译器对于这种警告的检查。当然,JAVA 中还有很多的警告类型,都会对应一个字符串,通过设置 value 属性的值即可压制对于这一类警告类型的检查。

2.2、自定义注解的使用

   步骤主要分为三部分:定义注解、使用注解、解析注解。
   定义注解:

 package com.annotation.test;

 import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; @Target(ElementType.METHOD)
//作用在方法上
@Retention(RetentionPolicy.RUNTIME)
//生命期编码、字节码到运行一直存在 public @interface ZyrAnnotation {
//定义注解的属性
String name(); //必选注解
int value() default 20;//可选属性
String desc() default "这是ZyrAnnotation注解";//可选属性
}

    使用注解:

 package com.annotation.test;

 //使用注解
public class AddZyrAnnotation { @ZyrAnnotation(name = "zyr",desc="正在使用show()...")
public void show(String str){
System.out.println(str);
} @ZyrAnnotation(name = "lsx",value=30)
public void print(String str){
System.out.println(str);
}
}

    解析注解:

 package com.annotation.test;

 import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; public class ParseZyrAnnotation { public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
//获取字节码对象
Class myclass = AddZyrAnnotation.class;
Method method1 = myclass.getMethod("show", String.class);
Method method2 = myclass.getMethod("print", String.class);
//获取方法上面的注解
ZyrAnnotation annotation1 = method1.getAnnotation(ZyrAnnotation.class);
ZyrAnnotation annotation2 = method2.getAnnotation(ZyrAnnotation.class);
//获取注解属性值并根据业务处理数据
//激活方法,也就是让方法执行
System.out.println(annotation1.name()+"\t"+annotation1.value()+"\t"+annotation1.desc());
method1.invoke(new AddZyrAnnotation(), "输出:zyr");
System.out.println(annotation2.name()+"\t"+annotation2.value()+"\t"+annotation2.desc());
method2.invoke(new AddZyrAnnotation(), "输出:lsx");
}
}

  运行结果:

沉淀再出发:java中注解的本质和使用

    由此我们也理解在一些框架中,比如springmvc里面的注解,其实都是被通过反射机制进行相应的处理之后产生作用的了,而这些注解的本质是继承了Annotation接口,这就是注解的本质。

三、总结

通过对注解的解析,我们更加的理解了一些框架背后做的事情,同样的培养了我们的框架思维,注解和xml两种方式的选择,在很多地方是必不可少的,我们把一些需要框架处理的定义成注解,把一些我们自己配置的定义成xml里面的bean,这样的一种折中非常的有用。

参考文献:https://www.cnblogs.com/yangming1996/p/9295168.html

https://www.cnblogs.com/qlqwjy/p/7139068.html