Java SE -- 注解

Java 注解

简介

注解的定义

注解也叫元数据,jdk1.5引入的,和类、接口等属于同一个层级。可用在类、字段、接口、方法、形参上。

注解与注释的区别

注释:告知程序员这段程序的用意。 注解:告知计算机这段程序的用意。

注解的三大作用

生成带有说明的文档

如下所示代码,由于添加了作者、版本、since这些注解,所以在生成文档的时候就会体现这些内容。

代码示例

/**
 * 注解javadoc演示
 *
 * @author test
 * @version 1.0
 * @since 1.5
 */
public class AnnoDemo1 {

    /**
     * 计算两数的和
     * @param a 整数
     * @param b 整数
     * @return 两数的和
     */
    public int add(int a, int b ){
        return a + b;
    }
}

生成文档示例

使用javadoc命令进行文档生成

img

可以看到生成的类文档的作者、版本等都是我们注解后面编写的值。

img

代码分析

使用反射完成基于代码里标识的注解对代码进行分析,后文我们会对此展开详细介绍。

编译检查

通过代码里标识的注解让编译器能够实现基本的编译检查【Override】

java内置的三大注解

@Override

源码如下所示,可以看到元注解有target和retenion,其中retention为source,即在编译时检查当前子类重写的方法在父类中是否存在,如果存在则编译通过,反之。

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

@Deprecated

这个注解常用于提醒开发被加上注解的玩意已经不推荐使用了

该注解用于提醒开发被加上注解的方法不推荐使用了。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

@SuppressWarnings

压制程序中某些警告。

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {

    String[] value();
}

元注解(用于描述注解的注解)

@Target:描述注解能够作用的位置

        * ElementType取值:
            * TYPE:可以作用于类上
            * METHOD:可以作用于方法上
            * FIELD:可以作用于成员变量上

如下注解 只可作用于类和字段上,在函数上则会报错

@Target({ElementType.TYPE,ElementType.FIELD})
public @interface MyAnno3 {
}

如下所示,我们将Target在字段上的注解用在方法上就报错了。

img

@Retention

描述注解被保留的阶段 @Retention(RetentionPolicy.RUNTIME):当前被描述的注解,会保留到class字节码文件中,并被JVM读取到。

@Documented

简介

描述注解是否被抽取到api文档中

示例

自定义注解Myanno3

如下代码,Myanno3加入注解@Documented后,worker类使用该Myanno3注解,当使用worker生成文档后该注解会被显示在使用注解的类、函数、字段上

import java.lang.annotation.*;

/**

 元注解:用于描述注解的注解
     * @Target:描述注解能够作用的位置
     * @Retention:描述注解被保留的阶段
     * @Documented:描述注解是否被抽取到api文档中
     * @Inherited:描述注解是否被子类继承


 *
 */

@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyAnno3 {
}
Worker上使用MyAnno注解

如下所示,可以看到我们在类、字段、方法上都用到了注解。

@MyAnno(value=12,per = Person.P1,anno2 = @MyAnno2,strs="bbb",name = "李四")
@MyAnno3
public class Worker {
    @MyAnno3
    public String name = "aaa";
    @MyAnno3
    public void show(){


    }
}

使用javadoc命令生成文档后,可以看到该类的myAnno3的注解都存在

img

@Inherited

描述注解是否被子类继承

基于注解实现简单的测试框架

需求描述

我们希望自定义一个注解check,每个增加check的我们都会通过反射获取,如果计算保存则假如统计文本中。

代码示例

首先定义check注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Check {
}

需要测试的类代码

public class Calculator {

    //加法
    @Check
    public void add() {
        String str = null;
        str.toString();
        System.out.println("1 + 0 =" + (1 + 0));
    }


    //减法
    @Check
    public void sub() {
        System.out.println("1 - 0 =" + (1 - 0));
    }

    //乘法
    @Check
    public void mul() {
        System.out.println("1 * 0 =" + (1 * 0));
    }

    //除法
    @Check
    public void div() {
        System.out.println("1 / 0 =" + (1 / 0));
    }


    public void show() {
        System.out.println("永无bug...");
    }

}

测试类

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class CheckUtil {


    public static void main(String[] args) throws IOException {
        Calculator calculator = new Calculator();
        Class<? extends Calculator> clzz = calculator.getClass();
        Method[] methods = clzz.getMethods();

        String methodName = null;


        int errCount = 0;
        BufferedWriter writer = new BufferedWriter(new FileWriter("err.log"));
        for (Method method : methods) {
            methodName = method.getName();
            if (method.isAnnotationPresent(Check.class)) {
                try {
                    method.invoke(calculator);
                } catch (Exception e) {
                    String msg = String.format("出错了,第%d个错误,方法名:%s,错误原因:%s", ++errCount, methodName, e.getCause().getMessage());
                    System.out.println(msg);
                    writer.write(msg);
                    writer.newLine();
                }
            }
        }

        writer.flush();
        writer.close();


    }
}

