Java 中注解的实现原理

一、引言

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

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

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

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

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

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

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

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

public interface Override extends Annotation {

}

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

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

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

三、元注解

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

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

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

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

@Retention: 注解的声明周期;

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

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

@Target

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

@Target 的源代码如下所示:

@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();
}

以上面 @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 用于指明当前注解的生命周期,源代码如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}

与上面一致,该注释也有一个 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. 注解也可以没有定义成员,,不过这样注解就没啥用了
PS:自定义注解需要使用到元注解

六、自定义注解实例

FruitName.java

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME; /**
* 水果名称注解
*/
@Target(ElementType.FIELD)
@Retention(RUNTIME)
@Documented
public @interface FruitName {
String value() default "";
}

FruitColor.java

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME; /**
* 水果颜色注解
*/
@Target(FIELD)
@Retention(RUNTIME)
@Documented
public @interface FruitColor {
/**
* 颜色枚举
*/
public enum Color{ BLUE,RED,GREEN}; /**
* 颜色属性
*/
Color fruitColor() default Color.GREEN; }

FruitProvider.java

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME; /**
* 水果供应者注解
*/
@Target(FIELD)
@Retention(RUNTIME)
@Documented
public @interface FruitProvider {
/**
* 供应商编号
*/
public int id() default -1; /**
* 供应商名称
*/
public String name() default ""; /**
* 供应商地址
*/
public String address() default "";
}

FruitInfoUtil.java

import java.lang.reflect.Field;

/**
* 注解处理器
*/
public class FruitInfoUtil {
public static void getFruitInfo(Class<?> clazz){ String strFruitName=" 水果名称:";
String strFruitColor=" 水果颜色:";
String strFruitProvicer="供应商信息:"; Field[] fields = clazz.getDeclaredFields(); for(Field field :fields){
if(field.isAnnotationPresent(FruitName.class)){
FruitName fruitName = (FruitName) field.getAnnotation(FruitName.class);
strFruitName=strFruitName+fruitName.value();
System.out.println(strFruitName);
}
else if(field.isAnnotationPresent(FruitColor.class)){
FruitColor fruitColor= (FruitColor) field.getAnnotation(FruitColor.class);
strFruitColor=strFruitColor+fruitColor.fruitColor().toString();
System.out.println(strFruitColor);
}
else if(field.isAnnotationPresent(FruitProvider.class)){
FruitProvider fruitProvider= (FruitProvider) field.getAnnotation(FruitProvider.class);
strFruitProvicer=" 供应商编号:"+fruitProvider.id()+" 供应商名称:"+fruitProvider.name()+" 供应商地址:"+fruitProvider.address();
System.out.println(strFruitProvicer);
}
}
}
}

Apple.java

import test.FruitColor.Color;

/**
* 注解使用
*/
public class Apple { @FruitName("Apple")
private String appleName; @FruitColor(fruitColor=Color.RED)
private String appleColor; @FruitProvider(id=1,name="陕西红富士集团",address="陕西省西安市延安路89号红富士大厦")
private String appleProvider; public void setAppleColor(String appleColor) {
this.appleColor = appleColor;
}
public String getAppleColor() {
return appleColor;
} public void setAppleName(String appleName) {
this.appleName = appleName;
}
public String getAppleName() {
return appleName;
} public void setAppleProvider(String appleProvider) {
this.appleProvider = appleProvider;
}
public String getAppleProvider() {
return appleProvider;
} public void displayName(){
System.out.println("水果的名字是:苹果");
}
}

FruitRun.java

/**
* 输出结果
*/
public class FruitRun {
public static void main(String[] args) {
FruitInfoUtil.getFruitInfo(Apple.class);
}
}

运行结果是:


水果名称: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. Creating your first iOS Framework

    转自:https://robots.thoughtbot.com/creating-your-first-ios-framework If you’ve ever tried to create yo ...

  2. 在mac上如何用safari浏览器调试ios手机的移动端页面

    第一步 打开iphone手机的开发者模式,流程是:[设置]->[Safari]->[高级]->开启[Web检查器] 第二步 打开Mac上Safari的开发者模式,流程是[Safari ...

  3. git 使用详解(3)—— 最基本命令 + .gitignore 文件

    Git 基础 本章将介绍几个最基本的,也是最常用的 Git 命令,以后绝大多数时间里用到的也就是这几个命令.读完本章,你就能初始化一个新的代码仓库,做一些适当配置:开始或停止跟踪某些文件:暂存或提交某 ...

  4. DRF Django REST framework 之 认证组件(五)

    引言 很久很久以前,Web站点只是作为浏览服务器资源(数据)和其他资源的工具,甚少有什么用户交互之类的烦人的事情需要处理,所以,Web站点的开发这根本不关心什么人在什么时候访问了什么资源,不需要记录任 ...

  5. CoderForces985F-Isomorphic Strings

    F. Isomorphic Strings time limit per test 3 seconds memory limit per test 256 megabytes input standa ...

  6. 用.NET模拟天体运动

    用.NET模拟天体运动 这将是一篇罕见而偏极客的文章. 我上大学时就见过一些模拟太阳系等天体运动的软件和网站,觉得非常酷炫,比如这个(http://www.astronoo.com/en/articl ...

  7. servlet读取请求参数后流失效的问题

    在用reset接口的时候,常常会使用request.getInputStream()方法,但是流只能读取一次,一旦想要加上一个过滤器用来检测用户请求的数据时就会出现异常. 在过滤器中通过流读取出用户p ...

  8. for-in的缺点

    for-in用来循环对象中的属性,但是通过for-in循环输出的属性名的顺序是不可测的.具体来说,所有属性都会被返回一次,但返回的先后次序可能会因浏览器而异. ES5之前:如果表示要迭代的对象的变量值 ...

  9. Java Properties 加载

    static{ Properties prop = new Properties(); prop.load(Thread.currentThread().getContextClassLoader() ...

  10. 大数据项目2(Java8聚合操作)

    前言:为很好的理解这些方法,你需要熟悉java8特性Lambda和方法引用的使用 一:简介 我们用集合的目的,往往不是简单的仅仅把数据保存哪里.而是要检索(遍历)或者去计算或统计....操作集合里面的 ...