Java 中注解的实现原理

一、引言

在 Java5 之前,利用 xml 进行配置是各大框架的常规操作,这种方式可以实现松耦合并完成框架中几乎所有需要的配置,但随着项目的扩展,xml 文件本身的内容将变得十分复杂,维护成本大大提升。

所以就有人提出使用一种标记式高耦合的配置方式,这种方式可以提供类似注释的机制,用来将信息或者元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。这种关联为程序的元素(类、方法、成员变量)加上更为直观的说明,不过这些说明与程序本身的业务逻辑无关,只用来提供给特定的工具或者框架使用。

二、什么是注解,其本质何?

Java 中的注解是附加在代码中的一些源信息,用于在一些工具在编译、运行时进行解释和使用,起到说明,配置的功能。你可以将注解理解为一种修饰符,应用于类、方法、参数、变量的声明语句中。注解不会也不能影响代码本身的业务逻辑,仅仅只能起到辅助性的作用。

Java 中对于 Annotation 接口的描述中有这么一段话:

意为:此接口是所有注解类型都继承的普通接口。这句话可能难以理解。以我们常见的@Override 注解进行举例,它的源码如下所示:

  1. @Target(ElementType.METHOD)
  2. @Retention(RetentionPolicy.SOURCE)
  3. public @interface Override {
  4. }

将上面代码以我们常见格式理解的话,就应该是下面这个亚子了:

  1. public interface Override extends Annotation {
  2. }

现在就可以理解,所谓注解,其本质也就是一个继承了 Annotation 接口的接口而已。

而作为一个接口,如果没有其实现类以及对应的解析代码的话,其作用可能还不如一段注释来得有用。

所以当我们定义好一个注解时,需要底层代码的协助,注解才会发挥其应有的作用。注解作为一个特殊的接口,其实现类是在代码运行时生成的动态代理类,而之后底层代码通过反射的方式获取到注解时,其会返回 Java 运行时生成的动态代理对象 $Proxy。通过代理对象代理对象调用自定义注解(接口)的方法,会最终调用 AnnotationInvocationHandlerinvoke 方法。该方法会从 memberValues 这个 Map 中索引出对应的值。而 memberValues 的来源是Java 常量池。

三、元注解

既然注解的本质只是一个接口,要实现其特定功能还需要底层代码的协助,那我们就必须先让底层代码知道我们让这个注解干什么?Java 中提供了元注解帮助我们来解决问题。

元注解是 Java 提供的用于修饰注解的注解,用于自定义注解。

annotation 一共提供了四种元注解,分别是:

@Target: 注解所作用的目标;

@Retention: 注解的声明周期;

@Inherited: 表示是否允许被继承;

@Documented: 注解是否将包含在JavaDoc中;

@Target

@Target 用于指明被修饰的注解最终所作用的目标,即用来指明这个注解最终是用来修饰方法,还是修饰类,还是修饰属性?

@Target 的源代码如下所示:

  1. @Documented
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target(ElementType.ANNOTATION_TYPE)
  4. public @interface Target {
  5. /**
  6. * Returns an array of the kinds of elements an annotation type
  7. * can be applied to.
  8. * @return an array of the kinds of elements an annotation type
  9. * can be applied to
  10. */
  11. ElementType[] value();
  12. }

以上面 @Override 的源代码举例的话,@Target(ElementType.METHOD),这段代码就表明了这个注解只能用于修饰方法,而不能用来修饰类或者属性。其中 ElementType 是一个枚举类型,有以下值可供选择:

ElementType.TYPE:允许被修饰的注解作用在类、接口和枚举上

ElementType.FIELD:允许作用在属性字段上

ElementType.METHOD:允许作用在方法上

ElementType.PARAMETER:允许作用在方法参数上

ElementType.CONSTRUCTOR:允许作用在构造器上

ElementType.LOCAL_VARIABLE:允许作用在本地局部变量上

ElementType.ANNOTATION_TYPE:允许作用在注解上

ElementType.PACKAGE:允许作用在包上

@Retation

@Retention 用于指明当前注解的生命周期,源代码如下:

  1. @Documented
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target(ElementType.ANNOTATION_TYPE)
  4. public @interface Retention {
  5. /**
  6. * Returns the retention policy.
  7. * @return the retention policy
  8. */
  9. RetentionPolicy value();
  10. }

