java 注解

背景

原先各大框架采用[XML]以松耦合的方式来完成框架中几乎所有的配置,但是随着项目的业务越来越庞大,[XML]的内容也越来越来越复杂,维护成本也越来越大。
于是就有人提出来一种标记式高耦合的配置方式,『注解』。方法上可以进行注解,类上也可以注解,字段属性上也可以注解,反正几乎需要配置的地方都可以进行注解。

注解的本质

java.lang.annotation.Annotation 接口中有以下这句话来描述[注解]

The common interface extended by all annotation types.
所有的注解类型都继承自这个普通的接口(Annotation)

看上去很抽象,但是查看JDK内置注解@Override的源码:

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

其实它本质上就是一个继承了 Annotation 接口的接口

public interface Override extends Annotation{
}

一个注解的如果没有解析它的代码,就如同一个注释一样

而解析一个类或者方法的注解往往有两种方式,一种是编译期直接的扫描,一种是运行期反射。编译期的扫描指的是编译器在对java代码编译字节码的过程中会检测到某个类或方法被一些注解修饰,这时编译器会对于这些注解进行某些特定处理。
典型的就是@Override ,一旦编译器检测到某个防范被@Override修饰了,编译器会检查当前方法的方法签名是否真正重写了父类的某个方法。

元注解

元注解是用于修饰注解的注解,通常用在注解的定义上,例如

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

只是JDK内置的注解@Deprecated注解定义,可以看到其中@Target、@Retention、@Documented 这三个注解就是所谓的元注解,元注解一般用于指定某个注解生命周期以及作用目标等信息。
Java中有以下几个注解:

  • @Target : 规定注解作用目标
    枚举值作用
    ElementType.TYPE允许被修斯的注解作用在类、接口和枚举上
    ElementType.FIELD允许被修斯的注解作用在属性字段上
    ElementType.METHOD允许作用在方法上
    ElementType.PARAMETER允许作用在参数上
    ElementType.CONSTRUCTOR允许作用在构造器上
    ElementType.LOCAL_VARIABLE允许作用在本地局部变量上
    ElementType.ANNOTATION_TYPE允许作用在注解上
    ElementType.PACKAGE允许作用在包上
  • @Retention : 规定注解生命周期
    枚举值作用
    RetentionPolicy.SOURCE当前注解编译期可见,不会写入 class 文件
    RetentionPolicy.CLASS类加载阶段丢弃,会写入 class 文件
    RetentionPolicy.RUNTIME永久保存,可以反射获取
  • @Documented : 规定注解是否应当被包含到JavaDoc中
  • @Inherited : 规定是否允许子类继承该注解

JDK三大内置注解

除了上面的元注解外,JDK还预定义了另外三种注解:

  • @Override
  • @Deprecated
  • @SuppressWarnings

@Override是一个标记性注解,仅编译器可知,编译器在对java文件编译时检测到某个方法上被修饰了该注解就会去父类去匹配是否存在同样的方法签名的函数,如果没有则不能通过编译

@Deprecated也是一个标记性注解,永久存在,作用是标记当前类或方法或者字段不在推荐被使用了,可能JDK下一版本就会删除。

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

@SuppressWarnings用来压制java警告,定义如下

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    /**
     * The set of warnings that are to be suppressed by the compiler in the
     * annotated element.  Duplicate names are permitted.  The second and
     * successive occurrences of a name are ignored.  The presence of
     * unrecognized warning names is <i>not</i> an error: Compilers must
     * ignore any warning names they do not recognize.  They are, however,
     * free to emit a warning if an annotation contains an unrecognized
     * warning name.
     *
     * <p> The string {@code "unchecked"} is used to suppress
     * unchecked warnings. Compiler vendors should document the
     * additional warning names they support in conjunction with this
     * annotation type. They are encouraged to cooperate to ensure
     * that the same names work across multiple compilers.
     * @return the set of warnings to be suppressed
     */
    String[] value();
}

它有一个value属性需要手动传值,value代表的就是需要被压制的警告类型。

注解与反射

我们了解了注解使用上的细节,也简单提到注解本质是一个继承了Annotation接口的接口,那么在虚拟机层面,注解的本质是什么呢?

注解的解析获取在AnnotationInvocationHandler类中

class AnnotationInvocationHandler implements InvocationHandler, Serializable {

public Object invoke(Object var1, Method var2, Object[] var3) {
        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;
                }
            }

            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;
                }
            }
        }
    }
}

它本质上就是一个代理类,你应当去理解好AnnotationInvocationHandlerinvoke 方法的实现逻辑,这是核心。一句话概括就是,通过方法名返回注解属性值


版权声明:本文为zlsxhlp原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。