java注解深入学习

一、自定义注解分为三个步骤:

  1. 注解类定义
  2. 使用注解
  3. 对注解进行解释说明

1.1、注解定义:

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Eat {
    String fruit() default "香蕉"; //在注解类中,这些抽象方法叫属性
    String vegetable() default "茄子"; //default 表示默认值

1.2、注解的实际存在形式:

public interface Eat implements java.lang.annotation.Annotation(){
    public abstract String fruit() default "香蕉"; //在注解类中,这些抽象方法在此处叫"属性"
    public abstract String vegetable() default "茄子"; //default 表示默认值

    从上可以看出注解也只是一种继承了Annotation的特殊接口。

2、注解的使用:

//注:没有默认值的注解属性必须使用,属性vegetable有因此此处就不用写
//从这可以看出赋值形式像是给属性赋值,可能这就是为什么注解接口的抽象方法也叫属性吧
@Eat(fruit = "梨")
class stu1@Eat(fruit = "橘子",vegetable = "番茄")
    public void eat(){}//注解可以在三个地方出现:类上、方法上、成员变量上,
//具体可以在哪里使用通过元注解@Target决定

    如前文章所述,注解的作用本质上是做标记,标记是没有实际意义的需要有代码去解释这个标记才有意义。

3、注解的解释:

class analy{
    public static void analy_class(){
        Class stuc = Class.forName("stu1");
        //判断类上是否有ano注解
        if(stuc.isAnnotationPresent(Eat.class)){
            //获取注解的实现类,该实现类是通过动态代理生成的,也是本文要讲的
            Eat res = stuc.getAnnotation(Eat.class);
            String a = res.fruit();
            String b = res.vegetable();
            dosomeing......;
        }
    }
    public static void analy_method(){
        Method eatt stuc.getMethod("eat");
        //判断方法上是否是ano注解
        if(eatt.isAnnotationPresent(Eat.class)){
            Eat res = eatt.getAnnotation(Eat.class);
            String a = res.fruit();
            String b = res.vegetable();
            dosomeing......;
        }
    }
    public static void analy_field(){
        Field eatt stuc.getField("...");
        //判断属性上是否是ano注解
        if(eatt.isAnnotationPresent(Eat.class)){
            Eat res = eatt.getAnnotation(Eat.class);
            String a = res.fruit();
            String b = res.vegetable();
            dosomeing......;
        }
    }
    public static void main(String[] args){
        analy_class();
        analy_method();
        analy_field();
    }
}

    这样的类是必须的,每个注解都要有一个对应的解释类。

二、本文主体

1、注解接口实现类:

    前文所说注解可以加在类、方法、成员变量上,那么首先我们自然要先获取这三样东西,通过反射获取Class、Method、Field这三个对象,每个对象都有

public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)

这样的方法来判断三个对象上是否存在某个注解,当存在注解后,我们接着就需要调用这个方法

public <A extends Annotation> A getAnnotation(Class<A> annotationClass)

来获取注解的实现类,该类通过动态代理实现,动态代理即系统帮我们按照一种定好的规则生成一个类,根据规则参数的不同生成的类也有所区别,此处省略具体解释,不懂的可以去了解下,想学java,动态代理是必需要学的。我们看下java帮我们生成的代理类:

public final class $Proxy1 extends Proxy implements Eat {
    private static Method m1;
    private static Method m4;
    private static Method m3;
    private static Method m2;
    private static Method m5;
    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 vegetable() throws  {
        try {
            return (String)super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String fruit() throws  {
        try {
            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 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, m5, (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"));
            m4 = Class.forName("AnnptationTest.annotation.Eat").getMethod("vegetable");
            m3 = Class.forName("AnnptationTest.annotation.Eat").getMethod("fruit");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m5 = Class.forName("AnnptationTest.annotation.Eat").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());
        }
    }
}
//上面这类就是我们使用的res这个对象的类了,即:
//Eat res = stuc.getAnnotation(Eat.class);   

    通过这个类我们可以看到6个方法期中两个是我们自己定义的,其余四个是系统自动生成的,也就是这四个是一定会有的无论我们是否给注解接口定义属性(抽象方法)

  • 自定义的:
  1. public final String vegetable()
  2. public final String fruit()
  • 系统自带的:
  1. public final boolean equals(Object var1)
  2. public final String toString()
  3. public final Class annotationType()
  4. public final int hashCode()
    这6个方法的使用:
//返回的是@Eat(fruit = "橘子",vegetable = "番茄")中vegetable 的值,即“番茄”
res.vegetable();
//同上
res.fruit();
//用来判断var1与res是否是同一对象
res.equals(var1);
//equals方法重写时hashCode也要重写,具体原因可自行查询,原因还是比较容易理解的
res.hashCode();
//重写后的toString返回的是:
//@AnnptationTest.annotation.Eat(fruit="梨", vegetable="茄子")
res.toString();
//返回的是interface AnnptationTest.annotation.Eat
//可以看出是注解接口
res.annotationType();

2、代理实现类解析:

public final class $Proxy1 extends Proxy implements Eat

    从上面代码段可以看出注解接口实现类除了继承了注解接口,还继承了一个类Proxy ,Proxy源码:

public interface Annotation {
    boolean equals(Object obj);
    int hashCode();
    String toString();
    Class<? extends Annotation> annotationType();
}
//可以看到这个接口带的方法刚好是我们重写的四个系统自带的方法

    而这个类是我们获取到@Eat(fruit = “橘子”,vegetable = “番茄”) 中键值对的关键,我们还能从$Proxy1代理类中可以看到所有方法都使用了类似的一段代码:

return (Class)super.h.invoke(this, m5, (Object[])null);

就不打谜语了直接展示下Proxy的部分代码:

public class Proxy implements java.io.Serializable {
   ...
   protected InvocationHandler h;
   ...//super.h就是上面这个属性了

    但是我们跟着点进去会发现InvocationHandler只是一个接口(关于这个类,在动态代理学习中会使用到),我们需要找到它的实现类,注解的这个接口实现类是AnnotationInvocationHandler,列出该类的部分代码:

class AnnotationInvocationHandler implements InvocationHandler, Serializable {
    private static final long serialVersionUID = 6182022883658399397L;
    //这个就是$Proxy1中annotationType()方法的返回值
    private final Class<? extends Annotation> type;
    //这个Map包含的就是@Eat(fruit = "橘子",vegetable = "番茄")
    //中的每个属性键值对,例如:以fruit为键,“橘子”为值
    private final Map<String, Object> memberValues;
    // 在前面解释注解的类中有这样的一段代码
    //  Eat res = stuc.getAnnotation(Eat.class);
    //  String a = res.fruit();
    // 我们已经了解res其实是Annotation的继承类
    /*   我们其实可以通过memberValues借由非法反射获取注解的所有值,
     *   并且可动态修改这些值,但根据官方声明未来会禁止非法访问
     *   InvocationHandler h = Proxy.getInvocationHandler(res);
     *   Field hField = h.getClass().getDeclaredField("memberValues");
     *   hField.setAccessible(true);
     *   Map<String, Object> values = (Map<String, Object>)hField.get(h);
     *   for (String key : values.keySet()) {
     *      System.out.println("Key: " + key + ", Value: " + values.get(key));
     *      输出key: fruit, value: "橘子" 和 key: vegetable , value: "番茄"
     *   }
     */
    
    ...
    
    public Object invoke(Object proxy, Method method, Object[] args) {
        String member = method.getName();
        int parameterCount = method.getParameterCount();
    
        // Handle Object and Annotation methods
        if (parameterCount == 1 && member == "equals" &&
                method.getParameterTypes()[0] == Object.class) {
            return equalsImpl(proxy, args[0]);
        }
        if (parameterCount != 0) {
            throw new AssertionError("Too many parameters for an annotation method");
        }
    
        if (member == "toString") {
            return toStringImpl();
        } else if (member == "hashCode") {
            return hashCodeImpl();
        } else if (member == "annotationType") {
            return type;
        }
    
        // Handle annotation member accessors
        Object result = memberValues.get(member);
    
        if (result == null)
            throw new IncompleteAnnotationException(type, member);
    
        if (result instanceof ExceptionProxy)
            throw ((ExceptionProxy) result).generateException();
    
        if (result.getClass().isArray() && Array.getLength(result) != 0)
            result = cloneArray(result);
    
        return result;
    }
    
    ...

}

    从AnnotationInvocationHandler的代码中可以看出invoke包含了之前代码:

(Class)super.h.invoke(this, m5, (Object[])null);

的处理逻辑,包含系统自带的几个方法如何执行与用户的方法适合是何时执行的。
    以类图描述上述几个类的关系式:

«interface» InvocationHandler +invoke(Object proxy, Method method, Object[] args) : Object AnnotationInvocationHandler -Class type -Map<String, Object> memberValues +invoke(Object proxy, Method method, Object[] args) : Object Proxy #InvocationHandler h «interface » Annotation #equals(Object obj) : boolean #hashCode() : int #toString() : String #annotationType() : Class Eat +fruit() : String +vegetable() : String Proxy1 -Method m1 -Method m4 -Method m3 -Method m2 -Method m5 -Method m0 InvocationHandler var1 +equals(Object var1) : boolean +vegetable() : String +fruit() : String +toString() : String +annotationType() : Class +hashCode() : int

    以类图为基础我们很容易看出我们最终使用的

Eat res = eatt.getAnnotation(Eat.class);

其实就是$Proxy1(类图中无法使用’$’,因此类图中标识的是Proxy1),代码也就可以写成:

Annotation res = eatt.getAnnotation(Eat.class);

    以上是关于java注解的深入学习,这些知识在平时的敲代码中可能用不到,但会影响我们如何去敲代码,多了解才能会使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值