注解实现原理

转载:https://blog.51cto.com/4247649/2109129

一、什么是注解?

注解也叫元数据,注解是JDK1.5版本开始引入的一个特性。
它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。为程序的元素(类、方法、成员变量)加上更直观更明了的说明,这些说明信息是与程序的业务逻辑无关,并且供指定的工具或框架使用。Annontation像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。
  Java注解:是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。包含在 java.lang.annotation 包中。

一般常用的注解可以分为三类:

  • 一类是Java自带的标准注解:包括@Override(标明重写某个方法)、@Deprecated(标明某个类或方法过时)和@SuppressWarnings(标明要忽略的警告),使用这些注解后编译器就会进行检查。
  • 一类为元注解,元注解是用于定义注解的注解:包括@Retention(标明注解被保留的阶段)、@Target(标明注解使用的范围)、@Inherited(标明注解可继承)、@Documented(标明是否生成javadoc文档)
  • 一类为自定义注解:可以根据自己的需求定义注解

二、注解的用途?

在看注解的用途之前,有必要简单的介绍下XML和注解区别

  • 注解:是一种分散式的元数据,与源代码紧绑定。
  • xml:是一种集中式的元数据,与源代码无绑定

这里主要介绍一下注解的主要用途:
生成文档,通过代码里标识的元数据生成javadoc文档。

  • 生成文档,通过代码里标识的元数据生成javadoc文档。
  • 编译检查,通过代码里标识的元数据让编译器在编译期间进行检查验证。
  • 编译时动态处理,编译时通过代码里标识的元数据动态处理,例如动态生成代码。
  • 运行时动态处理,运行时通过代码里标识的元数据动态处理, 例如使用反射注入实例

三、注解的实现原理

注解本质是一个继承了Annotation 的特殊接口,其具体实现类是Java 运行时生成的动态代理类。而我们通过反射获取注解时,返回的是Java 运行时生成的动态代理对象$Proxy1。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler 的invoke 方法。该方法会从memberValues 这个Map 中索引出对应的值。而memberValues 的来源是Java 常量池。

四、元注解

实现注解三要素

  • 注解声明
  • 使用注解的元素
  • 操作注解使其起作用(注解处理器)

注解声明
首先我们让看一下java中的元注解(也就是上面提到的注解的注解),总共有4个如下(在自定义注解的时候,需要使用到元注解):

@Target  -注解用于什么地方
@Retention – 什么时候使用该注解
@Documented – 注解是否将包含在JavaDoc中
@Inherited – 是否允许子类继承该注解

这4个元注解都是在jdk的java.lang.annotation包下面

1.)@Retention – 定义该注解的生命周期
● RetentionPolicy.SOURCE : 在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。@Override, @SuppressWarnings都属于这类注解。
● RetentionPolicy.CLASS : 在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式
● RetentionPolicy.RUNTIME : 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。

2.)Target – 表示该注解用于什么地方。默认值为任何元素,表示该注解用于什么地方。可用的ElementType 参数包括
● ElementType.CONSTRUCTOR: 用于描述构造器
● ElementType.FIELD: 成员变量、对象、属性(包括enum实例)
● ElementType.LOCAL_VARIABLE: 用于描述局部变量
● ElementType.METHOD: 用于描述方法
● ElementType.PACKAGE: 用于描述包
● ElementType.PARAMETER: 用于描述参数
● ElementType.TYPE: 用于描述类、接口(包括注解类型) 或enum声明

3.)@Documented – 一个简单的Annotations 标记注解,表示是否将注解信息添加在java 文档中。

4.)@Inherited – 定义该注释和子类的关系
@Inherited 元注解是一个标记注解,@Inherited 阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited 修饰的annotation 类型被用于一个class,则这个annotation 将被用于该class 的子类。

五、自定义注解

/**
 * 定义一个可以注解在Class,interface,enum上的注解
 * 增加了@Inherited注解代表允许继承
 * @author zhangqh
 * @date 2018年4月22日
 */
@Target({
    
    ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface MyAnTargetType {
    
    
    /**
     * 定义注解的一个元素 并给定默认值
     * @return
     */
    String value() default "我是定义在类接口枚举类上的注解元素value的默认值";
}

增加一个子类ChildAnnotationTest继承AnnotationTest测试如下:

/**
 * 增加一个子类继承AnnotationTest 演示@Inherited注解允许继承
 *
 * @author zhangqh
 * @date 2018年4月23日
 */
public class ChildAnnotationTest extends AnnotationTest {
    
    
    public static void main(String[] args) {
    
    
        // 获取类上的注解MyAnTargetType
        MyAnTargetType t = ChildAnnotationTest.class.getAnnotation(MyAnTargetType.class);
        System.out.println("类上的注解值 === "+t.value());
    }
}

运行如下:

类上的注解值 === 我是定义在类接口枚举类上的注解元素value的默认值

说明已经获取到了父类AnnotationTest的注解了

如果MyAnTargetType去掉@Inherited注解运行则报错如下:

Exception in thread "main" java.lang.NullPointerException
    at com.zhang.run.ChildAnnotationTest.main(ChildAnnotationTest.java:17)

六、注解处理器

这个是注解使用的核心了,前面我们说了那么多注解相关的,那到底Java是如何去处理这些注解的呢

注解背后处理器的工作原理如上源码实现:首先解析所有属性,判断属性上是否存在指定注解,如果存在则根据搜索规则取得bean,然后利用反射原理注入。如果标注在字段上面,也可以通过字段的反射技术取得注解,根据搜索规则取得bean,然后利用反射技术注入。
在这里插入图片描述

验证:注解标注完如何处理?

首先、定义一个注解处理类和注解处理方法;如果你熟悉反射代码,就会知道反射可以获取类名、方法和实例变量对象。所有这些对象都有getAnnotation()这个方法用来返回修饰它们的注解信息。因此可以通过反射获取注解标注的类或者方法或者变量等等并对其做相应处理;

import java.lang.reflect.Field;
public class AnnotationProccessor {
    
    
    public  static void process(Demo demo){
    
    
        Class demoClazz = Demo.class;
          for(Method method : demoClazz.getMethods()) {
    
    
             Company companyAnnotation = (Company)method.getAnnotation(Company.class);
             if(companyAnnotation !=null) {
    
    
                System.out.println(" Method Name : "+ method.getName());
                System.out.println(" name : "+ companyAnnotation.name());
                System.out.println(" Status : "+ companyAnnotation.status());
     }
  } 
}

上面的类只对特定的类进行了处理,在Spring中一个如@Service这种注解,Spring在启动IOC容器的时候会对每个类进行扫描,把所有标注@Component及其子注解如@Service的类进行Bean处理。

猜你喜欢

转载自blog.csdn.net/weixin_42754971/article/details/114308811