与上面一致,该注释也有一个 value 属性,也提供了 RetentionPolicy 这一枚举类型,其包括以下值:

RetentionPolicy.SOURCE : 在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节。@Override, @SuppressWarnings都属于这类注解。

RetentionPolicy.CLASS : 在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式

RetentionPolicy.RUNTIME : 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。

@Documented & @Inherited

@Documented 注解修饰的注解,当我们执行 JavaDoc 文档打包时会被保存进 doc 文档,反之将在打包时丢弃。

@Inherited 注解修饰的注解是具有可继承性的,也就说我们的注解修饰了一个类,而该类的子类将自动继承父类的该注解。

四、常见 Annotation

@Override

java.lang.Override 是一个标记类型注解,它被用作标注方法。它说明了被标注的方法重载了父类的方法,起到了断言的作用。如果我们使用了这种注解在一个没有覆盖父类方法的方法时,java 编译器将以一个编译错误来警示。

@Deprecated

Deprecated 也是一种标记类型注解。当一个类型或者类型成员使用@Deprecated 修饰的话,编译器将不鼓励使用这个被标注的程序元素。所以使用这种修饰具有一定的“延续性”:如果我们在代码中通过继承或者覆盖的方式使用了这个过时的类型或者成员,虽然继承或者覆盖后的类型或者成员并不是被声明为@Deprecated,但编译器仍然要报警。

@SuppressWarnings

SuppressWarning 不是一个标记类型注解。它有一个类型为String[] 的成员,这个成员的值为被禁止的警告名。对于javac 编译器来讲,被-Xlint 选项有效的警告名也同样对@SuppressWarings 有效,同时编译器忽略掉无法识别的警告名。@SuppressWarnings("unchecked")

五、自定义注解

自定义注解类编写的一些规则:

  1. Annotation 型定义为@interface, 所有的Annotation 会自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口.
  2. 参数成员只能用public 或默认(default) 这两个访问权修饰
  3. 参数成员只能用基本类型byte、short、char、int、long、float、double、boolean八种基本数据类型和String、Enum、Class、annotations等数据类型,以及这一些类型的数组.
  4. 要获取类方法和字段的注解信息,必须通过Java的反射技术来获取 Annotation 对象,因为你除此之外没有别的获取注解对象的方法
  5. 注解也可以没有定义成员,,不过这样注解就没啥用了
  1. PS:自定义注解需要使用到元注解

六、自定义注解实例

FruitName.java

  1. import java.lang.annotation.Documented;
  2. import java.lang.annotation.Retention;
  3. import java.lang.annotation.Target;
  4. import static java.lang.annotation.ElementType.FIELD;
  5. import static java.lang.annotation.RetentionPolicy.RUNTIME;
  6. /**
  7. * 水果名称注解
  8. */
  9. @Target(ElementType.FIELD)
  10. @Retention(RUNTIME)
  11. @Documented
  12. public @interface FruitName {
  13. String value() default "";
  14. }

FruitColor.java

  1. import java.lang.annotation.Documented;
  2. import java.lang.annotation.Retention;
  3. import java.lang.annotation.Target;
  4. import static java.lang.annotation.ElementType.FIELD;
  5. import static java.lang.annotation.RetentionPolicy.RUNTIME;
  6. /**
  7. * 水果颜色注解
  8. */
  9. @Target(FIELD)
  10. @Retention(RUNTIME)
  11. @Documented
  12. public @interface FruitColor {
  13. /**
  14. * 颜色枚举
  15. */
  16. public enum Color{ BLUE,RED,GREEN};
  17. /**
  18. * 颜色属性
  19. */
  20. Color fruitColor() default Color.GREEN;
  21. }

FruitProvider.java

  1. import java.lang.annotation.Documented;
  2. import java.lang.annotation.Retention;
  3. import java.lang.annotation.Target;
  4. import static java.lang.annotation.ElementType.FIELD;
  5. import static java.lang.annotation.RetentionPolicy.RUNTIME;
  6. /**
  7. * 水果供应者注解
  8. */
  9. @Target(FIELD)
  10. @Retention(RUNTIME)
  11. @Documented
  12. public @interface FruitProvider {
  13. /**
  14. * 供应商编号
  15. */
  16. public int id() default -1;
  17. /**
  18. * 供应商名称
  19. */
  20. public String name() default "";
  21. /**
  22. * 供应商地址
  23. */
  24. public String address() default "";
  25. }

