乐趣区

关于node.js:Springboot以方便闻名那么你知道其简便性的核心java注解的原理嘛

java 注解

用法

一个简略的注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test1 {String name() default "123";
    String[] name2() default {"123","342432","321321321"};
}

// 
public class Test2{@Test1(name = "2313122313",name2 = {"312231321","4342","65456543"})
    public static void sayHello(){System.out.println("123");
    }
    
    public static void main(String[] args){sayHello();
    }
}

有几个中央须要留神一下,首先 @interface 是编译器辨认的,表明该类型是一个继承了 java.lang.annotation.Annotation 接口的子接口,称之为注解。@Target 和 @Retention 都是 jdk 中定义好的注解,前者次要表明该注解能够用于润饰什么,而后者次要确定该注解保留的环境,即能够在哪些环境中运行,是编译期,运行期还是编写代码的时候。

public enum ElementType {TYPE, // Class, interface (including annotation type), or enum declaration
    FIELD, // Field declaration (includes enum constants)
    METHOD, // Method declaration
    PARAMETER, // Formal parameter declaration
    CONSTRUCTOR, // Constructor declaration
    LOCAL_VARIABLE, // Local variable declaration 
    ANNOTATION_TYPE,  //Annotation type declaration
    PACKAGE, // Package declaration
    TYPE_PARAMETER, // type parameter declaration
    TYPE_USE // Use of a type
}

public enum RetentionPolicy {
    SOURCE,
    CLASS,
    RUNTIME
}

下面两个枚举类型别离是注解保留机会(编码期,编译期,运行期)和作用范畴,也是注解最为外围的属性。

不过这样的接口是没有意义的。

让注解变得有意义

留神,刚刚咱们说到了,@interface 表明该对象是一个继承自 Annotation 的子接口,那么咱们首先得看看该接口的源码

public interface Annotation {boolean equals(Object obj);
    int hashCode();
    String toString();
    Class<? extends Annotation> annotationType();}

前三个办法比拟根底,第四个函数到底表白的是啥呢?我方才说,@interface 表明该对象是继承了 Annotation 的子接口,而没有说是实现了 Annotation 接口的类,从这里就可以看进去,如果实现类,而 @interface 无奈主动分别怎么去实现第四个办法,所以只能是接口,从 extends 关键字也能够看进去,由 @interface 润饰的对象就是 Annotation 的子接口。

只不过与个别的接口不同的是,注解类型,咱们能够在定义函数的时候能够设置默认值,通过 default 关键字实现,而且,注解这样的接口是没有实现类的。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test1 {String name() default "123";
    String[] name2() default {"123","342432","321321321"};
}

我看了几篇博客,都说注解就是元数据,能够了解为程序失常运行的配置文件,能够定义程序运行的先后关系,配置等。那么咱们看看其余几个元注解的实现

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {RetentionPolicy value();
}

能够看见 @Document 注解的源码和 @Inherited 注解的源码截然不同,然而咱们晓得这两个注解的语义是不一样的,编译器是怎么晓得两个不同的注解定义的予以的呢?这是因为 jdk 内置的注解,编译器是有一套对应的办法的,也就是编译器外部本身在扫描注解的时候,对于内置注解,会依据名字去辨认和做行为判断。

所以对于自定义注解,编译器是无奈辨认的,所以自定义注解个别都是作用于运行期,也就是 RetentionPolicy.RUNTIME,在编译期编译进字节码。在运行期,须要咱们通过反射技术,辨认该注解以及它所携带的信息,而后做相应的解决。

这就带来了一个问题,因为应用的是反射技术,所以,注解带来的问题,只能在运行期能力发现,同时,这样也给 debug 带来了难度。

运行期如何找到并解析

来看上面这个简略的例子

// Test1.java 定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test1 {String name() default "123";
    String[] name2() default {"123","342432","321321321"};
}

// Test2.java 定义被注解润饰的办法,一个批改了注解的默认值,一个没有批改注解的默认值
public class Test2{@Test1(name = "2313122313",name2 = {"312231321","4342","65456543"})
    public static void change(){}

    @Test1
    public static void notChange(){}
}

// Test3.java 用反射寻找被注解润饰的办法
public class Test3 {public static void parsing(Object obj) {if (Objects.isNull(obj)) return;
        Method[] methods = obj.getClass().getDeclaredMethods();
        for (Method method : methods) {if (method.isAnnotationPresent(Test1.class)) {Test1 test1 = method.getAnnotation(Test1.class);
                System.out.println("methodName =" + method.getName());
                System.out.println("name =" + test1.name());
                for (String s : test1.name2()) {System.out.println("name2 =" + s);
                }
            }
        }
    }
}

// Test4.java 调用

public class Test4{public static void main(String[] args){Test2 test2 = new Test2();
        Test3.parsing(test2);
    }
}

运行后果如下

methodName = notChange
name = 123
name2 = 123
name2 = 342432
name2 = 321321321
methodName = change
name = 2313122313
name2 = 312231321
name2 = 4342
name2 = 65456543

从下面这个例子,能够发现,jdk 实现的反射办法中提供了跟注解无关的办法,这样就不便了开发者找到注解润饰的对象并利用以后注解的值状况做一些解决,从而让代码能够自动化运行(即通过简略的配置使得代码可能失常运行)。

实质上来讲,我集体感觉注解是能够有替换的操作,只不过注解是从设计思维上的晋升,使得实现更为简略。

带来的益处

注解和正文是不同的,被正文的内容只存在于编码期,编译器会疏忽掉所有的正文。不同地,编译器会依据注解的保留期标记来确定是否将注解编译进字节码中。注解给程序的灵活性带来了微小的改革,这也是为啥 springboot 的呈现突破了 java 程序员被 xml 摆布的恐怖的日常,甚至我集体认为这间接导致了 java 程序员的内卷(springboot 是间接起因)。更为简单的是,知其所以然的程序员更少,心愿我能坚持下去,理解 jdk 底层。

炒鸡辣鸡原创文章,转载请注明起源

退出移动版