java注解使用及原理

1、概述

通俗的说是将我们需要的数据通过注解存储起来,当我们需要的时候(编译时或者是运行时)就去获取存储的数据。

(1)注解是一种对程序元素设置元数据的方法。

(2)元数据是指添加在类,方法,字段,包上的额外信息。

(3)注解是一种分散式的设置元数据的方法,而xml配置是一种集中式的设置元数据的方法

(4)注解不能直接干扰程序的运行,即添加和删除注解后程序都应当是可运行的

2、java.lang.Annotation

通过反编译可以知道所有的注解都是接口且继承自java.lang.Annotation

package annotation;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DemoAnnotation {
}

3、注解的作用

(1)作用于编译阶段:

  a、作为特定的标记,用于告诉编译器一些信息。如@Override。

  b、编译时动态处理,如生成动态代码,lombok中的@Data会在编译时被动态处理生成代码

(2)作用于运行阶段:运行时动态处理,作为额外信息的载体。

4、注解的分类

(1)标准注解:如@Override @Deprecated @SuppressWarnings

   (2)  元注解:用于标记注解的注解,包括:@Target @Retention @Inherited @Documented

   (3)自定义注解:自己定义的作为载体来存储额外信息。

5、元注解

元注解是用于标记(定义)注解的注解,用来定义注解的生命周期,目标等信息,有以下四种

(1)@Target:用于标记注解作用的目标,可同时作用到多种目标上如@Autowired

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})

       取值的范围:

public enum ElementType {
    /** 类、接口、枚举*/
    TYPE,

    /** 字段*/
    FIELD,

    /** 方法 */
    METHOD,

    /** 方法的参数定义上*/
    PARAMETER,

    /** 构造方法 */
    CONSTRUCTOR,

    /** 本地局部变量 */
    LOCAL_VARIABLE,

    /** 注解 */
    ANNOTATION_TYPE,

    /**  包 */
    PACKAGE,

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}

(2)@Retention:用于定义注解的生命周期,即什么范围内有效

        取值范围:

public enum RetentionPolicy {
    /**
     * 在源代码中保存,编译后的class文件中该注解不存在
     */
    SOURCE,

    /**
     *  在源代码及编译后生成的class文件中保存,但在运行中无效
     */
    CLASS,

    /**
     *  表示在运行时有效,能够获取通过注解的获取到额外的信息
     */
    RUNTIME
}
如@Autowired的生命周期为:@Retention(RetentionPolicy.RUNTIME),即在运行时能将容器中对应的Bean实例注入到如修饰的成员变量上。

(3)@Inherited:@Inherited()标记的注解能被子类所继承。

(4)@Documented:注解是否应当被包含在JavaDoc文档中。

6、自定义注解的实现

(1)格式:

package annotation;

import java.lang.annotation.*;

//注解不能继承其他注解或类,也不能实现接口
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited()
public @interface PersonInfoAnnotation {
    public String name() default "小明";
    public int age() default 10;
    public String[] hobbies() default {"吃饭","睡觉","打豆豆"};
}

 (2)注解中允许的数据类型:基本数据类型(不允许为基本数据类型的包装类),Class类,

String类,枚举,注解,及以上类型的数组,不能是自定义的类

 7、通过反射来获取注解中的信息

(1)Class,Filed,Constructor,Method类都实现了AnnotatedElement接口,通过AnnotatedElement接口可以获取对应的类、字段、构造函数、方法上的注解,然后再从注解中获取到额外的信息。

 

 (2)代码实例,通过反射获取注解中的信息

package annotation;

import java.lang.annotation.*;