FruitInfoUtil.java

  1. import java.lang.reflect.Field;
  2. /**
  3. * 注解处理器
  4. */
  5. public class FruitInfoUtil {
  6. public static void getFruitInfo(Class<?> clazz){
  7. String strFruitName=" 水果名称:";
  8. String strFruitColor=" 水果颜色:";
  9. String strFruitProvicer="供应商信息:";
  10. Field[] fields = clazz.getDeclaredFields();
  11. for(Field field :fields){
  12. if(field.isAnnotationPresent(FruitName.class)){
  13. FruitName fruitName = (FruitName) field.getAnnotation(FruitName.class);
  14. strFruitName=strFruitName+fruitName.value();
  15. System.out.println(strFruitName);
  16. }
  17. else if(field.isAnnotationPresent(FruitColor.class)){
  18. FruitColor fruitColor= (FruitColor) field.getAnnotation(FruitColor.class);
  19. strFruitColor=strFruitColor+fruitColor.fruitColor().toString();
  20. System.out.println(strFruitColor);
  21. }
  22. else if(field.isAnnotationPresent(FruitProvider.class)){
  23. FruitProvider fruitProvider= (FruitProvider) field.getAnnotation(FruitProvider.class);
  24. strFruitProvicer=" 供应商编号:"+fruitProvider.id()+" 供应商名称:"+fruitProvider.name()+" 供应商地址:"+fruitProvider.address();
  25. System.out.println(strFruitProvicer);
  26. }
  27. }
  28. }
  29. }

Apple.java

  1. import test.FruitColor.Color;
  2. /**
  3. * 注解使用
  4. */
  5. public class Apple {
  6. @FruitName("Apple")
  7. private String appleName;
  8. @FruitColor(fruitColor=Color.RED)
  9. private String appleColor;
  10. @FruitProvider(id=1,name="陕西红富士集团",address="陕西省西安市延安路89号红富士大厦")
  11. private String appleProvider;
  12. public void setAppleColor(String appleColor) {
  13. this.appleColor = appleColor;
  14. }
  15. public String getAppleColor() {
  16. return appleColor;
  17. }
  18. public void setAppleName(String appleName) {
  19. this.appleName = appleName;
  20. }
  21. public String getAppleName() {
  22. return appleName;
  23. }
  24. public void setAppleProvider(String appleProvider) {
  25. this.appleProvider = appleProvider;
  26. }
  27. public String getAppleProvider() {
  28. return appleProvider;
  29. }
  30. public void displayName(){
  31. System.out.println("水果的名字是:苹果");
  32. }
  33. }

FruitRun.java

  1. /**
  2. * 输出结果
  3. */
  4. public class FruitRun {
  5. public static void main(String[] args) {
  6. FruitInfoUtil.getFruitInfo(Apple.class);
  7. }
  8. }

运行结果是:


水果名称:Apple

水果颜色:RED

供应商编号:1 供应商名称:陕西红富士集团 供应商地址:陕西省西安市延安路89号红富士大厦

七、参考

如有侵权,请联系删除!

