前言:📫 作者简介:小明java问道之路,专注于研究计算机底层,就职于金融公司后端高级工程师,擅长交易领域的高安全/可用/并发/性能的设计和架构📫
🏆 Java领域优质创作者、阿里云专家博主、华为云享专家🏆
🔥 如果此文还不错的话,还请👍关注、点赞、收藏三连支持👍一下博主哦
本文导读
什么是注解?我们在工程代码中一定看过@Request,@Service,@Override,@Autowired等等,这个就是注解了,那我们看看官网是怎么说的这个@注解(Annotation)
1、https://docs.oracle.com/javase/1.5.0/docs/guide/
2、https://docs.oracle.com/javase/1.5.0/docs/guide/apt/index.html
3、https://docs.oracle.com/javase/1.5.0/docs/guide/language/annotations.html
What is Annotation in JDK1. 5?
Annotation Processing
The apt tool is a command-line utility for annotation processing. It includes a set of reflective APIs and supporting infrastructure to process program annotations. These reflective APIs provide a build-time, source-based, read-only view of program structure. They are designed to cleanly model the Java programming language's type system after the addition of generics. For more information, see the apt documentation.
注解处理
apt工具是用于注解处理的命令行实用程序。 它包括一组反射API和用于处理程序注解的支持基础结构。 这些反射API提供了程序结构的构建时基于源的只读视图。 它们被设计为在添加泛型之后对Java编程语言的类型系统进行干净的建模。 有关更多信息,请参见apt文档。
What is Annotation Processing Tool(APT) 1. 5?
Annotation Processing Tool (apt) is a command-line utility for annotation processing. It includes a set of reflective APIs and supporting infrastructure to process program annotations (JSR 175). These reflective APIs provide a build-time, source-based, read-only view of program structure. They are designed to cleanly model the JavaTM programming language's type system after the addition of generics (JSR 14).
Annotation Processing Tool (apt) first runs annotation processors that can produce new source code and other files. Next,
apt
can cause compilation of both original and generated source files, thus easing the development cycle注解处理工具 (apt) 是用于注解处理的命令行实用程序。 它包括一组反射API和用于处理程序注解的支持基础结构(JSR175)。 这些反射API提供了程序结构的构建时基于源的只读视图。 它们被设计为在添加泛型(JSR14)之后对JavaTM编程语言的类型系统进行干净的建模。
注解处理工具 (apt) 首先运行注解处理器,该注解处理器可以生成新的源代码和其他文件。 接下来,apt可能导致原始文件和生成的源文件都被编译,从而简化了开发周期。
What is Annotation?
Many APIs require a fair amount of boilerplate code. For example, in order to write a JAX-RPC web service, you must provide a paired interface and implementation. This boilerplate could be generated automatically by a tool if the program were “decorated” with annotations indicating which methods were remotely accessible.
Other APIs require “side files” to be maintained in parallel with programs. For example JavaBeans requires a BeanInfo class to be maintained in parallel with a bean, and Enterprise JavaBeans (EJB) requires a deployment descriptor. It would be more convenient and less error-prone if the information in these side files were maintained as annotations in the program itself.
The Java platform has always had various ad hoc annotation mechanisms. For example the
transient
modifier is an ad hoc annotation indicating that a field should be ignored by the serialization subsystem, and the@deprecated
javadoc tag is an ad hoc annotation indicating that the method should no longer be used. As of release 5.0, the platform has a general purpose annotation (also known as metadata) facility that permits you to define and use your own annotation types. The facility consists of a syntax for declaring annotation types, a syntax for annotating declarations, APIs for reading annotations, a class file representation for annotations, and an annotation processing tool.许多APIs需要相当数量的样板代码。例如,为了编写JAX-RPCWeb服务,必须提供成对的接口和实现。如果程序用注解“修饰”来表示哪些方法可以远程访问,那么这个样板可以由工具自动生成。
其他APIs要求“side file”与程序并行维护。例如,JavaBeans需要一个BeanInfo类与一个bean并行维护,而enterprisejavabeans(EJB)则需要一个部署描述符。如果这些辅助文件中的信息作为程序本身的注解来维护,则会更加方便,并且不容易出错。
Java平台一直有各种特别的注解机制。例如,transient修饰符是一个临时注解,指示序列化子系统应忽略某个字段,@deprecated javadoc标记是一个临时注解,指示不再使用该方法。从5.0版开始,该平台有一个通用的注解(也称为元数据)工具,允许您定义和使用自己的注解类型。该工具由声明注解类型的语法、注解声明的语法、读取注解的api、注解的类文件表示和注解处理工具组成。
想了很久也没能用白话解释什么是注解,还是找了一个相对准确一些的解释,一般来说:
What is Annotation?Java注解是Java5. 0版本开始支持特殊语法的元数据,Java中的类、方法、变量、参数都可以通过注解进行标记,并可以通过反射方式获取注解的元数据信息。
注解的学习目标可以分为5个部分
一、Why use Annotation?理解注解给Java带来的优雅之道
此处不多介绍直接上八股文:注解的使用可以简化配置信息,易于维护代码,可读性强,同时符合代码Convention Plugin替换Codebehind Plugin的原则(约定大于配置原则)
后面篇幅会展开说明注解的本质
二、理解注解的本质和原理
1、什么是元数据?
引言中注解定义加黑部分说注解就是一类特殊的元数据,元数据的概念可以这么理解,元数据本质上来讲就是对数据进行描述的数据,这个概念比较抽象,我们暂且理解为对我们自己的
数据例如 类、方法、属性,进行标注描述的数据。比如我们经常会用到的Java doc,这其实就是属于元数据的一种,总的来说元数据可以创建文档,执行编译器检查,替代配置文档等。
2、从字节码角度了解注解
上八股文:注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池。
下面对这句话,逐一讲解:这是Annotation.java的源码,从源码中可以看出,Anntation是一个接口
/** * 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™ 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 {
首先我们自定义一个注解:
很明显HelloAnnotation就是继承了Annotation的接口
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 先自定义一个运行时注解 * @author mac * @date 2021/3/28-10:19 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface HelloAnnotation { String say(); }
调用该注解内部元数据:
@HelloAnnotation(say = "TestAnnotation") public class TestAnnotation { public static void main(String[] args) { /** * 设置此系统属性,让JVM生成的Proxy类写入文件.保存路径为:com/sun/proxy(如果不存在请手工创建) */ System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); // 获取TestMain类上的注解对象 HelloAnnotation annotation = TestAnnotation.class.getAnnotation(HelloAnnotation.class); // 调用注解对象的say方法,并打印到控制台 System.out.println(annotation.say()); } }
打好断点执行,发现annotation对象是,查看代理对象(代码中使用System.setProperty,同理也可以使用jvm参数-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true)
从第一行我们可以看到,我们自定义的注解HelloAnnotation是一个接口,而$Proxy1这个Java生成的动态代理类就是它的实现类。(↓ 大量反编译代码预警)
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package com.sun.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; import naixue.HelloAnnotation; public final class $Proxy1 extends Proxy implements HelloAnnotation { private static Method m1; private static Method m2; private static Method m4; private static Method m3; private static Method m0; public $Proxy1(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final Class annotationType() throws { try { return (Class)super.h.invoke(this, m4, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final String say() throws { try { // say方法最终会执行(String)super.h.invoke(this, m3, (Object[])null); // 而这其中的h对象类型就是InvocationHandler接口的某个实现类 return (String)super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final int hashCode() throws { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m2 = Class.forName("java.lang.Object").getMethod("toString"); m4 = Class.forName("***.HelloAnnotation").getMethod("annotationType"); m3 = Class.forName("***.HelloAnnotation").getMethod("say"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
say方法最终会执行(String)super.h.invoke(this, m3, (Object[])null);,而这其中的h对象类型就是InvocationHandler接口的某个实现类(AnnotationInvocationHandler
的实例)
查看invoke实现,可以看到,memberValues是通过常量池获取到,return var2.getUTF8At(var3);
中的var3就是常量池中的序号(var2就是常量池),继续执行返回到parseMemberValue()方法;将var14 key(方法名)和var16 value (结果值)put到Map中
背诵总结:注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池。
3、注解处理器
首先我们要了解一下注解处理器是什么, 注解处理器是javac的一个工具用于编译期扫描处理对应的注解的工具,也就是我们可以利用对java源代码的编译期可以做做一些自己的逻辑处理,比如我们动态的为我们的类动态的生成get set。定义注解处理器需要继承一个 AbstractProcessor,并重写 Process 方法,当程序编译时扫描到对应的注解Javac工具会自动回调 Process 方法。
我们接下来看一个简单的例子。
import javax.annotation.processing.*; import javax.lang.model.SourceVersion; import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic; import java.util.Set; // 自定义注解处理器 @SupportedAnnotationTypes("MyAnnotation") @SupportedSourceVersion(SourceVersion.RELEASE_8) public class MyAnnotationProcess extends AbstractProcessor { Messager messager; @Override public synchronized void init(ProcessingEnvironment processingEnv) { messager = processingEnv.getMessager(); super.init(processingEnv); } public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { messager.printMessage(Diagnostic.Kind.NOTE, "Annotation Process"); return false; } }
三、理解注解定义和应用场景
1、什么是标准注解?
元注解也叫标准注解,不要跟上述元数据混淆,在 java 5.0 中提供了 4 种特殊的标准注解,这 4 种注解为其他注解提供注解能力。我们称这四种注解为元注解,元注解包括,@RetentionPolicy、@Target、@Docment、@Inherited,接下来我们详细针对这四
种标签进行一一展开。
2、@RetentionPolicy、@Target、@Docment、@Inherited 作用
(1)@Retention
该注解从单纯的单词来看,保留,那保留的是什么呢?保留其实是对其作用的注解类的一个生命周期或作用范围的定义,理解这个我们需要首先要理解我们编写的 java 原文是如何被 JVM 所执行的,一个 java源文件要被 JVM 执行大概需要经过 2 部分 java 源文件 -> 字节码(class) -> 二进制机器码。
Retention 可以接收 RetentionPolicy 枚举类型,枚举定义的三种策略,我们来看一下支持那三种类型:
package java.lang.annotation; /** * Annotation retention policy. The constants of this enumerated type * describe the various policies for retaining annotations. They are used * in conjunction with the {@link Retention} meta-annotation type to specify * how long annotations are to be retained. * * @author Joshua Bloch * @since 1.5 */ 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 }
SOURCE:注解将被编译器丢弃。(只会保留到 java 源代码阶段在编译期就会被编译期抛弃,所以通过反射获取返回 null)
CLASS:注解被编译器记录在类文件中,但在 VM 运行时不会被保留。(只会保留到编译期也就是 class 文件中,但是在运行时会被 JVM 抛弃,所以这里通过反射
获取也是 null)
RUNTIME:注解被编译器记录在类文件中,在 VM 运行时也会被保留,所以可以通过反射获取。(会保留注解到运行阶段所以通过反射可以获取到注解对象)
(2)@Target
Targt 这里表示可被作用的目标类型,其接受一个 ElementType 的枚举类型数组。
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 }
TYPE:作用为类
FIELD:作用属性
METHOD:作用方法
PARAMETER:作用方法参数
CONSTRUCTOR:作用构造方法
LOCAL_VARIABEL:作用本地变量
PACKAGE:作用于包
TYPE_PARAMETER:1.8 提供 作用于泛型参数
TYPE_USE:表示可以作用在包和方法除外的任何类型
(3)@Document 作用
被@Document 注解标记的注解将会在生成 Javadoc 时被包含在 Javadoc 中,注解默认在生成 Javadoc 中不会包含。
(4)@Inherited 作用
允许被子类继承的注解,注解默认是不能被继承的,但是在使用 Inherited 注解后子类可以继承父类的注解。
3、内置注解
内置注解是 Java 开发人员预先定制好的注解,可供开发人员直接使用。(@Override、@Deprecated、@SuppressWarings、@SafeVarargs (JDK7引入)、@FunctionalInterface (JDK 8引入))
@Override:用于表示覆盖了父类的方法,如果尝试使用 Override 标注一个父类没有方法将会报错,使用该注解的好处在于可以比较直观明确的知道子类重写了父类的那些方法。
@Deprecated:用于标记一个类、方法、属性,被标记的 类、方法、属性表示过时或废弃的、以及不建议使用。具有一定的延续性,如果一个父类被标记为 Deprecated,那么即使子类没有标记在被使用时编译期也会产生警告。
@SuppressWarings:用于关闭、方法、成员、编译时产生的特定警告的处理。
@SafeVarargs (JDK7 引入):用于告诉编译器在可变长参数中的泛型是类型安全的,可变长参数是本质上是一个数组,但是在java 中数组和泛型并不能很好的混合使用。
@FunctionalInterface (JDK 8 引入):用于标记一个函数式接口,这里需要注意注解标注的接口必须满足函数式接口的定义,如果接口不符合函数式接口定义将会报错。
四、注解和反射在框架中的实战
在 Spring 中有很多提供了很多注解,这里给大家介绍一些常用的注解共大家来参考。
@Autowired 提供依赖注入的一个注解,注解可以作用在类、构造方法、方法、参数、属性。
@Bean 可以将一个对象注册到 Spring 容器中,转换为 Spring 容器中的 Bean 可以理解最早通过XML 文件中的 Bean 标签。
@Configuration 配置类注解,这个注解可以帮助我们做一些配置型的工作,比如我们想配置一个数据源,或者配置一些项目需要的变量,那么可以使用这个注解。
@Qualifier 依赖注入类注解,可以指定注入某一个具体的注解,比如我们现在有两个类都继承了同一个接口那么在 Spring 注入的时候可以就不知道究竟要注入那个具体的类这个时候如果没有指定Spring 就会报错,所以这个时候我们可以通过@Qualifier 告诉容器我们具体是需要使用那个注解。