//注解不能继承其他注解或类,也不能实现接口
@Target({ElementType.FIELD,ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited()
public @interface PersonInfoAnnotation {
    public String name() default "小明";
    public int age() default 10;
    public String[] hobbies() default {"吃饭","睡觉","打豆豆"};
}

 

package annotation;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

@PersonInfoAnnotation(name="大明",age=1,hobbies = {"游泳"})
public class PerSonTest {

    @PersonInfoAnnotation(name="二明",age=2,hobbies = {"看书"})
    private String personInfo;

    @PersonInfoAnnotation(age=3)
    private void printInfo() throws NoSuchFieldException, NoSuchMethodException {
        printClazzAnnotation();
        printFieldAnnotation();
        printMethodAnnotation();
    }

    //获取类上的注解
    private void printClazzAnnotation(){
        Class calzz=this.getClass();
        PersonInfoAnnotation personInfoAnnotation=(PersonInfoAnnotation)calzz.getAnnotation(PersonInfoAnnotation.class);
        System.out.println("------------------打印类上的注解-------------------------");
        print(personInfoAnnotation);
    }

    //获取字段上的注解
    private void printFieldAnnotation() throws NoSuchFieldException {
        Class calzz=this.getClass();
        Field field=calzz.getDeclaredField("personInfo");
        System.out.println("------------------打印成员变量上的注解-------------------------");
        PersonInfoAnnotation personInfoAnnotation=(PersonInfoAnnotation)field.getAnnotation(PersonInfoAnnotation.class);
        print(personInfoAnnotation);
    }


    //获取方法上的注解
    private void printMethodAnnotation() throws NoSuchMethodException {
        Class calzz=this.getClass();
        Method method=calzz.getDeclaredMethod("printInfo");
        PersonInfoAnnotation personInfoAnnotation=(PersonInfoAnnotation)method.getAnnotation(PersonInfoAnnotation.class);
        System.out.println("------------------打印方法上的注解-------------------------");
        print(personInfoAnnotation);
    }

    private static  void print(PersonInfoAnnotation personInfoAnnotation){
        System.out.println("姓名:"+personInfoAnnotation.name()
                +"   年龄:"+personInfoAnnotation.age()
                +"   爱好:");
        for (String e:personInfoAnnotation.hobbies()) {
            System.out.println(e+" ");
        }
    }

    public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
        PerSonTest perSonTest=new PerSonTest();
        perSonTest.printInfo();
    }
}

 运行结果:

------------------打印类上的注解-------------------------
姓名:大明   年龄:1   爱好:
游泳 
------------------打印成员变量上的注解-------------------------
姓名:二明   年龄:2   爱好:
看书 
------------------打印方法上的注解-------------------------
姓名:小明   年龄:3   爱好:
吃饭 
睡觉 
打豆豆 

Process finished with exit code 0

注意:@Retention的生命周期必须是Runtime,否则无法获取到数据。

8、注解的原理

(1)通过键值对的形式为注解的属性赋值

(2)编译器在编译时会检查注解使用的范围(即@Target),并将注解的内容(键值对)保存到对应元素(类、方法、成员变量等)的属性表中

(3)运行时,如果元素对应注解的@Retention为Runtime,则JVM会将属性表中的内容生成一个Map

(4)创建一个AnnotationInvocationHandler实例对象,将上面生成的Map传入,保存在成员变量,memberValues中

 (5)JVM使用JDK动态代理生成代理类,并将AnnotationInvocationHandler实例对象传入生成一个对应的代理类对象,如下图所示:

        a、代理类继承自Proxy且实现了对应的注解接口,可以看出注解本质上是一个接口

        b、初始化时传入一个AnnotationInvocationHandler对象

        c、代码中注解方法name()的调用,实际调用的是代理类中的name()方法

        d、代理类中的name()调用了AnnotationInvocationHandeler的invoke方法。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.sun.proxy;

import annotation.PersonInfoAnnotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;


//代理类继承自Proxy且实现了对应的注解接口
//可以看出注解本质上是一个接口
public final class $Proxy2 extends Proxy implements PersonInfoAnnotation {
    private static Method m1;
    private static Method m3;
    private static Method m5;
    private static Method m2;
    private static Method m4;
    private static Method m6;
    private static Method m0;


    //初始化时传入一个AnnotationInvocationHandler对象
    public $Proxy2(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);
        }
    }

    //代码中注解方法name()的调用,实际调用的是这里
    public final String name() throws  {
        try {
            //调用了AnnotationInvocationHandeler的invoke方法。
            return (String)super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String[] hobbies() throws  {
        try {
            return (String[])super.h.invoke(this, m5, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    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 int age() throws  {
        try {
            return (Integer)super.h.invoke(this, m4, (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, m6, (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"));
            m3 = Class.forName("annotation.PersonInfoAnnotation").getMethod("name");
            m5 = Class.forName("annotation.PersonInfoAnnotation").getMethod("hobbies");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("annotation.PersonInfoAnnotation").getMethod("age");
            m6 = Class.forName("annotation.PersonInfoAnnotation").getMethod("annotationType");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

e、AnnotationInvocationHandeler的invoke就是在键值对memberValues中获取方法名name对应的值。

注:查看生成代理类的方法:

JDK1.8及之前的版本:-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

JDK1.8之后的版本:   -Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true

 设置后直接运行,可获取生成的动态代理类

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值