【Java基础】Annotation 的本质和自定义实现的更多相关文章

  1. Java 基础 面向对象修饰符和自定义数据类型

    不同修饰符使用细节 常用来修饰类.方法.变量的修饰符如下: public 权限修饰符,公共访问, 类,方法,成员变量 protected 权限修饰符,受保护访问, 方法,成员变量 默认什么也不写 也是 ...

  2. Java基础笔记 – Annotation注解的介绍和使用 自定义注解

    Java基础笔记 – Annotation注解的介绍和使用 自定义注解 本文由arthinking发表于5年前 | Java基础 | 评论数 7 |  被围观 25,969 views+ 1.Anno ...

  3. 第6天 Java基础语法

    第6天 Java基础语法 今日内容介绍 自定义类 ArrayList集合 引用数据类型(类) 引用数据类型分类 提到引用数据类型(类),其实我们对它并不陌生,如使用过的Scanner类.Random类 ...

  4. Java基础之理解Annotation(与@有关,即是注释)

    Java基础之理解Annotation 一.概念 Annontation是Java5开始引入的新特征.中文名称一般叫注解.它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata) ...

  5. Java 基础之认识 Annotation

    Java 基础之认识 Annotation 从 JDK 1.5 版本开始,Java 语言提供了通用的 Annotation 功能,允许开发者定义和使用自己的 Annotation 类型.Annotat ...

  6. [编织消息框架][JAVA核心技术]annotation基础

    应用动态代理技术要先掌握annotation技术 注解是JDK1.5之后才有的新特性,JDK1.5之后内部提供的三个注解 @Deprecated 意思是“废弃的,过时的” @Override 意思是“ ...

  7. 深入JAVA注解(Annotation):自定义注解 (转)

    原文出自:http://blog.csdn.net/yjclsx/article/details/52101922 一.基础知识:元注解 要深入学习注解,我们就必须能定义自己的注解,并使用注解,在定义 ...

  8. Java基础之理解Annotation

    一.概念 Annontation是Java5开始引入的新特征.中文名称一般叫注解.它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类.方法.成员变量等)进行关 ...

  9. Java注解Annotation的用法 - 自定义Annotation实现

    Java注解又称Java标注,是Java语言5.0版本开始支持加入源代码的特殊语法元数据. Java语言中的类.方法.变量.参数和包等都可以被标注.和Javadoc不同,Java标注可以通过反射获取标 ...

随机推荐

  1. luogu P2947 [USACO09MAR]向右看齐Look Up |单调队列

    题目描述 Farmer John's N (1 <= N <= 100,000) cows, conveniently numbered 1..N, are once again stan ...

  2. jQuery中的属性选择器

    先看代码,后面详细解释: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> ...

  3. cf1119d Frets On Fire 前缀和+二分

    题目:http://codeforces.com/problemset/problem/1119/D 题意:给一个数n,给出n个数组的第一个数(a[0]=m,a[1]=m+1,a[2]=m+2,... ...

  4. Python绘制KS曲线

    更多大数据分析.建模等内容请关注公众号<bigdatamodeling> python实现KS曲线,相关使用方法请参考上篇博客-R语言实现KS曲线 代码如下: ############## ...

  5. java笔记 -- 乐观锁与悲观锁

    何谓乐观锁和悲观锁 乐观锁对应于生活中乐观的人总是想着事情往好的方向发展,悲观锁对应于生活中悲观的人总是想着事情往坏的方向发展.这两种人各有优缺点,不能不以场景而定说一种人好于另外一种人. 悲观锁 - ...

  6. webpack 环境搭建

    Webpack环境搭建 一.安装node 1.node官网下载node并安装----node里面内置了npm所以用在安装npm了 2.命令行输入node -v查看node是否安装成功 二.全局安装we ...

  7. python 中 and or

    在Python 中,and 和 or 执行布尔逻辑演算,如你所期待的一样,但是它们并不返回布尔值:而是,返回它们实际进行比较的值之一. 一.and: 在布尔上下文中从左到右演算表达式的值,如果布尔上下 ...

  8. 如何为.NETCore安装汉化包智能感知

    引言 具体不记得是在群里还是什么地方有人问过,.NETCore有没有汉化包,答案是有,目前微软已经为我们提供了.NETCore多种语言的语言包.下面看看如何安装与使用吧. 在哪下载? 在微软官方下载 ...

  9. 《Java基础知识》Java变量的声明、初始化和作用域

    一.Java变量的声明 在 Java 程序设计中,每个声明的变量都必须分配一个类型.声明一个变量时,应该先声明变量的类型,随后再声明变量的名字.下面演示了变量的声明方式. double salary; ...

  10. 迈布-----UE4AI自动巡逻与攻击

    这个行为树给我恶心的都想吐,我用的是4.24,跟着官网做达不到那个效果,跟着视频做也达不到那个效果,跟我弄的非常不耐烦,最后终于在今天整出来了.有的地方用了一下我自己的逻辑.//诸位依靠教程的,一定得 ...