输出结果

出错了,第1个错误,方法名:add,错误原因:null
出错了,第2个错误,方法名:div,错误原因:/ by zero

注解的本质

聊到注解的本质,其实最简单的方法就是反编译看看实质,代码如下所示:

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface Foo{
    String[] value();
    boolean bar();
}

使用以下命令完成编译生成字节码还反编译为java文件

 javac Foo.java

 javap -c Foo.class

我们就可以得出如下一段输出,可以看到看注解的本质就是一个接口。

Compiled from "Foo.java"
public interface com.shark.wiki.interview.javaBase.annotation.Foo extends java.lang.annotation.Annotation {
public abstract java.lang.String[] value();

public abstract boolean bar();
}

注解与反射的关系与简单实践

其实我们在日常使用Spring框架时经常会用到注解,例如Service(“userSerivice”),那么请问Spring是如何通过注解拿到这个bean的值的呢? 我们不妨自定义一个注解来实验这个问题,首先我们自定义一个service

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
    String value();
    String scope();
}

然后我们编写如下一段代码,并在idea的vim option键入这一一段jvm命令-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

@Service(value = "userService", scope = "singleton")
public class Test {


    public static void main(String[] args) throws Exception{
        Service service = Test.class.getAnnotation(Service.class);
        System.out.println(service.value());
        System.out.println(service.scope());

    }
}

可以看到会输出userService,在查看项目文件中会出现下图这样一个文件

img

查看$Proxy1.class源码,我们可以看到这样一段代码,可以看到这两个变量Class.forName(“com.shark.wiki.interview.javaBase.annotation.Service”).getMethod(“scope”)、Class.forName(“com.shark.wiki.interview.javaBase.annotation.Service”).getMethod(“value”)不就是我们注解中定义的值吗?然后我们再找找他的调用处

  static {
        try {

    m4 =Class.forName("com.shark.wiki.interview.javaBase.annotation.Service").getMethod("scope");
    m3 = Class.forName("com.shark.wiki.interview.javaBase.annotation.Service").getMethod("value");



        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }

以scope为例,我们看到了这样一段代码,不难看出我们之前调用的Service.scope()就是使用这个方法。这里有个invoke,我们点进去看看调用

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

这时候我们看到一个接口,没有看到具体实现,没关系,源码的设计者命名永远是合理的,所以,我们完完全全可以通过查找与注解命名相关的继承类

img

这时候笔者就发现了这个

img

具体实现源码如下,关键笔者都在代码中注释了

public Object invoke(Object var1, Method var2, Object[] var3) {
//var2就是上一步传入的scope
        String var4 = var2.getName();
        Class[] var5 = var2.getParameterTypes();
        if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
            return this.equalsImpl(var3[0]);
        } else if (var5.length != 0) {
            throw new AssertionError("Too many parameters for an annotation method");
        } else {
            byte var7 = -1;
            switch(var4.hashCode()) {
            case -1776922004:
                if (var4.equals("toString")) {
                    var7 = 0;
                }
                break;
            case 147696667:
                if (var4.equals("hashCode")) {
                    var7 = 1;
                }
                break;
            case 1444986633:
                if (var4.equals("annotationType")) {
                    var7 = 2;
                }
            }
//根据var2标记var7,然后从返回响应的var值,这里scope就走最后一个分支了返回了scope的字符串
            switch(var7) {
            case 0:
                return this.toStringImpl();
            case 1:
                return this.hashCodeImpl();
            case 2:
                return this.type;
            default:
                Object var6 = this.memberValues.get(var4);
                if (var6 == null) {
                    throw new IncompleteAnnotationException(this.type, var4);
                } else if (var6 instanceof ExceptionProxy) {
                    throw ((ExceptionProxy)var6).generateException();
                } else {
                    if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
                        var6 = this.cloneArray(var6);
                    }

                    return var6;
                }
            }
        }
    }

总结

  1. 注解给谁用?
1. 编译器
2. 给解析程序用
  1. 注解不是程序的一部分,可以理解为注解让计算机理解代码说明的一个标签

由上述的例子我们很好的理解了注解在框架中用法,他的诞生就是为了解决长期使用xml配置导致维护愈发复杂的场景,即是一种通过高耦合解决低耦合导致维护困难的最佳实践。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值